Reputation: 499
I am trying to render the world map with elevation data using D3.
For this I use Natural Earth 50m land geojson : https://github.com/martynafford/natural-earth-geojson/tree/master/50m/physical
And Natural Earth elevation raster data : https://www.naturalearthdata.com/downloads/50m-raster-data/50m-shaded-relief/
I am using this tutorial : https://datawanderings.com/2020/08/08/raster-backgrounds/
So I first found the bounds of the geojson :
ogrinfo ne_50m_land.json -so -al | grep Extent
Extent: (-180.000000, -89.998926) - (180.000000, 83.599609)
I then cut the TIF file :
gdal_translate -projwin -180.000000 83.599609 180.000000 -89.998926 SR_50M.tif SR_50M-box.tif
And project into Mercator projection :
gdalwarp -overwrite -s_srs "+proj=longlat +datum=WGS84 +no_defs" -t_srs EPSG:3395 SR_50M-box.tif SR_50M-proj.tif
Finally I export to PNG :
gdal_translate -of PNG SR_50M-proj.tif SR_50M.png
Then I render everything using D3
this.projection = d3.geoMercator()
.translate([0, 0])
.scale(1)
this.rasterImage = new Image();
this.rasterImage.src = raster;
this.path = d3.geoPath(this.projection, this.ctx);
this.bb = this.path.bounds(this.land);
const s = 1 / Math.max((this.bb[1][0] - this.bb[0][0]) / this.getWidth(), (this.bb[1][1] - this.bb[0][1]) / this.getHeight());
// transform
const t = [(this.getWidth() - s * (this.bb[1][0] + this.bb[0][0])) / 2, (this.getHeight() - s * (this.bb[1][1] + this.bb[0][1])) / 2];
// update projection
this.projection
.scale(s)
.translate(t);
this.raster_width = (this.bb[1][0] - this.bb[0][0]) * s;
this.raster_height = (this.bb[1][1] - this.bb[0][1]) * s;
this.rtranslate_x = (this.getWidth() - this.raster_width) / 2;
this.rtranslate_y = (this.getHeight() - this.raster_height) / 2;
this.ctx.beginPath();
this.path(this.land);
this.ctx.fill();
this.ctx.save();
this.ctx.globalAlpha = 0.8;
this.ctx.translate(this.rtranslate_x, this.rtranslate_y);
this.ctx.drawImage(this.rasterImage, 0, 0, this.raster_width, this.raster_height);
this.ctx.restore();
However at the end, the geojson land and the raster are not aligned :
I tried to first project and then cut, or to use Pseudo-Mercator or Mercator when projection, but nothing works.. Anyone have an idea ?
Upvotes: 4
Views: 466
Reputation: 38181
A Mercator is usually clipped at roughly 85 degrees North/South (~85.05113 N/S) - as further than this you get a map that is taller than it is wide, and one that gets much much taller for every extra degree north/south included in the extent..
D3 clips features using this limit:
The spherical Mercator projection. Defines a default projection.clipExtent such that the world is projected to a square, clipped to approximately ±85° latitude.
The northern bounds are fine, but the southern bounds of the geojson is -89.998926
degrees which you use to cut the image. But as D3 clips the geojson, your stretching the image by a different amount as compared with the geojson, hence the issue you see.
The solution should be to clip the image to a bounds that is representative of the limits of what D3 will render for a Mercator (85.05113
degrees south) not the limits of the data itself.
I haven't looked up how faithfully gdal implements EPSG:3395 as the definition provides for a projected bounds of 80 degrees south and 84 degrees north - though looking at the image, this doesn't appear to be an issue.
You can also use the cleaner fitSize methods for D3 projections (d3v4+):
projection.fitSize([width,height],geojsonObject)
Which will set scale and translate for you given a provided width/height.
Upvotes: 3