fix(street-level): own the framing flight and the nearest-photo lookup

From the IC8 review of #768, each with tests that fail before:

- P1: closing a photo, switching the layer off or destroying it stops the
  1.6 s framing flight toward that photo. The flight is claimed with a
  token before flyToBoundingSphere and released by its complete/cancel
  callbacks, so cancelFraming() calls camera.cancelFlight() only while
  the flight is still Street Level's: a newer navigation flight (a search
  result, a scene) is never cancelled. Turning FOLLOW on also stops it, so
  the tween cannot overwrite the follow view.
- P2: an older nearest-photo lookup can no longer replace a newer choice.
  The core owns one AbortController per lookup, aborted by a newer lookup,
  a direct open or cone click, closing the viewer, disable and destroy;
  the signal reaches source.nearestImages through the provider, and an
  aborted lookup never surfaces as an error.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-06 15:16:57 +01:00
1 parent c2f1b54d00
commit 09fc10756c
8 files changed
+371 -27

No files matched your search

+76 -5
View File
@@ -8,6 +8,40 @@ import { FOLLOW_EYE_HEIGHT_M } from './policy.js';
const heightOf = (cartesian) =>
Cesium.Cartographic.fromCartesian(cartesian).height;
/**
* A camera with Cesium's flight bookkeeping: starting a flight cancels the
* current one (calling its `cancel`), `cancelFlight` cancels whichever flight
* is current, whoever started it, and `land()` completes it. `cancelled`
* counts the flights `cancelFlight` stopped.
*/
function flightCamera(flights, views) {
let current = null;
function stop() {
const flight = current;
current = null;
flight?.options.cancel?.();
return Boolean(flight);
}
const camera = {
cancelled: 0,
flyToBoundingSphere(sphere, options = {}) {
stop();
current = { sphere, options };
flights.push(current);
},
cancelFlight() {
if (stop()) camera.cancelled++;
},
land() {
const flight = current;
current = null;
flight?.options.complete?.();
},
setView: (view) => views.push(view),
};
return camera;
}
/**
* A viewer whose scene sample is `sampled` (the Austin case: −14,886 m before
* the tiles under the photo had loaded) and whose bare earth is `dem`.
@@ -31,11 +65,7 @@ function setup({ sampled, dem = null, globe = null, altitude = 149 }) {
sampleHeight: () => sampled,
globe: { getHeight: () => globe },
},
camera: {
flyToBoundingSphere: (sphere, options) =>
flights.push({ sphere, options }),
setView: (view) => views.push(view),
},
camera: flightCamera(flights, views),
},
};
const parts = {
@@ -87,3 +117,44 @@ test('following stands the camera at eye height above the checked ground', () =>
0.01,
);
});
test('cancelFraming stops the framing flight while it is still ours (review IC8 P1)', () => {
const { follow, state } = setup({ sampled: 121, dem: 117 });
const { camera } = state.viewer;
follow.lookAtPosition();
follow.cancelFraming();
assert.equal(camera.cancelled, 1, 'the flight toward the closed photo stops');
follow.cancelFraming();
assert.equal(camera.cancelled, 1, 'and only once');
// Re-framing (the next photo) supersedes the first flight, not the claim.
follow.lookAtPosition();
follow.lookAtPosition();
follow.cancelFraming();
assert.equal(camera.cancelled, 2);
});
test('cancelFraming leaves a landed flight and a newer navigation flight alone (review IC8 P1)', () => {
const { follow, state, flights } = setup({ sampled: 121, dem: 117 });
const { camera } = state.viewer;
follow.lookAtPosition();
camera.land();
follow.cancelFraming();
assert.equal(camera.cancelled, 0, 'landed: nothing to stop');
follow.lookAtPosition();
// A search result flies the globe elsewhere; Cesium cancels ours first.
camera.flyToBoundingSphere(null, {});
assert.equal(flights.length, 3);
follow.cancelFraming();
assert.equal(camera.cancelled, 0, 'the newer flight keeps going');
});
test('turning FOLLOW on stops the framing flight so it cannot fight the follow view', () => {
const { follow, state, views } = setup({ sampled: 121, dem: 117 });
const { camera } = state.viewer;
follow.lookAtPosition();
follow.setFollow(true);
assert.equal(camera.cancelled, 1, 'the framing tween stops');
assert.equal(views.length, 1, 'and the camera stands at the photo');
});