Routing Engine¶
Last updated: June 2026
The core of Crestr’s route generation is a custom A* pathfinding implementation, heavily optimised for large hiking networks and realistic hiking costs.
Overview¶
The routing engine finds the lowest-cost path between two points on the enriched graph while respecting user preferences such as minimum slope tolerance.
It combines:
- Accurate Naismith-based edge costs (from the elevation pipeline)
- Euclidean heuristic with elevation penalty
- Spatial optimisation using a global KDTree and dynamic subgraph extraction
Key Components¶
1. AStarRouteFinder Class¶
The main implementation of the A* algorithm:
- Priority Queue (
nodes_left): Usesheapqto always expand the most promising node next. - Cost Tracking:
actual_cost: Real cost from start to current nodefull_cost:actual_cost + heuristic- Early Termination: Returns as soon as the destination node is dequeued.
- Slope Filtering: Skips edges that violate the user’s
min_slopepreference.
2. Heuristic Function¶
A fast, admissible heuristic that estimates remaining time:
- Straight-line (Euclidean) distance at 1.4 m/s base speed
- Additional penalty for elevation difference to the destination
- Guarantees optimality while guiding the search efficiently
3. Spatial Optimisation (a_star function)¶
Because the full graph is large, the engine uses several techniques to reduce computation:
- Global KDTree: Built once for fast nearest-neighbour lookups.
- Radius-based Subgraph: Extracts a relevant portion of the graph around the start and end points.
- Bounding Box Culling: Further removes nodes outside the search area.
- Snapping: Snaps the user’s clicked coordinates to the nearest actual trail node.
These optimisations make routing feel instantaneous even on a dense national trail network.
Algorithm Flow¶
- Snap start and end coordinates to the nearest trail nodes using KDTree.
- Build a focused subgraph around the route corridor.
- Run A* on the subgraph using precomputed edge costs.
- Reconstruct and return the path.
Design Decisions¶
-
Why A*?
It guarantees the optimal path when using an admissible heuristic, while being significantly faster than Dijkstra in practice. The use of a heuristic allows me to tune the algorithm to the specific needs of my app making pathfinding highly customisable. -
Precomputed Costs
All elevation, terrain, and Naismith calculations are done once during preprocessing. The routing engine only reads these values. -
Scalability
The combination of KDTree + subgraph extraction allows the engine to scale to 1M+ node graphs without excessive memory or CPU usage. -
Customisability
Themin_slopeparameter enables future features such as “avoid steep sections”.
Performance Characteristics¶
- Typical routing time: well under 1 second on regional graphs (distance approx 5-7km).
- Memory efficient due to subgraph extraction.
- Scales with trail density rather than total graph size.
Future Improvements¶
- Bidirectional A* for even faster searches.
- Support for multi-criteria optimisation (e.g. shortest vs least ascent).
- Caching of common routes.
- Real-time dynamic obstacles or closures.
- Integration with live weather / trail conditions.