Центрировать карту в d3 с учетом объекта geoJSON
В настоящее время в d3, если у вас есть объект geoJSON, который вы собираетесь рисовать, вы должны его масштабировать и переводить, чтобы получить его до размера, который требуется, и перевести его, чтобы центрировать его. Это очень утомительная задача проб и ошибок, и мне было интересно, знает ли кто-нибудь лучший способ получить эти значения?
Так, например, если у меня есть этот код
var path, vis, xy;
xy = d3.geo.mercator().scale(8500).translate([0, -1200]);
path = d3.geo.path().projection(xy);
vis = d3.select("#vis").append("svg:svg").attr("width", 960).attr("height", 600);
d3.json("../../data/ireland2.geojson", function(json) {
return vis.append("svg:g")
.attr("class", "tracts")
.selectAll("path")
.data(json.features).enter()
.append("svg:path")
.attr("d", path)
.attr("fill", "#85C3C0")
.attr("stroke", "#222");
});
Как, черт возьми, я получаю .scale(8500) и .translate([0, -1200]) без малейшего внимания?
Ответы
Ответ 1
Следующее, похоже, делает примерно то, что вы хотите. Масштабирование, похоже, в порядке. При применении к моей карте небольшое смещение. Это небольшое смещение, вероятно, вызвано тем, что я использую команду translate для центрирования карты, хотя мне, вероятно, следует использовать команду center.
- Создайте проекцию и d3.geo.path
- Вычислить границы текущей проекции
- Используйте эти оценки для расчета масштаба и перевода
- Восстановить проекцию
В коде:
var width = 300;
var height = 400;
var vis = d3.select("#vis").append("svg")
.attr("width", width).attr("height", height)
d3.json("nld.json", function(json) {
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 150;
var offset = [width/2, height/2];
var projection = d3.geo.mercator().scale(scale).center(center)
.translate(offset);
// create the path
var path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(json);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
// new projection
projection = d3.geo.mercator().center(center)
.scale(scale).translate(offset);
path = path.projection(projection);
// add a rectangle to see the bound of the svg
vis.append("rect").attr('width', width).attr('height', height)
.style('stroke', 'black').style('fill', 'none');
vis.selectAll("path").data(json.features).enter().append("path")
.attr("d", path)
.style("fill", "red")
.style("stroke-width", "1")
.style("stroke", "black")
});
Ответ 2
Мой ответ близок к Ян ван дер Лаансу, но вы можете немного упростить ситуацию, потому что вам не нужно вычислять географический центроид; вам нужен только ограничивающий прямоугольник. И, используя немасштабированную, нетранслированную единичную проекцию, вы можете упростить математику.
Важная часть кода такова:
// Create a unit projection.
var projection = d3.geo.albers()
.scale(1)
.translate([0, 0]);
// Create a path generator.
var path = d3.geo.path()
.projection(projection);
// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(state),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
// Update the projection to use computed scale & translate.
projection
.scale(s)
.translate(t);
После сопоставления ограничительной рамки в проекции блока вы можете вычислить подходящую шкалу , сравнив аспект отношение ограничивающей рамки (b[1][0] - b[0][0]
и b[1][1] - b[0][1]
) к соотношению сторон холста (width
и height
). В этом случае Ive также масштабировал ограничительную рамку до 95% холста, а не на 100%, так что на маленькой дополнительной площади на краях для штрихов и окружающих функций или дополнений.
Затем вы можете вычислить перевести, используя центр ограничивающей рамки ((b[1][0] + b[0][0]) / 2
и (b[1][1] + b[0][1]) / 2
) и центр холста (width / 2
и height / 2
). Заметим, что поскольку ограничивающая рамка находится в координатах единичных проекций, ее нужно умножить на шкалу (s
).
Например, bl.ocks.org/4707858:
![project to bounding box]()
Это связанный с этим вопрос, каким образом можно масштабировать до определенной функции в коллекции без корректировки проекции, т.е. комбинировать проекцию с геометрическим преобразованием для увеличения и уменьшения масштаба. Это использует те же принципы, что и выше, но математика несколько отличается, потому что геометрическое преобразование (атрибут SVG "преобразование" ) сочетается с географической проекцией.
Например, bl.ocks.org/4699541:
![zoom to bounding box]()
Ответ 3
Я новичок в d3 - попытаюсь объяснить, как я это понимаю, но я не уверен, что все правильно.
Секрет - это знание того, что некоторые методы будут действовать на картографическом пространстве (широта, долгота) и другие на декартовом пространстве (x, y на экране). Картографическое пространство (наша планета) (почти) сферическое, декартово пространство (экран) плоское - для сопоставления одного над другим вам нужен алгоритм, который называется projection. Это пространство слишком короткое, чтобы углубиться в увлекательный объект проекций и как они искажают географические особенности, чтобы превратить сферическую плоскость; некоторые из них предназначены для сохранения углов, другие сохраняют расстояния и т.д. - всегда есть компромисс (у Майка Бостока есть огромная коллекция примеров).
![enter image description here]()
В d3 проекционный объект имеет свойство/сеттер центра, заданное в единицах карты:
projection.center([местоположение])
Если задан центр, устанавливает центр проекций в указанное место, двухэлементный массив долготы и широты в градусах и возвращает проекцию. Если центр не указан, возвращает текущий центр, который по умолчанию равен ⟨0 °, 0 °⟩.
Существует также перевод, заданный в пикселях - где проекционный центр стоит относительно холста:
projection.translate([точка])
Если задана точка, задает смещение сдвига проекций на указанный двухэлементный массив [x, y] и возвращает проекцию. Если точка не указана, возвращает текущее смещение сдвига, которое по умолчанию равно [480, 250]. Смещение сдвига определяет координаты пикселей центра выступов. Смещение смещения по умолчанию помещает ⟨0 °, 0 °⟩ в центр области 960 × 500.
Когда я хочу центрировать функцию на холсте, мне нравится устанавливать центр проекции в центр ограничивающего прямоугольника функции - это работает для меня при использовании mercator (WGS 84, используемая на картах Google) для моей страны (Бразилия), никогда не тестировалась с использованием других прогнозов и полушарий. Возможно, вам придется внести коррективы в другие ситуации, но если вы пригвоздите эти основные принципы, вы будете в порядке.
Например, с учетом проекции и пути:
var projection = d3.geo.mercator()
.scale(1);
var path = d3.geo.path()
.projection(projection);
Метод bounds
из path
возвращает ограничивающий прямоугольник в пикселях. Используйте его, чтобы найти правильный масштаб, сравнивая размер в пикселях с размером в единицах карты (0,95 дает вам 5% -ный запас по сравнению с максимальной шириной или высотой). Базовая геометрия здесь, вычисляя ширину/высоту прямоугольника, заданную по диагонали по углам:
var b = path.bounds(feature),
s = 0.9 / Math.max(
(b[1][0] - b[0][0]) / width,
(b[1][1] - b[0][1]) / height
);
projection.scale(s);
![enter image description here]()
Используйте метод d3.geo.bounds
, чтобы найти ограничивающий прямоугольник в единицах карты:
b = d3.geo.bounds(feature);
Установите центр проекции в центр ограничивающего прямоугольника:
projection.center([(b[1][0]+b[0][0])/2, (b[1][1]+b[0][1])/2]);
Используйте метод translate
, чтобы переместить центр карты в центр холста:
projection.translate([width/2, height/2]);
Теперь у вас должна быть функция в центре карты, увеличенная с шагом 5%.
Ответ 4
С d3 v4 его становится проще!
var projection = d3.geoMercator().fitSize([width, height], geojson);
var path = d3.geoPath().projection(projection);
и, наконец,
g.selectAll('path')
.data(geojson.features)
.enter()
.append('path')
.attr('d', path)
.style("fill", "red")
.style("stroke-width", "1")
.style("stroke", "black");
Наслаждайтесь, приветствиями
Ответ 5
Существует метод center(), который вы можете использовать, который принимает пару lat/lon.
Из того, что я понимаю, translate() используется только для перемещения букв на карте. Я не уверен, как определить масштаб.
Ответ 6
Я оглядывался по Интернету, пытаясь сосредоточиться на моей карте, и получил вдохновение от ответа Джона ван дер Лаана и mbostock. Здесь проще использовать jQuery, если вы используете контейнер для svg. Я создал границу 95% для заполнения/границ и т.д.
var width = $("#container").width() * 0.95,
height = $("#container").width() * 0.95 / 1.9 //using height() doesn't work since there nothing inside
var projection = d3.geo.mercator().translate([width / 2, height / 2]).scale(width);
var path = d3.geo.path().projection(projection);
var svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);
Если вы ищете точное масштабирование, этот ответ не будет работать для вас. Но если мне нравится, вы хотите отобразить карту, которая централизуется в контейнере, этого должно быть достаточно. Я пытался отобразить карту меркатора и обнаружил, что этот метод полезен для централизации моей карты, и я мог бы легко отрезать антарктическую часть, так как мне это не нужно.
Ответ 7
В дополнение к центрированию карты в d3 по объекту geoJSON, обратите внимание, что вы можете предпочесть fitExtent()
, а не fitSize()
, если вы хотите указать отступы вокруг границ вашего объекта. fitSize()
автоматически устанавливает это заполнение на 0.
Ответ 8
Чтобы панорамировать/масштабировать карту, вы должны посмотреть на наложение SVG на листовке. Это будет намного проще, чем преобразование SVG. См. Этот пример http://bost.ocks.org/mike/leaflet/, а затем Как изменить центр карты в листовке
Ответ 9
С ответом mbostocks и комментарием Herb Caudill, я начал сталкиваться с проблемами с Аляской, так как я использовал проекцию меркатора. Я должен отметить, что в своих целях я пытаюсь спроектировать и сосредоточить Штаты США. Я обнаружил, что я должен был жениться на двух ответах с ответом Джона ван дер Лаана со следующим исключением для полигонов, которые перекрывают полушария (полигоны, которые в конечном итоге имеют абсолютную ценность для Востока и Запада, которая больше 1):
-
установить в проекторе простую проекцию:
projection = d3.geo.mercator(). scale (1).translate([0,0]);
-
создать путь:
path = d3.geo.path(). проекция (проекция);
3. установите мои границы:
var bounds = path.bounds(topoJson),
dx = Math.abs(bounds[1][0] - bounds[0][0]),
dy = Math.abs(bounds[1][1] - bounds[0][1]),
x = (bounds[1][0] + bounds[0][0]),
y = (bounds[1][1] + bounds[0][1]);
4.Add исключение для Аляски и состояния, которые перекрывают полушария:
if(dx > 1){
var center = d3.geo.centroid(topojson.feature(json, json.objects[topoObj]));
scale = height / dy * 0.85;
console.log(scale);
projection = projection
.scale(scale)
.center(center)
.translate([ width/2, height/2]);
}else{
scale = 0.85 / Math.max( dx / width, dy / height );
offset = [ (width - scale * x)/2 , (height - scale * y)/2];
// new projection
projection = projection
.scale(scale)
.translate(offset);
}
Надеюсь, это поможет.
Ответ 10
Для людей, которые хотят настроить вертикаль и горизонталь, вот решение:
var width = 300;
var height = 400;
var vis = d3.select("#vis").append("svg")
.attr("width", width).attr("height", height)
d3.json("nld.json", function(json) {
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 150;
var offset = [width/2, height/2];
var projection = d3.geo.mercator().scale(scale).center(center)
.translate(offset);
// create the path
var path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(json);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
// new projection
projection = d3.geo.mercator().center(center)
.scale(scale).translate(offset);
path = path.projection(projection);
// adjust projection
var bounds = path.bounds(json);
offset[0] = offset[0] + (width - bounds[1][0] - bounds[0][0]) / 2;
offset[1] = offset[1] + (height - bounds[1][1] - bounds[0][1]) / 2;
projection = d3.geo.mercator().center(center)
.scale(scale).translate(offset);
path = path.projection(projection);
// add a rectangle to see the bound of the svg
vis.append("rect").attr('width', width).attr('height', height)
.style('stroke', 'black').style('fill', 'none');
vis.selectAll("path").data(json.features).enter().append("path")
.attr("d", path)
.style("fill", "red")
.style("stroke-width", "1")
.style("stroke", "black")
});
Ответ 11
Как я сосредоточился на Топойзоне, где мне нужно было вытащить эту функцию:
var projection = d3.geo.albersUsa();
var path = d3.geo.path()
.projection(projection);
var tracts = topojson.feature(mapdata, mapdata.objects.tx_counties);
projection
.scale(1)
.translate([0, 0]);
var b = path.bounds(tracts),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
svg.append("path")
.datum(topojson.feature(mapdata, mapdata.objects.tx_counties))
.attr("d", path)
Ответ 12
Может кто-нибудь опубликовать полный пример для этого?