Нарисуйте маленькую симметричную линию линии ar c между двумя маркерами на карте мира в Highmaps - PullRequest
1 голос
/ 17 января 2020

Я использую Highmaps для создания диаграммы траектории полета, используя их демо Простые маршруты полета ( jsfiddle ) в качестве отправной точки. Когда я обновляю код для использования карты мира, пути линий между местоположениями / маркерами искажаются из-за преувеличенной кривой.

См. мой jsfiddle , где я только что изменил из демо к следующему :

HTML

<!-- line 5 -->
<script src="https://code.highcharts.com/mapdata/custom/world.js"></script>

JavaScript

// line 39
mapData: Highcharts.maps['custom/world'],
// line 47
data: Highcharts.geojson(Highcharts.maps['custom/world'], 'mapline'),

См. Разницу между этими двумя до - это демо Highcharts, а после - мои несколько изменений, как отмечено выше:

comparison of map line paths

Пути рассчитывается с помощью функции pointsToPath, которая использует квадратичную c кривую Безье Q в SVG для кривой, проведенной между маркерами.

// Function to return an SVG path between two points, with an arc
function pointsToPath(from, to, invertArc) {
    var arcPointX = (from.x + to.x) / (invertArc ? 2.4 : 1.6),
        arcPointY = (from.y + to.y) / (invertArc ? 2.4 : 1.6);
    return 'M' + from.x + ',' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
            ',' + to.x + ' ' + to.y;
}

Если я изменю функцию, чтобы всегда делить на 2 для ar c точки x и y тогда я получаю прямую линию между маркерами:

var arcPointX = (from.x + to.x) / 2,
    arcPointY = (from.y + to.y) / 2;

Я не уверен в математике, чтобы получить меньшую, менее преувеличенную кривую.

В идеале я хотел бы, чтобы линия была симметричной, как в примере из MDN - Пути :

Quadratic Bézier with grid

<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
  <path d="M 10 80 Q 95 10 180 80" stroke="black" fill="transparent"/>
</svg>

Используя данные карты мира, как рассчитать пути линий между маркерами, чтобы они отображались с меньшей или симметричной кривой?

1 Ответ

4 голосов
/ 17 января 2020

Вам просто нужно приблизить свой знаменатель к 2,0, так как когда он равен 2,0, это совершенно прямая линия: https://jsfiddle.net/my7bx50p/1/

Так что я выбрал 2,03 и 1,97, и это дает вам много "более мягкие" кривые Надеюсь, это поможет.

function pointsToPath(from, to, invertArc) {
    var arcPointX = (from.x + to.x) / (invertArc ? 2.03 : 1.97),
        arcPointY = (from.y + to.y) / (invertArc ? 2.03 : 1.97);
    return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY + ' ' + to.x + ' ' + to.y;
}

enter image description here

ОБНОВЛЕНИЕ:

Я пытался сосредоточиться только на математика: https://jsfiddle.net/9gkvhfuL/1/

Я думаю, что математика теперь верна:

enter image description here

Которые обратно к реальный пример: https://jsfiddle.net/my7bx50p/6/

Дает, я полагаю, желаемый результат:):

enter image description here

Из кода (https://jsfiddle.net/my7bx50p/6/):

  function pointsToPath(from, to, invertArc) {
var centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
var slope = (to.x - from.x) / (to.y - from.y);
var invSlope = -1 / slope;
var distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );

if (Math.abs(slope) > Math.abs(invSlope) ){
  //then we should offset in the y direction
  var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
  var min_slope = Math.min( Math.abs(slope), Math.abs(invSlope) );
  var final_slope = Math.max(min_slope, 1);
  var offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];
  //console.log(centerPoint, slope, invSlope, distance);
  var arcPointX = offsetCenter[0], //(from.x + to.x) / (invertArc ? 2.03 : 1.97),
      arcPointY = offsetCenter[1] //(from.y + to.y) / (invertArc ? 2.03 : 1.97);
} else{ //invSlope <= slope
  //then we should offset in the x direction
  var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
  var min_slope = Math.min( Math.abs(slope), Math.abs(invSlope) );
  var final_slope = Math.max(min_slope, 1);
  var offsetCenter = [centerPoint[0] + offset, centerPoint[1] + (offset * (1/invSlope))];
  //console.log(centerPoint, slope, invSlope, distance);
  var arcPointX = offsetCenter[0], //(from.x + to.x) / (invertArc ? 2.03 : 1.97),
      arcPointY = offsetCenter[1] //(from.y + to.y) / (invertArc ? 2.03 : 1.97);
}   
return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
        ' ' + to.x + ' ' + to.y;
}

ОБНОВЛЕНИЕ 2: (чтобы попытаться объяснить математику и очистить код)

Проверьте математическую скрипку: https://jsfiddle.net/alexander_L/dcormfxy/53/

enter image description here

Черная линия solid между двумя точками - это прямая линия между ними и имеет также соответствующий уклон (используется в коде позже). Я также нарисовал центральную точку на каждой линии. Затем я нарисовал обратный наклон в виде пунктирной линии (также используется в коде). Обратный наклон по определению перпендикулярен наклону и связан с invSlope = -1/slope. Исходя из этого, мы теперь настроены на поиск перпендикулярных точек слева или справа от центральной точки, которые станут центром наших симметричных дуг. Мы делаем это, сначала выясняя, является ли наклон больше, чем обратный наклон, или если обратный наклон больше, чем наклон (абсолютные значения). Это необходимо только потому, что когда у нас совершенно горизонтальная или совершенно вертикальная линия, наклон равен нулю и не определен соответственно, и тогда наша математика не работает. . 1064 * Давайте подумаем о линии C from : {x: 40, y: 40}, to : {x: 220, y: 40}

