mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
refactor(street-level): draw the selected sequence as its own line; drop old-zoom tiles at once
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
fd2a21c0c6
commit
200844998b
5 files changed
+120
-334
No files matched your search
@@ -6,7 +6,6 @@ import { freshCoverage } from '../../state.js';
|
||||
import { encodeCoverageTile } from './coverageFixture.mjs';
|
||||
import { rayCamera } from '../../../../testSupport/streetLevelFakes.mjs';
|
||||
import {
|
||||
COLORS,
|
||||
COVERAGE_MAX_SEQUENCES,
|
||||
COVERAGE_MAX_TILES,
|
||||
} from './policy.js';
|
||||
@@ -48,29 +47,13 @@ function tileCentre(lon, lat) {
|
||||
|
||||
/** A viewer looking straight down on a 0.004° square around `view`. */
|
||||
function fakeViewer(view) {
|
||||
const postRender = new Set();
|
||||
const preRender = new Set();
|
||||
return {
|
||||
view,
|
||||
postRender,
|
||||
preRender,
|
||||
scene: {
|
||||
canvas: { clientWidth: 100, clientHeight: 100 },
|
||||
globe: { show: false, ellipsoid: Cesium.Ellipsoid.WGS84 },
|
||||
groundPrimitives: collection(),
|
||||
primitives: collection(),
|
||||
postRender: {
|
||||
addEventListener(listener) {
|
||||
postRender.add(listener);
|
||||
return () => postRender.delete(listener);
|
||||
},
|
||||
},
|
||||
preRender: {
|
||||
addEventListener(listener) {
|
||||
preRender.add(listener);
|
||||
return () => preRender.delete(listener);
|
||||
},
|
||||
},
|
||||
},
|
||||
camera: {
|
||||
get positionCartographic() {
|
||||
@@ -144,11 +127,6 @@ function setup() {
|
||||
return { viewer, source, state, coverage, bytes, tileKey, onGlobe, centre };
|
||||
}
|
||||
|
||||
/** Run one frame's listeners, as Cesium's render loop would. */
|
||||
function frame(listeners) {
|
||||
for (const listener of [...listeners]) listener();
|
||||
}
|
||||
|
||||
test('a superseded tile request cannot strand lines on the globe', async () => {
|
||||
const { viewer, source, state, coverage, bytes, tileKey, onGlobe } = setup();
|
||||
const forTile = () => source.calls.filter((call) => call.key === tileKey);
|
||||
@@ -345,7 +323,7 @@ test('the per-tile cap applies after the imagery filter, so a dense tile keeps i
|
||||
const cap = Math.floor(COVERAGE_MAX_SEQUENCES / COVERAGE_MAX_TILES);
|
||||
assert.equal(entry.count, Math.min(300, cap), 'every 360° line is drawn');
|
||||
assert.equal(coverage.sequenceCount(), Math.min(300, cap));
|
||||
// Picking and recolouring look sequences up among the drawn lines.
|
||||
// Picking and the highlight look sequences up among the drawn lines.
|
||||
assert.ok(entry.sequences.has('pano-0'), 'drawn lines can be looked up');
|
||||
assert.equal(entry.sequences.has('flat-699'), false);
|
||||
state.filter = { pano: 'all', sinceDays: 0 };
|
||||
@@ -356,56 +334,6 @@ test('the per-tile cap applies after the imagery filter, so a dense tile keeps i
|
||||
coverage.clear();
|
||||
});
|
||||
|
||||
/** The colour attribute value of a line, selected or not. */
|
||||
function colourValue(selected) {
|
||||
return Array.from(
|
||||
Cesium.ColorGeometryInstanceAttribute.toValue(
|
||||
selected
|
||||
? Cesium.Color.fromCssColorString(COLORS.selected)
|
||||
: Cesium.Color.fromCssColorString(COLORS.coverage).withAlpha(0.92),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** Take over a drawn primitive's readiness and seq-1's colour attribute. */
|
||||
function controlled(record, ready) {
|
||||
const control = { ready, attributes: { color: undefined } };
|
||||
Object.defineProperty(record.primitive, 'ready', {
|
||||
get: () => control.ready,
|
||||
});
|
||||
record.primitive.getGeometryInstanceAttributes = (id) =>
|
||||
id === 'mly:seq:seq-1' ? control.attributes : undefined;
|
||||
return control;
|
||||
}
|
||||
|
||||
test('a selection made or cleared while a tile builds is applied once it is ready', async () => {
|
||||
const { viewer, source, state, coverage, bytes } = setup();
|
||||
coverage.refresh();
|
||||
source.calls[0].resolve(bytes);
|
||||
await settle();
|
||||
const [entry] = state.coverage.tiles.values();
|
||||
// Selected while its line is still building: nothing to recolour yet...
|
||||
const first = controlled(entry.primitives[0], false);
|
||||
state.sequence.selectedId = 'seq-1';
|
||||
coverage.recolorSequence('seq-1', true);
|
||||
assert.equal(first.attributes.color, undefined);
|
||||
// ...so the highlight lands once it is ready.
|
||||
first.ready = true;
|
||||
frame(viewer.postRender);
|
||||
assert.deepEqual(Array.from(first.attributes.color), colourValue(true));
|
||||
|
||||
// Rebuilt with the highlight baked in, then cleared mid-build: not stuck.
|
||||
coverage.rebuild();
|
||||
const second = controlled(entry.primitives[0], false);
|
||||
coverage.recolorSequence('seq-1', false);
|
||||
state.sequence.selectedId = null;
|
||||
second.ready = true;
|
||||
frame(viewer.postRender);
|
||||
assert.deepEqual(Array.from(second.attributes.color), colourValue(false));
|
||||
coverage.clear();
|
||||
assert.equal(viewer.postRender.size, 0, 'the selection watch is gone');
|
||||
});
|
||||
|
||||
test('a sequence that crosses a tile edge is counted once', async () => {
|
||||
const { viewer, source, coverage, centre } = setup();
|
||||
// Look straight down on the edge between this tile and its east neighbour.
|
||||
@@ -517,56 +445,39 @@ test('a street view toward the horizon asks only for the ground within range', (
|
||||
coverage.clear();
|
||||
});
|
||||
|
||||
test('a selection cleared while the old zoom is still shown uncolours its lines', async () => {
|
||||
test('the selected sequence is drawn over the coverage, and goes with the selection', async () => {
|
||||
const { viewer, source, state, coverage, bytes } = setup();
|
||||
coverage.refresh();
|
||||
source.calls[0].resolve(bytes);
|
||||
await settle();
|
||||
const [entry] = state.coverage.tiles.values();
|
||||
const line = controlled(entry.primitives[0], true);
|
||||
state.sequence.selectedId = 'seq-1';
|
||||
coverage.recolorSequence('seq-1', true);
|
||||
assert.deepEqual(Array.from(line.attributes.color), colourValue(true));
|
||||
// Zoom out: the street tile stays on screen while the new zoom loads...
|
||||
viewer.view.height = 3000;
|
||||
coverage.refresh();
|
||||
assert.equal(state.coverage.stale.size, 1, 'the old zoom is kept');
|
||||
// ...and the selection is cleared meanwhile.
|
||||
state.sequence.selectedId = null;
|
||||
coverage.recolorSequence('seq-1', false);
|
||||
assert.deepEqual(
|
||||
Array.from(line.attributes.color),
|
||||
colourValue(false),
|
||||
'the retained line is not left cyan',
|
||||
);
|
||||
const lines = () => [...viewer.scene.groundPrimitives.items];
|
||||
assert.equal(lines().length, 1, 'the coverage');
|
||||
coverage.highlight('seq-1');
|
||||
assert.equal(lines().length, 2);
|
||||
const highlight = lines().at(-1);
|
||||
assert.equal(state.coverage.highlight, highlight, 'drawn last, on top');
|
||||
assert.equal(highlight.classificationType, Cesium.ClassificationType.BOTH);
|
||||
coverage.highlight('not-drawn');
|
||||
assert.equal(lines().length, 1, 'nothing to highlight off the map');
|
||||
coverage.highlight(null);
|
||||
assert.equal(state.coverage.highlight, null);
|
||||
coverage.clear();
|
||||
assert.equal(lines().length, 0);
|
||||
});
|
||||
|
||||
test('a selection made while the old zoom is still building is applied once it is ready', async () => {
|
||||
test('a tile that lands with the selected sequence draws the highlight again, on top', async () => {
|
||||
const { viewer, source, state, coverage, bytes } = setup();
|
||||
state.sequence.selectedId = 'seq-1';
|
||||
coverage.refresh();
|
||||
assert.equal(state.coverage.highlight, null, 'nothing loaded yet');
|
||||
source.calls[0].resolve(bytes);
|
||||
await settle();
|
||||
const [entry] = state.coverage.tiles.values();
|
||||
const line = controlled(entry.primitives[0], false);
|
||||
viewer.view.height = 3000;
|
||||
coverage.refresh();
|
||||
assert.equal(state.coverage.stale.size, 1);
|
||||
frame(viewer.postRender);
|
||||
state.sequence.selectedId = 'seq-1';
|
||||
coverage.recolorSequence('seq-1', true);
|
||||
line.ready = true;
|
||||
frame(viewer.postRender);
|
||||
assert.deepEqual(
|
||||
line.attributes.color && Array.from(line.attributes.color),
|
||||
colourValue(true),
|
||||
'the highlight lands on the retained line',
|
||||
);
|
||||
assert.ok(state.coverage.highlight, 'highlighted once its tile is in');
|
||||
assert.equal([...viewer.scene.groundPrimitives.items].at(-1), state.coverage.highlight);
|
||||
coverage.clear();
|
||||
assert.equal(viewer.postRender.size, 0, 'the selection watch is gone');
|
||||
});
|
||||
|
||||
test('the old zoom goes as soon as the new zoom has loaded, not after the stale wait', async () => {
|
||||
test('a zoom change drops the old zoom\'s lines', async () => {
|
||||
const { viewer, source, state, coverage, bytes } = setup();
|
||||
coverage.refresh();
|
||||
source.calls[0].resolve(bytes);
|
||||
@@ -576,23 +487,8 @@ test('the old zoom goes as soon as the new zoom has loaded, not after the stale
|
||||
viewer.view.height = 3000; // zoom out to z13
|
||||
coverage.refresh();
|
||||
assert.equal(state.coverage.zoom, 13);
|
||||
const fresh = source.calls.slice(1);
|
||||
assert.ok(fresh.length > 0, 'the new zoom is loading');
|
||||
assert.equal(state.coverage.stale.size, 1, 'the old zoom stays meanwhile');
|
||||
assert.ok(viewer.scene.groundPrimitives.items.has(old));
|
||||
for (const call of fresh) {
|
||||
assert.ok(
|
||||
viewer.scene.groundPrimitives.items.has(old),
|
||||
'kept while a new tile is still loading',
|
||||
);
|
||||
call.resolve(new Uint8Array());
|
||||
await settle();
|
||||
}
|
||||
// The last new tile is in: the old lines go now, long before the wait.
|
||||
assert.equal(state.coverage.pending.size, 0);
|
||||
assert.equal(state.coverage.stale.size, 0);
|
||||
assert.equal(viewer.scene.groundPrimitives.items.has(old), false);
|
||||
assert.equal(state.coverage.staleTimer, null, 'the stale wait is cancelled');
|
||||
assert.ok(state.coverage.pending.size > 0, 'the new zoom is loading');
|
||||
coverage.clear();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user