Live Production Link: https://gpx.eole.me/
Author: Eole <[EMAIL_ADDRESS]>
Reach the finish line of your ultra with a map that guides you to every checkpoint.
A high-performance, responsive frontend utility designed for ultra-trail runners. It parses UTMB race tables (either fetched from UTMB URLs or pasted as raw text/HTML), visualizes GPX routes on an interactive Leaflet map, displays a custom-rendered elevation profile, and merges checkpoints/aid stations directly into the GPX track as Garmin-compliant Course Points (<coursepoint>).
This ensures your Garmin Fenix, Forerunner, or Enduro device displays exact distances, elevation data, and customized icons (like food/water/medical stations) directly inside your navigation screens.
- 🔗 UTMB Race Integration:
- Fetch course tables and GPX files directly from
montblanc.utmb.worldorlive.utmb.worldURLs. - Robust fallback parser for copy-pasted HTML tables or raw schedule text.
- Fetch course tables and GPX files directly from
- 🗺️ Interactive Mapping & Visualization:
- Real-time route and aid station plotting on a Leaflet-powered map.
- Custom canvas elevation profile chart with interactive cross-hair hover tracking.
- ⌚ Garmin Device Optimization:
- Length Limits: Truncate POI names to 10 characters (older Fenix/Forerunner models) or 15 characters (newer models) to prevent cutoffs.
- Shorten Abbreviations: Automatically convert long French/English terms (e.g., "Ravitaillement" →
RAV, "Point d'eau" →EAU, "Secteur difficile" →DIFF). - Elevation Tagging: Optionally append station altitudes directly into the POI name (e.g.,
RAV 1025m). - Snapping Algorithm: Automatically snaps checkpoint coordinates to the closest point along the GPX track within a configurable threshold (in meters).
- 🗂️ Standardized Garmin Icons:
- Map checkpoints to native Garmin waypoint types (Aid Station, Water, First Aid, Summit, etc.) for clean on-device symbol rendering.
- ⚡ Production Grade Architecture:
- FastAPI backend serving the Leaflet map frontend and secure in-memory GPX/TCX merging.
- Containerized using Python, optimized for zero-disk operations and SSRF safe external downloads.
trail-mapper/
├── docker/
│ ├── Dockerfile # Production FastAPI container build
│ ├── docker-compose.yml # Local dev compose (hot-reloads code)
│ └── docker-compose.prod.yml # Production compose behind Traefik
├── public/ # Web frontend (Leaflet maps, ES Modules)
│ ├── index.html # HTML5 markup structure
│ ├── styles.css # Premium dark/light styles
│ ├── app.js # Core JS orchestrator
│ ├── state.js # Global reactive state & persistence
│ ├── translations.js # EN/FR text & symbol translations
│ ├── map-utils.js # Leaflet map styling & snapping logic
│ ├── elevation-chart.js # Canvas altimetric profile
│ └── utils.js # Garmin name cleanups & formatting
├── garmin_course_injector.py # Core waypoint calibration engine + CLI utility
├── server.py # FastAPI backend server
├── requirements.txt # Python backend package dependencies
├── Makefile # Dev env controls & VPS deployment commands
├── package.json # Project metadata & scripts
├── architecture.md # Design & component layout (see link below)
├── TODO.md # Active development roadmap (see link below)
└── README.md # Documentation (this file)
For more detailed technical components, check out the Architecture Overview and the Development Roadmap & TODOs.
For terminal speed and local scripting, you can run the core engine directly as a CLI tool without starting the web server.
# General help
python garmin_course_injector.py --help
# Run with defaults
python garmin_course_injector.py -i public/valdaran-cdh.gpx -d 110.4
# Download GPX and Google Sheets CSV directly
python garmin_course_injector.py \
--input-url "https://example.com/route.gpx" \
--stations-url "https://docs.google.com/spreadsheets/d/e/2PACX-.../pub?output=csv"The development environment is containerized and runs on Port 3040.
- Docker Desktop
- Doppler CLI (optional, for syncing environment secrets)
- Configure and sync secrets (falls back to
.env.exampleif Doppler isn't used):make configure
- Start the local Docker containers:
make dev
- Access the application:
Open http://localhost:3040/trail-mapper/ in your browser. Live reloading is supported via volume mounts mapping to the
public/directory.
- Stop containers:
make down - Restart environment:
make restart
Deployments are automated through a combination of GitHub Actions and the local Makefile.
-
Automated Builds: Pushing to the
mainbranch triggers a GitHub Actions workflow that builds the Docker image and publishes it to the GitHub Container Registry (GHCR) asghcr.io/gnueole/eoleme-trail-mapper:latest. -
VPS Deployment: Trigger the pull-and-recreate sequence on the production VPS (
eole.me):make deploy
Note: This downloads environment variables from Doppler (
config: prd), updates thedocker-compose.prod.ymlconfiguration on the server, stops conflicting containers, pulls the latest image from GHCR, and starts the container behind Traefik. -
Combined Push & Deploy: To commit, push, wait for the GitHub build to complete, and automatically trigger the VPS redeployment:
make deploy-delay
-
Monitoring logs:
make checklogs
This project is licensed under the MIT License - see the LICENSE file for details.

