Documentation menu
Example
Clustered points with totals
Thousands of points collapse into clusters that carry a running total of a property, not just a count. DataLayer's cluster mode sums each drop's parcels into its cluster, so the cluster under the pointer reads 1,240 parcels rather than 38 points.
ts
import { createMap, DataLayer } from "@mapmap/maps";
const map = createMap({
container: "map",
apiKey: "snk_...",
attributionPosition: "bottom-left",
center: [-2.2, 53.2],
zoom: 5.4,
});
await map.whenReady();
// Sample data: 1,500 drops around six cities, each with a parcels count.
// Swap in your own FeatureCollection, or a URL to one.
const hubs = [
[-0.1278, 51.5074], [-1.8904, 52.4862], [-2.2426, 53.4808],
[-1.5491, 53.8008], [-2.5879, 51.4545], [-4.2518, 55.8642],
];
let seed = 7;
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const drops: GeoJSON.FeatureCollection = {
type: "FeatureCollection",
features: Array.from({ length: 1500 }, (_, i) => {
const [lng, lat] = hubs[i % hubs.length];
return {
type: "Feature",
properties: { parcels: 1 + Math.floor(rand() * 40) },
geometry: {
type: "Point",
coordinates: [lng + (rand() - 0.5) * 0.6, lat + (rand() - 0.5) * 0.4],
},
};
}),
};
// Clusters, their counts and the single points, at slot "middle". A click
// on a cluster zooms to where it splits.
const layer = new DataLayer(map, {
data: drops,
mode: "cluster",
cluster: {
radius: 50,
maxZoom: 14,
// Each cluster gets a parcels sum beside MapLibre's own point_count.
properties: { parcels: ["+", ["get", "parcels"]] },
},
});
// Read the total off the cluster under the pointer.
const readout = document.querySelector("#readout")!;
layer.on("hover", ({ feature }) => {
const p = feature?.properties;
readout.textContent = !p
? ""
: p.point_count
? p.parcels.toLocaleString("en-GB") + " parcels in " + p.point_count + " drops"
: p.parcels + " parcels";
});Worth knowing
- cluster.properties is MapLibre's clusterProperties: [operator, map expression]. The map expression runs on each point and the operator folds the results together. ["max", ["get", "parcels"]] gives the biggest drop in each cluster instead of the sum.
- A cluster only carries point_count, cluster_id and the properties you aggregate. Anything else on the points is not on the cluster, so read and style clusters on what you summed.
- The source and its layers are persistent: map.setStyle("dark") keeps them, at the same slot. layer.remove() takes them off again.