Центрировать карту в 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]), не продвигаясь постепенно?


person climboid    schedule 24.01.2013    source источник
comment
См. Также D3.geo: кадрировать карту с учетом объекта geojson?   -  person Hugolpz    schedule 26.01.2015


Ответы (11)


Следующее, кажется, делает примерно то, что вы хотите. Масштабирование вроде в порядке. При применении его к моей карте есть небольшое смещение. Это небольшое смещение, вероятно, вызвано тем, что я использую команду translate для центрирования карты, тогда как мне, вероятно, следует использовать команду center.

  1. Создайте проекцию и d3.geo.path
  2. Вычислить границы текущей проекции
  3. Используйте эти границы для расчета масштаба и перевода
  4. Воссоздайте проекцию

В коде:

  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")
    });
person Jan van der Laan    schedule 01.02.2013
comment
Привет, Ян ван дер Лаан, я никогда не благодарил тебя за этот ответ. Кстати, это действительно хороший ответ, если бы я мог разделить награду, которую я бы сделал. Спасибо за это! - person climboid; 19.02.2013
comment
Если я применяю это, я получаю границы = бесконечность. Есть идеи, как это можно решить? - person Simke Nys; 10.02.2015
comment
@SimkeNys Это может быть та же проблема, что упомянута здесь stackoverflow.com/questions/23953366/ Попробуйте решение, упомянутое там. - person Jan van der Laan; 11.02.2015
comment
Привет, Ян, спасибо за код. Я пробовал ваш пример с некоторыми данными GeoJson, но это не сработало. Вы можете сказать мне, что я делаю не так? :) Я загрузил данные GeoJson: onedrive.com / - person user2644964; 29.05.2015
comment
В D3 v4 подгонка проекции является встроенным методом: projection.fitSize([width, height], geojson) (документация по API) - см. ответ @dnltsk ниже. - person Florian Ledermann; 04.05.2018

Мой ответ близок к ответу Яна ван дер Лаана, но вы можете немного упростить его, потому что вам не нужно вычислять географический центроид; вам нужна только ограничивающая рамка. И, используя немасштабированную, непереведенную единичную проекцию, вы можете упростить математику.

Важная часть кода такова:

// 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:

zoom  к ограничительной рамке

person mbostock    schedule 04.02.2013
comment
Хочу отметить, что в приведенном выше коде есть несколько ошибок, особенно в индексах границ. Должно получиться так: s = (0.95 / Math.max ((b [1] [0] - b [0] [0]) / width, (b [1] [1] - b [0] [0]] ) / высота)) * 500, t = [(ширина - s * (b [1] [0] + b [0] [0])) / 2, (высота - s * (b [1] [1]] + b [0] [1])) / 2]; - person iros; 21.03.2013
comment
@iros - Похоже, * 500 здесь лишний ... Кроме того, b[1][1] - b[0][0] должен быть b[1][1] - b[0][1] при расчете масштаба. - person nrabinowitz; 05.04.2013
comment
Похоже, в парсере уценки есть ошибка, которая мешает b[x][y]. Обозначение массива массивов [x][y] соответствует синтаксису для ссылок / изображений, вероятно, ответ Майка был правильным, но синтаксический анализатор испортил его, когда была добавлена ​​ссылка / изображение. - person Paulo Scardine; 12.06.2013
comment
Странно, похоже на ошибку с переполнением стека. Я исправил эти ошибки, но не могу гарантировать, что они больше не появятся ... - person mbostock; 12.06.2013
comment
Отчет об ошибке: meta. stackexchange.com/questions/184140/ - person Paulo Scardine; 12.06.2013
comment
Это очень помогло. Одно примечание: вы вычисляете ширину и высоту ограничивающей рамки в проекции объекта как север минус юг и восток минус запад соответственно. Это предполагает, что вы находитесь в американском секторе земного шара. Чтобы это работало для любой формы в любой точке мира, вам нужно взять абсолютное значение, в противном случае высота или ширина или и то, и другое могут быть отрицательными, что исключает расчет max (). - person Herb Caudill; 31.08.2013
comment
Именно из-за такого сообщества работать с D3 так приятно. Потрясающие! - person arunkjn; 08.07.2014
comment
@mbostock Комментарий Херба Кодилла - правильный ответ. Я протестировал ваш код на примере bl.ocks.org, и если вы скажете, что ваши границы - это карта Великобритании, и вы установили ширину намного меньше, чем высота, тогда высота по-прежнему работает правильно, но соотношение сторон не принимается во внимание. Вот что я имею в виду: i.imgur.com/UYEGSyY.png Однако с алгоритмом Херба : i.imgur.com/XMhP4Sf.png - person JARRRRG; 10.12.2014
comment
Лучшая поддержка когда-либо. - person superlukas; 27.03.2015
comment
Это лучший ответ, поскольку он позволяет использовать предварительно повернутую и / или центрированную проекцию, воздействуя только на translate () и scale (). - person Florian Ledermann; 29.07.2015
comment
@mbostock Возможно ли получить расчетные данные из d3 без его рисования? Мне просто понадобятся полные переведенные, масштабированные и вычисленные координаты (та, которая будет нарисована с использованием элемента пути svg). - person Manuel Rauber; 20.01.2016
comment
В каком состоянии? - person madhairsilence; 29.08.2016

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

Наслаждайтесь, ура

