Clustering data
Estimated reading time: 3 minutesWhen your GIS map is correctly configured and contains POI clusters, you can display them as is using MapLibre GL JS and add more behaviors.
💡 See Cluster datastyle component section in the manual for setup information.
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
color: #004b78;
}
#map-container {
height: 100%;
position: relative;
}
#map {
min-height: 500px;
height: 100%;
}
span.circle {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
border: solid #fff;
}
.maplibregl-popup-close-button {
padding: 5px 10px;
font-size: 1.5em;
}
.maplibregl-popup-content {
padding: 15px;
background: rgba(250, 250, 250, 0.9);
border-radius: 5px;
}
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip,
.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip {
border-top-color: rgba(250, 250, 250, 0.9);
}
.maplibregl-popup-anchor-top .maplibregl-popup-tip,
.maplibregl-popup-anchor-top-right .maplibregl-popup-tip,
.maplibregl-popup-anchor-top-left .maplibregl-popup-tip {
border-bottom-color: rgba(250, 250, 250, 0.9);
}
.measure {
display: flex;
flex-direction: row;
align-items: center;
}
</style>
</head>
<body>
<div id="map-container">
<div id="map"></div>
</div>
<script>
(async () => {
// Don't forget to replace <YOUR_GIS_ACCESS_TOKEN> by your own access token
const accessToken = "<YOUR_GIS_ACCESS_TOKEN>";
// Don't forget to replace <YOUR_GIS_MAP_ID> by your own map id
const mapId = "<YOUR_GIS_MAP_ID>";
const map = new maplibregl.Map({
container: "map",
style: `https://api.jawg.io/gis/maps/${mapId}/versions/latest/style?access-token=${accessToken}`,
zoom: 2,
center: [8.8, 10.9],
});
const useNavigationControl = new URLSearchParams(window.location.search).get("navigation-control");
if (useNavigationControl !== "false") {
map.addControl(new maplibregl.NavigationControl(), "top-right");
}
// This plugin is used for right to left languages
maplibregl.setRTLTextPlugin("https://unpkg.com/@mapbox/[email protected]/mapbox-gl-rtl-text.min.js");
const COLORS = ["#CEC7FF", "#A497FD", "#8F81EE", "#7867EB", "#6A5CD8", "#584DAE", "#241050"];
const buildHeader = ({ threshold, country, unit, value, parameter, location, city, source }) => {
let html = "";
if (country && city) html += `<h1>${city}, ${country}</h1>`;
if (country && !city) html += `<h1>${country}</h1>`;
if (location) html += `<h2>${location}</h2>`;
if (source) html += `<h3>Source: ${source}</h3>`;
return html;
};
const buildMeasures = ({ threshold, unit, value, parameter }) =>
`<div class="measure"><span class="circle" style="background-color: ${COLORS[threshold]}"></span>${parameter} : ${value} ${unit}</div>`;
const popupMove = new maplibregl.Popup({ closeButton: false, closeOnMove: true });
const popupStay = new maplibregl.Popup({ closeButton: true, closeOnMove: false });
let selectedPoi;
popupStay.on("close", () => (selectedPoi = undefined));
map.once("load", (e) => {
map.setLayoutProperty("aq-pois", "symbol-sort-key", ["to-number", ["get", "threshold"]]);
const onMouseEvent = (popup) => (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ["aq-pois"] });
if (features.length === 0) return popup.remove();
const header = buildHeader(features[0].properties);
let measures = features
.sort(({ properties: a }, { properties: b }) => b.threshold - a.threshold)
.map(({ properties }) => buildMeasures(properties));
const openPopup = () =>
popup
.setHTML(
`${header}
<hr>
<b>Measures</b>: ${measures.join("")}`,
)
.setLngLat(e.lngLat)
.addTo(map);
if (popup === popupStay && popup.isOpen()) {
setTimeout(openPopup, 50);
} else if (popup === popupStay || selectedPoi !== header) {
openPopup();
}
if (popup === popupStay) {
selectedPoi = header;
}
};
map.on("mousemove", "aq-pois", onMouseEvent(popupMove));
map.on("click", onMouseEvent(popupStay));
map.on("click", (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ["aq-clusters-circle"] });
if (features.length === 0) return;
map.flyTo({ center: features[0].geometry.coordinates, zoom: map.getZoom() + 2 });
});
map.on("mouseleave", "aq-pois", (e) => {
popupMove.remove();
});
["aq-clusters-circle", "aq-pois"].forEach((layer) => {
map.on("mouseenter", layer, () => (map.getCanvas().style.cursor = "pointer"));
map.on("mouseleave", layer, () => (map.getCanvas().style.cursor = "grab"));
});
});
})();
</script>
</body>
</html>