Files
gods-eye-view/src/data/alprCameras.test.mjs
T
daikaginza cec5d99e13 fix(voice): keep FIRMS and ALPR entity labels English in voice data
The fire and camera labels in the context store, getStrongestFire() and
getSelectedInfo() were translated at the source, so the voice model got
"Пожар · FRP 12 МВт" or "Cámara ALPR" in track_entity,
get_entity_context and pointer_context while every other entity name in
those payloads is English. Keep them English, as before the interface
was translated; the voice card translates them where it shows them.
2026-10-17 07:32:38 +07:00

1516 lines
46 KiB
JavaScript

// src/data/alprCameras.test.mjs
// Pure helpers and actual layer lifecycle with a minimal viewer/input harness.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as Cesium from 'cesium';
import alprCamerasLayer, {
createAlprCamerasLayer,
configureAlprSource,
} from './alprCameras.js';
import { createAlprTileSource } from '../layers/alpr/source.js';
import { DATA_CREDITS } from './dataCredits.js';
import { setLocale } from '../i18n/index.js';
import {
clearSelectedEntityContextForLayer,
getContextStore,
getSelectedEntityContext,
registerEntityContext,
selectEntityContext,
} from './contextStore.js';
import {
MAX_RENDERED,
QUERY_LIMIT,
QUERY_SNAP_DEGREES,
QUERY_REUSE_MS,
alprRetryDelayMs,
boxContains,
buildOverpassQuery,
snapAlprBox,
destinationPointDeg,
isAlprSurveillanceType,
normalizeAlprNode,
} from './alprCameras.js';
test('normalizeAlprNode: maps a full OSM node to a plain record', () => {
const record = normalizeAlprNode({
type: 'node',
id: 12345,
lat: 30.2672,
lon: -97.7431,
tags: {
man_made: 'surveillance',
'surveillance:type': 'ALPR',
operator: 'Flock Safety',
'camera:type': 'fixed',
'surveillance:zone': 'traffic',
'camera:direction': '270',
ref: 'ATX-001',
check_date: '2026-06-01',
},
});
assert.deepEqual(record, {
id: 'alpr:12345',
osmId: 12345,
latitude: 30.2672,
longitude: -97.7431,
operator: 'Flock Safety',
manufacturer: null,
cameraType: 'fixed',
zone: 'traffic',
directionDeg: 270,
ref: 'ATX-001',
lastVerified: '2026-06-01',
source: null,
});
});
test('normalizeAlprNode: rejects non-node elements and missing coordinates', () => {
assert.equal(normalizeAlprNode(null), null);
assert.equal(normalizeAlprNode({ type: 'way', id: 1, tags: {} }), null);
assert.equal(
normalizeAlprNode({ type: 'node', id: 1, lat: NaN, lon: 1, tags: {} }),
null,
);
});
test('normalizeAlprNode: a present-but-blank direction tag reads as missing, not zero', () => {
const record = normalizeAlprNode({
type: 'node',
id: 1,
lat: 30,
lon: -97,
tags: {
man_made: 'surveillance',
'surveillance:type': 'ALPR',
'camera:direction': '',
},
});
assert.equal(record.directionDeg, null);
});
test('destinationPointDeg: due-north offset increases latitude, keeps longitude', () => {
const dest = destinationPointDeg(30, -97, 0, 100);
assert.ok(dest.latitude > 30);
assert.ok(Math.abs(dest.longitude - -97) < 1e-6);
});
test('alprRetryDelayMs: doubles from a 30s floor to a 240s ceiling', () => {
assert.equal(alprRetryDelayMs(0), 30000);
assert.equal(alprRetryDelayMs(30000), 60000);
assert.equal(alprRetryDelayMs(200000), 240000);
assert.equal(alprRetryDelayMs(240000), 240000);
});
test('isAlprSurveillanceType: accepts semicolon multi-values and mixed case, rejects other cameras', () => {
assert.equal(isAlprSurveillanceType('ALPR'), true);
assert.equal(isAlprSurveillanceType('alpr'), true);
assert.equal(isAlprSurveillanceType('camera;ALPR'), true);
assert.equal(isAlprSurveillanceType('ALPR; camera'), true);
assert.equal(isAlprSurveillanceType('camera'), false);
assert.equal(isAlprSurveillanceType('ALPRX'), false);
assert.equal(isAlprSurveillanceType(undefined), false);
});
test('normalizeAlprNode: keeps a camera;ALPR multi-value node and drops a plain camera', () => {
const multi = normalizeAlprNode({
type: 'node',
id: 2,
lat: 30,
lon: -97,
tags: { man_made: 'surveillance', 'surveillance:type': 'camera;ALPR' },
});
assert.equal(multi?.id, 'alpr:2');
const plain = normalizeAlprNode({
type: 'node',
id: 3,
lat: 30,
lon: -97,
tags: { man_made: 'surveillance', 'surveillance:type': 'camera' },
});
assert.equal(plain, null);
});
test('buildOverpassQuery: matches multi-value tags case-insensitively and asks for the full cap', () => {
const query = buildOverpassQuery(30, -98, 31, -97);
assert.match(
query,
/\["surveillance:type"~"[^"]*ALPR[^"]*",i\]/,
'regex, case-insensitive match',
);
assert.doesNotMatch(
query,
/"surveillance:type"="ALPR"/,
'no exact-only match',
);
assert.ok(query.endsWith(`out body ${QUERY_LIMIT};`));
assert.ok(
QUERY_LIMIT > 0 && QUERY_LIMIT <= 1500,
'bounded query, not a global camera catalog',
);
assert.ok(
MAX_RENDERED >= QUERY_LIMIT,
'a render cap below the query cap silently hides cameras while count reports them',
);
});
test('normalizeAlprNode: a whitespace-only direction tag reads as missing, not due north', () => {
const record = normalizeAlprNode({
type: 'node',
id: 9,
lat: 30,
lon: -97,
tags: {
man_made: 'surveillance',
'surveillance:type': 'ALPR',
'camera:direction': ' ',
},
});
assert.equal(record.directionDeg, null);
});
test('snapAlprBox: snaps outward to the query grid and clamps to valid ranges', () => {
const snapped = snapAlprBox({
south: 30.2671,
west: -97.7431,
north: 30.3012,
east: -97.7002,
});
assert.deepEqual(snapped, {
south: 30.25,
west: -97.75,
north: 30.35,
east: -97.7,
});
assert.ok(
snapped.south <= 30.2671 &&
snapped.north >= 30.3012 &&
snapped.west <= -97.7431 &&
snapped.east >= -97.7002,
);
assert.equal(
snapAlprBox({ south: 89.99, west: 179.99, north: 90, east: 180 }).north,
90,
);
assert.equal(
snapAlprBox({ south: -90, west: -180, north: -89.99, east: -179.99 }).west,
-180,
);
assert.ok(
QUERY_SNAP_DEGREES > 0 && QUERY_SNAP_DEGREES <= 0.1,
'grid stays a fraction of a metro so reuse cannot over-fetch',
);
});
test('boxContains: a nudged view stays inside its snapped box; a real pan leaves it', () => {
const outer = snapAlprBox({
south: 30.2671,
west: -97.7431,
north: 30.3012,
east: -97.7002,
});
assert.equal(
boxContains(outer, {
south: 30.27,
west: -97.74,
north: 30.3,
east: -97.71,
}),
true,
);
assert.equal(
boxContains(outer, {
south: 30.36,
west: -97.74,
north: 30.4,
east: -97.71,
}),
false,
);
assert.equal(boxContains(null, outer), false);
});
const cameraNode = (id = 42, extra = {}) => ({
type: 'node',
id,
lat: 30.2672,
lon: -97.7431,
tags: {
man_made: 'surveillance',
'surveillance:type': 'ALPR',
manufacturer: 'Flock Safety',
},
...extra,
});
const cameraResponse = (elements = [cameraNode()], stale = false) => ({
ok: true,
status: 200,
headers: {
get: (name) => (name === 'x-overpass-cache' && stale ? 'STALE' : null),
},
json: async () => ({ elements }),
});
// Real layer, Cesium entities and context store. Only the DOM, upstream fetch,
// camera rectangle and input event delivery are substituted (no GL in Node).
function cameraHarness(layer = alprCamerasLayer) {
const originals = {
document: globalThis.document,
window: globalThis.window,
fetch: globalThis.fetch,
now: Date.now,
};
const events = new EventTarget();
globalThis.document = { addEventListener() {}, removeEventListener() {} };
globalThis.window = events;
let now = originals.now();
Date.now = () => now;
let response = async () => cameraResponse();
const requests = [];
globalThis.fetch = (...args) => {
// Marker floors resolve through the shared terrain service; these tests
// count camera requests only.
if (String(args[0]).includes('/api/terrain/heights'))
return Promise.resolve(Response.json({ results: [] }));
requests.push(args);
return response(...args);
};
// Rendering tests use normalized records; tile transport has its own fixture suite.
if (layer === alprCamerasLayer)
configureAlprSource({
...createAlprTileSource(),
async fetch(box, signal) {
const response = await globalThis.fetch('/test/cameras', {
signal,
body: `data=${encodeURIComponent(buildOverpassQuery(box.south, box.west, box.north, box.east))}`,
});
if (!response.ok)
throw new Error('Camera source temporarily unavailable');
const body = await response.json();
if (!Array.isArray(body.elements) || body.remark)
throw new Error('Incomplete fixture snapshot');
signal?.throwIfAborted();
return {
records: [
...new Map(
body.elements
.slice(0, QUERY_LIMIT)
.map(normalizeAlprNode)
.filter(Boolean)
.map((r) => [r.id, r]),
).values(),
],
stale: response.headers.get('x-overpass-cache') === 'STALE',
saturated: body.elements.length >= QUERY_LIMIT,
noCoverage: body.noCoverage,
zoomIn: body.zoomIn,
};
},
});
let box = { south: 30.26, west: -97.75, north: 30.28, east: -97.73 };
let source, click, picked;
const credits = new Set();
const viewer = {
creditDisplay: {
addStaticCredit: (credit) => credits.add(credit),
removeStaticCredit: (credit) => credits.delete(credit),
},
camera: {
moveEnd: new Cesium.Event(),
computeViewRectangle: () =>
box
? Cesium.Rectangle.fromDegrees(
box.west,
box.south,
box.east,
box.north,
)
: null,
},
scene: {
canvas: { addEventListener() {}, removeEventListener() {} },
globe: { ellipsoid: Cesium.Ellipsoid.WGS84 },
postRender: new Cesium.Event(),
pick: () => picked,
},
dataSources: {
add: (value) => {
source = value;
},
remove() {},
},
};
const setInput = Cesium.ScreenSpaceEventHandler.prototype.setInputAction;
Cesium.ScreenSpaceEventHandler.prototype.setInputAction = function (
fn,
type,
modifier,
) {
if (type === Cesium.ScreenSpaceEventType.LEFT_CLICK) click = fn;
return setInput.call(this, fn, type, modifier);
};
try {
layer.init(viewer);
} finally {
Cesium.ScreenSpaceEventHandler.prototype.setInputAction = setInput;
}
layer.enable();
return {
viewer,
requests,
credits,
get source() {
return source;
},
setFetch(fn) {
response = fn;
},
setBox(value) {
box = value;
},
expire() {
now += QUERY_REUSE_MS + 1;
},
advance(ms) {
now += ms;
},
click(id) {
const entity = typeof id === 'string' ? source.entities.getById(id) : id;
picked = entity ? { id: entity } : undefined;
click({ position: new Cesium.Cartesian2() });
},
restore() {
layer.destroy(viewer);
Date.now = originals.now;
for (const name of ['window', 'document', 'fetch']) {
if (originals[name] === undefined) delete globalThis[name];
else globalThis[name] = originals[name];
}
},
};
}
test('OSM attribution stays inline while ALPR displays and follows toggles', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const h = cameraHarness();
try {
assert.equal(h.credits.size, 0);
await alprCamerasLayer.update();
assert.equal(h.credits.size, 1);
const [credit] = h.credits;
assert.equal(credit.showOnScreen, true);
assert.match(credit.html, />© OpenStreetMap</);
assert.doesNotMatch(credit.html, /OpenMapTiles|Map and place data/);
t.mock.timers.tick(6000);
assert.equal(h.credits.size, 1);
alprCamerasLayer.disable();
assert.equal(h.credits.size, 0);
alprCamerasLayer.enable();
await alprCamerasLayer.update();
assert.equal(h.credits.size, 1);
assert.match(
DATA_CREDITS.find((entry) => entry.key === 'openstreetmap').html,
/OpenStreetMap contributors.*ODbL/,
);
} finally {
h.restore();
}
});
test('all manufacturers share one ALPR title and color; only supplied metadata appears underneath', async () => {
const h = cameraHarness();
try {
h.setFetch(async () =>
cameraResponse([
cameraNode(42, {
tags: {
'surveillance:type': 'ALPR',
manufacturer: 'Flock Safety',
operator: 'City Police',
'camera:type': 'fixed',
},
}),
cameraNode(43, {
tags: {
'surveillance:type': 'ALPR',
manufacturer: 'Motorola Solutions',
},
}),
cameraNode(44, { tags: { 'surveillance:type': 'ALPR' } }),
]),
);
await alprCamerasLayer.update();
const entities = h.source.entities.values;
assert.equal(alprCamerasLayer.name, 'ALPR Cameras');
assert.deepEqual(
entities.map((entity) => entity.gevLabelModel.title),
['ALPR-0042', 'ALPR-0043', 'ALPR-0044'],
);
assert.equal(
new Set(entities.map((entity) => entity.billboard.image.getValue())).size,
1,
);
assert.deepEqual(entities[0].gevLabelModel.details, [
'OSM MAPPED',
'FLOCK SAFETY · CITY POLICE · FIXED',
'PUBLIC MAP DATA',
]);
assert.deepEqual(entities[1].gevLabelModel.details, [
'OSM MAPPED',
'MOTOROLA SOLUTIONS',
'PUBLIC MAP DATA',
]);
assert.deepEqual(
entities[2].gevLabelModel.details,
['OSM MAPPED', 'PUBLIC MAP DATA'],
'unknown metadata is not guessed, but the source is always named',
);
for (const entity of entities) {
h.click(entity.id);
assert.equal(getSelectedEntityContext().label, 'ALPR camera');
assert.equal(getSelectedEntityContext().layerName, 'ALPR Cameras');
}
} finally {
h.restore();
}
});
// The voice model reads the label as data; the voice card translates it.
test('the camera label stays English data in a translated interface', async () => {
const h = cameraHarness();
setLocale('ru');
try {
h.setFetch(async () =>
cameraResponse([
cameraNode(42, { tags: { 'surveillance:type': 'ALPR' } }),
]),
);
await alprCamerasLayer.update();
h.click(h.source.entities.values[0].id);
assert.equal(getSelectedEntityContext().label, 'ALPR camera');
} finally {
setLocale('en');
h.restore();
}
});
test('invalid OSM ids and out-of-range coordinates never become camera entities', () => {
for (const id of [undefined, null, 0, -1, 1.5, '42', NaN])
assert.equal(normalizeAlprNode(cameraNode(id, { id })), null);
for (const coords of [
{ lat: 95 },
{ lat: -95 },
{ lon: 181 },
{ lon: -181 },
]) {
assert.equal(normalizeAlprNode(cameraNode(42, coords)), null);
}
});
test('clicking the selected camera or empty map deselects, including after refresh', async () => {
const h = cameraHarness();
try {
await alprCamerasLayer.update();
for (const next of ['alpr:42', null]) {
h.click('alpr:42');
assert.equal(getSelectedEntityContext()?.id, 'alpr:42');
h.click(next);
assert.equal(getSelectedEntityContext(), null);
h.expire();
await alprCamerasLayer.update();
assert.equal(getSelectedEntityContext(), null);
assert.equal(
h.source.entities.getById('alpr:42').billboard.width.getValue(),
38,
);
}
} finally {
h.restore();
}
});
test('a refresh cannot reclaim selection from another layer or an explicit clear', async () => {
const h = cameraHarness();
try {
await alprCamerasLayer.update();
h.click('alpr:42');
const aircraft = { id: 'flight:TEST' };
registerEntityContext(aircraft, { id: aircraft.id, layerId: 'flights' });
selectEntityContext(aircraft);
h.expire();
await alprCamerasLayer.update();
assert.equal(getSelectedEntityContext()?.id, aircraft.id);
h.click('alpr:42');
clearSelectedEntityContextForLayer('alpr-cameras');
h.expire();
await alprCamerasLayer.update();
assert.equal(getSelectedEntityContext(), null);
} finally {
h.restore();
}
});
test('only a surviving selected camera keeps selection through a dataset refresh', async () => {
const h = cameraHarness();
try {
await alprCamerasLayer.update();
h.click('alpr:42');
h.expire();
await alprCamerasLayer.update();
assert.equal(getSelectedEntityContext()?.id, 'alpr:42');
h.setFetch(async () => cameraResponse([cameraNode(43)]));
h.expire();
await alprCamerasLayer.update();
assert.equal(getSelectedEntityContext(), null);
} finally {
h.restore();
}
});
test('disable removes contexts and highlight; cache reuse after re-enable does not reselect', async () => {
const h = cameraHarness();
try {
await alprCamerasLayer.update();
h.click('alpr:42');
alprCamerasLayer.disable();
assert.equal(getSelectedEntityContext(), null);
assert.equal(getContextStore().entities.size, 0);
alprCamerasLayer.enable();
await alprCamerasLayer.update();
assert.equal(getSelectedEntityContext(), null);
assert.equal(
h.source.entities.getById('alpr:42').billboard.width.getValue(),
38,
);
} finally {
h.restore();
}
});
test('retry clears the old terminal error while the new request is loading', async () => {
const h = cameraHarness();
try {
h.setFetch(async () => ({ ...cameraResponse(), ok: false, status: 429 }));
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'unavailable');
let resolve;
h.setFetch(
() =>
new Promise((r) => {
resolve = r;
}),
);
const pending = alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().loading, true);
assert.equal(alprCamerasLayer.getStats().error, null);
assert.equal(alprCamerasLayer.getStats().status, 'loading');
resolve(cameraResponse());
await pending;
assert.equal(alprCamerasLayer.getStats().status, 'ready');
} finally {
h.restore();
}
});
test('a cancelled older request cannot overwrite a newer request with a late network error', async () => {
const h = cameraHarness();
try {
let rejectOld, resolveNew;
h.setFetch(
() =>
new Promise((resolve, reject) => {
rejectOld = reject;
}),
);
const old = alprCamerasLayer.update();
h.setBox({ south: 31, west: -98, north: 31.02, east: -97.98 });
h.setFetch(
() =>
new Promise((resolve) => {
resolveNew = resolve;
}),
);
const fresh = alprCamerasLayer.update();
rejectOld(new TypeError('Network error on a cancelled request'));
await old;
assert.equal(alprCamerasLayer.getStats().status, 'loading');
assert.equal(alprCamerasLayer.getStats().error, null);
resolveNew(cameraResponse([]));
await fresh;
assert.equal(alprCamerasLayer.getStats().status, 'empty');
} finally {
h.restore();
}
});
test('invalid or partial successful payloads are failures, not an empty all-clear', async () => {
const h = cameraHarness();
try {
for (const payload of [
{},
{ elements: null },
{ elements: [], remark: 'runtime error: Query timed out' },
]) {
h.setFetch(async () => ({
...cameraResponse(),
json: async () => payload,
}));
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'unavailable');
}
} finally {
h.restore();
}
});
test('duplicate nodes render once with a count that agrees with the entity set', async () => {
const h = cameraHarness();
try {
h.setFetch(async () => cameraResponse([cameraNode(), cameraNode()]));
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'ready');
assert.equal(alprCamerasLayer.getStats().count, 1);
assert.equal(h.source.entities.values.length, 1);
} finally {
h.restore();
}
});
test('zooming out hides local markers, releases selection and makes no unbounded request', async () => {
const h = cameraHarness();
try {
await alprCamerasLayer.update();
h.click('alpr:42');
h.setBox(null);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'zoom-in');
assert.equal(alprCamerasLayer.getStats().count, 0);
assert.equal(getSelectedEntityContext(), null);
assert.equal(h.source.entities.values.length, 0);
assert.equal(h.requests.length, 1);
} finally {
h.restore();
}
});
test('state, country and dateline views never request data; returning to the city reuses its cache', async () => {
const h = cameraHarness();
const city = { south: 30.26, west: -97.75, north: 30.28, east: -97.73 };
const wideViews = [
{ south: 26, west: -107, north: 36, east: -93 },
{ south: 24, west: -125, north: 50, east: -66 },
{ south: 30, west: -99, north: 31, east: -95 },
{ south: -1, west: 179, north: 1, east: -179 },
];
try {
for (const box of wideViews) {
h.setBox(box);
await alprCamerasLayer.update();
assert.equal(h.requests.length, 0, 'fresh wide view must not query');
assert.equal(alprCamerasLayer.getStats().status, 'zoom-in');
assert.equal(h.credits.size, 0, 'no data presentation has occurred');
}
h.setBox(city);
await alprCamerasLayer.update();
h.click('alpr:42');
for (const box of wideViews) {
h.setBox(box);
await alprCamerasLayer.update();
const stats = alprCamerasLayer.getStats();
assert.equal(stats.status, 'zoom-in');
assert.equal(stats.loading, false);
assert.equal(stats.error, null);
assert.equal(stats.retryAt, 0);
assert.equal(stats.count, 0);
assert.equal(getSelectedEntityContext(), null);
assert.equal(h.requests.length, 1, 'zoom out must not query');
}
h.setBox(city);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().count, 1);
assert.equal(h.requests.length, 1, 'return uses fresh cached city data');
} finally {
h.restore();
}
});
test('nearby camera moves reuse the snapped query but count and register only in-view nodes', async () => {
const h = cameraHarness();
try {
h.setFetch(async () =>
cameraResponse([cameraNode(), cameraNode(43, { lon: -97.71 })]),
);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().count, 1);
h.setBox({ south: 30.26, west: -97.72, north: 30.28, east: -97.7 });
await alprCamerasLayer.update();
assert.equal(h.requests.length, 1);
assert.deepEqual(
h.source.entities.values.map((e) => e.id),
['alpr:43'],
);
assert.deepEqual([...getContextStore().entities.keys()], ['alpr:43']);
h.expire();
await alprCamerasLayer.update();
assert.equal(h.requests.length, 2);
} finally {
h.restore();
}
});
test('empty mapped coverage, stale data and a saturated response remain distinct', async () => {
const h = cameraHarness();
try {
h.setFetch(async () => cameraResponse([]));
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'empty');
assert.match(
alprCamerasLayer.getStats().loadingLabel,
/No ALPR data for this area/,
);
h.expire();
h.setFetch(async () =>
cameraResponse(
Array.from({ length: QUERY_LIMIT }, (_, i) => cameraNode(i + 1)),
true,
),
);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'stale');
assert.equal(alprCamerasLayer.getStats().error, null);
assert.equal(alprCamerasLayer.getStats().saturated, true);
assert.match(
alprCamerasLayer.getStats().loadingLabel,
/cached.*Coverage limited/,
);
} finally {
h.restore();
}
});
test('selected readout samples the rendered ground, not zero elevation or the direction line midpoint', async () => {
const h = cameraHarness();
try {
let samples = 0;
h.viewer.scene.sampleHeightSupported = true;
h.viewer.scene.sampleHeight = (point, excluded) => {
samples += 1;
assert.equal(excluded[0].id, 'alpr:42');
return 187;
};
h.setFetch(async () =>
cameraResponse([
cameraNode(42, {
tags: { 'surveillance:type': 'ALPR', 'camera:direction': '90' },
}),
]),
);
await alprCamerasLayer.update();
h.click('alpr:42');
const entity = h.source.entities.getById('alpr:42');
const anchor = Cesium.Cartographic.fromCartesian(
entity.gevDisplayPosition(),
);
assert.ok(Math.abs(anchor.height - 187) < 0.001);
assert.ok(
Math.abs(Cesium.Math.toDegrees(anchor.longitude) + 97.7431) < 1e-7,
);
for (let i = 0; i < 60; i++) {
h.viewer.scene.postRender.raiseEvent();
entity.gevDisplayPosition();
}
assert.equal(samples, 1, 'no per-frame raycasts');
h.advance(1000);
h.viewer.scene.postRender.raiseEvent();
assert.equal(samples, 2);
h.click(null);
h.advance(1000);
h.viewer.scene.postRender.raiseEvent();
assert.equal(samples, 2, 'no terrain sampling without a selection');
} finally {
h.restore();
}
});
test('a late network failure after disable cannot resurrect retries or an error state', async () => {
const h = cameraHarness();
try {
let reject;
h.setFetch(
() =>
new Promise((resolve, rejectRequest) => {
reject = rejectRequest;
}),
);
const pending = alprCamerasLayer.update();
alprCamerasLayer.disable();
reject(new TypeError('cancelled connection'));
await pending;
assert.equal(alprCamerasLayer.getStats().status, 'idle');
assert.equal(alprCamerasLayer.getStats().error, null);
assert.equal(alprCamerasLayer.getStats().retryAt, 0);
} finally {
h.restore();
}
});
test('two factories keep their requests, records and destruction independent', async () => {
const services = {
render: { governorRequestRender() {} },
picking: { registerPickOwner() {}, unregisterPickOwner() {} },
groundFloor: {
cachedGroundFloor() {
return null;
},
},
context: {
clearSelectedEntityContextForLayer() {},
getSelectedEntityContext() {
return null;
},
registerEntityContext() {},
removeEntityContextsForLayer() {},
selectEntityContext() {},
},
};
let resolveFirst, firstSignal;
const first = createAlprCamerasLayer({
services,
source: {
fetch: (_box, signal) => {
firstSignal = signal;
return new Promise((resolve) => {
resolveFirst = resolve;
});
},
},
});
const second = createAlprCamerasLayer({
services,
source: {
label: 'Test camera directory',
attribution: { name: 'Test directory' },
fetch: async () => ({
records: [
{ id: 'directory:99', latitude: 30.2672, longitude: -97.7431 },
],
stale: false,
saturated: false,
}),
},
});
const a = cameraHarness(first),
b = cameraHarness(second);
try {
const pending = first.update();
await second.update();
first.destroy();
assert.equal(firstSignal.aborted, true);
resolveFirst({
records: [normalizeAlprNode(cameraNode(42))],
stale: false,
saturated: false,
});
await pending;
assert.equal(first.getStats().count, 0);
assert.equal(second.getStats().count, 1);
assert.equal(b.source.entities.values[0].id, 'directory:99');
assert.equal(second.source, 'Test camera directory');
assert.ok(
b.source.entities.values[0].gevLabelModel.details.includes(
'Source: Test directory',
),
);
assert.equal(a.viewer.camera.moveEnd.numberOfListeners, 0);
assert.equal(a.viewer.scene.postRender.numberOfListeners, 0);
} finally {
b.restore();
a.restore();
}
});
test('markers sit on the cached floor; unknown floors clamp only until they resolve', async () => {
// Ground clamping re-samples every marker whenever a photoreal tile streams
// in; a whole-city view (about 1,000 cameras) stuttered while zooming.
const floors = new Map([['30.2672', 150]]);
let resolveCalls = 0;
let releaseResolve;
const services = {
render: { governorRequestRender() {} },
picking: { registerPickOwner() {}, unregisterPickOwner() {} },
groundFloor: {
cachedGroundFloor: (lat) => floors.get(lat.toFixed(4)) ?? null,
resolveGroundFloorCells: async () => {
resolveCalls++;
await new Promise((resolve) => {
releaseResolve = resolve;
});
floors.set('30.2682', 160);
},
},
context: {
clearSelectedEntityContextForLayer() {},
getSelectedEntityContext() {
return null;
},
registerEntityContext() {},
removeEntityContextsForLayer() {},
selectEntityContext() {},
},
};
const layer = createAlprCamerasLayer({
services,
source: {
fetch: async () => ({
records: [
{ id: 'directory:1', latitude: 30.2672, longitude: -97.7431 },
{ id: 'directory:2', latitude: 30.2682, longitude: -97.7431 },
],
stale: false,
saturated: false,
}),
},
});
const h = cameraHarness(layer);
try {
await layer.update();
const known = h.source.entities.getById('directory:1');
const unknown = h.source.entities.getById('directory:2');
assert.equal(
known.billboard.heightReference.getValue(),
Cesium.HeightReference.NONE,
);
assert.ok(
Math.abs(
Cesium.Cartographic.fromCartesian(known.position.getValue()).height -
151.5,
) < 0.01,
);
assert.equal(
unknown.billboard.heightReference.getValue(),
Cesium.HeightReference.CLAMP_TO_GROUND,
);
assert.equal(resolveCalls, 1);
releaseResolve();
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
unknown.billboard.heightReference.getValue(),
Cesium.HeightReference.NONE,
);
assert.ok(
Math.abs(
Cesium.Cartographic.fromCartesian(unknown.position.getValue()).height -
161.5,
) < 0.01,
);
assert.equal(
h.source.entities.getById('directory:2'),
unknown,
'placed in place, not rebuilt',
);
} finally {
h.restore();
}
});
test('loaded cameras outside the view say so instead of looking unloaded', async () => {
// Field review: "nearby" spans twice the camera-to-ground range, so a view
// could hold cameras with none on screen and read as a failed load.
const h = cameraHarness();
const project = Cesium.SceneTransforms.worldToWindowCoordinates;
let inView = false;
Cesium.SceneTransforms.worldToWindowCoordinates = () =>
inView ? new Cesium.Cartesian2(100, 100) : new Cesium.Cartesian2(-500, 100);
try {
h.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
-97.7431,
30.2672,
2000,
);
h.viewer.scene.canvas.clientWidth = 800;
h.viewer.scene.canvas.clientHeight = 600;
await alprCamerasLayer.update();
let stats = alprCamerasLayer.getStats();
assert.equal(stats.onScreen, 0);
assert.match(stats.countLabel, /1 nearby · 0 on screen/);
assert.match(stats.loadingLabel, /None on screen/);
inView = true;
h.viewer.camera.moveEnd.raiseEvent();
stats = alprCamerasLayer.getStats();
assert.equal(stats.onScreen, 1);
assert.doesNotMatch(stats.loadingLabel, /None on screen/);
} finally {
Cesium.SceneTransforms.worldToWindowCoordinates = project;
h.restore();
}
});
test('orbit cache hits and metadata refreshes preserve marker geometry and selection events', async () => {
const h = cameraHarness();
try {
h.setFetch(async () =>
cameraResponse([
cameraNode(42, {
tags: { 'surveillance:type': 'ALPR', 'camera:direction': '90' },
}),
]),
);
await alprCamerasLayer.update();
h.click('alpr:42');
const entity = h.source.entities.getById('alpr:42');
const position = entity.position,
line = entity.polyline;
// Ground-clamped native wedges cost ~40 ms per frame for a downtown view;
// bearings are painted by the overlay for the nearest cameras instead.
assert.equal(line, undefined);
assert.equal(entity.polygon, undefined);
let selections = 0,
clears = 0,
collectionChanges = 0;
window.addEventListener('gev:entity-selected', () => selections++);
window.addEventListener('gev:entity-selection-cleared', () => clears++);
h.source.entities.collectionChanged.addEventListener(
(_collection, added, removed) => {
collectionChanges += added.length + removed.length;
},
);
h.setBox({ south: 30.261, west: -97.749, north: 30.279, east: -97.731 });
await alprCamerasLayer.update();
h.expire();
h.setFetch(async () =>
cameraResponse([
cameraNode(42, {
tags: {
'surveillance:type': 'ALPR',
'camera:direction': '90',
operator: 'Updated directory',
},
}),
]),
);
await alprCamerasLayer.update();
assert.equal(h.source.entities.getById('alpr:42'), entity);
assert.equal(entity.position, position);
assert.equal(entity.polyline, line);
assert.equal(
getSelectedEntityContext().properties.operator,
'Updated directory',
);
assert.equal(selections, 0);
assert.equal(clears, 0);
assert.equal(collectionChanges, 0);
assert.equal(h.requests.length, 2, 'only expiry needs another request');
h.expire();
h.setFetch(async () => cameraResponse([cameraNode(42, { lon: -97.742 })]));
await alprCamerasLayer.update();
assert.equal(h.source.entities.getById('alpr:42'), entity);
assert.equal(
entity.polyline,
undefined,
'removed direction must not persist',
);
assert.ok(
Cesium.Cartesian3.equals(
entity.position.getValue(),
Cesium.Cartesian3.fromDegrees(-97.742, 30.2672),
),
);
} finally {
h.restore();
}
});
test('moves inside a pending query do not restart it; moving to another city cancels it', async () => {
const h = cameraHarness();
try {
let resolve;
h.setFetch(
() =>
new Promise((r) => {
resolve = r;
}),
);
const first = alprCamerasLayer.update();
const signal = h.requests[0][1].signal;
h.setBox({ south: 30.261, west: -97.749, north: 30.279, east: -97.731 });
await alprCamerasLayer.update();
assert.equal(h.requests.length, 1);
assert.equal(signal.aborted, false);
assert.equal(alprCamerasLayer.getStats().loading, true);
const finishFirst = resolve;
h.setBox({ south: 51.49, west: -0.14, north: 51.5, east: -0.12 });
const second = alprCamerasLayer.update();
assert.equal(signal.aborted, true);
assert.equal(h.requests.length, 2);
finishFirst(cameraResponse());
await first;
assert.equal(
alprCamerasLayer.getStats().loading,
true,
'late old response cannot finish current request',
);
resolve(cameraResponse([cameraNode(43, { lat: 51.495, lon: -0.13 })]));
await second;
assert.deepEqual(
h.source.entities.values.map((e) => e.id),
['alpr:43'],
);
} finally {
h.restore();
}
});
test('ground-centered orbits ignore horizon rectangles while sky, distant and dateline views stay bounded', async () => {
const h = cameraHarness();
const camera = h.viewer.camera;
const focus = Cesium.Cartesian3.fromDegrees(-97.7431, 30.2672);
h.viewer.scene.canvas.clientWidth = 1440;
h.viewer.scene.canvas.clientHeight = 900;
camera.pickEllipsoid = () => focus;
camera.positionWC = Cesium.Cartesian3.fromDegrees(-97.7431, 30.2672, 1200);
try {
await alprCamerasLayer.update();
const entity = h.source.entities.getById('alpr:42');
h.click('alpr:42');
for (const box of [null, { south: 31, west: -99, north: 36, east: -95 }]) {
h.setBox(box);
await alprCamerasLayer.update();
assert.equal(h.source.entities.getById('alpr:42'), entity);
assert.equal(getSelectedEntityContext().id, 'alpr:42');
assert.equal(h.requests.length, 1);
}
camera.positionWC = Cesium.Cartesian3.fromDegrees(
-97.7431,
30.2672,
800000,
);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'zoom-in');
assert.equal(h.source.entities.values.length, 0);
camera.pickEllipsoid = () => undefined;
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'zoom-in');
camera.pickEllipsoid = () => Cesium.Cartesian3.fromDegrees(179.999, 0);
camera.positionWC = Cesium.Cartesian3.fromDegrees(179.999, 0, 1200);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().status, 'zoom-in');
assert.equal(h.requests.length, 1);
} finally {
h.restore();
}
});
test('nearby count and discovery control frame a real loaded camera without fetching or stealing tracking', async () => {
const h = cameraHarness();
const flights = [];
h.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
-97.7431,
30.2672,
600,
);
h.viewer.camera.flyToBoundingSphere = (sphere, options) =>
flights.push({ sphere, options });
h.viewer.scene.globe.show = true;
h.viewer.scene.globe.getHeight = () => 312;
try {
assert.equal(alprCamerasLayer.getRowControls().chips[0].disabled, true);
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().countLabel, '1 nearby');
const controls = alprCamerasLayer.getRowControls();
assert.equal(controls.legend[0].label, 'Camera badges');
assert.equal(controls.chips[0].onClick(), true);
assert.equal(flights.length, 1);
assert.equal(getSelectedEntityContext().id, 'alpr:42');
const center = Cesium.Cartographic.fromCartesian(flights[0].sphere.center);
assert.ok(
Math.abs(Cesium.Math.toDegrees(center.latitude) - 30.2672) < 1e-9,
);
assert.ok(
Math.abs(center.height - 312) < 1e-6,
'use the rendered globe floor on keyless basemaps',
);
assert.equal(flights[0].options.offset.range, 800);
assert.equal(h.requests.length, 1);
h.viewer.trackedEntity = {};
assert.equal(alprCamerasLayer.getRowControls().chips[0].disabled, true);
assert.equal(
controls.chips[0].onClick(),
false,
'a stale button cannot steal the follow camera',
);
h.viewer.trackedEntity = undefined;
alprCamerasLayer.disable();
assert.equal(alprCamerasLayer.getStats().countLabel, '');
assert.equal(controls.chips[0].onClick(), false);
assert.equal(flights.length, 1);
} finally {
h.restore();
}
});
test('a non-retryable capability error does not arm the ALPR retry timer', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const h = cameraHarness();
try {
h.setFetch(async () => {
throw Object.assign(new Error('Unavailable'), {
code: 'OVERPASS_NOT_CONFIGURED',
retryable: false,
});
});
await alprCamerasLayer.update();
assert.equal(alprCamerasLayer.getStats().retryAt, 0);
const requests = h.requests.length;
t.mock.timers.tick(300_000);
await Promise.resolve();
assert.equal(h.requests.length, requests);
} finally {
h.restore();
}
});
test('unsupported ALPR coverage suppresses the nearby count and names the extract region', async () => {
const h = cameraHarness();
try {
h.setFetch(async () => ({
...cameraResponse([]),
json: async () => ({ elements: [], noCoverage: true }),
}));
await alprCamerasLayer.update();
const stats = alprCamerasLayer.getStats();
assert.equal(stats.noCoverage, true);
assert.equal(stats.countLabel, '');
assert.equal(
stats.loadingLabel,
'No ALPR data for this area — US and Canada only',
);
} finally {
h.restore();
}
});
import { createGroundFloor } from '../services/groundFloor.js';
import { createTerrainHeights } from '../services/terrainHeights.js';
import { createApplicationRequestServices } from '../services/requests.js';
test('slow ALPR floors share real terrain requests across preparation, render and pans; disable aborts', async () => {
const requests = [];
const sourceServices = createApplicationRequestServices({
fetchImpl: (url, { signal }) =>
new Promise((resolve, reject) => {
assert.match(url, /^\/api\/terrain\/heights\?/);
const points = new URL(url, 'http://localhost').searchParams
.get('points')
.split(';');
requests.push({
signal,
points,
release: () =>
resolve(
Response.json({
results: points.map(() => ({ ellipsoid: 160 })),
}),
),
});
signal.addEventListener('abort', () => reject(signal.reason), {
once: true,
});
}),
});
const terrain = createTerrainHeights({ source: sourceServices.terrain });
const ground = createGroundFloor({ terrain });
ground.FLOOR_RESOLVE_DEADLINE_MS = 1;
const records = Array.from({ length: 130 }, (_, i) => ({
id: `slow:${i}`,
latitude: 30.261 + Math.floor(i / 15) * 0.001,
longitude: -97.749 + (i % 15) * 0.001,
}));
const layer = createAlprCamerasLayer({
source: {
fetch: async () => ({ records, stale: false, saturated: false }),
},
services: {
groundFloor: ground,
render: { governorRequestRender() {} },
picking: { registerPickOwner() {}, unregisterPickOwner() {} },
context: {
clearSelectedEntityContextForLayer() {},
getSelectedEntityContext() {},
registerEntityContext() {},
removeEntityContextsForLayer() {},
selectEntityContext() {},
},
},
});
const h = cameraHarness(layer);
try {
await layer.update();
assert.equal(
requests.length,
1,
'render joins the outstanding preparation batch',
);
assert.equal(
requests[0].points.length,
64,
'one bounded batch runs at a time',
);
h.expire();
await layer.update();
await layer.update();
assert.equal(
requests.length,
1,
'further preparation and render reuse cold cell promises',
);
requests[0].release();
await new Promise((done) => setTimeout(done, 0));
assert.equal(requests.length, 2);
const requested = requests.flatMap((request) => request.points);
assert.equal(
new Set(requested).size,
requested.length,
'no duplicate cells over HTTP',
);
layer.disable();
assert.equal(
requests[1].signal.aborted,
true,
'disable cancels the active HTTP request',
);
requests[1].release();
await new Promise((done) => setTimeout(done, 0));
assert.equal(requests.length, 2, 'queued final cells are discarded');
assert.equal(h.source.entities.values.length, 0);
layer.enable();
await layer.update();
assert.equal(
requests.length,
3,
'a fresh consumer can resume unresolved cells',
);
layer.destroy();
assert.equal(
requests[2].signal.aborted,
true,
'destroy also aborts its consumer',
);
} finally {
h.restore();
}
});
test('map-source changes reposition all ALPR entities and saved native appearances in place', async () => {
let floor = 180;
let listener;
let removed = 0;
const records = Array.from({ length: 70 }, (_, i) => ({
id: `surface:${i}`,
latitude: 30.2672 + i * 0.00001,
longitude: -97.7431,
directionDeg: i === 0 ? null : 90,
}));
const layer = createAlprCamerasLayer({
source: {
fetch: async () => ({ records, stale: false, saturated: false }),
},
services: {
groundFloor: { cachedGroundFloor: () => floor },
overlays: {
subscribeMapStack(fn) {
listener = fn;
return () => {
removed++;
};
},
},
render: { governorRequestRender() {} },
picking: { registerPickOwner() {}, unregisterPickOwner() {} },
context: {
clearSelectedEntityContextForLayer() {},
getSelectedEntityContext() {},
registerEntityContext() {},
removeEntityContextsForLayer() {},
selectEntityContext() {},
},
},
});
const h = cameraHarness(layer);
try {
await layer.update();
const entities = [...h.source.entities.values];
const saved = entities[1];
saved.gevAlprNativeAppearance = {
position: saved.position,
heightReference: saved.billboard.heightReference,
};
saved.position = Cesium.Cartesian3.fromDegrees(-97.7431, 30.26721, 190);
saved.gevAlprPickPosition = saved.position;
for (const next of [150, 120, null, 180]) {
floor = next;
// Includes a provider switch with the same globe.show value.
h.viewer.scene.globe.show = next !== 180;
listener({ detail: { status: 'ready' } });
for (const entity of entities) {
assert.equal(h.source.entities.getById(entity.id), entity);
assert.ok(
Math.abs(
Cesium.Cartographic.fromCartesian(entity.position.getValue())
.height - (next == null ? 0 : next + 1.5),
) < 0.01,
`${entity.id} refreshes at ${next}`,
);
assert.equal(
entity.billboard.heightReference.getValue(),
next == null
? Cesium.HeightReference.CLAMP_TO_GROUND
: Cesium.HeightReference.NONE,
);
assert.equal(entity.gevAlprCanvasPosition, null);
}
assert.equal(
saved.gevAlprNativeAppearance,
null,
'saved appearance restores at the new floor',
);
}
} finally {
h.restore();
}
assert.equal(removed, 1, 'map listener is disposed');
});
test('a repeated move inside an outstanding query keeps the displayed marker set until commit', async () => {
const h = cameraHarness();
try {
await alprCamerasLayer.update();
const previous = h.source.entities.getById('alpr:42');
const revision = alprCamerasLayer.getStats().renderRevision;
let release;
h.setFetch(
() =>
new Promise((resolve) => {
release = resolve;
}),
);
h.setBox({ south: 31, west: -98, north: 31.02, east: -97.98 });
const pending = alprCamerasLayer.update();
await alprCamerasLayer.update();
assert.equal(
h.source.entities.getById('alpr:42'),
previous,
'do not show an intermediate filtered snapshot while the new query prepares',
);
assert.equal(alprCamerasLayer.getStats().renderRevision, revision);
assert.equal(h.requests.length, 2, 'both moves share one new query');
release(cameraResponse([cameraNode(43, { lat: 31.01, lon: -97.99 })]));
await pending;
assert.equal(alprCamerasLayer.getStats().renderRevision, revision + 1);
assert.deepEqual(
h.source.entities.values.map((e) => e.id),
['alpr:43'],
);
} finally {
h.restore();
}
});