Local-first ride viewer β import, browse, tag, replay, compare and analyse your tours. TourGaze turns the FIT / GPX / TCX / KMZ files from your bike computer, watch or phone into a searchable, taggable library of rides β with a cinematic map replay, advanced per-ride analytics, a ghost-chase compare mode, geo-matched photos, and a JIRA-style faceted search across everything. It all runs on your machine; your data never leaves it.
π Overview & screenshots: tourgaze.github.io
The library, the map and the cinematic replay β one screen. Click any shot to open it full size.
License: AGPL-3.0 Β· No account, no cloud, no telemetry.
- Import FIT, GPX, TCX and KMZ/KML (OpenTracks) β drop into the inbox, or
let the Garmin device auto-scan pick up new
.fitfiles. Import pre-fills a sensible activity type and guesses the gear from your past similar rides. - Browse & filter with a faceted mini-language:
sport:cycling year:2014..2018 gear:"Race Bike" tag:alpine dist:>50. - Hierarchical tags (a parent matches all descendants), drag-to-apply, recursive filtering.
- Gear (bikes / shoes / β¦) with per-gear stats and bulk-assign across filtered rides.
- Cinematic replay β drone / helicopter / follow / top-down cameras (precomputed, buttery-smooth path; the drone steers by your general direction), with a rider marker, HR/slope-coloured track, photo fades β and a little rain animation when you ride through a detected shower.
- Ride events β typed moments pinned on the replay map: rain showers are auto-detected on import from the weather along your track, and you add your own (drink break, puncture, viewpoint, β¦) by clicking the map. Event kinds are user-managed masterdata (name + icon + colour) in Settings.
- Custom attributes β annotate any ride with free-form key/values (stored as JSON), editable in an Events tab on the ride.
- Elite stats β best efforts, climbing & VAM, gradient distribution, HR zones, training load (TRIMP), aerobic decoupling, estimated cycling power (NP / VI) and calorie burn (mechanical work when power is present, otherwise a Keytel heart-rate estimate).
- Raw data view β every channel a ride recorded (elevation, HR, speed, cadence, power) as per-channel charts and a virtualised table of all samples, with the ride's summary totals; the per-ride sensor set is detected on import. Reached from the Raw link in the viewer.
- Compare (ghost-chase) β finds rides on the same route (GPS overlap, of a comparable length) and races them: a live HUD (distance / time / HR gap) and a Mario-Kart off-screen arrow when a ghost leaves the viewport.
- Markers β global points of interest (food, viewpoint, peak, β¦) with categories and an editable description, shown on the map and a filterable list with click-to-jump (a ride only shows the markers near its route).
- Photos & video β drop your own (geo-matched by EXIF, shown as map pins, faded in during replay, in a gallery), or auto-discover Creative-Commons photos along the route from Wikimedia Commons. Public vs personal are clearly labelled; big uploads are downscaled (EXIF preserved).
- Dashboard β distance / elevation / time by year, sport and gear, with motor-assisted (e-bike) rides kept separate from your human-powered totals.
- Custom basemaps β raster XYZ or vector styles, managed in Settings.
- Backup & recovery β everything is local; per-ride
*.metadata.jsonsidecars are written on every change, and a one-click ZIP export recreates the original dropped files.
Grab a build from the latest release β no build tools required:
-
Windows / Linux portable (
*-windows-portable.zip,*-linux.tar.gz) β a self-contained app image with its own bundled Java runtime (no Java, no Node needed). Unpack and launchTourGaze(TourGaze.exeon Windows). It starts in the background β no console window β shows a brief splash, opens your browser at http://localhost:8085, and puts a goat icon in your system tray:- Open TourGaze β reopen the app in your browser
- Show Logs β live log viewer (handy now that there's no console)
- Quit TourGaze β shut it down cleanly
System tray: Windows & macOS. On Linux it's best-effort β shown on KDE / XFCE / MATE / Cinnamon; on GNOME (which dropped the legacy tray) the app still runs fine, just use the browser tab. All data lives under
~/.tourgaze/. -
Headless JAR (
*-headless.jar) β for a server / NAS, or if you prefer a terminal. Needs only a Java 25 runtime (JRE); runs in the foreground (Ctrl-C to stop), no tray:java -jar TourGaze-*-headless.jar # β http://localhost:8085
Or run it with Docker (below).
Open the app, complete the first-run setup to create your rider profile, then
drop a .fit / .gpx / .tcx / .kmz into ~/.tourgaze/inbox/ (or upload from
the Inbox view) and click Import.
Prerequisites (source build only): JDK 25+, Node 20+.
# Backend β http://localhost:8085
cd server && mvn spring-boot:run
# Frontend β http://localhost:5173 (dev server, proxies /api β 8085)
cd frontend && npm install && npm run devOpen http://localhost:5173 and complete the first-run setup as above.
TourGaze ships as a single container β the API and the web UI are served
together on port 8085. State (H2 DB, media, cache, tiles) lives under a
/data volume.
Pull the published mschwehl/tourgaze image and run it:
docker pull mschwehl/tourgaze
docker run -d --name tourgaze -p 8085:8085 -v tourgaze-data:/data mschwehl/tourgaze
# β open http://localhost:8085That mounts a named tourgaze-data volume (swap for a bind mount
-v "$PWD/data:/data" to keep the library host-visible). To encrypt the store
at rest β so the volume can safely sync to a cloud folder β add
-e TOURGAZE_STORE_CRYPTOR=AES-GCM -e TOURGAZE_STORE_PASSWORD=β¦ -e TOURGAZE_STORE_SALT=β¦;
lose the password or salt and the data is unrecoverable.
Or build the image from source (for development):
# from the project root: build the SPA + jar, then the image
cd frontend && npm ci && npm run build && cd ..
mvn -f server/pom.xml clean package -DskipTests
docker compose -f infra/docker-compose.yml up --build # β http://localhost:8085A production chart lives in helm/tourgaze/ (Deployment + Service + Ingress +
persistent volume):
helm install tourgaze ./helm/tourgaze \
--set ingress.hosts[0].host=tourgaze.example.com \
--set persistence.storageClass=<your-storage-class>Single replica only β the H2 file DB isn't horizontally scalable. The default ingress allows 100 MB uploads (FIT/GPX/KMZ + photos). See README-dev.md for the full knobs.
Everything lives under ~/.tourgaze/ (override with TOURGAZE_DATA_DIR):
~/.tourgaze/
repository/ the precious, cloud-syncable library β the only folder worth backing up
store/ original ride files + <name>_media/ photos + <name>.metadata.json sidecars
db-backup/ rotating H2 snapshot zips (newest 5 kept)
db/ H2 database (ride metadata, gear, tags, markers β rebuildable from the sidecars)
cache/ derived track / chart JSON (regenerable)
tiles/ cached map tiles (re-downloadable)
Point repository/ at a cloud-synced folder to back it up off-machine β set
TOURGAZE_REPOSITORY_DIR (e.g. a Dropbox / Google Drive path); everything else
stays local and rebuilds from the sidecars.
No accounts, no cloud, no telemetry. Map tiles are fetched once and cached locally.
β οΈ No authentication. TourGaze is a single-user, local-first app β it has no login. Run it onlocalhostor a trusted LAN. Do not expose it directly to the internet; if you must, put it behind a reverse proxy that adds authentication (and TLS). The library can be encrypted at rest (AES-GCM, opt-in) so the store can safely sync to a cloud folder β see the Docker section.
Vue 3 + Vite + TypeScript Β· Spring Boot 3 (Java 25) Β· H2 Β· MapLibre GL Β· ECharts Β· TanStack Query Β· Tailwind Β· Garmin FIT SDK Β· jpx (GPX) Β· GeographicLib (distance) Β· metadata-extractor + commons-imaging (EXIF) Β· MapStruct Β· Lucide.
Map data Β© OpenStreetMap contributors; basemap styles Β© CARTO / Esri / OpenFreeMap; discovered photos Β© their authors via Wikimedia Commons (CC); weather from Open-Meteo; reverse geocoding from Nominatim. Full credits on the in-app About page.
Developer guide: README-dev.md. See also CONTRIBUTING.md and CHANGELOG.md.
A file won't import or looks wrong? Drop a copy in tourgaze/tourgaze-testdata so we can reproduce and fix it.
GNU AGPL-3.0 Β© TourGaze contributors. If you run a modified version as a network service, you must offer your users the corresponding source.
The Garmin FIT SDK that TourGaze uses to read .fit files is distributed under
Garmin's own proprietary FIT Protocol License, which is not GPL-compatible. To
remove any doubt about combining it with AGPL-licensed code:
As a special exception, the copyright holders of TourGaze grant you, under section 7 of the GNU Affero General Public License version 3, additional permission to combine or link TourGaze with the Garmin FIT SDK (the "FIT SDK") and to convey the resulting work. The GNU AGPL v3 continues to govern the TourGaze portions; the FIT SDK remains governed by its own license. This permission applies only to the combination with the FIT SDK and does not otherwise limit the AGPL.
If you fork TourGaze, keep this exception (or replace the FIT SDK) so the combined work stays distributable.