Add running route planner with quality comparison and GPX export

This commit is contained in:
Peter Stockings
2026-10-03 00:33:41 +10:00
parent a06189849e
commit 52943cd05b
12 changed files with 899 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# Route planner
Open **Routes** in Stride. Select the start on the map, enter latitude/longitude,
or use your location. Choose a loop or select a separate finish, distance (1–50 km),
hill difficulty, surface preference and ranking priority. Generated candidates show
their actual distance, ascent, route geometry, elevation profile and quality coverage.
Save a named route privately, reopen it, download GPX, or delete it.
## Configuration
Apply migrations with `uv run --locked flask --app app db upgrade`.
Set `ORS_API_KEY` in the web process environment and restart the web process.
For local development, the app also reads `instance/ors-api-key` when the environment
key is absent. This private file is ignored by Git; production only uses environment
configuration. Keep the file within your private Windows account/workspace.
For Dokku, configure `ORS_API_KEY` through your normal secret-management workflow.
The key stays server-side and is never included in rendered HTML or requests from
the browser. Obtain a key at https://openrouteservice.org/ and review the provider's
current quotas. The planner sends selected coordinates to the fixed ORS HTTPS
endpoint. Browser map tiles use the app's existing OpenStreetMap tile service.
Without a key, generation is disabled with a configuration notice; saved routes,
maps and export remain available. No synthetic routes are shown in the real app.
## Generation and ranking
The server makes at most three requests per generation, each with a 12-second
network timeout and a 4 MB response limit. Requests use `foot-walking`, elevation,
and `surface`/`waytype` extras. Loops use three deterministic round-trip seeds.
Point-to-point candidates include a direct route and two estimated side waypoints
to seek a longer route near the target. Duplicate geometries are removed. This
bounded search can return fewer than three routes and cannot guarantee a distance
or ascent target. The interface flags results outside ±5% distance.
Preferences rank candidates; they are not strict exclusions. Hill targets are
approximately 3, 12 or 25 metres ascent/km. The cost combines distance deviation,
ascent mismatch, mapped road share, missing surface coverage, optional repeated
edges and surface preference. The priority weights distance, hills or quality
more strongly. This is an explainable heuristic, not a safety or fitness score.
Quality percentages follow ORS documented waytype and surface IDs:
- Paths/tracks: path, track, cycleway and footway.
- Road share: state road, road and street; this does not measure traffic.
- Paved: paved, asphalt, concrete, legacy cobblestone, metal and paving stones.
- Missing surface data is unknown; absent extras do not become zero.
- Repeated distance estimates subsequent traversals of the same rounded geometry
edge in either direction. Different segmentation can miss repeated sections.
Crossing counts, lighting, current access and live traffic are unverified.
GPX includes the route and available elevation, without fictional timestamps.
No direct Garmin course upload or place-name search is included in this version.
## Persistence and validation
Candidates and saved routes are server-side and owner-scoped. Other users,
including friends, cannot view, mutate or export them. Save and delete use CSRF
protection. Unsaved candidates older than one day are cleaned on the owner's next
generation. The library lists the latest 100 saved routes. Generation is limited
to six requests/minute and 60/day using the existing limiter storage configuration.
Offline tests mock ORS and cover input validation, ranking, point-to-point and
round-trip request shapes, deduplication, missing attributes, repeated sections,
CSRF, ownership, save/delete/export and draft cleanup. Live ORS verification needs
a configured API key; mocked tests do not establish live provider availability.
Live local verification on 2026-10-03 used public Centennial Park test coordinates.
Loop and point-to-point routing each returned three distinct candidates; private
save, reload and GPX export succeeded in a disposable in-memory test database.
The best loop was 5,030.9 m with 61.5 m ascent and 90% mapped paths/footways.
The local browser rendered the real route, quality figures and elevation profile
without console errors. The suite passed 129 tests. Live provider category IDs
arrive as decimal numbers; a regression test covers that response format.
Provider references:
https://giscience.github.io/openrouteservice/api-reference/endpoints/directions/extra-info/
https://giscience.github.io/openrouteservice/api-reference/endpoints/directions/extra-info/waytype
https://giscience.github.io/openrouteservice/api-reference/endpoints/directions/extra-info/surface