min_area_pixels is probably not the best measure. #36
Labels
No labels
claude-wip
map-report
map:is
map:se
needs-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
art/ismap#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
We do a lot of filtering/dropping of polygons by their area. While this works most of the time, it's probably not the most correct measure. What we want to filter out is stuff that will be rendered too small.
In my head math, which is one level below napkin math, some kind of measure of area divided by circumference (or the other way around) might be more useful. I guess this would be relevant for rivers. But also landcover in Poland is very stripey and would be affected.
Explore what kind of measure would be better at filtering landcover and rivers/lakes so that things that are too thin get dropped.
Context from the #26/#20/#27 work (commit
3ae69c3), which ran into this from the riverside and left it deliberately undone. Everything below is measured, not reasoned.
Your napkin math is dimensionally right, and better than it looks
ST_Area / ST_Perimeterhas units of length, and for the shapes we care about it isapproximately a quarter of the width: a long thin rectangle
w x lwithl >> wgivesA/P = wl / 2(w+l) ~= w/2, and a disc givesr/2. So4A/Pis an estimate of "how wideis this thing", which is exactly the quantity that should be compared against a pixel.
That makes it a much better-founded measure than area, which conflates a 96 km² pond with a
96 km² river reach.
The obvious objection is that perimeter is fractal — Sweden's lakes are extremely crinkly,
so P is inflated and
4A/Pshould underestimate their width badly. Measured on Sweden's sixlargest lakes at z9, against
ST_MaximumInscribedCircle(PostGIS 3.6.2 here, so it isavailable) as ground truth for width:
4A/PAgreement is 2–28%, not orders of magnitude. The reason is structural and worth knowing:
the filter runs on the geometry from the zoom above, which is already simplified for that
zoom (
importLayerZ:sizeFiltertestsgeom, the source column, whilegeomExprsimplifies into the output). Crinkle finer than the previous level's tolerance is already
gone, so P is bounded at every level rather than being the true fractal perimeter.
ST_MaximumInscribedCircleis the robust option if that ever stops holding, at real cost.There are two decisions here, and they disagree
For a river they point in opposite directions, which is the trap. At z6 one pixel is 2 446 m
and the Vistula is roughly 400 m wide — 0.16 px. A thinness rule would therefore drop
the Vistula polygon at z6, which is defensible, but only if
waterwayscarries a centrelinethere so the river still appears. #20 was fixed in the other direction (
min_area_pixels16 -> 4 for
water_bodies, so the polygon survives and renders as a hairline).So #36 and #20 have to be settled together, and the acceptance test is "is the river still
visible", never "does the polygon still exist". The cartographically standard answer is
area-to-line collapse — drop the polygon once it is sub-pixel wide and let the line layer
draw it — and the map is already shaped for that, since
waterwaysexists and is now cheapafter the merge work.
ST_ApproximateMedialAxis(SFCGAL) is the real collapse if it is everwanted; not needing it is why the layer split exists.
The threshold is one knob wired to two different mechanisms
This is the thing most likely to cause a surprise, because
min_area_pixelsdoes not meanthe same thing in both places (
importLayerZ):simplify(water_bodies) —sizeFilterisST_Area(geom) > minArea, an outrightdrop. Relaxing it can only add features and can never open a hole, so a keep-more rule
is monotone and safe to experiment with.
cover(landcover) — the threshold instead selects absorption candidates, and thesemantic-rollup path deliberately passes area 0 because dropping from a coverage creates a
hole. So a thinness measure there changes what gets recoloured into its neighbour, not
what gets deleted. That is exactly your stripey-Poland case: a field strip does not
disappear, it turns into whatever is beside it. Note it now also interacts with
absorb_boundary(added in3ae69c3), which already stops that happening across water.Baseline to test a fix against
River polygon counts, per zoom, measured just before the change:
min_area_pixels: 16Sweden's cost for that change was 295 -> 1 270 lake polygons at z6, which a thinness rule
should be able to win back — most of those are small and round, so both measures agree they
can go.
Practical notes for whoever picks this up
-regeneralize <lo>-<hi>makes this cheap: Poland'swater_bodiesz5–9 rebuilds in 2 sand Sweden's in 13 s, so a measure can be tried at one zoom before committing to a cascade.
cover— absorption is notrow-for-row reproducible (CLAUDE.md, "Generalization strategies"). Compare orders of
magnitude and class distributions.
already at 4, so it is the reference for what correct looks like, not a test case.
The river going through a removed polygon is ok if there is a centerline for that river through that polygon, but if it's considered a lake?
If this is a problem at all, I think it should be resolved early. Not as part of the generalization when the polygon disappears, but as part of the import - if we know that two linestrings are the same river (by name or some id) check if they all connect and if they don't create fake centerlines through any water polygons. But of course, if it rarely ever happens we probably shouldn't waste time doing this, so weigh the extra cost of doing this (if it's a trivial 10 second operation, we probably should always do it, if it's a 10 hour ordeal, we probably should never do it, no matter how bad the problem is).