Original: Http://www.smartjava.org/content/render-geographic-information-3d-threejs-and-d3js
The last couple of th I ' ve been playing around with Three.js and geo information. I wanted to is able to render map/geo data (e.g. in Geojson format) inside the three.js scene. That's the 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, which uses a color to indicate the Popula tion density per municipality and the height of each municipality represents the actual number of residents.
Or If you can look at aworking example.
This information was based on open data available from the Dutch government. If you are on the source from the example, you can see the JSON we use for this. For more information on Geojson and what to parse it see the other articles I do on this subject:
- Using D3.js to visualize GIS
- Election site part 1:basics with Knockout.js, Bootstrap and D3.js
To get this working we'll take the following steps:
- Load the input GEO data
- Setup a three.js scene
- Convert the input data to a three.js path using d3.js
- Set the color and height of the Three.js object
- Render everything
Just a reminder to see everything working, Just look at theexample.
Load the input GEO data
The
D3.js have support to load JSON and directly transform it to an SVG path. Though This is a convenient to, I only needed the path data and not the complete SVG elements. So-to-load JSON I just used jquery ' s JSON support.
Get the Data Jquery.getjson (function(data, textstatus, jqxhr) {});
This would load the data and pass it in the data object to the supplied function.
Setup a three.js scene
Before we do anything with the data lets first setup a basic three.js scene.
Set up the three.js scene. The most basic setup withoutAny special stufffunction Initscene(){Set the scene sizevar WIDTH =The HEIGHT =600;Set some camera attributesvar view_angle =ASPECT = width/height, near =0.1, far =10000;Create a WebGL renderer, camera, and a scene renderer =New three. Webglrenderer ({AntiAlias:true}); Camera =New three. Perspectivecamera (View_angle, ASPECT, near, far); Scene =New three. Scene ();Add and position the camera at a fixed position scene.add (camera); Camera.position.z =550; Camera.position.x =0; CAMERA.POSITION.Y =550; Camera.lookat (scene.position);Start the renderer, and black background renderer.setsize (WIDTH, HEIGHT); Renderer.setclearcolor (0x000);Add the render target to the page $ ("#chart"). Append (renderer.domelement);Add a light at a specific positionvar pointlight =New three. Pointlight (0xFFFFFF); Scene.add (Pointlight); Pointlight.position.x =800; POINTLIGHT.POSITION.Y =800; pointlight.position.z = 800; //add a base plane on which we'll render our map var Planegeo =
new three. Planegeometry (
10000, 10000, 10, Span class= "Hljs-number" >10); var Planemat = new three. Meshlambertmaterial ({color: 0x666699}); var plane = new three. Mesh (Planegeo, Planemat); //rotate it to correct position plane.rotation.x =-math.pi/< Span class= "Hljs-number" >2; Scene.add (plane); }
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 are we need to do next are convert our Geojson input format to a three. Path, we can use the 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 M e 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 the SVG path string to a three.shape element. Unfortunately this method is ' t exposed, but that's quickly solved by adding the D3-threed.js file:
At the topvar transformsvgpathexposed;Within the d3threed (exports) functiontransformsvgpathexposed = Transformsvgpath; </javscript> This "we can call"This method separately. Now and we have a-transform an SVG path to a three.js shape and we only need to convert the Geojson to an SVG string and pass it toThis function. 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 is used like this:
var feature = Geo.path (geofeature);
To convert a Geojson element to an SVG path. So what does this look combined?
Add the loaded GIS object (in Geojson format) to the mapfunction Addgeoobject () {//keep track of rendered objects var meshes = []; .... //Convert to mesh and calculate values for (var i = 0; i < data.features.length; i++) {var geofeature = data.features[i] var feature = Geo.path ( Geofeature); //we only need to convert it to a three.js path var mesh = transfor msvgpathexposed (feature); //Add to array meshes.push (mesh); ...}
As you can see we iterate over the Data.features list (this contains all the Geojson representations of the municipalities ). Each municipality are converted to an SVG string, and each SVG string is converted to a mesh. This mesh is a Three.js object and 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:
Add the loaded GIS object (in Geojson format) to the map function Addgeoobject(){Keep track of rendered objectsvar meshes = [];var averagevalues = [];var totalvalues = [];Keep track of Min and Max, used to color the objectsvar maxvalueaverage =0;var minvalueaverage =-1;Keep track of Max and Min of total valuevar maxvaluetotal =0;var minvaluetotal =-1;Convert to mesh and calculate valuesfor (var i =0; i < data.features.length; i++) {var geofeature = Data.features[i]var feature = Geo.path (geofeature);We only need to convert it to a three.js pathvar mesh = transformsvgpathexposed (feature);Add to array meshes.push (mesh);We get a property from the JSON object and use itTo determine the color later onVarValue = parseint (geoFeature.properties.bev_dichth);if (Value > Maxvalueaverage) maxvalueaverage =Valueif (Value < Minvalueaverage | | Minvalueaverage = =-1) Minvalueaverage =Value Averagevalues.push (Value);And we get the max values to determine height later on.Value = parseint (GEOFEATURE.PROPERTIES.AANT_INW);if (Value > Maxvaluetotal) maxvaluetotal =Valueif (Value < Minvaluetotal | | Minvaluetotal = =-1) Minvaluetotal =Value Totalvalues.push (Value); }We ' ve got our paths now extrude them to a height and add a colorfor (var i =0; i < averagevalues.length; i++) {Create material color based on averagevar scale = ((Averagevalues[i]-minvalueaverage)/(maxvalueaverage-minvalueaverage)) *255;var mathcolor = gradient (Math.Round (scale),255);var material =New three. Meshlambertmaterial ({color:mathcolor});Create extrude based on totalvar extrude = ((Totalvalues[i]-minvaluetotal)/(maxvaluetotal-minvaluetotal)) *100;var Shape3d = Meshes[i].extrude ({amount:Math.round (extrude), bevelenabled:False});Create a mesh based on material and extruded shapevar toadd =New three. Mesh (shape3d, material);Rotate and position the elements nicely in the center toadd.rotation.x = math.pi/2; Toadd.translatex (-490); Toadd.translatez (50); Toadd.translatey (extrude/2);//add to Scene scene. add (Toadd);} } //simple gradient function Function gradient (length, Maxlength) {var i = (length * 255/maxlength); var r = i; var g = 255-(i); var B = 0; var RGB = B | (g << 8) | (R << 16); return RGB;}
A big piece of code, but isn't that complex. What are we do here are we keep track of both values for each municipality:the population density and the total population. These values is 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 we 2D three.js path to a 3D shape. The color is used to define a material. This shape and material are used to create the Mesh, the we add to the scene.
Render everything
All of the are left are to render everything. For this example we "interested in animations" or anything so we can make a "a" to the renderer:
Renderer. Render (scene, camera);
And the result is as a saw in the beginning. The following image shows a different example. This time we once again show the population density, and now the height of 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.
SVG stretching, the original bump can play like this