Skip to content
Documentation menu
docs / examples / clustered-points
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.