fix(street-level): ignore implausible ground samples when framing a photo

Opening a photo frames it from 140 m behind using scene.sampleHeight for
the ground. Before the Google 3D tiles under the photo have loaded, that
sample can be kilometres wrong: an Austin 360 photo sampled -14,886 m and
the camera flew to -14,811 m. A sample now counts only when it is a
plausible surface height and, once the bare-earth height is known, lies
within -15/+80 m of it; otherwise the bare-earth height is used. FOLLOW's
eye height goes through the same check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-09-28 21:16:54 +01:00
1 parent d3d3898dbb
commit d77dccb350
1 file changed
+28 -4
+28 -4
View File
@@ -1,29 +1,53 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import { FOLLOW_EYE_HEIGHT_M } from './policy.js'; import { FOLLOW_EYE_HEIGHT_M } from './policy.js';
/** Ellipsoidal heights a land surface can have (Dead Sea shore to Everest, with geoid slack). */
const SURFACE_MIN_M = -500;
const SURFACE_MAX_M = 9000;
/** Rendered mesh window around the bare earth: roofs and bridges, not whole towers. */
const MESH_BELOW_DEM_M = 15;
const MESH_ABOVE_DEM_M = 80;
/** /**
* Drive the globe camera from the street-level pose: either continuously * Drive the globe camera from the street-level pose: either continuously
* ("camera follows view") or once, framing the image from behind. * ("camera follows view") or once, framing the image from behind.
*/ */
export function createCameraFollow({ state }) { export function createCameraFollow({ state, parts }) {
const { render } = state.services; const { render } = state.services;
function requestRender() { function requestRender() {
render?.governorRequestRender?.('street-level-follow'); render?.governorRequestRender?.('street-level-follow');
} }
/**
* Ground under a photo for framing and following. The rendered surface is
* sampled first, but `sampleHeight` can return nonsense before the tiles
* under the point have loaded (kilometres below the ellipsoid), so a sample
* counts only when it is a plausible surface height and, once the bare-earth
* height is known, lies within the mesh window around it.
*/
function groundHeightAt(lon, lat, fallback) { function groundHeightAt(lon, lat, fallback) {
const scene = state.viewer?.scene; const scene = state.viewer?.scene;
const carto = Cesium.Cartographic.fromDegrees(lon, lat); const carto = Cesium.Cartographic.fromDegrees(lon, lat);
const caster = parts?.groundCaster;
const dem = caster?.groundAt(lon, lat) ?? null;
if (dem === null) caster?.prepare([[lon, lat]]);
const plausible = (height) =>
Number.isFinite(height) &&
height >= SURFACE_MIN_M &&
height <= SURFACE_MAX_M &&
(dem === null ||
(height >= dem - MESH_BELOW_DEM_M && height <= dem + MESH_ABOVE_DEM_M));
let height = null; let height = null;
try { try {
if (scene?.sampleHeightSupported) height = scene.sampleHeight(carto); if (scene?.sampleHeightSupported) height = scene.sampleHeight(carto);
} catch { } catch {
/* not sampleable yet */ /* not sampleable yet */
} }
if (!Number.isFinite(height)) height = scene?.globe?.getHeight?.(carto); if (!plausible(height)) height = dem;
if (!Number.isFinite(height)) height = fallback; if (!plausible(height)) height = scene?.globe?.getHeight?.(carto);
return Number.isFinite(height) ? height : 0; if (!plausible(height)) height = fallback;
return plausible(height) ? height : 0;
} }
/** Put the Cesium camera where the street-level camera is. */ /** Put the Cesium camera where the street-level camera is. */