# SeasonRoute — Travel Smart. Every Season.

An app-like, single-page site for checking travel conditions to/from work in
Ontario (and beyond): a live map (standard/satellite, live traffic), weather
with wind/snow/heat gradient overlays, road surface & winter-closure mapping,
weather-alert polygons, real-time-traffic turn-by-turn navigation with a
fuel-savings option, light/dark mode, and a settings panel.

## Running it

No build step. Three ways to run it:

1. **`python server.py`** (recommended) — serves the site exactly like
   `python -m http.server`, but also proxies the three Ontario 511 endpoints
   server-side at `/api511/<name>` so the Traffic Cameras / Road Conditions /
   Closures layers work without depending on any third-party CORS proxy.
   Then open `http://localhost:5500` (pass a different port as an argument
   if you like, e.g. `python server.py 8000`).
2. **`python -m http.server 5500`** — works for everything except the three
   511 Ontario layers, which will automatically fall back to a public CORS
   proxy instead (see the limitations section below).
3. **Open `index.html` directly** in a browser. Most things still work, but
   the browser Geolocation API generally requires a "secure context" and may
   refuse to run from a plain `file://` page (falls back to IP-based
   location), and the 511 layers fall back to the public proxy same as #2.

## Hosting it (Cloudflare Pages)

To use it on a phone over a mobile network you need a public **HTTPS** URL —
phones only trust geolocation (the app's whole core) over HTTPS. Deploy the
folder to Cloudflare Pages (or any static host): dashboard → Workers & Pages
→ Create → Pages → Upload assets, drag the folder, and it serves at
`https://<name>.pages.dev`. No build command — it's a static site.

The `functions/api511/[key].js` file is a **Cloudflare Pages Function** that
replaces `server.py`'s 511 proxy on the hosted site: it forwards the same
whitelisted 511on.ca endpoints from the site's own origin, so the Traffic
Cameras / Road Conditions / Closures layers work reliably (no public CORS
proxy) on Pages. Pages auto-detects `functions/` — just deploy it with the
rest of the folder, nothing to configure. `server.py` still handles the same
job for local dev.

## API keys (Settings panel)

The map, live traffic, and routing/navigation need a **free TomTom API key**
— without one, the map falls back to OpenStreetMap tiles and the Route panel
shows "Add your TomTom API key" instead of attempting to route. A
**Tomorrow.io API key** is optional and only enriches your current location's
conditions (precipitation type, feels-like) — everything else keeps using
Open-Meteo. Both are pasted into Settings → API Keys and stored only in this
browser's `localStorage`, never in the source files.

**Neither integration has been tested against a real key** (built from
TomTom/Tomorrow.io's documented request/response shapes only) — please report
anything that doesn't behave once you've added your keys.

## Layout

Click **Layers**, **Route**, or **Settings** in the toolbar to open the
corresponding slide-out panel. Click the current-conditions card on the map
to open the **Conditions** panel (weather metrics, alerts, road risk).

## What it uses

- **Map**: TomTom raster tiles (standard or satellite, toggle in Layers) once
  a TomTom key is set; falls back to OpenStreetMap tiles otherwise. The
  winter/summer + dark-mode look is a CSS filter on the tile pane, skipped
  entirely for satellite imagery.
- **Live traffic flow**: TomTom traffic-flow tiles, toggle in Layers (needs a
  TomTom key).
- **Routing & turn-by-turn navigation**: TomTom Routing API with
  `traffic=true` — Fastest, Eco (fuel-efficient), and Fewer-Highways
  (`avoid=motorways`) routes are all real routing profiles, not heuristics.
  Each route card shows the live traffic delay
  (`travelTimeInSeconds - noTrafficTravelTimeInSeconds`) and, for the Eco
  route, either TomTom's fuel-consumption estimate (if returned) or a
  qualitative "more fuel-efficient" badge. "Start Navigation" on the selected
  route opens a HUD with spoken (Web Speech API) turn-by-turn guidance and
  best-effort **stop-sign audio alerts** from OSM data (`js/navigation.js`)
  — map stays north-up, no rotation.
- **Geocoding / search**: OpenStreetMap Nominatim. Each result has a
  "🧭 Route here" button that maps to it and immediately opens + submits the
  Route panel.
- **Weather**: Open-Meteo running Environment Canada's **GEM/HRDPS model**
  (`models=gem_seamless` — HRDPS's 2.5 km grid actually resolves individual
  lake-effect squall bands, unlike the ~25 km global models; fields GEM
  doesn't publish, like visibility and UV, are backfilled from the default
  blend automatically). Temperature, feels-like, wind, humidity, snow
  depth/fall, precipitation, visibility, pressure, UV index. Fetched and
  stored in metric; the Settings unit toggle converts for display only, so
  switching units doesn't need a re-fetch.
- **Real station observations**: Environment Canada SWOB realtime reports
  (`js/observations.js`, same free api.weather.gc.ca host as the alerts) —
  when a station within 60 km has reported in the last 90 minutes, the
  "current conditions" temp/wind/humidity/pressure/visibility/snow depth are
  actual instrument readings, not model output, and the risk score runs on
  them. A "📡 Measured at …" line under the Weather metrics shows which
  station and how fresh. If a Tomorrow.io key is set, the
  current-location fetch is enriched with real-time precipitation type
  (rain/snow/sleet/freezing rain) and feels-like — capped to that single
  point/cadence to stay well inside Tomorrow.io's free 500-requests/day limit.
- **Air quality (AQHI)**: Open-Meteo's free air-quality API (PM2.5 / NO₂ /
  O₃), run through Environment Canada's published AQHI formula client-side
  (`js/airQuality.js`) — shown as a Weather metric card, with a smoke/poor-air
  callout when the index hits the High band or PM2.5 reaches wildfire-smoke
  levels.
- **Stops along route**: fuel / coffee / food / rest areas within ~2 km of
  the selected route, from OpenStreetMap via Overpass (`js/routeStops.js`).
  Crowdsourced — names/locations only, hours not verified.
- **Trip Preview** (`js/routePreview.js`): a pre-drive briefing per planned
  route — drive verdict gauge, the camera nearest the riskiest stretch
  (when risk is Moderate+), weather at departure vs arrival, the hourly
  window you'll be on the road, per-checkpoint hazards with town names,
  alert polygons the route actually crosses, and cameras spaced along the
  corridor (~one per 25 km within 10 km of the line).
- **Navigation extras**: mid-drive faster-route switching (re-checks live
  traffic every 3 min and announces when 5+ min can be saved), spoken
  hazard callouts approaching risky checkpoints, a 5-minute watch for new
  Environment Canada warnings with banner + voice, and an Aa big-text HUD
  mode for whiteout/night glances.
- **Local advisories**: 511 advisories are name-matched to your area and
  route (they carry no coordinates); the rest stay one tap away behind
  "Show all Ontario advisories".
- **AI briefing & trip Q&A (optional)**: with your own Anthropic API key in
  Settings, the dashboard gets an on-demand plain-language briefing (with
  read-aloud) and the Route panel gets an "ask about your trip" box
  (`js/ai.js`). The model only summarizes data the app already computed —
  every probability and warning still comes from the deterministic logic and
  official feeds above, and answers carry an "AI-generated" disclaimer. The
  key is stored in this browser only and sent only to api.anthropic.com.
- **Radar**: RainViewer public radar tiles on the commute map (play/pause
  animation, opacity), plus a dedicated 📡 **Radar view** (`js/radarPage.js`)
  with a full timeline scrubber, speed control, color schemes, opacity, and
  four sources: RainViewer composite (past + 30 min nowcast), Environment
  Canada's own 1 km radar via GeoMet WMS as separate **rain** and **snow**
  products (6-minute updates, with ECCC's real legend), and infrared
  satellite cloud cover.
- **Animated dashboard backdrop** (`js/weatherFx.js`): a subtle live scene
  (sun/stars/clouds/rain/snow/lightning/fog) matching current conditions —
  pure CSS animation, disabled by `prefers-reduced-motion` or the Settings
  toggle.
- **Snow Depth / Heat Index overlays**: a smooth color gradient (inverse-
  distance-weighted canvas, `js/gridWeather.js`) interpolated across a 6×6
  sample grid — approximate/sampled, not true meteorological interpolation,
  but much easier to read at a glance than discrete points. Wind stays as
  directional arrows.
- **Weather alert polygons**: Environment Canada's public `weather-alerts`
  API for Canada (real GeoJSON polygons with severity/region/bilingual text),
  and NWS alert geometry (when NWS provides one) for the US. NWS also backs
  the always-available US alert list.
- **Road surface & winter-closure overlay**: OpenStreetMap data via the
  Overpass API — draws gravel/unpaved roads, 4x4 tracks, and
  winter-closed/seasonal-access roads once you zoom in to city/township
  level. **This is crowdsourced tagging and can have gaps**, especially on
  rural back roads — treat it as a helpful hint, not an authoritative
  source, and verify with the official links in the Conditions panel before
  relying on it in winter. Stop-sign locations for navigation come from the
  same source and carry the same caveat.
- **Heat index / wind chill**: computed client-side with the standard NWS
  formulas from live temperature/humidity/wind
- **Departure-Time Advisor**: scores the next 12 hours with the same risk
  logic as current conditions and suggests a lower-risk window to leave, if
  one exists (Conditions panel)
- **Black ice heuristic**: flags refreezing risk when recent hours were wet
  (rain/melt) and the temperature has since dropped to/near freezing —
  inferred from temperature/precipitation trend, not directly observed
- **Lake-effect snow squall heuristic** (`js/roadConditions.js`): for the
  Lake Huron snowbelt corridor (Grand Bend–Kincardine–Southampton), flags
  squall-favorable setups — onshore W-NW wind off the lake plus sub-zero
  temps — even before an official Environment Canada Snow Squall
  Watch/Warning is issued. It's wind/temperature only (no free source for
  lake-surface temperature, the other key ingredient in a real lake-effect
  forecast), so treat it as an early heads-up, not a substitute for the
  official EC alert — which still shows up automatically via the Alerts
  layer/list once issued, since that feed isn't filtered by alert type.
- **Alert-triggered notifications**: opt-in browser notification (Settings)
  when a new weather alert appears or travel risk rises, checked on load and
  every 15 minutes while the tab stays open
- **Offline last-known conditions**: caches the last successful fetch so the
  app still shows something useful (with a "last updated" banner) if you
  lose signal or a request fails
- **School bus cancellations (Southwestern Ontario)**: map pins (one per
  consortium, toggle in Layers) plus a link list in the Conditions panel, for
  the Windsor-Essex, Chatham-Kent, London/Middlesex, Huron-Perth, and
  Bruce-Grey consortiums — there's no province-wide feed, so these are fixed
  pins/links to each region's own cancellation page, not live bus status.
- **Ontario 511 traffic cameras, winter road conditions, and
  closures/construction**: real live data from `511on.ca` — 900+ camera
  locations, per-segment winter road status (bare/wet/snow/ice), and live
  closure/construction events with polylines, each a toggle in the Layers
  panel. Served through the local proxy in [`server.py`](server.py)
  (same-origin, no third party involved) when you run the site that way; see
  the limitations below for what happens otherwise. Camera *images* load as
  plain `<img>` tags directly from 511on.ca either way (no proxy needed for
  those, only for the JSON location list).

## Known limitations (by design, not oversights)

- **Routing/navigation/traffic/satellite all require a TomTom key** — there's
  no free/no-key fallback for these, since accurate real-time routing
  genuinely needs a real routing engine. Without a key: map uses OpenStreetMap
  tiles, and the Route panel tells you to add a key instead of failing
  silently.
- **The fuel-savings figure is only as good as TomTom's eco-routing
  profile.** It's a real routing mode (not a wind heuristic like an earlier
  version of this app used), but TomTom only returns an exact liters figure
  when a full vehicle consumption profile is supplied — otherwise you get a
  qualitative "more fuel-efficient" badge rather than a fabricated number.
