Thanks to visit codestin.com
Credit goes to github.com

Skip to content

vincentchalamon/bike-trip-planner

Bike Trip Planner

Plan your bikepacking adventures with confidence.

Paste a Komoot URL or upload a GPX file, and get a structured day-by-day roadbook
with smart pacing, safety alerts, and accommodation suggestions.

License PHP 8.5 Symfony 8 Next.js 16 React 19 TypeScript API Platform 4.3 Docker


Screenshots

Desktop — Split view with day-by-day timeline, contextual alerts, and interactive map.

Desktop - Split view

Mobile — Responsive timeline with weather, difficulty badge, and supply points.

Mobile - Timeline


Features

Import your route in seconds — Paste a link from Komoot, Strava, or RideWithGPS, or upload a GPX file directly. The backend fetches, parses, and processes everything asynchronously.

Smart pacing engine — Automatically distributes distance across days, accounting for cumulative fatigue and elevation gain. Configurable daily targets with a safety minimum threshold.

20+ safety & comfort alerts — A rule-based alert engine analyzes each stage for steep gradients, dangerous traffic, headwinds, surface quality, e-bike range, sunset timing, resupply gaps, and more — with three severity levels (critical, warning, nudge).

Accommodation finder — Discovers bivouac spots, refuges, and gites near each stage endpoint via OpenStreetMap, with heuristic pricing estimates.

Cultural points of interest — Detects museums, monuments, castles, viewpoints, and other attractions along the route with an "add to itinerary" action.

Real-time processing — Async workers compute your trip in parallel; live status updates stream to the browser via Mercure SSE. No page reload needed.

Multi-format export — Export enriched GPX files with waypoints for accommodation, water points, and POIs — ready for your GPS device. Download per-stage FIT files for Garmin, or generate a text roadbook summary.


Supported route sources

Platform Supported URL formats
Komoot komoot.com/[xx-xx/]tour/123 and komoot.com/[xx-xx/]collection/123
Strava strava.com/routes/123
RideWithGPS ridewithgps.com/routes/123
GPX upload Direct file upload (up to 30 MB)

Supported OSM accommodation tags

Logical type OSM query Pricing heuristic
hotel tourism=hotel €50–€120
motel tourism=motel €45–€90
guest_house tourism=guest_house €40–€80
chalet tourism=chalet €30–€70
hostel tourism=hostel €20–€35
alpine_hut tourism=alpine_hut €25–€45
camp_site tourism=camp_site €8–€25 (€8–€15 if backpack=yes or tents=yes)
wilderness_hut tourism=wilderness_hut free / donation (€0–€10)
shelter amenity=shelter + shelter_type~basic_hut|weather_shelter|lean_to free (€0)

Quick start

git clone https://github.com/vincentchalamon/bike-trip-planner.git
cd bike-trip-planner
make start-dev

The app is available at:

See Getting Started for prerequisites and detailed setup instructions.


Alert engine

The backend runs a pipeline of analyzers on each stage. Three severity levels are used:

Level Badge Description
critical critical Blocking issue requiring immediate attention
warning warning Significant issue to watch
nudge nudge Informational suggestion

Rules are executed in priority order (lower = higher priority):

Rule Priority Severity Trigger
Continuity 5 critical Gap > 500 m between consecutive stages
Continuity 5 warning Gap 100-500 m between stages
Elevation 10 warning Elevation gain > 1 200 m on a stage
Steep gradient 20 warning Sustained >= 8 % gradient over >= 500 m
Surface 20 warning Unpaved section >= 500 m (gravel, dirt, mud, grass, sand...)
Surface 20 warning OSM surface data missing on >= 30 % of ways
Traffic 20 critical Primary/trunk road without cycle infrastructure >= 500 m
Traffic 20 warning Secondary road, no cycleway, speed limit > 50 km/h
Traffic 20 nudge Secondary road, speed limit <= 50 km/h
E-bike range 20 warning Day distance > effective range (80 km - elevation / 25)
Sunset 20 warning Estimated arrival time exceeds civil twilight end at stage end point
Calendar -- nudge Stage falls on a French public holiday
Calendar -- nudge Stage falls on a Sunday (businesses may be closed)
Wind -- warning Headwind >= 25 km/h on >= 60 % of stages with weather data
Comfort -- warning Poor comfort index (< 40/100) on at least one stage
Bike shops -- nudge No repair shop within 2 km of stage midpoint (trips > 5 stages)
Bike shops -- nudge Nearby shop sells bikes but does not offer repair service
Resupply -- nudge Stage >= 40 km with no food/resupply POI along the route
Resupply -- warning All resupply POIs on the stage are closed at estimated passage time
Accommodation -- warning All detected accommodations on the stage are likely closed due to seasonality
Water points -- nudge Stretch > 30 km without a detected drinking water source
Rest day 100 nudge Every N consecutive cycling days without a rest day (default: every 3 days)
Cultural POI -- nudge Museum, monument, castle, church, viewpoint, or attraction within 500 m of route — enriched with opening hours, price and description when sourced from DataTourisme
Railway station -- nudge No train station within 10 km of a stage endpoint (emergency evacuation)
Health services -- nudge No pharmacy, hospital, or clinic within 15 km of a stage
Border crossing -- nudge Route crosses an international border (country change detected via Overpass is_in)

