# GeoJSON clustering > SuperCluster-backed point clustering for dl2.GeoJSON — cluster, pointToLayer, clusterToLayer, hideout, superClusterOptions. --- .. llms_copy::GeoJSON clustering .. toc:: ### Overview dash-leaflet 1.x lets `GeoJSON` collapse dense point sets into clusters that expand on zoom. dl2 now does the same — `dl2.GeoJSON(cluster=True, ...)` runs the [SuperCluster](https://github.com/mapbox/supercluster) index that dash-leaflet 1.x uses, and accepts the same customization hooks: | Prop | What it does | |-----------------------|--------------| | `cluster` | Turn clustering on/off. | | `superClusterOptions` | `{radius, minPoints, maxZoom, minZoom, extent}` — tuning passed to SuperCluster. | | `pointToLayer` | JS function source `(feature, latlng, ctx) => layer` for individual points. | | `clusterToLayer` | JS function source `(feature, latlng, index, ctx) => layer` for cluster bubbles. | | `hideout` | `dict` passed to your JS as `ctx.hideout` — color maps, label dicts, anything. | | `zoomToBoundsOnClick` | Click a cluster to fly the camera to fit its children. | The JS function source is wrapped in `new Function(...)` at construction time. `ctx` carries `{ hideout, leaflet, map }` so your function can build any Leaflet 2 layer without depending on a global. ### Live demo 200 random "vessel positions" around San Diego, CA, colored by category. Pan out and they collapse into glass bubbles; pan in and they expand. Click a cluster and you fly to its children's bounding box. .. exec::docs.geojson-cluster.example :code: false ### Source ```python # File: docs/geojson-cluster/example.py """ GeoJSON clustering — limited working example. 200 random vessel-position points around San Diego, CA. The hideout dict ships a {category: color} map into the JS pointToLayer so circles paint without a Python round-trip. Slider on the right tunes superClusterOptions.radius live. """ import json import random import dash_leaflet2 as dl2 import dash_mantine_components as dmc from dash import Input, Output, callback, html from dl2_tiles import OCEAN, register_theme_swap from dl2_locations import SAN_DIEGO from dl2_shared import code_panel, header, info_panel # Basemap pair for this page — dl2_tiles owns the light/dark wiring. TILES = OCEAN CATEGORIES = ["fishing", "sailing", "ferry", "cargo"] COLORS = { "fishing": "#4dabf7", "sailing": "#69db7c", "ferry": "#ffd43b", "cargo": "#ff8787", } def make_points(n=200, seed=42): rng = random.Random(seed) features = [] for i in range(n): # Scatter in kilometres, not degrees: a fixed degree jitter would # produce an east-west-stretched blob at low latitudes and a # squashed one up north. +/- 11 km N-S by +/- 13 km E-W. lat, lng = SAN_DIEGO.at( north_km=(rng.random() - 0.5) * 22.2, east_km=(rng.random() - 0.5) * 25.0, ) category = rng.choice(CATEGORIES) features.append( { "type": "Feature", "geometry": {"type": "Point", "coordinates": [lng, lat]}, "properties": { "id": i, "category": category, "name": f"{category.title()} #{i}", }, } ) return {"type": "FeatureCollection", "features": features} POINTS = make_points() # JS source — `new Function('return (' + source + ')')()` is called per prop. POINT_TO_LAYER = """ function (feature, latlng, ctx) { var color = (ctx.hideout && ctx.hideout.colors) ? ctx.hideout.colors[feature.properties.category] || '#868e96' : '#228be6'; return new ctx.leaflet.CircleMarker(latlng, { radius: 6, color: color, weight: 1.5, fillColor: color, fillOpacity: 0.85 }); } """ CLUSTER_TO_LAYER = """ function (feature, latlng, index, ctx) { var count = feature.properties.point_count; var leaves = index.getLeaves(feature.properties.cluster_id, Infinity); var counts = {}; for (var i = 0; i < leaves.length; i++) { var c = leaves[i].properties.category; counts[c] = (counts[c] || 0) + 1; } var top = Object.keys(counts).sort(function (a, b) { return counts[b] - counts[a]; })[0]; var color = (ctx.hideout && ctx.hideout.colors && ctx.hideout.colors[top]) || '#228be6'; var size = count >= 100 ? 56 : count >= 10 ? 44 : 36; var html = '