geo: add block extract, boundary verification, and the carried-over reference set
Inventory the sibling security-game project and copy only the evidence: nine licensed photos with their sidecar provenance into Art/reference/hollywood/, now LFS-tracked. Add the block-wide OSM extract with a manifest, resolve the demo bounds (Orange Dr -118.341434 .. Highland Ave -118.338718, 279 m) and the south/north building sets, and add Geo/scripts/block_report.py to reproduce it. The sibling docs place the El Capitan Theatre west of 6840; OSM geometry and the house numbering put it east. Recorded with the two unresolved measurement conflicts rather than averaged.
This commit is contained in:
1 parent
cbcee4040b
commit
737e16e7db
7 files changed
+28069
-16
No files matched your search
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Report streets, boundary intersections and the demo building set for an OSM extract.
|
||||
|
||||
Usage:
|
||||
Geo/scripts/block_report.py Geo/raw/osm-api-map-2026-11-06.osm \
|
||||
--blvd "Hollywood Boulevard" --west "North Orange Drive" --east "North Highland Avenue"
|
||||
|
||||
Reads a plain .osm XML file (the output of the OSM API `map` endpoint or an
|
||||
Overpass export). No network access, no dependencies beyond the standard library.
|
||||
|
||||
Output: the boulevard's block span in metres, the two boundary intersections, the
|
||||
buildings on the boulevard frontage split by side (even/odd is not trusted —
|
||||
latitude relative to the centreline is), and each building's distance from the
|
||||
centreline. Buildings behind the frontage are listed but marked as off-buffer.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
EARTH_R = 6378137.0
|
||||
|
||||
|
||||
def local_metres(lat, lon, lat0, lon0):
|
||||
x = math.radians(lon - lon0) * EARTH_R * math.cos(math.radians(lat0))
|
||||
y = math.radians(lat - lat0) * EARTH_R
|
||||
return x, y
|
||||
|
||||
|
||||
def load(path):
|
||||
root = ET.parse(path).getroot()
|
||||
nodes = {n.get("id"): (float(n.get("lat")), float(n.get("lon"))) for n in root.findall("node")}
|
||||
ways = []
|
||||
for w in root.findall("way"):
|
||||
tags = {c.get("k"): c.get("v") for c in w.findall("tag")}
|
||||
ways.append((w.get("id"), tags, [nd.get("ref") for nd in w.findall("nd")]))
|
||||
return nodes, ways
|
||||
|
||||
|
||||
def refs_by_name(ways, name):
|
||||
return {ref for _, tags, refs in ways if tags.get("name") == name for ref in refs}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("osm")
|
||||
ap.add_argument("--blvd", default="Hollywood Boulevard")
|
||||
ap.add_argument("--west", default="North Orange Drive")
|
||||
ap.add_argument("--east", default="North Highland Avenue")
|
||||
ap.add_argument("--buffer", type=float, default=50.0, help="north-side buffer in metres, from the centreline")
|
||||
args = ap.parse_args()
|
||||
|
||||
nodes, ways = load(args.osm)
|
||||
blvd = refs_by_name(ways, args.blvd)
|
||||
if not blvd:
|
||||
raise SystemExit(f"no highway named {args.blvd!r} in {args.osm}")
|
||||
|
||||
ends = {}
|
||||
for label, street in (("west", args.west), ("east", args.east)):
|
||||
inter = refs_by_name(ways, street) & blvd
|
||||
pts = sorted(nodes[i][1] for i in inter if i in nodes)
|
||||
if not pts:
|
||||
raise SystemExit(f"no intersection between {args.blvd!r} and {street!r}")
|
||||
ends[label] = pts[0] if label == "west" else pts[-1]
|
||||
print(f"{street} x {args.blvd}: {len(pts)} shared node(s), lon {pts[0]:.6f}..{pts[-1]:.6f}")
|
||||
|
||||
lo_min, lo_max = ends["west"], ends["east"]
|
||||
lat0 = sum(nodes[i][0] for i in blvd if lo_min <= nodes[i][1] <= lo_max) / max(
|
||||
1, len([i for i in blvd if lo_min <= nodes[i][1] <= lo_max])
|
||||
)
|
||||
span = math.radians(lo_max - lo_min) * EARTH_R * math.cos(math.radians(lat0))
|
||||
print(f"block span: {span:.1f} m of frontage, centreline lat {lat0:.6f}\n")
|
||||
|
||||
rows = []
|
||||
for wid, tags, refs in ways:
|
||||
if "building" not in tags:
|
||||
continue
|
||||
pts = [nodes[r] for r in refs if r in nodes]
|
||||
if not pts:
|
||||
continue
|
||||
lat = sum(p[0] for p in pts) / len(pts)
|
||||
lon = sum(p[1] for p in pts) / len(pts)
|
||||
if not (lo_min - 0.00005 <= lon <= lo_max + 0.00005):
|
||||
continue
|
||||
side = "S" if lat < lat0 else "N"
|
||||
_, y = local_metres(lat, lon, lat0, lon)
|
||||
rows.append((side, abs(y), lon, wid, tags))
|
||||
|
||||
rows.sort(key=lambda r: r[2])
|
||||
for side, dist, lon, wid, tags in rows:
|
||||
if side == "S":
|
||||
flag = ""
|
||||
else:
|
||||
flag = "" if dist <= args.buffer else " (outside buffer)"
|
||||
print(
|
||||
f"{side} way {wid:>10} {tags.get('addr:housenumber', '-'):>11} "
|
||||
f"{tags.get('addr:street', '')[:22]:22} h={tags.get('height', '-'):>5} "
|
||||
f"lv={tags.get('building:levels', '-'):>3} d={dist:5.1f}m {tags.get('name', '')[:30]}{flag}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user