mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 21:32:23 +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 keyless = process.argv.includes('--simulation');
|
||||||
const reads = new Map();
|
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);
|
await p.setRequestInterception(true);
|
||||||
p.on('request', async (req) => {
|
p.on('request', async (req) => {
|
||||||
const u = new URL(req.url());
|
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 (
|
if (
|
||||||
u.hostname === 'tiles.openfreemap.org' ||
|
u.hostname === 'tiles.openfreemap.org' ||
|
||||||
u.pathname.startsWith('/api/tomtom/')
|
u.pathname.startsWith('/api/tomtom/')
|
||||||
@@ -100,6 +120,7 @@ try {
|
|||||||
const first = await p.evaluate(
|
const first = await p.evaluate(
|
||||||
() => window.qaTraffic.getStats().motion.rebuilds,
|
() => window.qaTraffic.getStats().motion.rebuilds,
|
||||||
);
|
);
|
||||||
|
const population = await p.evaluate(() => window.qaTraffic.getStats().count);
|
||||||
for (const kind of ['navara', 'cesium', 'navara']) {
|
for (const kind of ['navara', 'cesium', 'navara']) {
|
||||||
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
|
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
|
||||||
await p.select('#globe-engine', kind);
|
await p.select('#globe-engine', kind);
|
||||||
@@ -143,39 +164,172 @@ try {
|
|||||||
}
|
}
|
||||||
if (keyless)
|
if (keyless)
|
||||||
assert.ok(![...reads.keys()].some((u) => u.includes('/tomtom/flow/')));
|
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())
|
sharp(await p.screenshot())
|
||||||
.extract({ left: 360, top: 255, width: 330, height: 260 })
|
|
||||||
.removeAlpha()
|
.removeAlpha()
|
||||||
.raw()
|
.raw()
|
||||||
.toBuffer();
|
.toBuffer({ resolveWithObject: true });
|
||||||
// Source counts can be ready before the incoming renderer has drawn terrain.
|
// Source counts can be ready before the incoming renderer has drawn terrain.
|
||||||
// Let the camera and the newly attached road heights settle before comparison.
|
// Let the camera and the newly attached road heights settle before comparison.
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2200));
|
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(() =>
|
await p.evaluate(() =>
|
||||||
window.__godsEyeView.dataManager.setEnabled('traffic', false, {
|
window.__godsEyeView.dataManager.setEnabled('traffic', false, {
|
||||||
origin: 'user',
|
origin: 'user',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600));
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
||||||
const hidden = await pixels();
|
const hidden = await raw();
|
||||||
let visible = 0;
|
const { width, height } = shown.info;
|
||||||
for (let i = 0; i < shown.length; i += 3) {
|
const scale = width / 1024;
|
||||||
if (
|
const changed = (x, y) => {
|
||||||
shown[i] +
|
const i = (y * width + x) * 3;
|
||||||
shown[i + 1] +
|
let d = 0;
|
||||||
shown[i + 2] -
|
for (let c = 0; c < 3; c++)
|
||||||
(hidden[i] + hidden[i + 1] + hidden[i + 2]) >
|
d += Math.abs(shown.data[i + c] - hidden.data[i + c]);
|
||||||
110
|
return d > 90;
|
||||||
)
|
};
|
||||||
visible++;
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
console.log('changed traffic pixels', visible);
|
let stray = 0,
|
||||||
assert.ok(visible > 15, 'changing the layer changes the rendered streets');
|
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, []);
|
assert.deepEqual(errors, []);
|
||||||
console.log(
|
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 {
|
} finally {
|
||||||
await b.close();
|
await b.close();
|
||||||
|
|||||||
Reference in new issue
Block a user