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
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Street profile for a block: frontage setbacks, carriageway width, crossings.
|
||||
|
||||
Usage:
|
||||
Geo/tools/street_profile.py Geo/raw/osm-api-map-2026-11-06.osm \
|
||||
--blvd "Hollywood Boulevard" --west "North Orange Drive" --east "North Highland Avenue"
|
||||
|
||||
Measures, in metres, from a plain .osm extract (standard library only):
|
||||
|
||||
* the boulevard centreline, stitched and clipped between the two boundary
|
||||
intersections;
|
||||
* each building's frontage setback: the distance from its nearest vertex to the
|
||||
centreline, and which side of the road it is on;
|
||||
* each mapped crossing (`footway=crossing`) across the boulevard: its length,
|
||||
which is the carriageway width at that point, and its position along the block;
|
||||
* the offset of the mapped sidewalk centrelines from the road centreline.
|
||||
|
||||
What it cannot measure: carriageway width where no crossing or kerb is mapped,
|
||||
and sidewalk width, because OSM models sidewalks here as centre lines with no
|
||||
width tag. Derive those from the numbers printed here and record them as
|
||||
assumptions.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
import osm_common as oc
|
||||
|
||||
|
||||
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")
|
||||
args = ap.parse_args()
|
||||
|
||||
nodes, ways = oc.load(args.osm)
|
||||
line, lat0, lon0, lengths, block_len = oc.block_extent(
|
||||
nodes, ways, args.blvd, args.west, args.east
|
||||
)
|
||||
print(f"centreline: {len(line)} vertices, {block_len:.1f} m between the boundary intersections\n")
|
||||
|
||||
print("crossings across the boulevard (length = carriageway width there):")
|
||||
for wid, tags, refs in ways:
|
||||
if tags.get("footway") != "crossing":
|
||||
continue
|
||||
pts = oc.way_points(nodes, refs)
|
||||
if not pts:
|
||||
continue
|
||||
if min(p[1] for p in pts) > line[-1][1] or max(p[1] for p in pts) < line[0][1]:
|
||||
continue
|
||||
a = oc.to_xy(pts[0][0], pts[0][1], lat0, lon0)
|
||||
b = oc.to_xy(pts[-1][0], pts[-1][1], lat0, lon0)
|
||||
length = ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** 0.5
|
||||
mid = (sum(p[0] for p in pts) / len(pts), sum(p[1] for p in pts) / len(pts))
|
||||
dist, s, _ = oc.project(mid, line, lat0, lon0, lengths)
|
||||
if not (0.0 <= s <= block_len) or dist > 4.0:
|
||||
continue
|
||||
print(
|
||||
f" way {wid:>10} {length:6.1f} m across, at {s:6.1f} m along the block "
|
||||
f"(offset {dist:4.1f} m) markings={tags.get('crossing:markings', '-'):6} "
|
||||
f"signals={tags.get('crossing')}"
|
||||
)
|
||||
|
||||
print("\nsidewalk centrelines near the block (offset from road centreline):")
|
||||
south, north = [], []
|
||||
for wid, tags, refs in ways:
|
||||
if tags.get("footway") != "sidewalk":
|
||||
continue
|
||||
pts = oc.way_points(nodes, refs)
|
||||
if not pts:
|
||||
continue
|
||||
if min(p[1] for p in pts) > line[-1][1] or max(p[1] for p in pts) < line[0][1]:
|
||||
continue
|
||||
offsets = [oc.project(p, line, lat0, lon0, lengths) for p in pts]
|
||||
mean = sum(o[0] for o in offsets) / len(offsets)
|
||||
along = max(o[1] for o in offsets) - min(o[1] for o in offsets)
|
||||
if not (6.0 <= mean <= 20.0) or along < 30.0:
|
||||
continue
|
||||
(south if offsets[0][2] < 0 else north).append((mean, along, wid, tags.get("name", "")))
|
||||
|
||||
for label, rows in (("south side", south), ("north side", north)):
|
||||
if not rows:
|
||||
print(f" {label}: none mapped")
|
||||
continue
|
||||
rows.sort()
|
||||
mean = sum(r[0] for r in rows) / len(rows)
|
||||
print(f" {label}: {len(rows)} line(s), mean offset {mean:.1f} m, range {rows[0][0]:.1f}..{rows[-1][0]:.1f} m")
|
||||
|
||||
print("\nbuilding frontage setbacks (nearest vertex to centreline):")
|
||||
rows = []
|
||||
for wid, tags, refs in ways:
|
||||
if "building" not in tags:
|
||||
continue
|
||||
pts = oc.way_points(nodes, refs)
|
||||
if not pts:
|
||||
continue
|
||||
if min(p[1] for p in pts) > line[-1][1] or max(p[1] for p in pts) < line[0][1]:
|
||||
continue
|
||||
proj = [oc.project(p, line, lat0, lon0, lengths) for p in pts]
|
||||
nearest = min(proj, key=lambda o: o[0])
|
||||
along = [o[1] for o in proj]
|
||||
rows.append((nearest[0], nearest[2], wid, tags, max(along) - min(along)))
|
||||
rows.sort()
|
||||
for dist, side, wid, tags, frontage in rows:
|
||||
side_name = "S" if side < 0 else "N"
|
||||
print(
|
||||
f" {side_name} way {wid:>10} {tags.get('addr:housenumber', '-'):>11} "
|
||||
f"setback {dist:5.1f} m, extent {frontage:5.1f} m {tags.get('name', '')[:34]}"
|
||||
)
|
||||
|
||||
print("\nassumptions to record, not measurements:")
|
||||
print(" - lane width is not tagged; the crossing lengths above are the best carriageway")
|
||||
print(" proxy, and crossing endpoints sit within about 1 m of the kerb line")
|
||||
print(" - sidewalk width is not tagged; it is setback minus half carriageway")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user