block by armollica e6371dcceed6d89312cf6a78be1a0b49

Proj4/WKT + D3

Full Screen

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

index.html

<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>

get-data.sh

#!/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