D3.js:为具有多个geoJSON文件的地图设置fitExtent或fitSize(或以编程方式居中和缩放)

时间:2019-06-09 16:28:46

标签: javascript d3.js d3.geo

我想绘制多个国家/地区,这些国家/地区的数据来自单独的topoJSON文件。这些国家/地区的地图应位于屏幕中央(居中),并设置适当的比例,以使地图填满整个窗口。

我已经尝试过的:

  • 要获取每个国家/地区带有path.bounds(d)的边界框,并分别在每个侧面(顶部,左侧,底部,右侧)使用最大值和最小值,并提供{{1}的均值中心}至path.centroid(d)。这没用。

  • 按照this解决方案中的建议应用projection.center()projection.fitExtent([[x0,y0],[x1,y1]],geojsonObject)。在这里,我无法按所述方式创建featureCollection,并无法使用它来创建地图。

projection.fitSize([width,height],geojsonObject)

我认为,安德鲁·里德(Andrew Reid)在this帖子中以<!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> fitSize提出的建议应该可以解决这个问题。但是我无法将其应用于我的问题。这也是我解决问题的首选方式。

或者,可以使用fitExtentcenter()解决此问题吗? 我如何确定必须向scale()提供哪些坐标以及向center()提供值? 非常感谢您的帮助,我尝试了几天,但没有成功。

1 个答案:

答案 0 :(得分:0)

projection.fitSizeprojection.fitExtent都使用一个geojson对象,而不是一个geojson对象数组。幸运的是,geojson的对象类型可以包含所需的子对象:featureCollection

我看到您正在使用topojson,topojson.feature返回了一个geojson对象。就您而言,每次使用topojson时,您都会返回一个功能集。

为了使用fitSize / fitExtent,我们需要创建一个包含所有单个要素的geojson对象,要素集合会执行此操作,其结构如下:

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循环(如上所示)创建功能集外,我保留了您的代码,并且没有循环通过附加每个路径,而是使用enter选择。最后,我更改了大小和位置,以使其更适合代码段视图。