@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.
@@ -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 MAX_ARCHIVE_CACHE_ENTRIES = 5e3;
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
- function setArchiveCache(url, archive) {
2901
- archiveCache.delete(url);
2902
- archiveCache.set(url, archive);
2903
- while (archiveCache.size > MAX_ARCHIVE_CACHE_ENTRIES) {
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
+ }
@@ -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