为什么我的 geo LineString 不遵循 latitude/graticule 曲线?

Why doesn't my geo LineString follow latitude/graticule curves?

我正在尝试绘制遵循各种纬度线段的 LineString,但是内置的测地线弧插值似乎并未绘制遵循纬度的弧线。我的问题是:为什么不,我该如何实现?

这是我的结果:

还有我的代码:

const width = 500;
const height = 500;
const scale = 200;

const svg = d3.select('svg').attr("viewBox", [0, 0, width, height]);

const projection = d3.geoStereographic().rotate([0, -90]).precision(0.1).clipAngle(90.01).scale(scale).translate([width / 2, height / 2]);
const path = d3.geoPath(projection);

const graticule = d3.geoGraticule().stepMajor([15, 15]).stepMinor([0, 0])();

svg
  .append("path")
  .datum(graticule)
  .attr("d", path)
  .attr("fill", "none")
  .attr("stroke", '#000000')
  .attr("stroke-width", 0.3)
  .attr("stroke-opacity", 1);

let curve = {
  "type": "Feature",
  "geometry": {
    "type": "LineString",
    "coordinates": [
      [-180, 15],
      [-90, 15]
    ]
  }
}

svg
  .append("path")
  .datum(curve)
  .attr("d", path)
  .attr('fill-opacity', 0)
  .attr('stroke', 'red')
  .attr("stroke-width", 1)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

我的fiddle:https://jsfiddle.net/jovrtn/komfxycz/

D3 在地理数据方面相当独特:它使用球面数学(尽管有很多好处,但确实带来了一些挑战)。 d3.geoPath 对两点之间的线段进行采样,使路径沿着大圆(地球上两点之间的最短路径)。纬线不遵循大圆距离,因此您的路径不遵循纬线。

您正在寻找的行为要求我们在经纬度的两个点之间画一条线,就好像它们是 Carteisan,即使它们不是,然后在应用立体投影时保留沿该线的点。

当使用圆柱投影时,解决方案很简单,不要在直线上的点之间进行采样。这个answer包含这样一个解决方案。

这对立体投影没有帮助 - 链接方法只会导致第一个点和终点之间的直线而不是沿平行线的曲线。

一种解决方案是手动对开始和结束之间的点进行采样,就好像数据是笛卡尔坐标一样,然后将它们视为 3D 以便使用立体投影对它们进行投影。这导致路径遵循平行线,其中起点和终点具有相同的 north/south 值。使用 d3.geoPath.

时,您采样 reduces/eliminates 大圆距离效果的频率

在我的解决方案中,我将使用两个 d3 辅助函数:

  • d3.geoDistance 以弧度为单位测量两对经纬度之间的距离。
  • d3.interpolate 在两个值之间创建插值函数。
    let sample = function(line) {
       let a = line.geometry.coordinates[0];  // first point
       let b = line.geometry.coordinates[1];  // end point

       let distance = d3.geoDistance(a, b);   // in radians
       let precision = 1*Math.PI/180;         // sample every degree.
       let n = Math.ceil(distance/precision); // number of sample points
       let interpolate = d3.interpolate(a,b)  // create an interpolator
       let points = [];                       // sampled points.
       for(var i = 0; i <= n; i++) {          // sample n+1 times
         points.push([...interpolate(i/n)]);  // interpolate a point
       }
       line.geometry.coordinates = points;    // replace the points in the feature
    }

上面假设一条线有两个 points/one 段,如果你的线比那更复杂,你自然需要调整它。它只是一个起点。

并在行动中:

const width = 500;
const height = 500;
const scale = 200;

const svg = d3.select('svg').attr("viewBox", [0, 0, width, height]);

const projection = d3.geoStereographic().rotate([0, -90]).precision(0.1).clipAngle(90.01).scale(scale).translate([width / 2, height / 2]);
const path = d3.geoPath(projection);

const graticule = d3.geoGraticule().stepMajor([15, 15]).stepMinor([0, 0])();

svg
  .append("path")
  .datum(graticule)
  .attr("d", path)
  .attr("fill", "none")
  .attr("stroke", '#000000')
  .attr("stroke-width", 0.3)
  .attr("stroke-opacity", 1);

let curve = {
  "type": "Feature",
  "geometry": {
    "type": "LineString",
    "coordinates": [
      [-180, 15],
      [-90, 15]
    ]
  }
}

svg
  .append("path")
  .datum(curve)
  .attr("d", path)
  .attr('fill-opacity', 0)
  .attr('stroke', 'red')
  .attr("stroke-width", 1)


let sample = function(line) {
   let a = line.geometry.coordinates[0];
   let b = line.geometry.coordinates[1];
   
   let distance = d3.geoDistance(a, b); // in radians
   let precision = 5*Math.PI/180;
   let n = Math.ceil(distance/precision);
   let interpolate = d3.interpolate(a,b)
   let points = [];
   for(var i = 0; i <= n; i++) {
     points.push([...interpolate(i/n)]);
   }
   line.geometry.coordinates = points;
}

sample(curve);


svg
  .append("path")
  .datum(curve)
  .attr("d", path)
  .attr('fill-opacity', 0)
  .attr('stroke', 'blue')
  .attr("stroke-width", 1)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>