Skip to content
Documentation menu
docs / examples / heatmap
Example

Heatmap

Density from your own points, weighted by a property, that hands over to individual circles as you zoom in. The weight, the radius and the fade are all expressions, so the same data reads at city scale and at street scale.

ts
import { createMap } from "@mapmap/maps";

const map = createMap({
  container: "map",
  apiKey: "snk_...",
  attributionPosition: "bottom-left",
  center: [-0.11, 51.51],
  zoom: 11,
});
await map.whenReady();

// Sample data: 3,000 reports bunched around a few hotspots, each with a
// severity from 0 to 5. Swap in your own points, or a URL to them.
const hotspots = [
  [-0.127, 51.507], [-0.075, 51.515], [-0.19, 51.495], [-0.02, 51.54], [-0.1, 51.46],
];
let seed = 11;
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const spread = () => rand() + rand() + rand() - 1.5;   // roughly bell-shaped
const reports: GeoJSON.FeatureCollection = {
  type: "FeatureCollection",
  features: Array.from({ length: 3000 }, (_, i) => {
    const [lng, lat] = hotspots[i % hotspots.length];
    return {
      type: "Feature",
      properties: { severity: Math.round(rand() * 5) },
      geometry: {
        type: "Point",
        coordinates: [lng + spread() * 0.04, lat + spread() * 0.025],
      },
    };
  }),
};

map.addPersistentSource("reports", { type: "geojson", data: reports });

map.addPersistentLayer({
  id: "reports-heat",
  type: "heatmap",
  source: "reports",
  maxzoom: 16,
  paint: {
    "heatmap-weight": ["interpolate", ["linear"], ["get", "severity"], 0, 0, 5, 1],
    "heatmap-intensity": ["interpolate", ["linear"], ["zoom"], 9, 0.6, 15, 2],
    "heatmap-radius": ["interpolate", ["linear"], ["zoom"], 9, 6, 15, 24],
    // Fade the heat out as the circles below fade in.
    "heatmap-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0.85, 16, 0],
  },
}, { slot: "middle" });

map.addPersistentLayer({
  id: "reports-points",
  type: "circle",
  source: "reports",
  minzoom: 13,
  paint: {
    "circle-radius": ["interpolate", ["linear"], ["get", "severity"], 0, 3, 5, 8],
    "circle-color": "#e63946",
    "circle-stroke-color": "#ffffff",
    "circle-stroke-width": 1,
    "circle-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0, 14, 1],
  },
}, { slot: "middle" });

Worth knowing

  • heatmap-weight is per point, heatmap-intensity is per zoom: weight says which points matter more, intensity keeps the picture from washing out as points spread apart.
  • The default heatmap-color ramp runs transparent, blue, cyan, lime, yellow, red. Override it with an interpolate on ["heatmap-density"] from 0 to 1, where the 0 stop must stay transparent or the whole map tints.