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
+26
-29
@@ -1,48 +1,45 @@
|
||||
# Geo
|
||||
|
||||
Geographic sources, manifests, and the scripts that turn them into Blender imports.
|
||||
Geographic sources, manifests, and the tools that turn them into Blender and Unreal content.
|
||||
|
||||
OSM is layout data only. It is not authoritative for façade detail, height, or pavement, and it is never sampled as a texture or panorama source.
|
||||
|
||||
The OSM source files are © OpenStreetMap contributors, available under the [Open Database License 1.0](https://opendatacommons.org/licenses/odbl/1-0/); see [OpenStreetMap attribution](https://www.openstreetmap.org/copyright). Public use of a derivative database can require sharing that database or the changes needed to recreate it under ODbL. Review the applicable obligations for each distributed product.
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | Contents |
|
||||
| --- | --- |
|
||||
| `Geo/raw/` | Verbatim source responses, checksummed. Never edited by hand. |
|
||||
| `Geo/verification/` | Per-pass verification notes: address, footprint, bounds, and what remains unresolved |
|
||||
| `Geo/manifests/` | One manifest per saved extract (see below) |
|
||||
| `Geo/scripts/` | Repeatable fetch and conversion helpers |
|
||||
| `Geo/raw/` | Verbatim source responses and downloads, checksummed. Never edited by hand. |
|
||||
| `Geo/manifests/` | One manifest per source: extract, DEM, and the local origin |
|
||||
| `Geo/verification/` | Per-pass notes: address, footprints, bounds, street profile, cross-sections, elevation |
|
||||
| `Geo/tools/` | Every geographic tool (Python, standard library only unless noted) |
|
||||
|
||||
Current state: the extract, both El Capitan footprints, the demo bounds, the building sets, the street profile, and the designated crossing are recorded in [`verification/`](verification/). The local CRS and origin are fixed in [`manifests/local-origin.md`](manifests/local-origin.md), with the projected easting/northing still to be computed. Elevation is measured and the 3DEP 1 m DEM window is committed (EPSG:26911, NAVD88, 984 KB). The street cross-sections (crown ~2 %, gutters at ±10–11 m) are in `verification/`. The hero façade width is resolved at 25.75 m. The geo pipeline is ready for the Blender blockout.
|
||||
Tools that build 3D content from these sources live next to their output in `Art/blender/*/` (`terrain_from_dem.py`, `street_mesh.py`, `site_assembly.py`, `blosm_blockout.py`) because they need Blender; their shared geo helpers and the unit conversion come from `Geo/tools/osm_common.py`.
|
||||
|
||||
## Required manifest
|
||||
## Tools
|
||||
|
||||
Every extract gets a manifest file `Geo/manifests/<source>-<date>.md` recording:
|
||||
|
||||
| Field | Example |
|
||||
| Tool | Purpose |
|
||||
| --- | --- |
|
||||
| Source | Overpass API / Geofabrik / OSM extract provider |
|
||||
| Extract date | date the query ran or the file was downloaded |
|
||||
| Query or file URL | the exact query text or download URL |
|
||||
| Data timestamp | OSM data date reported by the source (may differ from the extract date) |
|
||||
| Bounds | west, south, east, north in WGS84 decimal degrees |
|
||||
| Licence | ODbL 1.0, attribution string |
|
||||
| Checksum | SHA-256 of the committed file |
|
||||
| CRS and local origin | projected CRS plus the chosen origin, with its WGS84 coordinates |
|
||||
| Files | every file produced from this extract |
|
||||
| `osm_common.py` | Shared helpers: parse `.osm`, stitch and clip a street, project to UTM through PROJ, **and the single metres→centimetres conversion** (`to_unreal_local`) |
|
||||
| `block_report.py` | Streets, boundary intersections, and the building set for a block, split by side |
|
||||
| `block_height_report.py` | Every block building with its height **source** — tag, levels × 3.2 m, or a placeholder guess |
|
||||
| `street_profile.py` | Carriageway width from crossings, sidewalk offsets, per-building setbacks |
|
||||
| `street_section.py` | Cross-sections from the DEM: crown, gutters, kerb detection |
|
||||
| `elevation_probe.py` | Samples 3DEP for relief and records the baseline for the release checks |
|
||||
| `dem_stats.py` | Stdlib reader for the tiled GeoTIFF the 3DEP service returns; GDAL does not run on this host |
|
||||
| `asset_metadata.py` | Writes and audits `<asset>.geo.json` sidecars: position, provenance, conversion |
|
||||
|
||||
## Demo bounds
|
||||
## Unit boundary
|
||||
|
||||
`0.1.0` covers 6840 Hollywood Blvd / El Capitan Entertainment Centre, buildings on the south side of Hollywood Boulevard between Highland Avenue and Orange Drive, and north-side buildings whose footprint lies within 50 m of the north frontage. Verify address, footprints, and boundaries against source data before modeling; anything unverifiable is recorded here as unresolved and is not modeled.
|
||||
There is exactly one conversion in the repository:
|
||||
|
||||
## Coordinate convention
|
||||
```
|
||||
WGS84 (lat, lon) → EPSG:26911 (E, N metres) → project-local metres → Unreal centimetres
|
||||
```
|
||||
|
||||
- Work in a local projected CRS (EPSG:26911, NAD83 / UTM zone 11N, is the default candidate) and record the local origin explicitly.
|
||||
- Blender and Unreal both take metres → Unreal units at 1 m = 100 uu. The conversion is applied once, in one place, and recorded in the manifest. Never apply a second silent scale on import.
|
||||
- Preserve the original geographic coordinates in metadata so any asset can be traced back to its position.
|
||||
`Geo/tools/osm_common.py::to_unreal_local` is the only place that multiplies by 100. Per-asset sidecars record the result, and `asset_metadata.py audit` checks each saved sidecar against that function, so a file that drifts is caught rather than trusted.
|
||||
|
||||
## Unresolved items
|
||||
## Current state
|
||||
|
||||
Record here any structure whose address, footprint, or boundary could not be verified from permitted sources, with the sources checked. It does not get modeled.
|
||||
The extract, both El Capitan footprints, the demo bounds, the building sets, the street profile, cross-sections, and the 3DEP DEM window are recorded in `Geo/verification/` and `Geo/manifests/`. The local CRS and origin are fixed in `manifests/local-origin.md`.
|
||||
|
||||
Open items live in `ROADMAP.md` under **Geographic data**.
|
||||
@@ -35,7 +35,7 @@ Min 110.46 m, max 127.73 m, mean 117.43 m over the 550 × 350 m window — a 17.
|
||||
|
||||
## Reading the file
|
||||
|
||||
`Geo/scripts/dem_stats.py` parses it with the standard library. GDAL cannot run on the host (its flatpak libraries do not resolve outside the sandbox), and the exported TIFF is **tiled**, not strip-organised — a strip reader silently scrambles the rows. If another tool misreads it, suspect the tile layout before the data.
|
||||
`Geo/tools/dem_stats.py` parses it with the standard library. GDAL cannot run on the host (its flatpak libraries do not resolve outside the sandbox), and the exported TIFF is **tiled**, not strip-organised — a strip reader silently scrambles the rows. If another tool misreads it, suspect the tile layout before the data.
|
||||
|
||||
## Open
|
||||
|
||||
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read, write and audit per-asset geographic metadata — the provenance sidecar.
|
||||
|
||||
Every exported asset gets `<asset>.geo.json` next to it carrying the boundary it
|
||||
was modelled against, its position in EPSG:26911 and in Unreal centimetres, the
|
||||
reasoning behind every non-measured dimension, and the unit conversion used. The
|
||||
file makes three things checkable after the fact:
|
||||
|
||||
* where the asset belongs in the world, without re-deriving it;
|
||||
* which measurements are evidence and which are assumptions;
|
||||
* that the metres-to-centimetres conversion was applied once, consistently.
|
||||
|
||||
Geo/tools/asset_metadata.py write --asset hero-facade --kind building \\
|
||||
--bounds "OSM way 424024147" --centre-easting 376381.9 --centre-northing 3774184 \\
|
||||
--out Art/blender/hero
|
||||
Geo/tools/asset_metadata.py audit Art/blender/hero
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import osm_common as oc
|
||||
|
||||
SCHEMA = 1
|
||||
|
||||
|
||||
def build(asset, kind, bounds, easting, northing, elevation, report_path, extra):
|
||||
x, y, z = oc.to_unreal_local(easting, northing, elevation if elevation else 0.0)
|
||||
doc = {
|
||||
"schema": SCHEMA,
|
||||
"asset": asset,
|
||||
"kind": kind,
|
||||
"generated_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"),
|
||||
"geographic": {
|
||||
"crs": "EPSG:26911",
|
||||
"source": bounds,
|
||||
"centre_easting_m": easting,
|
||||
"centre_northing_m": northing,
|
||||
"elevation_navd88_m": elevation,
|
||||
"origin": {
|
||||
"easting_m": oc.ORIGIN_E,
|
||||
"northing_m": oc.ORIGIN_N,
|
||||
"reference": "Geo/manifests/local-origin.md",
|
||||
},
|
||||
},
|
||||
"unreal": {
|
||||
"centre_cm": [round(x, 1), round(y, 1), None if z is None else round(z, 1)],
|
||||
"conversion": f"{oc.METRES_TO_UNREAL:g} uu per metre, applied once in osm_common.to_unreal_local",
|
||||
"axes": "X east, Y south (FBX preset flips north), Z up",
|
||||
},
|
||||
"dims_report": report_path,
|
||||
"notes": extra,
|
||||
}
|
||||
return doc
|
||||
|
||||
|
||||
def cmd_write(args):
|
||||
doc = build(args.asset, args.kind, args.bounds, args.centre_easting, args.centre_northing,
|
||||
args.elevation, args.report, args.note or [])
|
||||
path = os.path.join(args.out, args.asset + ".geo.json")
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
with open(path, "w") as f:
|
||||
json.dump(doc, f, indent=2)
|
||||
f.write("\n")
|
||||
print(f"wrote {path}")
|
||||
|
||||
|
||||
def cmd_audit(targets):
|
||||
files = []
|
||||
for t in targets:
|
||||
if os.path.isdir(t):
|
||||
# assets keep their sidecars either in the same folder as the .blend
|
||||
# or one level down, so a directory target must recurse
|
||||
files += glob.glob(os.path.join(t, "**", "*.geo.json"), recursive=True)
|
||||
else:
|
||||
files.append(t)
|
||||
bad = 0
|
||||
for path in sorted(files):
|
||||
try:
|
||||
doc = json.load(open(path))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"UNREADABLE {path}: {exc}")
|
||||
bad += 1
|
||||
continue
|
||||
missing = [k for k in ("asset", "kind", "geographic", "unreal") if k not in doc]
|
||||
if missing:
|
||||
print(f"MISSING {path}: {missing}")
|
||||
bad += 1
|
||||
continue
|
||||
u = doc["unreal"]["centre_cm"]
|
||||
geo = doc["geographic"]
|
||||
expect_x = (geo["centre_easting_m"] - oc.ORIGIN_E) * oc.METRES_TO_UNREAL
|
||||
expect_y = -(geo["centre_northing_m"] - oc.ORIGIN_N) * oc.METRES_TO_UNREAL
|
||||
if abs(u[0] - expect_x) > 1.0 or abs(u[1] - expect_y) > 1.0:
|
||||
print(f"STALE {path}: centre_cm {u} disagrees with the origin conversion "
|
||||
f"({expect_x:.1f}, {expect_y:.1f})")
|
||||
bad += 1
|
||||
continue
|
||||
print(f"ok {doc['asset']:24} {doc['kind']:10} centre_cm {u[0]:10.1f} {u[1]:10.1f} "
|
||||
f"src {geo['source'][:40]}")
|
||||
print(f"\n{len(files)} sidecars, {bad} problem(s)")
|
||||
return 1 if bad else 0
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
w = sub.add_parser("write")
|
||||
w.add_argument("--asset", required=True)
|
||||
w.add_argument("--kind", required=True, choices=("building", "terrain", "street", "prop", "character"))
|
||||
w.add_argument("--bounds", required=True, help="what the asset was modelled against")
|
||||
w.add_argument("--centre-easting", type=float, required=True)
|
||||
w.add_argument("--centre-northing", type=float, required=True)
|
||||
w.add_argument("--elevation", type=float, default=None, help="NAVD88 metres")
|
||||
w.add_argument("--report", default=None, help="dimension report produced by the build script")
|
||||
w.add_argument("--note", action="append")
|
||||
w.add_argument("--out", required=True)
|
||||
a = sub.add_parser("audit")
|
||||
a.add_argument("targets", nargs="+")
|
||||
args = ap.parse_args()
|
||||
if args.cmd == "write":
|
||||
cmd_write(args)
|
||||
return 0
|
||||
return cmd_audit(args.targets)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
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())
|
||||
@@ -2,7 +2,7 @@
|
||||
"""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 \
|
||||
Geo/tools/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
|
||||
@@ -2,8 +2,8 @@
|
||||
"""Read a plain float32 GeoTIFF, report its georeferencing and elevation stats.
|
||||
|
||||
Usage:
|
||||
Geo/scripts/dem_stats.py Geo/raw/dem-3dep-1m-26911-2026-11-06.tif
|
||||
Geo/scripts/dem_stats.py <tif> --sample 376515.389,3774224.083
|
||||
Geo/tools/dem_stats.py Geo/raw/dem-3dep-1m-26911-2026-11-06.tif
|
||||
Geo/tools/dem_stats.py <tif> --sample 376515.389,3774224.083
|
||||
|
||||
Why this exists: GDAL is not runnable on the host (its flatpak libraries do not
|
||||
resolve outside the sandbox), so this reads the strip-based GeoTIFF that the
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Probe ground elevation across the demo block with the USGS 3DEP point service.
|
||||
|
||||
Usage:
|
||||
Geo/scripts/elevation_probe.py Geo/raw/osm-api-map-2026-11-06.osm
|
||||
Geo/tools/elevation_probe.py Geo/raw/osm-api-map-2026-11-06.osm
|
||||
|
||||
Samples a grid along the boulevard (centreline and both sidewalk centrelines),
|
||||
the hero footprint corners, and the crossing midpoints, then reports the relief
|
||||
@@ -1,17 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared helpers for the Geo scripts: parse .osm, stitch a street, project points.
|
||||
"""Shared geo helpers: parse .osm, stitch a street, project points, convert to Unreal.
|
||||
|
||||
Standard library only. Imported by `block_report.py`, `street_profile.py`, and
|
||||
`elevation_probe.py` when run from this directory.
|
||||
Standard library only. Imported by every script in `Geo/tools/`.
|
||||
|
||||
The unit boundary is deliberate and lives here, once:
|
||||
|
||||
WGS84 (lat, lon) -> EPSG:26911 (E, N metres) -> project local metres
|
||||
-> Unreal centimetres
|
||||
|
||||
`to_unreal` is the only place that multiplies by 100. Nothing else in the
|
||||
repository may convert metres to centimetres, so a second silent scale cannot be
|
||||
introduced by accident.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
EARTH_R = 6378137.0
|
||||
|
||||
# Project origin, EPSG:26911. See Geo/manifests/local-origin.md.
|
||||
ORIGIN_E = 376515.389
|
||||
ORIGIN_N = 3774224.083
|
||||
ORIGIN_Z_NAVD88 = 115.911
|
||||
METRES_TO_UNREAL = 100.0
|
||||
|
||||
|
||||
def to_unreal_local(easting, northing, elevation=None):
|
||||
"""EPSG:26911 metres -> Unreal centimetres in the project frame.
|
||||
|
||||
X = east of the origin, Y = south of the origin (the FBX preset maps Blender
|
||||
+Y/north to Unreal -Y), Z = height above the origin ground. This is the single
|
||||
meters->centimetres conversion for the repository.
|
||||
"""
|
||||
x_cm = (easting - ORIGIN_E) * METRES_TO_UNREAL
|
||||
y_cm = -(northing - ORIGIN_N) * METRES_TO_UNREAL
|
||||
if elevation is None:
|
||||
return x_cm, y_cm, None
|
||||
return x_cm, y_cm, (elevation - ORIGIN_Z_NAVD88) * METRES_TO_UNREAL
|
||||
|
||||
|
||||
def to_blender_local(easting, northing, elevation=None):
|
||||
"""EPSG:26911 metres -> Blender metres in the project frame (X east, Y north, Z up)."""
|
||||
x = easting - ORIGIN_E
|
||||
y = northing - ORIGIN_N
|
||||
z = None if elevation is None else elevation - ORIGIN_Z_NAVD88
|
||||
return x, y, z
|
||||
|
||||
|
||||
def find_proj():
|
||||
"""The PROJ binary: environment override, PATH, then the QGIS Flatpak."""
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
|
||||
env = os.environ.get("PROJ_BIN")
|
||||
if env and os.path.exists(env):
|
||||
return env
|
||||
which = shutil.which("proj")
|
||||
if which:
|
||||
return which
|
||||
matches = sorted(glob.glob(os.path.expanduser(
|
||||
"~/.local/share/flatpak/app/org.qgis.qgis/x86_64/stable/*/files/bin/proj")))
|
||||
if not matches:
|
||||
raise SystemExit("no PROJ binary found; set PROJ_BIN or install pyproj")
|
||||
return matches[0]
|
||||
|
||||
|
||||
def project_wgs84_to_utm(points, proj_bin=None):
|
||||
"""WGS84 (lat, lon) -> EPSG:26911 (E, N) for a list, in one PROJ call."""
|
||||
import subprocess
|
||||
|
||||
proj_bin = proj_bin or find_proj()
|
||||
app_files = os.path.dirname(os.path.dirname(proj_bin))
|
||||
env = dict(os.environ)
|
||||
env["LD_LIBRARY_PATH"] = f"{app_files}/lib:{env.get('LD_LIBRARY_PATH', '')}"
|
||||
payload = "\n".join(f"{lon:.9f} {lat:.9f}" for lat, lon in points) + "\n"
|
||||
out = subprocess.run(
|
||||
[proj_bin, "+proj=utm", "+zone=11", "+datum=NAD83", "-f", "%.3f"],
|
||||
input=payload, capture_output=True, text=True, env=env, check=True,
|
||||
).stdout.strip().splitlines()
|
||||
result = [tuple(float(v) for v in line.split()) for line in out]
|
||||
if len(result) != len(points):
|
||||
raise SystemExit(f"PROJ returned {len(result)} points for {len(points)} inputs")
|
||||
return result
|
||||
|
||||
|
||||
def to_xy(lat: float, lon: float, lat0: float, lon0: float) -> tuple[float, float]:
|
||||
"""Local planar metres, east/north, centred on (lat0, lon0)."""
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Street profile for a block: frontage setbacks, carriageway width, crossings.
|
||||
|
||||
Usage:
|
||||
Geo/scripts/street_profile.py Geo/raw/osm-api-map-2026-11-06.osm \
|
||||
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):
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Cross-sections of the street from the local 3DEP DEM.
|
||||
|
||||
Usage:
|
||||
Geo/scripts/street_section.py Geo/raw/osm-api-map-2026-11-06.osm \
|
||||
Geo/tools/street_section.py Geo/raw/osm-api-map-2026-11-06.osm \
|
||||
Geo/raw/dem-3dep-1m-26911-2026-11-06.tif --step 0.5
|
||||
|
||||
For several positions along the block it samples the DEM on a line perpendicular
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cross-sections — crowned road, gutters, sidewalk shelves
|
||||
|
||||
Sampled 2026-11-06 from the local 3DEP DEM (`Geo/raw/dem-3dep-1m-26911-2026-11-06.tif`) with `Geo/scripts/street_section.py`. Seven sections at 40 m spacing, offsets −18 m to +18 m from the boulevard centreline (negative = south), 1 m sampling step.
|
||||
Sampled 2026-11-06 from the local 3DEP DEM (`Geo/raw/dem-3dep-1m-26911-2026-11-06.tif`) with `Geo/tools/street_section.py`. Seven sections at 40 m spacing, offsets −18 m to +18 m from the boulevard centreline (negative = south), 1 m sampling step.
|
||||
|
||||
## What the profile shows
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Elevation check — Hollywood Blvd block
|
||||
|
||||
Measured 2026-11-06 with `Geo/scripts/elevation_probe.py` against the USGS Elevation Point Query Service (3DEP, 1 m seamless DEM, raster id 129943). Raw responses: `Geo/raw/epqs-2026-11-06.json`. Reproduce with:
|
||||
Measured 2026-11-06 with `Geo/tools/elevation_probe.py` against the USGS Elevation Point Query Service (3DEP, 1 m seamless DEM, raster id 129943). Raw responses: `Geo/raw/epqs-2026-11-06.json`. Reproduce with:
|
||||
|
||||
```bash
|
||||
python3 Geo/scripts/elevation_probe.py Geo/raw/osm-api-map-2026-11-06.osm --workers 5
|
||||
python3 Geo/tools/elevation_probe.py Geo/raw/osm-api-map-2026-11-06.osm --workers 5
|
||||
```
|
||||
|
||||
Heights are **orthometric, NAVD88**, as published for 3DEP CONUS. They are not ellipsoidal heights and must not be mixed with GPS/ellipsoid values.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Street profile — Hollywood Blvd block
|
||||
|
||||
Measured 2026-11-06 from the block extract (`Geo/raw/osm-api-map-2026-11-06.osm`) with `Geo/scripts/street_profile.py`. Reproduce with:
|
||||
Measured 2026-11-06 from the block extract (`Geo/raw/osm-api-map-2026-11-06.osm`) with `Geo/tools/street_profile.py`. Reproduce with:
|
||||
|
||||
```bash
|
||||
python3 Geo/scripts/street_profile.py Geo/raw/osm-api-map-2026-11-06.osm
|
||||
python3 Geo/tools/street_profile.py Geo/raw/osm-api-map-2026-11-06.osm
|
||||
```
|
||||
|
||||
All distances are metres, computed on a local planar approximation at the block's mean latitude (error well under 1 cm over 300 m). OSM is layout data; these are geometry measurements, not a survey.
|
||||
|
||||
Reference in new issue
Block a user