Skip to content

Instantly share code, notes, and snippets.

@mastersigat
Created January 21, 2021 17:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mastersigat/f76282a6a336ff954fc5b7a037b8da62 to your computer and use it in GitHub Desktop.
Save mastersigat/f76282a6a336ff954fc5b7a037b8da62 to your computer and use it in GitHub Desktop.
MapboxGL / Variation Cartographique
license: mit
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Create a heatmap layer</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src="https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; z-index: 1}
.map-overlay-2 {
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
position: absolute;
width: 400 px;
top: 50px;
left: 30px;
padding: 2px;
z-index: 2
}
.map-overlay-2 .map-overlay-inner {
background-color: #fff;
box-shadow:0 1px 2px rgba(0, 0, 0, 0.10);
border-radius: 3px;
padding: 10px;
margin-bottom: 10px;
}
.map-overlay-inner fieldset {
border: none;
padding: 0;
margin: 0 0 10px;
}
.map-overlay-inner .categoryLabel {
display: block;
font-weight: bold;
margin: 0 0 5px;
}
</style>
</head>
<body>
<div id='map'>
<div class='map-overlay-2 top'>
<div class='map-overlay-inner'>
<fieldset>
<label class="categoryLabel">Dotmap</label>
<div>
<input type="checkbox" id="classiqueCB" value="classique" onchange="switchlayer('classique')" />
<label for="classiqueCB">Classique</label>
</div>
<div>
<input type="checkbox" id="categoriseCB" value="categorise" onchange="switchlayer('categorise')" />
<label for="categoriseCB">Catégorisées</label>
</div>
<div>
<input type="checkbox" id="gradueeCB" value="graduee" onchange="switchlayer('graduee')" />
<label for="gradueeCB">Graduées</label>
</div>
<div>
<input type="checkbox" id="tailleCB" value="taille" onchange="switchlayer('taille')" />
<label for="tailleCB">Cercle gradués</label>
</div>
<div>
<input type="checkbox" id="combineCB" value="combine" onchange="switchlayer('combine')" />
<label for="combineCB">Cercle gradués et variation de couleur</label>
</div>
</fieldset>
<fieldset>
<label class="categoryLabel">Heatmap</label>
<div>
<input type="checkbox" id="heatmapCB" value="heatmap" onchange="switchlayer('heatmap')" />
<label for="heatmapCB">Classique</label>
</div>
<div>
<input type="checkbox" id="heatmap3CB" value="heatmap3" onchange="switchlayer('heatmap3')" />
<label for="heatmap3CB">Pondérée par le prix de vente</label>
</div>
<div>
<input type="checkbox" id="heatmap2CB" value="heatmap2" onchange="switchlayer('heatmap2')" />
<label for="heatmap2CB">Pondérée par le prix au m2</label>
</div>
</fieldset>
<fieldset>
<label class="categoryLabel">Cluster</label>
<div>
<input type="checkbox" id="clusterCB" value="cluster" onchange="switchlayer('cluster')" />
<label for="cluster">Clusters classiques</label>
</div>
<div>
<input type="checkbox" id="earthquake_labelCB" value="earthquake_label" onchange="switchlayer('earthquake_label')" />
<label for="earthquake_labelCB">Clusters thématiquees</label>
</div>
</fieldset>
</div>
</div>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoibWFzdGVyc2lnYXQiLCJhIjoiY2tpaXJsaTNsMmEyZjJ4cGU2dmoxMHBoeiJ9.hn9mX2nLGbnlFflMLfme8w';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mastersigat/cj1dmrd0700gp2rqmxdtjmjiw',
center: [-1.68, 48.11],
zoom: 10.3,
pitch : 20,
minZoom:10.3,
maxZoom:18
});
map.on('load', function() {
// LIMITES RM
map.addSource('limites', {
type: 'vector',
url: 'mapbox://mastersigat.52ukmnob'});
map.addLayer({
'id': 'communeslimites',
'type': 'line',
'source': 'limites',
'source-layer': 'limites-communales-referentie-14yj4j',
'layout': {'visibility': 'visible',
'line-join': 'round','line-cap': 'round'},
'paint': {'line-color': '#ffffff'},
'maxzoom':14
});
map.setPaintProperty('communeslimites', 'line-width', ["interpolate",["exponential", 1],["zoom"],11,0.5,14,0.7]);
// SOURCE DVF
map.addSource('DVF', {
type: 'vector',
url: 'mapbox://mastersigat.csfnbrl5'});
// DOTMAP Classique
map.addLayer({
'id': 'classique',
'type': 'circle',
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
'paint': {'circle-blur':0, 'circle-radius': {'base': 1.15,'stops': [[11, 1], [18, 5]]}, 'circle-color': '#3bb2d0'}
});
// DOTMAP CATEGORISEE
map.addLayer({
'id': 'categorise',
'type': 'circle',
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
'paint': {'circle-radius': {'base': 1.15, 'stops': [[11, 1.2], [18, 5]]},
'circle-color': ['match', ['get', 'type'],
'Maison', '#79d320', 'Appartement', '#fe4fc6', '#ccc']}
});
// DOTMAP GRADUEE COULEUR
map.addLayer({
'id': 'graduee',
'type': 'circle',
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
'paint': {'circle-radius': {'base': 1.15, 'stops': [[11, 1.2], [18, 5]]},
'circle-color': {property: 'Prixm2OK',type: 'exponential',
stops: [
[0, '#1a9641'],
[1800, '#a6d96a'],
[2180, '#ffffbf'],
[2520, '#fdae61'],
[3000, '#d7191c'],]
}}
});
// DOTMAP GRADUEE TAILLE
map.addLayer({
'id': 'taille',
'type': 'circle',
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
'paint': {'circle-color': '#ffd100',
'circle-radius': { property: 'PrixOK', stops: [ [{zoom: 11, value: 10000}, 0.2], [{zoom: 11, value: 1600000}, 3], [{zoom: 18, value: 10000}, 2], [{zoom: 18, value: 1600000}, 15] ]},
'circle-opacity': 0.8}
});
// DOTMAP GRADUEE TAILLE + COULEUR
map.addLayer({
'id': 'combine',
'type': 'circle',
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
'paint': {'circle-color': {property: 'Prixm2OK',type: 'exponential',
stops: [
[0, '#1a9850'],
[2000, '#66bd63'],
[2250, '#a6d96a'],
[2500, '#d9ef8b'],
[2750, '#fee08b'],
[3000, '#fdae61'],
[3250, '#f46d43'],
[3500, '#d73027'],]},
'circle-radius': { property: 'PrixOK', stops: [ [{zoom: 11, value: 10000}, 0.3], [{zoom: 11, value: 1600000}, 3.5], [{zoom: 18, value: 10000}, 2], [{zoom: 18, value: 1600000}, 15] ]},
'circle-opacity': 0.8}
});
// HEATMAP CLASSIQUE
map.addLayer({
"id": "heatmap",
"type": "heatmap",
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
"paint": {
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
"heatmap-intensity": [
"interpolate",
["linear"],
["zoom"],
10.3, 1,
16,2
],
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparancy color
// to create a blur-like effect.
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
0, "rgba(33,102,172,0)",
0.3, "rgb(103,169,207)",
0.5, "rgb(209,229,240)",
0.7, "rgb(253,219,199)",
0.9, "rgb(239,138,98)",
1, "rgb(178,24,43)"
],
// Adjust the heatmap radius by zoom level
"heatmap-radius": [
"interpolate",
["linear"],
["zoom"],
10.3, 3, 17,20
],
// Transition from heatmap to circle layer by zoom level
"heatmap-opacity": [
"interpolate",
["linear"],
["zoom"],
10.3, 1,
15, 0.7
],
}
}
);
// HEATMAP PONDERE
map.addLayer({
"id": "heatmap2",
"type": "heatmap",
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
"paint": {
'heatmap-weight': [
'interpolate',['linear'],['get', 'Prixm2OK'],1000,0, 2000,0.2, 3000,0.5,5000,0.7],
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
"heatmap-intensity": [
"interpolate",
["linear"],
["zoom"],
10.3, 1,
16,2
],
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparancy color
// to create a blur-like effect.
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
0, "rgba(33,102,172,0)",
0.3, "rgb(145,207,96)",
0.8, "rgb(217,239,139)",
0.9, "rgb(253,224,239)",
0.95, "rgb(233,163,201)",
1, "rgb(197,27,125)"
],
// Adjust the heatmap radius by zoom level
"heatmap-radius": [
"interpolate",
["linear"],
["zoom"],
10.3, 5, 17,20
],
// Transition from heatmap to circle layer by zoom level
"heatmap-opacity": [
"interpolate",
["linear"],
["zoom"],
10.3, 1,
15, 0.7
],
}
}
);
// HEATMAP PONDERE PRIX
map.addLayer({
"id": "heatmap3",
"type": "heatmap",
'source': 'DVF',
'source-layer': 'DVFRM-3fci8x',
'layout': {'visibility': 'none'},
"paint": {
'heatmap-weight': [
'interpolate',['linear'],['get', 'PrixOK'],100000,0,1600000,1],
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
"heatmap-intensity": [
"interpolate",
["linear"],
["zoom"],
10.3, 1,
16,2
],
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparancy color
// to create a blur-like effect.
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
0, "rgba(33,102,172,0)",
0.3, "rgb(145,207,96)",
0.5, "rgb(217,239,139)",
0.7, "rgb(254,224,139)",
0.9, "rgb(252,141,89)",
1, "rgb(215,48,39)"
],
// Adjust the heatmap radius by zoom level
"heatmap-radius": [
"interpolate",
["linear"],
["zoom"],
10.3, 5, 17,20
],
// Transition from heatmap to circle layer by zoom level
"heatmap-opacity": [
"interpolate",
["linear"],
["zoom"],
10.3, 1,
15, 0.7
],
}
}
);
// CLUSTER
map.addSource('DVFGIT', {
type: 'geojson',
data: 'https://raw.githubusercontent.com/mastersigat/data/main/DVFRM.geojson',
cluster: true,
clusterMaxZoom: 17,
clusterRadius: 40
});
map.addLayer({
'id': 'cluster',
'type': 'circle',
'source': 'DVFGIT',
'layout': {'visibility': 'none'},
filter: ['has', 'point_count'],
paint: {'circle-color': ['step',['get', 'point_count'],
'#51bbd6',50,
'#f1f075',500,
'#f28cb1'],
'circle-radius': ['step',['get', 'point_count'],5,50, 10,100,20,1000,30]}
});
// Configuration etiquette
map.addLayer({ id: 'cluster-count',
type: 'symbol',
source: 'DVFGIT',
filter: ['has', 'point_count'],
layout: {'visibility': 'none', 'text-field': '{point_count_abbreviated}', 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],'text-size': 12}
});
switchlayer = function (lname) {
if (document.getElementById(lname + "CB").checked) {
map.setLayoutProperty(lname, 'visibility', 'visible');
} else {
map.setLayoutProperty(lname, 'visibility', 'none');
}
}
});
// CLUSTER THEMATIQUE
var mag1 = ['match',['get', 'type'],
['Appartement'],
false,
true
];
var mag2 = [
'match',
['get', 'type'],
['Maison'],
false,
true
];
var colors = ['#79d320', '#fe4fc6'];
map.on('load', function () {
map.addSource('earthquakes', {
'type': 'geojson',
'data': 'https://raw.githubusercontent.com/mastersigat/data/main/DVFRM.geojson',
'cluster': true,
'clusterMaxZoom': 15,
'clusterRadius': 50,
'clusterProperties': {
// keep separate counts for each magnitude category in a cluster
'mag1': ['+', ['case', mag1, 1, 0]],
'mag2': ['+', ['case', mag2, 1, 0]]
}
});
map.addLayer({
'id': 'earthquake_label',
'type': 'symbol',
'source': 'earthquakes',
'filter': ['!=', 'cluster', true],
'layout': {'visibility': 'none', 'text-field': ['number-format',['get', 'mag'],{ 'min-fraction-digits': 0, 'max-fraction-digits': 0 }],
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],'text-size': 1},
'paint': {
'text-color': [
'case',
['<', ['get', 'mag'], 1],
'black',
'white'
]
}
});
var markers = {};
var markersOnScreen = {};
function updateMarkers() {
var newMarkers = {};
var features = map.querySourceFeatures('earthquakes');
for (var i = 0; i < features.length; i++) {
var coords = features[i].geometry.coordinates;
var props = features[i].properties;
if (!props.cluster) continue;
var id = props.cluster_id;
var marker = markers[id];
if (!marker) {
var el = createDonutChart(props);
marker = markers[id] = new mapboxgl.Marker({
element: el
}).setLngLat(coords);
}
newMarkers[id] = marker;
if (!markersOnScreen[id]) marker.addTo(map);
}
for (id in markersOnScreen) {
if (!newMarkers[id]) markersOnScreen[id].remove();
}
markersOnScreen = newMarkers;
}
map.on('data', function (e) {
if (e.sourceId !== 'earthquakes' || !e.isSourceLoaded) return;
map.on('move', updateMarkers);
map.on('moveend', updateMarkers);
updateMarkers();
});
switchlayer = function (lname) {
if (document.getElementById(lname + "CB").checked) {
map.setLayoutProperty(lname, 'visibility', 'visible');
} else {
map.setLayoutProperty(lname, 'visibility', 'none');
}
}
});
function createDonutChart(props) {
var offsets = [];
var counts = [
props.mag1,
props.mag2
];
var total = 0;
for (var i = 0; i < counts.length; i++) {
offsets.push(total);
total += counts[i];
}
var fontSize =
total >= 1000 ? 15 : total >= 100 ? 12 : total >= 6 ? 10 : 10;
var r = total >= 1000 ? 30 : total >= 100 ? 22 : total >= 10 ? 14 : 18;
var r0 = Math.round(r * 0.6);
var w = r * 2;
var html =
'<div><svg width="' +
w +
'" height="' +
w +
'" viewbox="0 0 ' +
w +
' ' +
w +
'" text-anchor="middle" style="font: ' +
fontSize +
'px sans-serif; display: block">';
for (i = 0; i < counts.length; i++) {
html += donutSegment(
offsets[i] / total,
(offsets[i] + counts[i]) / total,
r,
r0,
colors[i]
);
}
html +=
'<circle cx="' +
r +
'" cy="' +
r +
'" r="' +
r0 +
'" fill="white" /><text dominant-baseline="central" transform="translate(' +
r +
', ' +
r +
')">' +
total.toLocaleString() +
'</text></svg></div>';
var el = document.createElement('div');
el.innerHTML = html;
return el.firstChild;
}
function donutSegment(start, end, r, r0, color) {
if (end - start === 1) end -= 0.00001;
var a0 = 2 * Math.PI * (start - 0.25);
var a1 = 2 * Math.PI * (end - 0.25);
var x0 = Math.cos(a0),
y0 = Math.sin(a0);
var x1 = Math.cos(a1),
y1 = Math.sin(a1);
var largeArc = end - start > 0.5 ? 1 : 0;
return [
'<path d="M',
r + r0 * x0,
r + r0 * y0,
'L',
r + r * x0,
r + r * y0,
'A',
r,
r,
0,
largeArc,
1,
r + r * x1,
r + r * y1,
'L',
r + r0 * x1,
r + r0 * y1,
'A',
r0,
r0,
0,
largeArc,
0,
r + r0 * x0,
r + r0 * y0,
'" fill="' + color + '" />'
].join(' ');
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment