Or the GeoJSON, D3.js to Three.js pipeline.
It has been a while since our latest Three.js project! We’re mostly playing with Unity, but it’s always a nice feeling to get back to the web directly.
For this project, we’d to display a 3D map of the France with some sites informations points. The first thought concerned displaying a 3D map, we known we will need some svg at some point but then how to display it using Three.js? Also how could we put the sites/cities on the map correctly? Let see that!
We could easily find a svg for the France’s map, but it must be well optimized and we need to have the regions (so we can colorize them). Also having real data, than just some “paths”, with latitude, longitude was really important. So we started with the GeoJSON file format, and grabbed this version. Great, we have the data but now we need to visualize our map!
Obviously we started to have a look on Three.js plugin for displaying a GeoJSON map, but didn’t find one working correctly with a Mercator Projection. So we get back to our first intuition concerning svg. We know that Three.js has a SVGLoader, and then we can Extrude its geometry for having a 3D model, but how can we convert our GeoJSON file to a SVG map?
D3.js to the rescue! This is a powerful library for manipulating data, it includes SVG support and many map utilities! So from our GeoJSON file, we turn it at runtime into a SVG, that way we can tweak settings quickly!
const geojson = JSON.parse(geojsonText);
// we translate the projection at 0, 0, so the map is centered on our screen
const projection = geoMercator().scale(700).center(geoCentroid(geojson)).translate([0, 0]);
const pathGenerator = geoPath().projection(projection);
let mySvg = '<?xml version="1.0" encoding="utf-8"?><svg><g>';
for (let i = 0; i < geojson.features.length; i++)
mySvg += "<path id=\"" + (i + 1) + "\" name=\"" + geojson.features[i].properties.nom + "\" d=\"" + pathGenerator(geojson.features[i]) + "\"\/>";
mySvg += "</g></svg>";
// now load the SVG and display the map
const paths = new SVGLoader(manager).parse(mySvg).paths;
const group = new Group();
group.scale.y *= - 1;
let regionGeometry = null;
let regionMesh = null;
const regionTextMaterial = new MeshBasicMaterial({ color: new Color(0x666666) });
const shapeMaterial = new MeshBasicMaterial({ vertexColors: true, depthWrite: true });
for (let i = 0; i < paths.length; ++i)
{
const path = paths[i];
const region = data.regions.find(x => x.id == path.userData.node.id);
const color = new Color(region.color); // we grab some data from a static file
// draw region
const shapes = path.toShapes(true);
for (let j = 0; j < shapes.length; ++j)
{
const geometry = new ExtrudeGeometry(shapes[j], { depth: 0.001, bevelSize: 0 });
for (let k = 0; k < geometry.faces.length; ++k)
geometry.faces[k].vertexColors = [color, color, color];
if (regionGeometry == null)
{
regionGeometry = geometry
regionMesh = new Mesh(regionGeometry, shapeMaterial);
regionMesh.name = "regions"
}
else
regionMesh.geometry.merge(geometry);
}
}
group.add(regionMesh);
scene.add(group);
Et voilà! Did you notice the merge geometry function? Even if we’re are drawing several regions, they are considered as one mesh. This is an important optimization tips, you can read more about it on this awesome website.
Displaying sites and informations
We’d an Excel file from the client with all the sites and theirs informations (type, adress, phone, contact, logo etc.). Since we’ve a real map, and many sites, the quickest way to display to them wouldn’t be to position them via their GPS coordinate?
The french governement provides an API for doing some geocoding. We made a quick script in PHP for grabbing all addresses. We used the Guzzle library making HTTP request very easily!
require_once('guzzle/autoloader.php');
$client = new GuzzleHttp\Client(['verify' => false]);
$request = 'https://api-adresse.data.gouv.fr/search/?q='.$datas["sites"][$i]["adress"];
$response = $client->request('GET', $request);
$data = json_decode($response->getBody());
if (count($data->features) > 0)
{
$datas["sites"][$i]["lat"] = $data->features[0]->geometry->coordinates[1];
$datas["sites"][$i]["long"] = $data->features[0]->geometry->coordinates[0];
}
// https://stackoverflow.com/questions/6054033/pretty-printing-json-with-php
if (version_compare(phpversion(), '7.1', '>='))
ini_set( 'serialize_precision', -1 );
file_put_contents('../src/models/data.json', json_encode($datas, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));