- **Stop-sign awareness is inferred from OSM tagging**, same caveat as the
  road-surface overlay — coverage gaps are possible, treat it as a helpful
  hint, not a guarantee, and always obey posted signage.
- **Road closures are not comprehensive.** Beyond the OSM-tagged seasonal
  roads and the 511 Ontario closures layer, there's no free live *nationwide*
  closures feed, so the Conditions panel also links out to the official
  511/DOT (US) or provincial (Canada) traffic site for the detected region.
- **Snow Depth / Heat Index overlays are a sampled gradient, not true
  interpolation.** They batch-query a 6×6 grid across the visible map area —
  good for a quick visual read, not precise at a specific address (use the
  Conditions panel for an exact point).
- **The 511 Ontario layers depend on how you run the site.** 511on.ca's API
  has no CORS headers, so the browser can't call it directly. Run the site
  with `python server.py` and these three layers go through its same-origin
  `/api511/` proxy — no third party involved. If you instead run
  `python -m http.server` or open `index.html` directly, `js/on511.js`
  automatically falls back to a public CORS proxy (`corsproxy.io`, then
  `api.allorigins.win`) — that's a real tradeoff in the fallback case only: a
  third party sees those specific requests, and the layer shows
  "unavailable" in its Layers panel status line if both proxies are also
  down.
- **Notifications only fire while this tab is open.** There's no service
  worker, so the app can't wake up or notify you in the background once the
  tab or browser is closed.
- **Tomorrow.io enrichment is capped to the single current-location fetch**
  (not the grid overlay, hourly outlook, or black-ice trend) specifically to
  stay well under its free tier's 500-requests/day limit.

The state/province link table and the SW Ontario bus-consortium list live in
[`js/data.js`](js/data.js) — edit it if a link goes stale, or add more
regions/countries.

## Notes / things you may want to change

- Nominatim's usage policy asks for modest request volume; searches only fire
  on submit (not on every keystroke), and Overpass/Environment Canada queries
  are debounced on map movement.
- TomTom/Tomorrow.io key handling lives in `SettingsStore.getKey/setKey`
  (`js/settings.js`) — swap in a different provider by changing the tile/
  routing/weather URLs in `js/map.js`, `js/routing.js`, and
  `enrichWithTomorrowIo` in `js/weather.js`.
