在Highmaps中的世界地图上的两个标记之间绘制小的对称弧线路径 [英] Draw small symmetrical arc line path between two markers on world map in Highmaps

查看:225
本文介绍了在Highmaps中的世界地图上的两个标记之间绘制小的对称弧线路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Highmaps的演示简单的飞行来创建飞行路线图路线(

I'm using Highmaps to create a flight path chart, using their demo Simple flight routes (jsfiddle) as a starting point. When I update the code to use a world map the line paths between locations/markers become distorted with an exaggerated curve.

请参见我的jsfiddle ,在该示例中,我仅从演示中进行了以下修改:

See my jsfiddle where I have only modified from the demo to the following:

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演示,而之后是我上面提到的一些更改,两者之间的区别:

See the difference between both where before is the Highcharts demo and after is my few changes as noted above:

通过函数pointsToPath计算路径,该函数使用SVG中的二次Bézier曲线Q来弯曲标记之间绘制的线.

The paths are calculated through function pointsToPath which uses quadratic Bézier curve Q in SVG to curve the line drawn between markers.

// 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;
}

如果我修改函数以始终对弧点xy除以2,那么在标记之间会得到一条直线:

If I modify the function to always divide by 2 for arc points x and y then I get a straight line between markers:

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

我不确定数学会得出更小的,不太夸张的曲线.

I'm not sure of the math to get a smaller, less exaggerated curve.

理想情况下,我希望根据 MDN-路径:

Ideally I would like for the line to be symmetrical as per an example from MDN - Paths:

<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>

使用世界地图数据,如何计算标记之间的直线路径以显示较小或对称的曲线?

Using world map data, how do I calculate line paths between markers to appear with a smaller or symmetrical curve?

推荐答案

您只需要使分母更接近2.0,因为当它是2.0时,这是一条完美的直线:

You just need to get your denominator closer to 2.0 as when it is 2.0 is a perfectly straight line: https://jsfiddle.net/my7bx50p/1/

所以我选择了2.03和1.97,这给了你很多更柔和"的曲线.希望有帮助.

So I chose 2.03 and 1.97 and that gives you much "softer" curves. Hope that helps.

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;
}

更新:

我试图只关注数学: https://jsfiddle.net/9gkvhfuL/1/

我认为数学现在是正确的:

I think the math is now correct:

回到真实示例: https://jsfiddle.net/my7bx50p/6/

我相信,所期望的结果是:):

Gives, I believe, the desired outcome :):

通过代码( 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:(以尝试解释数学并清理代码)

UPDATE 2: (to try to explain the math and clean up the code)

查看数学小提琴: https://jsfiddle.net/alexander_L/dcormfxy/53/

两个点之间的黑色实线是它们之间的直线,并且还具有相应的斜率(稍后在代码中使用).我还在每条线上画了一个中心点.然后,我将反斜率绘制为虚线(也在代码中使用).反斜率根据定义垂直于该斜率,并且与invSlope = -1/slope相关.由此,我们现在可以找到中心点左侧或右侧的垂直点,这些垂直点将成为对称弧的中心.为此,我们首先要确定斜率是否大于反斜率,或者反斜率是否大于斜率(绝对值).这仅是必要的,因为当我们有一条完美的水平线或一条完美的垂直线时,斜率分别为零和未定义,那么我们的数学将不起作用. (请记住斜率=(y2-y1)/(x2-x1),所以当直线是垂直y轴变化时,x却不是x2 = x1,则分母为零,并给出了未定义的斜率)

The solid black line between the two points is the straight line between them and it also has the corresponding slope (used in the code later). I also drew a centre point on each line. Then I drew the inverse slope as a dotted line (also used in the code) The inverse slope is by definition perpendicular to the slope and is related by invSlope = -1/slope. From this, we are now set up to find the perpendicular points to the left or right of the centre point, that will become the centre of our symmetrical arcs. We do this by first figuring out if the slope is greater than the inverse slope or if the inverse slope is greater than the slope (absolute values). This is only necessary because when we have a perfectly horizontal or perfectly vertical line then the slope is zero and undefined respectively and then our math doesn't work. (remember slope = (y2 - y1)/(x2 - x1) so when the line is vertical y-changes but x-doesn't so x2 = x1 and then the denominator is zero and gives us undefined slope)

让我们考虑一下C行from : {x: 40, y: 40}, to : {x: 220, y: 40}

坡度=(y2-y1)/(x2-x1)

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

坡度=(40-40)/(220-40)

slope = (40 - 40)/(220 - 40)

斜率= 0/180

斜率= 0

invSlope = -1/斜率

invSlope = -1/slope

invSlope =未定义

