From 552292305f3d6b1755028e9cdecc19745ba4285f Mon Sep 17 00:00:00 2001 From: Brett Naul Date: Tue, 24 Mar 2026 18:51:07 -0400 Subject: [PATCH 1/3] Update CLAUDE.md: fix web app path and add Python openlr decode docs Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1140ee5..5796ca6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,10 +123,10 @@ Key design decisions driven by this: ### Visualization Web App -The `~/openlr_benchmark` repo contains a FastAPI visualization app for debugging: +The `openlr_web` repo contains a FastAPI visualization app for debugging: ```bash -cd ~/openlr_benchmark +cd ~/openlr_web uv run uvicorn app:app --reload ``` @@ -158,7 +158,9 @@ The app shows: ### Debugging Approach -1. IMPORTANT: Never manually decode OpenLR references to LRPs using custom code: only use the Python or Rust libraries. You can decode from the command line like: `uvx --with openlr python -m openlr ` +1. IMPORTANT: Never manually decode OpenLR references to LRPs using custom code: only use the Python or Rust libraries. + - **CLI**: `uvx --with openlr python -m openlr ` + - **Python**: `import openlr; ref = openlr.binary_decode("")` — pass the base64 string directly (not raw bytes). Returns a `LineLocationReference` with `.points` (list of `LocationReferencePoint` with lon, lat, frc, fow, bear, lfrcnp, dnp) and `.poffs`/`.noffs`. 2. **Visualize the LRPs**: See where they land on the map 3. **Check candidate edges**: Are the correct roads being found? 4. **Verify connectivity**: Can A* find a path between candidate pairs? From 5446a51f4b52993f81cb6dfddfee4d119626c50a Mon Sep 17 00:00:00 2001 From: Brett Naul Date: Tue, 24 Mar 2026 18:55:16 -0400 Subject: [PATCH 2/3] Use startOsmNode/endOsmNode for graph node IDs instead of startVertex/endVertex The OSM export assigns different internal vertex IDs to the same physical intersection at barrier nodes (gates, bollards), creating disconnected gaps in the graph. The startOsmNode/endOsmNode columns resolve these splits back to the original OSM node ID, so both sides of a barrier share the same node. The loader now prefers startOsmNode/endOsmNode and falls back to startVertex/endVertex for backwards compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 4 ++-- src/loader.rs | 28 ++++++++++++++++++---------- src/python.rs | 5 +++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5796ca6..7e5e37a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -251,8 +251,8 @@ cd ~/openlr-web && uv pip install --reinstall ~/openlr-decoder/target/wheels/ope | Column | Type | Description | |--------|------|-------------| | `stableEdgeId` | uint64 | Unique edge identifier | -| `startVertex` | int64 | Start node ID | -| `endVertex` | int64 | End node ID | +| `startOsmNode` | int64 | Start node ID (OSM node; resolves barrier splits) | +| `endOsmNode` | int64 | End node ID (OSM node; resolves barrier splits) | | `startLat`, `startLon` | float64 | Start coordinates | | `endLat`, `endLon` | float64 | End coordinates | | `highway` | string | OSM highway tag | diff --git a/src/loader.rs b/src/loader.rs index 1d4b1dd..d1ef626 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -57,8 +57,8 @@ struct GeometryWithMetrics { /// /// Required columns: /// - `stableEdgeId` (UInt64): unique edge identifier -/// - `startVertex` (Int64): start node ID -/// - `endVertex` (Int64): end node ID +/// - `startOsmNode` (Int64): start node ID (OSM node, resolves barrier splits) +/// - `endOsmNode` (Int64): end node ID (OSM node, resolves barrier splits) /// - `startLat`, `startLon`, `endLat`, `endLon` (Float64): endpoint coordinates /// - `highway` (Utf8): OSM highway tag /// @@ -68,11 +68,16 @@ struct GeometryWithMetrics { /// - WKB: Binary, LargeBinary, or BinaryView /// - WKT: String, LargeString, or StringView /// - GeoArrow native: List> with geoarrow.linestring extension +/// +/// Note: `startVertex`/`endVertex` are also accepted as fallbacks for backwards +/// compatibility, but `startOsmNode`/`endOsmNode` are preferred because they +/// correctly resolve barrier node splits (where the export assigns different +/// internal vertex IDs to the same physical intersection). pub fn road_network_schema() -> Schema { Schema::new(vec![ Field::new("stableEdgeId", DataType::UInt64, false), - Field::new("startVertex", DataType::Int64, false), - Field::new("endVertex", DataType::Int64, false), + Field::new("startOsmNode", DataType::Int64, false), + Field::new("endOsmNode", DataType::Int64, false), Field::new("startLat", DataType::Float64, false), Field::new("startLon", DataType::Float64, false), Field::new("endLat", DataType::Float64, false), @@ -87,8 +92,8 @@ pub fn road_network_schema() -> Schema { /// /// Expected columns: /// - stableEdgeId (STRING): unique edge identifier -/// - startVertex (INTEGER): start node ID -/// - endVertex (INTEGER): end node ID +/// - startOsmNode (INTEGER): start node ID (preferred; falls back to startVertex) +/// - endOsmNode (INTEGER): end node ID (preferred; falls back to endVertex) /// - startLat, startLon, endLat, endLon (FLOAT): endpoint coordinates /// - highway (STRING): OSM highway tag /// - lanes (INTEGER): number of lanes (optional, for FOW) @@ -118,8 +123,8 @@ pub fn load_network_from_parquet(path: &Path) -> Result<(RoadNetwork, SpatialInd /// /// Expected columns in each batch: /// - `stableEdgeId` (UInt64): unique edge identifier -/// - `startVertex` (Int64): start node ID -/// - `endVertex` (Int64): end node ID +/// - `startOsmNode` (Int64): start node ID (preferred; falls back to `startVertex`) +/// - `endOsmNode` (Int64): end node ID (preferred; falls back to `endVertex`) /// - `startLat`, `startLon`, `endLat`, `endLon` (Float64): endpoint coordinates /// - `highway` (Utf8): OSM highway tag /// - `lanes` (Int64, optional): number of lanes for FOW inference @@ -192,12 +197,15 @@ fn process_batch( .column_by_name("stableEdgeId") .and_then(|c| c.as_any().downcast_ref::()); + // Prefer startOsmNode/endOsmNode (resolves barrier splits) with fallback to startVertex/endVertex let start_vertex = batch - .column_by_name("startVertex") + .column_by_name("startOsmNode") + .or_else(|| batch.column_by_name("startVertex")) .and_then(|c| c.as_any().downcast_ref::()); let end_vertex = batch - .column_by_name("endVertex") + .column_by_name("endOsmNode") + .or_else(|| batch.column_by_name("endVertex")) .and_then(|c| c.as_any().downcast_ref::()); let start_lat = batch diff --git a/src/python.rs b/src/python.rs index 9eafc4f..c7c28e9 100644 --- a/src/python.rs +++ b/src/python.rs @@ -56,9 +56,10 @@ impl PyRoadNetwork { /// /// Args: /// data: Arrow-compatible data with the road network schema. - /// Must have columns: stableEdgeId (uint64), startVertex (int64), - /// endVertex (int64), startLat/startLon/endLat/endLon (float64), + /// Must have columns: stableEdgeId (uint64), startOsmNode (int64), + /// endOsmNode (int64), startLat/startLon/endLat/endLon (float64), /// highway (string). Optional: lanes (int64), geometry (binary/WKB). + /// Legacy: startVertex/endVertex are accepted as fallbacks. /// /// Returns: /// RoadNetwork: The loaded road network ready for decoding From 12665dddd2d39acdc6e083213a2ee84627273122 Mon Sep 17 00:00:00 2001 From: Brett Naul Date: Tue, 24 Mar 2026 19:46:13 -0400 Subject: [PATCH 3/3] =?UTF-8?q?Remove=20startVertex/endVertex=20fallback?= =?UTF-8?q?=20=E2=80=94=20require=20startOsmNode/endOsmNode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- src/loader.rs | 15 ++++----------- src/python.rs | 1 - 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/loader.rs b/src/loader.rs index d1ef626..a245674 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -69,10 +69,6 @@ struct GeometryWithMetrics { /// - WKT: String, LargeString, or StringView /// - GeoArrow native: List> with geoarrow.linestring extension /// -/// Note: `startVertex`/`endVertex` are also accepted as fallbacks for backwards -/// compatibility, but `startOsmNode`/`endOsmNode` are preferred because they -/// correctly resolve barrier node splits (where the export assigns different -/// internal vertex IDs to the same physical intersection). pub fn road_network_schema() -> Schema { Schema::new(vec![ Field::new("stableEdgeId", DataType::UInt64, false), @@ -92,8 +88,8 @@ pub fn road_network_schema() -> Schema { /// /// Expected columns: /// - stableEdgeId (STRING): unique edge identifier -/// - startOsmNode (INTEGER): start node ID (preferred; falls back to startVertex) -/// - endOsmNode (INTEGER): end node ID (preferred; falls back to endVertex) +/// - startOsmNode (INTEGER): start node ID (OSM node, resolves barrier splits) +/// - endOsmNode (INTEGER): end node ID (OSM node, resolves barrier splits) /// - startLat, startLon, endLat, endLon (FLOAT): endpoint coordinates /// - highway (STRING): OSM highway tag /// - lanes (INTEGER): number of lanes (optional, for FOW) @@ -123,8 +119,8 @@ pub fn load_network_from_parquet(path: &Path) -> Result<(RoadNetwork, SpatialInd /// /// Expected columns in each batch: /// - `stableEdgeId` (UInt64): unique edge identifier -/// - `startOsmNode` (Int64): start node ID (preferred; falls back to `startVertex`) -/// - `endOsmNode` (Int64): end node ID (preferred; falls back to `endVertex`) +/// - `startOsmNode` (Int64): start node ID (OSM node, resolves barrier splits) +/// - `endOsmNode` (Int64): end node ID (OSM node, resolves barrier splits) /// - `startLat`, `startLon`, `endLat`, `endLon` (Float64): endpoint coordinates /// - `highway` (Utf8): OSM highway tag /// - `lanes` (Int64, optional): number of lanes for FOW inference @@ -197,15 +193,12 @@ fn process_batch( .column_by_name("stableEdgeId") .and_then(|c| c.as_any().downcast_ref::()); - // Prefer startOsmNode/endOsmNode (resolves barrier splits) with fallback to startVertex/endVertex let start_vertex = batch .column_by_name("startOsmNode") - .or_else(|| batch.column_by_name("startVertex")) .and_then(|c| c.as_any().downcast_ref::()); let end_vertex = batch .column_by_name("endOsmNode") - .or_else(|| batch.column_by_name("endVertex")) .and_then(|c| c.as_any().downcast_ref::()); let start_lat = batch diff --git a/src/python.rs b/src/python.rs index c7c28e9..0a3c9e3 100644 --- a/src/python.rs +++ b/src/python.rs @@ -59,7 +59,6 @@ impl PyRoadNetwork { /// Must have columns: stableEdgeId (uint64), startOsmNode (int64), /// endOsmNode (int64), startLat/startLon/endLat/endLon (float64), /// highway (string). Optional: lanes (int64), geometry (binary/WKB). - /// Legacy: startVertex/endVertex are accepted as fallbacks. /// /// Returns: /// RoadNetwork: The loaded road network ready for decoding