2017-07-31 2 views
3

J'utilise la projection d3.geo.conicEquidistant() sur une carte du monde approximativement centrée sur le Pacifique, mon but est de dessiner des cercles à partir d'un point, illustrant une distance spécifique, disons 1000 km, à partir de ce point sur la carte.Comment dessiner des cercles avec des rayons donnés en kilomètres avec précision sur la carte du monde

Comment puis-je calculer le rayon correct sur la projection, compte tenu de certains kilomètres?

Voici mon exemple de code (oui, il est au sujet atteint missile de la Corée du Nord artificiellement par Pyongyang de lancer le point :), sur cette question. Scale a circle's radius (given in meters) to D3.js d3.geo.mercator map

<!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> 

Je regardais cette réponse, en essayant ci-dessus, mais n'était pas tout à fait en mesure de l'appliquer à mon code (je suis tout à fait nouveau à d3 pour être honnête, il pourrait y avoir quelque chose de très évident):

Autre question: est cette solution spécifique à la projection de mercator?

(Ignorons que nous avons vraiment besoin de se cacher dans l'Antarctique cette projection.)

+0

Pouvez-vous nous lier au code de travail, ou jsfiddle cela en quelque sorte? – barrycarter

+0

Voici un jfiddle je l'ai fait après avoir changé mon code avec la réponse d'Andrew: https://jsfiddle.net/y8qn8tr1/ - pour une raison quelconque, le chemin de la carte du monde ne montre pas dans le violon ... – Julius

+0

Essayez https: // plutôt que http: // lors de l'accès au geojson: https://jsfiddle.net/38he3mt9/ ​​ –

Répondre

4

Il y a deux façons d'y parvenir.

One (l'option plus facile) utilise la fonctionnalité geoCircle de d3 pour créer une fonction géographique circulaire:

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

Pour cela, x et y sont votre point central en degrés, et r est le rayon du cercle degrés. Pour trouver le rayon d'un cercle en mètres, nous devons convertir les mètres en degrés - ce qui est plus facile si nous supposons une terre ronde (La terre n'est que légèrement ellipsoïde, donc c'est une erreur induite jusqu'à 0.3%, mais en utilisant un ellipsoïde, la terre est vraiment plus en forme de pomme de terre, ce qui induira aussi une erreur). L'utilisation d'un rayon moyen de 6371 km, nous pouvons obtenir une formule brute comme:

var circumference = 6371000 * Math.PI * 2; 

var angle = distance in meters/circumference * 360; 

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

Cela nous donne quelque chose comme:

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>

L'autre option est de créer une fonction de GeoJSON à la volée de fonctions personnalisées plutôt que par le biais de d3. Le plus simple est de prendre un point et de calculer les points éloignés de x mètres à des intervalles espacés de 10 degrés (pour 36 points au cercle). Cela nécessite de calculer un point en utilisant le point de départ, le relèvement et la distance, la formule peut être trouvée ici. Il y a quelque temps, j'ai construit un exemple d'indicatrice de Tissot en utilisant cette méthode: Tissot's Indicatrix Bl.ock.

+0

Great Andrew, merci! C'était super utile. J'ai changé mon code en d3 v4 et j'étais capable de dessiner les cercles comme je l'espérais en utilisant la première approche, plus facile. Cela semble fonctionner indépendamment de la projection de la carte? D'où viennent les 6371 km? Je vais mon code édité comme une anser ici. – Julius

+0

J'ai utilisé le rayon terrestre moyen fourni par wikipedia, bien que vous puissiez utiliser la spécification WGS84 pour les axes majeurs et mineurs de la Terre (et toute autre donnée) et prendre la moyenne des deux pour un rayon terrestre relativement proche. –

1

Miser sur la réponse d'Andrew, voici comment je résolu mon problème:

style Ajouté, changé mon code à 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> 

Le script d3:

var width = 1200; 
var height = 400; 

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

Utilisation de la projection conique équidistante:

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

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

Création de cercles à partir de la réponse d'Andrew, l'un avec 1000km et l'autre avec un rayon de 3500km. Ceux-ci pourraient être arrangés plus joliment dans des listes/objets je devine.

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); 

Chargement et afficher le monde:

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: Ajout https dans le fichier JSON et modifié le code de sorte que les cercles sont tirés dessus de la carte du monde. J'ai un jfiddle de travail ici: https://jsfiddle.net/y8qn8tr1/2/

+0

Les questions dans les réponses reçoivent généralement peu de réponses - étant limitées aux commentaires, et limitées dans l'auditoire. Essayez 'https: //' pour le geojson dans le violon, et le dessin du monde arrive après que tout le reste a été dessiné - d3.json est asynchrone, ajoutez les cercles dans le rappel ou utilisez les éléments g ajoutés dans l'ordre spécifié (en dehors du rappel d3.json) et ajouter des fonctionnalités à ces éléments g (les ordonnant ainsi). –

+0

Merci encore! J'ai mis à jour le jfiddle avec https afin qu'il fonctionne maintenant. C'est ma première question et toujours essayer de comprendre ce que la pratique avec des questions de suivi est, j'étais un peu incertain si j'étais supposé poster une nouvelle réponse ou éditer l'original et avoir de nouvelles questions comme commentaires. Peut-être que l'édition originale était meilleure? Mais c'est peut-être trop hors sujet de ma question originale. – Julius

+2

Si vous avez une question nouvelle/différente s'il vous plaît demandez-le séparément. Vous pouvez toujours créer un lien vers cette question si vous pensez que cela ajouterait un contexte. –