Walking Path Snapping
Trims OSRM/Valhalla pedestrian walking legs so they terminate exactly where the passenger's path meets the bus route, eliminating visual gaps and unnecessary detours on the map.
This algorithm works in tandem with Geodesic Line-Segment Projection Snapping, which provides the distanceToRoute function used here.
Where It Is Used
apps/passengers-app/src/app/pages/route-detail/route-detail.page.ts
Applied after fetching walking directions from OSRM/Valhalla for both legs of a journey: - Leg 1: Origin → boarding stop on the bus route - Leg 2: Alighting stop on the bus route → destination
The Problem
The OSRM routing engine snaps its endpoints to the nearest walkable street. This snapped point rarely lies exactly at the bus route geometry, producing:
- Leg 1: A walking path that overshoots past the bus route before turning back.
- Leg 2: A walking path that starts by walking away from the destination before reaching it.
Both appear as confusing overlaps or backwards detours on the map.
Algorithm
Boarding Snap ("Walk to Bus")
Origin ──────────────────────────────── [trimmed here]
│
bus route ──────────●──────────────
- Fetch the OSRM walking path
walkToBusPts[]from origin to the raw boarding stop coordinate. - Iterate through
walkToBusPtsfrom the start (origin side). - For each point, call
distanceToRoute(point, busPolyline). - The first point within 30 m of any bus route segment is the true boarding intersection.
- Trim the walking leg to
walkToBusPts[0 … boardingIndex]. - Set
boardPt=walkToBusPts[boardingIndex].
Alighting Snap ("Walk from Bus")
[trimmed here] ──────────── Destination
│
bus route ──────────────────────────●
- Fetch the OSRM walking path
walkFromBusPts[]from the raw alighting stop coordinate to destination. - Iterate through
walkFromBusPtsfrom the end (destination side, walking backwards). - For each point, call
distanceToRoute(point, busPolyline). - The last point within 30 m of the bus route is the true alighting intersection.
- Trim the walking leg to
walkFromBusPts[alightingIndex … end]. - Set
alightPt=walkFromBusPts[alightingIndex].
Threshold
30 meters — chosen to reliably catch intersections while avoiding false positives from parallel streets one block away (~80–100 m in Bolivian urban grids).
Implementation Sketch
// Boarding: first point within 30m of the bus route
let boardIdx = walkToBusPts.length - 1;
for (let i = 0; i < walkToBusPts.length; i++) {
if (distanceToRoute(walkToBusPts[i], busPts) < 30) {
boardIdx = i;
break;
}
}
const trimmedWalkToBoard = walkToBusPts.slice(0, boardIdx + 1);
const boardPt = walkToBusPts[boardIdx];
// Alighting: last point within 30m of the bus route
let alightIdx = 0;
for (let i = walkFromBusPts.length - 1; i >= 0; i--) {
if (distanceToRoute(walkFromBusPts[i], busPts) < 30) {
alightIdx = i;
break;
}
}
const trimmedWalkFromAlight = walkFromBusPts.slice(alightIdx);
const alightPt = walkFromBusPts[alightIdx];
Related
- Geodesic Line-Segment Projection Snapping — provides
distanceToRouteand the underlying segment-projection math - Valhalla Polyline Decoder — decodes the walking paths before trimming
- ETA Estimation — walk distances are calculated on the trimmed legs