mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +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
@@ -112,7 +112,11 @@ function item(picture, pictures) {
|
||||
sd: { href: `${files}/sd.jpg` },
|
||||
thumb: { href: `${files}/thumb.jpg` },
|
||||
},
|
||||
links: [...link('prev', sibling(-1)), ...link('next', sibling(1))],
|
||||
links: [
|
||||
...link('prev', sibling(-1)),
|
||||
...link('next', sibling(1)),
|
||||
{ rel: 'via', href: HOST },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,11 +235,9 @@ export function answerPanoramaxRequest({ method, url }) {
|
||||
.map((p) => item(p, pictures)),
|
||||
});
|
||||
}
|
||||
const [lon, lat] = (q.get('place_position') || '').split(',').map(Number);
|
||||
const radius = Number((q.get('place_distance') || '0-50').split('-')[1]);
|
||||
const filter = q.get('filter') || '';
|
||||
const since = q.get('datetime')?.split('/')[0];
|
||||
const near = pictures
|
||||
const kept = pictures
|
||||
.filter(
|
||||
(p) => !q.get('collections') || p.sequenceId === q.get('collections'),
|
||||
)
|
||||
@@ -246,7 +248,29 @@ export function answerPanoramaxRequest({ method, url }) {
|
||||
? !p.isPano
|
||||
: true,
|
||||
)
|
||||
.filter((p) => !since || Date.parse(p.datetime) >= Date.parse(since))
|
||||
.filter((p) => !since || Date.parse(p.datetime) >= Date.parse(since));
|
||||
// A box: every picture in it, in no particular order (id order here).
|
||||
if (q.has('bbox')) {
|
||||
const [west, south, east, north] = q.get('bbox').split(',').map(Number);
|
||||
return json({
|
||||
type: 'FeatureCollection',
|
||||
features: kept
|
||||
.filter(
|
||||
(p) =>
|
||||
p.lon >= west && p.lon <= east && p.lat >= south && p.lat <= north,
|
||||
)
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
.slice(0, Number(q.get('limit')) || 10)
|
||||
.map((p) => item(p, pictures)),
|
||||
});
|
||||
}
|
||||
const [lon, lat] = (q.get('place_position') || '').split(',').map(Number);
|
||||
const radius = Number((q.get('place_distance') || '0-50').split('-')[1]);
|
||||
// As the API does, even at the widest tolerance: a place behind a
|
||||
// picture (every one looks east) is not in it.
|
||||
const facing = (p) => lon >= p.lon - 1e-7;
|
||||
const near = kept
|
||||
.filter(facing)
|
||||
.map((p) => ({ p, metres: metresApart({ lon, lat }, p) }))
|
||||
.filter(({ metres }) => metres <= radius)
|
||||
.sort((a, b) => a.metres - b.metres)
|
||||
|
||||
@@ -928,12 +928,16 @@
|
||||
"src/layers/streetLevel/providers/panoramax/viewer.js",
|
||||
"src/layers/streetLevel/photoViewer.js",
|
||||
"src/layers/streetLevel/limiter.js",
|
||||
"src/layers/streetLevel/tileCache.js",
|
||||
"src/layers/streetLevel/providers/panoramax/policy.test.mjs",
|
||||
"src/layers/streetLevel/providers/panoramax/decode.test.mjs",
|
||||
"src/layers/streetLevel/providers/panoramax/source.test.mjs",
|
||||
"src/layers/streetLevel/providers/panoramax/viewer.test.mjs",
|
||||
"src/layers/streetLevel/providers/panoramax/provider.test.mjs",
|
||||
"src/testSupport/panoramaxFixtures.mjs",
|
||||
"src/layers/streetLevel/photoViewer.test.mjs",
|
||||
"src/layers/streetLevel/limiter.test.mjs",
|
||||
"src/layers/streetLevel/panorama.test.mjs",
|
||||
"src/layers/streetLevel/lineCoverage.test.mjs",
|
||||
"src/testSupport/kartaViewFakes.mjs",
|
||||
"src/testSupport/googleStreetViewFakes.mjs",
|
||||
|
||||
@@ -1241,6 +1241,7 @@
|
||||
"src/layers/streetLevel/providers/panoramax/viewer.js",
|
||||
"src/layers/streetLevel/photoViewer.js",
|
||||
"src/layers/streetLevel/limiter.js",
|
||||
"src/layers/streetLevel/tileCache.js",
|
||||
"src/layers/streetLevel/panorama.js",
|
||||
"src/layers/streetLevel/lineCoverage.js",
|
||||
"src/layers/streetLevel/sequenceCones.js",
|
||||
@@ -2216,6 +2217,7 @@
|
||||
"src/layers/streetLevel/providers/panoramax/viewer.js",
|
||||
"src/layers/streetLevel/photoViewer.js",
|
||||
"src/layers/streetLevel/limiter.js",
|
||||
"src/layers/streetLevel/tileCache.js",
|
||||
"src/layers/streetLevel/panorama.js",
|
||||
"src/layers/streetLevel/lineCoverage.js",
|
||||
"src/layers/streetLevel/sequenceCones.js",
|
||||
@@ -2762,6 +2764,7 @@
|
||||
"src/layers/streetLevel/providers/panoramax/viewer.js",
|
||||
"src/layers/streetLevel/photoViewer.js",
|
||||
"src/layers/streetLevel/limiter.js",
|
||||
"src/layers/streetLevel/tileCache.js",
|
||||
"src/layers/streetLevel/panorama.js",
|
||||
"src/layers/streetLevel/lineCoverage.js",
|
||||
"src/layers/streetLevel/sequenceCones.js",
|
||||
|
||||
@@ -1255,7 +1255,30 @@ async function main() {
|
||||
.catch(() => null);
|
||||
if (!handle && !fixtures) return skip('no Panoramax line in view');
|
||||
assert.ok(handle, 'a Panoramax line is pickable');
|
||||
const at = await handle.jsonValue();
|
||||
let at = await handle.jsonValue();
|
||||
if (fixtures) {
|
||||
// Click the top line where the other sequence's pictures are:
|
||||
// the nearest picture is then the other's, and only the line
|
||||
// click's own-sequence lookup opens the clicked line's.
|
||||
const others = panoramaxPictures()
|
||||
.filter((p) => p.sequenceId !== at.id)
|
||||
.slice(40, 100)
|
||||
.map((p) => [p.lon, p.lat]);
|
||||
const aimed = await page
|
||||
.waitForFunction(
|
||||
findPickPoint,
|
||||
{ timeout: 60_000, polling: 500 },
|
||||
{
|
||||
prefix: 'pnx:seq:',
|
||||
id: at.id,
|
||||
points: others,
|
||||
billboardIds: null,
|
||||
},
|
||||
)
|
||||
.catch(() => null);
|
||||
assert.ok(aimed, 'the line is pickable over the other’s pictures');
|
||||
at = await aimed.jsonValue();
|
||||
}
|
||||
const parked = await camera();
|
||||
await page.mouse.click(at.x, at.y);
|
||||
await page.waitForFunction(
|
||||
@@ -1298,8 +1321,8 @@ async function main() {
|
||||
else
|
||||
assert.equal(
|
||||
caption,
|
||||
'Licence Ouverte 2.0',
|
||||
'an account named by an email address is not shown',
|
||||
'via panoramax.openstreetmap.fr · Licence Ouverte 2.0',
|
||||
'an account named by an email address is not shown; the instance is',
|
||||
);
|
||||
const pictures = panoramaxPictures().filter(
|
||||
(p) => p.sequenceId === street.sequenceId,
|
||||
|
||||
Reference in new issue
Block a user