From e3061872cc234cdfb39ab0365f2808438ba408dd Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Thu, 15 Oct 2026 00:19:13 +0400 Subject: [PATCH] refactor(streetLevel,traffic,transit,firms,installations,radio,bikeshare,cables,detection): take vector maths from src/geo Street level view/cameraFollow/groundClick/index, traffic footprint/viewport/ingestion, transit viewport/trails/state/rendering, FIRMS state/viewport, installations controls/viewport/state, radio queries/navigation/state, bikeshare viewport, submarine cable overlay and data/detection now use src/geo for Math, Cartesian2/3, Cartographic and WGS84 maths. Cesium stays for Rectangle, Ray, BoundingSphere, HeadingPitchRange, EasingFunction, EllipsoidGeodesic, EllipsoidalOccluder and the Ellipsoids passed into Cesium (pickEllipsoid, occluders). traffic/state.js keeps its Cesium lerp scratch (written per frame by Cesium.Cartesian3.lerp in animation.js; a geo scratch cost +4-7% there) and traffic/timing.js stays on Cesium (a test keeps it source-equivalent to model.parseRoads, outside this batch). street-level-layer, traffic-layer, installations-layer, bikeshare-layer and submarine-cables own the src/geo files. --- scripts/package-boundaries.json | 80 ++++++++++++++++++++++++-- src/data/detection.js | 4 +- src/layers/bikeshare/viewport.js | 9 +-- src/layers/firms/state.js | 11 ++-- src/layers/firms/viewport.js | 11 ++-- src/layers/installations/controls.js | 23 ++++---- src/layers/installations/state.js | 3 +- src/layers/installations/viewport.js | 10 ++-- src/layers/radio/navigation.js | 21 +++---- src/layers/radio/queries.js | 17 +++--- src/layers/radio/state.js | 6 +- src/layers/streetLevel/cameraFollow.js | 15 ++--- src/layers/streetLevel/groundClick.js | 8 +-- src/layers/streetLevel/index.js | 8 +-- src/layers/streetLevel/view.js | 55 +++++++++--------- src/layers/submarineCables/overlay.js | 18 +++--- src/layers/traffic/footprint.js | 17 +++--- src/layers/traffic/ingestion.js | 4 +- src/layers/traffic/viewport.js | 21 +++---- src/layers/transit/rendering.js | 21 +++---- src/layers/transit/state.js | 4 +- src/layers/transit/trails.js | 10 ++-- src/layers/transit/viewport.js | 17 +++--- 23 files changed, 231 insertions(+), 162 deletions(-) diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index a61b3f82d..47753cd76 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -1194,7 +1194,21 @@ "src/data/bundledJson.js", "src/data/retryableLoad.js", "src/layers/installations/namedMarkers.js", - "src/data/infrastructureOverlayEntry.js" + "src/data/infrastructureOverlayEntry.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", "@mapbox/vector-tile", "pbf"] }, @@ -1276,7 +1290,21 @@ "src/sources/militaryTileGeometry.js", "src/sources/vectorTiles.js", "src/sources/phaseTiming.js", - "src/layers/traffic/roadModes.js" + "src/layers/traffic/roadModes.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", "pbf", "@mapbox/vector-tile"] }, @@ -1299,7 +1327,21 @@ "src/layers/bikeshare/testing.js", "src/layers/bikeshare/viewport.js", "src/data/inputOwnership.js", - "src/data/cameraSensitivity.js" + "src/data/cameraSensitivity.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"] }, @@ -1398,7 +1440,21 @@ "src/layers/streetLevel/providers/google/viewer.js", "src/layers/streetLevel/providers/google/mapsLoader.js", "src/layers/streetLevel/providers/mapillary/source.js", - "scripts/qa-street-level.mjs" + "scripts/qa-street-level.mjs", + "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", "pbf", "@mapbox/vector-tile", "mapillary-js"] }, @@ -1500,7 +1556,21 @@ "src/layers/submarineCables/rendering.js", "src/layers/submarineCables/state.js", "src/layers/submarineCables/surface.js", - "src/data/inputOwnership.js" + "src/data/inputOwnership.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"] }, diff --git a/src/data/detection.js b/src/data/detection.js index 5c5e90396..0ab9b56b5 100644 --- a/src/data/detection.js +++ b/src/data/detection.js @@ -1,5 +1,5 @@ import { paintTransitBracket } from './detectionDraw.js'; -import * as Cesium from 'cesium'; +import * as geo from '../geo/index.js'; import { governorRequestRender } from '../renderGovernor.js'; import { DETECTION_ENABLE_FADE_MS, @@ -1391,7 +1391,7 @@ function _drawOverlay(frame) { let halfH; if (obj.type === 'AIR') { const bscale = nearFarScale( - Cesium.Cartesian3.distance(camPos, obj.position), + geo.cartesian3.distance(camPos, obj.position), BILL_NEAR, BILL_NEAR_SCALE, BILL_FAR, diff --git a/src/layers/bikeshare/viewport.js b/src/layers/bikeshare/viewport.js index e4c03a690..857be5d0d 100644 --- a/src/layers/bikeshare/viewport.js +++ b/src/layers/bikeshare/viewport.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { GBFS_CITY_REGISTRY } from './registry.js'; import { ACTIVATION_EXIT_ALTITUDE_M, @@ -37,8 +38,8 @@ export function createViewport({ state: layerState, services, parts, source }) { if (rect) { const center = Cesium.Rectangle.center(rect); return { - lat: Cesium.Math.toDegrees(center.latitude), - lon: Cesium.Math.toDegrees(center.longitude), + lat: geo.math.toDegrees(center.latitude), + lon: geo.math.toDegrees(center.longitude), }; } @@ -46,8 +47,8 @@ export function createViewport({ state: layerState, services, parts, source }) { const carto = viewer?.camera?.positionCartographic; if (carto) { return { - lat: Cesium.Math.toDegrees(carto.latitude), - lon: Cesium.Math.toDegrees(carto.longitude), + lat: geo.math.toDegrees(carto.latitude), + lon: geo.math.toDegrees(carto.longitude), }; } diff --git a/src/layers/firms/state.js b/src/layers/firms/state.js index 48ae11db2..7a2e58b77 100644 --- a/src/layers/firms/state.js +++ b/src/layers/firms/state.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; export function createFirmsState({ services, config }) { const layerState = {}; @@ -9,11 +10,11 @@ export function createFirmsState({ services, config }) { layerState.scratchViewRect = new Cesium.Rectangle(); - layerState.scratchCenterA = new Cesium.Cartographic(); + layerState.scratchCenterA = geo.cartographic.create(); - layerState.scratchCenterB = new Cesium.Cartographic(); + layerState.scratchCenterB = geo.cartographic.create(); - layerState.scratchWindowCoord = new Cesium.Cartesian2(); + layerState.scratchWindowCoord = geo.cartesian2.create(); layerState._viewer = null; @@ -107,9 +108,9 @@ export function createFirmsState({ services, config }) { layerState._camSnapValid = false; - layerState._camPos = new Cesium.Cartesian3(); + layerState._camPos = geo.cartesian3.create(); - layerState._camDir = new Cesium.Cartesian3(); + layerState._camDir = geo.cartesian3.create(); layerState.request = null; return layerState; } diff --git a/src/layers/firms/viewport.js b/src/layers/firms/viewport.js index 932fe2e75..0270def94 100644 --- a/src/layers/firms/viewport.js +++ b/src/layers/firms/viewport.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { LOD_LEVELS, LOD_HYSTERESIS, @@ -122,7 +123,7 @@ export function createViewport({ const center = Cesium.Rectangle.center(viewRect, layerState.scratchCenterB); const latShift = Math.abs(center.latitude - lastCenter.latitude); let lonShift = Math.abs(center.longitude - lastCenter.longitude); - if (lonShift > Math.PI) lonShift = Cesium.Math.TWO_PI - lonShift; + if (lonShift > Math.PI) lonShift = geo.math.TWO_PI - lonShift; return ( latShift > lastHeight * VIEW_PADDING * 0.6 || lonShift > lastWidth * VIEW_PADDING * 0.6 @@ -159,13 +160,13 @@ export function createViewport({ const camera = layerState._viewer.camera; if ( layerState._camSnapValid && - Cesium.Cartesian3.equalsEpsilon( + geo.cartesian3.equalsEpsilon( camera.positionWC, layerState._camPos, 0, 0.5, ) && - Cesium.Cartesian3.equalsEpsilon( + geo.cartesian3.equalsEpsilon( camera.directionWC, layerState._camDir, 0, @@ -174,8 +175,8 @@ export function createViewport({ ) { return; } - Cesium.Cartesian3.clone(camera.positionWC, layerState._camPos); - Cesium.Cartesian3.clone(camera.directionWC, layerState._camDir); + geo.cartesian3.clone(camera.positionWC, layerState._camPos); + geo.cartesian3.clone(camera.directionWC, layerState._camDir); layerState._camSnapValid = true; // The camera moved, so the horizon moved. A rebuild culls its own fresh // sprites (renderDetections); an early-out — same LOD band and view diff --git a/src/layers/installations/controls.js b/src/layers/installations/controls.js index db891043c..09ce43bf9 100644 --- a/src/layers/installations/controls.js +++ b/src/layers/installations/controls.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { installationFeedback } from '../../data/installationFeedback.js'; import { LAYER_ID, @@ -51,17 +52,17 @@ export function createControls({ state: layerState, services, parts, source }) { ) next = { latitude: position.latitude, longitude: position.longitude }; else { - const carto = Cesium.Cartographic.fromCartesian(position); + const carto = geo.cartographic.fromCartesian(position); if (carto) next = { - latitude: Cesium.Math.toDegrees(carto.latitude), - longitude: Cesium.Math.toDegrees(carto.longitude), + latitude: geo.math.toDegrees(carto.latitude), + longitude: geo.math.toDegrees(carto.longitude), }; } } // Keep the live centre even when the coarse fetch anchor has not moved. layerState.contextPosition = next - ? Cesium.Cartesian3.fromDegrees(next.longitude, next.latitude) + ? geo.cartesian3.fromDegrees(next.longitude, next.latitude) : null; const previous = layerState.contextAnchor; if (!next) { @@ -73,8 +74,8 @@ export function createControls({ state: layerState, services, parts, source }) { if ( previous && parts.model.approximateSurfaceDistanceM( - Cesium.Math.toRadians(previous.latitude), - Cesium.Math.toRadians(previous.longitude), + geo.math.toRadians(previous.latitude), + geo.math.toRadians(previous.longitude), next.latitude, next.longitude, ) < ANCHOR_REFRESH_M @@ -88,7 +89,7 @@ export function createControls({ state: layerState, services, parts, source }) { getNearby(center, rangeM, maxCount = 50) { if (!center) return []; const range = Number.isFinite(rangeM) ? rangeM : Infinity; - const centerCartographic = Cesium.Cartographic.fromCartesian(center); + const centerCartographic = geo.cartographic.fromCartesian(center); if (!centerCartographic) return []; const nearby = []; const approximateLimit = Number.isFinite(range) @@ -107,10 +108,10 @@ export function createControls({ state: layerState, services, parts, source }) { continue; // The awareness disk is projected onto the ground. Confirm candidates // with an exact ellipsoidal surface distance and reusable scratch state. - layerState.distanceEndpointScratch.longitude = Cesium.Math.toRadians( + layerState.distanceEndpointScratch.longitude = geo.math.toRadians( record.longitude, ); - layerState.distanceEndpointScratch.latitude = Cesium.Math.toRadians( + layerState.distanceEndpointScratch.latitude = geo.math.toRadians( record.latitude, ); layerState.distanceEndpointScratch.height = 0; @@ -122,7 +123,7 @@ export function createControls({ state: layerState, services, parts, source }) { if (!Number.isFinite(distanceM) || distanceM > range) continue; nearby.push({ ...record, - position: Cesium.Cartesian3.fromDegrees( + position: geo.cartesian3.fromDegrees( record.longitude, record.latitude, parts.rendering.installationSurfaceHeightM(record), @@ -150,7 +151,7 @@ export function createControls({ state: layerState, services, parts, source }) { if (!parts.selection.selectRecord(record.id)) return false; layerState.viewer.camera.flyToBoundingSphere( new Cesium.BoundingSphere( - Cesium.Cartesian3.fromDegrees( + geo.cartesian3.fromDegrees( record.longitude, record.latitude, parts.rendering.installationSurfaceHeightM(record), diff --git a/src/layers/installations/state.js b/src/layers/installations/state.js index 99be04af0..24ffc6e8f 100644 --- a/src/layers/installations/state.js +++ b/src/layers/installations/state.js @@ -1,9 +1,10 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; export function createState({ services }) { const state = {}; - state.distanceEndpointScratch = new Cesium.Cartographic(); + state.distanceEndpointScratch = geo.cartographic.create(); state.distanceGeodesicScratch = new Cesium.EllipsoidGeodesic(); diff --git a/src/layers/installations/viewport.js b/src/layers/installations/viewport.js index 387f97b66..7667152a6 100644 --- a/src/layers/installations/viewport.js +++ b/src/layers/installations/viewport.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { REQUEST_DEBOUNCE_MS } from './policy.js'; import { installationAnchorBox } from './source.js'; @@ -27,10 +27,10 @@ export function createViewport({ state: layerState, services, parts, source }) { viewer.scene.globe.ellipsoid, ); if (!rectangle) return null; - const south = Cesium.Math.toDegrees(rectangle.south); - const north = Cesium.Math.toDegrees(rectangle.north); - const west = Cesium.Math.toDegrees(rectangle.west); - const east = Cesium.Math.toDegrees(rectangle.east); + const south = geo.math.toDegrees(rectangle.south); + const north = geo.math.toDegrees(rectangle.north); + const west = geo.math.toDegrees(rectangle.west); + const east = geo.math.toDegrees(rectangle.east); // Wide and dateline views use the bundled point index. if ( !Number.isFinite(south + north + west + east) || diff --git a/src/layers/radio/navigation.js b/src/layers/radio/navigation.js index 243a1941b..049be7082 100644 --- a/src/layers/radio/navigation.js +++ b/src/layers/radio/navigation.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { HORIZON_CAMERA_MOVE_EPSILON_M, GLOBAL_RADIO_ALTITUDE_M, @@ -178,15 +179,13 @@ export function createNavigation({ ); } else { const earthRadius = earthDiscScreenRadius( - Cesium.Cartesian3.magnitude(camera.positionWC), + geo.cartesian3.magnitude(camera.positionWC), height, camera.frustum?.fovy, ); const facingEarthCenter = - Cesium.Cartesian3.dot( - camera.directionWC, - layerState._radioEarthToCenter, - ) > 0; + geo.cartesian3.dot(camera.directionWC, layerState._radioEarthToCenter) > + 0; if (!earthRadius || facingEarthCenter) return null; const keyhole = getKeyholeGeometry(width, height); fullGlobeCapable = @@ -198,7 +197,7 @@ export function createNavigation({ ); if (recenterHeight == null) return null; return { - destination: Cesium.Cartesian3.fromRadians( + destination: geo.cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, recenterHeight, @@ -207,7 +206,7 @@ export function createNavigation({ ...radioCameraState(camera), height: recenterHeight, heading: 0, - pitch: -Cesium.Math.PI_OVER_TWO, + pitch: -geo.math.PI_OVER_TWO, roll: 0, }, }; @@ -280,7 +279,7 @@ export function createNavigation({ return startRadioCameraFlight( navigation, { - destination: Cesium.Cartesian3.fromDegrees( + destination: geo.cartesian3.fromDegrees( plan.lon, plan.lat, plan.height, @@ -364,11 +363,7 @@ export function createNavigation({ return false; cancelActiveRadioCameraFlight(); layerState._viewer.camera.flyTo({ - destination: Cesium.Cartesian3.fromDegrees( - station.lon, - station.lat, - 85_000, - ), + destination: geo.cartesian3.fromDegrees(station.lon, station.lat, 85_000), duration: 1.1, }); return true; diff --git a/src/layers/radio/queries.js b/src/layers/radio/queries.js index 5bc4cc573..e58809476 100644 --- a/src/layers/radio/queries.js +++ b/src/layers/radio/queries.js @@ -1,12 +1,13 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { normalizeRadioCountryInput } from '../../data/radioCountry.js'; export function createQueries({ state: layerState, services, parts, source }) { function radioAngularDistance(station, anchor) { - const lat1 = Cesium.Math.toRadians(Number(anchor?.lat)); - const lon1 = Cesium.Math.toRadians(Number(anchor?.lon)); - const lat2 = Cesium.Math.toRadians(Number(station?.lat)); - const lon2 = Cesium.Math.toRadians(Number(station?.lon)); + const lat1 = geo.math.toRadians(Number(anchor?.lat)); + const lon1 = geo.math.toRadians(Number(anchor?.lon)); + const lat2 = geo.math.toRadians(Number(station?.lat)); + const lon2 = geo.math.toRadians(Number(station?.lon)); if (![lat1, lon1, lat2, lon2].every(Number.isFinite)) return Number.POSITIVE_INFINITY; const deltaLat = lat2 - lat1; @@ -113,7 +114,7 @@ export function createQueries({ state: layerState, services, parts, source }) { let cartographic = null; const canvas = scene?.canvas; if (canvas && typeof camera.pickEllipsoid === 'function') { - const center = new Cesium.Cartesian2( + const center = geo.cartesian2.create( canvas.clientWidth / 2, canvas.clientHeight / 2, ); @@ -121,13 +122,13 @@ export function createQueries({ state: layerState, services, parts, source }) { center, scene.globe?.ellipsoid || Cesium.Ellipsoid.WGS84, ); - if (position) cartographic = Cesium.Cartographic.fromCartesian(position); + if (position) cartographic = geo.cartographic.fromCartesian(position); } cartographic ||= camera.positionCartographic || null; if (!cartographic) return null; return { - lat: Cesium.Math.toDegrees(cartographic.latitude), - lon: Cesium.Math.toDegrees(cartographic.longitude), + lat: geo.math.toDegrees(cartographic.latitude), + lon: geo.math.toDegrees(cartographic.longitude), altitudeM, globalView: parts.navigation.radioViewIsGlobal(altitudeM), }; diff --git a/src/layers/radio/state.js b/src/layers/radio/state.js index dca461e03..ac915635a 100644 --- a/src/layers/radio/state.js +++ b/src/layers/radio/state.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { EMPTY_ACCEPTED_CATALOG_SNAPSHOT, DEFAULT_RADIO_FILTER, @@ -8,9 +8,9 @@ import { export function createState({ services }) { const layerState = {}; - layerState._radioEarthScreenCenter = new Cesium.Cartesian2(); + layerState._radioEarthScreenCenter = geo.cartesian2.create(); - layerState._radioEarthToCenter = new Cesium.Cartesian3(); + layerState._radioEarthToCenter = geo.cartesian3.create(); layerState._viewer = null; diff --git a/src/layers/streetLevel/cameraFollow.js b/src/layers/streetLevel/cameraFollow.js index 09733e172..7e78f8814 100644 --- a/src/layers/streetLevel/cameraFollow.js +++ b/src/layers/streetLevel/cameraFollow.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { FOLLOW_EYE_HEIGHT_M } from './policy.js'; /** Plausible ellipsoidal land heights: Dead Sea to Everest, with geoid slack. */ @@ -79,7 +80,7 @@ export function createCameraFollow({ state, parts }) { */ function groundHeightAt(lon, lat, fallback) { const scene = state.viewer?.scene; - const carto = Cesium.Cartographic.fromDegrees(lon, lat); + const carto = geo.cartographic.fromDegrees(lon, lat); const caster = parts?.groundCaster; const dem = caster?.groundAt(lon, lat) ?? null; if (dem === null) caster?.prepare([[lon, lat]]); @@ -108,14 +109,14 @@ export function createCameraFollow({ state, parts }) { if (!follow || !position || !state.viewer) return; const ground = groundHeightAt(position.lon, position.lat, altitude); state.viewer.camera.setView({ - destination: Cesium.Cartesian3.fromDegrees( + destination: geo.cartesian3.fromDegrees( position.lon, position.lat, ground + FOLLOW_EYE_HEIGHT_M, ), orientation: { - heading: Cesium.Math.toRadians(bearing || 0), - pitch: Cesium.Math.toRadians(Number.isFinite(tilt) ? tilt : 0), + heading: geo.math.toRadians(bearing || 0), + pitch: geo.math.toRadians(Number.isFinite(tilt) ? tilt : 0), roll: 0, }, }); @@ -148,13 +149,13 @@ export function createCameraFollow({ state, parts }) { }; state.viewer.camera.flyToBoundingSphere( new Cesium.BoundingSphere( - Cesium.Cartesian3.fromDegrees(position.lon, position.lat, ground + 2), + geo.cartesian3.fromDegrees(position.lon, position.lat, ground + 2), 4, ), { offset: new Cesium.HeadingPitchRange( - Cesium.Math.toRadians(bearing || 0), - Cesium.Math.toRadians(-32), + geo.math.toRadians(bearing || 0), + geo.math.toRadians(-32), 140, ), duration: 1.6, diff --git a/src/layers/streetLevel/groundClick.js b/src/layers/streetLevel/groundClick.js index fef6ddb47..7954e273f 100644 --- a/src/layers/streetLevel/groundClick.js +++ b/src/layers/streetLevel/groundClick.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { GROUND_CLICK_MAX_HEIGHT_M } from './policy.js'; import { cameraHeightAboveGround } from './view.js'; @@ -43,12 +43,12 @@ export function createGroundClick({ .map((entry) => entry.def.id); if (!providerIds.length) return false; const position = scenePick.pickGroundPosition(state.viewer, screenPosition); - const carto = position && Cesium.Cartographic.fromCartesian(position); + const carto = position && geo.cartographic.fromCartesian(position); if (!carto) return false; return openNearest( { - lon: Cesium.Math.toDegrees(carto.longitude), - lat: Cesium.Math.toDegrees(carto.latitude), + lon: geo.math.toDegrees(carto.longitude), + lat: geo.math.toDegrees(carto.latitude), }, { providerIds, frame: false }, ); diff --git a/src/layers/streetLevel/index.js b/src/layers/streetLevel/index.js index eed4230ab..75482fb6d 100644 --- a/src/layers/streetLevel/index.js +++ b/src/layers/streetLevel/index.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { createState } from './state.js'; import { createMarker } from './marker.js'; import { createCameraFollow } from './cameraFollow.js'; @@ -304,11 +304,11 @@ export function createStreetLevelLayer({ x: canvas.clientWidth / 2, y: canvas.clientHeight / 2, }); - const carto = position && Cesium.Cartographic.fromCartesian(position); + const carto = position && geo.cartographic.fromCartesian(position); return carto ? { - lon: Cesium.Math.toDegrees(carto.longitude), - lat: Cesium.Math.toDegrees(carto.latitude), + lon: geo.math.toDegrees(carto.longitude), + lat: geo.math.toDegrees(carto.latitude), } : null; } diff --git a/src/layers/streetLevel/view.js b/src/layers/streetLevel/view.js index 09bc516ed..4a16c6e77 100644 --- a/src/layers/streetLevel/view.js +++ b/src/layers/streetLevel/view.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { wrapLon } from './tileMath.js'; /** Equirectangular metres between {lon, lat} points, the short way round ±180°; street scale. */ @@ -33,8 +34,8 @@ export function groundUnderCamera(viewer, { groundAt } = {}) { 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), + geo.math.toDegrees(carto.longitude), + geo.math.toDegrees(carto.latitude), ); return Number.isFinite(ground) ? ground : null; } @@ -68,10 +69,10 @@ function groundHit(camera, point, ellipsoid, maxRange) { if ( Number.isFinite(maxRange) && camera.positionWC && - Cesium.Cartesian3.distance(cartesian, camera.positionWC) > maxRange + geo.cartesian3.distance(cartesian, camera.positionWC) > maxRange ) return null; - return Cesium.Cartographic.fromCartesian(cartesian, ellipsoid) || null; + return geo.cartographic.fromCartesian(cartesian, ellipsoid) || null; } /** @@ -96,7 +97,7 @@ export function visibleBbox( scene.globe?.ellipsoid || Cesium.Ellipsoid.WGS84, groundHeight, ); - const point = new Cesium.Cartesian2(); + const point = geo.cartesian2.create(); for (let i = 0; i <= grid; i++) { for (let j = 0; j <= grid; j++) { point.x = (width * i) / grid; @@ -112,8 +113,8 @@ export function visibleBbox( if (Number.isFinite(nearRange) && nearRange > 0 && nadir) { const dLat = nearRange / 111_320; const dLon = dLat / Math.max(0.05, Math.cos(nadir.latitude)); - const lat = Cesium.Math.toDegrees(nadir.latitude); - const lon = Cesium.Math.toDegrees(nadir.longitude); + const lat = geo.math.toDegrees(nadir.latitude); + const lon = geo.math.toDegrees(nadir.longitude); for (const [dx, dy] of [ [-1, -1], [-1, 1], @@ -121,7 +122,7 @@ export function visibleBbox( [1, 1], ]) hits.push( - Cesium.Cartographic.fromDegrees( + geo.cartographic.fromDegrees( lon + dx * dLon, Math.max(-85, Math.min(85, lat + dy * dLat)), ), @@ -133,8 +134,8 @@ export function visibleBbox( 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); + const lon = geo.math.toDegrees(carto.longitude); + const lat = geo.math.toDegrees(carto.latitude); west = Math.min(west, lon); east = Math.max(east, lon); south = Math.min(south, lat); @@ -147,7 +148,7 @@ export function visibleBbox( let west360 = Infinity; let east360 = -Infinity; for (const carto of hits) { - const lon = Cesium.Math.toDegrees(carto.longitude); + const lon = geo.math.toDegrees(carto.longitude); const shifted = lon < 0 ? lon + 360 : lon; west360 = Math.min(west360, shifted); east360 = Math.max(east360, shifted); @@ -162,10 +163,10 @@ export function visibleBbox( 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), + geo.math.toDegrees(rectangle.west), + geo.math.toDegrees(rectangle.south), + geo.math.toDegrees(rectangle.east), + geo.math.toDegrees(rectangle.north), ]; } @@ -183,8 +184,8 @@ export function viewCentre(viewer) { const carto = viewer.camera?.positionCartographic; if (!carto) return null; return { - lat: Cesium.Math.toDegrees(carto.latitude), - lon: Cesium.Math.toDegrees(carto.longitude), + lat: geo.math.toDegrees(carto.latitude), + lon: geo.math.toDegrees(carto.longitude), }; } @@ -200,8 +201,8 @@ export function viewFocus( const carto = camera?.positionCartographic; if (!carto) return null; const nadir = { - lon: Cesium.Math.toDegrees(carto.longitude), - lat: Cesium.Math.toDegrees(carto.latitude), + lon: geo.math.toDegrees(carto.longitude), + lat: geo.math.toDegrees(carto.latitude), }; const canvas = viewer.scene?.canvas; const width = canvas?.clientWidth || canvas?.width || 0; @@ -210,7 +211,7 @@ export function viewFocus( if (width > 0 && height > 0) { const hit = groundHit( camera, - new Cesium.Cartesian2(width / 2, height / 2), + geo.cartesian2.create(width / 2, height / 2), raisedEllipsoid( viewer.scene?.globe?.ellipsoid || Cesium.Ellipsoid.WGS84, groundHeight, @@ -219,8 +220,8 @@ export function viewFocus( ); if (hit) ahead = { - lon: Cesium.Math.toDegrees(hit.longitude), - lat: Cesium.Math.toDegrees(hit.latitude), + lon: geo.math.toDegrees(hit.longitude), + lat: geo.math.toDegrees(hit.latitude), }; } return { nadir, ahead }; @@ -232,9 +233,9 @@ export function viewFocus( */ export const HORIZON_CULL_DEPTH_M = 1000; const HORIZON_CULL_ELLIPSOID = new Cesium.Ellipsoid( - Cesium.Ellipsoid.WGS84.radii.x - HORIZON_CULL_DEPTH_M, - Cesium.Ellipsoid.WGS84.radii.y - HORIZON_CULL_DEPTH_M, - Cesium.Ellipsoid.WGS84.radii.z - HORIZON_CULL_DEPTH_M, + geo.ellipsoid.WGS84.radii.x - HORIZON_CULL_DEPTH_M, + geo.ellipsoid.WGS84.radii.y - HORIZON_CULL_DEPTH_M, + geo.ellipsoid.WGS84.radii.z - HORIZON_CULL_DEPTH_M, ); /** @@ -267,13 +268,13 @@ export function createHorizonCull({ getViewer, items, onChange }) { /** Cull everything from the camera at `position`; the count seen. */ function cullAll(position) { - from = Cesium.Cartesian3.clone(position, from); + from = geo.cartesian3.clone(position, from); return cull(items(), position); } function onPreRender() { const position = getViewer()?.camera?.positionWC; - if (!position || (from && Cesium.Cartesian3.equals(from, position))) return; + if (!position || (from && geo.cartesian3.equals(from, position))) return; if (!cullAll(position)) stop(); } diff --git a/src/layers/submarineCables/overlay.js b/src/layers/submarineCables/overlay.js index a51d2769b..d42e7c934 100644 --- a/src/layers/submarineCables/overlay.js +++ b/src/layers/submarineCables/overlay.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { CABLE_REFERENCE_LABEL_WINNER_CAP, CABLE_REFERENCE_LABEL_MAX_DISTANCE_M, @@ -193,7 +193,7 @@ export function createCableReferenceSweepGate({ const motionEpsilonSq = motionEpsilonM ** 2; // Preallocated: the probe clones into it, so a moving camera allocates // nothing per frame. - const lastSweptPosition = new Cesium.Cartesian3(); + const lastSweptPosition = geo.cartesian3.create(); let hasSweptPosition = false; let dirty = true; let lastProbeAt = -Infinity; @@ -203,7 +203,7 @@ export function createCableReferenceSweepGate({ lastProbeAt = time; const position = camera?.positionWC; hasSweptPosition = Boolean(position); - if (position) Cesium.Cartesian3.clone(position, lastSweptPosition); + if (position) geo.cartesian3.clone(position, lastSweptPosition); return true; } @@ -232,7 +232,7 @@ export function createCableReferenceSweepGate({ if (!position) return false; if ( hasSweptPosition && - Cesium.Cartesian3.distanceSquared(position, lastSweptPosition) <= + geo.cartesian3.distanceSquared(position, lastSweptPosition) <= motionEpsilonSq ) { return false; @@ -266,7 +266,7 @@ export function updateCableReferenceStem( canvasHeight, fov, ) { - const distance = Cesium.Cartesian3.distance(cameraPositionWC, record.base); + const distance = geo.cartesian3.distance(cameraPositionWC, record.base); const effectiveDistance = Math.max(distance, 5000); const height = canvasHeight || 1080; const fieldOfView = fov || Math.PI / 3; @@ -275,20 +275,20 @@ export function updateCableReferenceStem( 700, Math.min(85000, effectiveDistance * metersPerPixelFactor * STEM_TARGET_PX), ); - Cesium.Cartesian3.fromDegrees( + geo.cartesian3.fromDegrees( record.reference.lon, record.reference.lat, tipHeight, - Cesium.Ellipsoid.WGS84, + geo.ellipsoid.WGS84, record.nextTip, ); if ( - Cesium.Cartesian3.distanceSquared(record.tip, record.nextTip) <= + geo.cartesian3.distanceSquared(record.tip, record.nextTip) <= CABLE_STEM_TIP_EPSILON_SQ ) { return false; } - Cesium.Cartesian3.clone(record.nextTip, record.tip); + geo.cartesian3.clone(record.nextTip, record.tip); record.stemPositionBufferIndex = 1 - record.stemPositionBufferIndex; const stemPositions = record.stemPositionBuffers[record.stemPositionBufferIndex]; diff --git a/src/layers/traffic/footprint.js b/src/layers/traffic/footprint.js index d75947941..59e65073c 100644 --- a/src/layers/traffic/footprint.js +++ b/src/layers/traffic/footprint.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { tilesForBounds, tileToBBox } from '../../data/tomtomTiles.js'; export const RETICLE_RANGE_KM = 10; @@ -64,17 +65,17 @@ export function footprintTiles(polygon, zoom, near) { /** Reusable camera scratch; sampled only on camera events, never in the animation loop. */ export function createReticleFootprint() { - const pixels = Array.from({ length: 25 }, () => new Cesium.Cartesian2()); - const hits = Array.from({ length: 25 }, () => new Cesium.Cartesian3()); - const carto = new Cesium.Cartographic(); + const pixels = Array.from({ length: 25 }, () => geo.cartesian2.create()); + const hits = Array.from({ length: 25 }, () => geo.cartesian3.create()); + const carto = geo.cartographic.create(); const ray = new Cesium.Ray(); return (viewer, altitude) => { const { camera, scene } = viewer; if (!camera.getPickRay) return null; const nadir = camera.positionCartographic; const near = { - lat: Cesium.Math.toDegrees(nadir.latitude), - lon: Cesium.Math.toDegrees(nadir.longitude), + lat: geo.math.toDegrees(nadir.latitude), + lon: geo.math.toDegrees(nadir.longitude), }; const width = scene.canvas.clientWidth || scene.canvas.width, height = scene.canvas.clientHeight || scene.canvas.height; @@ -91,9 +92,9 @@ export function createReticleFootprint() { const hit = camera.pickEllipsoid(p, Cesium.Ellipsoid.WGS84, hits[i]); let dx, dy; if (hit) { - Cesium.Cartographic.fromCartesian(hit, Cesium.Ellipsoid.WGS84, carto); - dy = (Cesium.Math.toDegrees(carto.latitude) - near.lat) * 111.195; - let dlon = Cesium.Math.toDegrees(carto.longitude) - near.lon; + geo.cartographic.fromCartesian(hit, geo.ellipsoid.WGS84, carto); + dy = (geo.math.toDegrees(carto.latitude) - near.lat) * 111.195; + let dlon = geo.math.toDegrees(carto.longitude) - near.lon; dlon -= 360 * Math.round(dlon / 360); dx = dlon * 111.195 * Math.cos(nadir.latitude); } else { diff --git a/src/layers/traffic/ingestion.js b/src/layers/traffic/ingestion.js index 8bb1da5f4..6a2e68f82 100644 --- a/src/layers/traffic/ingestion.js +++ b/src/layers/traffic/ingestion.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { requestWithDeadline } from '../../sources/requestDeadline.js'; import { resolveRoadMode, ROAD_SOURCE_LABELS } from './roadModes.js'; import { phaseTiming } from '../../sources/phaseTiming.js'; @@ -271,7 +271,7 @@ export function createIngestion({ const centerLat = (clamped.south + clamped.north) / 2; const centerLon = (clamped.west + clamped.east) / 2; const scene = layerState._viewer?.scene; - const location = Cesium.Cartographic.fromDegrees(centerLon, centerLat); + const location = geo.cartographic.fromDegrees(centerLon, centerLat); let estimated = services.ground?.cachedGroundFloor?.(centerLat, centerLon); if (!Number.isFinite(estimated)) { try { diff --git a/src/layers/traffic/viewport.js b/src/layers/traffic/viewport.js index f147f5288..7d3013e6e 100644 --- a/src/layers/traffic/viewport.js +++ b/src/layers/traffic/viewport.js @@ -1,5 +1,6 @@ import { createReticleFootprint } from './footprint.js'; import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { deriveFetchCenter, clampBoundsAroundCenter, @@ -37,10 +38,10 @@ export function createViewport({ state: layerState, services, parts, source }) { const rect = layerState._viewer.camera.computeViewRectangle(); if (!rect) return null; return { - south: Cesium.Math.toDegrees(rect.south), - west: Cesium.Math.toDegrees(rect.west), - north: Cesium.Math.toDegrees(rect.north), - east: Cesium.Math.toDegrees(rect.east), + south: geo.math.toDegrees(rect.south), + west: geo.math.toDegrees(rect.west), + north: geo.math.toDegrees(rect.north), + east: geo.math.toDegrees(rect.east), }; } @@ -61,8 +62,8 @@ export function createViewport({ state: layerState, services, parts, source }) { function getFetchCenter() { const carto = layerState._viewer.camera.positionCartographic; if (!carto) return null; - const nadirLat = Cesium.Math.toDegrees(carto.latitude); - const nadirLon = Cesium.Math.toDegrees(carto.longitude); + const nadirLat = geo.math.toDegrees(carto.latitude); + const nadirLon = geo.math.toDegrees(carto.longitude); let hitLat; let hitLon; @@ -71,13 +72,13 @@ export function createViewport({ state: layerState, services, parts, source }) { const height = canvas.clientHeight || canvas.height; if (width > 0 && height > 0) { const hit = layerState._viewer.camera.pickEllipsoid( - new Cesium.Cartesian2(width / 2, height / 2), + geo.cartesian2.create(width / 2, height / 2), Cesium.Ellipsoid.WGS84, ); if (hit) { - const hitCarto = Cesium.Cartographic.fromCartesian(hit); - hitLat = Cesium.Math.toDegrees(hitCarto.latitude); - hitLon = Cesium.Math.toDegrees(hitCarto.longitude); + const hitCarto = geo.cartographic.fromCartesian(hit); + hitLat = geo.math.toDegrees(hitCarto.latitude); + hitLon = geo.math.toDegrees(hitCarto.longitude); } } diff --git a/src/layers/transit/rendering.js b/src/layers/transit/rendering.js index 96d77f2a2..a871c8d14 100644 --- a/src/layers/transit/rendering.js +++ b/src/layers/transit/rendering.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { cameraPoseSignature, perspectiveProjectedRotation, @@ -56,13 +57,7 @@ export function createRendering({ state, services, parts }) { function cartesianFor(lat, lon, heightM, out) { // `heightM` already carries the shared floor's own lift, so nothing is // added here: two lifts stacked is a vehicle hovering. - return Cesium.Cartesian3.fromDegrees( - lon, - lat, - heightM || 0, - undefined, - out, - ); + return geo.cartesian3.fromDegrees(lon, lat, heightM || 0, undefined, out); } /** Heights are aligned to work with Google 3D tiles. Rebuild both ends. */ @@ -74,13 +69,13 @@ export function createRendering({ state, services, parts }) { entry.from.lat, entry.from.lon, entry.from.h, - entry.fromCart || new Cesium.Cartesian3(), + entry.fromCart || geo.cartesian3.create(), ); entry.toCart = cartesianFor( entry.to.lat, entry.to.lon, entry.to.h, - entry.toCart || new Cesium.Cartesian3(), + entry.toCart || geo.cartesian3.create(), ); entry.endpointRevision = entry.sample.revision; entry.endpointFromSeq = entry.sample.fromSeq; @@ -262,7 +257,7 @@ export function createRendering({ state, services, parts }) { height: MARKER_PIXEL_SIZE, color: MODE_CESIUM_COLORS[entry.mode], rotation: 0, - alignedAxis: Cesium.Cartesian3.ZERO, + alignedAxis: geo.cartesian3.ZERO, disableDepthTestDistance: Number.POSITIVE_INFINITY, }); entry.marker = marker; @@ -443,8 +438,8 @@ export function createRendering({ state, services, parts }) { const occluder = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84); const sphere = new Cesium.BoundingSphere(undefined, 5); - const cameraPosition = new Cesium.Cartesian3(); - const candidatePosition = new Cesium.Cartesian3(); + const cameraPosition = geo.cartesian3.create(); + const candidatePosition = geo.cartesian3.create(); /** Visibility work runs off the frame path, coalesced at four sweeps/second. */ function requestVisibility() { state._visibilityDirty = true; @@ -471,7 +466,7 @@ export function createRendering({ state, services, parts }) { if (!camera) return 0; const position = camera.positionWC || - Cesium.Ellipsoid.WGS84.cartographicToCartesian( + geo.ellipsoid.WGS84.cartographicToCartesian( camera.positionCartographic, cameraPosition, ); diff --git a/src/layers/transit/state.js b/src/layers/transit/state.js index 75b13d84b..3f9b77243 100644 --- a/src/layers/transit/state.js +++ b/src/layers/transit/state.js @@ -1,4 +1,4 @@ -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { createHistoryBudget } from '../../data/contactPlayback.js'; /** @@ -120,7 +120,7 @@ export function createState({ services }) { state._error = null; state._limitWarned = false; - state._scratchCartesian = new Cesium.Cartesian3(); + state._scratchCartesian = geo.cartesian3.create(); return state; } diff --git a/src/layers/transit/trails.js b/src/layers/transit/trails.js index 47973c1a2..50130954c 100644 --- a/src/layers/transit/trails.js +++ b/src/layers/transit/trails.js @@ -1,5 +1,5 @@ import { getRegisteredTransitFeed } from '../../data/transitFeeds.js'; -import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { readFix, correctHeight, @@ -154,12 +154,10 @@ export function createTrails({ state, services, parts, source }) { // Ground polylines consume longitude/latitude and drape themselves. // Unknown floors still disqualify the marker corridor. positions.push( - draped ? Cesium.Cartesian3.fromDegrees(point.lon, point.lat) : null, + draped ? geo.cartesian3.fromDegrees(point.lon, point.lat) : null, ); } else - positions.push( - Cesium.Cartesian3.fromDegrees(point.lon, point.lat, h), - ); + positions.push(geo.cartesian3.fromDegrees(point.lon, point.lat, h)); } signature += `${a.seq}/${b.seq}:${heights.join(',')};`; if (retainBody) vertices += n + 1; @@ -216,7 +214,7 @@ export function createTrails({ state, services, parts, source }) { } if (sample.fromSeq === sample.toSeq) { if (!Number.isFinite(entry.segment.from.h)) return null; - Cesium.Cartesian3.clone(entry.fromCart, out); + geo.cartesian3.clone(entry.fromCart, out); return out; } const path = entry.displayPaths?.get(sample.fromSeq); diff --git a/src/layers/transit/viewport.js b/src/layers/transit/viewport.js index 641fab5a2..dac806d12 100644 --- a/src/layers/transit/viewport.js +++ b/src/layers/transit/viewport.js @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import * as geo from '../../geo/index.js'; import { ACTIVATION_ENTER_ALTITUDE_M, ACTIVATION_EXIT_ALTITUDE_M, @@ -29,15 +30,15 @@ export function createViewport({ state, services, parts }) { if (rect) { const center = Cesium.Rectangle.center(rect); return { - lat: Cesium.Math.toDegrees(center.latitude), - lon: Cesium.Math.toDegrees(center.longitude), + lat: geo.math.toDegrees(center.latitude), + lon: geo.math.toDegrees(center.longitude), }; } const carto = viewer?.camera?.positionCartographic; if (carto) { return { - lat: Cesium.Math.toDegrees(carto.latitude), - lon: Cesium.Math.toDegrees(carto.longitude), + lat: geo.math.toDegrees(carto.latitude), + lon: geo.math.toDegrees(carto.longitude), }; } return null; @@ -59,10 +60,10 @@ export function createViewport({ state, services, parts }) { ); state._viewBounds = rect ? inflateViewBounds({ - south: Cesium.Math.toDegrees(rect.south), - north: Cesium.Math.toDegrees(rect.north), - west: Cesium.Math.toDegrees(rect.west), - east: Cesium.Math.toDegrees(rect.east), + south: geo.math.toDegrees(rect.south), + north: geo.math.toDegrees(rect.north), + west: geo.math.toDegrees(rect.west), + east: geo.math.toDegrees(rect.east), }) : null; // The sweep itself is deferred: `marker.show` is a dirty flag, and a drag