mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
A weather source can name its provider for each product
The Rain radar, Lightning density and Satellite clouds rows named NOAA nowCOAST until a manifest named its own source, whatever the source read. A weather source may now supply sourceName(kind), kind being radar, lightning or satellite; the row shows that name until a manifest names one. The name is read once when the layer is built and held to the same rule as a manifest's display.source. A source without names, or one whose name is refused or fails, still shows NOAA nowCOAST.
This commit is contained in:
1 parent
c2e42b5dfd
commit
7f1c35c5ed
4 files changed
+73
-5
No files matched your search
@@ -39,6 +39,15 @@ function text(value, max) {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A source name a weather feed supplies itself (`sourceName` on the feed),
|
||||
* held to the same rule as a manifest's `display.source`; undefined when it
|
||||
* is not a fit name.
|
||||
*/
|
||||
export function weatherSourceName(value) {
|
||||
return text(value, TEXT_LIMITS.source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a label the legend formats as a number reads back exactly as given:
|
||||
* within the safe integer range and with no digits lost to rounding, so
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Cesium from 'cesium';
|
||||
import { createWeatherRendering } from './rendering.js';
|
||||
import { weatherFrameSource } from './source.js';
|
||||
import { imageryHostStatus } from './imageryHost.js';
|
||||
import { weatherDisplay, insideExtent } from './display.js';
|
||||
import { weatherDisplay, weatherSourceName, insideExtent } from './display.js';
|
||||
import {
|
||||
RADAR_MAX_GAP_MS,
|
||||
REGIONAL_INFRARED_MAX_GAP_MS,
|
||||
@@ -55,7 +55,9 @@ const utc = (value) =>
|
||||
|
||||
/** A per-application observation layer, using the existing layer lifecycle and
|
||||
* row controls. Its time is the world clock's, so a link made away from live
|
||||
* opens its imagery at that time; one made live opens the latest. */
|
||||
* opens its imagery at that time; one made live opens the latest. The feed
|
||||
* may also name its source with `sourceName(kind)`, `kind` being 'radar',
|
||||
* 'lightning' or 'satellite'; NOAA nowCOAST is named otherwise. */
|
||||
export function createWeatherLayer({
|
||||
feed,
|
||||
clock,
|
||||
@@ -72,6 +74,20 @@ export function createWeatherLayer({
|
||||
const radar = id === 'weather-radar';
|
||||
const lightning = id === 'weather-lightning';
|
||||
const satellite = !radar && !lightning;
|
||||
// A feed may name the source behind each product ('radar', 'lightning' or
|
||||
// 'satellite'); its row then says so before a manifest names it. A name
|
||||
// that fails, like a manifest's, leaves the built-in one.
|
||||
let sourceName = null;
|
||||
try {
|
||||
sourceName =
|
||||
weatherSourceName(
|
||||
feed.sourceName?.(
|
||||
radar ? 'radar' : lightning ? 'lightning' : 'satellite',
|
||||
),
|
||||
) ?? null;
|
||||
} catch (cause) {
|
||||
console.warn('[weather] source name unavailable:', cause);
|
||||
}
|
||||
let product = radar ? 'radar' : lightning ? 'lightning' : 'clouds-regional';
|
||||
let opacity = 'strong';
|
||||
let infrared = 100;
|
||||
@@ -301,7 +317,7 @@ export function createWeatherLayer({
|
||||
? 'Lightning density'
|
||||
: 'Satellite clouds',
|
||||
icon: radar ? '◉' : lightning ? 'ϟ' : '☁',
|
||||
source: 'NOAA nowCOAST · OBSERVED',
|
||||
source: sourceName ?? 'NOAA nowCOAST · OBSERVED',
|
||||
updateInterval: lightning ? 600_000 : 120_000,
|
||||
init(nextViewer) {
|
||||
viewer = nextViewer;
|
||||
@@ -870,10 +886,10 @@ export function createWeatherLayer({
|
||||
loading,
|
||||
error: error || rendering?.getDiagnostics().error || null,
|
||||
stale: Boolean(manifest?.stale || observationDelayed()),
|
||||
...(source === undefined
|
||||
...(source === undefined && sourceName === null
|
||||
? { source: 'NOAA nowCOAST' }
|
||||
: // Source text is data: the panel shows it untranslated.
|
||||
{ source, sourceVerbatim: true }),
|
||||
{ source: source ?? sourceName, sourceVerbatim: true }),
|
||||
observedAt: shownTime(),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -2694,3 +2694,40 @@ test('the layer hands rendering a frame source only when its feed is one', () =>
|
||||
assert.equal(typeof seen[1].getTile, 'function');
|
||||
assert.equal(seen[1].getImage, undefined);
|
||||
});
|
||||
|
||||
test('a feed names the source behind each product, before any manifest does', (t) => {
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
const quiet = {
|
||||
documentRef: target({ hidden: false }),
|
||||
eventTarget: target(),
|
||||
matchMedia: () => target({ matches: false }),
|
||||
};
|
||||
const asked = [];
|
||||
const feed = {
|
||||
getSnapshot: async () => snapshot,
|
||||
sourceName(kind) {
|
||||
asked.push(kind);
|
||||
if (kind === 'satellite') throw new Error('no name');
|
||||
return kind === 'radar' ? ' NOAA MRMS ' : 'NOAA\u200b GLM';
|
||||
},
|
||||
};
|
||||
const radar = createWeatherLayer({ ...quiet, feed, id: 'weather-radar' });
|
||||
assert.equal(radar.source, 'NOAA MRMS');
|
||||
const stats = radar.getStats();
|
||||
assert.equal(stats.source, 'NOAA MRMS');
|
||||
assert.equal(stats.sourceVerbatim, true);
|
||||
// A name a manifest could not carry, or a failing call, leaves nowCOAST, as
|
||||
// does a feed with no names.
|
||||
for (const id of ['weather-lightning', 'weather-satellite']) {
|
||||
const layer = createWeatherLayer({ ...quiet, feed, id });
|
||||
assert.equal(layer.source, 'NOAA nowCOAST · OBSERVED', id);
|
||||
assert.equal(layer.getStats().source, 'NOAA nowCOAST', id);
|
||||
assert.equal(layer.getStats().sourceVerbatim, undefined, id);
|
||||
}
|
||||
assert.deepEqual(asked, ['radar', 'lightning', 'satellite']);
|
||||
const unnamed = createWeatherLayer({
|
||||
...quiet,
|
||||
feed: { getSnapshot: async () => snapshot },
|
||||
});
|
||||
assert.equal(unnamed.source, 'NOAA nowCOAST · OBSERVED');
|
||||
});
|
||||
Reference in new issue
Block a user