feat: prepare Phase 1 free local edition
This commit is contained in:
parent
ac927de71a
commit
57cacf92c6
34
.env.example
34
.env.example
|
|
@ -1,24 +1,22 @@
|
|||
# God's Eye View — environment variables
|
||||
# Set to 1 to block paid Google/OpenAI services while allowing free-account keys.
|
||||
GEV_FREE_ONLY=1
|
||||
# Optional Photon endpoint override (server operator configured, not user input).
|
||||
# GEV_PHOTON_URL=https://photon.komoot.io/api/
|
||||
# Conservative local traffic tile budget. Use a provider account without paid overage.
|
||||
TOMTOM_DAILY_TILE_BUDGET=5000
|
||||
# Copy to .env and fill in your keys. On macOS the launcher can also read keys
|
||||
# from the Keychain (see below); on Linux/Windows use this file or env vars.
|
||||
#
|
||||
# macOS Keychain: store any of these and ./scripts/dev-fresh.sh pulls them in.
|
||||
# Each command prompts for the secret so it never lands in shell history:
|
||||
# security add-generic-password -U -s "google-maps-api" -a "api-key" -w
|
||||
# security add-generic-password -U -s "openai-api" -a "api-key" -w
|
||||
# security add-generic-password -U -s "aisstream-api" -a "api-key" -w
|
||||
# security add-generic-password -U -s "firms-map" -a "map-key" -w
|
||||
# security add-generic-password -U -s "cesium-ion" -a "token" -w
|
||||
# security add-generic-password -U -s "tomtom-api" -a "api-key" -w
|
||||
# from the Keychain (see README); on Linux/Windows use this file or env vars.
|
||||
#
|
||||
# NOTE ON CLIENT-EXPOSED KEYS: GOOGLE_MAPS_API_KEY and CESIUM_ION_TOKEN are
|
||||
# injected into the browser bundle by design (they're used client-side) and
|
||||
# WILL be visible in devtools. Restrict/scope them rather than trying to hide
|
||||
# them (see SECURITY.md). All other keys below stay server-side.
|
||||
|
||||
# Required: Google Maps API key (Map Tiles API must be enabled).
|
||||
# Optional: Google photorealistic 3D tiles (Map Tiles API must be enabled).
|
||||
# Leave blank to start with the free OpenStreetMap globe.
|
||||
# CLIENT-EXPOSED — restrict it (HTTP referrer + API restriction) in Google Cloud.
|
||||
GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
|
||||
GOOGLE_MAPS_API_KEY=
|
||||
# Optional: opt-in per-IP rate limit for the Google Places cost endpoint
|
||||
# (/api/google/nearby-places), in requests per minute.
|
||||
# DEFAULT IS UNLIMITED — unset (or 0) means no throttling, unchanged behavior.
|
||||
|
|
@ -58,9 +56,9 @@ OPENAI_HUD_SUMMARY_MODEL=gpt-5-nano
|
|||
# GEV_RATELIMIT_OPENAI_PER_MIN=30
|
||||
|
||||
# Optional: OpenSky auth mode (oauth | basic | auto | anon)
|
||||
# Default: oauth — requires client credentials below
|
||||
# Use anonymous access for the free, no-account setup.
|
||||
# Set to "anon" to skip aircraft auth (rate-limited but works)
|
||||
OPENSKY_AUTH_MODE=oauth
|
||||
OPENSKY_AUTH_MODE=anon
|
||||
|
||||
# Optional: OpenSky OAuth credentials (from opensky-network.org account dashboard)
|
||||
OPENSKY_CLIENT_ID=
|
||||
|
|
@ -107,15 +105,15 @@ AISSTREAM_API_KEY=
|
|||
# 0 disables the silence watch entirely.
|
||||
# AISSTREAM_SILENCE_TIMEOUT_MS=120000
|
||||
|
||||
# Optional: TomTom live traffic flow (BYOK freemium — free tier is ~50,000 tile
|
||||
# requests/day, get a key at https://developer.tomtom.com). SERVER-SIDE ONLY:
|
||||
# Optional: TomTom live traffic flow (BYOK — check your current account
|
||||
# allowance at https://developer.tomtom.com). SERVER-SIDE ONLY:
|
||||
# the browser fetches same-origin /api/tomtom/* and never sees the key.
|
||||
# Keyless fallback: the traffic layer runs its built-in simulation (white dots,
|
||||
# hardcoded per-road-class speeds) — no key required for the layer to work.
|
||||
# TOMTOM_API_KEY=
|
||||
# Optional soft cap on upstream tile fetches per UTC day (default 40000).
|
||||
# Optional soft cap on upstream tile fetches per UTC day (set to 5000 above).
|
||||
# Over the cap the proxy serves cached/stale tiles instead of hitting upstream.
|
||||
# TOMTOM_DAILY_TILE_BUDGET=40000
|
||||
# TOMTOM_DAILY_TILE_BUDGET=5000
|
||||
|
||||
# Optional: CCTV layer tuning (advanced). Defaults are sensible — leave unset
|
||||
# unless you're customizing the camera source pack.
|
||||
|
|
|
|||
|
|
@ -10,3 +10,11 @@ output/
|
|||
.DS_Store
|
||||
.gstack/
|
||||
3d-models/
|
||||
|
||||
# Private configuration and local development artifacts
|
||||
.env.*
|
||||
!.env.example
|
||||
*.pem
|
||||
*.key
|
||||
.codex/
|
||||
.agents/
|
||||
|
|
|
|||
|
|
@ -169,3 +169,9 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
|
|||
## [0.1.0] — 2026-02-09
|
||||
|
||||
- Initial project version.
|
||||
|
||||
## Phase 1 community preview
|
||||
- Add portable free-only startup and source-health diagnostics.
|
||||
- Expand camera regions and enable official HLS playback.
|
||||
- Add saved cities, Boston transit, global hazard reports, weather units and radar.
|
||||
- Retain known grounded-aircraft regression limitations; upstream integration pending.
|
||||
|
|
|
|||
|
|
@ -136,3 +136,14 @@ Douglas-Peucker simplification, 6-decimal rounding).
|
|||
## In-app attribution
|
||||
|
||||
The required Google Maps / Cesium credit renders on the on-globe credit line (`#cesium-credits`, bottom-left) and must stay visible — including in clean-view and recording modes (the whole line, logo + "Google Maps" + the "Data attribution" link, stays on screen; only the GEV panels/HUD fade). The layer-specific credits (adsb.lol, TeleGeography, OSM datacenters/dams/roads, NASA FIRMS, CelesTrak, USGS, City of Austin, GBFS, Radio Browser, OpenSky, AISStream) are registered into the expandable **"Data attribution"** popover on that credit line via `viewer.creditDisplay.addStaticCredit(new Cesium.Credit(html, /* showOnScreen */ false))` — see `src/data/dataCredits.js`. When you add a new data source, add its license and attribution to this file **and** append an entry to `DATA_CREDITS` in `src/data/dataCredits.js` so it surfaces in the app.
|
||||
|
||||
## Phase 1 runtime additions
|
||||
These connections fetch provider content at runtime; this contribution does not
|
||||
bundle camera images, video recordings or weather observations.
|
||||
- WSDOT camera catalog and snapshots: https://data.wsdot.wa.gov/ ; preserve WSDOT attribution and provider conditions.
|
||||
- Caltrans HLS: official streaming URLs in the existing Caltrans catalog; on-demand playback, not redistributed recordings.
|
||||
- MBTA vehicle positions: https://api-v3.mbta.com/ ; Boston only, subject to MBTA developer terms.
|
||||
- GDACS: https://www.gdacs.org/ ; attributed published disaster reports, not comprehensive warnings.
|
||||
- Open-Meteo: https://open-meteo.com/ ; CC BY 4.0 attribution; public endpoint limited to eligible noncommercial use.
|
||||
- RainViewer: https://www.rainviewer.com/api.html ; free API use within provider terms, attribution retained; incomplete radar coverage.
|
||||
- Photon: https://github.com/komoot/photon ; OpenStreetMap-based search, moderate public-service use; configurable self-hosted endpoint.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
# Gods Eye — free local setup
|
||||
|
||||
Open **Start Gods Eye.command** in this folder, then visit http://localhost:4173.
|
||||
Keep the launcher window open while using the app. Press Control-C to stop it.
|
||||
If Gods Eye is already running, use the existing browser page instead of starting a second copy.
|
||||
|
||||
This launcher selects a compatible Node runtime and enables `GEV_FREE_ONLY=1`.
|
||||
Google and OpenAI credentials are blocked; optional free-account keys in `.env`
|
||||
are allowed. No subscription or paid API account is configured.
|
||||
|
||||
Try the Locations buttons or search for a city, landmark, or address and press
|
||||
Enter. Bundled places work locally; other places use Photon / OpenStreetMap
|
||||
through a bounded, cached proxy. The public Photon service permits moderate
|
||||
project use and has no availability guarantee: https://github.com/komoot/photon.
|
||||
Set `GEV_PHOTON_URL` to use your own compatible service if needed.
|
||||
Turn on Earthquakes, Flights, Satellites, or the bundled infrastructure layers.
|
||||
Public feeds need internet access and can be delayed, rate-limited, or unavailable.
|
||||
Traffic without a TomTom key is a labeled simulation.
|
||||
|
||||
Use the **◉ Source Status** button at the top to inspect connections and receipt
|
||||
ages, and to refresh enabled sources. Enabling Space Missions temporarily stages
|
||||
its own layers; disabling it restores the previous view. It does not mean the
|
||||
other feeds stopped working.
|
||||
|
||||
## Free-account connections
|
||||
|
||||
Add your own keys to `.env`, keep `GEV_FREE_ONLY=1`, and restart the launcher.
|
||||
Do not paste keys into chat or save them in browser URLs.
|
||||
|
||||
| Feature | Variable | Account / key |
|
||||
| --- | --- | --- |
|
||||
| Live ships | `AISSTREAM_API_KEY` | https://aisstream.io/ |
|
||||
| Fire detections | `FIRMS_MAP_KEY` | https://firms.modaps.eosdis.nasa.gov/api/map_key/ |
|
||||
| Real traffic flow | `TOMTOM_API_KEY` | https://docs.tomtom.com/pricing |
|
||||
|
||||
TomTom lists a free vector-traffic tile allowance. Use an account with no paid
|
||||
overage enabled. This setup limits requests to 5,000 tiles/day; that local counter
|
||||
cannot account for other apps sharing your provider quota. Check your account's
|
||||
allowance before adding a key. Without it, traffic remains explicitly simulated.
|
||||
|
||||
Google photorealistic buildings and OpenAI voice/AI commands are disabled under
|
||||
the no-paid-services constraint. The free map is mapped imagery and terrain,
|
||||
not live satellite video. Satellites are propagated from orbital elements;
|
||||
CCTV is provider snapshots or streams with limited coverage; fire detections
|
||||
and aircraft reports have inherent source delays. Buildings, cables, dams,
|
||||
and datacenters are reference data rather than live activity.
|
||||
|
||||
For a manually configured launch, use a supported Node version, run `npm ci`,
|
||||
then `npm run dev -- --host localhost --port 4173`. That command uses `.env`.
|
||||
The free launcher keeps Google and OpenAI disabled even if `.env` later changes.
|
||||
|
||||
## Verification of this setup
|
||||
|
||||
- Production build passed using Node 24.19.0.
|
||||
- All 2,607 unit and allocation tests passed after the free-source changes.
|
||||
- Browser checks confirmed OpenStreetMap rendering, live aircraft tracking,
|
||||
a keyless Tokyo search, 36 earthquakes, and 834 satellites. Feed counts vary.
|
||||
- The retry verified free place search for Reykjavik, 5,537 aircraft in the
|
||||
server snapshot, 50 military aircraft, 25 space missions, 800 camera directory
|
||||
entries, and live weather. An Austin CCTV snapshot displayed `SNAPSHOT · OK`.
|
||||
Directory entries do not guarantee every camera is working. The station-only
|
||||
satellite endpoint returned 21 objects; the UI's four catalogs totalled 834.
|
||||
- The added AISStream, NASA FIRMS, and TomTom keys were verified against real
|
||||
payloads: a live ship stream, 183,552 fire detections from three sources with
|
||||
`stale: false`, and a traffic tile decoded into 870 road segments. Counts vary.
|
||||
- Fixed the mapped-installations proxy to try another public mirror after
|
||||
upstream access rejections. The previously failing request now returns HTTP
|
||||
200 with six mapped objects in the Austin test area. Free-mode HUD summaries
|
||||
now use local metrics without repeatedly calling the disabled OpenAI service.
|
||||
- The 21 targeted proxy/HUD tests and production build passed after these fixes.
|
||||
- Fresh browser checks showed ships and fires ON, TomTom traffic ON with 96%
|
||||
coverage, CCTV and mapped layers finishing loading, and no console errors.
|
||||
Visual inspection still found a provider's "Image Unavailable" camera picture
|
||||
delivered with HTTP 200. The badge now says RECEIVED instead of OK and explains
|
||||
that delivery does not prove camera availability. Individual cameras remain
|
||||
provider-dependent; a successful directory request is not a camera health test.
|
||||
- Ship client refresh is now 10 seconds (previously 60), reading the server's
|
||||
existing live stream cache; no extra upstream AIS subscription was added.
|
||||
- Additional UI checks loaded 750 radio directory entries, about 4,400
|
||||
datacenters, 716 dams, and about 2,600 cable reference objects. Radio playback
|
||||
itself was not tested. Bikeshare returned no stations at the current view;
|
||||
mapped installations requested a closer view. These are not verified live feeds.
|
||||
- During the retry, OpenSky switched to the existing adsb.lol regional fallback.
|
||||
That preserves nearby aircraft but is not global coverage. The Source Status
|
||||
panel now carries the same fallback/stale labels as the layer controls.
|
||||
- Before the latest proxy fix, the broader tracking harness finished with 96 passes and 4 failures:
|
||||
three checks reported one HTTP 503 from the military-installations endpoint;
|
||||
one ground-model sampling-count assertion also failed. The endpoint now passes
|
||||
the live payload check; this does not establish that every tracking assertion passes.
|
||||
- A later full tracking-harness run was interrupted by a development-page reload
|
||||
(execution context destroyed) during the camera-label change, so it is not a
|
||||
completed regression result. The final production build passed.
|
||||
- Dependency hardening on September 6 updated compatible packages plus Puppeteer
|
||||
and Sharp to patched releases. The install audit reported zero known
|
||||
vulnerabilities. This is not a security certification or a public deployment review.
|
||||
|
||||
## City Pulse, Storm Watch, and worldwide weather
|
||||
|
||||
Open **◈ City Pulse, Storm Watch & World Weather** at the top of the map.
|
||||
The panel adds its own map markers; selecting a result flies to its location.
|
||||
**Focus map** temporarily hides other enabled layers; **Restore other layers**
|
||||
or **Stop & close** brings them back. Closing stops these feeds and clears their markers.
|
||||
|
||||
- **City Pulse:** Boston transit positions from MBTA (up to 200 vehicles,
|
||||
refreshed every 30 seconds). Other cities retain access to the existing traffic,
|
||||
camera and bikeshare controls where those providers have coverage. Transit is
|
||||
not worldwide. Use **Enable city layers** and **Browse cameras** as needed.
|
||||
- **Storm Watch:** global GDACS cyclone, flood, drought, wildfire and volcano
|
||||
reports, refreshed every six minutes. Up to 300 reports are mapped; cyclone,
|
||||
flood, drought and volcano reports take priority over wildfire overflow.
|
||||
These are published event reports, not radar, comprehensive local warnings,
|
||||
or river-gauge measurements. Each record shows its report date and severity.
|
||||
- **Weather around the world:** an 18-city global overview plus weather search
|
||||
for other places. Open-Meteo provides model-based current conditions and
|
||||
three-day forecasts, labeled separately from direct station observations.
|
||||
Temperature offers Fahrenheit/Celsius, wind km/h, precipitation mm, and weather times UTC.
|
||||
Refresh interval is ten minutes; source model updates can be less frequent.
|
||||
|
||||
Server caches coalesce simultaneous requests. On provider failure, cached data
|
||||
less than an hour old may be returned explicitly marked STALE; older server
|
||||
cache is not returned as current. Client-retained data also gets a stale label
|
||||
if refresh fails. The Open-Meteo public endpoint is intended for noncommercial
|
||||
use within its free limits; no paid service or new key was configured.
|
||||
|
||||
Sources: https://api-v3.mbta.com/ · https://www.gdacs.org/ · https://open-meteo.com/
|
||||
|
||||
Verified: 18 overview weather locations, 200 Boston vehicle records, and 300
|
||||
hazard reports (including 7 cyclone and 15 flood reports in the test snapshot).
|
||||
Browser checks covered all three views, focused mapping, and weather search for
|
||||
Reykjavik. Five targeted tests and the production build passed. Existing unrelated
|
||||
grounded-aircraft regression failures have not been changed by this addition.
|
||||
|
||||
## More camera cities and live video
|
||||
|
||||
Use the **▣ Browse camera cities and live video** button at the top of the map.
|
||||
Choose a city/region and camera. Entries marked **VIDEO** expose an official
|
||||
Caltrans HLS stream; press **Play live video** to start it. Return to snapshot,
|
||||
change cameras, or close the dialog to release the stream. Playback is on demand
|
||||
and limited to one stream in this browser panel. Traffic layers can remain enabled.
|
||||
|
||||
The expanded catalog loads all 12 Caltrans districts and adds 300 WSDOT cameras
|
||||
across 10 Washington areas. The current 900-camera cap is shared across sources
|
||||
to retain geographic variety. On verification it contained 216 live-video links;
|
||||
individual stream availability varies. Los Angeles video played successfully in
|
||||
the browser. Washington and London are labeled snapshots, not continuous video.
|
||||
Washington source documentation: https://data.wsdot.wa.gov/arcgis/rest/services/TravelInformation/TravelInfoCamerasWeather/FeatureServer/0
|
||||
|
||||
No additional account or paid service is required for these additions. Set
|
||||
`CCTV_WSDOT_ENABLED=0` to omit Washington; `CCTV_CALTRANS_DISTRICTS` can restrict
|
||||
districts. Catalogs remain cached for 15 minutes. Selected snapshot requests run
|
||||
every 30 seconds; provider image-update intervals may be longer. The live player
|
||||
uses free HLS.js when the browser lacks native HLS playback. New York and Singapore
|
||||
have not been connected in this change.
|
||||
|
||||
## Source health without extra API usage
|
||||
|
||||
The top **◉** control now shows how many enabled sources report a fault, stale
|
||||
data, or a fallback. It checks the existing layer metadata every five seconds;
|
||||
it does not make extra provider requests or consume API allowance. Selected
|
||||
snapshot feeds also have conservative receipt deadlines to catch stalled updates.
|
||||
Reference catalogs and predicted satellite positions are explicitly distinguished.
|
||||
|
||||
Open the control and choose **Download health report** to save a JSON report of
|
||||
current states and the latest 200 state changes observed during this page session.
|
||||
The report excludes raw errors, URLs, credentials, and coordinates. It is a
|
||||
diagnostic journal, not a recording of observations, and resets on page reload.
|
||||
Receipt age cannot establish observation freshness. Individual camera availability
|
||||
still requires inspection; a provider can successfully return a placeholder image.
|
||||
|
||||
Validation after dependency and health changes: all 2,611 unit and allocation
|
||||
checks passed, including simulated stalled receipts and recovery, bounded journal
|
||||
retention, and exclusion of raw sensitive fields. The production build passed.
|
||||
The completed browser tracking run passed 87 checks and failed four: grounded
|
||||
models becoming ready, tracked model zoom transitions, retained hidden-model
|
||||
flooring, and shown-but-not-ready fleet-model flooring. These remain unresolved.
|
||||
The same full run recorded no console errors and no HTTP 5xx responses.
|
||||
|
||||
### Live view preferences and radar (September 6)
|
||||
City Pulse now supports place search and up to 50 favorite cities saved in this
|
||||
browser. Favorites appear first in the city selector and can be removed. Weather
|
||||
offers remembered Fahrenheit/Celsius units for map labels, current conditions and
|
||||
daily forecasts. New preferences default to Fahrenheit.
|
||||
Storm Watch uses hazard symbols, severity colors and a category filter; labels
|
||||
appear closer to the ground. These are report locations, not hazard footprints.
|
||||
Storm Watch and World Weather include optional RainViewer precipitation radar,
|
||||
a frame selector for the past two hours and opacity control. Metadata refreshes
|
||||
every five minutes while open; tiles use the documented maximum zoom of 7.
|
||||
Coverage is incomplete and frame generation times differ from observation times.
|
||||
This is reflectivity, not Doppler velocity. No new API key is required.
|
||||
Verification: 8 focused tests passed; production build passed; world weather,
|
||||
custom city search/save and radar metadata loaded in the browser; a public radar
|
||||
tile returned HTTP 200 image/png. This does not certify every external feed.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Phase 1 — public release candidate
|
||||
|
||||
This community edition extends Bilawal Sidhu's MIT-licensed God's Eye View.
|
||||
Release status: community preview on ModDayJob/gods-eye-view, branch codex/phase-1.
|
||||
Based on upstream ac927de; integration with newer upstream changes is pending.
|
||||
|
||||
## Included
|
||||
- Free-only launcher, OpenStreetMap and keyless place search.
|
||||
- Optional AISStream, NASA FIRMS and TomTom connections; faster ship refresh.
|
||||
- Source-health indicators, stale/fallback labels and downloadable diagnostics.
|
||||
- Expanded Caltrans and Washington cameras, region browsing and HLS video.
|
||||
- City Pulse with Boston transit, place search and 50 saved favorite cities.
|
||||
- Global GDACS hazard reports, category filters and severity symbols.
|
||||
- Worldwide weather, three-day forecasts and saved Fahrenheit/Celsius units.
|
||||
- RainViewer precipitation radar with recent-frame selection and opacity.
|
||||
- Dependency updates, public setup instructions and automated test workflow.
|
||||
|
||||
## Validation and limitations
|
||||
Earlier local checks passed 2,611 unit/allocation tests; the latest weather change
|
||||
passed eight focused checks and a production build. A prior browser regression
|
||||
run passed 87 checks and failed four grounded-aircraft/model checks. Those remain
|
||||
open. See FREE-START.md for specific live checks; historical counts are not a
|
||||
promise of current coverage. GitHub Actions has not run yet.
|
||||
|
||||
Transit is Boston-only. Radar is reflectivity, not Doppler velocity. Satellites
|
||||
are predicted from orbital data. Camera availability varies. Third-party data,
|
||||
maps, video and models remain subject to their source terms; the code's MIT
|
||||
license does not grant blanket redistribution rights for provider content.
|
||||
|
||||
## Publication route
|
||||
Fork the original repository under the chosen GitHub account. Clone that fork
|
||||
to a separate folder to preserve upstream history, then apply this edition's
|
||||
changes on a codex/phase-1 branch. Compare against upstream before committing;
|
||||
do not replace upstream changes blindly. Exclude private configuration, caches,
|
||||
generated builds and logs. Review the exact staged files for secrets and review
|
||||
bundled data/model attribution before pushing. Do not upload this entire working
|
||||
folder as a ZIP with local files included.
|
||||
|
||||
Open a draft pull request describing Phase 1 and its known limitations.
|
||||
Publish a Phase 1 release/tag after review and passing checks. Users download
|
||||
the source and run npm ci followed by npm start; each supplies their own optional
|
||||
keys. A shared hosted service needs a separate deployment and quota design.
|
||||
129
README.md
129
README.md
|
|
@ -1,3 +1,36 @@
|
|||
# Phase 1 — free local edition
|
||||
|
||||
Built on [Bilawal Sidhu's God's Eye View](https://github.com/bilawalsidhu/gods-eye-view).
|
||||
The original MIT license and attribution are preserved.
|
||||
|
||||
## Start Phase 1
|
||||
|
||||
1. Install Node.js **24.14+ within 24.x** (recommended) or **26.x**.
|
||||
2. Download this repository using **Code → Download ZIP**, extract it, and open a terminal in the extracted folder.
|
||||
3. Run:
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
npm start
|
||||
```
|
||||
|
||||
The app opens at **http://localhost:4173**. Keep the terminal open; Ctrl+C stops it.
|
||||
This start command enables free-only mode on Windows, macOS and Linux.
|
||||
No API keys are needed to explore the free map, weather, radar and public feeds.
|
||||
Optional ship, fire and traffic keys go in your own ignored `.env` file;
|
||||
copy `.env.example` first. Never publish that file.
|
||||
|
||||
Phase 1 adds source-health reporting, expanded cameras and live video, city
|
||||
favorites, Boston transit, global hazard reports, weather and precipitation radar.
|
||||
Read [the setup and limitations](FREE-START.md) and [Phase 1 release notes](PHASE-1.md).
|
||||
|
||||
This is a local application with a server, not a standalone GitHub Pages site.
|
||||
Public data providers have their own usage limits and coverage. Free-only mode
|
||||
disables Google photorealistic tiles and OpenAI voice; the original documentation
|
||||
below also describes those optional upstream features.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
# 🌐 God's Eye View
|
||||
|
|
@ -22,7 +55,7 @@ Photorealistic 3D globe. Live aircraft, ships, satellites, earthquakes, traffic,
|
|||
|
||||
<div align="center">
|
||||
|
||||
**[Quick Start](#-quick-start) · [First Five Minutes](#-the-first-five-minutes) · [Talk to It](#-talk-to-it) · [What's Live](#-whats-on-the-globe) · [Under the Hood](#-under-the-hood) · [Keys](#-api-keys) · [Costs](#-what-it-actually-costs)**
|
||||
**[Quick Start](#-quick-start) · [First Five Minutes](#-the-first-five-minutes) · [Talk to It](#-talk-to-it) · [What's Live](#-whats-on-the-globe) · [Under the Hood](#-under-the-hood) · [Keys & Costs](#-api-keys)**
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -42,14 +75,6 @@ The live layers are grounded in public feeds: the airliner crossing your screen
|
|||
|
||||
## 🎛️ What This Thing Does
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://www.youtube.com/watch?v=GRJaKcXZS94)
|
||||
|
||||
▶️ **[The full walkthrough of everything below, on YouTube](https://www.youtube.com/watch?v=GRJaKcXZS94)**
|
||||
|
||||
</div>
|
||||
|
||||
- **🛩️ Cockpit view:** Ride inside a tracked flight — the camera holds the terrain under you all the way down.
|
||||
- **📡 Contacts:** A 250 km roster of everything near your target — step through live aircraft and drop into any cockpit.
|
||||
- **🎯 Click-to-track anything:** Camera locks on, draws a fading trail, surfaces full metadata — and a tracked fire or vessel hands you off to the nearest live camera in one click.
|
||||
|
|
@ -69,7 +94,7 @@ The live layers are grounded in public feeds: the airliner crossing your screen
|
|||
|
||||
Requires Node.js 24.14.x or 26.x (enforced by `package.json`).
|
||||
|
||||
1. Copy `.env.example` → `.env` and set `GOOGLE_MAPS_API_KEY`.
|
||||
1. Copy `.env.example` → `.env`. Leave keys blank for the free OpenStreetMap globe, or set `GOOGLE_MAPS_API_KEY` for optional photorealistic tiles.
|
||||
2. Install and run:
|
||||
|
||||
```bash
|
||||
|
|
@ -79,22 +104,9 @@ npm run dev -- --host localhost --port 4173
|
|||
|
||||
3. Open **`http://localhost:4173`**. Cold start settles in under two seconds on a recent laptop (median 1.86 s in a point-in-time M5/Chrome capture — [docs/PERFORMANCE.md](docs/PERFORMANCE.md); a comparison baseline, not a hardware requirement). A first-run card offers to stage a mission for you — **Live Contacts**, **Space Missions**, **Environmental** — or leaves you to explore manually.
|
||||
|
||||
> [!TIP]
|
||||
> **Not a coder? Have an AI do this whole page for you.** A one-click installer is in the works — until then, install a coding agent ([Claude Code](https://claude.com/claude-code), [Codex](https://openai.com/codex/), [Cursor](https://cursor.com), or [Antigravity](https://antigravity.google)) and paste this:
|
||||
>
|
||||
> ```text
|
||||
> Clone https://github.com/bilawalsidhu/gods-eye-view and set it up on my machine.
|
||||
> Install everything it needs, walk me through getting the required Google Maps API
|
||||
> key step by step (plus any optional free keys I want), put the keys in .env, and
|
||||
> help me set a billing alert and a usage quota on the Google key so I can't
|
||||
> overspend. Then start the dev server and open it in my browser. I'm not a
|
||||
> developer — explain what you're doing as you go, and ask me before any step
|
||||
> that could cost money.
|
||||
> ```
|
||||
**Start free with no keys.** OpenStreetMap and keyless terrain provide the globe; Google Maps optionally adds photorealistic buildings. Everything in this README is color-coded — 🟢 needs nothing · 🟡 free key · 🔴 metered. See [Free local setup](FREE-START.md) and [Keys & Costs](#-api-keys).
|
||||
|
||||
**That one key is the whole entry fee.** Everything in this README is color-coded — 🟢 needs nothing · 🟡 free key · 🔴 metered — and Google Maps is the only 🔴 you need: it buys the photorealistic planet, and most of the globe lights up 🟢 from there. For typical solo exploring, expect **$0 on most layers** and pocket change on the metered two: Google currently gives **1,000 free 3D-tile sessions a month** — each good for up to three hours of rendering, which is very hard for one person to exhaust — and voice carries a built-in $5 session cap. Full map in [Keys & Costs](#-api-keys), full honest breakdown in [What it actually costs](#-what-it-actually-costs).
|
||||
|
||||
The dev server binds to **localhost** — your keys stay on your machine. Sharing on a LAN safely is covered in [Sharing an instance](#-sharing-an-instance) and [SECURITY.md](SECURITY.md).
|
||||
The dev server binds to **localhost** — your keys stay on your machine. Sharing on a LAN and the cost rails live in [Keys & Costs](#-api-keys) and [SECURITY.md](SECURITY.md).
|
||||
|
||||
**macOS shortcut:** `./scripts/dev-fresh.sh` clears the Vite cache and pulls your keys straight from the Keychain.
|
||||
|
||||
|
|
@ -105,12 +117,10 @@ The dev server binds to **localhost** — your keys stay on your machine. Sharin
|
|||
No account, no signup. The first-run card will offer to stage a mission for you — or run this gauntlet yourself. Somewhere in these five minutes it stops feeling like a demo:
|
||||
|
||||
1. **Light up the sky.** Take the **Live Contacts** mission (or turn on **Flights** yourself) — thousands of live aircraft, gliding on real telemetry, detection mesh already reading the scene. Click one: the camera locks on, a trail draws behind it, and its live telemetry card comes up.
|
||||
2. **Take the controls.** Hit **COCKPIT** on your tracked plane and ride it down, switching sensors mid-flight: NVG into Ironbow FLIR. The cockpit carries its own briefing strip — nearby live signals, regional headlines, and real local weather, with an opt-in **WX** mode that renders volumetric clouds from actual observations around your aircraft — and **Contacts** keeps the 250 km roster one click (or one sentence) away: jump plane to plane and fall straight into the next cockpit.
|
||||
2. **Take the controls.** Hit **COCKPIT** on your tracked plane and ride it down, switching sensors mid-flight: NVG into Ironbow FLIR.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
3. **Drop into a busy airport.** Search one and descend to the taxiways with **3D** aircraft on — grounded contacts, taxi trails, the whole apron working in real time.
|
||||
|
||||

|
||||
|
|
@ -119,25 +129,37 @@ No account, no signup. The first-run card will offer to stage a mission for you
|
|||
|
||||

|
||||
|
||||
5. **Paint the streets with rush hour.** Turn on **Traffic** and dive below ~8 km — per-vehicle flow colors to the real jams (with a TomTom key; keyless it's a labeled simulation). Then hit **NEAREST** in the CCTV panel and watch the jam through the camera pointed at it.
|
||||
|
||||

|
||||
|
||||
6. **Track something in orbit.** Turn on **Satellites** and click the ISS — you ride along at orbital distance, orbit ring and all.
|
||||
5. **Track something in orbit.** Turn on **Satellites** and click the ISS — you ride along at orbital distance, orbit ring and all.
|
||||
|
||||

|
||||
|
||||
7. **Switch the optics.** Tap `1`–`7` — CRT, NVG, FLIR — and the whole live planet re-renders through a different sensor.
|
||||
6. **Switch the optics.** Tap `1`–`7` — CRT, NVG, FLIR — and the whole live planet re-renders through a different sensor.
|
||||
|
||||

|
||||
|
||||
8. **Talk to it** *(needs an OpenAI key)*: *"Take me to LAX and select the nearest airborne aircraft."*
|
||||
9. **Come home.** Hit **Reset Globe** — or just say *"zoom out to a globe view."*
|
||||
7. **Talk to it** *(needs an OpenAI key)*: *"Take me to LAX and select the nearest airborne aircraft."*
|
||||
8. **Come home.** Hit **Reset Globe** — or just say *"zoom out to a globe view."*
|
||||
|
||||
**Keyboard:** `1`–`7` visual styles · `H` HUD · `D` detection · `C` cockpit · `Esc` out.
|
||||
|
||||
---
|
||||
|
||||
## 🛩️ The Cockpit
|
||||
|
||||
> Every plane should let you do this.
|
||||
|
||||
Real-time cockpit mode, built from live flight data: the camera rides your contact with real terrain holding underneath, all the way down — sensor styles come along for the ride, and **Contacts** keeps the 250 km roster one click away: jump plane to plane and fall straight into the next cockpit.
|
||||
|
||||

|
||||
|
||||
The cockpit even carries its own briefing strip: nearby live signals, regional headlines, and real local weather — with an opt-in **WX** mode that renders volumetric clouds from actual observations around your aircraft.
|
||||
|
||||

|
||||
|
||||
*Why cockpit mode exists: you're riding a real aircraft over real terrain — and you get to pick which sensor you see the world through.*
|
||||
|
||||
---
|
||||
|
||||
## 🎙️ Talk to It
|
||||
|
||||
> Voice needs an **OpenAI key**. Without one the entire app still runs — the mic button just reports voice is unavailable. The same key drives the **AI HUD summary**: a terse, five-word intelligence-style readout of the current view that regenerates as you move.
|
||||
|
|
@ -179,7 +201,7 @@ Twenty-eight tools, four jobs — the commands below come straight from the prod
|
|||
|
||||
## 🛰️ What's on the Globe
|
||||
|
||||
Thirteen live layers. **Ten of them need nothing at all** — no key, no account, no signup.
|
||||
Thirteen live layers. **Ten of them need nothing at all** — no key, no account, no signup. (🟢 nothing · 🟡 free key · 🔴 metered.)
|
||||
|
||||
| Layer | What you get | Source | Auth |
|
||||
|-------|--------------|--------|------|
|
||||
|
|
@ -197,8 +219,14 @@ Thirteen live layers. **Ten of them need nothing at all** — no key, no account
|
|||
| 🚀 **Space Missions** | Rolling 30-day launches with payload, stage, and recovery detail | Launch Library 2 | 🟢 (🟡 optional token raises the allowance) |
|
||||
| 🎖️ **Mapped Installations** | Viewport-bounded military-site context from community mapping — incomplete by nature, and labeled that way | OpenStreetMap | 🟢 |
|
||||
|
||||

|
||||
|
||||
*The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED ESTIMATE`, scrubbable 0.25×–4×.*
|
||||
|
||||
**Also on the globe:** neighborhood overlays · an optional cockpit WX cloud effect. **Bundled static infrastructure:** Datacenters (4,351), Dams (704), and Submarine Cables (712).
|
||||
|
||||

|
||||
|
||||
**Missing a layer you want?** Open an issue — or add it and send the PR.
|
||||
|
||||
---
|
||||
|
|
@ -220,7 +248,6 @@ Once the basics click, run these:
|
|||
| **🚀 Launch replay** | Open **Space Missions**, pick a launch from the last 30 days, and ride the T-minus countdown through ascent to orbit — scrub it at 0.25×–4×. Labeled `RECONSTRUCTED ESTIMATE`, because it is one. |
|
||||
| **🪦 Walk the boneyard** | Fly from regional context down into dense, fully resolved rows of retired aircraft. |
|
||||
| **🏗️ Orbit Three Gorges** | Sweep the dam and its terrain at a glance — then flip on the **Dams** layer and find 703 more. |
|
||||
| **🌊 Trace the backbone** | Dive to the Bahamas with **Submarine Cables** on — labeled routes reveal beneath the water, 712 of them worldwide. |
|
||||
|
||||
*🎙️ = voice missions — they need an OpenAI key.*
|
||||
|
||||
|
|
@ -236,14 +263,6 @@ Once the basics click, run these:
|
|||
|
||||
*Walk the boneyard: rows of retired airframes, fully resolved in 3D.*
|
||||
|
||||

|
||||
|
||||
*Launch replay: a Falcon 9 ascent, labeled `RECONSTRUCTED ESTIMATE`, scrubbable 0.25×–4×.*
|
||||
|
||||

|
||||
|
||||
*Trace the backbone: the submarine cable routes under the Bahamas.*
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Under the Hood
|
||||
|
|
@ -287,13 +306,15 @@ Five keys cover the fully keyed experience. Three currently offer no-cost develo
|
|||
|
||||
| | Key | Why | Get it |
|
||||
|---|-----|-----|--------|
|
||||
| 🔴 | **Google Maps** *(required)* | The photorealistic 3D planet ([Map Tiles API](https://developers.google.com/maps/documentation/tile)) | [Google Cloud Console](https://console.cloud.google.com/) — metered; [check current pricing](https://developers.google.com/maps/billing-and-pricing/pricing) and URL-restrict it |
|
||||
| 🔴 | **Google Maps** *(optional)* | Photorealistic 3D buildings instead of the free OpenStreetMap globe ([Map Tiles API](https://developers.google.com/maps/documentation/tile)) | [Google Cloud Console](https://console.cloud.google.com/) — metered; [check current pricing](https://developers.google.com/maps/billing-and-pricing/pricing) and URL-restrict it |
|
||||
| 🔴 | **OpenAI** | 🎙️ The voice experience + AI HUD summary. Want another provider behind the mic? PRs welcome | [platform.openai.com](https://platform.openai.com) — metered; [check current API pricing](https://openai.com/api/pricing/) |
|
||||
| 🟡 | **AISStream** | 🚢 Live global ships | [aisstream.io](https://aisstream.io) — free, seriously, it's a two-minute signup |
|
||||
| 🟡 | **NASA FIRMS** | 🔥 Live active fires | [firms.modaps.eosdis.nasa.gov](https://firms.modaps.eosdis.nasa.gov/api/map_key/) — free |
|
||||
| 🟡 | **TomTom** | 🚦 Real traffic instead of an approximate simulation | [developer.tomtom.com](https://developer.tomtom.com) — check the current developer allowance for your account |
|
||||
|
||||
*What the TomTom key buys you: step 5 of [The First Five Minutes](#-the-first-five-minutes) for real — actual rush-hour density painted on the city instead of an approximate simulation.*
|
||||

|
||||
|
||||
*What the TomTom key buys you: rush-hour density painted on the city — then dive from the jam straight into the camera watching it.*
|
||||
|
||||
### Cherry on top
|
||||
|
||||
|
|
@ -308,9 +329,15 @@ All of them are worth getting. None of them are required to start.
|
|||
```bash
|
||||
# Put keys in .env (see .env.example), or pass them as env vars:
|
||||
OPENAI_API_KEY="…" AISSTREAM_API_KEY="…" npm run dev -- --host localhost --port 4173
|
||||
```
|
||||
|
||||
On macOS you can also keep any key in the Keychain and `./scripts/dev-fresh.sh` pulls them in — the `security add-generic-password` service names are documented in `.env.example`.
|
||||
# On macOS, store any of them in the Keychain and dev-fresh.sh pulls them in:
|
||||
security add-generic-password -U -s "google-maps-api" -a "api-key" -w
|
||||
security add-generic-password -U -s "openai-api" -a "api-key" -w
|
||||
security add-generic-password -U -s "aisstream-api" -a "api-key" -w
|
||||
security add-generic-password -U -s "firms-map" -a "map-key" -w
|
||||
security add-generic-password -U -s "cesium-ion" -a "token" -w
|
||||
security add-generic-password -U -s "tomtom-api" -a "api-key" -w
|
||||
```
|
||||
|
||||
OpenSky can run fully anonymous (`OPENSKY_AUTH_MODE=anon`), or import OAuth credentials with `./scripts/opensky-import-client.sh /path/to/credentials.json`.
|
||||
|
||||
|
|
@ -322,7 +349,7 @@ Honest numbers, roughly, as of mid-2026 — always check the provider pricing pa
|
|||
|---|---|
|
||||
| **🟢 Most layers** | **$0, no signup.** OpenSky anon, USGS, CelesTrak, adsb.lol, city CCTV, Radio Browser, GBFS, Launch Library 2, bundled datasets. |
|
||||
| **🟡 Optional developer access** | AISStream, FIRMS, TomTom, Cesium ion, and authenticated OpenSky may offer no-cost access, but limits and permitted uses differ. Cesium ion and OpenSky in particular have plan or use restrictions; verify the current provider terms for your deployment. |
|
||||
| **🔴 Google 3D tiles** | More generous than you'd guess: billing counts **root tileset requests** — one buys up to **three hours** of unlimited tile rendering — and the first **1,000 per month are free**, then about **$6 per 1,000** (US pricing; [check the current page](https://developers.google.com/maps/billing-and-pricing/pricing), rates vary by billing region). A solo user rarely leaves the free tier. Still: restrict the key, set quotas, and configure a budget alert before sustained use. |
|
||||
| **🔴 Google 3D tiles** | Map Tiles usage is billed by session, with current prices and free-usage caps varying by billing region. Check Google's pricing page, restrict the key, set quotas, and configure a budget alert before sustained use. |
|
||||
| **🔴 OpenAI voice** | Realtime audio is usage-metered and the total depends on the selected model, conversation length, and audio volume. The app shows a live session estimate, warns at $2, and applies a **$5 in-app session cap**; provider-side usage limits remain the billing backstop. |
|
||||
|
||||
### 🧗 The floor is low on purpose
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Prefer a supported installed Node, then the runtime bundled with Codex.
|
||||
node_bin="$(command -v node || true)"
|
||||
supported_node() {
|
||||
[[ -n "$1" ]] && "$1" -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit((major === 24 && minor >= 14) || major === 26 ? 0 : 1)' 2>/dev/null
|
||||
}
|
||||
if ! supported_node "$node_bin"; then
|
||||
node_bin="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
|
||||
fi
|
||||
if ! supported_node "$node_bin"; then
|
||||
echo 'Gods Eye needs Node 24.14+ (version 24) or Node 26. Install it, then reopen this launcher.'
|
||||
read -r -p 'Press Enter to close.'
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f node_modules/vite/bin/vite.js ]]; then
|
||||
echo 'Dependencies are missing. Run npm ci with a supported Node version first.'
|
||||
read -r -p 'Press Enter to close.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Block metered services, while allowing optional free-account keys in .env.
|
||||
export GEV_FREE_ONLY=1 GOOGLE_MAPS_API_KEY='' OPENAI_API_KEY=''
|
||||
export OPENSKY_AUTH_MODE=anon HOST=localhost PORT=4173
|
||||
echo 'Gods Eye — free services mode: http://localhost:4173'
|
||||
echo 'Keep this window open. Press Control-C to stop.'
|
||||
exec "$node_bin" node_modules/vite/bin/vite.js --host localhost --port 4173 --strictPort --open
|
||||
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
|
@ -2,7 +2,7 @@
|
|||
"name": "gods-eye-view",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "A real-time intelligence console for planet Earth — photorealistic 3D globe, live aircraft/ships/satellites/earthquakes/CCTV, and hands-free voice control. Runs in a browser.",
|
||||
"description": "A real-time intelligence console for planet Earth \u2014 photorealistic 3D globe, live aircraft/ships/satellites/earthquakes/CCTV, and hands-free voice control. Runs in a browser.",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Bilawal Sidhu",
|
||||
|
|
@ -39,19 +39,22 @@
|
|||
"preview": "vite preview",
|
||||
"test": "node scripts/run-unit-tests.mjs",
|
||||
"test:track": "node scripts/track-regression.mjs",
|
||||
"qa:map-source-tray": "node scripts/qa-map-source-tray.mjs"
|
||||
"qa:map-source-tray": "node scripts/qa-map-source-tray.mjs",
|
||||
"start": "node scripts/start-free.mjs",
|
||||
"test:phase1": "node --test server/*.test.mjs src/liveViewPreferences.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mapbox/vector-tile": "^3.0.0",
|
||||
"cesium": "^1.124.0",
|
||||
"egm96-universal": "^1.1.1",
|
||||
"hls.js": "^1.7.2",
|
||||
"mgrs": "^2.1.0",
|
||||
"pbf": "^5.1.2",
|
||||
"satellite.js": "^6.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"puppeteer": "^24.37.5",
|
||||
"sharp": "^0.34.5",
|
||||
"puppeteer": "^25.10.0",
|
||||
"sharp": "^0.35.4",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-cesium": "^1.2.23",
|
||||
"ws": "^8.21.0"
|
||||
|
|
|
|||
47
style.css
47
style.css
|
|
@ -9161,3 +9161,50 @@ body.scene-playback-mode #first-run-launcher {
|
|||
/* Scrolling a tile into view must not animate either. */
|
||||
.first-run-choices { scroll-behavior: auto; }
|
||||
}
|
||||
/* Source health is on demand; no extra polling or panel work while closed. */
|
||||
#source-status-dialog {
|
||||
margin: auto;
|
||||
box-sizing: border-box;
|
||||
width: min(680px, calc(100vw - 40px));
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
color: #d8f4f3;
|
||||
background: #0b171f;
|
||||
border: 1px solid #46777d;
|
||||
border-radius: 14px;
|
||||
padding: 24px;
|
||||
font: 14px/1.5 system-ui, sans-serif;
|
||||
}
|
||||
#source-status-dialog::backdrop { background: #000b; }
|
||||
#source-status-dialog h2 { font-size: 22px; margin: 0 0 12px; }
|
||||
#source-status-dialog section { padding: 10px 0; border-bottom: 1px solid #29414b; }
|
||||
#source-status-dialog p { margin: 6px 0 14px; color: #b5ced3; }
|
||||
#source-status-dialog section p { margin: 4px 0 0; font-size: 12px; }
|
||||
#source-status-dialog a { color: #86e5e8; }
|
||||
#source-status-dialog button { padding: 8px 14px; margin: 0 8px 10px 0; border: 1px solid #568d97; border-radius: 6px; color: #e1ffff; background: #163b46; cursor: pointer; }
|
||||
#source-status-dialog button:disabled { opacity: .55; cursor: wait; }
|
||||
#camera-browser-dialog h2 { margin: 0 0 12px; font-size: 24px; }
|
||||
#camera-browser-dialog p { margin: 12px 0; line-height: 1.5; }
|
||||
#camera-browser-dialog button, #camera-browser-dialog select {
|
||||
font: inherit; color: #e1edf2; background: #19323e;
|
||||
border: 1px solid #547783; border-radius: 6px; padding: 8px 12px;
|
||||
margin: 4px 8px 4px 0;
|
||||
}
|
||||
#camera-browser-dialog button { cursor: pointer; }
|
||||
#camera-browser-dialog button:disabled { opacity: .5; cursor: default; }
|
||||
#camera-browser-dialog::backdrop { background: rgba(0,0,0,.55); }
|
||||
#live-views-panel { position:fixed;z-index:10000;right:16px;top:85px;width:min(390px,90vw);max-height:78vh;overflow:auto;padding:20px;background:#0b1922;color:#e1edf2;border:1px solid #547783;border-radius:12px;font:14px/1.5 sans-serif; }
|
||||
#live-views-panel h2 { font-size:22px;margin:0 0 12px; }
|
||||
#live-views-panel button,#live-views-panel select,#live-views-panel input { color:#e1edf2;background:#19323e;border:1px solid #547783;border-radius:6px;padding:8px;margin:4px;max-width:100%;font:inherit; }
|
||||
#live-views-panel button { cursor:pointer; }
|
||||
#live-views-panel section { border-top:1px solid #34505c;padding:8px 0; }
|
||||
#live-views-panel p { margin:8px 0; }
|
||||
|
||||
#live-views-panel fieldset { border:1px solid #39526e; border-radius:10px; margin:12px 0; padding:12px; background:#101c30; }
|
||||
#live-views-panel fieldset legend { color:#8fd9ff; font-weight:600; }
|
||||
#live-views-panel fieldset label { display:flex; align-items:center; gap:8px; }
|
||||
#live-views-panel fieldset input[type=checkbox] { width:auto; }
|
||||
#live-views-panel fieldset input[type=range] { width:100%; }
|
||||
#live-views-panel a { color:#8fd9ff; }
|
||||
#live-views-panel > div > section { border:1px solid #2e4158; border-radius:9px; padding:10px; margin:8px 0; background:#101a28; }
|
||||
#live-views-panel [hidden] { display:none!important; }
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import { liveViewsPlugin } from './server/liveViews.js';
|
||||
import { loadWashingtonCameras, balanceCameraCities, publicVideoUrl } from './server/cameraExpansion.js';
|
||||
import { freeServicesPlugin } from './server/freeServices.js';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
|
|
@ -2508,6 +2511,13 @@ function sendOverpassResponse(res, payload, cacheStatus = 'MISS') {
|
|||
* @param {number} [maxResponseBytes] Endpoint-specific response cap.
|
||||
* @returns {Promise<{status:number,body:string,contentType:string,endpoint:string,rateLimited:boolean}>}
|
||||
*/
|
||||
export function shouldRetryOverpassMirror(status) {
|
||||
// A mirror can reject this host/request at its edge (observed HTTP 406)
|
||||
// while other public mirrors remain available. Preserve HTTP 400 as a
|
||||
// query error instead of sending invalid queries to every provider.
|
||||
return [403, 406, 408].includes(status) || status >= 500;
|
||||
}
|
||||
|
||||
async function fetchOverpassPayload(body, maxResponseBytes = OVERPASS_MAX_RESPONSE_BYTES) {
|
||||
let lastError = null;
|
||||
let lastRateLimitPayload = null;
|
||||
|
|
@ -2551,7 +2561,7 @@ async function fetchOverpassPayload(body, maxResponseBytes = OVERPASS_MAX_RESPON
|
|||
lastError = new Error(`Overpass runtime error (${endpoint})`);
|
||||
continue;
|
||||
}
|
||||
if (status >= 500) {
|
||||
if (shouldRetryOverpassMirror(status)) {
|
||||
lastError = new Error(`Overpass upstream returned ${status} (${endpoint})`);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -3426,7 +3436,7 @@ const AUSTIN_DOWNTOWN = { lat: 30.2672, lon: -97.7431 };
|
|||
const CALTRANS_CCTV_URL = (district) =>
|
||||
`https://cwwp2.dot.ca.gov/data/d${district}/cctv/cctvStatusD${String(district).padStart(2, '0')}.json`;
|
||||
/** Districts fetched by default: SF Bay (4), LA (7), San Diego (11), Sacramento (3). */
|
||||
const DEFAULT_CALTRANS_DISTRICTS = '4,7,11,3';
|
||||
const DEFAULT_CALTRANS_DISTRICTS = '1,2,3,4,5,6,7,8,9,10,11,12';
|
||||
const DEFAULT_CALTRANS_MAX_SOURCES = 300;
|
||||
/** Prioritization anchors: downtown cores of the four default metros. */
|
||||
const CALTRANS_ANCHORS = [
|
||||
|
|
@ -3951,6 +3961,7 @@ async function loadCaltransSourcesFromOpenData() {
|
|||
city: String(loc.nearbyPlace || `Caltrans D${district}`),
|
||||
cityId: `ca-d${district}`,
|
||||
provider: 'Caltrans',
|
||||
liveVideoUrl: publicVideoUrl(cctv.imageData?.streamingVideoURL),
|
||||
lat,
|
||||
lon,
|
||||
headingDeg: hasHeading ? heading : fallbackHeadingFromId(cameraId),
|
||||
|
|
@ -3984,7 +3995,7 @@ async function loadCaltransSourcesFromOpenData() {
|
|||
|
||||
const maxRaw = Number(process.env.CCTV_CALTRANS_MAX_SOURCES || DEFAULT_CALTRANS_MAX_SOURCES);
|
||||
const maxCount = Number.isFinite(maxRaw) ? Math.max(8, Math.min(600, Math.floor(maxRaw))) : DEFAULT_CALTRANS_MAX_SOURCES;
|
||||
const prioritized = prioritizeSources(cameras, maxCount, CALTRANS_ANCHORS);
|
||||
const prioritized = balanceCameraCities(cameras, maxCount, source => source.cityId);
|
||||
console.log(`[CCTV] Loaded Caltrans camera sources: ${cameras.length} inService (using nearest ${prioritized.length})`);
|
||||
return prioritized;
|
||||
}
|
||||
|
|
@ -4090,6 +4101,7 @@ function normalizeSourceItem(item) {
|
|||
feedType: normalizeFeedType(item.feedType || item.type || ''),
|
||||
url: typeof item.url === 'string' ? item.url : '',
|
||||
snapshotUrl: typeof item.snapshotUrl === 'string' ? item.snapshotUrl : '',
|
||||
liveVideoUrl: publicVideoUrl(item.liveVideoUrl),
|
||||
license: String(item.license || item.licenseNote || ''),
|
||||
sourceKind: String(item.sourceKind || item.kind || 'configured'),
|
||||
// Optional CAL badge input (cctv-v2 design §3b/§9.2, additive-only per the
|
||||
|
|
@ -4145,18 +4157,21 @@ async function refreshCctvSources() {
|
|||
let fromAustin = [];
|
||||
let fromCaltrans = [];
|
||||
let fromTfl = [];
|
||||
let fromWashington = [];
|
||||
if (needsLiveSources) {
|
||||
const [austinResult, caltransResult, tflResult] = await Promise.allSettled([
|
||||
const [austinResult, caltransResult, tflResult, washingtonResult] = await Promise.allSettled([
|
||||
loadAustinSourcesFromOpenData(),
|
||||
loadCaltransSourcesFromOpenData(),
|
||||
tflEnabled ? loadTflSourcesFromOpenData() : Promise.resolve([]),
|
||||
loadWashingtonCameras(),
|
||||
]);
|
||||
fromAustin = austinResult.status === 'fulfilled' ? austinResult.value : [];
|
||||
fromCaltrans = caltransResult.status === 'fulfilled' ? caltransResult.value : [];
|
||||
fromTfl = tflResult.status === 'fulfilled' ? tflResult.value : [];
|
||||
fromWashington = washingtonResult.status === 'fulfilled' ? washingtonResult.value : [];
|
||||
}
|
||||
// Live sources first so file/env overrides win on duplicate IDs (Map last-write).
|
||||
const merged = [...fromAustin, ...fromCaltrans, ...fromTfl, ...fromFile, ...fromEnv];
|
||||
const merged = [...fromAustin, ...fromCaltrans, ...fromTfl, ...fromWashington, ...fromFile, ...fromEnv];
|
||||
|
||||
// Deduplicate by camera ID (last-write wins because of Map.set)
|
||||
const byId = new Map();
|
||||
|
|
@ -4173,7 +4188,7 @@ async function refreshCctvSources() {
|
|||
if (mergedSources.length > maxCount) {
|
||||
console.warn(`[CCTV] source catalog ${mergedSources.length} exceeds cap ${maxCount}; keeping the first ${maxCount} (raise CCTV_MAX_SOURCES or lower a per-pack cap to change which).`);
|
||||
}
|
||||
const capped = mergedSources.length > maxCount ? mergedSources.slice(0, maxCount) : mergedSources;
|
||||
const capped = mergedSources.length > maxCount ? balanceCameraCities(mergedSources, maxCount) : mergedSources;
|
||||
if (capped.length > 0 || _cctvSourceCache.length === 0) {
|
||||
_cctvSourceCache = capped;
|
||||
} else {
|
||||
|
|
@ -4518,6 +4533,7 @@ function cctvProxy() {
|
|||
sourceKind: source.sourceKind || (source.url ? 'configured' : 'fallback'),
|
||||
poseSource: source.poseSource,
|
||||
license: source.license,
|
||||
liveVideoUrl: source.liveVideoUrl || '',
|
||||
})),
|
||||
};
|
||||
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
||||
|
|
@ -7337,9 +7353,17 @@ export default defineConfig(({ mode }) => {
|
|||
for (const [key, val] of Object.entries(loaded)) {
|
||||
if (process.env[key] === undefined) process.env[key] = val;
|
||||
}
|
||||
// Explicit free mode suppresses metered Google/OpenAI credentials even when
|
||||
// inherited from a shell. Optional free-account keys remain server-side.
|
||||
if (process.env.GEV_FREE_ONLY === '1') {
|
||||
process.env.GOOGLE_MAPS_API_KEY = '';
|
||||
process.env.OPENAI_API_KEY = '';
|
||||
}
|
||||
const env = { ...process.env };
|
||||
return {
|
||||
plugins: [
|
||||
freeServicesPlugin({ fetchJson: fetchRegionalJson }),
|
||||
liveViewsPlugin({ fetchJson: fetchRegionalJson }),
|
||||
cesium(),
|
||||
openSkyProxy(),
|
||||
celestrakProxy(),
|
||||
|
|
@ -7371,6 +7395,7 @@ export default defineConfig(({ mode }) => {
|
|||
},
|
||||
// Expose selected API keys to the browser via import.meta.env.*
|
||||
define: {
|
||||
'import.meta.env.GEV_FREE_ONLY': JSON.stringify(env.GEV_FREE_ONLY === '1'),
|
||||
'import.meta.env.GOOGLE_MAPS_API_KEY': JSON.stringify(env.GOOGLE_MAPS_API_KEY),
|
||||
'import.meta.env.CESIUM_ION_TOKEN': JSON.stringify(env.CESIUM_ION_TOKEN),
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue