mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Merge pull request #492 from bilawalsidhu/refactor/construct-application-layers
refactor: construct application layers from supplied sources
This commit is contained in:
53 files changed
+1146
-394
No files matched your search
@@ -91,6 +91,8 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Construct application layers from explicit sources, with standalone provider selection and catalog-owned aircraft classification; controls and voice queries use those instances.
|
||||
|
||||
- Let application data and controls receive the same explicit layer catalog; keep standalone defaults and registration-before-restoration ordering.
|
||||
|
||||
- Expose existing FIRMS CSV parsing and UTC time-window helpers through a portable package export, with shared contract fixtures.
|
||||
|
||||
@@ -409,3 +409,24 @@ that schema requires a corresponding codec change.
|
||||
`standalone/catalog` selects the existing page-scoped default instances and metadata.
|
||||
Reusable data setup imports no standalone layer defaults. The current compatibility
|
||||
source setters remain available while callers migrate to instance construction.
|
||||
|
||||
### Layer construction
|
||||
|
||||
`application/layers` constructs the current catalog from explicit source objects
|
||||
and an application AbortSignal. Small `src/app/layers` modules wire existing scene
|
||||
services into each family factory. Standalone provider selection lives in
|
||||
`src/standalone/layerSources.js`. The construction export has its own checked
|
||||
dependency graph, excluding standalone setup and compatibility layer instances.
|
||||
|
||||
Both aircraft layers share the catalog's classification registry; launches use
|
||||
its satellites and Contacts uses its aircraft, vessels and installations. Data
|
||||
registration, controls and voice actions read those same instances. Destruction
|
||||
remains the manager's responsibility; classification also observes application
|
||||
abort when startup has not reached registration. Scene engines remain page-owned,
|
||||
so this change does not introduce multiple simultaneous viewers.
|
||||
|
||||
Direct `src/data` compatibility entries retain their old defaults and testing
|
||||
exports. Browser regression probes use the registered instance's testing surface
|
||||
to avoid accidentally inspecting an unused compatibility instance. Existing source
|
||||
setters apply only to compatibility instances; the normal application supplies
|
||||
its sources at construction.
|
||||
@@ -1,5 +1,13 @@
|
||||
# God's Eye View Current State
|
||||
|
||||
Application startup constructs fresh layer instances from explicit source objects.
|
||||
Standalone composition selects the existing providers. Controls, launch orbit
|
||||
lookups, Contacts voice answers and ISS pass queries use the registered catalog.
|
||||
Aircraft classification is catalog-owned and cancelled with application teardown;
|
||||
scene engines still support one application per page. Compatibility data entrypoints
|
||||
remain for direct callers, but normal startup does not configure global sources.
|
||||
|
||||
|
||||
Application data setup receives an ordered layer catalog and serialization metadata.
|
||||
Controls use the same instances. The standalone composition selects the existing
|
||||
default catalog, and restoration starts only after all registrations are sealed.
|
||||
|
||||
+2
-1
@@ -193,6 +193,7 @@
|
||||
"node": "./build/application-html.js"
|
||||
},
|
||||
"./application/catalog": "./src/app/catalog.js",
|
||||
"./standalone/catalog": "./src/standalone/catalog.js"
|
||||
"./standalone/catalog": "./src/standalone/catalog.js",
|
||||
"./application/layers": "./src/app/constructCatalog.js"
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,8 @@ if [[ ! -f "$SOURCE_ROOT/src/data/cctv.js" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q "^[[:space:]]*cctvLayer," "$SOURCE_ROOT/src/standalone/catalog.js"; then
|
||||
if ! grep -q "return createApplicationCatalog(" "$SOURCE_ROOT/src/standalone/catalog.js" || \
|
||||
! grep -q "^[[:space:]]*createApplicationCctv({" "$SOURCE_ROOT/src/app/constructCatalog.js"; then
|
||||
echo "error: CCTV layer not wired in src/standalone/catalog.js"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -558,5 +558,24 @@
|
||||
"src/toolProjectRoot.test.mjs",
|
||||
"src/app/catalog.js",
|
||||
"src/standalone/catalog.js",
|
||||
"src/app/catalog.test.mjs"
|
||||
"src/app/catalog.test.mjs",
|
||||
"src/app/layers/militaryInstallations.js",
|
||||
"src/app/layers/alprCameras.js",
|
||||
"src/app/layers/traffic.js",
|
||||
"src/app/layers/submarineCables.js",
|
||||
"src/app/layers/militaryFlights.js",
|
||||
"src/app/layers/bikeshare.js",
|
||||
"src/app/layers/flights.js",
|
||||
"src/app/layers/radio.js",
|
||||
"src/app/layers/earthquakes.js",
|
||||
"src/app/layers/cctv.js",
|
||||
"src/app/layers/firms.js",
|
||||
"src/app/layers/satellites.js",
|
||||
"src/app/layers/militaryAwareness.js",
|
||||
"src/app/layers/rocketLaunches.js",
|
||||
"src/app/layers/overlayHost.js",
|
||||
"src/app/layers/aisLiveVessels.js",
|
||||
"src/app/constructCatalog.js",
|
||||
"src/standalone/layerSources.js",
|
||||
"src/app/constructCatalog.test.mjs"
|
||||
]
|
||||
@@ -1503,7 +1503,25 @@
|
||||
"src/weatherEffectsMath.js",
|
||||
"src/worldFocus.js",
|
||||
"src/app/catalog.js",
|
||||
"src/standalone/catalog.js"
|
||||
"src/standalone/catalog.js",
|
||||
"src/app/layers/militaryInstallations.js",
|
||||
"src/app/layers/alprCameras.js",
|
||||
"src/app/layers/traffic.js",
|
||||
"src/app/layers/submarineCables.js",
|
||||
"src/app/layers/militaryFlights.js",
|
||||
"src/app/layers/bikeshare.js",
|
||||
"src/app/layers/flights.js",
|
||||
"src/app/layers/radio.js",
|
||||
"src/app/layers/earthquakes.js",
|
||||
"src/app/layers/cctv.js",
|
||||
"src/app/layers/firms.js",
|
||||
"src/app/layers/satellites.js",
|
||||
"src/app/layers/militaryAwareness.js",
|
||||
"src/app/layers/rocketLaunches.js",
|
||||
"src/app/layers/overlayHost.js",
|
||||
"src/app/layers/aisLiveVessels.js",
|
||||
"src/app/constructCatalog.js",
|
||||
"src/standalone/layerSources.js"
|
||||
],
|
||||
"external": [
|
||||
"@mapbox/vector-tile",
|
||||
@@ -1518,5 +1536,328 @@
|
||||
"exports": ["./application/catalog"],
|
||||
"modules": ["src/app/catalog.js"],
|
||||
"external": []
|
||||
},
|
||||
"application-layer-construction": {
|
||||
"exports": ["./application/layers"],
|
||||
"modules": [
|
||||
"src/annotations/annotationResolver.js",
|
||||
"src/app/catalog.js",
|
||||
"src/app/constructCatalog.js",
|
||||
"src/app/layers/aisLiveVessels.js",
|
||||
"src/app/layers/alprCameras.js",
|
||||
"src/app/layers/bikeshare.js",
|
||||
"src/app/layers/cctv.js",
|
||||
"src/app/layers/earthquakes.js",
|
||||
"src/app/layers/firms.js",
|
||||
"src/app/layers/flights.js",
|
||||
"src/app/layers/militaryAwareness.js",
|
||||
"src/app/layers/militaryFlights.js",
|
||||
"src/app/layers/militaryInstallations.js",
|
||||
"src/app/layers/overlayHost.js",
|
||||
"src/app/layers/radio.js",
|
||||
"src/app/layers/rocketLaunches.js",
|
||||
"src/app/layers/satellites.js",
|
||||
"src/app/layers/submarineCables.js",
|
||||
"src/app/layers/traffic.js",
|
||||
"src/app/sourceSlot.js",
|
||||
"src/cctvFocusRequest.js",
|
||||
"src/celestialRing.js",
|
||||
"src/cockpitMath.js",
|
||||
"src/data/aircraftClass.js",
|
||||
"src/data/aircraftIcons.js",
|
||||
"src/data/aircraftMeta.js",
|
||||
"src/data/aircraftNearbyPolicy.js",
|
||||
"src/data/aircraftRecession.js",
|
||||
"src/data/cctvCards.js",
|
||||
"src/data/cctvFootprint.js",
|
||||
"src/data/cctvGizmo.js",
|
||||
"src/data/cctvLod.js",
|
||||
"src/data/cctvViewshed.js",
|
||||
"src/data/cockpitAirLod.js",
|
||||
"src/data/cockpitContactDot.js",
|
||||
"src/data/contactMatch.js",
|
||||
"src/data/contextStore.js",
|
||||
"src/data/dataCredits.js",
|
||||
"src/data/detectionCohort.js",
|
||||
"src/data/detectionDraw.js",
|
||||
"src/data/detectionPolicy.js",
|
||||
"src/data/fireAnchors.js",
|
||||
"src/data/firmsAdapt.js",
|
||||
"src/data/firmsLabels.js",
|
||||
"src/data/flowMatch.js",
|
||||
"src/data/focusDeemphasis.js",
|
||||
"src/data/geoid.js",
|
||||
"src/data/groundFloor.js",
|
||||
"src/data/groundSnap.js",
|
||||
"src/data/iconOrientation.js",
|
||||
"src/data/infrastructure.js",
|
||||
"src/data/installationFeedback.js",
|
||||
"src/data/issPass.js",
|
||||
"src/data/labelArbiter.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/localGeojson.js",
|
||||
"src/data/localGeojsonCore.js",
|
||||
"src/data/localGeojsonLod.js",
|
||||
"src/data/local_data/natural_earth/marine.json",
|
||||
"src/data/local_data/natural_earth/regions.json",
|
||||
"src/data/local_data/neighborhoods/san-francisco.json",
|
||||
"src/data/meshFloorSampler.js",
|
||||
"src/data/militaryAwarenessEngine.js",
|
||||
"src/data/militaryInstallationData.js",
|
||||
"src/data/modelVisualAnchor.js",
|
||||
"src/data/motionModel.js",
|
||||
"src/data/naturalEarthRegions.js",
|
||||
"src/data/neighborhoodPolygons.js",
|
||||
"src/data/pickRegistry.js",
|
||||
"src/data/radioCountry.js",
|
||||
"src/data/renderAltitude.js",
|
||||
"src/data/retryableLoad.js",
|
||||
"src/data/routePlausible.js",
|
||||
"src/data/satelliteClass.js",
|
||||
"src/data/scenePick.js",
|
||||
"src/data/spriteOrder.js",
|
||||
"src/data/terrainHeights.js",
|
||||
"src/data/tomtomTiles.js",
|
||||
"src/data/tr3bRegistry.js",
|
||||
"src/data/trackedCamera.js",
|
||||
"src/data/trackedModelRegime.js",
|
||||
"src/data/trackedReadout.js",
|
||||
"src/data/trackingClickGesture.js",
|
||||
"src/data/trafficBounds.js",
|
||||
"src/data/trafficFlowStyle.js",
|
||||
"src/data/trafficPresetStyle.js",
|
||||
"src/data/trafficQueue.js",
|
||||
"src/data/trailRenderer.js",
|
||||
"src/data/vesselLabels.js",
|
||||
"src/keylessGeocoder.js",
|
||||
"src/layers/aircraft/classification.js",
|
||||
"src/layers/alpr/index.js",
|
||||
"src/layers/alpr/model.js",
|
||||
"src/layers/alpr/policy.js",
|
||||
"src/layers/alpr/presentation.js",
|
||||
"src/layers/alpr/source.js",
|
||||
"src/layers/awareness/controls.js",
|
||||
"src/layers/awareness/dependencies.js",
|
||||
"src/layers/awareness/focus.js",
|
||||
"src/layers/awareness/history.js",
|
||||
"src/layers/awareness/index.js",
|
||||
"src/layers/awareness/ingestion.js",
|
||||
"src/layers/awareness/lifecycle.js",
|
||||
"src/layers/awareness/model.js",
|
||||
"src/layers/awareness/navigation.js",
|
||||
"src/layers/awareness/panel.js",
|
||||
"src/layers/awareness/policy.js",
|
||||
"src/layers/awareness/queries.js",
|
||||
"src/layers/awareness/rendering.js",
|
||||
"src/layers/awareness/state.js",
|
||||
"src/layers/awareness/subject.js",
|
||||
"src/layers/awareness/testing.js",
|
||||
"src/layers/bikeshare/controls.js",
|
||||
"src/layers/bikeshare/index.js",
|
||||
"src/layers/bikeshare/ingestion.js",
|
||||
"src/layers/bikeshare/lifecycle.js",
|
||||
"src/layers/bikeshare/model.js",
|
||||
"src/layers/bikeshare/policy.js",
|
||||
"src/layers/bikeshare/queries.js",
|
||||
"src/layers/bikeshare/registry.js",
|
||||
"src/layers/bikeshare/rendering.js",
|
||||
"src/layers/bikeshare/selection.js",
|
||||
"src/layers/bikeshare/source.js",
|
||||
"src/layers/bikeshare/state.js",
|
||||
"src/layers/bikeshare/testing.js",
|
||||
"src/layers/bikeshare/viewport.js",
|
||||
"src/layers/cctv/calibration.js",
|
||||
"src/layers/cctv/cards.js",
|
||||
"src/layers/cctv/catalog.js",
|
||||
"src/layers/cctv/controls.js",
|
||||
"src/layers/cctv/frames.js",
|
||||
"src/layers/cctv/geometry.js",
|
||||
"src/layers/cctv/geometryQueue.js",
|
||||
"src/layers/cctv/ground.js",
|
||||
"src/layers/cctv/health.js",
|
||||
"src/layers/cctv/hover.js",
|
||||
"src/layers/cctv/index.js",
|
||||
"src/layers/cctv/ingestion.js",
|
||||
"src/layers/cctv/lifecycle.js",
|
||||
"src/layers/cctv/model.js",
|
||||
"src/layers/cctv/navigation.js",
|
||||
"src/layers/cctv/policy.js",
|
||||
"src/layers/cctv/presentation.js",
|
||||
"src/layers/cctv/projection.js",
|
||||
"src/layers/cctv/rendering.js",
|
||||
"src/layers/cctv/selection.js",
|
||||
"src/layers/cctv/source.js",
|
||||
"src/layers/cctv/state.js",
|
||||
"src/layers/cctv/testing.js",
|
||||
"src/layers/earthquakes/index.js",
|
||||
"src/layers/earthquakes/model.js",
|
||||
"src/layers/earthquakes/source.js",
|
||||
"src/layers/firms/anchors.js",
|
||||
"src/layers/firms/cards.js",
|
||||
"src/layers/firms/index.js",
|
||||
"src/layers/firms/ingestion.js",
|
||||
"src/layers/firms/lifecycle.js",
|
||||
"src/layers/firms/model.js",
|
||||
"src/layers/firms/policy.js",
|
||||
"src/layers/firms/queries.js",
|
||||
"src/layers/firms/rendering.js",
|
||||
"src/layers/firms/selection.js",
|
||||
"src/layers/firms/source.js",
|
||||
"src/layers/firms/state.js",
|
||||
"src/layers/firms/viewport.js",
|
||||
"src/layers/flights/controller.js",
|
||||
"src/layers/flights/enrichment.js",
|
||||
"src/layers/flights/evidence.js",
|
||||
"src/layers/flights/index.js",
|
||||
"src/layers/flights/ingestion.js",
|
||||
"src/layers/flights/lifecycle.js",
|
||||
"src/layers/flights/motion.js",
|
||||
"src/layers/flights/policy.js",
|
||||
"src/layers/flights/queries.js",
|
||||
"src/layers/flights/rendering.js",
|
||||
"src/layers/flights/state.js",
|
||||
"src/layers/flights/testing.js",
|
||||
"src/layers/flights/tracking.js",
|
||||
"src/layers/installations/controls.js",
|
||||
"src/layers/installations/index.js",
|
||||
"src/layers/installations/ingestion.js",
|
||||
"src/layers/installations/lifecycle.js",
|
||||
"src/layers/installations/model.js",
|
||||
"src/layers/installations/policy.js",
|
||||
"src/layers/installations/rendering.js",
|
||||
"src/layers/installations/selection.js",
|
||||
"src/layers/installations/source.js",
|
||||
"src/layers/installations/state.js",
|
||||
"src/layers/installations/viewport.js",
|
||||
"src/layers/launches/camera.js",
|
||||
"src/layers/launches/controls.js",
|
||||
"src/layers/launches/index.js",
|
||||
"src/layers/launches/ingestion.js",
|
||||
"src/layers/launches/launchPad.js",
|
||||
"src/layers/launches/lifecycle.js",
|
||||
"src/layers/launches/model.js",
|
||||
"src/layers/launches/orbitRendering.js",
|
||||
"src/layers/launches/overlays.js",
|
||||
"src/layers/launches/panel.js",
|
||||
"src/layers/launches/paths.js",
|
||||
"src/layers/launches/policy.js",
|
||||
"src/layers/launches/policyHelpers.js",
|
||||
"src/layers/launches/rendering.js",
|
||||
"src/layers/launches/replay.js",
|
||||
"src/layers/launches/selection.js",
|
||||
"src/layers/launches/source.js",
|
||||
"src/layers/launches/state.js",
|
||||
"src/layers/launches/testing.js",
|
||||
"src/layers/military/controller.js",
|
||||
"src/layers/military/index.js",
|
||||
"src/layers/military/ingestion.js",
|
||||
"src/layers/military/lifecycle.js",
|
||||
"src/layers/military/motion.js",
|
||||
"src/layers/military/policy.js",
|
||||
"src/layers/military/queries.js",
|
||||
"src/layers/military/rendering.js",
|
||||
"src/layers/military/state.js",
|
||||
"src/layers/military/testing.js",
|
||||
"src/layers/military/tracking.js",
|
||||
"src/layers/radio/catalogModel.js",
|
||||
"src/layers/radio/categories.js",
|
||||
"src/layers/radio/clustering.js",
|
||||
"src/layers/radio/controls.js",
|
||||
"src/layers/radio/index.js",
|
||||
"src/layers/radio/ingestion.js",
|
||||
"src/layers/radio/interaction.js",
|
||||
"src/layers/radio/labels.js",
|
||||
"src/layers/radio/lifecycle.js",
|
||||
"src/layers/radio/model.js",
|
||||
"src/layers/radio/navigation.js",
|
||||
"src/layers/radio/playback.js",
|
||||
"src/layers/radio/policy.js",
|
||||
"src/layers/radio/presentation.js",
|
||||
"src/layers/radio/queries.js",
|
||||
"src/layers/radio/rendering.js",
|
||||
"src/layers/radio/selection.js",
|
||||
"src/layers/radio/source.js",
|
||||
"src/layers/radio/state.js",
|
||||
"src/layers/radio/tuning.js",
|
||||
"src/layers/radio/tuningNoise.js",
|
||||
"src/layers/radio/volume.js",
|
||||
"src/layers/satellites/catalog.js",
|
||||
"src/layers/satellites/controls.js",
|
||||
"src/layers/satellites/index.js",
|
||||
"src/layers/satellites/ingestion.js",
|
||||
"src/layers/satellites/interaction.js",
|
||||
"src/layers/satellites/labels.js",
|
||||
"src/layers/satellites/lifecycle.js",
|
||||
"src/layers/satellites/orbits.js",
|
||||
"src/layers/satellites/policy.js",
|
||||
"src/layers/satellites/rendering.js",
|
||||
"src/layers/satellites/source.js",
|
||||
"src/layers/satellites/state.js",
|
||||
"src/layers/satellites/testing.js",
|
||||
"src/layers/satellites/tracking.js",
|
||||
"src/layers/submarineCables/geometry.js",
|
||||
"src/layers/submarineCables/index.js",
|
||||
"src/layers/submarineCables/ingestion.js",
|
||||
"src/layers/submarineCables/interaction.js",
|
||||
"src/layers/submarineCables/lifecycle.js",
|
||||
"src/layers/submarineCables/overlay.js",
|
||||
"src/layers/submarineCables/policy.js",
|
||||
"src/layers/submarineCables/rendering.js",
|
||||
"src/layers/submarineCables/state.js",
|
||||
"src/layers/submarineCables/surface.js",
|
||||
"src/layers/traffic/animation.js",
|
||||
"src/layers/traffic/controls.js",
|
||||
"src/layers/traffic/flow.js",
|
||||
"src/layers/traffic/flowDecode.js",
|
||||
"src/layers/traffic/flowSource.js",
|
||||
"src/layers/traffic/index.js",
|
||||
"src/layers/traffic/ingestion.js",
|
||||
"src/layers/traffic/lifecycle.js",
|
||||
"src/layers/traffic/model.js",
|
||||
"src/layers/traffic/policy.js",
|
||||
"src/layers/traffic/rendering.js",
|
||||
"src/layers/traffic/source.js",
|
||||
"src/layers/traffic/state.js",
|
||||
"src/layers/traffic/style.js",
|
||||
"src/layers/traffic/timing.js",
|
||||
"src/layers/traffic/viewport.js",
|
||||
"src/layers/vessels/cards.js",
|
||||
"src/layers/vessels/evidence.js",
|
||||
"src/layers/vessels/index.js",
|
||||
"src/layers/vessels/ingestion.js",
|
||||
"src/layers/vessels/lifecycle.js",
|
||||
"src/layers/vessels/policy.js",
|
||||
"src/layers/vessels/queries.js",
|
||||
"src/layers/vessels/rendering.js",
|
||||
"src/layers/vessels/selection.js",
|
||||
"src/layers/vessels/state.js",
|
||||
"src/layers/vessels/store.js",
|
||||
"src/layers/vessels/testing.js",
|
||||
"src/layers/vessels/tracking.js",
|
||||
"src/locations.js",
|
||||
"src/navigationPolicy.js",
|
||||
"src/overlays/worldOverlay.js",
|
||||
"src/overlays/worldOverlayDraw.js",
|
||||
"src/overlays/worldOverlayTokens.js",
|
||||
"src/renderGovernor.js",
|
||||
"src/search/defaults.js",
|
||||
"src/search/geospatial.js",
|
||||
"src/search/google.js",
|
||||
"src/search/http.js",
|
||||
"src/search/placeSearch.js",
|
||||
"src/services/application.js",
|
||||
"src/services/requests.js",
|
||||
"src/ui/panelRailGeometry.js",
|
||||
"src/ui/radioTunerModel.js",
|
||||
"src/worldFocus.js"
|
||||
],
|
||||
"external": [
|
||||
"cesium",
|
||||
"egm96-universal",
|
||||
"satellite.js",
|
||||
"@mapbox/vector-tile",
|
||||
"pbf",
|
||||
"mgrs"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -3813,27 +3813,11 @@ async function main() {
|
||||
const Cesium = await import('/node_modules/cesium/Build/Cesium/index.js');
|
||||
const v = window.__godsEyeView.viewer;
|
||||
const fl = window.__godsEyeView.dataManager.layers.get('flights').module;
|
||||
// `.module` is the layer OBJECT (the default export), not the module
|
||||
// namespace, so the handoff seam is not on it. Reach the namespace the
|
||||
// same way this group reaches groundFloor's: offer the URL the app
|
||||
// itself loaded (Vite serves an edited file as `…?t=<hmr stamp>`, and
|
||||
// the plain path would hand back a second, unrelated instance whose
|
||||
// module state the app never touches), then prove identity by requiring
|
||||
// its default export to BE the live layer object.
|
||||
let ns = null;
|
||||
const urls = [...new Set([
|
||||
...performance.getEntriesByType('resource').map((e) => e.name)
|
||||
.filter((n) => /\/src\/data\/flights\.js(\?|$)/.test(n)).reverse(),
|
||||
`${window.__gevQaSourceBase || '/src'}/data/flights.js`,
|
||||
])];
|
||||
for (const url of urls) {
|
||||
let mod; try { mod = await import(/* @vite-ignore */ url); } catch { continue; }
|
||||
if (mod?.default === fl && typeof mod._driveFleetModelHandoffForTest === 'function') {
|
||||
ns = mod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ns) return { skipped: `the app's own flights module was not reachable (tried ${urls.length})` };
|
||||
// Test the registered instance directly, including catalogs constructed
|
||||
// with their own sources. Never import a second compatibility instance.
|
||||
const ns = fl.testing;
|
||||
if (typeof ns?._driveFleetModelHandoffForTest !== 'function')
|
||||
return { error: "the registered flights instance has no handoff test seam" };
|
||||
// Use a scenario-owned contact so its groundSnap entry is provably cold;
|
||||
// earlier display-floor cases intentionally exercise aaa097's cache.
|
||||
const holdIcao = 'aaa098';
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { createLayerCatalog } from './catalog.js';
|
||||
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
|
||||
import { createMilitaryRegistry } from '../layers/aircraft/classification.js';
|
||||
import { createApplicationFlights } from './layers/flights.js';
|
||||
import { createApplicationMilitary } from './layers/militaryFlights.js';
|
||||
import { createApplicationVessels } from './layers/aisLiveVessels.js';
|
||||
import { createApplicationCctv } from './layers/cctv.js';
|
||||
import { createApplicationRadio } from './layers/radio.js';
|
||||
import { createApplicationTraffic } from './layers/traffic.js';
|
||||
import { createApplicationBikeshare } from './layers/bikeshare.js';
|
||||
import { createApplicationInstallations } from './layers/militaryInstallations.js';
|
||||
import { createApplicationSatellites } from './layers/satellites.js';
|
||||
import { createApplicationLaunches } from './layers/rocketLaunches.js';
|
||||
import { createApplicationAlpr } from './layers/alprCameras.js';
|
||||
import { createApplicationAwareness } from './layers/militaryAwareness.js';
|
||||
import { createApplicationFirms } from './layers/firms.js';
|
||||
import { createApplicationEarthquakes } from './layers/earthquakes.js';
|
||||
import { createApplicationCables } from './layers/submarineCables.js';
|
||||
import { createInfrastructureLayers } from '../data/infrastructure.js';
|
||||
import { localGeoJsonServices } from '../data/localGeojson.js';
|
||||
|
||||
const SOURCE_METHODS = Object.freeze({
|
||||
flights: ['getSnapshot'],
|
||||
military: ['getSnapshot'],
|
||||
vessels: ['getSnapshot'],
|
||||
cctv: ['getCatalog', 'getHealth', 'getFrameUrl', 'getMediaUrl'],
|
||||
radio: ['getDirectory', 'recordClick'],
|
||||
traffic: [
|
||||
'requestRoads',
|
||||
'getStatus',
|
||||
'fetchFlowForBounds',
|
||||
'getFlowSessionStats',
|
||||
'resetFlowTileCache',
|
||||
],
|
||||
bikeshare: ['getStations'],
|
||||
installations: ['getMappedSites', 'searchNearby'],
|
||||
satellites: ['readGroup'],
|
||||
launches: ['getLaunches', 'getActiveTle'],
|
||||
alpr: ['fetch'],
|
||||
firms: ['getSnapshot'],
|
||||
earthquakes: ['getSnapshot'],
|
||||
cables: ['fetch'],
|
||||
});
|
||||
|
||||
/** Construct the current catalog without choosing any source provider.
|
||||
* Scene engines remain page-owned; layers and classification have this app's lifetime.
|
||||
* The manager owns layer destruction, while abort releases classification even if startup fails.
|
||||
*/
|
||||
export function createApplicationCatalog({
|
||||
sources,
|
||||
signal,
|
||||
metadata = LAYER_STATE_REGISTRY,
|
||||
vesselOptions,
|
||||
resolveAsset,
|
||||
}) {
|
||||
if (!signal?.addEventListener)
|
||||
throw new TypeError('An application lifetime signal is required');
|
||||
signal.throwIfAborted();
|
||||
for (const [name, methods] of Object.entries(SOURCE_METHODS)) {
|
||||
if (
|
||||
methods.some((method) => typeof sources?.[name]?.[method] !== 'function')
|
||||
)
|
||||
throw new TypeError(`Invalid catalog source: ${name}`);
|
||||
}
|
||||
const militaryRegistry = createMilitaryRegistry();
|
||||
const dispose = () => {
|
||||
signal.removeEventListener('abort', dispose);
|
||||
militaryRegistry.dispose();
|
||||
};
|
||||
signal.addEventListener('abort', dispose, { once: true });
|
||||
try {
|
||||
militaryRegistry.configureSource(sources.military, { signal });
|
||||
const flights = createApplicationFlights({
|
||||
source: sources.flights,
|
||||
militaryRegistry,
|
||||
resolveAsset,
|
||||
});
|
||||
const military = createApplicationMilitary({
|
||||
source: sources.military,
|
||||
militaryRegistry,
|
||||
resolveAsset,
|
||||
});
|
||||
const vessels = createApplicationVessels({
|
||||
source: sources.vessels,
|
||||
options: vesselOptions,
|
||||
});
|
||||
const installations = createApplicationInstallations({
|
||||
source: sources.installations,
|
||||
});
|
||||
const satellites = createApplicationSatellites({
|
||||
source: sources.satellites,
|
||||
});
|
||||
const catalog = createLayerCatalog(
|
||||
[
|
||||
flights,
|
||||
military,
|
||||
createApplicationEarthquakes({ source: sources.earthquakes }),
|
||||
createApplicationAlpr({ source: sources.alpr }),
|
||||
satellites,
|
||||
createApplicationLaunches({ source: sources.launches, satellites }),
|
||||
createApplicationTraffic({ source: sources.traffic }),
|
||||
createApplicationCctv({ source: sources.cctv }),
|
||||
createApplicationRadio({ source: sources.radio }),
|
||||
createApplicationBikeshare({ source: sources.bikeshare }),
|
||||
vessels,
|
||||
installations,
|
||||
createApplicationAwareness({
|
||||
flights,
|
||||
military,
|
||||
vessels,
|
||||
installations,
|
||||
}),
|
||||
...createInfrastructureLayers(localGeoJsonServices),
|
||||
createApplicationCables({ source: sources.cables }),
|
||||
createApplicationFirms({
|
||||
id: 'local-firms',
|
||||
name: 'FIRMS Active Fires',
|
||||
icon: '▲',
|
||||
source: 'NASA FIRMS · LIVE',
|
||||
feed: sources.firms,
|
||||
}),
|
||||
],
|
||||
metadata,
|
||||
);
|
||||
return Object.freeze({ ...catalog, militaryRegistry });
|
||||
} catch (error) {
|
||||
dispose();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createApplicationCatalog } from './constructCatalog.js';
|
||||
import { createStandaloneLayerSources } from '../standalone/layerSources.js';
|
||||
import { catalogControlServices } from './catalog.js';
|
||||
|
||||
function fixtureSources(ids, calls) {
|
||||
const sources = createStandaloneLayerSources();
|
||||
sources.military = {
|
||||
getSnapshot: async () => {
|
||||
throw new Error('Use identity acquisition');
|
||||
},
|
||||
async getIdentities(_query, { signal }) {
|
||||
calls.push(signal);
|
||||
return ids;
|
||||
},
|
||||
};
|
||||
return sources;
|
||||
}
|
||||
|
||||
test('catalogs construct distinct layers and classification from their supplied source', async (t) => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
t.after(() => {
|
||||
a.abort();
|
||||
b.abort();
|
||||
});
|
||||
const callsA = [];
|
||||
const callsB = [];
|
||||
const first = createApplicationCatalog({
|
||||
sources: fixtureSources(['abc123'], callsA),
|
||||
signal: a.signal,
|
||||
});
|
||||
const second = createApplicationCatalog({
|
||||
sources: fixtureSources(['def456'], callsB),
|
||||
signal: b.signal,
|
||||
});
|
||||
assert.equal(first.layers.length, 17);
|
||||
assert.deepEqual(
|
||||
first.layers.map(({ id }) => id),
|
||||
second.layers.map(({ id }) => id),
|
||||
);
|
||||
for (const layer of first.layers)
|
||||
assert.notEqual(layer, second.get(layer.id));
|
||||
assert.equal(
|
||||
catalogControlServices(first).satellitesLayer,
|
||||
first.get('satellites'),
|
||||
);
|
||||
assert.equal(callsA.length, 0, 'construction must not acquire');
|
||||
await first.militaryRegistry.refreshMilitaryRegistryIfStale();
|
||||
await second.militaryRegistry.refreshMilitaryRegistryIfStale();
|
||||
assert.equal(first.militaryRegistry.isMilitaryIcao('abc123'), true);
|
||||
assert.equal(first.militaryRegistry.isMilitaryIcao('def456'), false);
|
||||
assert.equal(second.militaryRegistry.isMilitaryIcao('def456'), true);
|
||||
a.abort();
|
||||
assert.equal(callsA[0].aborted, true);
|
||||
assert.equal(first.militaryRegistry.isMilitaryIcao('abc123'), false);
|
||||
assert.equal(callsB[0].aborted, false);
|
||||
assert.equal(second.militaryRegistry.isMilitaryIcao('def456'), true);
|
||||
});
|
||||
|
||||
test('invalid or already cancelled construction fails before classification can acquire', () => {
|
||||
const lifetime = new AbortController();
|
||||
assert.throws(
|
||||
() => createApplicationCatalog({ sources: {}, signal: lifetime.signal }),
|
||||
/catalog source/,
|
||||
);
|
||||
lifetime.abort();
|
||||
assert.throws(
|
||||
() =>
|
||||
createApplicationCatalog({
|
||||
sources: createStandaloneLayerSources(),
|
||||
signal: lifetime.signal,
|
||||
}),
|
||||
{ name: 'AbortError' },
|
||||
);
|
||||
});
|
||||
+1
-1
@@ -49,5 +49,5 @@ export function createApplicationData({
|
||||
dataManager.buildTogglePanel(document.getElementById('data-toggles'));
|
||||
styleManager.attachDataManager(dataManager);
|
||||
|
||||
return { dataManager };
|
||||
return { dataManager, catalog };
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { createVesselLayer } from '../../layers/vessels/index.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as trails from '../../data/trailRenderer.js';
|
||||
import * as labels from '../../data/detectionDraw.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as overlay from '../../overlays/worldOverlay.js';
|
||||
import * as geoid from '../../data/geoid.js';
|
||||
import * as sprites from '../../data/spriteOrder.js';
|
||||
import * as focus from '../../data/focusDeemphasis.js';
|
||||
import * as worldFocus from '../../worldFocus.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationVessels({ source, options = {} }) {
|
||||
return createVesselLayer({
|
||||
source,
|
||||
options,
|
||||
services: {
|
||||
context,
|
||||
trails,
|
||||
labels,
|
||||
picking,
|
||||
overlay,
|
||||
geoid,
|
||||
sprites,
|
||||
focus,
|
||||
worldFocus,
|
||||
render,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createAlprCamerasLayer } from '../../layers/alpr/index.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as groundFloor from '../../data/groundFloor.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationAlpr({ source }) {
|
||||
return createAlprCamerasLayer({
|
||||
source,
|
||||
services: { render, context, picking, groundFloor },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createBikeshareLayer } from '../../layers/bikeshare/index.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as sprites from '../../data/spriteOrder.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationBikeshare({ source }) {
|
||||
return createBikeshareLayer({
|
||||
source,
|
||||
services: { render, sprites, picking, overlays },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createCctvLayer } from '../../layers/cctv/index.js';
|
||||
import * as sprites from '../../data/spriteOrder.js';
|
||||
import * as activation from '../../cctvFocusRequest.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
import * as locations from '../../locations.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as terrain from '../../data/terrainHeights.js';
|
||||
import * as ground from '../../data/groundFloor.js';
|
||||
import * as mesh from '../../data/meshFloorSampler.js';
|
||||
import * as focus from '../../data/focusDeemphasis.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationCctv({ source }) {
|
||||
return createCctvLayer({
|
||||
source,
|
||||
services: {
|
||||
sprites,
|
||||
activation,
|
||||
overlays,
|
||||
locations,
|
||||
picking,
|
||||
terrain,
|
||||
ground,
|
||||
mesh,
|
||||
focus,
|
||||
render,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createEarthquakesLayer } from '../../layers/earthquakes/index.js';
|
||||
import { overlayHost } from './overlayHost.js';
|
||||
/** Wire earthquake observations to the application overlay host. */
|
||||
export function createApplicationEarthquakes(options) {
|
||||
return createEarthquakesLayer({ overlayHost, ...options });
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { createFirmsHeatmapLayer as createLayer } from '../../layers/firms/index.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as sprites from '../../data/spriteOrder.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as anchors from '../../data/fireAnchors.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
import * as focus from '../../worldFocus.js';
|
||||
export const firmsServices = {
|
||||
render,
|
||||
sprites,
|
||||
context,
|
||||
picking,
|
||||
anchors,
|
||||
overlays,
|
||||
focus,
|
||||
};
|
||||
|
||||
/** Bind a supplied fire feed to the application scene service owners. */
|
||||
export function createApplicationFirms(options) {
|
||||
return createLayer({
|
||||
...options,
|
||||
icon: options.icon ?? '▲',
|
||||
source: options.source ?? 'NASA FIRMS',
|
||||
feed: options.feed,
|
||||
services: firmsServices,
|
||||
overlayHost: options.overlayHost ?? {
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
hitTest: overlays.hitTestWorldOverlay,
|
||||
},
|
||||
screenSpaceEventHandlerFactory:
|
||||
options.screenSpaceEventHandlerFactory ??
|
||||
((viewer) => new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { createCivilFlightLayer } from '../../layers/flights/index.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as sprites from '../../data/spriteOrder.js';
|
||||
import * as trails from '../../data/trailRenderer.js';
|
||||
import * as aircraftPresentation from '../../data/tr3bRegistry.js';
|
||||
import * as camera from '../../data/trackedCamera.js';
|
||||
import * as labels from '../../data/detectionDraw.js';
|
||||
import * as groundFloor from '../../data/groundFloor.js';
|
||||
import * as meshFloor from '../../data/meshFloorSampler.js';
|
||||
import * as geoid from '../../data/geoid.js';
|
||||
import * as focus from '../../data/focusDeemphasis.js';
|
||||
import * as readout from '../../data/trackedReadout.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as groundSnap from '../../data/groundSnap.js';
|
||||
import * as recession from '../../data/aircraftRecession.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationFlights({
|
||||
source,
|
||||
militaryRegistry,
|
||||
resolveAsset = (url) =>
|
||||
`${import.meta.env?.BASE_URL || '/'}${url.replace(/^\//, '')}`,
|
||||
}) {
|
||||
return createCivilFlightLayer({
|
||||
source,
|
||||
resolveAsset,
|
||||
services: {
|
||||
picking,
|
||||
sprites,
|
||||
trails,
|
||||
aircraftPresentation,
|
||||
camera,
|
||||
militaryRegistry,
|
||||
labels,
|
||||
groundFloor,
|
||||
meshFloor,
|
||||
geoid,
|
||||
focus,
|
||||
readout,
|
||||
context,
|
||||
render,
|
||||
groundSnap,
|
||||
recession,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createAwarenessLayer } from '../../layers/awareness/index.js';
|
||||
import * as navigation from '../../navigationPolicy.js';
|
||||
import * as geometry from '../../celestialRing.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
/** Bind Contacts to the same catalog instances used for rendering. */
|
||||
export function createApplicationAwareness({
|
||||
flights,
|
||||
military,
|
||||
vessels,
|
||||
installations,
|
||||
}) {
|
||||
return createAwarenessLayer({
|
||||
services: {
|
||||
flights,
|
||||
military,
|
||||
vessels,
|
||||
installations,
|
||||
navigation,
|
||||
geometry,
|
||||
render,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { createMilitaryFlightLayer } from '../../layers/military/index.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as sprites from '../../data/spriteOrder.js';
|
||||
import * as trails from '../../data/trailRenderer.js';
|
||||
import * as aircraftPresentation from '../../data/tr3bRegistry.js';
|
||||
import * as camera from '../../data/trackedCamera.js';
|
||||
import * as labels from '../../data/detectionDraw.js';
|
||||
import * as groundFloor from '../../data/groundFloor.js';
|
||||
import * as meshFloor from '../../data/meshFloorSampler.js';
|
||||
import * as geoid from '../../data/geoid.js';
|
||||
import * as focus from '../../data/focusDeemphasis.js';
|
||||
import * as readout from '../../data/trackedReadout.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as groundSnap from '../../data/groundSnap.js';
|
||||
import * as recession from '../../data/aircraftRecession.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationMilitary({
|
||||
source,
|
||||
militaryRegistry,
|
||||
resolveAsset = (url) =>
|
||||
`${import.meta.env?.BASE_URL || '/'}${url.replace(/^\//, '')}`,
|
||||
}) {
|
||||
return createMilitaryFlightLayer({
|
||||
source,
|
||||
resolveAsset,
|
||||
services: {
|
||||
picking,
|
||||
sprites,
|
||||
trails,
|
||||
aircraftPresentation,
|
||||
camera,
|
||||
militaryRegistry,
|
||||
labels,
|
||||
groundFloor,
|
||||
meshFloor,
|
||||
geoid,
|
||||
focus,
|
||||
readout,
|
||||
context,
|
||||
render,
|
||||
groundSnap,
|
||||
recession,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createInstallationsLayer } from '../../layers/installations/index.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as ground from '../../data/groundFloor.js';
|
||||
import * as anchors from '../../data/fireAnchors.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationInstallations({ source }) {
|
||||
return createInstallationsLayer({
|
||||
source,
|
||||
services: { render, context, ground, anchors, picking },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import {
|
||||
clearOverlaySource,
|
||||
setOverlayEntries,
|
||||
setOverlaySourceVisible,
|
||||
} from '../../overlays/worldOverlay.js';
|
||||
export const overlayHost = Object.freeze({
|
||||
clearSource: clearOverlaySource,
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createRadioLayer } from '../../layers/radio/index.js';
|
||||
import * as ground from '../../data/groundFloor.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
import * as globe from '../../celestialRing.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationRadio({ source }) {
|
||||
return createRadioLayer({
|
||||
source,
|
||||
services: { ground, picking, overlays, globe, render },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createRocketLaunchesLayer } from '../../layers/launches/index.js';
|
||||
import * as geometry from '../../celestialRing.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationLaunches({ source, satellites }) {
|
||||
return createRocketLaunchesLayer({
|
||||
source,
|
||||
services: { satellites, geometry, overlays, render },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createSatellitesLayer } from '../../layers/satellites/index.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as focus from '../../data/focusDeemphasis.js';
|
||||
import * as readout from '../../data/trackedReadout.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
import * as context from '../../data/contextStore.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
import * as layerState from '../../data/layerState.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationSatellites({ source }) {
|
||||
return createSatellitesLayer({
|
||||
source,
|
||||
services: {
|
||||
picking,
|
||||
focus,
|
||||
readout,
|
||||
overlays,
|
||||
context,
|
||||
render,
|
||||
layerState,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { createSubmarineCableLayer } from '../../layers/submarineCables/index.js';
|
||||
import { overlayHost } from './overlayHost.js';
|
||||
/** Wire cable source geometry to the application overlay host. */
|
||||
export function createApplicationCables(options) {
|
||||
return createSubmarineCableLayer({
|
||||
overlayHost,
|
||||
screenSpaceEventHandlerFactory: (canvas) =>
|
||||
new Cesium.ScreenSpaceEventHandler(canvas),
|
||||
mapStackEventTarget: typeof window !== 'undefined' ? window : null,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createTrafficLayer } from '../../layers/traffic/index.js';
|
||||
import * as credits from '../../data/dataCredits.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
|
||||
/** Construct one layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationTraffic({ source }) {
|
||||
return createTrafficLayer({
|
||||
source,
|
||||
services: { credits, render },
|
||||
});
|
||||
}
|
||||
@@ -1,17 +1,8 @@
|
||||
import { createVesselLayer } from '../layers/vessels/index.js';
|
||||
import { createAisStreamSource } from '../sources/live/standalone.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as trails from './trailRenderer.js';
|
||||
import * as labels from './detectionDraw.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as overlay from '../overlays/worldOverlay.js';
|
||||
import * as geoid from './geoid.js';
|
||||
import * as sprites from './spriteOrder.js';
|
||||
import * as focus from './focusDeemphasis.js';
|
||||
import * as worldFocus from '../worldFocus.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import { createApplicationVessels } from '../app/layers/aisLiveVessels.js';
|
||||
|
||||
const aisLiveVesselsLayer = createVesselLayer({
|
||||
import { createAisStreamSource } from '../sources/live/standalone.js';
|
||||
|
||||
const aisLiveVesselsLayer = createApplicationVessels({
|
||||
source: createAisStreamSource({
|
||||
apiUrl: import.meta.env?.VITE_AIS_LIVE_API_URL || '/api/ais-live',
|
||||
}),
|
||||
@@ -19,18 +10,6 @@ const aisLiveVesselsLayer = createVesselLayer({
|
||||
maxRows: import.meta.env?.VITE_AIS_LIVE_MAX_ROWS,
|
||||
maxLabels: import.meta.env?.VITE_AIS_LIVE_LABEL_MAX_ROWS,
|
||||
},
|
||||
services: {
|
||||
context,
|
||||
trails,
|
||||
labels,
|
||||
picking,
|
||||
overlay,
|
||||
geoid,
|
||||
sprites,
|
||||
focus,
|
||||
worldFocus,
|
||||
render,
|
||||
},
|
||||
});
|
||||
export { AIS_FIRST_CONNECT_GRACE_MS } from '../layers/vessels/policy.js';
|
||||
export const deriveAisFeedError = aisLiveVesselsLayer.deriveAisFeedError;
|
||||
|
||||
+3
-10
@@ -1,12 +1,6 @@
|
||||
import { createApplicationAlpr } from '../app/layers/alprCameras.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import {
|
||||
createAlprCamerasLayer,
|
||||
createOverpassAlprSource,
|
||||
} from '../layers/alpr/index.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as groundFloor from './groundFloor.js';
|
||||
import { createOverpassAlprSource } from '../layers/alpr/index.js';
|
||||
export * from '../layers/alpr/index.js';
|
||||
const slot = createSourceSlot(
|
||||
createOverpassAlprSource(),
|
||||
@@ -14,7 +8,6 @@ const slot = createSourceSlot(
|
||||
'ALPR source',
|
||||
);
|
||||
export const configureAlprSource = slot.configure;
|
||||
export default createAlprCamerasLayer({
|
||||
export default createApplicationAlpr({
|
||||
source: slot.source,
|
||||
services: { render, context, picking, groundFloor },
|
||||
});
|
||||
@@ -1,10 +1,6 @@
|
||||
import { createApplicationBikeshare } from '../app/layers/bikeshare.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import { createBikeshareSource } from '../layers/bikeshare/source.js';
|
||||
import { createBikeshareLayer } from '../layers/bikeshare/index.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as sprites from './spriteOrder.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as overlays from '../overlays/worldOverlay.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createBikeshareSource(),
|
||||
@@ -12,9 +8,8 @@ const sourceSlot = createSourceSlot(
|
||||
'Bikeshare source',
|
||||
);
|
||||
export const configureBikeshareSource = sourceSlot.configure;
|
||||
const layer = createBikeshareLayer({
|
||||
const layer = createApplicationBikeshare({
|
||||
source: sourceSlot.source,
|
||||
services: { render, sprites, picking, overlays },
|
||||
});
|
||||
export const createBikeshareSelectedOverlayEntry =
|
||||
layer.createBikeshareSelectedOverlayEntry;
|
||||
|
||||
+2
-24
@@ -1,16 +1,6 @@
|
||||
import { createApplicationCctv } from '../app/layers/cctv.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import { createCctvSource } from '../layers/cctv/source.js';
|
||||
import { createCctvLayer } from '../layers/cctv/index.js';
|
||||
import * as sprites from './spriteOrder.js';
|
||||
import * as activation from '../cctvFocusRequest.js';
|
||||
import * as overlays from '../overlays/worldOverlay.js';
|
||||
import * as locations from '../locations.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as terrain from './terrainHeights.js';
|
||||
import * as ground from './groundFloor.js';
|
||||
import * as mesh from './meshFloorSampler.js';
|
||||
import * as focus from './focusDeemphasis.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createCctvSource(),
|
||||
@@ -18,20 +8,8 @@ const sourceSlot = createSourceSlot(
|
||||
'Cctv source',
|
||||
);
|
||||
export const configureCctvSource = sourceSlot.configure;
|
||||
const layer = createCctvLayer({
|
||||
const layer = createApplicationCctv({
|
||||
source: sourceSlot.source,
|
||||
services: {
|
||||
sprites,
|
||||
activation,
|
||||
overlays,
|
||||
locations,
|
||||
picking,
|
||||
terrain,
|
||||
ground,
|
||||
mesh,
|
||||
focus,
|
||||
render,
|
||||
},
|
||||
});
|
||||
export const calibrationPatchMovesAnchor = layer.calibrationPatchMovesAnchor;
|
||||
export const migrateRangeScaleForFloor = layer.migrateRangeScaleForFloor;
|
||||
|
||||
+4
-15
@@ -1,22 +1,11 @@
|
||||
import {
|
||||
createEarthquakesLayer as createLayer,
|
||||
createUsgsEarthquakeSource,
|
||||
} from '../layers/earthquakes/index.js';
|
||||
import {
|
||||
clearOverlaySource,
|
||||
setOverlayEntries,
|
||||
setOverlaySourceVisible,
|
||||
} from '../overlays/worldOverlay.js';
|
||||
import { createUsgsEarthquakeSource } from '../layers/earthquakes/index.js';
|
||||
import { createApplicationEarthquakes } from '../app/layers/earthquakes.js';
|
||||
export * from '../layers/earthquakes/index.js';
|
||||
/** Wire the standalone source and application overlay owner. */
|
||||
export function createEarthquakesLayer({
|
||||
source = createUsgsEarthquakeSource(),
|
||||
overlayHost = {
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
clearSource: clearOverlaySource,
|
||||
},
|
||||
...options
|
||||
} = {}) {
|
||||
return createLayer({ source, overlayHost });
|
||||
return createApplicationEarthquakes({ source, ...options });
|
||||
}
|
||||
export default createEarthquakesLayer();
|
||||
@@ -1,33 +1,16 @@
|
||||
import { createApplicationFirms, firmsServices } from '../app/layers/firms.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import {
|
||||
createFirmsHeatmapLayer as createLayer,
|
||||
createFirmsHelpers,
|
||||
createFirmsSource,
|
||||
} from '../layers/firms/index.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as sprites from './spriteOrder.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as anchors from './fireAnchors.js';
|
||||
import * as overlays from '../overlays/worldOverlay.js';
|
||||
import * as focus from '../worldFocus.js';
|
||||
const services = {
|
||||
render,
|
||||
sprites,
|
||||
context,
|
||||
picking,
|
||||
anchors,
|
||||
overlays,
|
||||
focus,
|
||||
};
|
||||
const sourceSlot = createSourceSlot(
|
||||
createFirmsSource(),
|
||||
['getSnapshot'],
|
||||
'Fire source',
|
||||
);
|
||||
export const configureFirmsSource = sourceSlot.configure;
|
||||
const helpers = createFirmsHelpers({ services });
|
||||
const helpers = createFirmsHelpers({ services: firmsServices });
|
||||
export const mapAnalystRecord = helpers.mapAnalystRecord;
|
||||
export const fireCullPosition = helpers.fireCullPosition;
|
||||
export const applyHorizonCull = helpers.applyHorizonCull;
|
||||
@@ -36,22 +19,10 @@ export const buildFireCard = helpers.buildFireCard;
|
||||
export const buildCellCard = helpers.buildCellCard;
|
||||
export const applyFirmsOverlayPolicy = helpers.applyFirmsOverlayPolicy;
|
||||
|
||||
/** Supply the local source and the application's scene service owners. */
|
||||
/** Retain the existing default feed and helper exports for direct callers. */
|
||||
export function createFirmsHeatmapLayer(options) {
|
||||
return createLayer({
|
||||
return createApplicationFirms({
|
||||
...options,
|
||||
icon: options.icon ?? '▲',
|
||||
source: options.source ?? 'NASA FIRMS',
|
||||
feed: options.feed ?? sourceSlot.source,
|
||||
services,
|
||||
overlayHost: options.overlayHost ?? {
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
hitTest: overlays.hitTestWorldOverlay,
|
||||
},
|
||||
screenSpaceEventHandlerFactory:
|
||||
options.screenSpaceEventHandlerFactory ??
|
||||
((viewer) => new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)),
|
||||
});
|
||||
}
|
||||
+5
-38
@@ -1,44 +1,11 @@
|
||||
import { createCivilFlightLayer } from '../layers/flights/index.js';
|
||||
import { createOpenSkySource } from '../sources/live/standalone.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as sprites from './spriteOrder.js';
|
||||
import * as trails from './trailRenderer.js';
|
||||
import * as aircraftPresentation from './tr3bRegistry.js';
|
||||
import * as camera from './trackedCamera.js';
|
||||
import * as militaryRegistry from './militaryRegistry.js';
|
||||
import * as labels from './detectionDraw.js';
|
||||
import * as groundFloor from './groundFloor.js';
|
||||
import * as meshFloor from './meshFloorSampler.js';
|
||||
import * as geoid from './geoid.js';
|
||||
import * as focus from './focusDeemphasis.js';
|
||||
import * as readout from './trackedReadout.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as groundSnap from './groundSnap.js';
|
||||
import * as recession from './aircraftRecession.js';
|
||||
import { createApplicationFlights } from '../app/layers/flights.js';
|
||||
|
||||
const flightsLayer = createCivilFlightLayer({
|
||||
import { createOpenSkySource } from '../sources/live/standalone.js';
|
||||
import * as militaryRegistry from './militaryRegistry.js';
|
||||
|
||||
const flightsLayer = createApplicationFlights({
|
||||
source: createOpenSkySource(),
|
||||
resolveAsset: (url) =>
|
||||
`${import.meta.env?.BASE_URL || '/'}${url.replace(/^\//, '')}`,
|
||||
services: {
|
||||
picking,
|
||||
sprites,
|
||||
trails,
|
||||
aircraftPresentation,
|
||||
camera,
|
||||
militaryRegistry,
|
||||
labels,
|
||||
groundFloor,
|
||||
meshFloor,
|
||||
geoid,
|
||||
focus,
|
||||
readout,
|
||||
context,
|
||||
render,
|
||||
groundSnap,
|
||||
recession,
|
||||
},
|
||||
});
|
||||
export { TRACKED_MODEL_MAX_PX } from '../layers/flights/policy.js';
|
||||
export const _floorGroundedDisplayPositionForTest =
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import { createAwarenessLayer } from '../layers/awareness/index.js';
|
||||
import { createApplicationAwareness } from '../app/layers/militaryAwareness.js';
|
||||
import flights from './flights.js';
|
||||
import military from './militaryFlights.js';
|
||||
import vessels from './aisLiveVessels.js';
|
||||
import installations from './militaryInstallations.js';
|
||||
import * as navigation from '../navigationPolicy.js';
|
||||
import * as geometry from '../celestialRing.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
|
||||
const layer = createAwarenessLayer({
|
||||
services: {
|
||||
const layer = createApplicationAwareness({
|
||||
flights,
|
||||
military,
|
||||
vessels,
|
||||
installations,
|
||||
navigation,
|
||||
geometry,
|
||||
render,
|
||||
},
|
||||
});
|
||||
export const contextTargetFlyToAllowed = layer.contextTargetFlyToAllowed;
|
||||
export const awarenessClearMatchesSubject = layer.awarenessClearMatchesSubject;
|
||||
|
||||
@@ -1,44 +1,11 @@
|
||||
import { createMilitaryFlightLayer } from '../layers/military/index.js';
|
||||
import { createAdsbLolSource } from '../sources/live/standalone.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as sprites from './spriteOrder.js';
|
||||
import * as trails from './trailRenderer.js';
|
||||
import * as aircraftPresentation from './tr3bRegistry.js';
|
||||
import * as camera from './trackedCamera.js';
|
||||
import * as militaryRegistry from './militaryRegistry.js';
|
||||
import * as labels from './detectionDraw.js';
|
||||
import * as groundFloor from './groundFloor.js';
|
||||
import * as meshFloor from './meshFloorSampler.js';
|
||||
import * as geoid from './geoid.js';
|
||||
import * as focus from './focusDeemphasis.js';
|
||||
import * as readout from './trackedReadout.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as groundSnap from './groundSnap.js';
|
||||
import * as recession from './aircraftRecession.js';
|
||||
import { createApplicationMilitary } from '../app/layers/militaryFlights.js';
|
||||
|
||||
const militaryFlightsLayer = createMilitaryFlightLayer({
|
||||
import { createAdsbLolSource } from '../sources/live/standalone.js';
|
||||
import * as militaryRegistry from './militaryRegistry.js';
|
||||
|
||||
const militaryFlightsLayer = createApplicationMilitary({
|
||||
source: createAdsbLolSource(),
|
||||
resolveAsset: (url) =>
|
||||
`${import.meta.env?.BASE_URL || '/'}${url.replace(/^\//, '')}`,
|
||||
services: {
|
||||
picking,
|
||||
sprites,
|
||||
trails,
|
||||
aircraftPresentation,
|
||||
camera,
|
||||
militaryRegistry,
|
||||
labels,
|
||||
groundFloor,
|
||||
meshFloor,
|
||||
geoid,
|
||||
focus,
|
||||
readout,
|
||||
context,
|
||||
render,
|
||||
groundSnap,
|
||||
recession,
|
||||
},
|
||||
});
|
||||
export { TRACKED_MODEL_MAX_PX } from '../layers/military/policy.js';
|
||||
export const _setTrackedMilitaryRefreshStateForTest =
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { createApplicationInstallations } from '../app/layers/militaryInstallations.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import {
|
||||
createInstallationsLayer,
|
||||
createInstallationSource,
|
||||
} from '../layers/installations/index.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as ground from './groundFloor.js';
|
||||
import * as anchors from './fireAnchors.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import { createInstallationSource } from '../layers/installations/index.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createInstallationSource(),
|
||||
@@ -15,9 +8,8 @@ const sourceSlot = createSourceSlot(
|
||||
'Installation source',
|
||||
);
|
||||
export const configureInstallationSource = sourceSlot.configure;
|
||||
const layer = createInstallationsLayer({
|
||||
const layer = createApplicationInstallations({
|
||||
source: sourceSlot.source,
|
||||
services: { render, context, ground, anchors, picking },
|
||||
});
|
||||
export const approximateSurfaceDistanceM = layer.approximateSurfaceDistanceM;
|
||||
export const classifyGoogleMilitaryPlace = layer.classifyGoogleMilitaryPlace;
|
||||
|
||||
+2
-8
@@ -1,11 +1,6 @@
|
||||
import { createApplicationRadio } from '../app/layers/radio.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import { createRadioSource } from '../layers/radio/source.js';
|
||||
import { createRadioLayer } from '../layers/radio/index.js';
|
||||
import * as ground from './groundFloor.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as overlays from '../overlays/worldOverlay.js';
|
||||
import * as globe from '../celestialRing.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createRadioSource(),
|
||||
@@ -13,9 +8,8 @@ const sourceSlot = createSourceSlot(
|
||||
'Radio source',
|
||||
);
|
||||
export const configureRadioSource = sourceSlot.configure;
|
||||
const layer = createRadioLayer({
|
||||
const layer = createApplicationRadio({
|
||||
source: sourceSlot.source,
|
||||
services: { ground, picking, overlays, globe, render },
|
||||
});
|
||||
export const radioGlobeLabel = layer.radioGlobeLabel;
|
||||
export const createRadioSelectedOverlayEntry =
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { createApplicationLaunches } from '../app/layers/rocketLaunches.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import {
|
||||
createRocketLaunchesLayer,
|
||||
createLaunchSource,
|
||||
} from '../layers/launches/index.js';
|
||||
import { createLaunchSource } from '../layers/launches/index.js';
|
||||
import * as satellites from './satellites.js';
|
||||
import * as geometry from '../celestialRing.js';
|
||||
import * as overlays from '../overlays/worldOverlay.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createLaunchSource(),
|
||||
@@ -14,9 +9,9 @@ const sourceSlot = createSourceSlot(
|
||||
'Launch source',
|
||||
);
|
||||
export const configureLaunchSource = sourceSlot.configure;
|
||||
const layer = createRocketLaunchesLayer({
|
||||
const layer = createApplicationLaunches({
|
||||
source: sourceSlot.source,
|
||||
services: { satellites, geometry, overlays, render },
|
||||
satellites,
|
||||
});
|
||||
export const satelliteParamsForSpaceMissions =
|
||||
layer.satelliteParamsForSpaceMissions;
|
||||
|
||||
+3
-13
@@ -1,15 +1,6 @@
|
||||
import { createApplicationSatellites } from '../app/layers/satellites.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import {
|
||||
createSatellitesLayer,
|
||||
createSatelliteSource,
|
||||
} from '../layers/satellites/index.js';
|
||||
import * as picking from './pickRegistry.js';
|
||||
import * as focus from './focusDeemphasis.js';
|
||||
import * as readout from './trackedReadout.js';
|
||||
import * as overlays from '../overlays/worldOverlay.js';
|
||||
import * as context from './contextStore.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
import * as layerState from './layerState.js';
|
||||
import { createSatelliteSource } from '../layers/satellites/index.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createSatelliteSource(),
|
||||
@@ -17,9 +8,8 @@ const sourceSlot = createSourceSlot(
|
||||
'Satellite source',
|
||||
);
|
||||
export const configureSatelliteSource = sourceSlot.configure;
|
||||
const layer = createSatellitesLayer({
|
||||
const layer = createApplicationSatellites({
|
||||
source: sourceSlot.source,
|
||||
services: { picking, focus, readout, overlays, context, render, layerState },
|
||||
});
|
||||
export const satelliteVisualsVisible = layer.satelliteVisualsVisible;
|
||||
export const satelliteCatalogModeChanged = layer.satelliteCatalogModeChanged;
|
||||
|
||||
@@ -1,26 +1,10 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import {
|
||||
createSubmarineCableLayer,
|
||||
createCableOverlayPublisher as createPublisher,
|
||||
} from '../layers/submarineCables/index.js';
|
||||
import { createApplicationCables } from '../app/layers/submarineCables.js';
|
||||
import { overlayHost } from '../app/layers/overlayHost.js';
|
||||
import { createCableOverlayPublisher as createPublisher } from '../layers/submarineCables/index.js';
|
||||
import { createBundledCableSource } from '../layers/submarineCables/bundledSource.js';
|
||||
import {
|
||||
clearOverlaySource,
|
||||
setOverlayEntries,
|
||||
setOverlaySourceVisible,
|
||||
} from '../overlays/worldOverlay.js';
|
||||
const overlayHost = Object.freeze({
|
||||
clearSource: clearOverlaySource,
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
});
|
||||
export function createTeleGeographySubmarineCableLayer(options = {}) {
|
||||
return createSubmarineCableLayer({
|
||||
return createApplicationCables({
|
||||
source: createBundledCableSource(),
|
||||
overlayHost,
|
||||
screenSpaceEventHandlerFactory: (canvas) =>
|
||||
new Cesium.ScreenSpaceEventHandler(canvas),
|
||||
mapStackEventTarget: typeof window !== 'undefined' ? window : null,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -631,6 +631,8 @@ test('the depth decision is the dated Option-2 host migration, superseding Optio
|
||||
test('cables create no native labels and no per-frame geometry callbacks', () => {
|
||||
const source = [
|
||||
'../../data/telegeographySubmarineCables.js',
|
||||
'../../app/layers/submarineCables.js',
|
||||
'../../app/layers/overlayHost.js',
|
||||
'rendering.js',
|
||||
'overlay.js',
|
||||
'surface.js',
|
||||
|
||||
+2
-5
@@ -1,8 +1,6 @@
|
||||
import { createApplicationTraffic } from '../app/layers/traffic.js';
|
||||
import { createSourceSlot } from '../app/sourceSlot.js';
|
||||
import { createTrafficSource } from '../layers/traffic/source.js';
|
||||
import { createTrafficLayer } from '../layers/traffic/index.js';
|
||||
import * as credits from './dataCredits.js';
|
||||
import * as render from '../renderGovernor.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
createTrafficSource(),
|
||||
@@ -14,9 +12,8 @@ const sourceSlot = createSourceSlot(
|
||||
},
|
||||
);
|
||||
export const configureTrafficSource = sourceSlot.configure;
|
||||
const layer = createTrafficLayer({
|
||||
const layer = createApplicationTraffic({
|
||||
source: sourceSlot.source,
|
||||
services: { credits, render },
|
||||
});
|
||||
export const getTrafficTimingDiagnostics = layer.getTrafficTimingDiagnostics;
|
||||
export const deriveTrafficFlowError = layer.deriveTrafficFlowError;
|
||||
|
||||
@@ -22,7 +22,10 @@ async function launch(overrides = {}, dotenv = '', omitCctv = false) {
|
||||
}
|
||||
await fs.mkdir(path.join(root, 'src', 'standalone'), { recursive: true });
|
||||
const catalog = await fs.readFile(new URL('./standalone/catalog.js', import.meta.url), 'utf8');
|
||||
await fs.writeFile(path.join(root, 'src', 'standalone', 'catalog.js'), omitCctv ? catalog.replace(/^\s*cctvLayer,\s*$/m, '') : catalog);
|
||||
await fs.writeFile(path.join(root, 'src', 'standalone', 'catalog.js'), catalog);
|
||||
await fs.mkdir(path.join(root, 'src', 'app'), { recursive: true });
|
||||
const assembly = await fs.readFile(new URL('./app/constructCatalog.js', import.meta.url), 'utf8');
|
||||
await fs.writeFile(path.join(root, 'src', 'app', 'constructCatalog.js'), omitCctv ? assembly.replace(/^\s*createApplicationCctv\(.*$/m, '') : assembly);
|
||||
await fs.mkdir(path.join(root, 'node_modules'));
|
||||
await fs.symlink(fileURLToPath(new URL('.', import.meta.resolve('vite/package.json'))), path.join(root, 'node_modules', 'vite'), 'dir');
|
||||
await fs.writeFile(path.join(root, '.env'), dotenv);
|
||||
|
||||
@@ -23,9 +23,10 @@ export function createStandaloneApplication({
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
const loaderStatus = loadingScreen.querySelector('.loader-status');
|
||||
let placeSearch;
|
||||
const catalog = createStandaloneCatalog();
|
||||
let catalog;
|
||||
return createApplication({
|
||||
createScene: (context) => {
|
||||
catalog = createStandaloneCatalog({ signal: context.signal });
|
||||
placeSearch = createStandalonePlaceSearch({
|
||||
...geospatial,
|
||||
resolveApiKey: () => googleApiKey,
|
||||
|
||||
+20
-37
@@ -1,40 +1,23 @@
|
||||
import flightsLayer from '../data/flights.js';
|
||||
import militaryFlightsLayer from '../data/militaryFlights.js';
|
||||
import alprCamerasLayer from '../data/alprCameras.js';
|
||||
import earthquakesLayer from '../data/earthquakes.js';
|
||||
import satellitesLayer from '../data/satellites.js';
|
||||
import rocketLaunchesLayer from '../data/rocketLaunches.js';
|
||||
import trafficLayer from '../data/traffic.js';
|
||||
import cctvLayer from '../data/cctv.js';
|
||||
import radioLayer from '../data/radio.js';
|
||||
import bikeshareLayer from '../data/bikeshare.js';
|
||||
import aisLiveVesselsLayer from '../data/aisLiveVessels.js';
|
||||
import militaryInstallationsLayer from '../data/militaryInstallations.js';
|
||||
import militaryAwarenessLayer from '../data/militaryAwareness.js';
|
||||
import localDataLayers from '../data/localLayers.js';
|
||||
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
|
||||
import { createApplicationCatalog } from '../app/constructCatalog.js';
|
||||
import { createStandaloneLayerSources } from './layerSources.js';
|
||||
export { createStandaloneReferenceSources } from './layerSources.js';
|
||||
|
||||
import { createLayerCatalog } from '../app/catalog.js';
|
||||
/** Create fresh layer instances using the existing standalone source choices. */
|
||||
export function createStandaloneCatalog({
|
||||
signal = new AbortController().signal,
|
||||
} = {}) {
|
||||
return createApplicationCatalog({
|
||||
sources: createStandaloneLayerSources(),
|
||||
signal,
|
||||
vesselOptions: {
|
||||
maxRows: import.meta.env?.VITE_AIS_LIVE_MAX_ROWS,
|
||||
maxLabels: import.meta.env?.VITE_AIS_LIVE_LABEL_MAX_ROWS,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Select the existing standalone instances and current persistence schema. */
|
||||
export function createStandaloneCatalog() {
|
||||
return createLayerCatalog(
|
||||
[
|
||||
flightsLayer,
|
||||
militaryFlightsLayer,
|
||||
earthquakesLayer,
|
||||
alprCamerasLayer,
|
||||
satellitesLayer,
|
||||
rocketLaunchesLayer,
|
||||
trafficLayer,
|
||||
cctvLayer,
|
||||
radioLayer,
|
||||
bikeshareLayer,
|
||||
aisLiveVesselsLayer,
|
||||
militaryInstallationsLayer,
|
||||
militaryAwarenessLayer,
|
||||
...localDataLayers,
|
||||
],
|
||||
LAYER_STATE_REGISTRY,
|
||||
);
|
||||
// Direct compatibility callers share one catalog; application startup supplies its own.
|
||||
let compatibilityCatalog;
|
||||
export function getStandaloneCatalog() {
|
||||
return (compatibilityCatalog ||= createStandaloneCatalog());
|
||||
}
|
||||
Vendored
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { createApplicationControls } from '../app/controls.js';
|
||||
import { createStandaloneCatalog } from './catalog.js';
|
||||
import { getStandaloneCatalog } from './catalog.js';
|
||||
export function createStandaloneControls(options) {
|
||||
return createApplicationControls({
|
||||
catalog: createStandaloneCatalog(),
|
||||
catalog: options?.catalog ?? getStandaloneCatalog(),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createApplicationData } from '../app/data.js';
|
||||
import { createStandaloneCatalog } from './catalog.js';
|
||||
import { getStandaloneCatalog } from './catalog.js';
|
||||
export function createStandaloneData(options) {
|
||||
return createApplicationData({
|
||||
catalog: createStandaloneCatalog(),
|
||||
catalog: options?.catalog ?? getStandaloneCatalog(),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
createOpenSkySource,
|
||||
createAdsbLolSource,
|
||||
createAisStreamSource,
|
||||
} from '../sources/live/standalone.js';
|
||||
import { createCctvSource } from '../layers/cctv/source.js';
|
||||
import { createRadioSource } from '../layers/radio/source.js';
|
||||
import { createTrafficSource } from '../layers/traffic/source.js';
|
||||
import { createBikeshareSource } from '../layers/bikeshare/source.js';
|
||||
import { createInstallationSource } from '../layers/installations/index.js';
|
||||
import { createSatelliteSource } from '../layers/satellites/index.js';
|
||||
import { createLaunchSource } from '../layers/launches/index.js';
|
||||
import { createOverpassAlprSource } from '../layers/alpr/index.js';
|
||||
import { createFirmsSource } from '../layers/firms/index.js';
|
||||
import { createUsgsEarthquakeSource } from '../layers/earthquakes/source.js';
|
||||
import { createBundledCableSource } from '../layers/submarineCables/bundledSource.js';
|
||||
|
||||
/** Existing reference feeds, usable independently of live source selection. */
|
||||
export function createStandaloneReferenceSources() {
|
||||
return {
|
||||
earthquakes: createUsgsEarthquakeSource(),
|
||||
cables: createBundledCableSource(),
|
||||
};
|
||||
}
|
||||
|
||||
/** Select standalone providers without starting their acquisition. */
|
||||
export function createStandaloneLayerSources() {
|
||||
return {
|
||||
...createStandaloneReferenceSources(),
|
||||
flights: createOpenSkySource(),
|
||||
military: createAdsbLolSource(),
|
||||
vessels: createAisStreamSource({
|
||||
apiUrl: import.meta.env?.VITE_AIS_LIVE_API_URL || '/api/ais-live',
|
||||
}),
|
||||
cctv: createCctvSource(),
|
||||
radio: createRadioSource(),
|
||||
traffic: createTrafficSource(),
|
||||
bikeshare: createBikeshareSource(),
|
||||
installations: createInstallationSource(),
|
||||
satellites: createSatelliteSource(),
|
||||
launches: createLaunchSource(),
|
||||
alpr: createOverpassAlprSource(),
|
||||
firms: createFirmsSource(),
|
||||
};
|
||||
}
|
||||
@@ -1,2 +1,15 @@
|
||||
/** Compatibility entry for the application's UI composition. */
|
||||
export { StyleManager } from './ui/composition.js';
|
||||
import { StyleManager as ApplicationControls } from './ui/composition.js';
|
||||
import { catalogControlServices } from './app/catalog.js';
|
||||
import { getStandaloneCatalog } from './standalone/catalog.js';
|
||||
/** Compatibility entry for direct standalone control construction. */
|
||||
export class StyleManager extends ApplicationControls {
|
||||
constructor(viewer, options = {}) {
|
||||
super(viewer, {
|
||||
...options,
|
||||
services: {
|
||||
...catalogControlServices(getStandaloneCatalog()),
|
||||
...options.services,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -40,18 +40,7 @@ import {
|
||||
setDetectionStyle,
|
||||
setDetectionTuning,
|
||||
} from '../data/detection.js';
|
||||
import trafficLayer from '../data/traffic.js';
|
||||
import flightsLayer from '../data/flights.js';
|
||||
import militaryFlightsLayer from '../data/militaryFlights.js';
|
||||
import { isTr3b, toggleTr3b } from '../data/tr3bRegistry.js';
|
||||
import satellitesLayer from '../data/satellites.js';
|
||||
import cctvLayer from '../data/cctv.js';
|
||||
import radioLayer from '../data/radio.js';
|
||||
import bikeshareLayer from '../data/bikeshare.js';
|
||||
import aisLiveVesselsLayer from '../data/aisLiveVessels.js';
|
||||
import militaryAwarenessLayer from '../data/militaryAwareness.js';
|
||||
import militaryInstallationsLayer from '../data/militaryInstallations.js';
|
||||
import rocketLaunchesLayer from '../data/rocketLaunches.js';
|
||||
import {
|
||||
cachedGroundFloor,
|
||||
cachedMeshFloor,
|
||||
@@ -114,19 +103,8 @@ export class StyleManager extends ApplicationShell {
|
||||
resumeDetection,
|
||||
setDetectionStyle,
|
||||
setDetectionTuning,
|
||||
trafficLayer,
|
||||
flightsLayer,
|
||||
militaryFlightsLayer,
|
||||
isTr3b,
|
||||
toggleTr3b,
|
||||
satellitesLayer,
|
||||
cctvLayer,
|
||||
radioLayer,
|
||||
bikeshareLayer,
|
||||
aisLiveVesselsLayer,
|
||||
militaryAwarenessLayer,
|
||||
militaryInstallationsLayer,
|
||||
rocketLaunchesLayer,
|
||||
cachedGroundFloor,
|
||||
cachedMeshFloor,
|
||||
GROUND_FLOOR_LIFT_M,
|
||||
|
||||
+16
-20
@@ -8,15 +8,10 @@ import {
|
||||
getSelectedEntityContext,
|
||||
isContextRecordActive,
|
||||
} from '../data/contextStore.js';
|
||||
import { getNextIssPass } from '../data/satellites.js';
|
||||
import { CCTV_FOCUS_RESULT } from '../data/cctv.js';
|
||||
import { CCTV_FOCUS_RESULT } from '../layers/cctv/index.js';
|
||||
import { contextModeWord } from '../contextModePolicy.js';
|
||||
import { createAnalystEngine } from '../data/analystEngine.js';
|
||||
import { layerFeedState } from '../data/manager.js';
|
||||
import militaryAwarenessLayer, {
|
||||
collectAircraftProximityWindow,
|
||||
contactsWindowFromSnapshot,
|
||||
} from '../data/militaryAwareness.js';
|
||||
import { initCameraVerbs, moveCamera, flyRoute, interruptCameraMotion, adjustOrbitRange } from '../cameraVerbs.js';
|
||||
import { cachedGroundFloor, warmGroundFloor } from '../data/groundFloor.js';
|
||||
import { isPickedWorldPosition } from '../data/scenePick.js';
|
||||
@@ -612,7 +607,7 @@ export function createGevActionRunner({ viewer, styleManager, dataManager, scene
|
||||
};
|
||||
}
|
||||
const contactsWindow = ['contacts', 'flights'].includes(mode)
|
||||
? activeContactsWindow()
|
||||
? activeContactsWindow(dataManager)
|
||||
: null;
|
||||
return {
|
||||
...withContextModeVocabulary(result),
|
||||
@@ -792,7 +787,7 @@ export function createGevActionRunner({ viewer, styleManager, dataManager, scene
|
||||
}
|
||||
|
||||
if (name === 'next_iss_pass') {
|
||||
return nextIssPass(viewer, args);
|
||||
return nextIssPass(viewer, dataManager, args);
|
||||
}
|
||||
|
||||
if (name === 'analyst_query') {
|
||||
@@ -2007,7 +2002,7 @@ function compassDir(azDeg) {
|
||||
return COMPASS_16[Math.round(((((azDeg % 360) + 360) % 360)) / 22.5) % 16];
|
||||
}
|
||||
|
||||
function nextIssPass(viewer, args) {
|
||||
function nextIssPass(viewer, dataManager, args) {
|
||||
let latDeg = Number.isFinite(args.latitude) ? args.latitude : null;
|
||||
let lonDeg = Number.isFinite(args.longitude) ? args.longitude : null;
|
||||
if (latDeg == null || lonDeg == null) {
|
||||
@@ -2017,7 +2012,7 @@ function nextIssPass(viewer, args) {
|
||||
lonDeg = Cesium.Math.toDegrees(carto.longitude);
|
||||
}
|
||||
const minElevDeg = Number.isFinite(args.minElevationDeg) ? args.minElevationDeg : 10;
|
||||
const result = getNextIssPass({ latDeg, lonDeg, minElevDeg });
|
||||
const result = dataManager?.layers?.get('satellites')?.module?.getNextIssPass?.({ latDeg, lonDeg, minElevDeg }) ?? { status: 'no-tle' };
|
||||
if (result.status === 'no-tle') {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -2337,7 +2332,7 @@ function getCurrentViewState(viewer, styleManager, dataManager, sceneDirector =
|
||||
...withContextModeVocabulary(styleManager.getContextModeState()),
|
||||
// The numbers on the operator's Contacts panel, so a window/count
|
||||
// question can be answered from what they are looking at.
|
||||
...(activeContactsWindow() ? { contactsWindow: activeContactsWindow() } : {}),
|
||||
...(activeContactsWindow(dataManager) ? { contactsWindow: activeContactsWindow(dataManager) } : {}),
|
||||
}
|
||||
: null,
|
||||
cockpit: typeof styleManager.getCockpitState === 'function'
|
||||
@@ -2408,19 +2403,19 @@ async function getEntityContext(viewer, dataManager, styleManager, args = {}, se
|
||||
* @param {object} result The general engine's result, reused for scope text.
|
||||
* @returns {object|null} A unified-count payload, or null.
|
||||
*/
|
||||
function aircraftProximityWindowForQuery(args, result) {
|
||||
function aircraftProximityWindowForQuery(dataManager, args, result) {
|
||||
const scope = args?.scope;
|
||||
if (String(scope?.kind || '').toLowerCase() !== 'radius') return null;
|
||||
const layers = Array.isArray(args.layers) ? args.layers : [];
|
||||
if (!layers.some((layer) => layer === 'flights' || layer === 'military')) return null;
|
||||
const snapshot = militaryAwarenessLayer.getContextSnapshot?.();
|
||||
const snapshot = dataManager?.layers?.get('military-awareness')?.module?.getContextSnapshot?.();
|
||||
const subject = snapshot?.subject;
|
||||
if (!subject?.position) return null;
|
||||
// An explicit centre only qualifies when it IS the subject; otherwise the
|
||||
// operator asked about somewhere else and must get that answer.
|
||||
if (scope.center && !centerMatchesSubject(scope.center, subject.position)) return null;
|
||||
const radiusM = Number.isFinite(scope.km) ? scope.km * 1000 : (snapshot.radiusM || 250_000);
|
||||
const window = collectAircraftProximityWindow(subject.position, { radiusM, subject });
|
||||
const window = dataManager.layers.get('military-awareness').module.collectAircraftProximityWindow(subject.position, { radiusM, subject });
|
||||
if (!window) return null;
|
||||
const label = subject.label || subject.id || 'the selected contact';
|
||||
const radiusKm = Math.round(radiusM / 1000);
|
||||
@@ -2459,7 +2454,7 @@ function aircraftProximityWindowForQuery(args, result) {
|
||||
military: window.military.length,
|
||||
aircraft: window.aircraft,
|
||||
},
|
||||
...(activeContactsWindow() ? { contactsWindow: activeContactsWindow() } : {}),
|
||||
...(activeContactsWindow(dataManager) ? { contactsWindow: activeContactsWindow(dataManager) } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3235,9 +3230,10 @@ const VIEWPORT_LOADED_LAYERS = new Set(['flights']);
|
||||
* diverge no matter which surface asks.
|
||||
* @returns {object|null} Panel-equivalent window counts.
|
||||
*/
|
||||
function activeContactsWindow() {
|
||||
function activeContactsWindow(dataManager) {
|
||||
try {
|
||||
return contactsWindowFromSnapshot(militaryAwarenessLayer.getContextSnapshot?.());
|
||||
const layer = dataManager?.layers?.get('military-awareness')?.module;
|
||||
return layer?.contactsWindowFromSnapshot?.(layer.getContextSnapshot?.()) ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -3263,7 +3259,7 @@ function analystProviders(viewer, dataManager, { recordLimitByLayer = null, plac
|
||||
* @returns {{lat: number, lon: number, label: string|null}|null} Subject centre.
|
||||
*/
|
||||
getContextSubject() {
|
||||
const snapshot = militaryAwarenessLayer.getContextSnapshot?.();
|
||||
const snapshot = dataManager?.layers?.get('military-awareness')?.module?.getContextSnapshot?.();
|
||||
const subject = snapshot?.subject;
|
||||
if (!subject?.position) return null;
|
||||
const carto = Cesium.Cartographic.fromCartesian(subject.position);
|
||||
@@ -3343,10 +3339,10 @@ async function runAnalystQuery(viewer, dataManager, args = {}, placeSearch = una
|
||||
// differ. The generic record/scope engine still owns explicit regions and
|
||||
// arbitrary points — only "how many aircraft around <this contact>" is
|
||||
// unified, because that is the question the panel is already answering.
|
||||
const entityWindow = aircraftProximityWindowForQuery(args, result);
|
||||
const entityWindow = aircraftProximityWindowForQuery(dataManager, args, result);
|
||||
if (entityWindow) return entityWindow;
|
||||
|
||||
const contactsWindow = activeContactsWindow();
|
||||
const contactsWindow = activeContactsWindow(dataManager);
|
||||
const aircraftQueried = (result.coverage?.layersQueried || [])
|
||||
.some((l) => l.layerKey === 'flights' || l.layerKey === 'military');
|
||||
// Both numbers, and which one answers the question. The window counts have
|
||||
|
||||
@@ -2838,7 +2838,7 @@ async function withAwareness(harness, run) {
|
||||
: originals.cartoFrom(value)
|
||||
);
|
||||
try {
|
||||
return await run();
|
||||
return await run(awareness);
|
||||
} finally {
|
||||
awareness.getContextSnapshot = originals.snapshot;
|
||||
flightsLayer.getNearby = originals.flightsNearby;
|
||||
@@ -2847,7 +2847,7 @@ async function withAwareness(harness, run) {
|
||||
}
|
||||
}
|
||||
|
||||
function analystRunner() {
|
||||
function analystRunner(awareness) {
|
||||
const flights = {
|
||||
id: 'flights',
|
||||
// Deliberately a DIFFERENT population from the proximity window: this is
|
||||
@@ -2869,7 +2869,7 @@ function analystRunner() {
|
||||
viewer,
|
||||
styleManager: {},
|
||||
dataManager: {
|
||||
layers: new Map([['flights', { module: flights }]]),
|
||||
layers: new Map([['flights', { module: flights }], ['military-awareness', { module: awareness }]]),
|
||||
isEnabled: (id) => id === 'flights',
|
||||
getAll: () => [{ id: 'flights', name: 'Live Flights', enabled: true, stats: { count: 1 } }],
|
||||
},
|
||||
@@ -2883,8 +2883,8 @@ test('front5: a nearby ask centres on the Contacts SUBJECT, not the selected dat
|
||||
flights: Array.from({ length: 111 }, (_, i) => ({ id: `F${i}`, icao24: `f${i}`, distance: 1000 * i })),
|
||||
military: Array.from({ length: 5 }, (_, i) => ({ id: `M${i}`, icao24: `m${i}`, distance: 500 * i })),
|
||||
});
|
||||
await withAwareness(harness, async () => {
|
||||
const runner = analystRunner();
|
||||
await withAwareness(harness, async (awareness) => {
|
||||
const runner = analystRunner(awareness);
|
||||
const result = await runner('analyst_query', {
|
||||
layers: ['flights', 'military'],
|
||||
// The centre the model reached for in the field: the selected datacenter.
|
||||
@@ -2913,13 +2913,13 @@ test('front5: the spoken count and the panel window are ONE number by constructi
|
||||
flights: Array.from({ length: 111 }, (_, i) => ({ id: `F${i}`, icao24: `f${i}` })),
|
||||
military: Array.from({ length: 5 }, (_, i) => ({ id: `M${i}`, icao24: `m${i}` })),
|
||||
});
|
||||
await withAwareness(harness, async () => {
|
||||
await withAwareness(harness, async (awareness) => {
|
||||
// What the PANEL computes for this subject...
|
||||
const panel = collectAircraftProximityWindow(harness.snapshot.subject.position, {
|
||||
subject: harness.snapshot.subject,
|
||||
});
|
||||
// ...and what VOICE answers for the same subject.
|
||||
const spoken = await analystRunner()('analyst_query', {
|
||||
const spoken = await analystRunner(awareness)('analyst_query', {
|
||||
layers: ['flights', 'military'],
|
||||
scope: { kind: 'radius', km: 250 },
|
||||
});
|
||||
@@ -2936,8 +2936,8 @@ test('front5: the spoken count and the panel window are ONE number by constructi
|
||||
test('front5: Contacts active with NO subject falls back to the view, not an empty panel', async () => {
|
||||
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
|
||||
const harness = awarenessSubjectHarness({ subject: null });
|
||||
await withAwareness(harness, async () => {
|
||||
const result = await analystRunner()('analyst_query', {
|
||||
await withAwareness(harness, async (awareness) => {
|
||||
const result = await analystRunner(awareness)('analyst_query', {
|
||||
layers: ['flights'],
|
||||
scope: { kind: 'radius', km: 250 },
|
||||
});
|
||||
@@ -2954,8 +2954,8 @@ test('front5: an explicit region still uses the region engine while Contacts is
|
||||
subject: { id: 'a1b2c3', label: 'N546PC' },
|
||||
flights: Array.from({ length: 111 }, (_, i) => ({ id: `F${i}`, icao24: `f${i}` })),
|
||||
});
|
||||
await withAwareness(harness, async () => {
|
||||
const result = await analystRunner()('analyst_query', {
|
||||
await withAwareness(harness, async (awareness) => {
|
||||
const result = await analystRunner(awareness)('analyst_query', {
|
||||
layers: ['flights'],
|
||||
scope: { kind: 'region', name: 'Texas' },
|
||||
});
|
||||
@@ -2988,8 +2988,8 @@ test('front5: the box DIAGONAL is not the subject — 1.32 km away is somewhere
|
||||
// separation is 1.32 km. This is the case the coordinator flagged: a centre
|
||||
// far enough to be a different place, slipping through on the diagonal.
|
||||
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
|
||||
await withAwareness(subjectWindowHarness(), async () => {
|
||||
const result = await analystRunner()('analyst_query', {
|
||||
await withAwareness(subjectWindowHarness(), async (awareness) => {
|
||||
const result = await analystRunner(awareness)('analyst_query', {
|
||||
layers: ['flights', 'military'],
|
||||
scope: { kind: 'radius', km: 250, center: { lat: 29.9 + 0.009, lon: -97.9 + 0.009 } },
|
||||
});
|
||||
@@ -3009,8 +3009,8 @@ test('front5: 0.99 km due EAST is the subject, though a degree box rejects it',
|
||||
// 0.99 km — inside 1 km — yet over the 0.01 box threshold. A box would send
|
||||
// the operator a different, smaller number for a centre that IS the contact.
|
||||
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
|
||||
await withAwareness(subjectWindowHarness(), async () => {
|
||||
const result = await analystRunner()('analyst_query', {
|
||||
await withAwareness(subjectWindowHarness(), async (awareness) => {
|
||||
const result = await analystRunner(awareness)('analyst_query', {
|
||||
layers: ['flights', 'military'],
|
||||
scope: { kind: 'radius', km: 250, center: { lat: 29.9, lon: -97.9 + 0.0103 } },
|
||||
});
|
||||
@@ -3164,3 +3164,21 @@ test('ALPR common names toggle only the registered camera layer through the norm
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('ISS voice lookup uses the registered satellite instance', async () => {
|
||||
const calls = [];
|
||||
const viewer = {
|
||||
clock: { onTick: { addEventListener: () => () => {} } },
|
||||
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
|
||||
camera: { moveEnd: { addEventListener() {} } },
|
||||
};
|
||||
const runner = createGevActionRunner({ viewer, styleManager: {}, dataManager: {
|
||||
layers: new Map([['satellites', { module: { getNextIssPass(query) {
|
||||
calls.push(query);
|
||||
return { status: 'none' };
|
||||
} } }]]),
|
||||
} });
|
||||
const result = await runner('next_iss_pass', { latitude: 30, longitude: -97, minElevationDeg: 15 });
|
||||
assert.deepEqual(calls, [{ latDeg: 30, lonDeg: -97, minElevDeg: 15 }]);
|
||||
assert.match(result.error, /No ISS pass above 15/);
|
||||
});
|
||||
Reference in new issue
Block a user