mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Navara now matches Cesium where the review found it diverging: a flight holds the camera inputs, camera.changed counts turns as Cesium does, render requests made during a frame survive it, follow pinches zoom, ground widths follow the pixel ratio and Cyber sonar reaches glyph caches and models. On Cesium, a staged imagery swap keeps loading tiles, setView replaces a running flight, and a cleared weather detail goes away. The contract gains MoverSet.failed, maps.terrainRevision, render.maxTextureSize and surface.sampleTerrainMostDetailed on every engine, so layers retry failed models, key road heights by terrain, size weather textures to the GPU and place Bhote Koshi cards on detailed terrain. Engine switches keep the live view, follows, focus, layer options and cards: a pick hands over the exact pose, applies no address options and pans no carried-over card; a history visit still applies its entry. Layers put GhostMaps pins, cable landings, OSM Places and directions on the measured surface without per-frame sampling, draw the Cockpit contact dot, hide disabled CCTV on attach, and keep ALPR running outside secure contexts. KML imports draw raised polygon fills, rotated and coloured ground overlays and north-referenced icon headings, and report files with nothing to draw. QA scripts read the contract instead of Cesium internals (test:track is 109/109 again), wait for the camera to rest before clicking, and give cold switches and parked engines realistic time. Docs follow the code.
214 lines
7.3 KiB
JavaScript
214 lines
7.3 KiB
JavaScript
import { assertKeylessPreview } from './qa-strict-harness.mjs';
|
|
import assert from 'node:assert/strict';
|
|
import { installAlprFixture, cameras } from './fixtures/layers/alpr.mjs';
|
|
import { firmsFixture, FIRMS_VALID_COUNT } from './fixtures/layers/firms.mjs';
|
|
import {
|
|
clickEmptyGround,
|
|
createStrict,
|
|
localPreview,
|
|
waitForCameraAtRest,
|
|
} from './qa-strict-harness.mjs';
|
|
const url = localPreview(process.argv[2]),
|
|
fire = process.argv.includes('--fires'),
|
|
id = fire ? 'local-firms' : 'alpr-cameras';
|
|
url.search = '?engine=navara&welcome=0';
|
|
url.hash =
|
|
'v=2&' +
|
|
(fire
|
|
? 'lat=37.77&lon=-122.42&alt=100000'
|
|
: 'lat=30.262&lon=-97.7431&alt=5500') +
|
|
'&pitch=-90&heading=0&map=sentinel2&l=';
|
|
// Records the fixtures produce: the valid FIRMS rows; the ALPR cameras the
|
|
// fixture tiles carry around Austin.
|
|
const COUNT = fire ? FIRMS_VALID_COUNT : cameras.length;
|
|
const strict = createStrict({
|
|
origin: url.origin,
|
|
layers: [id],
|
|
// installAlprFixture answers the camera tiles.
|
|
fixtures: ['https://tiles.dontgetflocked.com/*'],
|
|
});
|
|
await strict.run({}, async (b) => {
|
|
const p = await b.newPage();
|
|
await p.setViewport({ width: 1280, height: 900 });
|
|
const errors = [];
|
|
p.on('pageerror', (e) => errors.push(e.message));
|
|
await installAlprFixture(p);
|
|
await p.evaluateOnNewDocument((data) => {
|
|
window.qaFirmsReads = 0;
|
|
const fetch = window.fetch.bind(window);
|
|
window.fetch = (input, opt) => {
|
|
if (
|
|
new URL(input.url ?? input, location.href).pathname === '/api/firms'
|
|
) {
|
|
window.qaFirmsReads++;
|
|
return Promise.resolve(Response.json(data));
|
|
}
|
|
return fetch(input, opt);
|
|
};
|
|
}, firmsFixture());
|
|
await p.goto(url.href);
|
|
await p.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView?.engine?.kind === 'navara' &&
|
|
document.querySelector('#loading-screen.hidden'),
|
|
);
|
|
await assertKeylessPreview(p);
|
|
await p.evaluate(async (id) => {
|
|
const { dataManager, styleManager } = window.__godsEyeView;
|
|
|
|
await styleManager.initialRestorePromise;
|
|
window.qaGround = dataManager.layers.get(id).module;
|
|
await dataManager.setEnabled(id, true, { origin: 'user' });
|
|
}, id);
|
|
await p.waitForFunction(() => window.qaGround?.getStats().count > 0, {
|
|
timeout: 30000,
|
|
});
|
|
// A pickable target in the middle of the view: the camera the engine's
|
|
// own picker returns, or the hottest fire.
|
|
const findTarget = () =>
|
|
p
|
|
.waitForFunction(
|
|
(fire) => {
|
|
const a = window.__godsEyeView,
|
|
m = window.qaGround;
|
|
const points = fire
|
|
? m
|
|
.getDetectableObjects({ maxCount: 1000 })
|
|
.filter((v) => v.id === m.getAnalystRecords(1)[0]?.id)
|
|
: [...window.__gevContextStore.entities.values()]
|
|
.filter((v) => v.layerId === 'alpr-cameras')
|
|
.map((v) => ({
|
|
id: v.id,
|
|
position:
|
|
v.entity.gevAlprCanvasPosition ||
|
|
v.entity.gevDisplayPosition?.(),
|
|
}));
|
|
for (const row of points) {
|
|
if (!row.position) continue;
|
|
const s = a.engine.projector.worldToWindow(row.position);
|
|
if (!s || s.x < 430 || s.x > 850 || s.y < 220 || s.y > 600)
|
|
continue;
|
|
const canvas = a.engine.host.canvas;
|
|
const box = canvas.getBoundingClientRect();
|
|
if (
|
|
!canvas.contains(
|
|
document.elementFromPoint(box.left + s.x, box.top + s.y),
|
|
)
|
|
)
|
|
continue;
|
|
const picked = a.engine.picker.pick(s);
|
|
if (fire || picked?.id === row.id || picked?.id?.id === row.id)
|
|
return { x: box.left + s.x, y: box.top + s.y, id: row.id };
|
|
}
|
|
return false;
|
|
},
|
|
{ timeout: 30000 },
|
|
fire,
|
|
)
|
|
.then((handle) => handle.jsonValue());
|
|
const selected = (id) =>
|
|
p.waitForFunction(
|
|
(id, fire) =>
|
|
fire
|
|
? window.qaGround.getSelectedInfo()?.frp === 999
|
|
: window.__gevContextStore?.selectedEntityId === id,
|
|
{ timeout: 15000 },
|
|
id,
|
|
fire,
|
|
);
|
|
// A click on empty ground (nothing picked there, nothing covering it).
|
|
const clickEmpty = async () => {
|
|
await clickEmptyGround(p, {
|
|
left: 440,
|
|
top: 240,
|
|
right: 840,
|
|
bottom: 600,
|
|
step: 9,
|
|
radius: 6,
|
|
});
|
|
await p.waitForFunction(
|
|
(fire) =>
|
|
fire
|
|
? !window.qaGround.getSelectedInfo()
|
|
: !window.__gevContextStore?.selectedEntityId,
|
|
{ timeout: 15000 },
|
|
fire,
|
|
);
|
|
};
|
|
const xy = await findTarget();
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
await p.mouse.click(xy.x, xy.y);
|
|
await selected(xy.id);
|
|
await strict.passed(
|
|
p,
|
|
id,
|
|
fire
|
|
? 'a click on the hottest fire selects it'
|
|
: 'a click on a camera selects it',
|
|
xy,
|
|
);
|
|
await new Promise((r) => setTimeout(r, 1600));
|
|
// The selection flies the camera to the record (1.9 s, longer on a busy
|
|
// machine); a switch before it lands opens the other engine on a view
|
|
// between the two, where the record can be off screen.
|
|
await waitForCameraAtRest(p);
|
|
const expectedId = fire
|
|
? await p.evaluate(() => window.qaGround.getSelectedInfo().id)
|
|
: xy.id;
|
|
const before = await p.evaluate(() => ({
|
|
stamp: window.qaGround.getStats().lastUpdate,
|
|
records: window.qaGround.getAnalystRecords(1000).map((v) => v.id),
|
|
reads: window.qaFirmsReads,
|
|
}));
|
|
for (const kind of ['cesium', 'navara']) {
|
|
await strict.switchTo(p, kind);
|
|
const result = await p.evaluate(
|
|
(id) => ({
|
|
same:
|
|
window.qaGround ===
|
|
window.__godsEyeView.dataManager.layers.get(id).module,
|
|
selected: window.__gevContextStore.selectedEntityId,
|
|
record: window.qaGround.getSelectedInfo?.()?.id,
|
|
stamp: window.qaGround.getStats().lastUpdate,
|
|
records: window.qaGround.getAnalystRecords(1000).map((v) => v.id),
|
|
reads: window.qaFirmsReads,
|
|
count: window.qaGround.getStats().count,
|
|
}),
|
|
id,
|
|
);
|
|
console.log(kind, id, result.selected, result.count, result.records.length);
|
|
assert.ok(result.same && result.count);
|
|
assert.equal(result.selected, expectedId);
|
|
if (fire) assert.equal(result.record, expectedId);
|
|
assert.equal(result.reads, before.reads);
|
|
assert.deepEqual(result.records, before.records);
|
|
assert.equal(result.stamp, before.stamp);
|
|
await strict.check(
|
|
p,
|
|
id,
|
|
`same model, ${COUNT} records, the clicked record still selected, no source read, same receipt time`,
|
|
result.count === COUNT,
|
|
{ count: result.count },
|
|
);
|
|
if (kind === 'cesium') {
|
|
// Click-picking on Cesium: clear the selection with a click on empty
|
|
// ground, then click the same record again.
|
|
await clickEmpty();
|
|
const again = await findTarget();
|
|
assert.equal(again.id, xy.id);
|
|
await p.mouse.click(again.x, again.y);
|
|
await selected(again.id);
|
|
await strict.passed(
|
|
p,
|
|
id,
|
|
fire
|
|
? 'a click on empty ground clears the fire; a click on it selects it again'
|
|
: 'a click on empty ground clears the camera; a click on it selects it again',
|
|
again,
|
|
);
|
|
}
|
|
}
|
|
assert.deepEqual(errors, []);
|
|
console.log('PASS retained source, stable clicked record on each planet');
|
|
});
|