mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
207 lines
7.9 KiB
JavaScript
207 lines
7.9 KiB
JavaScript
import { observeTrafficSurface } from './surface.js';
|
|
import * as Cesium from 'cesium';
|
|
import { TRAFFIC_TIMING_ENABLED } from './policy.js';
|
|
import {
|
|
claimCameraSensitivity,
|
|
releaseCameraSensitivity,
|
|
} from '../../data/cameraSensitivity.js';
|
|
|
|
export function createLifecycle({
|
|
state: layerState,
|
|
services,
|
|
parts,
|
|
source,
|
|
}) {
|
|
const { resetFlowTileCache } = source;
|
|
|
|
const methods = {
|
|
/**
|
|
* One-time initialisation. Creates the PointPrimitiveCollection and adds it
|
|
* to the scene (hidden). The collection is never removed and re-added — only
|
|
* toggled via `.show` to avoid destroy-on-remove errors.
|
|
*
|
|
* @param {Cesium.Viewer} viewer - The Cesium viewer instance.
|
|
*/
|
|
init(viewer) {
|
|
layerState._viewer = viewer;
|
|
layerState._pointCollection = new Cesium.PointPrimitiveCollection({
|
|
blendOption: Cesium.BlendOption.TRANSLUCENT,
|
|
});
|
|
// Add permanently — toggle with .show to avoid destroy-on-remove errors
|
|
viewer.scene.primitives.add(layerState._pointCollection);
|
|
layerState._pointCollection.show = false;
|
|
layerState._dots = [];
|
|
layerState._roads = [];
|
|
layerState._count = 0;
|
|
layerState._lastUpdate = null;
|
|
layerState._lastBounds = null;
|
|
layerState._fetching = false;
|
|
layerState._loadGeneration = 0;
|
|
layerState._densityScale = 1.0;
|
|
layerState._speedScale = 1.0;
|
|
layerState._lastViewCenter = null;
|
|
layerState._flowRoads = null;
|
|
layerState._flowError = null;
|
|
if (TRAFFIC_TIMING_ENABLED) {
|
|
layerState._trafficTimingCurrentAnchor = null;
|
|
layerState._trafficTimingSequence = 0;
|
|
layerState._trafficTimingTracesCreated = 0;
|
|
layerState._trafficTimingDroppedTraces = 0;
|
|
}
|
|
|
|
// Preset-aware dot styling: adopt the active post-FX style (persisted
|
|
// style restore may run before layer registration, so read the dataset)
|
|
// and follow StyleManager's gev:style-change event thereafter. Guarded
|
|
// for non-browser contexts; bound once per page (init survives layer
|
|
// destroy/re-register).
|
|
if (typeof window !== 'undefined') {
|
|
layerState._stylePreset =
|
|
document?.documentElement?.dataset?.gevStyle || 'normal';
|
|
if (!layerState._styleListenerBound) {
|
|
window.addEventListener('gev:style-change', (e) =>
|
|
parts.style.setStylePreset(e?.detail?.style),
|
|
);
|
|
layerState._styleListenerBound = true;
|
|
}
|
|
}
|
|
parts.style.refreshBucketColors();
|
|
console.log('[Data:Traffic] Initialized');
|
|
},
|
|
|
|
/**
|
|
* Enable the traffic layer. Shows the point collection, subscribes to the
|
|
* preRender animation loop and camera-change events, and kicks off an
|
|
* initial viewport check.
|
|
*
|
|
* @param {Cesium.Viewer} viewer - The Cesium viewer instance.
|
|
*/
|
|
enable(viewer) {
|
|
if (layerState._enabled) return;
|
|
layerState._enabled = true;
|
|
layerState._surfaceFrameRemover = observeTrafficSurface(viewer.scene);
|
|
if (layerState._roadMode !== 'tomtom') source.prefetch?.();
|
|
parts.animation.syncRenderDemand();
|
|
layerState._lastAnimTime = 0;
|
|
layerState._pointCollection.show = true;
|
|
|
|
layerState._preRenderRemover = viewer.scene.preRender.addEventListener(
|
|
parts.animation.animate,
|
|
);
|
|
if (TRAFFIC_TIMING_ENABLED) {
|
|
parts.timing.clearTrafficTimingEntries();
|
|
layerState._trafficTimingCurrentAnchor = null;
|
|
layerState._trafficTimingPostRenderRemovers = new Set();
|
|
layerState._trafficTimingMoveEndRemover =
|
|
viewer.camera.moveEnd.addEventListener(
|
|
parts.timing.markTrafficTimingMoveEnd,
|
|
);
|
|
}
|
|
|
|
// Share the 5% movement threshold with other camera-driven layers.
|
|
viewer.camera.changed.addEventListener(parts.viewport.onCameraChanged);
|
|
// Always inspect the final view, even when the last flight step is below
|
|
// camera.changed's movement threshold.
|
|
layerState._arrivalRemover = viewer.camera.moveEnd.addEventListener(() =>
|
|
parts.viewport.onCameraChanged({ immediate: true }),
|
|
);
|
|
claimCameraSensitivity(viewer.camera, 'traffic', 0.05);
|
|
|
|
// Enabling is explicit intent, not a camera gesture that needs settling.
|
|
parts.viewport.onCameraChanged({ immediate: true });
|
|
|
|
// Boot-order guard (field-test round 1: layer sat empty until the user
|
|
// moved): when the persisted layer state re-enables traffic during the
|
|
// intro flyTo, the initial check bails at high altitude — and a camera
|
|
// that then parks never re-fires camera.changed. Retry cheaply until the
|
|
// first load commits, then self-clear. Also acts as a safety kick if a
|
|
// failed first fetch left the viewport unloaded while parked.
|
|
clearInterval(layerState._enableKickTimer);
|
|
layerState._enableKickTimer = setInterval(() => {
|
|
if (
|
|
!layerState._enabled ||
|
|
layerState._lastUpdate ||
|
|
layerState._roadRetryStopped ||
|
|
layerState._retryAttempts >= 3
|
|
) {
|
|
clearInterval(layerState._enableKickTimer);
|
|
layerState._enableKickTimer = null;
|
|
return;
|
|
}
|
|
if (!layerState._fetching && !layerState._retryTimer)
|
|
parts.viewport.onCameraChanged();
|
|
}, 1500);
|
|
},
|
|
|
|
/**
|
|
* Disable the traffic layer. Cancels pending fetches, clears all dots,
|
|
* unsubscribes from events, and hides the point collection.
|
|
*
|
|
* @param {Cesium.Viewer} viewer - The Cesium viewer instance.
|
|
*/
|
|
disable(viewer) {
|
|
layerState._enabled = false;
|
|
services.credits?.hideOsmCredit?.(layerState._viewer, 'traffic');
|
|
parts.animation.syncRenderDemand();
|
|
clearTimeout(layerState._fetchTimeout);
|
|
clearInterval(layerState._enableKickTimer);
|
|
layerState._enableKickTimer = null;
|
|
clearTimeout(layerState._retryTimer);
|
|
layerState._retryTimer = null;
|
|
layerState._retryDelayMs = 1500;
|
|
parts.ingestion.cancelActiveFetch();
|
|
layerState._loadGeneration++;
|
|
layerState._fetching = false;
|
|
layerState._flowPending = 0;
|
|
layerState._roadError = null;
|
|
parts.animation.clearDots();
|
|
layerState._lastBounds = null;
|
|
layerState._lastViewCenter = null;
|
|
// A stale outage from the last session would misreport a fresh enable —
|
|
// the next load re-derives feed health from real evidence.
|
|
layerState._flowError = null;
|
|
|
|
layerState._surfaceFrameRemover?.();
|
|
layerState._surfaceFrameRemover = null;
|
|
if (layerState._preRenderRemover) {
|
|
layerState._preRenderRemover();
|
|
layerState._preRenderRemover = null;
|
|
}
|
|
if (TRAFFIC_TIMING_ENABLED) {
|
|
layerState._trafficTimingMoveEndRemover?.();
|
|
layerState._trafficTimingMoveEndRemover = null;
|
|
for (const remove of layerState._trafficTimingPostRenderRemovers || [])
|
|
remove();
|
|
layerState._trafficTimingPostRenderRemovers = null;
|
|
layerState._trafficTimingCurrentAnchor = null;
|
|
}
|
|
|
|
viewer.camera.changed.removeEventListener(parts.viewport.onCameraChanged);
|
|
layerState._arrivalRemover?.();
|
|
layerState._arrivalRemover = null;
|
|
releaseCameraSensitivity(viewer.camera, 'traffic');
|
|
if (layerState._pointCollection) layerState._pointCollection.show = false;
|
|
},
|
|
|
|
/**
|
|
* Permanently tear down the layer. Disables it, removes the point collection
|
|
* from the scene, and clears the tile cache.
|
|
*
|
|
* @param {Cesium.Viewer} viewer - The Cesium viewer instance.
|
|
*/
|
|
destroy(viewer) {
|
|
this.disable(viewer);
|
|
if (layerState._pointCollection) {
|
|
viewer.scene.primitives.remove(layerState._pointCollection);
|
|
layerState._pointCollection = null;
|
|
}
|
|
parts.rendering.removeHeatLines();
|
|
layerState._tileCache.clear();
|
|
resetFlowTileCache();
|
|
layerState._count = 0;
|
|
layerState._lastUpdate = null;
|
|
},
|
|
};
|
|
|
|
return { methods };
|
|
}
|