{"id":50823294,"url":"https://github.com/sunsided/pathplanning","last_synced_at":"2026-06-13T16:11:11.840Z","repository":{"id":354022642,"uuid":"1221462167","full_name":"sunsided/pathplanning","owner":"sunsided","description":"A Rust-based path planning playground on the OpenStreetMap road network of Berlin","archived":false,"fork":false,"pushed_at":"2026-04-26T20:17:16.000Z","size":5359,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T20:27:21.840Z","etag":null,"topics":["astar-pathfinding","berlin","open-street-map","osm","path-planning","pathfinding","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sunsided.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-26T08:37:11.000Z","updated_at":"2026-04-26T20:02:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sunsided/pathplanning","commit_stats":null,"previous_names":["sunsided/pathplanning"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sunsided/pathplanning","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sunsided%2Fpathplanning","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sunsided%2Fpathplanning/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sunsided%2Fpathplanning/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sunsided%2Fpathplanning/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sunsided","download_url":"https://codeload.github.com/sunsided/pathplanning/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sunsided%2Fpathplanning/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34290616,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astar-pathfinding","berlin","open-street-map","osm","path-planning","pathfinding","rust"],"created_at":"2026-06-13T16:11:11.258Z","updated_at":"2026-06-13T16:11:11.830Z","avatar_url":"https://github.com/sunsided.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pathplanning\n\n[![CI](https://github.com/sunsided/pathplanning/actions/workflows/ci.yml/badge.svg)](https://github.com/sunsided/pathplanning/actions/workflows/ci.yml)\n[![Rust](https://img.shields.io/badge/Rust-2024-orange?logo=rust)](https://www.rust-lang.org/)\n[![License](https://img.shields.io/badge/License-EUPL--1.2-blue)](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12)\n[![Vello](https://img.shields.io/badge/rendered%20with-Vello-8A2BE2)](https://github.com/linebender/vello)\n[![winit](https://img.shields.io/badge/windowing-winit-63863A)](https://github.com/rust-windowing/winit)\n\nPath planning playground on [Berlin OSM (2026-04-25)](https://download.geofabrik.de/europe/germany/berlin.html).\n\n![A* Animation](readme/video.webp)\n\n## Directed Graph\n\nPlanning is on the directed graph of the Berlin road network.\n\n| A*                                                                                                                               | A* (reverse)                                                                                                                  |\n|----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|\n| ![A* pathfinding from north to south on Berlin OSM map with blue route highlighted and green start marker](readme/astar-one.png) | ![A* pathfinding from south to north on Berlin OSM map with blue route highlighted and pink end marker](readme/astar-two.png) |\n\n## Usage\n\n### Running\n\n```bash\ncargo run --release -- [path/to/map.osm.pbf]\n```\n\nDefaults to `maps/berlin.osm.pbf` if no argument is given.\n\n### Mouse Controls\n\n| Action | Control |\n|--------|---------|\n| Place start marker | Left-click on map |\n| Place end marker | Left-click again (second click) |\n| Drag start/end marker | Left-click and drag on an existing marker |\n| Pan the map | Left-click and drag on empty map area |\n| Zoom | Mouse scroll wheel |\n| Remove nearest marker | Right-click near a marker |\n\n### Keyboard Controls\n\n| Key | Action |\n|-----|--------|\n| `R` | Randomize — places start and end markers on two random routable nodes within the current viewport and triggers a search |\n| `S` | Swap — swaps the start and end markers |\n| `Q` / `Escape` | Clear all markers and reset the planner |\n| `A` / `P` | Cycle forward through algorithms (`P` cycles backward) |\n| `H` | Cycle forward through heuristics (shift+H for backward) — only active for algorithms that use heuristics |\n| `C` | Cycle forward through cost modes (shift+C for backward) |\n| `Backspace` | Re-run the search with current markers and configuration |\n| `F1` | Toggle the HUD/debug overlay and menu panel |\n| `0` | Auto-select LOD tier based on zoom level |\n| `1` | Force LOD tier 0 (full detail, use with caution) |\n| `2` | Force LOD tier 1 |\n| `3` | Force LOD tier 2 (lowest detail) |\n\n### HUD Menu\n\nThe top-right HUD panel provides clickable menu items to change planner configuration:\n\n- **PLANNER** section: Select the pathfinding algorithm\n- **COST** section: Select the cost function\n- **HEURISTIC** section: Select the heuristic (greyed out for Dijkstra et al.)\n- **Random**: Places random start/end markers (same as `R` key)\n- **Swap**: Swaps start and end markers (same as `S` key)\n\n## Algorithms\n\nFour pathfinding algorithms are available, selectable via the `A`/`P` keys or the HUD menu:\n\n### A*\n\nThe default algorithm. Uses `f = g + h` where `g` is the actual cost from start and `h` is the heuristic estimate to goal. A* is optimal (guarantees shortest path) when the heuristic is admissible (never overestimates).\n\n### Dijkstra\n\nUses `f = g` only — equivalent to A* with a zero heuristic. Explores uniformly outward from the start, guaranteeing optimality but typically expanding significantly more nodes than A*. Useful for comparison and visualization of the search space.\n\n### Greedy Best-First\n\nUses `f = h` only — ignores actual travel cost and expands toward the goal purely based on the heuristic. Fast but **not optimal**; the resulting path may be longer than necessary. Does not perform relaxation (first visit to a node is final).\n\n### RRT\n\nA Rapidly-exploring Random Tree sampler that grows a tree from the start node toward randomly sampled world positions. **Not optimal** — it finds a feasible path quickly but does not guarantee the shortest one.\n\n- **Goal bias**: 5% of samples target the goal directly to ensure termination on reachable goals.\n- **Nearest neighbor**: Uses an `rstar::RTree` spatial index for O(log n) nearest-tree-vertex lookups.\n- **Focused sampling**: Samples uniformly from a workspace bbox around start and goal, expanding by 1.5× when the tree stalls (256 iterations without progress).\n- **No heuristic**: RRT is sampling-based and does not use a heuristic estimate; the heuristic menu is greyed out (same as Dijkstra).\n- **Graph-embedded**: The tree grows along existing directed road edges — \"steering\" means choosing the outgoing edge whose destination most reduces distance to the random sample.\n\n### RRT*\n\nAn asymptotically optimal variant of RRT that improves path quality over time through **choose-parent** and **rewire** operations. **Not optimal immediately** — it starts with a feasible path like RRT but continuously refines it as more samples arrive, converging toward the optimal solution given enough iterations.\n\n- **Choose-parent**: when inserting a new node, RRT* examines all tree vertices within a dynamic radius that have an edge to the new node, and picks the parent that minimizes total path cost — not just the spatially nearest one.\n- **Rewire**: after insertion, RRT* checks whether the new node can serve as a cheaper parent for nearby tree vertices. If so, it repoints `came_from` and propagates the cost improvement to all descendants via BFS.\n- **Anytime semantics**: unlike RRT (which stops on first goal reach), RRT\\* keeps searching until its iteration budget is exhausted. The `locked_path` cost decreases (or stays equal) over time as rewiring discovers cheaper routes.\n- **Graph-embedded**: rewiring is constrained to existing directed edges — we cannot draw new lines in continuous space. Gains are smaller than in free-space RRT\\* but still meaningful: a node reached via a long detour can be re-parented once a shorter route materializes nearby.\n- **Dynamic radius**: the neighborhood radius shrinks as the tree densifies using `r(n) = γ × √(log(n)/n)` with `γ = 2 × avg_edge_length`, floored at 50 m and capped at 2000 m.\n- **No heuristic**: same as RRT and Dijkstra, the heuristic menu is greyed out.\n\n## Heuristics\n\nHeuristics estimate the remaining cost from a node to the goal. They are used by A* and Greedy Best-First. All heuristics account for Web Mercator projection distortion by scaling distances with `cos(mean_latitude)` to return ground-true meters.\n\n| Heuristic | Formula | Admissible? | Description |\n|-----------|---------|-------------|-------------|\n| **Euclidean** | `√(dx² + dy²)` | Yes | Straight-line distance. The most accurate for road networks with free directional movement. Web Mercator coordinates are scaled by `cos(mean_latitude)` to convert back to ground-true meters — without this correction, the heuristic overestimates (especially at Berlin's latitude ~52.5°) and A* returns suboptimal paths. |\n| **Manhattan** | `dx + dy` | Yes | Sum of absolute axis differences. Overestimates on diagonal movement, making it inadmissible but sometimes faster. |\n| **Octile** | `(dmax - dmin) + dmin × √2` | Yes | Accounts for diagonal movement on grids. Admissible and tighter than Manhattan. |\n| **Zero** | `0` | Yes | Reduces A* to Dijkstra. Expands all reachable nodes uniformly. |\n\nDefault: **Euclidean**\n\n## Cost Functions\n\nCost functions determine what the planner optimizes for. Edge costs are derived from OSM road classification data.\n\n### Shortest Path (Distance)\n\n![A* with shortest path cost](readme/dist.png)\n\nMinimizes total travel distance in meters. Edge cost = `edge.weight_meters`.\n\nProduces the geometrically shortest route regardless of road speed limits or traffic conditions.\n\n### Shortest Time\n\n![A* with shortest time cost](readme/time.png)\n\nMinimizes estimated travel time. Edge cost = `edge.travel_time_s + stop_penalty`.\n\n- `travel_time_s` is derived from the road class's maximum speed\n- A **stop penalty** is added when traversing edges into nodes with 3+ outgoing connections (intersections), simulating deceleration/waiting time at junctions\n- Uses a 90 km/h (25 m/s) ceiling for the heuristic's time estimation\n\nDefault: **Shortest Time**\n\n## Cost × Heuristic Interaction\n\nThe heuristic's evaluation mode matches the active cost function:\n\n- In **Shortest Path** mode, the heuristic returns distance in meters\n- In **Shortest Time** mode, the heuristic returns `distance / MAX_SPEED_MPS` (time estimate)\n\nThis ensures the heuristic remains consistent with the cost function, preserving A* optimality guarantees when using admissible heuristics.\n\n## Architecture\n\n| Component | Description |\n|-----------|-------------|\n| `graph.rs` | Directed road graph with nodes, edges, and road classifications |\n| `osm_loader.rs` | OSM PBF file parser and graph construction |\n| `planner/` | Pathfinding algorithms (A*, Dijkstra, Greedy, RRT, RRT*) with configurable heuristics and cost modes |\n| `spatial_index.rs` | Nearest-node lookup for marker snapping |\n| `view_index.rs` | R*-tree spatial index for viewport-based edge queries |\n| `lod.rs` | LOD pyramid for multi-scale rendering |\n| `camera.rs` | Web Mercator camera with zoom and pan |\n| `renderer.rs` | Vello GPU renderer with edge styling, decoration rendering, and HUD |\n| `projection.rs` | Web Mercator projection utilities |\n\n## Visualization Layers\n\nThe renderer draws in the following order (back to front):\n\n1. **Decorations** — Buildings, landuse, water, pedestrian areas\n2. **Road edges** — Styled by road class (motorway, primary, residential, etc.)\n3. **Explored edges** — Closed-set edges in dark blue\n4. **Frontier nodes** — Open-set nodes as cyan dots\n5. **Best path** — Neon cyan (updated during search)\n6. **Locked path** — Electric blue (final path when search completes)\n7. **Markers** — Green (start) and pink (end)\n8. **Debug overlay** — Stats text (top-left)\n9. **HUD menu** — Interactive configuration panel (top-right)\n\n## Road Classes\n\nEdges are styled by their OSM road classification:\n\n- Motorway\n- Primary\n- Secondary\n- Tertiary\n- Residential\n- Service\n- Path\n- Other\n\nEach class has a distinct stroke width and color, and contributes different speed/stop-penalty values to the time-based cost function.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsunsided%2Fpathplanning","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsunsided%2Fpathplanning","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsunsided%2Fpathplanning/lists"}