block by fil fbbccd40266014ebe0364da8975a7992

Proj4/WKT + D3 + d3-interpolate-path

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.

forked from armollica‘s block: Proj4/WKT + D3


There are three ways to transition the shapes from one projection to the other:

– Mike Bostock’s and Jason Davies’ projection transitions is probably the correct way to do it

– Noah Veltman’s flubber interpolates shapes beautifully, but not lines (graticules) yet

– Peter Beshai’s d3-interpolate-path works well with those graticules since version 2

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="https://d3js.org/topojson.v1.min.js"></script>
<script src="https://unpkg.com/proj4"></script>
<script src="https://unpkg.com/d3-interpolate-path"></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" }
];


d3.geoProjectionProj4 = function(_) {
  function degreesToRadians(degrees) { return degrees * Math.PI / 180; }
  function radiansToDegrees(radians) { return radians * 180 / Math.PI; }

  var wkt = _;
  var proj = proj4(wkt);
  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 p = d3.geoProjection(project);
  p.wkt = function(_) {
    if (_) {
      proj = proj4(wkt = _);
      return p;
    }
    return wkt;
  }
  return p;
}

var projection = d3.geoProjectionProj4(options[0].proj4);

var width = 960,
		height = 500;

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()
  .precision(0.1)
	.extent([[-12, 33],[35, 70]])

d3.json("europe.json", function(error, data) {
	if (error) throw error;

	var europe = topojson.feature(data, data.objects.europe);

	projection.fitExtent([[0,0], [width, height]], 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() {
		projection
      .wkt(options[this.selectedIndex].proj4)
      .fitExtent([[0,0], [width, height]], europe);

		borders
			.transition(d3.transition().duration(750))
				.attr("d", path);
		
		// Make the graticule paths transition nicely
		grid
			.transition(d3.transition().duration(750))
			.attrTween('d', function (d) {
    var previous = d3.select(this).attr('d');
    var current = path(d);
    return d3.interpolatePath(previous, current);
  });

	}

	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);
	}
});



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