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
+96 -24

No files matched your search

+3
View File
@@ -1252,6 +1252,7 @@
"src/layers/installations/lifecycle.js",
"src/layers/installations/model.js",
"src/layers/installations/policy.js",
"src/layers/installations/render/cesium.js",
"src/layers/installations/rendering.js",
"src/layers/installations/selection.js",
"src/layers/installations/source.js",
@@ -2130,6 +2131,7 @@
"src/layers/installations/lifecycle.js",
"src/layers/installations/model.js",
"src/layers/installations/policy.js",
"src/layers/installations/render/cesium.js",
"src/layers/installations/rendering.js",
"src/layers/installations/selection.js",
"src/layers/installations/source.js",
@@ -2968,6 +2970,7 @@
"src/layers/installations/lifecycle.js",
"src/layers/installations/model.js",
"src/layers/installations/policy.js",
"src/layers/installations/render/cesium.js",
"src/layers/installations/rendering.js",
"src/layers/installations/selection.js",
"src/layers/installations/source.js",
+6
View File
@@ -1,6 +1,8 @@
import { overlayHost } from './overlayHost.js';
import * as credits from '../../data/dataCredits.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 context from '../../data/contextStore.js';
import * as picking from '../../data/pickRegistry.js';
@@ -11,6 +13,10 @@ export function createApplicationInstallations({ surface, source }) {
return createInstallationsLayer({
source,
overlayHost,
rendererFactory: ({ engine, ...context }) =>
engine?.kind === 'navara'
? rendererFor('military-installations', engine, context)
: createInstallationsCesiumRenderer(context),
services: {
credits,
render,
+18 -12
View File
@@ -144,21 +144,27 @@ export function createControls({ state: layerState, services, parts, source }) {
*/
focusById(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
// reads as success to Context navigation and strands NEXT on this item.
if (!parts.selection.selectRecord(record.id)) return false;
layerState.engine.camera.flyToSphere(
{
center: geo.cartesian3.fromDegrees(
record.longitude,
record.latitude,
parts.rendering.installationSurfaceHeightM(record),
),
radius: 18000,
},
{ duration: 1.4 },
);
const sphere = {
center: geo.cartesian3.fromDegrees(
record.longitude,
record.latitude,
parts.rendering.installationSurfaceHeightM(record),
),
radius: 18000,
};
if (flyToSphere) flyToSphere.call(camera, sphere, { duration: 1.4 });
else
flyToBoundingSphere.call(layerState.viewer.camera, sphere, {
duration: 1.4,
});
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 { createViewport } from './viewport.js';
import { createSelection } from './selection.js';
import { createControls } from './controls.js';
import { createLifecycle } from './lifecycle.js';
import { createState } from './state.js';
import { createModel } from './model.js';
/** 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 (
typeof source?.getMappedSites !== 'function' ||
typeof source?.searchNearby !== 'function'
)
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 parts = {};
const context = { state, services, parts, source, overlayHost };
parts.model = createModel(context);
parts.rendering = createRendering(context);
parts.namedMarkers = createNamedMarkers(context);
parts.ingestion = createIngestion(context);
parts.viewport = createViewport(context);
parts.selection = createSelection(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(
{},
parts.controls.methods,
parts.lifecycle.methods,
lifecycle,
parts.ingestion?.methods,
{
approximateSurfaceDistanceM: parts.model.approximateSurfaceDistanceM,
classifyGoogleMilitaryPlace: parts.model.classifyGoogleMilitaryPlace,
installationSourceLabel: parts.model.installationSourceLabel,
installationSurfaceHeightM: parts.rendering.installationSurfaceHeightM,
installationSurfaceHeightM: parts.model.installationSurfaceHeightM,
installationWithinViewport: parts.model.installationWithinViewport,
installationResponseSaturated: parts.model.installationResponseSaturated,
installationRetryDelayMs: parts.viewport.installationRetryDelayMs,
+2
View File
@@ -1,5 +1,6 @@
import * as Cesium from 'cesium';
import { LAYER_ID } from './policy.js';
import { engineFromViewer } from '../../globe/cesium/engineFromViewer.js';
import {
claimCameraSensitivity,
releaseCameraSensitivity,
@@ -16,6 +17,7 @@ export function createLifecycle({
const methods = {
init(viewer, { engine = null } = {}) {
engine ||= engineFromViewer(viewer);
layerState.viewer = viewer;
layerState.engine = engine;
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');
}
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
* mapped military-land claim. Google currently has no documented military
@@ -144,6 +152,7 @@ export function createModel({ state: layerState, services, parts, source }) {
return {
approximateSurfaceDistanceM,
colorFor,
installationSurfaceHeightM,
classifyGoogleMilitaryPlace,
installationSourceLabel,
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),
};
}