Commit Graph
1192 Commits
Author SHA1 Message Date
Sameh Khamis 7cb854c923 The timeline marks launches and strong earthquakes, not every weather frame
The timeline drew a tick for every registered mark, and the weather clock
registered every radar, satellite and lightning frame, which come every few
minutes, so the track was a wall of dashes and the arrow keys crept from
frame to frame.

Weather products now register only the span of their frames (start and
end, as before) with no marks. Holds, the gate and the next-frame wait
while the world plays are unchanged. A weather row's step param goes to
the previous or next weather frame (every shown product's frames, and the
row's own while it is suspended) rather than to the timeline's marks.

The Earthquakes layer registers with the world clock once, at init, and
unregisters on destroy. While on, its quakes of magnitude 6 and up
(EARTHQUAKE_MARK_MAGNITUDE) are marks, and its coverage is their own span,
or none without any, so it never stretches the timeline beyond the feed.
Off, it has none. It tells the clock when the set of marks changes. The
map still draws every M2.5+ quake.

Launches stay marks. With no mark ahead or behind, the arrow keys step
along the track as shown.
2026-11-14 20:28:56 -08:00
Sameh Khamis 3cfd414e70 The main timeline is the only time control; the Weather panel shows its frame time
The Weather panel kept a second time transport of its own: a slider, step
buttons, Latest and Play, backed by a replay in the weather clock that
stepped the observed frames every two seconds and went round from the last
to the first. The weather clock also attached itself to the world clock as
a driver, so the timeline's Play became that replay whenever weather was on.

The panel now shows the frame time only: the newest frame and its age while
live, the timeline time alone at a past time. The weather clock no longer
has setTarget, step, latest, play, pause or togglePlay, its state no longer
has playing, and it no longer drives the world clock. It still follows the
world time, registers each product's frames as marks the timeline's arrow
keys step to, holds its products, and gates the world's continuous play at
each next frame until it loads. weatherClock.world names the world clock it
follows.

A weather row's play, latest and step params move the world clock as the
timeline does: play toggles continuous play (from the start of the covered
time when live), latest goes live, and step goes to the previous or next
mark. The rail timeline component, which only the panel used, is removed,
along with its unused messages in every locale.
2026-11-14 20:25:31 -08:00
Manjunath PaparajuandSameh Khamis 1dc5a67518 Bundle Wikidata cities for offline place lookups
A pack of the world's settlements of 15,000 people or more (31,827 places,
1.6 MB, CC0) built from the Wikidata Query Service by
scripts/build-wikidata-cities.mjs. src/data/wikidataCities.js reads it on the
first lookup and answers the nearest city to a point, the cities around it,
a city by name ("Paris, France", or the nearest namesake in view) and a
country's name in the page language. Names match without accents or case.

The build leaves out groupings of settlements (metropolitan areas,
agglomerations) and places that no longer stand, keeps a city that is also a
narrower grouping kind (Istanbul) or a unit dissolved where nothing else
names the spot (the Vietnamese provincial cities of 2025), fills a country
from the area a place lies in, splits a population band the service cannot
answer in time, and fails if well-known cities are missing or a known
grouping gets in. The app never calls Wikidata itself.