person dnltsk    schedule 02.12.2016
comment
Я надеюсь, что за этот ответ проголосуют больше. Некоторое время работал с d3v4 и только что открыл для себя этот метод. - person Mark; 05.02.2017
comment
Откуда g? Это контейнер SVG? - person Tschallacka; 29.06.2017
comment
Tschallacka g должен быть тегом <g></g> - person giankotarola; 06.08.2017
comment
Жаль, что это так далеко после 2 качественных ответов. Это легко пропустить, и это, очевидно, проще, чем другие ответы. - person Kurt; 28.11.2017
comment
Спасибо. Работает и в v5! - person Chaitanya Bangera; 15.10.2018
comment
На самом деле это не работает для v5. У меня была альтернативная версия v4.5, на которую ссылался код. : / - person Chaitanya Bangera; 15.10.2018
comment
Определенно лучший способ сделать это на данный момент. - person obvdso; 30.08.2019
comment
Я вернулся сюда в третий раз! огромное спасибо! Если бы я мог проголосовать за твой ответ еще раз. - person SERG; 26.04.2020
comment
Идеально! Рад, что прокрутил немного вниз, это самый простой ответ - person Miguel Stevens; 24.07.2021

Я новичок в d3 - попытаюсь объяснить, как я это понимаю, но не уверен, что все правильно понял.

Секрет в том, что некоторые методы будут работать с картографическим пространством (широта, долгота), а другие - с декартовым пространством (x, y на экране). Картографическое пространство (наша планета) (почти) сферическое, декартово пространство (экран) - плоское - чтобы сопоставить одно над другим, вам понадобится алгоритм, который называется projection. Это пространство слишком мало, чтобы углубляться в увлекательную тему проекций и то, как они искажают географические особенности, чтобы превратить сферическую в плоскость; одни предназначены для сохранения углов, другие - для расстояний и т. д. - всегда есть компромисс (У Майка Бостока есть огромная коллекция примеров).

введите описание изображения здесь

В d3 объект проекции имеет свойство / сеттер center, заданное в единицах карты:

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

введите описание изображения здесь

Используйте метод 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%.

person Paulo Scardine    schedule 12.06.2013
comment
Есть где-нибудь бл.окс? - person Hugolpz; 22.08.2013
comment
Извините, нет ничего плохого, что вы пытаетесь сделать? Это что-то вроде масштабирования при нажатии? Опубликуйте его, и я смогу взглянуть на ваш код. - person Paulo Scardine; 22.08.2013
comment
Ответ и изображения Bostock содержат ссылки на примеры bl.ocks.org, которые позволяют мне скопировать инженерный код целиком. Работа сделана. +1 и спасибо за отличные иллюстрации! - person Hugolpz; 01.09.2013

Существует метод center (), который принимает пара широта / долгота.

Насколько я понимаю, translate () используется только для буквального перемещения пикселей карты. Не знаю, как определить масштаб.

person Dave Foster    schedule 29.01.2013
comment
Если вы используете TopoJSON и хотите центрировать всю карту, вы можете запустить topojson с --bbox, чтобы включить атрибут bbox в объект JSON. Координаты широты и долготы для центра должны быть [(b [0] + b [2]) / 2, (b [1] + b [3]) / 2] (где b - значение bbox). - person Paulo Scardine; 11.06.2013

В дополнение к Центрировать карту в d3 с учетом geoJSON, обратите внимание, что вы можете предпочесть fitExtent() fitSize(), если хотите указать отступ вокруг границ вашего объекта. fitSize() автоматически устанавливает для этого отступа значение 0.

person Sylvain Lesage    schedule 01.04.2018

Я искал в Интернете простой способ центрировать карту и меня вдохновил ответ Яна ван дер Лаана и 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);

Если вы ищете точное масштабирование, этот ответ вам не подойдет. Но если вы, как и я, хотите отобразить карту, которая централизована в контейнере, этого должно быть достаточно. Я пытался отобразить карту меркатора и обнаружил, что этот метод был полезен для централизации моей карты, и я мог легко отрезать антарктическую часть, поскольку она мне не нужна.

person Wei Hao    schedule 10.06.2013

Чтобы панорамировать / масштабировать карту, вы должны посмотреть на наложение SVG на Leaflet. Это будет намного проще, чем преобразовать SVG. См. Этот пример http://bost.ocks.org/mike/leaflet/, а затем Как изменить центр карты в буклете

person Kristofor Carle    schedule 31.01.2013
comment
Если добавление другой зависимости вызывает беспокойство, PAN и ZOOM можно легко выполнить в чистом d3, см. Заголовок stackoverflow.com/questions/17093614/ - person Paulo Scardine; 15.06.2013
comment
Этот ответ не имеет отношения к d3. Вы также можете панорамировать / масштабировать карту в d3, листовка не нужна. (Только что понял, что это старый пост, просто просматривал ответы) - person JARRRRG; 10.12.2014

С ответом mbostocks и комментарием Херба Кодилла я начал сталкиваться с проблемами с Аляской, так как использовал проекцию меркатора. Должен заметить, что для своих целей я пытаюсь спроецировать и центрировать Штаты. Я обнаружил, что мне пришлось объединить два ответа с ответом Яна ван дер Лаана со следующим исключением для многоугольников, которые перекрывают полушария (многоугольники, которые имеют абсолютное значение для Востока и Запада больше 1):

  1. настроить простую проекцию в меркаторе:

    проекция = d3.geo.mercator (). scale (1) .translate ([0,0]);

  2. создать путь:

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

Надеюсь, это поможет.

person DimeZilla    schedule 03.11.2015

Для людей, которые хотят отрегулировать вертикальное и горизонтальное положение, вот решение:

  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")
    });
person pcmanprogrammeur    schedule 13.02.2016

Как я центрировал 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)
person Union find    schedule 31.03.2016