Skip to content

Review location precision against the STREET-LEVEL imagery Stage 1 actually consumes, not an aerial proxy (#96) #103

Description

@jonfroehlich

Why

#96 §5m established that supply is not the constraint — the ArcGIS Hub synonym sweep found 1,972,275 candidate records across 65 publishers (a floor; every query hit the page cap), against a 500k target. DVRPC's Philadelphia-region layer alone is 295,389, larger than the entire current training corpus.

So the binding constraint is assessment throughput. Today that costs, per city:

  1. a municipal basemap hunt — which has failed in four distinct ways so far (§5e Esri grey tiles; §5h King County leaf-on canopy; §5h Charlotte 404ing ~25–35% of tiles that exist; and probe_basemap.py checking one downtown point while the sheet samples citywide),
  2. a 60-chip sheet build,
  3. an hour of the only reviewer we have,
  4. …who then discarded 41.7% of Seattle to tree canopy (§5l).

At that rate the queue above is decades of work. The next investment belongs in the instrument.

The proposal (Jon's)

Review against the street-level imagery Stage 1 actually consumes: show the panorama, the projected crop Stage 1 would cut, and the government point's projected bearing — and have the reviewer judge whether the ramp falls inside that crop.

Why this is better than the aerial sheet, beyond convenience

  1. It measures the actual gate quantity. §5g proved Stage 1 consumes the published coordinate only for its bearing, and cuts a ±18.37° strip around it. The aerial sheet measures a metric offset — a proxy — which then has to be pushed through a Monte Carlo to become a decision. This has the reviewer answer the real question directly.
  2. It eliminates the basemap problem. No municipal basemap, so all four failure modes above go away at once. GSV coverage is uniform and global.
  3. It looks under the canopy. Seattle's 41.7% unjudgeable was tree cover in plan view. Street level is beneath the trees.
  4. Temporal matching becomes per-record. GSV has many panos per location, each with a capture date, so you select one captured after the ramp's install date. The confound that dominated §5i/§5l is eliminated, not mitigated.

What already exists — this is mostly assembly

The rendering path is the production path, not a reimplementation:

need where
find panos near a lat/lng, with dates stage_one/dataset_generation/search_panos.py
assemble the equirectangular pano download_dataset.py::fetch_panorama(pano_id)
pano heading download_dataset.py::heading_to_azimuth(get_pano_heading(...))
the exact crop Stage 1 cuts equirectangular_to_perspective(equi, 90, azimuth, -30, 1024, 1024)[0:1024, 341:341+341]
the ±18.37° half-angle scripts/analysis/stage1_offset_tolerance.py::crop_half_angle_deg()import, never re-derive
frozen inventories + date parsing data/inventories/, temporal_gap.py::parse_ym (carries the §5c sentinel handling)
sheet scaffold: sampling, strata, localStorage, export scripts/analysis/inventory_review_sheet.py

Design decisions already taken

  • Show the full 90° perspective with the strip boundaries overlaid, not the bare 341-px strip. The reviewer must be able to distinguish "ramp just outside the crop" from "no ramp here" — that distinction is the measurement.
  • Record the verdict as an ANGULAR offset. This puts human review into the same units as §5j's automatic bearing residual (stage1_bearing_residual.py), whose null over 90,006 published records is |mean| ≤ 0.25°, |median| ~3.3°. §5j measures cities already in the corpus; this measures candidates; same units, so they cross-validate.
  • Reuse the strata sampler (--strata-year-field), because §5l showed the dated/undated split is city-specific and must be earned per city.

Calibration plan — do this first

Pilot on Denver. It is the one city with a trusted aerial answer (§5f: median 0.29 m, 92.3% within 1 m, and §5g says its coordinate error costs Stage 1 0.21%). If the street-level tool independently returns "Denver is Good", the instrument is calibrated and can be trusted where aerial fails. If it disagrees, we learn that before betting the queue on it.

Second pilot: Seattle, where the aerial instrument demonstrably struggled (41.7% unjudgeable, MEDIUM confidence). If the street-level unjudgeable rate collapses there, that is the proof the tool is worth its build cost.

Risks, stated up front

  • GSV endpoints are undocumented (search_panos.py and the tile fetch in download_dataset.py talk directly to them) and can break without notice. This is a real dependency risk that the aerial path does not have.
  • ~1,900 tile requests per 60-chip sheet, and panorama assembly is slower than tile fetch — expect 10–20 min per build. Rate limiting is the dominant unknown, same as §7's Stage 1 generation concern.
  • Angular offsets are not metric. This tool cannot produce a number comparable to Denver's 0.29 m or the paper's Table 1. That is acceptable — angular is what Stage 1 cares about — but it must be stated, and it means the aerial sheet is not fully retired.
  • Different selection bias, not none: parked vans, poles and low sun replace tree canopy. Probably smaller, but it must be measured, not assumed.
  • Sheet weight. 60 panoramas is a lot of pixels; the aerial sheet embeds tiles as data URIs and already reaches 3–5 MB. Will need downsampled context views plus full-res strips.

Lessons from the aerial sheet that must not be re-earned

  • Two-path bugs. Twice now a fix landed on one of two paths that must agree: §5h's retry logic was inert because the cache stored absences as zero-byte files; §5l's stratum was added to the verdicts template but not the chip dict the export rebuilds from, so every exported stratum came back null. When adding a field, find the second path.
  • Probe at the sample sites, not one point (probe_basemap_at_sites.py). The street-level analogue: check pano availability and date coverage at the actual sampled records before building.
  • A drop count is a claim about the fetcher until checked against the sample. Charlotte's no_imagery_dropped: 59 was reported as "outside the basemap footprint" and was false.
  • Null date sentinels — Boston's 18991230, the old 2000-01 — must go through parse_ym, never a fresh parser.
  • State what the instrument cannot measure, next to the numbers (§5e's recall note is the template).

Scope for v1

Denver, 60 records, context view + strip overlay + projected crosshair, angular verdict, reusing the production projection path and the existing sheet scaffold. Everything else is follow-on.

🤖 Generated with Claude Code (claude-opus-5[1m])

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions