mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
A final review of both PDFs found text the earlier checks missed:
- Map cards: fire confidence (high/nominal/low) and age (24h), vessel
types (TOWING, TANKER) and installation classes (BASE, AIRFIELD) showed
in English. They now go through the catalog; the data stays English.
- The wind readout kept "10-14 14:00 UTC" and "29.93°N 97.74°W"; it now
uses the locale's date, decimal separator and hemisphere letters.
- Catalog: one term per concept (label/подпись, basemap/подложка,
PROVIDER CONTROLS, HEARD, zoom in, REGIONAL NEWS), calques and broken
grammar in the scene editor, provider settings and Bhote Koshi notes,
and Spanish wording that read wrong ("restringidas por el proveedor",
"Incluye … no se incluyen", "justo ahora", "Sal").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
224 lines
6.9 KiB
JavaScript
224 lines
6.9 KiB
JavaScript
import { installationClassLabel } from './classLabel.js';
|
|
import * as Cesium from 'cesium';
|
|
import {
|
|
MILITARY_LABEL_CAP,
|
|
MILITARY_POINT_CAP,
|
|
} from '../../data/militaryNames.js';
|
|
import { LAYER_ID, COLOR_BY_CLASS } from './policy.js';
|
|
import { createInfrastructureOverlayEntry } from '../../data/infrastructureOverlayEntry.js';
|
|
|
|
/** Bounded point markers; the shared overlay owns text projection and decluttering. */
|
|
export function createNamedMarkers({ state, services, parts, overlayHost }) {
|
|
let points = null;
|
|
let entries = [];
|
|
const markers = new Map();
|
|
const screen = new Cesium.Cartesian2();
|
|
const occluder = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84);
|
|
let shownPoints = 0;
|
|
let ordered = [];
|
|
const viewMatrix = new Cesium.Matrix4();
|
|
let width = 0,
|
|
height = 0;
|
|
let removeChanged = null,
|
|
removeEnd = null,
|
|
removeFrame = null;
|
|
|
|
function refresh() {
|
|
const viewer = state.viewer;
|
|
if (!state.enabled || !points) return;
|
|
width = viewer.scene.canvas.clientWidth;
|
|
height = viewer.scene.canvas.clientHeight;
|
|
if (viewer.camera.viewMatrix)
|
|
Cesium.Matrix4.clone(viewer.camera.viewMatrix, viewMatrix);
|
|
occluder.cameraPosition = viewer.camera.positionWC;
|
|
shownPoints = 0;
|
|
for (let index = 0; index < ordered.length; index++) {
|
|
const marker = ordered[index];
|
|
const point = marker.point;
|
|
point.show = marker.named && occluder.isPointVisible(point.position);
|
|
if (!point.show) continue;
|
|
const projected = Cesium.SceneTransforms.worldToWindowCoordinates(
|
|
viewer.scene,
|
|
point.position,
|
|
screen,
|
|
);
|
|
if (
|
|
projected &&
|
|
screen.x >= 0 &&
|
|
screen.y >= 0 &&
|
|
screen.x < width &&
|
|
screen.y < height
|
|
)
|
|
shownPoints++;
|
|
}
|
|
}
|
|
|
|
function publishLabels() {
|
|
overlayHost.setEntries(LAYER_ID, entries, {
|
|
// Keep the bounded point cohort available while Contacts owns the window.
|
|
// The arbiter spends at most 24 slots on currently visible, separated titles.
|
|
cohortLimit: MILITARY_POINT_CAP,
|
|
collisionCapacity: MILITARY_LABEL_CAP,
|
|
maxVisible: MILITARY_LABEL_CAP - (state.selectedId ? 1 : 0),
|
|
moving: false,
|
|
visible: state.enabled,
|
|
});
|
|
}
|
|
|
|
function cameraChanged() {
|
|
refresh();
|
|
services.render.governorRequestRender('installation-names');
|
|
}
|
|
function frame() {
|
|
const viewer = state.viewer;
|
|
if (
|
|
viewer?.camera.viewMatrix &&
|
|
(!Cesium.Matrix4.equals(viewMatrix, viewer.camera.viewMatrix) ||
|
|
width !== viewer.scene.canvas.clientWidth ||
|
|
height !== viewer.scene.canvas.clientHeight)
|
|
)
|
|
refresh();
|
|
}
|
|
function enable() {
|
|
const viewer = state.viewer;
|
|
if (!viewer || removeChanged || removeEnd || removeFrame) return;
|
|
removeChanged = viewer.camera.changed?.addEventListener(cameraChanged);
|
|
removeEnd = viewer.camera.moveEnd?.addEventListener(cameraChanged);
|
|
removeFrame = viewer.scene.preRender?.addEventListener(frame);
|
|
if (points) points.show = true;
|
|
publishLabels();
|
|
overlayHost.setVisible(LAYER_ID, true);
|
|
cameraChanged();
|
|
}
|
|
function hide() {
|
|
removeChanged?.();
|
|
removeEnd?.();
|
|
removeFrame?.();
|
|
removeChanged = removeEnd = removeFrame = null;
|
|
if (points) points.show = false;
|
|
overlayHost.clearSource(LAYER_ID);
|
|
overlayHost.setVisible(LAYER_ID, false);
|
|
shownPoints = 0;
|
|
}
|
|
|
|
function sync(records) {
|
|
const viewer = state.viewer;
|
|
if (!viewer?.scene?.primitives) return;
|
|
if (!points) {
|
|
points = viewer.scene.primitives.add(
|
|
new Cesium.PointPrimitiveCollection(),
|
|
);
|
|
}
|
|
points.show = state.enabled;
|
|
const named = records
|
|
.filter((r) => r.namedArea || r.id === state.selectedId)
|
|
.sort(
|
|
(a, b) =>
|
|
Number(b.id === state.selectedId) -
|
|
Number(a.id === state.selectedId) || b.areaM2 - a.areaM2,
|
|
)
|
|
.slice(0, MILITARY_POINT_CAP);
|
|
const keep = new Set(named.map((r) => r.id));
|
|
for (const record of named) {
|
|
if (markers.has(record.id)) continue;
|
|
const alias = record.aliasIds?.find(
|
|
(id) => !keep.has(id) && markers.has(id),
|
|
);
|
|
if (!alias) continue;
|
|
const marker = markers.get(alias);
|
|
markers.delete(alias);
|
|
marker.point.id = { installationId: record.id };
|
|
marker.entry.id = record.id;
|
|
markers.set(record.id, marker);
|
|
}
|
|
for (const [id, marker] of markers)
|
|
if (!keep.has(id)) {
|
|
points.remove(marker.point);
|
|
markers.delete(id);
|
|
}
|
|
ordered = [];
|
|
entries = [];
|
|
for (const record of named) {
|
|
const position = Cesium.Cartesian3.fromDegrees(
|
|
record.longitude,
|
|
record.latitude,
|
|
record.pointOnly
|
|
? 0
|
|
: parts.rendering.installationSurfaceHeightM(record),
|
|
);
|
|
let marker = markers.get(record.id);
|
|
if (!marker) {
|
|
const id = { installationId: record.id };
|
|
marker = {
|
|
point: points.add({
|
|
id,
|
|
position,
|
|
pixelSize: 7,
|
|
color: parts.model.colorFor(record),
|
|
outlineColor: Cesium.Color.BLACK,
|
|
outlineWidth: 1,
|
|
disableDepthTestDistance: Infinity,
|
|
}),
|
|
entry: createInfrastructureOverlayEntry({
|
|
id: record.id,
|
|
source: LAYER_ID,
|
|
position,
|
|
title: record.name,
|
|
priority: record.areaM2 || 0,
|
|
accent: COLOR_BY_CLASS[record.class] || '#9ca6b0',
|
|
}),
|
|
};
|
|
markers.set(record.id, marker);
|
|
} else {
|
|
marker.point.position = position;
|
|
}
|
|
marker.named = record.namedArea;
|
|
marker.entry.variant =
|
|
record.id === state.selectedId ? 'selected' : 'card';
|
|
marker.entry.selected = record.id === state.selectedId;
|
|
marker.entry.details = marker.entry.selected
|
|
? [
|
|
installationClassLabel(record.class),
|
|
...(record.memberNames || []).slice(0, 3),
|
|
]
|
|
: [];
|
|
marker.entry.position = position;
|
|
marker.entry.title = record.name;
|
|
marker.entry.priority = record.areaM2 || 0;
|
|
entries.push(marker.entry);
|
|
ordered.push(marker);
|
|
}
|
|
if (state.enabled) publishLabels();
|
|
refresh();
|
|
services.render.governorRequestRender('installation-names');
|
|
}
|
|
return {
|
|
sync,
|
|
visit(visitor) {
|
|
for (let i = 0; i < ordered.length; i++) {
|
|
const marker = ordered[i];
|
|
visitor(marker.point.id.installationId, marker.point);
|
|
}
|
|
},
|
|
refresh,
|
|
enable,
|
|
hide,
|
|
stats: () => ({
|
|
namedMarkers: markers.size,
|
|
pointsOnScreen: shownPoints,
|
|
labelsOnScreen: state.enabled
|
|
? overlayHost.getDiagnostics?.().paintedBySource[LAYER_ID] || 0
|
|
: 0,
|
|
labelCap: MILITARY_LABEL_CAP,
|
|
}),
|
|
destroy() {
|
|
hide();
|
|
ordered = [];
|
|
if (points) state.viewer?.scene.primitives.remove(points);
|
|
points = null;
|
|
entries = [];
|
|
markers.clear();
|
|
},
|
|
};
|
|
}
|