June 23, 2019

Mapping Swiss coordinates (LV95) with d3: Part 2

This is the second of a two-part series about creating a map in SVG based on Swiss geographical data. Each part contains a certain step of the process:

  1. Converting shapefiles to GeoJSON/TopoJSON with node
  2. Creating a bivariate choropleth map with d3-geo and LV95 coordinates

The result of that process you can see and interact with on srf.ch

At the end of part one we ended up with a TopoJSON file that was at a reasonable file size. We now want to display this on our web page.

Working with d3-geo

I won’t go into great detail of the basics of d3-geo. There are plenty of resources (here, here or here). Instead we’ll focus on how Swiss coordinates are different to work with.

The Swiss coordinate system has one key difference to others: it is already flat. What do I mean by that? While lat and long describe degrees on a globe, the Swiss coordinates X and Y describe a position on a normal plane:

cartesian

So with most coordinate systems you need a projection like geoMercator to map coordinates from a sphere to a flat screen. This is not needed if you work with Swiss coordinates.

To draw our municipalities onto our svg we can set up the following variables:

// this is the upper left and lower right corner of all of Switzerland
// you can copy these values from the end of the topojson file or anywhere else, really
const bbox = {
  x1: 2485432,
  x2: 2833839,
  y1: 1075269,
  y2: 1295934
}

const x = scaleLinear()
  .range([ 0, width ])
  .domain([ bbox.x1, bbox.x2 ])

const y = scaleLinear()
  .range([ 0, height ])
  // invert top and bottom
  .domain([ bbox.y2, bbox.y1 ])

const projection = geoTransform({
  point: function (px, py) {
    this.stream.point(x(px), y(py))
  }
})

const path = geoPath().projection(projection)

We really just set up two linear scales for positioning points left and right.

The most important part here is what happens inside of geoTransform. The function is a helper that let’s us construct our own projection function. (Another example by mbostock here).

In there we just return for every coordinate cx and cy the corresponding pixel position generated by our linearScales x and y (Thx to @LucGuillemot for showing me this).

With the feature function from topojson-client we convert our TopoJSON back into GeoJSON. The rest is really straight forward d3 code. We pass our path function to the attribute 'd' that will draw our polygons onto the svg:

// convert the TopoJSON back to GeoJSON
const geoJson = topojsonFeature(
  topoJson,
  topoJson.objects.municipalities
)

// and add one path per municipality to the svg
svg
  .selectAll('path')
  .data(geoJson.features)
  .enter()
  .append('path')
  .attr('class', 'feature')
  .style('fill', 'steelblue')
  .attr('d', path)

For brevity, I omitted some parts of the code. You can find the whole code in this github repository.

The color of each municipality is just steelblue at the moment but yay 😊 we have a map – and it uses LV95 coordinates!

one colored map

The bivariate color scale


Angelo Zehr

Written by Angelo Zehr, data journalist at SRF Data and teacher.


Further reading