Files
gods-eye-view/src/app/scene.js
T
German Khokhlov 2448662574 Merge remote-tracking branch 'origin/main' into german/map-render-error-state
# Conflicts:
#	src/app/scene.js
#	src/app/viewer.js
#	src/i18n/locales/ar.json
#	src/i18n/locales/de.json
#	src/i18n/locales/es.json
#	src/i18n/locales/fr.json
#	src/i18n/locales/it.json
#	src/i18n/locales/ja.json
#	src/i18n/locales/pt.json
#	src/i18n/locales/ru.json
#	src/i18n/locales/uk.json
#	src/i18n/locales/zh-CN.json
#	src/i18n/locales/zh-Hant.json
#	src/ui/views/SceneChromeView.vue
2026-11-10 14:29:34 +04:00

151 lines
5.3 KiB
JavaScript

import { createApplicationOperations } from './operations.js';
import * as Cesium from 'cesium';
import {
createApplicationViewer,
installFreeLookHeightLimit,
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 { isEmbeddedInline } from './embed.js';
import { installRenderFailureNotice } from '../ui/renderFailure.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,
// The render failure notice below replaces Cesium's dead-end panel.
showRenderLoopErrors: false,
});
defer(() => {
uninstallRenderGovernor(viewer);
if (!viewer.isDestroyed()) viewer.destroy();
});
// An inline panel must not reload or navigate its host's page.
defer(
installRenderFailureNotice(viewer, { allowReload: !isEmbeddedInline() }),
);
defer(installTrackpadPinchZoom(viewer));
defer(
followWorldTime(viewer, worldTimeFor('sky'), {
subscribe: worldClock.subscribe,
requestRender: () => governorRequestRender('world-time'),
}),
);
defer(installFreeLookHeightLimit(viewer));
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 };
}