mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(weather): add globe wind, observed weather and cyclone context (#617)
Wind (NOAA GFS and ECMWF IFS: animated particles, colour field, a reading at map centre), observed weather (NOAA MRMS radar, GOES and NESDIS infrared clouds, lightning density) on one shared history timeline, NHC and CPHC cyclone advisories with tracks and cones, and a WEATHER panel in the right rail. On 3D Tiles the observed products render as raised shells with a sharper window around the view. Co-authored-by: Gustavo Beneduzi <[email protected]> Co-authored-by: Daniel Slay <[email protected]> Co-authored-by: Rehaan Delmotra <[email protected]> Co-authored-by: Cursor Agent <[email protected]>
This commit is contained in:
180 files changed
+30645
-434
No files matched your search
@@ -1,3 +1,7 @@
|
||||
import { createWeatherClock } from '../layers/weather/clock.js';
|
||||
import { createWeatherLayer } from '../layers/weather/index.js';
|
||||
import { createCyclonesLayer } from '../layers/cyclones/index.js';
|
||||
import { createWindLayer } from '../layers/wind/index.js';
|
||||
import { createLayerCatalog } from './catalog.js';
|
||||
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
|
||||
import { createMilitaryRegistry } from '../layers/aircraft/classification.js';
|
||||
@@ -42,6 +46,9 @@ const SOURCE_METHODS = Object.freeze({
|
||||
launches: ['getLaunches', 'getActiveTle'],
|
||||
alpr: ['fetch'],
|
||||
firms: ['getSnapshot'],
|
||||
wind: ['getSnapshot'],
|
||||
weather: ['getSnapshot'],
|
||||
cyclones: ['getSnapshot'],
|
||||
earthquakes: ['getSnapshot'],
|
||||
cables: ['fetch'],
|
||||
});
|
||||
@@ -72,9 +79,11 @@ export function createApplicationCatalog({
|
||||
throw new TypeError(`Invalid catalog source: ${name}`);
|
||||
}
|
||||
const militaryRegistry = createMilitaryRegistry();
|
||||
const weatherClock = createWeatherClock();
|
||||
const dispose = () => {
|
||||
signal.removeEventListener('abort', dispose);
|
||||
militaryRegistry.dispose();
|
||||
weatherClock.destroy();
|
||||
};
|
||||
signal.addEventListener('abort', dispose, { once: true });
|
||||
try {
|
||||
@@ -128,6 +137,23 @@ export function createApplicationCatalog({
|
||||
vessels,
|
||||
installations,
|
||||
}),
|
||||
createWindLayer({ feed: sources.wind, clock: weatherClock }),
|
||||
createWeatherLayer({
|
||||
feed: sources.weather,
|
||||
id: 'weather-radar',
|
||||
clock: weatherClock,
|
||||
}),
|
||||
createWeatherLayer({
|
||||
feed: sources.weather,
|
||||
id: 'weather-satellite',
|
||||
clock: weatherClock,
|
||||
}),
|
||||
createWeatherLayer({
|
||||
feed: sources.weather,
|
||||
id: 'weather-lightning',
|
||||
clock: weatherClock,
|
||||
}),
|
||||
createCyclonesLayer({ feed: sources.cyclones }),
|
||||
...createInfrastructureLayers(localGeoJsonServices),
|
||||
createApplicationCables({ source: sources.cables }),
|
||||
createApplicationFirms({
|
||||
@@ -141,7 +167,12 @@ export function createApplicationCatalog({
|
||||
],
|
||||
metadata,
|
||||
);
|
||||
return Object.freeze({ ...catalog, militaryRegistry, surface });
|
||||
return Object.freeze({
|
||||
...catalog,
|
||||
militaryRegistry,
|
||||
surface,
|
||||
weatherClock,
|
||||
});
|
||||
} catch (error) {
|
||||
dispose();
|
||||
throw error;
|
||||
|
||||
@@ -39,7 +39,19 @@ test('catalogs construct distinct layers and classification from their supplied
|
||||
signal: b.signal,
|
||||
surface: fixtureSurface(b.signal),
|
||||
});
|
||||
assert.equal(first.layers.length, 21);
|
||||
assert.equal(first.layers.length, 26);
|
||||
assert.notEqual(first.weatherClock, second.weatherClock);
|
||||
await first.weatherClock.setTarget('2026-09-21T12:00:00.000Z');
|
||||
assert.match(
|
||||
first.get('wind').getRowControls().info,
|
||||
/Forecast · does not follow history/,
|
||||
);
|
||||
assert.equal(second.get('wind').getRowControls().summary.status, null);
|
||||
for (const id of ['weather-radar', 'weather-satellite', 'weather-lightning'])
|
||||
assert.equal(
|
||||
first.get(id).getDiagnostics().clock.target,
|
||||
'2026-09-21T12:00:00.000Z',
|
||||
);
|
||||
assert.ok(first.get('transit'));
|
||||
const order = first.layers.map(({ id }) => id);
|
||||
assert.deepEqual(
|
||||
@@ -82,6 +94,10 @@ test('catalogs construct distinct layers and classification from their supplied
|
||||
assert.equal(first.militaryRegistry.isMilitaryIcao('def456'), false);
|
||||
assert.equal(second.militaryRegistry.isMilitaryIcao('def456'), true);
|
||||
a.abort();
|
||||
assert.equal(
|
||||
await first.weatherClock.setTarget('2026-09-21T13:00:00.000Z'),
|
||||
false,
|
||||
);
|
||||
assert.equal(callsA[0].aborted, true);
|
||||
assert.equal(first.militaryRegistry.isMilitaryIcao('abc123'), false);
|
||||
assert.equal(callsB[0].aborted, false);
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@ export function createApplicationData({
|
||||
`Data layers could not be destroyed: ${[...dataManager.layers.keys()].join(', ')}`,
|
||||
);
|
||||
});
|
||||
const presentation = new LayerPresentation(dataManager);
|
||||
const presentation = new LayerPresentation(dataManager, {
|
||||
weatherClock: catalog?.weatherClock,
|
||||
});
|
||||
defer(() => presentation.destroy());
|
||||
onData?.(dataManager);
|
||||
if (!catalog?.layers || !catalog?.metadata)
|
||||
|
||||
@@ -7,11 +7,13 @@ export class LayerPresentation {
|
||||
constructor(
|
||||
manager,
|
||||
{
|
||||
weatherClock,
|
||||
requestRender = governorRequestRender,
|
||||
invalidateDetection = markDetectionSourcesChanged,
|
||||
} = {},
|
||||
) {
|
||||
this.manager = manager;
|
||||
this.weatherClock = weatherClock;
|
||||
this._panel = null;
|
||||
this.pendingVisible = false;
|
||||
this._unsubscribe = manager.subscribeActivity((change) => {
|
||||
@@ -35,6 +37,7 @@ export class LayerPresentation {
|
||||
get panel() {
|
||||
if (!this._panel)
|
||||
this._panel = new LayerPanel({
|
||||
weatherClock: this.weatherClock,
|
||||
getLayers: () => this.manager.getAll(),
|
||||
isEnabled: (id) => this.manager.isEnabled(id),
|
||||
setEnabled: (id, enabled, options) =>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { configureInstallationSource } from '../data/militaryInstallations.js';
|
||||
import { configureSatelliteSource } from '../data/satellites.js';
|
||||
import { configureLaunchSource } from '../data/rocketLaunches.js';
|
||||
import { configureFirmsSource } from '../data/firmsHeatmap.js';
|
||||
import { configureWindSource } from '../data/wind.js';
|
||||
import { configureMilitaryRegistrySource } from '../data/militaryRegistry.js';
|
||||
const configure = {
|
||||
alpr: configureAlprSource,
|
||||
@@ -21,6 +22,7 @@ const configure = {
|
||||
satellites: configureSatelliteSource,
|
||||
launches: configureLaunchSource,
|
||||
firms: configureFirmsSource,
|
||||
wind: configureWindSource,
|
||||
};
|
||||
/** Configure sources before any registration or state restoration starts. */
|
||||
export function configureApplicationSources({
|
||||
|
||||
Reference in new issue
Block a user