mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
fix: address GNSS integrity and aircraft emergency review findings
This commit is contained in:
1 parent
4240d04642
commit
01a8ad7aa7
44 files changed
+1282
-388
No files matched your search
@@ -781,6 +781,7 @@
|
||||
"live-observations": {
|
||||
"exports": ["./sources/live"],
|
||||
"modules": [
|
||||
"src/data/aircraftEmergency.js",
|
||||
"src/sources/live/aircraft.js",
|
||||
"src/sources/live/contract.js",
|
||||
"src/sources/live/index.js",
|
||||
@@ -3505,6 +3506,7 @@
|
||||
"src/sources/featureGeometry.js",
|
||||
"src/sources/live/standalone.js",
|
||||
"src/sources/live/contract.js",
|
||||
"src/data/aircraftEmergency.js",
|
||||
"src/sources/live/aircraft.js",
|
||||
"src/sources/live/vessels.js",
|
||||
"src/tools/places.js",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* (i) TINT — a civil 7600 and a military ADS-B `general` emergency are
|
||||
* drawn red; a normal civil flight stays white, a normal military
|
||||
* flight stays amber, and a military `minfuel` priority stays amber.
|
||||
* POLL/MODELS verify existing untracked billboards and 3D models
|
||||
* repaint when emergency status starts and clears.
|
||||
* (ii) HUD — tracking the military emergency and entering Cockpit names
|
||||
* the broadcast code on the aircraft-meta line, in red.
|
||||
* (iii) CLEAR — the next poll drops the code: the HUD line returns to its
|
||||
@@ -20,6 +22,7 @@
|
||||
* Screenshots saved to qa-shots/emergency-*.png (gitignored).
|
||||
*
|
||||
* Run: node scripts/qa-emergency-squawk.mjs --url http://localhost:4173
|
||||
* Optional: --timeout-ms 180000 for a slow boot.
|
||||
* Exits non-zero on any FAIL. Does not commit anything.
|
||||
*/
|
||||
|
||||
@@ -39,6 +42,7 @@ const getOpt = (name, dflt) => {
|
||||
};
|
||||
const APP_URL = getOpt('--url', 'http://localhost:4173');
|
||||
const HEADFUL = argv.includes('--headful');
|
||||
const TIMEOUT_MS = Number(getOpt('--timeout-ms', '60000'));
|
||||
const CENTER = { lon: 4.0, lat: 53.5 };
|
||||
|
||||
const results = [];
|
||||
@@ -232,7 +236,7 @@ async function main() {
|
||||
civilRow('4847a2', 'KLM1234 ', lon - 0.6, lat - 0.25, '2000'),
|
||||
],
|
||||
mil: [
|
||||
milRow('ae7700', 'RCH7700', lon + 0.6, lat + 0.25, '7700', 'general'),
|
||||
milRow('ae7700', 'RCH7700', lon + 0.6, lat + 0.25, '1200', 'general'),
|
||||
milRow('ae0f01', 'RCH0F01', lon + 0.6, lat - 0.25, '1234', 'minfuel'),
|
||||
milRow('ae0001', 'RCH0001', lon, lat - 0.5, '4520', 'none'),
|
||||
],
|
||||
@@ -243,11 +247,11 @@ async function main() {
|
||||
await page.evaluateOnNewDocument(installStubs);
|
||||
await page.goto(`${APP_URL}/?welcome=0`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60000,
|
||||
timeout: TIMEOUT_MS,
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => window.__godsEyeView?.viewer && window.__godsEyeView?.dataManager,
|
||||
{ timeout: 60000 },
|
||||
{ timeout: TIMEOUT_MS },
|
||||
);
|
||||
await sleep(5000);
|
||||
await page.keyboard.press('Escape');
|
||||
@@ -287,7 +291,7 @@ async function main() {
|
||||
const probe = `(${billboardColors})(${JSON.stringify(ids)})`;
|
||||
await page
|
||||
.waitForFunction(`Object.keys(${probe}).length === ${ids.length}`, {
|
||||
timeout: 60000,
|
||||
timeout: TIMEOUT_MS,
|
||||
polling: 500,
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -321,6 +325,79 @@ async function main() {
|
||||
);
|
||||
await shoot(page, 'globe-tint');
|
||||
|
||||
// Existing untracked billboards and models must repaint in both directions.
|
||||
await page.evaluate(async () => {
|
||||
window.__squawkQaPhase = 'cleared';
|
||||
await window.__godsEyeView.dataManager.refreshLayer('military');
|
||||
});
|
||||
record(
|
||||
'POLL: existing military billboard clears to amber',
|
||||
isAmber((await page.evaluate(probe)).ae7700),
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
window.__squawkQaPhase = 'emergency';
|
||||
await window.__godsEyeView.dataManager.refreshLayer('military');
|
||||
});
|
||||
record(
|
||||
'POLL: existing military billboard turns red again',
|
||||
isRed((await page.evaluate(probe)).ae7700),
|
||||
);
|
||||
const modelProbe = () => {
|
||||
const found = {};
|
||||
const visit = (items) => {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items.get(i);
|
||||
if (item.ready && item.color && typeof item.id === 'string')
|
||||
found[item.id] = {
|
||||
r: Math.round(item.color.red * 255),
|
||||
g: Math.round(item.color.green * 255),
|
||||
b: Math.round(item.color.blue * 255),
|
||||
};
|
||||
else if (item.get) visit(item);
|
||||
}
|
||||
};
|
||||
visit(window.__godsEyeView.viewer.scene.primitives);
|
||||
return found;
|
||||
};
|
||||
await page.waitForFunction(
|
||||
`(${modelProbe})()['4847a1'] && (${modelProbe})().ae7700`,
|
||||
{ timeout: TIMEOUT_MS, polling: 250 },
|
||||
);
|
||||
const beforeModels = await page.evaluate(modelProbe);
|
||||
record(
|
||||
'MODELS: civil and military emergency models are red',
|
||||
isRed(beforeModels['4847a1']) && isRed(beforeModels.ae7700),
|
||||
JSON.stringify(beforeModels),
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
window.__squawkQaFixtures.civil[0][14] = '2000';
|
||||
window.__squawkQaFixtures.civil[1][14] = '7600';
|
||||
window.__squawkQaPhase = 'cleared';
|
||||
const dm = window.__godsEyeView.dataManager;
|
||||
await dm.refreshLayer('flights');
|
||||
await dm.refreshLayer('military');
|
||||
});
|
||||
await page.waitForFunction(
|
||||
`(${modelProbe})()['4847a1']?.g > 220 && (${modelProbe})()['4847a2']?.g < 110 && (${modelProbe})().ae7700?.g > 110`,
|
||||
{ timeout: TIMEOUT_MS, polling: 250 },
|
||||
);
|
||||
const afterModels = await page.evaluate(modelProbe);
|
||||
record(
|
||||
'MODELS: existing civil models start and clear, military clears',
|
||||
isWhite(afterModels['4847a1']) &&
|
||||
isRed(afterModels['4847a2']) &&
|
||||
isAmber(afterModels.ae7700),
|
||||
JSON.stringify(afterModels),
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
window.__squawkQaFixtures.civil[0][14] = '7600';
|
||||
window.__squawkQaFixtures.civil[1][14] = '2000';
|
||||
window.__squawkQaFixtures.mil[0].squawk = '7700';
|
||||
window.__squawkQaPhase = 'emergency';
|
||||
const dm = window.__godsEyeView.dataManager;
|
||||
await dm.refreshLayer('flights');
|
||||
await dm.refreshLayer('military');
|
||||
});
|
||||
// ── (ii) HUD ──────────────────────────────────────────────────────────
|
||||
console.log('\n(ii) HUD — track the emergency and enter Cockpit...');
|
||||
const tracked = await page.evaluate(() =>
|
||||
@@ -385,6 +462,17 @@ async function main() {
|
||||
hud.emergencyClass && /255,\s*68,\s*68/.test(hud.color || ''),
|
||||
`class=${hud.emergencyClass} color=${hud.color}`,
|
||||
);
|
||||
const trackedBroadcast = await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
.get('military')
|
||||
.module.getTrackedInfo().emergencyBroadcast,
|
||||
);
|
||||
record(
|
||||
'TRACKED: voice-facing status quotes the broadcast',
|
||||
trackedBroadcast === 'broadcasting squawk 7700 (general emergency)',
|
||||
trackedBroadcast,
|
||||
);
|
||||
await shoot(page, 'cockpit-hud');
|
||||
|
||||
// ── (iii) CLEAR ───────────────────────────────────────────────────────
|
||||
|
||||
+166
-41
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* qa-gnss.mjs — headless proof for the GNSS Integrity layer
|
||||
* (id `gnss-interference`): low navigation accuracy reported by ADS-B.
|
||||
* (id `gnss-integrity`): low navigation accuracy reported by ADS-B.
|
||||
*
|
||||
* Drives the REAL app in headless Chromium against a dev server (keyless).
|
||||
* The live section reads adsb.lol through `/api/gnss-integrity` (60 s server
|
||||
@@ -9,10 +9,10 @@
|
||||
* stub that route in the page so they are deterministic.
|
||||
*
|
||||
* (i) LIVE — over Poland and the southern Baltic the layer settles with no error,
|
||||
* one entity per cell and legend band counts that sum to the cell
|
||||
* one geometry per cell and legend band counts that sum to the cell
|
||||
* count; cells are required only when the returned rows, binned
|
||||
* with the layer's own rules, reach the per-cell minimum (live
|
||||
* traffic varies by hour).
|
||||
* traffic varies by hour). Requires a non-stale response no older than 60 s.
|
||||
* (ii) BANDS (synthetic) — a 10-aircraft cell with 5 degraded is high,
|
||||
* a 30-aircraft cell with 2 degraded is medium, a 3-aircraft cell
|
||||
* with 1 degraded is low (first bad discounted), a clean cell at sea
|
||||
@@ -20,9 +20,11 @@
|
||||
* getStats. Every band is drawn and low is shown over land and sea.
|
||||
* The row reads as low navigation accuracy, and stats carry the
|
||||
* machine-readable provenance (GEV classifier, gpsjam bands, window).
|
||||
* PICK verifies actual perimeter and empty-ground clicks under GNSS.
|
||||
* CAMERA/GLOBE verify automatic sampling and the screen-centre anchor.
|
||||
* (iii) FAILURE — the route stubbed to 502: stats.error is set and the
|
||||
* cells already on the globe are kept.
|
||||
* (iv) DISABLE — the layer's data source is hidden.
|
||||
* (iv) DISABLE — owned ground primitives are hidden.
|
||||
* (v) KEYBOARD — the row's toggle button, focused, turns the layer on
|
||||
* with Enter and off with Space, keeps focus and names its state.
|
||||
* (vi) VIEWPORT — at a 390x844 phone viewport the open row and its legend
|
||||
@@ -39,6 +41,7 @@
|
||||
* Run: node scripts/qa-gnss.mjs --url http://localhost:4173
|
||||
* (also against a built preview: npm run build && npm run preview,
|
||||
* then pass the preview URL)
|
||||
* Optional: --timeout-ms 180000 for a slow boot.
|
||||
* Exits non-zero on any FAIL. Does not commit anything.
|
||||
*/
|
||||
|
||||
@@ -62,7 +65,8 @@ const getOpt = (name, dflt) => {
|
||||
};
|
||||
const APP_URL = getOpt('--url', 'http://localhost:4173');
|
||||
const HEADFUL = argv.includes('--headful');
|
||||
const LAYER_ID = 'gnss-interference';
|
||||
const TIMEOUT_MS = Number(getOpt('--timeout-ms', '60000'));
|
||||
const LAYER_ID = 'gnss-integrity';
|
||||
|
||||
const results = [];
|
||||
function record(name, ok, detail) {
|
||||
@@ -76,11 +80,11 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
async function boot(page, { lon, lat, height }) {
|
||||
await page.goto(`${APP_URL}/?welcome=0`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60000,
|
||||
timeout: TIMEOUT_MS,
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => window.__godsEyeView?.viewer && window.__godsEyeView?.dataManager,
|
||||
{ timeout: 60000 },
|
||||
{ timeout: TIMEOUT_MS },
|
||||
);
|
||||
await sleep(5000);
|
||||
await page.keyboard.press('Escape');
|
||||
@@ -118,26 +122,24 @@ async function enableAndSettle(page, { timeoutS = 30, refresh = false } = {}) {
|
||||
const layer = dm.layers.get(id).module;
|
||||
if (again) await dm.refreshLayer(id);
|
||||
else await dm.setEnabled(id, true);
|
||||
let stats = null;
|
||||
for (let i = 0; i < tS; i++) {
|
||||
stats = layer.getStats();
|
||||
if (stats.count > 0 || stats.error) break;
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
const stats = layer.getStats();
|
||||
const rowControls = layer.getRowControls().legend;
|
||||
const legend = rowControls.map(({ label, count }) => ({ label, count }));
|
||||
const blurb = rowControls[0]?.blurb ?? '';
|
||||
const rowName =
|
||||
document.querySelector(`[data-layer-id="${id}"] .data-name`)
|
||||
?.textContent ?? '';
|
||||
const ds = window.__godsEyeView.viewer.dataSources.getByName(id)[0];
|
||||
const all = window.__godsEyeView.viewer.scene.groundPrimitives;
|
||||
const ds = Array.from({ length: all.length }, (_, i) => all.get(i)).find(
|
||||
(item) => item.id === id,
|
||||
);
|
||||
return {
|
||||
stats,
|
||||
legend,
|
||||
blurb,
|
||||
rowName,
|
||||
name: layer.name,
|
||||
entities: ds?.entities.values.length ?? -1,
|
||||
entities: ds?.get(0)?.geometryInstances.length ?? 0,
|
||||
shown: ds?.show ?? null,
|
||||
};
|
||||
},
|
||||
@@ -164,7 +166,7 @@ async function shoot(page, name) {
|
||||
await page
|
||||
.waitForFunction(
|
||||
() => window.__godsEyeView.viewer.scene.globe.tilesLoaded,
|
||||
{ timeout: 60000 },
|
||||
{ timeout: TIMEOUT_MS },
|
||||
)
|
||||
.catch(() => {});
|
||||
await showLayerRow(page);
|
||||
@@ -174,15 +176,12 @@ async function shoot(page, name) {
|
||||
/** Bounding-sphere state of every GNSS cell: DONE = 0, PENDING = 1, FAILED = 2. */
|
||||
function cellStates(id) {
|
||||
const { viewer } = window.__godsEyeView;
|
||||
const ds = viewer.dataSources.getByName(id)[0];
|
||||
const sphere = {};
|
||||
return ds.entities.values.map((entity) => {
|
||||
try {
|
||||
return viewer.dataSourceDisplay.getBoundingSphere(entity, false, sphere);
|
||||
} catch {
|
||||
return 1; // not yet seen by the geometry visualizer
|
||||
}
|
||||
});
|
||||
const all = viewer.scene.groundPrimitives;
|
||||
const ds = Array.from({ length: all.length }, (_, i) => all.get(i)).find(
|
||||
(item) => item.id === id,
|
||||
);
|
||||
if (!ds?.length) return [];
|
||||
return [ds.get(0).ready ? 0 : 1];
|
||||
}
|
||||
|
||||
/** Wait until every GNSS cell's ground rectangle has finished building. */
|
||||
@@ -371,7 +370,7 @@ async function main() {
|
||||
if (res.url().includes('/api/gnss-integrity') && res.ok())
|
||||
liveBodies.push(res.json());
|
||||
});
|
||||
await boot(live, { lon: 19.5, lat: 52.5, height: 1_100_000 });
|
||||
await boot(live, { lon: 19.6, lat: 52.6, height: 1_100_000 });
|
||||
const l = await enableAndSettle(live);
|
||||
const legendTotal = l.legend.reduce((sum, { count }) => sum + count, 0);
|
||||
// Bin what adsb.lol actually returned with the layer's own rules: live
|
||||
@@ -388,7 +387,9 @@ async function main() {
|
||||
Array.isArray(body.rows) &&
|
||||
Number.isFinite(body.ageMs) &&
|
||||
body.ageMs >= 0 &&
|
||||
body.classifier === 'gev-nic-nacp-v1',
|
||||
body.ageMs <= 60000 &&
|
||||
body.stale !== true &&
|
||||
body.classifier === 'gev-nic-nacp-v2',
|
||||
),
|
||||
`successful responses=${liveSnapshots.length}`,
|
||||
);
|
||||
@@ -405,7 +406,7 @@ async function main() {
|
||||
}
|
||||
const expectedCells = binGnssCells(expectedStore).length;
|
||||
record(
|
||||
'LIVE: no error, one entity per cell',
|
||||
'LIVE: no error, one geometry per cell',
|
||||
!l.stats.error && l.entities === l.stats.count,
|
||||
`cells=${l.stats.count} entities=${l.entities} error=${JSON.stringify(l.stats.error)}`,
|
||||
);
|
||||
@@ -456,17 +457,19 @@ async function main() {
|
||||
);
|
||||
record(
|
||||
'BANDS: 5/10 is high, 2/30 is medium, 1/3 and 0/4 are low',
|
||||
band('Over') === 1 && band('2') === 1 && band('Under') === 2,
|
||||
band('Low-accuracy score >') === 1 &&
|
||||
band('Low-accuracy score 2') === 1 &&
|
||||
band('Low-accuracy score <') === 2,
|
||||
JSON.stringify(f.legend),
|
||||
);
|
||||
const prov = f.stats.provenance;
|
||||
record(
|
||||
'PROVENANCE: GEV classifier, gpsjam bands and visited-view window are machine-readable',
|
||||
prov?.classifier?.id === 'gev-nic-nacp-v1' &&
|
||||
prov?.classifier?.id === 'gev-nic-nacp-v2' &&
|
||||
prov.classifier.definedBy === 'GEV' &&
|
||||
prov.classifier.validated === false &&
|
||||
prov.aggregation?.source === 'https://gpsjam.org/faq' &&
|
||||
JSON.stringify(prov.aggregation.bands) === '[0.02,0.1]' &&
|
||||
JSON.stringify(prov.aggregation.bands) === '[2,10]' &&
|
||||
prov.window?.minutes === 30 &&
|
||||
prov.window.scope === 'visited-view' &&
|
||||
prov.interpretation?.validated === false,
|
||||
@@ -477,12 +480,9 @@ async function main() {
|
||||
f.name === 'GNSS Integrity' &&
|
||||
f.rowName.includes('GNSS Integrity') &&
|
||||
!/interference/i.test(f.rowName) &&
|
||||
f.legend.every(({ label }) => label.endsWith('low accuracy')) &&
|
||||
f.blurb.startsWith(
|
||||
'Share of ADS-B aircraft reporting low navigation accuracy',
|
||||
) &&
|
||||
/GEV threshold/.test(f.blurb) &&
|
||||
/Suspected jamming or spoofing is one possible cause/.test(f.blurb),
|
||||
f.legend.every(({ label }) => label.startsWith('Low-accuracy score')) &&
|
||||
/one aircraft is discounted/.test(f.blurb) &&
|
||||
/not a detection/.test(f.blurb),
|
||||
`name=${JSON.stringify(f.name)}`,
|
||||
);
|
||||
record(
|
||||
@@ -498,6 +498,128 @@ async function main() {
|
||||
);
|
||||
await shoot(page, 'synthetic-bands');
|
||||
|
||||
// GNSS fills must not own map input, including over a real perimeter.
|
||||
await page.evaluate(async () => {
|
||||
const original = window.fetch.bind(window);
|
||||
window.fetch = (input, init) => {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
if (url.includes('/api/fire-perimeters/inciweb'))
|
||||
return Promise.resolve(Response.json({ rows: [] }));
|
||||
if (url.includes('/api/fire-perimeters'))
|
||||
return Promise.resolve(
|
||||
Response.json({
|
||||
rows: [
|
||||
{
|
||||
stableId: 'gnss-pick-qa',
|
||||
name: 'GNSS PICK QA FIRE',
|
||||
containedPct: 0,
|
||||
acres: 100,
|
||||
polygons: [
|
||||
[
|
||||
[
|
||||
[18.247, 54.247],
|
||||
[18.253, 54.247],
|
||||
[18.253, 54.253],
|
||||
[18.247, 54.253],
|
||||
[18.247, 54.247],
|
||||
],
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
return original(input, init);
|
||||
};
|
||||
const { viewer, dataManager } = window.__godsEyeView;
|
||||
viewer.camera.cancelFlight();
|
||||
viewer.camera.setView({
|
||||
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
|
||||
longitude: (18.25 * Math.PI) / 180,
|
||||
latitude: (54.25 * Math.PI) / 180,
|
||||
height: 2500,
|
||||
}),
|
||||
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
|
||||
});
|
||||
await dataManager.setEnabled('fire-perimeters', true);
|
||||
viewer.scene.requestRender();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const v = window.__godsEyeView.viewer,
|
||||
p = v.scene.pick({
|
||||
x: v.scene.canvas.clientWidth / 2,
|
||||
y: v.scene.canvas.clientHeight / 2,
|
||||
});
|
||||
return String(p?.id?.id ?? p?.id ?? '').startsWith(
|
||||
'fire-perimeter:gnss-pick-qa:',
|
||||
);
|
||||
},
|
||||
{ timeout: TIMEOUT_MS, polling: 250 },
|
||||
);
|
||||
const pickProbe = await page.evaluate(() => {
|
||||
const v = window.__godsEyeView.viewer,
|
||||
c = v.scene.canvas;
|
||||
const centre = { x: c.clientWidth / 2, y: c.clientHeight / 2 };
|
||||
const empty = { x: centre.x + 300, y: centre.y };
|
||||
const picked = v.scene.pick(empty),
|
||||
box = c.getBoundingClientRect();
|
||||
return {
|
||||
centre: { x: centre.x + box.left, y: centre.y + box.top },
|
||||
empty: { x: empty.x + box.left, y: empty.y + box.top },
|
||||
surface: !picked || picked.id === undefined,
|
||||
height: v.camera.positionCartographic.height,
|
||||
};
|
||||
});
|
||||
record('PICK: a perimeter remains selectable under GNSS', true);
|
||||
record(
|
||||
'PICK: street ground remains a surface pick under GNSS',
|
||||
pickProbe.surface,
|
||||
);
|
||||
await page.mouse.click(pickProbe.centre.x, pickProbe.centre.y);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
(window.__gevWorldOverlay.getDiagnostics().paintedBySource[
|
||||
'fire-perimeters'
|
||||
] ?? 0) > 0,
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
record('PICK: actual perimeter click opens its incident card', true);
|
||||
await page.screenshot({
|
||||
path: path.join(SHOTS_DIR, 'gnss-perimeter-selected.png'),
|
||||
});
|
||||
await page.mouse.click(pickProbe.empty.x, pickProbe.empty.y);
|
||||
await sleep(500);
|
||||
record(
|
||||
'PICK: empty ground clears the perimeter card',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
(window.__gevWorldOverlay.getDiagnostics().entriesBySource[
|
||||
'fire-perimeters'
|
||||
] ?? 0) === 0,
|
||||
),
|
||||
);
|
||||
await page.mouse.click(pickProbe.empty.x, pickProbe.empty.y, {
|
||||
clickCount: 2,
|
||||
});
|
||||
await sleep(1000);
|
||||
record(
|
||||
'PICK: empty double-click does not zoom to a GNSS cell',
|
||||
await page.evaluate(
|
||||
(height) =>
|
||||
Math.abs(
|
||||
window.__godsEyeView.viewer.camera.positionCartographic.height -
|
||||
height,
|
||||
) < 200,
|
||||
pickProbe.height,
|
||||
),
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
await window.__godsEyeView.dataManager.setEnabled(
|
||||
'fire-perimeters',
|
||||
false,
|
||||
);
|
||||
});
|
||||
console.log('\nCAMERA — automatic regional refresh without toggling...');
|
||||
const beforeMove = await page.evaluate(
|
||||
() => window.__gnssQaRequests.length,
|
||||
@@ -555,7 +677,7 @@ async function main() {
|
||||
const { viewer } = window.__godsEyeView;
|
||||
viewer.camera.setView({
|
||||
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
|
||||
longitude: (12.5 * Math.PI) / 180,
|
||||
longitude: (12.4 * Math.PI) / 180,
|
||||
latitude: (41.9 * Math.PI) / 180,
|
||||
height: 16_000_000,
|
||||
}),
|
||||
@@ -569,7 +691,7 @@ async function main() {
|
||||
const params = url && new URL(url, location.origin).searchParams;
|
||||
return (
|
||||
params &&
|
||||
Math.abs(Number(params.get('lon')) - 12.5) < 1 &&
|
||||
Math.abs(Number(params.get('lon')) - 12.4) < 1 &&
|
||||
Math.abs(Number(params.get('lat')) - 41.9) < 1
|
||||
);
|
||||
},
|
||||
@@ -592,7 +714,7 @@ async function main() {
|
||||
'GLOBE: zoomed-out sampling stays centred on Rome and exposes its regional radius',
|
||||
world.enabled &&
|
||||
world.height > 15_000_000 &&
|
||||
world.coverage?.longitude === 13 &&
|
||||
world.coverage?.longitude === 12 &&
|
||||
world.coverage?.latitude === 42 &&
|
||||
world.coverage?.radiusNm === 250,
|
||||
JSON.stringify(world),
|
||||
@@ -642,7 +764,10 @@ async function main() {
|
||||
console.log('\n(iv) DISABLE...');
|
||||
const shown = await page.evaluate(async (id) => {
|
||||
await window.__godsEyeView.dataManager.setEnabled(id, false);
|
||||
return window.__godsEyeView.viewer.dataSources.getByName(id)[0]?.show;
|
||||
const all = window.__godsEyeView.viewer.scene.groundPrimitives;
|
||||
return Array.from({ length: all.length }, (_, i) => all.get(i)).find(
|
||||
(item) => item.id === id,
|
||||
)?.show;
|
||||
}, LAYER_ID);
|
||||
record('DISABLE: data source hidden', shown === false, `show=${shown}`);
|
||||
await shoot(page, 'disabled');
|
||||
|
||||
Reference in new issue
Block a user