mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-07 03:44:00 +00:00
Keep installation and local GeoJSON pick state off Cesium entities
Installation selection fills and outlines record the installation they
belong to in the layer's installationMarkers store instead of an
installationId expando, and the click handler resolves them from there.
Named-marker points keep their { installationId } pick payload, which is
Core's own object, so sibling layers treat a point click as before; the
marker remembers its record id for visit() instead of reading the point.
The local GeoJSON package tracks the entities each layer loaded in a
per-instance set instead of a __localLayerId tag, and passes the stem
base to registerEntityContext as targetPosition instead of hanging it on
the entity; FIRMS does the same, and voice targeting reads the context
record. QA scripts find a layer's features by their Core context id.
This commit is contained in:
1 parent
c5c0803901
commit
7204d66f0b
13 files changed
+58
-28
No files matched your search
@@ -941,6 +941,11 @@ async function findClickableTarget() {
|
||||
const site = await page
|
||||
.evaluate(async () => {
|
||||
const viewer = window.__godsEyeView.viewer;
|
||||
// Core registers each local feature's context as `<layer id>:<feature id>`.
|
||||
const isDataCentre = (entity) =>
|
||||
window.__godsEyeView.entityRecords
|
||||
.read(entity)
|
||||
?.contextId?.startsWith('local-datacenters:');
|
||||
await window.__godsEyeView.dataManager.setEnabled(
|
||||
'local-datacenters',
|
||||
true,
|
||||
@@ -960,7 +965,7 @@ async function findClickableTarget() {
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
for (let i = 0; i < viewer.dataSources.length; i += 1) {
|
||||
for (const entity of viewer.dataSources.get(i).entities.values) {
|
||||
if (entity.__localLayerId !== 'local-datacenters') continue;
|
||||
if (!isDataCentre(entity)) continue;
|
||||
const point = centreOf(entity);
|
||||
if (!point) continue;
|
||||
const carto = window.Cesium
|
||||
@@ -993,8 +998,10 @@ async function findClickableTarget() {
|
||||
viewer.scene.requestRender();
|
||||
}, site);
|
||||
await wait(4000);
|
||||
const spot = await onScreen(
|
||||
(entity) => entity.__localLayerId === 'local-datacenters',
|
||||
const spot = await onScreen((entity) =>
|
||||
window.__godsEyeView.entityRecords
|
||||
.read(entity)
|
||||
?.contextId?.startsWith('local-datacenters:'),
|
||||
);
|
||||
return spot ? { ...spot, layer: 'data centre' } : null;
|
||||
}
|
||||
|
||||
@@ -218,9 +218,11 @@ try {
|
||||
const gev = window.__godsEyeView;
|
||||
const snap = () => infraIds.map((id) => {
|
||||
const visibleIds = [];
|
||||
// Core registers each local feature's context as `<layer id>:<feature id>`.
|
||||
for (let i = 0; i < gev.viewer.dataSources.length; i++) {
|
||||
for (const entity of gev.viewer.dataSources.get(i).entities.values) {
|
||||
if (entity.__localLayerId === id && entity.show) visibleIds.push(String(entity.id));
|
||||
const contextId = gev.entityRecords.read(entity)?.contextId;
|
||||
if (contextId?.startsWith(`${id}:`) && entity.show) visibleIds.push(String(entity.id));
|
||||
}
|
||||
}
|
||||
return visibleIds.sort();
|
||||
|
||||
@@ -17,9 +17,11 @@ export async function checkInstallationPan(page, dir) {
|
||||
'military-installations',
|
||||
).module;
|
||||
const centerIds = [];
|
||||
// Core registers each local feature's context as `<layer id>:<feature id>`.
|
||||
for (let i = 0; i < g.viewer.dataSources.length; i++)
|
||||
for (const e of g.viewer.dataSources.get(i).entities.values)
|
||||
if (e.__localLayerId === 'local-datacenters') centerIds.push(e.id);
|
||||
if (g.entityRecords.read(e)?.contextId?.startsWith('local-datacenters:'))
|
||||
centerIds.push(e.id);
|
||||
window.__polishPanFrames = [];
|
||||
window.__stopPolishPan = g.viewer.scene.postRender.addEventListener(() => {
|
||||
const sites = [],
|
||||
@@ -333,7 +335,8 @@ export async function checkInstallationSelection(
|
||||
for (let y = 250; y < 650; y += 75)
|
||||
for (let x = 400; x < 850; x += 75) {
|
||||
const pick = g.viewer.scene.pick({ x, y });
|
||||
if (!pick?.id?.installationId && !window.__godsEyeView.entityRecords.read(pick?.id)?.trackedId)
|
||||
// Nothing picked: no installation marker, outline or tracked contact.
|
||||
if (!pick?.id)
|
||||
return { x, y };
|
||||
}
|
||||
throw new Error('No empty map point');
|
||||
|
||||
@@ -1277,8 +1277,9 @@ try {
|
||||
outlines = 0;
|
||||
for (let i = 0; i < viewer.dataSources.length; i++)
|
||||
for (const entity of viewer.dataSources.get(i).entities.values) {
|
||||
if (!ids.has(entity.installationId || entity.id) || !entity.show)
|
||||
continue;
|
||||
// Outline entities are `<record id>:outline:<ring>`.
|
||||
const recordId = String(entity.id).replace(/:outline:\d+$/, '');
|
||||
if (!ids.has(recordId) || !entity.show) continue;
|
||||
if (entity.billboard || entity.point) markers++;
|
||||
if (entity.polyline) outlines++;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
getRenderGovernorDiagnostics,
|
||||
_resetRenderGovernorForTest,
|
||||
} from '../renderGovernor.js';
|
||||
import { getContextStore } from './contextStore.js';
|
||||
import { readEntityRecord } from './entityRecords.js';
|
||||
|
||||
/** The catalog's services, with the overlay host each test supplies. */
|
||||
const createLocalGeoJsonLayer = (options) =>
|
||||
@@ -486,7 +488,7 @@ for (const [heightM, minStemM, maxStemM] of [[500, 45, 90], [10000, 1100, 1400]]
|
||||
const env = await createRealLocalLayerHarness();
|
||||
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
|
||||
const entity = env.dataSources[0].entities.values[0];
|
||||
const carto = entity.__localBaseCarto;
|
||||
const carto = Cesium.Cartographic.fromCartesian(targetPositionOf(entity));
|
||||
env.viewer.camera.positionWC = Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, heightM);
|
||||
env.preRender.raise(env.viewer.scene, Cesium.JulianDate.now());
|
||||
const positions = entity.polyline.positions.getValue(Cesium.JulianDate.now());
|
||||
@@ -894,10 +896,16 @@ function runArmedRetryChain(env, t, clock, cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
/** The stem base an entity registered for voice targeting. */
|
||||
function targetPositionOf(entity) {
|
||||
const contextId = readEntityRecord(entity)?.contextId;
|
||||
return getContextStore().entities.get(contextId)?.targetPosition;
|
||||
}
|
||||
|
||||
/** Height of the (mutated-in-place) stem base — 0 until a sample lands. */
|
||||
function baseHeightM(env) {
|
||||
const entity = env.dataSources[0].entities.values[0];
|
||||
return Cesium.Cartographic.fromCartesian(entity.__localBaseCartesian).height;
|
||||
return Cesium.Cartographic.fromCartesian(targetPositionOf(entity)).height;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -327,6 +327,8 @@ export function createLocalGeoJsonLayer(
|
||||
},
|
||||
) {
|
||||
let _dataSource = null;
|
||||
// The entities this layer loaded, so its click and teardown recognise them.
|
||||
const _ownEntities = new WeakSet();
|
||||
let _enabled = false;
|
||||
let _clickHandler = null;
|
||||
let _count = 0;
|
||||
@@ -464,7 +466,7 @@ export function createLocalGeoJsonLayer(
|
||||
releaseDataSource(viewer);
|
||||
_overlayPublisher.hide();
|
||||
clearSelectedEntityContextForLayer(id);
|
||||
if (viewer?.selectedEntity?.__localLayerId === id) {
|
||||
if (_ownEntities.has(viewer?.selectedEntity)) {
|
||||
viewer.selectedEntity = undefined;
|
||||
}
|
||||
if (_preRenderRemover) {
|
||||
@@ -641,7 +643,7 @@ export function createLocalGeoJsonLayer(
|
||||
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
const feature = entities[i];
|
||||
feature.__localLayerId = id; // Tag it so our click handler knows it belongs to this layer
|
||||
_ownEntities.add(feature);
|
||||
|
||||
let pos = feature.position?.getValue(Cesium.JulianDate.now());
|
||||
|
||||
@@ -686,9 +688,6 @@ export function createLocalGeoJsonLayer(
|
||||
const properties = propertyObject(feature);
|
||||
const recordId = String(feature.id ?? i);
|
||||
|
||||
// Store references for bounded stem scaling and native picking.
|
||||
feature.__localBaseCarto = carto;
|
||||
feature.__localBaseCartesian = base;
|
||||
registerEntityContext(feature, {
|
||||
id: `${id}:${recordId}`,
|
||||
layerId: id,
|
||||
@@ -703,6 +702,9 @@ export function createLocalGeoJsonLayer(
|
||||
longitude: Number(
|
||||
Cesium.Math.toDegrees(carto.longitude).toFixed(6),
|
||||
),
|
||||
// The surface base voice targeting projects. Ground sampling
|
||||
// re-grounds it in place, so this stays current.
|
||||
targetPosition: base,
|
||||
});
|
||||
|
||||
// Constant properties are refreshed on the existing 450 ms source
|
||||
@@ -792,7 +794,7 @@ export function createLocalGeoJsonLayer(
|
||||
if (!_enabled) return;
|
||||
const picked = viewer.scene.pick(click.position);
|
||||
|
||||
if (picked && picked.id && picked.id.__localLayerId === id) {
|
||||
if (picked && _ownEntities.has(picked.id)) {
|
||||
const entity = picked.id;
|
||||
viewer.selectedEntity = entity;
|
||||
selectEntityContext(entity);
|
||||
@@ -1174,7 +1176,6 @@ function setLocalGroundHeight(record, height) {
|
||||
Cesium.Ellipsoid.WGS84,
|
||||
record.base,
|
||||
);
|
||||
record.entity.__localBaseCartesian = record.base;
|
||||
}
|
||||
|
||||
function updateLocalStemGeometry(viewer, record, now, knownDistance = null) {
|
||||
|
||||
@@ -185,9 +185,8 @@ export function createSelection({
|
||||
// Refreshed every registration: the anchor re-grounds onto the DEM when
|
||||
// its floor cell warms, and voice targeting must project the same point
|
||||
// the sprite renders at.
|
||||
fire.contextEntity.__localBaseCartesian =
|
||||
components.model.firePosition(fire);
|
||||
registerEntityContext(fire.contextEntity, {
|
||||
targetPosition: components.model.firePosition(fire),
|
||||
id: recordId,
|
||||
layerId: id,
|
||||
layerName: name,
|
||||
|
||||
@@ -3,5 +3,7 @@ import { createEntityStore } from '../../data/entityRecords.js';
|
||||
/**
|
||||
* What the installations layer last drew for a retained marker entity, so an
|
||||
* unchanged record leaves it alone: `heightM` (surface height) and `selected`.
|
||||
* Selection fills and outlines record the installation they belong to as
|
||||
* `recordId`, which a pick on them resolves through.
|
||||
*/
|
||||
export const installationMarkers = createEntityStore();
|
||||
@@ -128,6 +128,7 @@ export function createNamedMarkers({ state, services, parts, overlayHost }) {
|
||||
const marker = markers.get(alias);
|
||||
markers.delete(alias);
|
||||
marker.point.id = { installationId: record.id };
|
||||
marker.recordId = record.id;
|
||||
marker.entry.id = record.id;
|
||||
markers.set(record.id, marker);
|
||||
}
|
||||
@@ -148,10 +149,12 @@ export function createNamedMarkers({ state, services, parts, overlayHost }) {
|
||||
);
|
||||
let marker = markers.get(record.id);
|
||||
if (!marker) {
|
||||
const id = { installationId: record.id };
|
||||
marker = {
|
||||
recordId: record.id,
|
||||
point: points.add({
|
||||
id,
|
||||
// Core's own pick payload. An object, not the bare record id, so the
|
||||
// shared pick registry keeps treating a point click as it always has.
|
||||
id: { installationId: record.id },
|
||||
position,
|
||||
pixelSize: 7,
|
||||
color: parts.model.colorFor(record),
|
||||
@@ -197,7 +200,7 @@ export function createNamedMarkers({ state, services, parts, overlayHost }) {
|
||||
visit(visitor) {
|
||||
for (let i = 0; i < ordered.length; i++) {
|
||||
const marker = ordered[i];
|
||||
visitor(marker.point.id.installationId, marker.point);
|
||||
visitor(marker.recordId, marker.point);
|
||||
}
|
||||
},
|
||||
refresh,
|
||||
|
||||
@@ -84,7 +84,7 @@ export function createRendering({
|
||||
classificationType,
|
||||
},
|
||||
});
|
||||
fill.installationId = record.id;
|
||||
installationMarkers.write(fill).recordId = record.id;
|
||||
selectedFillIds.push(fill.id);
|
||||
}
|
||||
governorRequestRender('installation-selection-fill');
|
||||
@@ -267,7 +267,7 @@ export function createRendering({
|
||||
classificationType: Cesium.ClassificationType.BOTH,
|
||||
},
|
||||
});
|
||||
outline.installationId = record.id;
|
||||
installationMarkers.write(outline).recordId = record.id;
|
||||
entityIds.push(outline.id);
|
||||
}
|
||||
Object.assign(installationMarkers.write(entity), {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { isPointerFree } from '../../data/inputOwnership.js';
|
||||
import { LAYER_ID } from './policy.js';
|
||||
import { installationMarkers } from './markerState.js';
|
||||
|
||||
export function createSelection({
|
||||
state: layerState,
|
||||
@@ -58,9 +59,11 @@ export function createSelection({
|
||||
if (!isPointerFree()) return;
|
||||
if (!layerState.enabled) return;
|
||||
const picked = viewer.scene.pick(click.position);
|
||||
const target = picked?.id;
|
||||
const id =
|
||||
picked?.id?.installationId ||
|
||||
(typeof picked?.id?.id === 'string' ? picked.id.id : null);
|
||||
target?.installationId ||
|
||||
installationMarkers.read(target)?.recordId ||
|
||||
(typeof target?.id === 'string' ? target.id : null);
|
||||
if (id && layerState.recordById.has(id) && id !== layerState.selectedId) {
|
||||
selectRecord(id);
|
||||
} else if (layerState.selectedId) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import * as C from 'cesium';
|
||||
import { createRendering } from './rendering.js';
|
||||
import { createLifecycle } from './lifecycle.js';
|
||||
import { installationMarkers } from './markerState.js';
|
||||
|
||||
function harness() {
|
||||
const ring = [[-97, 30], [-96.99, 30], [-96.99, 30.01], [-97, 30]];
|
||||
@@ -23,7 +24,7 @@ test('selected multipart footprints classify Google tiles with one constant tran
|
||||
const fills = h.state.dataSource.entities.values.slice();
|
||||
assert.equal(fills.length, 2);
|
||||
for (const fill of fills) {
|
||||
assert.equal(fill.installationId, 'base');
|
||||
assert.equal(installationMarkers.read(fill)?.recordId, 'base');
|
||||
assert.equal(fill.polygon.classificationType.getValue(), C.ClassificationType.CESIUM_3D_TILE);
|
||||
assert.equal(fill.polygon.height, undefined);
|
||||
assert.equal(fill.polygon.extrudedHeight, undefined);
|
||||
|
||||
@@ -3983,7 +3983,7 @@ function visibleEntityContexts(
|
||||
|
||||
const candidates = [];
|
||||
for (const { record } of nearbyRecords) {
|
||||
const position = record.entity?.__localBaseCartesian;
|
||||
const position = record.targetPosition;
|
||||
if (!position) continue;
|
||||
const screen = Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
viewer.scene,
|
||||
|
||||
Reference in new issue
Block a user