mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Draped ground polylines and clamped billboards land on the top of the Google Photorealistic 3D mesh, so on a street under trees or an overpass the coverage seemed to float at roof height. At street zoom on Google 3D (camera below 1,400 m, back above 1,800 m) the core switches to a `terrain` surface mode. groundCast.js samples the existing terrain service on the ground-floor grid (0.001 deg), interpolates bilinearly and adds 2 m; Mapillary lines become plain polylines at those heights, and cones and the position marker stand on the street, so the mesh hides what is behind buildings and trees. A tile draws draped first and swaps to its cast lines once the heights arrive, keeping the old lines until the new primitives are ready. Lookups are once per tile and aborted when the tile is dropped; a geoid fallback counts as unknown and leaves lines draped. Other map stacks and higher views are unchanged. Providers get getSurface() and groundCaster in their context and an optional setSurface(mode); getUIState() exposes `surface`. The QA gate checks draped on Esri and terrain on Google 3D. Co-Authored-By: Claude Opus 5.5 <[email protected]>
111 lines
3.4 KiB
JavaScript
111 lines
3.4 KiB
JavaScript
import * as Cesium from 'cesium';
|
|
import { positionMarkerGlyph } from './glyphs.js';
|
|
import { COLORS, POSITION_PICK_ID } from './policy.js';
|
|
|
|
const SPRITE_ID = 'street-level:marker';
|
|
|
|
/**
|
|
* The on-globe marker for the image the viewer currently shows. In terrain
|
|
* mode (Google 3D at street zoom) it stands on the bare earth instead of
|
|
* clamping to the top of the photoreal mesh.
|
|
*/
|
|
export function createMarker({ state, parts }) {
|
|
const { render, sprites } = state.services;
|
|
let last = null;
|
|
|
|
function requestRender() {
|
|
render?.governorRequestRender?.('street-level-marker');
|
|
}
|
|
|
|
function ensure(viewer) {
|
|
if (state.marker.collection) return;
|
|
state.marker.collection = new Cesium.BillboardCollection({
|
|
scene: viewer.scene,
|
|
});
|
|
viewer.scene.primitives.add(state.marker.collection);
|
|
sprites?.registerSpriteCollection?.(SPRITE_ID, state.marker.collection);
|
|
}
|
|
|
|
/** Terrain height for the position, or null to clamp; fetches a missing cell. */
|
|
function castHeight(position) {
|
|
const ground = parts?.groundCaster;
|
|
if (state.surface !== 'terrain' || !ground) return null;
|
|
const height = ground.heightAt(position.lon, position.lat);
|
|
if (height !== null) return height;
|
|
ground.prepare([[position.lon, position.lat]]).then((ready) => {
|
|
if (ready && last?.position === position) set(position, last.bearing);
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/** Move (or create) the marker; a null position removes it. */
|
|
function set(position, bearing) {
|
|
const collection = state.marker.collection;
|
|
if (!collection) return;
|
|
last = position ? { position, bearing } : null;
|
|
if (!position) {
|
|
collection.removeAll();
|
|
state.marker.billboard = null;
|
|
requestRender();
|
|
return;
|
|
}
|
|
const height = castHeight(position);
|
|
const cartesian = Cesium.Cartesian3.fromDegrees(
|
|
position.lon,
|
|
position.lat,
|
|
height ?? 0,
|
|
);
|
|
const heightReference =
|
|
height === null
|
|
? Cesium.HeightReference.CLAMP_TO_GROUND
|
|
: Cesium.HeightReference.NONE;
|
|
if (!state.marker.billboard) {
|
|
state.marker.billboard = collection.add({
|
|
id: POSITION_PICK_ID,
|
|
position: cartesian,
|
|
image: positionMarkerGlyph({ color: COLORS.position }),
|
|
imageId: 'sl-position',
|
|
width: 40,
|
|
height: 40,
|
|
alignedAxis: Cesium.Cartesian3.UNIT_Z,
|
|
heightReference,
|
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
|
});
|
|
} else {
|
|
state.marker.billboard.heightReference = heightReference;
|
|
state.marker.billboard.position = cartesian;
|
|
}
|
|
state.marker.billboard.rotation = -Cesium.Math.toRadians(bearing || 0);
|
|
requestRender();
|
|
}
|
|
|
|
function setVisible(visible) {
|
|
if (state.marker.collection) state.marker.collection.show = visible;
|
|
requestRender();
|
|
}
|
|
|
|
function destroy(viewer) {
|
|
const collection = state.marker.collection;
|
|
if (!collection) return;
|
|
sprites?.unregisterSpriteCollection?.(SPRITE_ID, collection);
|
|
viewer?.scene?.primitives?.remove(collection);
|
|
state.marker.collection = null;
|
|
state.marker.billboard = null;
|
|
}
|
|
|
|
/** Re-place the marker after the surface mode changes. */
|
|
function setSurface() {
|
|
if (last) set(last.position, last.bearing);
|
|
}
|
|
|
|
return {
|
|
ensure,
|
|
set,
|
|
clear: () => set(null),
|
|
setVisible,
|
|
setSurface,
|
|
destroy,
|
|
};
|
|
}
|