Use Proj4js to convert proj4 or WKT projection definition strings into D3 projections.
// Create Proj4js projection function
var proj4Projection = proj4(wkt); // WGS84 to projection defined in `wkt`
// Use this to create a D3 projection
var project = function(lambda, phi) {
return proj4Projection
.forward([lambda, phi].map(radiansToDegrees));
};
project.invert = function(x, y) {
return proj4Projection
.inverse([x, y]).map(degreesToRadians);
};
var projection = d3.geoProjection(project);
Geographic data from Eurostat’s GISCO program. The example projections came from Spatial Reference.
I had trouble interpolating the graticules. See Fil’s block for a solution to that: Proj4/WKT + D3 + d3-interpolate-path
<html>
<head>
<style>
html {
font-family: monospace;
}
svg {
cursor: crosshair;
}
#projection-menu {
position: absolute;
right: 10px;
top: 10px;
}
.europe {
fill: none;
stroke: #000;
}
.graticule {
fill: none;
stroke: #aaa;
stroke-width: 0.5px;
stroke-opacity: 0.5;
}
.coordinates {
text-anchor: middle;
fill: #000;
text-shadow: -1px 0px 0px #fff,
0px 1px 0px #fff,
1px 0px 0px #fff,
0px -1px 0px #fff;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<select id="projection-menu"></select>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="proj4.js"></script>
<script>
var options = [
{ name: "Pulkovo 1942(58) / Poland Zone 1", proj4: "+proj=sterea +lat_0=50.625 +lon_0=21.08333333333333 +k=0.9998 +x_0=4637000 +y_0=5647000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs" },
{ name: "Madrid 1870 (Madrid) / Spain", proj4: "+proj=lcc +lat_1=40 +lat_0=40 +lon_0=0 +k_0=0.9988085293 +x_0=600000 +y_0=600000 +a=6378298.3 +b=6356657.142669561 +pm=madrid +units=m +no_defs" },
{ name: "IRENET95 / Irish Transverse Mercator", proj4: "+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=0.99982 +x_0=600000 +y_0=750000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" },
{ name: "NGO 1948 Norway Zone 8", proj4: "+proj=tmerc +lat_0=58 +lon_0=29.05625 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +units=m +no_defs" },
{ name: "ED50 / France EuroLambert", proj4: "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666667 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +ellps=intl +units=m +no_defs" },
{ name: "ELD79 / Libya Zone 6", proj4: "+proj=tmerc +lat_0=0 +lon_0=11 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs" },
{ name: "WGS 84 / North Pole LAEA Europe", proj4: "+proj=laea +lat_0=90 +lon_0=10 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs" },
{ name: "Albanian 1987 / Gauss-Kruger Zone 4", proj4: "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=4500000 +y_0=0 +ellps=krass +units=m +no_defs" },
{ name: "Andrew Special", proj4: "+proj=lcc +lat_1=40 +lat_0=40 +lon_0=0 +k_0=0.9988085293 +x_0=600000 +y_0=600000 +a=7378298.3 +b=7356657 +axis=wnd +units=m +no_defs" }
];
var proj = proj4(options[0].proj4);
var project = function(lambda, phi) {
return proj.forward([lambda, phi].map(radiansToDegrees));
};
project.invert = function(x, y) {
return proj.inverse([x, y]).map(degreesToRadians);
};
var projection = d3.geoProjection(project);
var width = 960,
height = 600;
var coordinateFormat = d3.format(".2f");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geoPath()
.projection(projection);
var graticule = d3.geoGraticule()
.extent([[-12, 33],[35, 70]])
d3.json("europe.json", function(error, data) {
if (error) throw error;
var europe = topojson.feature(data, data.objects.europe);
fitProjection(europe);
var grid = svg.selectAll(".graticule").data(graticule.lines())
.enter().append("path")
.attr("class", "graticule")
.attr("d", path);
var borders = svg.append("path").datum(europe)
.attr("class", "europe")
.attr("d", path);
var coordinates = svg.append("text")
.attr("class", "coordinates")
.attr("dy", "-1.66em")
.classed("hidden", true);
var menu = d3.select("#projection-menu")
.on("change", change);
menu.selectAll("option")
.data(options)
.enter().append("option")
.text(function(d) { return d.name; });
function change() {
proj = proj4(options[this.selectedIndex].proj4);
fitProjection(europe);
borders
.transition(d3.transition().duration(750))
.attr("d", path);
// TODO: Make the graticule paths transition nicely
grid
.transition(d3.transition().duration(250))
.style("stroke-opacity", 0)
.transition()
.attr("d", path)
.transition(d3.transition().duration(250).delay(500))
.style("stroke-opacity", 0.5);
}
svg
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
function mousemove() {
var mouse = d3.mouse(this),
p = projection.invert(mouse),
text = "(" + coordinateFormat(p[1]) + "°, " +
coordinateFormat(p[0]) + "°)";
coordinates
.classed("hidden", false)
.attr("x", mouse[0])
.attr("y", mouse[1])
.text(text);
}
function mouseleave() {
coordinates.classed("hidden", true);
}
});
function degreesToRadians(degrees) { return degrees * Math.PI / 180; }
function radiansToDegrees(radians) { return radians * 180 / Math.PI; }
function fitProjection(geometry) {
// Fit geometry to screen
// see //bl.ocks.org/mbostock/4707858
projection
.scale(1)
.translate([0, 0]);
var b = path.bounds(geometry),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
}
</script>
</body>
</html>
#!/usr/bin/env bash
curl -o countries.zip http://ec.europa.eu/eurostat/cache/GISCO/geodatafiles/CNTR_2014_60M_SH.zip
unzip countries.zip
ogr2ogr \
-f "GeoJSON" \
-t_srs "EPSG:4326" \
-where "'EU_FLAG' = 'T'" \
-clipsrc -12 33 35 71 \
europe.geojson \
CNTR_2014_60M_SH/CNTR_2014_60M_SH/CNTR_2014_60M_SH/Data/CNTR_BN_60M_2014.shp
topojson -o europe.json europe.geojson