D3 在地理数据方面相当独特:它使用球形数学(尽管有很多好处,但确实带来了一些挑战)。d3.geoPath 对两点之间的线段进行采样,以便路径遵循一个大圆(地球上两点之间的最短路径)。平行线不遵循大圆距离,因此您的路径不遵循平行线。
您正在寻找的行为要求我们在两个纬度经度点之间画一条线,就好像它们是笛卡尔一样,即使它们不是,然后在应用立体投影时保留该线沿线的点。
使用圆柱投影时,解决方案很简单,不要在直线上的点之间采样。这个答案包含这样一个解决方案。
这对立体投影没有帮助 - 链接方法只会导致第一个点和终点之间的直线而不是沿着平行线的曲线。
一种解决方案是手动对起点和终点之间的点进行采样,就好像数据是笛卡尔数据一样,然后将它们视为 3D,以便使用立体投影进行投影。这会导致路径遵循平行线,其中起点和终点具有相同的北/南值。使用 d3.geoPath 时,您采样的频率会减少/消除大圆距离的影响。
在我的解决方案中,我将使用两个 d3 辅助函数:
- d3.geoDistance 测量两个 lat long 对之间的距离,以弧度为单位。
- 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
}
上面假设一条线有两个点/一个段,如果你的线比你需要调整的更复杂,自然而然。它只是作为一个起点。
在行动中:
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>