Files
gods-eye-view/src/layers/streetLevel/view.test.mjs
T
German KhokhlovandClaude Opus 5.5 d59b4d05bb refactor(street-level): trim dead paths, duplicated labels and test-only API
- visibleBbox: no view-rectangle fallback or grid option; its one caller
  always ranges the rays
- tilesForBbox returns the tiles; normalizeBbox takes arrays only
- viewerHost: no try/catch around adapter calls that catch for themselves
- coverage: counts come from the drawn sequences; no sequenceCount export
- one keyStatusLabel for the pill and the layer list
- no constructor source check, double-init guard or selectSequence /
  clearSequence on the layer

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 17:04:05 +04:00

388 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import * as Cesium from 'cesium';
import {
cameraNadir,
createHorizonCull,
groundUnderCamera,
metresBetween,
viewFocus,
visibleBbox,
} from './view.js';
import { latToTileY, lonToTileX, tilesForBbox } from './tileMath.js';
import { rayCamera } from '../../testSupport/streetLevelFakes.mjs';
const RAD = Math.PI / 180;
/** A viewer 300 m above a street 1,600 m up (Denver-like). */
function viewer({ globeShown, globeHeight }) {
return {
camera: {
positionCartographic: {
longitude: -104.99 * RAD,
latitude: 39.74 * RAD,
height: 1900,
},
},
scene: { globe: { show: globeShown, getHeight: () => globeHeight } },
};
}
test('a shown globe answers the ground under the camera', () => {
assert.equal(
groundUnderCamera(viewer({ globeShown: true, globeHeight: 1600 })),
1600,
);
});
test('a hidden globe (Google 3D) or a missing sample gives no ground', () => {
// A hidden globe's getHeight is ignored even when it returns a number.
assert.equal(
groundUnderCamera(viewer({ globeShown: false, globeHeight: 0 })),
null,
);
assert.equal(
groundUnderCamera(viewer({ globeShown: true, globeHeight: undefined })),
null,
);
assert.equal(groundUnderCamera({}), null);
});
/** The Denver viewer with Google 3D: no globe, a sampleable rendered surface. */
function google3d(sample) {
const view = viewer({ globeShown: false, globeHeight: 0 });
view.scene.sampleHeightSupported = true;
view.scene.sampleHeight = () => sample;
return view;
}
test('on Google 3D the rendered surface answers the ground under the camera', () => {
assert.equal(groundUnderCamera(google3d(1612)), 1612);
});
test('an implausible or above-camera surface sample gives no ground', () => {
// Before tiles stream in, sampleHeight can read kilometres underground.
assert.equal(groundUnderCamera(google3d(-14_886)), null);
// A roof above the camera (1,900 m) would put the camera underground.
assert.equal(groundUnderCamera(google3d(1950)), null);
assert.equal(groundUnderCamera(google3d(undefined)), null);
});
/** A camera whose screen rays land on a grid of lon/lat points. */
function gridViewer(lons, lats) {
const points = [];
for (const lat of lats)
for (const lon of lons)
points.push(Cesium.Cartesian3.fromDegrees(lon, lat));
let next = 0;
return {
scene: { canvas: { clientWidth: 100, clientHeight: 100 }, globe: {} },
camera: {
pickEllipsoid: () => points[next++ % points.length],
positionCartographic: { longitude: 0, latitude: 0, height: 1000 },
},
};
}
test('a view across the date line is a narrow box with west > east', () => {
const viewer = gridViewer([178, 179, -179, -178], [-1, 0, 1]);
const bbox = visibleBbox(viewer);
assert.ok(bbox[0] > 177 && bbox[0] < 179, `west ${bbox[0]}`);
assert.ok(bbox[2] < -177 && bbox[2] > -179, `east ${bbox[2]}`);
});
test('an ordinary view keeps west < east', () => {
const bbox = visibleBbox(gridViewer([10, 11, 12], [50, 51]));
assert.ok(bbox[0] < bbox[2]);
});
/** A pinhole viewer `agl` m above ground `ground` m up, looking north. */
function pinhole({ lon, lat, ground, agl, pitch, w = 1600, h = 900 }) {
return {
scene: {
canvas: { clientWidth: w, clientHeight: h },
globe: { show: false, ellipsoid: Cesium.Ellipsoid.WGS84 },
},
camera: rayCamera({
lon,
lat,
altitude: ground + agl,
pitch,
width: w,
height: h,
}),
};
}
test('a tilted street view over high ground boxes the streets it looks at, not the horizon', () => {
// Denver, 600 m above a street 1,610 m up, looking 20° down: the centre of
// the screen meets the street about 1.65 km north of the camera.
const view = pinhole({
lon: -104.99,
lat: 39.74,
ground: 1610,
agl: 600,
pitch: -20,
});
const ahead = 39.74 + 1648 / 111_000;
const unranged = visibleBbox(view);
// Without options, rays to the bare ellipsoid travel 1.6 km further down:
// the box is 20 km wide and starts past the street at the screen centre.
assert.ok(unranged[2] - unranged[0] > 0.2);
assert.ok(unranged[1] > ahead, 'the old box missed the street in view');
const options = { groundHeight: 1610, maxRange: 6000 };
const [west, south, east, north] = visibleBbox(view, options);
assert.ok(north - south < 0.08 && east - west < 0.08, 'a street-sized box');
assert.ok(south < ahead && north > ahead, 'the screen centre is inside');
assert.ok(
south > 39.74,
'it starts ahead of the camera, where the view does',
);
assert.ok(west < -104.99 && east > -104.99);
const nadir = cameraNadir(view);
assert.ok(
Math.abs(nadir.lat - 39.74) < 1e-9 && Math.abs(nadir.lon + 104.99) < 1e-9,
);
});
test('a tilted view from 3 km loads the tile at the centre of the screen', () => {
// 3 km up, 70° from straight down: the screen centre meets the ground
// 8.2 km ahead, past the 3 × 3 z13 tiles around the camera's ground point.
const lon = -121.49;
const lat = 38.58;
const view = pinhole({ lon, lat, ground: 10, agl: 3000, pitch: -20 });
const ranged = { groundHeight: 10, maxRange: 30_000 };
const bbox = visibleBbox(view, ranged);
const ahead = lat + 3000 / Math.tan((20 * Math.PI) / 180) / 110_540;
const centreTile = `${lonToTileX(lon, 13)}/${latToTileY(ahead, 13)}`;
const chosen = (from) =>
tilesForBbox(bbox, 13, { limit: 9, from }).map(
(tile) => `${tile.x}/${tile.y}`,
);
assert.ok(
!chosen(cameraNadir(view)).includes(centreTile),
'ranked from the nadir alone, the screen centre is left out',
);
const focus = viewFocus(view, ranged);
assert.ok(focus.lat > lat && focus.lat < ahead, 'between camera and centre');
assert.ok(chosen(focus).includes(centreTile), 'the screen centre is loaded');
// The bottom edge of the screen (50° down) meets the ground 2.5 km ahead.
const nearest = lat + 3000 / Math.tan((50 * Math.PI) / 180) / 110_540;
assert.ok(
chosen(focus).includes(`${lonToTileX(lon, 13)}/${latToTileY(nearest, 13)}`),
'and so is the nearest ground in view',
);
});
test('looking straight down or at the sky, the focus is the ground under the camera', () => {
const lon = -121.49;
const lat = 38.58;
const down = viewFocus(
pinhole({ lon, lat, ground: 10, agl: 500, pitch: -90 }),
{ groundHeight: 10, maxRange: 5000 },
);
assert.ok(Math.abs(down.lat - lat) < 1e-4 && Math.abs(down.lon - lon) < 1e-4);
const sky = viewFocus(pinhole({ lon, lat, ground: 10, agl: 2, pitch: 60 }), {
groundHeight: 10,
maxRange: 2500,
});
assert.ok(Math.abs(sky.lat - lat) < 1e-9 && Math.abs(sky.lon - lon) < 1e-9);
});
test('looking at the horizon from eye height boxes the near ground, not the horizon', () => {
const view = pinhole({
lon: -121.4944,
lat: 38.5816,
ground: 10,
agl: 2,
pitch: -5,
});
const [west, south, east, north] = visibleBbox(view, {
groundHeight: 10,
maxRange: 2500,
});
assert.ok(south > 38.5816 && south - 38.5816 < 0.001, 'from just ahead');
assert.ok(north - 38.5816 < 2500 / 111_000, 'no further than the range');
assert.ok(west < -121.4944 && east > -121.4944);
// Looking at the sky, nothing is in range: no box, never the horizon's.
const sky = pinhole({
lon: -121.4944,
lat: 38.5816,
ground: 10,
agl: 2,
pitch: 60,
});
assert.equal(visibleBbox(sky, { groundHeight: 10, maxRange: 2500 }), null);
});
test('metresBetween measures the short way round the date line', () => {
const east = { lon: 179.9999, lat: 0 };
const west = { lon: -179.9999, lat: 0 };
// 0.0002° of longitude at the equator, not 40,000 km round the other way.
assert.ok(Math.abs(metresBetween(east, west) - 22.264) < 1e-6);
assert.ok(Math.abs(metresBetween(west, east) - 22.264) < 1e-6);
assert.ok(
Math.abs(
metresBetween({ lon: 180, lat: 0 }, { lon: -180, lat: 0.0001 }) - 11.054,
) < 1e-6,
'the two names of the same meridian',
);
// Away from the date line nothing changes.
assert.ok(
Math.abs(
metresBetween({ lon: 10, lat: 0 }, { lon: 10.001, lat: 0 }) - 111.32,
) < 1e-9,
);
});
/**
* A horizon cull over `items` for a camera the test moves, with the frame's
* pre-render listeners and a count of the points tested against the horizon.
*/
function cullHarness(t, items) {
const visible = t.mock.method(
Cesium.EllipsoidalOccluder.prototype,
'isPointVisible',
);
const preRender = new Set();
const camera = { positionWC: Cesium.Cartesian3.fromDegrees(0, 0, 1e7) };
const viewer = {
camera,
scene: {
preRender: {
addEventListener(listener) {
preRender.add(listener);
return () => preRender.delete(listener);
},
},
},
};
let changes = 0;
const cull = createHorizonCull({
getViewer: () => viewer,
items: () => items,
onChange: () => changes++,
});
return {
cull,
preRender,
tested: () => visible.mock.callCount(),
changes: () => changes,
frame: () => {
for (const listener of [...preRender]) listener();
},
// Moved in place, as Cesium updates positionWC.
moveTo: (lon, lat) =>
Cesium.Cartesian3.fromDegrees(
lon,
lat,
1e7,
undefined,
camera.positionWC,
),
};
}
const point = (lon, lat) => ({
position: Cesium.Cartesian3.fromDegrees(lon, lat),
show: true,
});
test('the horizon cull does no work while the camera stands still', (t) => {
const near = point(0, 0);
const far = point(180, 0);
const h = cullHarness(t, [near, far]);
h.cull.update();
assert.equal(h.tested(), 2);
assert.deepEqual([near.show, far.show], [true, false]);
assert.equal(h.preRender.size, 1, 'listening for camera moves');
for (let i = 0; i < 5; i++) h.frame();
assert.equal(h.tested(), 2, 'a still camera re-culls nothing');
h.moveTo(180, 0);
h.frame();
assert.equal(h.tested(), 4, 'a moved camera re-culls everything once');
assert.deepEqual([near.show, far.show], [false, true]);
h.frame();
h.frame();
assert.equal(h.tested(), 4, 'and then rests again');
h.cull.stop();
assert.equal(h.preRender.size, 0);
});
test('the horizon cull stops listening once nothing is left, and starts again for new items', (t) => {
const items = [point(0, 0)];
const h = cullHarness(t, items);
h.cull.update();
assert.equal(h.preRender.size, 1);
// Every item goes (the tiles are dropped); the next camera move finds none.
items.length = 0;
h.moveTo(10, 0);
h.frame();
assert.equal(h.preRender.size, 0, 'the listener is removed');
// New items arrive: they are culled now, and camera moves are watched again.
const far = point(-170, 0);
items.push(far);
h.cull.update([far]);
assert.equal(far.show, false, 'culled on arrival');
assert.equal(h.preRender.size, 1, 'the listener is back');
h.moveTo(-170, 0);
h.frame();
assert.equal(far.show, true, 're-culled when the camera moves');
assert.equal(h.changes(), 2, 'a redraw for each change');
h.cull.stop();
});
/** A cull over `points` ([lon, lat, height]) seen from a camera at `camera`. */
function cullFrom(camera, points) {
const items = points.map(([lon, lat, height]) => ({
position: Cesium.Cartesian3.fromDegrees(lon, lat, height),
show: true,
}));
const horizon = createHorizonCull({
getViewer: () => ({
camera: { positionWC: Cesium.Cartesian3.fromDegrees(...camera) },
scene: { preRender: { addEventListener: () => () => {} } },
}),
items: () => items,
});
horizon.update();
horizon.stop();
return items.map((item) => item.show);
}
test('cones and the marker stay visible on ground below the WGS84 ellipsoid', () => {
// NYC at street level: eye 2.4 m above ground at -22 m, a cone 10 m away.
assert.deepEqual(
cullFrom([-74.006, 40.7128, -19.6], [[-74.006, 40.71289, -22]]),
[true],
);
// Colombo framing view: camera at -21 m, cones at -95 m about 80 m away.
assert.deepEqual(
cullFrom(
[79.8612, 6.9271, -21],
[
[79.8612, 6.92782, -95],
[79.86192, 6.9271, -95],
],
),
[true, true],
);
// Austin, ground well above the ellipsoid.
assert.deepEqual(
cullFrom([-97.7431, 30.2672, 132], [[-97.7431, 30.2681, 130]]),
[true],
);
});
test('the horizon cull still hides the far side of the Earth from orbit', () => {
assert.deepEqual(
cullFrom(
[0, 0, 20_000_000],
[
[10, 10, 0],
[180, 0, 0],
],
),
[true, false],
);
});