用 d3 将测地线钳制到 min/max 纬度

Clamp geodesic to min/max latitude with d3

我正在使用 d3 在地图上绘制路线:

const width = 400;
const height = width / 2;

const projection = d3.geoEquirectangular()
  .translate([width / 2, height / 2])
  .scale((width - 1) / 2 / Math.pi);

const route_projection = d3.geoEquirectangular()
  .translate([width / 2, height / 2])
  .scale((width - 1) / 2 / Math.pi);
//.preclip(SOME CLIPPING FUNCTION)

const route_path = d3.geoPath()
  .projection(route_projection);

const path = d3.geoPath()
  .projection(projection);

const zoom = d3.zoom()
  .extent([
    [0, 0],
    [width, height]
  ])
  .translateExtent([
    [0, 0],
    [width, height]
  ])
  .scaleExtent([1, Infinity])
  .on("zoom", zoomed);

let svg = d3.select('#map')
  .append("svg")
  .attr("width", width)
  .attr("height", height);

svg.call(zoom);

svg.append("rect")
  .attr("class", "background")
  .attr("fill", "#b4dcfc")
  .attr("width", width)
  .attr("height", height);

let g = svg.append('g');

let geojson = {}

let curve = {
  "type": "FeatureCollection",
  "features": [{
      "type": "Feature",
      "properties": {
        "population": 200
      },
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [-56.12, -35.50],
          [47.81, -25.37],
          [78.71, 7.73]
        ]
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [-56.12, -35.50]
      },
      "properties": {
        "name": "Dinagat Islands"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [47.81, -25.37]
      },
      "properties": {
        "name": "Dinagat Islands"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [78.71, 7.73]
      },
      "properties": {
        "name": "Dinagat Islands"
      }
    }
  ]
}

function createMap() {
  projection.fitSize([width, height], geojson);
  route_projection.fitSize([width, height], geojson);
  g
    .attr("id", "dmas")
    .selectAll("path")
    .data(geojson.features)
    .enter()
    .append("path")
    .attr("fill", "white")
    .attr("stroke", "#222")
    .attr("stroke-width", 1)
    .attr("vector-effect", "non-scaling-stroke")
    .attr("d", path);

  g.append("path")
    .datum(curve)
    .attr("id", "route")
    .attr("d", route_path)
    .attr("fill", "none")
    .attr("stroke", 'red')
    .attr("stroke-width", 2)
    .attr("vector-effect", "non-scaling-stroke")
    .attr("stroke-opacity", 1);

}

function zoomed(transform) {
  g
    .selectAll('path')
    .attr('transform', transform.transform)
  // .attr('stroke-width', 2/transform.transform.k)
}

d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json")
  .then(function(data) {
    geojson = topojson.feature(data, data.objects.countries);
    createMap();
  })
<script src="https://unpkg.com/d3-geo-polygon@1.12.1/dist/d3-geo-polygon.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>

<div id="map"></div>

route_projection,我想使用预剪辑将红色路线的最大纬度限制在 -40 度纬度以上,这样它最终会成为从南美洲到南非的直线.

像这样:

我该怎么做?

裁剪函数不会达到正确的效果,因为它会裁剪您的功能而不是强制它们遵循该线。

相反,我们可以将您的投影与地理变换相结合,以强制线条遵守投影 space:

内的规则
const limit = d3.geoTransform({
  point: function(x,y) {
    this.stream.point(x, Math.min(y, projection([0,-36])[1]));
  }
});

const route_projection = {
  stream: function(s) {
      return projection.stream(limit.stream(s));
  }
};

const route_path = d3.geoPath()
  .projection(route_projection);

这意味着我们只需要一个等距柱状投影而不是两个,我们只需将它与 geoTransform 流组合并将组合流传递给第二个路径生成器:

const width = 400;
const height = width / 2;

const projection = d3.geoEquirectangular()
  .translate([width / 2, height / 2])
  .scale((width - 1) / 2 / Math.pi);


const limit = d3.geoTransform({
  point: function(x,y) {
    this.stream.point(x, Math.min(y, projection([0,-36])[1]));
  }
});

const route_projection = {
  stream: function(s) {
      return projection.stream(limit.stream(s));
  }
};

const route_path = d3.geoPath()
  .projection(route_projection);

const path = d3.geoPath()
  .projection(projection);

const zoom = d3.zoom()
  .extent([
    [0, 0],
    [width, height]
  ])
  .translateExtent([
    [0, 0],
    [width, height]
  ])
  .scaleExtent([1, Infinity])
  .on("zoom", zoomed);

let svg = d3.select('#map')
  .append("svg")
  .attr("width", width)
  .attr("height", height);

svg.call(zoom);

svg.append("rect")
  .attr("class", "background")
  .attr("fill", "#b4dcfc")
  .attr("width", width)
  .attr("height", height);

let g = svg.append('g');

let geojson = {}

let curve = {
  "type": "FeatureCollection",
  "features": [{
      "type": "Feature",
      "properties": {
        "population": 200
      },
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [-56.12, -35.50],
          [47.81, -25.37],
          [78.71, 7.73]
        ]
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [-56.12, -35.50]
      },
      "properties": {
        "name": "Dinagat Islands"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [47.81, -25.37]
      },
      "properties": {
        "name": "Dinagat Islands"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [78.71, 7.73]
      },
      "properties": {
        "name": "Dinagat Islands"
      }
    }
  ]
}

function createMap() {
  projection.fitSize([width, height], geojson);

  g
    .attr("id", "dmas")
    .selectAll("path")
    .data(geojson.features)
    .enter()
    .append("path")
    .attr("fill", "white")
    .attr("stroke", "#222")
    .attr("stroke-width", 1)
    .attr("vector-effect", "non-scaling-stroke")
    .attr("d", path);

  g.append("path")
    .datum(curve)
    .attr("id", "route")
    .attr("d", route_path)
    .attr("fill", "none")
    .attr("stroke", 'red')
    .attr("stroke-width", 2)
    .attr("vector-effect", "non-scaling-stroke")
    .attr("stroke-opacity", 1);

}

function zoomed(transform) {
  g
    .selectAll('path')
    .attr('transform', transform.transform)
}

d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json")
  .then(function(data) {
    geojson = topojson.feature(data, data.objects.countries);
    createMap();
  })
  
  
<script src="https://unpkg.com/d3-geo-polygon@1.12.1/dist/d3-geo-polygon.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>

<div id="map"></div>

哪个应该给出: