refactor(ui,voice,annotations,scenes): take vector maths from src/geo

Move the Cartesian3/Cartographic/Matrix4/Transforms/Ellipsoid/Math calls of
the second batch (cockpit camera, context and controller; camera orientation
controls; application shell; location navigation; the annotation resolver
and engine; voice actions and pointer pick; the scene director; the embed
app) to src/geo. Behaviour is unchanged: same argument order, same `result`
semantics.

Kept on Cesium: out-of-scope classes (HeadingPitchRange, BoundingSphere,
Intersect, EllipsoidalOccluder, EasingFunction, SceneTransforms, JulianDate),
WGS84 where it goes into pickEllipsoid or EllipsoidalOccluder,
Matrix4.IDENTITY handed to lookAtTransform, and the Cartesian2 window
positions that reach scene.pickPosition: Cesium keys its per-frame pick
cache on windowPosition.toString(), which a plain object would collapse to
one "[object Object]" key.

The Contacts-subject harness in gevActions.test stubbed
Cesium.Cartographic.fromCartesian to place its subject; it now builds a real
subject position at the same coordinates with geo.cartesian3.fromDegrees.
The cockpit-controls and scenes package-boundary groups now own the src/geo
files.
This commit is contained in:
German Khokhlov committed 2026-10-15 00:16:38 +04:00
1 parent d886be68a8
commit 1a69368ae6
14 files changed
+216 -200

No files matched your search

+30 -2
View File
@@ -520,7 +520,21 @@
"src/cockpitMath.js", "src/cockpitMath.js",
"src/cockpitUtilityLayout.js", "src/cockpitUtilityLayout.js",
"src/cockpitVisionPolicy.js", "src/cockpitVisionPolicy.js",
"src/ui/panelRailGeometry.js" "src/ui/panelRailGeometry.js",
"src/geo/index.js",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["cesium"] "external": ["cesium"]
}, },
@@ -788,7 +802,21 @@
"src/director/authoring.js", "src/director/authoring.js",
"src/scenes/sharing.js", "src/scenes/sharing.js",
"src/ui/sceneSharing.js", "src/ui/sceneSharing.js",
"src/data/cameraArrival.js" "src/data/cameraArrival.js",
"src/geo/index.js",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["cesium"] "external": ["cesium"]
}, },
+15 -14
View File
@@ -1,6 +1,7 @@
import { isUnavailableCapability } from '../sources/capability.js'; import { isUnavailableCapability } from '../sources/capability.js';
import { defaultGeospatial } from '../search/defaults.js'; import { defaultGeospatial } from '../search/defaults.js';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { import {
holdContinuousRender, holdContinuousRender,
releaseContinuousRender, releaseContinuousRender,
@@ -1013,13 +1014,13 @@ export function createAnnotationEngine({
return; return;
} }
const cart = points.map((p) => const cart = points.map((p) =>
Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height || 0), geo.cartesian3.fromDegrees(p.lon, p.lat, p.height || 0),
); );
const sphere = Cesium.BoundingSphere.fromPoints(cart); const sphere = Cesium.BoundingSphere.fromPoints(cart);
viewer.camera.flyToBoundingSphere(sphere, { viewer.camera.flyToBoundingSphere(sphere, {
offset: new Cesium.HeadingPitchRange( offset: new Cesium.HeadingPitchRange(
viewer.camera.heading, viewer.camera.heading,
Cesium.Math.toRadians(-35), geo.math.toRadians(-35),
Math.max(900, sphere.radius * 2.6), Math.max(900, sphere.radius * 2.6),
), ),
duration: 1.6, duration: 1.6,
@@ -1034,7 +1035,7 @@ export function createAnnotationEngine({
function isPointOnScreen(p) { function isPointOnScreen(p) {
try { try {
const camera = viewer.camera; const camera = viewer.camera;
const pos = Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height || 0); const pos = geo.cartesian3.fromDegrees(p.lon, p.lat, p.height || 0);
const cv = camera.frustum.computeCullingVolume( const cv = camera.frustum.computeCullingVolume(
camera.position, camera.position,
camera.direction, camera.direction,
@@ -1075,7 +1076,7 @@ export function createAnnotationEngine({
: viewportRange(anno.viewport) || 600; : viewportRange(anno.viewport) || 600;
viewer.camera.flyToBoundingSphere( viewer.camera.flyToBoundingSphere(
new Cesium.BoundingSphere( new Cesium.BoundingSphere(
Cesium.Cartesian3.fromDegrees( geo.cartesian3.fromDegrees(
target.lon, target.lon,
target.lat, target.lat,
anno.anchor.height || 0, anno.anchor.height || 0,
@@ -1085,7 +1086,7 @@ export function createAnnotationEngine({
{ {
offset: new Cesium.HeadingPitchRange( offset: new Cesium.HeadingPitchRange(
viewer.camera.heading, viewer.camera.heading,
Cesium.Math.toRadians(-35), geo.math.toRadians(-35),
range * 2.4, range * 2.4,
), ),
duration: 1.8, duration: 1.8,
@@ -1275,10 +1276,10 @@ export function createAnnotationEngine({
if (destroyed) return; if (destroyed) return;
try { try {
viewer.camera.flyTo({ viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, height), destination: geo.cartesian3.fromDegrees(lon, lat, height),
orientation: { orientation: {
heading: Cesium.Math.toRadians(heading), heading: geo.math.toRadians(heading),
pitch: Cesium.Math.toRadians(pitch), pitch: geo.math.toRadians(pitch),
roll: 0, roll: 0,
}, },
duration, duration,
@@ -1395,9 +1396,9 @@ const VIEWPORT_ASSIST_RANGE_CAP_M = 120000;
function viewportRange(vp) { function viewportRange(vp) {
if (!vp?.low || !vp?.high) return null; if (!vp?.low || !vp?.high) return null;
try { try {
const span = Cesium.Cartesian3.distance( const span = geo.cartesian3.distance(
Cesium.Cartesian3.fromDegrees(vp.low.longitude, vp.low.latitude), geo.cartesian3.fromDegrees(vp.low.longitude, vp.low.latitude),
Cesium.Cartesian3.fromDegrees(vp.high.longitude, vp.high.latitude), geo.cartesian3.fromDegrees(vp.high.longitude, vp.high.latitude),
); );
return Number.isFinite(span) && span > 0 return Number.isFinite(span) && span > 0
? Math.max(300, Math.min(span * 0.7, VIEWPORT_ASSIST_RANGE_CAP_M)) ? Math.max(300, Math.min(span * 0.7, VIEWPORT_ASSIST_RANGE_CAP_M))
@@ -1422,9 +1423,9 @@ function ringRange(ring) {
if (lon < minLon) minLon = lon; if (lon < minLon) minLon = lon;
if (lon > maxLon) maxLon = lon; if (lon > maxLon) maxLon = lon;
} }
const span = Cesium.Cartesian3.distance( const span = geo.cartesian3.distance(
Cesium.Cartesian3.fromDegrees(minLon, minLat), geo.cartesian3.fromDegrees(minLon, minLat),
Cesium.Cartesian3.fromDegrees(maxLon, maxLat), geo.cartesian3.fromDegrees(maxLon, maxLat),
); );
return Math.max(300, span * 0.7); return Math.max(300, span * 0.7);
} }
+14 -13
View File
@@ -14,6 +14,7 @@ import {
} from '../sources/featureGeometry.js'; } from '../sources/featureGeometry.js';
import { defaultGeospatial } from '../search/defaults.js'; import { defaultGeospatial } from '../search/defaults.js';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { lookupNeighborhoodRing } from '../data/neighborhoodPolygons.js'; import { lookupNeighborhoodRing } from '../data/neighborhoodPolygons.js';
import { import {
lookupNaturalRegionOutline, lookupNaturalRegionOutline,
@@ -1835,8 +1836,8 @@ export function createAnnotationResolver({
try { try {
const carto = viewer?.camera?.positionCartographic; const carto = viewer?.camera?.positionCartographic;
if (!carto) return null; if (!carto) return null;
const lat = Cesium.Math.toDegrees(carto.latitude); const lat = geo.math.toDegrees(carto.latitude);
const lon = Cesium.Math.toDegrees(carto.longitude); const lon = geo.math.toDegrees(carto.longitude);
const radiusKm = Math.max((carto.height / 1000) * 3, 5); const radiusKm = Math.max((carto.height / 1000) * 3, 5);
if (![lat, lon, radiusKm].every(Number.isFinite)) return null; if (![lat, lon, radiusKm].every(Number.isFinite)) return null;
return { lat, lon, radiusKm }; return { lat, lon, radiusKm };
@@ -1850,10 +1851,10 @@ export function createAnnotationResolver({
try { try {
const rect = viewer?.camera?.computeViewRectangle?.(); const rect = viewer?.camera?.computeViewRectangle?.();
if (!rect) return null; if (!rect) return null;
const swLat = Cesium.Math.toDegrees(rect.south).toFixed(4); const swLat = geo.math.toDegrees(rect.south).toFixed(4);
const swLng = Cesium.Math.toDegrees(rect.west).toFixed(4); const swLng = geo.math.toDegrees(rect.west).toFixed(4);
const neLat = Cesium.Math.toDegrees(rect.north).toFixed(4); const neLat = geo.math.toDegrees(rect.north).toFixed(4);
const neLng = Cesium.Math.toDegrees(rect.east).toFixed(4); const neLng = geo.math.toDegrees(rect.east).toFixed(4);
if ([swLat, swLng, neLat, neLng].some((v) => v === 'NaN')) return null; if ([swLat, swLng, neLat, neLng].some((v) => v === 'NaN')) return null;
return `${swLat},${swLng}|${neLat},${neLng}`; return `${swLat},${swLng}|${neLat},${neLng}`;
} catch { } catch {
@@ -1953,11 +1954,11 @@ export function createAnnotationResolver({
} }
} }
if (!isPickedWorldPosition(cart)) return null; if (!isPickedWorldPosition(cart)) return null;
const carto = Cesium.Cartographic.fromCartesian(cart); const carto = geo.cartographic.fromCartesian(cart);
if (!carto) return null; if (!carto) return null;
return { return {
lat: Cesium.Math.toDegrees(carto.latitude), lat: geo.math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude), lon: geo.math.toDegrees(carto.longitude),
// The height the pick actually landed on — a roof, a hillside, or 0 on // The height the pick actually landed on — a roof, a hillside, or 0 on
// the ellipsoid. Callers that only want a coordinate ignore it; the draw // the ellipsoid. Callers that only want a coordinate ignore it; the draw
// tool's live preview needs it, or the rubber band sinks to sea level // tool's live preview needs it, or the rubber band sinks to sea level
@@ -1979,11 +1980,11 @@ export function createAnnotationResolver({
scene.clampToHeightSupported && scene.clampToHeightSupported &&
typeof scene.clampToHeight === 'function' typeof scene.clampToHeight === 'function'
) { ) {
const carto = Cesium.Cartographic.fromDegrees(lon, lat); const carto = geo.cartographic.fromDegrees(lon, lat);
const surface = Cesium.Cartographic.toCartesian(carto); const surface = geo.cartographic.toCartesian(carto);
const clamped = scene.clampToHeight(surface); const clamped = scene.clampToHeight(surface);
if (clamped) { if (clamped) {
const h = Cesium.Cartographic.fromCartesian(clamped).height; const h = geo.cartographic.fromCartesian(clamped).height;
if (Number.isFinite(h)) return h; if (Number.isFinite(h)) return h;
} }
} }
@@ -1991,7 +1992,7 @@ export function createAnnotationResolver({
/* tiles not ready — fall through */ /* tiles not ready — fall through */
} }
const globeHeight = scene.globe?.getHeight?.( const globeHeight = scene.globe?.getHeight?.(
Cesium.Cartographic.fromDegrees(lon, lat), geo.cartographic.fromDegrees(lon, lat),
); );
return Number.isFinite(globeHeight) && globeHeight > 0 ? globeHeight : 0; return Number.isFinite(globeHeight) && globeHeight > 0 ? globeHeight : 0;
} }
+4 -4
View File
@@ -11,7 +11,7 @@
* embedded or not. * embedded or not.
*/ */
import * as Cesium from 'cesium'; import * as geo from '../geo/index.js';
import { announceNavigationAuthority } from '../navigationPolicy.js'; import { announceNavigationAuthority } from '../navigationPolicy.js';
import { annotationsFromParams, createView } from '../view/index.js'; import { annotationsFromParams, createView } from '../view/index.js';
@@ -126,14 +126,14 @@ function flyTo(viewer, camera) {
announceNavigationAuthority('embed-view'); announceNavigationAuthority('embed-view');
return new Promise((resolve) => { return new Promise((resolve) => {
viewer.camera.flyTo({ viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees( destination: geo.cartesian3.fromDegrees(
camera.lon, camera.lon,
camera.lat, camera.lat,
camera.altitude_m, camera.altitude_m,
), ),
orientation: { orientation: {
heading: Cesium.Math.toRadians(camera.heading_deg), heading: geo.math.toRadians(camera.heading_deg),
pitch: Cesium.Math.toRadians(camera.pitch_deg), pitch: geo.math.toRadians(camera.pitch_deg),
roll: 0, roll: 0,
}, },
duration: FLIGHT_SECONDS, duration: FLIGHT_SECONDS,
+9 -8
View File
@@ -20,6 +20,7 @@ import { SceneControls } from '../ui/scenes.js';
import { buildPlaybackQueue, playSceneQueue } from '../director/playback.js'; import { buildPlaybackQueue, playSceneQueue } from '../director/playback.js';
import { createScenePlaybackAdapter } from './playbackAdapter.js'; import { createScenePlaybackAdapter } from './playbackAdapter.js';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { import {
SCENE_APPEND_RECIPES, SCENE_APPEND_RECIPES,
SCENE_RECIPES, SCENE_RECIPES,
@@ -1577,15 +1578,15 @@ export class SceneDirector {
this.styleManager?.clearSearchedLocation?.(); this.styleManager?.clearSearchedLocation?.();
this.viewer.camera.cancelFlight?.(); this.viewer.camera.cancelFlight?.();
this.viewer.camera.setView({ this.viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees( destination: geo.cartesian3.fromDegrees(
cameraState.lon, cameraState.lon,
cameraState.lat, cameraState.lat,
cameraState.alt, cameraState.alt,
), ),
orientation: { orientation: {
heading: Cesium.Math.toRadians(cameraState.heading || 0), heading: geo.math.toRadians(cameraState.heading || 0),
pitch: Cesium.Math.toRadians(cameraState.pitch ?? -35), pitch: geo.math.toRadians(cameraState.pitch ?? -35),
roll: Cesium.Math.toRadians(cameraState.roll || 0), roll: geo.math.toRadians(cameraState.roll || 0),
}, },
}); });
return true; return true;
@@ -2268,7 +2269,7 @@ export class SceneDirector {
0.2, 0.2,
Number(durationSec) || DEFAULT_SHOT_DURATION_SEC, Number(durationSec) || DEFAULT_SHOT_DURATION_SEC,
); );
const destination = Cesium.Cartesian3.fromDegrees( const destination = geo.cartesian3.fromDegrees(
cameraState.lon, cameraState.lon,
cameraState.lat, cameraState.lat,
cameraState.alt, cameraState.alt,
@@ -2288,9 +2289,9 @@ export class SceneDirector {
this.viewer.camera.flyTo({ this.viewer.camera.flyTo({
destination, destination,
orientation: { orientation: {
heading: Cesium.Math.toRadians(cameraState.heading || 0), heading: geo.math.toRadians(cameraState.heading || 0),
pitch: Cesium.Math.toRadians(cameraState.pitch ?? -35), pitch: geo.math.toRadians(cameraState.pitch ?? -35),
roll: Cesium.Math.toRadians(cameraState.roll || 0), roll: geo.math.toRadians(cameraState.roll || 0),
}, },
duration, duration,
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
+10 -9
View File
@@ -30,6 +30,7 @@ import {
} from './cyberSonarControls.js'; } from './cyberSonarControls.js';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { aircraftTrackingTarget } from '../cockpitTracking.js'; import { aircraftTrackingTarget } from '../cockpitTracking.js';
@@ -1317,12 +1318,12 @@ export class StyleManager extends ShellFacade {
const carto = this.viewer.camera.positionCartographic; const carto = this.viewer.camera.positionCartographic;
if (!carto) return null; if (!carto) return null;
return { return {
lat: Cesium.Math.toDegrees(carto.latitude), lat: geo.math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude), lon: geo.math.toDegrees(carto.longitude),
alt: carto.height, alt: carto.height,
heading: Cesium.Math.toDegrees(this.viewer.camera.heading), heading: geo.math.toDegrees(this.viewer.camera.heading),
pitch: Cesium.Math.toDegrees(this.viewer.camera.pitch), pitch: geo.math.toDegrees(this.viewer.camera.pitch),
roll: Cesium.Math.toDegrees(this.viewer.camera.roll), roll: geo.math.toDegrees(this.viewer.camera.roll),
}; };
} }
@@ -1335,15 +1336,15 @@ export class StyleManager extends ShellFacade {
applyCameraState(cameraState, duration = 2.8) { applyCameraState(cameraState, duration = 2.8) {
if (!cameraState) return; if (!cameraState) return;
this.viewer.camera.flyTo({ this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees( destination: geo.cartesian3.fromDegrees(
cameraState.lon, cameraState.lon,
cameraState.lat, cameraState.lat,
cameraState.alt, cameraState.alt,
), ),
orientation: { orientation: {
heading: Cesium.Math.toRadians(cameraState.heading || 0), heading: geo.math.toRadians(cameraState.heading || 0),
pitch: Cesium.Math.toRadians(cameraState.pitch || -35), pitch: geo.math.toRadians(cameraState.pitch || -35),
roll: Cesium.Math.toRadians(cameraState.roll || 0), roll: geo.math.toRadians(cameraState.roll || 0),
}, },
duration: Math.max(0.2, duration || 0), duration: Math.max(0.2, duration || 0),
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
+22 -25
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { isPickedWorldPosition } from '../data/scenePick.js'; import { isPickedWorldPosition } from '../data/scenePick.js';
function trackedTarget(viewer) { function trackedTarget(viewer) {
@@ -11,17 +12,13 @@ function trackedTarget(viewer) {
); );
} }
export const OBLIQUE_PITCH = Cesium.Math.toRadians(-35); export const OBLIQUE_PITCH = geo.math.toRadians(-35);
export const STRAIGHT_DOWN_PITCH = Cesium.Math.toRadians(-89); export const STRAIGHT_DOWN_PITCH = geo.math.toRadians(-89);
const OBLIQUE_THRESHOLD = Cesium.Math.toRadians(-60); const OBLIQUE_THRESHOLD = geo.math.toRadians(-60);
function normalizedHeading(heading) { function normalizedHeading(heading) {
const wrapped = Cesium.Math.zeroToTwoPi( const wrapped = geo.math.zeroToTwoPi(Number.isFinite(heading) ? heading : 0);
Number.isFinite(heading) ? heading : 0, return Math.abs(wrapped - geo.math.TWO_PI) < geo.math.EPSILON10 ? 0 : wrapped;
);
return Math.abs(wrapped - Cesium.Math.TWO_PI) < Cesium.Math.EPSILON10
? 0
: wrapped;
} }
/** Return the world position under the center of the canvas, when available. */ /** Return the world position under the center of the canvas, when available. */
@@ -79,20 +76,20 @@ export function readCameraTargetFrame(viewer) {
if (!camera || !target || !isPickedWorldPosition(camera.positionWC)) if (!camera || !target || !isPickedWorldPosition(camera.positionWC))
return null; return null;
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(target); const transform = geo.transforms.eastNorthUpToFixedFrame(target);
const inverse = Cesium.Matrix4.inverseTransformation( const inverse = geo.matrix4.inverseTransformation(
transform, transform,
new Cesium.Matrix4(), geo.matrix4.create(),
); );
const localOffset = Cesium.Matrix4.multiplyByPoint( const localOffset = geo.matrix4.multiplyByPoint(
inverse, inverse,
camera.positionWC, camera.positionWC,
new Cesium.Cartesian3(), geo.cartesian3.create(),
); );
const range = Cesium.Cartesian3.magnitude(localOffset); const range = geo.cartesian3.magnitude(localOffset);
if (!Number.isFinite(range) || range < 1) return null; if (!Number.isFinite(range) || range < 1) return null;
const pitch = -Math.asin(Cesium.Math.clamp(localOffset.z / range, -1, 1)); const pitch = -Math.asin(geo.math.clamp(localOffset.z / range, -1, 1));
// Cesium places a lookAt camera at range * (-sin h cos p, -cos h cos p, // Cesium places a lookAt camera at range * (-sin h cos p, -cos h cos p,
// -sin p) in the target's east-north-up frame, so recovering h means // -sin p) in the target's east-north-up frame, so recovering h means
// negating BOTH horizontal components. Negating only the north one reads // negating BOTH horizontal components. Negating only the north one reads
@@ -100,7 +97,7 @@ export function readCameraTargetFrame(viewer) {
// make north-up rotate the wrong way from three quarters of the compass. // make north-up rotate the wrong way from three quarters of the compass.
const targetHeading = Math.atan2(-localOffset.x, -localOffset.y); const targetHeading = Math.atan2(-localOffset.x, -localOffset.y);
const heading = normalizedHeading( const heading = normalizedHeading(
pitch < Cesium.Math.toRadians(-88.5) ? camera.heading : targetHeading, pitch < geo.math.toRadians(-88.5) ? camera.heading : targetHeading,
); );
return { target, range, heading, pitch }; return { target, range, heading, pitch };
} }
@@ -111,7 +108,7 @@ export function setCameraTargetFrame(viewer, frame) {
if (!camera || !frame?.target || !Number.isFinite(frame.range)) return false; if (!camera || !frame?.target || !Number.isFinite(frame.range)) return false;
try { try {
const trackedTransform = viewer.trackedEntity const trackedTransform = viewer.trackedEntity
? Cesium.Matrix4.clone(camera.transform) ? geo.matrix4.clone(camera.transform)
: null; : null;
camera.lookAt( camera.lookAt(
frame.target, frame.target,
@@ -128,9 +125,9 @@ export function setCameraTargetFrame(viewer, frame) {
viewer.scene?.requestRender?.(); viewer.scene?.requestRender?.();
return true; return true;
} }
const destination = Cesium.Cartesian3.clone(camera.positionWC); const destination = geo.cartesian3.clone(camera.positionWC);
const direction = Cesium.Cartesian3.clone(camera.directionWC); const direction = geo.cartesian3.clone(camera.directionWC);
const up = Cesium.Cartesian3.clone(camera.upWC); const up = geo.cartesian3.clone(camera.upWC);
camera.lookAtTransform(Cesium.Matrix4.IDENTITY); camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
if (destination && direction && up) { if (destination && direction && up) {
camera.setView({ destination, orientation: { direction, up } }); camera.setView({ destination, orientation: { direction, up } });
@@ -179,7 +176,7 @@ export function resetCameraNorth(viewer) {
} }
function headingDegrees(camera) { function headingDegrees(camera) {
return Cesium.Math.toDegrees(normalizedHeading(camera?.heading)); return geo.math.toDegrees(normalizedHeading(camera?.heading));
} }
/** Ease an orbit without handing off the entity that owns the follow camera. */ /** Ease an orbit without handing off the entity that owns the follow camera. */
@@ -201,7 +198,7 @@ export function createCameraOrientationAnimator(
const entity = viewer.trackedEntity; const entity = viewer.trackedEntity;
pending = destination; pending = destination;
const start = now(); const start = now();
const headingDelta = Cesium.Math.negativePiToPi( const headingDelta = geo.math.negativePiToPi(
destination.heading - frame.heading, destination.heading - frame.heading,
); );
remove = viewer.scene.preUpdate.addEventListener(() => { remove = viewer.scene.preUpdate.addEventListener(() => {
@@ -209,7 +206,7 @@ export function createCameraOrientationAnimator(
cancel(); cancel();
return; return;
} }
const progress = Cesium.Math.clamp((now() - start) / duration, 0, 1); const progress = geo.math.clamp((now() - start) / duration, 0, 1);
const eased = Cesium.EasingFunction.CUBIC_IN_OUT(progress); const eased = Cesium.EasingFunction.CUBIC_IN_OUT(progress);
const target = entity ? trackedTarget(viewer) : frame.target; const target = entity ? trackedTarget(viewer) : frame.target;
if ( if (
@@ -218,7 +215,7 @@ export function createCameraOrientationAnimator(
...frame, ...frame,
target, target,
heading: frame.heading + headingDelta * eased, heading: frame.heading + headingDelta * eased,
pitch: Cesium.Math.lerp(frame.pitch, destination.pitch, eased), pitch: geo.math.lerp(frame.pitch, destination.pitch, eased),
}) })
) { ) {
cancel(); cancel();
+41 -45
View File
@@ -1,5 +1,5 @@
/** Camera placement, ground acquisition and motion correction for the Cockpit controller. */ /** Camera placement, ground acquisition and motion correction for the Cockpit controller. */
import * as Cesium from 'cesium'; import * as geo from '../geo/index.js';
import { import {
cockpitAnchorCorrectionStep, cockpitAnchorCorrectionStep,
cockpitGroundSafeHeight, cockpitGroundSafeHeight,
@@ -77,7 +77,7 @@ export function update() {
} }
if (!this.cockpitAnchorValid) { if (!this.cockpitAnchorValid) {
Cesium.Cartesian3.clone(target, this.cockpitAnchor); geo.cartesian3.clone(target, this.cockpitAnchor);
this.cockpitAnchorValid = true; this.cockpitAnchorValid = true;
} }
@@ -88,8 +88,8 @@ export function update() {
// display position at a bounded rate. This preserves the layer's required // display position at a bounded rate. This preserves the layer's required
// 15/30-second interpolation and per-frame cache without exposing its // 15/30-second interpolation and per-frame cache without exposing its
// sample-boundary corrections to the camera. // sample-boundary corrections to the camera.
const headingRad = Cesium.Math.toRadians(this.heading ?? 0); const headingRad = geo.math.toRadians(this.heading ?? 0);
const pitchRad = Cesium.Math.toRadians(COCKPIT_VIEW_PITCH_DEG); const pitchRad = geo.math.toRadians(COCKPIT_VIEW_PITCH_DEG);
const speedMps = Number.isFinite(info.velocityMps) const speedMps = Number.isFinite(info.velocityMps)
? Math.max(0, info.velocityMps) ? Math.max(0, info.velocityMps)
: 0; : 0;
@@ -98,38 +98,34 @@ export function update() {
// A feed backoff has no authoritative velocity epoch to advance from. // A feed backoff has no authoritative velocity epoch to advance from.
// Hold the cockpit on the exact layer-rendered position so the camera // Hold the cockpit on the exact layer-rendered position so the camera
// cannot coast away while the icon correctly remains fixed. // cannot coast away while the icon correctly remains fixed.
Cesium.Cartesian3.clone(target, this.cockpitAnchor); geo.cartesian3.clone(target, this.cockpitAnchor);
} else { } else {
Cesium.Transforms.eastNorthUpToFixedFrame( geo.transforms.eastNorthUpToFixedFrame(
this.cockpitAnchor, this.cockpitAnchor,
Cesium.Ellipsoid.WGS84, geo.ellipsoid.WGS84,
this.scratchEnu, this.scratchEnu,
); );
this.scratchLocal.x = Math.sin(headingRad); this.scratchLocal.x = Math.sin(headingRad);
this.scratchLocal.y = Math.cos(headingRad); this.scratchLocal.y = Math.cos(headingRad);
this.scratchLocal.z = 0; this.scratchLocal.z = 0;
Cesium.Matrix4.multiplyByPointAsVector( geo.matrix4.multiplyByPointAsVector(
this.scratchEnu, this.scratchEnu,
this.scratchLocal, this.scratchLocal,
this.scratchHorizontal, this.scratchHorizontal,
); );
Cesium.Cartesian3.normalize(this.scratchHorizontal, this.scratchHorizontal); geo.cartesian3.normalize(this.scratchHorizontal, this.scratchHorizontal);
Cesium.Cartesian3.multiplyByScalar( geo.cartesian3.multiplyByScalar(
this.scratchHorizontal, this.scratchHorizontal,
speedMps * dtSec, speedMps * dtSec,
this.scratchAdvance, this.scratchAdvance,
); );
Cesium.Cartesian3.add( geo.cartesian3.add(
this.cockpitAnchor, this.cockpitAnchor,
this.scratchAdvance, this.scratchAdvance,
this.cockpitAnchor, this.cockpitAnchor,
); );
Cesium.Cartesian3.subtract( geo.cartesian3.subtract(target, this.cockpitAnchor, this.scratchCorrection);
target, const correctionDistanceM = geo.cartesian3.magnitude(
this.cockpitAnchor,
this.scratchCorrection,
);
const correctionDistanceM = Cesium.Cartesian3.magnitude(
this.scratchCorrection, this.scratchCorrection,
); );
const correctionStepM = cockpitAnchorCorrectionStep( const correctionStepM = cockpitAnchorCorrectionStep(
@@ -138,12 +134,12 @@ export function update() {
dtSec, dtSec,
); );
if (correctionStepM > 0 && correctionDistanceM > 0) { if (correctionStepM > 0 && correctionDistanceM > 0) {
Cesium.Cartesian3.multiplyByScalar( geo.cartesian3.multiplyByScalar(
this.scratchCorrection, this.scratchCorrection,
correctionStepM / correctionDistanceM, correctionStepM / correctionDistanceM,
this.scratchCorrection, this.scratchCorrection,
); );
Cesium.Cartesian3.add( geo.cartesian3.add(
this.cockpitAnchor, this.cockpitAnchor,
this.scratchCorrection, this.scratchCorrection,
this.cockpitAnchor, this.cockpitAnchor,
@@ -156,14 +152,14 @@ export function update() {
// between fixes. Clamp it against the same mesh-first shared floor used by // between fixes. Clamp it against the same mesh-first shared floor used by
// aircraft rendering. For a slow contact whose floor cell is still cold, // aircraft rendering. For a slow contact whose floor cell is still cold,
// its already-clamped render position is a conservative temporary floor. // its already-clamped render position is a conservative temporary floor.
const anchorCartographic = Cesium.Cartographic.fromCartesian( const anchorCartographic = geo.cartographic.fromCartesian(
this.cockpitAnchor, this.cockpitAnchor,
Cesium.Ellipsoid.WGS84, geo.ellipsoid.WGS84,
this.scratchAnchorCartographic, this.scratchAnchorCartographic,
); );
const targetCartographic = Cesium.Cartographic.fromCartesian( const targetCartographic = geo.cartographic.fromCartesian(
target, target,
Cesium.Ellipsoid.WGS84, geo.ellipsoid.WGS84,
this.scratchTargetCartographic, this.scratchTargetCartographic,
); );
let cockpitFloorM = this.services.cachedGroundFloor( let cockpitFloorM = this.services.cachedGroundFloor(
@@ -189,8 +185,8 @@ export function update() {
const viewerCartographic = this.viewer.camera.positionCartographic; const viewerCartographic = this.viewer.camera.positionCartographic;
this.services.sampleMeshFloorCells(this.viewer.scene, groundPoint, { this.services.sampleMeshFloorCells(this.viewer.scene, groundPoint, {
excludeObjects: [this.trackedEntity], excludeObjects: [this.trackedEntity],
viewerLat: Cesium.Math.toDegrees(viewerCartographic.latitude), viewerLat: geo.math.toDegrees(viewerCartographic.latitude),
viewerLon: Cesium.Math.toDegrees(viewerCartographic.longitude), viewerLon: geo.math.toDegrees(viewerCartographic.longitude),
}); });
} }
cockpitFloorM = this.services.cachedMeshFloor( cockpitFloorM = this.services.cachedMeshFloor(
@@ -252,7 +248,7 @@ export function update() {
); );
if (minimumAnchorHeightM !== anchorCartographic.height) { if (minimumAnchorHeightM !== anchorCartographic.height) {
anchorCartographic.height = minimumAnchorHeightM; anchorCartographic.height = minimumAnchorHeightM;
Cesium.Ellipsoid.WGS84.cartographicToCartesian( geo.ellipsoid.WGS84.cartographicToCartesian(
anchorCartographic, anchorCartographic,
this.cockpitAnchor, this.cockpitAnchor,
); );
@@ -260,59 +256,59 @@ export function update() {
} }
// Rebuild the local frame at the stabilized anchor after advancing it. // Rebuild the local frame at the stabilized anchor after advancing it.
Cesium.Transforms.eastNorthUpToFixedFrame( geo.transforms.eastNorthUpToFixedFrame(
this.cockpitAnchor, this.cockpitAnchor,
Cesium.Ellipsoid.WGS84, geo.ellipsoid.WGS84,
this.scratchEnu, this.scratchEnu,
); );
this.scratchLocal.x = Math.sin(headingRad); this.scratchLocal.x = Math.sin(headingRad);
this.scratchLocal.y = Math.cos(headingRad); this.scratchLocal.y = Math.cos(headingRad);
this.scratchLocal.z = 0; this.scratchLocal.z = 0;
Cesium.Matrix4.multiplyByPointAsVector( geo.matrix4.multiplyByPointAsVector(
this.scratchEnu, this.scratchEnu,
this.scratchLocal, this.scratchLocal,
this.scratchHorizontal, this.scratchHorizontal,
); );
Cesium.Cartesian3.normalize(this.scratchHorizontal, this.scratchHorizontal); geo.cartesian3.normalize(this.scratchHorizontal, this.scratchHorizontal);
this.scratchLocal.x = Math.sin(headingRad) * Math.cos(pitchRad); this.scratchLocal.x = Math.sin(headingRad) * Math.cos(pitchRad);
this.scratchLocal.y = Math.cos(headingRad) * Math.cos(pitchRad); this.scratchLocal.y = Math.cos(headingRad) * Math.cos(pitchRad);
this.scratchLocal.z = Math.sin(pitchRad); this.scratchLocal.z = Math.sin(pitchRad);
Cesium.Matrix4.multiplyByPointAsVector( geo.matrix4.multiplyByPointAsVector(
this.scratchEnu, this.scratchEnu,
this.scratchLocal, this.scratchLocal,
this.scratchForward, this.scratchForward,
); );
Cesium.Cartesian3.normalize(this.scratchForward, this.scratchForward); geo.cartesian3.normalize(this.scratchForward, this.scratchForward);
this.scratchLocal.x = -Math.sin(headingRad) * Math.sin(pitchRad); this.scratchLocal.x = -Math.sin(headingRad) * Math.sin(pitchRad);
this.scratchLocal.y = -Math.cos(headingRad) * Math.sin(pitchRad); this.scratchLocal.y = -Math.cos(headingRad) * Math.sin(pitchRad);
this.scratchLocal.z = Math.cos(pitchRad); this.scratchLocal.z = Math.cos(pitchRad);
Cesium.Matrix4.multiplyByPointAsVector( geo.matrix4.multiplyByPointAsVector(
this.scratchEnu, this.scratchEnu,
this.scratchLocal, this.scratchLocal,
this.scratchUp, this.scratchUp,
); );
Cesium.Cartesian3.normalize(this.scratchUp, this.scratchUp); geo.cartesian3.normalize(this.scratchUp, this.scratchUp);
Cesium.Cartesian3.multiplyByScalar( geo.cartesian3.multiplyByScalar(
this.scratchHorizontal, this.scratchHorizontal,
COCKPIT_FORWARD_OFFSET_M, COCKPIT_FORWARD_OFFSET_M,
this.scratchCamera, this.scratchCamera,
); );
Cesium.Cartesian3.add( geo.cartesian3.add(
this.cockpitAnchor, this.cockpitAnchor,
this.scratchCamera, this.scratchCamera,
this.scratchCamera, this.scratchCamera,
); );
Cesium.Matrix4.getTranslation(this.scratchEnu, this.scratchTarget); geo.matrix4.getTranslation(this.scratchEnu, this.scratchTarget);
Cesium.Cartesian3.normalize(this.scratchTarget, this.scratchTarget); geo.cartesian3.normalize(this.scratchTarget, this.scratchTarget);
Cesium.Cartesian3.multiplyByScalar( geo.cartesian3.multiplyByScalar(
this.scratchTarget, this.scratchTarget,
COCKPIT_UP_OFFSET_M, COCKPIT_UP_OFFSET_M,
this.scratchTarget, this.scratchTarget,
); );
Cesium.Cartesian3.add( geo.cartesian3.add(
this.scratchCamera, this.scratchCamera,
this.scratchTarget, this.scratchTarget,
this.scratchCamera, this.scratchCamera,
@@ -320,14 +316,14 @@ export function update() {
// Recheck at the final forward-offset camera coordinate because a taxiing // Recheck at the final forward-offset camera coordinate because a taxiing
// aircraft can cross into an adjacent coarse floor cell between updates. // aircraft can cross into an adjacent coarse floor cell between updates.
const cameraCartographic = Cesium.Cartographic.fromCartesian( const cameraCartographic = geo.cartographic.fromCartesian(
this.scratchCamera, this.scratchCamera,
Cesium.Ellipsoid.WGS84, geo.ellipsoid.WGS84,
this.scratchCameraCartographic, this.scratchCameraCartographic,
); );
if (cameraCartographic) { if (cameraCartographic) {
const cameraLat = Cesium.Math.toDegrees(cameraCartographic.latitude); const cameraLat = geo.math.toDegrees(cameraCartographic.latitude);
const cameraLon = Cesium.Math.toDegrees(cameraCartographic.longitude); const cameraLon = geo.math.toDegrees(cameraCartographic.longitude);
const cameraFloorM = this.services.cachedGroundFloor(cameraLat, cameraLon); const cameraFloorM = this.services.cachedGroundFloor(cameraLat, cameraLon);
if (Number.isFinite(cameraFloorM)) { if (Number.isFinite(cameraFloorM)) {
cockpitFloorM = Math.max( cockpitFloorM = Math.max(
@@ -342,7 +338,7 @@ export function update() {
); );
if (safeHeightM !== cameraCartographic.height) { if (safeHeightM !== cameraCartographic.height) {
cameraCartographic.height = safeHeightM; cameraCartographic.height = safeHeightM;
Cesium.Ellipsoid.WGS84.cartographicToCartesian( geo.ellipsoid.WGS84.cartographicToCartesian(
cameraCartographic, cameraCartographic,
this.scratchCamera, this.scratchCamera,
); );
+4 -4
View File
@@ -1,5 +1,5 @@
/** Contact readouts and nearby-signal presentation for the Cockpit controller. */ /** Contact readouts and nearby-signal presentation for the Cockpit controller. */
import * as Cesium from 'cesium'; import * as geo from '../geo/index.js';
import { import {
bearingBetweenCoordinates, bearingBetweenCoordinates,
formatCockpitContextScope, formatCockpitContextScope,
@@ -117,13 +117,13 @@ export function updateContext(info, heading) {
Number.isFinite(info.latitude) && Number.isFinite(info.latitude) &&
Number.isFinite(info.longitude) Number.isFinite(info.longitude)
) { ) {
const cartographic = Cesium.Cartographic.fromCartesian(closest.position); const cartographic = geo.cartographic.fromCartesian(closest.position);
const bearing = cartographic const bearing = cartographic
? bearingBetweenCoordinates( ? bearingBetweenCoordinates(
info.latitude, info.latitude,
info.longitude, info.longitude,
Cesium.Math.toDegrees(cartographic.latitude), geo.math.toDegrees(cartographic.latitude),
Cesium.Math.toDegrees(cartographic.longitude), geo.math.toDegrees(cartographic.longitude),
) )
: null; : null;
relative = relativeBearing(bearing, heading); relative = relativeBearing(bearing, heading);
+14 -14
View File
@@ -44,7 +44,7 @@ import {
syncSignalLayout, syncSignalLayout,
} from './cockpitLayout.js'; } from './cockpitLayout.js';
import { onKeyDown } from './cockpitInput.js'; import { onKeyDown } from './cockpitInput.js';
import * as Cesium from 'cesium'; import * as geo from '../geo/index.js';
export class CockpitViewController { export class CockpitViewController {
constructor( constructor(
@@ -209,20 +209,20 @@ export class CockpitViewController {
this.regionalBriefSubjectId = null; this.regionalBriefSubjectId = null;
this.contextLayoutFrame = null; this.contextLayoutFrame = null;
this.contextLayoutStamp = null; this.contextLayoutStamp = null;
this.scratchTarget = new Cesium.Cartesian3(); this.scratchTarget = geo.cartesian3.create();
this.cockpitAnchor = new Cesium.Cartesian3(); this.cockpitAnchor = geo.cartesian3.create();
this.cockpitAnchorValid = false; this.cockpitAnchorValid = false;
this.scratchCamera = new Cesium.Cartesian3(); this.scratchCamera = geo.cartesian3.create();
this.scratchAdvance = new Cesium.Cartesian3(); this.scratchAdvance = geo.cartesian3.create();
this.scratchCorrection = new Cesium.Cartesian3(); this.scratchCorrection = geo.cartesian3.create();
this.scratchForward = new Cesium.Cartesian3(); this.scratchForward = geo.cartesian3.create();
this.scratchHorizontal = new Cesium.Cartesian3(); this.scratchHorizontal = geo.cartesian3.create();
this.scratchUp = new Cesium.Cartesian3(); this.scratchUp = geo.cartesian3.create();
this.scratchLocal = new Cesium.Cartesian3(); this.scratchLocal = geo.cartesian3.create();
this.scratchEnu = new Cesium.Matrix4(); this.scratchEnu = geo.matrix4.create();
this.scratchAnchorCartographic = new Cesium.Cartographic(); this.scratchAnchorCartographic = geo.cartographic.create();
this.scratchCameraCartographic = new Cesium.Cartographic(); this.scratchCameraCartographic = geo.cartographic.create();
this.scratchTargetCartographic = new Cesium.Cartographic(); this.scratchTargetCartographic = geo.cartographic.create();
this._listenerRemovers = []; this._listenerRemovers = [];
// Camera mutations belong before scene update/culling. Changing the camera // Camera mutations belong before scene update/culling. Changing the camera
+3 -2
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { createStateChannel } from '../app/stateChannel.js'; import { createStateChannel } from '../app/stateChannel.js';
import { LocationControls } from './location.js'; import { LocationControls } from './location.js';
@@ -373,8 +374,8 @@ export class LocationNavigation {
cancelled, cancelled,
heightKm: Math.round(GLOBE_VIEW.heightM / 1000), heightKm: Math.round(GLOBE_VIEW.heightM / 1000),
centeredOn: { centeredOn: {
latitude: Number(Cesium.Math.toDegrees(carto.latitude).toFixed(2)), latitude: Number(geo.math.toDegrees(carto.latitude).toFixed(2)),
longitude: Number(Cesium.Math.toDegrees(carto.longitude).toFixed(2)), longitude: Number(geo.math.toDegrees(carto.longitude).toFixed(2)),
}, },
}; };
this._resetGlobeBtn?.setAttribute( this._resetGlobeBtn?.setAttribute(
+45 -50
View File
@@ -9,6 +9,7 @@ import { resolveDeicticArgs } from './deixis.js';
export { readLayerLifecycleSummary } from './layerSummary.js'; export { readLayerLifecycleSummary } from './layerSummary.js';
import { defaultGeospatial } from '../search/defaults.js'; import { defaultGeospatial } from '../search/defaults.js';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { import {
CITY_POIS, CITY_POIS,
findPoiByName, findPoiByName,
@@ -2454,7 +2455,7 @@ async function frameOverhead(viewer, dataManager, styleManager, args = {}) {
.filter((entry) => entry.position) .filter((entry) => entry.position)
.map((entry) => ({ .map((entry) => ({
...entry, ...entry,
distance: Cesium.Cartesian3.distance(center, entry.position), distance: geo.cartesian3.distance(center, entry.position),
})) }))
.filter((entry) => entry.distance <= radiusKm * 1000) .filter((entry) => entry.distance <= radiusKm * 1000)
.sort((a, b) => a.distance - b.distance) .sort((a, b) => a.distance - b.distance)
@@ -2475,7 +2476,7 @@ async function frameOverhead(viewer, dataManager, styleManager, args = {}) {
entries.map((entry) => entry.position), entries.map((entry) => entry.position),
); );
sphere.radius = Math.max(sphere.radius * 1.25, 8000); sphere.radius = Math.max(sphere.radius * 1.25, 8000);
const pitch = Cesium.Math.toRadians(isSatellites ? -35 : -62); const pitch = geo.math.toRadians(isSatellites ? -35 : -62);
return runManagedVoiceNavigation( return runManagedVoiceNavigation(
styleManager, styleManager,
'frame', 'frame',
@@ -2583,8 +2584,8 @@ export async function getBasemapLabelContext(
}; };
} }
const latitude = Number(Cesium.Math.toDegrees(target.latitude).toFixed(6)); const latitude = Number(geo.math.toDegrees(target.latitude).toFixed(6));
const longitude = Number(Cesium.Math.toDegrees(target.longitude).toFixed(6)); const longitude = Number(geo.math.toDegrees(target.longitude).toFixed(6));
const cachedViewportPlaces = viewportPlacesFromCache( const cachedViewportPlaces = viewportPlacesFromCache(
samples, samples,
cameraHeightM, cameraHeightM,
@@ -2695,11 +2696,11 @@ function adjustCameraZoom(viewer, args) {
if (!fraction) throw new Error(`Unknown zoom amount: ${args.amount}`); if (!fraction) throw new Error(`Unknown zoom amount: ${args.amount}`);
const camera = viewer.camera; const camera = viewer.camera;
const beforePosition = Cesium.Cartesian3.clone(camera.positionWC); const beforePosition = geo.cartesian3.clone(camera.positionWC);
const beforeHeightM = camera.positionCartographic.height; const beforeHeightM = camera.positionCartographic.height;
const target = getViewTargetCartesian(viewer); const target = getViewTargetCartesian(viewer);
const targetDistanceM = target const targetDistanceM = target
? Cesium.Cartesian3.distance(beforePosition, target) ? geo.cartesian3.distance(beforePosition, target)
: Math.max(100, beforeHeightM); : Math.max(100, beforeHeightM);
const minimumDistanceM = direction === 'in' ? 20 : 50; const minimumDistanceM = direction === 'in' ? 20 : 50;
const movementM = Math.max(minimumDistanceM, targetDistanceM * fraction); const movementM = Math.max(minimumDistanceM, targetDistanceM * fraction);
@@ -2726,7 +2727,7 @@ function adjustCameraZoom(viewer, args) {
viewer.scene.requestRender(); viewer.scene.requestRender();
const afterPosition = camera.positionWC; const afterPosition = camera.positionWC;
const movedM = Cesium.Cartesian3.distance(beforePosition, afterPosition); const movedM = geo.cartesian3.distance(beforePosition, afterPosition);
const afterHeightM = camera.positionCartographic.height; const afterHeightM = camera.positionCartographic.height;
const moved = movedM >= 0.5; const moved = movedM >= 0.5;
return { return {
@@ -2771,8 +2772,8 @@ function nextIssPass(viewer, dataManager, args) {
if (latDeg == null || lonDeg == null) { if (latDeg == null || lonDeg == null) {
const carto = viewer?.camera?.positionCartographic; const carto = viewer?.camera?.positionCartographic;
if (!carto) throw new Error('Camera position unavailable'); if (!carto) throw new Error('Camera position unavailable');
latDeg = Cesium.Math.toDegrees(carto.latitude); latDeg = geo.math.toDegrees(carto.latitude);
lonDeg = Cesium.Math.toDegrees(carto.longitude); lonDeg = geo.math.toDegrees(carto.longitude);
} }
const minElevDeg = Number.isFinite(args.minElevationDeg) const minElevDeg = Number.isFinite(args.minElevationDeg)
? args.minElevationDeg ? args.minElevationDeg
@@ -3199,15 +3200,13 @@ function getCurrentViewState(
dataManager, dataManager,
sceneDirector = null, sceneDirector = null,
) { ) {
const cartographic = Cesium.Cartographic.fromCartesian( const cartographic = geo.cartographic.fromCartesian(viewer.camera.positionWC);
viewer.camera.positionWC,
);
return { return {
ok: true, ok: true,
action: 'get_current_view_state', action: 'get_current_view_state',
camera: { camera: {
latitude: Cesium.Math.toDegrees(cartographic.latitude), latitude: geo.math.toDegrees(cartographic.latitude),
longitude: Cesium.Math.toDegrees(cartographic.longitude), longitude: geo.math.toDegrees(cartographic.longitude),
heightM: cartographic.height, heightM: cartographic.height,
}, },
style: styleManager.activeStyle || 'normal', style: styleManager.activeStyle || 'normal',
@@ -3440,7 +3439,7 @@ async function getTargetContext(
} }
const hasPosition = Number.isFinite(lat) && Number.isFinite(lon); const hasPosition = Number.isFinite(lat) && Number.isFinite(lon);
const viewTarget = hasPosition const viewTarget = hasPosition
? Cesium.Cartographic.fromDegrees(lon, lat) ? geo.cartographic.fromDegrees(lon, lat)
: getViewTargetCartographic(viewer); : getViewTargetCartographic(viewer);
const selected = describeTargetEntity(dataManager, target); const selected = describeTargetEntity(dataManager, target);
const scene = await getSceneContext( const scene = await getSceneContext(
@@ -3565,10 +3564,10 @@ async function aircraftProximityWindowForQuery(
let lon = record?.lon; let lon = record?.lon;
if (!Number.isFinite(lat) || !Number.isFinite(lon)) { if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
const carto = item.position const carto = item.position
? Cesium.Cartographic.fromCartesian(item.position) ? geo.cartographic.fromCartesian(item.position)
: null; : null;
lat = carto ? Cesium.Math.toDegrees(carto.latitude) : null; lat = carto ? geo.math.toDegrees(carto.latitude) : null;
lon = carto ? Cesium.Math.toDegrees(carto.longitude) : null; lon = carto ? geo.math.toDegrees(carto.longitude) : null;
} }
const row = { const row = {
...(record || { ...(record || {
@@ -3727,10 +3726,10 @@ const SUBJECT_CENTER_TOLERANCE_KM = 1;
function centerMatchesSubject(center, subjectPosition) { function centerMatchesSubject(center, subjectPosition) {
if (!Number.isFinite(center?.lat) || !Number.isFinite(center?.lon)) if (!Number.isFinite(center?.lat) || !Number.isFinite(center?.lon))
return false; return false;
const carto = Cesium.Cartographic.fromCartesian(subjectPosition); const carto = geo.cartographic.fromCartesian(subjectPosition);
if (!carto) return false; if (!carto) return false;
const subjectLat = Cesium.Math.toDegrees(carto.latitude); const subjectLat = geo.math.toDegrees(carto.latitude);
const subjectLon = Cesium.Math.toDegrees(carto.longitude); const subjectLon = geo.math.toDegrees(carto.longitude);
return ( return (
haversineKm(subjectLat, subjectLon, center.lat, center.lon) <= haversineKm(subjectLat, subjectLon, center.lat, center.lon) <=
SUBJECT_CENTER_TOLERANCE_KM SUBJECT_CENTER_TOLERANCE_KM
@@ -3758,8 +3757,8 @@ function visibleEntityContexts(
const height = canvas.clientHeight || canvas.height || 0; const height = canvas.clientHeight || canvas.height || 0;
const centerX = width / 2; const centerX = width / 2;
const centerY = height / 2; const centerY = height / 2;
const targetLat = target ? Cesium.Math.toDegrees(target.latitude) : null; const targetLat = target ? geo.math.toDegrees(target.latitude) : null;
const targetLon = target ? Cesium.Math.toDegrees(target.longitude) : null; const targetLon = target ? geo.math.toDegrees(target.longitude) : null;
const store = getContextStore(); const store = getContextStore();
const enabledLayerIds = new Set( const enabledLayerIds = new Set(
dataManager dataManager
@@ -3850,9 +3849,7 @@ async function getSceneContext(
viewTarget = null, viewTarget = null,
service = defaultGeospatial, service = defaultGeospatial,
) { ) {
const cartographic = Cesium.Cartographic.fromCartesian( const cartographic = geo.cartographic.fromCartesian(viewer.camera.positionWC);
viewer.camera.positionWC,
);
const basemap = await getBasemapContext(viewer, viewTarget, service); const basemap = await getBasemapContext(viewer, viewTarget, service);
const enabledLayers = dataManager const enabledLayers = dataManager
.getAll() .getAll()
@@ -3865,10 +3862,8 @@ async function getSceneContext(
})); }));
return { return {
camera: { camera: {
latitude: Number(Cesium.Math.toDegrees(cartographic.latitude).toFixed(6)), latitude: Number(geo.math.toDegrees(cartographic.latitude).toFixed(6)),
longitude: Number( longitude: Number(geo.math.toDegrees(cartographic.longitude).toFixed(6)),
Cesium.Math.toDegrees(cartographic.longitude).toFixed(6),
),
heightM: Math.round(cartographic.height), heightM: Math.round(cartographic.height),
}, },
basemap, basemap,
@@ -3885,7 +3880,7 @@ async function getBasemapContext(
const { reverseGeocodeCache, nearbyPlacesCache } = cachesFor(service); const { reverseGeocodeCache, nearbyPlacesCache } = cachesFor(service);
const target = viewTarget; const target = viewTarget;
const samples = sampleViewportCartographics(viewer); const samples = sampleViewportCartographics(viewer);
const cameraCartographic = Cesium.Cartographic.fromCartesian( const cameraCartographic = geo.cartographic.fromCartesian(
viewer.camera.positionWC, viewer.camera.positionWC,
); );
const cameraHeightM = cameraCartographic.height; const cameraHeightM = cameraCartographic.height;
@@ -3915,8 +3910,8 @@ async function getBasemapContext(
}; };
} }
const latitude = Number(Cesium.Math.toDegrees(target.latitude).toFixed(6)); const latitude = Number(geo.math.toDegrees(target.latitude).toFixed(6));
const longitude = Number(Cesium.Math.toDegrees(target.longitude).toFixed(6)); const longitude = Number(geo.math.toDegrees(target.longitude).toFixed(6));
const inferredCountry = inferCountryFromSamples(samples); const inferredCountry = inferCountryFromSamples(samples);
const knownLandmarks = nearbyKnownLandmarks( const knownLandmarks = nearbyKnownLandmarks(
latitude, latitude,
@@ -4039,7 +4034,7 @@ function nearbyKnownLandmarks(latitude, longitude, cameraHeightM) {
} }
function haversineKm(lat1, lon1, lat2, lon2) { function haversineKm(lat1, lon1, lat2, lon2) {
const toRad = (value) => Cesium.Math.toRadians(value); const toRad = (value) => geo.math.toRadians(value);
const radiusKm = 6371.0088; const radiusKm = 6371.0088;
const dLat = toRad(lat2 - lat1); const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1); const dLon = toRad(lon2 - lon1);
@@ -4095,7 +4090,7 @@ function getViewTargetCartographic(viewer) {
// center to project; normalize that to the same "no target" null the callers // center to project; normalize that to the same "no target" null the callers
// already handle for a missed pick. // already handle for a missed pick.
const target = position const target = position
? Cesium.Cartographic.fromCartesian(position) || null ? geo.cartographic.fromCartesian(position) || null
: null; : null;
viewTargetCache.set(viewer, { viewTargetCache.set(viewer, {
signature, signature,
@@ -4109,8 +4104,8 @@ function cameraViewSignature(viewer) {
const camera = viewer.camera; const camera = viewer.camera;
const cartographic = camera.positionCartographic; const cartographic = camera.positionCartographic;
return [ return [
Cesium.Math.toDegrees(cartographic.latitude).toFixed(5), geo.math.toDegrees(cartographic.latitude).toFixed(5),
Cesium.Math.toDegrees(cartographic.longitude).toFixed(5), geo.math.toDegrees(cartographic.longitude).toFixed(5),
Math.round(cartographic.height / 2), Math.round(cartographic.height / 2),
camera.heading.toFixed(3), camera.heading.toFixed(3),
camera.pitch.toFixed(3), camera.pitch.toFixed(3),
@@ -4189,15 +4184,15 @@ function sampleViewportCartographics(viewer) {
const samples = []; const samples = [];
for (const [x, y] of points) { for (const [x, y] of points) {
const cartesian = viewer.camera.pickEllipsoid( const cartesian = viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(width * x, height * y), geo.cartesian2.create(width * x, height * y),
Cesium.Ellipsoid.WGS84, Cesium.Ellipsoid.WGS84,
); );
if (!isPickedWorldPosition(cartesian)) continue; if (!isPickedWorldPosition(cartesian)) continue;
const carto = Cesium.Cartographic.fromCartesian(cartesian); const carto = geo.cartographic.fromCartesian(cartesian);
if (!carto) continue; if (!carto) continue;
samples.push({ samples.push({
latitude: Number(Cesium.Math.toDegrees(carto.latitude).toFixed(4)), latitude: Number(geo.math.toDegrees(carto.latitude).toFixed(4)),
longitude: Number(Cesium.Math.toDegrees(carto.longitude).toFixed(4)), longitude: Number(geo.math.toDegrees(carto.longitude).toFixed(4)),
}); });
} }
return samples; return samples;
@@ -4466,7 +4461,7 @@ async function resolveWithin(promise, timeoutMs, fallback) {
function approximateCoordinateDistanceSq(latA, lonA, latB, lonB) { function approximateCoordinateDistanceSq(latA, lonA, latB, lonB) {
const latDelta = latB - latA; const latDelta = latB - latA;
const lonDelta = const lonDelta =
(lonB - lonA) * Math.cos(Cesium.Math.toRadians((latA + latB) / 2)); (lonB - lonA) * Math.cos(geo.math.toRadians((latA + latB) / 2));
return latDelta * latDelta + lonDelta * lonDelta; return latDelta * latDelta + lonDelta * lonDelta;
} }
@@ -4515,17 +4510,17 @@ function summarizeEntity(viewer, entity, { includeProperties = false } = {}) {
entity.__localBaseCartesian || entity.__localBaseCartesian ||
entity.position?.getValue?.(now) || entity.position?.getValue?.(now) ||
polygonCenter(entity, now); polygonCenter(entity, now);
const carto = position ? Cesium.Cartographic.fromCartesian(position) : null; const carto = position ? geo.cartographic.fromCartesian(position) : null;
return { return {
id: String(entity.id || ''), id: String(entity.id || ''),
name: label || layerTitle(layerId), name: label || layerTitle(layerId),
layerId, layerId,
layerName: layerTitle(layerId), layerName: layerTitle(layerId),
latitude: carto latitude: carto
? Number(Cesium.Math.toDegrees(carto.latitude).toFixed(6)) ? Number(geo.math.toDegrees(carto.latitude).toFixed(6))
: null, : null,
longitude: carto longitude: carto
? Number(Cesium.Math.toDegrees(carto.longitude).toFixed(6)) ? Number(geo.math.toDegrees(carto.longitude).toFixed(6))
: null, : null,
properties: includeProperties ? compactProperties(props) : undefined, properties: includeProperties ? compactProperties(props) : undefined,
}; };
@@ -4747,11 +4742,11 @@ function analystProviders(
?.module?.getContextSnapshot?.(); ?.module?.getContextSnapshot?.();
const subject = snapshot?.subject; const subject = snapshot?.subject;
if (!subject?.position) return null; if (!subject?.position) return null;
const carto = Cesium.Cartographic.fromCartesian(subject.position); const carto = geo.cartographic.fromCartesian(subject.position);
if (!carto) return null; if (!carto) return null;
return { return {
lat: Cesium.Math.toDegrees(carto.latitude), lat: geo.math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude), lon: geo.math.toDegrees(carto.longitude),
label: subject.label || subject.id || null, label: subject.label || subject.id || null,
}; };
}, },
@@ -4762,8 +4757,8 @@ function analystProviders(
// country-level asks sweep wide. Clamped so "in view" is never absurd. // country-level asks sweep wide. Clamped so "in view" is never absurd.
const viewRadiusKm = Math.max(25, Math.min(2500, altKm * 1.6)); const viewRadiusKm = Math.max(25, Math.min(2500, altKm * 1.6));
return { return {
lat: Cesium.Math.toDegrees(carto.latitude), lat: geo.math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude), lon: geo.math.toDegrees(carto.longitude),
viewRadiusKm, viewRadiusKm,
}; };
}, },
+3 -8
View File
@@ -3,6 +3,7 @@ import { createStandalonePlaceSearch } from '../standalone/placeSearch.js';
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as geo from '../geo/index.js';
import { CCTV_FOCUS_RESULT } from '../data/cctv.js'; import { CCTV_FOCUS_RESULT } from '../data/cctv.js';
import { getContextStore, registerEntityContext } from '../data/contextStore.js'; import { getContextStore, registerEntityContext } from '../data/contextStore.js';
import { DataLayerManager } from '../data/manager.js'; import { DataLayerManager } from '../data/manager.js';
@@ -2820,7 +2821,8 @@ test('a nested Cockpit rollback result is translated too', async () => {
* analyst's 2 000-record last-fix slice). These pin the unified engine. * analyst's 2 000-record last-fix slice). These pin the unified engine.
*/ */
function awarenessSubjectHarness({ subject, flights = [], military = [] }) { function awarenessSubjectHarness({ subject, flights = [], military = [] }) {
const position = subject ? { __subject: true } : null; // The Contacts subject sits at 29.9 N, 97.9 W, 9 000 m.
const position = subject ? geo.cartesian3.fromDegrees(-97.9, 29.9, 9000) : null;
const snapshotFlights = flights.slice(0, 20_000); const snapshotFlights = flights.slice(0, 20_000);
const snapshotMilitary = military.slice(0, 20_000); const snapshotMilitary = military.slice(0, 20_000);
return { return {
@@ -2887,7 +2889,6 @@ async function withAwareness(harness, run) {
aircraftSnapshot: awareness.getAircraftQuerySnapshot, aircraftSnapshot: awareness.getAircraftQuerySnapshot,
flightsNearby: flightsLayer.getNearby, flightsNearby: flightsLayer.getNearby,
militaryNearby: militaryLayer.getNearby, militaryNearby: militaryLayer.getNearby,
cartoFrom: Cesium.Cartographic.fromCartesian,
}; };
awareness.getContextSnapshot = () => harness.snapshot; awareness.getContextSnapshot = () => harness.snapshot;
awareness.getAircraftQuerySnapshot = () => { awareness.getAircraftQuerySnapshot = () => {
@@ -2920,11 +2921,6 @@ async function withAwareness(harness, run) {
}; };
flightsLayer.getNearby = () => harness.flights.slice(); flightsLayer.getNearby = () => harness.flights.slice();
militaryLayer.getNearby = () => harness.military.slice(); militaryLayer.getNearby = () => harness.military.slice();
Cesium.Cartographic.fromCartesian = (value) => (
value?.__subject
? { latitude: Cesium.Math.toRadians(29.9), longitude: Cesium.Math.toRadians(-97.9), height: 9000 }
: originals.cartoFrom(value)
);
try { try {
return await run(awareness); return await run(awareness);
} finally { } finally {
@@ -2932,7 +2928,6 @@ async function withAwareness(harness, run) {
awareness.getAircraftQuerySnapshot = originals.aircraftSnapshot; awareness.getAircraftQuerySnapshot = originals.aircraftSnapshot;
flightsLayer.getNearby = originals.flightsNearby; flightsLayer.getNearby = originals.flightsNearby;
militaryLayer.getNearby = originals.militaryNearby; militaryLayer.getNearby = originals.militaryNearby;
Cesium.Cartographic.fromCartesian = originals.cartoFrom;
} }
} }
+2 -2
View File
@@ -1,4 +1,4 @@
import * as Cesium from 'cesium'; import * as geo from '../geo/index.js';
import { resolvePickedContext } from '../data/pickedContext.js'; import { resolvePickedContext } from '../data/pickedContext.js';
import { cameraPoseKey } from './pointerContext.js'; import { cameraPoseKey } from './pointerContext.js';
import * as defaultAnnotationResolver from '../annotations/annotationResolver.js'; import * as defaultAnnotationResolver from '../annotations/annotationResolver.js';
@@ -34,7 +34,7 @@ export function createPointerPicker({
if (!width || !height) return null; if (!width || !height) return null;
let picked = null; let picked = null;
try { try {
picked = scene.pick(new Cesium.Cartesian2(x, y)); picked = scene.pick(geo.cartesian2.create(x, y));
} catch { } catch {
picked = null; picked = null;
} }