D3.js: Установите fitExtent или fitSize (или программно отцентрируйте и масштабируйте) для карты с несколькими файлами geoJSON - PullRequest
1 голос
/ 09 июня 2019

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

Что я уже пробовал:

  • Чтобы получить ограничивающую рамку с path.bounds(d) для каждой страны и использовать для каждой стороны (сверху, слева, снизу, справа) соответственно максимальное и минимальное значения и указать средний центр от path.centroid(d) до projection.center().Это не сработало.

  • Чтобы применить projection.fitExtent([[x0,y0],[x1,y1]],geojsonObject) или projection.fitSize([width,height],geojsonObject), как предлагается в этом решении.Здесь я не смог создать FeatureCollection, как описано, и использовать его для создания карты.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="https://d3js.org/d3.v5.min.js"></script>
        <script src="https://d3js.org/topojson.v2.min.js"></script>
        <style type="text/css">

    .svg-container {
        display: inline-block;
        position: relative;
        width: 100%;
        padding-bottom: 100%;
        vertical-align: top;
        overflow: hidden;
    }
    .svg-content {
        display: inline-block;
        position: absolute;
        margin: 0;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

        </style>
    </head>
    <body>
        <div id="container" class="svg-container"> </div>

        <script type="text/javascript">
                var width = 600; //for simplicity set to fixed value
                var height = 600; //for simplicity set to fixed value

                var projection = d3.geoMercator();

                var path = d3.geoPath()
                    .projection(projection);

                var svg = d3.select("#container").append("svg")
                      .classed("svg-content", true)
                      .attr("width", width)
                      .attr("height", height);

            //Example data
            var countries = ['germany', 'italy', 'switzerland', 'france']; 

            function loadJsonFiles() {

                  var files = ["https://api.myjson.com/bins/1b0ddz", 
                       "https://api.myjson.com/bins/11jkvb",
                       "https://api.myjson.com/bins/96x1j",
                       "https://api.myjson.com/bins/sspzr"];

                  var promises = [];
                  var allCountryData = [];

                  files.forEach(function(url) {
                          promises.push(d3.json(url))
                  });

                  return Promise.all(promises)
                      .then(function(countryData) {

                      for (var i = 0; i < countryData.length; i++) {

                        allCountryData.push(countryData[i]);

                      }
                    return allCountryData;
                  });
            }

            loadJsonFiles().then(function(allCountryData) {

                  var allBounds = [];
                  var objName; 
                  var countryData;

                  for (var i = 0; i < allCountryData.length; i++) {

                      objName = allCountryData[i].objects[countries[i]];
                      countryData = topojson.feature(allCountryData[i], objName);

                      //How can I use the right projection parameters for all country data loaded from topojson (outside the loop)?
                      projection
                          .scale(1000) //How to set this programmatically?
                          .center([8,47]) //How to set this programmatically?
                          .translate([width / 2, height / 2]);
                      //How can I append all the country data to the svg (outside the loop)?
                      svg.append("path")
                          .datum(countryData)
                          .attr("d", path);
                  }

            });

        </script>
    </body>
</html>   

Я думаю, что можно решить эту проблему с предложением Эндрю.Reid в этом сообщении с fitSize или fitExtent.Но я не смог применить это к моей проблеме.Это также будет моим предпочтительным способом решения моей проблемы.

В качестве альтернативы, можно ли решить эту проблему с center() и scale()?Как мне определить, какие координаты я должен предоставить center() и значение scale().?Большое спасибо за помощь, я пытался в течение нескольких дней, но не удалось.

1 Ответ

0 голосов
/ 09 июня 2019

projection.fitSize и projection.fitExtent оба принимают один объект геойсон, а не массив объектов геойсон. К счастью, у geojson есть тип объекта, который может содержать столько дочерних объектов, сколько нам нужно: a featureCollection.

Я вижу, что вы используете топойсон, topojson.feature возвращает объект геойсон. В вашем случае вы возвращаете коллекцию объектов каждый раз, когда используете топойсон.

Чтобы использовать fitSize / fitExtent, нам нужно создать объект геоджон, который содержит все отдельные объекты, для чего используется коллекция объектов, структура выглядит следующим образом:

featureCollection = {
  "type":"FeatureCollection",
  "features": [ ... ]
}

Мы могли бы создать коллекцию объектов с чем-то вроде:

var featureCollection = {type:"FeatureCollection","features":[]}

for (var i = 0; i < allCountryData.length; i++) {

     objName = allCountryData[i].objects[countries[i]];
     countryData = topojson.feature(allCountryData[i], objName);

     featureCollection.features.push(...countryData.features)

}

Теперь мы можем передать эту коллекцию объектов в fitSize / fitExtent с помощью

projection.fitSize([width,height],featureCollection);

Что должно выглядеть так:

                var width = 400; //for simplicity set to fixed value
                var height = 200; //for simplicity set to fixed value

                var projection = d3.geoMercator();

                var path = d3.geoPath()
                    .projection(projection);

                var svg = d3.select("#container").append("svg")
                      .classed("svg-content", true)
                      .attr("width", width)
                      .attr("height", height);

            //Example data
            var countries = ['germany', 'italy', 'switzerland', 'france']; 

            function loadJsonFiles() {

                  var files = ["https://api.myjson.com/bins/1b0ddz", 
                       "https://api.myjson.com/bins/11jkvb",
                       "https://api.myjson.com/bins/96x1j",
                       "https://api.myjson.com/bins/sspzr"];

                  var promises = [];
                  var allCountryData = [];

                  files.forEach(function(url) {
                          promises.push(d3.json(url))
                  });

                  return Promise.all(promises)
                      .then(function(countryData) {

                      for (var i = 0; i < countryData.length; i++) {

                        allCountryData.push(countryData[i]);

                      }
                    return allCountryData;
                  });
            }

            loadJsonFiles().then(function(allCountryData) {

                var allBounds = [];
                var objName; 
                var countryData;


				var featureCollection = {type:"FeatureCollection","features":[]}
				for (var i = 0; i < allCountryData.length; i++) {

					 objName = allCountryData[i].objects[countries[i]];
					 countryData = topojson.feature(allCountryData[i], objName);

					 featureCollection.features.push(...countryData.features)

				}
				
				projection.fitSize([400,200],featureCollection)
				
				svg.selectAll("path")
				  .data(featureCollection.features)
				  .enter()
				  .append("path")
				  .attr("d",path);

            });
svg {
   border: 1px solid black;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<div id="container" class="svg-container"> </div>

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

...