Inspiration
Every GTA commuter knows the feeling: you're standing at Finch Station watching the delay counter tick up, wondering if you should've just driven. Or you're stuck on the 401, wishing you'd taken the subway. The problem isn't a lack of transit options — it's that no single tool compares them honestly.
Google Maps tells you the fastest route. Transit apps show you schedules. Neither tells you the real cost of driving downtown with parking, or how stressed you'll be after three transfers in a snowstorm. We wanted to build the app we wished existed — one that compares every way to get from A to B and lets you choose based on what actually matters to you: time, money, or peace of mind.
What it does
FluxRoute is a multimodal routing app for the Greater Toronto Area that compares transit, driving, walking, and hybrid (park & ride) routes side-by-side. Every route is scored across three dimensions:
- Fastest — minimum travel time
- Thrifty — lowest total cost (fares, gas, parking, PRESTO discounts)
- Zen — lowest stress score, factoring in transfers, delays, traffic, weather, and road closures
Under the hood, an XGBoost machine learning model trained on real TTC subway delay data predicts how likely each transit leg is to be delayed — by line, station, time of day, and season. These predictions feed directly into route scoring.
The app also features:
- 5-agency transit routing across TTC, GO Transit, YRT, MiWay, and UP Express via OpenTripPlanner
- Real-time traffic with Mapbox congestion data color-coding driving segments
- Live TTC vehicle tracking with 15-second position updates on the map
- Service alerts and road closures from the City of Toronto, integrated into stress scoring
- AI chat assistant powered by Google Gemini with tool use — ask it “Should I drive or take the subway to Union at 5pm?” and it checks delays, weather, and traffic before answering
- GPS-style turn-by-turn navigation for driving and walking
- Toggleable map layers for transit lines, vehicles, and traffic
- Weather-aware routing via Open-Meteo, factored into delay predictions
How we built it
Backend — FastAPI + Python 3.12
The backend is a FastAPI server with a route engine that orchestrates multiple data sources. When a user requests routes, the engine:
- Queries OpenTripPlanner (self-hosted, built from 5 GTFS feeds + Ontario OSM data) for multi-agency transit routes
- Calls the Mapbox Directions API with the driving-traffic profile for driving routes with congestion annotations
- Runs the XGBoost delay predictor on each transit segment
- Calculates costs including multi-agency PRESTO co-fare discounts
- Computes a stress score weighted by transfers, delay probability, traffic congestion, weather, and road closures
- Returns ranked routes to the frontend
The ML pipeline uses feature_engineering.py to extract temporal features (hour, day of week, season) from TTC's historical subway delay CSV, then trains an XGBoost classifier + regressor. If the model file isn't available, it falls back to a hand-tuned heuristic based on real TTC delay patterns.
Stress score calculation
stress = ( 0.1 * transfers + 0.3 * delay_probability + 0.2 * weather_penalty + 0.15 * traffic_congestion + 0.05 * walking_distance_km + 0.15 * driving_base_stress )
Frontend — Next.js 14 + TypeScript + Mapbox GL JS
The frontend is a Next.js 14 app using the App Router with a glassmorphism dark theme. The map renders congestion-colored route polylines, live vehicle markers, transit line overlays with per-line toggle controls, and isochrone reachability polygons. State management uses custom React hooks (useRoutes, useChat, useNavigation) to keep things clean.
Infrastructure
OpenTripPlanner 2.5 runs as a self-hosted Java service, ingesting GTFS data from all 5 agencies plus an 890MB Ontario street network. Large data files are tracked via Git LFS. The entire stack runs locally, with Docker Compose available for OTP.
Challenges we ran into
- The OTP graph build takes 15–20 minutes. With 5 GTFS feeds and an 890MB OSM file, every restart meant waiting. We solved this by caching the built graph and adding a GTFS-only fallback, so the app works instantly even without OTP running.
- Mapbox congestion annotations are inconsistent. Some road segments return congestion data, others don't. We built a rush-hour heuristic fallback (7–9am, 4–7pm penalty) that kicks in when real congestion data is unavailable, so stress scores are always meaningful.
- GTFS-RT feeds go down unpredictably. The TTC's real-time vehicle position feed would sometimes return empty responses or time out. We implemented a mock data generator that places realistic vehicle positions along known subway lines, so the UI always looks complete during demos.
- Keeping the frontend responsive with 700+ modules. The map page loads Mapbox GL JS, geocoder, multiple overlay layers, vehicle polling, and alert updates. We used ResizeObserver, careful useEffect dependency management, and Mapbox setFilter() instead of recreating GeoJSON sources to keep layer toggling instant.
- Multi-agency fare calculation is surprisingly complex. TTC has flat fares, GO Transit is distance-based, and PRESTO offers co-fare discounts when transferring between agencies within a time window. We encoded all of these rules into the cost calculator.
Accomplishments that we're proud of
- The Decision Matrix genuinely changes which route you'd pick. The “Fastest” route is often not the “Zen” route — seeing that tradeoff visualized is powerful.
- Every external service has a fallback. OTP down? GTFS fallback. No ML model? Heuristic. Weather API fails? Default Toronto winter. The app always works.
- The XGBoost delay model produces meaningful predictions — Line 1 during Monday rush hour correctly shows ~65% delay probability, while Line 4 on weekends shows ~8%.
- The AI chat assistant actually uses tools — it can check live delays, pull weather data, and reason about routes before responding, not just generate text.
- 5 transit agencies routed through a single interface with accurate inter-agency transfer costs.
What we learned
- OpenTripPlanner is incredibly powerful but has a steep setup curve. The build-config.json and router-config.json files need precise tuning for multi-agency setups.
- Real-time data is messy. GTFS-RT, traffic APIs, and weather feeds all have different reliability profiles. Building resilient fallbacks isn't optional — it's the feature.
- Stress scoring is subjective but useful. Our weighted formula is heuristic-based, not ML-derived, but user testing showed people consistently preferred the “Zen” route on bad weather days.
- Hackathon code benefits from strong types. Using Pydantic v2 on the backend and strict TypeScript on the frontend caught dozens of integration bugs before they reached the browser.
What's next for FluxRoute
- Real-time schedule adjustments — feed GTFS-RT trip updates into OTP for live-adjusted ETAs
- Brampton Transit as a 6th agency
- Bike Share Toronto integration for first/last mile options
- User preferences — avoid transfers, prefer subway, accessibility filtering
- Carbon footprint comparison across modes
- Historical reliability tracking — learn which routes are consistently late
Built With
- city-of-toronto-open-data
- docker
- fastapi
- git-lfs
- google-gemini
- google-generativeai
- gtfs
- gtfs-realtime-bindings
- gtfs-rt
- httpx
- javascript
- joblib
- lucide-react
- mapbox-directions-api
- mapbox-geocoding-api
- mapbox-gl-js
- next.js-14
- node.js
- open-meteo-weather-api
- openstreetmap
- opentripplanner-2.5
- pandas
- protobuf
- pydantic-v2
- python
- react-18
- scikit-learn
- tailwind-css
- ttc-gtfs-rt
- typescript
- uvicorn
- xgboost
Log in or sign up for Devpost to join the conversation.