I've read and used Mike Bostock's answer to Center a map in D3 given a geoJSON object, which is a generic way to scale to fit one item in a geojson file based on its bounds in D3 version 3. I've also adapted it to use every item in a GeoJson. Key part of that algorithm is:
// Calculate bounding box transforms for entire collection
var b = path.bounds( geojson ),
s = .95 / Math.max((b[1][0] - b[0][0]) / w, (b[1][1] - b[0][1]) / h),
t = [(w - s * (b[1][0] + b[0][0])) / 2, (h - s * (b[1][1] + b[0][1])) / 2];
// Update the projection, which initially had .translate([0, 0]) .scale(1);
projection
.scale(s)
.translate(t);
Using the V4 D3-geo docs I've crudely adapted it to work in with the new/ammended d3.geoPath()
function / object in D3 version 4.0. It seems to work in the demo below (I'm using d3.geoPath().bounds()
, not yet tried V4's seemingly new d3.geoBounds()
). However, I then get stuck with adapting it to get the bounding box across all layers in a multi-layer topojson file that has been converted using topojson.js (i.e. the bounding box of multiple GeoJSON feature collections). d3.geoPath().bounds()
seems to only accept one "layer" and d3.geoBounds()
appears even more restricted; to one feature.
I'm also somewhat concerned about performance - all this seems to involve looping over potentially very many shapes in potentially many layers, I feel like maybe there might be a more efficient approach in D3 V4?
Here's a rough demo as a starting point with a very small simple TopoJSON file that should show two islands with a few subregions each (very very simplified versions of Wales and Northern Ireland for a simple demo, each on separate layers). After some trial and error, I've managed to get it to scale and translate the map to centre around the "Wales-like" island, but I can't figure out how to make it centre and scale on all the layers of the TopoJSON.
The standard approach to layered TopoJSON seems to be to turn each layer into what's essentially a seperate GeoJSON, so how do I get the bounds across multiple GeoJSONs while still being able to handle them as separate layers?
//Width and height
var w = 300;
var h = 200;
//Define map projection
var projection = d3.geoEquirectangular()
.translate([0, 0])
.scale(1);
//Define path generator
var path = d3.geoPath()
.projection(projection);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Load in GeoJSON data
var json = someUKJSON();
for (var key in json.objects) {
if (json.objects.hasOwnProperty(key)) {
// Topojson unpacks one layer at a time
var layer = json.objects[key];
var geojson = topojson.feature( json, layer );
// Calculate bounding box transforms for entire collection
var b = path.bounds(geojson),
s = .95 / Math.max((b[1][0] - b[0][0]) / w, (b[1][1] - b[0][1]) / h),
t = [(w - s * (b[1][0] + b[0][0])) / 2, (h - s * (b[1][1] + b[0][1])) / 2];
// Update the projection
projection
.scale(s)
.translate(t);
// Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", "steelblue");
};
// ...but each iteration will just zoom/centre on the latest
// How do we expand the bounding box with each layer?
}
function someUKJSON(){
return {"type":"Topology","transform":{"scale":[0.0034431267161520807,0.002017902170346754],"translate":[-7.8508544159644345,51.47680014500252]},
"arcs":[[[1366,700],[-216,-155]],[[1150,545],[-121,275],[292,101],[45,-221]],[[1366,700],[23,-449]],[[1389,251],[-77,-96]],[[1312,155],[-75,-18]],[[1237,137],[-62,17]],[[1175,154],[-35,383]],[[1140,537],[10,8]],[[1175,154],[-71,-37]],[[1104,117],[-314,73],[350,347]],[[1237,137],[27,-119]],
[[1264,18],[-35,-18]],[[1229,0],[-125,117]],[[1312,155],[28,-118]],[[1340,37],[-76,-19]],[[1385,12],[-45,25]],[[1389,251],[-4,-239]],[[1385,12],[-156,-12]],[[563,1572],[16,-7]],[[579,1565],[-55,-14]],[[524,1551],[39,21]],[[397,1870],[166,-298]],[[524,1551],[-63,-5]],[[461,1546],[-96,-18]],
[[365,1528],[-109,10]],[[256,1538],[35,290]],[[291,1828],[106,42]],[[574,1342],[-124,192]],[[450,1534],[6,4],[5,8]],[[579,1565],[-5,-223]],[[365,1528],[85,6]],[[574,1342],[-219,-72],[-162,148]],[[193,1418],[63,120]],[[0,1515],[291,313]],[[193,1418],[-193,97]]],
"objects":{"Wales":{"type":"GeometryCollection","geometries":[{"arcs":[[0,1]],"type":"Polygon","id":"Bedr"},{"arcs":[[2,3,4,5,6,7,-1]],"type":"Polygon","id":"Pong"},{"arcs":[[8,9,-7]],"type":"Polygon","id":"Hyda"},{"arcs":[[-6,10,11,12,-9]],"type":"Polygon","id":"Abwg"},
{"arcs":[[13,14,-11,-5]],"type":"Polygon","id":"Cwaf"},{"arcs":[[15,-14,-4,16]],"type":"Polygon","id":"Anan"},{"arcs":[[17,-12,-15,-16]],"type":"Polygon","id":"Cave"}]},"nernIrel":{"type":"GeometryCollection","geometries":[{"arcs":[[18,19,20]],"type":"Polygon","id":"Blft"},
{"arcs":[[21,-21,22,23,24,25,26]],"type":"Polygon","id":"nern"},{"arcs":[[27,28,-23,-20,29]],"type":"Polygon","id":"hern"},{"arcs":[[30,-28,31,32,-25]],"type":"Polygon","id":"sorn"},{"arcs":[[33,-26,-33,34]],"type":"Polygon","id":"wern"}]}}};
};
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>