mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-14 07:12:21 +00:00
Compare commits
488
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a00049b85 | ||
|
|
14b2adb7af | ||
|
|
6c77b1ed41 | ||
|
|
667190f815 | ||
|
|
a9b703f3ea | ||
|
|
a757beba77 | ||
|
|
ab5c593ab6 | ||
|
|
02ad2d045a | ||
|
|
ed47aeedbb | ||
|
|
6a7fed6cb7 | ||
|
|
2d0aa25e35 | ||
|
|
1856477e0a | ||
|
|
af0dc2e2ec | ||
|
|
ed68be0610 | ||
|
|
b870f446c6 | ||
|
|
49b2849528 | ||
|
|
00cb44ad2c | ||
|
|
ed6b6114cf | ||
|
|
c1795c7dc3 | ||
|
|
69617af7ef | ||
|
|
9e2c424030 | ||
|
|
982d197111 | ||
|
|
3ab0bbf7c1 | ||
|
|
697dc91e81 | ||
|
|
e98bf9c485 | ||
|
|
be395f0f52 | ||
|
|
d83f06755f | ||
|
|
34b4fdce57 | ||
|
|
ed47de6a2c | ||
|
|
ee87482f07 | ||
|
|
1d10ba242d | ||
|
|
cd571dfb32 | ||
|
|
ab41c765cc | ||
|
|
6b0d3f4675 | ||
|
|
4bbf9949d5 | ||
|
|
88f434ded7 | ||
|
|
7c03d68a2f | ||
|
|
705b7a5314 | ||
|
|
97e192ebce | ||
|
|
555906da6b | ||
|
|
b5cc4feacb | ||
|
|
2c62e1e378 | ||
|
|
b3daed4a9b | ||
|
|
087adc6dc9 | ||
|
|
d3433f66c9 | ||
|
|
2dcec2195e | ||
|
|
facc9541ab | ||
|
|
f288b6ba1e | ||
|
|
b73fe82e27 | ||
|
|
39206ef4c3 | ||
|
|
417890b862 | ||
|
|
8caff876cb | ||
|
|
560b72c3f7 | ||
|
|
00153acbeb | ||
|
|
7c5ebf4814 | ||
|
|
225c1c25c7 | ||
|
|
3c304502a0 | ||
|
|
df02033465 | ||
|
|
6c416f3fe6 | ||
|
|
53b07ba91f | ||
|
|
373edf2989 | ||
|
|
ee697422c9 | ||
|
|
731eadc34a | ||
|
|
08d5c86e80 | ||
|
|
dd56cba2f3 | ||
|
|
59ff0b38a2 | ||
|
|
a2905eb700 | ||
|
|
d51e4140b7 | ||
|
|
bcff542b16 | ||
|
|
aac65c21c4 | ||
|
|
6727aaadcd | ||
|
|
4f654df3c0 | ||
|
|
e0d3e93a28 | ||
|
|
82ed68f443 | ||
|
|
ef104aaac3 | ||
|
|
50e486ceec | ||
|
|
75cf0aeaf9 | ||
|
|
a07d9f5d85 | ||
|
|
27939a2d84 | ||
|
|
1ac9f28c4a | ||
|
|
e9ba5e9859 | ||
|
|
37263a3e2b | ||
|
|
18da76e8d8 | ||
|
|
60f9de8511 | ||
|
|
5b27f26e14 | ||
|
|
28f3030c31 | ||
|
|
a90407d547 | ||
|
|
33ccef9662 | ||
|
|
49b180f1fd | ||
|
|
233c4def5b | ||
|
|
0953287c3d | ||
|
|
b03edaaf2b | ||
|
|
9ae7dea918 | ||
|
|
ffbceb977c | ||
|
|
20ad9b41b3 | ||
|
|
bd4f9eaee4 | ||
|
|
9c2a2d6951 | ||
|
|
ba04ceaa32 | ||
|
|
ed6ae54ef6 | ||
|
|
a913e5466e | ||
|
|
2a761aa5eb | ||
|
|
f437c615a0 | ||
|
|
60fb8fee07 | ||
|
|
0ef29781e5 | ||
|
|
69fbf5b38d | ||
|
|
68845a4977 | ||
|
|
b4556f9763 | ||
|
|
7f478e3815 | ||
|
|
371d848eec | ||
|
|
18f69c2699 | ||
|
|
e1095ee553 | ||
|
|
aa6959c97b | ||
|
|
be08163caa | ||
|
|
db4bdba64f | ||
|
|
fbadf792d8 | ||
|
|
fd65288101 | ||
|
|
7f3754d812 | ||
|
|
c1e857f798 | ||
|
|
3814221cbc | ||
|
|
26597ad25c | ||
|
|
0459ee6d58 | ||
|
|
4252ed0798 | ||
|
|
52c6f68bd5 | ||
|
|
656943641c | ||
|
|
f385986383 | ||
|
|
dacfd33f10 | ||
|
|
910d693b94 | ||
|
|
f4b83a3a31 | ||
|
|
3907b7084e | ||
|
|
97c4dc76ce | ||
|
|
c079b329d0 | ||
|
|
c73c5382d4 | ||
|
|
b53b6a48a7 | ||
|
|
47d63128ff | ||
|
|
48885d9e61 | ||
|
|
f1ec9d4a22 | ||
|
|
406dd9122e | ||
|
|
1559bd57f4 | ||
|
|
2fbdca62be | ||
|
|
30c86a6001 | ||
|
|
e18f12d34c | ||
|
|
54fe0aea40 | ||
|
|
974a0b6361 | ||
|
|
6dc643182e | ||
|
|
dc1144b947 | ||
|
|
fc75e245a6 | ||
|
|
145adc08e0 | ||
|
|
f06b93fbc9 | ||
|
|
3e261d1baf | ||
|
|
35e70ec280 | ||
|
|
48988015f1 | ||
|
|
2213be6566 | ||
|
|
59a233db51 | ||
|
|
9daa4bd2db | ||
|
|
92c8f8d9da | ||
|
|
90166024e9 | ||
|
|
9825b02aab | ||
|
|
a6c64713ee | ||
|
|
7abf74518f | ||
|
|
c502404e1d | ||
|
|
350a7f5abb | ||
|
|
0ee92eb864 | ||
|
|
909be62ae7 | ||
|
|
8bea1419f6 | ||
|
|
e1d3a588fc | ||
|
|
843c4a111d | ||
|
|
43b4e9348b | ||
|
|
727bb29cae | ||
|
|
0c0df50a40 | ||
|
|
db9bf64821 | ||
|
|
a2f5b8fca3 | ||
|
|
06ae95ceb3 | ||
|
|
bc1b56b63a | ||
|
|
b3efcfb7a2 | ||
|
|
1100cdcff5 | ||
|
|
f89aee1eac | ||
|
|
b8305de14b | ||
|
|
f87da8523e | ||
|
|
91685c94d9 | ||
|
|
a56f2f9352 | ||
|
|
43ab3ecaaf | ||
|
|
ebc717df8b | ||
|
|
726e201314 | ||
|
|
90d64e6916 | ||
|
|
cbe1d841ea | ||
|
|
b49f9dddf0 | ||
|
|
125fbc5a3b | ||
|
|
9cc108835e | ||
|
|
8d7f055572 | ||
|
|
227e3c2051 | ||
|
|
ae13a56f6d | ||
|
|
9f8381ff71 | ||
|
|
01ae045346 | ||
|
|
59a2e31aa8 | ||
|
|
b20579b06a | ||
|
|
a9282b30bb | ||
|
|
1a3f6f2486 | ||
|
|
c2d7a6dbb0 | ||
|
|
89339cdd96 | ||
|
|
5983c663bd | ||
|
|
ef174b0ff7 | ||
|
|
6ba67d01af | ||
|
|
d8ef366ab1 | ||
|
|
f0d38897f4 | ||
|
|
d8c63c69d8 | ||
|
|
eee4f48745 | ||
|
|
c392d6fb50 | ||
|
|
074f4af10e | ||
|
|
dcb8de9d2c | ||
|
|
4c28cc0212 | ||
|
|
00dfed7959 | ||
|
|
f7945d6844 | ||
|
|
96b5b0bb1f | ||
|
|
5b7171f485 | ||
|
|
f5c14ec30d | ||
|
|
dea10cac4f | ||
|
|
60a230b2ff | ||
|
|
057dbe24af | ||
|
|
f0d7def9c7 | ||
|
|
1773494116 | ||
|
|
3144169057 | ||
|
|
37ea4e1f93 | ||
|
|
31b1f27a2e | ||
|
|
f2117a98f2 | ||
|
|
40a81c58e6 | ||
|
|
df73c5b21a | ||
|
|
c96d4ecfb1 | ||
|
|
d5218c4722 | ||
|
|
4f8a9f2615 | ||
|
|
1c09c90e13 | ||
|
|
dae4011604 | ||
|
|
299a5943a0 | ||
|
|
f7cd6797ec | ||
|
|
1910a3b2d5 | ||
|
|
c45ec634ee | ||
|
|
d86047fe8f | ||
|
|
58c8a481ed | ||
|
|
63b2590b9f | ||
|
|
4a9b95e31b | ||
|
|
b977ef29e3 | ||
|
|
293ffb2bd3 | ||
|
|
b62f60482e | ||
|
|
b29e20632b | ||
|
|
736ee8d694 | ||
|
|
03cd379c0e | ||
|
|
57f7edc693 | ||
|
|
c34a2c38fc | ||
|
|
8e1a3bab3e | ||
|
|
a99b862580 | ||
|
|
2bac200c01 | ||
|
|
061ed1b682 | ||
|
|
d1fdb9eb45 | ||
|
|
741b6f67eb | ||
|
|
17eea266f1 | ||
|
|
111ba1626c | ||
|
|
05578c61b9 | ||
|
|
35ed499ae4 | ||
|
|
f0c2523ac4 | ||
|
|
b2cc00833d | ||
|
|
5564156c09 | ||
|
|
eed8acc3f7 | ||
|
|
f4879fc24b | ||
|
|
7f0572cf4c | ||
|
|
1a1eef0e5c | ||
|
|
a066a2d1ca | ||
|
|
9cc34e324f | ||
|
|
cf6d04b7db | ||
|
|
7fb11507d5 | ||
|
|
53c114965d | ||
|
|
65d41b971c | ||
|
|
4d2b061e3b | ||
|
|
64c5cd25e6 | ||
|
|
64759c552e | ||
|
|
12cd73bed9 | ||
|
|
baabff6acf | ||
|
|
2853938df7 | ||
|
|
dd040b4f87 | ||
|
|
0c190c3a15 | ||
|
|
62296a833c | ||
|
|
05b646daa3 | ||
|
|
14f7b32537 | ||
|
|
160e445677 | ||
|
|
a49671a391 | ||
|
|
133ca14119 | ||
|
|
24190a3e35 | ||
|
|
c4a97a653c | ||
|
|
b01f1ee7dc | ||
|
|
b98cbeebf2 | ||
|
|
75df3389f3 | ||
|
|
4e09559337 | ||
|
|
811bcaff85 | ||
|
|
605b9490f3 | ||
|
|
5cea3e8cbb | ||
|
|
2b4955c817 | ||
|
|
d7b3d45135 | ||
|
|
c1d829ef51 | ||
|
|
868d061856 | ||
|
|
cb350fa58d | ||
|
|
11992828fb | ||
|
|
87bbe14748 | ||
|
|
81bcae907f | ||
|
|
e08ddd787b | ||
|
|
1bda760763 | ||
|
|
b7431d7823 | ||
|
|
d7ab85f5c4 | ||
|
|
2ee09c5911 | ||
|
|
1930ca247a | ||
|
|
6fe5bda028 | ||
|
|
99bda8a35e | ||
|
|
c0062c4d3d | ||
|
|
a0e5f04c30 | ||
|
|
4bd7f97cc4 | ||
|
|
73ea608881 | ||
|
|
2087b55b47 | ||
|
|
8b179a7e01 | ||
|
|
96ce795f8c | ||
|
|
3a2a53aef9 | ||
|
|
0d15e80e28 | ||
|
|
3cbc17a926 | ||
|
|
5e658d8c9d | ||
|
|
f71c76c306 | ||
|
|
b8f4d097c4 | ||
|
|
1da65dc264 | ||
|
|
dc7a7c4d53 | ||
|
|
15f1f9f5b6 | ||
|
|
c76872dfaa | ||
|
|
8279af2e6d | ||
|
|
0df0fb8261 | ||
|
|
8328aab779 | ||
|
|
9ba5a3aba9 | ||
|
|
f59d134928 | ||
|
|
5a8bc33fbd | ||
|
|
b8560e5b26 | ||
|
|
694616d5c2 | ||
|
|
798d6636ab | ||
|
|
93c71ffc86 | ||
|
|
f72bf6a774 | ||
|
|
311e178d0c | ||
|
|
a72cfc4098 | ||
|
|
46b3aceafc | ||
|
|
9d587c1d37 | ||
|
|
d82a6f1550 | ||
|
|
1a694baf5a | ||
|
|
965a82144c | ||
|
|
f4f0088814 | ||
|
|
c58bc9864b | ||
|
|
5dd05a4a81 | ||
|
|
a9b3125a99 | ||
|
|
a3152ab719 | ||
|
|
c89185f352 | ||
|
|
e8870a4766 | ||
|
|
0600964249 | ||
|
|
766c786093 | ||
|
|
4baefeb8f9 | ||
|
|
1839d62160 | ||
|
|
6bdb995fe3 | ||
|
|
000ef99464 | ||
|
|
83fbe6949f | ||
|
|
c8a4f3ed98 | ||
|
|
69a78710cc | ||
|
|
620ada6df3 | ||
|
|
1fe8267550 | ||
|
|
6b27b21cc4 | ||
|
|
6eec8f6cb0 | ||
|
|
71b7177dc0 | ||
|
|
5ff8c5d7a6 | ||
|
|
ed9ea7e679 | ||
|
|
ea1e2f9c04 | ||
|
|
65e30546dd | ||
|
|
0b8cb855fa | ||
|
|
941b308c29 | ||
|
|
10d0e49ddb | ||
|
|
9dc446bd24 | ||
|
|
3a0a3b0551 | ||
|
|
7c0891deda | ||
|
|
e007350645 | ||
|
|
2da7b6d0d9 | ||
|
|
dcfb1219d5 | ||
|
|
ba1191b2e5 | ||
|
|
132a941e9a | ||
|
|
03a0ff3a01 | ||
|
|
b14bf7e013 | ||
|
|
20e9d4a0fa | ||
|
|
1dd3220812 | ||
|
|
443c581280 | ||
|
|
4dfcba8989 | ||
|
|
d81ebf1268 | ||
|
|
2ce1c55f69 | ||
|
|
7f13ef4a87 | ||
|
|
8e702f0c3b | ||
|
|
9f1a0611fa | ||
|
|
c493a8303b | ||
|
|
bf46eb6d97 | ||
|
|
907cb2092d | ||
|
|
ba039f7974 | ||
|
|
18b5475b8a | ||
|
|
bc281ab246 | ||
|
|
ec7dcc944e | ||
|
|
834d34296f | ||
|
|
d6fe0f3805 | ||
|
|
f0ce1b04c7 | ||
|
|
b0cbed081b | ||
|
|
e1be2eac19 | ||
|
|
7d5a484796 | ||
|
|
311722e6de | ||
|
|
d7193e3b9e | ||
|
|
a4e753d550 | ||
|
|
4e195dac6b | ||
|
|
68ff4e1c25 | ||
|
|
645f042450 | ||
|
|
f640890cf6 | ||
|
|
a89719aba9 | ||
|
|
39d522c9a9 | ||
|
|
d483e5897e | ||
|
|
ae52695b15 | ||
|
|
6436f580d9 | ||
|
|
0957d9712b | ||
|
|
93866c42fc | ||
|
|
e804351205 | ||
|
|
3c2e924363 | ||
|
|
8812117013 | ||
|
|
c327a25744 | ||
|
|
b0cb8ce881 | ||
|
|
86aa0bc9ba | ||
|
|
0109097440 | ||
|
|
4623c8b616 | ||
|
|
76851ee949 | ||
|
|
f7f4e9a45c | ||
|
|
7c7422e281 | ||
|
|
4aaf974600 | ||
|
|
ce985768bc | ||
|
|
da6141be4b | ||
|
|
6dad3894bd | ||
|
|
5188e8869d | ||
|
|
bb4e6bafa4 | ||
|
|
c33ee31efc | ||
|
|
f1a2df5cfe | ||
|
|
27e28373d0 | ||
|
|
26968f4903 | ||
|
|
d50eaa28af | ||
|
|
1cca89adf2 | ||
|
|
a8a2ad5582 | ||
|
|
abf16ae8af | ||
|
|
bb8c0b83c4 | ||
|
|
ac081a008d | ||
|
|
177446c0be | ||
|
|
179c846f3e | ||
|
|
80cc0b536e | ||
|
|
312ed416e9 | ||
|
|
b65dbab709 | ||
|
|
8270172086 | ||
|
|
64d3297301 | ||
|
|
4ded3fbf15 | ||
|
|
306e3e9467 | ||
|
|
72fec292d4 | ||
|
|
d112ecf296 | ||
|
|
c6e1b65c67 | ||
|
|
0b9deddd70 | ||
|
|
ec38e6ef16 | ||
|
|
16e63f4c50 | ||
|
|
a3e929870d | ||
|
|
840e34a881 | ||
|
|
a4ac0b9b0d | ||
|
|
90c8e7763a | ||
|
|
b6ac0e9c99 | ||
|
|
6dbd9c983e | ||
|
|
ab0acb0e09 | ||
|
|
12c9651a67 | ||
|
|
3e3aa0bd9f | ||
|
|
83c13f330d | ||
|
|
be56144d75 | ||
|
|
c2a0d20f3d | ||
|
|
aaa6074663 | ||
|
|
58a196374f | ||
|
|
a5c98c6b07 | ||
|
|
ab2368f3f3 | ||
|
|
d831cbd23a | ||
|
|
cd94a26b9f | ||
|
|
708c96b6f6 | ||
|
|
528ec75f32 | ||
|
|
f2fcd82290 | ||
|
|
e3061872cc | ||
|
|
a915bdb3e5 | ||
|
|
ee8b94d987 | ||
|
|
2b83606765 | ||
|
|
1a69368ae6 | ||
|
|
d886be68a8 | ||
|
|
4a92ed8377 |
No files matched your search
@@ -280,11 +280,6 @@ AISSTREAM_API_KEY=
|
||||
# CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry
|
||||
# CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured
|
||||
|
||||
# Optional: Data Library (DATA LAYERS ▸ Browse data library). Every entry is
|
||||
# fetched live from its provider; nothing is hosted here. See docs/DATA-LIBRARY.md.
|
||||
# GEV_LIBRARY_ENABLE= # comma-separated ids of operator opt-in entries to serve (packs use their host's own switch, e.g. CCTV_NYC_ENABLED)
|
||||
# GEV_LIBRARY_DISABLE= # comma-separated ids to remove from this instance entirely
|
||||
|
||||
# Optional: comma-separated Overpass URLs; use only instances you run or pay for.
|
||||
# Also enables voice place search (osm_query, "hospitals in Kathmandu").
|
||||
OVERPASS_UPSTREAMS=
|
||||
@@ -357,11 +352,3 @@ GEMINI_CHAT_MODEL=gemini-3.5-flash-lite
|
||||
# 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=
|
||||
@@ -53,16 +53,16 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
run: npm run layer-token:check -- --base-ref origin/main
|
||||
|
||||
- name: Check Data Library manifests and share tokens
|
||||
if: github.event_name == 'pull_request'
|
||||
run: npm run library:check -- --base-ref origin/main
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm test
|
||||
|
||||
- name: Build production bundle
|
||||
run: npm run build
|
||||
|
||||
- name: Main chunk size budget
|
||||
if: matrix.node == '24.22.2'
|
||||
run: npm run check:main-chunk
|
||||
|
||||
street-level-browser:
|
||||
# Hermetic: every Mapillary request is answered from fixtures, so the
|
||||
# dummy token below never leaves the page.
|
||||
|
||||
@@ -14,8 +14,6 @@ qa-shots/
|
||||
output/
|
||||
.gev-logs/
|
||||
.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).
|
||||
qa-artifacts/
|
||||
# Playwright MCP session logs: browser consoles can carry tokens in URLs.
|
||||
|
||||
+1
-2
@@ -129,7 +129,6 @@ prints anything; never fix that by renaming or deleting PR A's published token.
|
||||
The highest-leverage places to jump in:
|
||||
|
||||
- **🌆 Add a CCTV source pack.** Austin is the reference camera source. 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 Data Library entry.** A regional or niche source that is not a core layer belongs in the Data Library: one JSON manifest pointing at a live API, provider-hosted file or tile endpoint, with its licence and attribution, and no JavaScript. See [docs/DATA-LIBRARY.md](docs/DATA-LIBRARY.md). Library entries use their own share-token ledger, not the layer tokens below.
|
||||
- **🛰️ 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).
|
||||
- **🎨 Add a visual style.** Styles are GLSL post-process shaders in `src/styles/`.
|
||||
@@ -229,7 +228,7 @@ ownership and adoption process.
|
||||
1. Branch off `main`.
|
||||
2. Keep `npm run build`, `npm test`, and `npm run test:track` green and avoid new console errors, plus the [feature gate](#feature-regression-gates) for the area you touched.
|
||||
3. If you change runtime behavior, update `docs/CURRENT-STATE.md` in the same PR, and add your changelog entry as `changelog.d/<pr-number>-<slug>.md` instead of editing `CHANGELOG.md` — see [changelog.d/README.md](changelog.d/README.md). Distinct filenames don't conflict; a release assembles the directory into `CHANGELOG.md`. A documentation claim that needs a regression test should assert against `docs/CURRENT-STATE.md` or `README.md`, which a PR still edits directly.
|
||||
4. If you add or change a data source, update [DATA_SOURCES.md](DATA_SOURCES.md) with its license and attribution (for a Data Library entry, `npm run library:docs` writes its row). **Don't add data you don't have the right to redistribute** — fetch it at runtime instead.
|
||||
4. If you add or change a data source, update [DATA_SOURCES.md](DATA_SOURCES.md) with its license and attribution. **Don't add data you don't have the right to redistribute** — fetch it at runtime instead.
|
||||
5. Describe what you changed and how you verified it (screenshots welcome for anything visual).
|
||||
6. If you add a new layer, fill in the new-layer section of the PR template. See [Adding a new layer](#adding-a-new-layer).
|
||||
|
||||
|
||||
+9
-152
@@ -1,13 +1,13 @@
|
||||
# Data Sources & Attribution
|
||||
|
||||
God's Eye View's **code** is [MIT](LICENSE)-licensed. **The MIT grant covers the source code only — it does NOT extend to third-party data or visual assets.** Every third-party source keeps its own license and terms. This file documents the live and bundled data sources. The bundled 3D models are the project's own procedural fleet models, MIT-licensed with the code ([`public/models/README.md`](public/models/README.md)).
|
||||
God's Eye View's **code** is [MIT](LICENSE)-licensed. **The MIT grant covers the source code only — it does NOT extend to third-party data or visual assets.** Every third-party source keeps its own license and terms. This file documents the live and bundled data sources; bundled 3D-model provenance is recorded in [`public/models/README.md`](public/models/README.md).
|
||||
|
||||
How to read this:
|
||||
|
||||
- **The non-permissive datasets are carved out, not omitted.** Some bundled data (e.g. TeleGeography, CC BY-NC-SA) isn't MIT-compatible. Rather than hide it, we **bundle it with a clear license carve-out** so the app works out of the box — but it stays under the provider's terms.
|
||||
- **If your use doesn't fit a dataset's license, remove that dataset.** Most importantly: TeleGeography is **NonCommercial** — commercial users must delete it (or license it from TeleGeography). It's one self-contained folder.
|
||||
- **If your use doesn't fit a dataset's license, remove that dataset.** Most importantly: TeleGeography is **NonCommercial** — commercial users must delete it (or license it from TeleGeography). It's one self-contained folder. **EOX Sentinel-2 cloudless 2020** is also **NonCommercial** (CC BY-NC-SA 4.0): remove that basemap or obtain a commercial licence from EOX.
|
||||
- **Attribution is shown in-app** and listed here. Keep it intact. The required Google/Cesium credit renders on the on-globe credit line (bottom-left, `#cesium-credits`), and every per-layer credit below is registered into the expandable **"Data attribution"** lightbox on that line (`src/data/dataCredits.js` → `viewer.creditDisplay.addStaticCredit`). Both stay visible in clean-view and recording modes.
|
||||
- **The bundled 3D models are the project's own.** Every model under `public/models/` is a procedural fleet model generated by the project maintainers and covered by the repository's MIT license; [`public/models/README.md`](public/models/README.md) describes the set.
|
||||
- **Bundled model attribution lives beside the model files.** [`public/models/README.md`](public/models/README.md) records each shipped model's creator, source, license, and modification status.
|
||||
|
||||
---
|
||||
|
||||
@@ -31,10 +31,10 @@ How to read this:
|
||||
| **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 |
|
||||
| **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 |
|
||||
| **Open Vessel Photos** (`openvesselphotos.org/v1/mmsi/{mmsi}`) | One photo of the selected vessel, by MMSI, in the CONTEXT panel's CONTACTS view under the vessel's name and MMSI | Free, keyless public API ([README](https://github.com/louptheron/openvesselphotos)); each photo keeps the open licence its author chose (CC BY, CC BY-SA, CC0, or public domain), given with it in the API | "Photo: {photographer} · {licence}" beside every photo, linked to the vessel's Open Vessel Photos page; "Open Vessel Photos" in Data attribution |
|
||||
| **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" |
|
||||
| **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) |
|
||||
| **EOX Sentinel-2 cloudless 2020** | Optional satellite basemap in Cesium and Navara. Navara uses daylight for terrain inspection. | [EOX Maps](https://maps.eox.at/); [CC BY-NC-SA 4.0](https://cloudless.eox.at/license-non-commercial). Commercial use requires a commercial licence from EOX. | [EOxCloudless](https://cloudless.eox.at/) by [EOX IT Services GmbH](https://eox.at/) (Contains modified Copernicus Sentinel data 2020) |
|
||||
| **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" |
|
||||
| **Environment and Climate Change Canada** (MSC GeoMet OGC API) | Air quality (AQHI) — Canadian station observations | [ECCC Data Servers End-use Licence](https://eccc-msc.github.io/open-data/licence/readme_en/) (open, attribution required, no endorsement implied) | "Contains information licensed under the Environment and Climate Change Canada Data Servers End-use Licence" |
|
||||
@@ -111,36 +111,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 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
@@ -239,8 +209,7 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
||||
|
||||
|
||||
- **adsbdb enrichment.** `/api/adsbdb` resolves an ICAO hex to aircraft type, model name and registration, and the tracked flight's callsign to an airline and an origin/destination pair. Fan-out is deliberately small because adsbdb is a free community API that publishes no quota: at most 4 requests in flight, dispatches dripped to roughly 5/s, and each key asked for at most once per session (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.
|
||||
- **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 (see Open Vessel Photos below), 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.
|
||||
- **Open Vessel Photos.** When you select a vessel with a nine-digit MMSI, the browser asks `https://openvesselphotos.org/v1/mmsi/{mmsi}` directly (the API is keyless and answers cross-origin, so no key and no server proxy are involved; account credentials are never sent) for that vessel only, once per MMSI per session, with an 8-second timeout. A miss (a 404 or a vessel without photos) is remembered for the session; a failed request may be asked again after five minutes. The first photo whose image is an `https://openvesselphotos.org` URL and which names a licence is shown, at the 800 px size, in the CONTEXT panel's CONTACTS view under the vessel's name and MMSI (`src/ui/views/panels/AwarenessPanel.vue`), and nowhere else (`src/data/openVesselPhoto.js`, `src/layers/vessels/photos.js`). The image is hotlinked from the URL the API gives (never copied or re-hosted); the photographer and licence are visible text next to it ("Photo: {photographer} · {licence}", or "Photo · {licence}" when the answer names no one), and the photo and credit link to the vessel's Open Vessel Photos page with `target="_blank" rel="noopener"`. If the image fails to load, the photo and its credit are hidden.
|
||||
- **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.
|
||||
- **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.
|
||||
@@ -269,7 +238,6 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
||||
- **Autostrada del Brennero (A22) webcams.** Keyless. The camera list is one request to the Open Data Hub tourism API (`tourism.api.opendatahub.com/v1/WebcamInfo?source=a22`), run by NOI Techpark in South Tyrol, which republishes Autobrennero's own list of 13 motorway webcams with coordinates and kilometre markers. Frames are the operator's stills on `www.autobrennero.it/WebCamImg/km<N>.jpg`, refreshed about once a minute, and only the exact image for each camera's kilometre is registered. The feed names no bearing, so headings use the id-hash fallback at low confidence. The road climbs from about 20 m in the Po valley to about 1,370 m at the Brenner Pass, so each camera's ground elevation comes from a per-kilometre table sampled from the Copernicus GLO-90 DEM (via Open-Meteo's elevation API); a camera not in the table uses a 200 m prior. The Open Data Hub tags every record CC0 (`ClosedData: false`, Autobrennero as licence holder), but that tag is the aggregator's metadata and Autobrennero publishes no terms of its own for the images, so they are credited to the operator as a courtesy and are not redistributed. Frames are fetched at request time and never stored. Disable with `CCTV_A22_ENABLED=0`.
|
||||
- **Camera frame content.** Public camera frames can contain people, vehicles and license plates, and the camera layer does nothing with them beyond putting them on the map. A successful upstream response is relayed as the provider served it: nothing in the camera pipeline blurs, redacts, enhances or restores it, and nothing there runs face, plate or object detection or otherwise recognises or classifies what is in the picture. (The ALPR layer described below maps where plate-reader cameras are, from locations contributors tagged in OpenStreetMap; it reads nothing from any camera frame.) When an upstream has no frame for a camera, what you see instead is a Google Street View still or the app's own placeholder card; the panel's source badge names which of them you are looking at, and a refresh that fails after a frame has arrived leaves the last good picture on screen rather than replacing it. The browser resamples each frame for display: onto a fixed 1920x1080 surface for the camera's projection plane in the scene, and onto a 192x108 canvas for the small ambient card. That scaling is the only change made to the picture, and the only pixels the camera layer reads back are a 64x36 downscale hashed to tell whether the picture has changed. Live streams are piped through; the still path reads the image into memory to pass it on — TxDOT wraps its JPEG in a JSON envelope, which is decoded there — and no frame is written to disk (the disk cache, `.gev-cache/`, holds feed JSON for other layers and never camera imagery). Outside that pipeline, the voice assistant is the one feature that sends imagery anywhere: when it answers a question about what is in view it may attach a downscaled screenshot of the rendered scene — including any camera plane on screen — to the request it sends to the voice model. Frames are fetched only from server-registered camera URLs — a client cannot hand the proxy a URL (see [SECURITY.md](SECURITY.md)). What the app does stays inside the line README draws: no named-person search, no face recognition, no tracking individuals.
|
||||
- **Transit (GTFS-Realtime).** The proxy requests only registered operator feeds, validates redirects, bounds responses, and uses conditional requests, short snapshot caching and failure backoff. All 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.
|
||||
- **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 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.
|
||||
@@ -290,113 +258,6 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
|
||||
|
||||
---
|
||||
|
||||
<!-- data-library:start (generated by npm run library:docs) -->
|
||||
|
||||
## Data Library entries (fetched at runtime — not stored in this repo)
|
||||
|
||||
Each Data Library entry is a manifest in `src/library/entries/` pointing at a live upstream its provider hosts; see [docs/DATA-LIBRARY.md](docs/DATA-LIBRARY.md). An entry appears on the map only after a viewer adds it from **DATA LAYERS ▸ Browse data library**, and its attribution is registered in the Data attribution popover while it is in use. This table is generated from the manifests: edit the manifest, then run `npm run library:docs`.
|
||||
|
||||
| Entry | How it is served | Licence / terms | Attribution |
|
||||
| --- | --- | --- | --- |
|
||||
| **Alaska Statewide Imagery (2023)** (`ak-statewide-imagery`, `apps.geo.fpac.usda.gov`) | tiles, bbox, browser-direct | [Public Domain (U.S. Government work)](https://www.usgs.gov/information-policies-and-instructions/copyrights-and-credits) | "USDA / Alaska Geospatial Council" |
|
||||
| **Amsterdam traffic and ANPR camera positions** (`amsterdam-traffic-cameras`, `api.data.amsterdam.nl`) | dataset, geojson, browser-direct; operator adds `amsterdam-traffic-cameras` to `GEV_LIBRARY_ENABLE` | [Gemeente Amsterdam open data: public unless stated otherwise; no named licence](https://schemas.data.amsterdam.nl/datasets/verkeersinformatiesystemen/dataset) | "Gemeente Amsterdam, verkeersinformatiesystemen register (entry contributed by @Maher-Reven)" |
|
||||
| **Arkansas Statewide Imagery (2023)** (`ar-statewide-imagery`, `gis.arkansas.gov`) | tiles, bbox, browser-direct | [Public domain — Arkansas GIS Office open data](https://gis.arkansas.gov/) | "Arkansas GIS Office" |
|
||||
| **Arizona Statewide Imagery (2017)** (`az-statewide-imagery`, `azgeo.az.gov`) | tiles, xyz, browser-direct; operator adds `az-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://azgeo.az.gov/) | "AZGEO" |
|
||||
| **Digitales Orthophoto Bayern (DOP 40 cm, open)** (`bayern-dop40`, `geoservices.bayern.de`) | tiles, bbox, browser-direct | [CC BY 4.0](https://geodaten.bayern.de/opengeodata/) | "© Bayerische Vermessungsverwaltung" |
|
||||
| **Bismarck–Mandan, North Dakota Aerial Photography** (`bismarck-mandan-nd`, `ndgishub.nd.gov`) | tiles, bbox, browser-direct; operator adds `bismarck-mandan-nd` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.gis.nd.gov/) | "95West Aerial Mapping" |
|
||||
| **TopPlusOpen basemap (BKG)** (`bkg-topplus-open`, `sgx.geodatenzentrum.de`) | tiles, xyz, browser-direct | [Datenlizenz Deutschland – Namensnennung 2.0](https://www.govdata.de/dl-de/by-2-0) | "© BKG, dl-de/by-2-0" |
|
||||
| **Florida cameras (FL511)** (`cctv-florida-fl511`, `fl511.com`) | CCTV pack; operator sets `CCTV_FLORIDA_ENABLED=1` | [FL511 content: individual personal use only](https://fl511.com/privacy#disclaimer) | "Florida DOT — FL511.com" |
|
||||
| **Lithuania road cameras (Via Lietuva)** (`cctv-lithuania`, `eismoinfo.lt`) | CCTV pack; operator sets `CCTV_LITHUANIA_ENABLED=1` | [Via Lietuva open-data statement; camera photo coverage unconfirmed](https://eismoinfo.lt/eismoinfo-backend/content?id=44) | "Via Lietuva / eismoinfo.lt" |
|
||||
| **New York City cameras (NYC DOT TMC)** (`cctv-nyc-dot`, `webcams.nyctmc.org`) | CCTV pack; operator sets `CCTV_NYC_ENABLED=1` | [NYC DOT camera feed: data-sharing agreement required](https://www.nyc.gov/html/dot/html/about/datafeeds.shtml) | "NYC DOT Traffic Management Center" |
|
||||
| **Connecticut Statewide Imagery (2019)** (`ct-statewide-imagery`, `cteco.uconn.edu`) | tiles, bbox, browser-direct; operator adds `ct-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://ct-deep-gis-open-data-website-ctdeep.hub.arcgis.com/) | "CT ECO — UConn CLEAR / CT DEEP" |
|
||||
| **Dakota County, Minnesota Air Photo** (`dakota-county-mn`, `gisimg.co.dakota.mn.us`) | tiles, bbox, browser-direct; operator adds `dakota-county-mn` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://gis.co.dakota.mn.us/) | "Dakota County GIS" |
|
||||
| **District of Columbia Statewide Imagery (2025)** (`dc-statewide-imagery`, `imagery.dcgis.dc.gov`) | tiles, bbox, browser-direct | [Open data — DC public domain terms](https://opendata.dc.gov/pages/terms-of-use) | "Office of the Chief Technology Officer" |
|
||||
| **Delaware Statewide Imagery (2024)** (`de-statewide-imagery`, `imagery.firstmap.delaware.gov`) | tiles, bbox, browser-direct; operator adds `de-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://firstmap.delaware.gov/) | "This data is meant for use by requested parties. This data cannot be used without express consent.This data is meant for use by requested parties. This data cannot be used without express consent." |
|
||||
| **Eesti ortofoto (Maa-amet)** (`ee-maa-amet-ortofoto`, `tiles.maaamet.ee`) | tiles, xyz, browser-direct | [Maa-amet open data licence (attribution)](https://geoportaal.maaamet.ee/eng/Spatial-Data/Orthophotos-p309.html) | "© Maa-amet (Estonian Land Board)" |
|
||||
| **US power plants, 100 MW and up (EIA)** (`eia-power-plants`, `services2.arcgis.com`) | dataset, arcgis, browser-direct | [U.S. Energy Information Administration data: public domain](https://www.eia.gov/about/copyrights_reuse.php) | "U.S. Energy Information Administration (EIA-860, EIA-860M, EIA-923)" |
|
||||
| **Superfund sites (US EPA)** (`epa-superfund-sites`, `geopub.epa.gov`) | dataset, arcgis, browser-direct | [U.S. EPA data: public domain](https://www.epa.gov/web-policies-and-procedures/epa-disclaimers) | "U.S. Environmental Protection Agency — Facility Registry Service (EnviroMapper)" |
|
||||
| **Toxic release facilities (US EPA TRI)** (`epa-toxic-release-facilities`, `geopub.epa.gov`) | dataset, arcgis, browser-direct | [U.S. EPA data: public domain](https://www.epa.gov/web-policies-and-procedures/epa-disclaimers) | "U.S. Environmental Protection Agency — Toxics Release Inventory via the Facility Registry Service" |
|
||||
| **Home energy ratings, England and Wales (EPC)** (`epc-england-wales`, `s3.us-west-2.amazonaws.com`) | dataset, geoparquet, browser-direct | [Open Government Licence v3.0 (EPC data); OS Open UPRN under OGL](https://epc.opendatacommunities.org/docs/copyright) | "Energy Performance of Buildings Data, DLUHC; geocoded by Addresscloud with OS Open UPRN — contains OS data © Crown copyright and database right; via Source Cooperative" |
|
||||
| **US building footprints (FEMA USA Structures)** (`fema-usa-structures`, `services2.arcgis.com`) | dataset, arcgis, browser-direct | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | "FEMA Response Geospatial Office and Oak Ridge National Laboratory — USA Structures" |
|
||||
| **Biodiversity records (GBIF)** (`gbif-occurrence-density`, `api.gbif.org`) | tiles, xyz, browser-direct | [GBIF data use agreement (records under CC0, CC BY or CC BY-NC)](https://www.gbif.org/terms) | "GBIF.org — Global Biodiversity Information Facility" |
|
||||
| **Disaster alerts (GDACS)** (`gdacs-disaster-alerts`, `www.gdacs.org`) | dataset, geojson, browser-direct | [GDACS terms of use (free, attribution required)](https://www.gdacs.org/About/termofuse.aspx) | "GDACS — Global Disaster Alert and Coordination System (UN, European Commission)" |
|
||||
| **Grand Forks, North Dakota Aerial Photography** (`grand-forks-nd`, `ndgishub.nd.gov`) | tiles, bbox, browser-direct; operator adds `grand-forks-nd` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.gis.nd.gov/) | "Grand Forks/East Grand Forks Metropolitan Planning Organization (MPO)" |
|
||||
| **Japan standard map (GSI)** (`gsi-japan-map`, `cyberjapandata.gsi.go.jp`) | tiles, xyz, browser-direct | [GSI Tiles terms (free use with attribution)](https://maps.gsi.go.jp/development/ichiran.html) | "Geospatial Information Authority of Japan (GSI Tiles)" |
|
||||
| **Japan aerial photos (GSI)** (`gsi-japan-photo`, `cyberjapandata.gsi.go.jp`) | tiles, xyz, browser-direct | [GSI Tiles terms (free use with attribution)](https://maps.gsi.go.jp/development/ichiran.html) | "Geospatial Information Authority of Japan (GSI Tiles)" |
|
||||
| **Holocene volcanoes (Smithsonian GVP)** (`gvp-holocene-volcanoes`, `webservices.volcano.si.edu`) | dataset, geojson, proxied | [Smithsonian Terms of Use (personal, educational, non-commercial use; cite the Global Volcanism Program)](https://www.si.edu/termsofuse) | "Global Volcanism Program, Smithsonian Institution — Volcanoes of the World" |
|
||||
| **Iowa Statewide Imagery (2023)** (`ia-statewide-imagery`, `ortho.gis.iastate.edu`) | tiles, bbox, browser-direct; operator adds `ia-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://geodata.iowa.gov/) | "USDA NAIP, Iowa State University GIS Facility" |
|
||||
| **Idaho Statewide Imagery (2023)** (`id-statewide-imagery`, `insideidaho.org`) | tiles, bbox, browser-direct | [Public domain / open — INSIDE Idaho terms](https://insideidaho.org/) | "United States Department of Agriculture, Farm Production and Conservation Business Center, Geospatial Enterprise Operations (FPAC-BC-GEO)" |
|
||||
| **France aerial photos (IGN)** (`ign-orthophotos-france`, `data.geopf.fr`) | tiles, xyz, browser-direct | [Licence Ouverte / Etalab 2.0](https://www.etalab.gouv.fr/licence-ouverte-open-licence/) | "© IGN — Géoplateforme" |
|
||||
| **Illinois Statewide Imagery** (`il-statewide-imagery`, `data.isgs.illinois.edu`) | tiles, bbox, browser-direct; operator adds `il-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://clearinghouse.isgs.illinois.edu/) | "Illinois State Geological Survey / IDOT" |
|
||||
| **Maritime chokepoints (IMF PortWatch)** (`imf-portwatch-chokepoints`, `services9.arcgis.com`) | dataset, arcgis, browser-direct; operator adds `imf-portwatch-chokepoints` to `GEV_LIBRARY_ENABLE` | [IMF PortWatch terms; attribution required (reuse terms under review)](https://www.imf.org/external/terms.htm) | "IMF PortWatch (portwatch.imf.org)" |
|
||||
| **Port disruptions (IMF PortWatch)** (`imf-portwatch-disruptions`, `services9.arcgis.com`) | dataset, arcgis, browser-direct; operator adds `imf-portwatch-disruptions` to `GEV_LIBRARY_ENABLE` | [IMF PortWatch terms; attribution required (reuse terms under review)](https://www.imf.org/external/terms.htm) | "IMF PortWatch (portwatch.imf.org), with GDACS event data" |
|
||||
| **Indiana Statewide Imagery (2016)** (`in-statewide-imagery`, `di-ingov.img.arcgis.com`) | tiles, bbox, browser-direct | [CC0 1.0 Universal (public domain dedication)](https://creativecommons.org/publicdomain/zero/1.0/legalcode) | "Indiana Geographic Information Office (CC0)" |
|
||||
| **Surface water occurrence (JRC)** (`jrc-surface-water`, `storage.googleapis.com`) | tiles, xyz, browser-direct | [Copernicus programme: free, without restriction of use](https://global-surface-water.appspot.com/download) | "Source: EC JRC/Google — Pekel et al. (2016), Global Surface Water" |
|
||||
| **Norway counties and municipalities (Kartverket)** (`kartverket-admin-units`, `wms.geonorge.no`) | tiles, bbox, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway cadastral map (Kartverket)** (`kartverket-cadastre`, `wms.geonorge.no`) | tiles, bbox, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway sea depths (Kartverket)** (`kartverket-depth`, `wms.geonorge.no`) | tiles, bbox, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway hillshade (Kartverket)** (`kartverket-hillshade`, `wms.geonorge.no`) | tiles, bbox, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway nautical charts (Kartverket)** (`kartverket-nautical-charts`, `cache.kartverket.no`) | tiles, xyz, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway terrain relief (Kartverket DTM)** (`kartverket-terrain-relief`, `wms.geonorge.no`) | tiles, bbox, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway topographic map (Kartverket)** (`kartverket-topo`, `cache.kartverket.no`) | tiles, xyz, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway topographic map, greyscale (Kartverket)** (`kartverket-topo-grey`, `cache.kartverket.no`) | tiles, xyz, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Norway topographic raster map (Kartverket)** (`kartverket-toporaster`, `cache.kartverket.no`) | tiles, xyz, browser-direct | [CC BY 4.0 (Kartverket free products)](https://www.kartverket.no/api-og-data/vilkar-for-bruk) | "© Kartverket" |
|
||||
| **Kansas Statewide Imagery (2021)** (`ks-statewide-imagery`, `services.kgs.ku.edu`) | tiles, bbox, browser-direct; operator adds `ks-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://www.kansasgis.org/) | "FSA, USDA," |
|
||||
| **Kentucky Statewide Imagery** (`ky-statewide-imagery`, `kyraster.ky.gov`) | tiles, bbox, browser-direct | [Public domain with attribution](https://kygeonet.ky.gov/) | "KyFromAbove" |
|
||||
| **Lake County, Indiana Aerial Imagery** (`lake-county-in`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `lake-county-in` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.lakecountyin.org/) | "Lake County Surveyor's Office" |
|
||||
| **Lawrence, Kansas Aerial Photos** (`lawrence-ks`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `lawrence-ks` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://gis-lawrenceks.opendata.arcgis.com/) | "City of Lawrence GIS" |
|
||||
| **Lee County, Florida Aerial Imagery** (`lee-county-fl`, `gisimageserver.leegov.com`) | tiles, bbox, browser-direct; operator adds `lee-county-fl` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.leegov.com/gis) | "Eagle View, Lee County Property Appraiser, Lee County GIS" |
|
||||
| **Loudoun County, Virginia Imagery** (`loudoun-county-va`, `logis.loudoun.gov`) | tiles, bbox, browser-direct; operator adds `loudoun-county-va` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://geohub-loudoungis.opendata.arcgis.com/) | "Loudoun County GIS" |
|
||||
| **Louisville / LOJIC, Kentucky Imagery** (`louisville-lojic-ky`, `services.lojic.org`) | tiles, bbox, browser-direct; operator adds `louisville-lojic-ky` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.lojic.org/) | "LOJIC, USDA-NAIP" |
|
||||
| **Massachusetts Statewide Imagery** (`ma-statewide-imagery`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `ma-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://gis.data.mass.gov/) | "MassGIS, Commonwealth of Massachusetts" |
|
||||
| **Geologic map (Macrostrat)** (`macrostrat-geology`, `tiles.macrostrat.org`) | tiles, xyz, browser-direct | [CC BY 4.0 (Macrostrat; source maps credited by their publishers)](https://macrostrat.org/about) | "Macrostrat — geologic maps from their original publishers" |
|
||||
| **Global terrain shaded relief (Mapterhorn)** (`mapterhorn-terrain`, `tiles.mapterhorn.com`) | tiles, xyz, browser-direct | [Open data, per source (mostly CC BY 4.0 and public domain)](https://mapterhorn.com/attribution) | "© Mapterhorn and its open elevation sources" |
|
||||
| **MARIS Imagery, Mississippi** (`maris-ms`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `maris-ms` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.gis.ms.gov/) | "MARIS" |
|
||||
| **Maryland Statewide Imagery** (`md-statewide-imagery`, `mdgeodata.md.gov`) | tiles, bbox, browser-direct; operator adds `md-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://imap.maryland.gov/) | "Maryland iMAP" |
|
||||
| **Maine Statewide Imagery** (`me-statewide-imagery`, `gis.maine.gov`) | tiles, bbox, browser-direct; operator adds `me-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://www.maine.gov/geolib/) | "USDA Aerial Photography Field Office" |
|
||||
| **Michigan Statewide Imagery (2024)** (`mi-statewide-imagery`, `imagery.michigan.gov`) | tiles, bbox, browser-direct; operator adds `mi-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://gis-michigan.opendata.arcgis.com/) | "State of Michigan" |
|
||||
| **Mountain Village, Colorado Orthoimagery** (`mountain-village-co`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `mountain-village-co` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.townofmountainvillage.com/) | "Town of Mountain Village" |
|
||||
| **Mississippi Statewide Imagery** (`ms-statewide-imagery`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `ms-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://www.gis.ms.gov/) | "USDA-FSA-APFO" |
|
||||
| **Montana Statewide Imagery (2023)** (`mt-statewide-imagery`, `gisservice.mt.gov`) | tiles, bbox, browser-direct | [Public domain — Montana State Library open data](https://msl.mt.gov/geoinfo/) | "Montana State Library" |
|
||||
| **NAIP — National Agriculture Imagery Program** (`naip`, `imagery.nationalmap.gov`) | tiles, bbox, browser-direct | [Public Domain (U.S. Government work)](https://naip-usdaonline.hub.arcgis.com/) | "USDA NAIP" |
|
||||
| **Earth at night (NASA Black Marble 2016)** (`nasa-black-marble`, `gibs.earthdata.nasa.gov`) | tiles, xyz, browser-direct | [NASA data: U.S. public domain; no endorsement implied](https://www.earthdata.nasa.gov/engage/open-data-services-software-policies) | "NASA Earth Observatory / GIBS — VIIRS Black Marble" |
|
||||
| **NASA EONET open natural events** (`nasa-eonet-events`, `eonet.gsfc.nasa.gov`) | dataset, geojson, browser-direct | [NASA data: U.S. public domain; no endorsement implied](https://www.nasa.gov/nasa-brand-center/images-and-media/) | "NASA Earth Observatory Natural Event Tracker (EONET)" |
|
||||
| **North Carolina Statewide Imagery (2023)** (`nc-statewide-imagery`, `services.nconemap.gov`) | tiles, bbox, browser-direct | [Public domain — NC OneMap terms of use](https://www.nconemap.gov/pages/terms) | "NC911, NCCGIA" |
|
||||
| **North Central Texas (NCTCOG) Ortho** (`nctcog-tx`, `mapitimage.tarrantcounty.com`) | tiles, bbox, browser-direct; operator adds `nctcog-tx` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.nctcog.org/) | "North Central Texas Council of Governments" |
|
||||
| **North Dakota Statewide Imagery (2013)** (`nd-statewide-imagery`, `ndgishub.nd.gov`) | tiles, xyz, browser-direct; operator adds `nd-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open state geodata — attribution requested](https://www.gis.nd.gov/) | "ND Dept. of Transportation, ND Dept. of Emergency Services, and the ND GIS Hub" |
|
||||
| **New Orleans Regional Planning Commission Imagery** (`new-orleans-rpc-la`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `new-orleans-rpc-la` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.norpc.org/) | "Regional Planning Commission for Jefferson, Orleans, Plaquemines, St. Bernard, St. Charles, St. John the Baptist, St. Tammany, and Tangipahoa Parishes (RPC)" |
|
||||
| **New Hampshire Statewide Imagery (2022)** (`nh-statewide-imagery`, `nhgeodata.unh.edu`) | tiles, bbox, browser-direct; operator adds `nh-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open — NH GRANIT terms](https://www.granit.unh.edu/) | "NH GRANIT, University of New Hampshire" |
|
||||
| **New Jersey Statewide Imagery (2015)** (`nj-statewide-imagery`, `maps.nj.gov`) | tiles, xyz, browser-direct; operator adds `nj-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open — State of New Jersey provides data 'as is'](https://njgin.nj.gov/) | "Project definition, funding requests, and data distribution by NJ Office of Information Technology, Office of Geographic Information Systems (NJOIT - OGIS). Funding for the imagery project provided by" |
|
||||
| **US tide stations (NOAA CO-OPS)** (`noaa-tide-stations`, `api.tidesandcurrents.noaa.gov`) | dataset, json, browser-direct | [NOAA data: U.S. public domain](https://tidesandcurrents.noaa.gov/disclaimers.html) | "NOAA Center for Operational Oceanographic Products and Services (CO-OPS)" |
|
||||
| **Digitale Orthophotos NRW (DOP 10 cm)** (`nrw-dop`, `www.wms.nrw.de`) | tiles, bbox, browser-direct | [Datenlizenz Deutschland – Zero (public-domain-like)](https://www.govdata.de/dl-de/zero-2-0) | "Geobasis NRW (dl-de/zero-2-0)" |
|
||||
| **NSW Imagery (Six Maps)** (`nsw-imagery`, `maps.six.nsw.gov.au`) | tiles, xyz, browser-direct | [CC BY 4.0](https://www.spatial.nsw.gov.au/products_and_services/web_services) | "© Spatial Services NSW" |
|
||||
| **US bridges and their condition (NBI)** (`ntad-national-bridges`, `services.arcgis.com`) | dataset, arcgis, browser-direct | [U.S. government work: unrestricted public use](https://www.bts.gov/ntad) | "USDOT Bureau of Transportation Statistics — National Transportation Atlas Database, National Bridge Inventory" |
|
||||
| **Northwest Arkansas (Benton & Washington County) Imagery** (`nwa-benton-washington-ar`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `nwa-benton-washington-ar` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://www.nwarpc.org/) | "NWARPC; Washington County; Benton County" |
|
||||
| **US weather alerts with drawn areas (NWS)** (`nws-active-alerts`, `api.weather.gov`) | dataset, geojson, browser-direct | [NWS data: U.S. public domain](https://www.weather.gov/disclaimer) | "NOAA National Weather Service" |
|
||||
| **New York Statewide Imagery (2019)** (`ny-statewide-imagery`, `orthos.its.ny.gov`) | tiles, xyz, browser-direct | [Public domain — NYS open data](https://data.gis.ny.gov/) | "USDA; NYS ITS Geospatial Services" |
|
||||
| **Oakland County, Michigan Ortho Imagery** (`oakland-county-mi`, `gisservices.oakgov.com`) | tiles, bbox, browser-direct; operator adds `oakland-county-mi` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://oakgov.maps.arcgis.com/) | "Oakland County GIS" |
|
||||
| **Ohio Statewide Imagery** (`oh-statewide-imagery`, `maps.ohio.gov`) | tiles, xyz, browser-direct | [Public domain — Ohio open data](https://maps.ohio.gov/) | "Ohio Geographically Referenced Information Program (OGRIP)" |
|
||||
| **Oregon Statewide Imagery (2000)** (`or-statewide-imagery`, `imagery.oregonexplorer.info`) | tiles, bbox, browser-direct | [Public domain — Oregon open data](https://www.oregon.gov/geo/) | "Oregon, GEO, DAS" |
|
||||
| **Pennsylvania Statewide Imagery** (`pa-statewide-imagery`, `imagery.pasda.psu.edu`) | tiles, bbox, browser-direct | [Public domain — PASDA open data](https://www.pasda.psu.edu/) | "PASDA / PAMAP, Commonwealth of Pennsylvania" |
|
||||
| **Ortofotomapa Polski (GUGiK)** (`pl-ortofotomapa`, `mapy.geoportal.gov.pl`) | tiles, bbox, browser-direct | [Polish open geodata (free reuse, Prawo geodezyjne art. 40a)](https://www.geoportal.gov.pl/) | "© GUGiK / geoportal.gov.pl" |
|
||||
| **PNOA Máxima Actualidad** (`pnoa-ma`, `www.ign.es`) | tiles, bbox, browser-direct | [CC BY 4.0 (compatible) — Orden FOM/2807/2015](https://www.scne.es/productos.html) | "PNOA © Instituto Geográfico Nacional de España" |
|
||||
| **Portland Metro (RLIS), Oregon Aerial Photos** (`portland-metro-or`, `utility.arcgis.com`) | tiles, xyz, browser-direct; operator adds `portland-metro-or` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://rlisdiscovery.oregonmetro.gov/) | "Oregon Metro RLIS" |
|
||||
| **Ortofotos de Portugal Continental (DGT)** (`pt-dgt-ortos`, `ortos.dgterritorio.gov.pt`) | tiles, bbox, browser-direct | [CC BY 4.0](https://www.dgterritorio.gov.pt/) | "© Direção-Geral do Território" |
|
||||
| **Rhode Island Statewide Imagery (2019)** (`ri-statewide-imagery`, `tiles.arcgis.com`) | tiles, xyz, browser-direct; operator adds `ri-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open — RIGIS terms](https://www.rigis.org/) | "URI EDC, RIGIS" |
|
||||
| **Switzerland aerial photos (SWISSIMAGE 10 cm)** (`swisstopo-swissimage`, `wmts.geo.admin.ch`) | tiles, xyz, browser-direct | [swisstopo free geodata (OGD): free use, including commercial, with source credit](https://www.swisstopo.admin.ch/en/terms-of-use-free-geodata-and-geoservices) | "©swisstopo — SWISSIMAGE 10 cm" |
|
||||
| **Texas Statewide Imagery (2022)** (`tx-statewide-imagery`, `imagery.geographic.texas.gov`) | tiles, bbox, browser-direct | [Public domain — TxGIO open data](https://geographic.texas.gov/) | "Texas Geographic Information Office (TxGIO)" |
|
||||
| **Union County, Ohio Aerial Imagery** (`union-county-oh`, `www7.co.union.oh.us`) | tiles, bbox, browser-direct; operator adds `union-county-oh` to `GEV_LIBRARY_ENABLE` | [Local government open data — see publisher terms](https://union-county-auditor-unionco.hub.arcgis.com/) | "Union County / City of Marysville GIS" |
|
||||
| **US rivers and lakes (USGS)** (`usgs-hydrography`, `basemap.nationalmap.gov`) | tiles, xyz, browser-direct | [USGS The National Map: U.S. public domain](https://www.usgs.gov/information-policies-and-instructions/copyrights-and-credits) | "USGS The National Map — National Hydrography Dataset" |
|
||||
| **US topographic map (USGS)** (`usgs-topo`, `basemap.nationalmap.gov`) | tiles, xyz, browser-direct | [USGS The National Map: U.S. public domain](https://www.usgs.gov/information-policies-and-instructions/copyrights-and-credits) | "USGS The National Map" |
|
||||
| **Virginia Statewide Imagery** (`va-statewide-imagery`, `vginmaps.vdem.virginia.gov`) | tiles, xyz, browser-direct; operator adds `va-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open — VGIN VBMP terms](https://vgin.vdem.virginia.gov/) | "Virginia Geographic Information Network (VGIN)" |
|
||||
| **Vermont Statewide Imagery** (`vt-statewide-imagery`, `maps.vcgi.vermont.gov`) | tiles, bbox, browser-direct | [Public domain — Vermont open data](https://vcgi.vermont.gov/) | "Vermont Center for Geographic Information (VCGI)" |
|
||||
| **Orthophotos de Wallonie** (`wallonie-ortho`, `geoservices.wallonie.be`) | tiles, bbox, browser-direct | [Conditions ouvertes SPW (attribution)](https://geoportail.wallonie.be/catalogue-donnees) | "© Service public de Wallonie" |
|
||||
| **Wisconsin Statewide Imagery (2020)** (`wi-statewide-imagery`, `di-wisdot.img.arcgis.com`) | tiles, bbox, browser-direct; operator adds `wi-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open — WisconsinView / state open data](https://www.wisconsinview.org/) | "WisDOT - NAIP 2020" |
|
||||
| **Global power plants (WRI)** (`wri-global-power-plants`, `raw.githubusercontent.com`) | dataset, csv, browser-direct | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | "World Resources Institute, Global Power Plant Database (2021)" |
|
||||
| **West Virginia Statewide Imagery** (`wv-statewide-imagery`, `services.wvgis.wvu.edu`) | tiles, bbox, browser-direct; operator adds `wv-statewide-imagery` to `GEV_LIBRARY_ENABLE` | [Open — WV GIS Technical Center terms](https://wvgis.wvu.edu/) | "WV GIS Technical Center, West Virginia University" |
|
||||
|
||||
<!-- data-library:end -->
|
||||
|
||||
## Bundled snapshots
|
||||
|
||||
Static datasets shipped in the repo for an out-of-the-box experience. **None are MIT** — each keeps its own license (see the carve-out in [LICENSE](LICENSE)). Each folder also has its own provenance README.
|
||||
@@ -408,7 +269,6 @@ The [Bhote Koshi event pack](public/events/bhote-koshi-2026/README.md), under `p
|
||||
| **Datacenters** (4,352) | `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 |
|
||||
| **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" |
|
||||
@@ -467,7 +327,7 @@ credits it anyway as `SPECS: CCTV-DATABASE.COM` beside any spec line it renders.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -564,11 +424,8 @@ Global Forecast System (GFS). The `/api/wind` server-side proxy selects the
|
||||
latest available 0.25° cycle, reads its `.idx` inventory, byte-range fetches only
|
||||
the `UGRD`/`VGRD` 10 m GRIB2 messages, optionally adding `TMP` at 2 m or
|
||||
`PRMSL` at mean sea level from the same run and forecast time. It decodes the
|
||||
selected messages with ecCodes (WASM) and resamples them to a compact grid (1° by
|
||||
default) on a worker thread, so a refresh does not stall other API routes, and
|
||||
serves a manifest plus Float32 U/V and optional scalar values. The sampled wind
|
||||
of the current forecast file is kept, so choosing a color field fetches only
|
||||
that field. It is keyless, cached
|
||||
selected messages with ecCodes (WASM), resamples to a compact grid (1° by
|
||||
default), and serves a manifest plus Float32 U/V and optional scalar values. It is keyless, cached
|
||||
per cycle for an hour, and is a **forecast, not an observation**: the particles
|
||||
show model flow, not measured wind. The renderer bakes bounded curves through
|
||||
the sampled field and animates their phase on the GPU, with a canvas fallback.
|
||||
@@ -691,4 +548,4 @@ NOAA density imagery with the raw GLM product.
|
||||
|
||||
## 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, Open Vessel Photos, 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, 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.
|
||||
@@ -56,6 +56,7 @@ In short: the MIT grant does not extend to any third-party data. See
|
||||
DATA_SOURCES.md for the full per-source license + attribution summary, and
|
||||
remove any dataset whose terms don't fit your use.
|
||||
|
||||
The 3D models under public/models/ are the project's procedural fleet
|
||||
models, generated from code by the project maintainers, and are covered by
|
||||
the MIT license above like the source code. See public/models/README.md.
|
||||
The 3D models under public/models/ are also third-party works and are NOT
|
||||
licensed under MIT. They remain under their individual licenses. See
|
||||
public/models/README.md for the author, source, license, modification notice,
|
||||
and attribution covering each model file.
|
||||
@@ -60,7 +60,7 @@ Start with the included data sources, then add your own. Each layer is a separat
|
||||
- **📡 Contacts:** A 250 km roster of everything near your target — step through live aircraft and drop into any cockpit.
|
||||
- **🎯 Click-to-track anything:** Camera locks on, draws a fading trail, surfaces full metadata — and a tracked fire or vessel hands you off to the nearest live camera in one click.
|
||||
- **🖊️ Voice whiteboard:** Speak annotations onto the world — real boundary polygons, marks, and routes.
|
||||
- **🛫 3D fleet:** 121 procedural models drawn life size by type — airliners, business jets, helicopters, drones, ships, buses, trams and trains — and every contact swaps from glyph to 3D model as you close in.
|
||||
- **🛫 3D hangar:** Real per-class aircraft models — 787, ATR-72, Citation, Bell 206, MQ-9 — and a tracked contact swaps from glyph to 3D model as you close in.
|
||||
- **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir.
|
||||
- **🟩 Detection overlay:** Screen-space bounding boxes and IDs on everything in view.
|
||||
- **🎖️ Military HUD:** Tactical heads-up display with intelligence-style telemetry.
|
||||
@@ -340,6 +340,8 @@ 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."_
|
||||
8. **Come home.** Hit **Reset Globe** — or just say _"zoom out to a globe view."_
|
||||
|
||||
Open **Visual Presets → Globe Engine** to switch between Cesium and experimental Navara. The globe switches in place at the current view and remembers your choice. A link with `?engine=cesium` or `?engine=navara` overrides the saved choice. A fresh visit starts with Cesium.
|
||||
|
||||
**Keyboard:** `1`–`6` visual styles · `H` HUD · `D` detection · `C` CCTV · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
|
||||
|
||||
---
|
||||
@@ -384,7 +386,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.
|
||||
- **🔒 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:
|
||||
|
||||
@@ -446,7 +448,6 @@ Twenty-three layers and map sources. **Twenty-one have a keyless path.** Some of
|
||||
| 📷 **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 | 🟢 |
|
||||
| 🚌 **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 | 🟢 |
|
||||
| 🚆 **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 | 🟢 |
|
||||
| 🧭 **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 |
|
||||
|
||||
@@ -73,9 +73,7 @@ The data proxies under `server/providers/` are written so the browser cannot tur
|
||||
- **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.
|
||||
- **The Data Library proxy fetches manifest upstreams only.** `/api/library/data/<id>`, `/api/library/tile/<id>/<z>/<x>/<y>` and `/api/library/index/<id>` (a partitioned GeoParquet entry's file index, built from an S3 ListObjectsV2 listing and one ranged footer read per file, cached in memory for the entry's `cacheSeconds`) resolve the id against the validated manifests in `src/library/entries/`; the browser never supplies a URL, and every derived request must stay on the manifest's https host. Redirects are refused, dataset bodies are capped while streaming at the manifest's `maxBytes` (at most 32 MiB) and tiles at 2 MiB of `image/png`, `image/jpeg` or `image/webp`, responses are cached in memory only (at most 512 tiles), requests are rate-limited per client, and upstream error text is never returned. A keyed entry must be proxied; its key is read from this server's environment through the key registry and its routes refuse cross-site and forwarded requests like the other cost-bearing routes. Browser-direct entries are fetched by the browser with no cookies or referrer.
|
||||
- **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.
|
||||
- **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.
|
||||
@@ -134,7 +132,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.
|
||||
- **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.
|
||||
- **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
|
||||
|
||||
|
||||
+21
-16
@@ -1,9 +1,9 @@
|
||||
# Third-party software notices
|
||||
|
||||
The project depends on third-party npm packages whose own licenses apply. This
|
||||
file records the packages added for the browser-local SDR feature, the
|
||||
Street Level viewer and the Data Library's GeoParquet reader; the complete resolved dependency inventory remains in
|
||||
`package-lock.json`.
|
||||
file records the packages added for the browser-local SDR feature and the
|
||||
Street Level viewer, and the CesiumJS code ported into `src/geo/`; the complete
|
||||
resolved dependency inventory remains in `package-lock.json`.
|
||||
|
||||
## Web RTL-SDR
|
||||
|
||||
@@ -46,17 +46,22 @@ Street Level viewer and the Data Library's GeoParquet reader; the complete resol
|
||||
- License: MIT
|
||||
- License text: <https://github.com/mrdoob/three.js/blob/dev/LICENSE>
|
||||
|
||||
## hyparquet
|
||||
## CesiumJS (portions ported to src/geo)
|
||||
|
||||
- Package: `hyparquet` 1.31.2 (loaded on demand when a Data Library
|
||||
GeoParquet entry is switched on)
|
||||
- Author: Hyperparam
|
||||
- Source: <https://github.com/hyparam/hyparquet>
|
||||
- License: MIT
|
||||
|
||||
## fzstd (hyparquet ZSTD decompressor)
|
||||
|
||||
- Package: `fzstd` 0.1.1
|
||||
- Author: Copyright (c) 2020 Arjun Barrett
|
||||
- Source: <https://github.com/101arrowz/fzstd>
|
||||
- License: MIT
|
||||
- Package: `cesium` 1.138.0 (`@cesium/engine` 22.3.0); the files in `src/geo/`
|
||||
are adapted from `@cesium/engine/Source/Core` (`Math.js`, `Ellipsoid.js`,
|
||||
`scaleToGeodeticSurface.js`, `Cartographic.js`, `Cartesian2.js`,
|
||||
`Cartesian3.js`, `Matrix3.js`, `Matrix4.js`, `Quaternion.js`,
|
||||
`Transforms.js`, `BoundingSphere.js`, `EllipsoidGeodesic.js`, `JulianDate.js`,
|
||||
`Simon1994PlanetaryPositions.js`) and say so in their first
|
||||
line. The changes: the classes' static functions became module functions,
|
||||
vectors, geodetic positions and quaternions became plain-data instances of
|
||||
module-private constructors (own enumerable fields only, no methods),
|
||||
matrices plain arrays, and the ellipsoid a frozen object; the debug
|
||||
argument checks were left out, as in Cesium's release build; only the subset
|
||||
gev uses was kept.
|
||||
- Author: Copyright 2011-2024 CesiumJS Contributors
|
||||
- Source: <https://github.com/CesiumGS/cesium>
|
||||
- License: Apache License 2.0
|
||||
- License text: `node_modules/@cesium/engine/LICENSE.md` (shipped with the
|
||||
dependency) and <https://www.apache.org/licenses/LICENSE-2.0>
|
||||
@@ -0,0 +1,58 @@
|
||||
// Build guard: the Cesium-mode page must not load three or Navara. The
|
||||
// initial chunks are every entry chunk and what it imports statically;
|
||||
// navaraMode.js reaches Navara only through import(), so three and
|
||||
// @navaramap/* belong to lazy chunks. This checks the bundle's module ids
|
||||
// (what Rollup put in each chunk), and fails the build otherwise.
|
||||
|
||||
/**
|
||||
* Module ids that only the Navara engine may load, when no package list is
|
||||
* given (the browser build passes the full dependency closure,
|
||||
* navaraDependencies.js).
|
||||
*/
|
||||
export const ENGINE_ONLY_MODULES =
|
||||
/[\\/]node_modules[\\/](?:three|@navaramap[\\/][^\\/]+|postprocessing)[\\/]/;
|
||||
|
||||
/**
|
||||
* Engine-only modules in initial chunks of a Rollup bundle, sorted by chunk.
|
||||
* @param {Record<string, { type: string, isEntry?: boolean, imports?: string[], moduleIds?: string[] }>} bundle
|
||||
* @returns {{ chunk: string, module: string }[]}
|
||||
*/
|
||||
export function initialChunkViolations(bundle, pattern = ENGINE_ONLY_MODULES) {
|
||||
const initial = new Set();
|
||||
const visit = (fileName) => {
|
||||
const chunk = bundle[fileName];
|
||||
if (!chunk || chunk.type !== 'chunk' || initial.has(fileName)) return;
|
||||
initial.add(fileName);
|
||||
for (const imported of chunk.imports ?? []) visit(imported);
|
||||
};
|
||||
for (const [fileName, chunk] of Object.entries(bundle))
|
||||
if (chunk.type === 'chunk' && chunk.isEntry) visit(fileName);
|
||||
const violations = [];
|
||||
for (const fileName of [...initial].sort())
|
||||
for (const module of bundle[fileName].moduleIds ?? [])
|
||||
if (pattern.test(module)) violations.push({ chunk: fileName, module });
|
||||
return violations;
|
||||
}
|
||||
|
||||
/** The Rollup plugin: fails generateBundle when an initial chunk holds three or Navara. */
|
||||
export function initialChunkGuard({
|
||||
log = console.log,
|
||||
pattern = ENGINE_ONLY_MODULES,
|
||||
} = {}) {
|
||||
return {
|
||||
name: 'gev-initial-chunk-guard',
|
||||
generateBundle(_options, bundle) {
|
||||
const violations = initialChunkViolations(bundle, pattern);
|
||||
if (violations.length) {
|
||||
this.error(
|
||||
'Navara-only modules in the Cesium-mode initial chunks:\n' +
|
||||
violations
|
||||
.map(({ chunk, module }) => ` ${chunk}: ${module}`)
|
||||
.join('\n'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
log('[initial-chunks] no Navara-only modules in the initial chunks');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/** Where @navaramap/three keeps its prebuilt worker assets, or null. */
|
||||
function defaultAssetsDir() {
|
||||
try {
|
||||
const entry = fileURLToPath(import.meta.resolve('@navaramap/three'));
|
||||
return path.join(path.dirname(entry), 'assets');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navara's prebuilt workers (@navaramap/three/dist/assets) load their wasm by
|
||||
* file name next to themselves (`new URL('x-hash.wasm', self.location)`), a
|
||||
* reference the bundler cannot see. When a build includes Navara (the
|
||||
* ?engine=navara chunk), copy every such wasm the bundle lacks into
|
||||
* `assets/` under its own name. A build without Navara gets none.
|
||||
* @param {{ assetsDir?: string | null }} [options]
|
||||
*/
|
||||
export function navaraWorkerAssets({ assetsDir } = {}) {
|
||||
return {
|
||||
name: 'navara-worker-assets',
|
||||
generateBundle(_options, bundle) {
|
||||
const usesNavara = Object.values(bundle).some(
|
||||
(file) => file.type === 'chunk' && file.code.includes('navara_wasm'),
|
||||
);
|
||||
if (!usesNavara) return;
|
||||
const dir = assetsDir === undefined ? defaultAssetsDir() : assetsDir;
|
||||
if (!dir || !existsSync(dir)) return;
|
||||
for (const name of readdirSync(dir).sort()) {
|
||||
if (!name.endsWith('.wasm')) continue;
|
||||
const fileName = `assets/${name}`;
|
||||
if (bundle[fileName]) continue;
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName,
|
||||
source: readFileSync(path.join(dir, name)),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const NAVARA_MODULE = /\/src\/globe\/navara\//;
|
||||
|
||||
/**
|
||||
* Rollup chunkFileNames: the lazy chunk holding the Navara engine
|
||||
* (src/globe/navara, with the Navara packages it pulls in) is named
|
||||
* `navara-engine-[hash].js`; every other chunk keeps Vite's default name.
|
||||
* Naming only (not manualChunks, which would pull modules shared with the
|
||||
* Cesium entry into the Navara chunk), so the chunking is unchanged.
|
||||
* @param {{ isEntry?: boolean, moduleIds?: string[] }} chunk
|
||||
* @returns {string}
|
||||
*/
|
||||
export function navaraChunkFileNames(chunk) {
|
||||
const navara =
|
||||
!chunk.isEntry &&
|
||||
(chunk.moduleIds ?? []).some((id) =>
|
||||
NAVARA_MODULE.test(id.replaceAll('\\', '/')),
|
||||
);
|
||||
return navara ? 'assets/navara-engine-[hash].js' : 'assets/[name]-[hash].js';
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// The packages only the Navara engine brings: the dependency closure of
|
||||
// @navaramap/three and @navaramap/three-default-plugin (dependencies and
|
||||
// peer dependencies, read from node_modules), without the packages the
|
||||
// app's other bundled dependencies reach too (Cesium is external), plus
|
||||
// three and postprocessing, which
|
||||
// the Cesium-mode page never loads statically either. The initial-chunk
|
||||
// guard (initialChunkGuard.js) fails the build when one of them is in an
|
||||
// initial chunk.
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export const NAVARA_ROOTS = Object.freeze([
|
||||
'@navaramap/three',
|
||||
'@navaramap/three-default-plugin',
|
||||
]);
|
||||
const ALWAYS_ENGINE_ONLY = Object.freeze(['three', 'postprocessing']);
|
||||
// Not bundled: vite-plugin-cesium serves Cesium as a script, so what only
|
||||
// Cesium reaches (draco3d, …) is not in the app's bundled graph.
|
||||
const EXTERNAL = Object.freeze(['cesium']);
|
||||
|
||||
function readManifest(root, name) {
|
||||
const file = path.join(root, 'node_modules', name, 'package.json');
|
||||
return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : null;
|
||||
}
|
||||
|
||||
/** Every package reachable from `names` through installed manifests. */
|
||||
export function dependencyClosure(root, names) {
|
||||
const seen = new Set();
|
||||
const queue = [...names];
|
||||
while (queue.length) {
|
||||
const name = queue.pop();
|
||||
if (seen.has(name)) continue;
|
||||
const manifest = readManifest(root, name);
|
||||
if (!manifest) continue;
|
||||
seen.add(name);
|
||||
for (const next of Object.keys({
|
||||
...manifest.dependencies,
|
||||
...manifest.peerDependencies,
|
||||
}))
|
||||
queue.push(next);
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
/** The engine-only package names, sorted. */
|
||||
export function navaraOnlyPackages(root) {
|
||||
const app = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||
const navara = dependencyClosure(root, NAVARA_ROOTS);
|
||||
const appRoots = Object.keys(app.dependencies ?? {}).filter(
|
||||
(name) =>
|
||||
!navara.has(name) &&
|
||||
!ALWAYS_ENGINE_ONLY.includes(name) &&
|
||||
!EXTERNAL.includes(name),
|
||||
);
|
||||
const shared = dependencyClosure(root, appRoots);
|
||||
return [
|
||||
...new Set([
|
||||
...[...navara].filter((name) => !shared.has(name)),
|
||||
...ALWAYS_ENGINE_ONLY,
|
||||
]),
|
||||
].sort();
|
||||
}
|
||||
|
||||
/** A module-id pattern matching node_modules/<package>/ for the names. */
|
||||
export function packagePattern(names) {
|
||||
const escaped = names.map((name) =>
|
||||
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('/', '[\\\\/]'),
|
||||
);
|
||||
return new RegExp(
|
||||
`[\\\\/]node_modules[\\\\/](?:${escaped.join('|')})[\\\\/]`,
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,9 @@
|
||||
* PANEL_BASE next to the app.
|
||||
*/
|
||||
|
||||
import cesium from 'vite-plugin-cesium';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
// The panel loads these paths (src/tools/globePanel.js); a test keeps the
|
||||
// two in step.
|
||||
export const PANEL_BASE = '/panel/';
|
||||
@@ -18,6 +21,27 @@ export function panelBuildConfig(config) {
|
||||
return {
|
||||
...config,
|
||||
base: PANEL_BASE,
|
||||
plugins: (config.plugins ?? []).map((plugin) =>
|
||||
plugin?.name === 'vite-plugin-cesium' ? cesium() : plugin,
|
||||
),
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: [
|
||||
// The panel runs Cesium: the Navara mode and Navara's wind mesh
|
||||
// (the panel's only paths to Navara and three) become stubs.
|
||||
{
|
||||
find: /^(?:\.\/|.*\/app\/)navaraMode\.js$|^.*\/globe\/navara\/windFlowMesh\.js$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./panelNavara.js', import.meta.url),
|
||||
),
|
||||
},
|
||||
...(Array.isArray(config.resolve?.alias)
|
||||
? config.resolve.alias
|
||||
: Object.entries(config.resolve?.alias ?? {}).map(
|
||||
([find, replacement]) => ({ find, replacement }),
|
||||
)),
|
||||
],
|
||||
},
|
||||
build: {
|
||||
...config.build,
|
||||
outDir: PANEL_OUT_DIR,
|
||||
@@ -26,6 +50,10 @@ export function panelBuildConfig(config) {
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
...config.build?.rollupOptions,
|
||||
// The panel is a single chunk; its alias excludes the Navara engine.
|
||||
plugins: (config.build?.rollupOptions?.plugins ?? []).filter(
|
||||
(plugin) => plugin?.name !== 'gev-initial-chunk-guard',
|
||||
),
|
||||
output: { inlineDynamicImports: true },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// A Vite build through the panel alias resolves renderers.js's relative
|
||||
// import of Navara's wind mesh to the stub (scripts/build-panel.mjs uses the
|
||||
// same alias).
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { build } from 'vite';
|
||||
import { panelBuildConfig } from './panel.js';
|
||||
|
||||
test('a Vite build resolves the relative wind-mesh import through the panel alias', async (t) => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'panel-alias-'));
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
mkdirSync(join(root, 'src/app/layers'), { recursive: true });
|
||||
mkdirSync(join(root, 'src/globe/navara'), { recursive: true });
|
||||
writeFileSync(
|
||||
join(root, 'src/app/layers/renderers.js'),
|
||||
"export const load = () => import('../../globe/navara/windFlowMesh.js');\n",
|
||||
);
|
||||
writeFileSync(
|
||||
join(root, 'src/globe/navara/windFlowMesh.js'),
|
||||
'export const real = true;\n',
|
||||
);
|
||||
const { resolve } = panelBuildConfig({ plugins: [] });
|
||||
const [output] = await build({
|
||||
configFile: false,
|
||||
root,
|
||||
logLevel: 'silent',
|
||||
resolve,
|
||||
build: {
|
||||
outDir: join(root, 'out'),
|
||||
write: false,
|
||||
lib: {
|
||||
entry: join(root, 'src/app/layers/renderers.js'),
|
||||
formats: ['es'],
|
||||
fileName: 'x',
|
||||
},
|
||||
rollupOptions: { output: { inlineDynamicImports: true } },
|
||||
},
|
||||
});
|
||||
const [chunk] = output.output;
|
||||
assert.match(chunk.code, /Navara is unavailable in the panel/);
|
||||
assert.doesNotMatch(chunk.code, /real = true/);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
// The panel host transports Cesium assets; Navara workers need a web page.
|
||||
export function createNavaraScene() {
|
||||
throw new Error('Navara is unavailable in the panel');
|
||||
}
|
||||
|
||||
export function navaraToolBackend() {
|
||||
throw new Error('Navara is unavailable in the panel');
|
||||
}
|
||||
|
||||
export function createWindFlowMesh() {
|
||||
throw new Error('Navara is unavailable in the panel');
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createBrowserViteConfig } from './vite.js';
|
||||
import { panelBuildConfig } from './panel.js';
|
||||
|
||||
test('panel HTML lists Cesium assets for the host resource loader', () => {
|
||||
const config = panelBuildConfig(createBrowserViteConfig());
|
||||
const plugin = config.plugins.find(
|
||||
({ name }) => name === 'vite-plugin-cesium',
|
||||
);
|
||||
plugin.config(config, { command: 'build' });
|
||||
assert.equal(typeof plugin.transformIndexHtml, 'function');
|
||||
const tags = plugin.transformIndexHtml();
|
||||
assert.ok(tags.some(({ attrs }) => attrs.src === '/panel/cesium/Cesium.js'));
|
||||
assert.ok(
|
||||
tags.some(
|
||||
({ attrs }) => attrs.href === '/panel/cesium/Widgets/widgets.css',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('the panel replaces Navara entry points and retains browser aliases', () => {
|
||||
const browser = createBrowserViteConfig();
|
||||
const config = panelBuildConfig(browser);
|
||||
const alias = config.resolve.alias.find(({ find }) =>
|
||||
find.test('./navaraMode.js'),
|
||||
);
|
||||
assert.ok(alias.replacement.endsWith('/build/panelNavara.js'));
|
||||
// Navara's wind mesh imports three; the Cesium wind backend stays.
|
||||
assert.equal(
|
||||
'../../globe/navara/windFlowMesh.js'.replace(alias.find, alias.replacement),
|
||||
alias.replacement,
|
||||
);
|
||||
assert.equal(alias.find.test('../../globe/cesium/windFlow.js'), false);
|
||||
assert.ok(config.resolve.alias.includes(browser.resolve.alias[0]));
|
||||
});
|
||||
|
||||
test('Three imports and requires share its ESM entry', () => {
|
||||
const { resolve } = createBrowserViteConfig();
|
||||
assert.deepEqual(resolve.dedupe, ['three']);
|
||||
const alias = resolve.alias.find(({ find }) => find.test('three'));
|
||||
assert.ok(alias.replacement.endsWith('/three/build/three.module.js'));
|
||||
assert.equal(alias.find.test('three/addons/loaders/GLTFLoader.js'), false);
|
||||
});
|
||||
+50
-2
@@ -5,6 +5,9 @@ import { VUE_PLUGIN_OPTIONS } from './vueOptions.js';
|
||||
import { DEFAULT_ALLOWED_HOSTS, hostCheckPlugin } from './allowedHosts.js';
|
||||
import { embedFramingPlugin } from './embed-framing.js';
|
||||
import { panelBuildPlugin } from './panel.js';
|
||||
import { navaraChunkFileNames, navaraWorkerAssets } from './navaraAssets.js';
|
||||
import { initialChunkGuard } from './initialChunkGuard.js';
|
||||
import { navaraOnlyPackages, packagePattern } from './navaraDependencies.js';
|
||||
|
||||
/**
|
||||
* The repository root (this file lives at `<root>/build/vite.js`), as a
|
||||
@@ -183,7 +186,9 @@ export function createBrowserViteConfig({
|
||||
apiResponseHeadersPlugin(),
|
||||
// Next, so no provider route answers a Host the server does not allow.
|
||||
hostCheckPlugin(),
|
||||
cesium(),
|
||||
// Keep the SDK asset serving/copying and external imports, but let the
|
||||
// scene load the script and stylesheet only when Cesium is requested.
|
||||
{ ...cesium(), transformIndexHtml: undefined },
|
||||
// Application views are Vue single-file components.
|
||||
vue(VUE_PLUGIN_OPTIONS),
|
||||
...plugins,
|
||||
@@ -194,6 +199,20 @@ export function createBrowserViteConfig({
|
||||
// A production build must not clean the dependency cache a running dev
|
||||
// server is still serving optimized module URLs from.
|
||||
...(command === 'build' ? { cacheDir: 'node_modules/.vite-build' } : {}),
|
||||
resolve: {
|
||||
dedupe: ['three'],
|
||||
alias: [
|
||||
{
|
||||
find: /^three$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL(
|
||||
'../node_modules/three/build/three.module.js',
|
||||
import.meta.url,
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
optimizeDeps: {
|
||||
// First reached through the SDR worker or a dynamic import. Pre-bundle
|
||||
// them at startup so first use cannot invalidate already-transformed
|
||||
@@ -206,6 +225,18 @@ export function createBrowserViteConfig({
|
||||
// Loaded on demand when the first street-level image opens.
|
||||
'mapillary-js',
|
||||
],
|
||||
// Navara's wasm-bindgen glue loads new URL('x_bg.wasm', import.meta.url)
|
||||
// and its workers by URL; both must resolve next to the package's own
|
||||
// files, not inside .vite/deps. Dev server only: the experimental
|
||||
// Navara engine is a dynamic import outside the Cesium-mode bundle.
|
||||
exclude: [
|
||||
'@navaramap/three',
|
||||
'@navaramap/three-default-plugin',
|
||||
'@navaramap/engine',
|
||||
'@navaramap/engine-api',
|
||||
'@navaramap/engine-worker',
|
||||
'@navaramap/engine-font-worker',
|
||||
],
|
||||
},
|
||||
server: {
|
||||
host: host || 'localhost',
|
||||
@@ -258,6 +289,23 @@ export function createBrowserViteConfig({
|
||||
mapillaryToken ?? '',
|
||||
),
|
||||
},
|
||||
build: { chunkSizeWarningLimit: 1500 },
|
||||
build: {
|
||||
chunkSizeWarningLimit: 1500,
|
||||
// Build only: the wasm Navara's workers load by name (the Navara
|
||||
// engine is a separate chunk loaded on ?engine=navara).
|
||||
// The lazy chunk with the Navara engine is named navara-engine.
|
||||
rollupOptions: {
|
||||
// initialChunkGuard: no three/Navara module in the initial chunks.
|
||||
plugins: [
|
||||
navaraWorkerAssets(),
|
||||
initialChunkGuard({
|
||||
pattern: packagePattern(
|
||||
navaraOnlyPackages(fileURLToPath(new URL('..', import.meta.url))),
|
||||
),
|
||||
}),
|
||||
],
|
||||
output: { chunkFileNames: navaraChunkFileNames },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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 @@
|
||||
- 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 @@
|
||||
- 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).
|
||||
@@ -0,0 +1,10 @@
|
||||
- Add Navara, an experimental three.js/WASM globe, as a second engine next to
|
||||
Cesium, switchable live under VISUAL PRESETS → Globe Engine or with
|
||||
`?engine=navara`. Navara draws at CSS pixels like Cesium; Navara HD
|
||||
(`?engine=navara-hd`) draws at the screen's full resolution and needs about
|
||||
1 GiB more on a Retina screen. Layers keep their data across a switch and
|
||||
draw on both engines; Cesium loads only when a Cesium globe or a
|
||||
Cesium-only feature needs it (German, @gekh, #1555).
|
||||
- Keep the cockpit camera steady near the ground: it no longer jumps up and
|
||||
down when an aircraft's reported altitude sits below the globe's terrain,
|
||||
on either engine (German, @gekh, #1555).
|
||||
@@ -1,29 +0,0 @@
|
||||
- Add the Data Library. New regional and niche sources no longer join the
|
||||
Data Layers panel: **Browse data library** at its foot lists live entries
|
||||
(a manifest each in `src/library/entries/`, fetched from the provider, never
|
||||
bundled) with their licence, coverage and what this instance needs to serve
|
||||
them, and **Add** puts one under a Library heading. Datasets (GeoJSON, CSV,
|
||||
ArcGIS), raster tiles and host-layer packs are supported; selections persist
|
||||
and travel in share links as `lib=`, separate from the layer tokens; one
|
||||
generic `/api/library` proxy serves entries without CORS. Seeded with NASA
|
||||
EONET events, EIA and WRI power plants, NWS alerts, NOAA tide stations,
|
||||
Smithsonian volcanoes, NASA Black Marble, and the
|
||||
opt-in IMF PortWatch and CCTV packs, plus the City of Amsterdam's
|
||||
traffic and ANPR camera positions (opt-in; contributed by @Maher-Reven in
|
||||
#251). FEMA USA Structures and the
|
||||
National Bridge Inventory load what is in view once you zoom in, and
|
||||
Addresscloud's EPC ratings stream from Source Cooperative's GeoParquet on
|
||||
S3, coloured A–G with live legend counts. Open tile overlays (geology,
|
||||
biodiversity, surface water, national aerial photos and maps)
|
||||
join GDACS alerts and EPA Superfund and TRI facilities. 57 aerial photo
|
||||
layers from the Open Imagery Register (national programmes across Europe,
|
||||
NSW, NAIP and US state, county and city services, WMS and ArcGIS export
|
||||
included) show their resolution, true-ortho flag, years flown and refresh
|
||||
cycle in Browse; aerial photos stay above every other drape, are clipped to
|
||||
their coverage, and the providers' blank fill no longer whitens the globe.
|
||||
Kartverket's open Norwegian maps (topographic, nautical charts, hillshade,
|
||||
terrain relief, sea depths, boundaries and the cadastre) and Mapterhorn's
|
||||
global terrain, drawn in the browser as shaded relief with an elevation
|
||||
legend, are in the library too. Stat tiles head the dialog. A library layer's row has
|
||||
Zoom to layer and Remove icons, and the dialog has Zoom and a confirmed
|
||||
Remove all (asturksever, #1604).
|
||||
@@ -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,5 +0,0 @@
|
||||
- Leave browser and system chords alone: a shortcut key held with Cmd, Ctrl or
|
||||
Alt no longer runs its single-key shortcut. Copying with Cmd+C or Ctrl+C used
|
||||
to turn the CCTV layer on and open the nearest camera, Cmd+F opened the layers
|
||||
panel and Cmd+1 to 6 changed the visual style. The Q/W/E/R/T keys of an
|
||||
expanded city's points of interest follow the same rule (asturksever, #1691).
|
||||
@@ -1,2 +0,0 @@
|
||||
- Show a new CCTV camera's frame the moment it is decoded, with no fade, so
|
||||
switching cameras no longer lingers on an empty preview (samehkhamis, #1693).
|
||||
@@ -1,3 +0,0 @@
|
||||
- End a CCTV camera's monitor plane where its view meets the ground on globe
|
||||
maps too, instead of a kilometre-wide plane hundreds of metres up
|
||||
(samehkhamis, #1694).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Show a selected vessel's photo from Open Vessel Photos, credited with its
|
||||
photographer and open licence, and its MMSI beside its name in the CONTACTS
|
||||
view (samehkhamis, #1695).
|
||||
@@ -1,3 +0,0 @@
|
||||
- Add an `./i18n` entry so an application with its own language menu can
|
||||
start, follow and switch the interface language as the globe picker does
|
||||
(samehkhamis, #1696).
|
||||
@@ -1,5 +0,0 @@
|
||||
- Fix the translations of the strings added since the last review (News,
|
||||
Data Library, Wildlife Sightings, Shipwrecks, Rail, the vessel and orbital
|
||||
cockpits) in all twelve languages: neutral side labels and counts no longer
|
||||
break the sentence, terms follow each glossary, and Traditional Chinese
|
||||
follows Simplified Chinese again (German, @gekh, #1698).
|
||||
@@ -1,8 +0,0 @@
|
||||
- Draw every 3D model from the project's procedural fleet set: 121 models
|
||||
(about 2 MB, MIT like the code) chosen per vehicle by type and drawn life
|
||||
size, replacing the eight third-party aircraft models. Flights, Military
|
||||
Flights and Local ADS-B pick a model by ICAO type, class and emitter
|
||||
category; Vessels, Transit and Rail now draw models too, under the same 3D
|
||||
toggle and distances, a ship at its AIS length and beam and a tram, bus or
|
||||
train in its mode or category colour. The whole set loads once when a layer
|
||||
starts (samehkhamis, #1714).
|
||||
@@ -1,4 +0,0 @@
|
||||
- The Wind server no longer blocks every other request while it decodes a
|
||||
forecast. Decoding runs on a worker thread (up to 400 ms of blocking down to
|
||||
about 4 ms), and choosing an overlay after wind has loaded fetches only the
|
||||
overlay's field (mazy1998, #1717).
|
||||
+5
-3
@@ -195,6 +195,10 @@ 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`).
|
||||
On globe surfaces, a source with `getFrameTile` currently requires Cesium for
|
||||
radar, regional infrared and lightning. Navara pauses those products with
|
||||
`This weather source requires Cesium`; decoded global infrared images and
|
||||
the whole-frame images used over 3D tiles draw through the shared display.
|
||||
|
||||
`application/services` accepts boundary, terrain, regional-context, weather and
|
||||
summary services; its weather service supplies cockpit local conditions, not the
|
||||
@@ -224,9 +228,7 @@ leaving out `command-dock` the voice control and its pointer reticle;
|
||||
leaving out `context` the Bhote Koshi reconstruction panel; and leaving out
|
||||
`intel-hud` the Intel HUD overlay (`#intel-hud`), whose readouts the HUD
|
||||
implementation publishes to the `gev-hud` store. An unknown
|
||||
view name is an error. An application that offers its own language choice
|
||||
starts and switches the language with the `./i18n` entry (`startPageLocale`,
|
||||
`followPageLocale`, `switchLocale`; see [I18N.md](./I18N.md)) and passes
|
||||
view name is an error. A host that does not initialize Core's locale passes
|
||||
`localePicker: false` to `createApplicationControls` instead of editing the
|
||||
markup. Node tests that import application modules register the component
|
||||
loader with `node --import=<core package>/build/register-vue`.
|
||||
+120
-12
@@ -19,6 +19,17 @@ excluded. The formatter validates every entry before writing any file.
|
||||
|
||||
New world text labels must use the world-overlay host; Cesium text labels are forbidden.
|
||||
|
||||
## QA scripts
|
||||
|
||||
New `scripts/qa-*.mjs` scenarios use `scripts/qa/browser.mjs` for headless launch
|
||||
and bounded shutdown, and `scripts/qa/ready.mjs` for application, camera and
|
||||
frame readiness. Select the GPU backend with `QA_ANGLE`. Keep timed pauses only
|
||||
when elapsed time is part of the assertion, and explain that measurement.
|
||||
Read scene state through the globe contract or the QA probe; native reads belong
|
||||
only in explicit engine-adapter checks. The counters in
|
||||
`node scripts/report-qa-harness.mjs` must not grow; lower the baseline with
|
||||
`--write-baseline` after removing duplication.
|
||||
|
||||
## Current component ownership
|
||||
|
||||
Package imports use `gods-eye-view`; `package.json` is the authoritative
|
||||
@@ -30,8 +41,10 @@ export inventory. Use declared exports rather than reaching into internal files.
|
||||
| `src/standalone/` | Select the default catalog, local sources and setup controls |
|
||||
| `src/ui/` | Navigation generations, restoration, visual state, panel snapshots and their subscriptions |
|
||||
| `src/data/` | Lifecycle, context and feed state; legacy default-layer facades |
|
||||
| `src/layers/<family>/` | Source acquisition, records and Cesium resources with explicit controller/renderer owners |
|
||||
| `src/layers/<family>/` | Source acquisition, records and shared contract displays with explicit lifecycle owners |
|
||||
| `src/sources/` | Portable protocols and source contracts; request state belongs to each factory instance |
|
||||
| `src/geo/` | Engine-neutral WGS84 maths, ported from Cesium, never imports Cesium |
|
||||
| `src/globe/` | Engine-neutral globe contract and its test support; engines only in `globe/cesium`, `globe/navara` |
|
||||
| `src/services/` | Supplied application operations; scene construction owns caches and cancellation |
|
||||
| `src/voice/` | Portable action schemas/session, common controls, action execution and separate protocol adapters |
|
||||
| `server/providers/` | Node route factories, process-scoped provider caches and shutdown cleanup |
|
||||
@@ -53,18 +66,70 @@ See [application construction](APPLICATION.md) and the
|
||||
2. `scripts/check-package-boundaries.mjs` builds every declared export without
|
||||
app Vite configuration or environment files. `scripts/package-boundaries.json`
|
||||
assigns each export exactly once and lists its owned modules and external
|
||||
dependencies. Unused imports still count. Node exports have only a `node`
|
||||
dependencies. Shared foundations use directory entries: `src/i18n/`,
|
||||
`src/geo/` and `src/globe/`. An entry ending in `/` owns the files below it;
|
||||
`src/globe/` excludes `cesium/`, `navara/` and `testing/`, which need separate
|
||||
ownership. The import-direction gate still enforces engine rules.
|
||||
Aggregate groups use directories where their module lists cover the directory;
|
||||
small groups retain explicit snapshots outside the shared foundations.
|
||||
Unused imports still count. Node exports have only a `node`
|
||||
condition; browser groups cannot use build-only dependency exceptions.
|
||||
|
||||
Run `node scripts/check-package-boundaries.mjs --write` to regenerate each
|
||||
group's `modules` list in `scripts/package-boundaries.json`, retaining useful
|
||||
entries in order and appending newly reached paths in sorted order. Directory
|
||||
entries stay directories while they own reached modules; they never expand
|
||||
into file lists. Without `--write`, the check rejects both undeclared reached
|
||||
modules and declarations no longer reached, printing additions and removals
|
||||
for each changed group. A directory becomes stale only when no reached module
|
||||
belongs to it. Review the manifest diff like a lockfile diff before accepting
|
||||
changes to ownership.
|
||||
|
||||
Portable source graphs cannot reach application/rendering, Node, Cesium or
|
||||
browser globals. This includes `sources/*`, dedicated `layers/*/source` exports,
|
||||
flight/military/vessel record and ingestion exports, action schemas, the session
|
||||
interface, lifecycle and feed state. The browser-global rule reserves platform
|
||||
names such as `document` and `window` in these modules; it is an architectural
|
||||
check, not a JavaScript sandbox. Common voice controls cannot depend on a
|
||||
interface, lifecycle and feed state, and every runtime file in `src/geo/` (the
|
||||
engine-neutral geometry ported from Cesium). The browser-global rule reserves
|
||||
platform names such as `document` and `window` in these modules; it is an
|
||||
architectural check, not a JavaScript sandbox. The globe contract
|
||||
(`src/globe/*.js` and `src/globe/testing/**`) cannot reach `cesium`,
|
||||
`@cesium/*`, `three`, `postprocessing` or `@navaramap/*`, directly or through
|
||||
helpers, nor import an engine implementation; the `src/globe/cesium/**` graph
|
||||
reaches no Navara/three package or `src/globe/navara/**` module, and the
|
||||
`src/globe/navara/**` graph no Cesium package or `src/globe/cesium/**` module,
|
||||
through helpers too. Outside its own directory, `src/globe/navara/**` is
|
||||
reached only through a literal `import()` (the app's `?engine=navara` mode), so
|
||||
Navara stays out of the Cesium-mode bundle. Common voice controls cannot depend on a
|
||||
Realtime protocol implementation. Negative fixtures cover indirect helpers,
|
||||
self-package imports, symlinks and unreachable files.
|
||||
|
||||
Runtime SDK imports are restricted by directory, including unreachable modules:
|
||||
`cesium` and `@cesium/*` belong in `src/globe/cesium/`; `three`,
|
||||
`postprocessing` and `@navaramap/*` belong in `src/globe/navara/`. Package
|
||||
subpaths, re-exports and literal dynamic imports obey the same rule, and the
|
||||
`<script>` blocks of Vue components count as runtime modules. A module outside
|
||||
the Navara engine reaches it only through `import()`: a static import of the
|
||||
same specifier fails even beside a dynamic one. Tests and Node test support
|
||||
retain their existing exemptions. The production script loader is
|
||||
`src/app/loadCesium.js`; it imports no SDK package and needs no exception, and
|
||||
it adds no `widgets.css` when the page's markup has already run `Cesium.js`
|
||||
(the MCP panel). The panel build replaces `src/app/navaraMode.js` and
|
||||
`src/globe/navara/windFlowMesh.js` with stubs (`build/panelNavara.js`), so the
|
||||
panel carries no Navara or three. There is no blanket exception for layer
|
||||
`render/` directories.
|
||||
|
||||
The temporary `engineImportExceptions` list in `scripts/package-boundaries.json`
|
||||
contains exact file/package pairs with reasons. The checker reports the used
|
||||
entries and rejects duplicates, missing reasons and unused entries. Its two
|
||||
exceptions are the Bhote Koshi event and locator models in `src/data/`, which
|
||||
still construct Cesium property records. Their drawing uses shared displays.
|
||||
Exceptions do not relax the portable, contract or cross-engine graph checks.
|
||||
Cesium annotation renderers live in `src/globe/cesium/annotations/`. Wind's
|
||||
Cesium rendering (the particle canvas, its GPU path and the relief shading)
|
||||
lives in `src/globe/cesium/`; `src/layers/wind/` keeps the data, model,
|
||||
presentation and engine-neutral inspection. The remaining annotation migration
|
||||
is separate debt.
|
||||
|
||||
Source-only factories use dedicated `layers/<family>/source` exports where
|
||||
available, preserving the same factory implementation without loading rendering.
|
||||
Record normalization and endpoint policies have plain owners separate from
|
||||
@@ -391,10 +456,12 @@ the caller.
|
||||
## Visual effects
|
||||
|
||||
`ui/effects` exports the effects controller and existing preset definitions.
|
||||
It owns shader stages and their clock, with explicit render ownership callbacks.
|
||||
Construction installs no stages or frame callbacks. Stop animation before
|
||||
releasing UI consumers, then destroy to remove owned stages and restore the
|
||||
borrowed bloom state. `ui/effects/bloom` exposes the pure intensity/version helpers
|
||||
It owns transitions and their clock, with explicit render ownership callbacks,
|
||||
and writes styles, bloom and sharpening through `engine.render`. Construction
|
||||
installs no stages or frame callbacks. Stop animation before releasing UI
|
||||
consumers; controller destruction disables its effects. Cesium's
|
||||
`src/globe/cesium/postStages.js` owns stage creation and destruction and restores
|
||||
the borrowed bloom state when the engine is destroyed. `ui/effects/bloom` exposes the pure intensity/version helpers
|
||||
without loading the renderer. UI presentation and product-action coordination
|
||||
remain in their callers.
|
||||
|
||||
@@ -506,7 +573,7 @@ perform no network requests and discover no additional modules.
|
||||
`maps/controller` coordinates scene changes and lifetimes. `maps/imagery`,
|
||||
`maps/terrain` and `maps/3d` supply constructors; `maps/defaults` selects the
|
||||
standard sources and their setup/fallback policy. The standalone facade wires
|
||||
these to the application's render governor.
|
||||
these to their engine's render requests.
|
||||
|
||||
A registry supplies `sources`, `defaultId`, `unknownId` and optional `recoveryId`.
|
||||
Each source has a user-facing `descriptor`, availability/reason, and either an
|
||||
@@ -550,12 +617,22 @@ services into each family factory. Standalone provider selection lives in
|
||||
`src/standalone/layerSources.js`. The construction export has its own checked
|
||||
dependency graph, excluding standalone setup and compatibility layer instances.
|
||||
|
||||
`src/app/layers/renderers.js` is the layer manifest. Its lazy display loaders
|
||||
resolve the shared factories exported by `displays.js` through `display`. All
|
||||
five drawing primitives are required by the globe contract. The only display
|
||||
backend options are wind streamlines, loaded from the engine folders. A missing wind backend leaves the raster field
|
||||
available. Wildlife Sightings draws itself through the contract; Sea Marks and
|
||||
Ski Pistes use their overlay controller. No layer display imports an engine SDK.
|
||||
`displayRehearsal.test.mjs` loads every manifest display on the fake engine with
|
||||
an unknown kind and no extensions, draws a small fixture and checks cleanup.
|
||||
|
||||
Both aircraft layers share the catalog's classification registry; launches use
|
||||
its satellites and Contacts uses its aircraft, vessels and installations. Data
|
||||
registration, controls and voice actions read those same instances. Destruction
|
||||
remains the manager's responsibility; classification also observes application
|
||||
abort when startup has not reached registration. Scene engines remain page-owned,
|
||||
so this change does not introduce multiple simultaneous viewers.
|
||||
abort when startup has not reached registration. The session retains models while
|
||||
scene attachment builds displays on the active engine. A parked engine can remain
|
||||
allocated for a warm switch; its layer drawing is detached.
|
||||
|
||||
Direct `src/data` compatibility entries retain their old defaults and testing
|
||||
exports. Browser regression probes use the registered instance's testing surface
|
||||
@@ -594,6 +671,23 @@ listener cleanup. Application data assembly constructs both owners explicitly;
|
||||
`data/manager` is the compatibility facade for direct callers. The ordinary
|
||||
state subscriptions retain their existing event contract.
|
||||
|
||||
`data/layerSceneBinding.js` owns renderer attach/detach, asynchronous settlement,
|
||||
parking and failed-release retries. Lifecycle keeps visibility intentions and
|
||||
refresh policy. `app/layerSession.js` defines the retained session fields and
|
||||
handoff predicate. `standalone/sceneSwitcher.js` coordinates page handoff;
|
||||
`ui/shellScene.js` owns scene-bound shell construction, while `applicationShell`
|
||||
keeps page controls and policy. `app/sceneHost.js` owns the transition queue,
|
||||
request ordering and recovery decision. Application acquisition, attachment,
|
||||
restoration and renderer release run in that queue, and shell calls use it too.
|
||||
Nested work receives an explicit transaction; outside calls wait for the whole
|
||||
transition. A replacement application shares the transition owner with fresh
|
||||
parts, and a shell used alone creates its own host. Pick ownership, selection
|
||||
dispatch and render demand belong to
|
||||
`scene.services`; render demand drives the engine contract, with no dependency
|
||||
from the Cesium render backend to the governor. Overlay and tracked-readout
|
||||
services still include module singletons; debug access still exposes the native
|
||||
viewer and tileset.
|
||||
|
||||
## UI state owners
|
||||
|
||||
- `ui/navigation` owns navigation generations, pending search presentation and
|
||||
@@ -768,3 +862,17 @@ CSS rather than mapping machine states to universal severity. CCTV owns
|
||||
frame/source and calibration rules and their data attributes, while Weather
|
||||
owns coverage labels. The rail card keeps node identity, header focus,
|
||||
disclosure and teardown, and does not announce every card update.
|
||||
|
||||
## Engine implementation modules
|
||||
|
||||
Cesium's viewer construction, atmosphere compatibility, zoom-out guard, starry
|
||||
sky, map scene application, imagery and terrain live in `src/globe/cesium/`.
|
||||
The public viewer export still resolves through `package.json`. Celestial
|
||||
rendering, screen-event constants, scene projection and the legacy trail
|
||||
backend also live there. Sonar commands and post stages are engine internals;
|
||||
feature controllers call `engine.render`.
|
||||
|
||||
Imported-file parsing (`geoFileImport.js`, `geoImportRecords.js`,
|
||||
`geoImportKml.js`) returns plain records. `geoImportDisplay.js` consumes them
|
||||
through ground, movers, lines and imagery. The UI owns file bytes, visibility
|
||||
and attachment lifetime, with no DataSource or engine-specific import backend.
|
||||
+391
-720
File diff suppressed because it is too large.
Load diff
@@ -1,183 +0,0 @@
|
||||
# Data Library
|
||||
|
||||
DATA LAYERS is the curated set of core layers. The Data Library holds
|
||||
everything else: regional sources, niche themes and community contributions.
|
||||
A viewer opens it from **DATA LAYERS ▸ Browse data library**, adds what they
|
||||
want, and the entry appears in the panel under **Library**. Nothing in the
|
||||
library is on until someone adds it.
|
||||
|
||||
Runtime behaviour is described in [CURRENT-STATE.md](CURRENT-STATE.md#data-library--october-21-2026).
|
||||
This page is the authoring guide.
|
||||
|
||||
## The one rule: live sources only
|
||||
|
||||
A library entry points at data its provider hosts. That can be a query API, a
|
||||
static file at a stable URL the provider controls, or a tile endpoint. The
|
||||
data is never copied into this repository. If the upstream goes away, the
|
||||
entry goes stale; this project never becomes the host of record. That is what
|
||||
lets every self-hosted instance serve the whole library with no data of its
|
||||
own.
|
||||
|
||||
It also keeps the existing rule in [CONTRIBUTING.md](../CONTRIBUTING.md): do
|
||||
not add data you have no right to redistribute. A live source still needs a
|
||||
licence that allows this use, and the manifest must say what it is.
|
||||
|
||||
## Kinds
|
||||
|
||||
| Kind | What it is | Renders through |
|
||||
| --- | --- | --- |
|
||||
| `dataset` | Vector features: `geojson`, `geojsonl`, `csv` (lat/lon columns), `json` (a record list with lat/lon fields), `arcgis` (a FeatureServer or MapServer layer, paged) or `geoparquet` (one file, or a partitioned set on S3) | The GeoJSON layer Datacenters and Dams use; view-bounded and GeoParquet sets draw as ground-clamped dots and polygons |
|
||||
| `tiles` | Raster tiles: `xyz` with a `{z}/{x}/{y}` template (`{reverseY}` for TMS rows), or `bbox` with a `{bbox-epsg-3857}` template (WMS GetMap, ArcGIS `export`/`exportImage`) | The active map's imagery host (globe or Google 3D tiles) |
|
||||
| `pack` | A source pack an existing host layer already loads (today: CCTV) | The host layer, switched on by its own environment variable |
|
||||
|
||||
A `dataset` or `tiles` entry needs no JavaScript. A `pack` documents a host
|
||||
layer's opt-in source so viewers can see it and operators know the switch; the
|
||||
host layer owns its code. Provider entries (code implementing a family
|
||||
contract, such as a Street Level provider) are not supported yet and are
|
||||
refused by the validator.
|
||||
|
||||
## Add an entry
|
||||
|
||||
1. Create `src/library/entries/<id>.json`. The id is 3–48 lowercase letters,
|
||||
digits or dashes and is permanent.
|
||||
2. Add one import line for it in `src/library/catalog.js` (keep the list in
|
||||
file-name order).
|
||||
3. For a `dataset` or `tiles` entry, run `npm run library:next -- <id>` and
|
||||
append `["<id>", "<token>"]` to `src/library/ledger.json`. Tokens are
|
||||
permanent: never change or reuse a row, even when an entry is removed.
|
||||
Packs take no token.
|
||||
4. Run `npm run library:docs` to regenerate the DATA_SOURCES.md table.
|
||||
5. Run `npm run library:check -- --base-ref origin/main`,
|
||||
`npm run library:probe`, `npm test` and `npm run qa:data-library`.
|
||||
6. Open a pull request with a `changelog.d/` fragment.
|
||||
|
||||
Start new entries at `"status": "community"`. Maintainers move an entry to
|
||||
`verified` after reviewing its licence and seeing it stay healthy; `stale` and
|
||||
`archived` mark entries whose upstream has stopped answering. Archived entries
|
||||
are hidden from Browse unless a viewer already added them.
|
||||
|
||||
## Manifest reference
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "eia-power-plants",
|
||||
"kind": "dataset",
|
||||
"family": "infrastructure",
|
||||
"title": "US power plants, 100 MW and up (EIA)",
|
||||
"provider": "EIA",
|
||||
"description": "Operating US power plants with at least 100 MW of capacity.",
|
||||
"icon": "ϟ",
|
||||
"maintainer": "github:your-handle",
|
||||
"coverage": { "countries": ["US"], "bbox": [-180, 13, -64, 72] },
|
||||
"source": {
|
||||
"format": "arcgis",
|
||||
"url": "https://services2.arcgis.com/…/Power_Plants_in_the_US/FeatureServer/0",
|
||||
"query": { "where": "Total_MW >= 100", "outFields": "Plant_Name,PrimSource,Total_MW" },
|
||||
"fetch": "browser-direct",
|
||||
"cacheSeconds": 86400,
|
||||
"maxFeatures": 4000,
|
||||
"maxPages": 3
|
||||
},
|
||||
"display": {
|
||||
"color": "#ffc23d",
|
||||
"label": "Plant_Name",
|
||||
"fields": ["PrimSource", "Total_MW"],
|
||||
"rank": { "field": "Total_MW", "order": "desc" }
|
||||
},
|
||||
"licence": { "name": "EIA data: public domain", "url": "https://www.eia.gov/about/copyrights_reuse.php" },
|
||||
"attribution": { "text": "U.S. Energy Information Administration", "url": "https://atlas.eia.gov/" },
|
||||
"status": "community",
|
||||
"reviewed": "2026-10-21"
|
||||
}
|
||||
```
|
||||
|
||||
Unknown fields are errors, so a typo cannot silently drop a cap or a licence.
|
||||
|
||||
| Field | Required | Notes |
|
||||
| --- | --- | --- |
|
||||
| `id`, `kind`, `family`, `title` (≤ 60), `provider` (≤ 32), `description` (≤ 240), `maintainer`, `status` | yes | `family` is one of movement, cameras, infrastructure, events, weather, environment, imagery. `provider` is the short name the panel row shows. |
|
||||
| `coverage` | yes | `global: true`, ISO 3166-1 alpha-2 `countries`, and/or a `bbox` `[west, south, east, north]`. |
|
||||
| `source.url` | yes | https, a real hostname, no credentials or fragment. |
|
||||
| `source.format` | yes | Datasets: `geojson`, `geojsonl`, `csv`, `json`, `arcgis`, `geoparquet`. Tiles: `xyz` (`{z}`, `{x}` and `{y}` or `{reverseY}`) or `bbox` (one `{bbox-epsg-3857}`, filled with the tile's Web Mercator bounds in metres, as MapLibre does). Packs: `host`. |
|
||||
| `source.records` | no | JSON only: the property path to the record list, such as `stations` for `{ "stations": [ … ] }`. Omit when the response is the list itself. |
|
||||
| `source.fetch` | datasets, tiles | `browser-direct` when the upstream sends CORS headers and needs no key; otherwise `proxied`. |
|
||||
| `source.query` | no | Query parameters added to the URL. For `arcgis`, `where` and `outFields`; geometry is always WGS84 GeoJSON. |
|
||||
| `source.columns` | no | CSV or JSON: `{ "lat": "…", "lon": "…" }` when the fields are not lat/latitude/y and lon/lng/longitude/x. |
|
||||
| `source.cacheSeconds` | no | 60–86,400, default 3,600. How long the proxy keeps a response. |
|
||||
| `source.maxBytes` | no | Body cap, default 16 MiB, at most 32 MiB. |
|
||||
| `source.maxFeatures` | no | Feature cap, default 10,000, at most 20,000. A capped dataset reads PARTIAL. |
|
||||
| `source.maxPages` | no | ArcGIS pages to follow, default 5, at most 10. |
|
||||
| `source.view` | no | `{ "maxAltitudeM": 300–50000, "sample": [w, s, e, n] }`. Makes a huge ArcGIS layer view-bounded: below that camera height it loads what is in view, one page at a time; above it the row asks the viewer to zoom in. Needs `arcgis` or `geoparquet`, and `browser-direct`. `sample` is where Zoom to layer lands and what `library:probe` queries. |
|
||||
| `source.partitions` | no | GeoParquet only: `{ "listUrl": "<S3 ListObjectsV2 URL with prefix>", "keyBase": "<URL keys are relative to>" }`, both on the source host. Needs `source.view`. The library indexes each file by its footer bbox and reads only the files in view. |
|
||||
| `source.minZoom`, `source.maxZoom` | no | Tiles only, 0–22. |
|
||||
| `source.encoding` | no | Tiles (`xyz`): `terrarium` when the pixels are elevation as terrain-RGB (height = R × 256 + G + B / 256 − 32768 m). The browser draws them as shaded relief: one orange ramp in six elevation classes (legend in metres), lit from the north-west; sea (exactly 0 m) is transparent. |
|
||||
| `source.tileSize` | no | Tiles: 256 (default) or 512 px. |
|
||||
| `source.bounded` | no | Tiles with `coverage.bbox`: `true` requests tiles inside the bbox only. Required for aerial imagery, whose servers paint a fill (white, a flat ocean, a coarse world mosaic) everywhere they have no photos. A 4xx tile answer is treated as "no imagery here", never retried or counted as an error. |
|
||||
| `source.key` | no | `{ "id": "<key registry id>", "in": "query" \| "header", "name": "…" }`. The id must exist in `src/keySetupCore.mjs`. A keyed entry must be `proxied`, so the key never reaches the browser. |
|
||||
| `display.color`, `display.label`, `display.fields` (≤ 4, two shown) | no | Datasets only, except `opacity`. |
|
||||
| `display.rank` | no | `{ "field": "…", "order": "desc" \| "asc" }`. Keeps the most important features when capped and lets the map prefer them at global zoom. Numbers or ISO dates. Without it, a zoomed-out view shows whichever features come first. |
|
||||
| `display.colorBy` | no | View-bounded datasets: `{ "field": "…", "colors": { "<value>": "#rrggbb" }, "labels": { "<value>": "…" } }`, up to 12 values. Colours each feature by value; the row legend lists each label with the count in view. |
|
||||
| `display.legend` | no | `[{ "label": "…", "color": "#rrggbb" }]`, up to 12: a static legend, such as a tile layer's colour scale. Take the colours from the provider's own legend or colour map. |
|
||||
| `display.outline` | no | Tiles: `countries` outlines every part of each covered country, `coverage` only the parts inside `coverage.bbox`, `bbox` the bbox rectangle itself (a state, county or city inside a country). Use it for aerial imagery so its edge shows. |
|
||||
| `display.aerial` | no | Tiles: `true` for aerial or satellite photography. It drapes above every other imagery layer, including core overlays, and stays there when another layer is switched on. Pair it with `source.bounded`. |
|
||||
| `display.noDataColor` | no | Tiles: `#rrggbb` of the provider's no-imagery fill, drawn transparent (within a small tolerance for JPEG noise along its edge). Measure it from a tile outside the coverage; do not guess. Globe maps only: Google 3D tiles ignore it. |
|
||||
| `display.opacity` | no | Tiles: (0, 1]. |
|
||||
| `imagery` | no | Tiles: what a photo layer is. `type` (`ortho`, `satellite-mosaic`, `oblique`, `historic`), `resolution` as the provider publishes it ("10 cm (plains) / 25 cm (Alps)"), `resolutionM` (the finest, in metres), `trueOrtho`, and optional `vintage` ("2021–present"), `updateCycle`, `orthoQuality` and `registry` (an https link to where the metadata came from). Browse shows it as a spec list on the card. |
|
||||
| `pack` | packs | `{ "host": "cctv", "env": "CCTV_EXAMPLE_ENABLED" }`. Packs are always `licence.operatorOptIn: true`. |
|
||||
| `licence` | yes | `name`, an https `url`, and `operatorOptIn: true` when the terms need the operator's own agreement or consent. |
|
||||
| `attribution` | yes | `text` and an optional https `url`; shown in the Data attribution popover while the entry is in use. |
|
||||
| `regionsServed` | no | ISO codes, when the upstream only answers from some countries. |
|
||||
| `reviewed` | no | `YYYY-MM-DD` of the last licence and endpoint review. |
|
||||
|
||||
## Aerial imagery from the Open Imagery Register
|
||||
|
||||
The aerial photo entries came from the [Open Imagery Register](https://imagery-registry.netlify.app/)
|
||||
on 2026-10-23: every register source with a map preview, less the ones that
|
||||
fail the library's rules. Each entry keeps the register's resolution, product
|
||||
type, true-ortho flag, years flown and refresh cycle in `imagery`. Before an
|
||||
entry was written, a tile inside its coverage was fetched in a browser from
|
||||
the app's origin: it had to be a real photo (not a blank fill), and the
|
||||
provider had to send CORS headers (or the entry would be `proxied`). The
|
||||
fill a provider paints at its coverage corners became `display.noDataColor`.
|
||||
|
||||
Left out, with the reason:
|
||||
|
||||
| Register source | Why |
|
||||
| --- | --- |
|
||||
| NASA GIBS true colour | Already in core (Recent imagery). |
|
||||
| Sentinel-2 cloudless (EOX) | CC BY-NC-SA: non-commercial. |
|
||||
| SWISSIMAGE Bern and Marion County PMTiles | Extracts hosted by the register, not the provider. |
|
||||
| Cabarrus County NC, Hawaii, Nashville, Sioux Falls | Credit a commercial imagery vendor (EagleView, Maxar WorldView, Pictometry, Nearmap). |
|
||||
| DVRPC, Missouri, Washington | The preview serves a blank white tile across the coverage centre. |
|
||||
| Sarasota County FL | The service now requires a token. |
|
||||
|
||||
Entries whose register licence reads "see publisher terms" or "verify portal
|
||||
terms" are `licence.operatorOptIn: true`: an operator reads the publisher's
|
||||
terms and lists the id in `GEV_LIBRARY_ENABLE`. Re-check an entry's terms and
|
||||
drop the opt-in when its publisher states an open licence.
|
||||
|
||||
## Choosing a fetch mode
|
||||
|
||||
Use `browser-direct` when the upstream answers a browser on another origin
|
||||
(`Access-Control-Allow-Origin`) and needs no key. Tile servers must send CORS
|
||||
headers for WebGL to use them; without them, use `proxied`.
|
||||
|
||||
Use `proxied` otherwise. The proxy is generic: there is no per-entry server
|
||||
code. It reaches only the manifest's host, refuses redirects, caps bodies,
|
||||
caches per `cacheSeconds` and never returns upstream error text. The browser
|
||||
names an entry id, never a URL, so the proxy cannot be pointed anywhere else.
|
||||
|
||||
## Operator switches
|
||||
|
||||
| Variable | Effect |
|
||||
| --- | --- |
|
||||
| `GEV_LIBRARY_ENABLE` | Comma-separated ids of `operatorOptIn` datasets and tiles to serve. |
|
||||
| `GEV_LIBRARY_DISABLE` | Comma-separated ids to remove from this instance. |
|
||||
| A pack's own variable | For example `CCTV_NYC_ENABLED=1`; Browse then shows the pack as switched on. |
|
||||
|
||||
## When an entry belongs in core instead
|
||||
|
||||
A library entry can be proposed for DATA LAYERS when it has global or
|
||||
near-global coverage, is keyless or uses a key the registry already has, has
|
||||
stayed `verified` with no stale period for about six months, and a core
|
||||
maintainer will own it. Promotion and demotion are maintainer decisions.
|
||||
@@ -90,7 +90,7 @@ load, removes partial results and reports a stable error without exposing a URL.
|
||||
Successful loading leaves the attribution cards visible with the assets.
|
||||
|
||||
`createApplicationTools({ sceneDataPacks: { sources } })` and
|
||||
`new SceneDirector(viewer, style, data, { dataPacks: { sources } })` accept a
|
||||
`new SceneDirector(scene, style, data, { dataPacks: { sources } })` accept a
|
||||
registry of source functions. Each receives `{ path, signal, maxBytes }` and
|
||||
returns `{ bytes: Uint8Array, mimeType }`. Custom transports must honor the
|
||||
signal, bound reads and return the real MIME type. They remain application code,
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
# Globe primitives
|
||||
|
||||
A layer draws through the globe-engine contract (`src/globe/contract.js`),
|
||||
not through Cesium or Navara. The contract has five drawing primitives:
|
||||
|
||||
- `engine.movers`: things at points;
|
||||
- `engine.lines`: lines in the air;
|
||||
- `engine.ground`: fills on the ground;
|
||||
- `engine.imagery`: rasters on the ground;
|
||||
- `engine.meshes`: textured or coloured world triangles.
|
||||
|
||||
Each engine implements all five. Cesium's are in `src/globe/cesium/`,
|
||||
Navara's in `src/globe/navara/`, and the fake engine's in
|
||||
`src/globe/testing/fakeEngine.js`. The checks every engine must pass are in
|
||||
`src/globe/testing/contractSuite/index.mjs`.
|
||||
|
||||
A new contract member lands in one change with its implementation on Cesium,
|
||||
Navara and the fake engine, a behavioral check in the contract suite, and a
|
||||
mutant that the check kills. Keep check names prefixed with the member they
|
||||
exercise; optional behavior belongs in `engineCapabilities`.
|
||||
|
||||
## Movers: things at a point
|
||||
|
||||
`engine.movers.createSet({ id, kind, capacity, ... })` makes a set of up to
|
||||
`capacity` instances drawn as one batch. Write them with
|
||||
`set(index, fields)` and `setCount(n)`, then upload the changes with one
|
||||
`commit()`.
|
||||
|
||||
There are three kinds:
|
||||
|
||||
- `sprite`: an image from an atlas, a square of `sizePx`, turned by
|
||||
`rotation`;
|
||||
- `point`: a filled disc with an outline;
|
||||
- `model`: a glTF in metres, at least `minimumPixelSize` on screen.
|
||||
|
||||
Use movers for aircraft, vessels, satellites, cameras, stations: anything
|
||||
that has a position and is drawn the same size on screen (or as a model).
|
||||
|
||||
Point instances may also set `outlineColor` independently of the fill,
|
||||
so an expanding callout can fade its outline without changing its width.
|
||||
|
||||
Instances move every frame without a rebuild. With `depthTest: 'normal'`
|
||||
(the default), terrain surface depth never hides a mover, even just below
|
||||
the surface. The Earth's horizon, 3D tiles and models still occlude it.
|
||||
Sprites and points can use `'always'` to draw over the scene, or
|
||||
`{ disableWithinM }` to skip depth testing within that camera distance;
|
||||
models always use normal depth. A pick returns the instance's `pickId` when
|
||||
the set is `pickable`. `ready(index?)` says whether a model's glTF or a sprite
|
||||
atlas has loaded and `failed(index?)` whether it failed to: a failed set is
|
||||
not loaded again, so a layer that retries makes a new set. Cesium loads a model per
|
||||
instance: an index written with another `pickId` loads its own model, so an
|
||||
instance moved into a failed index does not inherit the failure.
|
||||
|
||||
## Lines: polylines in the air
|
||||
|
||||
`engine.lines.createLine({ id, widthPx, color, ... })` makes one polyline.
|
||||
You can change its positions, colour and model matrix at any time.
|
||||
|
||||
The width is in CSS pixels. The line follows the 3D positions it is given;
|
||||
it does not follow the ground. Options:
|
||||
|
||||
- `arc: 'geodesic'` bends it along great circles;
|
||||
- `dash` draws dashes;
|
||||
- `depthFailAlpha` draws a dimmer copy where the scene hides it.
|
||||
|
||||
Use lines for trails, orbits, launch paths, a ring at a fixed height, and
|
||||
leader lines.
|
||||
|
||||
## Ground: fills on the ground
|
||||
|
||||
`engine.ground.createSet({ id, kind, order, pickable, drape })` makes a set
|
||||
of flat fills that drape over the terrain and the 3D tiles. Nothing is
|
||||
extruded.
|
||||
|
||||
Work with a set like this:
|
||||
|
||||
- `replace(items)` checks and stores all its items at once;
|
||||
- `commit()` draws them in place of the last ones;
|
||||
- `show(visible)` shows or hides the whole set;
|
||||
- `list()` names the sets.
|
||||
|
||||
Positions are `[longitude, latitude]` in degrees, as GeoJSON stores them.
|
||||
Edges follow great circles.
|
||||
|
||||
There are four kinds:
|
||||
|
||||
| kind | item | for |
|
||||
| ---------- | ------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| `polygon` | `rings: [outer, ...holes]`, `fill`, `outline` | perimeters, airspace, footprints, cones, KML polygons |
|
||||
| `disc` | `center`, `radiusM` (metres along the ground) | earthquakes, air-quality stations, a pad zone |
|
||||
| `cell` | `west`, `south`, `east`, `north` (meridians, parallels) | GNSS and FIRMS cells, privacy cells |
|
||||
| `corridor` | `positions`, `color`, `widthPx` or `widthM` | routes, cables, roads, coverage lines, outlines in metres |
|
||||
|
||||
Item options:
|
||||
|
||||
- Every item has its own colours, rgba in 0..1; the alpha is the opacity.
|
||||
- `fill` and `outline` (`{ color, widthPx }`) are optional; an outline runs
|
||||
along every ring.
|
||||
- Within a set, a later item draws on top. Between sets, a higher `order`
|
||||
draws on top. All ground fills draw under movers, lines and models.
|
||||
- `pickable: true` makes picks return an item's `pickId`, with `owner` (the
|
||||
set) and `instance` (the item's index).
|
||||
- `drape` is `'both'` (the default), `'terrain'` or `'tiles'`. Both engines
|
||||
honour it: Navara tells 3D tiles from terrain by its globe depth, and its
|
||||
CPU picks skip a set draped only on the surface not shown (terrain while
|
||||
photoreal tiles show, tiles otherwise).
|
||||
|
||||
Discs with a fill may use `radial: { fillAlpha, rimAlpha, rimStart, rimEnd,
|
||||
emission }`. Alpha multipliers are in 0..1; the radius fractions satisfy
|
||||
`0 <= rimStart < rimEnd < 1`; emission is nonnegative. The rim grows with
|
||||
smoothstep between the two radii, then fades smoothly to zero at the outer
|
||||
edge. Space Missions uses a 500 m disc with alpha 0.105 inside and 0.72 at
|
||||
the rim, transitions at 0.952 and 0.985, and emission 0.35. Cesium retains
|
||||
its textured ground material and polygon depth bias. Navara composes the
|
||||
same radial envelope in its ground pass after tone mapping.
|
||||
|
||||
An unpickable pixel corridor set with `reveal: true` accepts
|
||||
`setReveal(fraction)` in 0..1. Each path is clipped at that fraction of its
|
||||
length. Geometry stays intact: Cesium updates material uniforms and Navara
|
||||
updates the corridor item texture. Hiding, showing and committing a new
|
||||
path retain the fraction. A corridor may have `backing: { widthPx, color }`:
|
||||
a wider edge with its own colour, with the core blended only once.
|
||||
|
||||
Transit drapes its history and active head through ground corridors. Its
|
||||
completed runs retain their age bands, 5 px dark backing and 3 px colour,
|
||||
with band updates once per displayed second. The active head is one backed
|
||||
corridor revealed along the whole current segment; only a segment crossing
|
||||
rebuilds its geometry. Transit uses dynamic sets so new history and head
|
||||
corridors build synchronously once Cesium's terrain bounds are available.
|
||||
The ground pass handles occlusion on both engines.
|
||||
|
||||
Cesium also builds reveal sets synchronously after loading those bounds.
|
||||
For nonempty sets, `ready()` includes the visible globe's tile readiness:
|
||||
classification needs the current surface depth as well as built geometry.
|
||||
|
||||
A `widthPx` corridor can also take the looks Cesium's ground polyline
|
||||
materials gave it. The GPU works them out every frame from the time, so a
|
||||
commit never rebuilds them:
|
||||
|
||||
| look | fields | Cesium's |
|
||||
| ------- | ------------------------------------------------ | ---------------------------------------------------------------- |
|
||||
| `glow` | `power`, `taper` (1: none) | `PolylineGlow`'s `glowPower` and `taperPower` |
|
||||
| `pulse` | `periodMs`, `min`, `max` | the alpha times a factor from `min` to `max` and back, a sine |
|
||||
| `dash` | `cells`, `duty`, `speed`, `gapAlpha`, `emission` | the annotation route's `GevRouteFlow`: dashes flowing to the end |
|
||||
|
||||
- `glow` brightens the line toward white along its centre and fades it to
|
||||
nothing at its edges.
|
||||
- `pulse` follows the page's monotonic clock (`performance.now()`), as the
|
||||
old materials did, not the app clock.
|
||||
- `dash` lays `cells` dashes along the whole line. Each is lit for its first
|
||||
`duty`, with soft edges of 0.08 of a cell, and brightened by `emission`
|
||||
times its colour. Between them the alpha is times `gapAlpha`. The dashes
|
||||
move toward the line's end at `speed` cells a second.
|
||||
- A shown set with a `pulse` or a `dash` makes the engine draw a frame after
|
||||
every frame, as Cesium's continuous render did. `list()` reports it as
|
||||
`animated`. A `glow` alone does not animate.
|
||||
|
||||
Traffic's jam heat lines glow (power 0.25) and pulse (alpha 0.35 to 0.75,
|
||||
1.63 s). The Directions route's 64 dashes flow at 0.55 cells a second.
|
||||
|
||||
A pixel corridor may instead use `dashPattern: { lengthPx, pattern: 255 }`,
|
||||
a static 16-bit screen-space dash mask with transparent gaps, as Cesium's
|
||||
PolylineDash material. It cannot combine with the other corridor looks.
|
||||
Bhote Koshi uses an 18 px dash behind its 5 px glowing flood core.
|
||||
|
||||
Use `dynamic: true` on a set whose path changes each frame. Cesium waits
|
||||
for its shared terrain-height bounds, then builds these small reveals
|
||||
synchronously so successive frames do not discard unfinished worker jobs.
|
||||
Other sets retain asynchronous building.
|
||||
|
||||
### Radial pulse fields
|
||||
|
||||
`engine.ground.createPulseField({ id, center, radiusM, heightM, color,
|
||||
lineColor, haloColor, depth, lineWidth, haloWidth })` draws up to four
|
||||
concentric pulse fronts. The centre is Earth-fixed metres; the radius is
|
||||
metres along the surface. The disc lies on the ellipsoid raised by
|
||||
`heightM` (default 0), with normal scene depth testing. Colours are rgba in
|
||||
0..1, widths are CSS pixels, and `depth` is the inward band depth as a
|
||||
fraction of the radius. A band's opacity falls quadratically to zero.
|
||||
|
||||
Call `draw({ radii, alphas, level })` with up to four radius fractions and
|
||||
opacities in 0..1. Missing fronts are zero. `level` controls visibility;
|
||||
the caller includes it in the alphas. The field starts hidden. `move(center)`
|
||||
follows a new centre; `destroy()` releases it and is safe to repeat.
|
||||
`ground.list()` includes it as `kind: 'pulse-field'`. The caller owns its
|
||||
clock and render requests; the field starts no animation loop.
|
||||
|
||||
Global Context keeps its 2,500 m pulse height, fixed-width front and halo,
|
||||
wake and rest windows, reduced-motion behaviour and 80 ms frame cadence.
|
||||
Cesium retains one material disc, updating its uniforms and rotating for
|
||||
small moves. Navara evaluates the same bands after tone mapping against a
|
||||
raised ellipsoid and scene depth; its shader lives in
|
||||
`src/globe/navara/ground/pulse.js`. The fake engine records the same state.
|
||||
|
||||
Bhote Koshi's event and locator share `src/layers/bhoteKoshi/render/story.js`:
|
||||
the authored reveal properties feed ground corridors and polygons, and
|
||||
point movers retain their independent fill and outline opacity. No story
|
||||
backend is selected by engine. The authored model still uses its existing
|
||||
property records; removing that model dependency is separate work.
|
||||
|
||||
### Ground drawing guarantees
|
||||
|
||||
Both engines preserve item order, fill and outline colours, pixel and metre
|
||||
widths, visibility, and pick ownership. Non-pickable sets let picks through.
|
||||
`ready()` reports when committed geometry is ready to draw. Animated corridor
|
||||
looks use the shared ground time and request frames while shown.
|
||||
|
||||
Cesium translates ground items to classified fills and polylines. Navara
|
||||
classifies visible scene depth and blends the ground in display space.
|
||||
Navara picks ground items at the surface under the pointer. Packing, tiling
|
||||
and bin sizes are implementation details in `src/globe/navara/ground/`.
|
||||
|
||||
## Imagery: rasters on the ground
|
||||
|
||||
`engine.imagery.createLayer({ id, source, alpha, show, split, order, raise })`
|
||||
makes one raster layer draped over the terrain and the 3D tiles, under
|
||||
every ground fill, mover, line and model. Its colours are the image's own,
|
||||
blended by the texel's alpha times the layer's `alpha`.
|
||||
|
||||
There are two kinds of source:
|
||||
|
||||
| kind | source | for |
|
||||
| ------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| `tiles` | `url` with `{z}`, `{x}`, `{y}` (and `{s}` of `subdomains`), `minLevel`, `maxLevel`, `tileSize` | Recent Imagery's GIBS days, weather tiles, open overlays |
|
||||
| `image` | `image`: a canvas, an ImageBitmap or image element, or `{ width, height, data }` RGBA bytes; or `url` | the FIRMS heat raster, a field image, event imagery |
|
||||
|
||||
Both take:
|
||||
|
||||
- `rectangle` (`{ west, south, east, north }` in degrees; west > east
|
||||
crosses the antimeridian): where the layer draws, the globe when omitted;
|
||||
- `credit`: HTML shown while the layer is.
|
||||
|
||||
Tiles are Web Mercator XYZ tiles, which stop at ±85.05°, or with
|
||||
`scheme: 'geographic'` EPSG:4326 tiles (2×1 at level 0, y down from 90°N,
|
||||
Cesium's `GeographicTilingScheme`). `opaque` tiles have no alpha channel,
|
||||
and `maxRequests` caps the layer's tile requests in flight.
|
||||
`transform(image)` turns each decoded tile (row 0 on the north edge) into
|
||||
the canvas or image that draws: the weather's infrared mask. An image is
|
||||
stretched over its rectangle in longitude and latitude, row 0 on the north
|
||||
edge; one over the whole globe wraps across 180°.
|
||||
|
||||
`raise: heightM` puts an image layer on a shell that high above the
|
||||
ellipsoid instead of draping it: it draws over terrain, 3D tiles and the
|
||||
sky's edge, hidden only by what is nearer. The shell rises with the camera
|
||||
(4 m per kilometre of camera height, at most 60 km, in 500 m steps) so the
|
||||
coarse 3D tiles seen from far never hide it. Raised layers take image
|
||||
sources only.
|
||||
|
||||
Work with a layer like this:
|
||||
|
||||
- `setSource(source)` draws another source in its place, at its place in
|
||||
the order; the same canvas or bytes again draw their new pixels, and a
|
||||
source that differs only in its `credit` keeps its pixels;
|
||||
- `setSource(source, { staged: true, signal })` keeps the drawn source
|
||||
until the new one is complete (the image loaded; the tiles in view in),
|
||||
then swaps them. It resolves true then, and false when the new source
|
||||
failed, `signal` aborted it, or another `setSource` replaced it first. A
|
||||
hidden layer takes it at once. `staging()` says one is on its way;
|
||||
- `setDetail({ image, rectangle } | null)` (raised layers): inside the
|
||||
rectangle the layer draws the detail image in place of its own (a
|
||||
sharper view of the area in view). The last detail draws until the new
|
||||
one is uploaded; it resolves true once it draws. null clears it;
|
||||
- `setAlpha(alpha)` fades it: at 0 it draws nothing but stays on its
|
||||
surface, so it comes back without a gap (the FIRMS hand-off);
|
||||
- `show(visible)` shows or hides it (Cesium unmaps a hidden layer's tiles);
|
||||
- `setSplit('left' | 'right' | 'none')` puts it on one side of the split;
|
||||
- `ready()` says the source is built (an image URL loaded); tiles arrive
|
||||
after it;
|
||||
- `destroy()`.
|
||||
|
||||
On the engine:
|
||||
|
||||
- `list()` names the layers in draw order, bottom first: a higher `order`
|
||||
draws on top, then the later layer;
|
||||
- `setSplitPosition(fraction)` sets where the split runs, a fraction of the
|
||||
canvas width from the left (one per engine, Cesium's
|
||||
`scene.splitPosition`); `splitPosition()` reads it;
|
||||
- `surface()` says where imagery drapes now: `'globe'`, `'tiles'` (3D tiles)
|
||||
or `'none'`.
|
||||
|
||||
### Imagery drawing guarantees
|
||||
|
||||
Both engines draw imagery below ground shapes, movers, lines and models,
|
||||
in layer order. Each texel blends by its alpha times the layer alpha.
|
||||
Visibility, split position, rectangle bounds and raised-shell height follow
|
||||
the imagery contract. Replacing or destroying a source releases its owned
|
||||
resources; stale loads cannot replace the current source.
|
||||
|
||||
Cesium translates sources to imagery providers and raised shells. Navara
|
||||
samples imagery in its surface pass, including raised ellipsoid intersections.
|
||||
The adapters own texture storage, tile selection and depth reconstruction.
|
||||
|
||||
Cesium moves draped layers between the visible globe and tileset surfaces;
|
||||
without either surface, layers wait. Its split applies to globe imagery,
|
||||
not raised shells. Navara draws the top eight visible imagery slots, with
|
||||
detail images consuming a slot. Staged sources keep the current image visible
|
||||
until their replacement is ready; a failed source leaves it intact.
|
||||
|
||||
Open map overlays use `engine.imagery` when `maps.addOverlay` is absent.
|
||||
Navara's optional native raster path retains terrain detail, but does not
|
||||
provide rectangle bounds, split, alpha, order, or drawing on 3D tiles.
|
||||
|
||||
### Who draws through imagery
|
||||
|
||||
- **FIRMS heat** (`src/layers/firms/render/display.js`): one image.
|
||||
- **Recent Imagery** (`src/layers/recentImagery/render/display.js`): two
|
||||
Web Mercator tile layers under a split.
|
||||
- **Weather radar, satellite clouds and lightning**
|
||||
(`src/layers/weather/render/display.js`): on the globe, the product's
|
||||
geographic tiles (the regional infrared through its mask, a tile
|
||||
transform), or the global infrared mosaic as one image; each new frame a
|
||||
staged source; clouds, radar and lightning at orders 21, 22, 23. On 3D
|
||||
tiles, a raised layer at the product's shell height
|
||||
(`shellRendering.js` `WEATHER_SHELL_HEIGHTS`) with the detail window
|
||||
around the view as its detail.
|
||||
- **The wind field** (`src/layers/wind/render/display.js`): one image over
|
||||
the globe at order 20, faded with the camera's height while streamlines
|
||||
draw, raised at 5 km on 3D tiles. The streamlines stay each engine's,
|
||||
behind the display's `flow` backend, loaded by the manifest:
|
||||
`src/globe/cesium/windFlow.js` supplies Cesium's streamlines
|
||||
(`src/layers/wind/rendering.js`), and `src/globe/navara/windFlow.js`
|
||||
supplies Navara's flow mesh.
|
||||
- **The Bhote Koshi event imagery** (`src/layers/bhoteKoshi/render/display.js`):
|
||||
two `url` images over the event's rectangle at alpha 0.96, split left
|
||||
and right at the engine's split position, order 15. The event's and the
|
||||
locator's story graphics use the shared ground and mover display described
|
||||
above.
|
||||
|
||||
- **Imported KML/KMZ ground images** (`src/data/geoImportDisplay.js`): one
|
||||
image layer per rectangle at order 30, with the authored alpha capped at
|
||||
0.99 as on Cesium; a tinted or rotated image is drawn on a canvas first,
|
||||
and an absolute one is raised to its altitude.
|
||||
- **Sea Marks and Ski Pistes** (`src/data/osmOverlays.js`): tile layers at
|
||||
order 10 on engines without `maps.addOverlay`.
|
||||
|
||||
## Imported files
|
||||
|
||||
`geoFileImport.js` reads GeoJSON, GPX, KML and KMZ into the plain records in
|
||||
`src/data/geoImportRecords.js`. `geoImportKml.js` resolves KML styles with
|
||||
Cesium's precedence; its tests compare the records with `KmlDataSource`.
|
||||
No import loads the Cesium SDK. One `geoImportDisplay.js` draws the records
|
||||
on every engine: points on movers, ground paths and polygon fills on ground,
|
||||
raised paths and outlines on lines, raised polygon fills on meshes, and packed
|
||||
ground images on imagery.
|
||||
Every owned id starts with `geo-import-`; the file controller owns visibility,
|
||||
framing, refresh and destruction across engine switches.
|
||||
|
||||
Pins use composed square sprites to retain Cesium's pin tip, hot spot,
|
||||
subregion, colour, scale and rotation; a KML heading turns with the camera's
|
||||
heading, so it stays measured from north. Icon textures share a per-file
|
||||
budget of 16 million pixels: many distinct styles get lower-resolution
|
||||
textures at the same size. Ground heights come from the loaded surface,
|
||||
refreshed after the camera or map settles, in batches of 256; on 3D tiles
|
||||
only pins in the window are clamped to the tiles drawn. Ground pins ignore
|
||||
scene depth and are culled beyond the horizon; absolute pins retain their
|
||||
height.
|
||||
|
||||
Raised polygons (KML absolute or relative) draw their fills as meshes and
|
||||
their outlines as lines at the vertices' heights. A ground overlay's colour
|
||||
tints its image and its alpha fades it; a rotated overlay's image is turned
|
||||
on a canvas over the box around it, as Cesium turns its rectangle in degrees,
|
||||
and an absolute image or plain-colour overlay is a mesh at its altitude,
|
||||
including negative heights (Cesium raises only absolute ones).
|
||||
The shared display has known limits against Cesium: wall fills and extruded
|
||||
prisms are omitted (their top geometry remains), and
|
||||
`gx:LatLonQuad` and a rotated overlay whose box passes a pole are omitted.
|
||||
These are counted as undrawn. Ground edges use great circles rather than
|
||||
rhumb lines.
|
||||
|
||||
## Input ownership
|
||||
|
||||
`engine.input.on(type, listener, { pick, drill, pickWindow })` accepts
|
||||
`pickWindow: { width, height }` with positive integer dimensions passed to
|
||||
the picker. Omitted dimensions keep the engine default. Listeners share a
|
||||
pick only when their windows match, and each receives its requested drill
|
||||
limit. Pointer-down picks are synchronous. Moves, including drills, coalesce
|
||||
to the latest position per frame. Navara clicks still use one GPU answer
|
||||
from the gesture; a missing hit falls back to the requested CPU window.
|
||||
Street Level requests 9 × 9 px; CCTV's gizmo requests its own window and
|
||||
limit for presses and hover. Pointer ownership and removal still suppress
|
||||
delivery while a pick is pending.
|
||||
|
||||
`engine.projector.horizonVisible(position, { ellipsoid })` accepts a plain
|
||||
ellipsoid with positive finite `radii: { x, y, z }` in metres. Without the
|
||||
option it uses WGS84. Street Level supplies WGS84 radii reduced by 1,000 m
|
||||
so nearby cones remain visible below sea level. The shared math is tested
|
||||
against the native ellipsoidal occluder, including points near the limb.
|
||||
|
||||
`engine.input.suspendCameraInputs()` disables camera gestures and returns an
|
||||
idempotent resume function. Suspensions are counted: only the last resume
|
||||
restores the state captured before the first suspension. This preserves an
|
||||
already-disabled camera. `cameraInputsEnabled()` reads the current state.
|
||||
Cockpit, imagery-box drawing, CCTV calibration and infrastructure flights use
|
||||
these methods on every engine. Pointer claims remain separate from camera
|
||||
input suspension.
|
||||
|
||||
Pick ownership belongs to the scene attachment, not to an engine implementation.
|
||||
`scene.services.picking` owns its predicates and `scene.services.selection` owns
|
||||
its shared click dispatcher. Layer entry points resolve these services using
|
||||
their attached engine (`sceneServicesFor(engine)`); the pick-registry helper
|
||||
functions take that engine as their final argument. Standalone entries acquire
|
||||
the same services on first use. The application destroys them on detach and
|
||||
creates fresh services when reattaching a parked engine. Identical layer IDs in
|
||||
simultaneous scenes cannot replace or unregister one another's picks.
|
||||
|
||||
## Render demand
|
||||
|
||||
`scene.services.render` owns continuous-render holds and the last 16 idle
|
||||
request reasons for one attachment. `holdContinuousRender(id)` and
|
||||
`releaseContinuousRender(id)` are idempotent per owner; the last release
|
||||
requests one settling frame. `governorRequestRender(reason)` forwards a
|
||||
one-shot request even while another owner holds continuous rendering.
|
||||
The tools call `install()` after scene consumers have registered their holds.
|
||||
|
||||
The governor drives `engine.render` on every engine. Cesium implements
|
||||
`setContinuous` with its scene's `requestRenderMode` and disables automatic
|
||||
simulation-time renders with `maximumRenderTimeChange = Infinity`. Engine
|
||||
internals request their own scene directly and never import the governor.
|
||||
Layer service helpers take their attached engine as the final argument;
|
||||
attachment-bound callbacks can capture `renderGovernorFor(engine)` directly.
|
||||
Destroying the scene clears its holds and makes the captured service inert.
|
||||
A parked engine gets a fresh service when reattached.
|
||||
|
||||
## World overlays
|
||||
|
||||
`scene.services.overlay` owns one attachment's world labels and cards. It
|
||||
keeps its sources, collision domains, paint lanes, canvases, frame listeners,
|
||||
observers, timers and accessible action subscribers. `init()` installs its
|
||||
DOM inside `engine.host.container`; the detection blend surface stays beside
|
||||
the card root so it can blend against the scene.
|
||||
|
||||
Publish with `setEntries(sourceId, entries, options)`, `setVisible(sourceId,
|
||||
visible)` and `clearSource(sourceId)`. Read painted rectangles with
|
||||
`getPaintRect(sourceId, entryId)` and register custom painters with
|
||||
`registerPaintLane(laneId, painter, options)`. `subscribeActions(listener)`
|
||||
provides the accessible mirror; `activateAction(owner, key)` rejects an old
|
||||
host's actions. The shell reconnects these operations on every attachment.
|
||||
|
||||
`worldOverlayFor(engine)` resolves the same service for standalone consumers.
|
||||
The module helper functions take the engine first. Retained layer models use
|
||||
`bindOverlayHost` with their attached-engine getter; injected hosts without a
|
||||
`forEngine` resolver keep their own operations. Publications can buffer before
|
||||
`init()`. Destruction is terminal, including before initialization; a parked
|
||||
engine needs fresh scene services. An old paint-lane handle cannot mutate its
|
||||
replacement. Font measurements are shared until the last live host releases
|
||||
them.
|
||||
|
||||
`scene.services.trackedReadout` bridges the follow handle's label and selected
|
||||
ALPR or Local ADS-B card into this overlay. `init()` subscribes to that engine's
|
||||
follow changes and that attachment's context events; `refresh(entity)` republishes
|
||||
changed label text, and `activeId()` resolves the card for annotation placement.
|
||||
Positions stay live cached visual anchors. `trackedReadoutFor(engine)` resolves
|
||||
the service; destruction removes its listeners and clears only its own source.
|
||||
|
||||
`scene.services.context` owns selection records and notifications. Layer publishers
|
||||
use `bindOverlayHost` with their current engine, or a context service captured by
|
||||
their display. It mirrors records to the page context slot and preserves the
|
||||
`gev:entity-selected`, `gev:entity-selection-cleared` and aircraft awareness event
|
||||
payloads for page consumers. A clear reaches the page only when that attachment
|
||||
owns the page's selected record. Readouts listen only to their scene's event target.
|
||||
Identical IDs in different scenes cannot share a card or clear one another's
|
||||
selection. Retired context services cannot publish into a replacement attachment.
|
||||
An engine handoff carries the page-selected record through the layer session.
|
||||
The incoming context adopts it only while the page still selects that record;
|
||||
a clear between attachments wins. Rebuilt markers replace the retained carrier
|
||||
in the incoming readout without announcing another page selection.
|
||||
Readout initialization reads the retained record without testing the outgoing
|
||||
carrier's visibility; layers rebuild their carriers after camera restoration.
|
||||
The page chrome and detection controller still have page lifetimes.
|
||||
|
||||
## Render effects
|
||||
|
||||
The optional `visualStyles`, `bloom` and `contactSonar` capabilities are
|
||||
available on Cesium and Navara through `engine.render`. Cesium implements
|
||||
styles, sharpening, borrowed bloom and the Anime Milky Way fade in
|
||||
`src/globe/cesium/postStages.js`; contact sonar is in `sonar.js` and
|
||||
`sonarCommands.js`. Cesium does not advertise `cloudCover`.
|
||||
|
||||
`VisualEffects` drives transitions through the render contract. UI teardown
|
||||
stops animation and disables effects; engine destruction removes its stages
|
||||
and restores the borrowed bloom state. Sharpen intensity zero disables
|
||||
sharpening on both engines, following Cesium.
|
||||
|
||||
## Wildlife privacy cells
|
||||
|
||||
Wildlife privacy cells use one `cell` ground set for distinct bounds and taxon
|
||||
accents. The fill is 25% opaque and the 2 px outline is 80% opaque, matching
|
||||
Cesium's original ground geometry. Both drape on terrain and tiles and pass
|
||||
picks through to the badges. Wide views and detach release the set.
|
||||
|
||||
## Batched line segments
|
||||
|
||||
`engine.lines.createSegments({ id, widthPx, color, order })` groups independent
|
||||
straight segments in one batch. `replace(items)` copies pairs of Earth-fixed
|
||||
endpoints and optional per-item `widthPx`, `color` and `pickId`; `commit()`
|
||||
rebuilds the drawing once. Segments never connect to one another. `show()`
|
||||
and `destroy()` affect the whole batch; an empty replacement clears it.
|
||||
The batch shares `createLine`'s id namespace and `list()`, which includes its
|
||||
endpoint count and committed `pickIds` in item order.
|
||||
|
||||
Cesium draws one synchronous primitive with per-instance colours and widths.
|
||||
Navara draws one ribbon mesh in the overlay after tone mapping, with scene
|
||||
depth and independent segment picks. Batches are solid and depth-tested;
|
||||
curved, dashed, transformed and depth-fail lines still use `createLine`.
|
||||
Infrastructure and cable references each use one batch for their visible
|
||||
stems, preserving Cesium's colours, widths, label winners and update cadence.
|
||||
Infrastructure excludes the whole stem batch while sampling its bases.
|
||||
|
||||
### Choosing a primitive
|
||||
|
||||
- **A position and an on-screen size:** a mover.
|
||||
- **A line at given heights, or one that moves or is restyled every frame**
|
||||
(trails, orbits, a ring at the terrain height): a line.
|
||||
- **Anything that lies on the ground** and follows its relief:
|
||||
- an area: `polygon`, or `disc` or `cell` when that is its shape (they
|
||||
are exact and cheaper);
|
||||
- a line on the ground: `corridor`, with `widthM` when its width is a
|
||||
real width (an outline in metres) and `widthPx` otherwise.
|
||||
- Group items that change together into one set, and replace them in bulk.
|
||||
A ground set rebuilds on commit. Small paths revealed each frame use
|
||||
`dynamic: true`; corridor looks animate without a rebuild, and radial
|
||||
fronts use `ground.createPulseField`.
|
||||
- **Pixels on the ground** (tiles, a raster, a photo): an imagery layer.
|
||||
Fade it with `setAlpha`, and give a new raster to `setSource`.
|
||||
|
||||
## Meshes: world triangles
|
||||
|
||||
`engine.meshes.createMesh({ id, positions, indices, uv, image, color, show,
|
||||
depthTest, pickId })` draws indexed triangles on both sides. Positions are
|
||||
flat Earth-fixed xyz metres and indices name their vertices. Optional UVs run
|
||||
from (0, 0) at the image's bottom left to (1, 1) at its top right. The engine
|
||||
copies geometry arrays; the caller owns the canvas, image or video.
|
||||
|
||||
`update(patch)` replaces only supplied fields. Repeating `image` uploads new
|
||||
canvas pixels; videos update on rendered frames. RGBA `color` multiplies the
|
||||
image, white by default. `show()` reads visibility and `show(boolean)` changes
|
||||
it. `depthTest` is `normal` or `always`; null `pickId` disables picking. Picks
|
||||
return the authored id and the mesh id as owner. `destroy()` is idempotent;
|
||||
`engine.meshes.list()` returns live ids in creation order.
|
||||
|
||||
Cesium draws a synchronous Primitive with a flat material, no back-face
|
||||
culling and no depth writes. Colour, image and visibility changes retain the
|
||||
primitive; geometry, pick-id and depth-test changes replace it. The engine
|
||||
requests frames while the primitive or its image is preparing. Navara draws
|
||||
local coordinates relative to a double-precision world origin in the shared
|
||||
overlay pass after tone mapping and anti-aliasing, the pass that draws sprites
|
||||
and lines, with scene depth and triangle picking. Their authored colours are
|
||||
blended in display space.
|
||||
Both engines own geometry, material and texture disposal. The fake engine
|
||||
keeps the same state and tests triangle intersections for picks.
|
||||
|
||||
CCTV's `render/meshes.js` describes its live monitor quad and viewshed pyramid
|
||||
through this primitive. Its markers and outlines remain movers and lines.
|
||||
Obstruction probes use `engine.surface.raycast`; exclusions accept owned mesh,
|
||||
line and mover handles on Cesium and restore their visibility after the query.
|
||||
|
||||
Data packs use one `src/scenes/dataPacks/display.js`: points on movers,
|
||||
straight raised paths on lines, raised polygons and image rectangles on
|
||||
meshes, and media markers with captions in a composed sprite. Polygon holes
|
||||
and authored vertex heights are retained. Shared triangulation uses `earcut`,
|
||||
with tests comparing its area to Cesium's polygon geometry. Images retain
|
||||
Cesium's one-degree rectangle grid. Scene detach releases drawn resources and
|
||||
pick targets; reattach redraws the retained decoded assets.
|
||||
|
||||
Annotation drapes still use their existing renderers. Their bounded building
|
||||
classification volumes, animated fills and wire cages are not ordinary world
|
||||
triangles; they need a classification contract before that migration can
|
||||
preserve Cesium's behaviour.
|
||||
|
||||
## Shared follow offsets and manual drawing
|
||||
|
||||
`engine.camera.follow({ position, viewFrom, swingTo })` can swing from the
|
||||
initial follow frame to `swingTo`, an east/north/up offset in metres. Each
|
||||
frame turns at most 60 degrees and interpolates range, preserving Cesium's
|
||||
tracked-camera crossing guard. `handle.retarget({ swingTo })` changes only
|
||||
the pending swing; `null` cancels it. Stopping or replacing the follow, a
|
||||
camera command or engine destruction ends it. Share links read the current
|
||||
handle's target and label identity and the camera pose through the contract.
|
||||
|
||||
`engine.input.acquirePointer()` returns an exclusive claim, or `null` if
|
||||
one is held. Pass it to `input.on(type, listener, { pointer: claim })` and
|
||||
call `claim.release()` when done. Other screen subscriptions yield, including
|
||||
picks that settle after ownership changes. Camera gestures stay enabled.
|
||||
Cesium suspends its stock select and track clicks and restores the original
|
||||
functions when the claim is released or the engine is destroyed.
|
||||
|
||||
The Draw tool uses one `drawSurface.js` on every engine: a dashed geodesic
|
||||
line and a point mover set at the clicked heights. The dim, occluded band
|
||||
uses the selected colour as well as the visible band. Finished geometry still
|
||||
goes through the annotation board. Detaching the scene releases the claim,
|
||||
listeners and preview while retaining the chosen shape and colour. The
|
||||
page's pointer lease still arbitrates Draw against other tools.
|
||||
|
||||
## How to add an engine
|
||||
|
||||
Put the implementation in a new `src/globe/<name>/` folder. Keep SDK classes,
|
||||
shaders, resource disposal and SDK workarounds there. Use
|
||||
`src/globe/testing/fakeEngine.js` as the contract model, and
|
||||
`src/globe/contract.js` as the authoritative member and data-shape reference.
|
||||
Coordinates, colours, picks and display options crossing the boundary are plain
|
||||
data; displays do not receive native viewers or SDK types.
|
||||
|
||||
Implement the required host, camera, picker, projector, frame, render, surface,
|
||||
input, credit, clock and map interfaces, plus all five drawing primitives:
|
||||
`movers`, `lines`, `ground`, `imagery` and `meshes`. `assertGlobeEngine(engine)`
|
||||
checks these required interfaces. `engineCapabilities(engine)` reports only the
|
||||
optional visualStyles, bloom, cloudCover and contactSonar groups. An optional
|
||||
group must be complete or absent. Do not advertise a capability with a no-op implementation. Match Cesium's behavior where the
|
||||
contract permits both engines to implement it.
|
||||
|
||||
Run `createContractSuite(engine, { waitFrames, ... })` from
|
||||
`src/globe/testing/contractSuite.mjs`. Supply real pointer delivery, canvases and
|
||||
pixel reads for browser checks; keep the fake-engine tests as fast contract
|
||||
checks. Test ownership and repeated destruction as well as visible output.
|
||||
The browser harness is `scripts/qa-contract-suite.mjs`; use
|
||||
`--only=ground.,imagery.` for ground and imagery pixel checks.
|
||||
|
||||
Three reports keep migration debt from growing: `scripts/report-engine-coupling.mjs`
|
||||
counts application coupling, `scripts/report-qa-harness.mjs` counts browser QA
|
||||
duplication, and `src/globe/testing/contractCoverage.test.mjs` reports contract
|
||||
members the fake-engine suite leaves uncalled. After removing coupling or moving
|
||||
QA work to `scripts/qa-strict-harness.mjs` helpers, run the matching report with
|
||||
`--write-baseline` and keep only decreases in its baseline. After adding behavioral
|
||||
contract checks, remove the newly exercised names from
|
||||
`src/globe/testing/contract-uncalled-baseline.json`; keep it sorted, and remove
|
||||
members that no longer exist too. Run the matching tests with
|
||||
`node --import=./build/registerVue.js --test` followed by their paths; the numeric
|
||||
baselines reject growth, and the contract baseline also rejects stale entries.
|
||||
|
||||
The application currently registers two kinds: adding another still requires
|
||||
registration in `ENGINE_KINDS`, scene construction/choice and the corresponding
|
||||
QA harnesses. `assertGlobeEngine` currently validates that registration. The
|
||||
folder and passing contract suite are the implementation boundary, not a claim
|
||||
that arbitrary engine folders are auto-discovered today. Layers and their
|
||||
fixtures should need no new drawing implementation.
|
||||
|
||||
The layer manifest (`src/app/layers/renderers.js`) loads shared factories through
|
||||
`display`. Every valid engine implements all five primitives; the manifest has
|
||||
no per-kind primitive lists or `needs` declarations. `backends` supplies optional
|
||||
engine-specific display options, currently only wind's `flow`, loaded dynamically
|
||||
from its engine folder. A missing wind backend leaves the shared raster field
|
||||
available. Self-drawing layers and map overlays use the same contract and do not
|
||||
register supported engine kinds in the manifest.
|
||||
|
||||
Run `src/app/layers/displayRehearsal.test.mjs`: it loads every manifest display
|
||||
on the fake engine with an unknown kind and empty extensions. Its small fixtures
|
||||
exercise attachment, drawing, hiding or release, and destruction; primitive
|
||||
registries, frame/camera/input subscriptions, follow state and service records
|
||||
must be clean before the engine itself is destroyed. New display entries need a
|
||||
fixture. Wildlife Sightings and the two map overlays are explicit skips because
|
||||
they have no display factory; their layer tests remain the coverage for those
|
||||
controllers. Native wind streamlines, DOM rasterization and GPU pixels are not
|
||||
covered by this rehearsal.
|
||||
|
||||
`node scripts/check-import-directions.mjs` enforces SDK ownership for static
|
||||
imports, re-exports and literal dynamic imports, including package subpaths.
|
||||
Cesium packages belong in `src/globe/cesium/`; three, postprocessing and Navara
|
||||
packages belong in `src/globe/navara/`. Add the new SDK's ownership rule when
|
||||
registering it. Contract graphs cannot reach any engine, and the existing
|
||||
engines cannot reach one another. The temporary exact file/package exceptions
|
||||
in `scripts/package-boundaries.json` must have reasons and must still be used;
|
||||
the checker prints them. They are migration debt, not a pattern for new layers.
|
||||
Run package boundaries, formatting, the full unit suite and the coupling report
|
||||
as well; engine coupling counts may not grow.
|
||||
@@ -59,30 +59,6 @@ The language picker switches in place, without a reload. A view that calls `t()`
|
||||
|
||||
A toast already on screen keeps its words.
|
||||
|
||||
## An application with its own language choice
|
||||
|
||||
An application that composes Core and offers the language somewhere else (a menu, a settings page) imports the `./i18n` entry (`src/i18n/page.js`) and passes `localePicker: false` to `createApplicationControls`:
|
||||
|
||||
```js
|
||||
import {
|
||||
LOCALES,
|
||||
LOCALE_NAMES,
|
||||
followPageLocale,
|
||||
getLocale,
|
||||
onLocaleChange,
|
||||
startPageLocale,
|
||||
switchLocale,
|
||||
} from '<core package>/i18n';
|
||||
|
||||
startPageLocale(document); // before any view renders: ?lang=, stored choice, browser, English; translates data-i18n markup
|
||||
const stopFollowing = followPageLocale(document); // <html lang>/dir and data-i18n markup follow each switch
|
||||
|
||||
for (const code of LOCALES) addMenuItem(LOCALE_NAMES[code], code === getLocale());
|
||||
menu.onChoose = (code) => switchLocale(code); // switched in place and stored (in ?lang when storage is blocked)
|
||||
```
|
||||
|
||||
`switchLocale` does what a choice in the globe picker does, and returns `'unchanged'` for the active language. `onLocaleChange` tells the application's own menu to mark the new language.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Pass literals.** `t('Close')`, not `t(label)` (unless `label` holds a `msg()` value) and never a template literal with `${…}`.
|
||||
@@ -133,7 +109,6 @@ Icons (`aria-hidden="true"`, Material Symbols), comments, CSS values and lowerca
|
||||
| `src/ui/i18n.js` | the page's vue-i18n instance: the active locale and the catalogs |
|
||||
| `src/i18n/marker.js` | `msg` alone, for modules the server or tools import (no catalogs) |
|
||||
| `src/i18n/locale.js` | shipped languages, their names, choosing the language at boot |
|
||||
| `src/i18n/page.js` | the `./i18n` entry: start, follow and switch the page's language |
|
||||
| `src/i18n/locales/<code>.json` | one catalog per language, keyed by English text |
|
||||
| `src/i18n/same-as-english.json` | per language, the entries that stay as in English on purpose |
|
||||
| `scripts/i18n.mjs` | `npm run i18n`: extracts messages, checks the catalogs, finds unmarked UI text |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
`gods-eye-view/infrastructure` exports `createInfrastructureLayers(services)`.
|
||||
It returns fresh datacenter and dam layers in that order, with the existing IDs,
|
||||
appearance, label budgets and bundled public datasets. Importing the module or
|
||||
calling the factory does not load data, create a viewer, or start the application.
|
||||
calling the factory does not load data, create an engine, or start the application.
|
||||
The asset URLs resolve relative to the module; Vite rewrites them for production
|
||||
base paths. This browser source export expects a Vite-compatible asset build.
|
||||
|
||||
@@ -21,7 +21,13 @@ const layers = createInfrastructureLayers({
|
||||
governorRequestRender,
|
||||
});
|
||||
|
||||
// Register these layers with the existing data-layer manager and viewer.
|
||||
// Attach each layer to the current engine, then enable it when requested.
|
||||
for (const layer of layers) layer.init(null, { engine });
|
||||
await layers[0].enable();
|
||||
|
||||
// On a scene switch, keep the parsed data and replace only its display.
|
||||
for (const layer of layers) layer.detachRenderer();
|
||||
for (const layer of layers) await layer.attachRenderer(null, { engine: nextEngine });
|
||||
```
|
||||
|
||||
| Function | Arguments / responsibility |
|
||||
@@ -35,28 +41,39 @@ const layers = createInfrastructureLayers({
|
||||
| `removeEntityContextsForLayer` | `(layerId)`; remove records owned by the layer on destruction or failed setup |
|
||||
| `governorRequestRender` | `(reason)`; request a frame through the existing render scheduler |
|
||||
|
||||
Each returned layer supports `init(viewer)`, `enable(viewer)`, `disable(viewer)`,
|
||||
`update(viewer)`, `destroy(viewer)`, `getStats()`, `getLodDiagnostics()` and
|
||||
`getAnalystRecords()`.
|
||||
Use the same viewer for the instance's entire lifetime. Each viewer/context/overlay
|
||||
host permits one live instance per layer ID; destroy an old instance before
|
||||
replacing it. Separate applications may use identical IDs with separate hosts.
|
||||
Callbacks must be ready before enable and remain usable through destruction.
|
||||
Each returned session layer supports `init(null, { engine })`,
|
||||
`attachRenderer(null, { engine, signal })`, `detachRenderer()`, `enable()`,
|
||||
`disable()`, `update()`, `destroy()`, `getStats()`, `getLodDiagnostics()` and
|
||||
`getAnalystRecords(limit)`. The first argument is retained for lifecycle
|
||||
compatibility; drawing uses the engine passed in the options. Attach once per
|
||||
scene, detach before attaching another engine, and destroy when the session ends.
|
||||
Separate applications may use identical IDs with separate context/overlay hosts.
|
||||
Callbacks must remain usable through destruction.
|
||||
|
||||
Disable retains the loaded dataset for reuse and clears selection and listeners.
|
||||
Concurrent enables share one load. Destroy is permanent, aborts pending fetches,
|
||||
discards late results, and clears the owned data source, context records, overlays,
|
||||
input handler and listeners. The layer never creates an application context store,
|
||||
overlay host, viewer, or render scheduler. The consumer must resolve one compatible
|
||||
Cesium installation shared with its viewer; it must not bundle separate copies.
|
||||
`src/layers/infrastructure/index.js` owns the parsed dataset, shared in-flight
|
||||
load, enabled intention and selection across scene switches. Enable loads on
|
||||
first use. Disable hides the drawing and clears selection but retains parsed
|
||||
data. Detach destroys scene resources and retains the dataset and selected ID;
|
||||
reattach redraws enabled data and restores selection when no other selection
|
||||
owns the context. The attachment signal cancels that scene's wait, not the
|
||||
session's fetch. Destroy aborts the fetch, discards late results and releases
|
||||
both data and drawing.
|
||||
|
||||
`gods-eye-view/infrastructure/geojson` exports the lower-level
|
||||
`createLocalGeoJsonLayer(options, services)` and existing infrastructure overlay
|
||||
helpers. `gods-eye-view/infrastructure/lod` exports the existing LOD policy helpers.
|
||||
Consumers use the package exports, which do not import standalone application
|
||||
globals.
|
||||
`src/layers/infrastructure/render/display.js` draws the same records on every
|
||||
engine: point movers for tips and pins, lines for stems, ground polygons for
|
||||
footprints and ground corridors for paths. It owns the LOD walk, surface-height
|
||||
refresh, picks, context handles and overlay publications. Destruction releases
|
||||
its primitives and frame, camera and input subscriptions. The layer never
|
||||
creates an overlay host, engine or render scheduler and does not import Cesium.
|
||||
|
||||
`gods-eye-view/infrastructure/geojson` exports the overlay publication,
|
||||
selection, label and error helpers in `src/data/localGeojsonCore.js`; it no
|
||||
longer exports `createLocalGeoJsonLayer`. `gods-eye-view/infrastructure/lod`
|
||||
exports the LOD policy helpers. The package exports do not import standalone
|
||||
application globals.
|
||||
|
||||
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
|
||||
expand multipart geometries into multiple entities; entity and stem counts are
|
||||
different measurements. This change does not refresh or relicense the datasets.
|
||||
Their input counts are 4,352 datacenter features and 704 dam features. The session
|
||||
splits multipart geometries using Cesium's stable repeated-id suffixes on every
|
||||
engine. Feature, mover and stem counts are different measurements. These files
|
||||
and licenses are independent of the renderer implementation.
|
||||
+63
-1
@@ -1,6 +1,6 @@
|
||||
# KNOWN ISSUES
|
||||
|
||||
Updated: November 5, 2026
|
||||
Updated: November 10, 2026
|
||||
|
||||
This file tracks active runtime issues only.
|
||||
|
||||
@@ -10,6 +10,68 @@ For the roadmap and open backlog, see the repository issue tracker.
|
||||
|
||||
## Open
|
||||
|
||||
### Supplied decoded weather tiles require Cesium on globe surfaces
|
||||
|
||||
Status: Open (Cesium only)
|
||||
|
||||
Custom sources supplying individual decoded radar, regional infrared or
|
||||
lightning tiles use Cesium's imagery provider scheduler
|
||||
(`src/globe/cesium/weatherFrames.js`). Navara hides these controls on globe
|
||||
surfaces and requests no supplied tiles there. Full-extent cloud images and
|
||||
raised weather shells remain available on both engines. A full port needs
|
||||
decoded tile callbacks in the imagery contract and the Navara adapter.
|
||||
|
||||
### Street-vehicle label depth occlusion requires Cesium
|
||||
|
||||
Status: Open (Cesium only)
|
||||
|
||||
GPU depth occlusion for street-vehicle brackets and callouts uses Cesium's
|
||||
depth probe (`src/data/detection.js`). Navara retains the existing geometric
|
||||
visibility checks, without the GPU depth result. The contract needs an
|
||||
asynchronous depth-probe operation before this can run on both engines.
|
||||
|
||||
### Large raised KML polygons omit their fill
|
||||
|
||||
Status: Open (bounded triangulation)
|
||||
|
||||
On both engines, raised polygons with more than 32,768 vertices draw only
|
||||
their outlines. Those with more than 128 rings draw neither fill nor
|
||||
outlines. These limits bound main-thread triangulation and outline work;
|
||||
the omitted geometry is counted as undrawn in the import result.
|
||||
|
||||
### Navara terrain tokens are not refreshed while the page stays open
|
||||
|
||||
Status: Open (reload required)
|
||||
|
||||
An expired terrain token is not refreshed during an open Navara session.
|
||||
Reload the page to request a new terrain token.
|
||||
|
||||
### Navara does not fall back when a map source fails
|
||||
|
||||
Status: Open (manual map selection)
|
||||
|
||||
Navara does not report tile errors, so a failing map source does not trigger
|
||||
fallback to another map. Select another map manually when tiles fail to load.
|
||||
|
||||
### Orbital imagery look-ahead is unavailable on Navara
|
||||
|
||||
Status: Open (Cesium only)
|
||||
|
||||
The orbital cockpit works on both engines. On Navara it loads imagery for
|
||||
its current view but does not prefetch imagery ahead of the ground track.
|
||||
At high optical zoom, imagery can arrive later than on Cesium. Navara's
|
||||
package does not expose a raster prefetch operation sharing its tile cache
|
||||
and request priorities.
|
||||
|
||||
### Ship bridge waterline uses loaded geometry on Navara
|
||||
|
||||
Status: Open (terrain fallback)
|
||||
|
||||
Navara's ship bridge samples loaded mesh geometry and detailed terrain for
|
||||
the waterline. It does not request the finest mesh detail around the ship,
|
||||
so the bridge height can differ while nearby tiles load. Cesium retains its
|
||||
finest-detail mesh sampling.
|
||||
|
||||
### Wildlife grid relies on the iNaturalist UTFGrid cell schema
|
||||
|
||||
Status: Open (guarded fallback)
|
||||
|
||||
+31
-47
@@ -68,16 +68,6 @@ 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
|
||||
|
||||
The code is unpadded base64url (`A–Z a–z 0–9 - _`). The decoded bytes are:
|
||||
@@ -117,37 +107,36 @@ probability of about 255 in 256 (many of them already fail as base64url).
|
||||
|
||||
## Field registry (top level)
|
||||
|
||||
| # | State key | Type | Default | Encoding |
|
||||
| --- | ---------------------------- | ----- | ----------- | --------------------------------------------------------------------- |
|
||||
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
|
||||
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 4 | `alt` | sint | 800 | whole metres |
|
||||
| 5 | `heading` | angle | 0 | whole degrees, see angles |
|
||||
| 6 | `pitch` | sint | −35 | whole degrees |
|
||||
| 7 | `roll` | angle | 0 | whole degrees |
|
||||
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
|
||||
| 9 | booleans | flags | 0 | bit set when the value differs from default |
|
||||
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
|
||||
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
|
||||
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
|
||||
| 13 | thing | LEN | none | sub-message, see thing views |
|
||||
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow (retired) |
|
||||
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm |
|
||||
| 16 | `hudVariant` | enum | style | tactical, operator (retired), minimal, cyber |
|
||||
| 17 | `bloomIntensity` | uint | style | |
|
||||
| 18 | `bloomVersion` | uint | 2 | |
|
||||
| 19 | `sharpenIntensity` | uint | style | |
|
||||
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
|
||||
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
|
||||
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
|
||||
| 23 | `detectionFadePct` | uint | style | 0–40 |
|
||||
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
|
||||
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
|
||||
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
|
||||
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
|
||||
| 28 | `worldTime` | epoch | none (live) | zigzag seconds since 2026-01-01T00:00:00Z |
|
||||
| 29 | `library` | LEN | none | Data Library ledger tokens, two base-36 characters each, concatenated |
|
||||
| # | State key | Type | Default | Encoding |
|
||||
| --- | ---------------------------- | ----- | ----------- | ----------------------------------------------------------------- |
|
||||
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
|
||||
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 4 | `alt` | sint | 800 | whole metres |
|
||||
| 5 | `heading` | angle | 0 | whole degrees, see angles |
|
||||
| 6 | `pitch` | sint | −35 | whole degrees |
|
||||
| 7 | `roll` | angle | 0 | whole degrees |
|
||||
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
|
||||
| 9 | booleans | flags | 0 | bit set when the value differs from default |
|
||||
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
|
||||
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
|
||||
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
|
||||
| 13 | thing | LEN | none | sub-message, see thing views |
|
||||
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow (retired) |
|
||||
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm, sentinel2 |
|
||||
| 16 | `hudVariant` | enum | style | tactical, operator (retired), minimal, cyber |
|
||||
| 17 | `bloomIntensity` | uint | style | |
|
||||
| 18 | `bloomVersion` | uint | 2 | |
|
||||
| 19 | `sharpenIntensity` | uint | style | |
|
||||
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
|
||||
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
|
||||
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
|
||||
| 23 | `detectionFadePct` | uint | style | 0–40 |
|
||||
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
|
||||
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
|
||||
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
|
||||
| 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,
|
||||
so v1 never writes it; a later view kind writes its code.
|
||||
@@ -165,11 +154,6 @@ 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.
|
||||
|
||||
Field 29 lists the Data Library entries switched on, by their permanent
|
||||
tokens in `src/library/ledger.json` (the long form's `lib=ab.cd`). A share
|
||||
without it switches no library entry on; the recipient's added entries stay
|
||||
in their panel, off. Tokens this build does not know are skipped.
|
||||
|
||||
Flag bits (field 9): 0 `bloom`, 1 `sharpen`, 2 `hudVisible`, 3
|
||||
`celestialRing` (each defaulting by style), 4 `scopeEnabled` (default on).
|
||||
|
||||
@@ -397,7 +381,7 @@ the app that can open it.
|
||||
offers keeps its code: `snow` (6) is retired, so a link that names it
|
||||
opens as Normal and the parameters written for it are dropped.
|
||||
5. **Add, don't change.** A new setting takes the next free field number
|
||||
(30 onwards; fields 1–15 have one-byte tags and are all taken). A new
|
||||
(28 onwards; fields 1–15 have one-byte tags and are all taken). A new
|
||||
boolean takes the next flag bit instead, which costs nothing when it is at
|
||||
its default.
|
||||
6. **New view kinds and entity kinds** get the next code. A new entity kind's
|
||||
|
||||
+5
-23
@@ -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
|
||||
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
|
||||
`camera`, plus `layers`, `style`, `map`, `follow`, `annotations` and
|
||||
`street_level`. A tilted camera over an area looks at its center from behind;
|
||||
camera fields given with an area override its framing. A passed-through
|
||||
view's `street_level` point moves with a new `area` or camera position (a
|
||||
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`.
|
||||
`camera`, plus `layers`, `style`, `map`, `follow` and `annotations`. A
|
||||
tilted camera over an area looks at its center from behind; camera fields
|
||||
given with an area override its framing. `resolveViewArguments` turns them
|
||||
into a view.
|
||||
|
||||
### The God's Eye View panel
|
||||
|
||||
@@ -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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
@@ -270,9 +270,9 @@ a feature supplying only its accent.
|
||||
the tracked-entity fade is measured once the frame is in the document;
|
||||
the styles it injected now live in `src/ui/styles/annotations.css`), the
|
||||
visible map target list (the scene chrome renders the buttons and status
|
||||
the world overlay presents through `subscribeWorldOverlayActions()`, which
|
||||
the world overlay presents through `subscribeActions()`, which
|
||||
the application shell publishes to the `gev-world-overlay-actions` store
|
||||
and answers with `activateWorldOverlayAction()`; the module needs no Vue
|
||||
and answers with `activateAction()`; the module needs no Vue
|
||||
for the portable transit entry, and its card and detection canvases stay
|
||||
painting surfaces it draws on, like the Cesium canvas), the Global
|
||||
Context compass and contact bearings (`AwarenessDirection` teleports into
|
||||
|
||||
@@ -4,7 +4,7 @@ Catalog: `src/i18n/locales/zh-Hant.json`. General rules: [TRANSLATORS.md](../TRA
|
||||
|
||||
`zh-Hant` is the Simplified Chinese catalog (`zh-CN`, credited to abiyeenzo, #914, and EDIT-Aeterna, #979) written in Traditional characters with OpenCC character conversion. Wording and terminology are the same as in `zh-CN`: change a term in [zh-CN.md](./zh-CN.md) and the `zh-CN` catalog first, then convert. Only the script differs.
|
||||
|
||||
After `s2tw` conversion, fix the characters OpenCC picks wrongly: 年曆史 → 年歷史, 下遊 → 下游, 並道 → 併道, 發佈 → 發布, 公佈 → 公布, 控制檯 → 控制台, and 周 → 週 where it means a week (1 週/秒).
|
||||
After `s2tw` conversion, fix the characters OpenCC picks wrongly: 年曆史 → 年歷史, 下遊 → 下游, 並道 → 併道, 發佈 → 發布, 公佈 → 公布.
|
||||
|
||||
## Style
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
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
|
||||
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,delete,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,zoom_in_map" 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,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" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="gev-view" data-gev-view="scene-chrome"></div>
|
||||
|
||||
Generated
+1113
-19
File diff suppressed because it is too large.
Load diff
+11
-17
@@ -38,10 +38,6 @@
|
||||
"i18n": "node scripts/i18n.mjs",
|
||||
"layer-token:next": "node scripts/next-layer-state-token.mjs",
|
||||
"layer-token:check": "node scripts/check-layer-state-tokens.mjs",
|
||||
"library:check": "node scripts/library.mjs check",
|
||||
"library:next": "node scripts/library.mjs next",
|
||||
"library:docs": "node scripts/library.mjs docs",
|
||||
"library:probe": "node scripts/library.mjs probe",
|
||||
"changelog": "node scripts/assemble-changelog.mjs --write",
|
||||
"changelog:check": "node scripts/assemble-changelog.mjs --check",
|
||||
"dev": "vite",
|
||||
@@ -58,24 +54,28 @@
|
||||
"qa:street-level:fixtures": "node scripts/qa-street-level.mjs --fixtures",
|
||||
"check:boundaries": "node scripts/check-import-directions.mjs && node scripts/check-package-boundaries.mjs",
|
||||
"qa:transit": "node scripts/qa-transit.mjs",
|
||||
"qa:data-library": "node scripts/qa-data-library.mjs",
|
||||
"mcp": "node server/mcp/stdio.js"
|
||||
"mcp": "node server/mcp/stdio.js",
|
||||
"check:main-chunk": "node scripts/check-main-chunk.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jtarrio/signals": "^0.10.0",
|
||||
"@jtarrio/webrtlsdr": "^3.0.6",
|
||||
"@mapbox/vector-tile": "^3.0.0",
|
||||
"@meri-imperiumi/eccodes-wasm": "^2.48.2",
|
||||
"@navaramap/three": "0.2.1",
|
||||
"@navaramap/three-default-plugin": "0.2.1",
|
||||
"cesium": "^1.124.0",
|
||||
"earcut": "^3.0.2",
|
||||
"egm96-universal": "^1.1.1",
|
||||
"fzstd": "0.1.1",
|
||||
"hls.js": "^1.7.3",
|
||||
"hyparquet": "1.31.2",
|
||||
"kdbush": "4.0.2",
|
||||
"mapillary-js": "^4.1.2",
|
||||
"mgrs": "^2.1.0",
|
||||
"pbf": "^5.1.2",
|
||||
"pinia": "^2.3.1",
|
||||
"postprocessing": "6.39.4",
|
||||
"satellite.js": "^6.0.2",
|
||||
"three": "0.185.1",
|
||||
"vue": "^3.5.44",
|
||||
"vue-i18n": "^11.4.15"
|
||||
},
|
||||
@@ -97,9 +97,8 @@
|
||||
"./infrastructure/geojson": "./src/data/localGeojsonCore.js",
|
||||
"./infrastructure/lod": "./src/data/localGeojsonLod.js",
|
||||
"./application": "./src/app/application.js",
|
||||
"./application/viewer": "./src/app/viewer.js",
|
||||
"./application/viewer": "./src/globe/cesium/viewer.js",
|
||||
"./time": "./src/time/worldClock.js",
|
||||
"./i18n": "./src/i18n/page.js",
|
||||
"./build/vite": {
|
||||
"node": "./build/vite.js"
|
||||
},
|
||||
@@ -212,8 +211,8 @@
|
||||
"./geography/neighborhoods": "./src/data/neighborhoodPolygons.js",
|
||||
"./maps/controller": "./src/maps/controller.js",
|
||||
"./maps/catalog": "./src/maps/catalog.js",
|
||||
"./maps/imagery": "./src/maps/imagery.js",
|
||||
"./maps/terrain": "./src/maps/terrain.js",
|
||||
"./maps/imagery": "./src/globe/cesium/mapImagery.js",
|
||||
"./maps/terrain": "./src/globe/cesium/terrain.js",
|
||||
"./maps/3d": "./src/maps/google3d.js",
|
||||
"./maps/defaults": "./src/maps/defaultSources.js",
|
||||
"./voice/controller": "./src/voice/realtimeController.js",
|
||||
@@ -293,11 +292,6 @@
|
||||
"./layers/transit/standalone": "./src/data/transit.js",
|
||||
"./layers/transit/source": "./src/layers/transit/source.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",
|
||||
"./server/providers/mapillary": {
|
||||
"node": "./server/providers/mapillary.js"
|
||||
|
||||
+17
-30
@@ -1,33 +1,20 @@
|
||||
# Bundled 3D Models
|
||||
# Bundled 3D Model Attribution
|
||||
|
||||
Every 3D model shipped in this directory is one of the project's procedural
|
||||
fleet models under [`fleet/`](fleet/): aircraft, helicopters, ships, buoys,
|
||||
transit and rail vehicles, and a few other airborne and ground objects. They
|
||||
are generated from code by the project maintainers, contain no third-party
|
||||
artwork, and are covered by the repository's [MIT license](../../LICENSE)
|
||||
like the source code.
|
||||
The model files in this directory are third-party visual assets. They are not
|
||||
covered by the repository's MIT source-code license; each remains available
|
||||
under the license listed below.
|
||||
|
||||
| Path | What it holds |
|
||||
|---|---|
|
||||
| `fleet/catalog.json` | Every model by name: its file, category, built length, beam and bounds (metres, in Cesium's Z-up model frame: x along the length with the nose toward −X, y across the beam, z up), the clearance below its origin, and its 2D icon class. |
|
||||
| `fleet/types.json` | Which model draws which vehicle, and at what length: ICAO aircraft and helicopter type codes, ADS-B emitter categories, AIS ship-type and length ranges, GTFS route types, transit modes and rail categories, with the defaults. |
|
||||
| `fleet/aircraft/`, `fleet/helicopters/`, `fleet/ships/`, `fleet/buoys/`, `fleet/transit/`, `fleet/other/` | The models, one GLB each (EXT_meshopt_compression and KHR_mesh_quantization, vertex colours, no textures). |
|
||||
| File | Original work and creator | Source | License | Project modifications |
|
||||
|---|---|---|---|---|
|
||||
| `airplane.glb` | “boeing 747” by [zairiq-123](https://sketchfab.com/zairiq-123) | [Sketchfab model](https://sketchfab.com/3d-models/boeing-747-9b16672038ba48f98e6d80a159044ed9) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Substantially modified and optimized for God's Eye View, including geometry/material simplification and coordinate/orientation preparation. The former 24× runtime calibration is baked into the mesh; location, rotation, and scale are applied; the bounding-box centre is at the origin; and the model uses glTF +Y-up with its nose toward local −X. Geometry remains uncompressed so the aircraft asset does not compete with photogrammetry for Draco worker capacity. |
|
||||
| `jet.glb` | “Private Jet” by [Nick the Name](https://sketchfab.com/Nick_The_Name) | [Sketchfab model](https://sketchfab.com/3d-models/private-jet-cbdd1de6ced9461e950eafaa302cc82b) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Repackaged as a glTF binary for the project's military-flight visualization. Imported hierarchy transforms are baked into the meshes; location, rotation, and scale are applied; the bounding-box centre is at the origin; and the model uses meter scale, glTF +Y-up, and the shared nose −X convention. Existing materials are preserved. |
|
||||
| `bell206.glb` | “Bell 206 JetRanger” by [terran4627](https://sketchfab.com/terran4627) | [Sketchfab model](https://sketchfab.com/3d-models/bell-206-jetranger-d2f7ba1d671549d4b26aaf834139a1dd) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
|
||||
| `c172.glb` | “Cessna 172” by [e737](https://sketchfab.com/e0057537) | [Sketchfab model](https://sketchfab.com/3d-models/cessna-172-64cddaee5aff470682659a8c08525046) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
|
||||
| `citation2.glb` | “1990 Cessna Citation, Texture Detailed, Exterior” by [BlenderCommunityHead](https://sketchfab.com/aboodgoudagad) | [Sketchfab model](https://sketchfab.com/3d-models/1990-cessna-citation-texture-detailed-exterior-a78839624fe64900a8352cb23462350a) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
|
||||
| `mq9.glb` | “MQ-9” by [IProZenoN](https://sketchfab.com/IProZenoN) | [Sketchfab model](https://sketchfab.com/3d-models/mq-9-fabe963feb354c5584b51f9c470c3f7e) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
|
||||
| `b789.glb` | “Boeing 787-9” by [Nobilis 2](https://sketchfab.com/nobilishornet2) | [Sketchfab model](https://sketchfab.com/3d-models/boeing-787-9-b6711e2e698e4e469675c1154a50b7a3) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
|
||||
| `atr72.glb` | “ATR 72 - 600” by [Oyan3D](https://sketchfab.com/oyan3D) | [Sketchfab model](https://sketchfab.com/3d-models/atr-72-600-1e1a7186f7444d288675262fcee44744) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures removed with dominant material colors baked into PBR factors (flat “abstracted” style), orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
|
||||
|
||||
How the layers use them:
|
||||
|
||||
- Each layer draws a vehicle with the model its type selects, scaled to the
|
||||
type's published length (a vessel to its reported AIS length and beam), so
|
||||
every model is drawn life size. Ground objects, balloons and obstacles in
|
||||
the `other` category are drawn at the size they are built.
|
||||
- An aircraft model's origin is its axis; it stands that model's scaled
|
||||
clearance above the ground. A ship's origin is its waterline, and a transit
|
||||
or rail vehicle's the road or rail top (a monorail or gondola is raised by
|
||||
its `transit.elevation` entry, for a source that names those modes).
|
||||
- Transit and rail models carry a `_TINT` vertex attribute that marks where
|
||||
the route or category colour is painted.
|
||||
- The whole set loads once, up front, when a layer that draws it starts; no
|
||||
model is fetched while a session runs.
|
||||
|
||||
`src/data/fleetModels.js` reads the set and chooses each vehicle's model;
|
||||
`src/data/fleetModels.test.mjs` checks that every model `types.json` names is
|
||||
in the catalog and on disk, and that no GLB exceeds 3 MB.
|
||||
CC BY 4.0 permits sharing and adaptation, including commercial use, provided
|
||||
appropriate credit is retained, the license is linked, and modifications are
|
||||
identified. These credits do not imply endorsement by the original creators.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 2058 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user