mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
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]>
96 lines
3.5 KiB
JavaScript
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),
|
|
};
|
|
}
|