Interactive Delhi Metro route planning with station search, fare and time estimates, animated map playback, journey timelines, and PNG/MP4 exports.
Open the web app | Download for Android
- Search Delhi Metro origin and destination stations.
- View route fare, stop count, estimated travel time, interchanges, and line direction.
- Follow the metro on the SVG map with smooth or station-by-station animation.
- Choose route camera zoom levels for map playback and video export.
- Export the journey timeline as PNG.
- Export the animated metro route as MP4 using Mediabunny.
Routing is implemented in src/utils/routePlanner.ts as a weighted graph over station IDs.
src/data/stations-lite.jsonprovides station IDs, names, and coordinates.src/data/edge.jsonprovides the metro links between stations, line colors, and SVG path fragments used to draw each segment.- Each station is added as a graph node.
- Each edge is weighted by the haversine distance between station coordinates. If coordinates are missing or invalid, the edge falls back to
1. - Airport Express edges use a reduced routing weight so the faster express line can compete correctly with regular-distance routes.
- Rapid Metro loop links are selectively directed where the service pattern needs one-way traversal.
buildRoutes(from, to, language, limit)returns up to three route options. The first option is the shortest weighted path; alternate options are explored from a distance-sorted queue and capped to keep search responsive.- Route options are enriched with display names, stops, interchanges, distance, fare, holiday fare, fare type, time limit, estimated journey minutes, and one combined SVG path.
flowchart TD
A[Station search selects from/to IDs] --> B[buildRoutes]
B --> C[WeightedGraph]
D[stations-lite.json] --> C
E[edge.json] --> C
C --> F[Find shortest route options]
F --> G[Build RouteSummary]
G --> H[Detect interchanges from line-color changes]
G --> I[Calculate distance, fare, time, limits]
F --> J[Join edge SVG path fragments]
J --> K[RoutePlan: svgPath + route metadata]
K --> L[Zustand path store]
L --> M[Map, timeline, route cards, share panel]
The fare calculation follows two paths:
- Regular routes use distance slabs in
estimateFare(), with a lower holiday fare. - Airport Express routes split Airport Line fare from regular distance fare, using the explicit
AIRPORT_LINE_FAREStable for express-only segments.
Route sorting in the UI is separate from path finding. The planner can sort returned options by lowest interchange count first, or by lowest stop count first.
The animated route is rendered in src/components/graphsvg.tsx. The route planner returns a single SVG d string, and the map uses that path as the rail for both the visible route highlight and the animated train.
sequenceDiagram
participant UI as Search / Route option
participant Store as Zustand stores
participant Map as graphsvg.tsx
participant GSAP as GSAP timeline
participant SVG as SVG path API
UI->>Store: setRoute(svgPath, route)
Store->>Map: path, routeStationIds, play, animationMode
Map->>SVG: create path measure and getTotalLength()
Map->>SVG: find station progress points along path
Map->>GSAP: animate proxy.progress
GSAP->>Map: onUpdate(progress)
Map->>SVG: getPointAtLength(progress * length)
Map->>Map: translate/rotate train and move camera
GSAP->>Store: onComplete setPlay(false)
The train is not animated by directly tweening SVG transforms. GSAP animates a small proxy object:
const proxy = { progress: 0 };On each GSAP update, the current progress is converted into a real SVG point with getPointAtLength(). The next point on the path is sampled to compute the train's rotation angle, then the train group receives a translate(...) rotate(...) transform. The same progress value drives the camera transform, keeping the train centered during cinematic playback.
There are two route playback modes:
- Smooth mode animates continuously from the first route stop to the last stop with a
power1.inOutease. - Step mode creates one timeline segment per station pair, uses
power2.inOut, updates the active station at each stop, and adds a short dwell between stations.
flowchart LR
A[Route SVG path] --> B[Hidden measure path]
B --> C[getTotalLength]
C --> D[Station progress lookup]
D --> E{Animation mode}
E -->|smooth| F[One GSAP tween: first stop to last stop]
E -->|step| G[GSAP timeline: segment, active station, dwell]
F --> H[setCameraForProgress]
G --> H
H --> I[getPointAtLength]
I --> J[Train translate + rotate]
I --> K[Camera scale + translate]
Video export reuses the same route math. Instead of depending on live GSAP playback, export code calculates the expected progress for each frame, renders the SVG into a canvas, and writes the frames with Mediabunny.
The Android app is published on Google Play as a Trusted Web Activity (TWA):
- Package ID:
in.coolhead.metroroute - Play Store: https://play.google.com/store/apps/details?id=in.coolhead.metroroute&hl=en_IN
- Production host:
metro.coolhead.in - Start URL:
/
This repository includes the Bubblewrap-generated Android wrapper for the web app. Bubblewrap reads twa-manifest.json and creates the native Android project that launches https://metro.coolhead.in/ as a Trusted Web Activity.
The important files are:
twa-manifest.json: TWA configuration, package ID, host, icons, theme colors, version codes, and signing key metadata.app/: generated Android app module.build.gradle,settings.gradle,gradle/,gradlew: Android build files generated/used by Bubblewrap and Gradle.app-release-bundle.aaband signed APK artifacts: release outputs generated from the Android project.
Typical Bubblewrap flow:
npx @bubblewrap/cli init --manifest https://metro.coolhead.in/manifest.json
npx @bubblewrap/cli update
npx @bubblewrap/cli buildUse npx @bubblewrap/cli update after changing the web manifest, app metadata, icons, theme colors, Android version codes, or other TWA settings. Build and publish the generated .aab through the Google Play Console.
- React 19
- TypeScript
- Vite
- Tailwind CSS
- GSAP
- Zustand
- Mediabunny
- html-to-image
pnpm install
pnpm run devBuild for production:
pnpm run buildPreview the production build:
pnpm run previewpnpm run lint
pnpm run generate:seo
pnpm run update:labels
pnpm run update:gatesContributions are welcome. Please keep changes focused and easy to review.
- Fork the repository and create a feature branch from
main. - Install dependencies with
pnpm install. - Run the app locally with
pnpm run dev. - Make your change using the existing React, TypeScript, Tailwind CSS, and Zustand patterns.
- Run
pnpm run lintandpnpm run buildbefore opening a pull request. - Include a clear PR description with the problem, solution, screenshots or screen recordings for UI changes, and any known limitations.
For data updates, prefer the existing scripts in scripts/ over manual edits when possible. For Android changes, update twa-manifest.json first, then regenerate the Bubblewrap project so the native wrapper stays aligned with the deployed web app.
This project is licensed under the Apache License 2.0. See package.json for the current package license metadata.
