#!/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())