mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e494d04dc6 | ||
|
|
17395749d4 | ||
|
|
5657668265 | ||
|
|
78fc5262f4 | ||
|
|
fce328b7cd | ||
|
|
44d9455738 | ||
|
|
3fc731c98b | ||
|
|
9b76ecfe03 | ||
|
|
bdf3848dbc |
No files matched your search
+2
-41
@@ -115,8 +115,7 @@ GEMINI_LIVE_MODEL=gemini-3.8-live
|
||||
# Client Token). Client-exposed by design, like GOOGLE_MAPS_API_KEY.
|
||||
MAPILLARY_CLIENT_TOKEN=
|
||||
|
||||
# Optional: OpenSky auth mode (oauth | auto | anon). OpenSky retired
|
||||
# username/password (basic) sign-in on 2026-03-18; "basic" now loads anonymously.
|
||||
# Optional: OpenSky auth mode (oauth | basic | auto | anon)
|
||||
# Default: oauth — requires client credentials below
|
||||
# Set to "anon" to skip aircraft auth (rate-limited but works)
|
||||
OPENSKY_AUTH_MODE=oauth
|
||||
@@ -129,9 +128,7 @@ OPENSKY_CLIENT_SECRET=
|
||||
# Optional paid Patreon tokens raise request limits; there is no free API key.
|
||||
LL2_API_TOKEN=
|
||||
|
||||
# Optional: OpenSky credentials JSON file path, used when the two values above
|
||||
# are empty: the credentials.json your OpenSky account page downloads when you
|
||||
# create an API client. Use an absolute path for compatibility with launchers.
|
||||
# Optional: OpenSky credentials JSON file path (alternative to above)
|
||||
# OPENSKY_CREDENTIALS_FILE=/path/to/credentials.json
|
||||
|
||||
# Optional: dev server config
|
||||
@@ -210,7 +207,6 @@ AISSTREAM_API_KEY=
|
||||
# CCTV_TFL_ENABLED=1 # set 0 to disable Transport for London JamCams
|
||||
# CCTV_TFL_MAX_SOURCES=250 # cap cameras loaded from TfL
|
||||
# CCTV_ONTARIO_ENABLED=1 # set 0 to disable Ontario 511 cameras
|
||||
# ONTARIO_511_API_KEY= # free developer key: sign up at 511on.ca/my511/register, then request it at 511on.ca/developers/doc
|
||||
# CCTV_ONTARIO_MAX_SOURCES=1000 # cap cameras loaded from Ontario 511
|
||||
# CCTV_FINTRAFFIC_ENABLED=1 # set 0 to drop the Fintraffic (Finland) pack
|
||||
# CCTV_FINTRAFFIC_MAX_SOURCES=300 # cap cameras loaded from the Fintraffic catalog
|
||||
@@ -231,9 +227,6 @@ AISSTREAM_API_KEY=
|
||||
# CCTV_CALGARY_ROWS_URL= # override the Open Calgary catalog URL
|
||||
# CCTV_CATALONIA_ENABLED=1 # set 0 to disable the Servei Català de Trànsit cameras
|
||||
# CCTV_CATALONIA_MAX_SOURCES=150 # cap cameras loaded from Catalonia (~130 exist today)
|
||||
# CCTV_HK_ENABLED=1 # set 0 to disable the Hong Kong Transport Department cameras
|
||||
# CCTV_HK_MAX_SOURCES=1100 # cap cameras loaded from Hong Kong (~1,013 exist today); min 8, max 1200
|
||||
# CCTV_HK_CAMERAS_URL= # override the DATA.GOV.HK camera locations XML URL
|
||||
# CCTV_KINGCOUNTY_ENABLED=1 # set 0 to disable the King County (Washington) road cameras
|
||||
# CCTV_KINGCOUNTY_MAX_SOURCES=125 # cap cameras loaded from the King County catalog
|
||||
# CCTV_WSDOT_ENABLED=1 # set 0 to disable the WSDOT (Washington State) highway cameras
|
||||
@@ -241,7 +234,6 @@ AISSTREAM_API_KEY=
|
||||
# CCTV_VEGVESEN_ENABLED=1 # set 0 to disable the Statens vegvesen (Norway) road cameras
|
||||
# CCTV_VEGVESEN_MAX_SOURCES=900 # cap cameras loaded from Statens vegvesen (default loads all ~850 live; max 1000)
|
||||
# CCTV_VEGVESEN_VIDEO=1 # set 0 to show stills instead of live HLS for Statens vegvesen video cameras
|
||||
# CCTV_VEGVESEN_HEIGHTS=1 # set 0 to skip Kartverket ground heights for Statens vegvesen cameras (flat prior instead)
|
||||
# CCTV_VEGVESEN_URL= # override the Statens vegvesen catalog URL
|
||||
# CCTV_NYC_ENABLED= # off by default; set 1 only if you hold a NYC DOT data-sharing agreement ([email protected]) for the camera feed
|
||||
# CCTV_NYC_MAX_SOURCES=250 # cap cameras loaded from the NYC TMC catalog (nearest borough cores first)
|
||||
@@ -253,11 +245,6 @@ AISSTREAM_API_KEY=
|
||||
# CCTV_LITHUANIA_MAX_SOURCES=400 # cap Lithuanian cameras; no API key required
|
||||
# CCTV_FLORIDA_ENABLED= # off by default; set 1 to load FL511 (Florida) cameras (individual, personal use only; any other use, including a public deployment, needs FDOT's express written consent; see DATA_SOURCES.md)
|
||||
# CCTV_FLORIDA_MAX_SOURCES=500 # cap cameras loaded from the FL511 catalog (nearest regional anchors first)
|
||||
# CCTV_TAIWAN_ENABLED=1 # set 0 to skip Taiwan Freeway Bureau (MOTC) road cameras
|
||||
# CCTV_TAIWAN_MAX_SOURCES=600 # cap Taiwan freeway cameras (1-2000); no API key required
|
||||
# CCTV_TAIWAN_URL= # override the Freeway Bureau MOTC 2.0 CCTV.xml URL
|
||||
# CCTV_A22_ENABLED=1 # set 0 to disable the Autostrada del Brennero (A22, Italy) webcams
|
||||
# CCTV_A22_URL= # override the Open Data Hub A22 webcam catalog URL
|
||||
# CCTV_MAX_SOURCES=4000 # catalog-wide ceiling, shared round-robin across packs
|
||||
# CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry
|
||||
# CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured
|
||||
@@ -265,32 +252,6 @@ AISSTREAM_API_KEY=
|
||||
# Optional: comma-separated Overpass URLs; use only instances you run or pay for.
|
||||
OVERPASS_UPSTREAMS=
|
||||
|
||||
# Optional: place-lookup endpoints. Lookups try bundled data first, then the
|
||||
# endpoint set here. Nominatim stays off unless NOMINATIM_URL is set; Photon
|
||||
# uses its public service when PHOTON_URL is unset.
|
||||
# NOMINATIM_URL: search endpoint for last-resort place search and voice
|
||||
# outlines (e.g. https://nominatim.example.org/search). A configured URL
|
||||
# selects the endpoint explicitly. Unset, empty and invalid values disable
|
||||
# Nominatim; nothing falls back to nominatim.openstreetmap.org. Use an endpoint
|
||||
# you operate or are authorized to use. Answers are cached (outlines 90 days,
|
||||
# places 30 days, no-results 24 h in .gev-cache/nominatim).
|
||||
# Explicit public-service configuration retains per-install pacing and caps;
|
||||
# these do not establish an application-wide allowance or authorize public use.
|
||||
# NOMINATIM_URL=
|
||||
# Public-instance requests per UTC day, outlines and search together. Not
|
||||
# applied to your own NOMINATIM_URL.
|
||||
# NOMINATIM_DAILY_CAP=50
|
||||
# Public-instance pacing is shared through .gev-cache/nominatim/state.json. If
|
||||
# the server stopped mid-request (or its disk filled), it leaves a
|
||||
# state.json.dispatch.* marker and refuses public lookups ("unfinished durable
|
||||
# Nominatim dispatch") for ten minutes rather than risk breaking the spacing;
|
||||
# the next lookup after that removes the marker and resumes with fresh spacing.
|
||||
# PHOTON_URL: keyless place search endpoint used by the browser
|
||||
# (e.g. https://photon.example.org/api/). Unset uses photon.komoot.io; set it
|
||||
# empty to disable. Only https endpoints are accepted (the page's
|
||||
# Content-Security-Policy allows no plain-http origin); any other value disables it.
|
||||
# PHOTON_URL=
|
||||
|
||||
# Optional: who may frame the app in embed mode (?embed=1), as CSP
|
||||
# frame-ancestors sources such as "https://example.com", or * for any page.
|
||||
# Unset, no page may frame it. Other pages of the app can never be framed.
|
||||
|
||||
@@ -40,7 +40,7 @@ body:
|
||||
attributes:
|
||||
label: OS, Node version, browser
|
||||
description: Include the versions you can find. Write "unknown" for anything unavailable.
|
||||
placeholder: "Windows 11, Node 24.22.2, Chrome 151"
|
||||
placeholder: "Windows 11, Node 24.14.0, Chrome 151"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
## What this changes
|
||||
|
||||
<!-- One or two sentences: what changed and why. Link the issue if there is one. -->
|
||||
|
||||
## How I verified it
|
||||
|
||||
- [ ] `npm run build`
|
||||
- [ ] `npm test`
|
||||
- [ ] `npm run test:track` (dev server running)
|
||||
- [ ] Feature gate for the area I touched: <!-- e.g. `node scripts/qa-radio.mjs`, or "none applies" -->
|
||||
|
||||
<!-- Screenshots are welcome for anything visual. -->
|
||||
|
||||
## New layer checklist
|
||||
|
||||
<!--
|
||||
Fill this in if the PR adds a new data layer. Delete the whole section if it doesn't.
|
||||
See "Adding a new layer" in CONTRIBUTING.md.
|
||||
-->
|
||||
|
||||
**Screenshots**
|
||||
|
||||
<!-- At least one screenshot of the layer on the globe, plus its info card or panel if it has one. -->
|
||||
|
||||
**Recording**
|
||||
|
||||
<!--
|
||||
Preferably 15 to 30 seconds: turn the layer on, move around, open a record.
|
||||
Drag an .mp4 or .mov in here; GitHub accepts up to 10 MB on free accounts.
|
||||
If it's bigger, link to it instead.
|
||||
-->
|
||||
|
||||
**Proposed category**
|
||||
|
||||
<!--
|
||||
Which layer-panel group should it sit in? The current groups are in PANEL_GROUPS
|
||||
in src/ui/layerPanel.js: Movement, Cameras, Infrastructure, Events, Weather, Audio,
|
||||
and Utilities (its rows render in the separate UTILITIES panel).
|
||||
If none fits, propose a new group and say why.
|
||||
-->
|
||||
|
||||
Category:
|
||||
|
||||
**Data source and licence**
|
||||
|
||||
- Source (provider and endpoint):
|
||||
- Licence or terms (link):
|
||||
- Attribution shown in the app:
|
||||
- Needs a key? (none / optional / required):
|
||||
- Fetched at runtime, or bundled in the repo:
|
||||
|
||||
<!-- Bundled data must be data we have the right to redistribute. -->
|
||||
|
||||
- [ ] Added a row to `DATA_SOURCES.md`
|
||||
- [ ] Credit is registered in `src/data/dataCredits.js`
|
||||
- [ ] Share-link token assigned with `npm run layer-token:next`, if the layer is shareable
|
||||
|
||||
## Docs
|
||||
|
||||
- [ ] `docs/CURRENT-STATE.md` updated and a `changelog.d/<pr-number>-<slug>.md` fragment added, or this PR doesn't change runtime behaviour
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [24.22.2, 26.x]
|
||||
node: [24.14.0, 26.x]
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: '1'
|
||||
GEV_REQUIRE_ALLOCATION_GATE: ${{ matrix.node == '24.22.2' && '1' || '0' }}
|
||||
GEV_REQUIRE_ALLOCATION_GATE: ${{ matrix.node == '24.14.0' && '1' || '0' }}
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 24.22.2
|
||||
node-version: 24.14.0
|
||||
cache: npm
|
||||
|
||||
- name: Install locked dependencies and headless Chrome
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 24.22.2
|
||||
node-version: 24.14.0
|
||||
cache: npm
|
||||
|
||||
- name: Run the Pinokio install path
|
||||
|
||||
@@ -20,8 +20,3 @@ node_modules
|
||||
.vite
|
||||
.vite-*
|
||||
*.min.js
|
||||
|
||||
# Application views, and panels lifted from them, hold the page markup
|
||||
# verbatim; reflowing it changes the DOM
|
||||
src/ui/views/*View.vue
|
||||
src/ui/views/panels/
|
||||
+1
-7
@@ -2,11 +2,5 @@
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"endOfLine": "lf",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.vue",
|
||||
"options": { "htmlWhitespaceSensitivity": "strict" }
|
||||
}
|
||||
]
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
+29
-720
@@ -1,707 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.0] — 2026-10-27 — Street Level, On Your Phone, in Your Language
|
||||
|
||||
- Show a Street Level view facing 359.5° or more as 0°. The viewer caption
|
||||
rounded the bearing without wrapping, so a view facing 359.6° read "360°"
|
||||
(Aditya, @Rohilalala, #1431).
|
||||
|
||||
- The visual styles and their presets mark their labels with the catalog-free
|
||||
`msg()`, so the share-link codec, and any server that loads it, no longer
|
||||
bundles every translation catalog (about 0.9 MB) (samehkhamis, #1421).
|
||||
|
||||
- Keep a location search that found nothing, or failed, in the field so it can
|
||||
be corrected: the query stays, focused and selected, and a persistent
|
||||
announced message under the search row says what to do next, instead of a
|
||||
toast followed by a cleared and closed field (samehkhamis, #1420).
|
||||
|
||||
- Hosts can read share links on their server: the share-link codec and the
|
||||
`/share/<code>` address helpers are now package exports,
|
||||
`@gods-eye-view/core/share/view-codec` and `@gods-eye-view/core/share/link-path`
|
||||
(samehkhamis, #1419).
|
||||
|
||||
- Share links are now short: the share button makes `/share/<code>` links
|
||||
that open on any copy of the app, follow the aircraft, military aircraft or
|
||||
satellite you were following, and fall back to its last-known position with
|
||||
a notice if it isn't reporting. A mistyped link is refused instead of
|
||||
opening a different view. Long `#v=2` links keep working (samehkhamis, #1418).
|
||||
|
||||
- Make Clear All clear the saved and shared layer selection. Layers that a
|
||||
context mode had already switched off no longer stay in the share link and
|
||||
come back on reload (samehkhamis, #1417).
|
||||
|
||||
- Restore a share link opened in an already-open tab. Pasting a link into the
|
||||
address bar, following one, or using Back/Forward now moves the camera and
|
||||
restores the style, map, panels and layers instead of changing only the URL
|
||||
(samehkhamis, #1416).
|
||||
|
||||
- Add a Scope checkbox and extend feathering to 200%, retaining scope settings
|
||||
across HUD layouts, share links and scenes. First run opens with Scope ON at
|
||||
100% feather; explicit settings and historical links retain their values. Give
|
||||
Tactical, Operator and Minimal text black backplates only when Scope is OFF,
|
||||
including vertical metadata, without changing the original HUD text color, glow or
|
||||
opacity. Scope ON retains its original HUD presentation (bilawalsidhu, #1410).
|
||||
Keep title and tagline positions stable when toggling Scope, include the eye
|
||||
in the title backing, and brighten the two secondary labels only with Scope OFF.
|
||||
|
||||
- Remove four modules nothing in the app imports: `src/app/sourceSlot.js`,
|
||||
`src/data/flowTiles.js`, `src/data/issPass.js` and `src/data/localGeojson.js`.
|
||||
The local GeoJSON layer tests now build layers the way the catalog does, so
|
||||
they keep covering `localGeojsonCore.js`. Package exports are unchanged
|
||||
(samehkhamis, #1403).
|
||||
|
||||
- Aircraft on the ground keep getting ground-height samples after the
|
||||
computer's clock steps back, instead of waiting for it to catch up
|
||||
(quanticsoul4772, #1399).
|
||||
|
||||
- The Sharpen slider at 0 now turns the sharpen pass off instead of still
|
||||
sharpening at a low amount (quanticsoul4772, #1398).
|
||||
|
||||
- Show a locally heard aircraft tracking 359.5° or more as TRK 0°. The Local
|
||||
ADS-B card rounded the track without wrapping, so 359.7° read "TRK 360°"
|
||||
(Rudy Celekli, @rudycelekli, #1396).
|
||||
|
||||
- Report a fire detection's acquisition time as a UTC timestamp in
|
||||
`get_active_fires`. The tool joined FIRMS's unpadded date and time, so a
|
||||
00:45 UTC detection read "2026-01-01 45"; it now reads
|
||||
`2026-01-01T00:45:00.000Z`. Missing or invalid dates and times remain
|
||||
unknown rather than rolling into another calendar day
|
||||
(Rudy Celekli, @rudycelekli, #1395).
|
||||
|
||||
- Report Docker container health with a local HTTP check using Node, with the
|
||||
same port parsing as the launcher (John Rogers, @14traits, #1394).
|
||||
|
||||
- Start the Docker install on SELinux hosts such as Fedora and RHEL. Compose now
|
||||
mounts the checkout with `:z`; without it the container could not read `/app`
|
||||
and stopped with "Cannot find module '/app/scripts/docker-start.mjs'"
|
||||
(Anıl Güvenç, @anilguvenc, #1391).
|
||||
|
||||
- Render awareness-overlay bearings and courses that round through north as
|
||||
`000°`, rather than `360°` (Rudy Celekli, #1390).
|
||||
|
||||
- Show cockpit wind from 359.5° or more as 0°, not 360°. Rounding 359.6°
|
||||
printed "N · 360°" on the local wind direction
|
||||
(Rudy Celekli, @rudycelekli, #1389).
|
||||
|
||||
- Show a drawn line of 999.5 m or more as 1.0 km. The draw hint kept metres
|
||||
below 1000, then rounded 999.6 m up to "1000 m"
|
||||
(Rudy Celekli, @rudycelekli, #1388).
|
||||
|
||||
- Show a Contacts entry 999.5 m or more away as 1.0 km. The panel kept meters
|
||||
below 1000, then rounded 999.6 m up to "1000 m"
|
||||
(Rudy Celekli, @rudycelekli, #1387).
|
||||
|
||||
- Show a route leg of 995 m or more as 1.0 km. The route summary and step
|
||||
cards kept meters below 1000, then rounded 996 m up to "1000 m"
|
||||
(Rudy Celekli, @rudycelekli, #1386).
|
||||
|
||||
- Show a route leg of 59.5 seconds or more as 1 min. The route summary and step
|
||||
cards kept seconds below 60, then rounded 59.6 s up to "60 s"
|
||||
(Rudy Celekli, @rudycelekli, #1385).
|
||||
|
||||
- The HUD's AI place summary drops a reply that arrives after the camera has
|
||||
started moving, so a summary asked for before a flight no longer names the
|
||||
old place mid-flight. Cancelled or partially typed summaries retry even if the place
|
||||
context stays the same (quanticsoul4772, #1384).
|
||||
|
||||
- Reduce repeated work in aircraft detection candidates by caching unchanged
|
||||
callsign/registration text and reusing resolved fleet models. Metadata updates,
|
||||
live positions and tracked-aircraft/model handoffs remain immediate
|
||||
(samehkhamis, #1379).
|
||||
|
||||
- Increase touch-first globe zoom sensitivity to three times the default while
|
||||
retaining Cesium collision and distance limits.
|
||||
- Reuse one delayed display timestamp per civil-aircraft fleet update and cache
|
||||
extrapolation coordinate frames until their fix coordinates change. Preserve
|
||||
contact counts, movement cadence, tracked motion and ground corrections.
|
||||
|
||||
- Turning a layer off frees the memory its entities held. Cesium kept
|
||||
removed layers' draw commands in reused per-pass arrays, so Data Centers
|
||||
kept 116 MiB after it was switched off, submarine cables 74 MiB and dams
|
||||
30 MiB; they now keep 13, 9 and 9 MiB (quanticsoul4772, #1367).
|
||||
|
||||
- Aircraft 3D models (Flights, Military Flights, Local ADS-B) skip Cesium's
|
||||
dynamic environment map, a sky render and GPU readback per model. With
|
||||
~150 models in the scene at Atlanta on Google 3D, GPU time per frame at
|
||||
rest falls from 14.3-14.8 ms to 12.1-12.5 ms; a same-model comparison
|
||||
showed no visible change (quanticsoul4772, #1366).
|
||||
|
||||
- Drop the decimal when fire radiative power rounds to 10 MW. A 9.96 MW
|
||||
detection was labeled 10.0 MW because the one-decimal cutoff ran before
|
||||
rounding (Rudy Celekli, @rudycelekli, #1354).
|
||||
|
||||
- Count a transit report that is 90 seconds old as one minute. The card kept
|
||||
seconds through 89s, then rounded 90s up to "Reported 2 min ago"
|
||||
(Rudy Celekli, @rudycelekli, #1353).
|
||||
|
||||
- Read a destination heading that rounds to north as 000. 359.6° was shown as
|
||||
360° on the destination bearing while the compass tape already uses 000
|
||||
(Rudy Celekli, @rudycelekli, #1352).
|
||||
|
||||
- Keep a tracked vessel's speed, course, heading and fix time when AISStream
|
||||
static data arrives; the static message only merges name, type, destination
|
||||
and IMO (rudycelekli, #1351).
|
||||
- Apply changed static metadata immediately, retaining known fields when a
|
||||
later static report omits them, without refreshing the vessel's position
|
||||
(samehkhamis, #1351).
|
||||
|
||||
- Convert NHC cyclone movement speed from miles per hour to knots, so
|
||||
`movement.speedKt` in cyclone query results is no longer about 15% high
|
||||
(rudycelekli, #1350).
|
||||
|
||||
- Label NHC cyclone classifications by their published meanings: `PTC` is a
|
||||
post-tropical cyclone, `PC` a potential tropical cyclone, and subtropical
|
||||
systems arrive as `STS` and `STD` (rudycelekli, #1349).
|
||||
|
||||
- Wake staged weather imagery on a 150 ms timer while waiting for readiness,
|
||||
avoiding a continuous render loop and cancelling the wake when work ends
|
||||
(@nu-stats, #1345).
|
||||
|
||||
- Phrase slight and sharp ramps as directions, so a ramp reads "slightly left"
|
||||
instead of "on the slightly left", and a lane names its side in both
|
||||
the proxy response and localized browser instructions
|
||||
(Rudy Celekli, @rudycelekli, #1343).
|
||||
|
||||
- Keep an impossible wildfire acquisition date off the detection clock, so a
|
||||
fire is not labeled with a later day it did not happen
|
||||
(Rudy Celekli, @rudycelekli, #1342).
|
||||
|
||||
- Reject impossible KartaView photo timestamps, so 31 February is unknown
|
||||
instead of rolling forward into March and passing the imagery date filter
|
||||
(Rudy Celekli, @rudycelekli, #1341).
|
||||
|
||||
- Read a hemisphere letter that is separated from its number, so a search for
|
||||
`40.7128 N 74.0060 W` flies there instead of falling through to the geocoders
|
||||
(Rudy Celekli, @rudycelekli, #1340).
|
||||
|
||||
- Carry HUD LAT/LON seconds that round up to 60.00" into the minutes and
|
||||
degrees, so longitude -122.35 reads `122°21'00.00"W` instead of
|
||||
`122°20'60.00"W` (rudycelekli, #1339).
|
||||
|
||||
- Improve touch selection of aircraft and vessels through the shared contact
|
||||
picker, reveal tracked aircraft details when opening Context, support downward
|
||||
swipe dismissal of phone drawers while preserving scrolling and controls, and
|
||||
enlarge the phone logo (samehkhamis, #1338).
|
||||
|
||||
- Evict terrain-height cache entries in fetch-time order without repeatedly
|
||||
sorting the full cache, while keeping refreshed points newest and ordering
|
||||
restored entries by their fetch time (daikaginza, #1326).
|
||||
|
||||
- Add a phone layout for viewports 720px and narrower: the globe stays full
|
||||
screen and a bottom tab bar (Layers, Cameras, Context, Search, More) opens one
|
||||
panel sheet at a time, keeping attribution visible and scene STOP reachable.
|
||||
Adapted from the phone tab-shell in #203 by blearymoth (samehkhamis, #1325).
|
||||
|
||||
- Export the KartaView and Panoramax Street Level providers, so a host composing
|
||||
its own provider list can offer the keyless providers without Mapillary
|
||||
(samehkhamis, #1324).
|
||||
- A Mapillary source can take a `graphEndpoint`, a Graph API base that adds the
|
||||
token itself (such as a same-origin proxy). Its sequence lookups go there
|
||||
through the source's transport, and the photo viewer uses MapillaryJS's
|
||||
Graph provider at that endpoint, so no client token is needed in the page
|
||||
(samehkhamis, #1324).
|
||||
|
||||
- Remove dead code across the repository: three modules nothing imports
|
||||
(`src/ui.js`, `src/standalone/ui.js`, `src/data/localLayers.js`), exports
|
||||
and locals nothing reads, a duplicate copy of the bundled dams data and an
|
||||
unused ship model. The app no longer builds an unused default wind layer
|
||||
and Bhote Koshi locator layer when it loads. Package exports are unchanged
|
||||
(samehkhamis, #1321).
|
||||
|
||||
- Validate provider response formats, strengthen camera source checks, and apply consistent API response protections (samehkhamis, #1317).
|
||||
|
||||
- Clarify shared field CSS ownership: CCTV owns calibration editor sizing,
|
||||
while Radio and Cyber supply explicit field palettes without changing the
|
||||
current appearance or native control behavior (mazy1998, #1309).
|
||||
|
||||
- Share read-only status badge presentation across CCTV source/frame labels,
|
||||
calibration provenance and Weather coverage, preserving status meanings,
|
||||
palettes, translations and announcements while preparing the view contract
|
||||
for a future Vue migration (mazy1998, #1306).
|
||||
|
||||
- Share native dropdown and numeric-field styling across Scene, CCTV, Display
|
||||
and Radio controls, with a shared CCTV number-input builder that preserves
|
||||
existing precision, validation and commit behavior (mazy1998, #1305).
|
||||
|
||||
- Point distances wrap longitude differences across the antimeridian, in voice
|
||||
proximity checks and `places_nearby` (quanticsoul4772, #1304).
|
||||
|
||||
- `get_wind` flags stale forecasts (`stale`) when the wind proxy serves a
|
||||
cached model run (quanticsoul4772, #1303).
|
||||
|
||||
- Give ordinary Scene and CCTV actions shared native buttons with consistent
|
||||
default, destructive and compact styling while preserving their existing
|
||||
behavior and specialized controls (mazy1998, #1302).
|
||||
|
||||
- Unify segmented selectors for model coverage, detection allocation, drawing
|
||||
shape, Street View and imagery comparison while preserving their existing
|
||||
behavior. Keep compact labels such as FILL and FLAT on one line, and let
|
||||
collapsed Weather release space to the open rail panel. Reserve the full mode
|
||||
button height in Recent Imagery so keyboard focus is not clipped (mazy1998, #1301).
|
||||
|
||||
- Add a shared Voice Preview and Settings panel with browser-local preferences,
|
||||
inactivity controls and accessible floating provider controls on compact layouts;
|
||||
recover inactivity after confirmed audio-less replies, release retired audio
|
||||
observers and keep cost warnings visible outside Settings
|
||||
(manjunath22466, #1299).
|
||||
|
||||
- Add Simplified Chinese (zh-CN) to the language picker, with localized dates
|
||||
and readable Chinese labels. Based on translations by @abiyeenzo (#914)
|
||||
and @EDIT-Aeterna (#979).
|
||||
(German, @gekh, #1298).
|
||||
|
||||
- Add the S2 GhostMaps layer: S2 Underground's public KMZ intel products
|
||||
(Common Intelligence Picture, 30-day kinetic pull, Border Crisis Map, Iran
|
||||
and Yemen products and the newest incident drop) from
|
||||
github.com/s2underground/GhostMaps, with one chip per product and report
|
||||
cards on click. `/api/ghostmaps` watches the repository's branch head every
|
||||
15 s and pushes each new commit to open pages, which reload only the
|
||||
products that changed (Black Pine Defense).
|
||||
- Clear a temporary GhostMaps upstream error and notify open pages when the
|
||||
repository becomes reachable again, including when no new edition was published.
|
||||
- Release packed-image resources when a load is cancelled or fails, respect
|
||||
injected or omitted GhostMaps sources, and wait for background cache warm-up
|
||||
before removing provider test fixtures.
|
||||
- Include Simplified Chinese translations and keep the combined voice prompt
|
||||
within its existing size limit while retaining all spoken aliases.
|
||||
|
||||
- A Street Level provider whose `zoomFor` still answers `{zoom, kind}`, the
|
||||
shape before the core went slim, draws its lines again instead of a silent
|
||||
blank; an overview level or any answer that is not a whole zoom shows the
|
||||
zoom-in hint (asturksever, #1288).
|
||||
|
||||
- Give ordinary sliders a shared native control and consistent track, thumb,
|
||||
value, focus and disabled styling while preserving feature behavior and
|
||||
specialized controls (mazy1998, #1287).
|
||||
|
||||
- The Street Level SINCE note sits on the value's line, in its type and a
|
||||
size smaller: with KartaView lit it reads ANY DATE (by upload date). It
|
||||
had lost its size variable in the Street Level slim-down and rendered
|
||||
larger than the value, on a line of its own (asturksever, #1286).
|
||||
|
||||
- Keep aircraft route labels on the scheduled airport leg instead of accepting
|
||||
its distant great-circle extensions, preserving generous endpoint tolerance
|
||||
across the signed-angle branch cut (rudycelekli, #1284).
|
||||
|
||||
- Report cached CelesTrak satellite records as stale in the Data Layers row,
|
||||
preserving separate core and dense catalog provenance through refresh,
|
||||
removal and lifecycle cleanup (rudycelekli, #1283).
|
||||
|
||||
- Pause periodic network updates in hidden tabs; the overdue update on return restarts the polling schedule, so the next poll follows one interval later even when that update fails (asturksever, #1282).
|
||||
- Report and guard browser visibility during Transit QA so hidden-page polling pauses cannot masquerade as playback regressions (asturksever, #1282).
|
||||
- Require each live city to supply its own feed vehicles in Transit QA; retained vehicles cannot qualify another city (asturksever, #1282).
|
||||
- Bound the Transit card-anchor probe by observed poll coverage instead of a fixed 20-second window (asturksever, #1282).
|
||||
- Keep visible-page Transit probes focused through the browser testing API (asturksever, #1282).
|
||||
- Add a real-browser regression gate for hidden-tab periodic work, explicit refresh, resume and teardown (asturksever, #1282).
|
||||
- Restore minimized test windows before screenshots so native compositor pauses cannot stall browser acceptance (asturksever, #1282).
|
||||
|
||||
- Hold rendering only for animated content (asturksever, #1281).
|
||||
- Exercise current vector road sources and published camera bounds in Traffic fixture QA (asturksever, #1281).
|
||||
- Establish browser-level foreground visibility for Traffic QA and reject hidden-page samples (asturksever, #1281).
|
||||
- Supply CORS headers on cross-origin traffic fixtures and report browser exceptions without disabling browser security (asturksever, #1281).
|
||||
- Isolate the Traffic keyless browser scenario from the keyed page’s workers and intercepted cache (asturksever, #1281).
|
||||
- Restore minimized test windows before screenshots so native compositor pauses cannot stall browser acceptance (asturksever, #1281).
|
||||
- Intercept only Traffic provider URLs, leaving Cesium worker and module loading untouched (asturksever, #1281).
|
||||
- Settle an unfinished Traffic height correction when the last animated point leaves, so the layer stops reporting loading once its render hold is released (asturksever, #1281).
|
||||
|
||||
- Throttle mission hover picks with a trailing check for the last pointer position, and clear stale cursors (asturksever, #1279).
|
||||
|
||||
- Reserve singleflight before disk cache reads (asturksever, #1278).
|
||||
|
||||
- Bound each height request to one upstream chunk (asturksever, #1276).
|
||||
|
||||
- Preserve enable during first-load replacement (asturksever, #1275).
|
||||
- Release disabled weather camera listeners and make teardown resource leaks fail the browser probe (asturksever, #1275).
|
||||
- Keep visible-page weather teardown probes focused through the browser testing API (asturksever, #1275).
|
||||
|
||||
- Scan the kml root delimiter in bounded time (asturksever, #1274).
|
||||
|
||||
- Retire unavailable history and keep local trails (asturksever, #1273).
|
||||
|
||||
- Normalize geocoder bias across the antimeridian (asturksever, #1272).
|
||||
|
||||
- Reject inherited style keys during restore (asturksever, #1271).
|
||||
|
||||
- Respect provider cooldowns and bound tle groups (asturksever, #1270).
|
||||
|
||||
- Coalesce refreshes and cool down opensky failures (asturksever, #1269).
|
||||
|
||||
- Normalize station tags and retain valid mixed rows; skipped malformed rows still report degraded coverage (asturksever, #1268).
|
||||
- Correct Radio QA push-to-talk setup to target the mounted voice controller (asturksever, #1268).
|
||||
|
||||
- Honor Docker Compose trusted-peer and allowed-host settings from the shell
|
||||
or project `.env`, and let `GEV_KEY_SETUP_TRUSTED_PEERS=none` disable gateway
|
||||
access to Provider Settings before Vite starts (asturksever, #1265).
|
||||
|
||||
- Prevent malformed Overpass filters from blocking the local server, and reject
|
||||
cross-site or excessive query requests before reading their bodies
|
||||
(asturksever, #1264; fixes #1248).
|
||||
- Release unread responses from the optional Google-token probe so keyless
|
||||
browser validation can settle; the Overpass gate recognizes only that probe's
|
||||
expected 404 as an unavailable capability (asturksever, #1264).
|
||||
- Make tracking arrival checks respect the aircraft update cadence while checking
|
||||
orientation on the first reveal, avoiding false failures at faster render
|
||||
rates (asturksever, #1264).
|
||||
|
||||
- The terrain-heights bounds tests no longer read the checkout's
|
||||
`.gev-cache/terrain-heights.json`. Each test gets a cache file of its own, so
|
||||
`npm test` no longer fails after the dev server has cached terrain near
|
||||
Austin (KnottyDyes, #1244).
|
||||
|
||||
- Replace the blocky JPEG sky around the globe with the 9,096 stars of the
|
||||
Yale Bright Star Catalogue, drawn as sharp points where they are at the
|
||||
scene time, over a soft Milky Way from ESO/S. Brunier (CC BY 4.0). The Sun
|
||||
and Moon stay; the anime style hides the Milky Way. Assets: 296 kB (gekh,
|
||||
#1243).
|
||||
|
||||
- `get_traffic_flow` flags stale cached TomTom tiles and reports the oldest
|
||||
tile fetch time (`stale`, `oldest_tile_at`), with pending freshness metadata
|
||||
released on failed or cancelled tile reads (quanticsoul4772, #1239).
|
||||
|
||||
- Add Taiwan's national freeway cameras from the Freeway Bureau, MOTC to CCTV:
|
||||
~1,800 cameras read at runtime from the official MOTC 2.0 open-data table,
|
||||
with on-demand snapshots and attribution. No API key is required; set
|
||||
`CCTV_TAIWAN_ENABLED=0` to skip it (419vive, #1237).
|
||||
|
||||
- Require a geocoded containment anchor for ambiguous analyst region names,
|
||||
preserve boundary provenance and cancellation safety, and retain exact-name
|
||||
precedence for natural outlines (manjunath22466, #1235).
|
||||
|
||||
- Serve the camera catalog while it refreshes. An expired catalog made every
|
||||
camera request wait for the slowest pack to reload; it is now answered at
|
||||
once while one background refresh runs, so only the first load waits. Estonia's
|
||||
slow Tarktee feeds get 30 s instead of 15 s, so the pack no longer drops out
|
||||
on about a third of loads (quanticsoul4772, #1234).
|
||||
|
||||
- Harden the Tarktee (Estonia) DATEX2 camera catalog fetches: refuse redirects,
|
||||
cancel both response streams when either response fails or overflows the 4 MB
|
||||
byte cap, and deduplicate camera IDs before per-pack prioritization
|
||||
(yaka0007, #1233).
|
||||
|
||||
- Skip bike-share stations with missing, blank or invalid coordinates instead
|
||||
of placing them at coerced zero coordinates or accepting out-of-range values.
|
||||
Explicit zero coordinates and numeric strings remain supported (Bortlesboat, #1232).
|
||||
|
||||
- OpenSky sign-in matches what OpenSky accepts since 2026-03-18. The server
|
||||
now reads `OPENSKY_CREDENTIALS_FILE` (the `credentials.json` OpenSky's
|
||||
account page downloads) on every platform, not only through the macOS
|
||||
launcher, and no longer sends retired username/password Basic auth:
|
||||
`OPENSKY_AUTH_MODE=basic` loads anonymously with one warning saying what to
|
||||
set instead (quanticsoul4772, #1229).
|
||||
- Credential-file warnings omit parser excerpts and filesystem error details;
|
||||
the path example also works with the macOS launchers.
|
||||
|
||||
- Load Ontario 511 cameras again. 511on.ca's camera endpoint now requires a
|
||||
free developer key; set `ONTARIO_511_API_KEY` to enable the pack, which is
|
||||
otherwise skipped with one log line saying where to get a key
|
||||
(quanticsoul4772, #1228).
|
||||
|
||||
- The first-run card can be closed on a phone or tablet. "ESC to dismiss" is
|
||||
now a button that closes the card the same way ESC does; touch screens read
|
||||
DISMISS, and phones no longer hide it, so choosing a mission is not the only
|
||||
way out (KnottyDyes, #1226).
|
||||
|
||||
- Add continuous globe rotation with direction and speed controls and a globe-fit
|
||||
check before starting (manjunath22466, #1225).
|
||||
|
||||
- Open God's Eye View from Pinokio in the system browser using the supported
|
||||
`popout` menu option. The application's framing policy is unchanged
|
||||
(NukeThemAII, #1222).
|
||||
|
||||
- Avoid starting or queuing a reply when the local channel rejects a typed OpenAI
|
||||
command, while preserving an earlier accepted deferred input
|
||||
(Manjunath, #1220).
|
||||
|
||||
- Keep NASA FIRMS fire and ALPR camera labels English in voice tool results
|
||||
(`track_entity`, `get_entity_context`, pointer context); the voice card still
|
||||
shows them in the interface language (daikaginza, #1207).
|
||||
|
||||
- Report actual outcomes when voice enables or disables CCTV through the shared
|
||||
layer lifecycle, including failed and cancelled changes (manjunath22466,
|
||||
#1205).
|
||||
|
||||
- Draw CCTV stills at their source aspect on the monitor plane instead of
|
||||
stretching them to the 16:9 canvas. Most DOT cameras publish 4:3 frames, so
|
||||
every still was widened; they are now centered and pillarboxed on the dark
|
||||
the placeholder already ends on (Danielslay86, #1201).
|
||||
|
||||
- Color CCTV cameras with a video feed violet. Cameras with an HLS, MP4 or
|
||||
WebM feed were the same cyan as stills, so a DelDOT or Statens vegvesen
|
||||
video camera could only be found by opening it. Stills stay cyan and the
|
||||
active camera stays amber, and focus de-emphasis fades the violet the same
|
||||
way (Danielslay86, #1200).
|
||||
|
||||
- Keep Space Missions marked STALE when Launch Library 2 falls back to cached
|
||||
launch data, and return to NOMINAL after a fresh response without discarding
|
||||
retained missions (rudycelekli, #1199).
|
||||
|
||||
- Share Launch Library request cache keys across installations refreshing in the
|
||||
same UTC minute, identify the app to the provider, and explain that standard
|
||||
space-missions access needs no key. Optional higher-rate LL2 tokens are paid
|
||||
upgrades; existing local cache and server-only token handling are unchanged
|
||||
(rudycelekli, #1197).
|
||||
|
||||
- Add nine Spanish city bike systems to Bike Share and `get_bike_share`: BiciMAD
|
||||
(Madrid), Valenbisi (Valencia), Bizi Zaragoza, Sevici (Seville), BIKI
|
||||
(Valladolid), BiciCoruña, Dbizi (Donostia-San Sebastián), Bilbao Bizi and
|
||||
BicinRivas, about 1,840 stations. Station names read underscores as word
|
||||
breaks, and station capacity counts docks rather than PBSC's geofenced area
|
||||
(ulihhhh, #1190).
|
||||
|
||||
- Keep missing launch-pad coordinates unknown so launches use the location
|
||||
fallback or stay off the map instead of appearing at 0°N 0°E. Missing stage
|
||||
flight numbers no longer show "FLIGHT 0"
|
||||
(Rudy Celekli, @rudycelekli, #1186).
|
||||
|
||||
- Flights' ground floor works across the antimeridian. The viewer-distance
|
||||
check behind the 150 km floor radius and its copy in the photoreal mesh-floor
|
||||
sampler subtracted longitudes without wrapping, so two points 21 km apart
|
||||
across 180° measured about 38,000 km and low or grounded aircraft just across
|
||||
the line from the viewer were never floored. A contact's display corridor
|
||||
walked a segment across 180° the long way round, spending its cell budget on
|
||||
ground behind the contact, and a contact on the seam lost its cell hysteresis.
|
||||
SEARCH NEARBY SITES on a view across 180° searched Google Places around
|
||||
longitude 0, on the far side of the globe, with a radius sized from the view's
|
||||
height alone; it now searches the view (Aditya, @Rohilalala, #1173).
|
||||
|
||||
- Keep missing CCTV numbers unknown instead of reading them as zero. `Number`
|
||||
turns null, blank strings and booleans into 0 or 1, and the camera source
|
||||
list sends an unknown number as JSON null, so a source-pack camera that left
|
||||
out its pose reached the browser as zeros clamped to each field's floor —
|
||||
facing north, pitch −2°, FOV 20°, range 120 m, mount 6 m and ground 0 m —
|
||||
instead of the defaults (id-hash heading, −17°, 74°, 700 m, 24 m and the
|
||||
city's ground); a null or blank position placed the camera at 0,0. The
|
||||
server's `toFiniteNumber` and the browser's `safeNumber` now treat absent,
|
||||
blank and boolean values as missing. The same conversion also stops a
|
||||
Tallinn row with a null heading reading as a confident north-facing camera,
|
||||
a Caltrans camera with a blank elevation losing its 150 m ground prior, and
|
||||
a TfL or Catalonia camera with a null or blank coordinate being kept at 0,0
|
||||
or on the zero meridian (omerbberk, #1172).
|
||||
|
||||
- Director shot actions land on the requested shot. A seek to a shot's start
|
||||
could round a hair below it and select the end of the previous shot for some
|
||||
shot counts and durations (Aditya, @Rohilalala, #1171).
|
||||
|
||||
- Fire-perimeter queries handle outlines and holes crossing the antimeridian:
|
||||
areas enclosed near ±180° are included, areas at the opposite meridian are
|
||||
excluded, and returned perimeter coordinates stay near the dateline. Incident
|
||||
cards use the same circular longitude mean and compare multipart outline
|
||||
areas along short longitude edges, keeping the card on the largest polygon
|
||||
(Arianna Method, @ariannamethod, #1142).
|
||||
|
||||
- Scenes keep the "Scene not saved" status when a capture, update, append or
|
||||
import cannot be saved, and an unreadable saved project no longer overwrites
|
||||
the recovery checkpoint. `startScene()` resolves to its documented result,
|
||||
`afterShotId` works without an explicit scene, and `scenesRun` counts the
|
||||
distinct scenes queued (djabhi31, #1135).
|
||||
|
||||
- Label camera-tool Street View fallbacks as historical Google imagery rather
|
||||
than the current camera view; keep unknown frame provenance explicit
|
||||
(rudycelekli, #1118).
|
||||
|
||||
- Release superseded Google Street View open waits and suppress metadata
|
||||
requests whose viewer was unmounted while the service loaded (rudycelekli,
|
||||
#1117).
|
||||
|
||||
- Exclude aircraft without a reported altitude from requested altitude bands;
|
||||
unfiltered aircraft queries still retain them (rudycelekli, #1115).
|
||||
|
||||
- A failed Esri Satellite or keyless terrain load still shows OSM or flat terrain
|
||||
at once, but no longer lasts the whole session: while the fallback is on
|
||||
screen, the real provider is retried in the background (30 seconds, doubling
|
||||
to a 10-minute cap) and replaces it live once it loads. Map switches never
|
||||
wait for a retry (Aditya, @Rohilalala, #1111).
|
||||
|
||||
- Skip TLE sets whose line 1 and line 2 carry different catalog numbers, so a
|
||||
spliced catalog cannot pair one satellite's elements with another's; the next
|
||||
valid set still parses (VG31OP, #1101).
|
||||
|
||||
- Overpass data whose element tags contain a failure phrase ("timed out",
|
||||
"runtime error", "too many requests" and similar, e.g. a place named "Timed
|
||||
Out Escape Rooms") is returned as data instead of being refused and putting
|
||||
the endpoint on cooldown; upstream failures are read from the remark (Aditya,
|
||||
@Rohilalala, #1097).
|
||||
|
||||
- Military aircraft keep their last heading and speed when an adsb.lol row omits
|
||||
`track` or `gs`, instead of turning north and stopping; a grounded row without
|
||||
a speed still reads 0 (Aditya, @Rohilalala, #1096).
|
||||
|
||||
- Update DOMPurify to 3.4.17, Sharp to 0.35.5 and source-map-js to 1.2.2 for
|
||||
GHSA-p98j-92pf-mc4p, GHSA-6688-9rhm-gjv2, GHSA-wq5f-xc86-pv6w and
|
||||
GHSA-68fv-2mgg-jv7q; `npm audit` now reports no vulnerabilities (NurstarK,
|
||||
#1094).
|
||||
|
||||
- Location search accepts two-dimensional WGS84 `geo:` URIs offline, with
|
||||
the existing coordinate framing and label. Unsupported URI fields are passed
|
||||
to the remaining search providers unchanged (cj-vana, #1071).
|
||||
|
||||
- Keyless standalone startup answers an unavailable Google tile-token endpoint
|
||||
with uncached 204 instead of a browser-visible 404. Existing providers take
|
||||
precedence, and unrelated API routes behave as before. (dvilrgamerz, #1066)
|
||||
|
||||
- On Windows, `npm run doctor` and the Pinokio install no longer print Node's
|
||||
DEP0190 warning ("Passing args to a child process with shell option true can
|
||||
lead to security vulnerabilities"): npm.cmd's fixed subcommand is now part of
|
||||
the shell command string instead of an argument array (quanticsoul4772, #1057).
|
||||
|
||||
- `aircraft_in_area` and `vessels_in_area` take `group_by` and `stats`, so an
|
||||
MCP client can ask which airlines, countries, aircraft types or ship types
|
||||
dominate an area, or the average altitude or speed, over every match instead
|
||||
of estimating from the returned rows. Airlines come from the ICAO designator
|
||||
in each callsign (SWA1234 counts as SWA), so the breakdown works on feeds
|
||||
that report no operator; operator and type are counted only where the feed
|
||||
reports them. Ship types count by family, so AIS codes 70 to 79 are all
|
||||
cargo; type 0 ("not available") and the reserved codes 100 to 255 count as
|
||||
unknown. Altitude and speed stats cover airborne aircraft only, so parked
|
||||
aircraft do not pull the means down. Every vessel answer, `find_vessel`
|
||||
included, now reads AIS ship-type codes as their family and a destination
|
||||
with no letters as unknown (quanticsoul4772, #1056).
|
||||
|
||||
- Add Hong Kong Transport Department traffic snapshot cameras as a keyless CCTV
|
||||
source pack: the public DATA.GOV.HK English locations XML, frames pinned to
|
||||
`tdcctv.data.one.gov.hk`, with DATA.GOV.HK Terms of Use attribution. By
|
||||
default nearly the full TD catalog (~1,013 cameras; cap 1,100) loads,
|
||||
prioritized across Hong Kong Island, Kowloon and the New Territories. Headings
|
||||
use the shared id-hash fallback at low confidence. `CCTV_HK_MAX_SOURCES` sets
|
||||
the cap (8–1,200) and `CCTV_HK_ENABLED=0` turns the pack off (JCY2515, #990).
|
||||
|
||||
- Cross-site pages can no longer fill the terrain-height cache or spend the
|
||||
visitor's own `/api/terrain/heights` rate-limit quota through the visitor's
|
||||
browser. The route refuses a cross-site `Sec-Fetch-Site` or a foreign or
|
||||
opaque `Origin` before rate limiting, cache access or upstream work. Unlike
|
||||
the cost-bearing routes it still answers same-origin requests that arrive
|
||||
through a reverse proxy, so terrain keeps rendering behind nginx, Caddy or
|
||||
Cloudflare instead of falling back to the geoid (Sunil56224972, daikaginza,
|
||||
#975).
|
||||
|
||||
- Street Level is rebuilt around one small provider registry: Mapillary,
|
||||
KartaView, Panoramax and Google Street View share one coverage drawer and
|
||||
one photo viewer, one source lit at a time. Floating and docked panels,
|
||||
FOLLOW, surface modes, the legend and the Mapillary proxy's disk cache are
|
||||
gone; `.gev-cache/mapillary/` from earlier runs is no longer read and can be
|
||||
deleted (gekh, #974).
|
||||
|
||||
- Cross-site browser requests can no longer spend the TomTom traffic-tile
|
||||
budget through `/api/tomtom/*`. The shared same-site gate runs before status,
|
||||
cache, and upstream work; same-origin app requests and non-browser local
|
||||
callers remain supported (Sunil56224972, #973).
|
||||
|
||||
- Recent Imagery: opening DETAILS scrolls its card into view when it opens below
|
||||
the fold. The panel used to restore the previous scroll position right after
|
||||
the reveal, so the card stayed out of sight (Ian Blenke, #970).
|
||||
|
||||
- Harden Vite's filesystem deny list for local-only artifacts: repo `dist/`,
|
||||
`.gev-logs/`, and `.gev-cache/` are denied without blocking dependency
|
||||
`dist/` directories, and checkout paths containing glob metacharacters are
|
||||
escaped before building the root-anchored pattern (blowing-a-raspberry, #948).
|
||||
|
||||
- Give each Statens vegvesen camera its own ground height from Kartverket's
|
||||
keyless point-height API (CC BY 4.0) instead of one flat 150 m prior, so
|
||||
mountain-pass cameras no longer sit hundreds of metres below the terrain
|
||||
where neither the point-height prior nor the ground snap resolves. About 850
|
||||
of ~855 cameras get a height; the rest keep the old prior. Requests carry 20
|
||||
points, the most the API answers from its 1 m terrain model, eight at a
|
||||
time; a cold lookup takes about 6–13 s. Cameras over water use the water
|
||||
surface rather than the sea-floor depth the API returns there; for bridge
|
||||
cameras that is still below the deck. Heights are cached for good and misses
|
||||
for 6 h, so a refresh only asks about new or moved cameras. After a
|
||||
failing request (a 408, 429 or 5xx, a network error, a request over 10 s or
|
||||
an unreadable body) the lookup stops and backs off (Retry-After, else
|
||||
30 min, longer than the refresh). The lane waits at most 12 s from its
|
||||
start, and only on a cold cache; a slower first lookup leaves the cameras it
|
||||
had not reached on the flat prior until the next 15-minute refresh, and
|
||||
keeps filling the cache meanwhile.
|
||||
`CCTV_VEGVESEN_HEIGHTS=0` turns it off. Follow-up to #929 (trygvehhh, #945).
|
||||
|
||||
- Extract a reusable model-free World Packet from the existing voice
|
||||
`get_current_view_state` owner without restoring disabled layers to the
|
||||
model context. Keep the enabled-only payload, disabled-layer count, source
|
||||
provenance, action result and view-state contract; read the layer roster once
|
||||
per packet; voice retains the speech/card envelope at its own boundary.
|
||||
Credits: original GEV voice/layer contributors and #880 implementation.
|
||||
|
||||
- Add optional Gemini Live voice to the standalone app, with a provider chooser,
|
||||
server-held API key, microphone streaming, shared map actions and captions.
|
||||
Switching providers ends the old conversation and preserves the input mode
|
||||
(Manjunath, #872).
|
||||
|
||||
- Close the compact Radio disclosure when an adaptive tactical right-rail
|
||||
restore directly expands Context, before Space Missions can be covered.
|
||||
A repeated Context-open request also clears stale disclosure state.
|
||||
Real PanelChrome constructor wiring and the rail reflow path are covered
|
||||
by focused regressions. Credits: kvnloo (#816), raysung-2026's original
|
||||
#804 report, asturksever's review, and quanticsoul4772's independent
|
||||
Windows diagnosis.
|
||||
|
||||
- Changelog entries are now one file per pull request. A change adds
|
||||
`changelog.d/<pull request>-<slug>.md` instead of editing the top of
|
||||
`CHANGELOG.md`, which two pull requests in flight could not share — it was the
|
||||
most frequent merge conflict in the open queue, and for six of them the only
|
||||
one. `npm run changelog` splices the directory into `CHANGELOG.md` at release
|
||||
time and empties it; `npm run changelog:check` reports the batch and changes
|
||||
nothing. Fragment text is copied through unchanged (daikaginza, #787).
|
||||
|
||||
- Read satellite catalog numbers written in Alpha-5, the TLE spelling of
|
||||
numbers above 99999: a letter for the first two digits, skipping I and O
|
||||
(`A0000` is 100000, `Z9999` is 339999). The satellites layer converted such a
|
||||
field to `NaN`, so every Alpha-5 satellite shared one catalog entry and all
|
||||
but the first were dropped as duplicates; the satellite tools read the same
|
||||
field as no number, so such a satellite could not be found by it. The layer
|
||||
and the tools now decode the field in `src/sources/tle.js`, and reject a
|
||||
malformed one (a sign, decimal point, hex, embedded space or stray letter)
|
||||
instead of reading part of it as a number. This is Alpha-5 TLE
|
||||
compatibility: CelesTrak's TLE feed carries no satellite above 99999, which
|
||||
needs the OMM feed (#1191) (Honorius Neogy, #767, fixes #751).
|
||||
|
||||
- The dev server no longer watches `.gev-cache` and `.gev-logs`. Providers write
|
||||
cache files and logs there while the app runs, and none of it is source, so
|
||||
the watcher now skips it; this guards against the DNS-lookup delays heavy
|
||||
cache writes have caused, though no difference was measured on current main
|
||||
(monwiss, #761).
|
||||
|
||||
- Bound what a caller can grow through `/api/terrain/heights`. Every distinct
|
||||
5-decimal point was a permanent in-memory entry, a permanent line in an
|
||||
uncapped `.gev-cache/terrain-heights.json`, and a live upstream call, with
|
||||
nothing limiting the rate. The point cache now holds at most 50,000 entries
|
||||
and evicts oldest-first, a cache file above
|
||||
24 MB is not read back, and the route is limited to 90 requests/minute per
|
||||
client (270 global). Evicting a point costs one refetch, never a wrong
|
||||
answer, since terrain does not move. Ordinary use is unaffected: a full
|
||||
`test:track` run drives 10 requests, peaking at 8/min (daikaginza, #686).
|
||||
|
||||
- Add FL511 (Florida DOT) traffic cameras as an opt-in CCTV source pack:
|
||||
the statewide ArcGIS catalog, read a page at a time under a byte cap with
|
||||
redirects refused, and frames pinned to FL511's two image hosts over HTTPS.
|
||||
FL511's terms limit its content to individual, personal use, and any other
|
||||
use (a public deployment included) needs FDOT's express written consent, so
|
||||
the pack stays off unless `CCTV_FLORIDA_ENABLED=1`. Headings come from the
|
||||
catalog's direction codes, and camera ids come from the image channel, since
|
||||
the catalog's ID column repeats across regions. `CCTV_FLORIDA_MAX_SOURCES`
|
||||
sets the cap (Danielslay86, #605).
|
||||
|
||||
- The client's great-circle distances now come from one shared
|
||||
`src/geoDistance.js` (`greatCircleKm`/`greatCircleMeters`, `{lat, lon}`
|
||||
points). The Earth radius is standardized on 6371.0088 km, so distances shift
|
||||
by about 1.4 ppm and region areas by about 2.8 ppm, and missing or
|
||||
out-of-range points return `Infinity` (onionviolet, #287; Rajojha-1, #749).
|
||||
|
||||
- The tracked-aircraft descriptor now publishes `verticalRateMps`, on both the
|
||||
civil and military layers, so a consumer reading `getTrackedInfo()` no longer
|
||||
has to reach past it into the raw poll record for climb/descent. Reported,
|
||||
never derived: an aircraft that has not transmitted a rate reads as `null`
|
||||
rather than a level-flight `0`.
|
||||
|
||||
- Bound how often the GBFS proxy will relay. `/api/gbfs` already restricted
|
||||
where it could go — https, an allowlisted host, a station feed path, no
|
||||
redirects, a 12-second deadline and a 5 MB body cap — but nothing restricted
|
||||
how often, so the dev server would make unlimited requests at a third-party
|
||||
bikeshare operator on any caller's behalf. The route now admits 120
|
||||
requests/minute per client address, refusing the rest with 429 and
|
||||
`Retry-After` before any outbound fetch. The bikeshare layer needs about 15 a
|
||||
minute with every catalogued system in range, so ordinary use is unchanged.
|
||||
|
||||
- Add nine Ontario transit feeds: TTC, MiWay, Hamilton HSR, Durham Region,
|
||||
YRT/Viva, Guelph, Burlington, Barrie and Kingston, each carrying its own
|
||||
licence quote and attribution. A feed can now name its own modes, so Toronto's
|
||||
@@ -978,6 +282,8 @@
|
||||
point. A slow render gets one bounded second frame request, while hidden,
|
||||
stale, moved-camera, invalid and oversized captures still fail closed.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Add a keyless Air quality layer to the Weather group, with Environment and
|
||||
Climate Change Canada's AQHI as the first registered provider. Readings carry
|
||||
`{ provider, scale, value, band }` so another network can be added without
|
||||
@@ -1192,28 +498,6 @@
|
||||
needs only the Map Tiles API and the Maps JavaScript API (Street View). Keyless sessions and the Photon/Nominatim
|
||||
fallback order are unchanged.
|
||||
|
||||
- Voice can switch on every shipped data layer (the two scene-driven
|
||||
Bhote Koshi layers stay scene-only), including transit, Recent
|
||||
Imagery, wind, the weather layers, cyclones and mapped installations, and
|
||||
understands their spoken names ("buses", "hurricanes", "military bases").
|
||||
One voice manifest (`src/voice/layerManifest.js`) generates the tool enums
|
||||
and aliases, and a unit test fails when a new layer has no entry.
|
||||
- `analyst_query` counts up to 250,000 loaded records per layer instead of the
|
||||
first 2,000, and says when a layer went past that (`complete: false`). It
|
||||
also answers over bikeshare stations, transit vehicles, rocket launches,
|
||||
cyclones, ALPR cameras, mapped installations, local ADS-B aircraft and fire perimeters; fire
|
||||
records carry their sensor (VIIRS or MODIS).
|
||||
- Unknown fields, operators, units, value types, scope kinds and invalid
|
||||
centres are refused with the allowed values instead of answering zero or widening to anywhere. A
|
||||
layer that is off, still loading or unavailable is reported as such, not
|
||||
as a count of zero; a partly answerable query is marked `partial`.
|
||||
Distance sorts honour `sortDir`, a written unit such as `altitudeM(ft)`
|
||||
converts, and large rankings no longer sort the whole set.
|
||||
- Results carry `lat`/`lon` for each item and a `display` object with the
|
||||
scope, the feed window (earthquakes and fires cover the last 24 h) and any
|
||||
caveat for the screen. The model is asked to answer in one short phrase.
|
||||
- The weather and Recent Imagery panels can be opened by voice.
|
||||
|
||||
## [0.2.1] — 2026-10-02 — God's Eye View through Your Agent (with security fixes and other features)
|
||||
|
||||
- Ship each bundled data pack once. The region, marine, admin-boundary,
|
||||
@@ -1496,6 +780,32 @@
|
||||
(`src/ui/imagerySplit.js`, `src/maps/imageryComparison.js`), and
|
||||
`MapSourceController.subscribe()` reports every settled map switch.
|
||||
|
||||
## Unreleased — voice reaches every layer; honest analyst answers
|
||||
|
||||
- Voice can switch on every shipped data layer (the two scene-driven
|
||||
Bhote Koshi layers stay scene-only), including transit, Recent
|
||||
Imagery, wind, the weather layers, cyclones and mapped installations, and
|
||||
understands their spoken names ("buses", "hurricanes", "military bases").
|
||||
One voice manifest (`src/voice/layerManifest.js`) generates the tool enums
|
||||
and aliases, and a unit test fails when a new layer has no entry.
|
||||
- `analyst_query` counts up to 250,000 loaded records per layer instead of the
|
||||
first 2,000, and says when a layer went past that (`complete: false`). It
|
||||
also answers over bikeshare stations, transit vehicles, rocket launches,
|
||||
cyclones, ALPR cameras, mapped installations, local ADS-B aircraft and fire perimeters; fire
|
||||
records carry their sensor (VIIRS or MODIS).
|
||||
- Unknown fields, operators, units, value types, scope kinds and invalid
|
||||
centres are refused with the allowed values instead of answering zero or widening to anywhere. A
|
||||
layer that is off, still loading or unavailable is reported as such, not
|
||||
as a count of zero; a partly answerable query is marked `partial`.
|
||||
Distance sorts honour `sortDir`, a written unit such as `altitudeM(ft)`
|
||||
converts, and large rankings no longer sort the whole set.
|
||||
- Results carry `lat`/`lon` for each item and a `display` object with the
|
||||
scope, the feed window (earthquakes and fires cover the last 24 h) and any
|
||||
caveat for the screen. The model is asked to answer in one short phrase.
|
||||
- The weather and Recent Imagery panels can be opened by voice.
|
||||
|
||||
## Unreleased — local receiver feeds
|
||||
|
||||
### Local receiver feeds
|
||||
|
||||
- The Local ADS-B layer also reads local 1090 MHz and 978 MHz UAT decoder
|
||||
@@ -1638,6 +948,7 @@
|
||||
Add feed provenance to analyst/view answers and HUD context while retaining existing response fields and runner ownership (Matt Van Horn, #347).
|
||||
|
||||
Analyst records for loaded satellites, datacenters and dams, with explicit bounded count/rank coverage (Matt Van Horn, #351).
|
||||
|
||||
- New Fire Perimeters layer (Events group): live NIFC WFIGS interagency
|
||||
wildfire incident perimeters as ground-clamped polygons with a
|
||||
containment-colored fire line, refreshed every 5 minutes from the public
|
||||
@@ -1914,7 +1225,6 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
|
||||
|
||||
### Other changes
|
||||
|
||||
|
||||
- Add ECMWF IFS model selection to Wind (#464, thanks @beneduzi), with model-scoped forecast-step caches, cancellation of replaced requests, and separate issue/valid timestamps.
|
||||
|
||||
- Add bounded Director feature actions with accessible controls, explicit camera/layer admission and cancellation; restore pack geometry on same-shot seek. Preserve existing scenes and content attribution.
|
||||
@@ -2723,4 +2033,3 @@ represent previously published GitHub Releases.
|
||||
## [0.1.0] — 2026-02-09
|
||||
|
||||
- Initial project version.
|
||||
|
||||
+7
-112
@@ -4,15 +4,13 @@ Thanks for being here. God's Eye View is an open foundation for live spatial int
|
||||
|
||||
## Getting set up
|
||||
|
||||
Use Node.js 24 (24.14.0 or later) or 26.x (also enforced by `package.json`).
|
||||
Prefer 24.22.1 or later: earlier releases lack the October 21, 2026 security
|
||||
fixes, including CVE-2026-111497 (a Brotli decoder flaw).
|
||||
Use Node.js 24.14.x or 26.x (also enforced by `package.json`).
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bilawalsidhu/gods-eye-view.git
|
||||
cd gods-eye-view
|
||||
nvm install 24.22.2
|
||||
nvm use 24.22.2
|
||||
nvm install 24.14.0
|
||||
nvm use 24.14.0
|
||||
npm install
|
||||
npm run doctor
|
||||
./scripts/dev-fresh.sh # or: npm run dev (keys are optional)
|
||||
@@ -29,25 +27,6 @@ through Pinokio; the terminal path above remains the contributor path.
|
||||
|
||||
Open `http://localhost:4173`. Before sending a PR run `npm run build`, `npm test`, and `npm run test:track` (dev server must be up) — **all three must stay green.**
|
||||
|
||||
### Test prerequisites
|
||||
|
||||
- **`npm test`** — unit tests. Runs standalone; no dev server or credentials
|
||||
required. Exit code is the gate.
|
||||
- **`npm run test:track`** — the 3D flight-tracking regression harness. It
|
||||
drives the **real app** in headless Chromium, so it **requires a running
|
||||
dev server** (default `http://localhost:4173`). To target another instance,
|
||||
run `npm run test:track -- --url <app-url>` (or
|
||||
`node scripts/track-regression.mjs --url <app-url>`). It does not depend on
|
||||
live OpenSky / adsb.lol / AISStream (those are optional or rate-limited
|
||||
locally); it installs a persistent `fetch` shim that returns synthetic
|
||||
aircraft in the exact upstream payload shapes the layers parse.
|
||||
- **`npm run build`** — production Vite build. No server or credentials
|
||||
required; it validates that the client bundle compiles cleanly.
|
||||
|
||||
Each feature also has its own headless gate; see
|
||||
[Feature regression gates](#feature-regression-gates) below. Docs and changelog
|
||||
rules are under [Pull requests](#pull-requests).
|
||||
|
||||
## Checking a built app locally
|
||||
|
||||
Run `npm run build` followed by `npm run preview`. Preview serves the built
|
||||
@@ -129,15 +108,15 @@ prints anything; never fix that by renaming or deleting PR A's published token.
|
||||
The highest-leverage places to jump in:
|
||||
|
||||
- **🌆 Add a CCTV source pack.** Austin is the reference camera source. Adding another city means a clean public camera catalog with coordinates, attribution, and server-registered frame URLs (the proxy only fetches registered URLs — never client-supplied ones, see [SECURITY.md](SECURITY.md)). City packs are the best first lane.
|
||||
- **🛰️ Add or improve a data layer.** Layer factories live in `src/layers/<family>/`, with source, record, controller and renderer owners implementing the layer interface (`init/enable/disable/update/destroy/getStats`, optional `getDetectableObjects`/`getStats`). Use an existing layer as a template, and see [Adding a new layer](#adding-a-new-layer) for what the PR needs.
|
||||
- **🛰️ Add or improve a data layer.** Layer factories live in `src/layers/<family>/`, with source, record, controller and renderer owners implementing the layer interface (`init/enable/disable/update/destroy/getStats`, optional `getDetectableObjects`/`getStats`). Use an existing layer as a template.
|
||||
- **🎙️ Extend voice control.** Voice arguments are defined in `src/voice/actionSchemas.js`, with server-side descriptions in `server/providers/openai/tools.js` and client-side execution in `src/voice/gevActions.js`. Keep the tool surface tight and the responses honest (confirm only what actually happened).
|
||||
- **🎨 Add a visual style.** Styles are GLSL post-process shaders in `src/styles/`.
|
||||
- **🐛 Fix bugs / improve the first-run experience.** See [docs/KNOWN-ISSUES.md](docs/KNOWN-ISSUES.md).
|
||||
|
||||
## Architecture in one minute
|
||||
|
||||
- **Vue 3 + Pinia for the interface,** [CesiumJS](https://cesium.com/platform/cesiumjs/) for the globe and [Vite](https://vitejs.dev/) to build. See [Building UI](#building-ui).
|
||||
- **Assembly lives in `src/app/`; standalone defaults live in `src/standalone/`.** UI controllers live in `src/ui/`, layer factories in `src/layers/`, portable sources in `src/sources/`, and application operations in `src/services/`. Existing `src/data/<layer>.js` entries retain compatibility; new code belongs with its focused owner.
|
||||
- **No framework.** Vanilla JS + [CesiumJS](https://cesium.com/platform/cesiumjs/) + [Vite](https://vitejs.dev/).
|
||||
- **Assembly lives in `src/app/`; standalone defaults live in `src/standalone/`.** UI controllers live in `src/ui/`, layer factories in `src/layers/`, portable sources in `src/sources/`, and application operations in `src/services/`. Existing `src/ui.js` and `src/data/<layer>.js` entries retain compatibility; new code belongs with its focused owner.
|
||||
- Sources acquire records; renderers own Cesium resources. Import `gods-eye-view/layers/<family>/source` when only a source factory is needed. Common voice controls consume the session interface; protocol adapters own connection details.
|
||||
- **Secrets stay server-side.** Anything needing a private key goes through a local proxy under `server/providers/`. The browser only ever sees the Google Maps key (which you restrict) and ephemeral tokens.
|
||||
- `docs/CURRENT-STATE.md` is the authoritative runtime reference — read it first.
|
||||
@@ -147,65 +126,9 @@ The highest-leverage places to jump in:
|
||||
- ES modules, **2-space indent, single quotes, semicolons.**
|
||||
- JSDoc on exported/public functions.
|
||||
- Match the surrounding code — comment density, naming, and idiom.
|
||||
- User-visible text goes through `t()` or `tn()`, with `msg()` marking English that is stored and translated later. Components call `t()` in their templates, so their text follows the locale; `data-i18n*` attributes are only for static page markup such as the loading screen, and `npm run i18n` rejects them in a template. `npm run i18n` must pass. See [docs/I18N.md](docs/I18N.md).
|
||||
- User-visible text goes through `t()`, `tn()` or `msg()`, or a `data-i18n*` attribute in markup, and `npm run i18n` must pass. See [docs/I18N.md](docs/I18N.md).
|
||||
- Prefer small, reviewable commits. Conventional-commit-style prefixes (`feat:`, `fix:`, `perf:`, `docs:`) are appreciated but not required.
|
||||
|
||||
## Building UI
|
||||
|
||||
New UX elements are Vue components whose state lives in a Pinia store, and so
|
||||
are existing ones when a change converts them:
|
||||
|
||||
- **Views are Vue components.** Application views live in `src/ui/views/` and
|
||||
the components they place in `src/ui/views/panels/`; a component that
|
||||
belongs to one feature sits beside its owner (`src/ui/rail/`,
|
||||
`src/ui/weather/`). A view renders what its store holds and forwards
|
||||
clicks and input to the store; it holds no application logic.
|
||||
- **State lives in a Pinia store** in `src/ui/stores/`. The owner presents a
|
||||
plain view object and publishes it to the store; the view renders it. User
|
||||
actions return to the owner through handlers it connects to the store,
|
||||
which replace an earlier owner's. Where several owners answer different
|
||||
actions of one store, use `createOwners()` from `src/ui/stores/owners.js`:
|
||||
`act(name, ...args)` calls the newest connected owner that handles `name`,
|
||||
and the owner calls `detach(token)` when it stops.
|
||||
- **Portable owners stay framework-free.** An owner in a portable export
|
||||
imports neither Vue nor Pinia: it takes a `publish` option or offers a
|
||||
`subscribe…()`, and the application shell connects it to the store.
|
||||
`npm run check:boundaries` enforces this.
|
||||
- **Reuse the kit first.** `src/ui/kit/` has one component per control:
|
||||
`UiButton` (including pressed toggles and pressable chips), `UiSelect`,
|
||||
`UiSlider`, `UiSegmented`, `UiChip` (a label, not a control), `UiBadge`,
|
||||
`UiEmpty`, `UiIcon`, `UiTooltip`, `UiPanel` and `UiRail`. Use these before
|
||||
writing a control. When the kit lacks something, extend a kit component or
|
||||
add one that follows the kit's names, props, events and class names (see
|
||||
[Component kit](docs/VUE-MIGRATION.md#component-kit)), rather than
|
||||
building a one-off.
|
||||
- **No hand-written DOM binding.** Don't build elements or write text,
|
||||
classes or attributes from a controller to show UI state; render it from
|
||||
the store. New controls take input through the store rather than through
|
||||
listeners the owner adds. To render outside the view's host (the body, the
|
||||
globe container, or a slot another surface opens), use `<Teleport>` to
|
||||
`body`, a fixed id or a container the owner publishes; don't move nodes by
|
||||
hand. What stays imperative: classes on `<body>` and `<head>`, which sit
|
||||
outside Vue (`cockpit-mode`, `ui-clean-view`); focus moves; positions a
|
||||
layout pass measures in the same task that writes them; and surfaces an
|
||||
owner paints or plays, such as the Cesium canvas, overlay canvases and live
|
||||
video. A few existing surfaces keep listeners or bindings that
|
||||
[docs/VUE-MIGRATION.md](docs/VUE-MIGRATION.md) records; keep those
|
||||
contracts when you change them, and don't copy them into new UI.
|
||||
- **Text** goes through `t()` or `tn()` where the template renders it.
|
||||
English kept in a constant or a store is marked with `msg()` and passed to
|
||||
`t()` when it renders, since a `t()` at module load would fix the English.
|
||||
- **Test components, not markup strings.** Mount the component over
|
||||
happy-dom with `@vue/test-utils` or `createApp` and assert what it renders
|
||||
from its store (see `src/testSupport/vueDom.mjs`, `src/ui/kit/*.test.mjs`
|
||||
and `src/ui/views/intelHudView.test.mjs`). Run one such file with
|
||||
`node --import=./build/registerVue.js --test <file>`. Don't pass happy-dom
|
||||
nodes to `assert.equal`/`deepEqual`; compare them with `assertSameNode()`
|
||||
from `src/testSupport/domAssert.mjs`.
|
||||
|
||||
[docs/VUE-MIGRATION.md](docs/VUE-MIGRATION.md) records how each surface moved
|
||||
and what stays imperative, and why.
|
||||
|
||||
## Formatting and reusable components
|
||||
|
||||
Run `npm run format` before submitting changes, then `npm run format:check`
|
||||
@@ -230,34 +153,6 @@ ownership and adoption process.
|
||||
3. If you change runtime behavior, update `docs/CURRENT-STATE.md` in the same PR, and add your changelog entry as `changelog.d/<pr-number>-<slug>.md` instead of editing `CHANGELOG.md` — see [changelog.d/README.md](changelog.d/README.md). Distinct filenames don't conflict; a release assembles the directory into `CHANGELOG.md`. A documentation claim that needs a regression test should assert against `docs/CURRENT-STATE.md` or `README.md`, which a PR still edits directly.
|
||||
4. If you add or change a data source, update [DATA_SOURCES.md](DATA_SOURCES.md) with its license and attribution. **Don't add data you don't have the right to redistribute** — fetch it at runtime instead.
|
||||
5. Describe what you changed and how you verified it (screenshots welcome for anything visual).
|
||||
6. If you add a new layer, fill in the new-layer section of the PR template. See [Adding a new layer](#adding-a-new-layer).
|
||||
|
||||
## Adding a new layer
|
||||
|
||||
A new layer changes what everyone sees on the globe, so maintainers need to see
|
||||
it working and know where its data comes from before reviewing the code. PRs
|
||||
that add a layer must include all four of these in the description. The PR
|
||||
template has a section for them.
|
||||
|
||||
1. **Screenshots.** At least one of the layer on the globe, plus its info card
|
||||
or panel if it has one.
|
||||
2. **A screen recording**, preferably 15 to 30 seconds: turn the layer on, move
|
||||
around, and open a record. Drag an `.mp4` or `.mov` into the PR description.
|
||||
GitHub accepts videos up to 10 MB on free accounts, so link to a larger file
|
||||
instead of attaching it.
|
||||
3. **The proposed category.** Say which layer-panel group the layer belongs in.
|
||||
The groups are defined in `PANEL_GROUPS` in `src/ui/layerPanel.js` (today:
|
||||
Movement, Cameras, Infrastructure, Events, Weather and Audio, plus Utilities,
|
||||
whose rows render in the separate UTILITIES panel). A layer that isn't
|
||||
listed there falls under "Other layers". If none of the groups
|
||||
fits, propose a new one and explain why.
|
||||
4. **The data source and licence.** Name the provider and endpoint, link its
|
||||
licence or terms, give the attribution shown in the app, and say whether it
|
||||
needs a key and whether the data is fetched at runtime or bundled. Add the
|
||||
same details to [DATA_SOURCES.md](DATA_SOURCES.md) and register the credit
|
||||
in `src/data/dataCredits.js`.
|
||||
|
||||
Maintainers may hold a layer PR until all four are there.
|
||||
|
||||
## Maintainers
|
||||
|
||||
|
||||
+3
-30
@@ -38,7 +38,6 @@ How to read this:
|
||||
| **Environment and Climate Change Canada** (MSC GeoMet OGC API) | Air quality (AQHI) — Canadian station observations | [ECCC Data Servers End-use Licence](https://eccc-msc.github.io/open-data/licence/readme_en/) (open, attribution required, no endorsement implied) | "Contains information licensed under the Environment and Climate Change Canada Data Servers End-use Licence" |
|
||||
| **NASA GIBS** (WMTS tiles + Worldview Snapshots) and **NASA CMR** (granule catalog) | Recent Imagery layer: HLS S30/L30 (Sentinel-2, Landsat 8/9) and VIIRS true-colour tiles draped on the map, per-day thumbnails and PNG exports; the HLS granule catalog (dates, cloud, footprints) for a selected box | U.S. public domain (NASA data); keyless, browser-direct (CORS `*`); no bulk use — one 30-day catalog query per box, tiles bounded to the box, six tile requests in flight | "We acknowledge the use of imagery provided by services from NASA's Global Imagery Browse Services (GIBS), part of NASA's Earth Science Data and Information System (ESDIS)." — [gibs.earthdata.nasa.gov](https://gibs.earthdata.nasa.gov), [HLS](https://lpdaac.usgs.gov/products/hlss30v002/) |
|
||||
| **NIFC WFIGS** (Wildland Fire Interagency Geospatial Services, ArcGIS feature service) | Fire Perimeters layer: current interagency wildfire incident perimeters, containment, and incident facts | U.S. public domain (interagency wildland-fire data published through the [NIFC Open Data portal](https://data-nifc.opendata.arcgis.com/)); fetched keyless through `/api/fire-perimeters` (5-minute server cache) with `exceededTransferLimit` paging | "Wildfire perimeters: National Interagency Fire Center (WFIGS)" |
|
||||
| **S2 Underground GhostMaps** (`github.com/s2underground/GhostMaps`) | S2 GhostMaps layer: the current edition of S2's KMZ intel products (Common Intelligence Picture, 30-day kinetic pull, Border Crisis Map, Iran and Yemen products, newest incident drop). Keyless. `/api/ghostmaps` reads the branch head from git's ref advertisement every 15 s (`GHOSTMAPS_POLL_SECONDS`, minimum 5 s), lists the tree through the GitHub REST API only when the head changes (optional `GITHUB_TOKEN`), downloads files by commit-pinned raw URLs into `.gev-cache/ghostmaps/` and pushes new catalogs to browsers over server-sent events | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) (repository README: "S2 Underground © 2025 by S2A1"); non-commercial, attribution required; files are fetched at runtime, never stored in this repository | "S2 GhostMaps intel products: S2 Underground © by S2A1, CC BY-NC-SA 4.0" — shown in-app |
|
||||
| **InciWeb** (inciweb.wildfire.gov) | Per-incident "InciWeb ↗" card links to official incident pages (narratives, evacuation/closure notices) | U.S. government public incident information. Links are matched best-effort by incident name and state, with incident page origin and update times checked on selection. Same-origin `/api/fire-perimeters/inciweb` routes cache the catalog for 1 hour and publication metadata for 30 minutes; linked pages remain InciWeb content | "Incident information: InciWeb (inciweb.wildfire.gov)" |
|
||||
| **OpenFreeMap / OpenMapTiles** | Vector tiles | OpenStreetMap data under ODbL 1.0 | OpenFreeMap © OpenMapTiles |
|
||||
| **Overture Maps Foundation** | Military area names pack | [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/) | Overture Maps Foundation |
|
||||
@@ -64,12 +63,9 @@ How to read this:
|
||||
| **Servei Català de Trànsit** (+ Ajuntament de Barcelona, Ajuntament de Terrassa) | CCTV camera catalog + frames, Catalonia | [Llicència oberta d'ús d'informació - Catalunya](https://web.gencat.cat/ca/generalitat/dades-indicadors/dades-obertes/llicencies) — attribution REQUIRED | "Servei Català de Trànsit"; partner-supplied cameras also name their owner |
|
||||
| **King County Road Services** (`services.arcgis.com`, King County GIS) | CCTV camera catalog + frames, unincorporated King County WA road network | [KCGIS Center terms](https://kingcounty.gov/en/dept/kcit/data-information-services/gis-center/about/terms-conditions-copyrights) — attribution required; public ArcGIS layer | "King County Road Services" (partner-city cameras name their owner) |
|
||||
| **WSDOT** (`data.wsdot.wa.gov`) | CCTV camera catalog + frames, Washington State highways | [WSDOT travel information disclaimer](https://wsdot.wa.gov/about/policies/travel-information-disclaimer) — public "low volume" feed; catalog cached, frames on WSDOT's own image host only | "Washington State Department of Transportation (Travel Center)" (courtesy) |
|
||||
| **Hong Kong Transport Department / DATA.GOV.HK** | CCTV camera catalog + frames, Hong Kong traffic snapshot cameras | [DATA.GOV.HK Terms of Use](https://data.gov.hk/en/terms-and-conditions) — attribution REQUIRED | "Hong Kong Transport Department / DATA.GOV.HK" |
|
||||
| **Statens vegvesen** (catalog `ogckart-sn1.atlas.vegvesen.no`, frames `kamera.atlas.vegvesen.no`, HLS `kamera.vegvesen.no`) | CCTV camera catalog + frames + live HLS video, Norway road cameras | [NLOD 2.0](https://data.norge.no/nlod/en/2.0) — attribution REQUIRED | "Contains data under the Norwegian licence for Open Government data (NLOD) distributed by Statens vegvesen" |
|
||||
| **Kartverket** (`ws.geonorge.no/hoydedata/v1/punkt`) | Terrain heights (NN2000) used as the ground prior for Statens vegvesen cameras | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — attribution REQUIRED | "© Kartverket", modified (heights over water set to 0 m, rounded to 0.1 m) |
|
||||
| **NYC DOT Traffic Management Center** (`webcams.nyctmc.org`) | CCTV camera catalog + frames, New York City five boroughs | NYC DOT requires developers to sign a [data-sharing agreement](https://www.nyc.gov/html/dot/html/about/datafeeds.shtml) for the camera feed. **Off by default**; enable only under your own agreement | "NYC DOT Traffic Management Center" + NYC DOT's required application disclaimer (in the Data attribution popover) |
|
||||
| **City of Toronto Open Data** (`opendata.toronto.ca`) | CCTV camera catalog + frames, Toronto city streets | [Open Government Licence – Toronto](https://www.toronto.ca/city-government/data-research-maps/open-data/open-data-licence/) — attribution REQUIRED | "Contains information licensed under the Open Government Licence – Toronto" |
|
||||
| **Autostrada del Brennero (A22)** (catalog `tourism.api.opendatahub.com`, frames `www.autobrennero.it/WebCamImg/`) | CCTV camera catalog + frames, A22 Brenner–Modena motorway, Italy | Public Autobrennero webcam images; camera list republished by the Open Data Hub, whose records are tagged CC0 (`ClosedData: false`, licence holder Autobrennero); Autobrennero publishes no terms of its own for the images | "Webcam images © Autostrada del Brennero S.p.A., via the Open Data Hub (NOI Techpark)" (courtesy) |
|
||||
| **GBFS (Lyft / BCycle / Social Bicycles / PBSC)** | Bikeshare availability | Per-feed (attribution-only) | Credit the operator (e.g. Austin BCycle) + its `license_url` |
|
||||
| **Bike Share Toronto** (`toronto.publicbikesystem.net`) | Bikeshare station availability, Toronto | Operator GBFS feed. No licence is published: the City's open-data package declares none and the feed has no `license_url`. Attribution is given to the operator. | "Bike Share Toronto (Toronto Parking Authority)" |
|
||||
| **Bike Itaú / Tembici** (`*.publicbikesystem.net`, seven Brazilian cities, Santiago and Bogotá) | Bikeshare station availability: São Paulo, Rio de Janeiro, Salvador, Recife, Porto Alegre, Brasília, Curitiba, Santiago de Chile and Bogotá | Operator GBFS feeds. No licence is published: the feeds' `system_information.json` carries no `license_url`, operator or attribution fields. Attribution is given to the operator. | "Bike Itaú (Tembici)"; "Tembici Bogotá" |
|
||||
@@ -115,7 +111,6 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
||||
**Quebec is thinly represented**, and that is upstream rather than a filter here: the province runs its own Info-Smog program, so only a handful of Quebec stations appear in ECCC's national feed. The reporting station count also varies hour to hour as stations report or fall silent.
|
||||
|
||||
- **Via Lietuva / Eismoinfo (Lithuania).** Public road-camera catalogs and periodically updated still images from [Eismoinfo](https://eismoinfo.lt/), credited to Via Lietuva / eismoinfo.lt in the app. **Off by default.** The camera endpoints are the eismoinfo.lt website backend and are not among the services listed in Via Lietuva's [open-data statement](https://eismoinfo.lt/eismoinfo-backend/content?id=44), whose conditions are attribution and reuse on the same terms. Until the statement is confirmed to cover the camera photos, the pack loads only when an operator sets `CCTV_LITHUANIA_ENABLED=1`; with the flag unset, empty or any other value, the server makes no request to Eismoinfo. `eismoinfo-backend/camera-info-table` is joined by ID to `layer-static-features/VKR`. Without `lks=true`, the latter returns WGS84 `[latitude, longitude]`; the table's `x/y` instead use LKS-94 metres. Only the official HTTPS `/eismoinfo-backend/image-provider/camera/last?id=…` image endpoint is registered. Catalogs use the shared 15-minute cache; frames are fetched on demand and never bundled or archived. These are periodic snapshots, not continuous video. Camera headings are low-confidence estimates. No API key is required. `CCTV_LITHUANIA_MAX_SOURCES` defaults to 400.
|
||||
- **Freeway Bureau, MOTC (Taiwan).** National freeway cameras from the Freeway Bureau's keyless [CCTV static table](https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml) in the MOTC 2.0 standard, refreshed daily and fetched at runtime (never bundled). Freeway Bureau traffic data is published under the [Open Government Data License, Taiwan (Version 1.0)](https://data.gov.tw/license), which permits reuse, including commercial reuse, with attribution; the app credits the Freeway Bureau, MOTC, names the dataset and links the licence, as its attribution exhibit asks. The table publishes each camera's MJPEG stream on one of four official camera servers (`cctvn`, `cctvn5`, `cctvc`, `cctvs` under `freeway.gov.tw`). The frame proxy relays single images, so each published stream URL is mapped to the same server's single-JPEG path for the same camera number (`/abs2mjpg/bmjpg` → `/abs2mjpg/jpg`, `/live-view/mjpg/video.cgi` → `/live-view/jpg/image.cgi`); only those exact HTTPS pairs are registered, and the still path is not itself listed in the table. These are on-demand snapshots, not continuous video. Camera headings are low-confidence estimates. No API key is required. The table host does not answer from some networks outside Taiwan (the camera servers do); there the pack is empty, logs one warning and retries hourly. On by default; `CCTV_TAIWAN_ENABLED=0` skips the pack, and `CCTV_TAIWAN_MAX_SOURCES` defaults to 600 (bounded to 1–2000).
|
||||
|
||||
- **Google Maps Platform.** You supply your own API key and are bound by [Google's ToS](https://cloud.google.com/maps-platform/terms). Google Maps Content (tiles, geocodes, places) **may not be cached, stored, rehosted, or committed** — this app only ever uses it live, which is the compliant pattern. The "Google" attribution is displayed on the globe and must stay visible. Restrict your key (see [SECURITY.md](SECURITY.md)).
|
||||
- **OpenSky Network.** Its license is **non-commercial**, and operational use of the REST API in a live product can require a prior written agreement with OpenSky — even for non-profit/government use. If you deploy this commercially, contact OpenSky for your own terms. The flights layer is a toggle and runs anonymously by default.
|
||||
@@ -158,7 +153,7 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
||||
- **adsbdb enrichment.** `/api/adsbdb` resolves an ICAO hex to aircraft type, model name and registration, and the tracked flight's callsign to an airline and an origin/destination pair. Fan-out is deliberately small because adsbdb is a free community API that publishes no quota: at most 4 requests in flight, dispatches dripped to roughly 5/s, and each key asked for at most once per session, with route lookups reserved for the tracked aircraft. The dev-server proxy caches each key for 24 hours in `.gev-cache/adsbdb.json`, negative answers included, so fresh entries survive server restarts. That gitignored cache remains local to the deployment and is not bundled with this repo. adsbdb publishes no general service license; its README says the route data may not be copied, published, or incorporated into other databases without the explicit permission of David J Taylor, Edinburgh. Deployers are responsible for obtaining any permission their use requires. Enrichment is best-effort and fail-silent — a flight that gets no answer keeps what the flight feed already gave it.
|
||||
- **Launch Library 2.** `/api/launches` makes a server-side rolling-30-day query against the supported v2.3 detailed launch endpoint, caches successful responses for 15 minutes in memory and on disk, and serves the last successful response during a throttle or transient outage. Anonymous access is limited to 15 calls/hour; deployments can provide `LL2_API_TOKEN` for authenticated access. The Space Devs' published terms permit using and sharing the API data in any form, ask users not to forward it without adding value, disclaim complete accuracy, and encourage—but do not require—attribution. This app keeps a courtesy credit. Payload and stage/recovery records are shown only when supplied. Failed launches expose their source status and never receive fallback orbit geometry or a live/estimated marker. LL2 supplies launch context and event timing, not continuous ascent telemetry or live orbital state.
|
||||
- **TfL JamCams.** The camera list comes from the keyless `api.tfl.gov.uk` endpoint (an optional `TFL_APP_KEY` raises its rate limit); frames come from TfL's public S3 bucket. The "Powered by TfL Open Data" attribution is required by TfL's terms and is registered in the Data attribution popover.
|
||||
- **Ontario 511 CCTV.** The camera list comes from the `511on.ca/api/v2/get/cameras` endpoint, which now requires a free developer key (`ONTARIO_511_API_KEY`: sign up at 511on.ca/my511/register, then request it while logged in at 511on.ca/developers/doc; throttled to 10 calls a minute). Without the key the pack is skipped. Frames are server-registered still images under `511on.ca/map/Cctv/`. The source is live-fetched at runtime and attributed under the Open Government Licence - Ontario. Access is governed by Ontario 511's Developer's Access Agreement (511on.ca/developers/resources): the data stays under the Open Government Licence - Ontario unless otherwise specified; key requests are reviewed case by case and ask for the intended use and any third parties that will receive the data; attribution by link to 511on.ca is encouraged, and the Ontario 511 logo may not be altered or used to imply endorsement (this app links and shows no logo); and the data is provided "as is", a disclaimer distributors should pass on, which the credit does.
|
||||
- **Ontario 511 CCTV.** The camera list comes from the keyless `511on.ca/api/v2/get/cameras` endpoint; frames are server-registered still images under `511on.ca/map/Cctv/`. The source is live-fetched at runtime and attributed under the Open Government Licence - Ontario.
|
||||
- **DelDOT live video cameras.** The keyless `tmc.deldot.gov/json/videocamera.json` catalog supplies active cameras and official `https://video.deldot.gov/live/…/playlist.m3u8` links. The local server transiently buffers bounded MPEG-TS segments in memory and deletes them on session release or idle expiry. No recordings are retained. `CCTV_DELDOT_ENABLED=0` disables the pack. Public availability is not a blanket redistribution license; deployment beyond personal local viewing needs confirmation of applicable DelDOT terms.
|
||||
- **Fintraffic weathercams.** The camera list is one keyless GeoJSON request to `tie.digitraffic.fi/api/weathercam/v1/stations` (809 stations / 2,275 presets), sent with the `Digitraffic-User: gods-eye-view` header the service asks clients to identify themselves with and with gzip; frames are stills on `weathercam.digitraffic.fi`, refreshed on each station's 600 s collection interval, which is also the client's ambient-still refresh cadence for this pack (the active camera keeps the usual 10-second refresh). One preset (a fixed camera view) is one camera; stations not `GATHERING` and presets not `inCollection` are dropped. Attribution is required by the CC BY 4.0 licence and is registered in the Data attribution popover. `CCTV_FINTRAFFIC_ENABLED=0` is the kill switch.
|
||||
- **DriveBC highway cameras.** The camera list comes from the keyless `https://www.drivebc.ca/api/webcams/` endpoint served by the DriveBC.ca site ([bcgov/DriveBC.ca](https://github.com/bcgov/DriveBC.ca)); frames come from `https://www.drivebc.ca/images/<id>.jpg`, built from the numeric camera id. DataBC's [DriveBC HighwayCams](https://catalogue.data.gov.bc.ca/dataset/bc-highwaycams) catalogue entry publishes the same cameras under the Open Government Licence – British Columbia, but its CSV still lists the retired `images.drivebc.ca` frame URLs, which now return a placeholder. The licence's attribution statement is required and is registered in the Data attribution popover. By default the 250 cameras nearest Vancouver and Victoria load; `CCTV_DRIVEBC_MAX_SOURCES` changes the cap and `CCTV_DRIVEBC_ENABLED=0` turns the pack off. Some cameras in the feed are supplied by partners (TransLink, the City of Vancouver, the City of Surrey, Parks Canada / Alberta Motor Association and others); the feed's per-camera credit is carried onto the camera record and shown beside the provider in the CCTV panel.
|
||||
@@ -172,22 +167,18 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
||||
- **Catalonia (Servei Català de Trànsit) cameras.** The camera list is the keyless `https://www.gencat.cat/transit/opendata/cameres.xml` WFS/GML feed, fetched over https with redirects refused and the body read capped at 1 MiB, since the list decides ids, coordinates and which allowlisted frame path is proxied. It carries a `font` field naming the real publisher per camera; this pack keeps SCT's own highway cameras plus hotlinked Barcelona (IMI) and Terrassa municipal cameras (~130 total), each confirmed to return a real frame, so frame URLs are checked against a small host allowlist (`mct.gencat.cat`, `www.bcn.cat`, `emap.terrassa.cat`) rather than one pinned origin (a link carrying `user:pass@` userinfo or an explicit port, or pointing outside that host's frame directory, is rejected even on an allowed host; an SCT `sctidcam` must be a bare `.gif`/`.jpg` filename), and every non-SCT camera carries its real owner as a `credit` shown beside the shared `provider`. The feed carries no compass heading, so every camera loads at low heading confidence (same as TfL and Fintraffic). SCT's own feed link (`RenderService`) needed a workaround: it always 302s to a second endpoint (`TransitCamera`) on a Location the upstream hardcodes as plain http even when requested over https — refused by `fetchWithinHost`'s same-origin (scheme-included) redirect policy — and separately, `mct.gencat.cat`'s TLS setup fails the handshake with Node's fetch outright (`ERR_SSL_WRONG_SIGNATURE_TYPE`) even though other HTTP clients complete it. The loader therefore builds the `TransitCamera` URL itself, over plain http, from `RenderService`'s `sctidcam` id, rather than registering the (broken) upstream link — the only transport this one host actually serves to this runtime. Camera ids are built from the SCT camera file or the partner image path, not the feed's `fid` (which embeds an upstream server-start stamp and changes on every upstream restart), and rows the feed lists more than once collapse to one camera. The feed carries no camera status field, so a camera its publisher has taken offline still registers and shows the publisher's own "image unavailable" still (SCT and Barcelona both serve one with HTTP 200), the same as other packs without a status field. Frames are fetched at request time and never stored or redistributed. The feed itself states no licence; the pack relies on the Generalitat de Catalunya open-data portal's terms for this dataset (the licence and attribution above). Disable with `CCTV_CATALONIA_ENABLED=0`.
|
||||
- **King County road cameras.** Keyless. The camera list is one public ArcGIS FeatureServer GeoJSON query (King County DOT Road Services, "King County and WSDOT Traffic Cameras"). The `outFields` list is explicit on purpose: the layer also carries asset-administration columns — including staff names — that are never fetched. Only live, active cameras with frames on the county's own `info.kingcounty.gov` image handler are registered (the feed writes `http://`; the handler serves HTTPS, so URLs are upgraded and pinned before registration). WSDOT-owned rows in the layer are skipped because their frames are WSDOT-hosted, outside the pinned image origin; partner-city cameras hosted on the county image handler (e.g. Newcastle) name their owner as the per-camera credit. The layer publishes no facing, so headings use the shared id-hash fallback at low confidence. Uniquely, the county publishes each camera's hardware `Manufacturer`/`Model` (Cohu 3950/3965, Bosch AUTODOME, Axis PTZ domes); both are carried on the source (and served by `/api/cctv/sources`) as `model` and `manufacturer` for identified-hardware consumers (vendor-generic strings such as `PTZ` name no model and are omitted). Frames are fetched at request time and never stored or redistributed. Disable with `CCTV_KINGCOUNTY_ENABLED=0`.
|
||||
- **WSDOT highway cameras.** Keyless. The camera list is the Esri JSON layer `data.wsdot.wa.gov/travelcenter/Cameras.json` — the same layer WSDOT's own Travel Center map consumes (~1,700 point features, EPSG:3857 geometry converted to WGS84 on load; the body carries Windows-1252 bytes and is decoded as such). Only frames on WSDOT's own `images.wsdot.wa.gov` host are registered — the catalog also lists ~70 partner cameras (Oregon DOT, City of Seattle, lodges, airports) whose own terms apply, and those are skipped rather than proxied. WSDOT's layer metadata marks the feed for "low volume" use (see WSDOT's [travel information disclaimer](https://wsdot.wa.gov/about/policies/travel-information-disclaimer)). The catalog is fetched once per CCTV catalog refresh (every 15 minutes), and a camera's frame is fetched only when that camera is viewed; frames are never stored or redistributed. The live feed's `CompassDirection` is a single cardinal (N/E/S/W), `B` (both directions — most rows), `O`, or null. A single cardinal gives a high-confidence heading (the diagonals NE/SE/SW/NW are mapped too should they appear); `B`, `O`, null, and any other code carry no single facing and use the shared id-hash fallback at low confidence. By default the 250 cameras nearest Seattle and Spokane load; `CCTV_WSDOT_MAX_SOURCES` changes the cap (8–1,200) and `CCTV_WSDOT_ENABLED=0` turns the pack off. A keyed WSDOT Traveler Information API serves the same population for higher-volume use and is deliberately not required here.
|
||||
- **Hong Kong Transport Department traffic snapshot cameras.** Keyless. The English camera list is the public DATA.GOV.HK XML at `static.data.gov.hk/td/traffic-snapshot-images/code/Traffic_Camera_Locations_En.xml`; frames are stills on `tdcctv.data.one.gov.hk`, and only that host is registered. Frame URLs are rebuilt from each camera key (`https://tdcctv.data.one.gov.hk/{KEY}.JPG`) rather than taken verbatim from the catalog. Stills were probed to answer directly on that origin with no redirect hop. Frames are fetched at request time and never stored or redistributed. The dataset publishes no camera facing, so headings use the shared id-hash fallback at low confidence and are corrected with the in-app calibration gizmo. By default nearly the full catalog loads (~1,013; pack cap 1,100), prioritized nearest Central, Tsim Sha Tsui, Sha Tin, Tsuen Wan, Tai Po, Fanling, Tuen Mun, Yuen Long and Tseung Kwan O so a lowered cap still spans Hong Kong Island, Kowloon and the New Territories. When the sum of packs exceeds the catalog-wide ceiling, shared round-robin keeps every lane represented. `CCTV_HK_MAX_SOURCES` (8–1200) changes the cap and `CCTV_HK_ENABLED=0` turns the pack off. The DATA.GOV.HK Terms of Use require attribution to the Government / Transport Department / DATA.GOV.HK.
|
||||
- **Statens vegvesen road cameras.** Keyless. The camera list is one GeoJSON request to the OGC API Features view of the official DATEX 3.1 CCTV site table (`ogckart-sn1.atlas.vegvesen.no/ogc/features/v1/collections/datex_3_1:CctvSimple/items`), so no DATEX account is needed; frames are stills on `kamera.atlas.vegvesen.no/api/images/<cameraId>`, and only that exact path per camera is registered. Cameras the feed reports as faulty are dropped. About 135 working cameras also publish live HLS (`kamera.vegvesen.no/public/<cameraId>/manifest.m3u8`, MPEG-TS); those play as live video through the bounded in-memory HLS relay, with the still as the snapshot fallback, and only that exact manifest path per camera is registered. `CCTV_VEGVESEN_VIDEO=0` keeps every camera on stills. `orientationDescription` names the place a camera faces, not a bearing, so it goes into the label and headings use the id-hash fallback at low confidence. By default every live camera loads (~850); `CCTV_VEGVESEN_MAX_SOURCES` (8–1000) lowers the cap, keeping the cameras nearest Oslo, Bergen, Stavanger, Trondheim, Kristiansand, Bodø and Tromsø and `CCTV_VEGVESEN_ENABLED=0` turns the pack off. Frames are fetched at request time and never stored or redistributed. The NLOD attribution is registered in the Data attribution popover.
|
||||
- **Kartverket ground heights for Statens vegvesen cameras.** The camera feed's points are 2D, so the loader asks Kartverket's keyless point-height API (`ws.geonorge.no/hoydedata/v1/punkt`, CC BY 4.0) for the terrain height at each camera. Requests carry 20 points: the API accepts 50, but above ~20 it answers the extra points from contour lines (`hoydekurver`, metres to ~20 m off) or not at all. Eight requests run at a time, about 36 for the whole country: a cold lookup took 6–13 s in testing (single requests 0.6–9.5 s). Answers are matched by the coordinates the API echoes and recorded only for points that request asked about. Over water the API returns the sea-floor depth (`dybdekurver` or `Havflate`), which is replaced by 0 m, the water surface; for the ~14 bridge cameras that is below the deck, but much closer than the sea floor or the flat prior. Heights are cached in memory by position for the life of the server, and points with no height (a few ferry quays and bridges, or a point a 200 answer leaves out) for 6 h, so the 15-minute catalog refresh only asks about new or moved cameras. The first failing request (a 408, 429 or 5xx, a network error, a request over 10 s, or an unreadable or oversized body) stops the lookup, with no retries, and no new lookup starts until the backoff ends (Retry-After when sent, else 30 min, longer than the refresh; bounded to 1 min–1 h). Any other 4xx marks that request's points as having no height and the lookup carries on. The whole lookup has a 30 s deadline; a lookup cut short by it resumes on the next refresh without a backoff. The lane waits at most 12 s, counted from the start of the loader, and only when more than 10% of the cameras have no answer yet; a few new cameras or expired misses are looked up in the background. A slower lookup keeps filling the cache, and the cameras it had not reached keep the flat prior until the next 15-minute refresh (about 190 of 865 on one slow first run). Only coordinates are sent, with the same User-Agent as the other CCTV requests. `CCTV_VEGVESEN_HEIGHTS=0` turns the lookup off. The Kartverket credit, with a note that heights over water are set to 0 m and heights are rounded to 0.1 m, is shown beside the Statens vegvesen entry in the Data attribution popover.
|
||||
- **NYC DOT Traffic Management Center cameras.** Off by default. NYC DOT's [developer data feeds page](https://www.nyc.gov/html/dot/html/about/datafeeds.shtml) asks developers who want the camera feed to contact `[email protected]` and sign a data-sharing agreement. This project does not hold one that covers downstream installations, so the pack loads only when an operator who holds their own agreement sets `CCTV_NYC_ENABLED=1`; with the flag unset, empty or any other value, the server makes no request to NYC DOT. The endpoint needs no key. The camera list is the public `webcams.nyctmc.org/api/cameras` catalog (~980 cameras across the five boroughs); frames are stills at `webcams.nyctmc.org/api/cameras/<uuid>/image`, synthesized from a validated UUID rather than taken from the payload, and only that host is registered. Cameras the feed reports as offline are dropped. The catalog publishes no camera facing, so headings use the shared id-hash fallback at low confidence and are corrected with the in-app calibration gizmo. Once enabled, the 250 cameras nearest the borough cores load; `CCTV_NYC_MAX_SOURCES` (8–400) changes the cap. Frames are fetched at request time and never stored or redistributed. NYC DOT asks every application that uses its data to carry its disclaimer; it is registered verbatim in the Data attribution popover.
|
||||
- **City of Toronto RESCU cameras.** Keyless. The camera list is the City's public `tmcearthcameras.json` on `opendata.toronto.ca` (dataset "Traffic Cameras", Transportation Services), served as JSONP and unwrapped with an anchored pattern. This is the municipal network the Ontario 511 pack does not carry: downtown arterials plus the Gardiner, DVP, Allen and Lake Shore. The list publishes no frame URL, so frame URLs are BUILT from the documented `CameraImages/loc####.jpg` convention and a strictly-digits camera number, pinning the frame proxy to the portal origin by construction; the list fetch refuses redirects and bounds the body it buffers. Frames are fetched at request time and never stored or redistributed. The dataset publishes no camera facing: the `D1`-`D4` fields are the directions of the *comparison* images (static reference stills captured in 2016 under a different path), not a bearing for the live pan-tilt-zoom frame, so headings use the shared id-hash fallback at low confidence and are corrected with the in-app calibration gizmo. A camera that is temporarily out of service still answers 200 with a byte-identical "Image Unavailable" placeholder rather than an error (about 6% of the network at any moment), so such a camera reads as healthy; the frame it shows is the agency's, not a synthesized one. The licence requires the attribution statement above. Disable with `CCTV_TORONTO_ENABLED=0`.
|
||||
- **Autostrada del Brennero (A22) webcams.** Keyless. The camera list is one request to the Open Data Hub tourism API (`tourism.api.opendatahub.com/v1/WebcamInfo?source=a22`), run by NOI Techpark in South Tyrol, which republishes Autobrennero's own list of 13 motorway webcams with coordinates and kilometre markers. Frames are the operator's stills on `www.autobrennero.it/WebCamImg/km<N>.jpg`, refreshed about once a minute, and only the exact image for each camera's kilometre is registered. The feed names no bearing, so headings use the id-hash fallback at low confidence. The road climbs from about 20 m in the Po valley to about 1,370 m at the Brenner Pass, so each camera's ground elevation comes from a per-kilometre table sampled from the Copernicus GLO-90 DEM (via Open-Meteo's elevation API); a camera not in the table uses a 200 m prior. The Open Data Hub tags every record CC0 (`ClosedData: false`, Autobrennero as licence holder), but that tag is the aggregator's metadata and Autobrennero publishes no terms of its own for the images, so they are credited to the operator as a courtesy and are not redistributed. Frames are fetched at request time and never stored. Disable with `CCTV_A22_ENABLED=0`.
|
||||
- **Camera frame content.** Public camera frames can contain people, vehicles and license plates, and the camera layer does nothing with them beyond putting them on the map. A successful upstream response is relayed as the provider served it: nothing in the camera pipeline blurs, redacts, enhances or restores it, and nothing there runs face, plate or object detection or otherwise recognises or classifies what is in the picture. (The ALPR layer described below maps where plate-reader cameras are, from locations contributors tagged in OpenStreetMap; it reads nothing from any camera frame.) When an upstream has no frame for a camera, what you see instead is a Google Street View still or the app's own placeholder card; the panel's source badge names which of them you are looking at, and a refresh that fails after a frame has arrived leaves the last good picture on screen rather than replacing it. The browser resamples each frame for display: onto a fixed 1920x1080 surface for the camera's projection plane in the scene, and onto a 192x108 canvas for the small ambient card. That scaling is the only change made to the picture, and the only pixels the camera layer reads back are a 64x36 downscale hashed to tell whether the picture has changed. Live streams are piped through; the still path reads the image into memory to pass it on — TxDOT wraps its JPEG in a JSON envelope, which is decoded there — and no frame is written to disk (the disk cache, `.gev-cache/`, holds feed JSON for other layers and never camera imagery). Outside that pipeline, the voice assistant is the one feature that sends imagery anywhere: when it answers a question about what is in view it may attach a downscaled screenshot of the rendered scene — including any camera plane on screen — to the request it sends to the voice model. Frames are fetched only from server-registered camera URLs — a client cannot hand the proxy a URL (see [SECURITY.md](SECURITY.md)). What the app does stays inside the line README draws: no named-person search, no face recognition, no tracking individuals.
|
||||
- **Transit (GTFS-Realtime).** The proxy requests only registered operator feeds, validates redirects, bounds responses, and uses conditional requests, short snapshot caching and failure backoff. All sixteen registered regions are live-enabled; each can be disabled independently. Vehicles use delayed playback between reports, with report age and waiting states shown separately. Session-local selected trails are available for every feed. MBTA additionally retains up to 15 minutes of recently observed positions in the running proxy, so available history can survive a browser reload; server restart clears it. History reads never fetch upstream, and other feeds have no proxy retention. Nothing is archived to disk or collected in the background. Operator text credits are included as a courtesy and do not imply endorsement. Heights are aligned to work with Google 3D tiles; subway markers are shown at street level because these feeds do not supply depth. The TTC feed is BusTime, the surface system: it carries buses and streetcars and no subway at all. About four in ten of its vehicles report no route at all (unassigned or deadheading), and those are shown with the feed default rather than a route-established mode. It sends no ETag or Last-Modified, so its conditional requests fall through to the ordinary snapshot cache. Three Golden Horseshoe neighbours run alongside it — MiWay, Hamilton HSR and Durham Region Transit — each bus-only and each polled only near its own region. Durham Region Transit publishes no bearing on any vehicle, so those sprites take their heading from observed movement like any other feed that omits it. Guelph, Kingston and Barrie extend the same pattern eastward and north. Hamilton, Guelph, Burlington and YRT publish route ids that are not the number on the vehicle, so those entries carry a route-name table taken from each operator’s own published GTFS; any id missing from a table renders unchanged rather than blank. A label is never invented: YRT runs a route `9101` that appears nowhere in its published routes, so it stays as the feed sends it. Burlington’s is the one that cannot be shortcut: `351` is route 1 while `3510` is route 10, so the table is a real join rather than a trimmed prefix. GO Transit / Metrolinx is deliberately absent: its REALTIME feed requires an API key issued by manual review, which the keyless admission rule excludes (its static schedule download is keyless, but carries no vehicle positions). Barrie and YRT/Viva each invite acceptance of their licence through a form, and each licence also states that using the data is itself acceptance; this deployment relies on that clause, which is recorded in both entries. Grand River Transit in Waterloo is absent for a different reason: it answers, but its feed header was months stale and it publishes no per-vehicle timestamps, so it would render vehicles that look live and are not. Ottawa’s OC Transpo falls to the same keyless admission rule as GO Transit — its realtime feed requires an API key. London Transit and Thunder Bay Transit publish vehicle positions over plain HTTP with no HTTPS endpoint available. Every other Ontario system — Brampton, Oakville, Niagara, Milton, Windsor, Greater Sudbury, UP Express and a dozen smaller operators — publishes static GTFS schedules only, with no realtime vehicle positions to register. Between the nine included and these exclusions, every Ontario realtime vehicle-position feed catalogued in the Mobility Database is accounted for.
|
||||
- **Radio Browser.** `/api/radio/stations` discovers official API mirrors, makes bounded and coalesced healthy/geolocated HTTPS-station queries, caches the normalized public-domain directory for 45 minutes, and may serve the last good catalog for up to seven days during an outage. Refreshes must meet minimum accepted-query and station coverage before replacing a warm catalog; schema-valid responses whose rows all fail the product's health policy do not count as successful queries. A usable partial cold catalog is explicitly `DEGRADED`, and non-array or empty successful payloads are rejected atomically. Individual structurally malformed rows are skipped rather than failing the whole query; the response carries their bounded `rejectedStationCount` (at most 10,000), which the client adds to its own rejected rows and reports as degraded coverage without discarding the accepted generation. Every directory and click-count request rejects redirects, validates all resolved addresses as globally routable (including reserved/documentation IPv4 and special/non-global IPv6 exclusions), and pins the TLS connection to a validated address. Only MP3/AAC non-HLS directory rows with public HTTPS stream targets are returned; favicons are intentionally omitted. Pressing play connects one browser audio element directly to the selected broadcaster and calls the directory's click counter through known-ID-only `POST /api/radio/click/:uuid`. GEV never proxies, caches, records, bundles, or redistributes audio. Radio Browser supplies station-level tags, not dependable current-song or upcoming-program metadata, so Radio filtering never claims either. Direct playback exposes the listener's IP address to the broadcaster, whose own stream terms apply.
|
||||
- **Radio Browser.** `/api/radio/stations` discovers official API mirrors, makes bounded and coalesced healthy/geolocated HTTPS-station queries, caches the normalized public-domain directory for 45 minutes, and may serve the last good catalog for up to seven days during an outage. Refreshes must meet minimum accepted-query and station coverage before replacing a warm catalog; schema-valid responses whose rows all fail the product's health policy do not count as successful queries. A usable partial cold catalog is explicitly `DEGRADED`, and malformed or empty successful payloads are rejected atomically. Every directory and click-count request rejects redirects, validates all resolved addresses as globally routable (including reserved/documentation IPv4 and special/non-global IPv6 exclusions), and pins the TLS connection to a validated address. Only MP3/AAC non-HLS directory rows with public HTTPS stream targets are returned; favicons are intentionally omitted. Pressing play connects one browser audio element directly to the selected broadcaster and calls the directory's click counter through known-ID-only `POST /api/radio/click/:uuid`. GEV never proxies, caches, records, bundles, or redistributes audio. Radio Browser supplies station-level tags, not dependable current-song or upcoming-program metadata, so Radio filtering never claims either. Direct playback exposes the listener's IP address to the broadcaster, whose own stream terms apply.
|
||||
- **TomTom Traffic.** Optional and BYOK: without `TOMTOM_API_KEY` the traffic layer runs its built-in simulation and no TomTom data (or attribution) appears. With a key, flow vector tiles are fetched through the server-side `/api/tomtom` proxy (120 s cache + a daily tile-budget governor — `TOMTOM_DAILY_TILE_BUDGET`, default 6,000 — sized so a 31-day month stays inside TomTom's 200K monthly free allowance; still a configurable application safety ceiling, not a guarantee; TomTom's [current pricing](https://docs.tomtom.com/pricing/) lists 200K free tile requests per month) and the "Traffic flow data © TomTom" credit is registered in the Data attribution popover the moment live mode activates. TomTom data is served live and cached only transiently (≤120 s TTL under `.gev-cache/`, gitignored) — it is not bundled or redistributed. One 23 KB point-in-time tile snapshot is committed as a decode-test fixture (`src/data/fixtures/`, © TomTom, never served to the app).
|
||||
- **Routing (OSRM / FOSSGIS).** `/api/route` proxies the public OSRM instances FOSSGIS e.V. runs for the OpenStreetMap community (car, bike, foot profiles). They are keyless and intended for fair, interactive use — the proxy rate-limits clients (60/min, 200/min total), caps route span and response size, caches each route for 10 minutes, and identifies itself in the User-Agent. Heavy or automated use must run its own OSRM. Routes are computed from OpenStreetMap data (ODbL); the Directions layer and voice routes never redistribute them. A route the router cannot find is reported as such; the Directions layer draws nothing in that case, and voice routes fall back to a clearly labeled straight line.
|
||||
- **Re:Earth Terrain.** Keyless (no API key). Used two ways: (1) `src/mapStackController.js` swaps in a `Cesium.CesiumTerrainProvider` pointed at Re:Earth's `cesium-mesh/ellipsoid` quantized-mesh endpoint for globe stacks without a Cesium ion token (e.g. OSM), replacing a flat `EllipsoidTerrainProvider`; falls back to the flat provider if the endpoint can't be reached. (2) The server-side `/api/terrain/heights` proxy (disk-cached, serve-stale) resolves per-point ellipsoidal ground height for entity placement. Both are best-effort with a keyless-safe fallback (bundled EGM96 geoid math) if Re:Earth is unreachable.
|
||||
- **OSRM on the FOSSGIS routing servers.** Keyless (no API key). Every route is requested by a person: clicking A and B in the Directions layer, or a voice "route from A to B". The server-side `/api/route` proxy sends an identifying `User-Agent`, pins the upstream host, accepts 2-12 coordinates, rejects any leg over 600 km or a total over 2,500 km, caps the buffered response at 8 MB, times the upstream out at 12 s, rate-limits per client, caches each route for 10 minutes, and coalesces identical requests that are in flight at the same time. Turn-by-turn maneuvers are asked for only when a caller wants them (`steps=1`), so the callers that do not read them send the smaller request. The "fix the map" link the policy asks for is in the Data attribution popover beside the OpenStreetMap credit.
|
||||
- **Mapped installations.** OpenFreeMap military polygons use a bundled Overture/OSM name index; wide views show named points. Optional operator-configured Overpass and explicit Google Places searches keep their own source paths. Mapped data does not establish current activity or complete coverage.
|
||||
- **Place search.** Named-place fly-to uses Google Geocoding when a Maps key is configured, then Photon (`PHOTON_URL`; unset uses the public instance, empty disables it), then `/api/geocode` (Nominatim search) when neither answers. `/api/geocode` and the voice outline route `/api/geocode/outline` share one Nominatim queue (`NOMINATIM_URL`; unset, empty and invalid values disable it; an operator must explicitly select an authorized endpoint): one request at a time at least 1.1 s apart, an identifying `User-Agent` and `Referer`, a short queue that refuses a burst, a pause after 429/403/5xx answers (honouring `Retry-After` in full), at most one retry after a network failure, and a per-install daily cap on public-instance requests (`NOMINATIM_DAILY_CAP`, default 50). Processes using the same installation state file hold one lock through the actual fetch invocation and recording the next public start, with the daily slot persisted before invocation; restarts share that count, spacing and any pause. An unfinished dispatch marker fails closed until operator recovery; it is never automatically reclaimed. Public access fails closed if the state file cannot be read, locked, written or semantically validated. This coordination does not span independent installations or state directories and does not establish an application-wide public-service allowance or authorize public use. An operator-selected non-public endpoint is outside the public cap, spacing and durable-state requirement; an upstream cooldown is kept with the endpoint that returned it rather than leaking across operator URLs. Answers are cached on disk (outlines 90 days, places 30 days, no-results 24 hours); failures are not cached. Outlines are looked up only for an explicit ask, after bundled data and any configured Overpass, and only results whose type suits the ask are used. Administrative outline responses expose country/admin-1/admin-2 identity only when the selected Nominatim row has a recognized, unambiguous address type consistent with its own name; numeric rank, ambiguous types, missing fields and contradictory names do not manufacture identity. It is not used for systematic or bulk queries.
|
||||
- **Street and building outlines.** Voice street highlights, pointed-at buildings and grounds use the OpenFreeMap vector tiles the traffic layer already loads. Traffic and outlines share one bounded pending fetch/decode owner per immutable XYZ tile; materialized outline geometry is charged to the retained-byte ceiling and capped by coordinate count. Consumer result caches and request lifetimes remain independent, while eviction or explicit teardown cancels queued idle projections and releases their decoded owner. No Overpass request is made unless `OVERPASS_UPSTREAMS` is configured. Named grounds require a returned name matching the selected place, except that an ask whose head noun names a kind of grounds (a school, university or college, hospital, cemetery, stadium or zoo) may use the part of the smallest unnamed tile area of that class that holds the resolved place; a differently named area never answers. Other anonymous landcover polygons cannot establish identity, so unsupported grounds stay unavailable rather than borrowing a nearby POI name.
|
||||
- **Place search.** Named-place fly-to uses Google Geocoding when a Maps key is configured, then Photon's public instance, then `/api/geocode` (Nominatim search) when neither answers. `/api/geocode` uses the shared Nominatim forward-search queue at no more than one request per second, sends an identifying `User-Agent` and `Referer`, caches answers for five minutes, shares one upstream call between identical searches already in flight, bounds the queue so a burst is refused rather than held, and drops a queued search whose caller has given up. It asks for one result per search and is not used for systematic or bulk queries.
|
||||
- **Cockpit regional briefing.** `/api/regional-brief` rounds aircraft coordinates into 0.1° cache cells, caches results for five minutes, and resolves physical regions from bundled public-domain Natural Earth polygons without reverse-geocoding requests. Google News RSS is queried with the resolved locality/region first; GDELT is used only when that RSS query fails or is empty. Google's published Google News terms restrict that source to personal, noncommercial use, so commercial deployments must disable/replace it or obtain separate permission; GDELT permits commercial dataset use with citation. The Data attribution popover identifies the active headline sources; article links retain publisher attribution. Headlines are location-query matches, not verified incidents, risk rankings, or evidence that a location is safe. Empty, partial, stale, and unavailable source states remain distinct. Open-Meteo supplies current conditions independently of the news source. `WX OFF` disables cockpit weather rendering only; the Local Info briefing still fetches its source-backed weather values and displays the required linked Open-Meteo credit.
|
||||
- **Dynamic weather presentation.** While cockpit mode is active, `/api/weather-effects` requests current Open-Meteo observations for the aircraft/camera location, rounds coordinates into 0.1° cache cells, caches results for five minutes, and may retain a stale observation for up to 30 minutes during a transient outage. WMO condition code selects the visual family; observed cloud cover, precipitation, visibility, wind speed, and wind direction bound its strength and motion. Missing or expired weather renders no synthetic atmospheric effect, and normal globe view never renders the weather overlay.
|
||||
|
||||
@@ -209,10 +200,6 @@ The [Bhote Koshi event pack](public/events/bhote-koshi-2026/README.md), under `p
|
||||
| **DataSF Analysis Neighborhoods** (41 SF neighborhood polygons) | `neighborhoods/` | **PDDL 1.0** (public domain) | ✅ (no restrictions) | "City & County of San Francisco — DataSF" (courtesy — not legally required) |
|
||||
| **Natural Earth countries, states and provinces** (260 admin-0/map units + 4,587 admin-1 polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
|
||||
| **US Census Bureau counties** (3,235 county polygons) | `us_census_counties/` | **Public domain** | ✅ (no restrictions) | "U.S. Census Bureau" (courtesy — not legally required) |
|
||||
| **US Census Bureau places** (32,629 city, town and CDP polygons) | `us_census_places/` | **Public domain** | ✅ (no restrictions) | "U.S. Census Bureau" (courtesy — not legally required) |
|
||||
| **Who's On First neighborhoods** (61,441 polygons) | `wof_neighborhoods/` | Per source: CC BY, ODC-By, Open Government licences, CC0, PDDL, public domain (folder `ATTRIBUTION.md`) | ✅ (attribution) | "Neighborhood boundaries: Who's On First and contributors"; per-source credits in the folder |
|
||||
| **Yale Bright Star Catalogue** (9,096 stars, sky background) | `sky/` | **Public domain** | ✅ (no restrictions) | "Yale Bright Star Catalogue (Hoffleit & Warren, via CDS)" (courtesy credit) |
|
||||
| **ESO Milky Way panorama** (eso0932a, blurred cube map, sky background) | `sky/` | **CC BY 4.0** | ✅ (attribution; changes stated) | "ESO/S. Brunier" |
|
||||
| **CCTV ground heights** (3,445 cameras) | `cctv_ground_heights/` | Precomputed camera placement heights, aligned to work with Google Photorealistic 3D Tiles (folder README) | — | — |
|
||||
| **CCTV Camera Database** model-enrichment extract v2.26.0 (17 camera-model records) | `src/layers/cctv/modelSpecs.js` | **CC0-1.0** (public domain dedication) | ✅ (no restrictions) | "Specs: cctv-database.com" (courtesy — not legally required) |
|
||||
|
||||
@@ -285,11 +272,6 @@ https://www.naturalearthdata.com/about/terms-of-use/). We credit anyway: "Made w
|
||||
Earth". Registration in the in-app `dataCredits.js` attribution list ships with the resolver
|
||||
wiring (see below).
|
||||
|
||||
### Sky background (`sky/`)
|
||||
|
||||
Sources, licences, the changes CC BY asks to state, hashes and build commands
|
||||
are in [`sky/README.md`](src/data/local_data/sky/README.md).
|
||||
|
||||
### DataSF Analysis Neighborhoods (`neighborhoods/`)
|
||||
|
||||
`neighborhoods/san-francisco.json` bundles the City & County of San Francisco's official
|
||||
@@ -307,15 +289,6 @@ the retrieval date (2026-07-30), exact download URL, license evidence, and the
|
||||
deterministic transform (`scripts/build-sf-neighborhoods.mjs`: `nhood` → `name`, ~2 m
|
||||
Douglas-Peucker simplification, 6-decimal rounding).
|
||||
|
||||
### Local CCTV source packs (`config/`)
|
||||
|
||||
The server can read a local camera list from `CCTV_SOURCES_FILE` (default
|
||||
`config/cctv_sources.austin.json`, which ships empty) and inline JSON from
|
||||
`CCTV_SOURCES_JSON`; the two are merged, and inline entries win on a duplicate
|
||||
id. A non-empty local pack replaces the live open-data camera packs unless
|
||||
`CCTV_FORCE_AUSTIN=1` is set. The packs under `config/` show the schema, and a
|
||||
pack's cameras keep their own source's license and attribution.
|
||||
|
||||
---
|
||||
|
||||
## Weather sources
|
||||
|
||||
+1
-6
@@ -4,7 +4,7 @@
|
||||
# proxies (ADS-B, AIS, Overpass, OpenAI Realtime, ...) as middleware, so there
|
||||
# is nothing else to containerise. See compose.yaml for the usual entry point.
|
||||
# Same Node as CI (package.json engines, .github/workflows), pinned by digest.
|
||||
FROM node:24.22.2-slim@sha256:c86ecead9e55a5210ac1727de38a4ca840bf03dcaf34dc9749addbf383b1920c
|
||||
FROM node:24.14.0-slim@sha256:d8e448a56fc63242f70026718378bd4b00f8c82e78d20eefb199224a4d8e33d8
|
||||
|
||||
# Puppeteer is only used by the QA scripts; skip its ~150MB Chromium download.
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=1
|
||||
@@ -39,11 +39,6 @@ COPY --chown=node:node . .
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4173
|
||||
EXPOSE 4173
|
||||
# node:slim has no curl, so probe with Node's built-in fetch. 127.0.0.1 is
|
||||
# always on the Host allowlist, and the generous start period covers Vite's
|
||||
# first-run dependency optimisation.
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||
CMD ["node", "-e", "fetch('http://127.0.0.1:'+(Number.parseInt(process.env.PORT,10)||4173)+'/').then((r)=>process.exit(r.ok?0:1),()=>process.exit(1))"]
|
||||
# The launcher is `npm run dev` plus one step: it trusts the container's
|
||||
# gateway address for Provider Settings, because that is where Docker delivers
|
||||
# the host's own browser from (never 127.0.0.1). See scripts/docker-start.mjs.
|
||||
|
||||
@@ -61,7 +61,7 @@ Start with the included data sources, then add your own. Each layer is a separat
|
||||
- **🎯 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.
|
||||
- **🖊️ Voice whiteboard:** Speak annotations onto the world — real boundary polygons, marks, and routes.
|
||||
- **🛫 3D hangar:** Real per-class aircraft models — 787, ATR-72, Citation, Bell 206, MQ-9 — and a tracked contact swaps from glyph to 3D model as you close in.
|
||||
- **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir.
|
||||
- **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir, Snow.
|
||||
- **🟩 Detection overlay:** Screen-space bounding boxes and IDs on everything in view.
|
||||
- **🎖️ Military HUD:** Tactical heads-up display with intelligence-style telemetry.
|
||||
- **🌐 Global Context:** Stage the full situational picture with one switch — and get your exact view back when you leave.
|
||||
@@ -89,7 +89,7 @@ Start with the included data sources, then add your own. Each layer is a separat
|
||||
|
||||
## ⚡ Quick Start
|
||||
|
||||
**Start without an account or API keys.** Every path opens the same app with
|
||||
**Start without an account or API keys.** Both paths open the same app with
|
||||
Esri satellite imagery and keyless terrain. OSM is the fallback if Esri is
|
||||
unreachable. Flights, military traffic, satellites, earthquakes, public
|
||||
cameras, radio, and launches are available without keys.
|
||||
@@ -111,15 +111,13 @@ app's **POWER UP** panel; [Keys & Costs](#-api-keys) explains the options.
|
||||
|
||||
Available on **Windows, macOS, and Linux**. The Pinokio maintainer reports
|
||||
cross-platform testing of the fixed installer. The launcher installs the
|
||||
locked dependencies, finds a free local port, and opens the app in your system
|
||||
browser. The Server menu item keeps the launcher console available.
|
||||
locked dependencies, finds a free local port, and opens the app.
|
||||
|
||||
**Tried before and installation failed?** Update Pinokio and try again.
|
||||
Version 8.2 fixes the launcher installation issue;
|
||||
[details from the Pinokio maintainer](https://pinokio.co/posts/01m1m4p9xxm3qw7dnnpj2wr93g).
|
||||
|
||||
**Using an older launcher or opening the embedded view manually?** If the globe
|
||||
is very choppy, before treating that as a GPU
|
||||
**Pinokio opens but the globe is very choppy?** Before treating that as a GPU
|
||||
compatibility or minimum-hardware problem, copy the local server URL shown by
|
||||
Pinokio and open the same URL in your normal hardware-accelerated browser. One
|
||||
Pinokio 8.2.0 user reported severe stutter in Pinokio's embedded view that went
|
||||
@@ -129,9 +127,8 @@ hardware; it does not establish a minimum GPU requirement.
|
||||
|
||||
### Path 2 — Terminal / coding agent
|
||||
|
||||
Use **Node.js 24.x (24.14.0 or later) or 26.x**; 24.22.1 or later (26.12.1 or
|
||||
later on 26) includes the October 21, 2026 security fixes, among them
|
||||
CVE-2026-111497. The setup doctor warns about Node 25, which is end-of-life.
|
||||
Use **Node.js 24.x (24.14.0 or later) or 26.x**. The setup doctor warns about
|
||||
Node 25, which is end-of-life.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bilawalsidhu/gods-eye-view.git
|
||||
@@ -180,16 +177,6 @@ Publishing it wider is the [Sharing an instance](#-sharing-an-instance) opt-in,
|
||||
|
||||
When you widen the publish, also set `GEV_KEY_SETUP_TRUSTED_PEERS=none` so the launcher trusts no gateway address.
|
||||
|
||||
```bash
|
||||
GEV_KEY_SETUP_TRUSTED_PEERS=none docker compose up
|
||||
```
|
||||
|
||||
Compose forwards `GEV_KEY_SETUP_TRUSTED_PEERS` and `GEV_ALLOWED_HOSTS` from your
|
||||
shell or the project's `.env`; an exported shell value takes precedence. You can
|
||||
also put `GEV_KEY_SETUP_TRUSTED_PEERS=none` in `.env` for a persistent setting.
|
||||
Unset or blank trusted peers retain automatic gateway discovery; `none` explicitly
|
||||
disables it. The startup log confirms that no additional peers are trusted.
|
||||
|
||||
<details>
|
||||
<summary>Running on Linux, a NAS, or behind a reverse proxy</summary>
|
||||
|
||||
@@ -210,8 +197,6 @@ env UID=1026 GID=100 docker compose up -d
|
||||
|
||||
Keep the `env`: bash treats `UID` as read-only, so a plain `UID=1026` in front of a command is silently ignored. If the container can't write the checkout, it says so at start. Docker Desktop on macOS and Windows maps ownership for you.
|
||||
|
||||
On SELinux hosts (Fedora, RHEL), compose mounts the checkout with `:z` so the container may read it; Docker may relabel the checkout for container access, which can take a moment on a large checkout. Without that, the start fails with `Cannot find module '/app/scripts/docker-start.mjs'`.
|
||||
|
||||
Behind a reverse proxy, put your keys in `.env` or pass them as environment variables - Provider Settings refuses proxied requests on purpose - and add the hostname you serve it on, for example `GEV_ALLOWED_HOSTS=globe.example.lan` (see [Sharing an instance](#-sharing-an-instance)).
|
||||
|
||||
</details>
|
||||
@@ -234,41 +219,6 @@ the container.
|
||||
|
||||
</details>
|
||||
|
||||
### Path 4 — GitHub Codespaces (cloud, no local install)
|
||||
|
||||
Runs the same dev server in a cloud workspace, so Node and the dependencies
|
||||
never touch your machine. The globe still renders in your own browser, so it
|
||||
needs the same graphics there as any other path. GitHub Free includes 120
|
||||
core-hours a month with no card on file — about 60 hours on the default 2-core
|
||||
machine. Org and Enterprise plans include no free quota; current terms and
|
||||
quotas apply.
|
||||
|
||||
1. Fork this repository, then open **your fork** on GitHub.
|
||||
2. Click the green **Code** button, the **Codespaces** tab, then **Create codespace on main**.
|
||||
3. In the codespace terminal:
|
||||
```bash
|
||||
nvm install 24 && nvm use 24
|
||||
npm ci
|
||||
npm run doctor
|
||||
GEV_ALLOWED_HOSTS="$CODESPACE_NAME-4173.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" npm run dev
|
||||
```
|
||||
4. Open the **Ports** tab, find `4173`, and click the globe icon to open it in
|
||||
the browser. The port is private by default, so the URL asks for your GitHub
|
||||
login. Keep it private: a public port lets anyone use the app and the keys
|
||||
in your `.env`.
|
||||
|
||||
`GEV_ALLOWED_HOSTS` there is required, not optional. The dev server answers only
|
||||
the hostnames it trusts, and a forwarded port arrives on
|
||||
`$CODESPACE_NAME-4173.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN`. Without it the
|
||||
page is `Blocked request. This host is not allowed.` Leading-dot wildcards are
|
||||
ignored on purpose, so pass that exact name.
|
||||
|
||||
⚠️ **A codespace is reachable from the internet.** Treat it as the
|
||||
wider-publish case in [Sharing an instance](#-sharing-an-instance). Provider
|
||||
Settings does not open on the forwarded URL, so put keys in the workspace
|
||||
`.env` by hand. `.env` is gitignored — never commit it. [SECURITY.md](SECURITY.md) has the
|
||||
threat model.
|
||||
|
||||
### Then power it up — in the app, not in a file
|
||||
|
||||
Keys are upgrades, not prerequisites. When you want one, click the **POWER UP**
|
||||
@@ -330,14 +280,14 @@ Choose a first-run mission, or try these in order. The GIFs show Google Photorea
|
||||
|
||||

|
||||
|
||||
6. **Switch the optics.** Tap `1`–`6` — 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.
|
||||
|
||||

|
||||
|
||||
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`–`6` visual styles · `H` HUD · `D` detection · `C` cockpit · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
|
||||
**Keyboard:** `1`–`7` visual styles · `H` HUD · `D` detection · `C` cockpit · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
|
||||
|
||||
---
|
||||
|
||||
@@ -383,17 +333,13 @@ Thirty tools, four jobs — the commands below come straight from the product's
|
||||
|
||||
**🎥 Direct it** — drone-operator camera verbs:
|
||||
|
||||
“Rotate the globe” spins Earth to the right continuously; ask for left, a speed,
|
||||
or stop. If it refuses, follow its reset-and-rotate or further-zoom guidance;
|
||||
camera input stops rotation.
|
||||
|
||||
> 🗣️ _"Take me to Tokyo."_ · _"Orbit around this area slowly."_ · _"Draw the walking route from the Capitol to Zilker Park."_ → _"Fly the route we just drew."_ · _"Zoom out to a globe view."_
|
||||
|
||||
**🖊️ Annotate it** — a whiteboard over the real world:
|
||||
|
||||
> 🗣️ _"Outline the state of Texas."_ · _"Annotate the Texas State Capitol and its grounds"_ — it draws the **actual enclosing boundary**, not a circle. · _"How far is the Eiffel Tower from the Louvre?"_ — a connector arrow appears and it speaks the distance. Everything persists until you say _"clear the map."_
|
||||
|
||||
**✍️ Or draw it yourself** — UTILITIES ▸ **Draw**: pick Area, Line or Pin and a colour, click the vertices on the real world, double-click to finish, then name, recolour, hide or fly to each shape from its row. Same whiteboard, same persistence, no microphone needed.
|
||||
**✍️ Or draw it yourself** — DISPLAY ▸ **Draw**: pick Area, Line or Pin, click the vertices on the real world, double-click to finish, label it. Same whiteboard, same persistence, no microphone needed.
|
||||
|
||||

|
||||
|
||||
@@ -435,7 +381,7 @@ Twenty layers and map sources. **Eighteen have a keyless path.** Some offer addi
|
||||
| 🌍 **Earthquakes** | Global seismic activity, last 24h | USGS | 🟢 |
|
||||
| ◍ **Air quality** | Live AQHI on the published 1–10+ health-risk scale, with a Weather-panel card showing the band legend and the highest readings in view. **Canada only** — ECCC is the first registered provider | ECCC MSC GeoMet | 🟢 |
|
||||
| 🚗 **Traffic** | Simulated vehicles on OSM roads. With TomTom, live flow speeds drive the simulation and congestion colors below ~8 km; individual vehicle positions are not live observations | TomTom + OSM | 🟢 simulation · 🟡 live flow speeds |
|
||||
| 📹 **CCTV Mesh** | ~4,000 public cameras (shared cap) projected _into_ the 3D space — Austin · Texas (TxDOT) · California (Caltrans) · London (TfL) · Ontario (511, free developer key) · Finland (Fintraffic) · British Columbia (DriveBC) · Estonia (Tallinn, Tarktee) · Delaware (DelDOT live video) · New South Wales (Live Traffic NSW) · Calgary · Norway (Statens vegvesen) · Toronto · King County (WA) · Washington (WSDOT) · Catalonia (Servei Català de Trànsit) · Hong Kong (Transport Department) · Taiwan (Freeway Bureau, MOTC) · Italy (A22 Brennero motorway), plus opt-in New York City (NYC DOT TMC, needs your own NYC DOT agreement), opt-in Lithuania (Via Lietuva / Eismoinfo, photo licence unconfirmed) and opt-in Florida (FL511, individual personal use only; other use needs FDOT's written consent). Positions are published; poses are estimated priors **you calibrate by dragging a gizmo on the camera itself** | City APIs | 🟢 |
|
||||
| 📹 **CCTV Mesh** | ~4,000 public cameras (shared cap) projected _into_ the 3D space — Austin · Texas (TxDOT) · California (Caltrans) · London (TfL) · Ontario (511) · Finland (Fintraffic) · British Columbia (DriveBC) · Estonia (Tallinn, Tarktee) · Delaware (DelDOT live video) · New South Wales (Live Traffic NSW) · Calgary · Norway (Statens vegvesen) · Toronto · King County (WA) · Washington (WSDOT) · Catalonia (Servei Català de Trànsit), plus opt-in New York City (NYC DOT TMC, needs your own NYC DOT agreement), opt-in Lithuania (Via Lietuva / Eismoinfo, photo licence unconfirmed) and opt-in Florida (FL511, individual personal use only; other use needs FDOT's written consent). Positions are published; poses are estimated priors **you calibrate by dragging a gizmo on the camera itself** | City APIs | 🟢 |
|
||||
| 📷 **Mapped ALPR Cameras** | License-plate-reader camera locations tagged by OpenStreetMap contributors, loaded one city-sized view at a time, with **SHOW NEAREST**. Locations and tags only: no plate data, no video | OpenStreetMap (incl. DeFlock mapping) | 🟢 |
|
||||
| 📷 **Street Level** | Street-level photos behind one panel, one chip per provider: coverage on the streets, image cones, an embedded viewer with full-screen **EXPAND**. Mapillary, Google Street View, KartaView and Panoramax | Mapillary · Google · KartaView · Panoramax | 🟢 KartaView, Panoramax · 🟡 Mapillary token · 🔴 Google Maps key |
|
||||
| 📻 **Radio** | Geolocated world radio with an **analog tuner** — drag the needle across up to 750 stations and the globe flies to each broadcaster | Radio Browser / broadcasters | 🟢 |
|
||||
@@ -451,7 +397,7 @@ Twenty layers and map sources. **Eighteen have a keyless path.** Some offer addi
|
||||
|
||||
**Weather, keyless.** **Wind** animates 10 m forecast flow from NOAA GFS or ECMWF IFS, with an optional color field and a reading at map center. **Rain radar**, **Satellite clouds** and **Lightning density** are observations on one history timeline in the **WEATHER** panel (step back, **Play**, **Latest**); **Cyclone advisories** draw NHC and CPHC positions, forecast tracks and cones. On Google 3D Tiles the observed layers float above the city as translucent shells, sharpened around your view.
|
||||
|
||||
In the app, **Data Layers** groups them as Movement, Cameras, Infrastructure, Events, Weather and Audio; Directions sits in the separate **Utilities** panel with Draw and Import.
|
||||
In the app, **Data Layers** groups them as Movement, Cameras, Infrastructure, Events, Weather and Utilities.
|
||||
|
||||
**The basemap ladder — what each tier buys you:**
|
||||
|
||||
@@ -479,7 +425,7 @@ _The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED
|
||||
|
||||
**Setup.** At [mapillary.com/dashboard/developers](https://www.mapillary.com/dashboard/developers), register an application with **Read** access and copy its **Client Token** (`MLY|…`). Paste it into **POWER UP → Mapillary**, or set `MAPILLARY_CLIENT_TOKEN` in `.env` and restart. Without it the panel reads **KEY REQUIRED**. **Google Street View** uses `GOOGLE_MAPS_API_KEY` with the **Maps JavaScript API** enabled for it in Google Cloud; its chip starts off, since every panorama is billed to the key. **KartaView** and **Panoramax** need nothing: their public APIs are keyless.
|
||||
|
||||
**Use.** Turn on **Street Level** under DATA LAYERS → Cameras, or with the pill in the STREET LEVEL panel header. Each provider has a chip in its own colour (Mapillary green), and one is lit at a time: lighting one switches the others off. Mapillary, which is free, is lit by default. Zoom in to street level (below 60 km) for the coverage lines; from orbit the panel says to zoom in. The **IMAGERY** (ALL / 360° / FLAT) and **SINCE** filters apply to Mapillary, KartaView and Panoramax; Street View panoramas are all 360°, so with Street View lit the filters are hidden. Click a coverage line for its image cones and a cone to open the photo. Light **KARTAVIEW** (purple) and zoom in below about 6 km: its lines are the streets KartaView has photos of, and a click on one opens the photo nearest the click, flat or 360° (drag to look around), with cones along its drive; **‹ ›** or ← → step through the drive (Shift+← →, ↑ ↓ and + − look around a 360° photo; pinch zooms on a phone). Photos KartaView has archived are skipped, and where all are archived the panel says so. For KartaView, **SINCE** goes by upload date, as KartaView's own map does (it can filter by nothing else), and the panel notes it beside the SINCE value (ANY DATE (by upload date)); KartaView filters its lines on its own server, and a filtered area KartaView has not drawn before can take several seconds. Light **PANORAMAX** (orange) for the open, federated commons (strongest in France and Belgium): below about 60 km its lines are sequences. A click on a line opens that sequence's picture nearest the click without moving the camera, flat or 360°, with cones along the sequence around it; **‹ ›** or ← → step through it, and both filters apply. Its caption names the picture's authors (or the instance holding it) and licence (mostly CC BY-SA 4.0; pictures on the IGN instance are Licence Ouverte 2.0). On the **Google 3D** map (Google's terms keep Street View off other maps, where it does not appear), light **GOOGLE STREET VIEW** and click a street from below 3 km, or press **OPEN STREET VIEW** for the view centre, to open Google's panorama there; step along its arrows. Above the photo: **EXPAND** (full screen; Esc leaves) and **FIT / FILL** (Mapillary, and flat KartaView and Panoramax photos). The caption links to the image on the provider's site, and the globe credits each provider that is on ("© Mapillary contributors, CC BY-SA 4.0", "Street View imagery © Google", "© KartaView contributors, CC BY-SA 4.0", "© Panoramax contributors, licence per picture"). KartaView's own photo pages fail for signed-out visitors, so its link opens the full-size photo.
|
||||
**Use.** Turn on **Street Level** under DATA LAYERS → Cameras, or with the pill in the STREET LEVEL panel header. Each provider has a chip in its own colour (Mapillary green), and one is lit at a time: lighting one switches the others off. Mapillary, which is free, is lit by default. Zoom in to street level (below 60 km) for the coverage lines; from orbit the panel says to zoom in. The **IMAGERY** (ALL / 360° / FLAT) and **SINCE** filters apply to Mapillary, KartaView and Panoramax; Street View panoramas are all 360°, so with Street View lit the filters are hidden. Click a coverage line for its image cones and a cone to open the photo. Light **KARTAVIEW** (purple) and zoom in below about 6 km: its lines are the streets KartaView has photos of, and a click on one opens the photo nearest the click, flat or 360° (drag to look around), with cones along its drive; **‹ ›** or ← → step through the drive (Shift+← →, ↑ ↓ and + − look around a 360° photo; pinch zooms on a phone). Photos KartaView has archived are skipped, and where all are archived the panel says so. For KartaView, **SINCE** goes by upload date, as KartaView's own map does (it can filter by nothing else), and the panel notes it under SINCE; KartaView filters its lines on its own server, and a filtered area KartaView has not drawn before can take several seconds. Light **PANORAMAX** (orange) for the open, federated commons (strongest in France and Belgium): below about 60 km its lines are sequences. A click on a line opens that sequence's picture nearest the click without moving the camera, flat or 360°, with cones along the sequence around it; **‹ ›** or ← → step through it, and both filters apply. Its caption names the picture's authors (or the instance holding it) and licence (mostly CC BY-SA 4.0; pictures on the IGN instance are Licence Ouverte 2.0). On the **Google 3D** map (Google's terms keep Street View off other maps, where it does not appear), light **GOOGLE STREET VIEW** and click a street from below 3 km, or press **OPEN STREET VIEW** for the view centre, to open Google's panorama there; step along its arrows. Above the photo: **EXPAND** (full screen; Esc leaves) and **FIT / FILL** (Mapillary, and flat KartaView and Panoramax photos). The caption links to the image on the provider's site, and the globe credits each provider that is on ("© Mapillary contributors, CC BY-SA 4.0", "Street View imagery © Google", "© KartaView contributors, CC BY-SA 4.0", "© Panoramax contributors, licence per picture"). KartaView's own photo pages fail for signed-out visitors, so its link opens the full-size photo.
|
||||
|
||||
Share links carry the provider switches and filters (`0.m.0` Mapillary off, `0.m.0_0.r.google-1` Street View on, `0.m.0_0.r.google-0*kartaview-1*panoramax-0` KartaView on and `0.m.0_0.r.google-0*kartaview-0*panoramax-1` Panoramax on, as the app writes them (`0.m.0_0.r.kartaview-1` and `0.m.0_0.r.panoramax-1` work too; a link that leaves out `m` keeps Mapillary, the default, lit), `0.p.p` panoramas only, `0.s.365` the last year). To add a provider, implement the contract in `src/layers/streetLevel/registry.js` and register it in `src/app/layers/streetLevel.js`.
|
||||
|
||||
@@ -527,12 +473,12 @@ How the globe handles live data:
|
||||
- **Sits on the real ground.** Entity heights are aligned to work with Google 3D tiles, so aircraft park on aprons and cameras stand on street corners instead of floating.
|
||||
- **Caching and request budgets.** An OpenSky credit governor, a TomTom daily tile budget, and disk-cached TLEs reduce repeated requests. These controls do not replace provider quotas or billing controls.
|
||||
- **Server-side credentials.** Every API that touches a private key (OpenAI, AISStream, OpenSky OAuth, camera frames) is brokered through a hardened server-side proxy with SSRF protection, response caps, and sanitized errors. The only keys the browser sees are Google Maps and Cesium ion (restrict both at the provider).
|
||||
- **A small stack.** **Vue 3** and **Pinia** for the interface, **CesiumJS** for the globe and **Vite** to build it — plus **Google Photorealistic 3D Tiles** for the planet and the **OpenAI Realtime API** for voice. Fast to read, fast to hack on.
|
||||
- **No framework.** Vanilla JavaScript, **CesiumJS**, and **Vite** — plus **Google Photorealistic 3D Tiles** for the planet and the **OpenAI Realtime API** for voice. Fast to read, fast to hack on.
|
||||
|
||||
```
|
||||
src/
|
||||
├── main.js # Bootstrap: Google 3D tiles, layer registration
|
||||
├── ui/ # Runtime UI — Vue views, Pinia stores, kit, controllers, styles
|
||||
├── ui.js # Runtime UI — panels, HUD, styles, control facade
|
||||
├── hud.js # Intelligence HUD + AI scene summary
|
||||
├── keySetup.js # POWER UP panel — in-app provider keys (dev server only)
|
||||
├── mapStackController.js # Basemap switching — Google 3D / Esri / OSM / ion stacks
|
||||
|
||||
+1
-13
@@ -2,11 +2,6 @@
|
||||
|
||||
God's Eye View is a local-first client for **public** data. It is built for exploration, demos, and learning — not as a hardened production service. This document explains the security model so you can run it safely and report issues responsibly.
|
||||
|
||||
Vite's development file server also denies `.gev-logs/`, `.gev-cache/`, and
|
||||
the package checkout's root `dist/` directory. Dependency `dist/` directories
|
||||
remain available. This filesystem deny list does not filter `public/` assets or
|
||||
preview output; never put private artifacts in a public asset directory.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please report security issues **privately** — do not open a public issue for anything exploitable.
|
||||
@@ -74,13 +69,11 @@ The data proxies under `server/providers/` are written so the browser cannot tur
|
||||
- **No verbatim client headers upstream.** The CCTV media route is the one route that relays a request header (`Range`, for video seeking). It is parsed and canonicalized before it is forwarded: one `bytes=` range only, with every accepted form — explicit span, open-ended and suffix — bounded to the same 64 MiB ceiling the relay applies to a declared response body. Multi-range, malformed, inverted and non-`bytes` values are dropped and the request proceeds without a `Range`, as RFC 7233 §3.1 prescribes. Bounding the request bounds what is asked for: a response that declares no length — live streamed media, or a chunked body from an upstream that ignores the `Range` — has no ceiling. An upstream request the browser has stopped waiting for is cancelled rather than left running, whether the viewer leaves before the headers arrive or during the body.
|
||||
- **Transit fetches registered feeds only.** `/api/transit/vehicles/<id>` resolves the id against `src/data/transitFeeds.js`; the browser never supplies a URL, and a feed that is registered but disabled does not resolve at all. Redirects are followed manually and each hop is validated against the feed's own https origin before it is requested, so an off-origin or downgraded hop is refused rather than contacted. Bodies are capped at 8 MB, the protobuf is decoded server-side under entity-count and string-length ceilings, a differential feed is refused, and snapshots live 15 s in memory with no disk cache. A per-feed admission limiter and a failure cooldown ladder bound what this process can ask of any operator.
|
||||
- **Local receiver feeds are operator-configured and local-only.** `/api/local-receivers/aircraft` reads only the `aircraft.json` URLs in the server's `LOCAL_RECEIVER_FEEDS`; the browser never supplies an address. Each host must pass the shared tap address rule (`src/data/tapAddress.js`: loopback, RFC1918, `localhost`, `*.local`; no link-local, IPv6 or other names), the scheme must be http(s), the path must end in `aircraft.json`, and credentials, queries and fragments are refused. Invalid entries are logged at startup and never fetched. Reads use a 2 s timeout, refuse redirects, cap bodies at 2 MB and share one read per second; responses name each feed by band only and never carry upstream error text.
|
||||
- **Validated response formats.** Proxied bodies are relayed only in the format the route serves. CCTV frames and the Street View fallback accept JPEG, PNG, GIF, WebP and AVIF only, sent under their canonical type; any other type falls through to the next fallback. Live CCTV media must match its feed kind (`video/mp4`, `video/webm`, or `multipart/x-mixed-replace` with a boundary) before it is relayed or the camera counts as healthy, and its redirects stay on the registered origin. GBFS bodies must parse as JSON and are re-serialized as `application/json; charset=utf-8`. Camera catalog URLs are pinned by parsed URL — scheme, exact host, no credentials, default port, and a normalized path inside the agency's image directory — and a redirect on a pinned host must stay inside the same directory. Every `/api/cctv` route uses the keyless cross-site checks described below, so cameras stay reachable behind a reverse proxy; a request carrying forwarding headers never reaches the key-bearing Street View fallback and gets the placeholder frame instead, as when no key is configured.
|
||||
- **Consistent API response headers.** Every `/api` response, including errors, partial content, Host refusals and unknown routes, carries `Content-Security-Policy: default-src 'none'; sandbox` and `X-Content-Type-Options: nosniff` (`apiResponseHeadersPlugin` in `build/vite.js`, installed ahead of the Host check). A route that sets its own policy adds it as a further policy; it never replaces this one.
|
||||
- **Response-size caps and timeouts** on proxied responses.
|
||||
- **Sanitized errors** — internal error details are not echoed back to clients.
|
||||
- **Coalesced OAuth refresh** and cached successful responses only (OpenSky).
|
||||
- **Redacted debug logging.** The voice debug log (`.gev-logs/`, gitignored) strips API keys, bearer tokens, client secrets, and image data URLs before writing.
|
||||
- **Cross-site gate on cost/log endpoints.** The cost-bearing (`/api/realtime/token`, `/api/openai/hud-summary`, `/api/google/nearby-places`, `/api/google/text-search`), quota-bearing (`/api/mapillary/*`, with a per-IP limit on tiles, and `/api/tomtom/*`, backed by its persistent daily tile budget) and log (`/api/realtime/debug-log`) endpoints refuse cross-site browser requests: a foreign or opaque `Origin`, a `Sec-Fetch-Site` other than `same-origin`/`none` (this is what blocks an `<img>`/navigation that carries no `Origin`), or any reverse-proxy/CDN forwarding header returns `403`. Non-browser loopback tools (the repo QA harnesses POST the token endpoint with no `Origin`) and the explicit `HOST=0.0.0.0` LAN opt-in keep working — the gate deliberately does not require a loopback remote address; that stricter requirement belongs only to the credential panel (`admitKeySetupRequest`). The Host check (`allowedHosts`, applied by `hostCheckPlugin` ahead of every route, since Vite's own check runs only after plugin middleware) rejects a rebinding `Host` before any handler runs, in every mode, and the gate is defense in depth behind it: the LAN opt-in keeps that check and accepts only IP addresses, `localhost` and names listed in `GEV_ALLOWED_HOSTS`. **Limit:** a name you list there is trusted like the app's own address, so list only names you control. The keyless terrain route (`/api/terrain/heights`) uses the same cross-site checks, minus the forwarding-header refusal: it guards only the per-client quota and point cache, not a key or a bill, and the map needs it to render behind a reverse proxy. The CCTV routes (`/api/cctv/*`) use the same keyless checks; their one key-bearing step, the Street View fallback, is skipped for any request with forwarding headers.
|
||||
- **Cross-site gate on cost/log endpoints.** The cost-bearing (`/api/realtime/token`, `/api/openai/hud-summary`, `/api/google/nearby-places`, `/api/google/text-search`), quota-bearing (`/api/mapillary/*`, with a per-IP limit on tiles, and `/api/tomtom/*`, backed by its persistent daily tile budget) and log (`/api/realtime/debug-log`) endpoints refuse cross-site browser requests: a foreign or opaque `Origin`, a `Sec-Fetch-Site` other than `same-origin`/`none` (this is what blocks an `<img>`/navigation that carries no `Origin`), or any reverse-proxy/CDN forwarding header returns `403`. Non-browser loopback tools (the repo QA harnesses POST the token endpoint with no `Origin`) and the explicit `HOST=0.0.0.0` LAN opt-in keep working — the gate deliberately does not require a loopback remote address; that stricter requirement belongs only to the credential panel (`admitKeySetupRequest`). The Host check (`allowedHosts`, applied by `hostCheckPlugin` ahead of every route, since Vite's own check runs only after plugin middleware) rejects a rebinding `Host` before any handler runs, in every mode, and the gate is defense in depth behind it: the LAN opt-in keeps that check and accepts only IP addresses, `localhost` and names listed in `GEV_ALLOWED_HOSTS`. **Limit:** a name you list there is trusted like the app's own address, so list only names you control. The keyless terrain route (`/api/terrain/heights`) uses the same cross-site checks, minus the forwarding-header refusal: it guards only the per-client quota and point cache, not a key or a bill, and the map needs it to render behind a reverse proxy.
|
||||
- **Full Content-Security-Policy.** The dev and preview servers send a real CSP (`script-src 'self' 'unsafe-eval' blob:` plus the YouTube, Facebook and X scripts the Nepal event's embedded media loads and the Maps JavaScript API that Street View runs on, with a matching `frame-src` for their players and posts: no inline script and no other script origin; `blob:` covers the object URLs Cesium's bundled workers bootstrap through in the built app), alongside the existing `X-Frame-Options: DENY` and `frame-ancestors 'none'`. `'unsafe-eval'` stays because Knockout, bundled inside Cesium's widgets package, resolves the global object with `eval` at load time and the globe does not initialize without it; styles permit `'unsafe-inline'` and Google Fonts because Vite's dev client injects `<style>` elements.
|
||||
|
||||
Allowing `maps.googleapis.com` in `script-src` also trusts its JSONP endpoints, which can bypass the script allowlist if an HTML-injection bug exists; this is an accepted limitation of loading the Maps JavaScript API, alongside the existing `'unsafe-eval'` allowance.
|
||||
@@ -105,11 +98,6 @@ The dev server is a **key broker**: every server-side key above is spendable by
|
||||
`compose.yaml` publishes on `127.0.0.1`: **widen that mapping and every LAN
|
||||
client arrives from the same gateway address, which hands them Provider
|
||||
Settings too.** Treat publishing beyond loopback as the LAN opt-in above.
|
||||
Compose forwards `GEV_KEY_SETUP_TRUSTED_PEERS` and `GEV_ALLOWED_HOSTS` from
|
||||
the shell or project `.env` before the launcher runs. Set trusted peers to
|
||||
`none` when widening the publish: it becomes an explicit empty list before
|
||||
Vite loads configuration, so the gateway cannot regain Provider Settings
|
||||
access. Unset or blank values preserve automatic gateway discovery.
|
||||
- **Provider-side budgets are the real backstop.** For hard spend protection, configure limits where the money is: OpenAI platform usage limits, Google Cloud budget alerts + per-API quotas, and equivalent controls for any other keyed provider.
|
||||
- **Pinokio LAN and Cloudflare sharing are refused.** The current supported
|
||||
Pinokio release re-reads sharing state when an app registers its Open URL and
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
export const APPLICATION_TEMPLATES = Object.freeze([
|
||||
'scene-chrome',
|
||||
'cockpit',
|
||||
'display-controls',
|
||||
'command-dock',
|
||||
'layer-panels',
|
||||
'context',
|
||||
'welcome',
|
||||
'provider-settings',
|
||||
'hud-loading',
|
||||
]);
|
||||
const allowed = new Set(APPLICATION_TEMPLATES);
|
||||
|
||||
/** Expand only known component templates; markers cannot name filesystem paths. */
|
||||
export function expandApplicationHtml(html) {
|
||||
return html.replace(
|
||||
/^[ \t]*<!-- gev:template ([^\s]+) -->\r?\n?/gm,
|
||||
(_, name) => {
|
||||
if (!allowed.has(name))
|
||||
throw new Error(`Unknown application template: ${name}`);
|
||||
return readFileSync(
|
||||
new URL(`../src/ui/templates/${name}.html`, import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** Assemble static application markup before Vite processes scripts and assets. */
|
||||
export function applicationHtmlPlugin() {
|
||||
return {
|
||||
name: 'application-component-templates',
|
||||
transformIndexHtml: {
|
||||
order: 'pre',
|
||||
handler: expandApplicationHtml,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { registerHooks } from 'node:module';
|
||||
import { load } from './vueLoader.js';
|
||||
|
||||
// Lets Node import single-file components: `node --import=<this file>`.
|
||||
// Hosts reach it as `<core package>/build/register-vue`. The hooks run on the
|
||||
// loading thread: off-thread hooks (module.register) would add a round trip
|
||||
// to every module a test imports, not only the components.
|
||||
registerHooks({ load });
|
||||
+4
-149
@@ -1,56 +1,9 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { applicationHtmlPlugin } from './application-html.js';
|
||||
import cesium from 'vite-plugin-cesium';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { VUE_PLUGIN_OPTIONS } from './vueOptions.js';
|
||||
import { DEFAULT_ALLOWED_HOSTS, hostCheckPlugin } from './allowedHosts.js';
|
||||
import { embedFramingPlugin } from './embed-framing.js';
|
||||
import { panelBuildPlugin } from './panel.js';
|
||||
|
||||
/**
|
||||
* The repository root (this file lives at `<root>/build/vite.js`), as a
|
||||
* forward-slash path for glob matching. Vite's `fs.deny` patterns are
|
||||
* matched against the full absolute file path, and a bare `**\/dist/**`
|
||||
* matches a `dist` directory at *any* depth — including dependencies that
|
||||
* ship their own, such as `node_modules/vite/dist/client/client.mjs` (the
|
||||
* dev client itself). Anchoring to this repository's root denies only the
|
||||
* project's own build output.
|
||||
*/
|
||||
const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url))
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/\/$/, '');
|
||||
|
||||
export function escapeFsDenyPath(path) {
|
||||
return path.replace(/[()[\]{}*?!+@|]/g, '\\$&');
|
||||
}
|
||||
|
||||
const REPO_ROOT_GLOB = escapeFsDenyPath(REPO_ROOT);
|
||||
|
||||
/**
|
||||
* Browser-safe form of the optional Photon endpoint build input. Only https
|
||||
* is accepted: the page's connect-src allows no plain-http origin, so an
|
||||
* http endpoint would only fail later, silently, in the browser.
|
||||
*/
|
||||
function photonEndpointSetting(value) {
|
||||
if (value === undefined) return undefined;
|
||||
const text = String(value ?? '').trim();
|
||||
if (!text || ['off', 'none', 'disabled'].includes(text.toLowerCase()))
|
||||
return null;
|
||||
try {
|
||||
const url = new URL(text);
|
||||
if (
|
||||
url.protocol !== 'https:' ||
|
||||
url.username ||
|
||||
url.password ||
|
||||
url.search ||
|
||||
url.hash
|
||||
)
|
||||
return null;
|
||||
return url.href;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Content-Security-Policy for every document the dev/preview server serves.
|
||||
* No inline script is permitted, and the only foreign script origins are the
|
||||
@@ -94,98 +47,24 @@ export const BROWSER_HEADERS = Object.freeze({
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
});
|
||||
|
||||
/**
|
||||
* Content-Security-Policy for every `/api` response. API routes return data,
|
||||
* never a document, so a response opened directly in a tab gets no origin and
|
||||
* can load nothing.
|
||||
*/
|
||||
export const API_CSP = "default-src 'none'; sandbox";
|
||||
|
||||
/** Response headers every `/api` response carries. */
|
||||
export const API_HEADERS = Object.freeze({
|
||||
'Content-Security-Policy': API_CSP,
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
});
|
||||
|
||||
/**
|
||||
* Whether a request reaches an `/api` mount, by the router's own rule: the
|
||||
* prefix compared without case and followed by the end, `/` or `.`. The router
|
||||
* reads an absolute-form target's path without resolving dot segments, so the
|
||||
* path is taken here the same way (scheme and authority dropped, backslashes as
|
||||
* slashes), alongside the raw and the fully parsed path; any match counts.
|
||||
*/
|
||||
function isApiPath(url) {
|
||||
const raw = String(url || '/');
|
||||
const target = raw
|
||||
.trim()
|
||||
.split(/[?#]/, 1)[0]
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^[a-z][a-z0-9+.-]*:(?:\/\/[^/]*)?/i, '');
|
||||
let parsed = '';
|
||||
try {
|
||||
parsed = new URL(raw, 'http://localhost').pathname;
|
||||
} catch {
|
||||
/* the unparsed paths still decide */
|
||||
}
|
||||
return [raw.split(/[?#]/, 1)[0], target, parsed].some((path) =>
|
||||
/^\/api(?:$|[/.])/i.test(path),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin that sets API_HEADERS on every API response before any other
|
||||
* middleware runs, so success, error, partial, refused and unknown-route
|
||||
* answers all carry them. Vite's `server.headers` and `preview.headers` only
|
||||
* reach the documents and assets Vite serves itself. A route may still add
|
||||
* headers; a route's own Content-Security-Policy is sent as an additional
|
||||
* policy, which can only restrict further, never replace this one.
|
||||
*/
|
||||
export function apiResponseHeadersPlugin() {
|
||||
const install = (server) => {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
if (!isApiPath(req.url)) return next();
|
||||
for (const [name, value] of Object.entries(API_HEADERS))
|
||||
res.setHeader(name, value);
|
||||
const setHeader = res.setHeader.bind(res);
|
||||
res.setHeader = (name, value) =>
|
||||
String(name).toLowerCase() === 'content-security-policy' &&
|
||||
String(value) !== API_CSP
|
||||
? setHeader(name, `${API_CSP}, ${value}`)
|
||||
: setHeader(name, value);
|
||||
next();
|
||||
});
|
||||
};
|
||||
return {
|
||||
name: 'api-response-headers',
|
||||
enforce: 'pre',
|
||||
configureServer: install,
|
||||
configurePreviewServer: install,
|
||||
};
|
||||
}
|
||||
|
||||
/** Build browser assets with explicit inputs; never load environment or providers. */
|
||||
export function createBrowserViteConfig({
|
||||
plugins = [],
|
||||
publicDir,
|
||||
googleApiKey,
|
||||
cesiumToken,
|
||||
photonUrl,
|
||||
mapillaryToken,
|
||||
host = 'localhost',
|
||||
port = 4173,
|
||||
allowedHosts = DEFAULT_ALLOWED_HOSTS,
|
||||
command,
|
||||
} = {}) {
|
||||
const photonSetting = photonEndpointSetting(photonUrl);
|
||||
return {
|
||||
plugins: [
|
||||
// First, so every API response, including a Host refusal, carries them.
|
||||
apiResponseHeadersPlugin(),
|
||||
// Next, so no provider route answers a Host the server does not allow.
|
||||
// First, so no provider route answers a Host the server does not allow.
|
||||
hostCheckPlugin(),
|
||||
cesium(),
|
||||
// Application views are Vue single-file components.
|
||||
vue(VUE_PLUGIN_OPTIONS),
|
||||
applicationHtmlPlugin(),
|
||||
...plugins,
|
||||
embedFramingPlugin(),
|
||||
panelBuildPlugin(),
|
||||
@@ -215,19 +94,7 @@ export function createBrowserViteConfig({
|
||||
// build/allowedHosts.js); IP addresses are always accepted.
|
||||
allowedHosts: [...allowedHosts],
|
||||
fs: {
|
||||
deny: [
|
||||
'.env',
|
||||
'.env.*',
|
||||
'*.{crt,pem}',
|
||||
'**/.git/**',
|
||||
'**/ENVIRONMENT',
|
||||
'**/.gev-logs/**',
|
||||
'**/.gev-cache/**',
|
||||
// Root-anchored: a bare '**/dist/**' would also deny every
|
||||
// dependency's own dist/ (e.g. node_modules/vite/dist/client),
|
||||
// breaking /@vite/client and other dev-client assets.
|
||||
`${REPO_ROOT_GLOB}/dist/**`,
|
||||
],
|
||||
deny: ['.env', '.env.*', '*.{crt,pem}', '**/.git/**', '**/ENVIRONMENT'],
|
||||
},
|
||||
// These headers protect the document containing Provider Settings and
|
||||
// give the whole page a real Content-Security-Policy (BROWSER_CSP).
|
||||
@@ -239,20 +106,8 @@ export function createBrowserViteConfig({
|
||||
// framing + CSP hardening (one constant, no drift).
|
||||
preview: { headers: BROWSER_HEADERS },
|
||||
define: {
|
||||
// vue-i18n holds the locale and catalogs; Core formats the messages
|
||||
// (src/ui/i18n.js), so the build drops vue-i18n's message compiler.
|
||||
__VUE_I18N_FULL_INSTALL__: 'false',
|
||||
__VUE_I18N_LEGACY_API__: 'false',
|
||||
__INTLIFY_PROD_DEVTOOLS__: 'false',
|
||||
__INTLIFY_DROP_MESSAGE_COMPILER__: 'true',
|
||||
'import.meta.env.GOOGLE_MAPS_API_KEY': JSON.stringify(googleApiKey),
|
||||
'import.meta.env.CESIUM_ION_TOKEN': JSON.stringify(cesiumToken),
|
||||
// Only expose a validated URL or the explicit disabled sentinel.
|
||||
...(photonSetting === undefined
|
||||
? {}
|
||||
: {
|
||||
'import.meta.env.PHOTON_URL': JSON.stringify(photonSetting),
|
||||
}),
|
||||
// Mapillary client tokens are public by design (see SECURITY.md).
|
||||
'import.meta.env.MAPILLARY_CLIENT_TOKEN': JSON.stringify(
|
||||
mapillaryToken ?? '',
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import {
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { VUE_TEMPLATE_OPTIONS } from './vueOptions.js';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
// The compiler loads on the first component that is not cached, so a test
|
||||
// process that imports no views, or only cached ones, never pays for it.
|
||||
let compiler = null;
|
||||
const sfcCompiler = () => (compiler ??= require('vue/compiler-sfc'));
|
||||
|
||||
/**
|
||||
* Compile a single-file component to an ES module for Node, so unit tests (the
|
||||
* application's and a host's) can import components without a bundler.
|
||||
* Styles are dropped: tests read the global stylesheet separately.
|
||||
*/
|
||||
export function compileVueModule(source, filename) {
|
||||
const { compileScript, compileTemplate, parse } = sfcCompiler();
|
||||
// The template is parsed with the SFC; parse it as the browser build does.
|
||||
const { descriptor, errors } = parse(source, {
|
||||
filename,
|
||||
templateParseOptions: VUE_TEMPLATE_OPTIONS.compilerOptions,
|
||||
});
|
||||
if (errors.length) throw errors[0];
|
||||
const id = filename;
|
||||
if (descriptor.scriptSetup) {
|
||||
return compileScript(descriptor, {
|
||||
id,
|
||||
inlineTemplate: true,
|
||||
templateOptions: VUE_TEMPLATE_OPTIONS,
|
||||
}).content;
|
||||
}
|
||||
const script = descriptor.script
|
||||
? compileScript(descriptor, { id, genDefaultAs: '__sfc__' }).content
|
||||
: 'const __sfc__ = {};';
|
||||
let template = '';
|
||||
if (descriptor.template) {
|
||||
const compiled = compileTemplate({
|
||||
source: descriptor.template.content,
|
||||
filename,
|
||||
id,
|
||||
...VUE_TEMPLATE_OPTIONS,
|
||||
});
|
||||
if (compiled.errors.length) throw compiled.errors[0];
|
||||
template = compiled.code;
|
||||
}
|
||||
return `${script}\n${template}\nif (typeof render === 'function') __sfc__.render = render;\nexport default __sfc__;\n`;
|
||||
}
|
||||
|
||||
// The loader's own code is part of every key, so a change to how components
|
||||
// compile never reuses output from before it.
|
||||
let loaderFingerprint = null;
|
||||
const fingerprint = () =>
|
||||
(loaderFingerprint ??= createHash('sha256')
|
||||
.update(readFileSync(fileURLToPath(import.meta.url)))
|
||||
.update(readFileSync(new URL('./vueOptions.js', import.meta.url)))
|
||||
.update(require('vue/package.json').version)
|
||||
.digest('hex'));
|
||||
|
||||
/**
|
||||
* Where compiled components are kept between test processes, never a
|
||||
* package's own tree (a host's checks refuse files inside Core): the system
|
||||
* temp directory, or on Windows the per-user local app data directory, since
|
||||
* a temp directory there may be shared and its ownership is not checked.
|
||||
* GEV_VUE_CACHE=0 compiles every time.
|
||||
*/
|
||||
export function vueCacheDirectory(
|
||||
env = process.env,
|
||||
platform = process.platform,
|
||||
) {
|
||||
if (env.GEV_VUE_CACHE === '0') return null;
|
||||
if (platform === 'win32') {
|
||||
return env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'gev-vue-sfc') : null;
|
||||
}
|
||||
return path.join(tmpdir(), `gev-vue-sfc-${process.getuid()}`);
|
||||
}
|
||||
|
||||
/** The cache entry for one component: its path, its text and the loader. */
|
||||
export function vueCacheKey(source, filename) {
|
||||
return createHash('sha256')
|
||||
.update(JSON.stringify([fingerprint(), filename]))
|
||||
.update('\0')
|
||||
.update(source)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a component, reusing a previous process's result for the same text.
|
||||
* A cache that cannot be read or written only costs the compile.
|
||||
*/
|
||||
export function cachedVueModule(
|
||||
source,
|
||||
filename,
|
||||
directory = vueCacheDirectory(),
|
||||
) {
|
||||
if (!directory || !ownCacheDirectory(directory)) {
|
||||
return compileVueModule(source, filename);
|
||||
}
|
||||
const entry = path.join(directory, `${vueCacheKey(source, filename)}.mjs`);
|
||||
try {
|
||||
return readFileSync(entry, 'utf8');
|
||||
} catch {
|
||||
// Not compiled yet.
|
||||
}
|
||||
const compiled = compileVueModule(source, filename);
|
||||
// Concurrent test processes and threads may compile the same component;
|
||||
// each writes its own new file and the rename makes the entry appear whole.
|
||||
const partial = `${entry}.${randomBytes(6).toString('hex')}.tmp`;
|
||||
try {
|
||||
writeFileSync(partial, compiled, { flag: 'wx', mode: 0o600 });
|
||||
renameSync(partial, entry);
|
||||
} catch {
|
||||
// An unwritable directory, or a rename another process holds open,
|
||||
// leaves the cache without this entry.
|
||||
try {
|
||||
rmSync(partial, { force: true });
|
||||
} catch {
|
||||
// A partial file that cannot be removed is only clutter.
|
||||
}
|
||||
}
|
||||
return compiled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached modules run as code, so the directory must be this user's own: a
|
||||
* real directory (not a link), owned by the current user and writable by no
|
||||
* one else. In a shared temp directory another user could otherwise create it
|
||||
* first and plant modules. On Windows it sits in the user's own profile.
|
||||
*/
|
||||
const checkedDirectories = new Map();
|
||||
export function ownCacheDirectory(directory) {
|
||||
if (!checkedDirectories.has(directory)) {
|
||||
checkedDirectories.set(directory, isOwnDirectory(directory));
|
||||
}
|
||||
return checkedDirectories.get(directory);
|
||||
}
|
||||
|
||||
function isOwnDirectory(directory) {
|
||||
try {
|
||||
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
||||
const stats = lstatSync(directory);
|
||||
if (!stats.isDirectory()) return false;
|
||||
if (process.platform === 'win32') return true;
|
||||
return stats.uid === process.getuid() && (stats.mode & 0o022) === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Synchronous module hook (module.registerHooks): `.vue` loads compiled. */
|
||||
export function load(url, context, nextLoad) {
|
||||
if (!url.endsWith('.vue')) return nextLoad(url, context);
|
||||
const filename = fileURLToPath(url);
|
||||
return {
|
||||
format: 'module',
|
||||
source: cachedVueModule(readFileSync(filename, 'utf8'), filename),
|
||||
shortCircuit: true,
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Template options shared by the browser build, the boundary check and the
|
||||
* unit-test loader. Whitespace between nodes is kept (as a single space) so
|
||||
* an application view renders as its static markup did; Prettier formats
|
||||
* single-file components with strict whitespace sensitivity to match. Asset
|
||||
* URLs such as `/pin.svg` stay literal instead of becoming module imports.
|
||||
*/
|
||||
export const VUE_TEMPLATE_OPTIONS = Object.freeze({
|
||||
compilerOptions: Object.freeze({ whitespace: 'preserve' }),
|
||||
transformAssetUrls: false,
|
||||
});
|
||||
|
||||
/** `@vitejs/plugin-vue` options for application builds. */
|
||||
export const VUE_PLUGIN_OPTIONS = Object.freeze({
|
||||
template: VUE_TEMPLATE_OPTIONS,
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
- Director clock listeners, interaction observers and scene release failures
|
||||
no longer leave the director inconsistent: each scene is released once, and
|
||||
a phase error is kept (with the release failure logged) when cleanup also
|
||||
fails (deepeshbind, #1014).
|
||||
@@ -0,0 +1,12 @@
|
||||
- `aircraft_in_area` and `vessels_in_area` take `group_by` and `stats`, so an
|
||||
MCP client can ask which airlines, countries, aircraft types or ship types
|
||||
dominate an area, or the average altitude or speed, over every match instead
|
||||
of estimating from the returned rows. Airlines come from the ICAO designator
|
||||
in each callsign (SWA1234 counts as SWA), so the breakdown works on feeds
|
||||
that report no operator; operator and type are counted only where the feed
|
||||
reports them. Ship types count by family, so AIS codes 70 to 79 are all
|
||||
cargo; type 0 ("not available") and the reserved codes 100 to 255 count as
|
||||
unknown. Altitude and speed stats cover airborne aircraft only, so parked
|
||||
aircraft do not pull the means down. Every vessel answer, `find_vessel`
|
||||
included, now reads AIS ship-type codes as their family and a destination
|
||||
with no letters as unknown (quanticsoul4772, #1056).
|
||||
@@ -0,0 +1,4 @@
|
||||
- On Windows, `npm run doctor` and the Pinokio install no longer print Node's
|
||||
DEP0190 warning ("Passing args to a child process with shell option true can
|
||||
lead to security vulnerabilities"): npm.cmd's fixed subcommand is now part of
|
||||
the shell command string instead of an argument array (quanticsoul4772, #1057).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Location search accepts two-dimensional WGS84 `geo:` URIs offline, with
|
||||
the existing coordinate framing and label. Unsupported URI fields are passed
|
||||
to the remaining search providers unchanged (cj-vana, #1071).
|
||||
@@ -1,4 +0,0 @@
|
||||
- FIRMS fire data loads on hosts where IPv6 is unreachable and IPv4 is slow to
|
||||
connect: a request whose every address fails to connect retries once over
|
||||
IPv4, and FIRMS warnings name the underlying connection errors instead of
|
||||
only `fetch failed` (jeffblakely, #1078).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Keep every submarine-cable segment associated with its source feature after
|
||||
Cesium expands multipart geometry, correcting segment colors, click destinations
|
||||
and horizon culling while retaining one reference stem per feature
|
||||
(@Aliciawque, #1093).
|
||||
@@ -0,0 +1,4 @@
|
||||
- Update DOMPurify to 3.4.17, Sharp to 0.35.5 and source-map-js to 1.2.2 for
|
||||
GHSA-p98j-92pf-mc4p, GHSA-6688-9rhm-gjv2, GHSA-wq5f-xc86-pv6w and
|
||||
GHSA-68fv-2mgg-jv7q; `npm audit` now reports no vulnerabilities (NurstarK,
|
||||
#1094).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Military aircraft keep their last heading and speed when an adsb.lol row omits
|
||||
`track` or `gs`, instead of turning north and stopping; a grounded row without
|
||||
a speed still reads 0 (Aditya, @Rohilalala, #1096).
|
||||
@@ -0,0 +1,5 @@
|
||||
- Overpass data whose element tags contain a failure phrase ("timed out",
|
||||
"runtime error", "too many requests" and similar, e.g. a place named "Timed
|
||||
Out Escape Rooms") is returned as data instead of being refused and putting
|
||||
the endpoint on cooldown; upstream failures are read from the remark (Aditya,
|
||||
@Rohilalala, #1097).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Skip TLE sets whose line 1 and line 2 carry different catalog numbers, so a
|
||||
spliced catalog cannot pair one satellite's elements with another's; the next
|
||||
valid set still parses (VG31OP, #1101).
|
||||
@@ -0,0 +1,5 @@
|
||||
- A failed Esri Satellite or keyless terrain load still shows OSM or flat terrain
|
||||
at once, but no longer lasts the whole session: while the fallback is on
|
||||
screen, the real provider is retried in the background (30 seconds, doubling
|
||||
to a 10-minute cap) and replaces it live once it loads. Map switches never
|
||||
wait for a retry (Aditya, @Rohilalala, #1111).
|
||||
@@ -0,0 +1,2 @@
|
||||
- Exclude aircraft without a reported altitude from requested altitude bands;
|
||||
unfiltered aircraft queries still retain them (rudycelekli, #1115).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Release superseded Google Street View open waits and suppress metadata
|
||||
requests whose viewer was unmounted while the service loaded (rudycelekli,
|
||||
#1117).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Label camera-tool Street View fallbacks as historical Google imagery rather
|
||||
than the current camera view; keep unknown frame provenance explicit
|
||||
(rudycelekli, #1118).
|
||||
@@ -0,0 +1,5 @@
|
||||
- Scenes keep the "Scene not saved" status when a capture, update, append or
|
||||
import cannot be saved, and an unreadable saved project no longer overwrites
|
||||
the recovery checkpoint. `startScene()` resolves to its documented result,
|
||||
`afterShotId` works without an explicit scene, and `scenesRun` counts the
|
||||
distinct scenes queued (djabhi31, #1135).
|
||||
@@ -0,0 +1,6 @@
|
||||
- Fire-perimeter queries handle outlines and holes crossing the antimeridian:
|
||||
areas enclosed near ±180° are included, areas at the opposite meridian are
|
||||
excluded, and returned perimeter coordinates stay near the dateline. Incident
|
||||
cards use the same circular longitude mean and compare multipart outline
|
||||
areas along short longitude edges, keeping the card on the largest polygon
|
||||
(Arianna Method, @ariannamethod, #1142).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Director shot actions land on the requested shot. A seek to a shot's start
|
||||
could round a hair below it and select the end of the previous shot for some
|
||||
shot counts and durations (Aditya, @Rohilalala, #1171).
|
||||
@@ -0,0 +1,13 @@
|
||||
- Keep missing CCTV numbers unknown instead of reading them as zero. `Number`
|
||||
turns null, blank strings and booleans into 0 or 1, and the camera source
|
||||
list sends an unknown number as JSON null, so a source-pack camera that left
|
||||
out its pose reached the browser as zeros clamped to each field's floor —
|
||||
facing north, pitch −2°, FOV 20°, range 120 m, mount 6 m and ground 0 m —
|
||||
instead of the defaults (id-hash heading, −17°, 74°, 700 m, 24 m and the
|
||||
city's ground); a null or blank position placed the camera at 0,0. The
|
||||
server's `toFiniteNumber` and the browser's `safeNumber` now treat absent,
|
||||
blank and boolean values as missing. The same conversion also stops a
|
||||
Tallinn row with a null heading reading as a confident north-facing camera,
|
||||
a Caltrans camera with a blank elevation losing its 150 m ground prior, and
|
||||
a TfL or Catalonia camera with a null or blank coordinate being kept at 0,0
|
||||
or on the zero meridian (omerbberk, #1172).
|
||||
@@ -0,0 +1,10 @@
|
||||
- Flights' ground floor works across the antimeridian. The viewer-distance
|
||||
check behind the 150 km floor radius and its copy in the photoreal mesh-floor
|
||||
sampler subtracted longitudes without wrapping, so two points 21 km apart
|
||||
across 180° measured about 38,000 km and low or grounded aircraft just across
|
||||
the line from the viewer were never floored. A contact's display corridor
|
||||
walked a segment across 180° the long way round, spending its cell budget on
|
||||
ground behind the contact, and a contact on the seam lost its cell hysteresis.
|
||||
SEARCH NEARBY SITES on a view across 180° searched Google Places around
|
||||
longitude 0, on the far side of the globe, with a radius sized from the view's
|
||||
height alone; it now searches the view (Aditya, @Rohilalala, #1173).
|
||||
@@ -0,0 +1,4 @@
|
||||
- Keep missing launch-pad coordinates unknown so launches use the location
|
||||
fallback or stay off the map instead of appearing at 0°N 0°E. Missing stage
|
||||
flight numbers no longer show "FLIGHT 0"
|
||||
(Rudy Celekli, @rudycelekli, #1186).
|
||||
@@ -0,0 +1,6 @@
|
||||
- Add nine Spanish city bike systems to Bike Share and `get_bike_share`: BiciMAD
|
||||
(Madrid), Valenbisi (Valencia), Bizi Zaragoza, Sevici (Seville), BIKI
|
||||
(Valladolid), BiciCoruña, Dbizi (Donostia-San Sebastián), Bilbao Bizi and
|
||||
BicinRivas, about 1,840 stations. Station names read underscores as word
|
||||
breaks, and station capacity counts docks rather than PBSC's geofenced area
|
||||
(ulihhhh, #1190).
|
||||
@@ -0,0 +1,5 @@
|
||||
- Share Launch Library request cache keys across installations refreshing in the
|
||||
same UTC minute, identify the app to the provider, and explain that standard
|
||||
space-missions access needs no key. Optional higher-rate LL2 tokens are paid
|
||||
upgrades; existing local cache and server-only token handling are unchanged
|
||||
(rudycelekli, #1197).
|
||||
@@ -0,0 +1,3 @@
|
||||
- Keep Space Missions marked STALE when Launch Library 2 falls back to cached
|
||||
launch data, and return to NOMINAL after a fresh response without discarding
|
||||
retained missions (rudycelekli, #1199).
|
||||
@@ -0,0 +1,5 @@
|
||||
- Color CCTV cameras with a video feed violet. Cameras with an HLS, MP4 or
|
||||
WebM feed were the same cyan as stills, so a DelDOT or Statens vegvesen
|
||||
video camera could only be found by opening it. Stills stay cyan and the
|
||||
active camera stays amber, and focus de-emphasis fades the violet the same
|
||||
way (Danielslay86, #1200).
|
||||
@@ -0,0 +1,4 @@
|
||||
- Draw CCTV stills at their source aspect on the monitor plane instead of
|
||||
stretching them to the 16:9 canvas. Most DOT cameras publish 4:3 frames, so
|
||||
every still was widened; they are now centered and pillarboxed on the dark
|
||||
the placeholder already ends on (Danielslay86, #1201).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Keep Directions route errors, the Local ADS-B receiver error and Global
|
||||
Context cohort reasons English in layer data; the Layers panel, Radio card
|
||||
and awareness panel still show them in the interface language. The route
|
||||
proxy's "routing busy" and "route proxy error" refusals are now translated
|
||||
too (daikaginza, #1209).
|
||||
@@ -0,0 +1,5 @@
|
||||
- Street Level panel: SINCE is a dropdown of its nine windows (the tooltip
|
||||
names the cut-off date), IMAGERY and FIT / FILL are plain toggle buttons,
|
||||
and the panel opens by itself only when a photo opens or a click fails to
|
||||
load one, no longer when the layer is switched on. FIT / FILL starts at
|
||||
FIT each session (gekh, #1241).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Pause the HUD's camera readout while the page is hidden, as polled layers
|
||||
already do. An app embedded inline in a panel keeps both updating, because the
|
||||
panel's host can report the page as hidden while it is on screen (nu-stats,
|
||||
#1344).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Restore a shot's declared layer visibility when seeking within it after manual
|
||||
layer changes, including toggles still in flight. Only changed layers are
|
||||
reconciled; refused restoration reports a failed seek and keeps the last
|
||||
position, while undeclared operator layers are preserved (@oneill9, #1374).
|
||||
@@ -1,2 +0,0 @@
|
||||
- A "place, region" search such as "Paris, Texas" now lands on the place in
|
||||
that region instead of a namesake elsewhere (quanticsoul4772, #1401).
|
||||
@@ -1,5 +0,0 @@
|
||||
- The terrain proxy stops asking the upstream for a while after a 429
|
||||
(its Retry-After, at most 10 minutes, or 60 s) instead of retrying on every
|
||||
page request, and a Retry-After longer than its 10 s retry budget now ends
|
||||
the retries at once instead of sleeping the budget out (quanticsoul4772,
|
||||
#1402).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Fire Perimeters and `get_fire_perimeters` say when WFIGS is unreachable and
|
||||
the last snapshot is being shown (STALE, its age, "data may be stale"), and
|
||||
that snapshot is served for at most 24 hours (quanticsoul4772, #1404).
|
||||
@@ -1,3 +0,0 @@
|
||||
- The Traffic layer reads STALE instead of LIVE when the TomTom proxy serves
|
||||
cached flow tiles (budget spent or TomTom failing), and the row dates them by
|
||||
the oldest cached tile (quanticsoul4772, #1405).
|
||||
@@ -1,4 +0,0 @@
|
||||
- The military-installations proxy (`/api/military-installations`) now refuses
|
||||
cross-site and reverse-proxy-forwarded browser requests with 403 before any
|
||||
Overpass work, through the same shared gate as `/api/overpass`. Same-origin
|
||||
app requests and local tools such as curl still work (@Sunil56224972, #1427).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Add Traditional Chinese (zh-Hant) with language selection and localized
|
||||
Chinese readouts: the Simplified Chinese catalog written in Traditional
|
||||
characters, based on translations by @abiyeenzo (#914) and @EDIT-Aeterna
|
||||
(#979) (German, @gekh, #1430).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Show a vessel course of 359.5° or more as 0°. Vessel cards and the AIS HUD
|
||||
rounded the course without wrapping, so a ship with no reported heading on
|
||||
a 359.7° course read "360°" and "HDG: 360DEG" (Aditya, @Rohilalala, #1432).
|
||||
@@ -1,6 +0,0 @@
|
||||
- Show a Contacts marker 999.5 m or more away as 1.0 km. The on-globe marker
|
||||
chose metres before rounding, so 999.6 m read "1000 m" while the panel read
|
||||
"1.0 km" (Aditya, @Rohilalala, #1433).
|
||||
- Distance labels share one formatter that takes each unit cut after rounding,
|
||||
so the Contacts panel and markers, the drawn-line measure and the Cockpit
|
||||
show 9.96 km as 10 km rather than 10.0 km (#1433).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Selecting the nearest aircraft by voice no longer says "0 feet" for a contact
|
||||
whose feed reported no altitude; the reply and card leave the altitude out
|
||||
(Aditya, @Rohilalala, #1434).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Military aircraft whose transponders report a GNSS vertical rate now carry it
|
||||
in analyst and tracked-aircraft data instead of null (Aditya, @Rohilalala,
|
||||
#1436).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Show a CCTV camera facing 359.5° or more as HDG 0°. The HUD heading token
|
||||
and the camera panel rounded the bearing without wrapping, so a camera
|
||||
facing 359.6° read "HDG 360°" (Aditya, @Rohilalala, #1441).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Keep Street Traffic's flow status honest at the edges. A few TomTom-matched
|
||||
dots among hundreds rounded to 0% and read "(no matches)", and a few
|
||||
simulated dots rounded to "100% cov"; coverage now stays within 1–99% while
|
||||
both are shown (Aditya, @Rohilalala, #1442).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Run Space Missions' Replay Ascent countdown from T−10 after the five-second
|
||||
tile hold; the hold ran 10 s and the countdown started at T−05 (Aditya,
|
||||
@Rohilalala, #1443).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Keep Space Missions' replay countdown in real time when the replay speed
|
||||
changes before liftoff; 0.25× had stretched T−8 to T−32 (Aditya,
|
||||
@Rohilalala, #1444).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Label a route, arrow or drawn line of 995 m or more as 1.0 km, and of
|
||||
9,950 m or more as 10 km. The placed label picked the unit before rounding,
|
||||
so it read "1000 m" and "10.0 km" (Aditya, @Rohilalala, #1445).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Show a drawn area of 9,999.5 m² or more as 1.0 ha, and of 999,500 m² or
|
||||
more as 1.00 km². The draw hint picked the unit before rounding, so it read
|
||||
"10000 m²" and "100.0 ha" (Aditya, @Rohilalala, #1446).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Bike Share stations whose feed publishes no capacity now take it from each
|
||||
status poll's bikes and docks instead of keeping the first poll's value or
|
||||
the placeholder 15 (Aditya, @Rohilalala, #1447).
|
||||
@@ -1,6 +0,0 @@
|
||||
- Local ADS-B decoder feeds now carry a GNSS-sourced vertical rate
|
||||
(`geom_rate`) when no barometric rate is sent, so the card shows V/S
|
||||
instead of a dash (Aditya, @Rohilalala, #1448).
|
||||
- Local ADS-B, military and the adsb.lol flight fallback read the vertical
|
||||
rate through one shared readsb field reader, so all three prefer the
|
||||
barometric rate and fall back to the GNSS rate the same way (#1448).
|
||||
@@ -1,3 +0,0 @@
|
||||
- List each submarine cable once in find_submarine_cables. TeleGeography
|
||||
splits 17 cables (2Africa, Echo, Hawaiki) into several features, so a
|
||||
search for Echo read "3 submarine cables" (Aditya, @Rohilalala, #1449).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Show a route of 9.95 km or more as 10 km. The Directions row and step
|
||||
cards kept one decimal until the raw distance reached 10 km, so 9.96 km
|
||||
read "10.0 km" (Aditya, @Rohilalala, #1450).
|
||||
@@ -1,2 +0,0 @@
|
||||
- Show an ALPR camera's mapped bearing of 359.5° or more as DIRECTION 0°
|
||||
rather than DIRECTION 360° (Aditya, @Rohilalala, #1451).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Show a transit vehicle's reported bearing of 359.5° or more as 0°. The
|
||||
selected-vehicle card rounded it without wrapping, so a bus heading 359.6°
|
||||
read "reported hdg 360°" (Aditya, @Rohilalala, #1452).
|
||||
@@ -1,6 +0,0 @@
|
||||
- Space Missions no longer shows an older satellite with a similar name and a
|
||||
different known launch year as a new payload's orbit: the launch-year check
|
||||
on loaded satellites now runs (Aditya, @Rohilalala, #1453).
|
||||
- Satellite launch years come from one TLE designator parser for both the
|
||||
loaded catalog and the active-TLE fallback; a blank designator is an
|
||||
unknown year instead of 2000 (#1453).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Describe a selected flight or military contact on 359.5° or more to the
|
||||
voice assistant as heading 0°, not 360° (Aditya, @Rohilalala, #1454).
|
||||
- Heading and bearing readouts share one rounding helper, so the Cockpit's
|
||||
TRACK ACQUIRED course no longer reads 360°, and a negative bearing wraps
|
||||
instead of printing a minus sign (#1454).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Between 721px and 900px wide (tablet portrait, phone landscape), the
|
||||
Location and Visual Presets dock wings show only their icons, so their
|
||||
labels no longer spill over the voice control or take taps meant for the
|
||||
mic button. The full name stays in the tooltip and accessible name, and the
|
||||
Cyber layout keeps its labels (Denis Groshev, @ZloyS, #1457).
|
||||
@@ -1,4 +0,0 @@
|
||||
- The Docker image and CI now pin Node 24.22.2, the current 24 LTS patch. It
|
||||
carries the October 21, 2026 security fixes first released in 24.22.1,
|
||||
including CVE-2026-111497 in the Brotli decoder (Luis Ortiz, @anobjectn,
|
||||
#1458).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Cut the Google requests the HUD's AI summary makes on its own. It no longer
|
||||
looks up place names when no OpenAI key is configured, reverse-geocodes at
|
||||
most two points instead of up to four, and looks up again only once the view
|
||||
shows a different area, not after every small pan, so a small pan no longer
|
||||
asks OpenAI for a new summary either (samehkhamis, #1459).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Leave layer aliases the layer id already spells, such as "dams" for
|
||||
`local-dams`, out of the voice layer hint. The Realtime prompt is 305
|
||||
characters shorter, leaving room under the 52,000 gate for new
|
||||
voice-toggleable layers; every alias is still accepted (@quanticsoul4772,
|
||||
#1462).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Update the development tools to Vite 6.4.4 and Puppeteer 25.16.0, which
|
||||
clears the open npm advisories for the dev server and the browser test
|
||||
tooling, and remove the unused local screenshot helper `scripts/shot-sink.mjs`
|
||||
(samehkhamis, #1463).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Give the side panels one look. Every panel title now uses the command dock's
|
||||
label type in a brighter colour; a panel's edge and title light up on hover,
|
||||
as the dock's trays do; and in the default theme each collapse button sits
|
||||
19px from its panel's edge (plus the scrollbar's width in an open, scrolling
|
||||
panel where the system always shows scrollbars) (asturksever, #1466).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Remove the Operator HUD layout. Display > HUD > Layout and the HUD voice
|
||||
action now offer Tactical, Minimal and Cyber; share links, scenes and saved
|
||||
views that name Operator open in Tactical (asturksever, #1467).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Remove the Snow visual preset. Visual Presets, keyboard shortcuts, the
|
||||
Cockpit vision carousel and the visual-style voice action now offer Normal,
|
||||
CRT, NVG, FLIR, Anime and Noir; share links and scene shots that name Snow
|
||||
open on Normal (asturksever, #1468).
|
||||
@@ -1,6 +0,0 @@
|
||||
- Gather Directions, Draw and Import in a new UTILITIES panel in the left
|
||||
column, between Data Layers and Scenes. Draw and Import leave DISPLAY, and
|
||||
Radio stays in Data Layers under "Audio". Each drawn shape now gets a row
|
||||
under Draw, like an imported file: name it, recolour it, hide it, fly to it
|
||||
or remove it there. Area, Line and Pin are icon buttons, and the colour for
|
||||
the next shape is a row of swatches (asturksever, #1469).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Add Ukrainian (uk) to the language picker: the whole interface in Ukrainian,
|
||||
with its four plural forms and Cyrillic HUD units. Based on the Ukrainian
|
||||
translation by @meirm (#312) (German, @gekh, #1470).
|
||||
@@ -1,7 +0,0 @@
|
||||
- Remove the Radio icon from the Context panel header, along with the compact
|
||||
Radio player it opened; Radio stays in Context as its own section, and
|
||||
Cockpit's compact Radio is unchanged. Context is also easier to read: the
|
||||
Contacts / Space Missions modes are one segmented control, labels and
|
||||
guidance are larger and higher contrast, the standby hint is plain text
|
||||
instead of a dashed box, and the panel draws no glow halo. Cyber keeps its
|
||||
own framed buttons and red titles (asturksever, #1471).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Rename the Scenes panel to Authoring and lay it out in steps (Scene, Shots,
|
||||
Preview, Save & share) with larger, higher-contrast text, numbered shots, a
|
||||
tooltip on every action and buttons that are disabled when they would do
|
||||
nothing (asturksever, #1472).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Add Italian (it) to the language picker: the whole interface in Italian,
|
||||
with Italian plural forms and labels shortened to fit the panels. Based on
|
||||
the translation by @abiyeenzo (#912) (German, @gekh, #1473).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Add Brazilian Portuguese (pt) to the language picker, with every screen
|
||||
translated and Portuguese plural forms. The CCTV summary line now names its
|
||||
calibration, source and view in the page language. Based on translations by
|
||||
@abiyeenzo (#909) (German, @gekh, #1475).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Add French (fr) to the language picker, with French typography and plural
|
||||
forms. With nine languages the picker now shows its search field. Based on
|
||||
the translation by @abiyeenzo (#907), checked against @meirm's French
|
||||
catalog (#312) (German, @gekh, #1476).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Add German (de) to the language picker, with a full translation of the
|
||||
interface and a German style guide in docs/translations/de.md. Based on
|
||||
translations by @abiyeenzo (#908) (German, @gekh, #1477).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Add Japanese (ja) to the language picker, with year-first dates, Japanese
|
||||
counters, unit words joined to their numbers (5分前, 1時間5分) and CJK label
|
||||
rules for Japanese text. Based on translations by @abiyeenzo (#913)
|
||||
(German, @gekh, #1478).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Add Arabic (ar), the first right-to-left language: the title, rails, dock and
|
||||
corner row mirror, numbers keep Western digits, and plurals use all six
|
||||
Arabic forms. FIRMS cards and Space Missions landing labels are now
|
||||
translated in every language. Based on translations by @abiyeenzo (#915)
|
||||
(German, @gekh, #1479).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Draw only the CCTV cameras in view, and thin them to one per ground cell when
|
||||
zoomed out. Dragging the globe with the default 4,000-camera catalog no
|
||||
longer stalls, and the camera picker lists the cameras in view with a count
|
||||
of the ones outside it (samehkhamis, #1485).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Let a GNSS integrity source set the layer's evidence window (`windowMs`). A
|
||||
missing or invalid value keeps the 30-minute default, and the layer row now
|
||||
names the window in use instead of a fixed "30 min" (samehkhamis, #1491).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Let a weather source describe its own products through an optional
|
||||
`display` object in each manifest (source, credit, units, coverage, info,
|
||||
legend and extent). Each valid field replaces the built-in value, and
|
||||
manifests without it render as before (samehkhamis, #1494).
|
||||
@@ -1,205 +0,0 @@
|
||||
- The language picker switches language in place instead of reloading the
|
||||
page. Views re-render; code that publishes translated text words it again
|
||||
through `onLocaleChange` (`src/i18n/index.js`), and `<html lang>`/`dir` and
|
||||
the page's `data-i18n` markup follow. `persistLocale` replaces
|
||||
`persistLocaleAndReload`, and `registerUnsavedWork` and the switch's
|
||||
unsaved-work prompt are removed, since a switch no longer loses anything
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- A map source's `unavailableReason` may be a function, read when the reason
|
||||
is shown so it follows the locale; the sources from `createDefaultMapSources`
|
||||
(`./maps/defaults`) give functions. `MapSourceController` reads either form
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- The application views translate their text with `t()` as they render,
|
||||
so it follows the active locale; they no longer carry `data-i18n*`
|
||||
attributes, and `npm run i18n` rejects those in a Vue template. The boot
|
||||
pass (`applyDocumentTranslations`) still translates static page markup
|
||||
such as the loading screen and a host's own markup (samehkhamis, #1499).
|
||||
|
||||
- The page's locale lives in a vue-i18n instance installed in every
|
||||
application view, and `t()`, `tn()` and the Intl formatters read it from
|
||||
there, so a view that translates while rendering renders again when the
|
||||
locale changes. `bindLocale` (`src/i18n/index.js`) keeps the locale in a
|
||||
given ref; Core's Vite config builds vue-i18n without its message
|
||||
compiler. Catalogs, messages and call sites are unchanged
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- Cockpit's Display popover shows the Display panel's own HUD, Detection,
|
||||
Parameters and 3D groups through Vue teleports: `CockpitDisplayPortal`
|
||||
publishes the popover's slots (`publish`, with `afterRender` to lay out once
|
||||
they have moved) instead of moving the groups itself (samehkhamis, #1499).
|
||||
|
||||
- The Cockpit utility strip's measured layout renders from the `gev-cockpit`
|
||||
store: the layout pass publishes `utilityLayout` (the HUD's
|
||||
`--cockpit-utility-*` lengths, `primaryOnly` and `hideCollapsed`), and a host
|
||||
constructing `CockpitController` passes `publish.utilityLayout` beside its
|
||||
other publishers (samehkhamis, #1499).
|
||||
|
||||
- The Cockpit Contact and briefing cards' collapse renders from the
|
||||
`gev-cockpit` store: the controller publishes `contextCollapsed` and
|
||||
`signalCollapsed` in its presence, and each card's `data-collapsed` and its
|
||||
toggle's state, words and icon follow it (samehkhamis, #1499).
|
||||
|
||||
- The panels' own state classes (`collapsed`, `dock-pinned`, `dock-pinned-top`,
|
||||
`layout-auto-collapsed`, `accordion-collapsed`) render from the
|
||||
`gev-panel-chrome` store: the shell, the rails and the accordion present each
|
||||
panel's state there instead of writing its classes. `layoutLeftPanelRail` and
|
||||
`layoutRightPanelRail` (`./ui/layout`) now call
|
||||
`onCollapse(panel, {collapsed, auto})` and leave drawing it to the host.
|
||||
Without a shell to own the panels, the voice tools' `set_panel_open` now
|
||||
reports that there is no panel to open instead of writing its classes
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- The shell's panel chrome no longer writes the panels' collapse buttons, the
|
||||
dock tray toggles' names or the dock pins' pressed state: it presents each
|
||||
panel's open and pinned state to the `gev-panel-chrome` store, and the
|
||||
buttons, now views, call it there. `bindPanelDisclosure` (`./ui/panels`) binds
|
||||
only a panel's Escape; it no longer takes `buttons` or `onChange`. `UiButton`
|
||||
joins the component kit (samehkhamis, #1499).
|
||||
|
||||
- `SceneControls` (`./ui/scenes`) no longer takes `elements` or writes the Scene
|
||||
panel's buttons, status, progress or runtime chip: it presents them to the
|
||||
`gev-scenes` store and the panel's buttons call its actions there.
|
||||
`sceneElements`, `presentSceneButtons`, `presentSceneProgress` and
|
||||
`presentSceneRuntime` give way to `sceneButtonsDisabled` and `scenePercent`.
|
||||
It no longer stands down when the page has no Scene panel: ESC still stops a
|
||||
playing scene and the runtime chip still shows (samehkhamis, #1499).
|
||||
|
||||
- `initFirstRunExperience` (via `./application/chrome`) no longer writes the
|
||||
launcher's classes, attributes or text, or removes the card: it presents them
|
||||
to the welcome view's `gev-first-run` store (its new `store` option) and
|
||||
connects the controls the view's buttons call (samehkhamis, #1499).
|
||||
|
||||
- `IntelHUD` (`./ui/hud`) no longer builds or writes `#intel-hud`: it hands its
|
||||
readouts, visibility, layout variant and style colors to its `publish` option,
|
||||
and Core's `intel-hud` view renders them. A page places that view with
|
||||
`<div class="gev-view" data-gev-view="intel-hud">` instead of a bare
|
||||
`#intel-hud` element. `./layers/vessels` shows the selected vessel's HUD
|
||||
readout through `services.readouts.presentSelectedVessel()` instead of writing
|
||||
`#hud-ais-vessel`; the application passes `src/hudReadouts.js`, which IntelHUD
|
||||
follows (samehkhamis, #1499).
|
||||
|
||||
- `LocationControls` (`./ui/location`) no longer builds the city and landmark
|
||||
pills or writes the mini status: it presents the tray, including the search
|
||||
error (`setSearchError()`), and hands it to its `publish` option, and Core's
|
||||
dock renders it and calls `chooseCity()` and `choosePoi()`. Its `elements` are
|
||||
now only `search`: `resetButtons` and `searchToggle` are gone, and the reset
|
||||
buttons and the search toggle call its new `reset()` and `toggleSearch()`. It
|
||||
presents the search field's open and searching state too (`searchExpanded`,
|
||||
`searching`; a `searchExpanded` option starts it open), set through
|
||||
`setSearching()`, `settleSearch()` and `closeSearch()`, and a host renders the
|
||||
field's `expanded` and `searching` classes from it; `LocationSearch` takes a
|
||||
`setSearching` option in place of writing the field's class, and
|
||||
`NavigationController` a `settleSearch` option in place of `searchInput`.
|
||||
`createOrbitIndicator()` is replaced by `showOrbit(active)`, which adds the
|
||||
ORBIT indicator to the presented tray as `orbit: { active }` (null until
|
||||
offered) (samehkhamis, #1499).
|
||||
|
||||
- `createMapSourceControls()` (`./ui/maps`) no longer renders the map source
|
||||
chips into a `container` or writes a `statusElement`: it presents the chip row
|
||||
and status badge and hands them to its `publish` option, and Core's dock
|
||||
renders them and calls `select()`. `renderMapStackChips`, `syncMapStackChips`
|
||||
and `MAP_STACK_CHIP_CLASS` are removed (samehkhamis, #1499).
|
||||
|
||||
- `./layers/launches` no longer writes the Space Missions roster or the selected
|
||||
mission panel into `#space-mission-panel-host`: the layer publishes both
|
||||
through `subscribePanel(listener)` and Core's Space Missions views render
|
||||
them, calling the layer's panel commands (`selectMissionAt`,
|
||||
`selectPreviousMission`, `selectNextMission`, `deselectMission`,
|
||||
`focusSelectedLaunchSite`, `startSelectedReplay`, `toggleReplayPause`,
|
||||
`cancelReplay`, `setReplaySpeed` and the `roster*` preview inputs). The
|
||||
`bindMissionRosterItemKeyboardPreview`, `captureMissionRosterFocus` and
|
||||
`restoreMissionRosterFocus` helpers are gone; the roster keeps focus itself
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- `./layers/awareness` no longer writes the Contacts panel into
|
||||
`#military-awareness-panel`: the layer publishes a plain panel view through
|
||||
`subscribePanel(listener)` and Core's Contacts view renders it, calling the
|
||||
layer's `navigatePrevious`, `focusCurrent`, `navigateNext` and `focusTarget`.
|
||||
The `awarenessPanelControlKey`, `captureAwarenessPanelFocus` and
|
||||
`restoreAwarenessPanelFocus` helpers are gone; the view keeps focus itself
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- `./ui/cctv` and `./ui/radio` import Vue and Pinia: their controllers publish
|
||||
the panel's state to a store that the CCTV and Radio views render, instead of
|
||||
writing the panel's markup themselves (samehkhamis, #1499).
|
||||
|
||||
- `./ui/display`'s `bindDisplayControls` is gone: the Display panel's controls
|
||||
and the dock's style buttons call their owner through the display store's
|
||||
`connect`/`act`, and its allocation and 3D model choices are the kit's
|
||||
`UiSegmented`. `initDrawTool` takes `connect(actions)`, which returns their
|
||||
disconnect, and no longer listens on the Draw controls; its view publishes
|
||||
`color` and `label` too, and the shape choice is a `UiSegmented`.
|
||||
`initGeoFileImport` takes `connect(actions)` the same way and no longer
|
||||
listens on the Import button, picker or list, nor restores focus itself: the
|
||||
`GeoImport` view does both, and its `publish` need not settle once the list
|
||||
renders. Street Level's fit and panorama filters and Recent Imagery's mode are
|
||||
`UiSegmented` too: the Street Level store's owner answers `setPano` and
|
||||
`setRenderMode`, and `RecentImageryReadout` takes `onSetMode`. The Radio
|
||||
panel's and Cockpit's internet radio controls and the tuner slider's events
|
||||
call `RadioControls`, and the local RTL-SDR card's controls
|
||||
`LocalSdrControls`, through the radio store's new `connect`/`act`; the card
|
||||
labels its gain options itself. `act` returns what the answering action
|
||||
returned. The Display, Radio, RTL-SDR, Cockpit, mission replay, style
|
||||
parameter, rail card, observation timeline, imagery opacity, Street Level date
|
||||
and Bhote Koshi sliders are the kit's `UiSlider`, a native range input that
|
||||
emits numbers, and the pressed toggles are the kit's `UiButton`, which gains
|
||||
`pressedClass`; so are the rail card chips, map source chips and location
|
||||
pills (which now report `aria-pressed`), and the voice card's chips are the
|
||||
kit's new `UiChip`. The selects are the kit's new `UiSelect`, the status
|
||||
badges its new `UiBadge` and the one-line empty states its new `UiEmpty`. The
|
||||
top-center clear, share, tilt, north-up and reset buttons, and the cockpit's
|
||||
reset, call the shell through the new globe actions store, which also carries
|
||||
the camera's tilt and compass heading. The recording safe frame renders what
|
||||
`RecordingControls` publishes to the new recording store. `syncScopeControls`
|
||||
(`./ui/display`), `shouldHideCollapsedRightPanels` (`./ui/layout`),
|
||||
`bindClearLayersControl` (`./ui/layers`) and `createStyleParameters`
|
||||
(`./ui/input`; the Display panel renders the style parameters VisualSettings
|
||||
publishes to the display store) are deprecated: the Display view, the rail and
|
||||
the globe actions view now own what they did. `./ui/input` imports Vue
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- `./scenes` presents the scene sharing dialogs and the EDIT DETAILS / SHARE
|
||||
SCENE bar through a store that the scene panels view renders, so a page that
|
||||
omits `layer-panels` shows neither (samehkhamis, #1499).
|
||||
|
||||
- `./voice/control`'s `createVoiceControl({ panel })` returns the voice
|
||||
control's presenter instead of element handles: owners write it by part
|
||||
(`update('detail', { text })`) and listen with `on('button', fn)`, and the
|
||||
command dock renders it from a store. `createVoiceCommands`' `createControl`
|
||||
and `VoiceCardControls` (`{ ui }` instead of `{ root }`) take the same
|
||||
presenter; the `reset` option is gone, since a newer control replaces the
|
||||
previous one (samehkhamis, #1499).
|
||||
|
||||
- Pages place each view with a `<div class="gev-view" data-gev-view="NAME">`
|
||||
host and write their own loading screen; the `build/html` template expansion
|
||||
and its `gev:template` markers are gone. `createApplication` takes `omitViews`
|
||||
(for example `['provider-settings']`) for views an application leaves out
|
||||
(samehkhamis, #1499).
|
||||
|
||||
- The page's panels, dock, cockpit and dialogs are Vue views with the same
|
||||
markup as before. Hosts that do not initialize Core's locale pass
|
||||
`localePicker: false` to `createApplicationControls` to drop the language
|
||||
picker, and Node tests that import application modules register the component
|
||||
loader with `--import=<core>/build/register-vue` (samehkhamis, #1499).
|
||||
|
||||
- The Data, Authoring, Weather, Recent Imagery, Context, CCTV, Street Level and
|
||||
Radio panels are one component, `UiPanel` (`src/ui/kit/`), with the same
|
||||
markup plus the kit's `ui-panel` classes, their titles translated by `t()`
|
||||
rather than `data-i18n`; `RailPanel` binds it to the panel chrome store.
|
||||
`PanelCollapseButton` drops its `initial` and `initialTitle` props and takes
|
||||
`startCollapsed`, so before startup presents a panel its button already shows
|
||||
that state's glyph, words and `aria-expanded` (samehkhamis, #1499).
|
||||
|
||||
- The Authoring, shot list and CCTV buttons and Street Level's nearest button
|
||||
are the kit's `UiButton` (danger buttons in the `alert` tone, primary ones the
|
||||
`primary` variant), keeping their classes; the CCTV on/off, coverage,
|
||||
auto-hop, projection and adjust toggles now say their state with
|
||||
`aria-pressed` (samehkhamis, #1499).
|
||||
|
||||
- Each desktop sidebar has an icon rail that lists every panel on that side and
|
||||
opens one with a click. One panel is open per side in every HUD layout, so the
|
||||
collapsed panel headers no longer take up the sidebars. Phones keep the bottom
|
||||
tab bar. The rail is the first view built with Vue and Pinia, which now ship
|
||||
with the app; see docs/VUE-MIGRATION.md (samehkhamis, #1499).
|
||||
@@ -1,4 +0,0 @@
|
||||
- `build/register-vue` loads components on the test thread and keeps compiled
|
||||
components between test processes in a per-user cache directory, so Node
|
||||
tests that import views run about twice as fast. `GEV_VUE_CACHE=0` compiles
|
||||
every time (samehkhamis, #1500).
|
||||
@@ -1,3 +0,0 @@
|
||||
- The side panel rails show each panel as a labelled pill while no panel on
|
||||
that side is open, and fold to icons once one opens. `UiRail` takes
|
||||
`collapsible` for this (samehkhamis, #1501).
|
||||
@@ -1,5 +0,0 @@
|
||||
- One world clock now drives everything with a time of its own: the sky,
|
||||
the HUD clock, satellites, launches and weather. A timeline along the
|
||||
bottom of the screen, shown while one of those layers is on, plays, pauses
|
||||
and scrubs it, with a tick for each launch or frame and a way back to
|
||||
live. Share links made away from live carry the time (samehkhamis, #1512).
|
||||
@@ -1,7 +0,0 @@
|
||||
- The timeline brings launches up as the time passes them, offers play
|
||||
speeds from real time to a week a second, and plays smoothly, holding only
|
||||
when the next weather frame has yet to load, with a spinner by the time
|
||||
while it waits. The time rides in a bubble over the handle. The track zooms
|
||||
with a scroll or a pinch, drags along, and goes to the time under a tap, on
|
||||
a phone as on a desktop. Zooming out on the globe stops while the globe is
|
||||
still in view instead of leaving it a speck (samehkhamis, #1513).
|
||||
@@ -1,4 +0,0 @@
|
||||
- Contributor docs: CONTRIBUTING lists what each of the three baseline checks
|
||||
needs to run, DATA_SOURCES documents loading a local CCTV source pack, and
|
||||
KNOWN-ISSUES points the floor-placement note at its verification script
|
||||
instead of a missing report (Gaurav Patidar, @gaurav7902, #210).
|
||||
@@ -0,0 +1,5 @@
|
||||
- The client's great-circle distances now come from one shared
|
||||
`src/geoDistance.js` (`greatCircleKm`/`greatCircleMeters`, `{lat, lon}`
|
||||
points). The Earth radius is standardized on 6371.0088 km, so distances shift
|
||||
by about 1.4 ppm and region areas by about 2.8 ppm, and missing or
|
||||
out-of-range points return `Infinity` (onionviolet, #287; Rajojha-1, #749).
|
||||
@@ -0,0 +1,9 @@
|
||||
- Add FL511 (Florida DOT) traffic cameras as an opt-in CCTV source pack:
|
||||
the statewide ArcGIS catalog, read a page at a time under a byte cap with
|
||||
redirects refused, and frames pinned to FL511's two image hosts over HTTPS.
|
||||
FL511's terms limit its content to individual, personal use, and any other
|
||||
use (a public deployment included) needs FDOT's express written consent, so
|
||||
the pack stays off unless `CCTV_FLORIDA_ENABLED=1`. Headings come from the
|
||||
catalog's direction codes, and camera ids come from the image channel, since
|
||||
the catalog's ID column repeats across regions. `CCTV_FLORIDA_MAX_SOURCES`
|
||||
sets the cap (Danielslay86, #605).
|
||||
@@ -0,0 +1,9 @@
|
||||
- Bound what a caller can grow through `/api/terrain/heights`. Every distinct
|
||||
5-decimal point was a permanent in-memory entry, a permanent line in an
|
||||
uncapped `.gev-cache/terrain-heights.json`, and a live upstream call, with
|
||||
nothing limiting the rate. The point cache now holds at most 50,000 entries
|
||||
and evicts oldest-first, a cache file above
|
||||
24 MB is not read back, and the route is limited to 90 requests/minute per
|
||||
client (270 global). Evicting a point costs one refetch, never a wrong
|
||||
answer, since terrain does not move. Ordinary use is unaffected: a full
|
||||
`test:track` run drives 10 requests, peaking at 8/min (daikaginza, #686).
|
||||
Loaded 100 of 1646 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user