mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 19:34:01 +00:00
Check traffic vehicles one by one against a fixed dark ground after the globe round trip
Live Sentinel-2 imagery is bright at this altitude, so a fixed crop of white two-pixel vehicles changed anywhere from 14 to 300 pixels. The scenario now serves a uniform dark ground tile, waits for the layer to settle with vehicles in view, projects each vehicle to the window and requires 90% of those above the drawn terrain to change the picture where the layer places them; in simulation nothing else may change. Vehicles below the terrain and the population left after the round trip are reported, not asserted.
This commit is contained in:
1 parent
4d2b061e3b
commit
65d41b971c
1 file changed
+172
-18
@@ -31,9 +31,29 @@ try {
|
||||
|
||||
const keyless = process.argv.includes('--simulation');
|
||||
const reads = new Map();
|
||||
// A uniform dark Sentinel-2 tile: live imagery is bright and blurry at this
|
||||
// altitude, so white 2 px vehicles on it gave anything from 14 to 300
|
||||
// changed pixels. A fixed dark ground makes every drawn vehicle visible.
|
||||
const ground = await sharp({
|
||||
create: {
|
||||
width: 256,
|
||||
height: 256,
|
||||
channels: 3,
|
||||
background: { r: 28, g: 32, b: 30 },
|
||||
},
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
await p.setRequestInterception(true);
|
||||
p.on('request', async (req) => {
|
||||
const u = new URL(req.url());
|
||||
if (u.hostname === 'tiles.maps.eox.at')
|
||||
return req.respond({
|
||||
status: 200,
|
||||
contentType: 'image/jpeg',
|
||||
headers: { 'access-control-allow-origin': '*' },
|
||||
body: ground,
|
||||
});
|
||||
if (
|
||||
u.hostname === 'tiles.openfreemap.org' ||
|
||||
u.pathname.startsWith('/api/tomtom/')
|
||||
@@ -100,6 +120,7 @@ try {
|
||||
const first = await p.evaluate(
|
||||
() => window.qaTraffic.getStats().motion.rebuilds,
|
||||
);
|
||||
const population = await p.evaluate(() => window.qaTraffic.getStats().count);
|
||||
for (const kind of ['navara', 'cesium', 'navara']) {
|
||||
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
|
||||
await p.select('#globe-engine', kind);
|
||||
@@ -143,39 +164,172 @@ try {
|
||||
}
|
||||
if (keyless)
|
||||
assert.ok(![...reads.keys()].some((u) => u.includes('/tomtom/flow/')));
|
||||
const pixels = async () =>
|
||||
// Compare the same view with the layer shown and hidden, vehicle by
|
||||
// vehicle. Only a central window free of panels and status toasts counts.
|
||||
const region = { left: 310, top: 160, right: 810, bottom: 660 };
|
||||
// Each vehicle in the window with its window position and its height above
|
||||
// the terrain Navara draws there. A vehicle below that terrain is hidden by
|
||||
// the depth test, so it cannot be expected in the picture.
|
||||
const vehiclesInView = () =>
|
||||
p.evaluate((r) => {
|
||||
const { engine } = window.__godsEyeView;
|
||||
const a = 6378137,
|
||||
f = 1 / 298.257223563,
|
||||
e2 = f * (2 - f);
|
||||
const rows = [];
|
||||
for (const o of window.qaTraffic.getDetectableObjects()) {
|
||||
const w = engine.projector.worldToWindow(o.position);
|
||||
if (!w || w.x < r.left || w.y < r.top || w.x >= r.right) continue;
|
||||
if (w.y >= r.bottom) continue;
|
||||
const { x, y, z } = o.position;
|
||||
const longitude = Math.atan2(y, x),
|
||||
ring = Math.hypot(x, y);
|
||||
let latitude = Math.atan2(z, ring * (1 - e2)),
|
||||
height = 0;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const n = a / Math.sqrt(1 - e2 * Math.sin(latitude) ** 2);
|
||||
height = ring / Math.cos(latitude) - n;
|
||||
latitude = Math.atan2(z, ring * (1 - (e2 * n) / (n + height)));
|
||||
}
|
||||
const ground = engine.surface.terrainHeight({
|
||||
longitude,
|
||||
latitude,
|
||||
height: 0,
|
||||
});
|
||||
rows.push({
|
||||
id: o.id,
|
||||
x: w.x,
|
||||
y: w.y,
|
||||
clearance: Number.isFinite(ground) ? height - ground : null,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}, region);
|
||||
const settled = async () => {
|
||||
const deadline = Date.now() + 30000;
|
||||
for (;;) {
|
||||
const loading = await p.evaluate(
|
||||
() => window.qaTraffic.getStats().loading,
|
||||
);
|
||||
const rows = await vehiclesInView();
|
||||
if (!loading && rows.length >= 12) return rows;
|
||||
if (Date.now() > deadline)
|
||||
throw Error(
|
||||
'traffic did not settle with 12 vehicles in view: ' +
|
||||
JSON.stringify({ loading, inView: rows.length }),
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
};
|
||||
const raw = async () =>
|
||||
sharp(await p.screenshot())
|
||||
.extract({ left: 360, top: 255, width: 330, height: 260 })
|
||||
.removeAlpha()
|
||||
.raw()
|
||||
.toBuffer();
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
// Source counts can be ready before the incoming renderer has drawn terrain.
|
||||
// Let the camera and the newly attached road heights settle before comparison.
|
||||
await new Promise((resolve) => setTimeout(resolve, 2200));
|
||||
const shown = await pixels();
|
||||
await settled();
|
||||
const settledCount = await p.evaluate(
|
||||
() => window.qaTraffic.getStats().count,
|
||||
);
|
||||
const before = await vehiclesInView();
|
||||
const shown = await raw();
|
||||
const after = new Map((await vehiclesInView()).map((v) => [v.id, v]));
|
||||
await p.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.setEnabled('traffic', false, {
|
||||
origin: 'user',
|
||||
}),
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 600));
|
||||
const hidden = await pixels();
|
||||
let visible = 0;
|
||||
for (let i = 0; i < shown.length; i += 3) {
|
||||
if (
|
||||
shown[i] +
|
||||
shown[i + 1] +
|
||||
shown[i + 2] -
|
||||
(hidden[i] + hidden[i + 1] + hidden[i + 2]) >
|
||||
110
|
||||
)
|
||||
visible++;
|
||||
const hidden = await raw();
|
||||
const { width, height } = shown.info;
|
||||
const scale = width / 1024;
|
||||
const changed = (x, y) => {
|
||||
const i = (y * width + x) * 3;
|
||||
let d = 0;
|
||||
for (let c = 0; c < 3; c++)
|
||||
d += Math.abs(shown.data[i + c] - hidden.data[i + c]);
|
||||
return d > 90;
|
||||
};
|
||||
const near = new Set();
|
||||
const visible = { drawn: 0, missing: [] };
|
||||
let buried = 0,
|
||||
unclear = 0;
|
||||
for (const v of before) {
|
||||
const later = after.get(v.id);
|
||||
if (!later) continue;
|
||||
// The screenshot falls between the two projections.
|
||||
const cx = (v.x + later.x) / 2,
|
||||
cy = (v.y + later.y) / 2;
|
||||
let hit = false;
|
||||
for (let dy = -8; dy <= 8; dy++)
|
||||
for (let dx = -8; dx <= 8; dx++) {
|
||||
const x = Math.round((cx + dx) * scale),
|
||||
y = Math.round((cy + dy) * scale);
|
||||
if (x < 0 || y < 0 || x >= width || y >= height) continue;
|
||||
near.add(y * width + x);
|
||||
if (Math.abs(dx) <= 4 && Math.abs(dy) <= 4 && changed(x, y)) hit = true;
|
||||
}
|
||||
console.log('changed traffic pixels', visible);
|
||||
assert.ok(visible > 15, 'changing the layer changes the rendered streets');
|
||||
if (v.clearance == null || Math.abs(v.clearance) < 1) unclear++;
|
||||
else if (v.clearance < 0) buried++;
|
||||
else if (hit) visible.drawn++;
|
||||
else visible.missing.push(v);
|
||||
}
|
||||
let stray = 0,
|
||||
area = 0;
|
||||
for (let y = Math.round(region.top * scale); y < region.bottom * scale; y++)
|
||||
for (
|
||||
let x = Math.round(region.left * scale);
|
||||
x < region.right * scale;
|
||||
x++
|
||||
) {
|
||||
area++;
|
||||
if (!near.has(y * width + x) && changed(x, y)) stray++;
|
||||
}
|
||||
const aboveGround = visible.drawn + visible.missing.length;
|
||||
console.log(
|
||||
'vehicles in view',
|
||||
JSON.stringify({
|
||||
aboveGround,
|
||||
drawn: visible.drawn,
|
||||
belowTerrain: buried,
|
||||
withinOneMetre: unclear,
|
||||
strayChangedPixels: stray,
|
||||
area,
|
||||
}),
|
||||
);
|
||||
if (visible.missing.length)
|
||||
console.log('not drawn', JSON.stringify(visible.missing.slice(0, 10)));
|
||||
if (buried)
|
||||
console.log(
|
||||
'vehicles below the drawn terrain, hidden by the depth test:',
|
||||
buried,
|
||||
);
|
||||
// The checks after each switch run before the incoming globe republishes
|
||||
// the roads. Report what the population settles to; it is not asserted yet.
|
||||
console.log(
|
||||
'population before the switches',
|
||||
population,
|
||||
'settled after the round trip',
|
||||
settledCount,
|
||||
);
|
||||
if (settledCount < 0.8 * population)
|
||||
console.log(
|
||||
'known defect: the traffic population shrinks after returning to Navara',
|
||||
);
|
||||
assert.ok(aboveGround >= 12, 'at least 12 vehicles above ground in view');
|
||||
assert.ok(
|
||||
visible.drawn >= 0.9 * aboveGround,
|
||||
'at least 90% of vehicles above the ground are drawn where the layer places them',
|
||||
);
|
||||
// Live mode adds congestion lines along roads; only the simulation must
|
||||
// change nothing but the vehicles.
|
||||
if (keyless)
|
||||
assert.ok(stray < 0.002 * area, 'hiding the layer changes only vehicles');
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'PASS retained traffic population and flow, real geography and stable identities across both globes',
|
||||
'PASS kept the traffic module, parameters, flow and vehicle identities across both globes; vehicles in view are drawn where the layer places them',
|
||||
);
|
||||
} finally {
|
||||
await b.close();
|
||||
|
||||
Reference in new issue
Block a user