Leafleat with GeoJSON and TopoJSON
Transform Data into Interactive Maps
March 16, 2016 - Tommy Dräger
GeoJSON
"GeoJSON is a format for encoding a variety of geographic data structures. A GeoJSON object may represent a geometry, a feature, or a collection of features. GeoJSON supports the following geometry types: Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and GeometryCollection. Features in GeoJSON contain a geometry object and additional properties, and a feature collection represents a list of features."
so bassicly we can use this format to describe various kinds of geomatrical shapes with additional information.
TopoJSON
"TopoJSON is an extension of GeoJSON that encodes topology. Rather than representing geometries discretely, geometries in TopoJSON files are stitched together from shared line segments called arcs. Arcs are sequences of points, while line strings and polygons are defined as sequences of arcs. Each arc is defined only once, but can be referenced several times by different shapes, thus reducing redundancy and decreasing the file size. In addition, TopoJSON facilitates applications that use topology, such as topology-preserving shape simplification, automatic map coloring, and cartograms."
so TopoJSON is just an extension of GeoJSON for a specific task which uses ambiguous structures to decrease the size of data. Some advantages of topoJSON lies in its additonal features like the coloring and the shape-simplification.
Initialize Leaflet
Preparations
In order to get started with leaflet you need to include the leaflet.css at first
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
Next include leaflet javascript file
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js"></script>
After that you need to declare a unique div
<div id="map"></div>
Make sure to set a specific height and width via css
<style type="text/css">
html, body, #map {
height:512px;
width:512px;
}
#map {
background:#c0c0c0;
color:#CC6600
}
</style>
So far done things for preparation, lets go ahead and embedd leaflet.
Set Up Leaflet
Lets create a new empty map layer with no further JSONdata in it.
<script>
/**
* @func map: initialize the leaflet widget L*
* @param $div STRING elementname of the div where the widget shows up
* @param $settings JSON[] options to configure leaflet widget
* @result $map POINTER* reference to leaflet widget L*
**/
var map = L.map('map',{maxZoom:12,minZoom:3});
</script>
Use TopoJSON instead of GeoJSON
Unfortunatly Leaflet doesn't support TopoJSON nativly. We'll use an extension for leaflet to use TopoJSON, cause TopoJSON offers a greater variety of features which comes in very handy later. Additonally TopoJSON describes its file with a smaller amount of data.
For that matter we use this extension to convert topoJSON back to geoJSON and feed Leaflet with the converted data. Heres the extension to include:
<script src="http://d3js.org/topojson.v1.min.js"></script>
Once included we need to manually extend leaflet. With no further instructions insert this snippet to do so.
<script>
/*
* Extension for Leaflet to use TopoJSON
* Snippet by (c) 2013 Ryan Clark
* https://gist.github.com/rclark/5779673
*/
L.TopoJSON = L.GeoJSON.extend(
{
addData: function(jsonData)
{
if (jsonData.type === "Topology")
{
for (key in jsonData.objects)
{
geojson = topojson.feature( jsonData,
jsonData.objects[key]);
L.GeoJSON.prototype.addData.call(this, geojson);
}
}
else
{
L.GeoJSON.prototype.addData.call(this, jsonData);
}
}
});
</script>
The snippet basically creates a new class based on the Leaflet-GeoJSON class and overwrites its addData Method with the case of incoming Topology-data.
To use this new extension we need to load our TopoJSON-data into a new "topoLayer". You can initialize the topoLayer inside the map-method as an additional parameter.
<script>
/**
* @func map: initialize the leaflet widget L*
* @param $div STRING elementname of the div where the widget shows up
* @param $settings JSON[] options to configure leaflet widget
* @param $layer POINTER* topoLayer which holds the topodata
* @result $map POINTER* reference to leaflet widget L*
**/
var map = L.map('map',
{maxZoom:12,minZoom:3},
topoLayer = new L.TopoJSON());
</script>
The result so far:
Next up we're going to load the topoJSON data via AJAX into the topoLayer. So we first just import the JQueryLibrary.
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
Then we'll actually get the data and pass it over to the addTopoData-function. For this example we'll use the TopoData of all townships from Berlin.
https://github.com/berlinermorgenpost/Berlin-Geodaten/raw/master/berlin_postleitzahlen.topojson
first the addTopoData function:
/**
* @func addTopoData: fills topoLayer with data, add to 'map' and call a func for each shape
* @param $topoData topoJSON data
* @return $void
**/
function addTopoData(topoData)
{
// 1) fills the data inside the topoLayer
// 2) append the layer to the Leaflet-widget 'map'
// 3) calls a function called 'handleLayer' for each element
topoLayer.addData(topoData);
topoLayer.addTo(map);
topoLayer.eachLayer(handleLayer);
}
after you directly load and pass the data via ajax to the addTopoData-function
$.getJSON('data/berlin_postleitzahlen_topo.json').done(addTopoData);
Last you have to set the coordinates and a zoom to see something on the canvas
// set the Coordinates '52.5212' and '13.4102' and the Zoom level '10'map.setView([52.5212, 13.4102], 10);
The result should look like this
Colorize It
Next up we want to colorize the shapes of each township. Sometimes the color depends on a certain parameter like a scale of the average rental prices or the density of certain citizens inside the areas. For this exemple we'll create a colorscale in a range from #fee8c8 to #e34a33. Afterwards we'll let chromajs calculate and round up the random generated values to the most similar color inside the colorid.
To do so wel'll add some new Parameters to the L.map-method and don't forget to include the chromajs script.
<script src="//cdnjs.cloudflare.com/ajax/libs/chroma-js/0.5.9/chroma.min.js"></script>
<script>
/**
* @func map: initialize the leaflet widget L*
* @param $div STRING elementname of the div where the widget shows up
* @param $settings JSON[] options to configure leaflet widget
* @param $layer POINTER* topoLayer which holds the topodata
* @param $colscale POINTER* Pointer to the actual chromajs color scale
* @param $scale void defines the min-max-colorrange inside an array
* @param $domain void defines the min-max-inputrange inside an array
* @result $map POINTER* reference to leaflet widget
**/
var map = L.map('map',
{maxZoom:12,minZoom:3},
topoLayer = new L.TopoJSON()
colorScale = chroma
.scale(['#fee8c8', '#e34a33'])
.domain([0,1]));
</script>
Now its time to set up some specific styleparameters of the layer. We remind ourself to the point where we called a function named handleLayer() for each topo-element inside the addTopoData-function. Now its time to define this function.
We'll basicly generate a random value, convert it into a hexadecimal value and pass it over to fillColor of the specific element. Additionally we'll set some further attributes like opacity, linecolor, linethikness etc.
<script>function handleLayer(layer)
{
// generate a randomValue
// actually this should be
// a certain demographic
// property
var randomValue = Math.random(),
// convert it into a hex
// and pick the most similar
// color value out of the colorScale
fillColor = colorScale(randomValue).hex();
// set some self explanatory attributes
layer.setStyle
({
fillColor : fillColor,
fillOpacity: 0.65,
color:'#990000',
weight:0.25,
opacity:0.9
});
}
</script>
This is the result
Adding some Interactivity
One can actually expand the handleLayer function for some additional interactivity Features. We'll add the .on-method at the end of the function to react to user events like click, hover etc. Thus the both functions from mouseover: and mouseout: will trigger the functions enterLayer and leaveLayer. Furthermore the event will also pass the selfreference to the functions.
function handleLayer(layer)
{
// generate a randomValue
// actually this should be
// a certain demographic
// property
var randomValue = Math.random(),
// convert it into a hex
// and pick the most similar
// color value out of the colorScale
fillColor = colorScale(randomValue).hex();
// set some self explanatory attributes
layer.setStyle
({
fillColor : fillColor,
fillOpacity: 0.65,
color:'#990000',
weight:0.25,
opacity:0.9
});
layer.on
({
mouseover: enterLayer,
mouseout: leaveLayer
});
}
Finally we'll define enterLayer() and leaveLayer() to set some style to the interactive area.
function enterLayer()
{
this.bringToFront();
this.setStyle
({
fillOpacity: 0.65,
color:'#ffffff',
weight:2,
opacity:0.9
});
}
function leaveLayer()
{
this.bringToBack();
this.setStyle
({
fillOpacity: 0.65,
color:'#990000',
weight:0.25,
opacity:0.9
});
}
This is the final result