@aguacerowx/mapsgl 0.0.52 → 0.0.54
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/package.json +31 -31
- package/src/NexradWeatherController.js +510 -510
- package/src/NwsWatchesWarningsOverlay.js +54 -3
- package/src/WeatherLayerManager.js +14 -1
- package/src/nexrad/nexradArchiveCache.ts +286 -66
- package/src/nexrad/nexradLevel3Products.ts +581 -581
- package/src/nexrad/nexradMapboxFrameOpts.bundled.js +244 -0
- package/src/nexrad/radarArchiveCore.bundled.js +83 -5
- package/src/nwsAlertsFetchSpec.js +114 -0
- package/src/nwsAlertsSupport.js +28 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// src/nexrad/nexradMapboxFrameOpts.ts
|
|
2
|
+
import { clampNexradTiltForVariable as clampNexradTiltForVariable2, getDefaultRadarTilt as getDefaultRadarTilt2, getRadarTilts as getRadarTilts2 } from "@aguacerowx/javascript-sdk";
|
|
3
|
+
|
|
4
|
+
// src/nexrad/nexradLevel3Products.ts
|
|
5
|
+
import {
|
|
6
|
+
clampNexradTiltForVariable,
|
|
7
|
+
formatTiltForApi,
|
|
8
|
+
getDefaultRadarTilt,
|
|
9
|
+
getRadarTilts,
|
|
10
|
+
isTerminalRadar
|
|
11
|
+
} from "@aguacerowx/javascript-sdk";
|
|
12
|
+
var NEXRAD_LEVEL3_MENU = [
|
|
13
|
+
{
|
|
14
|
+
radarKey: "VEL",
|
|
15
|
+
product: "N0G",
|
|
16
|
+
menuLabel: "Storm Relative Velocity",
|
|
17
|
+
fldKey: "nexrad_vel",
|
|
18
|
+
cmapPropertyKey: "nexrad_vel",
|
|
19
|
+
decodeMode: "velocity",
|
|
20
|
+
stormRelativeVelocity: true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
radarKey: "KDP",
|
|
24
|
+
product: "N0K",
|
|
25
|
+
menuLabel: "Specific Differential Phase",
|
|
26
|
+
fldKey: "nexrad_l3_n0k",
|
|
27
|
+
cmapPropertyKey: "nexrad_kdp",
|
|
28
|
+
defaultUnit: "deg/km",
|
|
29
|
+
decodeMode: "generic_physical"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
radarKey: "N0H",
|
|
33
|
+
product: "N0H",
|
|
34
|
+
menuLabel: "Hydrometeor Classification",
|
|
35
|
+
fldKey: "nexrad_l3_n0h",
|
|
36
|
+
cmapPropertyKey: "nexrad_l3_n0h",
|
|
37
|
+
defaultUnit: "None",
|
|
38
|
+
decodeMode: "categorical"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
radarKey: "HHC",
|
|
42
|
+
product: "HHC",
|
|
43
|
+
menuLabel: "Hybrid Hydrometeor Classification",
|
|
44
|
+
fldKey: "nexrad_l3_hhc",
|
|
45
|
+
cmapPropertyKey: "nexrad_l3_hhc",
|
|
46
|
+
defaultUnit: "None",
|
|
47
|
+
decodeMode: "categorical"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
radarKey: "EET",
|
|
51
|
+
product: "EET",
|
|
52
|
+
menuLabel: "Enhanced Echo Tops",
|
|
53
|
+
fldKey: "nexrad_l3_eet",
|
|
54
|
+
cmapPropertyKey: "nexrad_l3_eet",
|
|
55
|
+
defaultUnit: "kft",
|
|
56
|
+
decodeMode: "tops_kft"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
radarKey: "DVL",
|
|
60
|
+
product: "DVL",
|
|
61
|
+
menuLabel: "Vertically Integrated Liquid",
|
|
62
|
+
fldKey: "nexrad_l3_dvl",
|
|
63
|
+
cmapPropertyKey: "nexrad_l3_dvl",
|
|
64
|
+
defaultUnit: "kg/m\xB2",
|
|
65
|
+
decodeMode: "vil"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
radarKey: "DAA",
|
|
69
|
+
product: "DAA",
|
|
70
|
+
menuLabel: "1-Hour Precipitation",
|
|
71
|
+
fldKey: "nexrad_l3_daa",
|
|
72
|
+
cmapPropertyKey: "tp_0_1",
|
|
73
|
+
defaultUnit: "in",
|
|
74
|
+
decodeMode: "precip"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
radarKey: "DU3",
|
|
78
|
+
product: "DU3",
|
|
79
|
+
menuLabel: "3-Hour Precipitation",
|
|
80
|
+
fldKey: "nexrad_l3_du3",
|
|
81
|
+
cmapPropertyKey: "tp_0_total",
|
|
82
|
+
defaultUnit: "in",
|
|
83
|
+
decodeMode: "precip"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
radarKey: "DTA",
|
|
87
|
+
product: "DTA",
|
|
88
|
+
menuLabel: "Storm Total Precipitation",
|
|
89
|
+
fldKey: "nexrad_l3_dta",
|
|
90
|
+
cmapPropertyKey: "tp_0_total",
|
|
91
|
+
defaultUnit: "in",
|
|
92
|
+
decodeMode: "precip"
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
var byRadarKey = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const e of NEXRAD_LEVEL3_MENU) {
|
|
97
|
+
byRadarKey.set(e.radarKey, e);
|
|
98
|
+
if (e.radarKey === "DVL") byRadarKey.set("NVL", e);
|
|
99
|
+
}
|
|
100
|
+
var NEXRAD_LEVEL3_KDP_PRODUCTS = [
|
|
101
|
+
"NXK",
|
|
102
|
+
"NYK",
|
|
103
|
+
"NZK",
|
|
104
|
+
"N0K",
|
|
105
|
+
"NAK",
|
|
106
|
+
"N1K",
|
|
107
|
+
"NBK",
|
|
108
|
+
"N2K",
|
|
109
|
+
"N3K"
|
|
110
|
+
];
|
|
111
|
+
var NEXRAD_LEVEL3_DHC_PRODUCTS = [
|
|
112
|
+
"NXH",
|
|
113
|
+
"NYH",
|
|
114
|
+
"NZH",
|
|
115
|
+
"N0H",
|
|
116
|
+
"NAH",
|
|
117
|
+
"N1H",
|
|
118
|
+
"NBH",
|
|
119
|
+
"N2H",
|
|
120
|
+
"N3H"
|
|
121
|
+
];
|
|
122
|
+
var NEXRAD_LEVEL3_KDP_PRODUCTS_FOR_MANIFEST = NEXRAD_LEVEL3_KDP_PRODUCTS.slice(3);
|
|
123
|
+
var NEXRAD_LEVEL3_DHC_PRODUCTS_FOR_MANIFEST = NEXRAD_LEVEL3_DHC_PRODUCTS.slice(3);
|
|
124
|
+
var NEXRAD_LEVEL3_VELOCITY_PRODUCTS = [
|
|
125
|
+
"NXG",
|
|
126
|
+
"NYG",
|
|
127
|
+
"NZG",
|
|
128
|
+
"N0G",
|
|
129
|
+
"NAG",
|
|
130
|
+
"N1G",
|
|
131
|
+
"NBG",
|
|
132
|
+
"N2G",
|
|
133
|
+
"N3G"
|
|
134
|
+
];
|
|
135
|
+
var NEXRAD_LEVEL3_VELOCITY_PRODUCTS_FOR_MANIFEST = NEXRAD_LEVEL3_VELOCITY_PRODUCTS.slice(3);
|
|
136
|
+
var NEXRAD_LEVEL3_MANIFEST_TILT_COUNT = NEXRAD_LEVEL3_KDP_PRODUCTS_FOR_MANIFEST.length;
|
|
137
|
+
function getNexradLevel3RadarTiltsForVelocity(siteId) {
|
|
138
|
+
return getRadarTilts(siteId, "VEL").slice(0, NEXRAD_LEVEL3_MANIFEST_TILT_COUNT);
|
|
139
|
+
}
|
|
140
|
+
function nexradLevel3S3VelocityProductForSiteTilt(siteId, tilt) {
|
|
141
|
+
const products = NEXRAD_LEVEL3_VELOCITY_PRODUCTS_FOR_MANIFEST;
|
|
142
|
+
const tilts = getNexradLevel3RadarTiltsForVelocity(siteId);
|
|
143
|
+
if (!tilts.length) {
|
|
144
|
+
return products[0];
|
|
145
|
+
}
|
|
146
|
+
const clamped = clampNexradTiltForVariable(siteId, "VEL", tilt);
|
|
147
|
+
let idx = tilts.indexOf(clamped);
|
|
148
|
+
if (idx === -1) {
|
|
149
|
+
idx = tilts.reduce(
|
|
150
|
+
(bestI, t, i) => Math.abs(t - clamped) < Math.abs(tilts[bestI] - clamped) ? i : bestI,
|
|
151
|
+
0
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
idx = Math.min(idx, products.length - 1);
|
|
155
|
+
return products[idx];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/nexrad/nexradMapboxFrameOpts.ts
|
|
159
|
+
var NEXRAD_LEVEL3_KDP_PRODUCTS_FOR_MANIFEST2 = [
|
|
160
|
+
"N0K",
|
|
161
|
+
"NAK",
|
|
162
|
+
"N1K",
|
|
163
|
+
"NBK",
|
|
164
|
+
"N2K",
|
|
165
|
+
"N3K"
|
|
166
|
+
];
|
|
167
|
+
var NEXRAD_LEVEL3_DHC_PRODUCTS_FOR_MANIFEST2 = [
|
|
168
|
+
"N0H",
|
|
169
|
+
"NAH",
|
|
170
|
+
"N1H",
|
|
171
|
+
"NBH",
|
|
172
|
+
"N2H",
|
|
173
|
+
"N3H"
|
|
174
|
+
];
|
|
175
|
+
var NEXRAD_LEVEL3_MANIFEST_TILT_COUNT2 = NEXRAD_LEVEL3_KDP_PRODUCTS_FOR_MANIFEST2.length;
|
|
176
|
+
function nearestTiltInSortedList(tilts, target) {
|
|
177
|
+
if (!tilts.length) return target;
|
|
178
|
+
let best = tilts[0];
|
|
179
|
+
let bestD = Math.abs(best - target);
|
|
180
|
+
for (let i = 1; i < tilts.length; i++) {
|
|
181
|
+
const t = tilts[i];
|
|
182
|
+
const d = Math.abs(t - target);
|
|
183
|
+
if (d < bestD || d === bestD && t < best) {
|
|
184
|
+
best = t;
|
|
185
|
+
bestD = d;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return best;
|
|
189
|
+
}
|
|
190
|
+
function getNexradLevel3RadarTilts(siteId, radarVariable) {
|
|
191
|
+
return getRadarTilts2(siteId, radarVariable).slice(0, NEXRAD_LEVEL3_MANIFEST_TILT_COUNT2);
|
|
192
|
+
}
|
|
193
|
+
function nexradLevel3UsesTiltIndexedS3Products(radarVariable) {
|
|
194
|
+
const v = radarVariable || "";
|
|
195
|
+
return v === "KDP" || v === "N0H";
|
|
196
|
+
}
|
|
197
|
+
function clampTiltForLevel3CompositeKey(siteId, radarVariable, tilt) {
|
|
198
|
+
if (!nexradLevel3UsesTiltIndexedS3Products(radarVariable)) {
|
|
199
|
+
return clampNexradTiltForVariable2(siteId, radarVariable, tilt);
|
|
200
|
+
}
|
|
201
|
+
const tilts = getNexradLevel3RadarTilts(siteId, radarVariable);
|
|
202
|
+
const c = clampNexradTiltForVariable2(siteId, radarVariable, tilt);
|
|
203
|
+
if (!tilts.length) return c;
|
|
204
|
+
return nearestTiltInSortedList(tilts, c);
|
|
205
|
+
}
|
|
206
|
+
function nexradLevel3S3ProductForSiteTilt(siteId, radarVariable, tilt) {
|
|
207
|
+
const products = radarVariable === "KDP" ? NEXRAD_LEVEL3_KDP_PRODUCTS_FOR_MANIFEST2 : NEXRAD_LEVEL3_DHC_PRODUCTS_FOR_MANIFEST2;
|
|
208
|
+
const tilts = getNexradLevel3RadarTilts(siteId, radarVariable);
|
|
209
|
+
if (!tilts.length) {
|
|
210
|
+
return products[0];
|
|
211
|
+
}
|
|
212
|
+
const clamped = clampTiltForLevel3CompositeKey(siteId, radarVariable, tilt);
|
|
213
|
+
let idx = tilts.indexOf(clamped);
|
|
214
|
+
if (idx === -1) {
|
|
215
|
+
idx = tilts.reduce(
|
|
216
|
+
(bestI, t, i) => Math.abs(t - clamped) < Math.abs(tilts[bestI] - clamped) ? i : bestI,
|
|
217
|
+
0
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
idx = Math.min(idx, products.length - 1);
|
|
221
|
+
return products[idx];
|
|
222
|
+
}
|
|
223
|
+
function mapboxFrameUploadOptionsForNexradState(state) {
|
|
224
|
+
if (!state || state.nexradDataSource !== "level3") return void 0;
|
|
225
|
+
const site = state.nexradSite;
|
|
226
|
+
const radarVar = (state.nexradProduct || "REF").toUpperCase();
|
|
227
|
+
if (nexradLevel3UsesTiltIndexedS3Products(radarVar) && site) {
|
|
228
|
+
const tilt = Number.isFinite(state.nexradTilt) ? Number(state.nexradTilt) : getDefaultRadarTilt2(site);
|
|
229
|
+
const kind = radarVar === "KDP" ? "KDP" : "N0H";
|
|
230
|
+
const mnemonic = nexradLevel3S3ProductForSiteTilt(site, kind, tilt);
|
|
231
|
+
return { geometryLayoutKey: `${site}|${radarVar}|${mnemonic}` };
|
|
232
|
+
}
|
|
233
|
+
if (radarVar === "VEL" && site) {
|
|
234
|
+
const tilt = Number.isFinite(state.nexradTilt) ? Number(state.nexradTilt) : getDefaultRadarTilt2(site);
|
|
235
|
+
const mnemonic = nexradLevel3S3VelocityProductForSiteTilt(site, tilt);
|
|
236
|
+
return { geometryLayoutKey: `${site}|${radarVar}|${mnemonic}` };
|
|
237
|
+
}
|
|
238
|
+
return { geometryCacheKeysRayBoundaries: true };
|
|
239
|
+
}
|
|
240
|
+
export {
|
|
241
|
+
mapboxFrameUploadOptionsForNexradState,
|
|
242
|
+
nexradLevel3S3ProductForSiteTilt,
|
|
243
|
+
nexradLevel3UsesTiltIndexedS3Products
|
|
244
|
+
};
|
|
@@ -2895,16 +2895,94 @@ function applyLevel3StormRelativeToFrame(frame, stormSpeedMs, stormDirectionDeg)
|
|
|
2895
2895
|
}
|
|
2896
2896
|
|
|
2897
2897
|
// src/nexrad/nexradArchiveCache.ts
|
|
2898
|
-
var
|
|
2898
|
+
var NEXRAD_ARCHIVE_LEVEL2_BASE_URL = "https://d3dc62msmxkrd7.cloudfront.net/level-2";
|
|
2899
|
+
var NEXRAD_ARCHIVE_LEVEL3_BASE_URL = "https://unidata-nexrad-level3.s3.amazonaws.com";
|
|
2900
|
+
var MAX_ARCHIVE_CACHE_ENTRIES = 400;
|
|
2901
|
+
var MAX_ARCHIVE_CACHE_BYTES = 180 * 1024 * 1024;
|
|
2899
2902
|
var archiveCache = /* @__PURE__ */ new Map();
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2903
|
+
var pruneAllowlistObjectKeys = null;
|
|
2904
|
+
var pruneSupplementalObjectKeys = /* @__PURE__ */ new Set();
|
|
2905
|
+
function canInsertCacheKey(cacheKey, frame) {
|
|
2906
|
+
if (frame == null) return true;
|
|
2907
|
+
if (pruneAllowlistObjectKeys == null) return true;
|
|
2908
|
+
const o = extractObjectKeyFromCacheKey(cacheKey);
|
|
2909
|
+
if (o == null) return true;
|
|
2910
|
+
if (pruneAllowlistObjectKeys.has(o)) return true;
|
|
2911
|
+
if (pruneSupplementalObjectKeys.has(o)) return true;
|
|
2912
|
+
return false;
|
|
2913
|
+
}
|
|
2914
|
+
function estimateFrameBytes(value) {
|
|
2915
|
+
if (value == null) return 0;
|
|
2916
|
+
const g = value.gateData;
|
|
2917
|
+
const r = value.rayBoundariesDeg;
|
|
2918
|
+
const a = value.azimuthsDeg;
|
|
2919
|
+
return (g?.byteLength ?? 0) + (r?.byteLength ?? 0) + (a?.byteLength ?? 0);
|
|
2920
|
+
}
|
|
2921
|
+
var cacheBytesTotal = 0;
|
|
2922
|
+
var onArchiveCacheKeysRemoved = null;
|
|
2923
|
+
function emitArchiveCacheKeysRemoved(keys) {
|
|
2924
|
+
if (keys.length === 0) return;
|
|
2925
|
+
onArchiveCacheKeysRemoved?.(keys);
|
|
2926
|
+
}
|
|
2927
|
+
function recomputeCacheBytes() {
|
|
2928
|
+
let n = 0;
|
|
2929
|
+
for (const v of archiveCache.values()) {
|
|
2930
|
+
n += estimateFrameBytes(v);
|
|
2931
|
+
}
|
|
2932
|
+
cacheBytesTotal = n;
|
|
2933
|
+
}
|
|
2934
|
+
function evictOldestWhileOverCap() {
|
|
2935
|
+
const removed = [];
|
|
2936
|
+
while (archiveCache.size > MAX_ARCHIVE_CACHE_ENTRIES || cacheBytesTotal > MAX_ARCHIVE_CACHE_BYTES) {
|
|
2904
2937
|
const oldestKey = archiveCache.keys().next().value;
|
|
2905
2938
|
if (!oldestKey) break;
|
|
2939
|
+
const v = archiveCache.get(oldestKey);
|
|
2906
2940
|
archiveCache.delete(oldestKey);
|
|
2941
|
+
removed.push(oldestKey);
|
|
2942
|
+
cacheBytesTotal -= estimateFrameBytes(v);
|
|
2943
|
+
if (cacheBytesTotal < 0) recomputeCacheBytes();
|
|
2944
|
+
}
|
|
2945
|
+
emitArchiveCacheKeysRemoved(removed);
|
|
2946
|
+
}
|
|
2947
|
+
function setArchiveCache(cacheKey, archive) {
|
|
2948
|
+
if (archive && !canInsertCacheKey(cacheKey, archive)) {
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
const prev = archiveCache.get(cacheKey);
|
|
2952
|
+
if (prev !== void 0) {
|
|
2953
|
+
cacheBytesTotal -= estimateFrameBytes(prev);
|
|
2954
|
+
}
|
|
2955
|
+
archiveCache.delete(cacheKey);
|
|
2956
|
+
archiveCache.set(cacheKey, archive);
|
|
2957
|
+
if (archive) {
|
|
2958
|
+
cacheBytesTotal += estimateFrameBytes(archive);
|
|
2907
2959
|
}
|
|
2960
|
+
evictOldestWhileOverCap();
|
|
2961
|
+
}
|
|
2962
|
+
function extractObjectKeyFromCacheKey(cacheKey) {
|
|
2963
|
+
const m = cacheKey.match(/:([A-Za-z0-9_]+):((?:level2|level3))(?:\|.*)?$/i);
|
|
2964
|
+
if (!m || m.index == null || m.index < 1) return null;
|
|
2965
|
+
const urlPart = cacheKey.slice(0, m.index);
|
|
2966
|
+
return objectKeyFromArchiveUrlString(urlPart);
|
|
2967
|
+
}
|
|
2968
|
+
function objectKeyFromArchiveUrlString(url) {
|
|
2969
|
+
for (const base of [NEXRAD_ARCHIVE_LEVEL2_BASE_URL, NEXRAD_ARCHIVE_LEVEL3_BASE_URL]) {
|
|
2970
|
+
const prefix = base.endsWith("/") ? base : `${base}/`;
|
|
2971
|
+
if (url.startsWith(prefix)) {
|
|
2972
|
+
return url.slice(prefix.length);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
if (url.startsWith("http")) {
|
|
2976
|
+
try {
|
|
2977
|
+
const u = new URL(url);
|
|
2978
|
+
const p = u.pathname.replace(/^\/+/, "");
|
|
2979
|
+
if (p.startsWith("level-2/")) return p.slice("level-2/".length);
|
|
2980
|
+
return p || null;
|
|
2981
|
+
} catch {
|
|
2982
|
+
return null;
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
return null;
|
|
2908
2986
|
}
|
|
2909
2987
|
|
|
2910
2988
|
// src/nexrad/loadNexradSites.ts
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NWWS `/alerts?hours=` sizing (aguacero-frontend baseline + overlap window parity).
|
|
3
|
+
*
|
|
4
|
+
* Hours follow the **active** observational mode only: NEXRAD → `nexradDurationValue`,
|
|
5
|
+
* MRMS → `mrmsDurationValue`, satellite → `satelliteDurationValue`, otherwise `1` (e.g. model-only).
|
|
6
|
+
* Tier cap uses {@link AguaceroCore} `satelliteTier` — the only subscription-style field on core today
|
|
7
|
+
* (same values as the frontend board tier: basic / enthusiast / professional).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const MAX_ALERT_HISTORY_HOURS = 8760;
|
|
11
|
+
|
|
12
|
+
/** Matches javascript-sdk `satellite_support.js` timeline clamp. */
|
|
13
|
+
const TIMELINE_DURATION_MAX_HOURS = 12;
|
|
14
|
+
|
|
15
|
+
const LEGACY_DURATION_ALIAS = {
|
|
16
|
+
'0.5': '1',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Same rules as `parseTimelineDurationHours` in `@aguacerowx/javascript-sdk` (inlined so mapsgl
|
|
21
|
+
* does not depend on a package subpath that Vite may not resolve).
|
|
22
|
+
*
|
|
23
|
+
* @param {string | number | null | undefined} value
|
|
24
|
+
* @returns {number}
|
|
25
|
+
*/
|
|
26
|
+
function parseTimelineDurationHours(value) {
|
|
27
|
+
let s = value == null ? '1' : String(value).trim();
|
|
28
|
+
s = LEGACY_DURATION_ALIAS[s] || s;
|
|
29
|
+
const n = Number(s);
|
|
30
|
+
if (!Number.isFinite(n) || n <= 0) return 1;
|
|
31
|
+
if (n > TIMELINE_DURATION_MAX_HOURS) return TIMELINE_DURATION_MAX_HOURS;
|
|
32
|
+
return n;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Max option span (hours) per tier from frontend `RADAR_DURATION_CONFIG`. */
|
|
36
|
+
const TIER_MAX_HOURS = {
|
|
37
|
+
professional: 12,
|
|
38
|
+
enthusiast: 4,
|
|
39
|
+
basic: 1,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string | undefined} tier
|
|
44
|
+
* @returns {'professional'|'enthusiast'|'basic'}
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeNwsSubscriptionTier(tier) {
|
|
47
|
+
const t = String(tier || 'basic').toLowerCase();
|
|
48
|
+
if (t === 'commercial' || t === 'lifetime' || t === 'partner') return 'professional';
|
|
49
|
+
if (t === 'professional' || t === 'enthusiast' || t === 'basic') return t;
|
|
50
|
+
return 'basic';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string | undefined} tier
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
export function getMaxRadarHistoryHoursForTier(tier) {
|
|
58
|
+
const k = normalizeNwsSubscriptionTier(tier);
|
|
59
|
+
return TIER_MAX_HOURS[k] ?? TIER_MAX_HOURS.basic;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {object | null | undefined} state - {@link AguaceroCore} `state`
|
|
64
|
+
* @returns {number} integer hours in [1, MAX_ALERT_HISTORY_HOURS]
|
|
65
|
+
*/
|
|
66
|
+
export function computeNwsAlertsFetchHoursFromAguaceroState(state) {
|
|
67
|
+
const tierMax = getMaxRadarHistoryHoursForTier(state?.satelliteTier);
|
|
68
|
+
let desired = 1;
|
|
69
|
+
if (state?.isNexrad) {
|
|
70
|
+
desired = parseTimelineDurationHours(state.nexradDurationValue);
|
|
71
|
+
} else if (state?.isMRMS) {
|
|
72
|
+
desired = parseTimelineDurationHours(state.mrmsDurationValue);
|
|
73
|
+
} else if (state?.isSatellite) {
|
|
74
|
+
desired = parseTimelineDurationHours(state.satelliteDurationValue);
|
|
75
|
+
}
|
|
76
|
+
const clamped = Math.min(tierMax, Math.max(1, desired));
|
|
77
|
+
return Math.min(MAX_ALERT_HISTORY_HOURS, Math.floor(clamped));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {number} hours
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
export function nwsAlertsFetchSpecCacheKey(hours) {
|
|
85
|
+
return `h${hours}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Unix window [start, end] for client-side validity overlap (wall-clock end at “now”).
|
|
90
|
+
*
|
|
91
|
+
* @param {number} hours
|
|
92
|
+
* @param {number | null} [anchorSec] - reserved; default now (matches frontend `anchorSec: null`)
|
|
93
|
+
* @returns {{ winStartSec: number; winEndSec: number }}
|
|
94
|
+
*/
|
|
95
|
+
export function nwwsAlertsFetchUnixWindow(hours, anchorSec = null) {
|
|
96
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
97
|
+
const winEndSec = anchorSec ?? nowSec;
|
|
98
|
+
const winStartSec = winEndSec - hours * 3600;
|
|
99
|
+
return { winStartSec, winEndSec };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {string} baseUrl - `/alerts` root (no query)
|
|
104
|
+
* @param {number} hours
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
export function buildNwwsActiveAlertsUrl(baseUrl, hours) {
|
|
108
|
+
const h = Math.max(1, Math.floor(Number(hours) || 1));
|
|
109
|
+
const params = new URLSearchParams();
|
|
110
|
+
params.set('hours', String(h));
|
|
111
|
+
const q = params.toString();
|
|
112
|
+
const base = String(baseUrl || '').replace(/\/$/, '');
|
|
113
|
+
return q ? `${base}?${q}` : base;
|
|
114
|
+
}
|
package/src/nwsAlertsSupport.js
CHANGED
|
@@ -155,6 +155,34 @@ export function parseNwsTimeToUnix(value) {
|
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/** CAP validity start fields (order matches aguacero-frontend `nwsWarningsModalHelpers`). */
|
|
159
|
+
const NWS_VALIDITY_START_PROP_KEYS = ['onset', 'effective', 'issued_at', 'issued', 'sent'];
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* True if CAP-validity [start, end] overlaps [winStartSec, winEndSec] (inclusive).
|
|
163
|
+
* Missing end time is treated as ongoing.
|
|
164
|
+
*
|
|
165
|
+
* @param {{ properties?: Record<string, unknown> }} feature
|
|
166
|
+
* @param {number} winStartSec
|
|
167
|
+
* @param {number} winEndSec
|
|
168
|
+
* @returns {boolean}
|
|
169
|
+
*/
|
|
170
|
+
export function nwsFeatureOverlapsUnixWindow(feature, winStartSec, winEndSec) {
|
|
171
|
+
const p = feature?.properties;
|
|
172
|
+
if (!p) return false;
|
|
173
|
+
const start =
|
|
174
|
+
typeof p.start_unix === 'number' && Number.isFinite(p.start_unix)
|
|
175
|
+
? Math.floor(p.start_unix)
|
|
176
|
+
: parseNwsTimeToUnix(getNwsTimeProp(p, NWS_VALIDITY_START_PROP_KEYS));
|
|
177
|
+
const end =
|
|
178
|
+
typeof p.end_unix === 'number' && Number.isFinite(p.end_unix)
|
|
179
|
+
? Math.floor(p.end_unix)
|
|
180
|
+
: parseNwsTimeToUnix(getNwsTimeProp(p, [...NWS_ALERT_END_TIME_PROP_KEYS]));
|
|
181
|
+
const s = typeof start === 'number' && Number.isFinite(start) ? start : 0;
|
|
182
|
+
const e = typeof end === 'number' && Number.isFinite(end) ? end : Number.POSITIVE_INFINITY;
|
|
183
|
+
return s <= winEndSec && e >= winStartSec;
|
|
184
|
+
}
|
|
185
|
+
|
|
158
186
|
const NWS_START_UNIX_KEYS = ['issued_at', 'issued', 'sent', 'onset', 'effective'];
|
|
159
187
|
const NWS_ACTIVE_START_UNIX_KEYS = ['onset', 'effective', 'issued_at', 'issued', 'sent'];
|
|
160
188
|
|