fix: clarify nearby ALPR counts and help locate cameras

This commit is contained in:
Sameh Khamis committed 2026-09-13 20:35:38 -07:00
1 parent 7068ee7dc7
commit fe1aa3b6f6
8 files changed
+186 -14

No files matched your search

+42
View File
@@ -1082,3 +1082,45 @@ test('ground-centered orbits ignore horizon rectangles while sky, distant and da
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 });
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, 'Purple dots');
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.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();
}
});
+34
View File
@@ -3302,3 +3302,37 @@ test('panel remount releases old listeners and destruction revokes retained cont
else globalThis.document = originalDocument;
}
});
test('row action chips use live disabled state and descriptive counts without writing parameters', async () => {
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement };
const mgr = new DataLayerManager({});
const layer = makeRowControlLayer();
let calls = 0, blocked = false;
layer.module.getStats = () => ({ count: 3, countLabel: '3 nearby' });
layer.module.getRowControls = () => ({ chips: [{ id: 'find', label: 'SHOW NEAREST', disabled: blocked, onClick: () => calls++ }] });
layer.module.setParams = () => { throw new Error('navigation must not write persistent params'); };
mgr.register(layer.module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
await mgr.setEnabled('satellites', true);
const row = container.querySelector('[data-layer-id="satellites"]');
assert.equal(row.querySelector('.data-count').textContent, '3 nearby');
const controls = row.querySelector('.data-toggle-controls');
const chip = collectByClass(controls, 'data-toggle-chip')[0];
controls.listeners.click({ target: chip });
assert.equal(calls, 1);
blocked = true;
controls.listeners.click({ target: chip });
assert.equal(calls, 1, 'live disabled descriptor wins before row repaint');
blocked = false;
await mgr.setEnabled('satellites', false);
controls.listeners.click({ target: chip });
assert.equal(calls, 1, 'disabled layer cannot navigate');
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
+31
View File
@@ -6,6 +6,7 @@ import {
MAX_RENDERED,
QUERY_SNAP_DEGREES,
QUERY_REUSE_MS,
ALPR_COLOR,
} from './policy.js';
import {
snapAlprBox,
@@ -76,6 +77,7 @@ export function createAlprCamerasLayer({ source, services } = {}) {
presentOnMapCredit,
renderRecords,
selectRecord,
focusNearest,
clearSelection,
updateSelectedAnchor,
installInteraction,
@@ -301,9 +303,38 @@ export function createAlprCamerasLayer({ source, services } = {}) {
state.saturated = false;
state.viewer = null;
},
getRowControls() {
const count = state.dataSource?.entities.values.length || 0;
return {
chips: [
{
id: 'find-camera',
label: 'SHOW NEAREST',
title: state.viewer?.trackedEntity
? 'Stop following the current object before navigating to a camera'
: 'Move to the nearest loaded camera and show its details',
disabled:
!state.enabled || !count || Boolean(state.viewer?.trackedEntity),
onClick: focusNearest,
},
],
legend: [
{
label: 'Purple dots',
color: ALPR_COLOR,
count,
blurb:
'Nearby mapped cameras may be outside the screen. Dots stay 8 pixels wide as you zoom; click one for details.',
},
],
};
},
getStats() {
return {
count: state.dataSource?.entities.values.length || 0,
countLabel: state.enabled
? `${state.dataSource?.entities.values.length || 0} nearby`
: '',
lastUpdate: state.lastUpdate,
stale: state.stale,
saturated: state.saturated,
+52
View File
@@ -245,6 +245,57 @@ export function createAlprPresentation({ state, services, source }) {
if (visible.length) presentOnMapCredit();
}
function focusNearest() {
const camera = state.viewer?.camera;
if (!state.enabled || !camera?.positionWC || state.viewer.trackedEntity)
return false;
let nearest = null,
distance = Infinity;
for (const entity of state.dataSource.entities.values) {
const position = entity.position.getValue(Cesium.JulianDate.now());
const candidate = Cesium.Cartesian3.distanceSquared(
camera.positionWC,
position,
);
if (candidate < distance) {
nearest = entity;
distance = candidate;
}
}
if (!nearest) return false;
const record = state.recordById.get(nearest.id);
const location = Cesium.Cartographic.fromDegrees(
record.longitude,
record.latitude,
);
let height;
if (state.viewer.scene.sampleHeightSupported) {
try {
height = state.viewer.scene.sampleHeight(location, [nearest]);
} catch {
/* tiles may still be streaming */
}
}
if (!Number.isFinite(height) || height < -500 || height > 10000)
height = cachedGroundFloor(record.latitude, record.longitude);
const center = Cesium.Cartesian3.fromDegrees(
record.longitude,
record.latitude,
Number.isFinite(height) ? height : 0,
);
if (!selectRecord(nearest.id)) return false;
camera.flyToBoundingSphere(new Cesium.BoundingSphere(center, 30), {
duration: 1.2,
offset: new Cesium.HeadingPitchRange(
camera.heading || 0,
-Math.PI / 4,
800,
),
});
governorRequestRender('alpr-focus');
return true;
}
function selectRecord(id) {
const entity = state.dataSource?.entities.getById(id);
if (!entity || !state.recordById.has(id)) return false;
@@ -343,6 +394,7 @@ export function createAlprPresentation({ state, services, source }) {
presentOnMapCredit,
renderRecords,
selectRecord,
focusNearest,
clearSelection,
updateSelectedAnchor,
installInteraction,
+11 -14
View File
@@ -136,9 +136,7 @@ export class LayerPanel {
const count = document.createElement('span');
count.className = 'data-count';
count.textContent = layer.stats.count
? this._formatCount(layer.stats.count)
: '—';
count.textContent = this._layerCountText(layer.stats);
const toggle = document.createElement('button');
toggle.type = 'button';
@@ -202,7 +200,9 @@ export class LayerPanel {
const chip = this._rowControlsFor(layer.id)?.chips?.find(
(entry) => entry.id === button.dataset.chipId,
);
if (chip?.params)
if (!chip || chip.disabled || !this.isEnabled(layer.id)) return;
if (typeof chip.onClick === 'function') chip.onClick();
else if (chip.params)
this.setLayerParams(layer.id, chip.params, { origin: 'user' });
});
row.appendChild(controls);
@@ -213,13 +213,12 @@ export class LayerPanel {
}
}
/**
* Read a layer's optional row-control descriptor, tolerating a throw so one
* misbehaving layer cannot blank the whole panel. Resolved from the registry
* rather than the `getAll()` projection, which deliberately omits `module`.
* @param {string} layerId Registered layer id.
* @returns {{ chips?: Array<object>, legend?: Array<object> }|null} Descriptor.
*/
/** Qualify a loaded count when it does not mean items currently on screen. */
_layerCountText(stats) {
if (typeof stats.countLabel === 'string' && stats.countLabel.trim())
return stats.countLabel;
return stats.count ? this._formatCount(stats.count) : '—';
}
/**
* Render a layer's row chips and color legend, and keep the whole block
@@ -307,9 +306,7 @@ export class LayerPanel {
const count = row.querySelector('.data-count');
if (count) {
count.textContent = layer.stats.count
? this._formatCount(layer.stats.count)
: '—';
count.textContent = this._layerCountText(layer.stats);
}
const meta = row.querySelector('.data-toggle-meta');
+8
View File
@@ -1273,3 +1273,11 @@
#global-context-panel #cockpit-entry kbd {
padding: 2px 4px;
}
/* Qualified nearby counts must also fit beside the wider loading chip. */
.data-toggle-row[data-layer-id='alpr-cameras'] .data-toggle-top {
flex-wrap: wrap;
}
.data-toggle-row[data-layer-id='alpr-cameras'] .data-toggle-right {
margin-left: auto;
}