- JavaScript 76.9%
- HTML 12.9%
- CSS 10.2%
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> |
||
|---|---|---|
| public | ||
| src | ||
| .gitignore | ||
| CONCEPT.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| server.js | ||
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 thecycling-regular,cycling-road, andcycling-mountainprofiles enabled and be built withelevation: true(SRTM), or generation and ascent targeting will fail. Raiseors.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: Road → cycling-road, Mixed → cycling-regular (favors bike infrastructure), Gravel → cycling-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
- Set a starting point — search an address, drop a pin, or hit "Use home" (set your home in Settings ⚙️).
- Pick loop or point-to-point (with a destination).
- Optionally pick a preset to pre-fill distance/elevation/preferences — still editable per-ride.
- Enter target distance and elevation gain, tweak road preferences if needed.
- Generate. Review the route on the map and the elevation profile below it (hover to see position on the map).
- 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.
- 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.