В настоящее время в 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]), не продвигаясь постепенно?
Ответы:
Следующее, кажется, делает примерно то, что вы хотите. Масштабирование вроде в порядке. При применении его к моей карте есть небольшое смещение. Это небольшое смещение, вероятно, вызвано тем, что я использую команду translate для центрирования карты, тогда как мне, вероятно, следует использовать команду center.
В коде:
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") });
источник
projection.fitSize([width, height], geojson)
( Документы API ) - см. Ответ @dnltsk ниже.Мой ответ близок к ответу Яна ван дер Лаана, но вы можете немного упростить его, потому что вам не нужно вычислять географический центроид; вам нужна только ограничивающая рамка. И, используя немасштабированную, непереведенную единичную проекцию, вы можете упростить математику.
Важная часть кода такова:
// 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
). В этом случае я также масштабировал ограничивающую рамку до 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 :
Возникает связанный с этим вопрос: как увеличить или уменьшить масштаб до определенной функции в коллекции, не регулируя проекцию, т. Е. Комбинируя проекцию с геометрическим преобразованием для увеличения и уменьшения масштаба. Здесь используются те же принципы, что и выше, но математика немного отличается, потому что геометрическое преобразование (атрибут SVG «преобразование») сочетается с географической проекцией.
Например, bl.ocks.org/4699541 :
источник
* 500
здесь посторонний ... тожеb[1][1] - b[0][0]
должен бытьb[1][1] - b[0][1]
в расчете шкалы.b.s = b[0][1]; b.n = b[1][1]; b.w = b[0][0]; b.e = b[1][0]; b.height = Math.abs(b.n - b.s); b.width = Math.abs(b.e - b.w); s = .9 / Math.max(b.width / width, (b.height / height));
С d3 v4 или v5 это становится проще!
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");
Наслаждайтесь, ура
источник
d3v4
и только что открыл для себя этот метод.g
взялось? Это контейнер SVG?g
должна быть<g></g>
биркаЯ новичок в d3 - попробую объяснить, как я это понимаю, но не уверен, что все правильно понял.
Секрет в том, что некоторые методы работают с картографическим пространством (широта, долгота), а другие - с декартовым пространством (x, y на экране). Картографическое пространство (наша планета) (почти) сферическое, декартово пространство (экран) - плоское - чтобы наложить одно на другое, вам понадобится алгоритм, который называется проекцией . Это пространство слишком мало, чтобы углубляться в увлекательную тему проекций и то, как они искажают географические особенности, чтобы превратить сферическую в плоскость; одни предназначены для сохранения углов, другие - для сохранения расстояний и так далее - всегда есть компромисс (у Майка Бостока есть огромная коллекция примеров ).
В d3 объект проекции имеет свойство / сеттер center, заданное в единицах карты:
Также есть перевод в пикселях, где центр проекции стоит относительно холста:
Когда я хочу центрировать объект на холсте, мне нравится устанавливать центр проекции в центр ограничивающего прямоугольника объекта - это работает для меня при использовании меркатора. (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);
Используйте этот
d3.geo.bounds
метод, чтобы найти ограничивающую рамку в единицах карты:Установите центр проекции в центр ограничивающей рамки:
projection.center([(b[1][0]+b[0][0])/2, (b[1][1]+b[0][1])/2]);
Используйте этот
translate
метод, чтобы переместить центр карты в центр холста:projection.translate([width/2, height/2]);
К настоящему времени у вас должен быть объект в центре карты, увеличенный с запасом 5%.
источник
Есть центр () метод , который вы можете использовать , который принимает широчайшие пару / долготы.
Насколько я понимаю, translate () используется только для буквального перемещения пикселей карты. Не знаю, как определить масштаб.
источник
Помимо Центр карта в d3 данного объекта GeoJSON , обратите внимание , что вы можете предпочесть
fitExtent()
более ,fitSize()
если вы хотите задать отступы вокруг границ объекта.fitSize()
автоматически устанавливает для этого отступа значение 0.источник
Я искал в Интернете простой способ центрировать свою карту и меня вдохновил ответ Яна ван дер Лаана и mbostock. Вот более простой способ использования jQuery, если вы используете контейнер для svg. Я создал границу 95% для отступов / границ и т. Д.
var width = $("#container").width() * 0.95, height = $("#container").width() * 0.95 / 1.9 //using height() doesn't work since there's 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);
Если вы ищете точное масштабирование, этот ответ вам не подойдет. Но если вы, как и я, хотите отобразить карту, которая централизована в контейнере, этого должно быть достаточно. Я пытался отобразить карту меркатора и обнаружил, что этот метод был полезен для централизации моей карты, и я мог легко отрезать антарктическую часть, поскольку она мне не нужна.
источник
Чтобы панорамировать / масштабировать карту, вы должны посмотреть на наложение SVG на Leaflet. Это будет намного проще, чем преобразовать SVG. См. Этот пример http://bost.ocks.org/mike/leaflet/, а затем Как изменить центр карты в буклете
источник
С ответом mbostocks и комментарием Херба Кодилла я начал сталкиваться с проблемами с Аляской, так как использовал проекцию меркатора. Должен отметить, что для своих целей я пытаюсь спроецировать и центрировать штаты США. Я обнаружил, что мне пришлось объединить два ответа с ответом Яна ван дер Лаана со следующим исключением для многоугольников, которые перекрывают полушария (многоугольники, которые имеют абсолютное значение для Востока и Запада больше 1):
настроить простую проекцию в меркаторе:
проекция = d3.geo.mercator (). scale (1) .translate ([0,0]);
создать путь:
path = d3.geo.path (). projection (проекция);
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. Добавьте исключение для Аляски и укажите, что полушария перекрывают друг друга:
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); }
Надеюсь, это поможет.
источник
Для людей, которые хотят отрегулировать вертикальное и горизонтальное положение, вот решение:
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") });
источник
Как я центрировал Topojson, где мне нужно было вытащить функцию:
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)
источник