Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active Sep 26, 2018
Embed
What would you like to do?
Projected TopoJSON
license: gpl-3.0
height: 800

Cartographic boundaries are commonly represented using latitude and longitude, but sometimes it’s convenient to use projected coordinates instead. This eliminates the need to project the geometry from the sphere to the plane while rendering, improving performance.

Furthermore, simplifying shapes in projected coordinates produces higher quality results, since the importance of each point is measured in area on-screen rather than on the Earth’s surface. For example, D3’s Albers USA projection displays Alaska at 0.6x its true size; simplifying in spherical coordinates preserves almost twice as much detail as needed for Alaska, whereas simplifying in projected coordinates produces a smaller file with uniform detail.

To generate a shapefile in projected coordinates, such as California Albers, use ogr2ogr’s -t_srs argument:

ogr2ogr \
	-f 'ESRI Shapefile' \
	-t_srs 'EPSG:3310' \
	counties-projected.shp \
	counties.shp

Alternatively, you can use topojson --projection to apply one of D3’s many geographic projections without needing ogr2ogr.

To convert the shapefile to TopoJSON, while simplifying, translating and scaling the geometry to fit the display, use topojson’s --width and --height arguments:

topojson \
	--width 960 \
	--height 800 \
	--margin 20 \
	-s .25 \
	-o ca.json \
	-- counties=counties-projected.shp

(For more on generating TopoJSON and manipulating shapefiles, read my Let’s Make a Map tutorial, and see the US Atlas repo for some sample shapefiles.)

Lastly, to render the projected TopoJSON, create a d3.geoPath with a null projection. This indicates that input geometry is in screen coordinates and can be displayed as-is:

var path = d3.geoPath()
    .projection(null);

Boom!

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="800"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var canvas = d3.select("canvas").node(),
context = canvas.getContext("2d"),
path = d3.geoPath().projection(null).context(context);
d3.json("ca.json", function(error, ca) {
if (error) throw error;
context.beginPath();
path(topojson.feature(ca, ca.objects.counties));
context.fillStyle = "#ddd";
context.fill();
context.beginPath();
path(topojson.mesh(ca, ca.objects.counties, function(a, b) { return a !== b; }));
context.strokeStyle = "#999";
context.stroke();
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment