geo: one tool directory, one unit boundary, per-asset provenance
Geo/tools/ is now every geographic script, with Geo/README.md documenting each one; the Blender-side builders moved beside their outputs in Art/blender/*/ and import their helpers from Geo/tools/osm_common.py. That module gains to_unreal_local() and to_blender_local(): the only place in the repository that multiplies metres by 100, so a second silent scale cannot be introduced. block_height_report.py adds height provenance for the block — 3 of 15 buildings have no height data and render at a placeholder 6.4 m. asset_metadata.py writes and audits <asset>.geo.json sidecars; four now exist (hero-facade, terrain, street, blockout) and the audit recomputes each centre from the origin, failing anything that disagrees.
This commit is contained in:
1 parent
5e5e5ae9b4
commit
386d4bee78
26 files changed
+471
-57
No files matched your search
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Report which block buildings have real height data and which are guesses.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Exit code is 0 either way; the count of guessed buildings is the deliverable.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import sys
|
||||
|
||||
import osm_common as oc
|
||||
|
||||
LEVEL_HEIGHT = 3.2
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("osm")
|
||||
args = ap.parse_args()
|
||||
nodes, ways = oc.load(args.osm)
|
||||
|
||||
line, lat0, lon0, lengths, block_len = oc.block_extent(
|
||||
nodes, ways, "Hollywood Boulevard", "North Orange Drive", "North Highland Avenue")
|
||||
|
||||
def in_block(pts):
|
||||
if min(p[1] for p in pts) > line[-1][1] or max(p[1] for p in pts) < line[0][1]:
|
||||
return False
|
||||
return True
|
||||
|
||||
rows = []
|
||||
for wid, tags, refs in ways:
|
||||
if "building" not in tags:
|
||||
continue
|
||||
pts = oc.way_points(nodes, refs)
|
||||
if not pts or not in_block(pts):
|
||||
continue
|
||||
tag_height = tags.get("height")
|
||||
levels = tags.get("building:levels")
|
||||
if tag_height:
|
||||
height, source = float(tag_height), "height tag"
|
||||
elif levels:
|
||||
height, source = float(levels) * LEVEL_HEIGHT, f"levels x {LEVEL_HEIGHT} m"
|
||||
else:
|
||||
height, source = 2 * LEVEL_HEIGHT, f"GUESS: default 2 levels x {LEVEL_HEIGHT} m"
|
||||
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", ""),
|
||||
height, source))
|
||||
|
||||
rows.sort(key=lambda r: (r[0], r[1]))
|
||||
guessed = [r for r in rows if r[5].startswith("GUESS")]
|
||||
print(f"{len(rows)} buildings in the block, {len(guessed)} with a guessed height\n")
|
||||
for side, setback, wid, name, height, source in rows:
|
||||
flag = " <-- guessed" if source.startswith("GUESS") else ""
|
||||
print(f"{side} way {wid:>10} setback {setback:5.1f} m height {height:5.1f} m "
|
||||
f"({source:26}) {name[:34]}{flag}")
|
||||
|
||||
if guessed:
|
||||
print("\nGuessed heights to replace from the reference photography:")
|
||||
for side, setback, wid, name, height, source in guessed:
|
||||
print(f" {side} way {wid} {name}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user