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