Reputation: 13
I'm struggling to align a topojson of Canada and a PNG DEM.
I've done a lot of hunting and cobbled together a few tests based largely on https://gist.github.com/ThomasThoren/5e676d0de41bac8a0e96. My understanding is that the PNG must be preprojected and is simply scaled + translated to the appropriate spot on the page, while the topojson is either fed in as lat/long and reprojected with d3 tools or preprojected and drawn using d3.geoIdentity (as per https://medium.com/p/e9ccc6c0bba3/responses/show). I just cannot get the two datasets aligned despite knowing they are aligned in projected coordinate space (at least when preprojecting the topojson and using d3.geoIdentity), which leads me to believe I'm missing something in the d3 code. Any ideas? I have no problem manipulating the topojson or source raster in other tools if that's the solution, but I have yet to find a recipe that works for this alignment. Here is an example where the topojson is preprojected. In any case, whether I preproject or project in d3 using geoConicConformal, I get the same result.
Here's the PNG file:
And here's my TopoJSON file.
This is the code I'm using to display the topojson and png:
<html>
<head>
<meta charset="utf-8">
<title>D3 Canada</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>
<script src="test-canada-topojson-canlam.js"></script>
<style>
body {
padding: 0;
margin: 0;
background: whitesmoke;
display: flex;
flex-direction: column;
align-items: center;
}
.province {
fill: white;
fill-opacity: 10%;
transition: fill 0.25s ease-in-out;
}
.province:hover {
fill: aliceblue;
fill-opacity: 90%;
transition: fill 0.25s ease-in-out;
}
</style>
</head>
<body>
<div class="home"></div>
<script>
var width = window.innerWidth,
height = window.innerHeight;
const container = d3.select(".home");
var svg = container.append("svg");
svg.attr("width", width)
.attr("height", height);
var provinces = topojson.feature(canada,canada.objects.canada).features;
var projection = d3.geoIdentity().reflectY(true).fitSize([width,height],topojson.feature(canada, canada.objects.canada));
var path = d3.geoPath(projection);
var bounds = path.bounds(topojson.feature(canada, canada.objects.canada));
var scale = 1 / Math.max((bounds[1][0] - bounds[0][0]) / width,(bounds[1][1] - bounds[0][1]) / height);
var raster_width = (bounds[1][0] - bounds[0][0]) * scale;
var raster_height = (bounds[1][1] - bounds[0][1]) * scale;
var rtranslate_x = (width - raster_width) / 2;
var rtranslate_y = (height - raster_height) / 2;
const g = svg.append("g");
g.append("image")
.attr("xlink:href", "canada-dem-conic.png")
.attr("class", "raster")
.attr("width", raster_width)
.attr("height", raster_height)
.attr("transform",
"translate(" + rtranslate_x + ", " + rtranslate_y + ")");
g.append("g")
.selectAll("path")
.data(provinces)
.enter()
.append("path")
.attr("d", path)
.attr("stroke", "lightslategrey")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin","round")
.attr("class", "province")
</script>
</body>
</html>
Upvotes: 1
Views: 332
Reputation: 38181
I'll admit this was a bit more frustrating than I would have liked. Normally I would have pointed to an answer such as this. Unfortunately, that essentially gives you the projection you already used:
var projection = d3.geoConicConformal()
.parallels([50,70])
.rotate([96,0])
.fitSize([width,height],geojson)
The issue lies elsewhere.
It appears that your raster's pixels are not square:
Well they certainly are rendered square, but they represent an area 5.7 km by 7.1 km.
Spacing or cell size is the fixed distance in the x and y dimensions between each pixel in the raster. Some formats store only one spacing value, meaning that it must be the same for both the x and y dimensions – this is often referred to as square cells. source
But D3 treats the pixels as square. If we stretch your image with the following:
var height = width / pixelWidth * pixelHeight
We should get pretty good alignment:
At least this looks like it is supposed to align.
Here's the simplest example of that implementation.
Of course you could squish the projected geojson coordinates instead of stretching the image; however, this is more complex (in retaining the fitSize method) than simply scaling the parent container.
Upvotes: 1