Merge pull request #1321 from bilawalsidhu/chore/dead-code-cleanup

chore: remove dead code across the repository
This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-10-22 20:54:09 -07:00
commit 224745bd0a
195 files changed
+256 -773

No files matched your search

+1 -1
View File
@@ -116,7 +116,7 @@ The highest-leverage places to jump in:
## Architecture in one minute ## Architecture in one minute
- **No framework.** Vanilla JS + [CesiumJS](https://cesium.com/platform/cesiumjs/) + [Vite](https://vitejs.dev/). - **No framework.** Vanilla JS + [CesiumJS](https://cesium.com/platform/cesiumjs/) + [Vite](https://vitejs.dev/).
- **Assembly lives in `src/app/`; standalone defaults live in `src/standalone/`.** UI controllers live in `src/ui/`, layer factories in `src/layers/`, portable sources in `src/sources/`, and application operations in `src/services/`. Existing `src/ui.js` and `src/data/<layer>.js` entries retain compatibility; new code belongs with its focused owner. - **Assembly lives in `src/app/`; standalone defaults live in `src/standalone/`.** UI controllers live in `src/ui/`, layer factories in `src/layers/`, portable sources in `src/sources/`, and application operations in `src/services/`. Existing `src/data/<layer>.js` entries retain compatibility; new code belongs with its focused owner.
- Sources acquire records; renderers own Cesium resources. Import `gods-eye-view/layers/<family>/source` when only a source factory is needed. Common voice controls consume the session interface; protocol adapters own connection details. - Sources acquire records; renderers own Cesium resources. Import `gods-eye-view/layers/<family>/source` when only a source factory is needed. Common voice controls consume the session interface; protocol adapters own connection details.
- **Secrets stay server-side.** Anything needing a private key goes through a local proxy under `server/providers/`. The browser only ever sees the Google Maps key (which you restrict) and ephemeral tokens. - **Secrets stay server-side.** Anything needing a private key goes through a local proxy under `server/providers/`. The browser only ever sees the Google Maps key (which you restrict) and ephemeral tokens.
- `docs/CURRENT-STATE.md` is the authoritative runtime reference — read it first. - `docs/CURRENT-STATE.md` is the authoritative runtime reference — read it first.
+1 -1
View File
@@ -488,7 +488,7 @@ How the globe handles live data:
``` ```
src/ src/
├── main.js # Bootstrap: Google 3D tiles, layer registration ├── main.js # Bootstrap: Google 3D tiles, layer registration
├── ui.js # Runtime UI — panels, HUD, styles, control facade ├── ui/ # Runtime UI — panels, HUD, styles, controls
├── hud.js # Intelligence HUD + AI scene summary ├── hud.js # Intelligence HUD + AI scene summary
├── keySetup.js # POWER UP panel — in-app provider keys (dev server only) ├── keySetup.js # POWER UP panel — in-app provider keys (dev server only)
├── mapStackController.js # Basemap switching — Google 3D / Esri / OSM / ion stacks ├── mapStackController.js # Basemap switching — Google 3D / Esri / OSM / ion stacks
+6
View File
@@ -0,0 +1,6 @@
- Remove dead code across the repository: three modules nothing imports
(`src/ui.js`, `src/standalone/ui.js`, `src/data/localLayers.js`), exports
and locals nothing reads, a duplicate copy of the bundled dams data and an
unused ship model. The app no longer builds an unused default wind layer
and Bhote Koshi locator layer when it loads. Package exports are unchanged
(samehkhamis, #1321).
+5 -7
View File
@@ -79,7 +79,6 @@ moves it. Do not label all ingestion modules platform-independent by filename.
| Retained entry | Owner and current reason | | Retained entry | Owner and current reason |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `src/ui.js` | UI facade; direct callers retain standalone StyleManager defaults. Normal assembly uses `src/ui/composition.js` |
| `src/data/manager.js` | Lifecycle/panel compatibility and existing tests; normal assembly constructs lifecycle and presentation separately | | `src/data/manager.js` | Lifecycle/panel compatibility and existing tests; normal assembly constructs lifecycle and presentation separately |
| `src/data/<layer>.js` | Legacy default instances; new assembly calls layer factories with supplied sources | | `src/data/<layer>.js` | Legacy default instances; new assembly calls layer factories with supplied sources |
| `src/data/localGeojson.js` | Infrastructure compatibility factories; catalog imports services from `src/app/localGeojsonServices.js` directly | | `src/data/localGeojson.js` | Infrastructure compatibility factories; catalog imports services from `src/app/localGeojsonServices.js` directly |
@@ -87,9 +86,8 @@ moves it. Do not label all ingestion modules platform-independent by filename.
| `src/app/sources.js`, `src/services/application.js` | Existing default/override API; current application construction supplies its own instances | | `src/app/sources.js`, `src/services/application.js` | Existing default/override API; current application construction supplies its own instances |
| `server/providers/local.js` | Existing Node composition barrel and provider helper re-exports | | `server/providers/local.js` | Existing Node composition barrel and provider helper re-exports |
The two exact standalone-import exceptions are `src/ui.js` to The one exact standalone-import exception is `server/providers/local.js` to
`src/standalone/catalog.js` and `server/providers/local.js` to `server/standalone/key-setup.js`. It does not authorize new compatibility
`server/standalone/key-setup.js`. They do not authorize new compatibility
back-edges. Voice actions use plain `src/data/feedState.js`, not the manager back-edges. Voice actions use plain `src/data/feedState.js`, not the manager
facade. Settings filesystem hardening belongs in facade. Settings filesystem hardening belongs in
`server/standalone/key-setup-hardening.mjs`; its settings policy is unchanged. `server/standalone/key-setup-hardening.mjs`; its settings policy is unchanged.
@@ -237,7 +235,7 @@ DOM elements and callbacks; importing it creates no browser state. `destroy()`
removes owned listeners and cancels pending work without changing saved state removes owned listeners and cancels pending work without changing saved state
or moving focus. Call it before removing or replacing the controls. or moving focus. Call it before removing or replacing the controls.
`src/ui.js` retains panel layout, persistence, share restoration and application `src/ui/applicationShell.js` retains panel layout, persistence, share restoration and application
reactions to state changes. Map Source selection and Location draft cleanup are reactions to state changes. Map Source selection and Location draft cleanup are
provided through callbacks. The component imports no globe, data, application provided through callbacks. The component imports no globe, data, application
or server modules. Package checks and scoped formatting cover this entry. or server modules. Package checks and scoped formatting cover this entry.
@@ -470,8 +468,8 @@ release listeners; pending action feedback is limited to its current owner.
## UI assembly and styles ## UI assembly and styles
`ui/shell` assembles controls from supplied existing layer, navigation, terrain, `ui/shell` assembles controls from supplied existing layer, navigation, terrain,
rendering, HUD and share operations. `src/standalone/ui.js` provides the running rendering, HUD and share operations. `src/standalone/controls.js` provides the running
application's instances; `src/ui.js` remains the compatibility entry. The shell application's instances. The shell
imports no standalone bootstrap or concrete live layer implementation. imports no standalone bootstrap or concrete live layer implementation.
Panel layout, position/drag, notices, recording and deferred UI work have separate Panel layout, position/drag, notices, recording and deferred UI work have separate
owners with synchronous cleanup. Existing scene, share and HUD engines retain owners with synchronous cleanup. Existing scene, share and HUD engines retain
+6 -6
View File
@@ -3595,7 +3595,7 @@ Historical planning documents may not match runtime behavior.
## Runtime Stack ## Runtime Stack
- Vite + CesiumJS app with Google Photorealistic 3D Tiles - Vite + CesiumJS app with Google Photorealistic 3D Tiles
- Scene/HUD/style systems in `src/ui.js` and `src/hud.js` - Scene/HUD/style systems in `src/ui/` and `src/hud.js`
- Layer management in `src/data/manager.js` - Layer management in `src/data/manager.js`
- Map stack switching in `src/mapStackController.js` - Map stack switching in `src/mapStackController.js`
- Voice control in `src/voice/` (OpenAI Realtime over WebRTC) - Voice control in `src/voice/` (OpenAI Realtime over WebRTC)
@@ -3627,8 +3627,8 @@ its criteria cannot be silently ignored.
| Transit 🚌 | Operator GTFS-Realtime VehiclePositions (7 keyless regions, `src/data/transitFeeds.js`) | `src/layers/transit/` via `src/app/layers/transit.js` | `/api/transit` | 15s (poll + delayed playback) | | Transit 🚌 | Operator GTFS-Realtime VehiclePositions (7 keyless regions, `src/data/transitFeeds.js`) | `src/layers/transit/` via `src/app/layers/transit.js` | `/api/transit` | 15s (poll + delayed playback) |
| Bikeshare 🚲 | GBFS (Lyft + BCycle + Social Bicycles + PBSC incl. Bike Share Toronto, Bike Itaú (Brazil), Bicing (Barcelona) and seven other Spanish cities + Urban Sharing, Entur, JCDecaux (Lillestrøm, Valencia, Seville) + nextbike (AMBici)) | `src/data/bikeshare.js` | `/api/gbfs` | 60s | | Bikeshare 🚲 | GBFS (Lyft + BCycle + Social Bicycles + PBSC incl. Bike Share Toronto, Bike Itaú (Brazil), Bicing (Barcelona) and seven other Spanish cities + Urban Sharing, Entur, JCDecaux (Lillestrøm, Valencia, Seville) + nextbike (AMBici)) | `src/data/bikeshare.js` | `/api/gbfs` | 60s |
| Directions 🧭 | OSRM on FOSSGIS servers (OpenStreetMap) | `src/data/directions.js` | `/api/route` (`steps=1`) | on placement / mode change | | Directions 🧭 | OSRM on FOSSGIS servers (OpenStreetMap) | `src/data/directions.js` | `/api/route` (`steps=1`) | on placement / mode change |
| Datacenters ▣ | OSM extract (bundled) | `src/data/localLayers.js` | — | static | | Datacenters ▣ | OSM extract (bundled) | `src/data/infrastructure.js` | — | static |
| Dams ▰ | OpenInfraMap/OSM extract (bundled) | `src/data/localLayers.js` | — | static | | Dams ▰ | OpenInfraMap/OSM extract (bundled) | `src/data/infrastructure.js` | — | static |
| Submarine Cables ◠ | TeleGeography public map (bundled) | `src/data/telegeographySubmarineCables.js` | — | static | | Submarine Cables ◠ | TeleGeography public map (bundled) | `src/data/telegeographySubmarineCables.js` | — | static |
| FIRMS Active Fires ▲ | NASA FIRMS live (VIIRS ×3 NRT + MODIS NRT, trailing 24h) | `src/data/firmsHeatmap.js` | `/api/firms` (`FIRMS_MAP_KEY`) | 10 min (proxy TTL 30 min) | | FIRMS Active Fires ▲ | NASA FIRMS live (VIIRS ×3 NRT + MODIS NRT, trailing 24h) | `src/data/firmsHeatmap.js` | `/api/firms` (`FIRMS_MAP_KEY`) | 10 min (proxy TTL 30 min) |
| Street Level 📷 | Mapillary street-level imagery (vector tiles, Graph API, MapillaryJS) | `src/layers/streetLevel/` via `src/app/layers/streetLevel.js` | `/api/mapillary/status`, `/api/mapillary/tiles/coverage/{z}/{x}/{y}` (`MAPILLARY_CLIENT_TOKEN`) | camera-driven (320 ms debounce, ≤9 tiles, server memory cache 24 h) | | Street Level 📷 | Mapillary street-level imagery (vector tiles, Graph API, MapillaryJS) | `src/layers/streetLevel/` via `src/app/layers/streetLevel.js` | `/api/mapillary/status`, `/api/mapillary/tiles/coverage/{z}/{x}/{y}` (`MAPILLARY_CLIENT_TOKEN`) | camera-driven (320 ms debounce, ≤9 tiles, server memory cache 24 h) |
@@ -5164,7 +5164,7 @@ pins and share links already supply complete boxes.
- **Grounded 3D handoff is terrain-validity gated (2026-08-23).** A ready civilian or military glTF does not own the visual until `groundSnap` (`src/data/groundSnap.js`) can answer with a MEASURED photoreal-surface height. On success the layer writes `height + the model's measured belly offset` before revealing the model. Cache movement is measured on the WGS84 surface, not across altitude, so a stationary contact keeps its snap through poll-time vertical-datum changes. Model existence or GPU readiness alone never suppresses the billboard floor, and ordinary zoom/style/deselect transitions preserve a valid snap cache. **Two states, and the difference is the whole design.** COLD — nothing has ever resolved for this icao (tiles still streaming on first sight, sample failure, backoff after a first miss): there is no evidence of where the ground is, `heightFor` returns null, the model stays hidden and the depth-test-free 2D billboard remains opaque and floored. WARM — a snap resolved and then a >`MOVE_INVALIDATE_M` (50 m) taxi move stopped it answering directly: the measurement is DEMOTED to a bounded last-known rather than deleted, and it keeps answering while the resample is outstanding, so a taxiing aircraft does not pop 3D→2D→3D across a 2–30 s retry backoff. The bound is `HELD_SNAP_MAX_DRIFT_M` = 250 m from the spot the value was measured at, past which the hold is dropped rather than stretched and the contact is COLD again. It is spatial with no timer beside it (ground under a contact that has not moved does not change; what invalidates the value is the contact MOVING) and deliberately a quarter of the billboard chain's `HELD_FLOOR_MAX_DRIFT_KM` — that hold only ever RAISES a sprite, while a held snap IS the model's placement, so its error shows in both directions. A fresh sample releases the hold, and so does a ground flip (which already calls `forget`). **A loading model is HIDDEN, never zero-scaled:** admission sets `show = false` (Cesium's default is `true`, and an unplaced primitive would claim the visual at the identity matrix), and ownership is `ready && show` — Cesium 1.138's `Model.update` has no `show` guard, so hiding a primitive costs its load nothing. - **Grounded 3D handoff is terrain-validity gated (2026-08-23).** A ready civilian or military glTF does not own the visual until `groundSnap` (`src/data/groundSnap.js`) can answer with a MEASURED photoreal-surface height. On success the layer writes `height + the model's measured belly offset` before revealing the model. Cache movement is measured on the WGS84 surface, not across altitude, so a stationary contact keeps its snap through poll-time vertical-datum changes. Model existence or GPU readiness alone never suppresses the billboard floor, and ordinary zoom/style/deselect transitions preserve a valid snap cache. **Two states, and the difference is the whole design.** COLD — nothing has ever resolved for this icao (tiles still streaming on first sight, sample failure, backoff after a first miss): there is no evidence of where the ground is, `heightFor` returns null, the model stays hidden and the depth-test-free 2D billboard remains opaque and floored. WARM — a snap resolved and then a >`MOVE_INVALIDATE_M` (50 m) taxi move stopped it answering directly: the measurement is DEMOTED to a bounded last-known rather than deleted, and it keeps answering while the resample is outstanding, so a taxiing aircraft does not pop 3D→2D→3D across a 2–30 s retry backoff. The bound is `HELD_SNAP_MAX_DRIFT_M` = 250 m from the spot the value was measured at, past which the hold is dropped rather than stretched and the contact is COLD again. It is spatial with no timer beside it (ground under a contact that has not moved does not change; what invalidates the value is the contact MOVING) and deliberately a quarter of the billboard chain's `HELD_FLOOR_MAX_DRIFT_KM` — that hold only ever RAISES a sprite, while a held snap IS the model's placement, so its error shows in both directions. A fresh sample releases the hold, and so does a ground flip (which already calls `forget`). **A loading model is HIDDEN, never zero-scaled:** admission sets `show = false` (Cesium's default is `true`, and an unplaced primitive would claim the visual at the identity matrix), and ownership is `ready && show` — Cesium 1.138's `Model.update` has no `show` guard, so hiding a primitive costs its load nothing.
- Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, flat amber or emergency red). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit. - Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, flat amber or emergency red). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit.
- The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged. - The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged.
- **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design. - **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui/aircraftDisplay.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design.
- **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed. - **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed.
- Civilian and military 2D aircraft use the established distance scale: `3×` near - Civilian and military 2D aircraft use the established distance scale: `3×` near
the camera and a `0.5×` floor from 8,000 km outward. A standard 20 px ambient the camera and a `0.5×` floor from 8,000 km outward. A standard 20 px ambient
@@ -5373,7 +5373,7 @@ demand with square-root demand scaling and semantic layer weights
(`labelArbiter.js:181`). First-run default: ELASTIC. (`labelArbiter.js:181`). First-run default: ELASTIC.
**A default has THREE surfaces, and a PARSE fallback that is not one of them.** **A default has THREE surfaces, and a PARSE fallback that is not one of them.**
The value literals (engine constant, markup value, markup readout, `ui.js` The value literals (engine constant, markup value, markup readout, `src/ui/visualPresets.js`
`GLOBAL_POST_DEFAULTS`, the share generator's starting state) must all move `GLOBAL_POST_DEFAULTS`, the share generator's starting state) must all move
together, because a fresh boot runs no restore and those literals ARE the startup together, because a fresh boot runs no restore and those literals ARE the startup
state. The `scf` / `ko` PARSE fallbacks deliberately do NOT move: they answer state. The `scf` / `ko` PARSE fallbacks deliberately do NOT move: they answer
@@ -5421,7 +5421,7 @@ easier to meet (detection is now on more often), but does not create it.
- **Detection OUTSIDE opacity: `1%`** (moved `5% → 3% → 1%` on 2026-08-24). - **Detection OUTSIDE opacity: `1%`** (moved `5% → 3% → 1%` on 2026-08-24).
`KEYHOLE_OUTSIDE_OPACITY_DEFAULT` in `src/celestialRing.js`, mirrored by `KEYHOLE_OUTSIDE_OPACITY_DEFAULT` in `src/celestialRing.js`, mirrored by
`#detection-opacity-slider`'s markup value AND readout, `#detection-opacity-slider`'s markup value AND readout,
`GLOBAL_POST_DEFAULTS.detectionOutsideOpacityPct` in `ui.js`, and `GLOBAL_POST_DEFAULTS.detectionOutsideOpacityPct` in `src/ui/visualPresets.js`, and
`_detectionOutsideOpacityPct` in `sharelink.js`. The slider's `step` is now `_detectionOutsideOpacityPct` in `sharelink.js`. The slider's `step` is now
`1`, so 1–4 % are reachable at all (at the previous step of 5 the entire `1`, so 1–4 % are reachable at all (at the previous step of 5 the entire
sub-default range was one stop wide). `AIRCRAFT_BRACKET_FLOOR_ANCHOR` in sub-default range was one stop wide). `AIRCRAFT_BRACKET_FLOOR_ANCHOR` in
-1
View File
@@ -8,7 +8,6 @@ under the license listed below.
|---|---|---|---|---| |---|---|---|---|---|
| `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. | | `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. | | `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. |
| `ship.glb` | “Low Poly Cargo Ship” by [Javier_Fernandez](https://sketchfab.com/Javier.Fernandez) | [Sketchfab model](https://sketchfab.com/3d-models/low-poly-cargo-ship-4c22cbaf01c1427f8ab60b3a07b1b32c) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized and repackaged as a glTF binary for the project's vessel visualization. |
| `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. | | `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. | | `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. | | `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. |
Binary file not shown.
+3 -4
View File
@@ -24,11 +24,10 @@ const renderer = (file) =>
/^src\/layers\/[^/]+\/(?:index|rendering|snapshotRenderer|overlay|presentation|cards|controls)\.js$/.test( /^src\/layers\/[^/]+\/(?:index|rendering|snapshotRenderer|overlay|presentation|cards|controls)\.js$/.test(
file, file,
); );
// Two compatibility composition entries intentionally select standalone defaults. // One compatibility composition entry intentionally selects standalone defaults.
const compatibilityEdge = (from, to) => const compatibilityEdge = (from, to) =>
(from === 'server/providers/local.js' && from === 'server/providers/local.js' &&
to === 'server/standalone/key-setup.js') || to === 'server/standalone/key-setup.js';
(from === 'src/ui.js' && to === 'src/standalone/catalog.js');
const portableExport = (key) => const portableExport = (key) =>
key.startsWith('./sources/') || key.startsWith('./sources/') ||
/\/source$/.test(key) || /\/source$/.test(key) ||
-2
View File
@@ -285,8 +285,6 @@
"src/ui/sceneControls.test.mjs", "src/ui/sceneControls.test.mjs",
"scripts/qa-scene-controls.mjs", "scripts/qa-scene-controls.mjs",
"scripts/qa-action-buttons.mjs", "scripts/qa-action-buttons.mjs",
"src/ui.js",
"src/standalone/ui.js",
"src/ui/applicationShell.js", "src/ui/applicationShell.js",
"src/ui/panelLayoutController.js", "src/ui/panelLayoutController.js",
"src/ui/panelPositionControls.js", "src/ui/panelPositionControls.js",
-1
View File
@@ -1852,7 +1852,6 @@
"src/data/localGeojson.js", "src/data/localGeojson.js",
"src/data/localGeojsonCore.js", "src/data/localGeojsonCore.js",
"src/data/localGeojsonLod.js", "src/data/localGeojsonLod.js",
"src/data/localLayers.js",
"src/data/local_data/natural_earth/marine.json", "src/data/local_data/natural_earth/marine.json",
"src/data/local_data/natural_earth/regions.json", "src/data/local_data/natural_earth/regions.json",
"src/data/local_data/neighborhoods/san-francisco.json", "src/data/local_data/neighborhoods/san-francisco.json",
+1 -1
View File
@@ -12,7 +12,7 @@
* passed with the painter stubbed out — the number was right and nothing was * passed with the painter stubbed out — the number was right and nothing was
* drawn. Every assertion here now comes from the RENDERED path instead: * drawn. Every assertion here now comes from the RENDERED path instead:
* *
* ids/counts — `styleManager.getDetectionDiagnostics()` (src/ui.js), the * ids/counts — `styleManager.getDetectionDiagnostics()` (src/ui/shellFacade.js), the
* overlay's own published `visibleCount` / `labeledKeys`, so a contact only * overlay's own published `visibleCount` / `labeledKeys`, so a contact only
* counts once the sweep, the arbiter and the placement solve have all run. * counts once the sweep, the arbiter and the placement solve have all run.
* plate scale — tapped off `CanvasRenderingContext2D` while the frame paints. * plate scale — tapped off `CanvasRenderingContext2D` while the frame paints.
-4
View File
@@ -34,10 +34,6 @@
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
const argv = process.argv.slice(2); const argv = process.argv.slice(2);
const getOpt = (name, dflt) => { const getOpt = (name, dflt) => {
-1
View File
@@ -52,7 +52,6 @@ try {
id, id,
value, value,
); );
const text = () => page.$eval('#cesium-credits', (e) => e.innerText);
const shot = async (name, osm, tiles) => { const shot = async (name, osm, tiles) => {
await sleep(300); await sleep(300);
const state = await page.$eval('#cesium-credits', (e) => { const state = await page.$eval('#cesium-credits', (e) => {
+1 -1
View File
@@ -1884,7 +1884,7 @@ async function runBrowserGroup(record) {
await step('C15', async () => { await step('C15', async () => {
// CRT/NVG/FLIR are DISPLAY LABELS; the real style ids are retro / // CRT/NVG/FLIR are DISPLAY LABELS; the real style ids are retro /
// surveillance / thermal (src/ui.js STYLE_STATUS_LABELS). Passing the // surveillance / thermal (src/ui/visualPresets.js STYLE_STATUS_LABELS). Passing the
// labels made every call a silent no-op that still "passed" — so assert // labels made every call a silent no-op that still "passed" — so assert
// the returned visual state actually changed to the requested style. // the returned visual state actually changed to the requested style.
const wanted = [['retro', 'CRT'], ['surveillance', 'NVG'], ['thermal', 'FLIR'], ['normal', 'Normal']]; const wanted = [['retro', 'CRT'], ['surveillance', 'NVG'], ['thermal', 'FLIR'], ['normal', 'Normal']];
-8
View File
@@ -86,14 +86,6 @@ function percentile(values, pct) {
return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * pct) - 1)]; return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * pct) - 1)];
} }
function parseJson(value, fallback) {
try {
return JSON.parse(value);
} catch {
return fallback;
}
}
function analyzeMembership(samples) { function analyzeMembership(samples) {
let replacements = 0; let replacements = 0;
let exposure = 0; let exposure = 0;
-2
View File
@@ -1291,8 +1291,6 @@ async function main() {
const clusterBadge = await page.evaluate(async () => { const clusterBadge = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer; const viewer = window.__godsEyeView.viewer;
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js'); const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const { distanceFade } = await import('/src/overlays/worldOverlayDraw.js');
const { radioStationIdFromPick } = await import('/src/data/radio.js');
const baselineSolveRevision = getWorldOverlayDiagnostics().solveRevision; const baselineSolveRevision = getWorldOverlayDiagnostics().solveRevision;
const source = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index)) const source = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations'); .find((item) => item.name === 'Radio stations');
-1
View File
@@ -30,7 +30,6 @@ import {
import { import {
answerKartaViewRequest, answerKartaViewRequest,
describeKartaViewCall, describeKartaViewCall,
KV_LINE,
KV_SEQUENCE_ID, KV_SEQUENCE_ID,
kartaViewPhotos, kartaViewPhotos,
resetKartaViewFixture, resetKartaViewFixture,
+1 -3
View File
@@ -80,9 +80,7 @@ try {
page.evaluate(() => { page.evaluate(() => {
const { viewer, dataManager } = window.__godsEyeView; const { viewer, dataManager } = window.__godsEyeView;
let upper = 0, let upper = 0,
lower = 0, lower = 0;
near = 0;
const camera = viewer.camera.positionWC;
dataManager.layers dataManager.layers
.get('traffic') .get('traffic')
.module.visitMotionDots((id, key, p) => { .module.visitMotionDots((id, key, p) => {
-2
View File
@@ -47,8 +47,6 @@ import {
reduceScenario, reduceScenario,
reduceSensorContrast, reduceSensorContrast,
reduceScriptedMotion, reduceScriptedMotion,
reduceMotion,
reduceFleet,
} from '../src/layers/transit/qaMetrics.js'; } from '../src/layers/transit/qaMetrics.js';
const BASE = process.env.QA_BASE_URL || 'http://localhost:4173'; const BASE = process.env.QA_BASE_URL || 'http://localhost:4173';
+1 -1
View File
@@ -295,7 +295,7 @@ async function main() {
); );
// Wait for the photoreal tileset and at least one vessel refresh. // Wait for the photoreal tileset and at least one vessel refresh.
const settled = await page await page
.waitForFunction(() => { .waitForFunction(() => {
const gev = window.__godsEyeView; const gev = window.__godsEyeView;
const t = gev.tileset; const t = gev.tileset;
+1 -7
View File
@@ -8,13 +8,7 @@
* node scripts/qa-weather-swap.mjs --url http://localhost:4173 --label google * node scripts/qa-weather-swap.mjs --url http://localhost:4173 --label google
* [--layer weather-satellite] [--product clouds] [--headless] * [--layer weather-satellite] [--product clouds] [--headless]
*/ */
import { import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
existsSync,
mkdirSync,
readdirSync,
readFileSync,
writeFileSync,
} from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
+1 -1
View File
@@ -1,7 +1,7 @@
import { readdirSync } from 'node:fs'; import { readdirSync } from 'node:fs';
import { spawnSync } from 'node:child_process'; import { spawnSync } from 'node:child_process';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
export const ALLOCATION_TEST_FILES = Object.freeze([ export const ALLOCATION_TEST_FILES = Object.freeze([
'src/data/focusAllocations.test.mjs', 'src/data/focusAllocations.test.mjs',
+1 -5
View File
@@ -1,9 +1,5 @@
import { createCctvCatalog } from './cctv/catalog.js'; import { createCctvCatalog } from './cctv/catalog.js';
import { import { normalizeFeedType, isVideoFeedType } from './cctv/normalize.js';
normalizeFeedType,
isVideoFeedType,
toFiniteNumber,
} from './cctv/normalize.js';
import { import {
buildSyntheticCctvSvg, buildSyntheticCctvSvg,
cctvLiveMediaType, cctvLiveMediaType,
+1 -1
View File
@@ -18,7 +18,7 @@ export const CCTV_MAX_SOURCES_CEILING = 5000;
export const AUSTIN_DOWNTOWN = { lat: 30.2672, lon: -97.7431 }; export const AUSTIN_DOWNTOWN = { lat: 30.2672, lon: -97.7431 };
/** Caltrans CCTV: one JSON feed per district, identical schema statewide. */ /** Caltrans CCTV: one JSON feed per district, identical schema statewide. */
/** TxDOT ITS: one keyless JSON catalog per district (25 districts statewide). */ /** TxDOT ITS: one keyless JSON catalog per district (25 districts statewide). */
export const TXDOT_ORIGIN = 'https://its.txdot.gov'; const TXDOT_ORIGIN = 'https://its.txdot.gov';
export const TXDOT_CCTV_STATUS_URL = (district) => export const TXDOT_CCTV_STATUS_URL = (district) =>
`${TXDOT_ORIGIN}/its/DistrictIts/GetCctvStatusListByDistrict?districtCode=${encodeURIComponent(district)}`; `${TXDOT_ORIGIN}/its/DistrictIts/GetCctvStatusListByDistrict?districtCode=${encodeURIComponent(district)}`;
/** Per-camera frame. Returns JSON `{snippet:<base64 jpeg>}`, not an image body; /** Per-camera frame. Returns JSON `{snippet:<base64 jpeg>}`, not an image body;
+1 -1
View File
@@ -3,7 +3,7 @@ import path from 'node:path';
import { poseHash } from '../../../src/data/cctvFootprint.js'; import { poseHash } from '../../../src/data/cctvFootprint.js';
/** Sidecar written by scripts/precompute-cctv-heights.mjs. */ /** Sidecar written by scripts/precompute-cctv-heights.mjs. */
export const DEFAULT_GROUND_HEIGHTS_FILE = const DEFAULT_GROUND_HEIGHTS_FILE =
'src/data/local_data/cctv_ground_heights/cctv_ground_heights.json'; 'src/data/local_data/cctv_ground_heights/cctv_ground_heights.json';
/** @type {{path:string, mtimeMs:number, cameras:object}|null} */ /** @type {{path:string, mtimeMs:number, cameras:object}|null} */
+2 -2
View File
@@ -92,7 +92,7 @@ export function buildSyntheticCctvSvg({ cameraId, label, city, status }) {
* @param {ReadableStream|NodeJS.ReadableStream|null} body * @param {ReadableStream|NodeJS.ReadableStream|null} body
* @returns {import('stream').Readable|null} * @returns {import('stream').Readable|null}
*/ */
export function toReadable(body) { function toReadable(body) {
if (!body) return null; if (!body) return null;
if (typeof body.pipe === 'function') return body; if (typeof body.pipe === 'function') return body;
if (typeof body.getReader === 'function') { if (typeof body.getReader === 'function') {
@@ -108,7 +108,7 @@ export function toReadable(body) {
* @returns {{essence:string,params:Map<string,string>}|null} Null when the * @returns {{essence:string,params:Map<string,string>}|null} Null when the
* value is missing or is not a `type/subtype` media type. * value is missing or is not a `type/subtype` media type.
*/ */
export function parseMediaType(value) { function parseMediaType(value) {
const [head, ...rest] = String(value ?? '').split(';'); const [head, ...rest] = String(value ?? '').split(';');
const essence = head.trim().toLowerCase(); const essence = head.trim().toLowerCase();
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(essence)) return null; if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(essence)) return null;
+3 -3
View File
@@ -64,7 +64,7 @@ export function toFiniteNumber(value, fallback = NaN) {
* @param {string} text * @param {string} text
* @returns {string} * @returns {string}
*/ */
export function normalizeKey(text) { function normalizeKey(text) {
return String(text || '') return String(text || '')
.trim() .trim()
.toLowerCase() .toLowerCase()
@@ -80,7 +80,7 @@ export function normalizeKey(text) {
* @param {string} value * @param {string} value
* @returns {{lat:number, lon:number}} * @returns {{lat:number, lon:number}}
*/ */
export function parsePointString(value) { function parsePointString(value) {
const match = String(value || '').match( const match = String(value || '').match(
/POINT\s*\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)/i, /POINT\s*\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)/i,
); );
@@ -100,7 +100,7 @@ export function parsePointString(value) {
* @param {string|object|null} value * @param {string|object|null} value
* @returns {{lat:number, lon:number}} * @returns {{lat:number, lon:number}}
*/ */
export function coerceLatLon(value) { function coerceLatLon(value) {
if (!value) return { lat: NaN, lon: NaN }; if (!value) return { lat: NaN, lon: NaN };
if (typeof value === 'string') { if (typeof value === 'string') {
+1 -2
View File
@@ -133,7 +133,6 @@ import {
fallbackHeadingFromId, fallbackHeadingFromId,
isLikelyFinlandCoordinate, isLikelyFinlandCoordinate,
fintrafficCameraName, fintrafficCameraName,
hashSeed,
isPlausibleLatLon, isPlausibleLatLon,
isLikelyBcCoordinate, isLikelyBcCoordinate,
isLikelyTexasCoordinate, isLikelyTexasCoordinate,
@@ -1869,7 +1868,7 @@ export function normalizeCataloniaImageUrl(rawUrl) {
* @param {string} imageUrl - A URL normalizeCataloniaImageUrl accepted. * @param {string} imageUrl - A URL normalizeCataloniaImageUrl accepted.
* @returns {string} * @returns {string}
*/ */
export function cataloniaCameraId(imageUrl) { function cataloniaCameraId(imageUrl) {
const parsed = new URL(imageUrl); const parsed = new URL(imageUrl);
const slug = (text) => const slug = (text) =>
text text
+1 -1
View File
@@ -70,7 +70,7 @@ export async function fetchHlsBytes(
} }
} }
export function sameOriginHlsUrl(value, base) { function sameOriginHlsUrl(value, base) {
const url = new URL(value, base); const url = new URL(value, base);
if ( if (
!['https:', 'http:'].includes(url.protocol) || !['https:', 'http:'].includes(url.protocol) ||
+3 -3
View File
@@ -12,14 +12,14 @@ export const TAIWAN_FREEWAY_CCTV_URL =
'https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml'; 'https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml';
/** The whole network is ~1.2 MB. */ /** The whole network is ~1.2 MB. */
export const TAIWAN_MAX_CATALOG_BYTES = 6 * 1024 * 1024; export const TAIWAN_MAX_CATALOG_BYTES = 6 * 1024 * 1024;
export const DEFAULT_TAIWAN_MAX_SOURCES = 600; const DEFAULT_TAIWAN_MAX_SOURCES = 600;
/** After a failed download, wait this long before asking again. The catalog /** After a failed download, wait this long before asking again. The catalog
* host does not answer from some networks outside Taiwan, and every attempt * host does not answer from some networks outside Taiwan, and every attempt
* there holds the shared catalog refresh until the connect timeout. */ * there holds the shared catalog refresh until the connect timeout. */
export const TAIWAN_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000; export const TAIWAN_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000;
/** The table is republished daily; a good copy stands in for a failed /** The table is republished daily; a good copy stands in for a failed
* download for up to this long. */ * download for up to this long. */
export const TAIWAN_LAST_GOOD_MAX_AGE_MS = 24 * 60 * 60 * 1000; const TAIWAN_LAST_GOOD_MAX_AGE_MS = 24 * 60 * 60 * 1000;
// The table publishes each camera's MJPEG stream. The same camera servers // The table publishes each camera's MJPEG stream. The same camera servers
// answer a single JPEG for the same camera ID on a sibling path, which is what // answer a single JPEG for the same camera ID on a sibling path, which is what
@@ -50,7 +50,7 @@ const ANCHORS = [
/** Taiwan main island with slack for the coast; the freeway network has no /** Taiwan main island with slack for the coast; the freeway network has no
* offshore-island cameras. */ * offshore-island cameras. */
export function isLikelyTaiwanCoordinate(lat, lon) { function isLikelyTaiwanCoordinate(lat, lon) {
return ( return (
Number.isFinite(lat) && Number.isFinite(lat) &&
Number.isFinite(lon) && Number.isFinite(lon) &&
+9 -9
View File
@@ -27,13 +27,13 @@ import { normalizeDump1090Aircraft } from '../../src/sources/adsbRecords.js';
*/ */
export const LOCAL_RECEIVERS_ROUTE = '/api/local-receivers/aircraft'; export const LOCAL_RECEIVERS_ROUTE = '/api/local-receivers/aircraft';
export const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS'; const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS';
export const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']); const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']);
export const LOCAL_RECEIVER_TIMEOUT_MS = 2_000; const LOCAL_RECEIVER_TIMEOUT_MS = 2_000;
export const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024; const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024;
export const LOCAL_RECEIVER_CACHE_MS = 1_000; const LOCAL_RECEIVER_CACHE_MS = 1_000;
/** A feed whose own `now` is older than this is reported stale. */ /** A feed whose own `now` is older than this is reported stale. */
export const LOCAL_RECEIVER_STALE_MS = 10_000; const LOCAL_RECEIVER_STALE_MS = 10_000;
const MAX_FEEDS = 8; const MAX_FEEDS = 8;
const LOG_PREFIX = '[local-receivers]'; const LOG_PREFIX = '[local-receivers]';
@@ -139,7 +139,7 @@ class FeedError extends Error {
* @param {string} address Resolved IP address. * @param {string} address Resolved IP address.
* @returns {boolean} * @returns {boolean}
*/ */
export function isLocalReceiverAddress(address) { function isLocalReceiverAddress(address) {
const value = String(address || '').toLowerCase(); const value = String(address || '').toLowerCase();
if (value === '::1') return true; if (value === '::1') return true;
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(value); const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(value);
@@ -152,7 +152,7 @@ export function isLocalReceiverAddress(address) {
* @param {typeof dnsLookup} lookupImpl `dns.promises.lookup`-compatible. * @param {typeof dnsLookup} lookupImpl `dns.promises.lookup`-compatible.
* @returns {Promise<Array<{address:string, family:number}>>} * @returns {Promise<Array<{address:string, family:number}>>}
*/ */
export async function resolveLocalReceiverAddresses(hostname, lookupImpl) { async function resolveLocalReceiverAddresses(hostname, lookupImpl) {
let resolved; let resolved;
try { try {
resolved = await lookupImpl(hostname, { all: true, verbatim: true }); resolved = await lookupImpl(hostname, { all: true, verbatim: true });
@@ -274,7 +274,7 @@ function fetchLocalReceiverFeed(url, options) {
* @param {object} options * @param {object} options
* @returns {Promise<object>} Parsed JSON document. * @returns {Promise<object>} Parsed JSON document.
*/ */
export async function fetchLocalReceiverDocument( async function fetchLocalReceiverDocument(
url, url,
{ {
fetchImpl = fetchLocalReceiverFeed, fetchImpl = fetchLocalReceiverFeed,
+1 -1
View File
@@ -42,7 +42,7 @@ export class TileUpstreamError extends Error {
} }
/** Seconds a Retry-After header asks for (an integer or an HTTP date), else null. */ /** Seconds a Retry-After header asks for (an integer or an HTTP date), else null. */
export function retryAfterSeconds(header, now = Date.now()) { function retryAfterSeconds(header, now = Date.now()) {
const text = String(header ?? '').trim(); const text = String(header ?? '').trim();
if (!text) return null; if (!text) return null;
const seconds = /^\d+$/.test(text) const seconds = /^\d+$/.test(text)
+1 -1
View File
@@ -145,7 +145,7 @@ const CODEX_LOGIN_ENV =
/^(?:PATH|PATHEXT|HOME|USER|USERNAME|LOGNAME|SHELL|LANG|LANGUAGE|LC_[A-Z_]+|TERM|TZ|TMPDIR|TEMP|TMP|XDG_[A-Z_]+|DISPLAY|WAYLAND_DISPLAY|DBUS_SESSION_BUS_ADDRESS|BROWSER|WSL_[A-Z_]+|WSLENV|(?:HTTPS?|ALL|NO)_PROXY|SSL_CERT_(?:FILE|DIR)|NODE_EXTRA_CA_CERTS|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|APPDATA|LOCALAPPDATA|PROGRAMDATA|HOMEDRIVE|HOMEPATH|CODEX_[A-Z_]+)$/i; /^(?:PATH|PATHEXT|HOME|USER|USERNAME|LOGNAME|SHELL|LANG|LANGUAGE|LC_[A-Z_]+|TERM|TZ|TMPDIR|TEMP|TMP|XDG_[A-Z_]+|DISPLAY|WAYLAND_DISPLAY|DBUS_SESSION_BUS_ADDRESS|BROWSER|WSL_[A-Z_]+|WSLENV|(?:HTTPS?|ALL|NO)_PROXY|SSL_CERT_(?:FILE|DIR)|NODE_EXTRA_CA_CERTS|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|APPDATA|LOCALAPPDATA|PROGRAMDATA|HOMEDRIVE|HOMEPATH|CODEX_[A-Z_]+)$/i;
/** The environment `codex login` runs with: the allowlist, then `CODEX_HOME`. */ /** The environment `codex login` runs with: the allowlist, then `CODEX_HOME`. */
export function codexLoginEnvironment(environment, codexHome) { function codexLoginEnvironment(environment, codexHome) {
const env = {}; const env = {};
for (const [name, value] of Object.entries(environment || {})) for (const [name, value] of Object.entries(environment || {}))
if (CODEX_LOGIN_ENV.test(name) && value !== undefined) env[name] = value; if (CODEX_LOGIN_ENV.test(name) && value !== undefined) env[name] = value;
+1 -1
View File
@@ -157,4 +157,4 @@ function createDebugLogHandler({
}; };
} }
export { createDebugLogHandler, omitVoiceContent }; export { createDebugLogHandler };
+1 -1
View File
@@ -26,7 +26,7 @@ import {
RADIO_UUID_RE, RADIO_UUID_RE,
RADIO_FALLBACK_MIRRORS, RADIO_FALLBACK_MIRRORS,
} from './constants.js'; } from './constants.js';
export async function mapRadioConcurrent(values, concurrency, mapper) { async function mapRadioConcurrent(values, concurrency, mapper) {
const results = new Array(values.length); const results = new Array(values.length);
let cursor = 0; let cursor = 0;
const workers = Array.from( const workers = Array.from(
-1
View File
@@ -1,5 +1,4 @@
export { export {
createTransitHistory, createTransitHistory,
TRANSIT_HISTORY_LIMITS,
validTransitIdentifier, validTransitIdentifier,
} from '../../src/sources/transitHistoryStore.js'; } from '../../src/sources/transitHistoryStore.js';
+1 -1
View File
@@ -3,7 +3,7 @@ import { decodeWindGribMessage } from './decode.js';
import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js'; import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js';
/** Keyless ECMWF Open Data root for real-time IFS forecasts. */ /** Keyless ECMWF Open Data root for real-time IFS forecasts. */
export const IFS_BASE = 'https://data.ecmwf.int/forecasts'; const IFS_BASE = 'https://data.ecmwf.int/forecasts';
/** /**
* Select the latest IFS cycle that should be available. * Select the latest IFS cycle that should be available.
+4 -4
View File
@@ -31,10 +31,10 @@ export const MIN_VERTICES = Object.freeze({ area: 3, line: 2, pin: 1 });
*/ */
export const MAX_VERTICES = 512; export const MAX_VERTICES = 512;
/** Two clicks closer than this are one vertex: a double-click to finish must not add a stray point. */ /** Two clicks closer than this are one vertex: a double-click to finish must not add a stray point. */
export const MIN_VERTEX_SEPARATION_M = 0.5; const MIN_VERTEX_SEPARATION_M = 0.5;
/** A line shorter than this, or an area thinner than this, is a mis-click rather than a shape. */ /** A line shorter than this, or an area thinner than this, is a mis-click rather than a shape. */
export const MIN_PATH_LENGTH_M = 1; const MIN_PATH_LENGTH_M = 1;
export const MIN_AREA_M2 = 1; const MIN_AREA_M2 = 1;
/** @param {string} shape @returns {'area'|'line'|'pin'} */ /** @param {string} shape @returns {'area'|'line'|'pin'} */
export function normalizeShape(shape) { export function normalizeShape(shape) {
@@ -50,7 +50,7 @@ export function createDrawSession(shape = 'area') {
} }
/** Great-circle distance in metres between two {lon, lat} points. */ /** Great-circle distance in metres between two {lon, lat} points. */
export function greatCircleM(a, b) { function greatCircleM(a, b) {
return greatCircleMeters(a, b); return greatCircleMeters(a, b);
} }
+1 -1
View File
@@ -52,7 +52,7 @@ import {
import { msg, registerUnsavedWork, t, translateKnown } from '../i18n/index.js'; import { msg, registerUnsavedWork, t, translateKnown } from '../i18n/index.js';
/** The id this tool claims the pointer under. */ /** The id this tool claims the pointer under. */
export const DRAW_POINTER_OWNER = 'draw'; const DRAW_POINTER_OWNER = 'draw';
const PREVIEW_DATA_SOURCE_NAME = 'gev-draw-preview'; const PREVIEW_DATA_SOURCE_NAME = 'gev-draw-preview';
const COLORS = ['primary', 'amber', 'cyan', 'green', 'red']; const COLORS = ['primary', 'amber', 'cyan', 'green', 'red'];
+1 -13
View File
@@ -29,7 +29,7 @@ import { unavailablePlaceSearch } from '../search/placeSearch.js';
import { isPickedWorldPosition } from '../data/scenePick.js'; import { isPickedWorldPosition } from '../data/scenePick.js';
/** Ms the analyst region lookup waits on geocode + admin boundary. */ /** Ms the analyst region lookup waits on geocode + admin boundary. */
export const REGION_FALLBACK_BUDGET_MS = 3_000; const REGION_FALLBACK_BUDGET_MS = 3_000;
/** Entity facts that rule out a state/county reading of the ask. */ /** Entity facts that rule out a state/county reading of the ask. */
const NON_ADMIN_ENTITY_KINDS = new Set([ const NON_ADMIN_ENTITY_KINDS = new Set([
@@ -103,18 +103,6 @@ export function createAnnotationResolver({
cacheWrite(cache, key, null); cacheWrite(cache, key, null);
} }
/** Wire an external AbortSignal to a local controller; returns a detach fn. */
function linkAbort(controller, externalSignal) {
if (!externalSignal) return () => {};
if (externalSignal.aborted) {
controller.abort();
return () => {};
}
const onAbort = () => controller.abort();
externalSignal.addEventListener('abort', onAbort, { once: true });
return () => externalSignal.removeEventListener('abort', onAbort);
}
/** /**
* Resolve a single annotation target to a normalized world anchor. * Resolve a single annotation target to a normalized world anchor.
* *
+3 -3
View File
@@ -15,9 +15,9 @@ import * as Cesium from 'cesium';
import { announceNavigationAuthority } from '../navigationPolicy.js'; import { announceNavigationAuthority } from '../navigationPolicy.js';
import { annotationsFromParams, createView } from '../view/index.js'; import { annotationsFromParams, createView } from '../view/index.js';
export const EMBED_VIEW_MESSAGE = 'gev:view'; const EMBED_VIEW_MESSAGE = 'gev:view';
export const EMBED_APPLIED_MESSAGE = 'gev:view-applied'; const EMBED_APPLIED_MESSAGE = 'gev:view-applied';
export const EMBED_READY_MESSAGE = 'gev:ready'; const EMBED_READY_MESSAGE = 'gev:ready';
const FOLLOW_LAYERS = { const FOLLOW_LAYERS = {
aircraft: 'flights', aircraft: 'flights',
+1 -1
View File
@@ -53,7 +53,7 @@ export const CATALOG_SOURCE_CONTRACTS = Object.freeze({
}); });
/** An omitted provider is unavailable, never an authoritative empty dataset. */ /** An omitted provider is unavailable, never an authoritative empty dataset. */
export class SourceUnavailableError extends Error { class SourceUnavailableError extends Error {
constructor(name) { constructor(name) {
super(`Data source not configured: ${name}`); super(`Data source not configured: ${name}`);
this.name = 'SourceUnavailableError'; this.name = 'SourceUnavailableError';
-46
View File
@@ -1,51 +1,5 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
/**
* Camera presets for notable locations.
* Phase 1 default: fly to Austin, TX on load.
*/
export const CAMERA_PRESETS = {
austin: {
destination: Cesium.Cartesian3.fromDegrees(-97.7431, 30.2672, 800),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-35),
roll: 0.0,
},
},
sf: {
destination: Cesium.Cartesian3.fromDegrees(-122.4194, 37.7749, 1000),
orientation: {
heading: Cesium.Math.toRadians(30),
pitch: Cesium.Math.toRadians(-30),
roll: 0.0,
},
},
nyc: {
destination: Cesium.Cartesian3.fromDegrees(-73.9857, 40.7484, 1200),
orientation: {
heading: Cesium.Math.toRadians(-20),
pitch: Cesium.Math.toRadians(-30),
roll: 0.0,
},
},
};
/**
* Fly the camera to a preset location with a smooth animation.
*/
export function flyToPreset(viewer, presetName, duration = 3.0) {
const preset = CAMERA_PRESETS[presetName];
if (!preset) return;
viewer.camera.flyTo({
destination: preset.destination,
orientation: preset.orientation,
duration,
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
});
}
/** /**
* Set camera to Austin on load with a cinematic fly-in. * Set camera to Austin on load with a cinematic fly-in.
* @returns {Function} Cancels the pending or active startup flight. * @returns {Function} Cancels the pending or active startup flight.
+2 -2
View File
@@ -20,8 +20,8 @@ import { ownCameraArrival } from './data/cameraArrival.js';
/** Minimum eye height above the rendered surface for a usable view. */ /** Minimum eye height above the rendered surface for a usable view. */
export const MIN_EYE_CLEARANCE_M = 120; export const MIN_EYE_CLEARANCE_M = 120;
/** Retries while tiles stream in; sampling fails until the mesh exists. */ /** Retries while tiles stream in; sampling fails until the mesh exists. */
export const GUARD_ATTEMPTS = 8; const GUARD_ATTEMPTS = 8;
export const GUARD_INTERVAL_MS = 600; const GUARD_INTERVAL_MS = 600;
/** Ignore sub-metre noise rather than nudging the camera forever. */ /** Ignore sub-metre noise rather than nudging the camera forever. */
const GUARD_EPSILON_M = 2; const GUARD_EPSILON_M = 2;
+1 -2
View File
@@ -1,7 +1,6 @@
import { LayerBindings } from './ui/layerBindings.js'; import { LayerBindings } from './ui/layerBindings.js';
import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs'; import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs';
import { readLayerSource } from './testSupport/readLayerSource.mjs'; import { readLayerSource } from './testSupport/readLayerSource.mjs';
import { StyleManager } from './ui/applicationShell.js';
import { enter as cockpitEnter, navigateContext } from './ui/cockpitTrackingController.js'; import { enter as cockpitEnter, navigateContext } from './ui/cockpitTrackingController.js';
import { CockpitViewController } from './ui/cockpitController.js'; import { CockpitViewController } from './ui/cockpitController.js';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
@@ -384,7 +383,7 @@ test('world-focus listener lifecycle is symmetric and idempotent', () => {
}); });
test('vessel and fire layers announce valid clicks and never fly cameras', () => { test('vessel and fire layers announce valid clicks and never fly cameras', () => {
for (const [label, source] of [['vessels', vessels], ['fires', firms]]) { for (const source of [vessels, firms]) {
assert.match(source, /requestWorldFocus\(\{/); assert.match(source, /requestWorldFocus\(\{/);
assert.doesNotMatch(source, /camera\.flyTo/); assert.doesNotMatch(source, /camera\.flyTo/);
} }
+1 -1
View File
@@ -27,7 +27,7 @@ export function runCctvLayerEnableFocus({
* @param {Object} [after={}] Ownership immediately after the await. * @param {Object} [after={}] Ownership immediately after the await.
* @returns {{ trackedEntity: *, cockpitActive: boolean }} Conservative ownership. * @returns {{ trackedEntity: *, cockpitActive: boolean }} Conservative ownership.
*/ */
export function mergeCctvEnableOwnership(before = {}, after = {}) { function mergeCctvEnableOwnership(before = {}, after = {}) {
return { return {
trackedEntity: before.trackedEntity || after.trackedEntity || null, trackedEntity: before.trackedEntity || after.trackedEntity || null,
cockpitActive: !!(before.cockpitActive || after.cockpitActive), cockpitActive: !!(before.cockpitActive || after.cockpitActive),
-1
View File
@@ -1,7 +1,6 @@
import { readShellSource } from './testSupport/readShellSource.mjs'; import { readShellSource } from './testSupport/readShellSource.mjs';
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { import {
runCctvLayerEnableFocus, runCctvLayerEnableFocus,
runCctvLayerEnableTransition, runCctvLayerEnableTransition,
-1
View File
@@ -1,7 +1,6 @@
import { readShellSource } from './testSupport/readShellSource.mjs'; import { readShellSource } from './testSupport/readShellSource.mjs';
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { import {
CCTV_WORLD_CLICK_FOCUS_DURATION_SEC, CCTV_WORLD_CLICK_FOCUS_DURATION_SEC,
CCTV_FOCUS_REQUEST_EVENT, CCTV_FOCUS_REQUEST_EVENT,
+1 -1
View File
@@ -174,7 +174,7 @@ export function cockpitWeatherEnabledFromStoredValue(value) {
* Weather-backed volumetric cloud pass that exists only while cockpit mode is * Weather-backed volumetric cloud pass that exists only while cockpit mode is
* active. It owns no Cesium fog/post-process stages and cannot affect map mode. * active. It owns no Cesium fog/post-process stages and cannot affect map mode.
*/ */
export class CockpitCloudEffectsController { class CockpitCloudEffectsController {
constructor(viewer, { weatherService = applicationServices.weather } = {}) { constructor(viewer, { weatherService = applicationServices.weather } = {}) {
this.weatherService = weatherService; this.weatherService = weatherService;
this.viewer = viewer; this.viewer = viewer;
+2 -2
View File
@@ -281,7 +281,7 @@ test('detection is wired to the Contacts transaction, and cockpit no longer touc
uiSource.indexOf('_syncContactsDetection() {'), uiSource.indexOf('_syncContactsDetection() {'),
uiSource.indexOf('/** Apply a temporary cockpit-only'), uiSource.indexOf('/** Apply a temporary cockpit-only'),
); );
assert.ok(helper.length > 0, 'the Contacts detection helper is present in ui.js'); assert.ok(helper.length > 0, 'the Contacts detection helper is present in the UI shell source');
assert.match(helper, /if \(this\._contextModeChanging\) return;/, 'fires at settle, not at click'); assert.match(helper, /if \(this\._contextModeChanging\) return;/, 'fires at settle, not at click');
assert.match(helper, /active: this\._contextMode === 'flights'/); assert.match(helper, /active: this\._contextMode === 'flights'/);
assert.match(helper, /styleOwnsDetection:\s*!this\._detectionUserOverridden/); assert.match(helper, /styleOwnsDetection:\s*!this\._detectionUserOverridden/);
@@ -302,7 +302,7 @@ test('detection is wired to the Contacts transaction, and cockpit no longer touc
uiSource.indexOf('_setCockpitVision(mode, active, { revealParameters = false } = {}) {'), uiSource.indexOf('_setCockpitVision(mode, active, { revealParameters = false } = {}) {'),
uiSource.indexOf('_syncIrBoost() {'), uiSource.indexOf('_syncIrBoost() {'),
); );
assert.ok(visionHook.length > 0, 'the cockpit vision hook is present in ui.js'); assert.ok(visionHook.length > 0, 'the cockpit vision hook is present in the UI shell source');
assert.doesNotMatch(visionHook, /etection/, 'cockpit transitions must not touch detection'); assert.doesNotMatch(visionHook, /etection/, 'cockpit transitions must not touch detection');
}); });
-3
View File
@@ -8,7 +8,6 @@ import { setContextMode as contextModeAction } from './ui/contextActions.js';
import { ContextControls } from './ui/contextControls.js'; import { ContextControls } from './ui/contextControls.js';
import { readFileSync as readRadioSource } from 'node:fs'; import { readFileSync as readRadioSource } from 'node:fs';
const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8'); const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8');
const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8');
const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8'); const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8');
// Source-contract pins read the actual Context owners and root disposal wiring. Each pin guards a bug that shipped or nearly shipped: // Source-contract pins read the actual Context owners and root disposal wiring. Each pin guards a bug that shipped or nearly shipped:
// - session bookkeeping ran AFTER the exit early-return, so the compensating // - session bookkeeping ran AFTER the exit early-return, so the compensating
@@ -21,8 +20,6 @@ const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', imp
// - the right-rail entry ignored the activation result entirely. // - the right-rail entry ignored the activation result entirely.
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const src = readShellSource(); const src = readShellSource();
-5
View File
@@ -1,4 +1,3 @@
import { readShellSource } from './testSupport/readShellSource.mjs';
import { expandApplicationHtml } from '../build/application-html.js'; import { expandApplicationHtml } from '../build/application-html.js';
import { readStylesheet } from './testSupport/readStylesheet.mjs'; import { readStylesheet } from './testSupport/readStylesheet.mjs';
import { _selectContextMode } from './ui/contextTransactions.js'; import { _selectContextMode } from './ui/contextTransactions.js';
@@ -8,13 +7,11 @@ import { clearSelectedLayers } from './ui/contextActions.js';
import { readFileSync as readRadioSource } from 'node:fs'; import { readFileSync as readRadioSource } from 'node:fs';
const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8'); const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8');
const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8'); const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8');
const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8');
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
const html = expandApplicationHtml(readFileSync(new URL('../index.html', import.meta.url), 'utf8')); const html = expandApplicationHtml(readFileSync(new URL('../index.html', import.meta.url), 'utf8'));
const ui = readShellSource();
const css = readStylesheet(new URL('../style.css', import.meta.url)); const css = readStylesheet(new URL('../style.css', import.meta.url));
test('Contacts and Space Missions both participate in the ordinary Tab sequence', () => { test('Contacts and Space Missions both participate in the ordinary Tab sequence', () => {
@@ -32,8 +29,6 @@ test('Contacts and Space Missions both participate in the ordinary Tab sequence'
}); });
test('Context transition state preserves focus and Tab availability until settle', () => { test('Context transition state preserves focus and Tab availability until settle', () => {
const syncSource = _syncContextModeButtons.toString();
const attributes = () => new Map(); const attributes = () => new Map();
const makeButton = () => { const makeButton = () => {
const attrs = attributes(); const attrs = attributes();
-2
View File
@@ -1,4 +1,3 @@
import { readShellSource } from './testSupport/readShellSource.mjs';
import { readStylesheet } from './testSupport/readStylesheet.mjs'; import { readStylesheet } from './testSupport/readStylesheet.mjs';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import fs from 'node:fs'; import fs from 'node:fs';
@@ -8,7 +7,6 @@ import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const css = readStylesheet(path.join(ROOT, 'style.css')); const css = readStylesheet(path.join(ROOT, 'style.css'));
const ui = readShellSource();
/* /*
* Required-attribution keep-out pin. * Required-attribution keep-out pin.
+1 -1
View File
@@ -123,7 +123,7 @@ export function decodeRing(encoded, decimals) {
} }
/** Even-odd point-in-ring test; ring is [[lon, lat], …], open or closed. */ /** Even-odd point-in-ring test; ring is [[lon, lat], …], open or closed. */
export function pointInRing(ring, lat, lon) { function pointInRing(ring, lat, lon) {
if (!Array.isArray(ring) || ring.length < 3) return false; if (!Array.isArray(ring) || ring.length < 3) return false;
let inside = false; let inside = false;
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) { for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
+1 -1
View File
@@ -215,7 +215,7 @@ export const AIS_RECOGNIZED_MESSAGE_TYPES = Object.freeze(
* @param {Object} body * @param {Object} body
* @returns {string|null} * @returns {string|null}
*/ */
export function aisEnvelopeMmsi(envelope, body = {}) { function aisEnvelopeMmsi(envelope, body = {}) {
const metadata = envelope?.MetaData || envelope?.Metadata || {}; const metadata = envelope?.MetaData || envelope?.Metadata || {};
const raw = metadata.MMSI ?? body.UserID ?? body.UserId ?? body.Mmsi; const raw = metadata.MMSI ?? body.UserID ?? body.UserId ?? body.Mmsi;
if (raw === null || raw === undefined) return null; if (raw === null || raw === undefined) return null;
+1 -21
View File
@@ -46,28 +46,8 @@ export const AIS_WATCHDOG_DEFAULTS = Object.freeze({
authProbeMs: 3_600_000, authProbeMs: 3_600_000,
}); });
/**
* Statuses reported to the browser. 'missing-key' and 'unsupported' keep their
* pre-watchdog meaning so a keyless install still reads as "feature off".
*/
export const AIS_WATCHDOG_STATUSES = Object.freeze([
'idle',
'missing-key',
'unsupported',
'connecting',
'live',
'stale',
'reconnecting',
'down',
'auth-failed',
]);
/** Failure classes. Only 'transport' is allowed to walk the fast ladder. */ /** Failure classes. Only 'transport' is allowed to walk the fast ladder. */
export const AIS_FAILURE_KINDS = Object.freeze([ const AIS_FAILURE_KINDS = Object.freeze(['transport', 'auth', 'rate-limit']);
'transport',
'auth',
'rate-limit',
]);
/** Statuses that must not flip back to a hopeful 'connecting' on a retry. */ /** Statuses that must not flip back to a hopeful 'connecting' on a retry. */
const QUIET_TERMINAL = new Set(['down', 'auth-failed']); const QUIET_TERMINAL = new Set(['down', 'auth-failed']);
+3 -63
View File
@@ -62,7 +62,7 @@ const COMMON_FIELDS = Object.freeze(['id', 'lat', 'lon']);
/** Derived per-record field for layers with a time field (hours before now). */ /** Derived per-record field for layers with a time field (hours before now). */
const AGE_FIELD = 'ageHours'; const AGE_FIELD = 'ageHours';
/** Spatial scope kinds `query()` accepts. */ /** Spatial scope kinds `query()` accepts. */
export const ANALYST_SCOPE_KINDS = Object.freeze([ const ANALYST_SCOPE_KINDS = Object.freeze([
'view', 'view',
'region', 'region',
'radius', 'radius',
@@ -74,7 +74,7 @@ export const ANALYST_SCOPE_KINDS = Object.freeze([
* @param {string[]} layerKeys Queried layer ids. * @param {string[]} layerKeys Queried layer ids.
* @returns {string[]} Field names, common fields first. * @returns {string[]} Field names, common fields first.
*/ */
export function analystFieldsFor(layerKeys) { function analystFieldsFor(layerKeys) {
const fields = new Set(COMMON_FIELDS); const fields = new Set(COMMON_FIELDS);
for (const key of layerKeys) { for (const key of layerKeys) {
const layer = ANALYST_LAYERS[key]; const layer = ANALYST_LAYERS[key];
@@ -104,44 +104,6 @@ export function haversineKm(lat1, lon1, lat2, lon2) {
return greatCircleKm({ lat: lat1, lon: lon1 }, { lat: lat2, lon: lon2 }); return greatCircleKm({ lat: lat1, lon: lon1 }, { lat: lat2, lon: lon2 });
} }
/**
* One filter: {field, op:'gt'|'lt'|'gte'|'lte'|'eq'|'neq'|'contains', value}.
* With `time: true`, both sides compare as epoch ms (ISO strings accepted).
*/
export function applyFilter(records, filter, { time = false } = {}) {
const { field, op } = filter || {};
if (!field || !op) return records;
const value = time ? toEpochMs(filter.value) : filter.value;
return records.filter((r) => {
const got = time ? toEpochMs(r[field]) : r[field];
if (got === null || got === undefined || (time && !Number.isFinite(got)))
return false;
if (time && (op === 'eq' || op === 'neq'))
return (got === value) === (op === 'eq');
switch (op) {
case 'gt':
return Number(got) > Number(value);
case 'gte':
return Number(got) >= Number(value);
case 'lt':
return Number(got) < Number(value);
case 'lte':
return Number(got) <= Number(value);
case 'eq': {
if (typeof got === 'boolean' || typeof value === 'boolean')
return Boolean(got) === Boolean(value);
return String(got).toLowerCase() === String(value).toLowerCase();
}
case 'neq':
return String(got).toLowerCase() !== String(value).toLowerCase();
case 'contains':
return String(got).toLowerCase().includes(String(value).toLowerCase());
default:
return false;
}
});
}
/** Scope records spatially. scope: {kind:'view'|'region'|'radius'|'anywhere', …}. */ /** Scope records spatially. scope: {kind:'view'|'region'|'radius'|'anywhere', …}. */
export function applyScope(records, scope, resolved) { export function applyScope(records, scope, resolved) {
if (!scope || scope.kind === 'anywhere') return records; if (!scope || scope.kind === 'anywhere') return records;
@@ -167,28 +129,6 @@ export function applyScope(records, scope, resolved) {
return records; return records;
} }
/** Numeric summary for the narration layer. */
function summarize(items, sortField) {
const summary = { count: items.length };
if (sortField && items.length) {
// A loop, not Math.min(...vals): a full FIRMS day is ~160k values, past
// the engine's argument limit.
let min = Infinity;
let max = -Infinity;
for (const item of items) {
const value = Number(item[sortField]);
if (!Number.isFinite(value)) continue;
if (value < min) min = value;
if (value > max) max = value;
}
if (Number.isFinite(min)) {
summary[`${sortField}Min`] = min;
summary[`${sortField}Max`] = max;
}
}
return summary;
}
const OPS_BY_TYPE = Object.freeze({ const OPS_BY_TYPE = Object.freeze({
number: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq'], number: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq'],
time: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq'], time: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq'],
@@ -213,7 +153,7 @@ const COMMON_UNITS = Object.freeze({
* @param {string} field Field name. * @param {string} field Field name.
* @returns {'number'|'text'|'flag'|'time'|null} Field type. * @returns {'number'|'text'|'flag'|'time'|null} Field type.
*/ */
export function analystFieldType(layerKeys, field) { function analystFieldType(layerKeys, field) {
if (!analystFieldsFor(layerKeys).includes(field)) return null; if (!analystFieldsFor(layerKeys).includes(field)) return null;
if (COMMON_TYPES[field]) return COMMON_TYPES[field]; if (COMMON_TYPES[field]) return COMMON_TYPES[field];
for (const key of layerKeys) { for (const key of layerKeys) {
+1 -5
View File
@@ -14,7 +14,7 @@ import { BHOTE_KOSHI_FLOOD_PATH } from './bhoteKoshiFloodPath.js';
import { INCIDENT_OVERVIEW_PLACES } from './bhoteKoshiIncidentPlaces.js'; import { INCIDENT_OVERVIEW_PLACES } from './bhoteKoshiIncidentPlaces.js';
import { t, msg, getLocale } from '../i18n/index.js'; import { t, msg, getLocale } from '../i18n/index.js';
export const BHOTE_KOSHI_LOCATOR_LAYER_ID = 'bhote-koshi-locator'; const BHOTE_KOSHI_LOCATOR_LAYER_ID = 'bhote-koshi-locator';
export const BHOTE_KOSHI_LOCATOR_OVERLAY_SOURCE_ID = export const BHOTE_KOSHI_LOCATOR_OVERLAY_SOURCE_ID =
'bhote-koshi-locator-callout'; 'bhote-koshi-locator-callout';
export const NEPAL_CONTEXT_PRESENTATION = 'nepal-context'; export const NEPAL_CONTEXT_PRESENTATION = 'nepal-context';
@@ -35,7 +35,6 @@ const BORDER_START_DELAY_MS = 700;
const INCIDENT_CALLOUT_GAP_MS = 200; const INCIDENT_CALLOUT_GAP_MS = 200;
const CALLOUT_REVEAL_DURATION_MS = 1200; const CALLOUT_REVEAL_DURATION_MS = 1200;
const CALLOUT_PULSE_DURATION_MS = 1800; const CALLOUT_PULSE_DURATION_MS = 1800;
const CALLOUT_DOT_SIZE_PX = 7;
const CALLOUT_RING_MIN_SIZE_PX = 16; const CALLOUT_RING_MIN_SIZE_PX = 16;
const CALLOUT_RING_SIZE_RANGE_PX = 14; const CALLOUT_RING_SIZE_RANGE_PX = 14;
const CITY_CALLOUT_START_DELAY_MS = 350; const CITY_CALLOUT_START_DELAY_MS = 350;
@@ -1248,6 +1247,3 @@ export function createBhoteKoshiLocatorLayer({
}, },
}; };
} }
const bhoteKoshiLocatorLayer = createBhoteKoshiLocatorLayer();
export default bhoteKoshiLocatorLayer;
-1
View File
@@ -23,4 +23,3 @@ export {
BIKESHARE_SELECTED_OVERLAY_SOURCE_ID, BIKESHARE_SELECTED_OVERLAY_SOURCE_ID,
BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS, BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS,
} from '../layers/bikeshare/index.js'; } from '../layers/bikeshare/index.js';
export default layer;
+2 -2
View File
@@ -17,7 +17,7 @@
*/ */
const EARTH_RADIUS_M = 6371000; const EARTH_RADIUS_M = 6371000;
export const PLANE_VERT_ASPECT = 16 / 9; const PLANE_VERT_ASPECT = 16 / 9;
export const SUPPORT_KEYS = Object.freeze([ export const SUPPORT_KEYS = Object.freeze([
'bl', 'bl',
'bm', 'bm',
@@ -128,7 +128,7 @@ export function planeSupportPoints(pose) {
* @param {number} capAltM @param {-1|0|1} row @param {{upVert:number}} dims * @param {number} capAltM @param {-1|0|1} row @param {{upVert:number}} dims
* @returns {number} * @returns {number}
*/ */
export function supportAltitude(capAltM, row, dims) { function supportAltitude(capAltM, row, dims) {
return capAltM + row * dims.upVert; return capAltM + row * dims.upVert;
} }
+1 -1
View File
@@ -316,7 +316,7 @@ export function createCalibrationGizmo({
} }
const mount = positions.mount; const mount = positions.mount;
const axes = enuAxes(mount); const axes = enuAxes(mount);
const { forwardHoriz, right, view } = viewAxesFor( const { forwardHoriz } = viewAxesFor(
camera.headingDeg, camera.headingDeg,
camera.pitchDeg, camera.pitchDeg,
axes, axes,
+3 -3
View File
@@ -94,7 +94,7 @@ export function incumbentRankKm(
export const CCTV_CARD_CENTER_WEIGHT = 0.5; export const CCTV_CARD_CENTER_WEIGHT = 0.5;
/** Robust pool-distance percentile used to scale the screen-space term. */ /** Robust pool-distance percentile used to scale the screen-space term. */
export const CCTV_CARD_SPREAD_PERCENTILE = 0.9; const CCTV_CARD_SPREAD_PERCENTILE = 0.9;
/** True only when both viewport dimensions are finite positive pixels. */ /** True only when both viewport dimensions are finite positive pixels. */
export function hasFiniteCctvViewport(viewW, viewH) { export function hasFiniteCctvViewport(viewW, viewH) {
@@ -172,8 +172,8 @@ export function blendCenterRankKm(
// viewport is bucketed into this grid and every occupied cell gets its best // viewport is bucketed into this grid and every occupied cell gets its best
// candidate before global rank fills the rest. Owner-tunable together with // candidate before global rank fills the rest. Owner-tunable together with
// the budgets above. // the budgets above.
export const CCTV_CARD_GRID_COLS = 5; const CCTV_CARD_GRID_COLS = 5;
export const CCTV_CARD_GRID_ROWS = 4; const CCTV_CARD_GRID_ROWS = 4;
/** /**
* Screen-space distribution pass (owner round 2, item C). Buckets the * Screen-space distribution pass (owner round 2, item C). Buckets the
@@ -2,7 +2,6 @@ import { readLayerSource } from '../testSupport/readLayerSource.mjs';
import { dispatchCockpitModeChanged, enter, exit, _adoptTrackedEntity } from '../ui/cockpitTrackingController.js'; import { dispatchCockpitModeChanged, enter, exit, _adoptTrackedEntity } from '../ui/cockpitTrackingController.js';
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import flightsLayer, { import flightsLayer, {
_setCockpitDetectionSubjectForTest as setFlightsCockpitSubject, _setCockpitDetectionSubjectForTest as setFlightsCockpitSubject,
-1
View File
@@ -1,7 +1,6 @@
import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import { readLayerSource } from '../testSupport/readLayerSource.mjs';
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
/** /**
* Cockpit 3D aircraft policy. * Cockpit 3D aircraft policy.
+1 -1
View File
@@ -181,7 +181,7 @@ export function trimTrack(track, nowMs, keep = track.capacity) {
for (const id of track.epochs.keys()) for (const id of track.epochs.keys())
if (!used.has(id)) track.epochs.delete(id); if (!used.has(id)) track.epochs.delete(id);
} }
export function validFix(fix) { function validFix(fix) {
return ( return (
Number.isFinite(fix?.t) && Number.isFinite(fix?.t) &&
fix.t >= 0 && fix.t >= 0 &&
+2 -2
View File
@@ -25,11 +25,11 @@ import * as Cesium from 'cesium';
* follows DATA_SOURCES.md (live sources, then bundled snapshots). * follows DATA_SOURCES.md (live sources, then bundled snapshots).
* @type {{ key: string, html: string }[]} * @type {{ key: string, html: string }[]}
*/ */
export const OSM_CREDIT = { const OSM_CREDIT = {
key: 'openstreetmap', key: 'openstreetmap',
html: 'Map and place data <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">© OpenStreetMap contributors</a> (<a href="https://opendatacommons.org/licenses/odbl/1-0/" target="_blank" rel="noopener">ODbL</a>)', html: 'Map and place data <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">© OpenStreetMap contributors</a> (<a href="https://opendatacommons.org/licenses/odbl/1-0/" target="_blank" rel="noopener">ODbL</a>)',
}; };
export const OPENMAPTILES_CREDIT = { const OPENMAPTILES_CREDIT = {
key: 'openfreemap', key: 'openfreemap',
html: 'Vector tiles: <a href="https://openfreemap.org" target="_blank" rel="noopener">OpenFreeMap</a> © <a href="https://openmaptiles.org" target="_blank" rel="noopener">OpenMapTiles</a>', html: 'Vector tiles: <a href="https://openfreemap.org" target="_blank" rel="noopener">OpenFreeMap</a> © <a href="https://openmaptiles.org" target="_blank" rel="noopener">OpenMapTiles</a>',
}; };
-1
View File
@@ -43,7 +43,6 @@ import {
detectionHorizontalSector, detectionHorizontalSector,
labelBudgetFor, labelBudgetFor,
normalizeAllocationStrategy, normalizeAllocationStrategy,
normalizeProfile,
profileForDensity, profileForDensity,
viewScaleForAltitude, viewScaleForAltitude,
} from './detectionPolicy.js'; } from './detectionPolicy.js';
+1 -1
View File
@@ -18,7 +18,7 @@ export function stableIdentityHash(layerId, sourceId) {
} }
/** Negative means a is a better deterministic contender than b. */ /** Negative means a is a better deterministic contender than b. */
export function compareCohortContenders(a, b) { function compareCohortContenders(a, b) {
const priorityDelta = const priorityDelta =
(Number(b?._cohortPriority) || 0) - (Number(a?._cohortPriority) || 0); (Number(b?._cohortPriority) || 0) - (Number(a?._cohortPriority) || 0);
if (priorityDelta) return priorityDelta; if (priorityDelta) return priorityDelta;
+1 -2
View File
@@ -5,7 +5,6 @@
* module so density cannot drift into contradictory mode semantics again. * module so density cannot drift into contradictory mode semantics again.
*/ */
export const DENSITY_STOPS = Object.freeze([0, 25, 50, 75, 100]);
export const ALLOCATION_ELASTIC = 'ELASTIC'; export const ALLOCATION_ELASTIC = 'ELASTIC';
export const ALLOCATION_WEIGHTED = 'WEIGHTED'; export const ALLOCATION_WEIGHTED = 'WEIGHTED';
export const ALLOCATION_STRATEGIES = Object.freeze([ export const ALLOCATION_STRATEGIES = Object.freeze([
@@ -118,7 +117,7 @@ export function detectionHorizontalSector(screenX, viewportWidth) {
return 'front'; return 'front';
} }
export const VIEW_SCALE_BUDGETS = Object.freeze({ const VIEW_SCALE_BUDGETS = Object.freeze({
street: Object.freeze({ 0: 9, 25: 23, 50: 45, 75: 68, 100: 90 }), street: Object.freeze({ 0: 9, 25: 23, 50: 45, 75: 68, 100: 90 }),
city: Object.freeze({ 0: 7, 25: 18, 50: 35, 75: 53, 100: 70 }), city: Object.freeze({ 0: 7, 25: 18, 50: 35, 75: 53, 100: 70 }),
metro: Object.freeze({ 0: 6, 25: 14, 50: 28, 75: 41, 100: 55 }), metro: Object.freeze({ 0: 6, 25: 14, 50: 28, 75: 41, 100: 55 }),
+1 -5
View File
@@ -24,7 +24,7 @@ import * as input from './inputOwnership.js';
import { createDirectionsLayer } from '../layers/directions/index.js'; import { createDirectionsLayer } from '../layers/directions/index.js';
/** The shared scene owners every Directions instance runs on. */ /** The shared scene owners every Directions instance runs on. */
export const directionsServices = Object.freeze({ const directionsServices = Object.freeze({
credits, credits,
render, render,
sprites, sprites,
@@ -49,15 +49,11 @@ export {
DIRECTIONS_MODES, DIRECTIONS_MODES,
DIRECTIONS_POINTER_OWNER, DIRECTIONS_POINTER_OWNER,
DIRECTIONS_ROUTE_COLOR, DIRECTIONS_ROUTE_COLOR,
DIRECTIONS_STEP_OVERLAY_SOURCE_ID,
DIRECTIONS_STEP_OVERLAY_SOURCE_OPTIONS,
FLIGHT_PROGRESS_MS,
POINTER_TOOL_EXITS, POINTER_TOOL_EXITS,
STEP_ANCHOR_DEADLINE_MS, STEP_ANCHOR_DEADLINE_MS,
STEP_ANCHOR_FAST_ATTEMPTS, STEP_ANCHOR_FAST_ATTEMPTS,
STEP_ANCHOR_RETRY_MS, STEP_ANCHOR_RETRY_MS,
STEP_ANCHOR_SLOW_RETRY_MS, STEP_ANCHOR_SLOW_RETRY_MS,
createDirectionsLayer,
createDirectionsStepOverlayEntry, createDirectionsStepOverlayEntry,
directionsRequestUrl, directionsRequestUrl,
directionsRowControls, directionsRowControls,
-1
View File
@@ -8,4 +8,3 @@ export function createEarthquakesLayer({
} = {}) { } = {}) {
return createApplicationEarthquakes({ source, ...options }); return createApplicationEarthquakes({ source, ...options });
} }
export default createEarthquakesLayer();
+4 -4
View File
@@ -5,9 +5,9 @@ import { createImportedPins } from './geoImportPins.js';
import { msg } from '../i18n/marker.js'; import { msg } from '../i18n/marker.js';
/** Largest file accepted, in bytes. */ /** Largest file accepted, in bytes. */
export const MAX_IMPORT_BYTES = 50 * 1024 * 1024; const MAX_IMPORT_BYTES = 50 * 1024 * 1024;
/** Largest total a KMZ may inflate to — a zip bomb stops here. */ /** Largest total a KMZ may inflate to — a zip bomb stops here. */
export const MAX_KMZ_INFLATED_BYTES = 120 * 1024 * 1024; const MAX_KMZ_INFLATED_BYTES = 120 * 1024 * 1024;
/** Most files on the globe at once. */ /** Most files on the globe at once. */
export const MAX_IMPORTED_FILES = 12; export const MAX_IMPORTED_FILES = 12;
/** Most entities one file may create before it is refused. */ /** Most entities one file may create before it is refused. */
@@ -15,9 +15,9 @@ export const MAX_IMPORT_ENTITIES = 20_000;
/** Most vertices (points of every line, ring and marker) in one file. */ /** Most vertices (points of every line, ring and marker) in one file. */
export const MAX_IMPORT_POSITIONS = 250_000; export const MAX_IMPORT_POSITIONS = 250_000;
/** Most normalized style definitions and local image links in one KML/KMZ. */ /** Most normalized style definitions and local image links in one KML/KMZ. */
export const MAX_KML_LINKS = 2_000; const MAX_KML_LINKS = 2_000;
/** Longest plain-text name kept from a file's features. */ /** Longest plain-text name kept from a file's features. */
export const MAX_IMPORT_NAME_LENGTH = 120; const MAX_IMPORT_NAME_LENGTH = 120;
/** What the file picker offers. */ /** What the file picker offers. */
export const GEO_FILE_ACCEPT = '.geojson,.json,.kml,.kmz,.gpx'; export const GEO_FILE_ACCEPT = '.geojson,.json,.kml,.kmz,.gpx';
+1 -1
View File
@@ -1,5 +1,5 @@
// Inspect encoded headers before allowing the browser to decode local images. // Inspect encoded headers before allowing the browser to decode local images.
export const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024; const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024;
const MAX_SIDE = 8192; const MAX_SIDE = 8192;
/** Width and height from a supported raster header; no image is decoded. */ /** Width and height from a supported raster header; no image is decoded. */
+2 -3
View File
@@ -35,14 +35,14 @@
import { PbfReader } from 'pbf'; import { PbfReader } from 'pbf';
/** VehiclePosition.VehicleStopStatus enum → label. */ /** VehiclePosition.VehicleStopStatus enum → label. */
export const VEHICLE_STOP_STATUS = Object.freeze({ const VEHICLE_STOP_STATUS = Object.freeze({
0: 'INCOMING_AT', 0: 'INCOMING_AT',
1: 'STOPPED_AT', 1: 'STOPPED_AT',
2: 'IN_TRANSIT_TO', 2: 'IN_TRANSIT_TO',
}); });
/** VehiclePosition.OccupancyStatus enum → label. */ /** VehiclePosition.OccupancyStatus enum → label. */
export const OCCUPANCY_STATUS = Object.freeze({ const OCCUPANCY_STATUS = Object.freeze({
0: 'EMPTY', 0: 'EMPTY',
1: 'MANY_SEATS_AVAILABLE', 1: 'MANY_SEATS_AVAILABLE',
2: 'FEW_SEATS_AVAILABLE', 2: 'FEW_SEATS_AVAILABLE',
@@ -64,7 +64,6 @@ export const GTFS_MAX_ENTITIES = 50_000;
export const GTFS_MAX_STRING_CHARS = 256; export const GTFS_MAX_STRING_CHARS = 256;
/** FeedHeader.Incrementality: only FULL_DATASET carries a complete snapshot. */ /** FeedHeader.Incrementality: only FULL_DATASET carries a complete snapshot. */
export const GTFS_INCREMENTALITY_FULL_DATASET = 0; export const GTFS_INCREMENTALITY_FULL_DATASET = 0;
export const GTFS_INCREMENTALITY_DIFFERENTIAL = 1;
/** /**
* Read a length-delimited string, refusing one that is implausibly long. * Read a length-delimited string, refusing one that is implausibly long.
-20
View File
@@ -157,26 +157,6 @@ function trackingIdOption(key, token, defaultValue = null) {
}); });
} }
function stringOption(key, token, defaultValue) {
return Object.freeze({
key,
token,
defaultValue,
normalize: (value) => {
if (typeof value === 'number' && Number.isFinite(value))
return String(value).trim().toLowerCase();
if (typeof value !== 'string') return null;
const normalized = value.trim().toLowerCase();
return normalized ? normalized : null;
},
encode: (value) => String(value),
decode: (value) =>
typeof value === 'string' && value.trim()
? value.trim().toLowerCase()
: null,
});
}
function enumOption( function enumOption(
key, key,
token, token,
-1
View File
@@ -1,5 +1,4 @@
import { localGeoJsonServices } from '../app/localGeojsonServices.js'; import { localGeoJsonServices } from '../app/localGeojsonServices.js';
export { localGeoJsonServices } from '../app/localGeojsonServices.js';
import { import {
createLocalGeoJsonLayer as createLayer, createLocalGeoJsonLayer as createLayer,
createLocalInfrastructureOverlayPublisher as createPublisher, createLocalInfrastructureOverlayPublisher as createPublisher,
-18
View File
@@ -1,18 +0,0 @@
import { localGeoJsonServices } from './localGeojson.js';
import { createInfrastructureLayers } from './infrastructure.js';
import { createFirmsHeatmapLayer } from './firmsHeatmap.js';
import submarineCablesLayer from './telegeographySubmarineCables.js';
const [datacenters, dams] = createInfrastructureLayers(localGeoJsonServices);
// Live NASA FIRMS fires (VIIRS ×3 NRT via the /api/firms proxy). The id keeps
// the historical `local-` prefix for persistence + voice-tool-enum compat,
// but the data is NOT bundled anymore — it needs FIRMS_MAP_KEY server-side.
const fires = createFirmsHeatmapLayer({
id: 'local-firms',
name: 'FIRMS Active Fires',
icon: '▲',
source: 'NASA FIRMS · LIVE',
});
export default [datacenters, dams, submarineCablesLayer, fires];
+1 -2
View File
@@ -14,5 +14,4 @@ this derived database.
The public-release snapshot removes contact-oriented fields and note values The public-release snapshot removes contact-oriented fields and note values
that contain email or phone identifiers. The runtime layer does not display or that contain email or phone identifiers. The runtime layer does not display or
depend on those fields. Both `dams.geojson` and the runtime JSONL form carry the depend on those fields.
same privacy transform.
-1
View File
@@ -1 +0,0 @@
{"type":"FeatureCollection","features":[{"id":-19953989,"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[14.292076717,46.16897504],[14.292076711,46.168976415],[14.292080818,46.169315044],[14.292082791,46.169327497],[14.292088326,46.169339424],[14.292097199,46.169350341],[14.292109051,46.169359805],[14.2921234,46.169367433],[14.292139665,46.169372916],[14.292454909,46.16945313],[14.292472725,46.169456276],[14.292491094,46.169456843],[14.292509244,46.169454808],[14.293490178,46.169271915],[14.293505685,46.169267932],[14.293519874,46.169262048],[14.293532266,46.169254461],[14.293542439,46.16924543],[14.293550049,46.169235261],[14.293554837,46.169224298],[14.293556643,46.169212912],[14.293555403,46.16920149],[14.293552972,46.169191516],[14.293548058,46.169179216],[14.293539623,46.169167869],[14.293528021,46.169157953],[14.293513742,46.169149885],[14.293497386,46.169144005],[14.293479642,46.16914056],[14.29260928,46.169036741],[14.292221224,46.168859348],[14.292205414,46.168853594],[14.292188274,46.16885012],[14.29217048,46.168849063],[14.292152735,46.168850464],[14.292135742,46.168854269],[14.292120171,46.168860327],[14.292106638,46.168868398],[14.292095679,46.168878164],[14.292087726,46.168889239],[14.292083093,46.168901184],[14.292078818,46.168919205],[14.292077639,46.168928437],[14.292076717,46.16897504]]]},"properties":{"name":"HE Podlubnik - Centrala","tags":{"name":"HE Podlubnik - Centrala","type":"site","power":"plant","alt_name":"HE Škofja Loka","operator":"Elektro Gorenjska","waterway":"dam","start_date":"1969","plant:method":"run-of-the-river","plant:source":"hydro","plant:output:electricity":"330KW"},"osm_id":-19953989,"output":"330KW","source":"hydro","construction":"","source_layer":"power_plant"}},{"id":-19754263,"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[69.715342348,34.641127032],[69.71550043,34.641135916],[69.715570922,34.64110658],[69.717521056,34.641462978],[69.71812514,34.641577224],[69.718373076,34.641638579],[69.718438371,34.641451327],[69.718438036,34.641377064],[69.718220274,34.641368766],[69.718144586,34.64121412],[69.718014247,34.640948246],[69.717587357,34.640673654],[69.71740899,34.64063493],[69.717223833,34.640595284],[69.716953182,34.640548261],[69.716766517,34.640531246],[69.716362928,34.640542142],[69.716212976,34.640950425],[69.715856829,34.640920669],[69.715476123,34.640943636],[69.715342348,34.641127032]]]},"properties":{"name":"نغلو برېښناکوټ","tags":{"name":"نغلو برېښناکوټ","type":"multipolygon","power":"plant","name:de":"Naghlu-Staudamm","name:en":"Naghlu Dam","name:ja":"ナグルーダム","name:no":"Naghludemningen","name:ru":"ГЭС Наглу","name:uk":"ГЕС Наглу","waterway":"dam","wikidata":"Q4755783","wikipedia":"en:Naghlu Dam","start_date":"1967","plant:source":"hydro","plant:output:electricity":"100 MW"},"osm_id":-19754263,"output":"100 MW","source":"hydro","construction":"","source_layer":"power_plant"}},{"id":-19685440,"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-54.597119846,-25.41383667],[-54.597234845,-25.413703733],[-54.597107524,-25.413586135],[-54.59730056,-25.412625569],[-54.597341379,-25.412122152],[-54.597361664,-25.411634241],[-54.597330064,-25.410920019],[-54.597191679,-25.410193224],[-54.597057568,-25.409737668],[-54.59698146,-25.409498365],[-54.596806446,-25.409085472],[-54.596524311,-25.408510306],[-54.596161124,-25.407928853],[-54.59586826,-25.40753591],[-54.595651001,-25.407280513],[-54.595510101,-25.407146654],[-54.595350007,-25.407013549],[-54.595191002,-25.406910368],[-54.595019173,-25.406815317],[-54.594639221,-25.40665405],[-54.594135721,-25.406544833],[-54.593702209,-25.406530333],[-54.593262159,-25.406579954],[-54.592841471,-25.406631754],[-54.592785815,-25.406430001],[-54.588964338,-25.407187725],[-54.584364349,-25.408041757],[-54.584397877,-25.408225908],[-54.58083188,-25.40887408],[-54.580662817,-25.408794033],[-54.578582764,-25.409343216],[-54.577176783,-25.409921902],[-54.574835215,-25.411356968],[-54.573420266,-25.412660605],[-54.56995393,-25.41756251],[-54.565822741,-25.423818762],[-54.564863516,-25.426166114],[-54.561372454,-25.436599237],[-54.55977612,-25.441166117],[-54.559956834,-25.441255635],[-54.560411552,-25.441039718],[-54.561530369,-25.438307217],[-54.56325352,-25.43362584],[-54.564224731,-25.430147434],[-54.564559924,-25.42936314],[-54.564880364,-25.429084861],[-54.565817042,-25.428049779],[-54.566482565,-25.427226006],[-54.567271218,-25.426135353],[-54.568109324,-25.425411827],[-54.569107525,-25.423653136],[-54.568448121,-25.422704724],[-54.568378384,-25.422578744],[-54.568555074,-25.42190861],[-54.568801502,-25.421307544],[-54.569317157,-25.42054873],[-54.570266659,-25.419124226],[-54.57099622,-25.417694944],[-54.571731145,-25.416653241],[-54.572525079,-25.415601815],[-54.573302919,-25.414637645],[-54.573759146,-25.414053929],[-54.574652741,-25.413023961],[-54.574928338,-25.412760769],[-54.575345505,-25.412317702],[-54.57570995,-25.4Line truncated
-1
View File
@@ -1,7 +1,6 @@
import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import { readLayerSource } from '../testSupport/readLayerSource.mjs';
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import fs from 'node:fs';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import militaryAwarenessLayer, { import militaryAwarenessLayer, {
_getAwarenessNavigationStateForTest, _getAwarenessNavigationStateForTest,
-1
View File
@@ -798,7 +798,6 @@ test('zoom-out aborts an active installation request and returns non-loading gui
// by source probes against the shipped file, the same technique the HUD datum // by source probes against the shipped file, the same technique the HUD datum
// tests use where a full boot is impractical. // tests use where a full boot is impractical.
import { installationRetryDelayMs } from './militaryInstallations.js'; import { installationRetryDelayMs } from './militaryInstallations.js';
import fs from 'node:fs';
const installationsSource = readLayerSource( const installationsSource = readLayerSource(
new URL('./militaryInstallations.js', import.meta.url), 'utf8'); new URL('./militaryInstallations.js', import.meta.url), 'utf8');
+1 -1
View File
@@ -7,7 +7,7 @@ import { createRetryableLoader } from './retryableLoad.js';
export const MILITARY_POINT_CAP = 512; export const MILITARY_POINT_CAP = 512;
export const MILITARY_LABEL_CAP = 24; export const MILITARY_LABEL_CAP = 24;
export const MILITARY_NAMES_TIMEOUT_MS = 10_000; const MILITARY_NAMES_TIMEOUT_MS = 10_000;
/** Share a bounded acquisition; failed assets become retryable after cooldown. */ /** Share a bounded acquisition; failed assets become retryable after cooldown. */
export function createMilitaryNamesLoader({ export function createMilitaryNamesLoader({
-24
View File
@@ -49,34 +49,12 @@ export const buildRadioTunerBand = layer.buildRadioTunerBand;
export const radioTuningStaticShouldPlay = layer.radioTuningStaticShouldPlay; export const radioTuningStaticShouldPlay = layer.radioTuningStaticShouldPlay;
export const getRadioAcceptedCatalogSnapshot = export const getRadioAcceptedCatalogSnapshot =
layer.getRadioAcceptedCatalogSnapshot; layer.getRadioAcceptedCatalogSnapshot;
export const radioStationResolutionMatches =
layer.radioStationResolutionMatches;
export const getRadioUIState = layer.getRadioUIState; export const getRadioUIState = layer.getRadioUIState;
export const subscribeToRadio = layer.subscribeToRadio;
export const subscribeToRadioPlaybackControls =
layer.subscribeToRadioPlaybackControls;
export const getRadioTunerStations = layer.getRadioTunerStations;
export const beginRadioTuning = layer.beginRadioTuning;
export const setRadioTuningStatic = layer.setRadioTuningStatic;
export const radioCameraNavigationAllowed = layer.radioCameraNavigationAllowed; export const radioCameraNavigationAllowed = layer.radioCameraNavigationAllowed;
export const previewRadioTuningStation = layer.previewRadioTuningStation;
export const endRadioTuning = layer.endRadioTuning;
export const cancelRadioTuning = layer.cancelRadioTuning;
export const commitRadioTuningStation = layer.commitRadioTuningStation;
export const playSelectedRadio = layer.playSelectedRadio;
export const confirmRadioPlayback = layer.confirmRadioPlayback; export const confirmRadioPlayback = layer.confirmRadioPlayback;
export const playPreparedRadioForVoice = layer.playPreparedRadioForVoice;
export const stopRadioPlayback = layer.stopRadioPlayback;
export const toggleRadioPlayback = layer.toggleRadioPlayback;
export const pauseRadioPlayback = layer.pauseRadioPlayback;
export const setRadioVolume = layer.setRadioVolume; export const setRadioVolume = layer.setRadioVolume;
export const setRadioParams = layer.setRadioParams; export const setRadioParams = layer.setRadioParams;
export const getRadioParams = layer.getRadioParams;
export const setRadioVoiceDucking = layer.setRadioVoiceDucking; export const setRadioVoiceDucking = layer.setRadioVoiceDucking;
export const selectRadioStation = layer.selectRadioStation;
export const cycleRadioStation = layer.cycleRadioStation;
export const selectRequestedRadioStation = layer.selectRequestedRadioStation;
export const setRadioFilter = layer.setRadioFilter;
export const retainRadioClusterIdentitiesForStations = export const retainRadioClusterIdentitiesForStations =
layer.retainRadioClusterIdentitiesForStations; layer.retainRadioClusterIdentitiesForStations;
export { export {
@@ -86,10 +64,8 @@ export {
RADIO_SINGLETON_MID_LIMIT, RADIO_SINGLETON_MID_LIMIT,
RADIO_SINGLETON_NEAR_LIMIT, RADIO_SINGLETON_NEAR_LIMIT,
RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M, RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M,
RADIO_GLOBE_RECENTER_MAX_HEIGHT_M,
RADIO_OVERLAY_SOURCE_OPTIONS, RADIO_OVERLAY_SOURCE_OPTIONS,
DEFAULT_RADIO_FILTER, DEFAULT_RADIO_FILTER,
GLOBAL_RADIO_ALTITUDE_M,
} from '../layers/radio/index.js'; } from '../layers/radio/index.js';
export default layer; export default layer;
+1 -3
View File
@@ -1779,7 +1779,6 @@ test('voice playback confirmation waits for playing and requires a hard duck', a
}); });
test('voice playback confirmation accepts fallback buffering but times out safely', async () => { test('voice playback confirmation accepts fallback buffering but times out safely', async () => {
let listener = () => {};
const state = { const state = {
audioState: 'loading', audioState: 'loading',
playingStationId: 'fallback', playingStationId: 'fallback',
@@ -1789,9 +1788,8 @@ test('voice playback confirmation accepts fallback buffering but times out safel
const confirmed = await confirmRadioPlayback({ const confirmed = await confirmRadioPlayback({
startPlayback: async () => false, startPlayback: async () => false,
subscribe: (next) => { subscribe: (next) => {
listener = next;
next(state); next(state);
return () => { listener = () => {}; }; return () => {};
}, },
getState: () => state, getState: () => state,
timeoutMs: 10, timeoutMs: 10,
-5
View File
@@ -117,8 +117,3 @@ export function normalizeRadioCountryInput(value) {
name: canonicalCountryName(code), name: canonicalCountryName(code),
}); });
} }
/** Return whether a value is a recognized ISO 3166-1 alpha-2 code. */
export function isRadioCountryCode(value) {
return typeof value === 'string' && ISO_ALPHA_2_SET.has(value.toUpperCase());
}
-1
View File
@@ -28,4 +28,3 @@ export {
createCableReferenceSweepGate, createCableReferenceSweepGate,
updateCableReferenceStem, updateCableReferenceStem,
} from '../layers/submarineCables/index.js'; } from '../layers/submarineCables/index.js';
export default createTeleGeographySubmarineCableLayer();
-2
View File
@@ -930,7 +930,6 @@ for (const fixture of LAYERS) {
} }
test('both layers gate the tracked-model load and record its failures', async () => { test('both layers gate the tracked-model load and record its failures', async () => {
const { readFile } = await import('node:fs/promises');
for (const name of ['flights.js', 'militaryFlights.js']) { for (const name of ['flights.js', 'militaryFlights.js']) {
const source = readLayerSource(new URL(`./${name}`, import.meta.url)); const source = readLayerSource(new URL(`./${name}`, import.meta.url));
assert.match(source, /!(?:flightState\.)?_trackedModel\s*&&\s*!(?:flightState\.)?_trackedModelLoading\s*&&\s*(?:parts\.\w+\.)?_trackedModelLoadAllowed\(\s*,?\s*\)/, assert.match(source, /!(?:flightState\.)?_trackedModel\s*&&\s*!(?:flightState\.)?_trackedModelLoading\s*&&\s*(?:parts\.\w+\.)?_trackedModelLoadAllowed\(\s*,?\s*\)/,
@@ -946,7 +945,6 @@ test('both layers gate the tracked-model load and record its failures', async ()
}); });
test('the tracked regime never consults the fleet models3d toggle', async () => { test('the tracked regime never consults the fleet models3d toggle', async () => {
const { readFile } = await import('node:fs/promises');
for (const name of ['flights.js', 'militaryFlights.js']) { for (const name of ['flights.js', 'militaryFlights.js']) {
const source = readLayerSource(new URL(`./${name}`, import.meta.url)); const source = readLayerSource(new URL(`./${name}`, import.meta.url));
const regime = /^([ \t]*)function _trackedModelRegimeActive\b[\s\S]*?\n\1\}/m.exec(source)?.[0]; const regime = /^([ \t]*)function _trackedModelRegimeActive\b[\s\S]*?\n\1\}/m.exec(source)?.[0];
-1
View File
@@ -1,7 +1,6 @@
import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import { readLayerSource } from '../testSupport/readLayerSource.mjs';
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { import {
bindTrackingClickGesture, bindTrackingClickGesture,
isTrackingClickGesture, isTrackingClickGesture,
-8
View File
@@ -1,5 +1,4 @@
import { createSourceSlot } from '../sources/sourceSlot.js'; import { createSourceSlot } from '../sources/sourceSlot.js';
import { createWindLayer as createLayer } from '../layers/wind/index.js';
import { createWindSource } from '../layers/wind/source.js'; import { createWindSource } from '../layers/wind/source.js';
const sourceSlot = createSourceSlot( const sourceSlot = createSourceSlot(
@@ -8,10 +7,3 @@ const sourceSlot = createSourceSlot(
'Wind source', 'Wind source',
); );
export const configureWindSource = sourceSlot.configure; export const configureWindSource = sourceSlot.configure;
/** Create a wind layer using the configured application source by default. */
export function createWindLayer(options = {}) {
return createLayer({ ...options, feed: options.feed ?? sourceSlot.source });
}
export default createWindLayer();
-4
View File
@@ -373,10 +373,6 @@ test('one ESC does one thing — the radio disclosure stops the launcher outrigh
new URL('./ui/radioBindings.js', import.meta.url), new URL('./ui/radioBindings.js', import.meta.url),
'utf8', 'utf8',
); );
const module = fs.readFileSync(
new URL('./firstRunExperience.js', import.meta.url),
'utf8',
);
// stopPropagation() does NOT stop later listeners on the SAME document, so the // stopPropagation() does NOT stop later listeners on the SAME document, so the
// disclosure's earlier capture handler closed the disclosure and the launcher // disclosure's earlier capture handler closed the disclosure and the launcher
+1 -1
View File
@@ -210,7 +210,7 @@ export function formatRelativeTime(value, unit = 'second', options) {
} }
/** Collapse markup whitespace so a message reads the same in HTML and JS. */ /** Collapse markup whitespace so a message reads the same in HTML and JS. */
export function normalizeMarkupText(text) { function normalizeMarkupText(text) {
return String(text ?? '') return String(text ?? '')
.replace(/\s+/g, ' ') .replace(/\s+/g, ' ')
.trim(); .trim();
-1
View File
@@ -1,7 +1,6 @@
import { readStylesheet } from './testSupport/readStylesheet.mjs'; import { readStylesheet } from './testSupport/readStylesheet.mjs';
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const css = readStylesheet(new URL('../style.css', import.meta.url)); const css = readStylesheet(new URL('../style.css', import.meta.url));
-6
View File
@@ -3,16 +3,12 @@ import * as Cesium from 'cesium';
import { import {
LAYER_ID, LAYER_ID,
REQUEST_DEBOUNCE_MS, REQUEST_DEBOUNCE_MS,
QUERY_LIMIT,
MAX_RENDERED,
QUERY_SNAP_DEGREES,
QUERY_REUSE_MS, QUERY_REUSE_MS,
ALPR_COLOR, ALPR_COLOR,
} from './policy.js'; } from './policy.js';
import { import {
snapAlprBox, snapAlprBox,
boxContains, boxContains,
destinationPointDeg,
alprRetryDelayMs, alprRetryDelayMs,
validateAlprSnapshot, validateAlprSnapshot,
alprCreditMarkup, alprCreditMarkup,
@@ -84,9 +80,7 @@ export function createAlprCamerasLayer({ source, services } = {}) {
viewportBox, viewportBox,
clearRendered, clearRendered,
hideOnMapCredit, hideOnMapCredit,
presentOnMapCredit,
renderRecords, renderRecords,
selectRecord,
focusNearest, focusNearest,
clearSelection, clearSelection,
updateSelectedAnchor, updateSelectedAnchor,
+3 -3
View File
@@ -1,11 +1,11 @@
import { QUERY_LIMIT, QUERY_SNAP_DEGREES } from './policy.js'; import { QUERY_LIMIT, QUERY_SNAP_DEGREES } from './policy.js';
export function textTag(value) { function textTag(value) {
const t = String(value ?? '').trim(); const t = String(value ?? '').trim();
return t || null; return t || null;
} }
export function numTag(value) { function numTag(value) {
// `Number('')` and `Number(' ')` are 0, not NaN — a blank or whitespace-only // `Number('')` and `Number(' ')` are 0, not NaN — a blank or whitespace-only
// tag value must read as missing, never as a due-north bearing. // tag value must read as missing, never as a due-north bearing.
if (value == null) return null; if (value == null) return null;
@@ -102,7 +102,7 @@ export function normalizeAlprNode(el) {
}; };
} }
export function normalizeDirection(value) { function normalizeDirection(value) {
const degrees = numTag(value); const degrees = numTag(value);
return degrees != null && degrees >= 0 && degrees <= 360 return degrees != null && degrees >= 0 && degrees <= 360
? degrees % 360 ? degrees % 360
+1 -1
View File
@@ -3,7 +3,7 @@ import { VectorTile } from '@mapbox/vector-tile';
import { normalizeAlprNode } from './records.js'; import { normalizeAlprNode } from './records.js';
/** Map extract attributes without treating an OSM edit date as a field verification. */ /** Map extract attributes without treating an OSM edit date as a field verification. */
export function normalizeAlprTileFeature(feature) { function normalizeAlprTileFeature(feature) {
const p = feature?.properties || {}; const p = feature?.properties || {};
if (feature?.geometry?.type !== 'Point' || p.osmType !== 'node') return null; if (feature?.geometry?.type !== 'Point' || p.osmType !== 'node') return null;
const [lon, lat] = feature.geometry.coordinates; const [lon, lat] = feature.geometry.coordinates;
+1 -4
View File
@@ -1,9 +1,6 @@
export const AIRCRAFT_DEPENDENCIES = ['flights', 'military']; export const AIRCRAFT_DEPENDENCIES = ['flights', 'military'];
export const DEFERRED_DEPENDENCIES = [ const DEFERRED_DEPENDENCIES = ['ais-live-vessels', 'military-installations'];
'ais-live-vessels',
'military-installations',
];
export const DEPENDENCIES = [ export const DEPENDENCIES = [
...AIRCRAFT_DEPENDENCIES, ...AIRCRAFT_DEPENDENCIES,
@@ -60,7 +60,7 @@ test('the layer reports failure when a failed city left it with nothing', () =>
}); });
test('a layer with nothing to draw and nothing failed is not an error', () => { test('a layer with nothing to draw and nothing failed is not an error', () => {
const { state, methods } = makeLayer(); const { methods } = makeLayer();
// Out of range of every city is empty, not broken. // Out of range of every city is empty, not broken.
assert.equal(methods.getStats().error, undefined); assert.equal(methods.getStats().error, undefined);
+1 -1
View File
@@ -11,7 +11,7 @@ export const BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS = Object.freeze({
// --- Activation / display thresholds --- // --- Activation / display thresholds ---
/** Altitude (m) at which bikeshare layer becomes eligible for display. */ /** Altitude (m) at which bikeshare layer becomes eligible for display. */
export const ACTIVATION_ALTITUDE_M = 50000; const ACTIVATION_ALTITUDE_M = 50000;
/** Hysteresis enter threshold — layer activates when camera drops below this. */ /** Hysteresis enter threshold — layer activates when camera drops below this. */
+1 -1
View File
@@ -1,4 +1,4 @@
import { CAMERA_SEEDS, SOURCE_ENDPOINT } from './policy.js'; import { CAMERA_SEEDS } from './policy.js';
import { import {
lookupModelSpec, lookupModelSpec,
horizontalFovRangeDeg, horizontalFovRangeDeg,
-3
View File
@@ -5,9 +5,6 @@ import {
PROJECTION_CANVAS_HEIGHT, PROJECTION_CANVAS_HEIGHT,
PROJECTION_MATTE_COLOR, PROJECTION_MATTE_COLOR,
PROJECTION_TEXTURE_SWAP_MS, PROJECTION_TEXTURE_SWAP_MS,
ACTIVE_FRAME_REFRESH_MS,
FRAME_ENDPOINT,
MEDIA_ENDPOINT,
PROJECTION_ACTIVE_REFRESH_MS, PROJECTION_ACTIVE_REFRESH_MS,
PROJECTION_IDLE_REFRESH_MS, PROJECTION_IDLE_REFRESH_MS,
PLACEHOLDER_REPAINT_MS, PLACEHOLDER_REPAINT_MS,
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more