mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 14:22:21 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f579b23f52 | ||
|
|
f0d3db3164 | ||
|
|
078a6c0db0 | ||
|
|
7af0453f34 |
No files matched your search
+1
-44
@@ -218,7 +218,6 @@ AISSTREAM_API_KEY=
|
|||||||
# CCTV_TFL_MAX_SOURCES=250 # cap cameras loaded from TfL
|
# CCTV_TFL_MAX_SOURCES=250 # cap cameras loaded from TfL
|
||||||
# CCTV_ONTARIO_ENABLED=1 # set 0 to disable Ontario 511 cameras
|
# 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
|
# ONTARIO_511_API_KEY= # free developer key: sign up at 511on.ca/my511/register, then request it at 511on.ca/developers/doc
|
||||||
# OTTAWA_CAMERA_CERTIFICATE= # City of Ottawa Traffic Camera Images API certificate (free; request it via the form on traffic.ottawa.ca 'About the data'); without it the Ottawa pack is skipped
|
|
||||||
# CCTV_ONTARIO_MAX_SOURCES=1000 # cap cameras loaded from Ontario 511
|
# 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_ENABLED=1 # set 0 to drop the Fintraffic (Finland) pack
|
||||||
# CCTV_FINTRAFFIC_MAX_SOURCES=300 # cap cameras loaded from the Fintraffic catalog
|
# CCTV_FINTRAFFIC_MAX_SOURCES=300 # cap cameras loaded from the Fintraffic catalog
|
||||||
@@ -239,8 +238,6 @@ AISSTREAM_API_KEY=
|
|||||||
# CCTV_CALGARY_ROWS_URL= # override the Open Calgary catalog URL
|
# 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_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_CATALONIA_MAX_SOURCES=150 # cap cameras loaded from Catalonia (~130 exist today)
|
||||||
# CCTV_ICELAND_ENABLED=1 # set 0 to disable the Vegagerðin (Iceland) road cameras
|
|
||||||
# CCTV_ICELAND_MAX_SOURCES=250 # cap cameras loaded from Iceland (~500 views exist today)
|
|
||||||
# CCTV_HK_ENABLED=1 # set 0 to disable the Hong Kong Transport Department cameras
|
# 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_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_HK_CAMERAS_URL= # override the DATA.GOV.HK camera locations XML URL
|
||||||
@@ -271,12 +268,7 @@ AISSTREAM_API_KEY=
|
|||||||
# CCTV_HOUSTON_CAMERAS_URL= # override the Houston TranStar catalog URL
|
# CCTV_HOUSTON_CAMERAS_URL= # override the Houston TranStar catalog URL
|
||||||
# CCTV_A22_ENABLED=1 # set 0 to disable the Autostrada del Brennero (A22, Italy) webcams
|
# 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_A22_URL= # override the Open Data Hub A22 webcam catalog URL
|
||||||
# CCTV_OTTAWA_ENABLED=1 # set 0 to skip City of Ottawa cameras (also needs OTTAWA_CAMERA_CERTIFICATE)
|
# CCTV_MAX_SOURCES=4000 # catalog-wide ceiling, shared round-robin across packs
|
||||||
# CCTV_OTTAWA_MAX_SOURCES=250 # cap Ottawa cameras (8-500, nearest downtown first)
|
|
||||||
# CCTV_QUEBEC_ENABLED=1 # set 0 to skip the Québec camera locations (MTQ, Montréal, scenic links)
|
|
||||||
# CCTV_QUEBEC_VIDEO_ENABLED= # off by default; set 1 to let viewers play Québec 511 clips in their browser after pressing Play (511 terms of use unconfirmed; see DATA_SOURCES.md)
|
|
||||||
# CCTV_WORLD_CAMERAS_ENABLED=1 # set 0 to skip the Oregon and MLIT (Japan) still cameras
|
|
||||||
# CCTV_MAX_SOURCES=7350 # catalog-wide ceiling: 4,000 slots shared round-robin, the rest reserved for Québec and World
|
|
||||||
# CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry
|
# CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry
|
||||||
# CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured
|
# CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured
|
||||||
|
|
||||||
@@ -325,38 +317,3 @@ GEMINI_CHAT_MODEL=gemini-3.5-flash-lite
|
|||||||
# Each reserves up to 40 stations within the existing 750-station directory.
|
# Each reserves up to 40 stations within the existing 750-station directory.
|
||||||
# Restart after changing. Leave blank for the existing global selection.
|
# Restart after changing. Leave blank for the existing global selection.
|
||||||
# RADIO_PRIORITY_COUNTRIES=IN,PL
|
# RADIO_PRIORITY_COUNTRIES=IN,PL
|
||||||
|
|
||||||
# Optional: a news server for the News layer (its /api/news/hotspots, /feed
|
|
||||||
# and /outline routes), such as https://news.example. The local server
|
|
||||||
# forwards only the layer's own GET requests there, with a 15 s timeout. Set,
|
|
||||||
# it wins over the settings below. SERVER-SIDE ONLY.
|
|
||||||
# GEV_NEWS_ORIGIN=
|
|
||||||
#
|
|
||||||
# Without a news server Core reads the News layer itself. SERVER-SIDE ONLY.
|
|
||||||
# Conflicts need no key: the last 24 h of GDELT events (its 15-minute export
|
|
||||||
# files, about 100 KB each, about 10 MB a day; violence only, one report per
|
|
||||||
# story at GDELT's place for it), titled from GDELT's GKG files (about 6 MB
|
|
||||||
# each, about 23 MB an hour: the newest 3 h are read at start, then each new
|
|
||||||
# one). Optional: Liveuamap's mapped reports beside them (paid API,
|
|
||||||
# https://liveuamap.com/promo/api), read once every 5 minutes.
|
|
||||||
# LIVEUAMAP_API_KEY=
|
|
||||||
# Local: the day's news from the outlets of the country in view (free plan
|
|
||||||
# of 50 points a day, https://worldnewsapi.com/console/), read once per
|
|
||||||
# country every 30 minutes, about 1.5 points a read. Without it Local says
|
|
||||||
# "key required". Each country's news is kept in memory for at most 1 hour.
|
|
||||||
# World News API's terms (https://worldnewsapi.com/terms/) allow caching
|
|
||||||
# only with its prior written permission: get that before setting the key.
|
|
||||||
# The app's Data attribution popover credits World News API with a link.
|
|
||||||
# WORLDNEWSAPI_KEY=
|
|
||||||
# The most World News API reads a day (default 30, about 45 points, inside
|
|
||||||
# the free plan). After that Local says the daily limit is reached.
|
|
||||||
# Raise it on a paid plan.
|
|
||||||
# WORLDNEWSAPI_DAILY_READS=30
|
|
||||||
|
|
||||||
# Rail layer: every feed is keyless, so nothing needs setting. Packs are an
|
|
||||||
# opt-in extra for live trains (the operator's lines, stop names, next stops):
|
|
||||||
# scripts/build-rail-paths.mjs (--feed <id> ... or --all) writes path and
|
|
||||||
# schedule packs, and the server reads them from this directory
|
|
||||||
# (default .cache/rail-paths). The build reads it from its own environment,
|
|
||||||
# not from this file: export it, or pass the same folder as --out.
|
|
||||||
# RAIL_PATHS_DIR=
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
name: Announce merged PRs in Discord
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [closed]
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
announce:
|
|
||||||
if: github.repository == 'bilawalsidhu/gods-eye-view' && github.event.pull_request.merged == true
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 2
|
|
||||||
steps:
|
|
||||||
# This uses the trusted base workflow and never checks out or runs PR code.
|
|
||||||
- name: Post to Discord contributions
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_CONTRIBUTIONS_WEBHOOK_URL }}
|
|
||||||
run: |
|
|
||||||
if [[ ! "$DISCORD_WEBHOOK_URL" =~ ^https://discord\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+$ ]]; then
|
|
||||||
echo '::error::Set DISCORD_CONTRIBUTIONS_WEBHOOK_URL to the Discord channel webhook URL.'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
payload=$(jq -ce '
|
|
||||||
.pull_request | {
|
|
||||||
allowed_mentions: {parse: []},
|
|
||||||
embeds: [{
|
|
||||||
title: ("Merged #" + (.number | tostring) + ": " + (.title | .[0:115])),
|
|
||||||
url: .html_url,
|
|
||||||
footer: {text: ("By " + .user.login + " · into " + (.base.ref | .[0:100]))}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
' "$GITHUB_EVENT_PATH")
|
|
||||||
|
|
||||||
curl --silent --show-error --fail --output /dev/null \
|
|
||||||
--connect-timeout 10 --max-time 20 --retry 3 --retry-max-time 50 \
|
|
||||||
--header 'Content-Type: application/json' --data-binary "$payload" \
|
|
||||||
--url "$DISCORD_WEBHOOK_URL?wait=true"
|
|
||||||
@@ -14,8 +14,6 @@ qa-shots/
|
|||||||
output/
|
output/
|
||||||
.gev-logs/
|
.gev-logs/
|
||||||
.gev-cache/
|
.gev-cache/
|
||||||
# Local build output, e.g. rail path packs (scripts/build-rail-paths.mjs).
|
|
||||||
.cache/
|
|
||||||
# Browser QA gate evidence (screenshots, console and network dumps).
|
# Browser QA gate evidence (screenshots, console and network dumps).
|
||||||
qa-artifacts/
|
qa-artifacts/
|
||||||
# Playwright MCP session logs: browser consoles can carry tokens in URLs.
|
# Playwright MCP session logs: browser consoles can carry tokens in URLs.
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- An application's own tool can take the pointer as Draw does, and its click
|
|
||||||
handlers can yield while a tool holds it, as the layers' do (`pointer` from
|
|
||||||
`createApplicationTools`) (samehkhamis, #1641).
|
|
||||||
|
|
||||||
## [0.3.0] — 2026-10-27 — Street Level, On Your Phone, in Your Language
|
## [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
|
- Show a Street Level view facing 359.5° or more as 0°. The viewer caption
|
||||||
|
|||||||
+1
-1
@@ -128,7 +128,7 @@ prints anything; never fix that by renaming or deleting PR A's published token.
|
|||||||
|
|
||||||
The highest-leverage places to jump in:
|
The highest-leverage places to jump in:
|
||||||
|
|
||||||
- **🌆 Add a CCTV source pack.** Austin is the reference camera source. Follow the [CCTV integration workflow](docs/CCTV_INTEGRATION.md) for source review, provider registration, proxy security, tests, and attribution. The proxy only fetches server-registered URLs — never client-supplied ones; see [SECURITY.md](SECURITY.md).
|
- **🌆 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, and see [Adding a new layer](#adding-a-new-layer) for what the PR needs.
|
||||||
- **🎙️ 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).
|
- **🎙️ 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/`.
|
- **🎨 Add a visual style.** Styles are GLSL post-process shaders in `src/styles/`.
|
||||||
|
|||||||
+10
-115
@@ -30,9 +30,8 @@ How to read this:
|
|||||||
| **adsb.lol point API** | Bounded live-flight fallback when OpenSky has no usable snapshot; GNSS Integrity layer (aircraft-reported NIC/NACp and GPS-loss integrity facts, via `/api/gnss-integrity`, 60 s server cache) | ODbL 1.0 | adsb.lol contributors; `api.adsb.lol/v2/lat/{lat}/lon/{lon}/dist/{radius}` |
|
| **adsb.lol point API** | Bounded live-flight fallback when OpenSky has no usable snapshot; GNSS Integrity layer (aircraft-reported NIC/NACp and GPS-loss integrity facts, via `/api/gnss-integrity`, 60 s server cache) | ODbL 1.0 | adsb.lol contributors; `api.adsb.lol/v2/lat/{lat}/lon/{lon}/dist/{radius}` |
|
||||||
| **adsb.lol** | Military flights + aircraft traces | ODbL 1.0 | "adsb.lol" (ODbL) |
|
| **adsb.lol** | Military flights + aircraft traces | ODbL 1.0 | "adsb.lol" (ODbL) |
|
||||||
| **adsbdb** (`api.adsbdb.com`) | Aircraft type, model name and registration by ICAO hex for enriched flights; airline and origin/destination airports for the tracked flight's callsign | No licence or rate limit is published for the service. Its own credits: aircraft data from PlaneBase, ICAO-to-N-number conversion by Guillaume Michel, and flight route data that "is the work of David Taylor, Edinburgh and Jim Mason, Glasgow, and may not be copied, published, or incorporated into other databases without the explicit permission of David J Taylor, Edinburgh" | "adsbdb — api.adsbdb.com" (courtesy); route data credited to David Taylor, Edinburgh, and Jim Mason, Glasgow |
|
| **adsbdb** (`api.adsbdb.com`) | Aircraft type, model name and registration by ICAO hex for enriched flights; airline and origin/destination airports for the tracked flight's callsign | No licence or rate limit is published for the service. Its own credits: aircraft data from PlaneBase, ICAO-to-N-number conversion by Guillaume Michel, and flight route data that "is the work of David Taylor, Edinburgh and Jim Mason, Glasgow, and may not be copied, published, or incorporated into other databases without the explicit permission of David J Taylor, Edinburgh" | "adsbdb — api.adsbdb.com" (courtesy); route data credited to David Taylor, Edinburgh, and Jim Mason, Glasgow |
|
||||||
| **Planespotters.net** (`api.planespotters.net/pub/photos/hex/{hex}`) | One thumbnail photo of the tracked aircraft, by ICAO hex, in the CONTEXT panel's CONTACTS view under the tracked flight's name | [Planespotters photo API terms](https://www.planespotters.net/photo/api); each photo remains the copyright of its photographer. Thumbnails may be hotlinked on the condition that the photographer is named next to the photo and the photo links to its Planespotters page with a followable link | "Photo © {photographer}" beside every photo that names one, linked to its Planespotters page; "Planespotters.net" in Data attribution |
|
|
||||||
| **AISStream.io** | Live vessels (AIS) | Free, beta, no formal ToS; AIS is a public broadcast | "AISStream.io" (courtesy) |
|
| **AISStream.io** | Live vessels (AIS) | Free, beta, no formal ToS; AIS is a public broadcast | "AISStream.io" (courtesy) |
|
||||||
| **CelesTrak** | Satellite TLEs (SGP4) and SATCAT records (object type, operational status, owner) that file each satellite under a class | US-government-origin data, no license; citation requested | "CelesTrak (celestrak.org), Dr. T.S. Kelso" |
|
| **CelesTrak** | Satellite TLEs (SGP4) | US-government-origin data, no license; citation requested | "CelesTrak (celestrak.org), Dr. T.S. Kelso" |
|
||||||
| **The Space Devs — Launch Library 2 v2.3** | Recent launch, payload, stage, and recovery metadata for Space Missions (30d) | [The Space Devs terms of use](https://github.com/TheSpaceDevs/Tutorials/blob/main/faqs/faq_TSD.md#terms-of-use): data may be used and shared in any form; avoid forwarding it without added value; attribution is encouraged (not mandatory). [Official API limits](https://ll.thespacedevs.com/docs/): 15 unauthenticated calls/hour; optional token | "Launch Library 2 — The Space Devs" (courtesy attribution) |
|
| **The Space Devs — Launch Library 2 v2.3** | Recent launch, payload, stage, and recovery metadata for Space Missions (30d) | [The Space Devs terms of use](https://github.com/TheSpaceDevs/Tutorials/blob/main/faqs/faq_TSD.md#terms-of-use): data may be used and shared in any form; avoid forwarding it without added value; attribution is encouraged (not mandatory). [Official API limits](https://ll.thespacedevs.com/docs/): 15 unauthenticated calls/hour; optional token | "Launch Library 2 — The Space Devs" (courtesy attribution) |
|
||||||
| **Esri World Imagery** (ArcGIS Online tile service) | The keyless satellite basemap — the default landing when no Google/ion credential is configured, and the "Esri Satellite" map stack | [Esri Master Agreement](https://www.esri.com/en-us/legal/terms/full-master-agreement): the public World Imagery service is usable in public-facing apps with attribution; no key is required for this classic endpoint, but Esri governs and can change access — an app at scale should review current ArcGIS Location Platform terms | "Powered by Esri — Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community" (provider carries the service's own credit line) |
|
| **Esri World Imagery** (ArcGIS Online tile service) | The keyless satellite basemap — the default landing when no Google/ion credential is configured, and the "Esri Satellite" map stack | [Esri Master Agreement](https://www.esri.com/en-us/legal/terms/full-master-agreement): the public World Imagery service is usable in public-facing apps with attribution; no key is required for this classic endpoint, but Esri governs and can change access — an app at scale should review current ArcGIS Location Platform terms | "Powered by Esri — Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community" (provider carries the service's own credit line) |
|
||||||
| **USGS** | Earthquakes | U.S. public domain | "Data courtesy of the U.S. Geological Survey" |
|
| **USGS** | Earthquakes | U.S. public domain | "Data courtesy of the U.S. Geological Survey" |
|
||||||
@@ -40,12 +39,6 @@ How to read this:
|
|||||||
| **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/) |
|
| **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)" |
|
| **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 |
|
| **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 |
|
||||||
| **News server** (`GEV_NEWS_ORIGIN`, optional; none by default) | News layer, both sections: the last 24 h of reports from many publishers. Set, the local server forwards the layer's `/api/news/hotspots`, `/feed` and `/outline` GET requests to it and Core reads none of the three sources below. Which providers stand behind its feed is the news server's to state | Each report stays its publisher's: headlines and summaries link to the original article, and pictures are hotlinked from the publisher's site (no referrer), never stored | Per report: publisher name and article link on every card; picture cards name the picture's publisher. The Data attribution popover states that places, categories, severity and English headlines are derived automatically and can be wrong |
|
|
||||||
| **GDELT 2.0 events and GKG** (`data.gdeltproject.org/gdeltv2/`; no key; used only without a news server) | News layer, Conflicts: the last 24 h of GDELT's 15-minute event exports (`*.export.CSV.zip`, about 100 KB each), read server-side as they appear. Physical violence only (CAMEO roots 18–20); one report per story, copies of one agency story merged; placed at GDELT's location for it (a town the article's words name beats a bare country). Titled by the article's page title from the GKG export of the same quarter hour (`*.gkg.csv.zip`, about 6 MB each) for the newest 3 h, else by the words of the article's address. GDELT's actor coding is never shown. Categories, relevance and severity by bundled rules | [GDELT Terms of Use](https://www.gdeltproject.org/about.html#termsofuse): unrestricted academic, commercial and governmental use, with citation and a link to the GDELT Project. Coded events, page titles and article links only; linked articles keep their publishers' terms | "GDELT Project" with a link to gdeltproject.org in the Data attribution popover; "via GDELT Project", the publisher's domain and the article link on every card |
|
|
||||||
| **Liveuamap** (`a.liveuamap.com/api`, `LIVEUAMAP_API_KEY`, optional, paid; used only without a news server) | News layer, Conflicts, beside GDELT: Liveuamap's worldwide mapped items of the last 24 h (`a=mpts`), read server-side every 5 minutes; categories and severity are read from the headline by bundled rules | Liveuamap API terms of the operator's own subscription. Headlines, places and links only; item pictures and clips are hotlinked from Liveuamap, never stored | "Liveuamap" with a link in the Data attribution popover once its items show; "Liveuamap" and the item's Liveuamap link on every card |
|
|
||||||
| **World News API** (`api.worldnewsapi.com/search-news`, `WORLDNEWSAPI_KEY`, optional; used only without a news server) | News layer, Local: the last 24 h of news by the source country at the view centre (country from the bundled Natural Earth admin-0 pack; none in a disputed area), read server-side once per country every 30 minutes, about 1.5 points a read, kept in memory for at most 1 hour and then deleted (a read that fails is served from that copy only within the hour); at most `WORLDNEWSAPI_DAILY_READS` reads a day (default 30), after which Local says the daily limit is reached. Each story keeps World News API's own category (politics, business, else other) with no severity of its own; sport, entertainment, lifestyle and travel left out | [World News API terms](https://worldnewsapi.com/terms/) of the operator's own plan. The terms allow caching only with World News API's prior written permission, and then for at most 1 hour: getting that permission before setting the key is the operator's responsibility. Titles and links only; article text and summaries are not kept or shown; pictures are hotlinked from the publisher | The "World News API" link to worldnewsapi.com, always in the Data attribution popover (a credit; the terms require a backlink only of invited free plans); "via World News API", the publisher's domain and the article link on every card |
|
|
||||||
| **EMODnet Human Activities — Ship wrecks** (`ows.emodnet-humanactivities.eu/wfs`, layer `emodnet:heritageshipwrecks`) | Shipwrecks layer: about 7,000 wrecks in European waters (name, sinking year and text, vessel type, least depth and its quality, position accuracy, period, cargo, protected status). Keyless WFS GeoJSON, fetched by `/api/shipwrecks` once a day; rows sourced from wrecksite.eu are skipped, and free text that refers to a person by a title or role is dropped by a keyword filter (a bare name can still pass) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) (EMODnet catalogue record `e965088b-a265-4517-84df-c49b156af8a7`; data originator AND-International, compiled from SHOM, National Monuments Service, Historic England and the Oxford Roman Economy Project); fetched at runtime, not stored in this repository | "Shipwrecks (Europe): EMODnet Human Activities (data originator AND-International, …), CC BY 4.0" — shown in-app |
|
|
||||||
| **Wikidata — shipwrecks** (`query.wikidata.org/sparql`) | Shipwrecks layer: shipwreck items (Q852190) with a coordinate and at least one Wikipedia article, plus sinking events (Q906512, where the death toll lives): name, ship type, sinking date with its precision, deaths and survivors (counts only), description and article link. Keyless, fetched by `/api/shipwrecks` once a day | [CC0](https://creativecommons.org/publicdomain/zero/1.0/); fetched at runtime | "Shipwrecks (worldwide): Wikidata (CC0)" — 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)" |
|
| **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)" |
|
||||||
| **FAA Special Use Airspace** (`services6.arcgis.com` FeatureServer `Special_Use_Airspace`) | Prohibited & Restricted Airspace layer: US prohibited (P) and restricted (R) special-use airspace polygons (not the UAS Facility Map or UAS flight restrictions), altitude limits from the `*_VAL`/`*_UOM`/`*_CODE` fields, times of use, and controlling agency | U.S. public domain (FAA aeronautical data published through the [FAA AIS Open Data portal](https://adds-faa.opendata.arcgis.com/)); fetched keyless through `/api/drone-airspace` (1-hour server cache). Informational only — not a substitute for official UAS flight-planning tools | "Prohibited & restricted airspace: FAA Special Use Airspace" |
|
| **FAA Special Use Airspace** (`services6.arcgis.com` FeatureServer `Special_Use_Airspace`) | Prohibited & Restricted Airspace layer: US prohibited (P) and restricted (R) special-use airspace polygons (not the UAS Facility Map or UAS flight restrictions), altitude limits from the `*_VAL`/`*_UOM`/`*_CODE` fields, times of use, and controlling agency | U.S. public domain (FAA aeronautical data published through the [FAA AIS Open Data portal](https://adds-faa.opendata.arcgis.com/)); fetched keyless through `/api/drone-airspace` (1-hour server cache). Informational only — not a substitute for official UAS flight-planning tools | "Prohibited & restricted airspace: FAA Special Use Airspace" |
|
||||||
| **iNaturalist API** (`api.inaturalist.org/v2/observations`, `/v1/grid/{z}/{x}/{y}.grid.json`) | Wildlife Sightings layer: verifiable wildlife observed within the last 48 h (whales 7 d), by observation time, in the current view, with taxon, observation time, quality grade and eligible photo | Per-record licences; observations default CC BY-NC. Photos CC0/BY/BY-SA/BY-NC/BY-NC-SA shown; ND and all-rights-reserved photos link-only. Keyless via `/api/inaturalist/observations` (5-min cache; global sample 10 min; stale ≤ 1 h; 60 requests/min and 8,000/day upstream caps). **NonCommercial content:** commercial redistributors must filter to CC0/BY/BY-SA or remove the layer. No AI training on this content | "Wildlife sightings: iNaturalist contributors (licences per record; photos credited on each card)" |
|
| **iNaturalist API** (`api.inaturalist.org/v2/observations`, `/v1/grid/{z}/{x}/{y}.grid.json`) | Wildlife Sightings layer: verifiable wildlife observed within the last 48 h (whales 7 d), by observation time, in the current view, with taxon, observation time, quality grade and eligible photo | Per-record licences; observations default CC BY-NC. Photos CC0/BY/BY-SA/BY-NC/BY-NC-SA shown; ND and all-rights-reserved photos link-only. Keyless via `/api/inaturalist/observations` (5-min cache; global sample 10 min; stale ≤ 1 h; 60 requests/min and 8,000/day upstream caps). **NonCommercial content:** commercial redistributors must filter to CC0/BY/BY-SA or remove the layer. No AI training on this content | "Wildlife sightings: iNaturalist contributors (licences per record; photos credited on each card)" |
|
||||||
@@ -72,11 +65,9 @@ How to read this:
|
|||||||
| **DriveBC** (Government of British Columbia) | CCTV camera catalog + frames, British Columbia highways | [Open Government Licence – British Columbia](https://www2.gov.bc.ca/gov/content/data/open-data/open-government-licence-bc) — attribution REQUIRED | "DriveBC. Contains information licensed under the Open Government Licence – British Columbia" |
|
| **DriveBC** (Government of British Columbia) | CCTV camera catalog + frames, British Columbia highways | [Open Government Licence – British Columbia](https://www2.gov.bc.ca/gov/content/data/open-data/open-government-licence-bc) — attribution REQUIRED | "DriveBC. Contains information licensed under the Open Government Licence – British Columbia" |
|
||||||
| **Open Calgary** (`data.calgary.ca`) | CCTV camera catalog + frames, Calgary | [Open Government Licence – City of Calgary](https://data.calgary.ca/stories/s/Open-Calgary-Terms-of-Use/u45n-7awa) — attribution REQUIRED | "Contains information licensed under the Open Government Licence – City of Calgary" |
|
| **Open Calgary** (`data.calgary.ca`) | CCTV camera catalog + frames, Calgary | [Open Government Licence – City of Calgary](https://data.calgary.ca/stories/s/Open-Calgary-Terms-of-Use/u45n-7awa) — attribution REQUIRED | "Contains information licensed under the Open Government Licence – City of Calgary" |
|
||||||
| **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 |
|
| **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 |
|
||||||
| **Icelandic Road and Coastal Administration (Vegagerðin, IRCA)** | CCTV camera catalog + frames, Iceland road cameras | [CC BY 4.0](https://www.vegagerdin.is/vegagerdin/gagnasafn/vefthjonustur/terms-and-conditions) (IRCA terms of use, Act 45/2018) — attribution REQUIRED; no suggestion of IRCA endorsement; IRCA and State logos are excluded; real-time data is provided as is | "Icelandic Road and Coastal Administration (Vegagerðin)", CC BY 4.0 |
|
|
||||||
| **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) |
|
| **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) |
|
| **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" |
|
| **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" |
|
||||||
| **Webcam Romania — Giroc** (`webcamromania.ro`, `w4.webcamromania.ro`) | Optional Giroc camera metadata, JPEG snapshot and live HLS video | [Provider terms](https://webcamromania.ro/termeni-si-conditii/): video belongs to the original camera operator; no open video redistribution licence asserted. The [camera page](https://webcamromania.ro/webcam-orase/webcam-giroc/) offers an embed. Local opt-in via `CCTV_SOURCES_FILE`; media is fetched at runtime, never bundled. | "Webcam Romania · webcamromania.ro"; source link and rights notice in the Data attribution popover |
|
|
||||||
| **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" |
|
| **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) |
|
| **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) |
|
| **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) |
|
||||||
@@ -110,70 +101,6 @@ How to read this:
|
|||||||
| **YRT/Viva** (rtu.york.ca) | Transit layer live vehicles, York Region ON | York Region Transit realtime data, first-party York Region infrastructure under the YRT Open Data Licence v2.0, which permits commercial use and makes attribution optional. Viva route ids are named by colour from York Region’s published GTFS. | "Contains public transit Information made available under YRT’s Open Data Licence" — the licence’s own wording, given though not required |
|
| **YRT/Viva** (rtu.york.ca) | Transit layer live vehicles, York Region ON | York Region Transit realtime data, first-party York Region infrastructure under the YRT Open Data Licence v2.0, which permits commercial use and makes attribution optional. Viva route ids are named by colour from York Region’s published GTFS. | "Contains public transit Information made available under YRT’s Open Data Licence" — the licence’s own wording, given though not required |
|
||||||
| **Burlington Transit** (opendata.burlington.ca) | Transit layer live vehicles, Burlington ON | Burlington Transit realtime data under the City of Burlington Open Data Terms of Use, which permit any lawful purpose. The terms are revocable and must travel with the data, so the credit links them. Route ids are internal keys and are joined to the City’s published GTFS. | Credit to the City of Burlington plus a link to its Open Data Terms of Use, which the licence requires |
|
| **Burlington Transit** (opendata.burlington.ca) | Transit layer live vehicles, Burlington ON | Burlington Transit realtime data under the City of Burlington Open Data Terms of Use, which permit any lawful purpose. The terms are revocable and must travel with the data, so the credit links them. Route ids are internal keys and are joined to the City’s published GTFS. | Credit to the City of Burlington plus a link to its Open Data Terms of Use, which the licence requires |
|
||||||
| **SMTR Rio de Janeiro** (dados.mobilidade.rio) | Transit layer live vehicles, Rio de Janeiro: municipal buses (SPPO) and the Mobi-Rio BRT, as two feeds | CC BY 4.0, stated on both data.rio catalog pages. Keyless GTFS-Realtime published by the Secretaria Municipal de Transportes, labelled beta, with a gateway limit of 5 requests a second and 60 a minute. Line names come from SMTR’s GTFS `routes.txt` (dados.mobilidade.rio/gtfs/schedule), under the same licence. | Attribution to SMTR / Prefeitura da Cidade do Rio de Janeiro under CC BY 4.0 |
|
| **SMTR Rio de Janeiro** (dados.mobilidade.rio) | Transit layer live vehicles, Rio de Janeiro: municipal buses (SPPO) and the Mobi-Rio BRT, as two feeds | CC BY 4.0, stated on both data.rio catalog pages. Keyless GTFS-Realtime published by the Secretaria Municipal de Transportes, labelled beta, with a gateway limit of 5 requests a second and 60 a minute. Line names come from SMTR’s GTFS `routes.txt` (dados.mobilidade.rio/gtfs/schedule), under the same licence. | Attribution to SMTR / Prefeitura da Cidade do Rio de Janeiro under CC BY 4.0 |
|
||||||
| **Metro-North** (api-endpoint.mta.info, rrgtfsfeeds.s3.amazonaws.com) | Rail layer trains, New York, NY: Metro-North Railroad (MTA). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (api-endpoint.mta.info) add delays at its stops. | [MTA Data Feed Terms and Conditions](https://www.mta.info/developers/terms-and-conditions): "Developers must download and store the MTA data feed on a non-MTA server and redistribute the data to users from that server." This proxy is that server: browsers never contact MTA. The feed stamps every vehicle with the header time, so a fix is aged from the snapshot, not from the train. | "Trains (MTA): [MTA Data Feed Terms and Conditions](https://www.mta.info/developers/terms-and-conditions)" |
|
|
||||||
| **LIRR** (api-endpoint.mta.info, rrgtfsfeeds.s3.amazonaws.com) | Rail layer trains, New York, NY: Long Island Rail Road (MTA). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (api-endpoint.mta.info) add delays at its stops. | [MTA Data Feed Terms and Conditions](https://www.mta.info/developers/terms-and-conditions): "Developers must download and store the MTA data feed on a non-MTA server and redistribute the data to users from that server." The feed was seen frozen for two days on 2026-11-06; the layer ages trains by their own report time, so a frozen feed empties instead of showing old trains as live. | "Trains (MTA): [MTA Data Feed Terms and Conditions](https://www.mta.info/developers/terms-and-conditions)" |
|
|
||||||
| **MBTA rail** (cdn.mbta.com) | Rail layer trains, Boston, MA: Massachusetts Bay Transportation Authority. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (cdn.mbta.com) add delays at its stops. | [MassDOT Developers License Agreement](https://cdn.mbta.com/sites/default/files/2023-08/mbta-massdot-develop-license-agreement.pdf): "MassDOT ... hereby grants You (Licensee) non-exclusive, limited, and revocable rights to use, reproduce, and redistribute the Data. ... Clearly acknowledge MassDOT as the provider of the Data." The same feed the Transit layer polls; this layer keeps only Commuter Rail, rapid transit and light rail. | "Trains (MBTA / MassDOT): [MassDOT Developers License Agreement](https://cdn.mbta.com/sites/default/files/2023-08/mbta-massdot-develop-license-agreement.pdf)" |
|
|
||||||
| **SEPTA Regional Rail** (www3.septa.org, github.com) | Rail layer trains, Philadelphia, PA: Southeastern Pennsylvania Transportation Authority. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (www3.septa.org) add delays at its stops. | [SEPTA Trip Planning Data Agreement](https://www3.septa.org/developer/): "non-exclusive, limited and revocable right to use, reproduce and redistribute the Trip Planning Data". The agreement names the static data; SEPTA publishes the realtime feeds on the same developer page with no separate terms. Credit is text only: SEPTA trademarks may not be used. | "Trains (SEPTA): [SEPTA Trip Planning Data Agreement](https://www3.septa.org/developer/)" |
|
|
||||||
| **SEPTA trolleys** (www3.septa.org, github.com) | Rail layer trains, Philadelphia, PA: Southeastern Pennsylvania Transportation Authority. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (www3.septa.org) add delays at its stops. | [SEPTA Trip Planning Data Agreement](https://www3.septa.org/developer/): "non-exclusive, limited and revocable right to use, reproduce and redistribute the Trip Planning Data". The bus feed, filtered to the trolley routes and the Norristown High Speed Line. The Market-Frankford and Broad Street lines are in no SEPTA feed. Routes T1–T5, G1, D1 and D2 (trams) and M1, the Norristown High Speed Line (metro). | "Trains (SEPTA): [SEPTA Trip Planning Data Agreement](https://www3.septa.org/developer/)" |
|
|
||||||
| **MARC** (mdotmta-gtfs-rt.s3.amazonaws.com, feeds.mta.maryland.gov) | Rail layer trains, Baltimore–Washington: Maryland Transit Administration. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (mdotmta-gtfs-rt.s3.amazonaws.com) add delays at its stops. | [MDOT MTA developer feeds (no endorsement implied)](https://www.mta.maryland.gov/developer-resources): "MTA does not guarantee the accuracy of the data nor does it warrant or endorse any application that uses this data." Published for open fetch with this disclaimer and no licence; the site terms cover only the website and apps. Shown with attribution and the no-endorsement wording, and switched off if MDOT MTA publishes restrictive terms. | "Trains (Maryland Transit Administration): [MDOT MTA developer feeds (no endorsement implied)](https://www.mta.maryland.gov/developer-resources)" |
|
|
||||||
| **Tri-Rail** (gtfsr.tri-rail.com, gtfs.tri-rail.com) | Rail layer trains, South Florida: South Florida Regional Transportation Authority. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfsr.tri-rail.com) add delays at its stops. | [No licence stated (open feed pages)](https://gtfsr.tri-rail.com/): "Download Realtime Feeds Below:". Used without a published licence (project owner decision, 2026-11-10). gtfsr.tri-rail.com (the page quoted above, read 2026-11-10) links the GTFS-RT trip updates, alerts and position updates with no terms, and the static GTFS (gtfs.tri-rail.com/gtfs.zip, publisher ETA Transit Systems) states none either; no SFRTA data terms were found. The realtime vehicles carry the train number as their trip id and no route. | "Trains (South Florida Regional Transportation Authority (Tri-Rail)): [No licence stated (open feed pages)](https://gtfsr.tri-rail.com/)" |
|
|
||||||
| **RTD** (open-data.rtd-denver.com, www.rtd-denver.com) | Rail layer trains, Denver, CO: Regional Transportation District. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (open-data.rtd-denver.com) add delays at its stops. | [RTD GTFS-Realtime License Agreement](https://www.rtd-denver.com/business-center/open-spatial-data/gtfs-realtime-license-agreement): "non-exclusive, limited and revocable rights to use, reproduce, and redistribute the Data". RTD marks may not be used with the data, so the credit is text only. The published `www` URL redirects off its own origin, which the proxy refuses, so the final host is the one registered. | "Trains (RTD): [RTD GTFS-Realtime License Agreement](https://www.rtd-denver.com/business-center/open-spatial-data/gtfs-realtime-license-agreement)" |
|
|
||||||
| **CapMetro MetroRail** (data.texas.gov) | Rail layer trains, Austin, TX: Capital Metropolitan Transportation Authority. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. | [CapMetro Open Data Terms](https://www.capmetro.org/metrolabs/): "non-exclusive, limited and revocable rights to use, reproduce, and redistribute CMTA Data". The bus feed the Transit layer polls; MetroRail is route 550. | "Trains (CapMetro): [CapMetro Open Data Terms](https://www.capmetro.org/metrolabs/)" |
|
|
||||||
| **METRO light rail** (svc.metrotransit.org) | Rail layer trains, Minneapolis–St Paul, MN: Metro Transit (Metropolitan Council). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. | [No licence published](https://svc.metrotransit.org/). Publishes no licence or terms: the data feeds page linked above (read 2026-11-10) lists the GTFS and GTFS-realtime files and shows only "© 2020 Metro Transit", so there is nothing to quote. Used without a published licence, on the same basis as the Transit layer's Metro Transit feed, which polls the same file (DATA_SOURCES.md). Blue Line 901 and Green Line 902 only; Northstar ended on 2026-01-04 and route 888 is now a bus. | "Trains (Metro Transit / Metropolitan Council): [No licence published](https://svc.metrotransit.org/)" |
|
|
||||||
| **LYNX** (gtfsrealtime.ridetransit.org) | Rail layer trains, Charlotte, NC: Charlotte Area Transit System. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfsrealtime.ridetransit.org) add delays at its stops. | [No licence published](https://www.charlottenc.gov/cats). Publishes no terms; used without a published licence (project owner decision, 2026-11-10). The feed host (gtfsrealtime.ridetransit.org) serves the files with no page and no terms, so there is nothing to quote (2026-11-10: 126 vehicles, 13 of them trains; 205 trip updates, 11 on the two lines; every realtime trip id is a static trip id). No CATS page with terms for them was found: charlottenc.gov answered 403 to automated requests, and the CATS Terms and Conditions found there by search govern the CATS mobile app. Routes 501 LYNX Blue Line (light rail) and 510 CityLYNX Gold Line (streetcar), both route_type 0: transit, tram. | "Trains (Charlotte Area Transit System): [No licence published](https://www.charlottenc.gov/cats)" |
|
|
||||||
| **GRT ION** (webapps.regionofwaterloo.ca) | Rail layer trains, Waterloo Region, ON: Grand River Transit (Region of Waterloo). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. | [Region of Waterloo Open Data Licence v2.0](https://www.regionofwaterloo.ca/government-and-council/transparency-and-accountability/open-data/): "You are free to: Copy, publish, distribute and transmit the Information ... Use the Information commercially". The registered URL is the `/2` light-rail feed; the suffix-less URL serves a file frozen since February 2026 and is not used. | "Trains (Contains information licensed under the Open Data Licence – Region of Waterloo): [Region of Waterloo Open Data Licence v2.0](https://www.regionofwaterloo.ca/government-and-council/transparency-and-accountability/open-data/)" |
|
|
||||||
| **Queensland Rail Citytrain** (gtfsrt.api.translink.com.au) | Rail layer trains, South East Queensland, Australia: Translink (Queensland Government). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfsrt.api.translink.com.au) add delays at its stops. | [CC BY 4.0](https://translink.com.au/about-translink/open-data): "Our data is licensed under a Creative Commons Attribution 4.0 International License." The terms add: "You must not represent that the State in any way endorses your Application." | "Trains (Translink / Queensland Government): [CC BY 4.0](https://translink.com.au/about-translink/open-data)" |
|
|
||||||
| **G:link** (gtfsrt.api.translink.com.au) | Rail layer trains, Gold Coast, Australia: Translink (Queensland Government). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfsrt.api.translink.com.au) add delays at its stops. | [CC BY 4.0](https://translink.com.au/about-translink/open-data): "Our data is licensed under a Creative Commons Attribution 4.0 International License." | "Trains (Translink / Queensland Government): [CC BY 4.0](https://translink.com.au/about-translink/open-data)" |
|
|
||||||
| **Adelaide Metro** (gtfs.adelaidemetro.com.au) | Rail layer trains, Adelaide, Australia: Adelaide Metro (Rail Commissioner). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfs.adelaidemetro.com.au) add delays at its stops. | [CC BY 4.0](https://data.sa.gov.au/data/dataset/https-gtfs-adelaidemetro-com-au): "Creative Commons Attribution 4.0". Licence as catalogued on data.sa.gov.au; the operator developer page sits behind a bot check. The feed is labelled beta. Its gateway answers base64 text unless the request sends `Accept: application/x-google-protobuf`, so the proxy sends that header for this feed. | "Trains (Adelaide Metro / Government of South Australia): [CC BY 4.0](https://data.sa.gov.au/data/dataset/https-gtfs-adelaidemetro-com-au)" |
|
|
||||||
| **Irish Rail** (api.irishrail.ie, www.transportforireland.ie) | Rail layer trains, Ireland: Iarnród Éireann / Irish Rail. Placed at the station or timing point last reported, not by GPS. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, the delay each train's report states moves its stops. | [No licence published (positions); CC BY 4.0 (NTA timetable)](https://developer.nationaltransport.ie/usagepolicy). The train positions publish no licence or terms: the Irish Rail realtime API page (api.irishrail.ie/realtime/, read 2026-11-10) documents the calls and says only that "Irish Rail provides this information as is" (quoted under `limits`), so there is nothing to quote. They are used without a published licence, credited "Iarnród Éireann / Irish Rail". The NTA's CC BY 4.0 covers only the NTA's GTFS data (the timetable), not these positions. Positions are the station or timing point the signalling system last reported, not GPS. | "Trains (Iarnród Éireann / Irish Rail; timetable: National Transport Authority, provided as is): [No licence published (positions); CC BY 4.0 (NTA timetable)](https://developer.nationaltransport.ie/usagepolicy)" |
|
|
||||||
| **VR (Finland)** (rata.digitraffic.fi) | Rail layer trains, Finland: VR, via Fintraffic Digitraffic. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (rata.digitraffic.fi) add delays at its stops. | [CC BY 4.0](https://www.digitraffic.fi/en/terms-of-service/): "Fintraffic's open data is licensed under the Creative Commons 4.0 By license. It gives the right to distribute, remix, tweak, and build upon our data, even commercially, as long as you credit the source for the original creation." The Digitraffic-User header names the application, never a person. | "Trains (Source: Fintraffic / digitraffic.fi, license CC 4.0 BY): [CC BY 4.0](https://www.digitraffic.fi/en/terms-of-service/)" |
|
|
||||||
| **Renfe long and medium distance** (gtfsrt.renfe.com, ssl.renfe.com) | Rail layer trains, Spain: Renfe (AVE, Avlo, Alvia, Euromed, Intercity, Media Distancia). Live GPS positions. Its vehicles name no route, so it is listed only once its path and schedule packs are built from its static GTFS; trip updates (gtfsrt.renfe.com) then add delays at its stops. | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/): "CC-BY-4.0". Licence field of the data.renfe.com datasets (posicion-vehiculos-av-ld-md and the static GTFS). "Esta información se actualiza cada 15 segundos". Iryo and OUIGO España are not in Renfe’s feeds. | "Trains (Renfe): [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)" |
|
|
||||||
| **Renfe Cercanías** (gtfsrt.renfe.com, ssl.renfe.com) | Rail layer trains, Spain: Renfe Cercanías and Rodalies. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfsrt.renfe.com) add delays at its stops. | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/): "CC-BY-4.0". Licence field of the data.renfe.com Cercanías datasets. Every vehicle in this feed is a Cercanías or Rodalies train. | "Trains (Renfe): [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)" |
|
|
||||||
| **Vy** (api.entur.io, storage.googleapis.com) | Rail layer trains, Norway: Vy (Norway), via Entur. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. | [NLOD 2.0](https://data.norge.no/nlod/en/2.0): "Entur's open data is published under the Norwegian Licence for Open Government Data (NLOD). NLOD permits free reuse for any purpose, including commercial use, subject to attribution requirements". | "Trains (Inneholder data under Norsk lisens for offentlige data (NLOD) tilgjengeliggjort av Entur AS): [NLOD 2.0](https://data.norge.no/nlod/en/2.0)" |
|
|
||||||
| **Go-Ahead** (api.entur.io, storage.googleapis.com) | Rail layer trains, Norway: Go-Ahead Norge, via Entur. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (api.entur.io) add delays at its stops. | [NLOD 2.0](https://data.norge.no/nlod/en/2.0): "Entur's open data is published under the Norwegian Licence for Open Government Data (NLOD). NLOD permits free reuse for any purpose, including commercial use, subject to attribution requirements". | "Trains (Inneholder data under Norsk lisens for offentlige data (NLOD) tilgjengeliggjort av Entur AS): [NLOD 2.0](https://data.norge.no/nlod/en/2.0)" |
|
|
||||||
| **Flytoget** (api.entur.io, storage.googleapis.com) | Rail layer trains, Norway: Flytoget, via Entur. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (api.entur.io) add delays at its stops. | [NLOD 2.0](https://data.norge.no/nlod/en/2.0): "Entur's open data is published under the Norwegian Licence for Open Government Data (NLOD). NLOD permits free reuse for any purpose, including commercial use, subject to attribution requirements". | "Trains (Inneholder data under Norsk lisens for offentlige data (NLOD) tilgjengeliggjort av Entur AS): [NLOD 2.0](https://data.norge.no/nlod/en/2.0)" |
|
|
||||||
| **SJ Nord** (api.entur.io, storage.googleapis.com) | Rail layer trains, Norway: SJ Nord, via Entur. Live GPS positions, read from SIRI VM. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, the delay each train's report states moves its stops. | [NLOD 2.0](https://data.norge.no/nlod/en/2.0): "Entur's open data is published under the Norwegian Licence for Open Government Data (NLOD). NLOD permits free reuse for any purpose, including commercial use, subject to attribution requirements". SJ Nord trains are missing from Entur’s GTFS-RT vehicle positions (0 vehicles on 2026-11-06); they are read from SIRI VM instead (13 trains on 2026-11-09). Its journey refs are DatedServiceJourneys, not the GTFS trip ids, so a train is matched to its run by line and position. | "Trains (Inneholder data under Norsk lisens for offentlige data (NLOD) tilgjengeliggjort av Entur AS): [NLOD 2.0](https://data.norge.no/nlod/en/2.0)" |
|
|
||||||
| **KTMB** (api.data.gov.my) | Rail layer trains, Peninsular Malaysia: Keretapi Tanah Melayu (ETS, Intercity, KTM Komuter, Shuttle Tebrau). Live GPS positions. Its vehicles name no route, so it is listed only once its path and schedule packs are built from its static GTFS. | [CC BY 4.0](https://developer.data.gov.my/realtime-api/gtfs-realtime): "All GTFS Realtime feeds are licensed under CC BY 4.0". developer.data.gov.my, read 2026-11-09; its GTFS Static page says the same of "All GTFS Static feeds". The static GTFS has no shapes. | "Trains (KTMB, via data.gov.my (Government of Malaysia)): [CC BY 4.0](https://developer.data.gov.my/realtime-api/gtfs-realtime)" |
|
|
||||||
| **Seattle Streetcar** (s3.amazonaws.com, metro.kingcounty.gov) | Rail layer trains, Seattle, WA: City of Seattle (South Lake Union and First Hill lines), via King County Metro. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (s3.amazonaws.com) add delays at its stops. | [King County Transit Data Terms of Use](https://kingcounty.gov/en/dept/metro/rider-tools/mobile-and-web-apps): "King County grants you a limited, revocable license to use, reproduce, and redistribute the Data in accordance with these Terms." Transit Data Terms of Use on the page linked above (undated; read 2026-11-10), which links the GTFS and GTFS-RT used here and covers "schedule, geographic, real-time, or any other data posted by King County". King County also permits users "to copy, distribute, store (electronically or otherwise), create derivative works based on and perform and display (publicly or otherwise) such Data, and make such Data available to end users", and asks for the legend the credit carries; no King County service marks beyond it. Commercial use is not mentioned. The feed bundles King County Metro, Sound Transit and City of Seattle routes; only the City of Seattle streetcars (agency 23) are kept, `100340` South Lake Union and `102638` First Hill (transit, tram). Link light rail is not in this GTFS. | "Trains (Transit scheduling, geographic, and real-time data provided by permission of King County): [King County Transit Data Terms of Use](https://kingcounty.gov/en/dept/metro/rider-tools/mobile-and-web-apps)" |
|
|
||||||
| **Connector streetcar** (tmgtfsprd.sorttrpcloud.com, www.go-metro.com) | Rail layer trains, Cincinnati, OH: Southwest Ohio Regional Transit Authority (SORTA/Metro). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (tmgtfsprd.sorttrpcloud.com) add delays at its stops. | [SORTA/Metro developer data Terms](https://www.go-metro.com/about/developer-data/): "The Southwest Ohio Regional Transit Authority (SORTA/Metro) hereby grants you (Licensee) non-exclusive, limited and revocable rights to use, reproduce, and redistribute SORTA/Metro’s transit route schedules, pricing, and other associated data (“Data”) subject to the following Terms:". The developer data page linked above (read 2026-11-10), which links the GTFS and these GTFS-RT files. SORTA/Metro trademarks "may not be used in association with Data unless approved in writing by SORTA/Metro", so the credit is text only; "Any modification, alteration, combination or arrangement of the Data is solely at your own risk." Commercial use is not mentioned. Seen 2026-11-10: 2 streetcars among 147 vehicles, 6 streetcar trips among 265 updates, trip ids those of the static file. The file of 2026-10-01 ends on 2026-12-05. One rail route, `100` Streetcar - OTR - Banks (route_type 0): transit, tram. | "Trains (Southwest Ohio Regional Transit Authority (SORTA/Metro)): [SORTA/Metro developer data Terms](https://www.go-metro.com/about/developer-data/)" |
|
|
||||||
| **Manyōsen** (gtfs-rt-files.buscatch.jp, api.gtfs-data.jp) | Rail layer trains, Toyama, Japan: 万葉線 Manyōsen (Takaoka–Kōshinokata tram). Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfs-rt-files.buscatch.jp) add delays at its stops. | [CC0 1.0](https://api.gtfs-data.jp/v2/organizations/manyosen/feeds/manyosen): ""feed_license": "CC0 1.0"". GTFS Data Repository record of the feed "万葉線", read 2026-11-10: the record lists the GTFS-RT VehiclePositions and TripUpdates (buscatch.jp, keyless) with the CC0 feed. Tram: transit. | "Trains (万葉線株式会社, via the GTFS Data Repository (gtfs-data.jp)): [CC0 1.0](https://api.gtfs-data.jp/v2/organizations/manyosen/feeds/manyosen)" |
|
|
||||||
| **Toyama tram** (gtfs-rt-files.buscatch.jp) | Rail layer trains, Toyama, Japan: 富山地方鉄道 Toyama Chihō Railway city tram (city lines, Toyamakō Line, loop line). Live GPS positions. No static GTFS is registered, so there are no path or schedule packs, and its trip updates are not used. | [CC0 1.0](https://api.gtfs-data.jp/v2/organizations/chitetsu/feeds/chitetsushinaidensha): ""feed_license": "CC0 1.0"". GTFS Data Repository record of the feed "富山地方鉄道市内電車", read 2026-11-10: the record lists the GTFS-RT VehiclePositions and TripUpdates (buscatch.jp, keyless) with the CC0 feed. The tram-only feed (the company's buses are a separate feed). Tram: transit. | "Trains (富山地方鉄道株式会社, via the GTFS Data Repository (gtfs-data.jp)): [CC0 1.0](https://api.gtfs-data.jp/v2/organizations/chitetsu/feeds/chitetsushinaidensha)" |
|
|
||||||
| **Bordeaux tram** (bdx.mecatran.com) | Rail layer trains, Bordeaux, France: TBM (Bordeaux Métropole): trams A to F. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (bdx.mecatran.com) add delays at its stops. | [Licence Ouverte / Open Licence 1.0](https://transport.data.gouv.fr/datasets/offres-de-services-bus-tram-et-scolaire-au-format-gtfs-netex-gtfs-rt-siri-lite): "fr-lo". Licence field of the transport.data.gouv.fr dataset "Réseau urbain et scolaire TBM" (publisher Bordeaux Métropole; read through its API on 2026-11-10), covering its GTFS and its GTFS-RT VehiclePositions and TripUpdates; Licence Ouverte 1.0 (Etalab) allows reuse, commercial included, with attribution. Tram: transit, tram. | "Trains (Bordeaux Métropole (TBM)): [Licence Ouverte / Open Licence 1.0](https://transport.data.gouv.fr/datasets/offres-de-services-bus-tram-et-scolaire-au-format-gtfs-netex-gtfs-rt-siri-lite)" |
|
|
||||||
| **Montpellier tram** (gtfsproxy.e-tam.fr) | Rail layer trains, Montpellier, France: TaM (Montpellier Méditerranée Métropole): trams 1 to 5. Live GPS positions. Optional path and schedule packs built from its static GTFS add its lines, stop names and next stops. With a built schedule pack, trip updates (gtfsproxy.e-tam.fr) add delays at its stops. | [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/): "odc-odbl". Licence field of the transport.data.gouv.fr dataset "Réseau urbain TaM" (publisher Montpellier Méditerranée Métropole; read through its API on 2026-11-10), covering its GTFS and its GTFS-RT VehiclePositions and TripUpdates. ODbL is share-alike, with the access point's special conditions (doc.transport.data.gouv.fr "Licence ODbL", read 2026-11-10): the share-alike clause covers information "de même nature, de même granularité, de mêmes conditions temporelles et de même emprise géographique", and "Les bases de données ainsi repartagées doivent être publiées sur le Point d'accès national transport.data.gouv.fr, dans le format d'origine." The path and schedule packs built from this GTFS are such a database once shared: offered under ODbL with the attribution kept. Tram: transit, tram. | "Trains (TaM / Montpellier Méditerranée Métropole): [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/)" |
|
|
||||||
| **ZDiTM Szczecin** (zditm.szczecin.pl) | Transit layer live vehicles, Szczecin, Poland: buses and trams as one feed | CC0 1.0, stated on the ZDiTM developer page: “The data are provided free of charge under the CC0 1.0 licence, for use in any context, with no prior notification required.” Keyless GTFS-Realtime; the page says one update a minute, measured at about every 20 seconds. The feed carries no route id, so every vehicle takes the default bus mode. | None required (CC0); credited to ZDiTM Szczecin as a courtesy |
|
|
||||||
| **Carris Metropolitana** (api.carrismetropolitana.pt) | Transit layer live vehicles, Lisbon Metropolitan Area, Portugal | Keyless GTFS-Realtime documented in the operator’s public API repository (github.com/carrismetropolitana/api). No licence is stated for the realtime endpoint; the operator’s static GTFS is CC BY 4.0. Included on the same basis as the Metro Transit and OVapi feeds and credited as a courtesy; it can be switched off if Carris objects. Speeds are published in km/h and converted to m/s by the proxy. Route ids are read as the line number per the operator’s `/v2/routes`. | Courtesy credit to Carris Metropolitana |
|
|
||||||
| **Prague PID** (api.golemio.cz) | Transit layer live vehicles, Prague and Central Bohemia, Czechia | CC BY (https://pid.cz/o-systemu/opendata/). Stated: “Data, která lze stáhnout přímo zde z webu, jsou stejně jako celý obsah webu opatřena licencí CC-BY” The PID open-data page lists this realtime feed and licenses the data it publishes under CC BY, crediting the author and any changes. The same page says data served from other sources follows that source’s licence; the feed is served by Golemio, the city’s data platform, which states none of its own. Keyless since the catalogue moved it off the X-Access-Token endpoint. Speeds are m/s, checked against movement on 2026-11-07. Route ids are `L` plus a number whose range gives the mode and the line (metro `L991` is A, train `L1004` is S4, ferry `L1801` is P1), read from PID’s GTFS routes.txt. | Pražská integrovaná doprava (PID) / Golemio |
|
|
||||||
| **Warsaw buses and trams** (mkuran.pl) | Transit layer live vehicles, Warsaw, Poland | Warsaw Open Data Project Usage Terms (https://mkuran.pl/gtfs/). Stated: “The City Hall requires a credit by providing the name Miasto Stołeczne Warszawa, data download date (use the timestamp from the FeedHeader) and link to http://api.um.warszawa.pl.” Positions come from the Warsaw City Hall API, repackaged as GTFS-RT by Mikołaj Kuranowski’s unofficial feed. The credit names the city, links its API and carries the date of the feed timestamp, the three things the terms ask for. Vehicle timestamps ran about two minutes old on 2026-11-07, which the delayed playback absorbs. | Miasto Stołeczne Warszawa (api.um.warszawa.pl) |
|
|
||||||
| **Bordeaux TBM** (bdx.mecatran.com) | Transit layer live vehicles, Bordeaux, France | Licence Ouverte / Etalab 2.0 (https://transport.data.gouv.fr/datasets/offres-de-services-bus-tram-et-scolaire-au-format-gtfs-netex-gtfs-rt-siri-lite). The quote is the licence field of the national access point’s dataset record (fr-lo is the Licence Ouverte), which asks only for credit. The `apiKey` in the URL is not a credential: it is the same fixed string printed in the open-data listing for everyone. Speeds are m/s. The trams (A–F) and BAT³ boats ride numbered route ids (tram A is `59`), mapped from TBM’s static GTFS; other numbered lines are buses. | Bordeaux Métropole (TBM) |
|
|
||||||
| **Sendai City Bus** (api-public.odpt.org) | Transit layer live vehicles, Sendai, Japan | CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Served by the Open Data Challenge for Public Transportation (ODPT) public API, which needs no key for this feed; the catalogue records CC BY 4.0 as its licence. Vehicle timestamps ran about two minutes old on 2026-11-07. Speeds are m/s. | 仙台市交通局 (Sendai City Transportation Bureau) / ODPT |
|
|
||||||
| **Poznań buses and trams** (ztm.poznan.pl) | Transit layer live vehicles, Poznań, Poland | ZTM Poznań open-data terms (https://www.ztm.poznan.pl/otwarte-dane/dla-deweloperow/). Stated: “Treści udostępniane są bezwnioskowo i bezpłatnie, na zasadach powszechnego dostępu i stanowią źródło informacji.” Reuse is free on three conditions: name the source, give the date of the last update, and say the data were processed. The credit names ZTM, says processed, and carries the date of the feed timestamp as the update date. The feed carries no bearing, so heading comes from movement between fixes. | Zarząd Transportu Miejskiego w Poznaniu (processed) |
|
|
||||||
| **Gdańsk buses and trams** (ckan2.multimediagdansk.pl) | Transit layer live vehicles, Gdańsk, Poland | CC BY 4.0 (https://dane.gov.pl/pl/dataset/38599,rozklady-jazdy-i-lokalizacja-przystankow-gtfs-rt). The national catalogue (dane.gov.pl) lists this dataset under CC BY 4.0. No bearing; heading comes from movement. Speeds are m/s. | Zarząd Transportu Miejskiego w Gdańsku |
|
|
||||||
| **Edmonton Transit Service** (gtfs.edmonton.ca) | Transit layer live vehicles, Edmonton, AB | City of Edmonton Open Data Agreement (https://www.edmonton.ca/transportation/Web-version2.1-OpenDataAgreement.pdf). The agreement is the same municipal template as Mississauga and Burlington: a licence to use, modify and distribute for any lawful purpose, in any medium. It was read from search excerpts; the PDF itself would not extract here. Route ids are zero-padded. Speeds are m/s. | Contains information licensed under the Open Data Agreement – City of Edmonton |
|
|
||||||
| **Halifax Transit** (gtfs.halifax.ca) | Transit layer live vehicles, Halifax, NS | Open Government Licence – Halifax v1.0 (https://www.halifax.ca/home/open-data/open-data-license), modelled on OGL – Canada 2.0. Stated: “The information provider grants you a worldwide, royalty-free, perpetual, non-exclusive licence to use the Information, including for commercial purposes, subject to the terms below.” No logos or implied endorsement. Speeds are m/s. | Contains information licenced under the Open Government Licence – Halifax |
|
|
||||||
| **Victoria Regional Transit** (bct.tmix.se) | Transit layer live vehicles, Victoria, BC | BC Transit Open Data Terms of Use (https://www.bctransit.com/open-data/terms-of-use). Stated: “BC Transit grants you a limited, revocable and non-exclusive license to use, reproduce, and redistribute the Data in accordance with these Terms.” The grant is revocable, like the Ontario municipal templates. Route ids carry a `-VIC` system suffix. Speeds are m/s. | BC Transit |
|
|
||||||
| **Montpellier TaM** (gtfsproxy.e-tam.fr) | Transit layer live vehicles, Montpellier, France | ODbL (https://transport.data.gouv.fr/datasets/offre-de-transport-tam-en-temps-reel-gtfs-rt-urbain-et-suburbain). The quote is the dataset record’s licence field. ODbL asks for credit and share-alike on a derived database; the layer redistributes no database, it draws the positions. Speeds are km/h: readings ran 3.3 times the measured movement on 2026-11-07, so the proxy converts them. | Montpellier Méditerranée Métropole (TaM) |
|
|
||||||
| **Kraków buses** (gtfs.ztp.krakow.pl) | Transit layer live vehicles, Kraków, Poland | No licence stated (https://gtfs.ztp.krakow.pl/). ZTP publishes this feed keyless on its own GTFS server and lists it in the Mobility Database, but states no licence there or on dane.gov.pl. Shipped on the same basis as Metro Transit and OVapi; switch it off if ZTP objects. | Zarząd Transportu Publicznego w Krakowie |
|
|
||||||
| **Kraków trams** (gtfs.ztp.krakow.pl) | Transit layer live vehicles, Kraków, Poland | No licence stated (https://gtfs.ztp.krakow.pl/). Same server and basis as the bus feed. | Zarząd Transportu Publicznego w Krakowie |
|
|
||||||
| **Vilnius public transport** (stops.lt) | Transit layer live vehicles, Vilnius, Lithuania | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2383). Published keyless by the city transport agency on stops.lt; no licence found. Shipped on the same basis as Metro Transit and OVapi. Answered 522 once on 2026-11-07 and normally since. | SĮ Susisiekimo paslaugos (stops.lt) |
|
|
||||||
| **Bucharest public transport** (gtfs.tpbi.ro) | Transit layer live vehicles, Bucharest, Romania | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2104). Keyless public endpoint from the metropolitan transport authority; no licence found. Same basis as Metro Transit and OVapi. | TPBI (Transport Public București–Ilfov) |
|
|
||||||
| **Rome public transport** (romamobilita.it) | Transit layer live vehicles, Rome, Italy | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-1776). Keyless file on the agency’s own site; the catalogue’s older `/sites/default/files/` path now 404s. No licence found; same basis as Metro Transit and OVapi. Fewer than one vehicle in five carries a bearing, so most headings come from movement. | Roma Servizi per la Mobilità |
|
|
||||||
| **Turin GTT** (percorsieorari.gtt.to.it) | Transit layer live vehicles, Turin, Italy | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2696). Official keyless endpoint; no licence found. Same basis as Metro Transit and OVapi. Route ids append `U` for urban lines. | Gruppo Torinese Trasporti (GTT) |
|
|
||||||
| **Venice ACTV buses** (tpl.actv.it) | Transit layer live vehicles, Venice, Italy | ACTV open-data catalogue (https://actv.avmspa.it/it/content/catalogo-dei-dati-metadati-e-banche-dati-actv). ACTV lists this feed in its own open-data catalogue; that page would not render for a script, so its licence is not quoted. Same basis as Metro Transit and OVapi. Route ids append a two-letter network code (`_UM`, `_ES`, `_UN`). | ACTV S.p.A. |
|
|
||||||
| **Venice ACTV waterbuses** (tpl.actv.it) | Transit layer live vehicles, Venice, Italy | ACTV open-data catalogue (https://actv.avmspa.it/it/content/catalogo-dei-dati-metadati-e-banche-dati-actv). Same catalogue and basis as the ACTV bus feed. | ACTV S.p.A. |
|
|
||||||
| **Adelaide Metro** (gtfs.adelaidemetro.com.au) | Transit layer live vehicles, Adelaide, SA | No licence stated for the realtime feed (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-1772). Official keyless endpoint; the state catalogue page refused automated reads, so no licence is quoted. Same basis as Metro Transit and OVapi. Speeds are m/s. The server picks its encoding from Accept, hence the override. | Adelaide Metro |
|
|
||||||
| **MARTA buses** (gtfs-rt.itsmarta.com) | Transit layer live vehicles, Atlanta, GA | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-1601). Official keyless endpoint; no licence found. Same basis as Metro Transit and OVapi. Speeds are m/s. | MARTA |
|
|
||||||
| **Denver RTD** (open-data.rtd-denver.com) | Transit layer live vehicles, Denver, CO | RTD GTFS-realtime licence agreement (https://www.rtd-denver.com/business-center/open-spatial-data/gtfs-realtime-license-agreement). Stated: “RTD hereby grants you non-exclusive, limited and revocable rights to use, reproduce, and redistribute the Data”. Read 2026-11-09: a click-through agreement that RTD may change without notice; it permits redistribution, asks for no credit wording, and bars RTD trademarks from use with the data, so the credit stays plain text. The feed itself is keyless. Speeds are m/s. | Regional Transportation District (RTD) |
|
|
||||||
| **Cluj-Napoca CTP** (cluj-rt-feed.gtfs.ro) | Transit layer live vehicles, Cluj-Napoca, Romania | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2614). Keyless; no licence found. Same basis as Metro Transit and OVapi. | Compania de Transport Public Cluj-Napoca |
|
|
||||||
| **Iași CTP** (iasi-rt-feed.gtfs.ro) | Transit layer live vehicles, Iași, Romania | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2118). Keyless; no licence found. Same basis as Metro Transit and OVapi. | Compania de Transport Public Iași |
|
|
||||||
| **Rzeszów city transport** (mpkrzeszow.pl) | Transit layer live vehicles, Rzeszów, Poland | MPK Rzeszów open data (https://www.mpkrzeszow.pl/otwartedane/). Listed on the operator’s open-data page, which names no licence. No bearing; heading comes from movement. | MPK Rzeszów |
|
|
||||||
| **Málaga EMT** (gtfs.emtmalaga.es) | Transit layer live vehicles, Málaga, Spain | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2213). Keyless; no licence found. Same basis as Metro Transit and OVapi. No bearing; heading comes from movement. | EMT Málaga |
|
|
||||||
| **Carris (Lisbon city)** (gateway.carris.pt) | Transit layer live vehicles, Lisbon, Portugal | No licence stated (https://mobilitydatabase.org/feeds/gtfs_rt/mdb-2930). Keyless official gateway; no licence found. Same basis as Metro Transit and OVapi. Carries neither bearing nor speed, and vehicle reports run about a minute old. Route ids append a variant. | Carris |
|
|
||||||
| **BAS.MY Johor Bahru** (api.data.gov.my) | Transit layer live vehicles, Johor Bahru, Malaysia | CC BY 4.0 (https://developer.data.gov.my/realtime-api/gtfs-realtime). Stated: “All GTFS Realtime feeds are licensed under CC BY 4.0.” Served keyless by Malaysia’s national open-data API, which caps GTFS Realtime at 4 requests a minute and refreshes positions every 30 seconds, so the proxy caches this feed for 30 seconds. Without the trailing slash the API redirects. Route ids append an operator code. | BAS.MY / data.gov.my |
|
|
||||||
| **BAS.MY Kota Bharu** (api.data.gov.my) | Transit layer live vehicles, Kota Bharu, Malaysia | CC BY 4.0 (https://developer.data.gov.my/realtime-api/gtfs-realtime). Same API, licence and rate limit as Johor Bahru. | BAS.MY / data.gov.my |
|
|
||||||
| **Rouen Astuce** (api.mrn.cityway.fr) | Transit layer live vehicles, Rouen, France | Licence Ouverte 2.0 (https://transport.data.gouv.fr/datasets/donnees-statiques-et-temps-reel-du-reseau-astuce-metropole-rouen-normandie). The metropolis publishes its realtime data under the Licence Ouverte (dataset record quoted); and this is its own TCAR vehicle feed from that record; the suburban TAE and TNI networks are separate feeds and are not read. Route ids carry the network prefix (`TCAR:`); line 90 is the Métro (a street-running light rail, drawn as a tram) and 99 the Calypso boat. | Métropole Rouen Normandie (Astuce) |
|
|
||||||
| **Toulon Mistral** (feed-rdtpm-toulon.ratpdev.com) | Transit layer live vehicles, Toulon, France | Licence Ouverte 2.0 (https://transport.data.gouv.fr/datasets/reseau-de-transport-urbain-de-la-metropole-toulon-provence-mediterranee). Dataset record’s licence field quoted. Speeds are km/h: readings ran 3.5 times the measured movement on 2026-11-07, so the proxy converts them. Route ids are zero-padded. | Métropole Toulon Provence Méditerranée (Mistral) |
|
|
||||||
| **Le Havre LiA** (opendata.transports-lia.fr) | Transit layer live vehicles, Le Havre, France | ODbL (https://transport.data.gouv.fr/datasets/reseau-de-transport-en-commun-lia-gtfs-siri). The network publishes under ODbL (dataset record quoted); and this is its own GTFS-RT vehicle feed from that record; route ids carry the network prefix `8-`. The layer draws positions and redistributes no database. Lines A and B are the tramway. | Le Havre Seine Métropole (LiA) |
|
|
||||||
| **Tours Fil Bleu** (data.filbleu.fr) | Transit layer live vehicles, Tours, France | Licence Ouverte 2.0 (https://transport.data.gouv.fr/datasets/fil-bleu-syndicat-des-mobilites-gtfs-gtfs-rt). Dataset record’s licence field quoted. | Fil Bleu – Syndicat des Mobilités de Touraine |
|
|
||||||
| **Radio Browser** | Geolocated internet-radio station directory and station-level tags | Public-domain directory data under PDDL 1.0; individual broadcaster stream terms apply | "Radio Browser" plus a link to the selected broadcaster |
|
| **Radio Browser** | Geolocated internet-radio station directory and station-level tags | Public-domain directory data under PDDL 1.0; individual broadcaster stream terms apply | "Radio Browser" plus a link to the selected broadcaster |
|
||||||
| **Re:Earth Terrain** (Mapterhorn) | Terrain (keyless globe stacks — OSM etc. — + `/api/terrain/heights` ellipsoidal-height lookups) | Terrain mesh: CC BY 4.0; geoid: EGM2008 (NGA, public domain) | "Terrain (keyless globe stacks): Re:Earth Terrain / Mapterhorn (CC BY 4.0) / EGM2008 (NGA)" |
|
| **Re:Earth Terrain** (Mapterhorn) | Terrain (keyless globe stacks — OSM etc. — + `/api/terrain/heights` ellipsoidal-height lookups) | Terrain mesh: CC BY 4.0; geoid: EGM2008 (NGA, public domain) | "Terrain (keyless globe stacks): Re:Earth Terrain / Mapterhorn (CC BY 4.0) / EGM2008 (NGA)" |
|
||||||
| **OpenSeaMap** (`tiles.openseamap.org`) | Sea Marks layer: raster overlay of buoys, lighthouses and ports (#10) | Chart tiles CC BY-SA 2.0; data ODbL 1.0 via OpenStreetMap | "Sea marks © OpenSeaMap contributors (CC BY-SA 2.0) · Data © OpenStreetMap contributors (ODbL)" |
|
| **OpenSeaMap** (`tiles.openseamap.org`) | Sea Marks layer: raster overlay of buoys, lighthouses and ports (#10) | Chart tiles CC BY-SA 2.0; data ODbL 1.0 via OpenStreetMap | "Sea marks © OpenSeaMap contributors (CC BY-SA 2.0) · Data © OpenStreetMap contributors (ODbL)" |
|
||||||
@@ -195,8 +122,7 @@ 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.
|
**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.
|
- **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 and Highway Bureau, MOTC (Taiwan).** National freeway cameras use the Freeway Bureau’s keyless [MOTC 2.0 CCTV table](https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml), refreshed daily and fetched at runtime (never bundled). Freeway Bureau data follows the [Open Government Data License, Taiwan v1.0](https://data.gov.tw/license); the app credits the Bureau, names the dataset and links the licence. If that catalog is unreachable and no last-good source list exists, the app falls back to the Highway Bureau’s free [provincial-road CCTV XML feed](https://thbapp.thb.gov.tw/opendata/), with attribution as requested by the Bureau. Its MJPEG streams are accepted only from cctv-ss01 through cctv-ss08.thb.gov.tw over HTTPS; the frame proxy extracts one bounded JPEG per request. This fallback covers provincial roads, not national freeways. Neither feed requires an API key. CCTV_TAIWAN_ENABLED=0 disables both; CCTV_TAIWAN_MAX_SOURCES defaults to 600 (bounded to 1–2000).
|
- **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).
|
||||||
- **Québec CCTV (MTQ / Québec 511, Montréal and scenic links).** `public/quebec-cameras.json` contains 680 MTQ positions from the official georeferenced WFS (CC BY 4.0), 426 Montréal camera locations derived from OSM (ODbL 1.0), and 14 scenic records with per-row provenance and accuracy. The MTQ video is **opt-in** (`CCTV_QUEBEC_VIDEO_ENABLED=1`): the CC BY 4.0 licence covers the camera locations only, and Québec 511's terms for embedding its clips on another site are unconfirmed, so by default MTQ cameras link to the Québec 511 viewer page instead. With the flag set, the 680 MTQ cameras play the operator's five-second MP4 clip directly in the viewer's browser from `www.quebec511.info/Carte/Fenetres/camera.ashx?id=<id>&format=mp4`, only after the viewer presses Play, so Québec 511 sees the viewer's IP address; Québec 511 refreshes those clips every few minutes. A second browser request supplies an approximate map still. The app does not proxy, store, or read video pixels, and no camera images are bundled. The selected-camera credit names both sources: locations MTQ under CC BY 4.0, video Québec 511. Montréal and scenic entries have provider-page links rather than direct frame URLs; their source-page terms apply. The video remains hosted by Québec 511 under its service terms. No API key is required; `CCTV_QUEBEC_ENABLED=0` disables the pack. Confirm Québec 511's terms of use before setting `CCTV_QUEBEC_VIDEO_ENABLED=1` on a public deployment.
|
|
||||||
|
|
||||||
- **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)).
|
- **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)).
|
||||||
- **Sea Marks and Ski Pistes (#10).** Two keyless raster overlay layers under Infrastructure, OpenSeaMap sea marks and OpenSnowMap ski pistes, requested tile by tile as the camera views them, with no prefetch, bulk download or caching on our side (OpenSnowMap forbids bulk tile download without prior agreement). They drape on the globe on the OSM and Bing stacks and on the Google Photorealistic tileset on Google 3D (see `src/maps/imageryHost.js`). OpenRailwayMap and OpenInfraMap, also requested in #10, are not included: OpenRailwayMap's tile server only answers requests whose Referer is its own site, and OpenInfraMap serves vector tiles that need a vector rendering pipeline.
|
- **Sea Marks and Ski Pistes (#10).** Two keyless raster overlay layers under Infrastructure, OpenSeaMap sea marks and OpenSnowMap ski pistes, requested tile by tile as the camera views them, with no prefetch, bulk download or caching on our side (OpenSnowMap forbids bulk tile download without prior agreement). They drape on the globe on the OSM and Bing stacks and on the Google Photorealistic tileset on Google 3D (see `src/maps/imageryHost.js`). OpenRailwayMap and OpenInfraMap, also requested in #10, are not included: OpenRailwayMap's tile server only answers requests whose Referer is its own site, and OpenInfraMap serves vector tiles that need a vector rendering pipeline.
|
||||||
@@ -237,16 +163,13 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
|||||||
contributors, ODbL 1.0; GPSJAM for aggregation only.
|
contributors, ODbL 1.0; GPSJAM for aggregation only.
|
||||||
|
|
||||||
|
|
||||||
- **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 (for the flights layer's enrichment, a lookup that failed temporarily, such as an outage, a timeout, a rate limit or an authorization refusal that may clear, may be asked again after a 60-second cool-down; an answer, found or not, and a permanent refusal such as a 404 are final; Local ADS-B lookups are asked once), 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.
|
- **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.
|
||||||
- **Planespotters photos.** When you track an aircraft, the browser asks `https://api.planespotters.net/pub/photos/hex/{hex}` directly (the API answers cross-origin, so no key and no server proxy are involved) for that aircraft only, once per hex per session, through the same enrichment queue as adsbdb, with an 8-second timeout. A miss is remembered for the session; a request that failed temporarily (an outage, the timeout or a rate limit) may be asked again after a 60-second cool-down while the aircraft stays tracked, as for the flights layer's other enrichment. Either way no photo shows meanwhile. The first photo whose image is an `https://` URL on `*.plnspttrs.net`, and whose page is an `https://www.planespotters.net` URL is shown in the CONTEXT panel's CONTACTS view, under the tracked flight's name (`src/ui/views/panels/AwarenessPanel.vue`), and nowhere else; a vessel, an installation, a military aircraft or a converted TR-3B has none, and a military aircraft or TR-3B is never looked up. Anything else is dropped, so the app never shows or links outside Planespotters (`src/data/planespottersPhoto.js`, also exported as `gods-eye-view/sources/planespotters` for servers that cache the lookup). The photo follows the attribution rules: the thumbnail is hotlinked from the URL the API gives (never copied or re-hosted), the photographer's name, when the answer gives one, is visible text next to it ("Photo © {photographer}"; a photo whose answer names no one shows no credit line), and the photo and credit are a plain link to the photo's Planespotters page with `target="_blank" rel="noopener"` and never `rel="nofollow"`. If the image fails to load, the photo and its credit are hidden.
|
|
||||||
- **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.
|
- **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.
|
- **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 `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.
|
||||||
- **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.
|
- **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.
|
- **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.
|
- **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.
|
||||||
- **City of Ottawa traffic cameras.** Camera images come from the city's [Traffic Camera Images API](https://traffic.ottawa.ca/en/traffic-map-data-lists-and-resources/about-the-data), which requires an access certificate requested through the city's registration form and allows one certificate and user id to fetch a camera at most once every 60 seconds. Set `OTTAWA_CAMERA_CERTIFICATE`; without it the pack is skipped and makes no request. The camera list is the documented keyless `traffic.ottawa.ca/map/service/camera` JSON (camera number, English and French names, coordinates, owner). Frames are `traffic.ottawa.ca/opendata/camera?c=<number>`; the server adds the certificate and the user id `gev` at fetch time, so no catalog response carries the key, and serves each camera from one shared in-memory frame per minute. MTO cameras carried on the city feed keep their owner as a per-camera credit. The feed publishes no bearing, so headings use the shared id-hash fallback at low confidence, and no status field, so a camera the city takes offline still registers. Frames are never stored on disk. The city states no redistribution licence for the images; the certificate request asks how they will be used, so describe the deployment there. `CCTV_OTTAWA_MAX_SOURCES` caps the pack (default 250, nearest downtown) and `CCTV_OTTAWA_ENABLED=0` turns it off.
|
|
||||||
- **World cams image subset.** The normal CCTV catalog adds 1,176 Oregon TripCheck stills and 1,054 MLIT river cameras from the curated `config/cctv_sources.world-image-cameras.json` export; disable it with `CCTV_WORLD_CAMERAS_ENABLED=0`. The export's Vegagerðin rows are left out because the Iceland pack already loads those cameras. The server validates and pins each image URL to its provider path, fetches a frame only when a camera is viewed, and never stores or redistributes it. [ODOT allows businesses, private citizens and organizations to use TripCheck images and asks for credit](https://www.tripcheck.com/Pages/Frequently-Asked-Questions) ("Camera courtesy of ODOT"); TripCheck also carries partner cameras run by Oregon counties and cities, Caltrans and WSDOT, which keep their owner's name in the camera label. MLIT's [site terms](https://www.river.go.jp/kawabou/kwb_apend/html/caution.html) follow the Government of Japan Standard Terms of Use 2.0 (CC BY 4.0 compatible, commercial use allowed, source credit required) and ask tools not to collect data periodically, pointing regular collection to the river information data service; frames are therefore fetched only on view and refresh at most every ten minutes per camera. Both credits are registered in the Data attribution popover.
|
|
||||||
- **TxDOT ITS cameras.** The catalog comes from the keyless `its.txdot.gov/its/DistrictIts/GetCctvStatusListByDistrict` endpoint, one call per district. Frames come from the per-camera `GetCctvSnapshotByIcdId` endpoint, which answers JSON carrying a base64 JPEG rather than an image body; the proxy decodes it only for responses from the official TxDOT origin and validates the JPEG header before serving. Only cameras reporting `Device Online` are registered. Districts default to Austin and San Antonio (`CCTV_TXDOT_DISTRICTS`; 25 codes exist statewide); `CCTV_TXDOT_MAX_SOURCES` caps the pack and `CCTV_TXDOT_ENABLED=0` turns it off. Frames are fetched live at request time and are never stored or redistributed.
|
- **TxDOT ITS cameras.** The catalog comes from the keyless `its.txdot.gov/its/DistrictIts/GetCctvStatusListByDistrict` endpoint, one call per district. Frames come from the per-camera `GetCctvSnapshotByIcdId` endpoint, which answers JSON carrying a base64 JPEG rather than an image body; the proxy decodes it only for responses from the official TxDOT origin and validates the JPEG header before serving. Only cameras reporting `Device Online` are registered. Districts default to Austin and San Antonio (`CCTV_TXDOT_DISTRICTS`; 25 codes exist statewide); `CCTV_TXDOT_MAX_SOURCES` caps the pack and `CCTV_TXDOT_ENABLED=0` turns it off. Frames are fetched live at request time and are never stored or redistributed.
|
||||||
- **Tallinn ristmikud.** Intersection camera stills are fetched live from `ristmikud.tallinn.ee` (`/last/camNNN.jpg`). The curated catalog (`config/cctv_sources.tallinn.json`) supplies coordinates and heading priors; only server-registered ristmikud HTTPS URLs are proxied. Frames are fetched at request time and never stored or redistributed. Disable with `CCTV_TALLINN_ENABLED=0`.
|
- **Tallinn ristmikud.** Intersection camera stills are fetched live from `ristmikud.tallinn.ee` (`/last/camNNN.jpg`). The curated catalog (`config/cctv_sources.tallinn.json`) supplies coordinates and heading priors; only server-registered ristmikud HTTPS URLs are proxied. Frames are fetched at request time and never stored or redistributed. Disable with `CCTV_TALLINN_ENABLED=0`.
|
||||||
- **Tarktee / Transpordiamet.** Estonia road-weather camera locations and current JPEG URLs come from keyless DATEX2 endpoints on `tarktee.transpordiamet.ee` (`/api/v1/datex/roadCameraLocations` + `roadCameraImages`). Frame URLs rotate with each capture; the 15-minute CCTV source cache refreshes them. Only `https://tarktee.transpordiamet.ee/images/…` URLs are registered. Disable with `CCTV_TARKTEE_ENABLED=0`.
|
- **Tarktee / Transpordiamet.** Estonia road-weather camera locations and current JPEG URLs come from keyless DATEX2 endpoints on `tarktee.transpordiamet.ee` (`/api/v1/datex/roadCameraLocations` + `roadCameraImages`). Frame URLs rotate with each capture; the 15-minute CCTV source cache refreshes them. Only `https://tarktee.transpordiamet.ee/images/…` URLs are registered. Disable with `CCTV_TARKTEE_ENABLED=0`.
|
||||||
@@ -255,7 +178,6 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
|||||||
- **Open Calgary traffic cameras.** Keyless. The camera list is the public Socrata dataset `k7p9-kppz` on `data.calgary.ca`; frames are stills on the city's own `trafficcam.calgary.ca` host, and only that host is registered. Most rows publish an `http://` frame URL and the host redirects those to HTTPS, so URLs are upgraded and pinned to that origin before registration. Frames are fetched at request time and never stored or redistributed. The dataset publishes no camera facing: the `quadrant` field and the quadrant suffix on each camera name are Calgary's address grid, not a bearing, so headings use the shared id-hash fallback at low confidence and are corrected with the in-app calibration gizmo. The licence requires the attribution statement above. Disable with `CCTV_CALGARY_ENABLED=0`.
|
- **Open Calgary traffic cameras.** Keyless. The camera list is the public Socrata dataset `k7p9-kppz` on `data.calgary.ca`; frames are stills on the city's own `trafficcam.calgary.ca` host, and only that host is registered. Most rows publish an `http://` frame URL and the host redirects those to HTTPS, so URLs are upgraded and pinned to that origin before registration. Frames are fetched at request time and never stored or redistributed. The dataset publishes no camera facing: the `quadrant` field and the quadrant suffix on each camera name are Calgary's address grid, not a bearing, so headings use the shared id-hash fallback at low confidence and are corrected with the in-app calibration gizmo. The licence requires the attribution statement above. Disable with `CCTV_CALGARY_ENABLED=0`.
|
||||||
- **FL511 Florida cameras.** Off by default; set `CCTV_FLORIDA_ENABLED=1` to load them. The camera list is the keyless FL511 traffic camera layer on ArcGIS (`services.arcgis.com/.../FL511_Traffic_Cameras`), published from a Florida Division of Emergency Management account rather than by FL511 directly, and the service carries no licence text of its own. It is paged up to 2,000 rows at a time, ordered by the layer's object id and following ArcGIS's `exceededTransferLimit`, to cover the ~4,000-camera statewide set. Frames are stills on two Divas Cloud hosts (`images-dis.divas.cloud`, `snapshots.divas.cloud`); both are host-pinned and a row whose image resolves elsewhere is dropped. `CCTV_FLORIDA_MAX_SOURCES` caps the pack (default 500, anchored per region across the state). Each catalog page is read under a 2 MiB cap and redirects are refused. Frames are fetched live at request time and never stored or redistributed. FL511's terms (fl511.com/privacy#disclaimer) license its content for individual, personal use only; any other use, including a public deployment, needs FDOT's express written consent.
|
- **FL511 Florida cameras.** Off by default; set `CCTV_FLORIDA_ENABLED=1` to load them. The camera list is the keyless FL511 traffic camera layer on ArcGIS (`services.arcgis.com/.../FL511_Traffic_Cameras`), published from a Florida Division of Emergency Management account rather than by FL511 directly, and the service carries no licence text of its own. It is paged up to 2,000 rows at a time, ordered by the layer's object id and following ArcGIS's `exceededTransferLimit`, to cover the ~4,000-camera statewide set. Frames are stills on two Divas Cloud hosts (`images-dis.divas.cloud`, `snapshots.divas.cloud`); both are host-pinned and a row whose image resolves elsewhere is dropped. `CCTV_FLORIDA_MAX_SOURCES` caps the pack (default 500, anchored per region across the state). Each catalog page is read under a 2 MiB cap and redirects are refused. Frames are fetched live at request time and never stored or redistributed. FL511's terms (fl511.com/privacy#disclaimer) license its content for individual, personal use only; any other use, including a public deployment, needs FDOT's express written consent.
|
||||||
- **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`.
|
- **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`.
|
||||||
- **Iceland (Vegagerðin / IRCA) cameras.** The camera list is the keyless `https://gagnaveita.vegagerdin.is/api/vefmyndavelar2014_1` JSON feed (about 500 camera views at roughly 170 stations), fetched over https with redirects refused and the body read capped at 2 MiB, since the list decides ids, coordinates and which frame path is proxied. Frames are JPEG stills in `https://www.vegagerdin.is/vgdata/vefmyndavelar/`; only links inside that directory are registered (userinfo, explicit ports and encoded path separators are rejected). Camera ids come from the frame file name. The feed's `Skyring` view description is the camera label, and a stated facing ("til vesturs", "í suður", "séð austur") sets a high-confidence heading; a view that states none, or two different ones ("niður á veg", a bare road direction), takes the id-hash low-confidence pose. The feed has no elevation, so cameras carry a flat prior that the client's ground snap corrects. IRCA publishes road camera images under CC BY 4.0 and requires the licensor to be acknowledged without implying endorsement; the pack shows the attribution above (licensor, dataset, CC BY 4.0, terms link and no-endorsement note) in the Data attribution popover, and every camera record carries the licence string. IRCA describes the images as fetched several times per hour (a live frame's `Last-Modified` was about four minutes old on 2026-11-06) and the real-time data as provided as is, without warranty. Frames are fetched at request time and never stored or redistributed. Disable with `CCTV_ICELAND_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`.
|
- **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.
|
- **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.
|
- **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.
|
||||||
@@ -266,8 +188,7 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
|||||||
- **Houston TranStar cameras.** Keyless. The camera list is the public `traffic.houstontranstar.org/data/layers/cctvSnapshots_json.js` catalog (~1,000 cameras across Greater Houston); frames are stills at `www.houstontranstar.org/snapshots/cctv/<id>.jpg`, synthesized from a validated numeric id rather than taken from the payload, and only that host is registered. Cameras whose catalog id is `0` (a different, non-snapshot class) and cameras outside Greater Houston are dropped. TranStar's `dir` field is the side of the freeway the camera sits on, not the way it faces: TranStar's own map shows it as "Traffic closest to the camera is traveling …" (South → eastbound, North → westbound, West → southbound, East → northbound). That travel direction (90°, 270°, 180°, 0°) seeds the heading as a low-confidence prior with the low-confidence pose; `None` or an unknown value uses the shared id-hash fallback at low confidence. Headings are corrected with the in-app calibration gizmo. Off by default. TranStar's [Content Usage terms](https://traffic.houstontranstar.org/disclaimer.aspx) prohibit copying or distributing the site's information, and external framing or linking to specific images, without TxDOT's prior written permission; its FAQ grants permission-free use only for the RSS feeds. This project does not hold that permission for downstream installations, so the pack loads only when an operator who holds TxDOT's written permission sets `CCTV_HOUSTON_ENABLED=1`; with the flag unset, empty or any other value, the server makes no request to TranStar. Once enabled, it loads the 250 cameras nearest downtown, Uptown, the Medical Center, IAH and Galveston; `CCTV_HOUSTON_MAX_SOURCES` (8–400) changes the cap. Snapshots update about every 3 minutes, which is also the ambient still cadence. Frames are fetched from TranStar and relayed to the browser on demand, buffered only transiently per request, sent with `Cache-Control: no-store`, and not archived.
|
- **Houston TranStar cameras.** Keyless. The camera list is the public `traffic.houstontranstar.org/data/layers/cctvSnapshots_json.js` catalog (~1,000 cameras across Greater Houston); frames are stills at `www.houstontranstar.org/snapshots/cctv/<id>.jpg`, synthesized from a validated numeric id rather than taken from the payload, and only that host is registered. Cameras whose catalog id is `0` (a different, non-snapshot class) and cameras outside Greater Houston are dropped. TranStar's `dir` field is the side of the freeway the camera sits on, not the way it faces: TranStar's own map shows it as "Traffic closest to the camera is traveling …" (South → eastbound, North → westbound, West → southbound, East → northbound). That travel direction (90°, 270°, 180°, 0°) seeds the heading as a low-confidence prior with the low-confidence pose; `None` or an unknown value uses the shared id-hash fallback at low confidence. Headings are corrected with the in-app calibration gizmo. Off by default. TranStar's [Content Usage terms](https://traffic.houstontranstar.org/disclaimer.aspx) prohibit copying or distributing the site's information, and external framing or linking to specific images, without TxDOT's prior written permission; its FAQ grants permission-free use only for the RSS feeds. This project does not hold that permission for downstream installations, so the pack loads only when an operator who holds TxDOT's written permission sets `CCTV_HOUSTON_ENABLED=1`; with the flag unset, empty or any other value, the server makes no request to TranStar. Once enabled, it loads the 250 cameras nearest downtown, Uptown, the Medical Center, IAH and Galveston; `CCTV_HOUSTON_MAX_SOURCES` (8–400) changes the cap. Snapshots update about every 3 minutes, which is also the ambient still cadence. Frames are fetched from TranStar and relayed to the browser on demand, buffered only transiently per request, sent with `Cache-Control: no-store`, and not archived.
|
||||||
- **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`.
|
- **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.
|
- **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 fifty-two registered feeds, covering forty-nine 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, YRT and Rio’s SMTR 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.
|
- **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 eighteen registered feeds, covering seventeen 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, YRT and Rio’s SMTR 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.
|
||||||
- **Rail (GTFS-Realtime trains).** Each feed in `src/data/railFeeds.js` records its licence, its credit and the passage of its terms the decision rests on, and a feed is switched on only when those terms were read and cover this use, or, for those that publish no licence at all, on the basis its entry records. Such a basis is never a licence, and the credit does not present it as one: its label says there is no licence ("No licence published", "No licence stated") or names the publication the use rests on. Five enabled feeds publish no licence for their train positions. MARC is used on what its publication says (open publication with a no-endorsement disclaimer), and METRO light rail on the same basis as the Transit layer's Metro Transit feed. Tri-Rail and Charlotte's LYNX are used without a published licence by project owner decision of 2026-11-10. The fifth is Irish Rail, whose train positions publish no licence while its NTA timetable is CC BY 4.0. The registry lists 30 feeds, all switched on, and every one is keyless: its realtime feed and its static timetable are published for anyone to fetch, so no feed needs a key, an account or a registration, and the server holds no rail credential. No rail request carries a credential of the deployer's; one feed's published URL includes a fixed public value its publisher prints for everyone (Bordeaux's `apiKey`). `/api/rail/feeds` lists the offered feeds, `/api/rail/trains/<feedId>` returns one feed's train snapshot, `/api/rail/updates/<feedId>` its trip updates, and `/api/rail/paths/<feedId>` and `/api/rail/schedule/<feedId>` its path and schedule packs, when built. The proxy requests only registered feeds and reuses the Transit proxy's request policy (a short snapshot cache with single-flight refresh, serve-stale, admission limits, the failure cooldown ladder, redirects followed hop by hop only on the feed's own https origin, and an 8 MiB body cap unless a feed sets its own, at most 64 MiB). Many operators publish trains in the same feed as their buses, so each feed has a classifier: the proxy keeps only vehicles it recognises as trains, tags each with a category (`transit`, `local`, `regional`, `national` or `international`) and a mode (`rail`, `metro` or `tram`), and drops everything else server-side, counted in the snapshot as `dropped`; the browser never receives a bus. Every train shown is live, where its operator last reported it: 29 feeds publish live GPS positions (SJ Nord's through SIRI VM), and Irish Rail reports the station or timing point a train last passed. No train is placed or moved from a timetable; a feed whose live positions fail shows as unavailable, and its trains age out as any quiet train does. 22 feeds register trip updates, polled while one of their trains is selected and that feed's schedule pack is loaded. 28 feeds work out of the box. Packs are an opt-in extra for live trains: nothing is downloaded when the app starts, and `node scripts/build-rail-paths.mjs --feed <id>` (repeatable) or `--all` downloads a feed's static GTFS (29 of the 30 have one) and writes a path pack (the operator's track shapes) and a schedule pack (the stop times for a window of days) to `.cache/rail-paths/` (or to `RAIL_PATHS_DIR` when set, the directory the server then serves). With them, a live train is drawn on its operator's route line and its card names its stops, its next stop and, from the trip updates, their delays. Renfe long and medium distance and KTMB, whose vehicles name no route, are classified from their pack's routes, so `/api/rail/feeds` lists them only once that pack is built. Lines are the operator's own shapes as published: nothing is routed or redrawn from other map data, and no line is drawn where an operator publishes no shape. **Share-alike sources bind the deployer.** A pack built from a ShareAlike or ODbL source is a derived database of it, so a deployer who serves it publicly makes it subject to the same licence and must offer it under that licence with the attribution kept. That covers the Montpellier packs (ODbL). For the ODbL datasets of France's national access point, its special conditions also require that "Les bases de données ainsi repartagées doivent être publiées sur le Point d'accès national transport.data.gouv.fr, dans le format d'origine" ([Licence ODbL](https://doc.transport.data.gouv.fr/le-point-d-acces-national/cadre-juridique/conditions-dutilisation-des-donnees/licence-odbl), read 2026-11-10): a deployer who serves the Montpellier packs publicly takes on republishing that derived data there, as GTFS. The build downloads each static GTFS itself, over https and with no credential. A feed's `staticRouteTypes` lists the route types it keeps when its classifier accepts every route of a mode-specific feed. A frequency-based trip becomes one run per departure on its template's stops and run times. Packs are build output, not part of the repository, and cover a fixed window of days, so they are rebuilt before it ends, anLine truncated
|
|
||||||
- **Radio country coverage (optional).** Set `RADIO_PRIORITY_COUNTRIES=IN,PL` in the local `.env` and restart to include stations beyond the global click charts. The operator may choose up to eight unique ISO alpha-2 country codes; no key is needed. Each country adds one bounded query (at most 200 rows) and reserves up to 40 unique, usable stations inside the existing 750-station catalog. Country seeds precede specialist-tag seeds and global popularity fill; unused slots return to the shared pool. This is a coverage preference, not an exhaustive national directory or a guarantee of stations in every city. Countries with no eligible HTTPS MP3/AAC geolocated stations remain empty. An unset list preserves global selection. Country requests share the directory cache and three-worker pool; failed country queries are reported in coverage without blocking a healthy global refresh. Failed/degraded refreshes back off for one minute, with at most three mirrors per query.
|
- **Radio country coverage (optional).** Set `RADIO_PRIORITY_COUNTRIES=IN,PL` in the local `.env` and restart to include stations beyond the global click charts. The operator may choose up to eight unique ISO alpha-2 country codes; no key is needed. Each country adds one bounded query (at most 200 rows) and reserves up to 40 unique, usable stations inside the existing 750-station catalog. Country seeds precede specialist-tag seeds and global popularity fill; unused slots return to the shared pool. This is a coverage preference, not an exhaustive national directory or a guarantee of stations in every city. Countries with no eligible HTTPS MP3/AAC geolocated stations remain empty. An unset list preserves global selection. Country requests share the directory cache and three-worker pool; failed country queries are reported in coverage without blocking a healthy global refresh. Failed/degraded refreshes back off for one minute, with at most three mirrors per query.
|
||||||
|
|
||||||
- **Radio Browser.** In every installation, including when `RADIO_PRIORITY_COUNTRIES` is unset, each request tries at most three mirror candidates (later fallback candidates are skipped), and failed or partial catalog refreshes back off for 60 seconds. A cold-start failure therefore continues returning 503 during that delay even if upstream recovers. `/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; broad/specialist responses whose rows all fail the product's health policy do not count as successful queries. Optional priority-country outcomes are advisory: valid, in-limit responses with no eligible stations succeed as empty results, while failed or malformed country queries report unsuccessful coverage without degrading the global catalog. A usable partial cold catalog is explicitly `DEGRADED`, and non-array or empty broad/specialist 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.** In every installation, including when `RADIO_PRIORITY_COUNTRIES` is unset, each request tries at most three mirror candidates (later fallback candidates are skipped), and failed or partial catalog refreshes back off for 60 seconds. A cold-start failure therefore continues returning 503 during that delay even if upstream recovers. `/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; broad/specialist responses whose rows all fail the product's health policy do not count as successful queries. Optional priority-country outcomes are advisory: valid, in-limit responses with no eligible stations succeed as empty results, while failed or malformed country queries report unsuccessful coverage without degrading the global catalog. A usable partial cold catalog is explicitly `DEGRADED`, and non-array or empty broad/specialist 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.
|
||||||
@@ -296,20 +217,13 @@ The [Bhote Koshi event pack](public/events/bhote-koshi-2026/README.md), under `p
|
|||||||
|
|
||||||
| Dataset | Folder | License | Commercial use? | Attribution |
|
| Dataset | Folder | License | Commercial use? | Attribution |
|
||||||
| --------------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------- |
|
| --------------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------- |
|
||||||
| **Datacenters** (4,352) | `datacenters/` | **ODbL 1.0** (OpenStreetMap extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) |
|
| **Datacenters** (4,351) | `datacenters/` | **ODbL 1.0** (OpenStreetMap extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) |
|
||||||
| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Open Infrastructure Map |
|
| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Open Infrastructure Map |
|
||||||
| **Military area names** (36,466) | `osm_military_names/` | **ODbL 1.0** (OpenStreetMap, via Overture Maps) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Overture Maps Foundation |
|
| **Military area names** (36,466) | `osm_military_names/` | **ODbL 1.0** (OpenStreetMap, via Overture Maps) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Overture Maps Foundation |
|
||||||
| **Ukraine outline** (Rail layer no-positions rule; 1 ring, 7,801 points) | `src/data/railNoPositions.js` | **ODbL 1.0** (OpenStreetMap, via geoBoundaries gbOpen UKR ADM0, commit 9469f09; simplified to 0.0005°) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + geoBoundaries |
|
|
||||||
| **Québec CCTV source catalog** (1,120 locations) | `public/quebec-cameras.json` | Mixed: MTQ locations CC BY 4.0; Montréal camera nodes ODbL 1.0; scenic records retain per-record provenance. No video files are bundled or cached. | Location data is per-record; video remains hosted by Québec 511 and its platform terms apply; scenic source pages may have separate terms | MTQ / Données Québec CC BY 4.0; © OpenStreetMap contributors (ODbL); scenic operator and position source shown in the selected-camera panel |
|
|
||||||
| **World camera image subset** (2,230 locations) | `config/cctv_sources.world-image-cameras.json` | Mixed: ODOT TripCheck public images (ODOT permits use with credit) and MLIT site content under terms compatible with CC BY 4.0. Frames are not bundled or persisted; the server holds a bounded in-memory cache. | Per-source terms apply; NZTA Journey Planner web-cache rows are excluded; Vegagerðin rows are served by the Iceland pack instead | Oregon Department of Transportation (Camera courtesy of ODOT) and Japan Ministry of Land, Infrastructure, Transport and Tourism (出典:国土交通省 川の防災情報) |
|
|
||||||
| **TeleGeography Submarine Cable Map** (712 cables + 1,917 landing points) | `telegeography_submarine_cables/` | **CC BY-NC-SA 3.0** | ❌ **NonCommercial — remove for commercial use** | "© TeleGeography — submarinecablemap.com" |
|
| **TeleGeography Submarine Cable Map** (712 cables + 1,917 landing points) | `telegeography_submarine_cables/` | **CC BY-NC-SA 3.0** | ❌ **NonCommercial — remove for commercial use** | "© TeleGeography — submarinecablemap.com" |
|
||||||
| **UCS Satellite Database** UCS-Satellite-Database 5-1-2023 (3,564 records; operator, owner country, purpose) | `ucs_satellites/` | **Free, unrestricted use** (UCS database manual, section 3) | ✅ (acknowledgement requested) | "UCS Satellite Database (UCS-Satellite-Database 5-1-2023), https://www.ucsusa.org/satellite_database" |
|
|
||||||
| **Imaging sensor pack** (46 Earth-observation platforms: swath, resolution) | `imaging_sensors/` | Individual specification facts from operator and agency pages, cited per entry | ✅ (facts only) | "Imaging sensor swaths: operator and agency specifications" |
|
|
||||||
| **Natural Earth physical regions** (1,046 land + 292 marine named polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
|
| **Natural Earth physical regions** (1,046 land + 292 marine named polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
|
||||||
| **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) |
|
| **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) |
|
| **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) |
|
||||||
| **Natural Earth disputed areas** (11 of the source's 99 features, hand-picked; [list](src/data/local_data/natural_earth/README.md#disputed-areas-disputed_areasjson)) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
|
|
||||||
| **Natural Earth populated places** (7,342 points: name, position, rank) | `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 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) |
|
| **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 |
|
| **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 |
|
||||||
@@ -358,27 +272,18 @@ credits it anyway as `SPECS: CCTV-DATABASE.COM` beside any spec line it renders.
|
|||||||
|
|
||||||
### Bundled OpenStreetMap data
|
### Bundled OpenStreetMap data
|
||||||
|
|
||||||
Bundled OSM-derived data is distributed under [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/), separately from the MIT code: [datacenters](src/data/local_data/datacenters/), [dams](src/data/local_data/dams/) (via Open Infrastructure Map), [military names](src/data/local_data/osm_military_names/), the Rail layer's Ukraine outline in [`railNoPositions.js`](src/data/railNoPositions.js) (OpenStreetMap via geoBoundaries gbOpen, Runfola et al. 2020, simplified; the module's code stays MIT), and [OSM test fixtures](src/data/fixtures/). Keep © OpenStreetMap contributors and offer modified databases under ODbL.
|
Bundled OSM-derived data is distributed under [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/), separately from the MIT code: [datacenters](src/data/local_data/datacenters/), [dams](src/data/local_data/dams/) (via Open Infrastructure Map), [military names](src/data/local_data/osm_military_names/), and [OSM test fixtures](src/data/fixtures/). Keep © OpenStreetMap contributors and offer modified databases under ODbL.
|
||||||
|
|
||||||
### NASA FIRMS acknowledgement
|
### NASA FIRMS acknowledgement
|
||||||
|
|
||||||
> We acknowledge the use of data and/or imagery from NASA's Fire Information for Resource Management System (FIRMS) (https://earthdata.nasa.gov/firms), part of NASA's Earth Observing System Data and Information System (EOSDIS).
|
> We acknowledge the use of data and/or imagery from NASA's Fire Information for Resource Management System (FIRMS) (https://earthdata.nasa.gov/firms), part of NASA's Earth Observing System Data and Information System (EOSDIS).
|
||||||
|
|
||||||
FIRMS active fires are **fetched live at runtime** (CC0 / U.S. public domain data): the
|
FIRMS active fires are **fetched live at runtime** (CC0 / U.S. public domain data): the
|
||||||
`/api/firms` server-side proxy merges the two VIIRS NRT sources (NOAA-20, NOAA-21)
|
`/api/firms` server-side proxy merges the three VIIRS NRT sources (NOAA-20, NOAA-21,
|
||||||
and MODIS NRT (Terra + Aqua), clamped to the trailing 24 h, cached 30 min to respect the shared MAP_KEY
|
Suomi-NPP) and MODIS NRT (Terra + Aqua), clamped to the trailing 24 h, cached 30 min to respect the shared MAP_KEY
|
||||||
transaction quota. Requires a free `FIRMS_MAP_KEY`
|
transaction quota. Requires a free `FIRMS_MAP_KEY`
|
||||||
(https://firms.modaps.eosdis.nasa.gov/api/map_key/); the layer is empty without it.
|
(https://firms.modaps.eosdis.nasa.gov/api/map_key/); the layer is empty without it.
|
||||||
The former bundled 2026-05-25 snapshot was removed 2026-07-16. Suomi NPP (`VIIRS_SNPP_NRT`)
|
The former bundled 2026-05-25 snapshot was removed 2026-07-16.
|
||||||
is not requested: NASA stopped delivering it on 2026-11-02.
|
|
||||||
|
|
||||||
### UCS Satellite Database (`ucs_satellites/`)
|
|
||||||
|
|
||||||
Operator, owner country and purpose for active satellites, keyed by NORAD number, from the Union of Concerned Scientists Satellite Database release **UCS-Satellite-Database 5-1-2023** (data current through 2023-05-01; https://www.ucsusa.org/satellite_database). The satellites layer joins it to CelesTrak positions and SATCAT records to file each satellite as a station, imaging, telecom, Starlink, probe or debris object, and to name its operator and owner. Section 3 of the UCS database manual: "The Database is free and its use is unrestricted. We request that its use be acknowledged and referenced in written materials. References should include the version of the Database that was used, which is indicated by the name of the Excel file, and a link to or URL for the webpage www.ucsusa.org/satellite_database." The app acknowledges it in the Data attribution popover. UCS has paused updates since this release; Starlink rows are dropped (Starlink is classed by name). Build: `scripts/build-ucs-satellites.py`; curation in the folder README.
|
|
||||||
|
|
||||||
### Imaging sensor pack (`imaging_sensors/`)
|
|
||||||
|
|
||||||
Swath widths and ground resolutions for 46 Earth-observation platforms (Landsat, Sentinel, MODIS, VIIRS, GOES, Himawari, Gaofen, CBERS, RADARSAT, TerraSAR-X, COSMO-SkyMed, ALOS, KOMPSAT, SPOT, Pléiades, WorldView, SkySat, PlanetScope, ICEYE, Capella, Umbra and others), compiled 2026-11-04 from the operator and agency pages cited per entry. The orbital view draws each satellite's sensor swath to scale and caps its zoom at that swath. Facts only; no text or images copied. A swath is confirmed on an operator, agency (ESA's eoPortal included) or academic page; resellers are not cited. The CEOS MIM Database (CC BY 4.0) is not yet used. Sources and unverified values: the folder README.
|
|
||||||
|
|
||||||
### Natural Earth physical regions (`natural_earth/`)
|
### Natural Earth physical regions (`natural_earth/`)
|
||||||
|
|
||||||
@@ -399,16 +304,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
|
Earth". Registration in the in-app `dataCredits.js` attribution list ships with the resolver
|
||||||
wiring (see below).
|
wiring (see below).
|
||||||
|
|
||||||
### Natural Earth populated places (`natural_earth/populated_places.json`)
|
|
||||||
|
|
||||||
Natural Earth 10m populated places (`ne_10m_populated_places_simple`, the same pinned
|
|
||||||
`nvkelso/natural-earth-vector` commit and SHA-256 as the admin-1 build reads), reduced to one
|
|
||||||
row per place: name, latitude and longitude rounded to 3 decimals, `scalerank` and `rank_max`
|
|
||||||
(7,342 places, 229 KB). From 8x the orbital view names the most prominent place within half
|
|
||||||
its width ("Sichuan, China · near Chengdu"), offline, with the states and provinces pack giving
|
|
||||||
the division. Public domain; credited "Made with Natural Earth" with the other packs. Rebuild:
|
|
||||||
`node scripts/build-admin-packs.mjs --only places`; provenance in the folder README.
|
|
||||||
|
|
||||||
### Sky background (`sky/`)
|
### Sky background (`sky/`)
|
||||||
|
|
||||||
Sources, licences, the changes CC BY asks to state, hashes and build commands
|
Sources, licences, the changes CC BY asks to state, hashes and build commands
|
||||||
@@ -579,4 +474,4 @@ NOAA density imagery with the raw GLM product.
|
|||||||
|
|
||||||
## In-app attribution
|
## In-app attribution
|
||||||
|
|
||||||
The required Google Maps / Cesium credit renders on the on-globe credit line (`#cesium-credits`, bottom-left) and must stay visible — including in clean-view and recording modes (the whole line, logo + "Google Maps" + the "Data attribution" link, stays on screen; only the GEV panels/HUD fade). The layer-specific credits (adsb.lol, adsbdb, Planespotters.net, TeleGeography, OSM datacenters/dams/roads, NASA FIRMS, CelesTrak, USGS, City of Austin, Fintraffic, GBFS, Radio Browser, OpenSky, AISStream) are registered into the expandable **"Data attribution"** popover on that credit line via `viewer.creditDisplay.addStaticCredit(new Cesium.Credit(html, /* showOnScreen */ false))` — see `src/data/dataCredits.js`. When you add a new data source, add its license and attribution to this file **and** append an entry to `DATA_CREDITS` in `src/data/dataCredits.js` so it surfaces in the app.
|
The required Google Maps / Cesium credit renders on the on-globe credit line (`#cesium-credits`, bottom-left) and must stay visible — including in clean-view and recording modes (the whole line, logo + "Google Maps" + the "Data attribution" link, stays on screen; only the GEV panels/HUD fade). The layer-specific credits (adsb.lol, adsbdb, TeleGeography, OSM datacenters/dams/roads, NASA FIRMS, CelesTrak, USGS, City of Austin, Fintraffic, GBFS, Radio Browser, OpenSky, AISStream) are registered into the expandable **"Data attribution"** popover on that credit line via `viewer.creditDisplay.addStaticCredit(new Cesium.Credit(html, /* showOnScreen */ false))` — see `src/data/dataCredits.js`. When you add a new data source, add its license and attribution to this file **and** append an entry to `DATA_CREDITS` in `src/data/dataCredits.js` so it surfaces in the app.
|
||||||
@@ -212,7 +212,7 @@ Keep the `env`: bash treats `UID` as read-only, so a plain `UID=1026` in front o
|
|||||||
|
|
||||||
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'`.
|
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 and the News layer refuse 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)). The map routes compare the browser's `Origin` with the `Host` the server sees, so the proxy must pass the original `Host` and, when it terminates TLS, `X-Forwarded-Proto`. Caddy does both by default; for nginx add `proxy_set_header Host $http_host;`, `proxy_set_header X-Forwarded-Proto $scheme;` and `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`. On an install outside Docker, that forwarding header also rate-limits the proxy's users apart from your own browser. Under the default Docker bridge network every client on the host, your browser included, reaches the container from its gateway address and shares one Overpass bucket.
|
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)). The map routes compare the browser's `Origin` with the `Host` the server sees, so the proxy must pass the original `Host` and, when it terminates TLS, `X-Forwarded-Proto`. Caddy does both by default; for nginx add `proxy_set_header Host $http_host;`, `proxy_set_header X-Forwarded-Proto $scheme;` and `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`. On an install outside Docker, that forwarding header also rate-limits the proxy's users apart from your own browser. Under the default Docker bridge network every client on the host, your browser included, reaches the container from its gateway address and shares one Overpass bucket.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -236,8 +236,8 @@ the container.
|
|||||||
|
|
||||||
### Path 4 — GitHub Codespaces (cloud, no local install)
|
### Path 4 — GitHub Codespaces (cloud, no local install)
|
||||||
|
|
||||||
Codespaces requires a GitHub account. Overpass queries, the News layer and OpenAI
|
Codespaces requires a GitHub account. Overpass queries and OpenAI voice token
|
||||||
voice token requests return HTTP 403 through its forwarded HTTPS URL, even with API keys
|
requests return HTTP 403 through its forwarded HTTPS URL, even with API keys
|
||||||
in `.env` and `GEV_ALLOWED_HOSTS` configured. Use a local setup for these features.
|
in `.env` and `GEV_ALLOWED_HOSTS` configured. Use a local setup for these features.
|
||||||
|
|
||||||
The codespace runs the dev server in a cloud workspace, so Node and the dependencies
|
The codespace runs the dev server in a cloud workspace, so Node and the dependencies
|
||||||
@@ -340,7 +340,7 @@ Choose a first-run mission, or try these in order. The GIFs show Google Photorea
|
|||||||
7. **Talk to it** _(needs an OpenAI key)_: _"Take me to LAX and select the nearest airborne aircraft."_
|
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."_
|
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` CCTV · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
|
**Keyboard:** `1`–`6` visual styles · `H` HUD · `D` detection · `C` cockpit · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ Click **GEV MIC**, grant the microphone, and just talk. This is more than a voic
|
|||||||
- **🎬 Cinematic framing.** _"Show me the planes overhead"_ pulls the camera back, angles it, and frames the live traffic like a director.
|
- **🎬 Cinematic framing.** _"Show me the planes overhead"_ pulls the camera back, angles it, and frames the live traffic like a director.
|
||||||
- **🔒 Honest and secure.** The agent only confirms actions that succeeded. Your `OPENAI_API_KEY` never touches the browser; the client only gets a short-lived session token. If you choose **USE CHATGPT OAUTH** in Provider Settings, the local server uses that sign-in only to mint the same kind of short-lived Realtime credential, and refuses the OAuth path for non-loopback clients.
|
- **🔒 Honest and secure.** The agent only confirms actions that succeeded. Your `OPENAI_API_KEY` never touches the browser; the client only gets a short-lived session token. If you choose **USE CHATGPT OAUTH** in Provider Settings, the local server uses that sign-in only to mint the same kind of short-lived Realtime credential, and refuses the OAuth path for non-loopback clients.
|
||||||
|
|
||||||
Thirty-five tools (thirty-four unless an Overpass server is set), four jobs — the commands below come straight from the product's voice test suite and tool playbook:
|
Thirty tools, four jobs — the commands below come straight from the product's voice test suite and tool playbook:
|
||||||
|
|
||||||
**🎥 Direct it** — drone-operator camera verbs:
|
**🎥 Direct it** — drone-operator camera verbs:
|
||||||
|
|
||||||
@@ -426,7 +426,7 @@ _Ask for radio near anywhere and the globe starts broadcasting — every station
|
|||||||
|
|
||||||
## 🛰️ What's on the Globe
|
## 🛰️ What's on the Globe
|
||||||
|
|
||||||
Twenty-three layers and map sources. **Twenty-one have a keyless path.** Some offer additional capabilities with a provider key. (🟢 no key · 🟡 free key · 🔴 metered.)
|
Twenty-two layers and map sources. **Twenty have a keyless path.** Some offer additional capabilities with a provider key. (🟢 no key · 🟡 free key · 🔴 metered.)
|
||||||
|
|
||||||
| Layer | What you get | Source | Auth |
|
| Layer | What you get | Source | Auth |
|
||||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
@@ -436,17 +436,15 @@ Twenty-three layers and map sources. **Twenty-one have a keyless path.** Some of
|
|||||||
| 📡 **GNSS Integrity** | Regional navigation accuracy scores; anomalies do not confirm interference | adsb.lol, GPSJAM aggregation | 🟢 |
|
| 📡 **GNSS Integrity** | Regional navigation accuracy scores; anomalies do not confirm interference | adsb.lol, GPSJAM aggregation | 🟢 |
|
||||||
| 🔥 **Fire Perimeters** | Incident boundaries and reported containment | WFIGS / NIFC | 🟢 |
|
| 🔥 **Fire Perimeters** | Incident boundaries and reported containment | WFIGS / NIFC | 🟢 |
|
||||||
| 🚢 **Live Vessels** | Thousands of ships worldwide | AISStream | 🟡 |
|
| 🚢 **Live Vessels** | Thousands of ships worldwide | AISStream | 🟡 |
|
||||||
| 🛰️ **Satellites** | About 1,300 objects in six classes (Stations, Imaging, Telecom, Starlink, Probes, Debris) with a toggle and a live count each; Starlink loads its whole shell | CelesTrak | 🟢 |
|
| 🛰️ **Satellites** | 838-object catalog, color-coded by class with a live legend — the **DENSE** chip drops in the whole Starlink shell | CelesTrak | 🟢 |
|
||||||
| 🌍 **Earthquakes** | Global seismic activity, last 24h | USGS | 🟢 |
|
| 🌍 **Earthquakes** | Global seismic activity, last 24h | USGS | 🟢 |
|
||||||
| 📰 **News** | The last 24 hours of news in the right-rail **NEWS** panel, narrowed to 12, 6, 3 or 1 hours, with picture cards and clips. **Conflicts**: physical violence worldwide from GDELT's event files, one report per story at GDELT's place for it, plus Liveuamap's mapped items with a key. **Local**: the news of the country at the view centre, its outline and name on the globe; a disputed area of the bundled list, or a territory without a country code, is named as itself. Places and categories are machine-coded and can be wrong | GDELT · Liveuamap · World News API, or your own news server | 🟢 Conflicts · 🟡 Local (World News API) · 🔴 Liveuamap items |
|
|
||||||
| ◍ **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 | 🟢 |
|
| ◍ **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 |
|
| 🚗 **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** | up to 7,350 camera locations (4,000 shared round-robin slots, plus room for 1,120 Québec and 2,230 World image cameras) projected _into_ the 3D space — Austin · Texas (TxDOT) · California (Caltrans) · London (TfL) · Ontario (511, free developer key) · Ottawa (City of Ottawa, free access certificate) · 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) · Iceland (Vegagerðin) · Hong Kong (Transport Department) · Taiwan (Freeway Bureau, MOTC) · Québec (MTQ locations with Québec 511 links, Montréal locations, regional webcams) · Oregon (TripCheck) · Japan (MLIT river cameras) · 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), opt-in Québec 511 in-browser video (embedding terms unconfirmed), opt-in Florida (FL511, individual personal use only; other use needs FDOT's written consent) and opt-in Houston (TranStar, enable only with TxDOT's written permission per TranStar's Content Usage terms). Positions are published or attributed; 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, 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), opt-in Florida (FL511, individual personal use only; other use needs FDOT's written consent) and opt-in Houston (TranStar, enable only with TxDOT's written permission per TranStar's Content Usage terms). 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) | 🟢 |
|
| 📷 **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 |
|
| 📷 **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 | 🟢 |
|
| 📻 **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 | 🟢 |
|
||||||
| 🚌 **Transit** | Live buses, trams, metros, trains and ferries with delayed playback between reports, selected-vehicle trails, and mode-coloured DETECT labels — Boston, Austin, Minneapolis, Helsinki, the Netherlands, Norway, South East Queensland, Rio de Janeiro (buses and BRT), Szczecin, Lisbon (Carris Metropolitana and the city’s Carris), Prague, Warsaw, Kraków, Poznań, Gdańsk, Rzeszów, Vilnius, Bucharest, Cluj-Napoca, Iași, Rome, Turin, Venice (buses and waterbuses), Bordeaux, Montpellier, Rouen, Toulon, Le Havre, Tours, Málaga, Sendai, Johor Bahru, Kota Bharu, Adelaide, Atlanta, Denver, Edmonton, Halifax, Victoria, and nine in Ontario: Toronto (surface vehicles; the subway publishes no positions), Mississauga, Hamilton, Durham Region, York Region, Guelph, Burlington, Barrie and Kingston | Operator GTFS-Realtime feeds | 🟢 |
|
| 🚌 **Transit** | Live buses, trams, metros, trains and ferries with delayed playback between reports, selected-vehicle trails, and mode-coloured DETECT labels — Boston, Austin, Minneapolis, Helsinki, the Netherlands, Norway, South East Queensland, Rio de Janeiro (buses and BRT), and nine in Ontario: Toronto (surface vehicles; the subway publishes no positions), Mississauga, Hamilton, Durham Region, York Region, Guelph, Burlington, Barrie and Kingston | Operator GTFS-Realtime feeds | 🟢 |
|
||||||
| 🚆 **Rail** | Live trains across the US, Canada, Australia, Ireland, Finland, Spain, Norway, Malaysia, Japan and France, from 30 keyless operator feeds: live GPS positions from 29 of them and Irish Rail’s station reports, 28 live out of the box, and Renfe long and medium distance and KTMB once their pack is built. No train is placed from a timetable. Opt-in packs (`node scripts/build-rail-paths.mjs --feed <id>` or `--all`) add the operator’s own route lines, stop names, next stops and trip-update delays. Coloured by service category (urban, local, regional, national, international) with a filter chip and legend for each; trains are drawn once when several feeds carry them, and open a card per train. Buses in mixed feeds are dropped by the server | Operator GTFS-Realtime feeds (static GTFS for opt-in packs) | 🟢 |
|
|
||||||
| 🚲 **Bikeshare** | Live station availability | GBFS | 🟢 |
|
| 🚲 **Bikeshare** | Live station availability | GBFS | 🟢 |
|
||||||
| 🧭 **Directions** | Click A and B on the globe for a street-following drive, walk or cycle route draped on the terrain with turn-by-turn steps — then FLY the camera along it. No key, no geocoder, no mic | OSRM on FOSSGIS servers (OpenStreetMap) | 🟢 |
|
| 🧭 **Directions** | Click A and B on the globe for a street-following drive, walk or cycle route draped on the terrain with turn-by-turn steps — then FLY the camera along it. No key, no geocoder, no mic | OSRM on FOSSGIS servers (OpenStreetMap) | 🟢 |
|
||||||
| ⌖ **OSM Places** | Voice place search ("hospitals in Kathmandu") from OpenStreetMap, drawn as numbered points that follow-up questions can filter. Needs your own Overpass server (`OVERPASS_UPSTREAMS`); off otherwise |
|
| ⌖ **OSM Places** | Voice place search ("hospitals in Kathmandu") from OpenStreetMap, drawn as numbered points that follow-up questions can filter. Needs your own Overpass server (`OVERPASS_UPSTREAMS`); off otherwise |
|
||||||
@@ -475,7 +473,7 @@ In the app, **Data Layers** groups them as Movement, Cameras, Infrastructure, Ev
|
|||||||
|
|
||||||
_The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED ESTIMATE`, scrubbable 0.25×–4×._
|
_The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED ESTIMATE`, scrubbable 0.25×–4×._
|
||||||
|
|
||||||
**Also on the globe:** neighborhood overlays · an optional cockpit WX cloud effect. **Bundled static infrastructure:** Datacenters (4,352), Dams (704), and Submarine Cables (712).
|
**Also on the globe:** neighborhood overlays · an optional cockpit WX cloud effect. **Bundled static infrastructure:** Datacenters (4,351), Dams (704), and Submarine Cables (712).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -487,9 +485,9 @@ _The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED
|
|||||||
|
|
||||||
> Street-level photos from any provider in one panel. This build ships **Mapillary** (a free client token), **Google Street View** (your Google Maps key), **KartaView** and **Panoramax** (no key).
|
> Street-level photos from any provider in one panel. This build ships **Mapillary** (a free client token), **Google Street View** (your Google Maps key), **KartaView** and **Panoramax** (no key).
|
||||||
|
|
||||||
**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 Street Level starts on **KartaView** instead, and the Mapillary chip says which key to add. **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.
|
**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 when its token is set; otherwise KartaView is. 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 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.
|
||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
@@ -569,14 +567,12 @@ and configuration details.
|
|||||||
|
|
||||||
No key needed for Wind, Rain radar, Satellite clouds, Lightning density,
|
No key needed for Wind, Rain radar, Satellite clouds, Lightning density,
|
||||||
Cyclone advisories, Mapped ALPR Cameras, Transit, Directions, Delaware live
|
Cyclone advisories, Mapped ALPR Cameras, Transit, Directions, Delaware live
|
||||||
video, the News layer's Conflicts section, and coordinate or bundled-place
|
video, and coordinate or bundled-place search. A layer row waiting on a key
|
||||||
search. A layer row waiting on a key
|
|
||||||
names it.
|
names it.
|
||||||
|
|
||||||
### Choose the capabilities you want
|
### Choose the capabilities you want
|
||||||
|
|
||||||
Nine keys. Six have a free tier. Of the three 🔴 ones, Google Maps and OpenAI are
|
Seven keys. Five have a free tier, and the two 🔴 ones are metered:
|
||||||
metered and Liveuamap is a paid subscription:
|
|
||||||
|
|
||||||
| | Key | Why | Get it |
|
| | Key | Why | Get it |
|
||||||
| --- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
@@ -587,8 +583,6 @@ metered and Liveuamap is a paid subscription:
|
|||||||
| 🟡 | **NASA FIRMS** | 🔥 Live active fires | [firms.modaps.eosdis.nasa.gov](https://firms.modaps.eosdis.nasa.gov/api/map_key/) — free |
|
| 🟡 | **NASA FIRMS** | 🔥 Live active fires | [firms.modaps.eosdis.nasa.gov](https://firms.modaps.eosdis.nasa.gov/api/map_key/) — free |
|
||||||
| 🟡 | **TomTom** | 🚦 Live flow speeds and congestion colors for the simulated traffic layer | [my.tomtom.com](https://my.tomtom.com/keys) — free tier available |
|
| 🟡 | **TomTom** | 🚦 Live flow speeds and congestion colors for the simulated traffic layer | [my.tomtom.com](https://my.tomtom.com/keys) — free tier available |
|
||||||
| 🟡 | **Mapillary** | 📷 Street Level coverage and photos. A public client token by design (see SECURITY.md) | [mapillary.com/dashboard/developers](https://www.mapillary.com/dashboard/developers) — register an app, copy its Client Token |
|
| 🟡 | **Mapillary** | 📷 Street Level coverage and photos. A public client token by design (see SECURITY.md) | [mapillary.com/dashboard/developers](https://www.mapillary.com/dashboard/developers) — register an app, copy its Client Token |
|
||||||
| 🟡 | **World News API** | 📰 News layer, Local: the news of the country in view. The free plan allows 50 points a day; `WORLDNEWSAPI_DAILY_READS` (default 30) keeps Core inside it. Core keeps each country's news for up to 1 hour, and World News API's [terms](https://worldnewsapi.com/terms/) allow caching only with its prior written permission, which is yours to get. The Data attribution popover credits it with a link | [worldnewsapi.com/console](https://worldnewsapi.com/console/) — free plan |
|
|
||||||
| 🔴 | **Liveuamap** | 📰 News layer, Conflicts: Liveuamap's mapped items beside GDELT's reports (🟢 Conflicts works without) | [liveuamap.com/promo/api](https://liveuamap.com/promo/api) — paid API |
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -645,7 +639,7 @@ and panel. DelDOT uses its official HTTPS HLS catalog links. Disable that
|
|||||||
pack with `CCTV_DELDOT_ENABLED=0`.
|
pack with `CCTV_DELDOT_ENABLED=0`.
|
||||||
|
|
||||||
The server allows two concurrent sessions. Each retains at most 12 segments
|
The server allows two concurrent sessions. Each retains at most 12 segments
|
||||||
and 24 MiB in memory; individual downloads are capped at 8 MiB with a ten
|
and 24 MiB in memory; individual downloads are capped at 4 MiB with a ten
|
||||||
second deadline. There are no segment files or ffmpeg processes. Redirects,
|
second deadline. There are no segment files or ffmpeg processes. Redirects,
|
||||||
off-origin references, encrypted playlists and non-MPEG-TS segments are refused.
|
off-origin references, encrypted playlists and non-MPEG-TS segments are refused.
|
||||||
Each decoder has its own client lease (at most eight per session), including
|
Each decoder has its own client lease (at most eight per session), including
|
||||||
@@ -662,8 +656,8 @@ Honest numbers, roughly, as of mid-2026 — always check the provider pricing pa
|
|||||||
|
|
||||||
| | Cost reality |
|
| | Cost reality |
|
||||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **🟢 Most layers** | **$0, no signup.** OpenSky anon, USGS, CelesTrak, adsb.lol, city CCTV, Radio Browser, GBFS, Launch Library 2, transit feeds, OSRM routing, NOAA and ECMWF weather, OpenStreetMap ALPR mapping, GDELT news events, bundled datasets. |
|
| **🟢 Most layers** | **$0, no signup.** OpenSky anon, USGS, CelesTrak, adsb.lol, city CCTV, Radio Browser, GBFS, Launch Library 2, transit feeds, OSRM routing, NOAA and ECMWF weather, OpenStreetMap ALPR mapping, bundled datasets. |
|
||||||
| **🟡 The free-key tier** | **$0 with a signup.** AISStream, FIRMS, TomTom, OpenSky, World News API, plus Cesium ion for eligible personal/non-commercial use. Provider quotas and eligibility still apply. |
|
| **🟡 The free-key tier** | **$0 with a signup.** AISStream, FIRMS, TomTom, OpenSky, plus Cesium ion for eligible personal/non-commercial use. Provider quotas and eligibility still apply. |
|
||||||
| **🗺️ Google 3D tiles** | **Free through an eligible Cesium ion Community account within its quota; metered through a direct Google key.** Use the direct route for Google place search or commercial deployment, verify current provider terms, and set budget alerts where billing is enabled. |
|
| **🗺️ Google 3D tiles** | **Free through an eligible Cesium ion Community account within its quota; metered through a direct Google key.** Use the direct route for Google place search or commercial deployment, verify current provider terms, and set budget alerts where billing is enabled. |
|
||||||
| **🔴 OpenAI voice** | API-key sessions keep the live spend readout, STD/MINI model toggle, $2 warning, and **$5 hard cap**. ChatGPT OAuth is an explicit choice in Provider Settings. Its usage is tracked without an API-dollar estimate or spend cap. The voice context window stays deliberately short. |
|
| **🔴 OpenAI voice** | API-key sessions keep the live spend readout, STD/MINI model toggle, $2 warning, and **$5 hard cap**. ChatGPT OAuth is an explicit choice in Provider Settings. Its usage is tracked without an API-dollar estimate or spend cap. The voice context window stays deliberately short. |
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -69,21 +69,19 @@ browser-key fallback for existing single-key setups.
|
|||||||
The data proxies under `server/providers/` are written so the browser cannot turn the server into an open relay:
|
The data proxies under `server/providers/` are written so the browser cannot turn the server into an open relay:
|
||||||
|
|
||||||
- **No arbitrary-URL fetching.** The CCTV frame proxy fetches only server-registered camera/frame URLs — clients cannot pass an upstream URL to fetch (SSRF mitigation). Other proxies target fixed upstream hosts.
|
- **No arbitrary-URL fetching.** The CCTV frame proxy fetches only server-registered camera/frame URLs — clients cannot pass an upstream URL to fetch (SSRF mitigation). Other proxies target fixed upstream hosts.
|
||||||
- **Live HLS is bounded and origin-pinned.** Registered HTTP(S) HLS sources use a Node puller that rejects redirects and off-origin playlist references, bounds playlists to 256 KiB and segment bodies to 8 MiB while streaming, and aborts timed-out or released sessions. At most two sessions retain 12 segments/24 MiB each, plus bounded download buffers. Stores are memory-only and idle sessions expire after 15 seconds. RTMP/ffmpeg execution is not enabled. Locally configured source URLs remain an operator trust boundary.
|
- **Live HLS is bounded and origin-pinned.** Registered HTTP(S) HLS sources use a Node puller that rejects redirects and off-origin playlist references, bounds playlists and segment bodies while streaming, and aborts timed-out or released sessions. At most two sessions retain 12 segments/24 MiB each, plus bounded download buffers. Stores are memory-only and idle sessions expire after 15 seconds. RTMP/ffmpeg execution is not enabled. Locally configured source URLs remain an operator trust boundary.
|
||||||
- **Radio is not an audio relay.** `/api/radio/stations` contacts only allowlisted Radio Browser HTTPS hosts and paths, rejects redirects, rejects any hostname with a loopback/private/link-local/metadata/non-public A or AAAA result, and pins each TLS connection to a validated address. It returns normalized public HTTPS stream URLs; `/api/radio/click/:uuid` applies the same destination policy and accepts only station IDs from the current bounded catalog. The browser then connects directly to the broadcaster after an explicit playback action, so the broadcaster sees the listener's IP address. GEV never proxies, caches, records, or redistributes audio.
|
- **Radio is not an audio relay.** `/api/radio/stations` contacts only allowlisted Radio Browser HTTPS hosts and paths, rejects redirects, rejects any hostname with a loopback/private/link-local/metadata/non-public A or AAAA result, and pins each TLS connection to a validated address. It returns normalized public HTTPS stream URLs; `/api/radio/click/:uuid` applies the same destination policy and accepts only station IDs from the current bounded catalog. The browser then connects directly to the broadcaster after an explicit playback action, so the broadcaster sees the listener's IP address. GEV never proxies, caches, records, or redistributes audio.
|
||||||
- **Québec 511 video plays in the browser, opt-in and after Play.** With `CCTV_QUEBEC_VIDEO_ENABLED=1`, `/api/cctv/sources` lists, for each MTQ camera, one URL built server-side from its numeric id on `https://www.quebec511.info/Carte/Fenetres/camera.ashx`; the overlay accepts no other host, path or format. The browser loads that clip only after the viewer presses Play for the selected camera, so Québec 511 sees the viewer's IP address, and the panel says so before Play. Selecting a camera by click, auto-hop, a scene or voice never loads it, and switching cameras stops playback. GEV never proxies, caches or reads the video's pixels. With the flag unset (the default) the browser makes no request to Québec 511.
|
|
||||||
- **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.
|
- **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.
|
- **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.
|
||||||
- **Rail fetches registered feeds only, and none needs a credential.** `/api/rail/feeds` lists the offered feeds; `/api/rail/trains/<id>`, `/api/rail/updates/<id>`, `/api/rail/paths/<id>` and `/api/rail/schedule/<id>` resolve the id against `src/data/railFeeds.js` (`RAIL_FEED_ID_PATTERN`: lowercase letters, digits and hyphens). The browser never supplies a URL, and an id that is not registered, or a trip-updates route for a feed that registers none, does not resolve at all; any method but GET answers 405 (`Allow: GET`, never cached). Every registered feed and every static timetable is open: no rail request carries a credential of the deployer's; one feed's published URL includes a fixed public value its publisher prints for everyone (Bordeaux's `apiKey`), and the only rail setting the server reads is `RAIL_PATHS_DIR`. Upstream requests follow the Transit proxy's policy: https only, each redirect hop validated against the feed's own https origin before it is requested (three hops at most), a request timeout, bodies capped at 8 MiB unless the registry sets a feed's own cap (never above 64 MiB), and a per-feed admission limiter and the failure cooldown ladder bound what the process asks of any operator. Responses are decoded server-side: GTFS-Realtime under entity-count and string-length ceilings, a differential feed is refused, and an empty or header-less GTFS-Realtime reply (an error page served as 200, say) is refused as unreadable, so the last good snapshot is served stale or the request fails with 502. The Irish Rail and SIRI VM XML is read by small scanners, not an XML library: a document that declares a DTD or an entity is refused, and the whole text, the number of trains and every field are capped. Only vehicles the feed's classifier recognises as trains leave the server, and no train position inside Ukraine is ever sent (`src/data/railNoPositions.js`). Snapshots and trip updates live in memory for the feed's own freshness window, with serve-stale after a failure; there is no disk cache and no history. Path and schedule packs are optional build output, read from disk (`.cache/rail-paths/` or `RAIL_PATHS_DIR`) by a reader that re-checks the id against the same pattern, so a name never leaves that directory. A pack over 24 MiB is refused before it is read and answers 503, never a partial file, and so does a pack that cannot be read: only a missing pack is 404; the rest pass through as bytes, through a 64 MiB in-memory read cache keyed by each file's modification time and size, and are served as JSON holding timetables and track shapes only, `no-cache` with an ETag from that time and size (304 when unchanged). A schedule pack of another format version or another feed, or whose window has ended, is not used, and a feed that cannot be read without it answers 503 rather than an empty list. The pack build (`scripts/build-rail-paths.mjs`) runs only when a person runs it, never on app start: it downloads over https only (a redirect to plain http is refused), caps each download in size and time, sends no credential, and refuses an `--out` folder (or a `RAIL_PATHS_DIR` it writes to) inside `src/` or `public/`.
|
|
||||||
- **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.
|
- **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.
|
||||||
- **Voice place search runs only on the operator's Overpass.** `/api/osm/features` answers only when `OVERPASS_UPSTREAMS` is set and never falls back to a public instance. The client sends a curated preset id, a box, a mode and a limit; the server writes and quotes the Overpass query itself, so no client or model text reaches it. Boxes are capped (36 deg² for a count, 4 deg² for a listing), as are the feature limit, the Overpass timeout and memory, and the response bytes read. Each client (IPv6 clients keyed by /64, as on `/api/overpass`) runs one search at a time and two run at once in all, under a per-client rate limit (12/min) and a global one (30/min); a search gets 22 s across every configured endpoint in turn (Overpass itself is told 20 s), which ends before the voice answer stops waiting at 25 s. The route sits behind the cross-site gate below. Answers are cached in memory and on disk (`.gev-cache/osm-features/`, at most 400 entries and 256 MB, kept a day), keyed to the configured instance so a repointed `OVERPASS_UPSTREAMS` never serves the old one's answers; a malformed answer is never cached.
|
- **Voice place search runs only on the operator's Overpass.** `/api/osm/features` answers only when `OVERPASS_UPSTREAMS` is set and never falls back to a public instance. The client sends a curated preset id, a box, a mode and a limit; the server writes and quotes the Overpass query itself, so no client or model text reaches it. Boxes are capped (36 deg² for a count, 4 deg² for a listing), as are the feature limit, the Overpass timeout and memory, and the response bytes read. Each client (IPv6 clients keyed by /64, as on `/api/overpass`) runs one search at a time and two run at once in all, under a per-client rate limit (12/min) and a global one (30/min); a search gets 22 s across every configured endpoint in turn (Overpass itself is told 20 s), which ends before the voice answer stops waiting at 25 s. The route sits behind the cross-site gate below. Answers are cached in memory and on disk (`.gev-cache/osm-features/`, at most 400 entries and 256 MB, kept a day), keyed to the configured instance so a repointed `OVERPASS_UPSTREAMS` never serves the old one's answers; a malformed answer is never cached.
|
||||||
- **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. The one exception is a source registered as an MJPEG still (the Taiwan Highway Bureau feeds): its `multipart/x-mixed-replace` answer is read only up to its first JPEG part, capped at 2 MB, and sent as `image/jpeg`; a multipart answer from any other camera falls through. A camera whose API needs an operator credential (the City of Ottawa certificate, `OTTAWA_CAMERA_CERTIFICATE`) has a keyless catalog URL; the frame proxy adds the credential only at fetch time, only for a URL inside that camera's pinned path, so `/api/cctv/sources` and `/api/cctv/stream` never carry it. 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.
|
- **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.
|
- **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.
|
- **Response-size caps and timeouts** on proxied responses.
|
||||||
- **Sanitized errors** — internal error details are not echoed back to clients.
|
- **Sanitized errors** — internal error details are not echoed back to clients.
|
||||||
- **Coalesced OAuth refresh** and cached successful responses only (OpenSky).
|
- **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.
|
- **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, `/api/tomtom/*`, backed by its persistent daily tile budget, and `/api/news/*`, which spends the World News API daily reads, Liveuamap reads and GDELT downloads) 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 map routes (`/api/terrain/heights`, `/api/drone-airspace`, `/api/inaturalist/observations`, `/api/acartia/sightings`, `/api/overpass` and voice place search's `/api/osm/features`, which spends the same operator Overpass) use the same cross-site checks, minus the forwarding-header refusal: they guard only per-client quotas and caches, not a key or a bill, and the map and voice need them to work behind a reverse proxy. `/api/overpass` also admits at most 300 POSTs a minute per peer (IPv6 peers keyed by /64) and 900 a minute across all remote peers before it reads a body. A direct loopback request — a loopback socket with none of the forwarding headers `hasProxySignals` checks (`Forwarded`, `Via`, `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Proto`, `X-Real-IP`, `CF-Connecting-IP`, `CF-Ray`) — draws on its own per-peer admission window, so remote peers cannot exhaust the owner's admission. A reverse proxy on the same machine must add at least one of those headers (Caddy and Cloudflare do by default; nginx needs `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`, plus `proxy_set_header Host $http_host;` and `proxy_set_header X-Forwarded-Proto $scheme;` so the `Origin` check matches), or its traffic is indistinguishable from the owner's and shares the owner's window. With one, its requests are then counted as remote, in one bucket per proxy socket — the forwarded client address is never trusted as identity — so all users behind one proxy share 300/min. The isolation needs a loopback socket: under the default Docker bridge network every host-side client, the owner included, arrives from the container gateway and is one remote peer. **Limit:** this isolates admission only. The upstream budget (90 requests/min per client key, 300/min across all clients) is shared, so once remote traffic has used the global upstream budget, an owner request that misses the cache can still get `429`, or `503` when remote misses hold every upstream concurrency slot; cache hits are unaffected. 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 map routes (`/api/terrain/heights`, `/api/drone-airspace`, `/api/inaturalist/observations`, `/api/acartia/sightings`, `/api/overpass` and voice place search's `/api/osm/features`, which spends the same operator Overpass) use the same cross-site checks, minus the forwarding-header refusal: they guard only per-client quotas and caches, not a key or a bill, and the map and voice need them to work behind a reverse proxy. `/api/overpass` also admits at most 300 POSTs a minute per peer (IPv6 peers keyed by /64) and 900 a minute across all remote peers before it reads a body. A direct loopback request — a loopback socket with none of the forwarding headers `hasProxySignals` checks (`Forwarded`, `Via`, `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Proto`, `X-Real-IP`, `CF-Connecting-IP`, `CF-Ray`) — draws on its own per-peer admission window, so remote peers cannot exhaust the owner's admission. A reverse proxy on the same machine must add at least one of those headers (Caddy and Cloudflare do by default; nginx needs `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`, plus `proxy_set_header Host $http_host;` and `proxy_set_header X-Forwarded-Proto $scheme;` so the `Origin` check matches), or its traffic is indistinguishable from the owner's and shares the owner's window. With one, its requests are then counted as remote, in one bucket per proxy socket — the forwarded client address is never trusted as identity — so all users behind one proxy share 300/min. The isolation needs a loopback socket: under the default Docker bridge network every host-side client, the owner included, arrives from the container gateway and is one remote peer. **Limit:** this isolates admission only. The upstream budget (90 requests/min per client key, 300/min across all clients) is shared, so once remote traffic has used the global upstream budget, an owner request that misses the cache can still get `429`, or `503` when remote misses hold every upstream concurrency slot; cache hits are unaffected. 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.
|
||||||
- **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.
|
- **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.
|
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.
|
||||||
@@ -133,7 +131,6 @@ God's Eye View's tools are also served to MCP clients: over stdio (`npm run mcp`
|
|||||||
- **What the panel's requests can reach.** In clients that display MCP Apps, the panel loads the app through the `panel_request` tool, which requests paths on the app's server. It is marked for the panel only and requires a key that each MCP server puts in its panel page, which keeps it from clients that list tools to the model without loading the panel. That key is not access control: any MCP client can read the panel page, key included, and then reach the app's files and data routes through `panel_request`, much as its other tools reach the data. Provider Settings, credential and model endpoints, `/mcp` and the development server's internal routes are refused to every caller.
|
- **What the panel's requests can reach.** In clients that display MCP Apps, the panel loads the app through the `panel_request` tool, which requests paths on the app's server. It is marked for the panel only and requires a key that each MCP server puts in its panel page, which keeps it from clients that list tools to the model without loading the panel. That key is not access control: any MCP client can read the panel page, key included, and then reach the app's files and data routes through `panel_request`, much as its other tools reach the data. Provider Settings, credential and model endpoints, `/mcp` and the development server's internal routes are refused to every caller.
|
||||||
- **The panel carries the two browser keys.** The panel build (`npm run build:panel`) includes the same Google Maps key and Cesium ion token as the app, and they run inside the client's panel page, on the client's site. A key restricted by HTTP referrer must also allow that page, or the panel cannot load tiles. To keep the app's own keys narrower, build the panel with separately restricted keys in the environment of `npm run build:panel`.
|
- **The panel carries the two browser keys.** The panel build (`npm run build:panel`) includes the same Google Maps key and Cesium ion token as the app, and they run inside the client's panel page, on the client's site. A key restricted by HTTP referrer must also allow that page, or the panel cannot load tiles. To keep the app's own keys narrower, build the panel with separately restricted keys in the environment of `npm run build:panel`.
|
||||||
- **Embed mode is not framable by default.** `?embed=1` documents keep `X-Frame-Options: DENY` and `frame-ancestors 'none'` unless `GEV_EMBED_FRAME_ANCESTORS` names the pages that may frame them; a framing page can change what the app shows. Other documents can never be framed.
|
- **Embed mode is not framable by default.** `?embed=1` documents keep `X-Frame-Options: DENY` and `frame-ancestors 'none'` unless `GEV_EMBED_FRAME_ANCESTORS` names the pages that may frame them; a framing page can change what the app shows. Other documents can never be framed.
|
||||||
- **A plain link never loads a billed Street View panorama without a click.** A share link with `sl=google,<lat>,<lon>` flies there, switches to Google 3D and lights Street View, then stops: the panel is armed and the photo loads only when the visitor presses OPEN GOOGLE STREET VIEW (one lookup and one panorama load on the browser Maps key, no retries, no geocoding or server call). A view posted into the embedded app (the God's Eye View panel, or a page `GEV_EMBED_FRAME_ANCESTORS` allows), and the panel page's own opening link for a tool result issued in the last five minutes, are that client's own request and open the photo; freshness is judged only by the result's `issued_at`, so a result older than five minutes only arms, whether the panel was mounted again (reopening a conversation) or got it later on the same mount, while one re-sent or remounted within five minutes of issue opens the photo again; adding `?embed=1` to a published link does not lift the gate (in a frame the link only arms; the panel that holds OPEN is hidden there, so a posted view is the way to open a photo). The OPEN press uses the link's point only while the ground under the screen centre is within 1 km of it, or when the screen centre cannot be picked at all (no ground picking, or a globe with no size yet); with the sky in the middle it opens nothing. The armed lookup dies when its source is switched off, when its map leaves, or when any other photo opens or closes. A link pasted into an open tab follows the same rule, and a link's source switch and layer are never saved as the recipient's own preferences. A link that follows an entity ignores `sl`. Mapillary, KartaView and Panoramax links open their photo at once; none of them bills per image.
|
|
||||||
|
|
||||||
## Scope & expectations
|
## Scope & expectations
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
- "Show me X on Street View" (or on Mapillary, KartaView or Panoramax): a
|
|
||||||
`show_street_level` voice and typed action flies to the place, switches to
|
|
||||||
Google 3D when the source needs it, lights that source in Street Level,
|
|
||||||
turns the layer on and opens the nearest photo, saying which step failed.
|
|
||||||
With no place named it looks under the screen centre, as OPEN does. Views
|
|
||||||
and share links carry the same request (`street_level`, `sl=`). A plain
|
|
||||||
`sl=google` link never loads a billed panorama by itself: it arms Street
|
|
||||||
View and leaves the load to OPEN GOOGLE STREET VIEW; embedded views open it,
|
|
||||||
and so does the God's Eye View panel for a result issued in the last five
|
|
||||||
minutes; a link whose source has no working key says so. A request stops
|
|
||||||
when the user navigates, or enters cockpit, clean view, recording or scene
|
|
||||||
playback, before its photo loads (collapsing the panel does not stop it: the
|
|
||||||
photo reveals it); a new photo replaces the open one in place, so EXPAND
|
|
||||||
stays. When nothing is found and a 360°, flat or SINCE filter is on, the
|
|
||||||
answer names the filter. Failure cards are in the interface language. An
|
|
||||||
unavailable map stack now reports the
|
|
||||||
controller's own reason instead of always asking for a Cesium ion token
|
|
||||||
(Said Turksever, @asturksever, #1113).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Keep outside text in voice and MCP tool results to one line of 180
|
|
||||||
characters: ALPR tags, station names and other display fields (asturksever,
|
|
||||||
#1280).
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
- Expand CCTV coverage with a keyless Taiwan Highway Bureau fallback, 1,120
|
|
||||||
Québec camera locations, City of Ottawa traffic cameras (needs a free city
|
|
||||||
access certificate, `OTTAWA_CAMERA_CERTIFICATE`), and 2,230 curated
|
|
||||||
image-backed cameras from Oregon and Japan (InoukT, #1506).
|
|
||||||
- Québec 511 in-browser video is opt-in (`CCTV_QUEBEC_VIDEO_ENABLED=1`) and
|
|
||||||
loads only after the viewer presses Play; enforce provider-specific frame
|
|
||||||
refresh limits, and report recognized MLIT maintenance images as degraded
|
|
||||||
instead of healthy camera snapshots.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Routing outages report "Routing service unavailable" in voice route planning
|
|
||||||
and "Routing unavailable" in Directions instead of "no route found"
|
|
||||||
(quanticsoul4772, #1509).
|
|
||||||
@@ -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,3 +0,0 @@
|
|||||||
- The agent and voice query tools wrap whole-degree directions into 0–359, so
|
|
||||||
a vessel course or heading, aircraft course, transit bearing, camera heading
|
|
||||||
or satellite azimuth that rounds to 360 reads 0 (@quanticsoul4772, #1524).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Add an adjustable event-age filter across S2 GhostMaps products, defaulting
|
|
||||||
to seven days with 1-, 3-, 14- and 30-day choices and an All markers option.
|
|
||||||
Preserve undated features, save the selected window and expire old events
|
|
||||||
while the map stays open. Thanks @dylanmaniatakes (#1528).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- FIRMS fire ages round to whole hours before choosing a unit, so an age of
|
|
||||||
47.5–48 hours reads "2d", as exactly 48 hours does, not "48h". Detections
|
|
||||||
are filtered to the trailing 24 hours, so this shows mostly in the
|
|
||||||
`STALE · cached {age}` stats line after about two days without a successful
|
|
||||||
refresh (@quanticsoul4772, #1531).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- The Nominatim disk-cache test checks the cache directory and file modes
|
|
||||||
only where POSIX modes exist, so it no longer always fails on Windows
|
|
||||||
(quanticsoul4772, #1547).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Stop requesting Suomi NPP in Active Fires. NASA stopped delivering its data
|
|
||||||
on November 2, 2026, so `VIIRS_SNPP_NRT` returned no rows while still costing
|
|
||||||
a MAP_KEY transaction on every refresh; the layer now merges VIIRS NOAA-20,
|
|
||||||
VIIRS NOAA-21 and MODIS (IMBrandao, #1552).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Ride the selected AIS vessel from its bridge: an eye height by vessel type,
|
|
||||||
dead reckoning between fixes, a bridge HUD with a 3–24 NM radar, look-around
|
|
||||||
with the arrow keys, and a waterline read from the scene so river boats sit on
|
|
||||||
the river rather than at sea level (Denis Groshev, @ZloyS, #1557).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- File satellites under six classes, Stations, Imaging, Telecom, Starlink,
|
|
||||||
Probes and Debris, each with a toggle; all but Starlink show by default.
|
|
||||||
Classes come from CelesTrak SATCAT, the UCS Satellite Database and name rules
|
|
||||||
(Denis Groshev, @ZloyS, #1558).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Ride a tracked satellite in an orbital view: a track-up nadir camera, 1–8×
|
|
||||||
zoom, off-nadir tilt with an approximate swath, a CURRENTLY OVER line, and the
|
|
||||||
owner's flag, operator and TLE identity. `C` no longer enters or leaves first
|
|
||||||
person; double-click and the entry buttons do (Denis Groshev, @ZloyS, #1559).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Zoom imaging satellites past 8×, up to 128×, as far as their sensor's
|
|
||||||
confirmed swath allows at the live altitude, with a to-scale swath strip, a
|
|
||||||
SENSOR step, mouse-wheel zoom, imagery pre-loaded ahead of the ground track and
|
|
||||||
the TLE's age (Denis Groshev, @ZloyS, #1560).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Show a fire perimeter under one acre at its reported size. The incident card
|
|
||||||
rounded WFIGS sizes to whole acres, so a 0.25-acre fire read "0 ac"
|
|
||||||
(Rudy Celekli, @rudycelekli, #1569).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- Give transit vehicle icons a readable front: the bus, tram, metro and unknown
|
|
||||||
glyphs now carry a nose or cab in their outline, so the direction of travel
|
|
||||||
shows at fleet size and survives the sensor profiles. A vehicle with no known
|
|
||||||
course gets a symmetric outline instead of one that points north, and
|
|
||||||
switches to the pointed glyph once a course arrives (IMBrandao, #1574).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Say why Transit and Bike Share are empty outside coverage. Over a city with
|
|
||||||
no feed or system (London, Tokyo) their Layers rows say so and count the
|
|
||||||
covered regions or systems, or say how low to fly, instead of `never`;
|
|
||||||
leaving coverage clears the last-update time (German, @gekh, #1576).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- A vessel whose ambient card is on the map no longer gets a Detection callout
|
|
||||||
showing through the card and covering its speed and course. A vessel whose
|
|
||||||
card the overlay did not paint keeps its callout (German, @gekh, #1577).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Space Missions no longer locks the workspace when its mission index fails to
|
|
||||||
load (429, timeout or bad payload): entry rolls back to the previous layers
|
|
||||||
and camera view and a toast says the index could not load (German, @gekh,
|
|
||||||
#1578).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Hold the orbital camera on a mapped site as the satellite passes over it,
|
|
||||||
from a click on its marker or its Live Signals row, and list nearby sites
|
|
||||||
and satellites in orbit's Live Signals, where a row focuses a site or rides
|
|
||||||
a satellite (Denis Groshev, @ZloyS, #1580).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Fix the translations of the voice area cards, the fire confidence filter,
|
|
||||||
the drone airspace layer and Sea Marks: one word for "landmark" per
|
|
||||||
language, the airspace layer names the airspace again, and Traditional
|
|
||||||
Chinese follows Simplified Chinese again (German, @gekh, #1581).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- New Shipwrecks layer (Events): about 8,000 wrecks from EMODnet (CC BY 4.0)
|
|
||||||
and Wikidata (CC0). Each has a card with deaths, date, vessel, cause, depth,
|
|
||||||
position quality and a source link. Markers grow with the death toll, and
|
|
||||||
a start and end year picker filters by period (IMBrandao, #1583).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Keep keyless place search on a nearby exact match for names written in
|
|
||||||
non-Latin scripts instead of discarding the match and retrying without its
|
|
||||||
proximity ranking (mertkayacs, #1585).
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
- Add the News layer (Events ▸ News) and its right-rail NEWS panel: the last
|
|
||||||
24 hours of news, narrowed in the panel to the last 12, 6, 3 or 1 hours with
|
|
||||||
the hotspot counts read for that window. Markers are coloured by severity
|
|
||||||
only, with hotspot beacons, outlines with disputed areas, picture cards for
|
|
||||||
major events and a continuous feed whose cards fly the globe to the event.
|
|
||||||
An open card plays the post's clip on request. The panel has two sections.
|
|
||||||
Conflicts lists the hotspots' news. Local, below 3,000 km, lists every
|
|
||||||
report in the area at the view centre, newest first, names and outlines that area on the
|
|
||||||
globe, and shows those reports on the map instead while it is chosen; a
|
|
||||||
disputed area is named as itself and read from no country's outlets. Set
|
|
||||||
`GEV_NEWS_ORIGIN` to a news server and the local server forwards the
|
|
||||||
layer's three read routes there. Unset, Core answers them itself:
|
|
||||||
Conflicts from the last 24 hours of GDELT 2.0 event exports with no key
|
|
||||||
(physical violence only, one report per story at GDELT's place for it,
|
|
||||||
titled from GDELT's GKG files for the newest 3 hours), plus Liveuamap's
|
|
||||||
mapped items with `LIVEUAMAP_API_KEY`; Local from World News API with
|
|
||||||
`WORLDNEWSAPI_KEY`, at most `WORLDNEWSAPI_DAILY_READS` reads a day
|
|
||||||
(default 30), each country's news kept for at most 1 hour. The routes
|
|
||||||
refuse cross-site and proxied requests. A section says when its key is
|
|
||||||
required, refused or out of its daily allowance. The Data attribution popover credits the GDELT
|
|
||||||
Project, World News API and, once its items show, Liveuamap
|
|
||||||
(Denis Groshev, @ZloyS, #1593).
|
|
||||||
- Cockpit Regional News holds its list to the last 24 hours, newest first, on
|
|
||||||
the server and in the client, and labels its four states apart: fresh
|
|
||||||
results with no banner and a `LAST 24 H` footer, earlier results kept after
|
|
||||||
a failed refresh near the same place as `CACHED · REFRESH FAILED`, `NO NEWS IN THE LAST 24 H`,
|
|
||||||
and `REGIONAL NEWS UNAVAILABLE`. The banner is now really hidden over a
|
|
||||||
populated list, and the footer no longer reverts to a static `RECENT` on a
|
|
||||||
page change (Denis Groshev, @ZloyS, #1593).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- Add Hebrew (he), the second right-to-left language: the title, rails, dock
|
|
||||||
and corner row mirror, numbers keep Western digits, and plurals use the
|
|
||||||
one/two/other Hebrew forms. Noto Sans Hebrew joins the font stacks and the
|
|
||||||
style, plural and glossary notes arrive in docs/translations/he.md
|
|
||||||
(Hebrew, @meirm, #1594).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Without `OPENAI_API_KEY`, the mic now says the key is missing and to add it in
|
|
||||||
Provider Settings, instead of showing the raw server error with a hint to
|
|
||||||
check microphone permissions. `/api/realtime/token` returns
|
|
||||||
`code: "API_KEY_MISSING"` with its 503 (asturksever, #1599).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Without a Mapillary token, Street Level starts on the first keyless source
|
|
||||||
(KartaView) instead of opening on Mapillary's `KEY REQUIRED` with nothing on
|
|
||||||
the globe. A share link that turns a source off is respected
|
|
||||||
(asturksever, #1601).
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
- The CCTV Street View fallback serves only catalog cameras, at their catalog
|
||||||
|
position, heading, field of view and pitch, and ignores coordinates a
|
||||||
|
request supplies. It spends from the per-client Google budget
|
||||||
|
(`GEV_RATELIMIT_GOOGLE_PER_MIN`, default 120), so the Google key can no
|
||||||
|
longer be used as an unmetered Street View service (asturksever, #1602).
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Street Traffic offers separate Lines and Dots toggles while live flow is on:
|
|
||||||
Lines draws each road with flow in green, amber or red, and Dots shows or
|
|
||||||
hides the moving dots. A host can choose which road modes it offers; without
|
|
||||||
OpenStreetMap the layer never draws simulated roads (#1609).
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
- French catalog review: missing narrow no-break spaces, apostrophes, one
|
|
||||||
mistranslated message and the Starlink shell wording (abiyeenzo, #1611).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Typed chat on OpenAI answers again: OpenAI refused every message because
|
|
||||||
`resolve_area`'s area-id pattern uses Unicode classes Chat Completions does
|
|
||||||
not accept. Chat leaves those patterns out; area ids are still checked
|
|
||||||
before the tool runs (samehkhamis, #1616).
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
- Add Icelandic road cameras from the Icelandic Road and Coastal Administration
|
|
||||||
(Vegagerðin) as a CCTV pack. The camera list is one keyless JSON request to
|
|
||||||
`gagnaveita.vegagerdin.is/api/vefmyndavelar2014_1` (about 500 views at 166
|
|
||||||
stations); frames are JPEG stills in `www.vegagerdin.is/vgdata/vefmyndavelar/`,
|
|
||||||
and only links inside that directory are registered. The catalog is fetched
|
|
||||||
over https with redirects refused and a 2 MiB body cap. An explicit facing in
|
|
||||||
the view description ("til vesturs", "í suður", "séð austur") sets a
|
|
||||||
high-confidence heading; other views keep the low-confidence fallback pose.
|
|
||||||
IRCA publishes the images under CC BY 4.0, credited in the Data attribution
|
|
||||||
popover. Loads the 250 views nearest Reykjavík, Akureyri, Egilsstaðir,
|
|
||||||
Ísafjörður and Selfoss; `CCTV_ICELAND_MAX_SOURCES` changes that cap and
|
|
||||||
`CCTV_ICELAND_ENABLED=0` turns the pack off (daikaginza, #1617).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- Add an optional Giroc / Webcam Romania CCTV source pack with corrected
|
|
||||||
coordinates, live HLS, a snapshot fallback and provider attribution. Allow
|
|
||||||
HLS segments up to 8 MiB so high-resolution cameras whose segments exceed
|
|
||||||
4 MiB can play, while retaining the 24 MiB per-session cache and existing
|
|
||||||
origin and session limits (NeoXrated23, #1619).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Turning CCTV on opens the camera nearest you instead of the first camera in
|
|
||||||
the catalog, which showed a Sydney camera in London. A share link opens on
|
|
||||||
the camera nearest the shared view once its flight lands (samehkhamis, #1620).
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
- Add 34 keyless feeds to the Transit layer, the first in Central and Eastern
|
|
||||||
Europe, Italy, Spain, France, Japan and Malaysia: Szczecin, Lisbon (Carris
|
|
||||||
Metropolitana and the city's Carris), Prague, Warsaw, Kraków, Poznań,
|
|
||||||
Gdańsk, Rzeszów, Vilnius, Bucharest, Cluj-Napoca, Iași, Rome, Turin, Venice
|
|
||||||
(buses and waterbuses), Bordeaux, Montpellier, Rouen, Toulon, Le Havre,
|
|
||||||
Tours, Málaga, Sendai, Johor Bahru, Kota Bharu, Adelaide, Atlanta, Denver,
|
|
||||||
Edmonton, Halifax and Victoria. Each feed records its licence, or that none
|
|
||||||
is stated, in the registry and DATA_SOURCES.md. Speeds published in km/h
|
|
||||||
(Lisbon, Montpellier, Toulon) are converted to m/s by the proxy; route ids
|
|
||||||
read as the line on the vehicle; Prague metro, trains, trams and ferries,
|
|
||||||
Bordeaux trams and boats, the Rouen Métro, Le Havre trams and Venice
|
|
||||||
waterbuses take their own mode. The Warsaw and Poznań credits carry the
|
|
||||||
data date their terms ask for, and the data.gov.my feeds are fetched at
|
|
||||||
most twice a minute to stay inside the API's rate limit (IMBrandao).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Add Swedish (sv) to the language picker: the whole interface in Swedish,
|
|
||||||
with Swedish number and date formats and a style guide and glossary in
|
|
||||||
docs/translations/sv.md (Kaj Obst, @Kajjan7610, #1630).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- An application built with fewer layers no longer restores, saves or shares a
|
|
||||||
layer it does not have when a link or saved state from a full build names it
|
|
||||||
(samehkhamis, #1634).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Draw rows take an application's own actions: `createApplicationTools({
|
|
||||||
drawMarkActions })` adds buttons to drawn areas' and lines' rows, each given
|
|
||||||
the shape's `[lon, lat]` geometry, with what it answers shown under the row.
|
|
||||||
The standalone build passes none and is unchanged (samehkhamis, #1636).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Keep the orbital cockpit's sensor strip steady through a zoom step, load the
|
|
||||||
satellite owner flags at startup from one generated 15 KB sheet, and name
|
|
||||||
"Place names" in the loading status while the place-name packs load on first
|
|
||||||
lookup (Sameh Khamis, @samehkhamis, #1637).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- Voice on OpenAI connects again: OpenAI Realtime refused every session, so
|
|
||||||
the mic failed with "Failed to create Realtime token", because
|
|
||||||
`resolve_area`'s area-id pattern uses Unicode classes it no longer accepts.
|
|
||||||
Voice leaves those patterns out, as typed chat does; area ids are still
|
|
||||||
checked before the tool runs (avaldini, #1638).
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
- Keep buses and trains off rooftops and out of bridge decks on the photoreal
|
|
||||||
3D tiles: the ground floor probes the rendered mesh where each vehicle is,
|
|
||||||
keeps each reading at the point it was taken (up to sixteen a cell) and
|
|
||||||
answers from the reading nearest the vehicle, instead of one probe at each
|
|
||||||
110 m cell's centre that lifted every vehicle in the cell onto whatever lay
|
|
||||||
there. Transit probes each vehicle's latest point, and grounded flights read
|
|
||||||
the floor at their own position (Sameh Khamis, @samehkhamis, #1642).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Street Traffic Lines build one set at a time while a city loads, instead of
|
|
||||||
one per paint, so panning with Lines on no longer piles up line builds
|
|
||||||
(Sameh Khamis, @samehkhamis, #1645).
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
- Show a photo of the tracked aircraft. Tracking a civil flight asks
|
|
||||||
Planespotters for that aircraft's photo, and the CONTEXT panel's CONTACTS view
|
|
||||||
shows it with the photographer's credit and a link to the photo's page. A
|
|
||||||
flight lookup (type, route or photo) that failed temporarily is now asked
|
|
||||||
again after a minute instead of staying empty for the session, and a callsign
|
|
||||||
change drops the previous callsign's route (Sameh Khamis, @samehkhamis, #1647).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- A FIRMS snapshot that ends at a set time (`at`, epoch ms, from a source that
|
|
||||||
serves one) is no longer labelled live: the Active Fires row reads
|
|
||||||
`NASA FIRMS · 24 h to 10-21 15:00 UTC`, and `get_active_fires` says "in the
|
|
||||||
24 hours to" that time and reports it as `window_end` (samehkhamis, #1650).
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
- A tracked flight's trail splits at feed gaps, impossible jumps and reports
|
|
||||||
without altitude instead of joining a day of history into one line, keeps
|
|
||||||
every history point, and follows the path the aircraft was drawn along so it
|
|
||||||
no longer cuts across turns. A tracked aircraft that stops reporting shows
|
|
||||||
STALE, keeps flying on its last heading for five minutes, then stops dimmed
|
|
||||||
as SIGNAL LOST and is kept ten minutes after its last report; aircraft on or
|
|
||||||
near the ground hold instead (Sameh Khamis, @samehkhamis, #1654).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- A long flight keeps its origin and destination when weather or airways take
|
|
||||||
it far off the great circle: the route check now allows 200 km or 15% of the
|
|
||||||
route's length, whichever is larger (623 km for JFK–SFO), while a short hop
|
|
||||||
still allows 200 km (Sameh Khamis, @samehkhamis, #1655).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- A tracked aircraft is dimmed as STALE while the whole flight feed is
|
|
||||||
stalled, as it is after a missed update, instead of staying at full
|
|
||||||
brightness (Sameh Khamis, @samehkhamis, #1656).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- A tracked aircraft's Planespotters photo is shown even when the answer names
|
|
||||||
no photographer, linked to its Planespotters page with no credit line,
|
|
||||||
instead of no photo at all (Sameh Khamis, @samehkhamis, #1657).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- Hide street-vehicle brackets and callouts behind buildings and terrain. A
|
|
||||||
Street Traffic contact behind a building, a 3D tile, a hill or a tunnel roof
|
|
||||||
now loses its bracket and "VEH-nnnn" callout along with its dot, instead of
|
|
||||||
having them painted over the facade; aircraft, vessels and satellites keep
|
|
||||||
their labels over the scene as before (Sameh Khamis, @samehkhamis, #1659).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Keyless Gemini voice no longer blames the microphone. Without
|
|
||||||
`GEMINI_API_KEY`, the voice control shows only "Add a Gemini API key in
|
|
||||||
Provider Settings", without the microphone-permission hint, as OpenAI voice
|
|
||||||
already does (Sameh Khamis, @samehkhamis, #1660).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Gemini voice messages follow a language change. A Gemini Live status or
|
|
||||||
error, such as "Gemini quota is unavailable", is shown in the new interface
|
|
||||||
language after you switch it, instead of staying in the old one, as OpenAI
|
|
||||||
voice messages already do (Sameh Khamis, @samehkhamis, #1661).
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
- Flights, Military Flights and AIS Vessels follow the world clock when their
|
|
||||||
source declares a timeline, that is, can answer a snapshot at a given time
|
|
||||||
(`source.timeline`, read by `readSnapshotTimeline` from
|
|
||||||
`gods-eye-view/sources/live`). Away from live they show the snapshot at the
|
|
||||||
clock's time and move on with it as it plays; back at live they poll as
|
|
||||||
before. A source's `timeline.subscribe` may also tell of a better answer
|
|
||||||
for a time it has answered; paused, the layer loads the time shown again.
|
|
||||||
A source with no timeline keeps them live, unchanged (Sameh Khamis,
|
|
||||||
@samehkhamis, #1662).
|
|
||||||
- AIS Vessels are keyed by the stable id their live source gives (`id` on a
|
|
||||||
vessel record), not by MMSI: vessels can share an MMSI, through spoofing,
|
|
||||||
reused numbers or defaults such as 0, and a number can move to another
|
|
||||||
hull. Two vessels sharing an MMSI are now two vessels, each selected,
|
|
||||||
followed and carded on its own. The MMSI is an attribute (`mmsi`), shown
|
|
||||||
when known and `--` otherwise, up to ten digits; looking a vessel up or
|
|
||||||
selecting it by MMSI (an older link, say) still finds a vessel with it, and
|
|
||||||
a vessel with no name is no longer named by its id. `vesselSnapshot` takes
|
|
||||||
`idFor(row)` for a source whose rows carry an id of their own. Tool and voice results carry the vessel's id beside its MMSI
|
|
||||||
(`id`, or `vesselId` where `id` is a display label), and `get_vessel_track`
|
|
||||||
also takes an `id`. AISStream has only MMSIs, so its vessels are keyed by
|
|
||||||
MMSI and nothing changes there (Sameh Khamis, @samehkhamis, #1662).
|
|
||||||
- A share link made while a vessel is selected (and the camera follows
|
|
||||||
nothing) now opens that vessel: the link names it, opens with the camera
|
|
||||||
where it was, looking at the vessel, and selects the vessel once the feed
|
|
||||||
has it, at the link's world time when it has one. A vessel id that is a
|
|
||||||
source's stable id, such as a UUID, travels as its 16 bytes in a new thing
|
|
||||||
field (13), so the link stays short (62 characters where a followed
|
|
||||||
aircraft's is 44). MMSI and other ids, and links already shared, are
|
|
||||||
unchanged; an app that predates the field opens such a link at the same
|
|
||||||
camera, without selecting the vessel. A camera that no follow offset can
|
|
||||||
carry (below the vessel's horizon, say) still makes a place view.
|
|
||||||
A shared flight, military flight, vessel or satellite that is not reporting
|
|
||||||
now says so in a sentence ("The shared vessel isn't reporting now — showing
|
|
||||||
where it was at 14:05"), with its id on a second line under it (ICAO,
|
|
||||||
NORAD, MMSI or vessel UUID) instead of at the head of the sentence (Sameh
|
|
||||||
Khamis, @samehkhamis, #1662).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- A weather feed can supply decoded frames to the weather layers. With an
|
|
||||||
optional `getFrameImage` or `getFrameTile`, Core draws the images and tiles
|
|
||||||
the feed returns instead of reading the image and tile routes; a feed
|
|
||||||
without them is unchanged (samehkhamis, #1665).
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
- Add Rail in the Movement group, off by default: live trains from 30 keyless
|
|
||||||
operator feeds in the United States, Canada, Australia, Ireland, Finland,
|
|
||||||
Spain, Norway, Malaysia, Japan and France, from commuter and long-distance
|
|
||||||
rail to the streetcars and trams of Seattle, Cincinnati, Toyama, Bordeaux
|
|
||||||
and Montpellier. A new `/api/rail` proxy fetches registered feeds only,
|
|
||||||
needs no key and drops buses from mixed feeds. Every train shown is live:
|
|
||||||
29 feeds publish GPS positions and Irish Rail its station reports, and 28
|
|
||||||
of them show with no setup. No train is placed or moved from a timetable;
|
|
||||||
a feed whose live positions fail shows as unavailable. Packs are an opt-in
|
|
||||||
extra: `node scripts/build-rail-paths.mjs --feed <id>` (or `--all`) builds
|
|
||||||
a feed's path and schedule packs into `.cache/rail-paths/`, which add the
|
|
||||||
operator's own route lines, stop names, the next stop and trip-update
|
|
||||||
delays to its live trains; Renfe long and medium distance and KTMB, whose
|
|
||||||
vehicles name no route, appear once their pack is built. Nothing is
|
|
||||||
downloaded when the app starts, and lines are only ever the operator's own
|
|
||||||
shapes. Trains are coloured by category (urban, local, regional, national,
|
|
||||||
international), each with a chip and legend entry shared via token `02`,
|
|
||||||
glide between reports with Transit's delayed playback, are drawn once when
|
|
||||||
several feeds carry them, and open a card on click. A selected train that
|
|
||||||
is moving is followed by the camera, as an aircraft is, and any other is
|
|
||||||
flown to once; the selected train and its line stay on the map from orbit.
|
|
||||||
No train is placed in Ukraine. Feeds that publish no licence are used on
|
|
||||||
the basis each one's entry records, and their credit never presents that
|
|
||||||
as a licence (Denis Groshev, @ZloyS, #1675).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- The News layer reads its time from the world clock, so a host that follows
|
|
||||||
the clock for News can show a past time's reports with their ages, window and
|
|
||||||
counts as they were then. Nothing changes while the timeline is live
|
|
||||||
(Denis Groshev, @ZloyS, #1677).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Move the tile budget helpers `utcDayKey`, `normalizeBudget` and
|
|
||||||
`isOverBudget` out of the TomTom module into `src/data/tileBudget.js`, so
|
|
||||||
other keyed tile proxies can share them. `src/data/tomtomTiles.js`
|
|
||||||
re-exports them; behaviour does not change (monwiss, #762).
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
- Run the existing GC-bracketed allocation regression gates on Node 26 as well as Node 24. Keep the current byte budgets so CI catches allocation regressions on both supported runtimes (BreezeDelegate, #766; fixes #39).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Answer `405 Method Not Allowed` with `Allow: GET` to anything but `GET` on
|
|
||||||
`/api/flights`, `/api/military` and `/api/route`, as the other query proxies
|
|
||||||
do. They answered every verb with the same body. The refusal comes before
|
|
||||||
rate limiting and any upstream call (daikaginza, #794).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
- Compress the live flight snapshot on the wire. `/api/flights` gzips its
|
|
||||||
cached global snapshot and its regional adsb.lol fallback once per refresh
|
|
||||||
and sends that frame to clients that offer gzip: about 860 KB becomes about
|
|
||||||
250 KB. Other clients get the same bytes uncompressed, and both answers carry
|
|
||||||
`Vary: Accept-Encoding` (daikaginza, #795).
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
- The voice debug log now says why OpenAI Realtime did or did not get a
|
|
||||||
viewport screenshot or pointer crop. When an entity-context answer reaches
|
|
||||||
the image step, the log records a reason-coded decision and, when an image
|
|
||||||
was due, one outcome under the same id: sent, or why it was dropped (a stale
|
|
||||||
pointer, a camera move, a newer turn). What is captured, sent and kept is
|
|
||||||
unchanged, and no image data or user text is logged (Kevin Rajan, @kvnloo,
|
|
||||||
Manjunath, @manjunath22466, #835).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
- Let the voice assistant read the storm you select in Cyclone advisories:
|
|
||||||
its official advisory, position, intensity and movement. Clearing the
|
|
||||||
selection and the storm leaving the feed are reported apart (kvnloo, #847).
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
- Reduce dead-reckoning work for flights already hidden behind the horizon.
|
|
||||||
Hidden contacts update on a staggered one-in-13-ticks rotation (about once a
|
|
||||||
second) while visible contacts and contacts with fleet models retain the full
|
|
||||||
update path (Wilmer Alzate, #870).
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
- Add Troll Housing to the Datacenters layer (4,352 features) by refreshing
|
|
||||||
Norway from OpenStreetMap data of 2026-10-05 (trygvehhh, #953).
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "ro-giroc-webcamromania",
|
|
||||||
"name": "Giroc — Webcam Romania",
|
|
||||||
"city": "Giroc, Timiș",
|
|
||||||
"cityId": "giroc",
|
|
||||||
"provider": "Webcam Romania · webcamromania.ro",
|
|
||||||
"sourceKind": "configured",
|
|
||||||
"feedType": "hls",
|
|
||||||
"url": "https://w4.webcamromania.ro/giroc/index.m3u8",
|
|
||||||
"snapshotUrl": "https://webcamromania.ro/media/snapshots/01a0267e-d893-7203-a7eb-7ce3c8c99703/latest.jpg",
|
|
||||||
"lat": 45.70028947266781,
|
|
||||||
"lon": 21.233675914004227,
|
|
||||||
"headingDeg": 0,
|
|
||||||
"headingConfidence": "low",
|
|
||||||
"pitchDeg": -17,
|
|
||||||
"fovDeg": 74,
|
|
||||||
"rangeM": 700,
|
|
||||||
"mountHeightM": 24,
|
|
||||||
"groundElevationM": 87,
|
|
||||||
"license": "Public camera: https://webcamromania.ro/webcam-orase/webcam-giroc/ . Provider terms: https://webcamromania.ro/termeni-si-conditii/ . Video rights remain with the camera operator. Coordinates corrected by the contributor; direction, height, elevation and field of view are uncalibrated estimates."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -188,13 +188,6 @@ state restoration, and release them after the consumers stop. The catalog
|
|||||||
constructor (`application/layers`) also requires `wind`, `weather` and
|
constructor (`application/layers`) also requires `wind`, `weather` and
|
||||||
`cyclones` sources with `getSnapshot` for the Weather layers, and owns the
|
`cyclones` sources with `getSnapshot` for the Weather layers, and owns the
|
||||||
observed-history clock those layers share for the catalog's lifetime.
|
observed-history clock those layers share for the catalog's lifetime.
|
||||||
A `weather` source may also supply decoded frames: an optional
|
|
||||||
`getFrameImage({ product, time, size, bbox, signal })` returns a whole-extent or
|
|
||||||
detail image and an optional `getFrameTile({ product, time, z, x, y, size,
|
|
||||||
signal })` returns one tile or null for a transparent tile, each as an
|
|
||||||
`ImageBitmap`, `ImageData`, canvas or `VideoFrame` of exactly the requested
|
|
||||||
size, which the layer draws once and closes. Without them the layers read the
|
|
||||||
image and tile routes (`WeatherFrameFeed` in `src/layers/weather/source.js`).
|
|
||||||
|
|
||||||
`application/services` accepts boundary, terrain, regional-context, weather and
|
`application/services` accepts boundary, terrain, regional-context, weather and
|
||||||
summary services; its weather service supplies cockpit local conditions, not the
|
summary services; its weather service supplies cockpit local conditions, not the
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
# CCTV integration workflow
|
|
||||||
|
|
||||||
Use this workflow when adding a camera provider or repairing an existing feed.
|
|
||||||
The CCTV catalog comes through the local server; media normally does too. Keep
|
|
||||||
provider-specific URL policy on the server, including any validated browser-only
|
|
||||||
media URL permitted by the exception below.
|
|
||||||
|
|
||||||
## 1. Check the source before coding
|
|
||||||
|
|
||||||
- Identify the catalog endpoint and the actual frame or live-media endpoint.
|
|
||||||
They may be different hosts or use different formats.
|
|
||||||
- Read the provider's license, terms, attribution, rate limits, and access
|
|
||||||
requirements. Publicly reachable data is not automatically licensed for
|
|
||||||
reuse. Record whether the app may display it and whether any use is
|
|
||||||
restricted.
|
|
||||||
- Prefer a public, keyless feed. If credentials are required, keep them in a
|
|
||||||
server-only environment variable; never put a secret in browser code or a
|
|
||||||
Vite-exposed variable. A pack that needs a key is opt-in by that key: with
|
|
||||||
the variable unset it makes no request and logs once (Ontario 511 and
|
|
||||||
Ottawa). Keep the key out of catalog URLs and add it only at fetch time. Use
|
|
||||||
the provider's documented API, not a website endpoint that skips its key. If
|
|
||||||
access terms require an agreement or remain unconfirmed, make the pack
|
|
||||||
opt-in and ensure it makes no request by default.
|
|
||||||
- Confirm the feed can be fetched from the app's Node server environment. A URL
|
|
||||||
opening in a browser does not prove the server can reach it, and browser CORS
|
|
||||||
behavior does not diagnose a server-side fetch.
|
|
||||||
- Treat camera directories and aggregators such as [TW Live](https://tw.live/)
|
|
||||||
as discovery aids. Trace each listing to the original agency or camera owner;
|
|
||||||
their data and media terms govern reuse. For example, SkylineWebcams says
|
|
||||||
only camera hosts may embed its live feed and its terms restrict copying or
|
|
||||||
retransmitting footage, so do not proxy its stream without written
|
|
||||||
authorization ([FAQ](https://www.skylinewebcams.com/support/faq.html),
|
|
||||||
[terms](https://www.skylinewebcams.com/en/terms-of-use.html)).
|
|
||||||
- Decide whether the provider supplies still snapshots or continuous video.
|
|
||||||
Prefer stills unless live video is necessary and supported by the existing
|
|
||||||
proxy path.
|
|
||||||
|
|
||||||
## 2. Add and register the provider pack
|
|
||||||
|
|
||||||
Use the smallest existing provider as a template. Current owners are:
|
|
||||||
|
|
||||||
- `server/providers/cctv/sources.js` for standard open-data loaders.
|
|
||||||
- `server/providers/cctv/<provider>.js` for a provider with its own parser,
|
|
||||||
fallback, or more involved URL policy.
|
|
||||||
- `server/providers/cctv/catalog.js` to import the loader and add it to
|
|
||||||
`LIVE_PACKS`. Each pack is independently gated and the shared catalog cap
|
|
||||||
allocates space across packs.
|
|
||||||
- `server/providers/cctv/constants.js` for provider endpoints, caps, and
|
|
||||||
timeouts.
|
|
||||||
|
|
||||||
Return normalized camera records with stable unique IDs, readable names,
|
|
||||||
`city`/`cityId`, provider attribution, valid `lat`/`lon`, and the correct
|
|
||||||
`feedType`. Include camera heading only when the source actually provides a
|
|
||||||
bearing; descriptive direction text is not a numeric heading. Mark uncertain
|
|
||||||
heading data with the existing low-confidence conventions. Set `sourceKind` so
|
|
||||||
health and source diagnostics identify the provider. Apply a bounded per-pack
|
|
||||||
camera limit and geographic filtering where appropriate; avoid one provider
|
|
||||||
silently consuming the whole shared catalog cap.
|
|
||||||
|
|
||||||
Camera coordinates need their own verification. Prefer the original provider's
|
|
||||||
camera coordinates, then corroborate them against its location description,
|
|
||||||
route/mile marker, or official map. An aggregator's map dot, a town/county name,
|
|
||||||
or a geocoded attraction name is not a precise camera position. Do not pin a
|
|
||||||
camera to a city center or nearby landmark; if its actual location cannot be
|
|
||||||
verified, leave it out until it can. For manually curated coordinates, record
|
|
||||||
the evidence in the provider data or an adjacent comment. For example, TW Live
|
|
||||||
camera pages identify the originating agency; use that agency's catalog for
|
|
||||||
the camera point when available.
|
|
||||||
|
|
||||||
If the source becomes opt-in, use the catalog's opt-in gate (`envOptIn`) and
|
|
||||||
document the flag and reason in `DATA_SOURCES.md`. Otherwise use the normal
|
|
||||||
enabled-by-default flag pattern and document how to disable the pack.
|
|
||||||
|
|
||||||
## 3. Keep frame and media requests server-registered
|
|
||||||
|
|
||||||
The browser should request the local `/api/cctv/frame/:id` or
|
|
||||||
`/api/cctv/media/:id` endpoint. Do not pass an arbitrary upstream URL from the
|
|
||||||
browser, and do not fetch an unvalidated URL supplied by a catalog record.
|
|
||||||
Every provider must have a server-side rule that pins frame and media URLs to
|
|
||||||
the expected scheme, host, and path; use HTTPS where the provider supports it.
|
|
||||||
Use `pinnedCameraUrl()` for URLs that belong under a fixed provider directory,
|
|
||||||
or a stricter provider-specific validator when hosts or paths vary. Register
|
|
||||||
any new permitted image/media origin in the existing
|
|
||||||
`server/providers/cctv` media policy.
|
|
||||||
|
|
||||||
Catalog and media loaders must also:
|
|
||||||
|
|
||||||
- refuse unsafe schemes, credentials, unexpected ports, redirects outside the
|
|
||||||
registered origin/path, and malformed identifiers;
|
|
||||||
- set request deadlines and cap catalog and buffered frame bodies;
|
|
||||||
- parse only the expected format and validate coordinates before returning a
|
|
||||||
camera;
|
|
||||||
- handle offline cameras and partial provider failures without failing the
|
|
||||||
entire merged catalog; and
|
|
||||||
- avoid persistent frame storage unless the provider's terms explicitly allow
|
|
||||||
it and the design has been reviewed.
|
|
||||||
|
|
||||||
For live media, prefer the existing proxy and supported feed type (`mjpeg`,
|
|
||||||
`mp4`, `webm`, or `hls`). A direct browser-only video is a narrow fallback for
|
|
||||||
a source whose terms allow embedding when the browser can play it but the app
|
|
||||||
server cannot fetch it. Unconfirmed embedding terms make it opt-in like any
|
|
||||||
other pack, and the browser loads it only after an explicit play action, never
|
|
||||||
on selection, because the provider sees the viewer's IP address. Build its URL
|
|
||||||
from a validated provider ID on the server; never accept a browser-supplied
|
|
||||||
media URL. Render it in a native DOM
|
|
||||||
`<video>` without `crossOrigin`, and do not read its pixels into canvas, WebGL,
|
|
||||||
or a Cesium texture. If a map preview is needed, a second video request may be
|
|
||||||
paused as a DOM overlay at the camera location; label it as an approximate
|
|
||||||
still and scale it with map distance. If terms disallow embedding, use a source
|
|
||||||
link instead. Read [SECURITY.md](../SECURITY.md) before changing URL
|
|
||||||
validation, redirects, or proxy behavior.
|
|
||||||
|
|
||||||
When an image provider sets a minimum refresh interval, enforce it at the
|
|
||||||
server frame boundary as well as in the client. A bounded in-memory cache and
|
|
||||||
single-flight request per camera prevent projection, panel, and re-selection
|
|
||||||
paths from multiplying upstream requests; do not persist frames unless the
|
|
||||||
provider's terms explicitly allow it.
|
|
||||||
|
|
||||||
## 4. Add focused regression coverage
|
|
||||||
|
|
||||||
Add provider tests under `src/data/` following the existing CCTV test naming
|
|
||||||
and fixtures. Cover at least:
|
|
||||||
|
|
||||||
- representative valid catalog rows and their normalized output;
|
|
||||||
- missing or invalid IDs, coordinates, and frame URLs;
|
|
||||||
- duplicate records and the pack's cap/filter behavior;
|
|
||||||
- unsafe host/path, credentials, and redirect cases;
|
|
||||||
- timeout, oversized, malformed, and unavailable upstream responses; and
|
|
||||||
- the frame or live-media content type the provider actually serves.
|
|
||||||
|
|
||||||
Use `src/data/cctvProxy.test.mjs` for shared proxy/media behavior and
|
|
||||||
`src/data/cctvCatalogCap.test.mjs` for shared catalog allocation. For a
|
|
||||||
browser-only source, verify native playback, denied canvas pixel access, the
|
|
||||||
paused second-player overlay, and its cleanup separately from proxy tests. Run
|
|
||||||
the focused provider and proxy tests, then the standard checks from
|
|
||||||
[CONTRIBUTING.md](../CONTRIBUTING.md) that apply to the change. For runtime
|
|
||||||
verification, inspect `/api/cctv/sources`, `/api/cctv/health`, and the camera's
|
|
||||||
`/api/cctv/frame/:id` or `/api/cctv/stream/:id` response locally. Confirm a
|
|
||||||
successful frame is from the upstream camera by checking its content type and
|
|
||||||
`X-CCTV-Source`; a fallback image is not proof that the upstream feed works.
|
|
||||||
|
|
||||||
## 5. Update source documentation and submit
|
|
||||||
|
|
||||||
- Add the provider, endpoint, terms/license, required attribution, key status,
|
|
||||||
caching behavior, and any restrictions to `DATA_SOURCES.md`.
|
|
||||||
- Update the CCTV source section in `docs/CURRENT-STATE.md` when runtime
|
|
||||||
behavior, flags, caps, or fallback behavior changes.
|
|
||||||
- Register in-app attribution through the existing source/provider credit
|
|
||||||
fields; update `src/data/dataCredits.js` when the source needs a layer-level
|
|
||||||
credit.
|
|
||||||
- Add the required `changelog.d/<pr-number>-<slug>.md` entry for a runtime
|
|
||||||
change, and include the source, license, attribution, key/runtime-fetch
|
|
||||||
details, and verification evidence in the PR description.
|
|
||||||
- For a new layer, also follow the camera/source requirements in the
|
|
||||||
[new-layer PR checklist](../CONTRIBUTING.md#adding-a-new-layer).
|
|
||||||
@@ -136,9 +136,6 @@ socket/watchdog on server close and re-reads configuration after restart.
|
|||||||
The portable `gods-eye-view/sources/adsb-lol` export normalizes existing aircraft
|
The portable `gods-eye-view/sources/adsb-lol` export normalizes existing aircraft
|
||||||
records without importing Node middleware or a renderer. Browser layer/controller
|
records without importing Node middleware or a renderer. Browser layer/controller
|
||||||
separation is outside this server extraction.
|
separation is outside this server extraction.
|
||||||
The portable `gods-eye-view/sources/planespotters` export reads a Planespotters
|
|
||||||
photo answer (upstream or a server's cached shape) into one host-checked,
|
|
||||||
credited thumbnail; it has no fetch, DOM or Node dependency.
|
|
||||||
|
|
||||||
## Place-search and routing providers
|
## Place-search and routing providers
|
||||||
|
|
||||||
@@ -166,8 +163,7 @@ local composition mounts them in the same order. Importing the entry performs
|
|||||||
no acquisition; factory calls create independent cache state.
|
no acquisition; factory calls create independent cache state.
|
||||||
|
|
||||||
`gods-eye-view/sources/space` owns only fixed upstream URL construction: the
|
`gods-eye-view/sources/space` owns only fixed upstream URL construction: the
|
||||||
CelesTrak group/TLE and SATCAT record queries and Launch Library's recent
|
CelesTrak group/TLE query and Launch Library's recent 30-day detailed feed.
|
||||||
30-day detailed feed.
|
|
||||||
Callers supply the group or end date and own validation, credentials, transport,
|
Callers supply the group or end date and own validation, credentials, transport,
|
||||||
response limits and cache policy. The boundary gate checks this portable entry
|
response limits and cache policy. The boundary gate checks this portable entry
|
||||||
separately from the Node providers. Satellite rendering and launch replay remain
|
separately from the Node providers. Satellite rendering and launch replay remain
|
||||||
@@ -634,9 +630,8 @@ source-time fallback and model-owned ground policy. The renderer supplies that
|
|||||||
ownership fact and handles stale-ground lifting, Cesium history and primitives;
|
ownership fact and handles stale-ground lifting, Cesium history and primitives;
|
||||||
record reconciliation does not import the engine or application.
|
record reconciliation does not import the engine or application.
|
||||||
|
|
||||||
`layers/vessels/records` owns plain AIS metadata, identity by the stable id the
|
`layers/vessels/records` owns plain AIS metadata, stable MMSI identity and bounded
|
||||||
source gives (an MMSI only for a feed that has nothing else; the MMSI itself is
|
retention. `layers/vessels/ingestion` owns source requests and feed state through
|
||||||
an attribute) and bounded retention. `layers/vessels/ingestion` owns source requests and feed state through
|
|
||||||
explicit operations. Both exports exclude Cesium, DOM and application assembly.
|
explicit operations. Both exports exclude Cesium, DOM and application assembly.
|
||||||
The snapshot renderer applies record changes; rendering owns a weak map of
|
The snapshot renderer applies record changes; rendering owns a weak map of
|
||||||
geometry and billboard resources used by cards, picking and trails.
|
geometry and billboard resources used by cards, picking and trails.
|
||||||
|
|||||||
+98
-1175
File diff suppressed because it is too large.
Load diff
@@ -57,6 +57,6 @@ Consumers use the package exports, which do not import standalone application
|
|||||||
globals.
|
globals.
|
||||||
|
|
||||||
Dataset files and source/license notices remain under `src/data/local_data/`.
|
Dataset files and source/license notices remain under `src/data/local_data/`.
|
||||||
Their input counts are 4,352 datacenter features and 704 dam features. Cesium may
|
Their input counts are 4,351 datacenter features and 704 dam features. Cesium may
|
||||||
expand multipart geometries into multiple entities; entity and stem counts are
|
expand multipart geometries into multiple entities; entity and stem counts are
|
||||||
different measurements. This change does not refresh or relicense the datasets.
|
different measurements. This change does not refresh or relicense the datasets.
|
||||||
+1
-44
@@ -1,6 +1,6 @@
|
|||||||
# KNOWN ISSUES
|
# KNOWN ISSUES
|
||||||
|
|
||||||
Updated: November 5, 2026
|
Updated: September 22, 2026
|
||||||
|
|
||||||
This file tracks active runtime issues only.
|
This file tracks active runtime issues only.
|
||||||
|
|
||||||
@@ -145,49 +145,6 @@ Status: Open (limits)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### News layer without a news server
|
|
||||||
Status: Open (limits)
|
|
||||||
|
|
||||||
- GDELT's places and categories are machine-coded and approximate. A report can
|
|
||||||
be placed in the wrong town of the same name, or at a country an article only
|
|
||||||
names as a nationality ("Syrian refugees" placed in Syria).
|
|
||||||
- Copies of one agency story are merged, but the same event under different
|
|
||||||
wording stays as separate reports.
|
|
||||||
- Only the 11 disputed areas in the bundled list
|
|
||||||
(`src/data/local_data/natural_earth/README.md`) and the territories Natural
|
|
||||||
Earth gives no country code (Somaliland, Northern Cyprus, Siachen Glacier,
|
|
||||||
the Spratly Islands and a few more) are named as themselves; everywhere else
|
|
||||||
Natural Earth's default country lines apply, so a place in Western Sahara,
|
|
||||||
for example, is named with the country those lines put it in. GDELT places a whole city at one point: Jerusalem's falls
|
|
||||||
inside East Jerusalem, so it is named "Jerusalem" alone.
|
|
||||||
- A country's own stories in Local keep World News API's category (politics as
|
|
||||||
political, business as economy, else other) and carry no severity; fighting
|
|
||||||
there shows through the Conflicts reports placed in the area.
|
|
||||||
- When World News API takes 10 to 20 s to answer a country's first read, Local
|
|
||||||
reads unavailable until it is asked again (the next refresh, every 2 minutes
|
|
||||||
while the tab is shown, or the camera settling in another half-degree cell,
|
|
||||||
or a section switch), which is served from that read. A read over 20 s fails
|
|
||||||
and is tried again with the first ask a minute or more later.
|
|
||||||
- Local needs a World News API key. Its free plan allows 50 points a day; Core
|
|
||||||
stops at `WORLDNEWSAPI_DAILY_READS` reads (default 30) and Local then says the
|
|
||||||
daily limit is reached. A request that never reached World News API (offline)
|
|
||||||
is not counted; any answer from it is, a failing one too, and so is a
|
|
||||||
request that timed out.
|
|
||||||
- A country's news is kept for at most 1 hour (World News API's terms, which
|
|
||||||
also ask the operator for its written permission to cache at all). While
|
|
||||||
World News API cannot be read, Local in that country therefore reads
|
|
||||||
unavailable from an hour after its last good read.
|
|
||||||
- A story GDELT codes as violence whose address has no words, and whose page
|
|
||||||
title is not in yet (titles are read for the newest 3 hours only), is not
|
|
||||||
listed under Conflicts or counted in its hotspot: GDELT's coding alone is
|
|
||||||
wrong too often. It still shows in Local for its area, as "… reported in
|
|
||||||
<place>".
|
|
||||||
- `/api/news/*` refuses cross-site browser requests and requests carrying
|
|
||||||
reverse-proxy forwarding headers, like the other key-spending routes
|
|
||||||
(SECURITY.md): behind a reverse proxy the News layer reads unavailable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Closed / Intentional (for clarity)
|
## Closed / Intentional (for clarity)
|
||||||
|
|
||||||
### Proxy SSRF and error-surface hardening gaps
|
### Proxy SSRF and error-surface hardening gaps
|
||||||
|
|||||||
+14
-56
@@ -28,11 +28,7 @@ Following an aircraft is 42–50: 42 when the link is made from the app's own
|
|||||||
follow camera with the fix taken as the link is made, 44 with a fix four
|
follow camera with the fix taken as the link is made, 44 with a fix four
|
||||||
seconds older, and 50 after orbiting to another heading and pitch. A view in
|
seconds older, and 50 after orbiting to another heading and pitch. A view in
|
||||||
any other style that keeps the style's own look adds only the style (two
|
any other style that keeps the style's own look adds only the style (two
|
||||||
bytes): the 42-character followed aircraft in Noir is 44. A selected vessel
|
bytes): the 42-character followed aircraft in Noir is 44.
|
||||||
named by its MMSI costs what an aircraft does (46 characters for the
|
|
||||||
44-character aircraft above); one named by a UUID costs 13 bytes more, as its
|
|
||||||
16 bytes travel raw (62 characters where the aircraft is 44, rather than about
|
|
||||||
88 with the UUID as text).
|
|
||||||
|
|
||||||
## Opening a short link
|
## Opening a short link
|
||||||
|
|
||||||
@@ -51,32 +47,9 @@ best effort: until the entity is found, and if it is not found in time, the
|
|||||||
camera is where the link was made, looking at the entity's last-known fix,
|
camera is where the link was made, looking at the entity's last-known fix,
|
||||||
and the app says that the entity isn't reporting and when the fix was taken.
|
and the app says that the entity isn't reporting and when the fix was taken.
|
||||||
The share button makes a thing view while the camera follows an aircraft,
|
The share button makes a thing view while the camera follows an aircraft,
|
||||||
military aircraft or satellite. While the camera follows nothing and a vessel
|
military aircraft or satellite. A selected vessel is not followed (the camera
|
||||||
is selected, it makes a vessel thing view: the fix is the vessel's position
|
stays where the user puts it), so a link made then is a place view; vessel
|
||||||
as drawn (as of the world time while the vessel layer follows the world
|
thing views open, but the app does not make them yet.
|
||||||
clock, else as of its latest position report), and the offset is the one whose derived camera is
|
|
||||||
where the user's camera is (`followOffsetFor` in `src/share/follow.js`, the
|
|
||||||
inverse of `followCameraFor`, taken from the fix at the format's precision).
|
|
||||||
The opened camera is then within the offset's whole metres and degrees of
|
|
||||||
where the link was made, looking at the vessel. When no offset can carry the
|
|
||||||
camera, because it is not above the vessel's local horizontal (it would look
|
|
||||||
up at the vessel, or through the earth) or its range is outside 1 m to
|
|
||||||
10⁸ m, the link is a place view as before. A vessel is selected, not
|
|
||||||
followed, so the camera stays where the user puts it. Following an entity
|
|
||||||
takes priority over a selected vessel, and a followed entity the format
|
|
||||||
cannot name still makes a place view. Opening a vessel thing view with a
|
|
||||||
world time waits for the vessel layer's snapshot at that time and selects the
|
|
||||||
vessel once it has it.
|
|
||||||
|
|
||||||
## Street-level photos (`sl`)
|
|
||||||
|
|
||||||
Views built by `show_in_gods_eye_view` or `src/view/index.js` can add
|
|
||||||
`sl=<source>,<lat>,<lon>` to the long form. Once the link has restored, the
|
|
||||||
app opens the nearest photo there, or, for a source that bills every load,
|
|
||||||
arms the panel so OPEN loads it (see `docs/TOOLS.md`). The app's own links do
|
|
||||||
not carry it: the address bar's long form drops `sl` when the camera next
|
|
||||||
moves, and the short form has no field for it. A reload or the app's Copy
|
|
||||||
Link keeps the lit source, the layer and the map, but not the photo request.
|
|
||||||
|
|
||||||
## Payload
|
## Payload
|
||||||
|
|
||||||
@@ -146,7 +119,6 @@ probability of about 255 in 256 (many of them already fail as base64url).
|
|||||||
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
|
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
|
||||||
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
|
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
|
||||||
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
|
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
|
||||||
| 28 | `worldTime` | epoch | none (live) | zigzag seconds since 2026-01-01T00:00:00Z |
|
|
||||||
|
|
||||||
Field 1 absent means `thing` when field 13 is present and `place` otherwise,
|
Field 1 absent means `thing` when field 13 is present and `place` otherwise,
|
||||||
so v1 never writes it; a later view kind writes its code.
|
so v1 never writes it; a later view kind writes its code.
|
||||||
@@ -160,10 +132,6 @@ style also extends the pinned style-defaults table.
|
|||||||
`hudVariant` code 1 (`operator`) is retired: the app no longer offers that
|
`hudVariant` code 1 (`operator`) is retired: the app no longer offers that
|
||||||
layout and restores it as `tactical`. The code stays reserved.
|
layout and restores it as `tactical`. The code stays reserved.
|
||||||
|
|
||||||
Field 28 is the world time the link was made at, written only when it was away
|
|
||||||
from live. A link without it opens live. Unlike field 8 it may lie ahead of the
|
|
||||||
link's creation (a forecast moment), and it opens paused at that time.
|
|
||||||
|
|
||||||
Flag bits (field 9): 0 `bloom`, 1 `sharpen`, 2 `hudVisible`, 3
|
Flag bits (field 9): 0 `bloom`, 1 `sharpen`, 2 `hudVisible`, 3
|
||||||
`celestialRing` (each defaulting by style), 4 `scopeEnabled` (default on).
|
`celestialRing` (each defaulting by style), 4 `scopeEnabled` (default on).
|
||||||
|
|
||||||
@@ -301,23 +269,14 @@ sub-message:
|
|||||||
| 10 | fix alt | sint metres | 0 |
|
| 10 | fix alt | sint metres | 0 |
|
||||||
| 11 | fix age | sint seconds the fix predates the created-at time | 0 |
|
| 11 | fix age | sint seconds the fix predates the created-at time | 0 |
|
||||||
| 12 | observed-at | epoch, when the fix was taken; only without a created-at | none |
|
| 12 | observed-at | epoch, when the fix was taken; only without a created-at | none |
|
||||||
| 13 | vessel id | UUID, 16 bytes (LEN) | |
|
|
||||||
|
|
||||||
The entity kind is the field that carries the id (its code in
|
The entity kind is the field that carries the id (its code in
|
||||||
`SHARE_VIEW_ENTITY_KINDS`, or field 13 for a vessel), so it costs nothing;
|
`SHARE_VIEW_ENTITY_KINDS`), so it costs nothing; exactly one of fields 1–4 is
|
||||||
exactly one of fields 1–4 and 13 is present (two is malformed). Entity ids are
|
present (two is malformed). Entity ids are the ones the layers look up: an
|
||||||
the ones the layers look up: an aircraft's ICAO 24-bit address (six hex
|
aircraft's ICAO 24-bit address (six hex digits, stored as a number; other
|
||||||
digits, stored as a number; other forms such as TIS-B `~abc123` as text), a
|
forms such as TIS-B `~abc123` as text), a vessel's MMSI and a satellite's
|
||||||
vessel's id (its MMSI, or a source's stable id, such as a UUID) and a
|
NORAD catalog number (decimal numbers; an MMSI with a leading zero stays
|
||||||
satellite's NORAD catalog number (decimal numbers; an MMSI with a leading
|
text). Ids are lowercase and match `[0-9a-z~_-]{1,16}`.
|
||||||
zero stays text). Ids are lowercase and match `[0-9a-z~_-]{1,16}`, except a
|
|
||||||
vessel id that is a UUID (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, lowercase
|
|
||||||
hex): it is written as its 16 bytes in field 13 rather than as 36 characters
|
|
||||||
of text in field 3, after the other thing fields, and decodes as the
|
|
||||||
lowercase dashed UUID. Field 13 of any other length is malformed. A decoder
|
|
||||||
that predates field 13 skips it, finds no entity and opens the view as a
|
|
||||||
place at its derived camera (`unknown-entity-kind`), as for a new entity kind,
|
|
||||||
so the field needed no new version.
|
|
||||||
|
|
||||||
Fields 5–7 are the follow offset: the camera looks along (heading, pitch) at
|
Fields 5–7 are the follow offset: the camera looks along (heading, pitch) at
|
||||||
the entity from `range` metres away, heading clockwise from north, in the
|
the entity from `range` metres away, heading clockwise from north, in the
|
||||||
@@ -355,9 +314,8 @@ bytes, a check byte that does not match (`checksum`), an unknown version byte,
|
|||||||
a truncated value, a varint over 8 bytes or above 2⁵³ − 1, an
|
a truncated value, a varint over 8 bytes or above 2⁵³ − 1, an
|
||||||
overlong varint, a LEN longer than what remains (at any level, including
|
overlong varint, a LEN longer than what remains (at any level, including
|
||||||
inside the thing sub-message), a reserved wire type, field number 0, a known
|
inside the thing sub-message), a reserved wire type, field number 0, a known
|
||||||
field with the wrong wire type or repeated, two entity ids in one thing
|
field with the wrong wire type or repeated, two entity ids in one thing, a
|
||||||
(field 13 counts as one), a UUID vessel id that is not 16 bytes
|
duplicate panel/option/style entry, non-ASCII text, and a latitude or
|
||||||
(`bad-length`), a duplicate panel/option/style entry, non-ASCII text, and a latitude or
|
|
||||||
longitude (camera or fix) beyond ±90/±180.
|
longitude (camera or fix) beyond ±90/±180.
|
||||||
|
|
||||||
Tolerated and reported in `unknownFields`: unknown fields at any level
|
Tolerated and reported in `unknownFields`: unknown fields at any level
|
||||||
@@ -395,8 +353,8 @@ the app that can open it.
|
|||||||
boolean takes the next flag bit instead, which costs nothing when it is at
|
boolean takes the next flag bit instead, which costs nothing when it is at
|
||||||
its default.
|
its default.
|
||||||
6. **New view kinds and entity kinds** get the next code. A new entity kind's
|
6. **New view kinds and entity kinds** get the next code. A new entity kind's
|
||||||
id takes the next free thing field (14 onwards; 13 is a vessel id that is
|
id takes the next free thing field (13 onwards), so older apps skip it and
|
||||||
a UUID), so older apps skip it and fall back to the camera. A thing that needs more than an id (for example a
|
fall back to the camera. A thing that needs more than an id (for example a
|
||||||
CCTV camera, a radio station or a transit vehicle) adds further thing
|
CCTV camera, a radio station or a transit vehicle) adds further thing
|
||||||
fields the same way.
|
fields the same way.
|
||||||
7. **A new layer option** needs an entry in `SHARE_VIEW_LAYER_OPTION_ABSENT`
|
7. **A new layer option** needs an entry in `SHARE_VIEW_LAYER_OPTION_ABSENT`
|
||||||
|
|||||||
+6
-24
@@ -102,29 +102,11 @@ action draws (pins, highlights, areas, arrows, routes and labels at a place
|
|||||||
name or coordinates). Links carry them in the `an` parameter, and the app
|
name or coordinates). Links carry them in the `an` parameter, and the app
|
||||||
draws them once the link has been restored.
|
draws them once the link has been restored.
|
||||||
|
|
||||||
A view can open street-level photos too: `street_level` names a source
|
|
||||||
(`google` for Street View, `mapillary`, `kartaview`, `panoramax`, or none for
|
|
||||||
the lit one) and a point, by default the spot the camera looks at. Street View
|
|
||||||
shows only on the Google 3D map, so a `google` source sets `map` to
|
|
||||||
`photoreal` and refuses another; a view cannot both follow an entity and open
|
|
||||||
photos. Links carry it as `sl=<source>,<lat>,<lon>`: once restored, the app
|
|
||||||
runs `show_street_level`, which flies there and opens the nearest photo. A
|
|
||||||
plain link with a `google` source only arms the panel for OPEN GOOGLE STREET
|
|
||||||
VIEW; a view posted into the embedded app opens it, and so does the panel
|
|
||||||
page's own opening link, but only for a tool result issued in the last five
|
|
||||||
minutes (an older one, such as a host remounting the panel, only arms);
|
|
||||||
`?embed=1` on a published link does not. Mapillary,
|
|
||||||
KartaView and Panoramax, which do not bill per image, open at once. A link
|
|
||||||
that follows an entity ignores `sl`.
|
|
||||||
|
|
||||||
Tools take a view as `VIEW_ARGUMENTS`: an `area` to frame from above, or a
|
Tools take a view as `VIEW_ARGUMENTS`: an `area` to frame from above, or a
|
||||||
`camera`, plus `layers`, `style`, `map`, `follow`, `annotations` and
|
`camera`, plus `layers`, `style`, `map`, `follow` and `annotations`. A
|
||||||
`street_level`. A tilted camera over an area looks at its center from behind;
|
tilted camera over an area looks at its center from behind; camera fields
|
||||||
camera fields given with an area override its framing. A passed-through
|
given with an area override its framing. `resolveViewArguments` turns them
|
||||||
view's `street_level` point moves with a new `area` or camera position (a
|
into a view.
|
||||||
turned camera keeps it). `resolveViewArguments` turns them into a view. The
|
|
||||||
app's own links (the address bar, Copy Link, `/share/<code>`) do not carry
|
|
||||||
`sl`; see `docs/SHARE-LINK-FORMAT.md`.
|
|
||||||
|
|
||||||
### The God's Eye View panel
|
### The God's Eye View panel
|
||||||
|
|
||||||
@@ -288,7 +270,7 @@ and the development server's internal routes. See SECURITY.md.
|
|||||||
| `get_aircraft_info` | `aircraft` | Aircraft type and registration, and flight route, from adsbdb |
|
| `get_aircraft_info` | `aircraft` | Aircraft type and registration, and flight route, from adsbdb |
|
||||||
| `vessels_in_area` | `vessels` | Ships reported by AIS in an area, nearest first, optionally by type; `group_by` and `stats` count and summarize every match |
|
| `vessels_in_area` | `vessels` | Ships reported by AIS in an area, nearest first, optionally by type; `group_by` and `stats` count and summarize every match |
|
||||||
| `find_vessel` | `vessels` | Ships anywhere by MMSI, IMO number or name |
|
| `find_vessel` | `vessels` | Ships anywhere by MMSI, IMO number or name |
|
||||||
| `get_vessel_track` | `vessels` | Recent positions of one ship by vessel id or MMSI, thinned to 200 points |
|
| `get_vessel_track` | `vessels` | Recent positions of one ship, thinned to 200 points |
|
||||||
| `next_satellite_pass` | `satellites` | Next pass over a place or point (default the ISS), with naked-eye visibility |
|
| `next_satellite_pass` | `satellites` | Next pass over a place or point (default the ISS), with naked-eye visibility |
|
||||||
| `satellites_overhead` | `satellites` | Satellites in a CelesTrak group above a place or point now, highest first |
|
| `satellites_overhead` | `satellites` | Satellites in a CelesTrak group above a place or point now, highest first |
|
||||||
| `find_cctv_cameras` | `cctv` | Public cameras in an area, nearest first |
|
| `find_cctv_cameras` | `cctv` | Public cameras in an area, nearest first |
|
||||||
@@ -317,5 +299,5 @@ and the development server's internal routes. See SECURITY.md.
|
|||||||
| `situation_brief` | `weather` | Weather, earthquakes, fires, aircraft, ships and cyclones for an area, by section |
|
| `situation_brief` | `weather` | Weather, earthquakes, fires, aircraft, ships and cyclones for an area, by section |
|
||||||
| `military_awareness` | `military` | Military and other aircraft, ships and military installations within 250 km of a point, by section |
|
| `military_awareness` | `military` | Military and other aircraft, ships and military installations within 250 km of a point, by section |
|
||||||
| `get_hud_caption` | `weather`, `summary` | The app's heads-up display caption for an area |
|
| `get_hud_caption` | `weather`, `summary` | The app's heads-up display caption for an area |
|
||||||
| `show_in_gods_eye_view` | `app` | A view in God's Eye View: the live panel in clients with MCP Apps, and a link everywhere; takes another answer's view or an area or camera, layers, style, map, marks, something to follow, or street-level photos (Street View or Mapillary) at a place |
|
| `show_in_gods_eye_view` | `app` | A view in God's Eye View: the live panel in clients with MCP Apps, and a link everywhere; takes another answer's view or an area or camera, layers, style, map, marks and something to follow |
|
||||||
| `panel_request` | `app` | Panel only: loads a path from the app's server for the God's Eye View panel |
|
| `panel_request` | `app` | Panel only: loads a path from the app's server for the God's Eye View panel |
|
||||||
+4
-5
@@ -1,6 +1,6 @@
|
|||||||
# Translating God's Eye View
|
# Translating God's Eye View
|
||||||
|
|
||||||
Shipped languages: **English** (source and fallback), **Arabic** (`ar`), **German** (`de`), **Spanish** (`es`), **French** (`fr`), **Hebrew** (`he`), **Italian** (`it`), **Japanese** (`ja`), **Brazilian Portuguese** (`pt`), **Russian** (`ru`), **Swedish** (`sv`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
|
Shipped languages: **English** (source and fallback), **Arabic** (`ar`), **German** (`de`), **Spanish** (`es`), **French** (`fr`), **Italian** (`it`), **Japanese** (`ja`), **Brazilian Portuguese** (`pt`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
@@ -45,10 +45,9 @@ Edit `src/i18n/locales/<code>.json` and run `npm run i18n`. To see your change,
|
|||||||
1. Create `src/i18n/locales/<code>.json`, fill it from `npm run i18n -- --missing <code>` (start from `{}`), and translate every entry.
|
1. Create `src/i18n/locales/<code>.json`, fill it from `npm run i18n -- --missing <code>` (start from `{}`), and translate every entry.
|
||||||
2. Import it in `src/i18n/index.js` and add it to `CATALOGS`.
|
2. Import it in `src/i18n/index.js` and add it to `CATALOGS`.
|
||||||
3. Add the code to `LOCALES` and its own-language name to `LOCALE_NAMES` in `src/i18n/locale.js`. Right-to-left languages also go in `RTL_LOCALES`; the chrome mirrors through logical CSS properties (`inset-inline-*`, `margin-inline-*`), and `src/ui/styles/rtl.css` holds what those cannot express (see the Arabic notes). If the language should keep Western digits, map it in `INTL_TAGS` in `locale.js`.
|
3. Add the code to `LOCALES` and its own-language name to `LOCALE_NAMES` in `src/i18n/locale.js`. Right-to-left languages also go in `RTL_LOCALES`; the chrome mirrors through logical CSS properties (`inset-inline-*`, `margin-inline-*`), and `src/ui/styles/rtl.css` holds what those cannot express (see the Arabic notes). If the language should keep Western digits, map it in `INTL_TAGS` in `locale.js`.
|
||||||
4. Add `src/i18n/locales/<code>.json` next to `ru.json` in `scripts/package-boundaries.json` and in `scripts/format-scope.json`.
|
4. Add `src/i18n/locales/<code>.json` next to `ru.json` in `scripts/package-boundaries.json`.
|
||||||
5. Write `docs/translations/<code>.md`: glossary, plural forms, units and style (copy the structure of `ru.md`).
|
5. Write `docs/translations/<code>.md`: glossary, plural forms, units and style (copy the structure of `ru.md`).
|
||||||
6. If the language is not written in Latin letters, also add it to `NON_LATIN_LOCALES` in `src/hudSummaryResponse.js` — that guard falls back to the translated text when a summary provider answers in English.
|
6. Run `npm run i18n` and `npm test`, then check the panels in the app at narrow widths.
|
||||||
7. Run `npm run i18n` and `npm test`, then check the panels in the app at narrow widths.
|
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
@@ -64,4 +63,4 @@ These are contracts, not copy: layer ids and registry keys, status enums and oth
|
|||||||
|
|
||||||
## Language notes
|
## Language notes
|
||||||
|
|
||||||
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Arabic](./translations/ar.md), [German](./translations/de.md), [Spanish](./translations/es.md), [French](./translations/fr.md), [Hebrew](./translations/he.md), [Italian](./translations/it.md), [Japanese](./translations/ja.md), [Brazilian Portuguese](./translations/pt.md), [Russian](./translations/ru.md), [Swedish](./translations/sv.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one.
|
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Arabic](./translations/ar.md), [German](./translations/de.md), [Spanish](./translations/es.md), [French](./translations/fr.md), [Italian](./translations/it.md), [Japanese](./translations/ja.md), [Brazilian Portuguese](./translations/pt.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one.
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# Hebrew (`he`)
|
|
||||||
|
|
||||||
Catalog: `src/i18n/locales/he.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
|
||||||
|
|
||||||
## Style
|
|
||||||
|
|
||||||
- Address the user with a masculine imperative, Google style: הסתר, הצג, נסה שוב, התקרב. Menu items and tooltips take the noun form: הסרת {name}, הרחבת {name}, שינוי הצבע של {name}.
|
|
||||||
- Sentence case: Hebrew has no case forms, so UPPERCASE English is normal sentence style here; short HUD labels stay short, favor an abbreviation over a wrapped line.
|
|
||||||
- Units follow the catalog: ק"מ, מ׳, קמ"ש, שנ׳, דק׳, שע׳; an abbreviation always ends in the geresh (׳). Instrument readouts and formatted shared-unit labels keep the Latin units (km, NM, FT, KTS, MW, KM/S); catalog prose outside those readouts keeps ק"מ/מ׳. Codes like HDG, FOV, CAL, SRC, REC stay exactly as in English — see `same-as-english.json`.
|
|
||||||
|
|
||||||
## Digits and direction
|
|
||||||
|
|
||||||
- Western digits 0–9 only, like every catalog: "5 מטוסים", "26.08.2026". Dates in scene labels use dd.mm.yyyy.
|
|
||||||
- `{direction}` values are adverbs (שמאלה, ימינה, ישר); maneuver templates are written to place them after the verb: פנה {direction} אל {road}.
|
|
||||||
- Values inserted by t() are isolated (U+2068…U+2069); do not add manual RTL marks — write the sentence as natural Hebrew and let isolation do the rest.
|
|
||||||
|
|
||||||
## Plurals
|
|
||||||
|
|
||||||
A `tn()` entry needs three forms under `Intl.PluralRules('he')`:
|
|
||||||
|
|
||||||
| Form | Counts | Example |
|
|
||||||
| ------- | ---------------------------- | -------------- |
|
|
||||||
| `one` | 1 and fractions below 1 | 1 מצלמה · 0.5 |
|
|
||||||
| `two` | 2 | 2 מצלמות |
|
|
||||||
| `other` | 0, 3, 10, 100, integers… | 5 מצלמות |
|
|
||||||
|
|
||||||
With {count} shown as a digit, `two` and `other` share the plural noun (2 מצלמות, 5 מצלמות); `one` takes the singular (1 מצלמה). For constructs that resist agreement with a noun (אין, הכול, זמין), phrase around the number: "{count} תחנות מדווחות" is fine, but prefer "ממתין: {count}" over a mismatched genitive chain.
|
|
||||||
|
|
||||||
## Glossary
|
|
||||||
|
|
||||||
| English | עברית |
|
|
||||||
| ---------------------------------------------- | ----------------------------------------------------- |
|
|
||||||
| layer / data layer | שכבה / שכבת נתונים |
|
|
||||||
| feed | פיד |
|
|
||||||
| source / data source / Provider Settings | מקור / מקור נתונים / הגדרות הספקים |
|
|
||||||
| LIVE / live data / LIVE TRACK | חי / נתונים חיים / מעקב חי |
|
|
||||||
| SIMULATED | מדומה |
|
|
||||||
| stale / degraded / unavailable | מיושן / מופחת / לא זמין |
|
|
||||||
| key (API key) / POWER UP | מפתח / שדרוג |
|
|
||||||
| globe / map / map source / basemap | גלובוס / מפה / מקור מפה / מפת בסיס |
|
|
||||||
| aircraft / flight / military flight | מטוס (כלי טיס in class lists) / טיסה / טיסה צבאית |
|
|
||||||
| vessel / ship / ships | אונייה / אוניות |
|
|
||||||
| satellite | לוויין |
|
|
||||||
| launch (rocket) / space mission | שיגור / משימת חלל |
|
|
||||||
| contact / tracking | מגע / עקיבה |
|
|
||||||
| installation / site | מתקן / אתר |
|
|
||||||
| camera / CCTV camera / ALPR camera | מצלמה / מצלמת CCTV / מצלמת ALPR |
|
|
||||||
| earthquake / active fire / cyclone | רעידת אדמה / שריפה פעילה / ציקלון |
|
|
||||||
| scene / shot | סצנה / שוט |
|
|
||||||
| briefing / panel / dock | תדריך / פאנל / עגינה |
|
|
||||||
| visual preset / style | ערכת ראייה / סגנון |
|
|
||||||
| Scope (circular viewport mask) / keyhole | סקופ / חור המנעול |
|
|
||||||
| heading (compass °) / course | כיוון / מהלך |
|
|
||||||
| HDG / BRG / CRS / FOV / DEST / SPD / RANGE | בלי שינוי — קודי מכשיר |
|
|
||||||
| advisory (NHC/CPHC cyclone) | עדכון; Advisory center — מרכז הסערה לפי העדכון |
|
|
||||||
| Street View (Google) | Street View, בלי תרגום |
|
|
||||||
| Street Level / Street Level layer | Street Level, בלי תרגום (שם שכבה) |
|
|
||||||
| WHOT/BHOT, Ironbow, CRT, NVG, FLIR | בלי שינוי — קודים ושמות מצבים |
|
|
||||||
| PREV / NEXT | הקודם / הבא |
|
|
||||||
| anchor / data pack / asset bundle (scene) | עוגן / חבילת נתונים / חבילת נכסים |
|
|
||||||
| forecast (weather) | תחזית |
|
|
||||||
| witness / geolocated source | עד ראייה / מקור מאוכן |
|
|
||||||
| contained (wildland fire) / FRP | הכלה / FRP, בלי שינוי |
|
|
||||||
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
# Swedish (`sv`)
|
|
||||||
|
|
||||||
Catalog: `src/i18n/locales/sv.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
|
||||||
|
|
||||||
Translation source: [God's Eye View Sverige](https://github.com/Kajjan7610/Gods-view-sweden) by Kaj Obst, November 2026.
|
|
||||||
|
|
||||||
## Style
|
|
||||||
|
|
||||||
- Standard Swedish as in Swedish software and on Swedish authority sites (SMHI, Trafikverket, Polisen).
|
|
||||||
- Actions and hints use the imperative without a pronoun: «Försök igen», «Klicka på en kamera för att aktivera den», «Välj en tagning först», «Tillåt mikrofonen och försök igen». Avoid «du»/«ni»; "your" becomes «egen» or is left out: «Egen RTL-SDR-mottagare», «Stationen ser din IP-adress» only where a person must be addressed.
|
|
||||||
- Sentence case, not Title Case: "Live Flights" → «Liveflyg», "Provider Settings" → «Leverantörsinställningar». UPPERCASE English stays UPPERCASE: «LIVEDATA AV», NOT AN ALL-CLEAR → «INTE FARA ÖVER».
|
|
||||||
- Compounds are written as one word, with a hyphen after a name, code or abbreviation: «datalager», «väderrutor», «TomTom-nyckel», «AIS-fartyg», «Street View-reserv». Never split a compound with a space.
|
|
||||||
- Short labels, chips and HUD readouts stay short; use a standard abbreviation with a period over a wrapped line: RÄCKV., HAST., NEDERB., ÖVR., MEDD., FÖRST., KAL. «Jämför» is JFR, without a period.
|
|
||||||
- The avionics codes HDG, BRG, CRS, DEST, SPD, ALT, GS, TRK, V/S, FOV and SQUAWK stay in English, as in the cockpit. Words in a sentence are translated: heading → «kurs», bearing → «bäring», range → «räckvidd».
|
|
||||||
- A dash between clauses is a spaced en dash « – ». Quotes are ”…” (Swedish closing quotes on both sides).
|
|
||||||
- Status and waiting messages: «Väntar på uppdatering», «Laddar …». Reasons after a dash stay lower case: «Scenen sparades inte – projektet är större än 5 MiB».
|
|
||||||
- A layer named in a sentence takes its panel name in quotes: «Lagret ”Liveflyg” är inte aktiverat».
|
|
||||||
- Rolling windows say their length: LAST YEAR → «SENASTE 12 MÅN.», LAST MONTH → «SENASTE 30 DAGARNA».
|
|
||||||
- Keyboard keys: Mellanslag, Backsteg, Skift; Esc, Enter, Tab and letter keys stay.
|
|
||||||
- Decimal comma in fixed text: «M4,4», «28,2710° N». Thousands separator is a space: «1 000 km». Formatted numbers come from `Intl` and are already Swedish.
|
|
||||||
- Units: km, m, km/h, m/s, kn (knop), ft (fot), hPa, MW, h, min, s, d (dygn, in short readouts); in UPPERCASE text KM, KM/H, KN, FT, MIN, H, D. A space between number and unit, also before %: «{value} m», «{count} min», «50 %». `°` sticks to the number.
|
|
||||||
- "ago": «för {count} min sedan», short «{count} min sedan». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add «sedan» again.
|
|
||||||
- Compass points use O for öst and V for väst, as SMHI does: N, NNO, NO, ONO, O, OSO, SO, SSO, S, SSV, SV, VSV, V, VNV, NV, NNV; hemisphere letters N, S, O, V.
|
|
||||||
- Routing: «Sväng {direction} in på {road}», «Fortsätt {direction}», «Ta {exit} avfarten» with ordinals «2:a», «3:e».
|
|
||||||
|
|
||||||
## Plurals
|
|
||||||
|
|
||||||
A `tn()` entry needs two forms. `Intl.PluralRules('sv')` picks:
|
|
||||||
|
|
||||||
| Form | Counts | Example |
|
|
||||||
| ------- | --------------------------- | ----------- |
|
|
||||||
| `one` | 1 | 1 kamera |
|
|
||||||
| `other` | everything else: 0, 2, 1,5… | 3 kameror |
|
|
||||||
|
|
||||||
Abbreviated units and codes do not change: «{count} MEDD.».
|
|
||||||
|
|
||||||
## Glossary
|
|
||||||
|
|
||||||
| English | Svenska |
|
|
||||||
| ---------------------------------------------- | --------------------------------------------------------------- |
|
|
||||||
| layer / data layer | lager / datalager |
|
|
||||||
| feed / source / data source | flöde / källa / datakälla |
|
|
||||||
| LIVE (status) / live data | LIVE / livedata |
|
|
||||||
| SIMULATED | SIMULERAT |
|
|
||||||
| stale / degraded / unavailable | inaktuell / begränsad / otillgänglig |
|
|
||||||
| retry / retrying / retrying slowly | nytt försök / försöker igen / försöker igen med längre intervall |
|
|
||||||
| fallback | reserv |
|
|
||||||
| key (API key) / POWER UP / POWERED UP | nyckel / KOPPLA PÅ / PÅKOPPLAD |
|
|
||||||
| Provider Settings | Leverantörsinställningar |
|
|
||||||
| ON / OFF (state chips) | PÅ / AV |
|
|
||||||
| enable / disable | aktivera / inaktivera |
|
|
||||||
| globe / map / map source / basemap | glob / karta / kartkälla / bakgrundskarta |
|
|
||||||
| aircraft / flight / military flights | luftfartyg (short labels: flygplan) / flygning / militärflyg |
|
|
||||||
| vessel, ship | fartyg |
|
|
||||||
| satellite / shell (Starlink) | satellit / skal |
|
|
||||||
| launch (rocket) / launch site / space mission | uppskjutning / uppskjutningsplats / rymduppdrag |
|
|
||||||
| contact / tracking | kontakt / följning |
|
|
||||||
| installation / site | anläggning / plats |
|
|
||||||
| camera / CCTV camera | kamera / CCTV-kamera |
|
|
||||||
| earthquake / active fire / fire perimeter | jordbävning / aktiv brand / brandgräns |
|
|
||||||
| cyclone / advisory (NHC) / cone | cyklon / bulletin / kon |
|
|
||||||
| radio station / broadcast FM | radiostation / FM-radio |
|
|
||||||
| road / traffic flow | väg / trafikflöde |
|
|
||||||
| transit / bike share | kollektivtrafik / hyrcyklar |
|
|
||||||
| scene / shot / capture shot / camera move | scen / tagning / spara tagning / kameraåkning |
|
|
||||||
| anchor / data pack / bundle | ankare / datapaket / paket |
|
|
||||||
| briefing / panel / dock | genomgång / panel / docka |
|
|
||||||
| preset / visual preset | förval / visuellt förval |
|
|
||||||
| Scope (circular viewport mask) / keyhole | Scope / nyckelhål |
|
|
||||||
| overlay / detection overlay | överlägg / detekteringsöverlägg |
|
|
||||||
| Clean UI | Rent gränssnitt |
|
|
||||||
| Night vision / Thermal / Snow (styles) | Mörkerseende / Värmekamera / Snö |
|
|
||||||
| heading / altitude / ground speed | kurs / höjd / markfart |
|
|
||||||
| RANGE / PITCH / HGT (CCTV calibration chips) | RÄCKV. / LUTN. / HÖJD |
|
|
||||||
| PREV / NEXT / RESUME | FÖRRA / NÄSTA / FORTSÄTT |
|
|
||||||
| nearest (spatial) | närmaste |
|
|
||||||
| expand / collapse | fäll ut / fäll ihop |
|
|
||||||
| Street Level / Street View (Google) | Gatunivå / Street View |
|
|
||||||
| Recent Imagery | Senaste bilder |
|
|
||||||
| voice (control) / voice session | röststyrning / röstsession |
|
|
||||||
| click / tap / press a key | klicka / tryck / tryck på tangenten |
|
|
||||||
| card / label (on the map) / clear (layers) | kort / etikett / stäng av |
|
|
||||||
| frame (weather, video) / image | bildruta / bild |
|
|
||||||
| incident | händelse |
|
|
||||||
+2
-2
@@ -21,12 +21,12 @@
|
|||||||
<link rel="stylesheet" href="/style.css" />
|
<link rel="stylesheet" href="/style.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans+Hebrew:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&family=Noto+Sans+Arabic:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
<!-- Icon font subset to the glyphs this app renders. Keep `icon_names` in sync
|
<!-- Icon font subset to the glyphs this app renders. Keep `icon_names` in sync
|
||||||
with the sources: src/materialSymbolsSubset.test.mjs fails when a source
|
with the sources: src/materialSymbolsSubset.test.mjs fails when a source
|
||||||
names a glyph this list is missing, because a missing glyph does not draw
|
names a glyph this list is missing, because a missing glyph does not draw
|
||||||
a box — the ligature never forms and the element renders the literal word. -->
|
a box — the ligature never forms and the element renders the literal word. -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,bug_report,category,chevron_left,chevron_right,close,close_fullscreen,dark_mode,directions_boat,draw,east,eco,egg_alt,flare,flight,handyman,layers,layers_clear,light_mode,local_fire_department,location_on,more_horiz,movie,my_location,navigation,normal,on,open_in_full,partly_cloudy_day,pause,pentagon,pest_control,pets,play_arrow,public,question_mark,radar,radio,raven,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,set_meal,settings,skip_next,skip_previous,streetview,timeline,traffic,tune,umbrella,upload_file,videocam,view_in_ar,visibility,visibility_off,water_drop" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,bug_report,category,chevron_left,chevron_right,close,close_fullscreen,dark_mode,draw,east,eco,egg_alt,flare,flight,handyman,layers,layers_clear,light_mode,local_fire_department,location_on,more_horiz,movie,my_location,navigation,normal,on,open_in_full,partly_cloudy_day,pentagon,pest_control,pets,public,question_mark,radar,radio,raven,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,set_meal,settings,skip_next,skip_previous,streetview,timeline,tune,umbrella,upload_file,videocam,view_in_ar,visibility,visibility_off,water_drop" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="gev-view" data-gev-view="scene-chrome"></div>
|
<div class="gev-view" data-gev-view="scene-chrome"></div>
|
||||||
|
|||||||
Generated
-8
@@ -28,7 +28,6 @@
|
|||||||
"@happy-dom/global-registrator": "^20.14.6",
|
"@happy-dom/global-registrator": "^20.14.6",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@vue/test-utils": "^2.5.1",
|
"@vue/test-utils": "^2.5.1",
|
||||||
"flag-icons": "7.5.0",
|
|
||||||
"happy-dom": "^20.14.6",
|
"happy-dom": "^20.14.6",
|
||||||
"prettier": "3.9.6",
|
"prettier": "3.9.6",
|
||||||
"puppeteer": "^25.16.0",
|
"puppeteer": "^25.16.0",
|
||||||
@@ -2412,13 +2411,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flag-icons": {
|
|
||||||
"version": "7.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz",
|
|
||||||
"integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
|||||||
@@ -76,7 +76,6 @@
|
|||||||
"@happy-dom/global-registrator": "^20.14.6",
|
"@happy-dom/global-registrator": "^20.14.6",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@vue/test-utils": "^2.5.1",
|
"@vue/test-utils": "^2.5.1",
|
||||||
"flag-icons": "7.5.0",
|
|
||||||
"happy-dom": "^20.14.6",
|
"happy-dom": "^20.14.6",
|
||||||
"prettier": "3.9.6",
|
"prettier": "3.9.6",
|
||||||
"puppeteer": "^25.16.0",
|
"puppeteer": "^25.16.0",
|
||||||
@@ -91,7 +90,6 @@
|
|||||||
"./infrastructure/lod": "./src/data/localGeojsonLod.js",
|
"./infrastructure/lod": "./src/data/localGeojsonLod.js",
|
||||||
"./application": "./src/app/application.js",
|
"./application": "./src/app/application.js",
|
||||||
"./application/viewer": "./src/app/viewer.js",
|
"./application/viewer": "./src/app/viewer.js",
|
||||||
"./time": "./src/time/worldClock.js",
|
|
||||||
"./build/vite": {
|
"./build/vite": {
|
||||||
"node": "./build/vite.js"
|
"node": "./build/vite.js"
|
||||||
},
|
},
|
||||||
@@ -99,7 +97,6 @@
|
|||||||
"node": "./server/providers/live.js"
|
"node": "./server/providers/live.js"
|
||||||
},
|
},
|
||||||
"./sources/adsb-lol": "./src/data/adsbLolFallback.js",
|
"./sources/adsb-lol": "./src/data/adsbLolFallback.js",
|
||||||
"./sources/planespotters": "./src/data/planespottersPhoto.js",
|
|
||||||
"./server/providers/places": {
|
"./server/providers/places": {
|
||||||
"node": "./server/providers/places.js"
|
"node": "./server/providers/places.js"
|
||||||
},
|
},
|
||||||
@@ -138,9 +135,6 @@
|
|||||||
"./server/providers/regional": {
|
"./server/providers/regional": {
|
||||||
"node": "./server/providers/regional.js"
|
"node": "./server/providers/regional.js"
|
||||||
},
|
},
|
||||||
"./server/providers/news": {
|
|
||||||
"node": "./server/providers/news.js"
|
|
||||||
},
|
|
||||||
"./server/providers/openai": {
|
"./server/providers/openai": {
|
||||||
"node": "./server/providers/openai.js"
|
"node": "./server/providers/openai.js"
|
||||||
},
|
},
|
||||||
@@ -264,7 +258,6 @@
|
|||||||
"./layers/gnss/source": "./src/layers/gnss/source.js",
|
"./layers/gnss/source": "./src/layers/gnss/source.js",
|
||||||
"./layers/ghostmaps/source": "./src/layers/ghostMaps/source.js",
|
"./layers/ghostmaps/source": "./src/layers/ghostMaps/source.js",
|
||||||
"./layers/drone-airspace/source": "./src/layers/droneAirspace/source.js",
|
"./layers/drone-airspace/source": "./src/layers/droneAirspace/source.js",
|
||||||
"./layers/news/source": "./src/layers/news/source.js",
|
|
||||||
"./layers/firms/source": "./src/layers/firms/source.js",
|
"./layers/firms/source": "./src/layers/firms/source.js",
|
||||||
"./layers/installations/source": "./src/layers/installations/source.js",
|
"./layers/installations/source": "./src/layers/installations/source.js",
|
||||||
"./layers/launches/source": "./src/layers/launches/source.js",
|
"./layers/launches/source": "./src/layers/launches/source.js",
|
||||||
@@ -285,11 +278,6 @@
|
|||||||
"./layers/transit/standalone": "./src/data/transit.js",
|
"./layers/transit/standalone": "./src/data/transit.js",
|
||||||
"./layers/transit/source": "./src/layers/transit/source.js",
|
"./layers/transit/source": "./src/layers/transit/source.js",
|
||||||
"./sources/transit-service": "./src/sources/transitService.js",
|
"./sources/transit-service": "./src/sources/transitService.js",
|
||||||
"./server/providers/rail": {
|
|
||||||
"node": "./server/providers/rail.js"
|
|
||||||
},
|
|
||||||
"./sources/rail-service": "./src/sources/railService.js",
|
|
||||||
"./layers/rail/source": "./src/layers/rail/source.js",
|
|
||||||
"./sources/map-features": "./src/sources/overpassFeatures.js",
|
"./sources/map-features": "./src/sources/overpassFeatures.js",
|
||||||
"./server/providers/mapillary": {
|
"./server/providers/mapillary": {
|
||||||
"node": "./server/providers/mapillary.js"
|
"node": "./server/providers/mapillary.js"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{"source":"Revos addons/Qc cams/quebec_all_cameras.geojson","count":1120,"cameras":[{"id":"4103","kind":"traffic","name":"Route 117 à la jonction de la route 101 et à la hauteur du carrefour giratoire (Rouyn-Noranda)","nameEn":"Junction of route 117 and route 101 and at roundabout (Rouyn-Noranda)","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Abitibi-Témiscamingue","lat":48.26461,"lon":-79.01808,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=4103","notes":"Live since 2025/11/26"},{"id":"3820","kind":"traffic","name":"Aut. 20 à la hauteur de la montée Industrielle-et-Commerciale","nameEn":"Aut. 20 at montée Industrielle-et-Commerciale","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":48.43993,"lon":-68.47784,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3820","notes":"Live since 2018/12/12"},{"id":"3821","kind":"traffic","name":"Montée Industrielle-et-Commerciale à Rimouski","nameEn":"Montée Industrielle-et-Commerciale at Rimouski","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":48.44639,"lon":-68.48528,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3821","notes":"Live since 2018/12/12"},{"id":"3822","kind":"traffic","name":"Route 132 à la hauteur du Bic à la jonction de l'Aut. 20","nameEn":"Route 132 at Bic at the junction of Aut. 20","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":48.3775,"lon":-68.68694,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3822","notes":"Live since 2018/12/12"},{"id":"3834","kind":"traffic","name":"Route 132 à la hauteur de Saint-Fabien","nameEn":"Route 132 at Saint-Fabien","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":48.32927,"lon":-68.80262,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3834","notes":"Live since 2019/02/07"},{"id":"3835","kind":"traffic","name":"Route 132 à la hauteur de Cap-Chat (secteur Capucins)","nameEn":"Route 132 at Cap-Chat (Capucins sector)","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":49.04153,"lon":-66.86847,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3835","notes":"Live since 2019/02/07"},{"id":"3856","kind":"traffic","name":"Route 132 à l'est de Gros-Morne","nameEn":"Route 132, east of Gros-Morne","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":49.25302,"lon":-65.48355,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3856","notes":"Live since 2019/12/06"},{"id":"3836","kind":"traffic","name":"Route 132 à la hauteur de la baie du Petit Pabos","nameEn":"Route 132 at baie du Petit Pabos","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":48.37216,"lon":-64.58866,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3836","notes":"Live since 2019/02/07"},{"id":"3837","kind":"traffic","name":"Route 132 à la hauteur de Bonaventure","nameEn":"Route 132 at Bonaventure","category":"Highway traffic","operator":"Ministere des Transports et de la Mobilite durable (MTQ) - Quebec 511","region":"Bas-Saint-Laurent—Gaspésie—Îles-de-la-Madeleine","lat":48.03448,"lon":-65.46953,"accuracyM":0,"positionSource":"MTQ georeferenced point (WFS, EPSG:4326, 5 decimals)","viewerUrl":"https://www.quebec511.info/Carte/Fenetres/FenetreVideo.html?id=3837","Line truncated
|
|
||||||
@@ -7,16 +7,13 @@
|
|||||||
* Natural Earth 10m admin-1 states and provinces (public domain).
|
* Natural Earth 10m admin-1 states and provinces (public domain).
|
||||||
* src/data/local_data/us_census_counties/counties.json
|
* src/data/local_data/us_census_counties/counties.json
|
||||||
* US Census Bureau cartographic boundary counties (public domain).
|
* US Census Bureau cartographic boundary counties (public domain).
|
||||||
* src/data/local_data/natural_earth/populated_places.json
|
|
||||||
* Natural Earth 10m populated places as points, for the orbital view's
|
|
||||||
* "near <city>" (`src/data/placeBelow.js`; public domain).
|
|
||||||
*
|
*
|
||||||
* Both sources are pinned by URL and SHA-256, so a rerun reproduces the packs
|
* Both sources are pinned by URL and SHA-256, so a rerun reproduces the packs
|
||||||
* byte for byte. Downloads are cached outside the repository (default: the
|
* byte for byte. Downloads are cached outside the repository (default: the
|
||||||
* system temp directory) and are never committed.
|
* system temp directory) and are never committed.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* node scripts/build-admin-packs.mjs [--cache <dir>] [--only countries|admin1|counties|places]
|
* node scripts/build-admin-packs.mjs [--cache <dir>] [--only countries|admin1|counties]
|
||||||
*
|
*
|
||||||
* The provenance READMEs beside each pack record these parameters.
|
* The provenance READMEs beside each pack record these parameters.
|
||||||
*/
|
*/
|
||||||
@@ -104,9 +101,6 @@ export const PARAMS = Object.freeze({
|
|||||||
elsewhereMinPopulation: 50_000,
|
elsewhereMinPopulation: 50_000,
|
||||||
cityStateMaxKm2: 1_000,
|
cityStateMaxKm2: 1_000,
|
||||||
},
|
},
|
||||||
// Every populated place, kept as a point: name, position and the two
|
|
||||||
// prominence ranks the orbital view picks "near <city>" by.
|
|
||||||
places: { decimals: 3 },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@@ -694,47 +688,6 @@ async function buildAdmin1() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildPlaces() {
|
|
||||||
const params = PARAMS.places;
|
|
||||||
const source = await fetchPinned('places');
|
|
||||||
const factor = 10 ** params.decimals;
|
|
||||||
const round = (value) => Math.round(value * factor) / factor;
|
|
||||||
const places = JSON.parse(source.bytes.toString('utf8'))
|
|
||||||
.features.map((f) => f.properties)
|
|
||||||
.filter(
|
|
||||||
(p) =>
|
|
||||||
p.name &&
|
|
||||||
Number.isFinite(p.latitude) &&
|
|
||||||
Number.isFinite(p.longitude) &&
|
|
||||||
Number.isFinite(p.scalerank),
|
|
||||||
)
|
|
||||||
.map((p) => [
|
|
||||||
p.name,
|
|
||||||
round(p.latitude),
|
|
||||||
round(p.longitude),
|
|
||||||
p.scalerank,
|
|
||||||
Number.isFinite(p.rank_max) ? p.rank_max : 0,
|
|
||||||
])
|
|
||||||
.sort((a, b) => a[3] - b[3] || b[4] - a[4] || a[0].localeCompare(b[0]));
|
|
||||||
return {
|
|
||||||
meta: {
|
|
||||||
title: 'Natural Earth populated places',
|
|
||||||
source: 'ne_10m_populated_places_simple',
|
|
||||||
url: SOURCES.places.url,
|
|
||||||
sha256: source.digest,
|
|
||||||
commit: NE_COMMIT,
|
|
||||||
license:
|
|
||||||
'Public domain (https://www.naturalearthdata.com/about/terms-of-use/)',
|
|
||||||
schema:
|
|
||||||
'places[]: [name, latitude, longitude, scalerank (0 most prominent .. 10), rank_max (population class, 0..14; higher is larger)]',
|
|
||||||
decimals: params.decimals,
|
|
||||||
curation: params,
|
|
||||||
script: 'scripts/build-admin-packs.mjs',
|
|
||||||
},
|
|
||||||
places,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildCounties() {
|
async function buildCounties() {
|
||||||
const params = PARAMS.counties;
|
const params = PARAMS.counties;
|
||||||
const source = await fetchPinned('counties');
|
const source = await fetchPinned('counties');
|
||||||
@@ -815,13 +768,4 @@ if (isMain) {
|
|||||||
'src/data/local_data/us_census_counties/counties.json',
|
'src/data/local_data/us_census_counties/counties.json',
|
||||||
await buildCounties(),
|
await buildCounties(),
|
||||||
);
|
);
|
||||||
if (!only || only === 'places') {
|
|
||||||
const relative = 'src/data/local_data/natural_earth/populated_places.json';
|
|
||||||
const pack = await buildPlaces();
|
|
||||||
const text = JSON.stringify(pack) + '\n';
|
|
||||||
await writeFile(path.join(ROOT, relative), text);
|
|
||||||
console.log(
|
|
||||||
`${relative}: ${pack.places.length} places, ${Buffer.byteLength(text)} bytes`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Build the owner flag sheet the orbital Cockpit marks a satellite's owner
|
|
||||||
* country with (`src/ui/ownerFlag.js`): every flag in OWNER_COUNTRY_CODES
|
|
||||||
* (`src/layers/satellites/classes.js`), drawn from the flag-icons 4:3 SVGs
|
|
||||||
* at twice the readout's 20 × 15 px, in one PNG the app loads at startup.
|
|
||||||
*
|
|
||||||
* src/data/local_data/owner_flags/owner_flags.png the sheet
|
|
||||||
* src/ui/ownerFlagSheet.js its layout (generated)
|
|
||||||
*
|
|
||||||
* Flag i (in the sorted code list) sits at column i % COLUMNS, row
|
|
||||||
* floor(i / COLUMNS), each cell followed by a GUTTER of transparent pixels. A rerun with the same flag-icons and sharp versions
|
|
||||||
* reproduces both files.
|
|
||||||
*
|
|
||||||
* Usage: node scripts/build-owner-flags.mjs
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { readFile, writeFile } from 'node:fs/promises';
|
|
||||||
import { createRequire } from 'node:module';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import * as prettier from 'prettier';
|
|
||||||
import sharp from 'sharp';
|
|
||||||
import { OWNER_COUNTRY_CODES } from '../src/layers/satellites/classes.js';
|
|
||||||
|
|
||||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
const FLAGS_DIR = path.join(
|
|
||||||
path.dirname(require.resolve('flag-icons/package.json')),
|
|
||||||
'flags/4x3',
|
|
||||||
);
|
|
||||||
const FLAG_ICONS_VERSION = require('flag-icons/package.json').version;
|
|
||||||
|
|
||||||
/** One flag's cell, CSS pixels × 2. */
|
|
||||||
export const CELL = Object.freeze({ width: 40, height: 30 });
|
|
||||||
/**
|
|
||||||
* Transparent sheet pixels after each flag, so a browser smoothing a scaled
|
|
||||||
* flag (a 125% display) never reaches into its neighbour.
|
|
||||||
*/
|
|
||||||
export const GUTTER = 2;
|
|
||||||
export const COLUMNS = 16;
|
|
||||||
|
|
||||||
const SHEET = path.join(
|
|
||||||
ROOT,
|
|
||||||
'src/data/local_data/owner_flags/owner_flags.png',
|
|
||||||
);
|
|
||||||
const LAYOUT = path.join(ROOT, 'src/ui/ownerFlagSheet.js');
|
|
||||||
|
|
||||||
async function flagCell(code) {
|
|
||||||
const svg = await readFile(path.join(FLAGS_DIR, `${code.toLowerCase()}.svg`));
|
|
||||||
return sharp(svg, { density: 300 })
|
|
||||||
.resize(CELL.width, CELL.height, { fit: 'fill' })
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
const codes = [...OWNER_COUNTRY_CODES];
|
|
||||||
const rows = Math.ceil(codes.length / COLUMNS);
|
|
||||||
const pitchX = CELL.width + GUTTER;
|
|
||||||
const pitchY = CELL.height + GUTTER;
|
|
||||||
const cells = await Promise.all(codes.map(flagCell));
|
|
||||||
const sheet = await sharp({
|
|
||||||
create: {
|
|
||||||
width: COLUMNS * pitchX,
|
|
||||||
height: rows * pitchY,
|
|
||||||
channels: 4,
|
|
||||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.composite(
|
|
||||||
cells.map((input, i) => ({
|
|
||||||
input,
|
|
||||||
left: (i % COLUMNS) * pitchX,
|
|
||||||
top: Math.floor(i / COLUMNS) * pitchY,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.png({ compressionLevel: 9, palette: true, quality: 100, effort: 10 })
|
|
||||||
.toBuffer();
|
|
||||||
await writeFile(SHEET, sheet);
|
|
||||||
|
|
||||||
const layout = `// Generated by scripts/build-owner-flags.mjs from flag-icons ${FLAG_ICONS_VERSION}; do not edit.
|
|
||||||
/** The owner flag sheet's layout: flag i at column i % columns, row i / columns. */
|
|
||||||
export const OWNER_FLAG_SHEET = Object.freeze({
|
|
||||||
cellWidthPx: ${CELL.width},
|
|
||||||
cellHeightPx: ${CELL.height},
|
|
||||||
gutterPx: ${GUTTER},
|
|
||||||
columns: ${COLUMNS},
|
|
||||||
rows: ${rows},
|
|
||||||
codes: Object.freeze(${JSON.stringify(codes)}),
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
await writeFile(
|
|
||||||
LAYOUT,
|
|
||||||
await prettier.format(layout, {
|
|
||||||
...(await prettier.resolveConfig(LAYOUT)),
|
|
||||||
filepath: LAYOUT,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`Wrote ${codes.length} flags (${COLUMNS} × ${rows}), ${(sheet.length / 1024).toFixed(1)} KB.`,
|
|
||||||
);
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,118 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
'''Maintainer-only UCS Satellite Database pack build (openpyxl).
|
|
||||||
|
|
||||||
Reads the UCS Excel release and writes the compact pack the satellites layer
|
|
||||||
loads: one row per satellite, keyed by NORAD number, with string-table indexes
|
|
||||||
for the repeated text columns. Starlink rows are left out: the layer files
|
|
||||||
every STARLINK object under its own class and operator without them.
|
|
||||||
|
|
||||||
Usage: python scripts/build-ucs-satellites.py "<path>/UCS-Satellite-Database 5-1-2023.xlsx"
|
|
||||||
'''
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
import re
|
|
||||||
|
|
||||||
import openpyxl
|
|
||||||
|
|
||||||
LOG = logging.getLogger('build-ucs-satellites')
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent.parent
|
|
||||||
PACK = ROOT / 'src/data/local_data/ucs_satellites'
|
|
||||||
VERSION = 'UCS-Satellite-Database 5-1-2023'
|
|
||||||
DATA_CURRENT_TO = '2023-05-01'
|
|
||||||
URL = 'https://www.ucsusa.org/satellite_database'
|
|
||||||
|
|
||||||
# Output column -> spreadsheet header.
|
|
||||||
COLUMNS = {
|
|
||||||
'operator': 'Operator/Owner',
|
|
||||||
'operatorCountry': 'Country of Operator/Owner',
|
|
||||||
'purpose': 'Purpose',
|
|
||||||
}
|
|
||||||
STARLINK = re.compile(r'\bstarlink\b', re.I)
|
|
||||||
|
|
||||||
|
|
||||||
def clean(value: object) -> str:
|
|
||||||
'''One cell as tidy text; UCS's placeholders for nothing become ''.'''
|
|
||||||
text = re.sub(r'\s+', ' ', str(value or '')).strip()
|
|
||||||
return '' if text.lower() in ('', 'none', 'n/a') else text
|
|
||||||
|
|
||||||
|
|
||||||
def build(xlsx: Path) -> dict:
|
|
||||||
'''Read the release's first sheet into the pack the layer loads.'''
|
|
||||||
sheet = openpyxl.load_workbook(xlsx, read_only=True, data_only=True).worksheets[0]
|
|
||||||
rows = sheet.iter_rows(values_only=True)
|
|
||||||
header = {str(h).strip(): i for i, h in enumerate(next(rows)) if h}
|
|
||||||
names = [header['Current Official Name of Satellite'], header['Name of Satellite, Alternate Names']]
|
|
||||||
norad_col = header['NORAD Number']
|
|
||||||
tables = {key: [''] for key in COLUMNS}
|
|
||||||
index = {key: {'': 0} for key in COLUMNS}
|
|
||||||
records = {}
|
|
||||||
source_rows = skipped_starlink = invalid_norad = 0
|
|
||||||
for row in rows:
|
|
||||||
if not any(row):
|
|
||||||
continue
|
|
||||||
source_rows += 1
|
|
||||||
if any(STARLINK.search(clean(row[i])) for i in names):
|
|
||||||
skipped_starlink += 1
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
norad = int(float(row[norad_col]))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
invalid_norad += 1
|
|
||||||
continue
|
|
||||||
if norad <= 0:
|
|
||||||
invalid_norad += 1
|
|
||||||
continue
|
|
||||||
record = [norad]
|
|
||||||
for key, column in COLUMNS.items():
|
|
||||||
text = clean(row[header[column]])
|
|
||||||
if text not in index[key]:
|
|
||||||
index[key][text] = len(tables[key])
|
|
||||||
tables[key].append(text)
|
|
||||||
record.append(index[key][text])
|
|
||||||
# A duplicate NORAD number keeps the first row, as UCS lists it.
|
|
||||||
records.setdefault(norad, record)
|
|
||||||
ordered = [records[n] for n in sorted(records)]
|
|
||||||
return {
|
|
||||||
'meta': {
|
|
||||||
'source': 'Union of Concerned Scientists Satellite Database',
|
|
||||||
'version': VERSION,
|
|
||||||
'url': URL,
|
|
||||||
'dataCurrentTo': DATA_CURRENT_TO,
|
|
||||||
'license': 'Free, unrestricted use; acknowledgement requested (UCS database manual, section 3)',
|
|
||||||
'curation': {
|
|
||||||
'sourceRows': source_rows,
|
|
||||||
'starlinkRowsDropped': skipped_starlink,
|
|
||||||
'invalidNoradDropped': invalid_norad,
|
|
||||||
'duplicateNoradDropped': source_rows - skipped_starlink - invalid_norad - len(ordered),
|
|
||||||
'columns': COLUMNS,
|
|
||||||
},
|
|
||||||
'recordCount': len(ordered),
|
|
||||||
},
|
|
||||||
'fields': ['noradId', *COLUMNS],
|
|
||||||
'strings': tables,
|
|
||||||
'records': ordered,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
'''Build the pack from the release given on the command line.'''
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
|
||||||
parser = argparse.ArgumentParser(description='Build the UCS satellite pack from the UCS Excel release.')
|
|
||||||
parser.add_argument('xlsx', type=Path, help='UCS-Satellite-Database <version>.xlsx')
|
|
||||||
pack = build(parser.parse_args().xlsx)
|
|
||||||
PACK.mkdir(parents=True, exist_ok=True)
|
|
||||||
out = PACK / 'ucs_satellites.json'
|
|
||||||
# LF on every platform: the repository stores text with LF endings.
|
|
||||||
out.write_text(
|
|
||||||
json.dumps(pack, ensure_ascii=False, separators=(',', ':')) + '\n',
|
|
||||||
encoding='utf-8',
|
|
||||||
newline='\n',
|
|
||||||
)
|
|
||||||
LOG.info('%s: %d records, %d bytes', out.relative_to(ROOT), pack['meta']['recordCount'], out.stat().st_size)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -44,7 +44,7 @@ CCTV_NSW_ENABLED="${CCTV_NSW_ENABLED:-1}"
|
|||||||
CCTV_NSW_MAX_SOURCES="${CCTV_NSW_MAX_SOURCES:-250}"
|
CCTV_NSW_MAX_SOURCES="${CCTV_NSW_MAX_SOURCES:-250}"
|
||||||
CCTV_CALGARY_ENABLED="${CCTV_CALGARY_ENABLED:-1}"
|
CCTV_CALGARY_ENABLED="${CCTV_CALGARY_ENABLED:-1}"
|
||||||
CCTV_CALGARY_MAX_SOURCES="${CCTV_CALGARY_MAX_SOURCES:-220}"
|
CCTV_CALGARY_MAX_SOURCES="${CCTV_CALGARY_MAX_SOURCES:-220}"
|
||||||
CCTV_MAX_SOURCES="${CCTV_MAX_SOURCES:-7350}"
|
CCTV_MAX_SOURCES="${CCTV_MAX_SOURCES:-4000}"
|
||||||
|
|
||||||
# Capture which provider credentials genuinely came from the parent shell
|
# Capture which provider credentials genuinely came from the parent shell
|
||||||
# before this launcher resolves dotenv and Keychain fallbacks. Only names are
|
# before this launcher resolves dotenv and Keychain fallbacks. Only names are
|
||||||
|
|||||||
@@ -181,18 +181,6 @@ export function answerMapillaryRequest({ method, url, images }) {
|
|||||||
: [];
|
: [];
|
||||||
return { ...json(200, { data }), known: true };
|
return { ...json(200, { data }), known: true };
|
||||||
}
|
}
|
||||||
if (q.has('lat') && q.has('lng')) {
|
|
||||||
const at = { lat: Number(q.get('lat')), lon: Number(q.get('lng')) };
|
|
||||||
const radius = Math.min(50, Number(q.get('radius')) || 50);
|
|
||||||
const limit = Number(q.get('limit')) || 10;
|
|
||||||
// In no particular order, as the real API answers.
|
|
||||||
const data = photos
|
|
||||||
.filter((image) => metresApart(at, image) <= radius)
|
|
||||||
.reverse()
|
|
||||||
.slice(0, limit)
|
|
||||||
.map((image) => withFields(imageRecord(image), fields));
|
|
||||||
return { ...json(200, { data }), known: true };
|
|
||||||
}
|
|
||||||
if (q.has('s2')) return { ...json(200, { data: [] }), known: true };
|
if (q.has('s2')) return { ...json(200, { data: [] }), known: true };
|
||||||
return {
|
return {
|
||||||
...json(400, { error: 'unsupported images query' }),
|
...json(400, { error: 'unsupported images query' }),
|
||||||
|
|||||||
@@ -112,8 +112,6 @@
|
|||||||
"src/data/terrainHeightsProxy.test.mjs",
|
"src/data/terrainHeightsProxy.test.mjs",
|
||||||
"src/data/tomtomTiles.js",
|
"src/data/tomtomTiles.js",
|
||||||
"src/data/tomtomTiles.test.mjs",
|
"src/data/tomtomTiles.test.mjs",
|
||||||
"src/data/tileBudget.js",
|
|
||||||
"src/data/tileBudget.test.mjs",
|
|
||||||
"src/data/firmsCsv.js",
|
"src/data/firmsCsv.js",
|
||||||
"src/data/firmsCsv.test.mjs",
|
"src/data/firmsCsv.test.mjs",
|
||||||
"src/tooling/environmentProviders.test.mjs",
|
"src/tooling/environmentProviders.test.mjs",
|
||||||
@@ -330,12 +328,6 @@
|
|||||||
"src/ui/styles/responsive.css",
|
"src/ui/styles/responsive.css",
|
||||||
"src/ui/styles/voice-cost.css",
|
"src/ui/styles/voice-cost.css",
|
||||||
"src/ui/styles/cockpit.css",
|
"src/ui/styles/cockpit.css",
|
||||||
"src/ui/styles/cockpitBridge.css",
|
|
||||||
"src/ui/styles/cockpitRadar.css",
|
|
||||||
"src/ui/styles/cockpitOrbital.css",
|
|
||||||
"src/layers/satellites/sensors.js",
|
|
||||||
"src/layers/satellites/sensors.test.mjs",
|
|
||||||
"src/data/imagingSensors.js",
|
|
||||||
"src/ui/styles/command-dock-compact.css",
|
"src/ui/styles/command-dock-compact.css",
|
||||||
"src/app/stateChannel.js",
|
"src/app/stateChannel.js",
|
||||||
"src/app/stateChannel.test.mjs",
|
"src/app/stateChannel.test.mjs",
|
||||||
@@ -355,7 +347,6 @@
|
|||||||
"src/layers/flights/tracking.js",
|
"src/layers/flights/tracking.js",
|
||||||
"src/layers/flights/rendering.js",
|
"src/layers/flights/rendering.js",
|
||||||
"src/layers/flights/testing.js",
|
"src/layers/flights/testing.js",
|
||||||
"src/layers/flights/hiddenContactRotation.test.mjs",
|
|
||||||
"src/layers/flights/evidence.js",
|
"src/layers/flights/evidence.js",
|
||||||
"src/layers/flights/ingestion.js",
|
"src/layers/flights/ingestion.js",
|
||||||
"src/layers/flights/queries.js",
|
"src/layers/flights/queries.js",
|
||||||
@@ -483,7 +474,6 @@
|
|||||||
"src/layers/satellites/stale.test.mjs",
|
"src/layers/satellites/stale.test.mjs",
|
||||||
"src/layers/launches/source.test.mjs",
|
"src/layers/launches/source.test.mjs",
|
||||||
"src/layers/launches/ingestion.test.mjs",
|
"src/layers/launches/ingestion.test.mjs",
|
||||||
"src/layers/launches/failedEntry.test.mjs",
|
|
||||||
"src/layers/installations/lifecycle.js",
|
"src/layers/installations/lifecycle.js",
|
||||||
"src/layers/installations/viewport.js",
|
"src/layers/installations/viewport.js",
|
||||||
"src/layers/installations/controls.js",
|
"src/layers/installations/controls.js",
|
||||||
@@ -591,7 +581,6 @@
|
|||||||
"src/data/cctvHk.test.mjs",
|
"src/data/cctvHk.test.mjs",
|
||||||
"src/data/cctvVegvesen.test.mjs",
|
"src/data/cctvVegvesen.test.mjs",
|
||||||
"src/data/cctvA22.test.mjs",
|
"src/data/cctvA22.test.mjs",
|
||||||
"src/data/cctvIceland.test.mjs",
|
|
||||||
"src/data/cctvNyc.test.mjs",
|
"src/data/cctvNyc.test.mjs",
|
||||||
"src/data/cctvHouston.test.mjs",
|
"src/data/cctvHouston.test.mjs",
|
||||||
"src/data/cctvNswSource.test.mjs",
|
"src/data/cctvNswSource.test.mjs",
|
||||||
@@ -679,7 +668,6 @@
|
|||||||
"src/voice/control.js",
|
"src/voice/control.js",
|
||||||
"src/voice/realtimeBackend.js",
|
"src/voice/realtimeBackend.js",
|
||||||
"src/voice/realtimeBackend.test.mjs",
|
"src/voice/realtimeBackend.test.mjs",
|
||||||
"src/voice/realtimeViewportPolicy.test.mjs",
|
|
||||||
"src/agent/worldPacket.test.mjs",
|
"src/agent/worldPacket.test.mjs",
|
||||||
"src/voice/layerSummary.js",
|
"src/voice/layerSummary.js",
|
||||||
"src/app/sources.js",
|
"src/app/sources.js",
|
||||||
@@ -847,45 +835,6 @@
|
|||||||
"scripts/qa-transit-recovery.mjs",
|
"scripts/qa-transit-recovery.mjs",
|
||||||
"scripts/format-runtime.json",
|
"scripts/format-runtime.json",
|
||||||
"src/tooling/transitService.test.mjs",
|
"src/tooling/transitService.test.mjs",
|
||||||
"src/tooling/railService.test.mjs",
|
|
||||||
"src/data/railFeeds.test.mjs",
|
|
||||||
"src/data/railProxy.test.mjs",
|
|
||||||
"src/layers/rail/policy.test.mjs",
|
|
||||||
"src/layers/rail/source.test.mjs",
|
|
||||||
"src/layers/rail/pathBinding.test.mjs",
|
|
||||||
"src/layers/rail/routeLines.test.mjs",
|
|
||||||
"src/layers/rail/stopLabels.test.mjs",
|
|
||||||
"src/layers/rail/schedules.test.mjs",
|
|
||||||
"src/data/railPaths.test.mjs",
|
|
||||||
"src/data/railSchedule.test.mjs",
|
|
||||||
"src/data/railTripUpdates.test.mjs",
|
|
||||||
"src/data/irishRail.test.mjs",
|
|
||||||
"src/tooling/buildRailPaths.test.mjs",
|
|
||||||
"src/tooling/buildRailPathsRoutes.test.mjs",
|
|
||||||
"scripts/build-rail-paths.mjs",
|
|
||||||
"src/tooling/buildRailPathsUnlocated.test.mjs",
|
|
||||||
"src/layers/rail/follow.test.mjs",
|
|
||||||
"src/layers/rail/groundHeight.test.mjs",
|
|
||||||
"src/layers/rail/liveOnly.test.mjs",
|
|
||||||
"src/testSupport/railLayerHarness.mjs",
|
|
||||||
"src/layers/rail/dedupe.test.mjs",
|
|
||||||
"src/data/railIreland.test.mjs",
|
|
||||||
"src/data/railNoPositions.test.mjs",
|
|
||||||
"src/data/railFeedsNational.test.mjs",
|
|
||||||
"src/data/railSiriVm.test.mjs",
|
|
||||||
"src/tooling/buildRailPathsTripEnds.test.mjs",
|
|
||||||
"src/data/railFeedsUs.test.mjs",
|
|
||||||
"src/data/railFeedsAsia.test.mjs",
|
|
||||||
"src/data/railFeedsFrance.test.mjs",
|
|
||||||
"src/data/railFeedsUsNoTerms.test.mjs",
|
|
||||||
"src/data/railFeedsUsUrban.test.mjs",
|
|
||||||
"src/tooling/buildRailPathsLocal.test.mjs",
|
|
||||||
"src/tooling/buildRailPathsWrite.test.mjs",
|
|
||||||
"src/tooling/railServiceFaults.test.mjs",
|
|
||||||
"src/tooling/railCatalogPacks.test.mjs",
|
|
||||||
"src/tooling/railServicePacks.test.mjs",
|
|
||||||
"src/tooling/buildRailPathsCli.test.mjs",
|
|
||||||
"src/data/railFeedsDocs.test.mjs",
|
|
||||||
"src/sources/overpassFeatures.test.mjs",
|
"src/sources/overpassFeatures.test.mjs",
|
||||||
"src/data/cctvHlsStream.test.mjs",
|
"src/data/cctvHlsStream.test.mjs",
|
||||||
"src/layers/cctv/videoPlayback.test.mjs",
|
"src/layers/cctv/videoPlayback.test.mjs",
|
||||||
@@ -1117,7 +1066,6 @@
|
|||||||
"src/layers/traffic/roadModes.test.mjs",
|
"src/layers/traffic/roadModes.test.mjs",
|
||||||
"scripts/qa-traffic-motion.mjs",
|
"scripts/qa-traffic-motion.mjs",
|
||||||
"src/layers/traffic/motion.test.mjs",
|
"src/layers/traffic/motion.test.mjs",
|
||||||
"src/layers/traffic/lineRebuild.test.mjs",
|
|
||||||
"scripts/qa-journey-recorder.mjs",
|
"scripts/qa-journey-recorder.mjs",
|
||||||
"scripts/qa-alpr-journey.mjs",
|
"scripts/qa-alpr-journey.mjs",
|
||||||
"scripts/qa-context-arrivals.mjs",
|
"scripts/qa-context-arrivals.mjs",
|
||||||
@@ -1169,7 +1117,6 @@
|
|||||||
"src/sky/starrySky.test.mjs",
|
"src/sky/starrySky.test.mjs",
|
||||||
"src/tooling/skyAssets.test.mjs",
|
"src/tooling/skyAssets.test.mjs",
|
||||||
"src/tooling/skyAssets.paintOut.test.mjs",
|
"src/tooling/skyAssets.paintOut.test.mjs",
|
||||||
"src/tooling/responseCompression.test.mjs",
|
|
||||||
"scripts/qa-anime-sky.mjs",
|
"scripts/qa-anime-sky.mjs",
|
||||||
"scripts/qa-sky-model-lighting.mjs",
|
"scripts/qa-sky-model-lighting.mjs",
|
||||||
"server/providers/overpass/requestAdmission.test.mjs",
|
"server/providers/overpass/requestAdmission.test.mjs",
|
||||||
@@ -1270,7 +1217,6 @@
|
|||||||
"src/data/firmsIntensity.js",
|
"src/data/firmsIntensity.js",
|
||||||
"src/data/firmsIntensity.test.mjs",
|
"src/data/firmsIntensity.test.mjs",
|
||||||
"src/ui/layerPanel/RowSwitch.vue",
|
"src/ui/layerPanel/RowSwitch.vue",
|
||||||
"src/ui/layerPanel/RowRange.vue",
|
|
||||||
"src/ui/layerPanel/RowSlider.vue",
|
"src/ui/layerPanel/RowSlider.vue",
|
||||||
"src/sources/phaseTiming.test.mjs",
|
"src/sources/phaseTiming.test.mjs",
|
||||||
"scripts/build-census-places.mjs",
|
"scripts/build-census-places.mjs",
|
||||||
@@ -1325,13 +1271,6 @@
|
|||||||
"src/voice/panelLayout.js",
|
"src/voice/panelLayout.js",
|
||||||
"src/voice/textChat.js",
|
"src/voice/textChat.js",
|
||||||
"src/voice/textProtocol.js",
|
"src/voice/textProtocol.js",
|
||||||
"src/layers/shipwrecks/records.test.mjs",
|
|
||||||
"src/layers/shipwrecks/cards.test.mjs",
|
|
||||||
"src/layers/shipwrecks/eras.test.mjs",
|
|
||||||
"src/layers/shipwrecks/period.test.mjs",
|
|
||||||
"src/layers/shipwrecks/index.test.mjs",
|
|
||||||
"src/layers/shipwrecks/source.test.mjs",
|
|
||||||
"src/data/shipwrecksProxy.test.mjs",
|
|
||||||
"src/app/sourceComposition.js",
|
"src/app/sourceComposition.js",
|
||||||
"src/app/sourceComposition.test.mjs",
|
"src/app/sourceComposition.test.mjs",
|
||||||
"src/data/dataCredits.js",
|
"src/data/dataCredits.js",
|
||||||
@@ -1342,12 +1281,10 @@
|
|||||||
"src/i18n/locales/de.json",
|
"src/i18n/locales/de.json",
|
||||||
"src/i18n/locales/es.json",
|
"src/i18n/locales/es.json",
|
||||||
"src/i18n/locales/fr.json",
|
"src/i18n/locales/fr.json",
|
||||||
"src/i18n/locales/he.json",
|
|
||||||
"src/i18n/locales/it.json",
|
"src/i18n/locales/it.json",
|
||||||
"src/i18n/locales/ja.json",
|
"src/i18n/locales/ja.json",
|
||||||
"src/i18n/locales/pt.json",
|
"src/i18n/locales/pt.json",
|
||||||
"src/i18n/locales/ru.json",
|
"src/i18n/locales/ru.json",
|
||||||
"src/i18n/locales/sv.json",
|
|
||||||
"src/i18n/locales/uk.json",
|
"src/i18n/locales/uk.json",
|
||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
@@ -1417,10 +1354,5 @@
|
|||||||
"src/layers/wildlifeSightings/cache.integration.test.mjs",
|
"src/layers/wildlifeSightings/cache.integration.test.mjs",
|
||||||
"src/layers/wildlifeSightings/density.test.mjs",
|
"src/layers/wildlifeSightings/density.test.mjs",
|
||||||
"src/layers/wildlifeSightings/selection.test.mjs",
|
"src/layers/wildlifeSightings/selection.test.mjs",
|
||||||
"server/providers/inaturalist/proxy.fixture.mjs",
|
"server/providers/inaturalist/proxy.fixture.mjs"
|
||||||
"scripts/build-owner-flags.mjs",
|
|
||||||
"src/ui/ownerFlagSheet.js",
|
|
||||||
"src/data/placePacksLoading.test.mjs",
|
|
||||||
"src/ui/placePacksLoading.js",
|
|
||||||
"src/ui/placePacksLoading.test.mjs"
|
|
||||||
]
|
]
|
||||||
+19
-513
File diff suppressed because it is too large.
Load diff
@@ -4,7 +4,6 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
import { stubPlanespotters } from './qa-photo-stub.mjs';
|
|
||||||
|
|
||||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
const shotsDir = path.join(repoRoot, 'qa-shots', 'cockpit-utility');
|
const shotsDir = path.join(repoRoot, 'qa-shots', 'cockpit-utility');
|
||||||
@@ -30,7 +29,6 @@ const browser = await puppeteer.launch({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await stubPlanespotters(page);
|
|
||||||
const failures = [];
|
const failures = [];
|
||||||
const consoleErrors = [];
|
const consoleErrors = [];
|
||||||
const localHttpErrors = [];
|
const localHttpErrors = [];
|
||||||
@@ -827,7 +825,6 @@ try {
|
|||||||
// a legitimate retained snapshot cannot satisfy or invalidate a FIRST-fetch
|
// a legitimate retained snapshot cannot satisfy or invalidate a FIRST-fetch
|
||||||
// assertion when camera motion supersedes the manager's initial update.
|
// assertion when camera motion supersedes the manager's initial update.
|
||||||
const readinessPage = await browser.newPage();
|
const readinessPage = await browser.newPage();
|
||||||
await stubPlanespotters(readinessPage);
|
|
||||||
let deferredInstallationReadiness;
|
let deferredInstallationReadiness;
|
||||||
try {
|
try {
|
||||||
await readinessPage.setViewport({ width: 1440, height: 900 });
|
await readinessPage.setViewport({ width: 1440, height: 900 });
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
import { stubPlanespotters } from './qa-photo-stub.mjs';
|
|
||||||
import { createScreencast, moveCamera } from './qa-journey-recorder.mjs';
|
import { createScreencast, moveCamera } from './qa-journey-recorder.mjs';
|
||||||
|
|
||||||
const argv = process.argv.slice(2);
|
const argv = process.argv.slice(2);
|
||||||
@@ -78,7 +77,6 @@ const browser = await puppeteer.launch({
|
|||||||
|
|
||||||
async function openPage({ aircraft = false } = {}) {
|
async function openPage({ aircraft = false } = {}) {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await stubPlanespotters(page);
|
|
||||||
await page.setViewport({ width: 1440, height: 900 });
|
await page.setViewport({ width: 1440, height: 900 });
|
||||||
page.on('console', (message) => {
|
page.on('console', (message) => {
|
||||||
if (message.type() === 'error') result.consoleErrors.push(message.text());
|
if (message.type() === 'error') result.consoleErrors.push(message.text());
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
import { stubPlanespotters } from './qa-photo-stub.mjs';
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
@@ -224,7 +223,6 @@ async function main() {
|
|||||||
const errors = [];
|
const errors = [];
|
||||||
try {
|
try {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await stubPlanespotters(page);
|
|
||||||
await page.setViewport({ width: 1440, height: 900 });
|
await page.setViewport({ width: 1440, height: 900 });
|
||||||
page.on('pageerror', (e) => errors.push(e.message));
|
page.on('pageerror', (e) => errors.push(e.message));
|
||||||
const { lon, lat } = CENTER;
|
const { lon, lat } = CENTER;
|
||||||
|
|||||||
Loaded 100 of 822 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user