@aguacerowx/javascript-sdk 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AguaceroCore.js +794 -207
- package/dist/default-colormaps.js +556 -437
- package/dist/dictionaries.js +2069 -2411
- package/dist/events.js +2 -2
- package/dist/getBundleId.js +9 -20
- package/dist/getBundleId.native.js +18 -0
- package/dist/gridDecodePipeline.js +37 -0
- package/dist/gridDecodeWorker.js +31 -0
- package/dist/index.js +172 -1
- package/dist/nexradTiltCoalesce.js +95 -0
- package/dist/nexradTilts.js +129 -0
- package/dist/nexrad_level3_catalog.js +56 -0
- package/dist/nexrad_support.js +269 -0
- package/dist/satellite_support.js +395 -0
- package/dist/unitConversions.js +10 -10
- package/package.json +99 -99
- package/src/coordinate_configs.js +374 -374
- package/src/default-colormaps.js +3369 -3369
- package/src/dictionaries.js +3857 -3857
- package/src/events.js +31 -31
- package/src/fill-layer-worker.js +26 -26
- package/src/getBundleId.js +8 -8
- package/src/getBundleId.native.js +14 -14
- package/src/gridDecodePipeline.js +32 -32
- package/src/gridDecodeWorker.js +24 -24
- package/src/index.js +48 -48
- package/src/map-styles.js +101 -101
- package/src/nexradTiltCoalesce.js +95 -95
- package/src/nexradTilts.js +138 -128
- package/src/nexrad_level3_catalog.js +26 -26
- package/src/nexrad_support.js +40 -6
- package/src/nws/nwsAlertsFetchSpec.js +93 -93
- package/src/nws/nwsEventColorsDefaults.js +133 -133
- package/src/nws/nwsSdkConstants.js +368 -368
- package/src/satellite_support.js +376 -376
- package/src/spawnGridDecodeWorker.js +5 -5
- package/src/unitConversions.js +102 -102
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NEXRAD tilt coalescing (matches aguacero-frontend `nexradTiltCoalesce.ts`).
|
|
3
|
-
* Adjacent manifest tilts within {@link NEXRAD_TILT_COALESCE_MAX_GAP}° are merged for UI/listings:
|
|
4
|
-
* pairs → higher tilt; runs of three or more consecutive steps merge the first two into the lower tilt.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** Adjacent tilts (sorted) within this gap share one listing / control. */
|
|
8
|
-
export const NEXRAD_TILT_COALESCE_MAX_GAP = 0.1;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @param {number[]} segment
|
|
12
|
-
* @param {Map<number, number>} map
|
|
13
|
-
*/
|
|
14
|
-
function mergeCoalesceSegment(segment, map) {
|
|
15
|
-
if (segment.length === 0) return;
|
|
16
|
-
if (segment.length === 1) {
|
|
17
|
-
map.set(segment[0], segment[0]);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (segment.length === 2) {
|
|
21
|
-
const canonical = Math.max(segment[0], segment[1]);
|
|
22
|
-
map.set(segment[0], canonical);
|
|
23
|
-
map.set(segment[1], canonical);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const low = segment[0];
|
|
27
|
-
map.set(segment[0], low);
|
|
28
|
-
map.set(segment[1], low);
|
|
29
|
-
mergeCoalesceSegment(segment.slice(2), map);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {number[]} sortedUnique
|
|
34
|
-
* @returns {Map<number, number>}
|
|
35
|
-
*/
|
|
36
|
-
export function chainCoalesceToCanonical(sortedUnique) {
|
|
37
|
-
const map = new Map();
|
|
38
|
-
let i = 0;
|
|
39
|
-
while (i < sortedUnique.length) {
|
|
40
|
-
let j = i + 1;
|
|
41
|
-
while (
|
|
42
|
-
j < sortedUnique.length &&
|
|
43
|
-
sortedUnique[j] - sortedUnique[j - 1] <= NEXRAD_TILT_COALESCE_MAX_GAP + 1e-9
|
|
44
|
-
) {
|
|
45
|
-
j++;
|
|
46
|
-
}
|
|
47
|
-
const segment = sortedUnique.slice(i, j);
|
|
48
|
-
mergeCoalesceSegment(segment, map);
|
|
49
|
-
i = j;
|
|
50
|
-
}
|
|
51
|
-
return map;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Manifest tilt list with adjacent options merged (pairs → higher; triple+ splits per merge rules).
|
|
56
|
-
* @param {number[]} tilts
|
|
57
|
-
* @returns {number[]}
|
|
58
|
-
*/
|
|
59
|
-
export function coalesceNexradTiltOptionsForDisplay(tilts) {
|
|
60
|
-
const sorted = [...new Set(tilts)]
|
|
61
|
-
.filter((t) => Number.isFinite(t))
|
|
62
|
-
.sort((a, b) => a - b);
|
|
63
|
-
if (sorted.length <= 1) return sorted;
|
|
64
|
-
const tiltToCanonical = chainCoalesceToCanonical(sorted);
|
|
65
|
-
const reps = [];
|
|
66
|
-
let prevRep;
|
|
67
|
-
for (const t of sorted) {
|
|
68
|
-
const c = tiltToCanonical.get(t);
|
|
69
|
-
if (c === undefined) continue;
|
|
70
|
-
if (c !== prevRep) {
|
|
71
|
-
reps.push(c);
|
|
72
|
-
prevRep = c;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return reps;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Map stored/clamped tilt to the coalesced elevation value (same rules as {@link coalesceNexradTiltOptionsForDisplay}).
|
|
80
|
-
* @param {number} layerTilt
|
|
81
|
-
* @param {number[]} manifestTilts raw manifest tilts (before coalescing)
|
|
82
|
-
* @returns {number}
|
|
83
|
-
*/
|
|
84
|
-
export function nexradLayerTiltToDisplayOption(layerTilt, manifestTilts) {
|
|
85
|
-
const sorted = [...new Set(manifestTilts)]
|
|
86
|
-
.filter((t) => Number.isFinite(t))
|
|
87
|
-
.sort((a, b) => a - b);
|
|
88
|
-
if (sorted.length === 0) return layerTilt;
|
|
89
|
-
const tiltToCanonical = chainCoalesceToCanonical(sorted);
|
|
90
|
-
if (tiltToCanonical.has(layerTilt)) return tiltToCanonical.get(layerTilt);
|
|
91
|
-
const nearest = sorted.reduce((best, x) =>
|
|
92
|
-
Math.abs(x - layerTilt) < Math.abs(best - layerTilt) ? x : best,
|
|
93
|
-
sorted[0]);
|
|
94
|
-
return tiltToCanonical.get(nearest) ?? layerTilt;
|
|
95
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* NEXRAD tilt coalescing (matches aguacero-frontend `nexradTiltCoalesce.ts`).
|
|
3
|
+
* Adjacent manifest tilts within {@link NEXRAD_TILT_COALESCE_MAX_GAP}° are merged for UI/listings:
|
|
4
|
+
* pairs → higher tilt; runs of three or more consecutive steps merge the first two into the lower tilt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Adjacent tilts (sorted) within this gap share one listing / control. */
|
|
8
|
+
export const NEXRAD_TILT_COALESCE_MAX_GAP = 0.1;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {number[]} segment
|
|
12
|
+
* @param {Map<number, number>} map
|
|
13
|
+
*/
|
|
14
|
+
function mergeCoalesceSegment(segment, map) {
|
|
15
|
+
if (segment.length === 0) return;
|
|
16
|
+
if (segment.length === 1) {
|
|
17
|
+
map.set(segment[0], segment[0]);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (segment.length === 2) {
|
|
21
|
+
const canonical = Math.max(segment[0], segment[1]);
|
|
22
|
+
map.set(segment[0], canonical);
|
|
23
|
+
map.set(segment[1], canonical);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const low = segment[0];
|
|
27
|
+
map.set(segment[0], low);
|
|
28
|
+
map.set(segment[1], low);
|
|
29
|
+
mergeCoalesceSegment(segment.slice(2), map);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number[]} sortedUnique
|
|
34
|
+
* @returns {Map<number, number>}
|
|
35
|
+
*/
|
|
36
|
+
export function chainCoalesceToCanonical(sortedUnique) {
|
|
37
|
+
const map = new Map();
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < sortedUnique.length) {
|
|
40
|
+
let j = i + 1;
|
|
41
|
+
while (
|
|
42
|
+
j < sortedUnique.length &&
|
|
43
|
+
sortedUnique[j] - sortedUnique[j - 1] <= NEXRAD_TILT_COALESCE_MAX_GAP + 1e-9
|
|
44
|
+
) {
|
|
45
|
+
j++;
|
|
46
|
+
}
|
|
47
|
+
const segment = sortedUnique.slice(i, j);
|
|
48
|
+
mergeCoalesceSegment(segment, map);
|
|
49
|
+
i = j;
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Manifest tilt list with adjacent options merged (pairs → higher; triple+ splits per merge rules).
|
|
56
|
+
* @param {number[]} tilts
|
|
57
|
+
* @returns {number[]}
|
|
58
|
+
*/
|
|
59
|
+
export function coalesceNexradTiltOptionsForDisplay(tilts) {
|
|
60
|
+
const sorted = [...new Set(tilts)]
|
|
61
|
+
.filter((t) => Number.isFinite(t))
|
|
62
|
+
.sort((a, b) => a - b);
|
|
63
|
+
if (sorted.length <= 1) return sorted;
|
|
64
|
+
const tiltToCanonical = chainCoalesceToCanonical(sorted);
|
|
65
|
+
const reps = [];
|
|
66
|
+
let prevRep;
|
|
67
|
+
for (const t of sorted) {
|
|
68
|
+
const c = tiltToCanonical.get(t);
|
|
69
|
+
if (c === undefined) continue;
|
|
70
|
+
if (c !== prevRep) {
|
|
71
|
+
reps.push(c);
|
|
72
|
+
prevRep = c;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return reps;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Map stored/clamped tilt to the coalesced elevation value (same rules as {@link coalesceNexradTiltOptionsForDisplay}).
|
|
80
|
+
* @param {number} layerTilt
|
|
81
|
+
* @param {number[]} manifestTilts raw manifest tilts (before coalescing)
|
|
82
|
+
* @returns {number}
|
|
83
|
+
*/
|
|
84
|
+
export function nexradLayerTiltToDisplayOption(layerTilt, manifestTilts) {
|
|
85
|
+
const sorted = [...new Set(manifestTilts)]
|
|
86
|
+
.filter((t) => Number.isFinite(t))
|
|
87
|
+
.sort((a, b) => a - b);
|
|
88
|
+
if (sorted.length === 0) return layerTilt;
|
|
89
|
+
const tiltToCanonical = chainCoalesceToCanonical(sorted);
|
|
90
|
+
if (tiltToCanonical.has(layerTilt)) return tiltToCanonical.get(layerTilt);
|
|
91
|
+
const nearest = sorted.reduce((best, x) =>
|
|
92
|
+
Math.abs(x - layerTilt) < Math.abs(best - layerTilt) ? x : best,
|
|
93
|
+
sorted[0]);
|
|
94
|
+
return tiltToCanonical.get(nearest) ?? layerTilt;
|
|
95
|
+
}
|
package/src/nexradTilts.js
CHANGED
|
@@ -1,128 +1,138 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NEXRAD tilt helpers (mirrors aguacero-frontend dictionaries + radar tilts manifest behavior).
|
|
3
|
-
* When {@link setRadarTiltsManifest} has been called with a non-empty map, per-site tilts follow it.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const WSR88D_TILTS = [0.5, 0.9, 1.3, 1.8, 2.4, 3.1, 4.0, 5.1, 6.4];
|
|
7
|
-
const TDWR_TILTS = [0.3, 1.0, 2, 6.5, 8.8, 16.0, 21.3];
|
|
8
|
-
|
|
9
|
-
const WSR88D_SITE_IDS_WITH_T_PREFIX = new Set(['TJUA']);
|
|
10
|
-
|
|
11
|
-
/** @type {Record<string, { g1_tilts: number[]; g2_tilts: number[]; last_seen?: number }>} */
|
|
12
|
-
let manifestBySite = {};
|
|
13
|
-
|
|
14
|
-
export function setRadarTiltsManifest(map) {
|
|
15
|
-
manifestBySite = map && typeof map === 'object' ? map : {};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function getRadarTiltsManifest() {
|
|
19
|
-
return manifestBySite;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function
|
|
75
|
-
const id = siteId?.toUpperCase();
|
|
76
|
-
const entry = id ? manifestBySite[id] : undefined;
|
|
77
|
-
if (entry) {
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
1
|
+
/**
|
|
2
|
+
* NEXRAD tilt helpers (mirrors aguacero-frontend dictionaries + radar tilts manifest behavior).
|
|
3
|
+
* When {@link setRadarTiltsManifest} has been called with a non-empty map, per-site tilts follow it.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const WSR88D_TILTS = [0.5, 0.9, 1.3, 1.8, 2.4, 3.1, 4.0, 5.1, 6.4];
|
|
7
|
+
const TDWR_TILTS = [0.3, 1.0, 2, 6.5, 8.8, 16.0, 21.3];
|
|
8
|
+
|
|
9
|
+
const WSR88D_SITE_IDS_WITH_T_PREFIX = new Set(['TJUA']);
|
|
10
|
+
|
|
11
|
+
/** @type {Record<string, { g1_tilts: number[]; g2_tilts: number[]; last_seen?: number }>} */
|
|
12
|
+
let manifestBySite = {};
|
|
13
|
+
|
|
14
|
+
export function setRadarTiltsManifest(map) {
|
|
15
|
+
manifestBySite = map && typeof map === 'object' ? map : {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getRadarTiltsManifest() {
|
|
19
|
+
return manifestBySite;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True when the tilt manifest has at least one g1 or g2 tilt row for this site. */
|
|
23
|
+
export function radarTiltsManifestHasUsableRow(siteId) {
|
|
24
|
+
const id = String(siteId ?? '')
|
|
25
|
+
.trim()
|
|
26
|
+
.toUpperCase();
|
|
27
|
+
if (!id) return false;
|
|
28
|
+
const row = manifestBySite[id];
|
|
29
|
+
return !!(row?.g1_tilts?.length || row?.g2_tilts?.length);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isNexradTdwrSiteId(siteId) {
|
|
33
|
+
const id = String(siteId ?? '').trim().toUpperCase();
|
|
34
|
+
if (!id) return false;
|
|
35
|
+
if (WSR88D_SITE_IDS_WITH_T_PREFIX.has(id)) return false;
|
|
36
|
+
return id.startsWith('T');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isTerminalRadar(siteId) {
|
|
40
|
+
return isNexradTdwrSiteId(siteId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const NEXRAD_G2_VARIABLES = new Set(['VEL', 'SW']);
|
|
44
|
+
const TDWR_DUAL_POL_CAPPED = new Set(['ZDR', 'PHI', 'RHO', 'KDP']);
|
|
45
|
+
const TDWR_DUAL_POL_MAX_TILT = 0.3;
|
|
46
|
+
|
|
47
|
+
function uniqSorted(nums) {
|
|
48
|
+
const arr = nums.filter((n) => typeof n === 'number' && Number.isFinite(n));
|
|
49
|
+
const rounded = arr.map((n) => Math.round(n * 1000) / 1000);
|
|
50
|
+
return [...new Set(rounded)].sort((a, b) => a - b);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function computeRadarTiltsFromManifestEntry(entry, siteId, radarVariable) {
|
|
54
|
+
const v = (radarVariable || 'REF').toUpperCase();
|
|
55
|
+
let base;
|
|
56
|
+
if (NEXRAD_G2_VARIABLES.has(v)) {
|
|
57
|
+
base = uniqSorted(entry.g2_tilts || []);
|
|
58
|
+
} else {
|
|
59
|
+
base = uniqSorted([...(entry.g1_tilts || []), ...(entry.g2_tilts || [])]);
|
|
60
|
+
}
|
|
61
|
+
if (isNexradTdwrSiteId(siteId) && TDWR_DUAL_POL_CAPPED.has(v)) {
|
|
62
|
+
return base.filter((t) => t <= TDWR_DUAL_POL_MAX_TILT);
|
|
63
|
+
}
|
|
64
|
+
return base;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getDefaultTiltFromManifestEntry(entry) {
|
|
68
|
+
const g1 = entry.g1_tilts;
|
|
69
|
+
if (!Array.isArray(g1) || g1.length === 0) return null;
|
|
70
|
+
const sorted = uniqSorted(g1);
|
|
71
|
+
return sorted[0] ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getDefaultRadarTilt(siteId) {
|
|
75
|
+
const id = siteId?.toUpperCase();
|
|
76
|
+
const entry = id ? manifestBySite[id] : undefined;
|
|
77
|
+
if (entry) {
|
|
78
|
+
const t = getDefaultTiltFromManifestEntry(entry);
|
|
79
|
+
if (t != null) return t;
|
|
80
|
+
}
|
|
81
|
+
return isTerminalRadar(siteId) ? 0.3 : 0.5;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getRadarTilts(siteId, radarVariable) {
|
|
85
|
+
const id = siteId?.toUpperCase();
|
|
86
|
+
const entry = id ? manifestBySite[id] : undefined;
|
|
87
|
+
if (entry) {
|
|
88
|
+
const fromManifest = computeRadarTiltsFromManifestEntry(entry, siteId, radarVariable);
|
|
89
|
+
if (fromManifest.length) return fromManifest;
|
|
90
|
+
}
|
|
91
|
+
const base = isTerminalRadar(siteId) ? TDWR_TILTS : WSR88D_TILTS;
|
|
92
|
+
if (!radarVariable || !isTerminalRadar(siteId)) return [...base];
|
|
93
|
+
const v = radarVariable.toUpperCase();
|
|
94
|
+
if (['ZDR', 'PHI', 'RHO'].includes(v)) {
|
|
95
|
+
return base.filter((t) => t <= TDWR_DUAL_POL_MAX_TILT);
|
|
96
|
+
}
|
|
97
|
+
return [...base];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function clampNexradTiltForVariable(siteId, variable, tilt) {
|
|
101
|
+
if (!isTerminalRadar(siteId)) return tilt;
|
|
102
|
+
const v = (variable || 'REF').toUpperCase();
|
|
103
|
+
if (['ZDR', 'PHI', 'RHO'].includes(v)) {
|
|
104
|
+
return Math.min(tilt, TDWR_DUAL_POL_MAX_TILT);
|
|
105
|
+
}
|
|
106
|
+
return tilt;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function formatTiltForApi(tilt) {
|
|
110
|
+
return tilt.toFixed(2);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const RADAR_TILTS_MANIFEST_URL =
|
|
114
|
+
'https://radar-tilts.s3.us-east-2.amazonaws.com/manifest.json';
|
|
115
|
+
|
|
116
|
+
export async function fetchRadarTiltsManifestFromNetwork() {
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(RADAR_TILTS_MANIFEST_URL, { cache: 'no-store' });
|
|
119
|
+
if (!res.ok) return null;
|
|
120
|
+
const raw = await res.json();
|
|
121
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
124
|
+
if (!val || typeof val !== 'object' || Array.isArray(val)) continue;
|
|
125
|
+
const g1 = val.g1_tilts;
|
|
126
|
+
const g2 = val.g2_tilts;
|
|
127
|
+
if (!Array.isArray(g1) || !Array.isArray(g2)) continue;
|
|
128
|
+
out[key.toUpperCase()] = {
|
|
129
|
+
g1_tilts: g1.filter((n) => typeof n === 'number' && Number.isFinite(n)),
|
|
130
|
+
g2_tilts: g2.filter((n) => typeof n === 'number' && Number.isFinite(n)),
|
|
131
|
+
last_seen: typeof val.last_seen === 'number' ? val.last_seen : undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return Object.keys(out).length ? out : null;
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
/** Subset of aguacero-frontend NEXRAD_LEVEL3_MENU for API product resolution. */
|
|
2
|
-
|
|
3
|
-
export const NEXRAD_LEVEL3_ELEV = '0.50';
|
|
4
|
-
export const NEXRAD_LEVEL3_MOTION_PRODUCT = 'N0S';
|
|
5
|
-
|
|
6
|
-
const MENU = [
|
|
7
|
-
{ radarKey: 'VEL', product: 'N0G', fldKey: 'nexrad_vel' },
|
|
8
|
-
{ radarKey: 'KDP', product: 'N0K', fldKey: 'nexrad_l3_n0k' },
|
|
9
|
-
{ radarKey: 'N0H', product: 'N0H', fldKey: 'nexrad_l3_n0h' },
|
|
10
|
-
{ radarKey: 'HHC', product: 'HHC', fldKey: 'nexrad_l3_hhc' },
|
|
11
|
-
{ radarKey: 'EET', product: 'EET', fldKey: 'nexrad_l3_eet' },
|
|
12
|
-
{ radarKey: 'DVL', product: 'DVL', fldKey: 'nexrad_l3_dvl' },
|
|
13
|
-
{ radarKey: 'DAA', product: 'DAA', fldKey: 'nexrad_l3_daa' },
|
|
14
|
-
{ radarKey: 'DU3', product: 'DU3', fldKey: 'nexrad_l3_du3' },
|
|
15
|
-
{ radarKey: 'DTA', product: 'DTA', fldKey: 'nexrad_l3_dta' },
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
const byRadarKey = new Map();
|
|
19
|
-
for (const e of MENU) {
|
|
20
|
-
byRadarKey.set(e.radarKey, e);
|
|
21
|
-
}
|
|
22
|
-
byRadarKey.set('NVL', byRadarKey.get('DVL'));
|
|
23
|
-
|
|
24
|
-
export function getNexradLevel3EntryByRadarKey(radarKey) {
|
|
25
|
-
return byRadarKey.get(radarKey);
|
|
26
|
-
}
|
|
1
|
+
/** Subset of aguacero-frontend NEXRAD_LEVEL3_MENU for API product resolution. */
|
|
2
|
+
|
|
3
|
+
export const NEXRAD_LEVEL3_ELEV = '0.50';
|
|
4
|
+
export const NEXRAD_LEVEL3_MOTION_PRODUCT = 'N0S';
|
|
5
|
+
|
|
6
|
+
const MENU = [
|
|
7
|
+
{ radarKey: 'VEL', product: 'N0G', fldKey: 'nexrad_vel' },
|
|
8
|
+
{ radarKey: 'KDP', product: 'N0K', fldKey: 'nexrad_l3_n0k' },
|
|
9
|
+
{ radarKey: 'N0H', product: 'N0H', fldKey: 'nexrad_l3_n0h' },
|
|
10
|
+
{ radarKey: 'HHC', product: 'HHC', fldKey: 'nexrad_l3_hhc' },
|
|
11
|
+
{ radarKey: 'EET', product: 'EET', fldKey: 'nexrad_l3_eet' },
|
|
12
|
+
{ radarKey: 'DVL', product: 'DVL', fldKey: 'nexrad_l3_dvl' },
|
|
13
|
+
{ radarKey: 'DAA', product: 'DAA', fldKey: 'nexrad_l3_daa' },
|
|
14
|
+
{ radarKey: 'DU3', product: 'DU3', fldKey: 'nexrad_l3_du3' },
|
|
15
|
+
{ radarKey: 'DTA', product: 'DTA', fldKey: 'nexrad_l3_dta' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const byRadarKey = new Map();
|
|
19
|
+
for (const e of MENU) {
|
|
20
|
+
byRadarKey.set(e.radarKey, e);
|
|
21
|
+
}
|
|
22
|
+
byRadarKey.set('NVL', byRadarKey.get('DVL'));
|
|
23
|
+
|
|
24
|
+
export function getNexradLevel3EntryByRadarKey(radarKey) {
|
|
25
|
+
return byRadarKey.get(radarKey);
|
|
26
|
+
}
|
package/src/nexrad_support.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
clampNexradTiltForVariable,
|
|
8
8
|
getDefaultRadarTilt,
|
|
9
9
|
getRadarTilts,
|
|
10
|
+
radarTiltsManifestHasUsableRow,
|
|
10
11
|
} from './nexradTilts.js';
|
|
11
12
|
import { coalesceNexradTiltOptionsForDisplay } from './nexradTiltCoalesce.js';
|
|
12
13
|
import {
|
|
@@ -49,11 +50,17 @@ export const NEXRAD_SWEEP_LAMBDA_URL = 'https://ddknicwcw2wyov7v5bzxmbqu440tzntf
|
|
|
49
50
|
export const NEXRAD_LEVEL3_BASE_URL = 'https://unidata-nexrad-level3.s3.amazonaws.com';
|
|
50
51
|
|
|
51
52
|
const NEXRAD_GROUP_G1 = ['REF', 'PHI', 'ZDR', 'RHO'];
|
|
52
|
-
const NEXRAD_GROUP_G2 = ['
|
|
53
|
+
const NEXRAD_GROUP_G2 = ['SW'];
|
|
54
|
+
|
|
55
|
+
/** Level-II sweep Lambda may return unix ms; store + duration filters expect seconds. */
|
|
56
|
+
function nexradLevel2ListingTimesToUnixSec(times) {
|
|
57
|
+
return times.map((t) => (typeof t === 'number' && t > 10 ** 10 ? Math.floor(t / 1000) : t));
|
|
58
|
+
}
|
|
53
59
|
|
|
54
60
|
export function variableToNexradGroup(variable) {
|
|
55
61
|
const v = (variable || 'REF').toUpperCase();
|
|
56
|
-
|
|
62
|
+
/** VEL listings shard under g2 for cache clears; display fetch is usually level3. */
|
|
63
|
+
if (v === 'VEL' || NEXRAD_GROUP_G2.includes(v)) return 'g2';
|
|
57
64
|
return 'g1';
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -61,6 +68,12 @@ export function nexradBinGroupIdForKey(cacheGroup) {
|
|
|
61
68
|
return cacheGroup === 'g1' ? 1 : 2;
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
/** S3 object key for a Level-II sweep (content-length suffix for overwrite cache busting). */
|
|
72
|
+
export function buildLevel2SweepObjectKey(stationId, elevNorm, ts, group, v2_clen) {
|
|
73
|
+
const groupId = nexradBinGroupIdForKey(group);
|
|
74
|
+
return `${stationId}_${elevNorm}_${ts}_${v2_clen}_g${groupId}.bin`;
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
function getLevel3StationPrefix(stationId) {
|
|
65
78
|
const upper = (stationId || '').toUpperCase();
|
|
66
79
|
if (upper.startsWith('K') && upper.length === 4) return upper.slice(1);
|
|
@@ -217,6 +230,17 @@ export async function fetchNexradTimesListing(opts) {
|
|
|
217
230
|
l3MotionTimeToKeyMap = l3Motion.timeToKeyMap;
|
|
218
231
|
}
|
|
219
232
|
} else {
|
|
233
|
+
if (!radarTiltsManifestHasUsableRow(stationId)) {
|
|
234
|
+
return {
|
|
235
|
+
cacheKey: key,
|
|
236
|
+
unixTimes: [],
|
|
237
|
+
timeToKeyMap: {},
|
|
238
|
+
level3MotionKey: level3MotionKey || null,
|
|
239
|
+
level3MotionUnixTimes: [],
|
|
240
|
+
level3MotionTimeToKeyMap: {},
|
|
241
|
+
l2StormMotionListKey: l2StormMotionListKey || null,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
220
244
|
const params = new URLSearchParams({
|
|
221
245
|
station: stationId,
|
|
222
246
|
field: group,
|
|
@@ -227,16 +251,26 @@ export async function fetchNexradTimesListing(opts) {
|
|
|
227
251
|
const res = await fetch(url);
|
|
228
252
|
if (!res.ok) throw new Error(`NEXRAD lambda HTTP ${res.status}`);
|
|
229
253
|
const data = await res.json();
|
|
254
|
+
let clens = {};
|
|
230
255
|
if (Array.isArray(data?.times)) {
|
|
231
|
-
times = data.times;
|
|
256
|
+
times = nexradLevel2ListingTimesToUnixSec(data.times);
|
|
257
|
+
clens = data?.clens || {};
|
|
232
258
|
} else if (data?.body) {
|
|
233
259
|
const body = typeof data.body === 'string' ? JSON.parse(data.body) : data.body;
|
|
234
|
-
times = Array.isArray(body?.times) ? body.times : [];
|
|
260
|
+
times = Array.isArray(body?.times) ? nexradLevel2ListingTimesToUnixSec(body.times) : [];
|
|
261
|
+
clens = body?.clens || {};
|
|
235
262
|
}
|
|
236
|
-
const groupId = nexradBinGroupIdForKey(group);
|
|
237
263
|
const elevNorm = elevNormUse;
|
|
238
264
|
times.forEach((t) => {
|
|
239
|
-
|
|
265
|
+
const clen = clens[String(t)];
|
|
266
|
+
if (clen == null || !Number.isFinite(Number(clen))) return;
|
|
267
|
+
timeToKeyMap[String(t)] = buildLevel2SweepObjectKey(
|
|
268
|
+
stationId,
|
|
269
|
+
elevNorm,
|
|
270
|
+
t,
|
|
271
|
+
group,
|
|
272
|
+
Number(clen),
|
|
273
|
+
);
|
|
240
274
|
});
|
|
241
275
|
if (fetchL2VelMotion) {
|
|
242
276
|
const l3Motion = await fetchLevel3ProductTimesForStation(
|