fix(street-level): address gekh's review of #768

P0
- tileMath: lonToTileX(180) wrapped to column 0, so a whole-world view at
  z1 loaded only the western column; 180 is now the last column.
- coverage.loadTile: a superseded request (after a retire or clear) still
  deleted the newer request's pending entry and loading count in `finally`,
  letting a duplicate load attach lines that nothing could remove. Only the
  request that still owns the key attaches its tile or settles the key.

P1
- view/tileMath: a view across the date line read as a ~358 deg box and
  loaded tiles on the far side of the globe. visibleBbox now returns
  west > east for such views and tilesForBbox splits them at +/-180;
  viewCentre averages across the line.
- decode: the parts of a multi-line sequence were joined into one line,
  drawing straight segments over capture gaps. Each part is now its own
  line (`parts`); later parts carry `~n` in their pick id, and picking or
  recolouring any part acts on the whole sequence.
- viewerHost: a prewarmed MapillaryJS viewer was never destroyed, leaking
  its WebGL context. Prewarmed adapters are tracked and released on
  unmount, at once when the layer went off mid-load, and per provider when
  only that provider is switched off.
- nearestImage took the first record the API returned; it now takes the
  closest one that passes the filter.

P2
- groundCast: the cache was cleared after a request counted its corners,
  dropping ones it relied on; it is now cleared first.
- streetLevelControls: the viewer was resized on every render (every frame
  while a panorama is dragged); the ResizeObserver handles size changes and
  the viewer is resized once when it opens. The legend now rebuilds when its
  entries change, not only their count.
- panelPositionControls: the portable-panel paths this PR added called the
  CCTV viewport sync directly; they go through onPanelResized, which the
  shell routes to CCTV. The "Compose camera panel controls" JSDoc is back on
  _initCctvPanel.

New tests: zoom-1 columns, date-line tiles and bbox, multi-part decode and
part pick ids, nearest image, prewarmed viewer release, and the cache
ordering (fails on the old order).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-02 19:00:01 +01:00
1 parent 514788303d
commit a1f8d6ca25
17 files changed
+435 -117

No files matched your search

@@ -1,4 +1,4 @@
import { createCoverage } from './coverage.js';
import { createCoverage, sequenceIdFromPick } from './coverage.js';
import { createSequences } from './sequences.js';
import { createMapillaryViewer } from './viewer.js';
import { passesImageryFilter } from '../../filter.js';
@@ -168,8 +168,9 @@ export function createMapillaryProvider({ source }) {
},
handlePick(pickId) {
if (pickId.startsWith(PICK_PREFIX.sequence)) {
parts.sequences.select(pickId.slice(PICK_PREFIX.sequence.length));
const sequenceId = sequenceIdFromPick(pickId);
if (sequenceId) {
parts.sequences.select(sequenceId);
return true;
}
if (pickId.startsWith(PICK_PREFIX.image)) {
@@ -195,7 +196,17 @@ export function createMapillaryProvider({ source }) {
radius: NEAREST_RADIUS_M,
limit: NEAREST_LIMIT,
});
const hit = images.find((record) =>
// The API returns the images in the radius in no particular order.
const metres = (record) => {
const [lon2, lat2] = (record.computed_geometry || record.geometry)
?.coordinates || [NaN, NaN];
const dx = (lon2 - lon) * 111_320 * Math.cos((lat * Math.PI) / 180);
const dy = (lat2 - lat) * 110_540;
const d = Math.hypot(dx, dy);
return Number.isFinite(d) ? d : Infinity;
};
const byDistance = [...images].sort((a, b) => metres(a) - metres(b));
const hit = byDistance.find((record) =>
passesImageryFilter(
{
isPano: record.is_pano === true,