mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Navara's 3D Tiles source defaults to model.height 1, which moves a transformed tileset by a metre along Earth-fixed X: on the synthetic set flat roofs read 0.12 m high and a sloping roof 0.68 m off, and the drawn meshes move with them. The adapter sets the height to 0 and records the package behaviour in WORKAROUNDS.md, with a test that loads the real package. The downward surface ray now takes its direction from the place's own normal, so it no longer drifts sideways. qa-tiles-fixture gains --report (heights and drape pixels per engine, without asserting), starts on Cesium, and checks each drape hidden, shown and hidden again; qa-surface-heights uses the plan's 0.5 m tolerance under tiles and names the point that misses.
240 lines
8.2 KiB
JavaScript
240 lines
8.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { compareHeights } from './qa-surface-heights.mjs';
|
|
import {
|
|
layers,
|
|
observeSurfaceMovers,
|
|
fixtureRoutes,
|
|
center,
|
|
points,
|
|
assertTileHeight,
|
|
waitForSurfaceHeights,
|
|
} from './qa/surfaceHeights.mjs';
|
|
import { createFakeEngine } from '../src/globe/testing/fakeEngine.js';
|
|
import { decodeAlprTile } from '../src/layers/alpr/tileRecords.js';
|
|
|
|
const snapshot = (heightM = 50) => ({
|
|
version: 1,
|
|
fixture: 'surface-heights-v1',
|
|
surface: 'terrain',
|
|
engines: Object.fromEntries(
|
|
['cesium', 'navara'].map((engine) => [
|
|
engine,
|
|
Object.fromEntries(
|
|
layers.map((layer) => [layer, { a: { heightM }, b: { heightM } }]),
|
|
),
|
|
]),
|
|
),
|
|
});
|
|
|
|
test('comparison accepts identical heights and the inclusive 0.1 m boundary', () => {
|
|
for (const delta of [0, 0.099, 0.1, -0.1]) {
|
|
const result = compareHeights(snapshot(), snapshot(50 + delta));
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.reports.length, 12);
|
|
assert.ok(Math.abs(result.reports[0].maxAbsM - Math.abs(delta)) < 1e-10);
|
|
}
|
|
});
|
|
|
|
test('comparison reports the largest absolute difference and fails above tolerance', () => {
|
|
const after = snapshot();
|
|
after.engines.navara.import.a.heightM = 50.01;
|
|
after.engines.navara.import.b.heightM = 49.8999;
|
|
const result = compareHeights(snapshot(), after);
|
|
assert.equal(result.ok, false);
|
|
const failures = result.reports.filter((r) => !r.ok);
|
|
assert.equal(failures.length, 1);
|
|
assert.equal(failures[0].point, 'b');
|
|
assert.ok(Math.abs(failures[0].maxAbsM - 0.1001) < 1e-10);
|
|
});
|
|
|
|
test('missing and extra points fail even with identical surviving heights', () => {
|
|
for (const engine of ['cesium', 'navara'])
|
|
for (const layer of layers)
|
|
for (const change of ['missing', 'extra']) {
|
|
const after = snapshot();
|
|
if (change === 'missing') delete after.engines[engine][layer].a;
|
|
else after.engines[engine][layer].extra = { heightM: 50 };
|
|
const result = compareHeights(snapshot(), after);
|
|
assert.equal(result.ok, false);
|
|
const report = result.reports.find(
|
|
(r) => r.engine === engine && r.layer === layer,
|
|
);
|
|
assert.deepEqual(report.missing, change === 'missing' ? ['a'] : []);
|
|
assert.deepEqual(report.extra, change === 'extra' ? ['extra'] : []);
|
|
}
|
|
});
|
|
|
|
test('missing engines, layers, empty cohorts and invalid heights cannot pass', () => {
|
|
for (const change of [
|
|
(s) => {
|
|
delete s.engines.navara;
|
|
},
|
|
(s) => {
|
|
delete s.engines.cesium.story;
|
|
},
|
|
(s) => {
|
|
s.engines.navara.alpr = {};
|
|
},
|
|
...[null, NaN, Infinity, '50'].map((height) => (s) => {
|
|
s.engines.cesium.import.a.heightM = height;
|
|
}),
|
|
]) {
|
|
const after = snapshot();
|
|
change(after);
|
|
assert.equal(compareHeights(snapshot(), after).ok, false);
|
|
}
|
|
const empty = snapshot();
|
|
empty.engines.cesium.story = {};
|
|
assert.equal(compareHeights(empty, empty).ok, false);
|
|
});
|
|
|
|
test('tiles require explicit opt-in and cannot be compared with terrain', () => {
|
|
const tiles = snapshot();
|
|
tiles.surface = 'tiles';
|
|
assert.throws(() => compareHeights(tiles, tiles), /tiles require --tiles/);
|
|
assert.throws(() => compareHeights(snapshot(), tiles), /matching surfaces/);
|
|
assert.equal(compareHeights(tiles, tiles, { tiles: true }).ok, true);
|
|
});
|
|
|
|
test('tile heights and tile comparisons use 0.5 m while terrain keeps 0.1 m', () => {
|
|
const before = { ...snapshot(), surface: 'tiles' };
|
|
for (const delta of [0.118, 0.5, -0.5]) {
|
|
assertTileHeight(50 + delta, 50, 'navara/surface-0');
|
|
const after = { ...snapshot(50 + delta), surface: 'tiles' };
|
|
assert.equal(compareHeights(before, after, { tiles: true }).ok, true);
|
|
assert.equal(compareHeights(snapshot(), snapshot(50 + delta)).ok, false);
|
|
}
|
|
for (const height of [50.5001, 49.4999, null, NaN, Infinity])
|
|
assert.throws(
|
|
() => assertTileHeight(height, 50, 'navara/surface-1'),
|
|
/navara\/surface-1: .* m, expected 50 m \(±0.5 m\)/,
|
|
);
|
|
const after = { ...snapshot(50.5001), surface: 'tiles' };
|
|
assert.equal(compareHeights(before, after, { tiles: true }).ok, false);
|
|
});
|
|
|
|
test('loaded incorrect tile heights fail immediately with the point and measured height', async (t) => {
|
|
const previous = globalThis.window;
|
|
t.after(() => {
|
|
if (previous === undefined) delete globalThis.window;
|
|
else globalThis.window = previous;
|
|
});
|
|
const engine = createFakeEngine();
|
|
t.after(() => engine.destroy());
|
|
globalThis.window = { __godsEyeView: { engine } };
|
|
engine.maps.photorealActive = () => true;
|
|
let height = 50.118;
|
|
engine.surface.sampleHeight = () => height;
|
|
const dispose = t.mock.fn();
|
|
const page = {
|
|
async waitForFunction(fn, options, args) {
|
|
const state = fn(args);
|
|
assert.ok(
|
|
state,
|
|
'finite loaded values return without waiting for tolerance',
|
|
);
|
|
return { jsonValue: async () => state, dispose };
|
|
},
|
|
};
|
|
await waitForSurfaceHeights(page, 'navara', true);
|
|
height = 51.25;
|
|
await assert.rejects(
|
|
waitForSurfaceHeights(page, 'navara', true),
|
|
/navara\/surface-0 sampleHeight: 51.25 m, expected 50 m/,
|
|
);
|
|
assert.equal(dispose.mock.callCount(), 2);
|
|
});
|
|
|
|
test('unavailable surface timeout reports all points and readiness state', async () => {
|
|
const cause = new Error('timeout');
|
|
const page = {
|
|
waitForFunction: async () => {
|
|
throw cause;
|
|
},
|
|
evaluate: async () => ({
|
|
loaded: false,
|
|
photoreal: true,
|
|
points: points.map((p) => ({ id: p.id, height: null })),
|
|
}),
|
|
};
|
|
await assert.rejects(waitForSurfaceHeights(page, 'navara', true), (error) => {
|
|
assert.equal(error.cause, cause);
|
|
for (const p of points)
|
|
assert.ok(error.message.includes(`${p.id}: null m`));
|
|
assert.match(error.message, /loaded=false, photoreal=true/);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test('the probe reads committed contract positions, snapshots mutable inputs and drops destroyed sets', () => {
|
|
const engine = createFakeEngine();
|
|
const original = globalThis.window;
|
|
globalThis.window = { __godsEyeView: { engine } };
|
|
try {
|
|
observeSurfaceMovers();
|
|
const probe = window.qaSurfaceMovers;
|
|
const set = engine.movers.createSet({
|
|
id: 'alpr-cameras',
|
|
kind: 'point',
|
|
capacity: 1,
|
|
point: { sizePx: 5 },
|
|
});
|
|
const position = { x: 1, y: 2, z: 3 };
|
|
set.set(0, { position, show: true, pickId: 'camera' });
|
|
set.setCount(1);
|
|
assert.throws(() => probe.read('alpr'), /Uncommitted mover count/);
|
|
set.commit();
|
|
position.z = 500;
|
|
assert.equal(probe.read('alpr')[0].position.z, 3);
|
|
set.set(0, { position });
|
|
assert.equal(probe.read('alpr')[0].position.z, 3);
|
|
set.commit();
|
|
assert.equal(probe.read('alpr')[0].position.z, 500);
|
|
assert.equal(probe.read('alpr')[0].id, 'camera');
|
|
set.destroy();
|
|
assert.deepEqual(probe.read('alpr'), []);
|
|
probe.restore();
|
|
} finally {
|
|
engine.destroy();
|
|
if (original === undefined) delete globalThis.window;
|
|
else globalThis.window = original;
|
|
}
|
|
});
|
|
|
|
test('fixture routes match both bundled and dev infrastructure URLs and decode real camera tiles', () => {
|
|
const origin = 'http://localhost:4308';
|
|
const routes = fixtureRoutes(origin, true);
|
|
const answer = (url) =>
|
|
routes.find((r) => new URLPattern(r.pattern).test(url))?.respond({ url });
|
|
for (const path of [
|
|
'/assets/datacenters-abc.geojsonl',
|
|
'/src/data/local_data/dams/dams.geojsonl',
|
|
]) {
|
|
const rows = answer(origin + path)
|
|
.body.split('\n')
|
|
.map(JSON.parse);
|
|
assert.equal(rows.length, 3);
|
|
assert.ok(
|
|
rows.every(
|
|
(r) => Math.abs(r.geometry.coordinates[1] - center.lat) < 0.0004,
|
|
),
|
|
);
|
|
}
|
|
const z = 12,
|
|
scale = 2 ** z;
|
|
const x = Math.floor(((center.lon + 180) / 360) * scale);
|
|
const y = Math.floor(
|
|
((1 - Math.asinh(Math.tan((center.lat * Math.PI) / 180)) / Math.PI) / 2) *
|
|
scale,
|
|
);
|
|
const tile = answer(
|
|
`https://tiles.dontgetflocked.com/cameras-us-hourly/${z}/${x}/${y}.mvt`,
|
|
);
|
|
const cameras = decodeAlprTile(tile.body, z, x, y);
|
|
assert.equal(cameras.length, 3);
|
|
assert.equal(new Set(cameras.map((r) => r.id)).size, 3);
|
|
assert.ok(cameras.every((r) => Math.abs(r.longitude - center.lon) < 0.0004));
|
|
assert.equal(answer(origin + '/api/unexpected-feed').status, 503);
|
|
});
|