invSlope = undefined

这就是为什么我们需要在代码中包含两种情况(如果是其他情况),因为每当我们获得未定义的斜率或invSlope时,数学将无法正常工作.因此,现在,尽管斜率是零,但它大于invSlope(未定义). (请注意,SVG与正常图表以及我们如何看待它们相比,倒挂了,因此您的大脑需要牢记这一点,否则很容易迷失方向)

This is why we need to have the two cases (the if else) in the code as whenever we get slope or invSlope as undefined the math is not going to work. So now, although slope is zero, it is greater than invSlope (undefined). (note SVGs are upside down compared to normal charts and how we think about them so it requires your brain to keep that in mind, otherwise, it's easy to get lost)

因此,我们现在可以在y方向上偏移中心点,然后找出在x方向上必须偏移多少.如果您的直线的斜率是1,那么您将在x和y方向上偏移相同的直线,因为直线的斜率为1(直线与x轴成45度角),因此垂直移开仅通过例如在x方向上移动5并在y方向上移动-5即可实现这一点.

So now we can offset the centre point in the y-direction and then figure out how much we have to offset in the x-direction. If you had a line with slope 1, then you would offset the same in the x and the y-directions because the slope of the line is 1 (line makes a 45-degree angle with the x-axis) and so moving perpendicularly away from that line is achieved just by moving for example 5 in the x-direction and -5 in the y-direction.

幸运的是,在这种情况下(斜率= 0),我们仅在y方向上移动,而x方向的偏移量=0.看一下数学示例中的C行,您可以明白我的意思了,垂直移动,我们只需从中心点向正或负y方向移动即可.从代码中:

Luckily, with this edge case (slope = 0), then we just move in the y-direction and the x-direction offset = 0. Look at line C in the math example and you can see what I mean, to move perpendicularly, we just move either positive or negative y-direction from the centre point. From the code:

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

因此,正如我所说,我们在y方向上从centerPoint偏移,并且在这里+ (offset * (1/slope))项将为零,因为未定义1/slope.我们可以选择使用此行中使用的函数参数invertArc来偏移左"或右":var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);基本上意味着将正方向或负方向从centerPoint移开,其幅度等于平方的两倍.点之间距离的根.我将点定为两点之间距离的平方根的两倍,因为这给了我们圆弧的offsetCenter,它为长和短的所有线提供了相似的软曲线.

So as I said, we offset in the y-direction from the centerPoint and the term + (offset * (1/slope)) will be zero here because 1/slope is undefined. We can chose to offset "left" or "right" by the function's argument invertArc which is used in this line: var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance); which basically means move postive or negative direction away from the centerPoint in a magnitude equal to two times the square root of the distance between the points. I settled on two times the square root of the distance between the points because this gives us an offsetCenter of our arc that gives similarly soft curves for all lines both short and long.

现在,让我们考虑A行from : {x: 40, y: 40}, to : {x: 320, y: 360}

Now, let's think about line A from : {x: 40, y: 40}, to : {x: 320, y: 360}

坡度=(y2-y1)/(x2-x1)

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

坡度=(360-40)/(320-40)

slope = (360 - 40)/(320 - 40)

斜率= 320/280

slope = 320 / 280

斜率= 1.143

invSlope = -1/斜率

invSlope = -1/slope

invSlope = -0.875

invSlope = -0.875

最终在此处清理代码和实际示例 https://jsfiddle.net/alexander_L/o43ka9u5/4/:

Final cleaned up code and real example here 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控制流/switch语句的需求.我希望我的草图有助于解释逻辑,您可能还想阅读一些东西(

I figured out how to remove the need for the if else control flow / switch statement by using trigonometry instead. I hope my sketch helps explain the logic, you might also want to read some stuff (https://study.com/academy/lesson/sohcahtoa-definition-example-problems-quiz.html) etc. as I would struggle to explain briefly here (and I am already writing an essay here :) so will not explain SOH CAH TOA etc.)

这使得核心功能代码如下(仅适用于数学- https://jsfiddle.net/alexander_L/dcormfxy/107/)(完整示例- https://jsfiddle.net/alexander_L/o43ka9u5/6/):

This makes the core function code like this (Math only - https://jsfiddle.net/alexander_L/dcormfxy/107/) (full example - 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;
 }

我相信这段代码更优雅,更简洁,更健壮并且在数学上有效:)也感谢Ash提供的有关Const&让我们使用vs.

I believe this code is more elegant, cleaner and more robust and mathematically valid :) Thanks also Ash for the tips on Const & Let use versus var.

这也给出了最终结果:

这篇关于在Highmaps中的世界地图上的两个标记之间绘制小的对称弧线路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