mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Core told time eight ways: Date.now() in most layers, JulianDate.now() for flights and military, Cesium's clock frozen at page load for the sky, the weather clock for observed imagery. They now read one clock, src/time/worldClock.js, exported as ./time. Live is a mode, not a timestamp: while live the clock is the wall clock, so every layer draws as before. Away from live only the layers that follow the clock move with it. The weather products follow it, as they followed the weather transport; a host opts others in with follow(id). - The weather clock takes its time from the world clock and drives its replay, stepping its frames as before. - Satellites, launches, flights and military (still 30 s and 15 s behind), transit, GNSS, Recent Imagery, street level, the HUD and the celestial ring read worldTimeFor(id). Cadences stay on the wall clock. - Cesium's sky (stars, sun, moon, atmosphere, lighting) draws at the world time each frame instead of the moment the page loaded. - Weather frames and satellites declare coverage() for a host's timeline.
141 lines
4.9 KiB
JavaScript
141 lines
4.9 KiB
JavaScript
import { createApplicationOperations } from './operations.js';
|
|
import * as Cesium from 'cesium';
|
|
import {
|
|
createApplicationViewer,
|
|
installTrackpadPinchZoom,
|
|
} from '../app/viewer.js';
|
|
import { registerDataCredits } from '../data/dataCredits.js';
|
|
import { configureCreditKeyboardAccess } from '../creditKeyboard.js';
|
|
import { MapStackController } from '../mapStackController.js';
|
|
import { loadPhotorealisticTileset } from '../mapStartup.js';
|
|
import { initLogoGaze } from '../logoGaze.js';
|
|
import {
|
|
uninstallRenderGovernor,
|
|
governorRequestRender,
|
|
} from '../renderGovernor.js';
|
|
import { describeError } from './errors.js';
|
|
import { worldClock, worldTimeFor } from '../time/worldClock.js';
|
|
import { followWorldTime } from '../time/viewerClock.js';
|
|
import { onLocaleChange, t } from '../i18n/index.js';
|
|
|
|
/** Construct the application globe using the caller's local configuration. */
|
|
export async function createApplicationScene({
|
|
requestServices,
|
|
googleApiKey,
|
|
googleTokens = null,
|
|
cesiumToken,
|
|
credits,
|
|
MapController = MapStackController,
|
|
mapOptions = {},
|
|
loaderStatus,
|
|
signal,
|
|
defer,
|
|
}) {
|
|
const operations = createApplicationOperations({
|
|
requests: requestServices,
|
|
signal,
|
|
});
|
|
defer(initLogoGaze());
|
|
const previousKey = window.__GOOGLE_MAPS_API_KEY__;
|
|
if (googleApiKey) {
|
|
window.__GOOGLE_MAPS_API_KEY__ = googleApiKey;
|
|
defer(() => {
|
|
if (window.__GOOGLE_MAPS_API_KEY__ !== googleApiKey) return;
|
|
if (previousKey === undefined) delete window.__GOOGLE_MAPS_API_KEY__;
|
|
else window.__GOOGLE_MAPS_API_KEY__ = previousKey;
|
|
});
|
|
}
|
|
loaderStatus.textContent = t('Configuring viewer...');
|
|
// Provider attribution stays visible, including clean-view and recording.
|
|
const creditContainer = document.createElement('div');
|
|
creditContainer.id = 'cesium-credits';
|
|
document.body.appendChild(creditContainer);
|
|
defer(() => creditContainer.remove());
|
|
const viewer = createApplicationViewer({
|
|
container: 'cesiumContainer',
|
|
creditContainer,
|
|
});
|
|
defer(() => {
|
|
uninstallRenderGovernor(viewer);
|
|
if (!viewer.isDestroyed()) viewer.destroy();
|
|
});
|
|
defer(installTrackpadPinchZoom(viewer));
|
|
defer(
|
|
followWorldTime(viewer, worldTimeFor('sky'), {
|
|
subscribe: worldClock.subscribe,
|
|
requestRender: () => governorRequestRender('world-time'),
|
|
}),
|
|
);
|
|
registerDataCredits(viewer, credits);
|
|
configureCreditKeyboardAccess(document);
|
|
defer(onLocaleChange(() => configureCreditKeyboardAccess(document)));
|
|
loaderStatus.textContent =
|
|
googleApiKey || googleTokens || cesiumToken
|
|
? t('Loading Google 3D Tiles...')
|
|
: t('Loading the keyless globe...');
|
|
const photoreal = await loadPhotorealisticTileset(Cesium, {
|
|
googleApiKey,
|
|
googleTokens,
|
|
cesiumToken,
|
|
});
|
|
const tileset = photoreal.tileset;
|
|
// A provider can finish after cancellation; retain ownership of its result.
|
|
defer(() => {
|
|
if (tileset && !tileset.isDestroyed()) {
|
|
if (!viewer.scene.primitives.remove(tileset)) tileset.destroy();
|
|
}
|
|
});
|
|
signal.throwIfAborted();
|
|
if (tileset) {
|
|
viewer.scene.primitives.add(tileset);
|
|
// NOTE: Cesium World Terrain intentionally disabled — conflicts with Google 3D Tiles at high zoom.
|
|
// Google Photorealistic 3D Tiles provide their own terrain/elevation.
|
|
viewer.scene.globe.show = false;
|
|
console.info(`[Init] Google 3D Tiles loaded via ${photoreal.route}.`);
|
|
} else {
|
|
if (photoreal.errors.length) {
|
|
const tileError = photoreal.errors.at(-1);
|
|
console.warn(
|
|
'[Init] Google 3D Tiles unavailable, using the keyless globe:',
|
|
tileError,
|
|
);
|
|
const tileErrorDetail = describeError(tileError);
|
|
loaderStatus.textContent = t(
|
|
'Google 3D Tiles unavailable ({detail}). Loading the keyless globe...',
|
|
{
|
|
detail: tileErrorDetail,
|
|
},
|
|
);
|
|
}
|
|
viewer.scene.globe.show = true;
|
|
}
|
|
|
|
loaderStatus.textContent = t('Initializing systems...');
|
|
|
|
const mapStackController = new MapController(viewer, {
|
|
requestRender: governorRequestRender,
|
|
...mapOptions,
|
|
googleTileset: tileset,
|
|
cesiumToken,
|
|
initialStack: tileset ? 'photoreal' : 'esri-imagery',
|
|
// Task 5 (height-datum fix): rebroadcast stack changes as a window
|
|
// CustomEvent so data layers (CCTV per-regime ground resolution) can
|
|
// react without coupling MapStackController to layer modules. Fires on
|
|
// 'switching'/'ready'/'error'; listeners derive the surface regime from
|
|
// live scene state, so intermediate emissions are harmless.
|
|
onChange: (state) => {
|
|
window.dispatchEvent(
|
|
new CustomEvent('gev:map-stack-changed', { detail: state }),
|
|
);
|
|
},
|
|
onError: (message) => console.warn('[MapStack]', message),
|
|
});
|
|
defer(() => mapStackController.destroy());
|
|
await mapStackController.setStack(tileset ? 'photoreal' : 'esri-imagery', {
|
|
silent: true,
|
|
});
|
|
|
|
signal.throwIfAborted();
|
|
return { viewer, tileset, mapStackController, operations };
|
|
}
|