feat(geo): adopt Mapbox building calibration (3.1 m per level) (#26)
Merged PR #26: adopt Mapbox building calibration Co-authored-by: Zwitschi <zwitschi82@gmail.com>
This commit was merged in pull request #26.
This commit is contained in:
1 parent
81f7cd3991
commit
5ffe62f6aa
37 files changed
+125
-88
No files matched your search
@@ -4,9 +4,21 @@
|
||||
Usage:
|
||||
Geo/tools/block_height_report.py Geo/raw/osm-api-map-2026-11-06.osm
|
||||
|
||||
`height` and `building:levels` are the only height evidence OSM carries. Where
|
||||
neither exists, Blosm falls back to `defaultLevels * levelHeight`, which is a
|
||||
placeholder: the demo renders a constant-height block instead of a real skyline.
|
||||
Height evidence, in priority order:
|
||||
|
||||
1. the OSM `height` tag (a direct measurement);
|
||||
2. an explicit override in `HEIGHT_OVERRIDES` (currently Mapbox-derived — see below);
|
||||
3. `building:levels` x `LEVEL_HEIGHT`;
|
||||
4. a flagged placeholder guess.
|
||||
|
||||
`LEVEL_HEIGHT` is 3.1 m, not the 3.2 m first assumed: cross-checking every block
|
||||
building against Mapbox's building data (`Geo/tools/mapbox_buildings.py`) showed
|
||||
our `levels x 3.2 m` figure running 0.6 m high per six levels, i.e. 3.1 m per
|
||||
level. That calibration was adopted on 2026-11-09.
|
||||
|
||||
`HEIGHT_OVERRIDES` carries the few buildings where Mapbox disagrees with a guess;
|
||||
each entry records why, so the number is not orphaned from its evidence.
|
||||
|
||||
This tool lists every building in the demo bounds with its height source, so the
|
||||
substitutions are visible and measurable rather than buried in the model.
|
||||
|
||||
@@ -19,10 +31,22 @@ import sys
|
||||
|
||||
import osm_common as oc
|
||||
|
||||
LEVEL_HEIGHT = 3.2
|
||||
LEVEL_HEIGHT = 3.1
|
||||
|
||||
# Per-way height overrides, applied after the OSM height tag and before the
|
||||
# levels/guess derivation. Keep `reason` short but specific: a height without its
|
||||
# evidence is a guess wearing a costume.
|
||||
HEIGHT_OVERRIDES = {
|
||||
# Rear outbuildings tagged only `building=yes`/`apartments`; the two-level
|
||||
# fallback rendered them as 6.4 m blocks. Mapbox's building layer gives 3.0 m
|
||||
# for the same footprints (single storey plus parapet), adopted 2026-11-09.
|
||||
1490910971: (3.0, "Mapbox building layer: 3.0 m (single storey)"),
|
||||
1492058875: (3.0, "Mapbox building layer: 3.0 m (single storey)"),
|
||||
1492747608: (3.0, "Mapbox building layer: 3.0 m (single storey)"),
|
||||
}
|
||||
|
||||
|
||||
def classify_height(tags, level_height=LEVEL_HEIGHT):
|
||||
def classify_height(tags, level_height=LEVEL_HEIGHT, way_id=None):
|
||||
"""Return (height_m, source) for one OSM way's tags.
|
||||
|
||||
Extracted from `main()` so the classification is testable: the roof/shelter
|
||||
@@ -39,6 +63,9 @@ def classify_height(tags, level_height=LEVEL_HEIGHT):
|
||||
return 0.0, f"not a building volume ({kind})"
|
||||
if tags.get("height"):
|
||||
return float(tags["height"]), "height tag"
|
||||
if way_id is not None and int(way_id) in HEIGHT_OVERRIDES:
|
||||
height, reason = HEIGHT_OVERRIDES[int(way_id)]
|
||||
return height, f"override: {reason}"
|
||||
if tags.get("building:levels"):
|
||||
return (float(tags["building:levels"]) * level_height,
|
||||
f"levels x {level_height} m")
|
||||
@@ -66,7 +93,7 @@ def main():
|
||||
pts = oc.way_points(nodes, refs)
|
||||
if not pts or not in_block(pts):
|
||||
continue
|
||||
height, source = classify_height(tags)
|
||||
height, source = classify_height(tags, way_id=wid)
|
||||
nearest = min(oc.project(p, line, lat0, lon0, lengths)[0] for p in pts)
|
||||
side = "S" if oc.project(pts[0], line, lat0, lon0, lengths)[2] < 0 else "N"
|
||||
rows.append((side, nearest, wid, tags.get("name", "") or tags.get("addr:housenumber", ""),
|
||||
|
||||
@@ -184,7 +184,7 @@ def build_rows(nodes, ways, token, radius, max_distance, delay) -> list[dict]:
|
||||
lat, lon = centroid_lat_lon(points)
|
||||
payload, cached = tilequery(lat, lon, token, radius, delay)
|
||||
feature = nearest_building(payload, max_distance)
|
||||
ours_height, ours_source = bhr.classify_height(tags)
|
||||
ours_height, ours_source = bhr.classify_height(tags, way_id=wid)
|
||||
theirs = None if feature is None else feature.get("height")
|
||||
verdict, delta = compare(ours_height, ours_source, theirs)
|
||||
rows.append({
|
||||
|
||||
Reference in new issue
Block a user