Geospatial Data in D3
An introduction d3-geo
and geoPath
-
These notes summarize methods for static visualization of geospatial data in D3. Before we can make any plots, we need to be able to read in data. To read in vector data, it’s most convenient to store the data as geojson and read it in using
d3.json()
. The javascript object created by this function includes afeatures
array. Elementi
in this array gives properties of featurei
in the vector dataset. -
For example, we can use this to read in and visualize the glaciers data.
d3.json("https://raw.githubusercontent.com/krisrs1128/stat679_code/main/examples/week7/week7-3/glaciers.geojson") .then(visualize) // we define the visualize function
which shows an object like this in the console,
-
Even though these look like basic javascript objects,
d3.json
is actually keeping track geographic metadata behind the scenes. This allows us to do some basic geographic queries directly from javascript. For example, if we want to query the geographic centroid, bounds, or areas of each feature, we can use the calls below,let centroids = data.features.map(d3.geoCentroid), bounds = data.features.map(d3.geoBounds), areas = data.features.map(d3.geoArea); console.log(centroids, bounds, areas)
You can view the console output at this link. A similar query for the Brasilia roads dataset prints output like this. A variety of related processing functions can be found in the
d3-geo
library. -
How can we display these data? We need a way of translating the abstract data into SVGs on the screen. For vector data, we can use D3’s
geoPath
generators. These work like line generators — they are initialized with visual encoding functions and can then be applied to any new collection of vector features. -
For example, we can use a path generator to draw the glacier boundaries,
To draw the glacier boundaries, we defined the geographic path generator. The
proj
object serves a role similar to thex
andy
scales used ind3.line()
generators – it is mapping abstract geographical coordinates to the pixel extent of the screen. Specifically,.fitSize
takes the spatial coordinates indata
and relates it to the size of the screen,[width, height]
. For geographic data, this process is formally called aprojection
, because we have to associate the surface of the 3D globe to a 2D plane.let proj = d3.geoMercator() .fitSize([width, height], data) let path = d3.geoPath() .projection(proj);
-
Given this
d3.geoPath()
generator, we can append one polygon per glacier in the dataset using the usual D3 data bind. Note that we binddata.features
rather than justdata
. In geojson objects, the.features
attribute contains an array with the spatial features coordinates.d3.select("#map") .selectAll("path") .data(data.features).enter() .append("path") .attrs({ d: path, fill: d => scales.fill(d.properties.Thickness) })
In the block above, we’ve also passed a color scale to encode glacier depth with color. In general, these geospatial data are treated just like any other SVG
path
element, and we can set set their attributes using the usual.attrs
command. This is also how we achieved the mouseover effect – we simply added a.on("mouseover", ...)
listener for whenever we hover over one of these paths. -
We haven’t considered raster data in this lecture. This is because javascript doesn’t have a simple built-in way to handle raster data. If we want to visualize a raster dataset, we need to first convert them to simple PNG images. This loses the geographic metadata, and so any geographic processing has to be done before this step. In practice, it’s common to either manually convert to a PNG or to use a tiling library, which automatically converts raster data into a collection of PNGs. Both of these techniques are beyond the scope of our class, but if you are curious, you can check out these resources [1, 2, 3].