slope = (y2 - y1) / (x2 - x1)

клон = (40 - 40) / (220 - 40)

slope = 0/180

slope = 0

invSlope = -1 / slope

invSlope = undefined

Вот почему нам нужно иметь два случая (if if) в коде, так как всякий раз, когда мы получаем наклон или invSlope как неопределенный, математика не будет работать. Так что теперь, хотя наклон равен нулю, он больше, чем invSlope (undefined). (обратите внимание, что SVG вверх ногами по сравнению с обычными графиками и тем, как мы о них думаем, поэтому вам нужно помнить об этом свой мозг, иначе легко потеряться)

Так что теперь мы можем сместить центральную точку в направление у, а затем выяснить, сколько мы должны сместить в направлении х. Если бы у вас была линия с наклоном 1, то вы бы сместили ее в направлениях x и y, потому что наклон линии равен 1 (линия образует угол 45 градусов с осью x) и поэтому перемещается перпендикулярно от этой линии достигается просто перемещением, например, 5 в направлении x и -5 в направлении y.

К счастью, с этим краевым случаем (наклон = 0), тогда мы просто перемещаемся в y -направление и смещение по оси x = 0. Посмотрите на строку C в математическом примере, и вы можете увидеть, что я имею в виду, чтобы двигаться перпендикулярно, мы просто перемещаем либо положительное, либо отрицательное направление y от центральной точки. Из кода:

offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];

Таким образом, как я уже сказал, мы смещаемся в направлении Y от центральной точки, и термин + (offset * (1/slope)) здесь будет равен нулю, потому что 1 / наклон не определен , Мы можем выбрать смещение «влево» или «вправо» с помощью аргумента функции invertArc, который используется в этой строке: var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);, что в основном означает смещение положительного или отрицательного направления от центральной точки на величину, равную двум значениям квадрата. root расстояния между точками. Я установил в два раза квадрат root расстояния между точками, потому что это дает нам центр смещения нашего ar c, который дает одинаково мягкие кривые для всех линий, как коротких, так и длинных.

Теперь давайте подумайте о линии A from : {x: 40, y: 40}, to : {x: 320, y: 360}

slope = (y2 - y1) / (x2 - x1)

наклон = (360 - 40) / (320 - 40)

наклон = 320/280

наклон = 1,143

invSlope = -1 / наклон

invSlope = -0,875

Окончательный исправленный код и реальный пример здесь https://jsfiddle.net/alexander_L/o43ka9u5/4/:

  function pointsToPath(from, to, invertArc) {
  var centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
  var slope = (to.x - from.x) / (to.y - from.y);
  var invSlope = -1 / slope;
  var distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );
  var arcPointX = 0;
  var arcPointY = 0;
  var offset = 0;
  var offsetCenter = 0;

  if (Math.abs(slope) > Math.abs(invSlope) ){
    //then we should offset in the y direction (then calc. x-offset)
    offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);

    offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];

    arcPointX = offsetCenter[0] 
    arcPointY = offsetCenter[1]
  } else{ //invSlope >= slope
    //then we should offset in the x direction (then calc. y-offset)
    offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
    offsetCenter = [centerPoint[0] + offset, centerPoint[1] + (offset * (1/invSlope))];

    arcPointX = offsetCenter[0] 
    arcPointY = offsetCenter[1] 
  }   
  return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
          ' ' + to.x + ' ' + to.y;
  }

ОБНОВЛЕНИЕ 3:

Я понял, как устранить необходимость в операторе управления потоком / переключением if else, используя вместо этого тригонометрию. Я надеюсь, что мой набросок поможет объяснить логику c, вы также можете прочитать кое-что (https://study.com/academy/lesson/sohcahtoa-definition-example-problems-quiz.html) и c. как я изо всех сил пытаюсь объяснить здесь кратко (а я уже пишу здесь эссе :), поэтому не буду объяснять SOH CAH TOA et c.)

enter image description here

Таким образом код основной функции выглядит следующим образом (только Math - https://jsfiddle.net/alexander_L/dcormfxy/107/) (полный пример - https://jsfiddle.net/alexander_L/o43ka9u5/6/):

function pointsToPath(from, to, invertArc) {
  const centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
  const slope = (to.y - from.y) / (to.x - from.x);
  const invSlope = -1 / slope;
  const distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );
  const offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);

  const angle = Math.atan(slope);
  //Math.cos(angle) = offsetY/offset;
  //Math.sin(angle) = offsetX/offset;
  const offsetY = Math.cos(angle)*offset;
  const offsetX = Math.sin(angle)*offset;
  //if slope = 0 then effectively only offset y-direction
  const offsetCenter = [centerPoint[0] - offsetX, centerPoint[1] + offsetY];
  const arcPointX = offsetCenter[0]
  const arcPointY = offsetCenter[1]   
  return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
          ' ' + to.x + ' ' + to.y;
 }

I считаю, что этот код более элегантный, чистый, надежный и математически обоснованный :) Спасибо также A sh за советы по использованию Const & Let по сравнению с var.

Это также дает конечный результат:

enter image description here

enter image description here

...