繁体   English   中英

在 Highmaps 中的世界地图上的两个标记之间绘制对称的小弧线路径

[英]Draw small symmetrical arc line path between two markers on world map in Highmaps

我正在使用 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 演示,之后是我上面提到的一些更改:

地图线路径比较

路径是通过函数pointsToPath计算的,该函数使用 SVG 中的二次贝塞尔曲线Q来弯曲标记之间绘制的线。

// 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 ,那么我会在标记之间得到一条直线:

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

我不确定获得更小、更不夸张的曲线的数学方法。

理想情况下,我希望根据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>

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

你只需要让你的分母更接近 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;
}

在此处输入图片说明

更新:

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

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

在此处输入图片说明

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

我相信,给出了预期的结果:):

在此处输入图片说明

从代码( 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/

在此处输入图片说明

两点之间的黑色实线就是它们之间的直线,也有对应的斜率(后面代码中用到)。 我还在每条线上画了一个中心点。 然后我将反斜率绘制为虚线(也在代码中使用)反斜率根据定义垂直于斜率,并与invSlope = -1/slope 从这里,我们现在设置找到中心点左侧或右侧的垂直点,这将成为我们对称弧的中心。 为此,我们首先要确定斜率是否大于反斜率或反斜率是否大于斜率(绝对值)。 这只是必要的,因为当我们有一条完美的水平线或完美的垂直线时,斜率分别为零和未定义,然后我们的数学就不起作用了。 (记住斜率 = (y2 - y1)/(x2 - x1) 所以当这条线是垂直的 y 变化但 x 不是这样 x2 = x1 然后分母为零并给我们未定义的斜率)

让我们想想 C 行from : {x: 40, y: 40}, to : {x: 220, y: 40}

斜率 = (y2 - y1)/(x2 - x1)

斜率 = (40 - 40)/(220 - 40)

斜率 = 0 / 180

斜率 = 0

invSlope = -1/斜率

invSlope = 未定义

这就是为什么我们需要在代码中包含两种情况(if else)的原因,因为每当我们得到斜率或 invSlope 未定义时,数学就不会起作用。 所以现在,虽然斜率为零,但它大于 invSlope(未定义)。 (注意 SVG 与普通图表相比是颠倒的,以及我们如何看待它们,因此需要您的大脑牢记这一点,否则很容易迷失方向)

所以现在我们可以在 y 方向偏移中心点,然后计算在 x 方向上需要偏移多少。 如果您有一条斜率为 1 的线,那么您将在 x 和 y 方向上偏移相同的值,因为该线的斜率为 1(线与 x 轴成 45 度角),因此垂直移开仅通过在 x 方向上移动例如 5 和在 y 方向上移动 -5 来实现从那条线。

幸运的是,在这种边缘情况下(斜率 = 0),我们只需沿 y 方向移动,x 方向偏移量 = 0。看看数学示例中的 C 线,你可以明白我的意思,垂直移动,我们只是从中心点向正或负 y 方向移动。 从代码:

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 向正或负方向移动,其幅度等于点之间距离的平方根的两倍。 我选择了点之间距离平方根的两倍,因为这为我们提供了弧的偏移中心,它为所有短线和长线提供了类似的软曲线。

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

斜率 = (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 控制流/ switch 语句的需要。 我希望我的草图有助于解释逻辑,你可能还想阅读一些东西( https://study.com/academy/lesson/sohcahtoa-definition-example-problems-quiz.html )等,因为我很难解释在这里简要介绍一下(我已经在这里写了一篇文章:) 所以不会解释 SOH CAH TOA 等)

在此处输入图片说明

这使得核心函数代码如下(仅数学 - 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;
 }

我相信这段代码更优雅、更简洁、更健壮,而且在数学上更有效:) 也感谢 Ash 关于 Const & Let use 与 var 的提示。

这也给出了最终结果:

在此处输入图片说明

在此处输入图片说明

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM