@aguacerowx/mapsgl 0.0.53 → 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.
@@ -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