How to draw circles with radii given in kilometers exactly on the world map

I am using the d3.geo.conicEquidistant () projection on a world map roughly oriented towards the Pacific Ocean, my goal is to draw circles from a point illustrating a certain distance, say 1000 km, from that point on the map.

How can I calculate the correct radius on the projection given the specific kilometers?

Here is my example code (yes, this is for a missile coming from North Korea. Artificially artificial Pyongyang as a launch point :), based on this question: Increase the circle radius (given in meters) to the map D3.js d3.geo.mercator

<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
  stroke: white;
  stroke-width: 0.25px;
  fill: grey;
}
circle {
  stroke: red;
  stroke-width: 1px;
  stroke-dasharray: 5;
  fill: transparent;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960,
    height = 500;

var projection = d3.geo.conicEquidistant()
    .center([0, 5 ])
    .scale((width + 1) / 2 / Math.PI)//.scale(150)
    .rotate([-160,0]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var path = d3.geo.path()
    .projection(projection);

var g = svg.append("g");

var missiles = [
  {
    name: "missile1",
    location: { // appx lat&long of pyongyang
      latitude: 125.6720717, 
      longitude: 39.0292506
    },
    reach: 1000 //radius of circle in kilometers on map
  },
    {
    name: "missile2",
    location: { // appx lat&long of pyongyang
      latitude: 125.6720717, 
      longitude: 39.0292506
    },
    reach: 3500 //radius of circle in kilometers on map
  },
];


// load and display the World
d3.json("https://s3-us-west-2.amazonaws.com/vida-public/geo/world-topo-min.json", function(error, topology) {
    g.selectAll("path")
      .data(topojson.object(topology, topology.objects.countries)
          .geometries)
    .enter()
      .append("path")
      .attr("d", path)
});


svg.selectAll(".pin")
    .data(missiles)
  .enter().append("circle", ".pin")
    .attr("r", scaledRadius)
      /*function(d) {
      return d.reach
      })*/


    .attr("transform", function(d) {
      return "translate(" + projection([
        d.location.latitude,
        d.location.longitude
      ]) + ")"
    })

</script>
</body>
</html>

      

I looked at this answer, trying it above, but was unable to apply it to my code (I'm pretty new to d3, to be honest there might be something very obvious): https://stackoverflow.com/a/2423932/ ...

Further question: is this a solution specific to the mercator projection?

(Let's not ignore that we really need to hide Antarctica in this projection.)

+3


source to share


2 answers


There are two ways to accomplish this.

One (simpler option) uses the d3 function geoCircle

to create a geographically circular function:

var circle = d3.geoCircle().center([x,y]).radius(r);

      

For that, x and y are your center point in degrees, and r is the radius of the circle in degrees. To find the radius of a circle in meters, we need to convert meters to degrees - this is easiest if we assume a round earth (Earth is only slightly ellipsoid, so this is an induced error of up to 0.3%, but even if using an ellipsoid, the earth is indeed larger than potatoes so that will throw an error too). Using an average radius of 6,371 km, we can get a rough formula:

var circumference = 6371000 * Math.PI * 2;

var angle = distance in meters / circumference * 360;

var circle = d3.geoCircle().center([x,y]).radius(angle);

      



This gives us something like:

var width = 500;
var height = 300;

var svg = d3.select("body")
  .append("svg")
  .attr("width",width)
  .attr("height",height);
  
var projection = d3.geoAlbers()
  .scale(200)
  .translate([width/2,height/2]);
  
var path = d3.geoPath().projection(projection);

var usa = {"type":"FeatureCollection", "features": [
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-94.81758,49.38905],[-88.378114,48.302918],[-82.550925,45.347517],[-82.439278,41.675105],[-71.50506,45.0082],[-69.237216,47.447781],[-66.96466,44.8097],[-70.11617,43.68405],[-70.64,41.475],[-73.982,40.628],[-75.72205,37.93705],[-75.72749,35.55074],[-81.49042,30.72999],[-80.056539,26.88],[-81.17213,25.20126],[-83.70959,29.93656],[-89.18049,30.31598],[-94.69,29.48],[-99.02,26.37],[-100.9576,29.38071],[-104.45697,29.57196],[-106.50759,31.75452],[-111.02361,31.33472],[-117.12776,32.53534],[-120.36778,34.44711],[-123.7272,38.95166],[-124.53284,42.76599],[-124.68721,48.184433],[-122.84,49],[-116.04818,49],[-107.05,49],[-100.65,49],[-94.81758,49.38905]]],[[[-155.06779,71.147776],[-140.985988,69.711998],[-140.99777,60.306397],[-148.018066,59.978329],[-157.72277,57.570001],[-166.121379,61.500019],[-164.562508,63.146378],[-168.11056,65.669997],[-161.908897,70.33333],[-155.06779,71.147776]]]]},"properties":{"name":"United States of America"},"id":"USA"}
]};

var circumference = 6371000 * Math.PI * 2;
var angle = 1000000 / circumference * 360;

var circle = d3.geoCircle().center([-100,40]).radius(angle);

svg.append("path")
      .attr("d",path(usa));

svg.append("path")
  .attr("d", path(circle()))
  .attr("fill","steelblue");
  
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
      

Run codeHide result


Another option is to create the geojson function "on the fly" from UDFs rather than via d3. The easiest way is to take a point and calculate the points located at a distance of x meters, on supports located at a distance of 10 degrees (36 points in a circle). This requires calculating a point using the start point, pivot and distance, the formula can be found here. Some time ago I built an example of a cloth indicatrix using this method: Tissot Indicatrix Bl.ock .

+5


source


Based on Andrew's answer, this is how I solved my problem:

Added style, changed my code to d3 v4:

</!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
        path {
      stroke: white;
      stroke-width: 0.25px;
      fill: grey;
    }
    .circle {
      stroke: red;
      stroke-width: 1px;
      stroke-dasharray: 5;
      fill: transparent;
    }
    </style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

      

d3 script:

var width = 1200;
var height = 400;

var svg = d3.select("body")
  .append("svg")
  .attr("width",width)
  .attr("height",height);

      

Using a conical equidistant projection:



var projection = d3.geoConicEquidistant()
    .center([0, 20])
    .scale((width + 1) / 2 / Math.PI)
    .rotate([-140,0]);

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

      

Making circles from Andrew's answer, one with 1000 km and others with a radius of 3500 km. They might be more convenient in lists / objects I assume.

var circumference = 6371000 * Math.PI * 2;

var angle1 = 1000000 / circumference * 360;
var missile1 = d3.geoCircle().center([125.6720717,39.0292506]).radius(angle1);

var angle2 = 3500000 / circumference * 360;
var missile2 = d3.geoCircle().center([125.6720717,39.0292506]).radius(angle2);

      

Loading and displaying the world:

var url = "http://enjalot.github.io/wwsd/data/world/world-110m.geojson";
//display the world
    d3.json(url, function(err, geojson) {
      svg.append("path")
        .attr("d", path(geojson));
//append the circles
      svg.append("path")
        .attr("d", path(missile1()))
        .attr("class","circle");
      svg.append("path")
        .attr("d", path(missile2()))
        .attr("class","circle");
   });

      

Edit: Added https to json file and changed the code so that circles are drawn over the world map. I have a working script here: https://jsfiddle.net/y8qn8tr1/2/

+2


source







All Articles