Масштабирование радиуса круга (в метрах) до D3.js d3.geo.mercator map
Я использую библиотеки D3.js и TopoJSON для рендеринга плоской SVG-карты мира в небольшом элементе div на веб-странице. Я также беру некоторые географические объекты (многоугольники и круги) и наносю их на карту с помощью координат широты / долготы. Кажется, что все это работает довольно хорошо, однако объекты кругов, которые я рисую на карте, содержат элемент радиуса, который указан в метрах. Я не могу найти или выяснить, как преобразовать / масштабировать это измерение соответствующим образом на карту SVG. Любая помощь будет принята с благодарностью.
Фрагмент кода, который рисует круг и настройки:
if (formattedGeoObjects[a].shape.indexOf('circle') >= 0) {
//plot point for circle
svg.selectAll('.pin')
.data(formattedGeoObjects).enter().append('circle', '.pin')
.attr({fill: formattedGeoObjects[a].color.toString()})
.attr('r', 5) //formattedGeoObjects[a].radius is in meters
.attr('transform', 'translate(' +
projection([
formattedGeoObjects[a].location[0],
formattedGeoObjects[a].location[1]
]) + ')'
);
}
Ссылка JSFiddle для сокращенной версии кода: https://jsfiddle.net/vnrL0fdc/7/
Вот полный код для справки...
Функция, которая выполняет основную часть работы:
setupMap: function(mapJson, theElement, geoObject, colorCb, normalizeCb) {
var width = 200;
var height = 120;
//define projection of spherical coordinates to Cartesian plane
var projection = d3.geo.mercator().scale((width + 1) / 2 / Math.PI).translate([width / 2, height / 2]);
//define path that takes projected geometry from above and formats it appropriately
var path = d3.geo.path().projection(projection);
//select the canvas-svg div and apply styling attributes
var svg =
d3.select('#' + theElement + ' .canvas-svg').append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'ocean');
//convert the topoJSON back to GeoJSON
var countries = topojson.feature(mapJson, mapJson.objects.countries).features;
//give each country its own path element and add styling
svg.selectAll('.countries')
.data(countries).enter().append('path')
.attr('class', 'country')
.attr('d', path);
//add borders around all countries with mesh
svg.append('path')
.datum(topojson.mesh(mapJson, mapJson.objects.countries, function() {
return true;
}))
.attr('d', path)
.attr('class', 'border');
//if shape data exists, draw it on the map
if (geoObject !== null && geoObject.length !== 0) {
//normalize geoObject into format needed for d3 arc functionality and store each shapes color
var formattedGeoObjects = normalizeCb(geoObject, colorCb);
for (a = 0; a < formattedGeoObjects.length; a++) {
if (formattedGeoObjects[a].shape.indexOf('polygon') >= 0) {
for (b = 0; b < formattedGeoObjects[a].lines.length; b++) {
//plot point for polygon
svg.selectAll('.pin')
.data(formattedGeoObjects).enter().append('circle', '.pin')
.style({fill: formattedGeoObjects[a].color.toString()}).attr('r', 2)
.attr('transform', 'translate(' +
projection([
formattedGeoObjects[a].lines[b].coordinates[0][0],
formattedGeoObjects[a].lines[b].coordinates[0][1]
]) + ')'
);
}
//draw lines for polygon
svg.append('g').selectAll('.arc')
.data(formattedGeoObjects[a].lines).enter().append('path')
.attr({d: path})
.style({
stroke: formattedGeoObjects[a].color.toString(),
'stroke-width': '1px'
});
}
if (formattedGeoObjects[a].shape.indexOf('circle') >= 0) {
//plot point for circle
svg.selectAll('.pin')
.data(formattedGeoObjects).enter().append('circle', '.pin')
.attr({fill: formattedGeoObjects[a].color.toString()})
.attr('r', 5)
.attr('transform', 'translate(' +
projection([
formattedGeoObjects[a].location[0],
formattedGeoObjects[a].location[1]
]) + ')'
);
}
}
}
}
Вот сжатая версия того, как выглядит отформатированный GeoObjects:
[
{
"shape": "polygon0",
"color": "#000000",
"lines": [
{
"type": "LineString",
"coordinates": [
[
-24.9609375,
36.5625
],
[
-24.9609375,
55.1953125
]
]
}
..... more coords
]
},
{
"shape": "polygon1",
"color": "#006600",
"lines": [
{
"type": "LineString",
"coordinates": [
[
-42.1875,
26.3671875
],
[
-71.71875,
7.734375
]
]
}
..... more coordindates
]
},
{
"shape": "circle2",
"color": "#FF0000",
"location": [
13.359375,
31.640625
],
"radius": 1881365.33
}
]
И, наконец, CSS/HTML:
.canvas-svg {
.ocean {
background: #85E0FF;
}
.country {
fill: #FFFFFF;
}
.border {
fill: none;
stroke: #777;
stroke-width: .5;
}
}
<div class="canvas-svg"></div>
2 ответа
Мой коллега помог мне, показав мне гораздо более простой способ сделать это (к вашему сведению - было обновлено значение широты / долготы для центра круга). Построение двух точек на холсте и вычисление расстояния для определения масштаба работает и является точным, но есть гораздо более простой способ сделать это, используя общее количество пикселей на изображении и общую площадь мира, фрагменты кода и JSFiddle ниже.:
var width = 200;
var height = 120;
//variables for scaling circle radius
var totPixels = (width * height);
var totWorldMeters = 510000000000;
var metersPerPixel = (totWorldMeters / totPixels);
var scaledRadius;
//scale the radius given in meters to pixels on the map
scaledRadius = (100 * (formattedGeoObjects[a].radius / metersPerPixel));
if(scaledRadius < 2) {
scaledRadius = 2;
}
Работающий JSFiddle: https://jsfiddle.net/vnrL0fdc/15/
Итак, я "думаю", что нашел способ масштабирования радиуса (в метрах) до пикселей на географической карте d3 декартовой плоскости. Я, вероятно, делаю этот путь более сложным, чем нужно - но я не уверен, как еще это сделать.
Высота, ширина и проекция карты определяются как:
var width = 200;
var height = 120;
var projection =
d3.geo.mercator()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height / 2]);
Географические объекты, которые я строю на карте, содержат координаты широты и долготы для нескольких точек. Поиском по stackru я нашел формулу расстояния:
d = sqrt((x2 - x1)^2 + (y2 - y1)^2)
Формула требует двух точек от декартовой плоскости (x1,y1) и (x2,y2). Я выбрал точку окружности и одну из точек многоугольника, координаты широты и долготы следующие:
Lat/Long for polygon1, point1: 36.5625, -24.9609375
Lat/Long for circle: 31.640625, 13.359375
Я использовал следующий веб-сайт, чтобы узнать, сколько миль находится между двумя вышеуказанными координатами - http://www.freemaptools.com/how-far-is-it-between.htm
Miles between the two coordinates on the map are: 3020.207
Затем я нашел проекционные координаты (x,y) на декартовой плоскости для двух координат широты / долготы через:
projection([long,lat])
X/Y for polygon1, point1: 86.0634765625, 38.040230671805666
X/Y for circle: 107.458984375, 41.36090550209383
Затем я вставил эти значения в формулу, чтобы вычислить расстояние в пикселях между двумя точками:
d = sqrt ( (107.458984375 - 86.0634765625)^2 + (41.36090550209383 - 38.040230671805666)^2 )
result = 21.651665891641176 pixels
miles per pixel = 139.49074473599643 (calculated by: 3020.207/21.651665891641176)
meters per pixel = 224488.5930964074505 (calculated by 139.49074473599643 * 1609.34)
Работающий JSFiddle: https://jsfiddle.net/vnrL0fdc/8/
Это похоже на очень окольный способ масштабирования метров до проекции карты Меркатора. Если у кого есть намного более простое решение - поделитесь пожалуйста!