lgtm
Some checks failed
Build and publish Docker image / build-and-push (push) Successful in 18m12s
CI / Check (push) Failing after 23m38s

This commit is contained in:
Andras Schmelczer 2026-06-22 22:12:27 +01:00
parent f7e0814a38
commit fd2860070a
55 changed files with 4084 additions and 186 deletions

View file

@ -53,12 +53,13 @@ def _write_geojsonseq(csvs: list[Path], output_path: Path) -> tuple[int, int]:
to a shared "map point" anchor, so many incidents land on the exact same
coordinate. Collapsing them into one feature carrying ``count`` (the number
of incidents) keeps the per-crime-type and per-month filters intact while
turning each hotspot into a single high-weight point. That matters because
tippecanoe's ``--drop-densest-as-needed`` thins *feature density*, not
weight: with one feature per row the busiest streets were silently deleted;
with one weighted feature per anchor those hotspots survive and the dropped
detail is only redundant duplicate points. The heatmap reads ``count`` as
its weight.
turning each hotspot into a single high-weight point. That matters for the
heatmap weight: each anchor becomes one high-weight point, and tippecanoe's
``--cluster-densest-as-needed`` (with ``--accumulate-attribute=count:sum``)
merges any still-too-dense low-zoom features by *summing* their counts
rather than dropping them, so the total heat weight is conserved across zoom
levels and the surface no longer jumps at tile-zoom boundaries. The heatmap
reads ``count`` as its weight.
"""
grouped = (
pl.scan_csv(
@ -144,7 +145,18 @@ def build_crime_hotspot_tiles(
str(min_zoom),
"--maximum-zoom",
str(max_zoom),
"--drop-densest-as-needed",
# Merge (don't delete) the densest features at low zoom and sum
# their incident counts into the surviving point, so total heat
# weight is conserved across zoom levels. With --drop-densest the
# z14 tile lost hotspots that z15 kept, so the heatmap visibly
# collapsed into smaller spots when crossing the z14<->z15 tile
# boundary. Clustering is spatial only (it can merge different
# crime_types into one representative point), so per-type
# filtering is slightly approximate in the densest z14 tiles;
# the all-types surface and every zoom >= 15 stay accurate.
"--cluster-densest-as-needed",
"--accumulate-attribute=count:sum",
"--accumulate-attribute=weight:sum",
"--extend-zooms-if-still-dropping",
"--temporary-directory",
tmp,