refactor(installations): inject engine renderer adapter

This commit is contained in:
German Khokhlov committed 2026-10-28 09:54:05 +04:00
1 parent f4f0088814
commit 965a82144c
7 files changed
+90 -18

No files matched your search

+3
View File
@@ -1252,6 +1252,7 @@
"src/layers/installations/lifecycle.js", "src/layers/installations/lifecycle.js",
"src/layers/installations/model.js", "src/layers/installations/model.js",
"src/layers/installations/policy.js", "src/layers/installations/policy.js",
"src/layers/installations/render/cesium.js",
"src/layers/installations/rendering.js", "src/layers/installations/rendering.js",
"src/layers/installations/selection.js", "src/layers/installations/selection.js",
"src/layers/installations/source.js", "src/layers/installations/source.js",
@@ -2130,6 +2131,7 @@
"src/layers/installations/lifecycle.js", "src/layers/installations/lifecycle.js",
"src/layers/installations/model.js", "src/layers/installations/model.js",
"src/layers/installations/policy.js", "src/layers/installations/policy.js",
"src/layers/installations/render/cesium.js",
"src/layers/installations/rendering.js", "src/layers/installations/rendering.js",
"src/layers/installations/selection.js", "src/layers/installations/selection.js",
"src/layers/installations/source.js", "src/layers/installations/source.js",
@@ -2968,6 +2970,7 @@
"src/layers/installations/lifecycle.js", "src/layers/installations/lifecycle.js",
"src/layers/installations/model.js", "src/layers/installations/model.js",
"src/layers/installations/policy.js", "src/layers/installations/policy.js",
"src/layers/installations/render/cesium.js",
"src/layers/installations/rendering.js", "src/layers/installations/rendering.js",
"src/layers/installations/selection.js", "src/layers/installations/selection.js",
"src/layers/installations/source.js", "src/layers/installations/source.js",
+6
View File
@@ -1,6 +1,8 @@
import { overlayHost } from './overlayHost.js'; import { overlayHost } from './overlayHost.js';
import * as credits from '../../data/dataCredits.js'; import * as credits from '../../data/dataCredits.js';
import { createInstallationsLayer } from '../../layers/installations/index.js'; import { createInstallationsLayer } from '../../layers/installations/index.js';
import { createInstallationsCesiumRenderer } from '../../layers/installations/render/cesium.js';
import { rendererFor } from './renderers.js';
import * as render from '../../renderGovernor.js'; import * as render from '../../renderGovernor.js';
import * as context from '../../data/contextStore.js'; import * as context from '../../data/contextStore.js';
import * as picking from '../../data/pickRegistry.js'; import * as picking from '../../data/pickRegistry.js';
@@ -11,6 +13,10 @@ export function createApplicationInstallations({ surface, source }) {
return createInstallationsLayer({ return createInstallationsLayer({
source, source,
overlayHost, overlayHost,
rendererFactory: ({ engine, ...context }) =>
engine?.kind === 'navara'
? rendererFor('military-installations', engine, context)
: createInstallationsCesiumRenderer(context),
services: { services: {
credits, credits,
render, render,
+12 -6
View File
@@ -144,21 +144,27 @@ export function createControls({ state: layerState, services, parts, source }) {
*/ */
focusById(id) { focusById(id) {
const record = layerState.recordById.get(String(id)); const record = layerState.recordById.get(String(id));
if (!record || !layerState.engine?.camera?.flyToSphere) return false; const camera = layerState.engine?.camera;
const flyToSphere = camera?.flyToSphere;
const flyToBoundingSphere =
layerState.viewer?.camera?.flyToBoundingSphere;
if (!record || (!flyToSphere && !flyToBoundingSphere)) return false;
// No camera flight without a real selection: a flight plus a stale subject // No camera flight without a real selection: a flight plus a stale subject
// reads as success to Context navigation and strands NEXT on this item. // reads as success to Context navigation and strands NEXT on this item.
if (!parts.selection.selectRecord(record.id)) return false; if (!parts.selection.selectRecord(record.id)) return false;
layerState.engine.camera.flyToSphere( const sphere = {
{
center: geo.cartesian3.fromDegrees( center: geo.cartesian3.fromDegrees(
record.longitude, record.longitude,
record.latitude, record.latitude,
parts.rendering.installationSurfaceHeightM(record), parts.rendering.installationSurfaceHeightM(record),
), ),
radius: 18000, radius: 18000,
}, };
{ duration: 1.4 }, if (flyToSphere) flyToSphere.call(camera, sphere, { duration: 1.4 });
); else
flyToBoundingSphere.call(layerState.viewer.camera, sphere, {
duration: 1.4,
});
return true; return true;
}, },
+44 -12
View File
@@ -1,41 +1,73 @@
import { createNamedMarkers } from './namedMarkers.js';
import { createModel } from './model.js';
import { createRendering } from './rendering.js';
import { createIngestion } from './ingestion.js'; import { createIngestion } from './ingestion.js';
import { createViewport } from './viewport.js'; import { createViewport } from './viewport.js';
import { createSelection } from './selection.js';
import { createControls } from './controls.js'; import { createControls } from './controls.js';
import { createLifecycle } from './lifecycle.js';
import { createState } from './state.js'; import { createState } from './state.js';
import { createModel } from './model.js';
/** Construct one layer with its own scene state and supplied application services. */ /** Construct one layer with its own scene state and supplied application services. */
export function createInstallationsLayer({ services, source, overlayHost }) { export function createInstallationsLayer({
services,
source,
overlayHost,
rendererFactory,
}) {
if ( if (
typeof source?.getMappedSites !== 'function' || typeof source?.getMappedSites !== 'function' ||
typeof source?.searchNearby !== 'function' typeof source?.searchNearby !== 'function'
) )
throw new TypeError('An installation source is required'); throw new TypeError('An installation source is required');
if (typeof rendererFactory !== 'function')
throw new TypeError('An installation renderer factory is required');
const state = createState({ services }); const state = createState({ services });
const parts = {}; const parts = {};
const context = { state, services, parts, source, overlayHost }; const context = { state, services, parts, source, overlayHost };
parts.model = createModel(context); parts.model = createModel(context);
parts.rendering = createRendering(context);
parts.namedMarkers = createNamedMarkers(context);
parts.ingestion = createIngestion(context); parts.ingestion = createIngestion(context);
parts.viewport = createViewport(context); parts.viewport = createViewport(context);
parts.selection = createSelection(context);
parts.controls = createControls(context); parts.controls = createControls(context);
parts.lifecycle = createLifecycle(context); let renderer = null;
const lifecycle = {
init(viewer, options = {}) {
if (renderer)
throw new Error('Installations layer is already initialized');
const engine = options.engine ?? null;
renderer = rendererFactory({ ...context, viewer, engine });
if (!renderer?.lifecycle?.methods)
throw new Error('Installation renderer is unavailable');
Object.assign(parts, renderer);
return renderer.lifecycle.methods.init(viewer, options);
},
enable(...args) {
return renderer?.lifecycle?.methods.enable(...args);
},
disable(...args) {
return renderer?.lifecycle?.methods.disable(...args);
},
destroy(...args) {
try {
return renderer?.lifecycle?.methods.destroy(...args);
} finally {
renderer = null;
for (const key of [
'rendering',
'namedMarkers',
'selection',
'lifecycle',
])
delete parts[key];
}
},
};
return Object.assign( return Object.assign(
{}, {},
parts.controls.methods, parts.controls.methods,
parts.lifecycle.methods, lifecycle,
parts.ingestion?.methods, parts.ingestion?.methods,
{ {
approximateSurfaceDistanceM: parts.model.approximateSurfaceDistanceM, approximateSurfaceDistanceM: parts.model.approximateSurfaceDistanceM,
classifyGoogleMilitaryPlace: parts.model.classifyGoogleMilitaryPlace, classifyGoogleMilitaryPlace: parts.model.classifyGoogleMilitaryPlace,
installationSourceLabel: parts.model.installationSourceLabel, installationSourceLabel: parts.model.installationSourceLabel,
installationSurfaceHeightM: parts.rendering.installationSurfaceHeightM, installationSurfaceHeightM: parts.model.installationSurfaceHeightM,
installationWithinViewport: parts.model.installationWithinViewport, installationWithinViewport: parts.model.installationWithinViewport,
installationResponseSaturated: parts.model.installationResponseSaturated, installationResponseSaturated: parts.model.installationResponseSaturated,
installationRetryDelayMs: parts.viewport.installationRetryDelayMs, installationRetryDelayMs: parts.viewport.installationRetryDelayMs,
+2
View File
@@ -1,5 +1,6 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import { LAYER_ID } from './policy.js'; import { LAYER_ID } from './policy.js';
import { engineFromViewer } from '../../globe/cesium/engineFromViewer.js';
import { import {
claimCameraSensitivity, claimCameraSensitivity,
releaseCameraSensitivity, releaseCameraSensitivity,
@@ -16,6 +17,7 @@ export function createLifecycle({
const methods = { const methods = {
init(viewer, { engine = null } = {}) { init(viewer, { engine = null } = {}) {
engine ||= engineFromViewer(viewer);
layerState.viewer = viewer; layerState.viewer = viewer;
layerState.engine = engine; layerState.engine = engine;
layerState.dataSource = new Cesium.CustomDataSource( layerState.dataSource = new Cesium.CustomDataSource(
+9
View File
@@ -43,6 +43,14 @@ export function createModel({ state: layerState, services, parts, source }) {
return rgbaFromCss(COLOR_BY_CLASS[record.class] || '#9ca6b0'); return rgbaFromCss(COLOR_BY_CLASS[record.class] || '#9ca6b0');
} }
function installationSurfaceHeightM(record) {
const floor = services.ground?.cachedGroundFloor?.(
record?.latitude,
record?.longitude,
);
return services.ground?.floorAltitudeM?.(null, floor) ?? 0;
}
/** /**
* Classify a Places text-search result without turning a name match into a * Classify a Places text-search result without turning a name match into a
* mapped military-land claim. Google currently has no documented military * mapped military-land claim. Google currently has no documented military
@@ -144,6 +152,7 @@ export function createModel({ state: layerState, services, parts, source }) {
return { return {
approximateSurfaceDistanceM, approximateSurfaceDistanceM,
colorFor, colorFor,
installationSurfaceHeightM,
classifyGoogleMilitaryPlace, classifyGoogleMilitaryPlace,
installationSourceLabel, installationSourceLabel,
installationWithinViewport, installationWithinViewport,
+14
View File
@@ -0,0 +1,14 @@
import { createLifecycle } from '../lifecycle.js';
import { createNamedMarkers } from '../namedMarkers.js';
import { createRendering } from '../rendering.js';
import { createSelection } from '../selection.js';
/** Compose the existing Cesium-owned scene, input and entity adapters. */
export function createInstallationsCesiumRenderer(context) {
return {
lifecycle: createLifecycle(context),
namedMarkers: createNamedMarkers(context),
rendering: createRendering(context),
selection: createSelection(context),
};
}