Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
525 views
in Technique[技术] by (71.8m points)

d3.js - Curved geojson polygon edges on map projections

I am trying to draw rectangles on different map projections using d3.js and geojson. The mapped coordinates seem right, however the edges appear curved in a strange way. I understand that this may have to do with the shortest path on the real Earth, but what I would like is that the edges follow the parallels/meridians graticule of the projection. Is there a way to do that? Can anyone help?

Example: Aitoff Projection Example: Mercator Here is the code I am using:

<!DOCTYPE html>
<html>

<head>
  <title>World Map</title>
  <meta charset="utf-8">

  <script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>

  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/topojson.v2.min.js"></script>
  <style>
    path {
      fill: red;
      stroke: #000;
      stroke-width: .1px;
    }
    .graticule {
      fill: none;
      stroke: #000;
      stroke-width: .2px;
    }
    .foreground {
      fill: none;
      stroke: #333;
      stroke-width: 1.2px;
    }

  </style>
</head>

<body>
  <svg width="960" height="600"></svg>
  <script>
    const svg = d3.select("svg")
    const myProjection = d3.geoMercator()
    const path = d3.geoPath().projection(myProjection)
    const graticule = d3.geoGraticule()

    const geojson = {

"type": "FeatureCollection",                                                      
"features": [
{ "type": "Feature", "properties": {
    "color": "blue"
},
"geometry": { 
    "type": "Polygon", 
    "coordinates": [[[-80.0, 50.0], [-20.0, 50.0], [-20.0, -10.0], [-80.0, -10.0], [-80.0, 50.0]]]
} }
]
}
    function drawMap(err, world) {
      if (err) throw err

      svg.append("g")
        .selectAll("path")
        .data(topojson.feature(world, world.objects.land).features)
        .enter().append("path")
        .attr("d", path);

      svg.append("path")
        .datum(graticule)
        .attr("class", "graticule")
        .attr("d", path);

      svg.append("path")
        .datum(graticule.outline)
        .attr("class", "foreground")
        .attr("d", path);

      svg.append("g")
        .selectAll("path")
        .data(geojson.features)
        .enter().append("path")
        .attr("d", path)

    }
    d3.json("https://unpkg.com/[email protected]/world/50m.json", drawMap)


  </script>
</body>

</html>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Your assumption is right: d3 uses great circle distances to draw lines: this means that any path between two points using a d3 geoProjection and geoPath follows the shortest real world path between those two points. This means:

  • that the same path between two points aligns with other geographic points and features no matter the projection
  • that the anti-meridian can be accounted for
  • and the resulting map more accurately depicts lines.

To draw straight lines and/or lines that follow parallels (meridians are the shortest path between two points that fall on them - so paths follow this already, assuming an unrotated graticule) there are a few possibilities.

The easiest solution is to use a cylindrical projection like a Mercator to create a custom geoTransform. d3.geoTransforms do not use spherical geometry, unlike d3.geoProjections, instead they use Cartesian data. Consequently they don't sample along lines to create curved lines: this is unecessary when working with Cartesian data. This allows us to use spherical geometry for the geojson vertices within the geoTransform while still keeping straight lines on the map:

var transform = d3.geoTransform({
    point: function(x, y) {
      var projection = d3.geoMercator();
      this.stream.point(...projection([x,y]));
    }
});

As seen below:

var projection = d3.geoMercator();

var transform = d3.geoTransform({
    point: function(x, y) {
      var projection = d3.geoMercator();
      this.stream.point(...projection([x,y]));
    }
});

var color = ["steelblue","orange"]


var geojson = {type:"LineString",coordinates:[[-160,60],[30,45]]};
var geojson2 = {type:"Polygon",coordinates:[[[-160,60,],[-80,60],[-100,30],[-160,60]]]}

var svg = d3.select("body")
  .append("svg")
  .attr("width",960)
  .attr("height",500);
  
svg.selectAll(null)
  .data([projection,transform])
  .enter()
  .append("path")
  .attr("d", function(d) {
    return d3.geoPath().projection(d)(geojson)
  })
  .attr("fill","none")
  .attr("stroke",function(d,i) { return color[i]; } )
  .attr("stroke-width",1);

svg.selectAll(null)
  .data([projection,transform])
  .enter()
  .append("path")
  .attr("d", function(d) {
    return d3.geoPath().projection(d)(geojson2)
  })
  .attr("fill","none")
  .attr("stroke",function(d,i) { return color[i]; } )
  .attr("stroke-width",2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...