Use topojson.js to get the interior mesh of the study area and use it to compute absolute discontinuities between the mean income per household of these areas.
Finally use Mapbox GL JS API to render a choropleth map and the graduated lines.
Last active
December 7, 2017 23:54
-
-
Save mthh/6acfed31d6004a544b9a90d3585f3a30 to your computer and use it in GitHub Desktop.
Choropleth map + graduated lines using Mapbox GL JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
border: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset='utf-8' /> | |
<title>Choropleth map using Mapbox GL JS</title> | |
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<script src="https://unpkg.com/topojson@3"></script> | |
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.41.0/mapbox-gl.js'></script> | |
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.41.0/mapbox-gl.css' rel='stylesheet' /> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
} | |
#cont { | |
width: 800px; | |
height: 600px; | |
margin: auto; | |
} | |
#map { | |
width: 100%; | |
height: 100%; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="cont"><div id='map'></div> | |
</div> | |
<script> | |
mapboxgl.accessToken = 'pk.eyJ1IjoibXRoaCIsImEiOiJjaXBkcmYyOHUwMDBndWVuZnU5YWdka2w2In0.fbZYSGMQgjc2B3_LdWvRbA'; | |
let map; | |
let polygon_features; | |
let discont; | |
const req = new XMLHttpRequest(); | |
req.open('GET', 'GrandParis.topojson', true); | |
req.onload = () => { prepare(JSON.parse(req.responseText)) }; | |
req.send(); | |
function prepare(data) { | |
polygon_features = topojson.feature(data, data.objects.GrandParis); | |
discont = makeDiscont(data, 'GrandParis', 'IncPerTH', 'abs'); | |
map = new mapboxgl.Map({ | |
container: 'map', // container id | |
style: 'mapbox://styles/mapbox/dark-v9', // stylesheet location | |
center:[2.39125, 48.835117], // [lng, lat] | |
zoom: 9 // starting zoom | |
}); | |
map.on('load', function() { | |
map.addLayer({ | |
id: 'communes', | |
type: 'fill', | |
'source': { | |
'type': 'geojson', | |
'data': polygon_features, | |
}, | |
paint: { | |
'fill-color': [ | |
'curve', | |
['step'], | |
['number', ['get', 'IncPerTH'], 1], | |
'#FFEDA0', 19733, '#FED976', 23229, '#FEB24C', 25782, '#FD8D3C', 29701, '#FC4E2A', 35055, '#E31A1C', 49221, '#BD0026', 97000, 'pink' | |
], | |
'fill-opacity': 0.95, | |
}, | |
layout: {} | |
}); | |
map.addLayer({ | |
id: 'discont', | |
type: 'line', | |
'source': { | |
'type': 'geojson', | |
'data': discont, | |
}, | |
paint: { | |
'line-color': '#7CFC00', | |
'line-width':[ | |
'curve', | |
['step'], | |
['number', ['get', 'disc_value'], 1], | |
0.5, 5000, 3, 15000, 6, 30000, 9, 69000, 0 | |
], | |
'line-opacity': 1, | |
}, | |
layout: { | |
'line-join': 'round', | |
'line-cap': 'round', | |
} | |
}); | |
}); | |
} | |
function makeDiscont(topo_to_use, layer, field, discontinuity_type='abs') { | |
const result_value = new Map(), | |
topo_mesh = topojson.mesh, | |
math_max = Math.max, | |
getId = (a, b) => ([ | |
[a.properties.LIBCOM, b.properties.LIBCOM].join('_'), | |
[b.properties.LIBCOM, a.properties.LIBCOM].join('_')]), | |
disc_func = discontinuity_type === 'rel' | |
? (val_a, val_b) => math_max(val_a / val_b, val_b / val_a) | |
: (val_a, val_b) => math_max(val_a - val_b, val_b - val_a); | |
topo_mesh(topo_to_use, topo_to_use.objects[layer], (a, b) => { | |
if (a !== b) { | |
let [new_id, new_id_rev] = getId(a, b), | |
val_a = a.properties[field], | |
val_b = b.properties[field]; | |
if (val_a == '' || val_a == null || isNaN(+val_a) || val_b == '' || val_b == null || isNaN(+val_b)) { | |
return; | |
} | |
if (!(result_value.get(new_id) || result_value.get(new_id_rev))) { | |
result_value.set(new_id, disc_func(+val_a, +val_b)); | |
} | |
} | |
return false; | |
}); | |
let entries = Array.from(result_value.entries()), | |
arr_disc = [], | |
d_res = [], | |
nb_ft; | |
for (let i = 0, n = entries.length; i < n; i++) { | |
let kv = entries[i]; | |
if (!isNaN(kv[1])) { | |
arr_disc.push(kv); | |
} | |
} | |
arr_disc.sort((a, b) => a[1] - b[1]); | |
nb_ft = arr_disc.length; | |
for (let i = 0; i < nb_ft; i++) { | |
let id_ft = arr_disc[i][0], | |
val = arr_disc[i][1], | |
datum = topo_mesh(topo_to_use, topo_to_use.objects[layer], (a, b) => { | |
let a_id = id_ft.split('_')[0], | |
b_id = id_ft.split('_')[1]; | |
const [ref_a_id, ref_b_id] = getId(a, b)[0].split('_'); | |
return a != b && ( | |
ref_a_id === a_id && ref_b_id === b_id | |
|| ref_a_id === b_id && ref_b_id === a_id); | |
}); | |
d_res.push([val, { | |
type: 'Feature', | |
geometry: datum, | |
properties: { id: id_ft, disc_value: val }, | |
}]); | |
} | |
d_res.sort((a, b) => b[0] - a[0]); | |
return { | |
type: 'FeatureCollection', | |
features: d_res.map(a => a[1]), | |
}; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment