Как нарисовать круги с радиусами, приведенными в километрах точно на карте мира
Я использую проекцию d3.geo.conicEquidistant() на карте мира приблизительно с центром в Тихом океане, моя цель - нарисовать круги из точки, иллюстрирующей определенное расстояние, скажем 1000 км, от этой точки на карте.
Как я могу рассчитать правильный радиус на проекции, учитывая конкретные километры?
Вот мой пример кода (да, речь идет о ракетных вылетах из Северной Кореи. Использование искусственного Пхеньяна в качестве стартовой точки:), опираясь на этот вопрос: масштабируйте радиус окружности (в метрах) до 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>
Я посмотрел на этот ответ и попробовал его выше, но не смог применить его к своему коду (честно говоря, я новичок в d3, может быть, что-то совершенно не так): /questions/10158173/masshtabirovanie-radiusa-kruga-v-metrah-do-d3js-d3geomercator-map/10158183#10158183
Еще один вопрос: является ли это решение специфичным для проекции Меркатора?
(Давайте проигнорируем, что нам действительно нужно скрыть Антарктиду в этой проекции.)
2 ответа
Есть два способа добиться этого.
Один (более простой вариант) использует d3 geoCircle
функциональность для создания географически кругового объекта:
var circle = d3.geoCircle().center([x,y]).radius(r);
Для этого x и y - ваша центральная точка в градусах, а r - радиус круга в градусах. Чтобы найти радиус круга в метрах, нам нужно преобразовать метры в градусы - что проще всего, если мы примем круглую землю (Земля имеет лишь слегка эллипсоидальную форму, так что это индуцированная ошибка до 0,3%, но даже если при использовании эллипсоида земля действительно имеет более картофельную форму, что также приведет к ошибке). Используя средний радиус 6 371 км, мы можем получить грубую формулу, такую как:
var circumference = 6371000 * Math.PI * 2;
var angle = distance in meters / circumference * 360;
var circle = d3.geoCircle().center([x,y]).radius(angle);
Это дает нам что-то вроде:
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>
Другим вариантом является создание функции геоджона на лету из пользовательских функций, а не через d3. Самый простой способ - взять точку и вычислить точки, которые находятся на расстоянии х метров от подшипников, расположенных на расстоянии 10 градусов друг от друга (для 36 точек на окружности). Для этого необходимо вычислить точку, используя начальную точку, азимут и расстояние, формулу можно найти здесь. Некоторое время назад я построил пример индикатрисы тканей с помощью этого метода: Indicatrix Bliss Tissot.
Основываясь на ответе Эндрю, вот как я решил свою проблему:
Добавил стилизацию, изменил код на 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:
var width = 1200;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
Использование конической эквидистантной проекции:
var projection = d3.geoConicEquidistant()
.center([0, 20])
.scale((width + 1) / 2 / Math.PI)
.rotate([-140,0]);
var path = d3.geoPath()
.projection(projection);
Создание кругов из ответа Андрея, один с 1000 км, а другой с радиусом 3500 км. Я думаю, их можно было бы лучше разместить в списках / объектах.
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);
Загрузка и отображение мира:
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");
});
Изменить: добавлен https в файл json и изменил код, так что круги нарисованы над картой мира. У меня есть рабочий jfiddle здесь: https://jsfiddle.net/y8qn8tr1/2/