From 4f654df3c06842711e2275a06ec71483868ad7da Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 10 Nov 2026 16:14:14 +0400 Subject: [PATCH] Inspect the wind field through the engine contract The inspection marker and the centre probe use the engine's host, projection, frame events, ellipsoid pick and render requests instead of Cesium's scene, so they behave the same on Navara. --- src/layers/wind/edgeCases.test.mjs | 10 +++---- src/layers/wind/index.js | 12 ++------ src/layers/wind/index.test.mjs | 6 ++-- src/layers/wind/inspection.js | 17 +++++------ src/layers/wind/inspection.test.mjs | 10 +++---- src/layers/wind/inspectionMarker.js | 31 +++++-------------- src/layers/wind/inspectionMarker.test.mjs | 36 +++++++++++------------ 7 files changed, 47 insertions(+), 75 deletions(-) diff --git a/src/layers/wind/edgeCases.test.mjs b/src/layers/wind/edgeCases.test.mjs index 1b7a0e2ec..2ae1512ae 100644 --- a/src/layers/wind/edgeCases.test.mjs +++ b/src/layers/wind/edgeCases.test.mjs @@ -23,13 +23,13 @@ const options = { validTime: '2026-09-14 18:00 UTC', status: 'Model forecast', }; -/** The Cesium path picks the centre itself; Navara hands in the position. */ -const viaViewer = (snapshot, point) => +/** Inspect either the current map centre or a captured position. */ +const viaEngine = (snapshot, point) => inspectWindAtCenter( snapshot, { camera: { pickEllipsoid: () => point }, - scene: { canvas: { clientWidth: 800, clientHeight: 600 } }, + host: { canvas: { clientWidth: 800, clientHeight: 600 } }, }, options, ); @@ -42,7 +42,7 @@ test('edge:wind: a fixed grid gives the same direction, speed and legend colour // 5 m/s blowing toward the west: the wind comes FROM the east. const snapshot = field(-5, 0); for (const point of [at(10, 20), at(180, 0), at(-180, 0), at(-45.5, -60)]) { - const cesiumReading = viaViewer(snapshot, point); + const cesiumReading = viaEngine(snapshot, point); const navaraReading = viaPosition(snapshot, point); for (const reading of [cesiumReading, navaraReading]) { assert.equal(reading.from, 'E'); @@ -76,7 +76,7 @@ test('edge:wind: an empty or unavailable field is not reported as calm, a real 0 ]; for (const snapshot of empty) { for (const reading of [ - viaViewer(snapshot, point), + viaEngine(snapshot, point), viaPosition(snapshot, point), ]) { assert.equal(reading.speed, undefined, 'no speed is invented'); diff --git a/src/layers/wind/index.js b/src/layers/wind/index.js index 928573057..3d7b659fd 100644 --- a/src/layers/wind/index.js +++ b/src/layers/wind/index.js @@ -82,14 +82,7 @@ export function createWindLayer({ inspectionMarker?.clear(); }; const sample = (position) => { - if (!position && engine && !viewer) { - const canvas = engine.host.canvas; - position = engine.camera.pickEllipsoid({ - x: canvas.clientWidth / 2, - y: canvas.clientHeight / 2, - }); - } - reading = inspectWindAtCenter(manifest, viewer, { + reading = inspectWindAtCenter(manifest, engine, { position, units, overlay, @@ -130,7 +123,6 @@ export function createWindLayer({ rendering?.setOptions?.({ overlay, paused }); inspectionMarker = createWindInspectionMarker({ container: engine.host.container, - viewer, engine, }); if (enabled && manifest && !manifest.unavailable) { @@ -254,7 +246,7 @@ export function createWindLayer({ loading = enabled && needsField; if (enabled && needsField) queueMicrotask(() => { - if (enabled && generation === current) void layer.update(viewer); + if (enabled && generation === current) void layer.update(); }); else if (enabled && manifest && !manifest.unavailable) rendering?.start(); diff --git a/src/layers/wind/index.test.mjs b/src/layers/wind/index.test.mjs index 30ef80423..5ef39e521 100644 --- a/src/layers/wind/index.test.mjs +++ b/src/layers/wind/index.test.mjs @@ -365,8 +365,10 @@ test('sample stays fixed across model, field and unit changes; dismissal and dis feed: { getSnapshot: async ({ model }) => complete(model) }, createNativeRendering: () => rendering, }); - // A Cesium page: the viewer samples, the engine projects the marker. - layer.init(viewer, { engine: createFakeEngine({ container }) }); + const engine = createFakeEngine({ container, canvas: viewer.scene.canvas }); + engine.camera.pickEllipsoid = (point) => viewer.camera.pickEllipsoid(point); + engine.frames.postRender = viewer.scene.postRender; + layer.init(viewer, { engine }); layer.enable(); await layer.update(); layer.setParams({ inspect: true }); diff --git a/src/layers/wind/inspection.js b/src/layers/wind/inspection.js index 3459a1df2..8299ddfe1 100644 --- a/src/layers/wind/inspection.js +++ b/src/layers/wind/inspection.js @@ -45,20 +45,17 @@ export function formatWindSpeed(speed, units = 'km/h') { /** A deliberate snapshot at the map center; does not own pointer input. */ export function inspectWindAtCenter( snapshot, - viewer, + engine, { units, overlay, model, validTime, status, position } = {}, ) { - const scene = viewer?.scene; - const canvas = scene?.canvas; - // The globe's own ellipsoid, else WGS84 (geo's default). - const ellipsoid = scene?.globe?.ellipsoid; + const canvas = engine?.host.canvas; const point = position || (canvas && - viewer.camera?.pickEllipsoid?.( - geo.cartesian2.create(canvas.clientWidth / 2, canvas.clientHeight / 2), - ellipsoid, - )); + engine.camera.pickEllipsoid({ + x: canvas.clientWidth / 2, + y: canvas.clientHeight / 2, + })); const noReading = () => ({ coordinates: msg('No surface reading'), wind: msg('Aim the map center at Earth'), @@ -70,7 +67,7 @@ export function inspectWindAtCenter( ), }); if (!point || !snapshot?.u || !snapshot?.v) return noReading(); - const location = geo.cartographic.fromCartesian(point, ellipsoid); + const location = geo.cartographic.fromCartesian(point); const lon = geo.math.toDegrees(location.longitude); const lat = geo.math.toDegrees(location.latitude); const vector = sampleWind( diff --git a/src/layers/wind/inspection.test.mjs b/src/layers/wind/inspection.test.mjs index a19801228..1161c1d76 100644 --- a/src/layers/wind/inspection.test.mjs +++ b/src/layers/wind/inspection.test.mjs @@ -27,11 +27,11 @@ test('center inspection uses a fixed forecast reading without double-converting values: Float32Array.from([20, 20, 20, 20]), }, }; - const viewer = { + const engine = { camera: { pickEllipsoid: () => geo.cartesian3.fromDegrees(0, 0) }, - scene: { canvas: { clientWidth: 800, clientHeight: 600 } }, + host: { canvas: { clientWidth: 800, clientHeight: 600 } }, }; - const result = inspectWindAtCenter(snapshot, viewer, { + const result = inspectWindAtCenter(snapshot, engine, { overlay: 'temperature', units: 'km/h', model: 'GFS', @@ -42,9 +42,9 @@ test('center inspection uses a fixed forecast reading without double-converting assert.equal(result.scalarValue, '20.0 °C'); assert.equal(result.validTime, 'fixed'); assert.equal(result.coordinates, '0.00°N · 0.00°E'); - viewer.camera.pickEllipsoid = () => null; + engine.camera.pickEllipsoid = () => null; assert.equal( - inspectWindAtCenter(snapshot, viewer).coordinates, + inspectWindAtCenter(snapshot, engine).coordinates, 'No surface reading', ); }); diff --git a/src/layers/wind/inspectionMarker.js b/src/layers/wind/inspectionMarker.js index 7291158bb..028bd7ade 100644 --- a/src/layers/wind/inspectionMarker.js +++ b/src/layers/wind/inspectionMarker.js @@ -9,9 +9,7 @@ import { applicationPinia } from '../../ui/vueRuntime.js'; */ export function createWindInspectionMarker({ container, - viewer, engine, - cesium, store = useWindInspectionStore(applicationPinia()), }) { const owner = {}; @@ -28,45 +26,31 @@ export function createWindInspectionMarker({ removeRender = null; if (shown()) store.publish(null); marker = null; - if (engine) engine.render.request('wind-inspection'); - else viewer?.scene?.requestRender?.(); + engine.render.request('wind-inspection'); }; return { show(position) { clear(); - const scene = viewer?.scene; - const canvas = scene?.canvas ?? engine?.host.canvas; - const frames = scene?.postRender ?? engine?.frames.postRender; + const canvas = engine.host.canvas; + const frames = engine.frames.postRender; if ( !position || !container?.ownerDocument || !frames?.addEventListener || - (!engine?.projector && !scene?.cartesianToCanvasCoordinates) + !engine.projector ) return; present({ owner, container, visible: false, left: 0, top: 0 }); - const occluder = - !engine && - new cesium.EllipsoidalOccluder( - scene.globe?.ellipsoid || cesium.Ellipsoid.WGS84, - viewer.camera.positionWC, - ); const render = () => { if (!shown()) return; - if (occluder) occluder.cameraPosition = viewer.camera.positionWC; - const point = engine - ? engine.projector.worldToWindow(position, {}) - : scene.cartesianToCanvasCoordinates(position); + const point = engine.projector.worldToWindow(position, {}); const visible = point && point.x >= 0 && point.y >= 0 && point.x <= canvas.clientWidth && point.y <= canvas.clientHeight && - (engine - ? engine.projector.horizonVisible(position) - : scene.mode !== cesium.SceneMode.SCENE3D || - occluder.isPointVisible(position)); + engine.projector.horizonVisible(position); if (!visible) { // A hidden marker keeps its last place, as the element did. if (marker.visible) present({ visible: false }); @@ -82,8 +66,7 @@ export function createWindInspectionMarker({ }; removeRender = frames.addEventListener(render); render(); - if (engine) engine.render.request('wind-inspection'); - else scene.requestRender?.(); + engine.render.request('wind-inspection'); }, clear, destroy: clear, diff --git a/src/layers/wind/inspectionMarker.test.mjs b/src/layers/wind/inspectionMarker.test.mjs index 49f0a6574..94d84630d 100644 --- a/src/layers/wind/inspectionMarker.test.mjs +++ b/src/layers/wind/inspectionMarker.test.mjs @@ -10,16 +10,7 @@ import { useWindInspectionStore } from '../../ui/stores/windInspection.js'; import WindInspectionMarker from '../../ui/views/panels/WindInspectionMarker.vue'; import { createWindInspectionMarker } from './inspectionMarker.js'; -const cesium = { - SceneMode: { SCENE3D: 3 }, - Ellipsoid: { WGS84: {} }, - EllipsoidalOccluder: class { - isPointVisible() { - return cesium.visible; - } - }, - visible: true, -}; +const visibility = { visible: true }; function fixture(t) { document.body.replaceChildren(); @@ -56,18 +47,26 @@ function fixture(t) { requestRender() {}, }, }; + const engine = { + host: { canvas: viewer.scene.canvas }, + frames: { postRender: viewer.scene.postRender }, + projector: { + worldToWindow: viewer.scene.cartesianToCanvasCoordinates, + horizonVisible: () => visibility.visible, + }, + render: { request() {} }, + }; const store = useWindInspectionStore(pinia); - cesium.visible = true; + visibility.visible = true; const owner = createWindInspectionMarker({ container, - viewer, - cesium, + engine, store, }); const nodes = () => [ ...container.querySelectorAll('.gev-wind-inspection-marker'), ]; - return { owner, viewer, store, frames, projected, nodes, container, cesium }; + return { owner, viewer, store, frames, projected, nodes, container, engine }; } test('the marker follows only its captured point, hides beyond the limb, and releases its listener', async (t) => { @@ -85,13 +84,13 @@ test('the marker follows only its captured point, hides beyond the limb, and rel assert.equal(marker.style.top, '220px'); assert.equal(marker.hidden, false); viewer.camera.positionWC = { moved: true }; - cesium.visible = false; + visibility.visible = false; frames.listener(); await nextTick(); assert.equal(marker.hidden, true); assert.equal(marker.style.left, '110px', 'a hidden marker keeps its place'); assert.ok(projected.every((value) => value === point)); - cesium.visible = true; + visibility.visible = true; frames.listener(); await nextTick(); assert.equal(marker.hidden, false); @@ -111,7 +110,7 @@ test('the marker follows only its captured point, hides beyond the limb, and rel }); test('an unchanged frame publishes nothing, and an older owner leaves a newer marker', (t) => { - const { owner, viewer, store, frames, container } = fixture(t); + const { owner, engine, store, frames, container } = fixture(t); owner.show({ x: 1 }); let publishes = 0; const publish = store.publish; @@ -124,8 +123,7 @@ test('an unchanged frame publishes nothing, and an older owner leaves a newer ma frames.listener = null; const newer = createWindInspectionMarker({ container, - viewer, - cesium, + engine, store, }); newer.show({ x: 2 });