mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Merge pull request #1744 from bilawalsidhu/weather/source-names
A weather source can name its provider for each product
This commit is contained in:
5 files changed
+77
-5
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
- A weather source can name its provider for each product with
|
||||||
|
`sourceName(kind)`; the Rain radar, Lightning density and Satellite clouds
|
||||||
|
rows show it until a manifest names its own, instead of always saying NOAA
|
||||||
|
nowCOAST (samehkhamis, #1744).
|
||||||
@@ -195,6 +195,12 @@ signal })` returns one tile or null for a transparent tile, each as an
|
|||||||
`ImageBitmap`, `ImageData`, canvas or `VideoFrame` of exactly the requested
|
`ImageBitmap`, `ImageData`, canvas or `VideoFrame` of exactly the requested
|
||||||
size, which the layer draws once and closes. Without them the layers read the
|
size, which the layer draws once and closes. Without them the layers read the
|
||||||
image and tile routes (`WeatherFrameFeed` in `src/layers/weather/source.js`).
|
image and tile routes (`WeatherFrameFeed` in `src/layers/weather/source.js`).
|
||||||
|
It may also name its source with an optional `sourceName(kind)`, `kind` being
|
||||||
|
`'radar'`, `'lightning'` or `'satellite'`: the layer's row shows that name until
|
||||||
|
a manifest names its own, and NOAA nowCOAST when the source names none. The
|
||||||
|
name is read once, when the layer is built, and held to the same rule as a
|
||||||
|
manifest's `display.source`. The map credit still comes from each manifest's
|
||||||
|
`display.credit`, so a source that is not nowCOAST sets that too.
|
||||||
|
|
||||||
`application/services` accepts boundary, terrain, regional-context, weather and
|
`application/services` accepts boundary, terrain, regional-context, weather and
|
||||||
summary services; its weather service supplies cockpit local conditions, not the
|
summary services; its weather service supplies cockpit local conditions, not the
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ function text(value, max) {
|
|||||||
: undefined;
|
: 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:
|
* 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
|
* 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 { createWeatherRendering } from './rendering.js';
|
||||||
import { weatherFrameSource } from './source.js';
|
import { weatherFrameSource } from './source.js';
|
||||||
import { imageryHostStatus } from './imageryHost.js';
|
import { imageryHostStatus } from './imageryHost.js';
|
||||||
import { weatherDisplay, insideExtent } from './display.js';
|
import { weatherDisplay, weatherSourceName, insideExtent } from './display.js';
|
||||||
import {
|
import {
|
||||||
RADAR_MAX_GAP_MS,
|
RADAR_MAX_GAP_MS,
|
||||||
REGIONAL_INFRARED_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
|
/** 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
|
* 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({
|
export function createWeatherLayer({
|
||||||
feed,
|
feed,
|
||||||
clock,
|
clock,
|
||||||
@@ -72,6 +74,20 @@ export function createWeatherLayer({
|
|||||||
const radar = id === 'weather-radar';
|
const radar = id === 'weather-radar';
|
||||||
const lightning = id === 'weather-lightning';
|
const lightning = id === 'weather-lightning';
|
||||||
const satellite = !radar && !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 product = radar ? 'radar' : lightning ? 'lightning' : 'clouds-regional';
|
||||||
let opacity = 'strong';
|
let opacity = 'strong';
|
||||||
let infrared = 100;
|
let infrared = 100;
|
||||||
@@ -301,7 +317,7 @@ export function createWeatherLayer({
|
|||||||
? 'Lightning density'
|
? 'Lightning density'
|
||||||
: 'Satellite clouds',
|
: 'Satellite clouds',
|
||||||
icon: radar ? '◉' : lightning ? 'ϟ' : '☁',
|
icon: radar ? '◉' : lightning ? 'ϟ' : '☁',
|
||||||
source: 'NOAA nowCOAST · OBSERVED',
|
source: sourceName ?? 'NOAA nowCOAST · OBSERVED',
|
||||||
updateInterval: lightning ? 600_000 : 120_000,
|
updateInterval: lightning ? 600_000 : 120_000,
|
||||||
init(nextViewer) {
|
init(nextViewer) {
|
||||||
viewer = nextViewer;
|
viewer = nextViewer;
|
||||||
@@ -870,10 +886,10 @@ export function createWeatherLayer({
|
|||||||
loading,
|
loading,
|
||||||
error: error || rendering?.getDiagnostics().error || null,
|
error: error || rendering?.getDiagnostics().error || null,
|
||||||
stale: Boolean(manifest?.stale || observationDelayed()),
|
stale: Boolean(manifest?.stale || observationDelayed()),
|
||||||
...(source === undefined
|
...(source === undefined && sourceName === null
|
||||||
? { source: 'NOAA nowCOAST' }
|
? { source: 'NOAA nowCOAST' }
|
||||||
: // Source text is data: the panel shows it untranslated.
|
: // Source text is data: the panel shows it untranslated.
|
||||||
{ source, sourceVerbatim: true }),
|
{ source: source ?? sourceName, sourceVerbatim: true }),
|
||||||
observedAt: shownTime(),
|
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(typeof seen[1].getTile, 'function');
|
||||||
assert.equal(seen[1].getImage, undefined);
|
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