Files
gods-eye-view/src/layers/streetLevel/view.js
T
Said TurkseverandClaude Opus 5.5 abbf69a512 fix(street-level): camera height over Google 3D, cast only when it helps, gate token
From the code review of #768:

- view.js measured the camera's height above ground with globe.getHeight,
  but Google 3D hides the globe, so coverage picked its zoom from the
  ellipsoidal height. Over a high city (Denver, ~1,600 m) a camera 500 m up
  read 2,100 m: coverage stayed at z12/z13, whose tiles are too big to cast,
  and lines stayed draped on the roofs. With the globe hidden, the bare-earth
  height from the ground caster now answers; coverage re-picks its zoom when
  the surface mode changes. Verified in Denver: terrain mode, z14, cast lines.
  The core's surface check uses the same helper (and no longer subtracts the
  2 m lift from the camera height).
- castTile rebuilt a tile even when no line could be cast (terrain proxy
  down, tile too big); it now leaves such tiles as they are.
- The feature gate read Street Level's share token as a literal `0`; it now
  takes it from LAYER_STATE_REGISTRY, since a rebase can reassign it.

New view.test.mjs covers the shown-globe, hidden-globe and no-sample cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 01:01:40 +01:00

96 lines
3.5 KiB
JavaScript

import * as Cesium from 'cesium';
/**
* Camera height above the surface under the camera, in metres. The globe's
* terrain answers when the globe is shown; Google 3D hides the globe, so
* there the bare-earth height from `groundAt(lon, lat)` answers instead
* (without it a camera over a city 1,600 m up reads 1,600 m too high). Falls
* back to the ellipsoidal height when neither has a sample yet.
* @param {object} viewer
* @param {{groundAt?: (lon: number, lat: number) => number|null}} [options]
*/
export function cameraHeightAboveGround(viewer, { groundAt } = {}) {
const carto = viewer?.camera?.positionCartographic;
if (!carto) return null;
const globe = viewer.scene?.globe;
let ground = globe?.show === false ? null : globe?.getHeight?.(carto);
if (!Number.isFinite(ground))
ground = groundAt?.(
Cesium.Math.toDegrees(carto.longitude),
Cesium.Math.toDegrees(carto.latitude),
);
return carto.height - (Number.isFinite(ground) ? ground : 0);
}
/**
* Visible bbox as [west, south, east, north] degrees, or null when the camera
* does not see the ground. A grid of screen rays is cast onto the ellipsoid
* and only the rays that hit count, so a view that includes the horizon (or a
* canvas whose frustum is stale) cannot inflate the box to the whole world;
* `computeViewRectangle` is the fallback when too few rays land.
*/
export function visibleBbox(viewer, { grid = 5 } = {}) {
const scene = viewer?.scene;
const camera = viewer?.camera;
if (!scene || !camera) return null;
const width = scene.canvas?.clientWidth || scene.canvas?.width || 0;
const height = scene.canvas?.clientHeight || scene.canvas?.height || 0;
const hits = [];
if (width > 0 && height > 0) {
const ellipsoid = scene.globe?.ellipsoid || Cesium.Ellipsoid.WGS84;
const point = new Cesium.Cartesian2();
for (let i = 0; i <= grid; i++) {
for (let j = 0; j <= grid; j++) {
point.x = (width * i) / grid;
point.y = (height * j) / grid;
let cartesian = null;
try {
cartesian = camera.pickEllipsoid(point, ellipsoid);
} catch {
cartesian = null;
}
if (!cartesian) continue;
const carto = Cesium.Cartographic.fromCartesian(cartesian, ellipsoid);
if (carto) hits.push(carto);
}
}
}
if (hits.length >= 4) {
let west = Infinity;
let south = Infinity;
let east = -Infinity;
let north = -Infinity;
for (const carto of hits) {
const lon = Cesium.Math.toDegrees(carto.longitude);
const lat = Cesium.Math.toDegrees(carto.latitude);
west = Math.min(west, lon);
east = Math.max(east, lon);
south = Math.min(south, lat);
north = Math.max(north, lat);
}
if (east - west > 0 && north - south > 0) return [west, south, east, north];
}
const rectangle = camera.computeViewRectangle?.(scene.globe?.ellipsoid);
if (!rectangle) return null;
return [
Cesium.Math.toDegrees(rectangle.west),
Cesium.Math.toDegrees(rectangle.south),
Cesium.Math.toDegrees(rectangle.east),
Cesium.Math.toDegrees(rectangle.north),
];
}
/** Centre of the visible ground, or the camera's own footprint. */
export function viewCentre(viewer) {
if (!viewer) return null;
const bbox = visibleBbox(viewer);
if (bbox)
return { lat: (bbox[1] + bbox[3]) / 2, lon: (bbox[0] + bbox[2]) / 2 };
const carto = viewer.camera?.positionCartographic;
if (!carto) return null;
return {
lat: Cesium.Math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude),
};
}