Co-authored-by: Sameh Khamis <[email protected]>
2026-11-14 19:27:46 -08:00
Sameh KhamisandManjunath Paparaju a375ace8eb Test the Data Layers finder and section index
Co-authored-by: Manjunath Paparaju <[email protected]>
2026-11-14 16:25:32 -08:00
Sameh KhamisandManjunath Paparaju 355fbe0bb0 Load the finder and section index styles, and own their files
Co-authored-by: Manjunath Paparaju <[email protected]>
2026-11-14 16:16:24 -08:00
Sameh KhamisandManjunath Paparaju d157198ecb Load the cockpit panel styles with the shell, formatted
Co-authored-by: Manjunath Paparaju <[email protected]>
2026-11-14 15:51:13 -08:00
Sameh KhamisandManjunath Paparaju 493e76401e Own the Visual panel's new files in the package boundaries and format scope
Co-authored-by: Manjunath Paparaju <[email protected]>
2026-11-14 15:31:33 -08:00
Sameh Khamis 6f830a2e70 Let a host application place live contacts
createApplicationCatalog takes an optional placement provider. Its
layer(kind) hands Flights, Military Flights, AIS Vessels, Transit and Rail
a place(id, wallMs), and optionally observe(id, fix) and forget(id). While
live, each layer tells it every fix it accepts and draws a contact where it
places it: position, height (the layer's own when none is given), course
and speed. A null answer, a provider that throws, a kind it leaves out, or
no provider at all keeps the layer's own motion unchanged, and following
the world clock the layers never ask it. A transit vehicle or train it
holds still lets the render loop idle and is asked again each second;
vessels ask for frames only while it places one, and stop when the layer
turns off.

Tests show the layers draw and reconcile exactly as before without a
provider and with one that places nothing, draw where a provider places
them, and idle while it holds them still.
2026-11-14 15:07:25 -08:00
Sameh KhamisandManjunath Paparaju 7578fcc9a1 Own the rail modules in their package boundaries and format scope
Co-authored-by: Manjunath Paparaju <[email protected]>
2026-11-14 14:01:04 -08:00
Sameh Khamis 279aedb2f4 Draw aids to navigation as buoys
A buoy, beacon, RACON, light or IALA mark in the vessel feed was drawn
as a ship. A vessel record whose type names one, or whose MMSI is an
aid-to-navigation station's (99MIDxxxx), is now isAidToNavigation and
vesselModelSpec draws it as the fleet set's buoy of its mark (cardinal
N/E/S/W, port or starboard hand, safe water, isolated danger, special
mark; a red can when the type names no mark), at its built size and
flagged fixed so its model does not turn to a heading. Its icon is a
disc instead of a chevron.

AISStream now subscribes to AidsToNavigationReport by default, and the
server names each report's aid type code (0 to 31) as its kind instead
of reading it as a ship type.
2026-11-14 09:54:01 -08:00
Sameh Khamis 6e655b801d feat(fleet): draw airport ground vehicles by callsign and ground stations as a radio mast
Feeds carry many airport ground vehicles (type ZVEH) and ground stations
(GRND) that were drawn as the generic plane, some at the 10 km airborne
default.

- types.json gains aircraft_callsign rules: a ZVEH callsign starting FIRE is
  a fire truck, PUSH/TUG a pushback tug, DEICER/SQUID/SQB a van, else a car.
  GRND draws a new 11.8 m radio mast (other/radio_mast.glb).
- resolveAircraft and aircraftModelSpec take the callsign; a matched rule
  names the model outright, ahead of the type's class model.
- The layer spec cache keys such types by the rule matched, so it holds at
  most one entry per rule.
- These models (catalog category `other`) draw at their built size, as such
  models already do, and a
  contact with no altitude whose model is one now stands on the ground; the
  feed's own type is kept for such contacts, as no airframe lookup knows it.
- Local ADS-B passes the callsign too.
2026-11-13 21:20:16 -08:00
Sameh Khamis 16d163a886 Merge branch 'main' into denis/cockpit-subject-providers 2026-11-13 18:58:00 -08:00
Sameh Khamis 53cbd55181 Merge branch 'main' into fix/cctv-panel-collapse-on-disable 2026-11-13 18:47:22 -08:00
Sameh Khamis c43bf103d6 Merge branch 'main' into german/map-render-error-state 2026-11-13 18:37:03 -08:00
Said Turksever f29b0b2639 Keep the CCTV panel through Context isolation, and collapse through the panel chrome
Contacts and Space Missions switch CCTV off to isolate themselves and restore
it on the way out: the panel and its share preference now stay as they are for
that round trip, as they do for Clear selected data layers. The controls no
longer read the collapsed class before asking: the panel chrome tells a panel
the accordion folded from one whose saved state is closed.