Terrain rules (Continuity, Elevation, Steep gradient, Surface, Traffic, E-bike range, Sunset, Rest day) implement StageAnalyzerInterface and are auto-discovered via #[AutoconfigureTag('app.stage_analyzer')]. Rules with -- priority (Calendar, Wind + Comfort, Bike shops, Resupply, Accommodation, Water points, Cultural POI, Railway station, Health services, Border crossing) are separate async Symfony Message handlers; Comfort is co-located with Wind inside AnalyzeWindHandler.


Architecture overview

Browser (Next.js 16)           PHP Backend (API Platform 4.3)
  Zustand + Immer (in-memory)    Stateless computation
  Zod validation                 GPX parsing + pacing engine
  openapi-fetch (typed)          OSM Overpass + weather APIs
  Mercure SSE (real-time)  <--   Async workers (Symfony Messenger)
                                 Redis cache + Mercure publisher

The frontend sends a trip request via REST; the backend processes it asynchronously across multiple workers and pushes status updates via Mercure SSE. PostgreSQL 18 persists trip configuration and stages; Redis handles transient computation state, Messenger transport, and external API caches.

Type safety is enforced end-to-end: PHP DTOs define the schema -> API Platform exports an OpenAPI spec -> npm run typegen generates TypeScript types -> openapi-fetch provides type-safe API calls. A schema change on the backend intentionally causes a TypeScript compilation failure.


Tech stack

Layer Technology
Backend PHP 8.5, Symfony 8, API Platform 4.3, Caddy
Frontend Next.js 16 (App Router), React 19, TypeScript (strict)
State Zustand + Immer (in-memory), Mercure SSE (real-time)
Styling Tailwind CSS
Testing PHPUnit 13 (backend), Playwright 1.58 (E2E)
Quality PHPStan level 9, PHP-CS-Fixer, ESLint, Prettier
Async Symfony Messenger, Redis transport, 5 workers
Runtime Docker (Caddy, Mercure, Redis, PostgreSQL, Node)

Documentation

Document Description
Getting Started Requirements, installation, and local setup
Contributing Development workflow, standards, and tooling
Architecture Decisions 26 ADRs explaining every major technical choice
Claude Code Tooling MCP servers, hooks, and skills for AI-assisted development

External data sources

Source Role Licence Coverage Prerequisite
OpenStreetMap Primary: roads, bike infra, water points, bike shops, resupply, base POIs & accommodations ODbL Global None
DataTourisme Complementary: enriched accommodations and cultural POIs; exclusive: dated events Licence Ouverte 2.0 France DATATOURISME_API_KEY
Wikidata Cross-cutting enricher: multilingual descriptions, images, Wikipedia links via Q-IDs CC0 Europe None
data.gouv.fr Weekly recurring markets (offline import) Licence Ouverte 2.0 France make markets-import

OpenStreetMap

All geographic and infrastructure data is sourced from OpenStreetMap via the public Overpass API. OSM data is cached in Redis for 24 hours per query.

Licence: ODbL 1.0 — attribution required: "© OpenStreetMap contributors".

DataTourisme

DataTourisme provides enriched POI data (accommodations, cultural sites, dated events) for France. It is used as an optional supplementary source alongside OpenStreetMap.

Licence: Licence Ouverte 2.0 Etalab — commercial use and modification permitted; attribution required.

Quota: 1 000 requests/hour, ~10 req/s sustained. Rate limiting is enforced server-side via a fixed_window limiter.

Registration: https://www.datatourisme.fr/ — free sign-up, personal API key delivered by email.

To enable DataTourisme integration, set the following environment variables:

DATATOURISME_API_KEY=your-api-key
DATATOURISME_ENABLED=true

When DATATOURISME_ENABLED=false (the default) or the API key is absent, all DataTourisme calls are skipped and the application falls back to OpenStreetMap data exclusively.

Wikidata

Wikidata enriches POI, accommodation, and event data already returned by other sources that carry a Wikidata Q-ID (via OSM tag wikidata=Q12345 or DataTourisme property owl:sameAs). Coverage is European. Licence is CC0 — no attribution required.

Fields added: multilingual description (FR/EN/DE/ES/IT), Wikimedia Commons thumbnail, Wikipedia article link, official website, and structured opening hours when available.

Configuration (optional):

WIKIDATA_USER_AGENT=BikeTripPlanner/1.0 ([email protected])

Wikidata is always enabled. Results are cached in Redis for 7 days. Errors (timeout, 5xx) are handled silently — the application continues without enrichment.

data.gouv.fr — Weekly markets

The weekly market dataset from data.gouv.fr is imported into the PostgreSQL market table via a one-time (or periodic) CLI command. Markets are included automatically in the event scan for each stage.

make markets-import

Options available via bin/console app:markets:import:

Option Description
--dry-run Prints statistics without writing to the database
--limit N Limits the number of rows processed (debug / CI)

The environment variable MARKETS_DATASET_URL can override the dataset URL.


Contributing

Contributions are welcome! Please read the Contributing Guide before submitting a pull request.

make start-dev    # Boot Docker environment
make qa           # Run full QA suite (linting, static analysis, formatting)
make test         # Run all tests (QA + PHPUnit + Playwright)

License

This project is licensed under the GNU Affero General Public License v3.0.

About

A bike trip planner. Paste a Komoot URL, get a structured day-by-day roadbook with pacing, elevation alerts, accommodation suggestions, and a shareable content.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Contributors