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:
authored and GitHub committed 2026-09-22 17:45:39 -05:00
1 parent f01b6a5d84
commit 0251c74109
180 files changed
+30645 -434

No files matched your search

+9
View File
@@ -66,6 +66,15 @@ export class MapSourceController {
`${stack?.label || 'This map stack'} is unavailable`
);
}
/** Return the shown supplied or controller-owned tileset, if any. */
getImageryHostTileset() {
if (this._destroyed) return null;
for (const source of this._sources.values())
if (source.tileset?.show === true) return source.tileset;
for (const tileset of this._ownedTilesets)
if (tileset.show === true) return tileset;
return null;
}
getActiveId() {
return this._activeId;
}
+23
View File
@@ -435,3 +435,26 @@ test('a failed recovery reports its error and leaves switching settled', async (
assert.equal(env.imagery.length, 0);
env.controller.destroy();
});
test('imagery host tileset follows supplied, owned and globe map sources', async () => {
const supplied = { show: true };
const owned = { show: false };
const registry = {
defaultId: 'supplied',
sources: [
{ descriptor: descriptor('supplied'), tileset: supplied },
{ descriptor: descriptor('owned'), createTileset: async () => owned },
{ descriptor: descriptor('globe'), imagery: async () => ({}) },
],
};
const { controller } = fixture(registry);
assert.equal(controller.getImageryHostTileset(), supplied);
await controller.setStack('owned');
assert.equal(controller.getImageryHostTileset(), owned);
await controller.setStack('globe');
assert.equal(controller.getImageryHostTileset(), null);
await controller.setStack('supplied');
assert.equal(controller.getImageryHostTileset(), supplied);
controller.destroy();
assert.equal(controller.getImageryHostTileset(), null);
});
+7 -4
View File
@@ -53,10 +53,11 @@ export async function loadPhotorealisticTileset(
export function createGoogleDirectTileset(Cesium, key) {
key = clean(key);
if (!key) throw new Error('Google 3D requires an explicit browser key');
return Cesium.createGooglePhotorealistic3DTileset({
key,
onlyUsingWithGoogleGeocoder: true,
});
// Tiles keep drawing their own texture while draped weather loads.
return Cesium.createGooglePhotorealistic3DTileset(
{ key, onlyUsingWithGoogleGeocoder: true },
{ asynchronouslyLoadImagery: true },
);
}
export async function createGoogleIonTileset(
@@ -77,5 +78,7 @@ export async function createGoogleIonTileset(
cacheBytes: 1536 * 1024 * 1024,
maximumCacheOverflowBytes: 1024 * 1024 * 1024,
enableCollision: true,
// Tiles keep drawing their own texture while draped weather loads.
asynchronouslyLoadImagery: true,
});
}
+29
View File
@@ -126,3 +126,32 @@ test('credentialed source factories reject an omitted token instead of consuming
);
await assert.rejects(createWorldTerrain(' '), /explicit ion token/);
});
test('both Google 3D routes keep tiles drawing while draped imagery loads', async () => {
const { createGoogleDirectTileset, createGoogleIonTileset } =
await import('./google3d.js');
const calls = [];
const sdk = {
createGooglePhotorealistic3DTileset: async (...args) => {
calls.push(args);
return 'direct';
},
IonResource: { fromAssetId: async (id, options) => ({ id, ...options }) },
Cesium3DTileset: {
fromUrl: async (...args) => {
calls.push(args);
return 'ion';
},
},
};
assert.equal(await createGoogleDirectTileset(sdk, 'key'), 'direct');
assert.equal(await createGoogleIonTileset(sdk, 'token'), 'ion');
assert.deepEqual(calls[0], [
{ key: 'key', onlyUsingWithGoogleGeocoder: true },
{ asynchronouslyLoadImagery: true },
]);
assert.equal(calls[1][0].id, 2275207);
assert.equal(calls[1][0].accessToken, 'token');
assert.equal(calls[1][1].asynchronouslyLoadImagery, true);
assert.equal(calls[1][1].enableCollision, true);
});