mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(street-level): Panoramax IC8 review — line geometry budget, caches, attribution, tests
From a five-lens IC8 review (correctness, security, tests with mutation
testing, performance, UX):
- P0: over Paris at 12–60 km the unsimplified z10–z12 lines sent ~2M
points to Cesium's ground-polyline builder (gigabytes of geometry; the
tab hung). decode.js now simplifies lines (Douglas–Peucker, 4 tile
units: 6–10% of the points), and lineCoverage gains an opt-in point
budget (`maxPoints`, 10,000 a tile for Panoramax).
- Decoded coverage tiles are kept (KartaView's tile cache moves to the
shared tileCache.js); a sequence's z15 picture lists are kept and load
on their own connection; cones know their tiles from the start of a
load, so a picture opening meanwhile re-centres them and ‹ › no longer
restarts the load.
- The HD panorama loads only for the expanded viewer (a docked one on a
dense screen asked for 3 MB originals) and is aborted when another
photo opens; picture files over 48 MB or 16,384 px are refused; a
bitmap no renderer took after a context loss is closed.
- A nearest lookup survives one of its two searches failing; only a
timeout is retried; offline reads as unreachable; Retry-After is
capped at five minutes; the search box stays on the map at ±180°;
cones near both edges of a narrow (high-latitude) tile read both.
- Every producer is credited (app and photographer); with none to name,
the caption says "via <instance>" rather than crediting the host.
- Esc anywhere in the docked viewer (toolbar, caption) clears the
sequence before the panel takes it. A filter that hides every density
dot says so; wording aligned ("coverage", "photo").
- Tests for every surviving mutant (Esc consumed, keepOrder and dot-size
wiring, licence in the core, limiter aborts, radius floor, bbox corner,
first-producer-only, byte and size caps), a limiter test, and a gate
fixture that answers box searches and leaves out pictures facing away;
the gate now clicks over the other sequence's pictures, so only the
line click's own-sequence lookup can pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5526666894
commit
cf502429a3
29 files changed
+1153
-216
No files matched your search
@@ -25,7 +25,15 @@ const TUNING = Object.freeze({
|
||||
*/
|
||||
function setup(
|
||||
t,
|
||||
{ zoomFor, messages = { rateLimited: 'busy' }, passes, fail } = {},
|
||||
{
|
||||
zoomFor,
|
||||
messages = { rateLimited: 'busy' },
|
||||
passes,
|
||||
fail,
|
||||
decoded = null,
|
||||
tuning = TUNING,
|
||||
overviewPointSize = null,
|
||||
} = {},
|
||||
) {
|
||||
const centre = tileCentre(-121.4944, 38.5816);
|
||||
const viewer = coverageViewer(
|
||||
@@ -68,6 +76,7 @@ function setup(
|
||||
fetchTile: async (tile) => {
|
||||
asked.push(tile);
|
||||
if (fail) throw fail;
|
||||
if (decoded) return decoded(centre);
|
||||
return {
|
||||
sequences: [
|
||||
{
|
||||
@@ -94,9 +103,10 @@ function setup(
|
||||
colors: { coverage: '#635bff', selected: '#00e5ff' },
|
||||
pickPrefix: 'test:line:',
|
||||
renderKey: 'test-coverage',
|
||||
tuning: TUNING,
|
||||
tuning,
|
||||
messages,
|
||||
...(passes ? { passes } : {}),
|
||||
...(overviewPointSize ? { overviewPointSize } : {}),
|
||||
});
|
||||
t.after(() => {
|
||||
coverage.clear();
|
||||
@@ -140,3 +150,88 @@ test('a refused key without its own message keeps the error it came with', async
|
||||
assert.ok(await until(() => state.keyRejected === true));
|
||||
assert.equal(state.coverage.lastError, 'the source refused the key');
|
||||
});
|
||||
|
||||
/** Three lines in one tile: an old long one, then two newer short ones. */
|
||||
function threeLines(centre) {
|
||||
const line = (id, capturedAt, span) => ({
|
||||
id,
|
||||
parts: [
|
||||
[
|
||||
[centre.lon - span, centre.lat],
|
||||
[centre.lon + span, centre.lat],
|
||||
],
|
||||
],
|
||||
capturedAt,
|
||||
isPano: false,
|
||||
});
|
||||
return {
|
||||
sequences: [
|
||||
line('old-long', 1, 0.002),
|
||||
line('new-short', 2, 0.0002),
|
||||
line('newer-short', 3, 0.0001),
|
||||
],
|
||||
overview: [],
|
||||
};
|
||||
}
|
||||
|
||||
test('the per-tile cap keeps the newest lines, or the provider’s own order with keepOrder', async (t) => {
|
||||
const capped = { ...TUNING, maxTiles: 1, maxSequences: 2 };
|
||||
const drawnIds = (state) =>
|
||||
[...state.coverage.tiles.values()].flatMap((tile) => [
|
||||
...tile.sequences.keys(),
|
||||
]);
|
||||
const newest = setup(t, { decoded: threeLines, tuning: capped });
|
||||
newest.coverage.refresh();
|
||||
assert.ok(await until(() => newest.state.coverage.tiles.size === 1));
|
||||
assert.deepEqual(drawnIds(newest.state).sort(), ['new-short', 'newer-short']);
|
||||
const kept = setup(t, {
|
||||
decoded: threeLines,
|
||||
tuning: { ...capped, keepOrder: true },
|
||||
});
|
||||
kept.coverage.refresh();
|
||||
assert.ok(await until(() => kept.state.coverage.tiles.size === 1));
|
||||
assert.deepEqual(drawnIds(kept.state), ['old-long', 'new-short']);
|
||||
});
|
||||
|
||||
test('overview points take the provider’s size, each its own', async (t) => {
|
||||
const { state, coverage, viewer } = setup(t, {
|
||||
zoomFor: () => ({ zoom: 3, kind: 'overview' }),
|
||||
tuning: { ...TUNING, overviewMaxTiles: 4 },
|
||||
decoded: (centre) => ({
|
||||
sequences: [],
|
||||
overview: [
|
||||
{ id: 'a', lon: centre.lon, lat: centre.lat, weight: 1 },
|
||||
{ id: 'b', lon: centre.lon + 0.01, lat: centre.lat, weight: 50_000 },
|
||||
],
|
||||
}),
|
||||
overviewPointSize: (point) => (point.weight > 1 ? 7 : 3),
|
||||
});
|
||||
coverage.refresh();
|
||||
assert.ok(await until(() => state.coverage.tiles.size > 0));
|
||||
const sizes = [];
|
||||
for (const primitive of viewer.scene.primitives.items)
|
||||
for (let i = 0; i < (primitive.length ?? 0); i++)
|
||||
sizes.push(primitive.get(i).pixelSize);
|
||||
assert.deepEqual(sizes.sort(), [3, 7]);
|
||||
});
|
||||
|
||||
test('a point budget skips lines past it, and a shorter one later may still fit', async (t) => {
|
||||
const { state, coverage } = setup(t, {
|
||||
tuning: { ...TUNING, maxTiles: 1, maxSequences: 10, maxPoints: 5, keepOrder: true },
|
||||
decoded: (centre) => {
|
||||
const line = (id, n) => ({
|
||||
id,
|
||||
parts: [
|
||||
Array.from({ length: n }, (_, i) => [centre.lon - 0.001 + i * 0.0001, centre.lat]),
|
||||
],
|
||||
capturedAt: 0,
|
||||
isPano: false,
|
||||
});
|
||||
return { sequences: [line('three', 3), line('four', 4), line('two', 2)], overview: [] };
|
||||
},
|
||||
});
|
||||
coverage.refresh();
|
||||
assert.ok(await until(() => state.coverage.tiles.size === 1));
|
||||
const [tile] = state.coverage.tiles.values();
|
||||
assert.deepEqual([...tile.sequences.keys()], ['three', 'two']);
|
||||
});
|
||||
Reference in new issue
Block a user