Search form

You are here

Render geographic information in 3D with Three.js and D3.js

By jos.dirksen on Wed, 07/04/2012 - 20:43

The last couple of days I've been playing around with three.js and geo information. I wanted to be able to render map/geo data (e.g. in geojson format) inside the three.js scene. That way I have another dimension I could use to show a specific metric instead of just using the color in a 2D map. In this article I'll show you how you can do this. The example we'll create shows a 3D map of the Netherlands, rendered in Three.js, that uses a color to indicate the population density per municipality and the height of each municipality represents the actual number of residents.

This information is based on open data available from the Dutch government. If you look at the source from the example, you can see the json we use for this. For more information on geojson and how to parse it see the other articles I did on this subject:

Load the input geo data

D3.js has support to load json and directly transform it to an SVG path. Though this is a convenient way, I only needed the path data, not the complete SVG elements. So to load json I just used jquery's json support.

Nothing to special, the comments inline should nicely explain what we're doing here. Next it gets more interesting.

Convert the input data to a Three.js path using d3.js

What we need to do next is convert our geojson input format to a THREE.Path that we can use in our scene. Three.js itself doesn't support geojson or SVG for that matter. Luckily though someone already started work on integrating d3.js with three.js. This project is called "d3-threeD" (sources can be found on github here). With this extension you can automagically render SVG elements in 3D directly from D3.js. Cool stuff, but it didn't allow me any control over how the elements were rendered. It does however contain a function we can use for our scenario. If you look through the source code of this project you'll find a method called "transformSVGPath". This method converts an SVG path string to a Three.Shape element. Unfortunately this method isn't exposed, but that's quickly solved by adding this to the d3-threeD.js file:

// at the topvar transformSVGPathExposed;
...
// within the d3threeD(exports) function
transformSVGPathExposed = transformSVGPath;</javscript>This way we can call this method separately. Now that we have a way to transform an SVG path to a Three.js shape, we only need to convert the geojson to an SVG string and pass it to thisfunction. We can use the geo functionaly from D3.jsforthis:<javascript>
geons.geoConfig=function(){this.TRANSLATE_0= appConstants.TRANSLATE_0;this.TRANSLATE_1= appConstants.TRANSLATE_1;this.SCALE= appConstants.SCALE;this.mercator= d3.geo.mercator();this.path= d3.geo.path().projection(this.mercator);this.setupGeo=function(){var translate =this.mercator.translate();
translate[0]=this.TRANSLATE_0;
translate[1]=this.TRANSLATE_1;this.mercator.translate(translate);this.mercator.scale(this.SCALE);}}

The path variable from the previous piece of code can now be used like this:

var feature = geo.path(geoFeature);

To convert a geojson element to an SVG path. So how does this look combined?

As you can see we iterate over the data.features list (this contains all the geojson representations of the municipalities). Each municipality is converted to an svg string, and each svg string is converted to a mesh. This mesh is a Three.js object that we can render on the scene.

Set the color and height of the Three.js object

Now we just need to set the height and the color of the Three.js shape and add it to the scene. The extended addGeoObject method now looks like this:

A big piece of code, but not that complex. What we do here is we keep track of two values for each municipality: the population density and the total population. These values are used to respectively calculate the color (using the gradient function) and the height. The height is used in the Three.js extrude function which converts our 2D Three.Js path to a 3D shape. The color is used to define a material. This shape and material is used to create the Mesh that we add to the scene.

Render everything

All that is left is to render everything. For this example we're not interested in animations or anything so we can make a single call to the renderer:

renderer.render( scene, camera );

And the result is as you saw in the beginning. The following image shows a different example. This time we once again show the population density, but now the height represents the land area of the municipality.

I'm currently creating a new set of geojson data, but this time for the whole of Europe. So in the next couple of weeks expect some articles using maps of Europe.

About me

My name is Jos Dirksen. I'm an independent consultant, currently working at ING. My focus areas are Scala, Java, HTML5, SOA, governance, ESBs, integration, REST and quality. I've also written a couple of books. Two for Manning and four for Packt. I frequently talk about my interests on conferences and workshops, and you can read my articles on this website.