Generate cycling routes using parameters.
  • JavaScript 76.9%
  • HTML 12.9%
  • CSS 10.2%
Find a file
Joshua Renker c4f6da40d2 Show gradient instead of elevation in the profile chart
The profile now plots percent grade around a zero baseline in the
original single-color style, with corner labels for max/min gradient.
Elevation smoothed over +/-75 m and gradient taken as a centered
difference over +/-100 m, since raw SRTM point-to-point slopes are
noise. Hover tooltip shows distance, gradient, and elevation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:31:50 -04:00
public Show gradient instead of elevation in the profile chart 2026-07-02 20:31:50 -04:00
src Fix silent preset/save loss on Enter; make regeneration produce new routes 2026-07-02 13:43:50 -04:00
.gitignore Initial implementation of constraint-first cycling route generator 2026-07-01 20:55:21 -04:00
CONCEPT.md Initial implementation of constraint-first cycling route generator 2026-07-01 20:55:21 -04:00
package-lock.json Initial implementation of constraint-first cycling route generator 2026-07-01 20:55:21 -04:00
package.json Initial implementation of constraint-first cycling route generator 2026-07-01 20:55:21 -04:00
README.md Replace round-trip routing with terrain-aware ring search; fix ascent inflation 2026-07-02 12:59:10 -04:00
server.js Fix silent preset/save loss on Enter; make regeneration produce new routes 2026-07-02 13:43:50 -04:00

Cycling Route Generator

Constraint-first cycling route generation: you say what you want (distance, elevation gain, road preferences), it finds the path. Self-hosted, single-user. See CONCEPT.md for the full concept.

Quick start

npm install
ORS_API_KEY=your-key npm start   # real routing via openrouteservice.org
# or, with no key — demo mode with synthetic routes:
npm start

Then open http://localhost:3000.

Routing providers

Real route generation uses OpenRouteService:

  • Public API — sign up for a free API key at openrouteservice.org and set ORS_API_KEY.
  • Self-hosted ORS — set ORS_BASE_URL (e.g. http://localhost:8085/ors) to point at your own instance; no key needed. The instance must have the cycling-regular, cycling-road, and cycling-mountain profiles enabled and be built with elevation: true (SRTM), or generation and ascent targeting will fail. Raise ors.endpoints.routing.maximum_distance / maximum_distance_round_trip_routes (default 100 km) if you want long routes.
  • Mock mode — with neither set (or PROVIDER=mock), the app generates plausible synthetic routes so you can explore the UI offline.

Loops are generated by placing rings of via points around the start and routing through them, probing 8 compass bearings per generation. The ring shape is matched to the requested climb rate: flat targets use thin elongated ellipses (the shape of a river-valley or rail-trail loop), climbing targets use round loops that sweep more terrain. The search then refines the best candidates — thinner rings along the flattest/hilliest bearings when the climb rate is off, and iterative radius correction until the distance lands within ~8%. "Generate Another" rotates the probe bearings for a fresh set of suggestions. Point-to-point routes add detour via-points, iteratively resized, when the direct route is shorter than your distance target.

Elevation gain is computed from route geometry with hysteresis smoothing (climbs count only once they rise 5 m above the running local minimum) — raw SRTM elevation data is noisy, and naive summing reports hundreds of feet of phantom climbing on flat rides.

Road preferences map to ORS cycling profiles: Roadcycling-road, Mixedcycling-regular (favors bike infrastructure), Gravelcycling-mountain.

Environment variables

Variable Default Purpose
PORT 3000 HTTP port
ORS_API_KEY openrouteservice.org API key
ORS_BASE_URL https://api.openrouteservice.org Self-hosted ORS instance
PROVIDER auto Force ors or mock
DATA_DIR ./data SQLite database location

Using the app

  1. Set a starting point — search an address, drop a pin, or hit "Use home" (set your home in Settings ⚙️).
  2. Pick loop or point-to-point (with a destination).
  3. Optionally pick a preset to pre-fill distance/elevation/preferences — still editable per-ride.
  4. Enter target distance and elevation gain, tweak road preferences if needed.
  5. Generate. Review the route on the map and the elevation profile below it (hover to see position on the map).
  6. Refine: click the route line to add a drag handle, drag handles to reshape (the route re-solves through them), right-click a handle to remove it. Or hit Generate Another.
  7. Save it (re-loadable from the Saved Routes tab, with all parameters) and/or Export GPX for your bike computer or Strava.

Settings hold your home address, units (mi/ft or km/m), default road preferences, and preset management.

Stack

  • Backend: Node.js + Express, SQLite (better-sqlite3) for settings/presets/saved routes, GPX export, Nominatim geocoding proxy.
  • Frontend: vanilla JS + Leaflet (OSM tiles), hand-rolled SVG elevation profile. No build step.