The camera controls gate asserts the collapse, reopens CCTV from the right
rail before switching it back on, and runs a Contacts entry and exit with the
panel open.
2026-11-13 17:46:43 +00:00
German Khokhlov 73bd952736 Share one render loop fixture across the render failure tests
Four test files built the same widget on CesiumWidget.prototype, viewer
proxy, store and notice; renderLoopFixture.mjs now does it once. The notice
test drives Cesium.Event instead of a hand-made double. frameEvents() drops
tileset.tileLoad, which Cesium raises inside its own error handling, and the
notice no longer guards a viewer without a canvas or requestRender.
2026-11-13 20:16:08 +04:00
Denis Groshev 974023510f fix(rail, transit): a train or vehicle a first-person view holds draws no 3D model either, since the view's eye is inside it; let go, its model draws again
The fleet models (#1714) draw nearby trains and vehicles under the 3D toggle. A
held one now leaves out its model as it does its icon, as the Vessels layer does
for its bridge subject: its model item is hidden, and handing the visual back
to its icon keeps the icon hidden while held. Tests drive each layer with the
real fleet-model layer and a stubbed glTF loader; both fail without the fix.
2026-11-13 16:43:50 +01:00
Denis Groshev 546ac22708 Merge branch 'main' into denis/cockpit-subject-providers 2026-11-13 16:38:13 +01:00
Sameh Khamis 7b93045895 feat(vessels, transit, rail): draw fleet models under the 3D toggle
Vessels, Transit and Rail turn nearby icons into fleet models at the
aircraft layers' distances. A vessel is drawn at its AIS length and beam
(static dimensions now reach vessel rows), standing at the sea surface; a
transit vehicle or train stands on its road or rail, raised by its type's
elevation, and is painted in its mode or category colour where the model
marks it.
2026-11-13 07:16:25 -08:00
Sameh Khamis fcc44d0eaf feat(flights): aircraft draw life-size fleet models chosen by type
Flights, Military Flights and Local ADS-B draw each aircraft with the fleet
model its ICAO type, class or emitter category selects, scaled to the type's
length, built from the up-front loaded bytes. The eight third-party aircraft
models and their attribution are removed; every shipped model is now the
project's own.
2026-11-13 07:16:25 -08:00
Sameh Khamis f6760ad96c feat(models): the project's procedural fleet models and the resolver that picks one per vehicle
121 GLBs under public/models/fleet (about 2 MB, MIT like the code) with
catalog.json and types.json. src/data/fleetModels.js picks a model by ICAO
type, aircraft class, emitter category, AIS ship type and size, GTFS route
type, transit mode or rail category, and sizes it life size; the set loads
once, up front. Trail anchors are measured from the meshes.
2026-11-13 07:16:25 -08:00
German Khokhlov d42b6fe764 Merge branch 'main' into german/map-render-error-state 2026-11-13 18:47:36 +04:00
Denis Groshev 22f43aab01 refactor(cockpit): a provider words its text with getLocale and onLocaleChange from the ./i18n entry; the ./i18n/locale export, which gave only those two, is removed 2026-11-13 07:39:27 +01:00
Denis Groshev a7d722a1f4 Merge branch 'main' into denis/cockpit-subject-providers 2026-11-13 07:26:22 +01:00
Sameh Khamis 5f96009b67 feat(i18n): an ./i18n entry for an application with its own language choice
startPageLocale, followPageLocale and switchLocale start, follow and switch
the page's language as the standalone application and the globe picker do,
for an application that offers the language elsewhere and passes
localePicker: false. The standalone application uses them.
2026-11-12 22:07:13 -08:00
Sameh Khamis 347e77d1f6 feat(vessels): a selected vessel shows its Open Vessel Photos photo and MMSI
Selecting a vessel asks Open Vessel Photos (a free, keyless API) for its
photo by MMSI, once per MMSI. The CONTACTS view shows it under the vessel's
name and MMSI with the photographer, the photo's open licence and a link to
the vessel's page; only images on Open Vessel Photos' own host are shown, and
an answer about another MMSI is no photo. The vessel source exposes the lookup
as getEnrichment({kind: 'photo'}) and fetchOpenVesselPhoto for applications
with their own vessel source.
2026-11-12 20:10:30 -08:00
Sameh Khamis 1a657e2a64 Keep the library layer namespace in the layer lifecycle package
The core layer lifecycle imported the whole Data Library manifest
contract only to check the lib- prefix, so the layer-lifecycle package
owned a feature module. The namespace rule (prefix, entry id pattern,
the two id helpers) now lives in src/data/libraryLayerIds.js, which the
lifecycle owns; the manifest contract imports and re-exports it, so
library callers are unchanged.
2026-11-12 16:04:55 -08:00
Said Turksever 1a2589d4ef Port the Data Library Browse dialog to Vue and Pinia
The dialog is now DataLibraryDialog.vue (with DataLibraryCard.vue for each
entry), placed with the Browse button by DataLibraryBrowse.vue in
LayerPanelsView and teleported to the body. It renders what the
gev-data-library store holds and sends clicks and typing to the owner.

src/ui/libraryBrowser.js is now the framework-free owner: it presents the
open state, filters, the rows they show, the stat summary and Remove all
through `publish`, and its controls answer the view. Its text helpers are
what the view renders with t(), so every label follows the locale without
rebuilding anything. Only showModal/close and focus moves stay imperative;
focus returns to Browse also when the browser closes the dialog on Escape
itself.

Component tests mount the view over the real owner (open, filters, Add to
Remove keeping focus, Zoom, Remove all, a language switch, clean view).
2026-11-12 23:01:00 +00:00
Said Turksever 24ccdd4dc3 Fix Data Library share restore, dense tile layers and EPC partition reads
- Share links: the compact /share/<code> format carries the switched-on
  library entries (field 29), so Copy Link keeps them; a share opened in the
  same tab restores them; any share decides what is on, so one with every
  library entry off no longer switches on the recipient's stored entries.
- A tile entry too dense at its minimum zoom (Norway cadastre z14, depth z11)
  is hidden below that scale via minimumTerrainLevel instead of allocating
  millions of tiles when zoomed out.
- EPC partitions keep the full extent for exclusion and the 98 % extent for
  ranking only; the old trimmed-only extents are retired.
- Partition index footers that fail transiently are retried; an incomplete
  index is never cached as complete, a complete prior index is kept, and it
  is rebuilt after five minutes.
- Views that leave touching partitions unread are partial; listing and
  footer bodies are capped and ranges validated.
- The Browse dialog relabels after a language switch.
2026-11-12 22:37:34 +00:00
Said Turksever 871b7f0fcc Remove Data Library entries whose licence or provider links are broken
A link check of every manifest found 404 licence or provider pages for
OpenSeaMap seamarks, EMODnet bathymetry and the PDOK, basemap.at, Geneva
SITG, ICGC Catalunya, Luxembourg and Vlaanderen aerial photos, and no
answer from the SEDAC GPW, Minnesota and Bangor hosts. Drop those 11
entries, renumber the unpublished share-token ledger, point the tests at
remaining entries and regenerate DATA_SOURCES.md.
2026-11-12 17:05:21 +00:00
Said TurkseverandMaher Kurdi 11706a68cb Add Amsterdam traffic and ANPR camera positions to the Data Library
The City of Amsterdam's verkeersinformatiesystemen register lists where
its ~500 traffic and ANPR cameras stand, with type and asset number, but
publishes no pictures. That fits a Data Library dataset better than a
CCTV pack: the register answers GeoJSON in WGS84 with CORS headers, so
the entry fetches it browser-direct with no proxy code.

The register names no licence ("public unless stated otherwise"), so the
entry stays operator opt-in, as #251 had it. The dataset and the
position-only camera idea come from @Maher-Reven's #251, which this
replaces; the entry lists them as maintainer and in its attribution.

Co-authored-by: Maher Kurdi <[email protected]>
2026-11-12 12:26:45 +00:00
Said Turksever db80d82b8d Add the Data Library: live regional datasets, tiles and packs
DATA LAYERS keeps its curated core layers; regional and niche sources
live in the Data Library, opened from Browse data library at the foot
of the panel. Each entry is a JSON manifest in src/library/entries/
pointing at a live upstream (query API, provider-hosted file or tile
endpoint), so nothing is bundled and a self-hosted instance hosts no
data. Sources without CORS go through one allowlisted /api/library
route. Kinds: dataset (GeoJSON, CSV, JSON records, ArcGIS, partitioned
GeoParquet), tiles (XYZ, WMS/ArcGIS bbox export, terrarium relief) and
pack. Selections persist and travel in share links as lib=, separate
from the core layer tokens. Library rows get Zoom and Remove icon
chips that work while the layer is off, rendered through RailChips.
Strings are translated in every locale.
2026-11-12 12:26:45 +00:00
Denis Groshev f75179eea0 feat(cockpit): an application can add its own cockpit subjects; the Rail and Transit layers expose the read and hold APIs such a subject needs
GEV itself registers no new subject: with no provider, the Cockpit offers
aircraft, satellites and vessels as before, except that the most recently
selected of them now wins entry.

- services.cockpitSubjects: providers ({kind, layerId, entryFace, priority,
  isEntryAllowed, read, hold, release, hud}) are checked once by the
  coordinator; frames and HUD views on every update; a throwing provider
  ends its view or keeps the last HUD.
- Entry: the most recently selected subject wins, across all kinds.
- Camera: a frame may turn the ground clamp off, anchor exactly on the
  subject and update every frame.
- A generic HUD: kicker, title, cards, always-visible source rows, banner,
  a veil with light streaks (off under reduced motion) and a bottom
  progress line whose close ticks merge.
- Text comes from the provider, already in the page's language
  (./i18n/locale: getLocale, onLocaleChange).
- Rail and Transit read and hold APIs (getSelectedTrain/Vehicle,
  getCockpitSubject, setCockpitSubject, focusTrain/Vehicle); a held
  train's marker, card and route line are hidden, and its feed keeps
  polling while held.
- ./layers/rail/paths is exported.
2026-11-12 10:50:26 +01:00
Denis Groshev 13818d0b9f Merge upstream main into denis/rail-layer 2026-11-11 18:02:52 +01:00
German Khokhlov 897a74ea27 Merge remote-tracking branch 'origin/main' into german/map-render-error-state
# Conflicts:
#	src/i18n/locales/ar.json
#	src/i18n/locales/de.json
#	src/i18n/locales/es.json
#	src/i18n/locales/fr.json
#	src/i18n/locales/he.json
#	src/i18n/locales/it.json
#	src/i18n/locales/ja.json
#	src/i18n/locales/pt.json
#	src/i18n/locales/ru.json
#	src/i18n/locales/uk.json
#	src/i18n/locales/zh-CN.json
#	src/i18n/locales/zh-Hant.json
2026-11-11 19:33:49 +04:00
Denis Groshev 830c080803 feat(rail): add a Rail layer with keyless live trains
Live trains from 30 keyless operator feeds: GPS positions from 29 and
Irish Rail's station reports, shown as the operators publish them. 28
work out of the box; Renfe long and medium distance and KTMB appear once
their pack is built, because their vehicles name no route.

Packs are opt-in (scripts/build-rail-paths.mjs): the operator's own
route lines, stop names, next stops and trip-update delays for live
trains. Nothing downloads when the app starts, no train is placed from a
timetable, and no request carries a deployer's key.
2026-11-11 16:06:22 +01:00
Said Turksever 43893697aa Merge upstream main into pr/street-level-show-action 2026-11-11 12:15:18 +00:00
German Khokhlov 45d9ff1d86 Merge branch 'main' into sv-locale
Translate the 148 strings main added since (mostly the News layer) and
prune the 5 it no longer uses; keep both Hebrew and Swedish in the
language lists.
2026-11-11 13:59:40 +04:00
Said Turksever f878efe22d Merge commit '0ceee8df40b97c6c693e86a4443794cd4ed1ff35' into HEAD 2026-11-11 08:54:28 +00:00
German Khokhlov 282ad6c5f6 Merge remote-tracking branch 'origin/main' into german/map-render-error-state
# Conflicts:
#	src/i18n/locales/ar.json
#	src/i18n/locales/de.json
#	src/i18n/locales/es.json
#	src/i18n/locales/fr.json
#	src/i18n/locales/it.json
#	src/i18n/locales/ja.json
#	src/i18n/locales/pt.json
#	src/i18n/locales/ru.json
#	src/i18n/locales/uk.json
#	src/i18n/locales/zh-CN.json
#	src/i18n/locales/zh-Hant.json
2026-11-11 12:45:05 +04:00
German Khokhlov ebaa439739 Tighten the render failure notice's error handling and focus
- An error a frame of the inline panel's timer throws no longer reaches the
  host page; it has been raised as a render error by then.
- An error Scene raised and threw on (rethrowRenderErrors) is raised once.
- After a lost context, later render errors are only logged, so the notice
  keeps saying why.
- A render error without a value shows no detail.
- The globe's tile load progress and the scene's tileset load events are
  finished too when a listener's error cuts them short.
- Focus goes back where it was when the globe draws again, or to the map.
- The appearance counter and the guard for a missing viewer are gone.

Tests cover every finished event, a thrown timer frame, a single report under
rethrowRenderErrors and the focus return; the inline tests share one setup.
2026-11-11 12:44:08 +04:00
Sameh Khamis 57ff81f723 feat(live): flights, military and vessels follow the world clock when their source declares a timeline
A live source may now declare a timeline: it can answer a snapshot at a
given time (source.timeline = { coverage(), stepMs, subscribe? }, read by
readSnapshotTimeline). While such a layer is on it follows the world clock
and registers the coverage. At the live edge it polls exactly as before;
away from it the layer asks for getSnapshot({ ...query, atMs }) at the step
at or before the clock time and shows that snapshot instead of polling.

src/time/snapshotTimeline.js owns the following: a seek, time going
backwards or leaving live replaces every contact with the snapshot at the
new time (play waits on it, for at most 8 s); playing on keeps one
snapshot ahead and gates play on it, applying those snapshots as polls so
contacts move smoothly; outside coverage the layer is empty; back at live
it is cleared and polls at once. One load at a time, and a load the
source drops for a newer one is not a failure.

While following, aircraft are drawn at the clock time with no render
delay, records and absences are judged at the clock, track requests carry
atMs, and vessels move between fixes along the great circle. The world
clock counts jumps (seeks and returns to live) in getState().

A source with no timeline never follows the clock, and every layer stays
live, unchanged.
2026-11-10 18:12:53 -08:00
Sameh Khamis 49edc31879 Merge branch 'main' into feat/viewport-context-decision-upstream 2026-11-10 15:54:49 -08:00
Sameh Khamis 100369f9af Merge remote-tracking branch 'origin/main' into feat/viewport-context-decision-upstream 2026-11-10 15:44:17 -08:00
Sameh Khamis 630e1b1998 Hide street-vehicle brackets and labels behind buildings and terrain
The detection overlay only hid a contact behind the Earth's curve, so a
street vehicle behind or under a building, a 3D tile or a hill still had
its bracket and "VEH-nnnn" callout painted over that geometry, while its
own dot was depth-tested and correctly hidden.

A contact a layer marks `depthTested: true` (only Street Traffic does) is
now tested against the depth the scene drew and left out of the overlay
while a drawn surface stands in front of it by more than max(3 m, 2% of
its range). Other layers are untouched: their markers are drawn over the
scene on purpose.

The test runs on the GPU and never waits for it. Each tested contact is
one point drawn inside an occlusion query; the vertex shader looks up the
frame's packed pick depth at the contact's pixel, decodes it and drops the
point when the surface is nearer than the contact's threshold. Query
results are collected on later frames. Reading depth back was measured
and rejected: `scene.pickPosition` per contact costs 0.2 to 1.4 ms each,
and Chromium answers `getBufferSubData` with a round trip to its GPU
process even behind a signalled sync object (1.5 ms on a quiet machine,
15 to 25 ms on a busy one).

- Contacts are retested every 100 to 200 ms, spread across frames, at
  most 256 a frame; with more in view the pace stretches so none starves.
- Hiding is immediate. A hidden contact returns 600 ms after its last
  "hidden" answer, so one on a silhouette edge does not blink, and a
  contact new to the view is held back until its first answer (400 ms at
  most), so orbiting does not flash brackets behind buildings.
- A callout whose contact goes behind something is dropped at once, not
  carried to the next solve and not faded out on the facade.
- These times run on painted frames, so a hidden tab does not expire them.
- A control point in every batch must draw; if it does not, the batch is
  ignored and after three the probe stands down for ten seconds. With no
  pick depth (2D, Columbus view, depth picking off) nothing is hidden.
  A WebGL 1 context reads depth back synchronously about 8 times a second.

The renderer internals this depends on are read in one function,
`readSceneDepthSource`, with the engine version they were checked against.
2026-11-10 15:08:26 -08:00
Sameh Khamis 8ff22f1a12 Merge main into feat/shipwrecks
Main added the News layer at the same places: both layers are kept in the
layer names, panel order (News stays last of Events), standalone sources,
voice manifest, credits, DATA_SOURCES and the CURRENT-STATE table. News took
share-link token 00 first, so Shipwrecks moves to 01 (ledger, registry,
CURRENT-STATE and the token tests). The catalog now has 40 layers and the
share-link registry 38. Shipwreck strings are added to the new Hebrew
catalog; the other catalogs keep both sides' strings.
2026-11-10 14:03:39 -08:00
Sameh Khamis 41641cd91a fix(shipwrecks): the page clears shown wrecks as a whole, once even the newest source is too old
Removing one source's wrecks on the page split merged records: a wreck both
sources list is one Wikidata record carrying EMODnet facts, so expiring
Wikidata dropped wrecks EMODnet still had, and expiring EMODnet kept its
facts. The page now keeps every shown wreck, marked stale, until the newest
source passes the limit, then clears them all. Adds a source test for the
reported ages and limit.
2026-11-10 13:40:40 -08:00
Mazeyar Moeini Feizabadi 8f489d203f Merge branch 'main' into denis/gev-news-layer 2026-11-10 22:38:56 +01:00
Sameh Khamis 32e5768954 fix(shipwrecks): take the shipwreck modules out of the GhostMaps package lists
They were listed as owned by the ghostmaps and ghostmaps-source packages,
which they are not part of; ghostmaps-source is meant to stay free of
rendering code, and shipwrecks/index.js imports Cesium. The app groups that
own every layer keep them.
2026-11-10 13:26:23 -08:00
Sameh Khamis 38ba8a3b59 perf(shipwrecks): draw the wreck markers from one billboard collection, on the wreck, and rebuild them only on new data
- The ~8,000 markers were one Cesium entity each; they are now one
  BillboardCollection (as FIRMS, vessels and wildlife sightings do), ordered
  at the bottom of the shared sprite stack so moving traffic draws over it.
- eyeOffset.z was the death toll in metres, so a 1,500-death wreck's marker
  sat 1.5 km toward the camera and clipped or drifted when close. It is gone;
  markers are added by rising toll, so a bigger toll still draws on top.
- The page asks every 6 h but the proxy changes data at most daily: the proxy
  now sends a version that changes with either source's data, and the layer
  rebuilds only when it does.
- Adds a controller test: enable/disable, teardown, draw order, unchanged
  snapshots, a late response after turning off, and the period closing an
  open card.
2026-11-10 13:25:03 -08:00