@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
package/dist/AguaceroCore.js
CHANGED
|
@@ -10,7 +10,13 @@ var _unitConversions = require("./unitConversions.js");
|
|
|
10
10
|
var _dictionaries = require("./dictionaries.js");
|
|
11
11
|
var _defaultColormaps = require("./default-colormaps.js");
|
|
12
12
|
var _proj = _interopRequireDefault(require("proj4"));
|
|
13
|
+
var _gridDecodePipeline = require("./gridDecodePipeline.js");
|
|
13
14
|
var _getBundleId = require("./getBundleId");
|
|
15
|
+
var _satellite_support = require("./satellite_support.js");
|
|
16
|
+
var _nexrad_support = require("./nexrad_support.js");
|
|
17
|
+
var _nexradTiltCoalesce = require("./nexradTiltCoalesce.js");
|
|
18
|
+
var _nexrad_level3_catalog = require("./nexrad_level3_catalog.js");
|
|
19
|
+
var _nexradTilts = require("./nexradTilts.js");
|
|
14
20
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
21
|
// AguaceroCore.js - The Headless "Engine"
|
|
16
22
|
|
|
@@ -54,26 +60,80 @@ function findLatestModelRun(modelsData, modelName) {
|
|
|
54
60
|
}
|
|
55
61
|
return null;
|
|
56
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* model-status JSON uses string keys for runs (often zero-padded: "00", "06").
|
|
66
|
+
* Direct lookup modelStatus[model][date][run] fails if state.run is "6" but the key is "06".
|
|
67
|
+
* Returns the hour list and the run key that matched.
|
|
68
|
+
*/
|
|
69
|
+
function resolveModelRunHours(modelStatus, model, date, run) {
|
|
70
|
+
var _modelStatus$model;
|
|
71
|
+
const runs = modelStatus === null || modelStatus === void 0 || (_modelStatus$model = modelStatus[model]) === null || _modelStatus$model === void 0 ? void 0 : _modelStatus$model[date];
|
|
72
|
+
if (!runs || run == null || run === '') {
|
|
73
|
+
return {
|
|
74
|
+
hours: [],
|
|
75
|
+
matchedRunKey: null,
|
|
76
|
+
availableRunKeys: runs ? Object.keys(runs) : []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const runStr = String(run);
|
|
80
|
+
const candidates = new Set([runStr]);
|
|
81
|
+
const n = parseInt(runStr, 10);
|
|
82
|
+
if (!Number.isNaN(n)) {
|
|
83
|
+
candidates.add(String(n));
|
|
84
|
+
candidates.add(String(n).padStart(2, '0'));
|
|
85
|
+
candidates.add(String(n).padStart(3, '0'));
|
|
86
|
+
}
|
|
87
|
+
for (const key of candidates) {
|
|
88
|
+
const h = runs[key];
|
|
89
|
+
if (h && Array.isArray(h) && h.length > 0) {
|
|
90
|
+
return {
|
|
91
|
+
hours: h,
|
|
92
|
+
matchedRunKey: key,
|
|
93
|
+
availableRunKeys: Object.keys(runs)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!Number.isNaN(n)) {
|
|
98
|
+
for (const k of Object.keys(runs)) {
|
|
99
|
+
const kn = parseInt(k, 10);
|
|
100
|
+
if (!Number.isNaN(kn) && kn === n) {
|
|
101
|
+
const h = runs[k];
|
|
102
|
+
if (h && Array.isArray(h) && h.length > 0) {
|
|
103
|
+
return {
|
|
104
|
+
hours: h,
|
|
105
|
+
matchedRunKey: k,
|
|
106
|
+
availableRunKeys: Object.keys(runs)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
hours: [],
|
|
114
|
+
matchedRunKey: null,
|
|
115
|
+
availableRunKeys: Object.keys(runs)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
57
118
|
class AguaceroCore extends _events.EventEmitter {
|
|
58
119
|
constructor(options = {}) {
|
|
59
120
|
super();
|
|
60
121
|
this.isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
61
122
|
this.apiKey = options.apiKey;
|
|
123
|
+
/** Passed as CloudFront `userId` for satellite KTX2 URLs (production uses the authenticated account id). */
|
|
124
|
+
this.userId = options.userId ?? 'sdk-user';
|
|
62
125
|
this.bundleId = (0, _getBundleId.getBundleId)();
|
|
63
126
|
this.baseGridUrl = 'https://d3dc62msmxkrd7.cloudfront.net';
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.resultQueue = [];
|
|
70
|
-
this.isProcessingQueue = false;
|
|
71
|
-
} else {
|
|
72
|
-
this.worker = null;
|
|
73
|
-
}
|
|
127
|
+
/** @type {Worker | null} */
|
|
128
|
+
this._gridDecodeWorker = null;
|
|
129
|
+
/** When true, skip Worker and use {@link processCompressedGrid} on the main thread. */
|
|
130
|
+
this._gridDecodeWorkerDisabled = false;
|
|
131
|
+
this._gridDecodeMsgId = 0;
|
|
74
132
|
this.statusUrl = 'https://d3dc62msmxkrd7.cloudfront.net/model-status';
|
|
75
133
|
this.modelStatus = null;
|
|
76
134
|
this.mrmsStatus = null;
|
|
135
|
+
/** @type {{ objects?: Array<{ key: string }> } | null} */
|
|
136
|
+
this.satelliteListing = null;
|
|
77
137
|
this.dataCache = new Map();
|
|
78
138
|
this.abortControllers = new Map();
|
|
79
139
|
this.isPlaying = false;
|
|
@@ -84,30 +144,207 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
84
144
|
// EDIT: Determine initial mode from options
|
|
85
145
|
const initialMode = userLayerOptions.mode || 'model';
|
|
86
146
|
const initialVariable = userLayerOptions.variable || null;
|
|
147
|
+
const initialSatellite = initialMode === 'satellite';
|
|
148
|
+
const initialNexrad = initialMode === 'nexrad';
|
|
149
|
+
const initialNexradProd = userLayerOptions.nexradProduct || 'REF';
|
|
150
|
+
const initialNexradDs = (0, _nexrad_support.inferNexradDataSourceForProduct)(initialNexradProd);
|
|
151
|
+
const initialNexradFld = initialNexrad ? (0, _nexrad_support.nexradColormapFldKey)(initialNexradDs, initialNexradProd) : initialVariable;
|
|
152
|
+
let initialSatelliteInstrumentId = null;
|
|
153
|
+
let initialSatelliteSectorLabel = null;
|
|
154
|
+
let initialSatelliteChannel = null;
|
|
155
|
+
if (initialSatellite) {
|
|
156
|
+
initialSatelliteInstrumentId = userLayerOptions.satelliteId ?? 'GOES19-EAST';
|
|
157
|
+
initialSatelliteSectorLabel = (0, _satellite_support.resolveSatelliteSectorLabel)(userLayerOptions.satelliteSector ?? userLayerOptions.sector ?? 'conus');
|
|
158
|
+
initialSatelliteChannel = userLayerOptions.satelliteProduct ?? userLayerOptions.satelliteChannel ?? initialVariable ?? 'C13';
|
|
159
|
+
}
|
|
87
160
|
this.state = {
|
|
88
161
|
model: userLayerOptions.model || 'gfs',
|
|
89
162
|
// EDIT: Set isMRMS based on the initial mode
|
|
90
|
-
isMRMS: initialMode === 'mrms',
|
|
163
|
+
isMRMS: initialMode === 'mrms' && !initialSatellite && !initialNexrad,
|
|
91
164
|
mrmsTimestamp: null,
|
|
92
|
-
variable: initialVariable,
|
|
165
|
+
variable: initialNexrad ? initialNexradFld : initialSatellite && initialSatelliteInstrumentId ? initialSatelliteChannel : initialVariable,
|
|
93
166
|
date: null,
|
|
94
167
|
run: null,
|
|
95
168
|
forecastHour: 0,
|
|
96
169
|
visible: true,
|
|
97
170
|
opacity: userLayerOptions.opacity ?? 1,
|
|
98
171
|
units: options.initialUnit || 'imperial',
|
|
99
|
-
shaderSmoothingEnabled: options.shaderSmoothingEnabled ?? true
|
|
172
|
+
shaderSmoothingEnabled: options.shaderSmoothingEnabled ?? true,
|
|
173
|
+
isSatellite: initialSatellite,
|
|
174
|
+
satelliteInstrumentId: initialSatelliteInstrumentId,
|
|
175
|
+
satelliteSectorLabel: initialSatelliteSectorLabel,
|
|
176
|
+
satelliteChannel: initialSatelliteChannel,
|
|
177
|
+
satelliteTimestamp: userLayerOptions.satelliteTimestamp != null ? Number(userLayerOptions.satelliteTimestamp) : null,
|
|
178
|
+
satelliteTier: userLayerOptions.satelliteTier || 'basic',
|
|
179
|
+
satelliteDurationValue: (0, _satellite_support.formatTimelineDurationValue)(userLayerOptions.satelliteDurationValue != null ? userLayerOptions.satelliteDurationValue : '1'),
|
|
180
|
+
mrmsDurationValue: (0, _satellite_support.formatTimelineDurationValue)(userLayerOptions.mrmsDurationValue != null ? userLayerOptions.mrmsDurationValue : '1'),
|
|
181
|
+
nexradDurationValue: (0, _satellite_support.formatTimelineDurationValue)(userLayerOptions.nexradDurationValue != null ? userLayerOptions.nexradDurationValue : '1'),
|
|
182
|
+
isNexrad: initialNexrad,
|
|
183
|
+
nexradSite: userLayerOptions.nexradSite ?? null,
|
|
184
|
+
nexradDataSource: initialNexradDs,
|
|
185
|
+
nexradProduct: initialNexradProd,
|
|
186
|
+
nexradTilt: userLayerOptions.nexradTilt != null ? Number(userLayerOptions.nexradTilt) : userLayerOptions.nexradSite ? (0, _nexradTilts.getDefaultRadarTilt)(userLayerOptions.nexradSite) : null,
|
|
187
|
+
nexradTimestamp: userLayerOptions.nexradTimestamp != null ? Number(userLayerOptions.nexradTimestamp) : null,
|
|
188
|
+
nexradStormRelative: userLayerOptions.nexradStormRelative === true,
|
|
189
|
+
/** When true, mapsgl shows clickable NEXRAD site markers (independent of selected site). */
|
|
190
|
+
nexradShowSitesPicker: userLayerOptions.nexradShowSitesPicker !== false
|
|
100
191
|
};
|
|
101
192
|
this.autoRefreshEnabled = options.autoRefresh ?? false;
|
|
102
193
|
this.autoRefreshIntervalSeconds = options.autoRefreshInterval ?? 60;
|
|
103
194
|
this.autoRefreshIntervalId = null;
|
|
195
|
+
|
|
196
|
+
/** @type {Record<string, { unixTimes?: number[]; timeToKeyMap?: Record<string, string>; listWindowHours?: number }>} */
|
|
197
|
+
this.nexradTimesByStation = {};
|
|
104
198
|
}
|
|
105
199
|
async setState(newState) {
|
|
106
|
-
|
|
200
|
+
const patch = {
|
|
201
|
+
...newState
|
|
202
|
+
};
|
|
203
|
+
if ('satelliteKey' in patch) delete patch.satelliteKey;
|
|
204
|
+
if ('nexradDataSource' in patch && !('nexradProduct' in patch)) {
|
|
205
|
+
delete patch.nexradDataSource;
|
|
206
|
+
}
|
|
207
|
+
const willBeNexrad = patch.isNexrad !== undefined ? Boolean(patch.isNexrad) : this.state.isNexrad;
|
|
208
|
+
if (willBeNexrad && 'nexradProduct' in patch && patch.nexradProduct != null) {
|
|
209
|
+
const p = (patch.nexradProduct || 'REF').toUpperCase();
|
|
210
|
+
const prevP = (this.state.nexradProduct || 'REF').toUpperCase();
|
|
211
|
+
const ds = (0, _nexrad_support.inferNexradDataSourceForProduct)(p);
|
|
212
|
+
patch.nexradProduct = p;
|
|
213
|
+
patch.nexradDataSource = ds;
|
|
214
|
+
patch.variable = (0, _nexrad_support.nexradColormapFldKey)(ds, p);
|
|
215
|
+
if (!('nexradStormRelative' in newState)) {
|
|
216
|
+
// Default: base radial velocity (L3 N0G only) — one fetch per time; fast scrub.
|
|
217
|
+
// SRV (N0G + N0S) is opt-in: setNexradStormRelative(true) or pass nexradStormRelative in setState.
|
|
218
|
+
// Re-selecting the same product (e.g. VEL → VEL) leaves the current SRV flag alone.
|
|
219
|
+
if (p !== 'VEL' || prevP !== 'VEL') {
|
|
220
|
+
patch.nexradStormRelative = false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if ('forecastHour' in patch && patch.forecastHour != null) {
|
|
225
|
+
patch.forecastHour = Number(patch.forecastHour);
|
|
226
|
+
}
|
|
227
|
+
if ('mrmsTimestamp' in patch && patch.mrmsTimestamp != null) {
|
|
228
|
+
patch.mrmsTimestamp = Number(patch.mrmsTimestamp);
|
|
229
|
+
}
|
|
230
|
+
if ('satelliteTimestamp' in patch && patch.satelliteTimestamp != null) {
|
|
231
|
+
patch.satelliteTimestamp = Number(patch.satelliteTimestamp);
|
|
232
|
+
}
|
|
233
|
+
if ('satelliteDurationValue' in patch && patch.satelliteDurationValue != null) {
|
|
234
|
+
patch.satelliteDurationValue = (0, _satellite_support.formatTimelineDurationValue)(patch.satelliteDurationValue);
|
|
235
|
+
}
|
|
236
|
+
if ('mrmsDurationValue' in patch && patch.mrmsDurationValue != null) {
|
|
237
|
+
patch.mrmsDurationValue = (0, _satellite_support.formatTimelineDurationValue)(patch.mrmsDurationValue);
|
|
238
|
+
}
|
|
239
|
+
if ('nexradDurationValue' in patch && patch.nexradDurationValue != null) {
|
|
240
|
+
patch.nexradDurationValue = (0, _satellite_support.formatTimelineDurationValue)(patch.nexradDurationValue);
|
|
241
|
+
}
|
|
242
|
+
if ('nexradTimestamp' in patch && patch.nexradTimestamp != null) {
|
|
243
|
+
patch.nexradTimestamp = Number(patch.nexradTimestamp);
|
|
244
|
+
}
|
|
245
|
+
if ('nexradTilt' in patch && patch.nexradTilt != null) {
|
|
246
|
+
patch.nexradTilt = Number(patch.nexradTilt);
|
|
247
|
+
}
|
|
248
|
+
Object.assign(this.state, patch);
|
|
107
249
|
this._emitStateChange();
|
|
108
250
|
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Forecast hours for the current model/date/run (normalized numbers).
|
|
254
|
+
* Tolerates model-status run keys like "06" vs state.run "6" so preload and slider stay in sync.
|
|
255
|
+
*/
|
|
256
|
+
getAvailableForecastHours() {
|
|
257
|
+
if (this.state.isMRMS || this.state.isSatellite || this.state.isNexrad) return [];
|
|
258
|
+
if (!this.state.model || this.state.date == null || this.state.run == null) return [];
|
|
259
|
+
const resolved = resolveModelRunHours(this.modelStatus, this.state.model, this.state.date, this.state.run);
|
|
260
|
+
let hours = resolved.hours || [];
|
|
261
|
+
if (hours.length > 0) {
|
|
262
|
+
hours = hours.map(h => typeof h === 'string' ? parseInt(h, 10) : Number(h)).filter(h => !Number.isNaN(h));
|
|
263
|
+
}
|
|
264
|
+
if (this.state.variable === 'ptypeRefl' && this.state.model === 'hrrr' && hours.length > 0) {
|
|
265
|
+
hours = hours.filter(hour => hour !== 0);
|
|
266
|
+
}
|
|
267
|
+
return hours;
|
|
268
|
+
}
|
|
269
|
+
_computeSatelliteTimeline() {
|
|
270
|
+
var _this$satelliteListin;
|
|
271
|
+
const {
|
|
272
|
+
satelliteInstrumentId,
|
|
273
|
+
satelliteSectorLabel,
|
|
274
|
+
satelliteChannel
|
|
275
|
+
} = this.state;
|
|
276
|
+
if (!satelliteInstrumentId || !satelliteSectorLabel || !satelliteChannel || !((_this$satelliteListin = this.satelliteListing) !== null && _this$satelliteListin !== void 0 && _this$satelliteListin.objects)) {
|
|
277
|
+
return {
|
|
278
|
+
unixTimes: [],
|
|
279
|
+
timeToFileMap: {}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const allFiles = this.satelliteListing.objects.map(o => o.key);
|
|
283
|
+
const sectorName = satelliteSectorLabel;
|
|
284
|
+
const tier = this.state.satelliteTier || 'basic';
|
|
285
|
+
const durationOpt = (0, _satellite_support.resolveSatelliteDurationOption)(sectorName, tier, this.state.satelliteDurationValue);
|
|
286
|
+
return (0, _satellite_support.buildSatelliteTimelineForSelection)({
|
|
287
|
+
satelliteInstrumentId,
|
|
288
|
+
satelliteSectorLabel,
|
|
289
|
+
satelliteChannel
|
|
290
|
+
}, allFiles, durationOpt);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* MRMS timestamps for a variable, oldest first (matches satellite timelines and left→right time sliders).
|
|
295
|
+
* Limited to the last N hours relative to the newest frame (see mrmsDurationValue).
|
|
296
|
+
* @param {string} variable
|
|
297
|
+
* @returns {number[]}
|
|
298
|
+
*/
|
|
299
|
+
_getFilteredMrmsTimestampsForVariable(variable) {
|
|
300
|
+
var _this$mrmsStatus;
|
|
301
|
+
const raw = (_this$mrmsStatus = this.mrmsStatus) === null || _this$mrmsStatus === void 0 ? void 0 : _this$mrmsStatus[variable];
|
|
302
|
+
if (!raw || !raw.length) return [];
|
|
303
|
+
const hours = (0, _satellite_support.parseTimelineDurationHours)(this.state.mrmsDurationValue);
|
|
304
|
+
let list = [...raw].map(t => Number(t)).filter(t => !Number.isNaN(t)).sort((a, b) => a - b);
|
|
305
|
+
if (hours > 0 && list.length > 0) {
|
|
306
|
+
const latest = list[list.length - 1];
|
|
307
|
+
const cutoff = latest - hours * 3600;
|
|
308
|
+
list = list.filter(t => t >= cutoff);
|
|
309
|
+
}
|
|
310
|
+
return list;
|
|
311
|
+
}
|
|
312
|
+
_nexradListingWindowHours() {
|
|
313
|
+
return (0, _satellite_support.parseTimelineDurationHours)(this.state.nexradDurationValue);
|
|
314
|
+
}
|
|
315
|
+
_getFilteredNexradTimestampsForVariable(rawList) {
|
|
316
|
+
if (!rawList || !rawList.length) return [];
|
|
317
|
+
const hours = this._nexradListingWindowHours();
|
|
318
|
+
let list = [...rawList].map(t => Number(t)).filter(t => !Number.isNaN(t)).sort((a, b) => a - b);
|
|
319
|
+
if (hours > 0 && list.length > 0) {
|
|
320
|
+
const latest = list[list.length - 1];
|
|
321
|
+
const cutoff = latest - hours * 3600;
|
|
322
|
+
list = list.filter(t => t >= cutoff);
|
|
323
|
+
}
|
|
324
|
+
return list;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Cache key for {@link this.nexradTimesByStation} — matches frontend composite keys (tilt + variable group/source).
|
|
329
|
+
*/
|
|
330
|
+
_nexradTimesCacheKey() {
|
|
331
|
+
var _getNexradLevel3Entry;
|
|
332
|
+
const s = this.state;
|
|
333
|
+
if (!s.isNexrad || !s.nexradSite) return null;
|
|
334
|
+
const site = s.nexradSite;
|
|
335
|
+
const variable = s.nexradProduct || 'REF';
|
|
336
|
+
const ds = s.nexradDataSource || 'level2';
|
|
337
|
+
const tiltNum = s.nexradTilt != null ? s.nexradTilt : (0, _nexradTilts.getDefaultRadarTilt)(site);
|
|
338
|
+
const elevNormUse = ds === 'level3' ? _nexrad_level3_catalog.NEXRAD_LEVEL3_ELEV : (0, _nexradTilts.formatTiltForApi)((0, _nexradTilts.clampNexradTiltForVariable)(site, variable, tiltNum));
|
|
339
|
+
const group = ds === 'level3' ? 'l3' : (0, _nexrad_support.variableToNexradGroup)(variable);
|
|
340
|
+
const l3Product = ds === 'level3' ? ((_getNexradLevel3Entry = (0, _nexrad_level3_catalog.getNexradLevel3EntryByRadarKey)(variable)) === null || _getNexradLevel3Entry === void 0 ? void 0 : _getNexradLevel3Entry.product) ?? (variable === 'VEL' ? 'N0G' : variable) : '';
|
|
341
|
+
if (ds === 'level3') {
|
|
342
|
+
return `${site}_l3_${l3Product}_${elevNormUse}`;
|
|
343
|
+
}
|
|
344
|
+
return `${site}_${group}_${elevNormUse}`;
|
|
345
|
+
}
|
|
109
346
|
_emitStateChange() {
|
|
110
|
-
var _this$modelStatus
|
|
347
|
+
var _this$modelStatus;
|
|
111
348
|
const {
|
|
112
349
|
colormap,
|
|
113
350
|
baseUnit
|
|
@@ -116,23 +353,45 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
116
353
|
const displayColormap = this._convertColormapUnits(colormap, baseUnit, toUnit);
|
|
117
354
|
let availableTimestamps = [];
|
|
118
355
|
if (this.state.isMRMS && this.state.variable && this.mrmsStatus) {
|
|
119
|
-
|
|
120
|
-
availableTimestamps = [...timestamps].reverse();
|
|
356
|
+
availableTimestamps = this._getFilteredMrmsTimestampsForVariable(this.state.variable);
|
|
121
357
|
}
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
358
|
+
let availableSatelliteTimestamps = [];
|
|
359
|
+
let satelliteTimeToFileMap = {};
|
|
360
|
+
if (this.state.isSatellite && this.state.satelliteInstrumentId) {
|
|
361
|
+
const timeline = this._computeSatelliteTimeline();
|
|
362
|
+
satelliteTimeToFileMap = timeline.timeToFileMap || {};
|
|
363
|
+
availableSatelliteTimestamps = [...(timeline.unixTimes || [])].map(t => Number(t)).filter(t => !Number.isNaN(t)).sort((a, b) => a - b);
|
|
125
364
|
}
|
|
365
|
+
let availableNexradTimestamps = [];
|
|
366
|
+
let nexradTimeToKeyMap = {};
|
|
367
|
+
let nexradLevel3MotionTimeToKeyMap = {};
|
|
368
|
+
let availableNexradTilts = [];
|
|
369
|
+
if (this.state.isNexrad && this.state.nexradSite) {
|
|
370
|
+
const nk = this._nexradTimesCacheKey();
|
|
371
|
+
const ent = nk ? this.nexradTimesByStation[nk] : null;
|
|
372
|
+
const raw = (ent === null || ent === void 0 ? void 0 : ent.unixTimes) || [];
|
|
373
|
+
availableNexradTimestamps = this._getFilteredNexradTimestampsForVariable(raw);
|
|
374
|
+
nexradTimeToKeyMap = (ent === null || ent === void 0 ? void 0 : ent.timeToKeyMap) || {};
|
|
375
|
+
nexradLevel3MotionTimeToKeyMap = (ent === null || ent === void 0 ? void 0 : ent.level3MotionTimeToKeyMap) || {};
|
|
376
|
+
availableNexradTilts = (0, _nexrad_support.getAvailableNexradTilts)(this.state.nexradSite, this.state.nexradDataSource || 'level2', this.state.nexradProduct || 'REF');
|
|
377
|
+
}
|
|
378
|
+
const availableHours = this.getAvailableForecastHours();
|
|
126
379
|
const eventPayload = {
|
|
127
380
|
...this.state,
|
|
128
381
|
availableModels: this.modelStatus ? Object.keys(this.modelStatus).sort() : [],
|
|
129
|
-
availableRuns: ((_this$
|
|
382
|
+
availableRuns: ((_this$modelStatus = this.modelStatus) === null || _this$modelStatus === void 0 ? void 0 : _this$modelStatus[this.state.model]) || {},
|
|
130
383
|
availableHours: availableHours,
|
|
131
384
|
// <-- Changed from inline calculation
|
|
132
385
|
availableVariables: this.getAvailableVariables(this.state.isMRMS ? 'mrms' : this.state.model),
|
|
133
386
|
// We need to confirm this line is working as expected.
|
|
134
387
|
availableMRMSVariables: this.getAvailableVariables('mrms'),
|
|
135
388
|
availableTimestamps: availableTimestamps,
|
|
389
|
+
availableSatelliteTimestamps,
|
|
390
|
+
satelliteTimeToFileMap,
|
|
391
|
+
availableNexradTimestamps,
|
|
392
|
+
nexradTimeToKeyMap,
|
|
393
|
+
nexradLevel3MotionTimeToKeyMap,
|
|
394
|
+
availableNexradTilts,
|
|
136
395
|
isPlaying: this.isPlaying,
|
|
137
396
|
colormap: displayColormap,
|
|
138
397
|
colormapBaseUnit: toUnit
|
|
@@ -142,28 +401,34 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
142
401
|
async initialize(options = {}) {
|
|
143
402
|
await this.fetchModelStatus(true);
|
|
144
403
|
await this.fetchMRMSStatus(true);
|
|
404
|
+
await this.fetchSatelliteListing(true);
|
|
145
405
|
let initialState = {
|
|
146
406
|
...this.state
|
|
147
407
|
};
|
|
408
|
+
if (initialState.isSatellite && initialState.satelliteInstrumentId) {
|
|
409
|
+
const timeline = this._computeSatelliteTimeline();
|
|
410
|
+
const tsList = [...(timeline.unixTimes || [])].sort((a, b) => a - b);
|
|
411
|
+
if (initialState.satelliteTimestamp == null && tsList.length > 0) {
|
|
412
|
+
initialState.satelliteTimestamp = tsList[tsList.length - 1];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
148
415
|
|
|
149
416
|
// ADD: Logic to handle an initial MRMS state
|
|
150
417
|
if (initialState.isMRMS) {
|
|
151
418
|
const variable = initialState.variable;
|
|
152
419
|
if (variable && this.mrmsStatus && this.mrmsStatus[variable]) {
|
|
153
|
-
const sortedTimestamps =
|
|
154
|
-
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[
|
|
420
|
+
const sortedTimestamps = this._getFilteredMrmsTimestampsForVariable(variable);
|
|
421
|
+
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[sortedTimestamps.length - 1] : null;
|
|
155
422
|
} else {
|
|
156
|
-
// Fallback if the provided variable is not valid
|
|
157
|
-
console.warn(`Initial MRMS variable '${variable}' not found. Using default.`);
|
|
158
423
|
const availableMRMSVars = this.getAvailableVariables('mrms');
|
|
159
424
|
if (availableMRMSVars.length > 0) {
|
|
160
425
|
const firstVar = availableMRMSVars[0];
|
|
161
426
|
initialState.variable = firstVar;
|
|
162
|
-
const sortedTimestamps =
|
|
163
|
-
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[
|
|
427
|
+
const sortedTimestamps = this._getFilteredMrmsTimestampsForVariable(firstVar);
|
|
428
|
+
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[sortedTimestamps.length - 1] : null;
|
|
164
429
|
}
|
|
165
430
|
}
|
|
166
|
-
} else {
|
|
431
|
+
} else if (!initialState.isSatellite && !initialState.isNexrad) {
|
|
167
432
|
// EDIT: This is the existing logic, now in an else block
|
|
168
433
|
const latestRun = findLatestModelRun(this.modelStatus, initialState.model);
|
|
169
434
|
if (latestRun) {
|
|
@@ -181,6 +446,13 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
181
446
|
}
|
|
182
447
|
}
|
|
183
448
|
await this.setState(initialState);
|
|
449
|
+
if (this.state.isNexrad) {
|
|
450
|
+
const manifest = await (0, _nexradTilts.fetchRadarTiltsManifestFromNetwork)();
|
|
451
|
+
if (manifest) (0, _nexradTilts.setRadarTiltsManifest)(manifest);
|
|
452
|
+
if (this.state.nexradSite) {
|
|
453
|
+
await this.refreshNexradTimes();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
184
456
|
if (options.autoRefresh ?? this.autoRefreshEnabled) {
|
|
185
457
|
this.startAutoRefresh(options.refreshInterval ?? this.autoRefreshIntervalSeconds);
|
|
186
458
|
}
|
|
@@ -189,11 +461,11 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
189
461
|
this.pause();
|
|
190
462
|
this.stopAutoRefresh();
|
|
191
463
|
this.dataCache.clear();
|
|
192
|
-
if (this.worker) {
|
|
193
|
-
this.worker.terminate();
|
|
194
|
-
}
|
|
195
464
|
this.callbacks = {};
|
|
196
|
-
|
|
465
|
+
if (this._gridDecodeWorker) {
|
|
466
|
+
this._gridDecodeWorker.terminate();
|
|
467
|
+
this._gridDecodeWorker = null;
|
|
468
|
+
}
|
|
197
469
|
}
|
|
198
470
|
|
|
199
471
|
// --- Public API Methods ---
|
|
@@ -222,6 +494,21 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
222
494
|
this.isPlaying ? this.pause() : this.play();
|
|
223
495
|
}
|
|
224
496
|
step(direction = 1) {
|
|
497
|
+
if (this.state.isSatellite) {
|
|
498
|
+
const timeline = this._computeSatelliteTimeline();
|
|
499
|
+
const availableTimestamps = [...(timeline.unixTimes || [])].sort((a, b) => a - b).map(t => Number(t));
|
|
500
|
+
if (availableTimestamps.length === 0) return;
|
|
501
|
+
const ts = this.state.satelliteTimestamp == null ? null : Number(this.state.satelliteTimestamp);
|
|
502
|
+
const currentIndex = ts == null ? -1 : availableTimestamps.indexOf(ts);
|
|
503
|
+
let nextIndex = currentIndex === -1 ? availableTimestamps.length - 1 : currentIndex + direction;
|
|
504
|
+
const maxIndex = availableTimestamps.length - 1;
|
|
505
|
+
if (nextIndex > maxIndex) nextIndex = 0;
|
|
506
|
+
if (nextIndex < 0) nextIndex = maxIndex;
|
|
507
|
+
this.setState({
|
|
508
|
+
satelliteTimestamp: availableTimestamps[nextIndex]
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
225
512
|
// --- THIS IS THE CORRECTED MRMS LOGIC ---
|
|
226
513
|
if (this.state.isMRMS) {
|
|
227
514
|
const {
|
|
@@ -229,19 +516,16 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
229
516
|
mrmsTimestamp
|
|
230
517
|
} = this.state;
|
|
231
518
|
if (!this.mrmsStatus || !this.mrmsStatus[variable]) {
|
|
232
|
-
console.warn('[Core.step] MRMS status or variable not available.');
|
|
233
519
|
return;
|
|
234
520
|
}
|
|
235
|
-
|
|
236
|
-
// CRITICAL FIX: The UI and state emissions use a REVERSED array (newest first).
|
|
237
|
-
// The step logic MUST use the same reversed array for indexes to match.
|
|
238
|
-
const availableTimestamps = [...(this.mrmsStatus[variable] || [])].reverse();
|
|
521
|
+
const availableTimestamps = this._getFilteredMrmsTimestampsForVariable(variable);
|
|
239
522
|
if (availableTimestamps.length === 0) return;
|
|
240
|
-
const
|
|
523
|
+
const ts = mrmsTimestamp == null ? null : Number(mrmsTimestamp);
|
|
524
|
+
const currentIndex = ts == null ? -1 : availableTimestamps.indexOf(ts);
|
|
241
525
|
if (currentIndex === -1) {
|
|
242
|
-
// If not found, reset to the
|
|
526
|
+
// If not found, reset to the latest frame (end of ascending list)
|
|
243
527
|
this.setState({
|
|
244
|
-
mrmsTimestamp: availableTimestamps[
|
|
528
|
+
mrmsTimestamp: availableTimestamps[availableTimestamps.length - 1]
|
|
245
529
|
});
|
|
246
530
|
return;
|
|
247
531
|
}
|
|
@@ -255,17 +539,29 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
255
539
|
this.setState({
|
|
256
540
|
mrmsTimestamp: newTimestamp
|
|
257
541
|
});
|
|
542
|
+
} else if (this.state.isNexrad) {
|
|
543
|
+
var _this$nexradTimesBySt;
|
|
544
|
+
const nk = this._nexradTimesCacheKey();
|
|
545
|
+
const raw = nk ? (_this$nexradTimesBySt = this.nexradTimesByStation[nk]) === null || _this$nexradTimesBySt === void 0 ? void 0 : _this$nexradTimesBySt.unixTimes : [];
|
|
546
|
+
const availableTimestamps = this._getFilteredNexradTimestampsForVariable(raw || []);
|
|
547
|
+
if (availableTimestamps.length === 0) return;
|
|
548
|
+
const ts = this.state.nexradTimestamp == null ? null : Number(this.state.nexradTimestamp);
|
|
549
|
+
const currentIndex = ts == null ? -1 : availableTimestamps.indexOf(ts);
|
|
550
|
+
let nextIndex = currentIndex === -1 ? availableTimestamps.length - 1 : currentIndex + direction;
|
|
551
|
+
const maxIndex = availableTimestamps.length - 1;
|
|
552
|
+
if (nextIndex > maxIndex) nextIndex = 0;
|
|
553
|
+
if (nextIndex < 0) nextIndex = maxIndex;
|
|
554
|
+
this.setState({
|
|
555
|
+
nexradTimestamp: availableTimestamps[nextIndex]
|
|
556
|
+
});
|
|
258
557
|
} else {
|
|
259
|
-
var _this$modelStatus3;
|
|
260
558
|
const {
|
|
261
|
-
model,
|
|
262
|
-
date,
|
|
263
|
-
run,
|
|
264
559
|
forecastHour
|
|
265
560
|
} = this.state;
|
|
266
|
-
const forecastHours =
|
|
561
|
+
const forecastHours = this.getAvailableForecastHours();
|
|
267
562
|
if (!forecastHours || forecastHours.length === 0) return;
|
|
268
|
-
const
|
|
563
|
+
const fh = Number(forecastHour);
|
|
564
|
+
const currentIndex = forecastHours.indexOf(fh);
|
|
269
565
|
if (currentIndex === -1) return;
|
|
270
566
|
const maxIndex = forecastHours.length - 1;
|
|
271
567
|
let nextIndex = currentIndex + direction;
|
|
@@ -299,8 +595,7 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
299
595
|
async setVariable(variable) {
|
|
300
596
|
// --- NEW CODE: Handle switching TO ptypeRefl on HRRR ---
|
|
301
597
|
if (variable === 'ptypeRefl' && this.state.model === 'hrrr' && this.state.forecastHour === 0) {
|
|
302
|
-
|
|
303
|
-
const availableHours = ((_this$modelStatus4 = this.modelStatus) === null || _this$modelStatus4 === void 0 || (_this$modelStatus4 = _this$modelStatus4[this.state.model]) === null || _this$modelStatus4 === void 0 || (_this$modelStatus4 = _this$modelStatus4[this.state.date]) === null || _this$modelStatus4 === void 0 ? void 0 : _this$modelStatus4[this.state.run]) || [];
|
|
598
|
+
const availableHours = resolveModelRunHours(this.modelStatus, this.state.model, this.state.date, this.state.run).hours || [];
|
|
304
599
|
const firstValidHour = availableHours.find(hour => hour !== 0) || 0;
|
|
305
600
|
await this.setState({
|
|
306
601
|
variable,
|
|
@@ -315,8 +610,17 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
315
610
|
});
|
|
316
611
|
}
|
|
317
612
|
async setModel(modelName) {
|
|
318
|
-
var _this$
|
|
319
|
-
if (modelName === this.state.model || !((_this$
|
|
613
|
+
var _this$modelStatus2;
|
|
614
|
+
if (modelName === this.state.model || !((_this$modelStatus2 = this.modelStatus) !== null && _this$modelStatus2 !== void 0 && _this$modelStatus2[modelName])) return;
|
|
615
|
+
if (this.state.isSatellite) {
|
|
616
|
+
await this.setState({
|
|
617
|
+
isSatellite: false,
|
|
618
|
+
satelliteInstrumentId: null,
|
|
619
|
+
satelliteSectorLabel: null,
|
|
620
|
+
satelliteChannel: null,
|
|
621
|
+
satelliteTimestamp: null
|
|
622
|
+
});
|
|
623
|
+
}
|
|
320
624
|
const latestRun = findLatestModelRun(this.modelStatus, modelName);
|
|
321
625
|
if (latestRun) {
|
|
322
626
|
// --- NEW CODE: Determine initial forecast hour ---
|
|
@@ -324,9 +628,7 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
324
628
|
|
|
325
629
|
// If switching to HRRR with ptypeRefl, start at hour 1 instead of 0
|
|
326
630
|
if (modelName === 'hrrr' && this.state.variable === 'ptypeRefl') {
|
|
327
|
-
|
|
328
|
-
const availableHours = ((_this$modelStatus6 = this.modelStatus) === null || _this$modelStatus6 === void 0 || (_this$modelStatus6 = _this$modelStatus6[modelName]) === null || _this$modelStatus6 === void 0 || (_this$modelStatus6 = _this$modelStatus6[latestRun.date]) === null || _this$modelStatus6 === void 0 ? void 0 : _this$modelStatus6[latestRun.run]) || [];
|
|
329
|
-
// Find the first valid hour (should be 1)
|
|
631
|
+
const availableHours = resolveModelRunHours(this.modelStatus, modelName, latestRun.date, latestRun.run).hours || [];
|
|
330
632
|
initialHour = availableHours.find(hour => hour !== 0) || 0;
|
|
331
633
|
}
|
|
332
634
|
// --- END NEW CODE ---
|
|
@@ -356,11 +658,16 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
356
658
|
});
|
|
357
659
|
}
|
|
358
660
|
async setMRMSVariable(variable) {
|
|
359
|
-
const sortedTimestamps =
|
|
360
|
-
const initialTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[
|
|
661
|
+
const sortedTimestamps = this._getFilteredMrmsTimestampsForVariable(variable);
|
|
662
|
+
const initialTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[sortedTimestamps.length - 1] : null;
|
|
361
663
|
await this.setState({
|
|
362
664
|
variable,
|
|
363
665
|
isMRMS: true,
|
|
666
|
+
isSatellite: false,
|
|
667
|
+
satelliteInstrumentId: null,
|
|
668
|
+
satelliteSectorLabel: null,
|
|
669
|
+
satelliteChannel: null,
|
|
670
|
+
satelliteTimestamp: null,
|
|
364
671
|
mrmsTimestamp: initialTimestamp
|
|
365
672
|
});
|
|
366
673
|
}
|
|
@@ -370,42 +677,184 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
370
677
|
mrmsTimestamp: timestamp
|
|
371
678
|
});
|
|
372
679
|
}
|
|
680
|
+
async setSatelliteTimestamp(timestamp) {
|
|
681
|
+
if (!this.state.isSatellite) return;
|
|
682
|
+
await this.setState({
|
|
683
|
+
satelliteTimestamp: timestamp != null ? Number(timestamp) : null
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* How many hours of satellite frames to include in the timeline (positive, at most 12 hours).
|
|
689
|
+
* API default: `layerOptions.satelliteDurationValue` on construction.
|
|
690
|
+
*/
|
|
691
|
+
async setSatelliteDurationValue(value) {
|
|
692
|
+
const v = (0, _satellite_support.formatTimelineDurationValue)(value);
|
|
693
|
+
await this.setState({
|
|
694
|
+
satelliteDurationValue: v
|
|
695
|
+
});
|
|
696
|
+
if (!this.state.isSatellite || !this.state.satelliteInstrumentId) return;
|
|
697
|
+
const timeline = this._computeSatelliteTimeline();
|
|
698
|
+
const tsList = [...(timeline.unixTimes || [])].map(t => Number(t)).filter(t => !Number.isNaN(t)).sort((a, b) => a - b);
|
|
699
|
+
if (tsList.length === 0) return;
|
|
700
|
+
const cur = this.state.satelliteTimestamp;
|
|
701
|
+
const curN = cur == null ? null : Number(cur);
|
|
702
|
+
if (curN == null || !tsList.includes(curN)) {
|
|
703
|
+
await this.setState({
|
|
704
|
+
satelliteTimestamp: tsList[tsList.length - 1]
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Set satellite view using spacecraft id, sector, and channel/product.
|
|
711
|
+
* Omitted fields keep the current selection; when not in satellite mode, missing fields use GOES-19 East CONUS / C13 defaults.
|
|
712
|
+
* @param {{ satelliteId?: string, sector?: string, satelliteSector?: string, satelliteProduct?: string, satelliteChannel?: string, satelliteTimestamp?: number|null }} opts
|
|
713
|
+
*/
|
|
714
|
+
async setSatelliteSelection(opts = {}) {
|
|
715
|
+
const cur = this.state.isSatellite ? this.state : null;
|
|
716
|
+
const tsArg = opts.satelliteTimestamp !== undefined ? opts.satelliteTimestamp : this.state.isSatellite && this.state.satelliteTimestamp != null ? Number(this.state.satelliteTimestamp) : undefined;
|
|
717
|
+
return this.switchMode({
|
|
718
|
+
mode: 'satellite',
|
|
719
|
+
satelliteId: opts.satelliteId ?? (cur === null || cur === void 0 ? void 0 : cur.satelliteInstrumentId) ?? 'GOES19-EAST',
|
|
720
|
+
satelliteSector: opts.satelliteSector ?? opts.sector ?? (cur === null || cur === void 0 ? void 0 : cur.satelliteSectorLabel) ?? 'conus',
|
|
721
|
+
satelliteProduct: opts.satelliteProduct ?? opts.satelliteChannel ?? (cur === null || cur === void 0 ? void 0 : cur.satelliteChannel) ?? 'C13',
|
|
722
|
+
satelliteTimestamp: tsArg
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* How many hours of MRMS frames to include in the timeline (positive, at most 12 hours).
|
|
728
|
+
* API default: `layerOptions.mrmsDurationValue` on construction.
|
|
729
|
+
*/
|
|
730
|
+
async setMRMSDurationValue(value) {
|
|
731
|
+
const v = (0, _satellite_support.formatTimelineDurationValue)(value);
|
|
732
|
+
await this.setState({
|
|
733
|
+
mrmsDurationValue: v
|
|
734
|
+
});
|
|
735
|
+
if (!this.state.isMRMS || !this.state.variable) return;
|
|
736
|
+
const filtered = this._getFilteredMrmsTimestampsForVariable(this.state.variable);
|
|
737
|
+
if (filtered.length === 0) return;
|
|
738
|
+
const cur = this.state.mrmsTimestamp;
|
|
739
|
+
const curN = cur == null ? null : Number(cur);
|
|
740
|
+
if (curN == null || !filtered.includes(curN)) {
|
|
741
|
+
await this.setState({
|
|
742
|
+
mrmsTimestamp: filtered[filtered.length - 1]
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* NEXRAD sweep listing / scrub window in hours (independent of MRMS duration).
|
|
749
|
+
* API default: `layerOptions.nexradDurationValue` on construction.
|
|
750
|
+
*/
|
|
751
|
+
async setNexradDurationValue(value) {
|
|
752
|
+
var _this$nexradTimesBySt2;
|
|
753
|
+
const v = (0, _satellite_support.formatTimelineDurationValue)(value);
|
|
754
|
+
await this.setState({
|
|
755
|
+
nexradDurationValue: v
|
|
756
|
+
});
|
|
757
|
+
if (!this.state.isNexrad || !this.state.nexradSite) return;
|
|
758
|
+
await this.refreshNexradTimes();
|
|
759
|
+
const nk = this._nexradTimesCacheKey();
|
|
760
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(nk ? ((_this$nexradTimesBySt2 = this.nexradTimesByStation[nk]) === null || _this$nexradTimesBySt2 === void 0 ? void 0 : _this$nexradTimesBySt2.unixTimes) || [] : []);
|
|
761
|
+
if (filtered.length === 0) return;
|
|
762
|
+
const cur = this.state.nexradTimestamp;
|
|
763
|
+
const curN = cur == null ? null : Number(cur);
|
|
764
|
+
if (curN == null || !filtered.includes(curN)) {
|
|
765
|
+
await this.setState({
|
|
766
|
+
nexradTimestamp: filtered[filtered.length - 1]
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
373
770
|
async switchMode(options) {
|
|
374
|
-
|
|
771
|
+
let {
|
|
375
772
|
mode,
|
|
376
773
|
variable,
|
|
377
774
|
model,
|
|
378
775
|
forecastHour,
|
|
379
|
-
mrmsTimestamp
|
|
776
|
+
mrmsTimestamp,
|
|
777
|
+
satelliteTimestamp
|
|
380
778
|
} = options;
|
|
381
|
-
if (!mode
|
|
382
|
-
console.error("switchMode requires 'mode' ('mrms' | 'model') and 'variable' properties.");
|
|
779
|
+
if (!mode) {
|
|
383
780
|
return;
|
|
384
781
|
}
|
|
385
782
|
if (mode === 'model' && !model) {
|
|
386
|
-
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if ((mode === 'mrms' || mode === 'model') && !variable) {
|
|
387
786
|
return;
|
|
388
787
|
}
|
|
389
788
|
let targetState = {};
|
|
390
|
-
if (mode === '
|
|
789
|
+
if (mode === 'satellite') {
|
|
790
|
+
var _this$satelliteListin2;
|
|
791
|
+
const satelliteInstrumentId = options.satelliteId ?? 'GOES19-EAST';
|
|
792
|
+
const satelliteSectorLabel = (0, _satellite_support.resolveSatelliteSectorLabel)(options.satelliteSector ?? options.sector ?? 'conus');
|
|
793
|
+
const satelliteChannel = options.satelliteProduct ?? options.satelliteChannel ?? variable ?? 'C13';
|
|
794
|
+
const channelToken = satelliteChannel;
|
|
795
|
+
// Emit satellite mode immediately so map layers (e.g. model grid) clear before listing fetch finishes.
|
|
796
|
+
await this.setState({
|
|
797
|
+
isSatellite: true,
|
|
798
|
+
isMRMS: false,
|
|
799
|
+
isNexrad: false,
|
|
800
|
+
satelliteInstrumentId,
|
|
801
|
+
satelliteSectorLabel,
|
|
802
|
+
satelliteChannel,
|
|
803
|
+
variable: channelToken,
|
|
804
|
+
satelliteTimestamp: null,
|
|
805
|
+
mrmsTimestamp: null,
|
|
806
|
+
date: null,
|
|
807
|
+
run: null,
|
|
808
|
+
forecastHour: 0
|
|
809
|
+
});
|
|
810
|
+
await this.fetchSatelliteListing(true);
|
|
811
|
+
const allFiles = ((_this$satelliteListin2 = this.satelliteListing) === null || _this$satelliteListin2 === void 0 || (_this$satelliteListin2 = _this$satelliteListin2.objects) === null || _this$satelliteListin2 === void 0 ? void 0 : _this$satelliteListin2.map(o => o.key)) || [];
|
|
812
|
+
const sectorName = satelliteSectorLabel;
|
|
813
|
+
const tier = this.state.satelliteTier || 'basic';
|
|
814
|
+
const durationOpt = (0, _satellite_support.resolveSatelliteDurationOption)(sectorName, tier, this.state.satelliteDurationValue);
|
|
815
|
+
const timeline = (0, _satellite_support.buildSatelliteTimelineForSelection)({
|
|
816
|
+
satelliteInstrumentId,
|
|
817
|
+
satelliteSectorLabel,
|
|
818
|
+
satelliteChannel
|
|
819
|
+
}, allFiles, durationOpt);
|
|
820
|
+
const tsList = [...(timeline.unixTimes || [])].sort((a, b) => a - b);
|
|
821
|
+
let finalTs = satelliteTimestamp !== undefined ? satelliteTimestamp : null;
|
|
822
|
+
if (finalTs == null && tsList.length > 0) {
|
|
823
|
+
finalTs = tsList[tsList.length - 1];
|
|
824
|
+
}
|
|
825
|
+
await this.setState({
|
|
826
|
+
satelliteTimestamp: finalTs != null ? Number(finalTs) : null
|
|
827
|
+
});
|
|
828
|
+
return;
|
|
829
|
+
} else if (mode === 'mrms') {
|
|
830
|
+
const sortedTimestamps = this._getFilteredMrmsTimestampsForVariable(variable);
|
|
391
831
|
let finalTimestamp = mrmsTimestamp;
|
|
392
832
|
if (finalTimestamp === undefined) {
|
|
393
|
-
|
|
394
|
-
|
|
833
|
+
finalTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[sortedTimestamps.length - 1] : null;
|
|
834
|
+
} else if (sortedTimestamps.length > 0) {
|
|
835
|
+
const n = Number(finalTimestamp);
|
|
836
|
+
if (!sortedTimestamps.includes(n)) {
|
|
837
|
+
finalTimestamp = sortedTimestamps[sortedTimestamps.length - 1];
|
|
838
|
+
}
|
|
395
839
|
}
|
|
396
840
|
targetState = {
|
|
397
841
|
isMRMS: true,
|
|
842
|
+
isSatellite: false,
|
|
843
|
+
isNexrad: false,
|
|
398
844
|
variable: variable,
|
|
399
845
|
mrmsTimestamp: finalTimestamp,
|
|
400
846
|
model: this.state.model,
|
|
401
847
|
date: null,
|
|
402
848
|
run: null,
|
|
403
|
-
forecastHour: 0
|
|
849
|
+
forecastHour: 0,
|
|
850
|
+
satelliteInstrumentId: null,
|
|
851
|
+
satelliteSectorLabel: null,
|
|
852
|
+
satelliteChannel: null,
|
|
853
|
+
satelliteTimestamp: null
|
|
404
854
|
};
|
|
405
855
|
} else if (mode === 'model') {
|
|
406
856
|
const latestRun = findLatestModelRun(this.modelStatus, model);
|
|
407
857
|
if (!latestRun) {
|
|
408
|
-
console.error(`Could not find a valid run for model: ${model}`);
|
|
409
858
|
return;
|
|
410
859
|
}
|
|
411
860
|
|
|
@@ -414,50 +863,289 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
414
863
|
|
|
415
864
|
// If switching to HRRR with ptypeRefl and hour is 0, use hour 1
|
|
416
865
|
if (model === 'hrrr' && variable === 'ptypeRefl' && initialHour === 0) {
|
|
417
|
-
|
|
418
|
-
const availableHours = ((_this$modelStatus7 = this.modelStatus) === null || _this$modelStatus7 === void 0 || (_this$modelStatus7 = _this$modelStatus7[model]) === null || _this$modelStatus7 === void 0 || (_this$modelStatus7 = _this$modelStatus7[latestRun.date]) === null || _this$modelStatus7 === void 0 ? void 0 : _this$modelStatus7[latestRun.run]) || [];
|
|
866
|
+
const availableHours = resolveModelRunHours(this.modelStatus, model, latestRun.date, latestRun.run).hours || [];
|
|
419
867
|
initialHour = availableHours.find(hour => hour !== 0) || 0;
|
|
420
868
|
}
|
|
421
869
|
// --- END NEW CODE ---
|
|
422
870
|
|
|
423
871
|
targetState = {
|
|
424
872
|
isMRMS: false,
|
|
873
|
+
isSatellite: false,
|
|
874
|
+
isNexrad: false,
|
|
425
875
|
model: model,
|
|
426
876
|
variable: variable,
|
|
427
877
|
date: latestRun.date,
|
|
428
878
|
run: latestRun.run,
|
|
429
879
|
forecastHour: initialHour,
|
|
430
880
|
// <-- Changed
|
|
431
|
-
mrmsTimestamp: null
|
|
881
|
+
mrmsTimestamp: null,
|
|
882
|
+
satelliteInstrumentId: null,
|
|
883
|
+
satelliteSectorLabel: null,
|
|
884
|
+
satelliteChannel: null,
|
|
885
|
+
satelliteTimestamp: null
|
|
432
886
|
};
|
|
887
|
+
} else if (mode === 'nexrad') {
|
|
888
|
+
const nexradProduct = options.nexradProduct || 'REF';
|
|
889
|
+
const p = nexradProduct.toUpperCase();
|
|
890
|
+
const site = options.nexradSite ?? null;
|
|
891
|
+
let tilt = options.nexradTilt != null ? Number(options.nexradTilt) : site != null ? (0, _nexradTilts.getDefaultRadarTilt)(site) : null;
|
|
892
|
+
await this.setState({
|
|
893
|
+
isNexrad: true,
|
|
894
|
+
isMRMS: false,
|
|
895
|
+
isSatellite: false,
|
|
896
|
+
nexradSite: site,
|
|
897
|
+
nexradProduct: p,
|
|
898
|
+
nexradTilt: tilt,
|
|
899
|
+
nexradTimestamp: options.nexradTimestamp != null ? Number(options.nexradTimestamp) : null,
|
|
900
|
+
nexradStormRelative: options.nexradStormRelative === true,
|
|
901
|
+
nexradShowSitesPicker: options.nexradShowSitesPicker !== false,
|
|
902
|
+
...(options.nexradDurationValue != null ? {
|
|
903
|
+
nexradDurationValue: (0, _satellite_support.formatTimelineDurationValue)(options.nexradDurationValue)
|
|
904
|
+
} : {}),
|
|
905
|
+
mrmsTimestamp: null,
|
|
906
|
+
satelliteInstrumentId: null,
|
|
907
|
+
satelliteSectorLabel: null,
|
|
908
|
+
satelliteChannel: null,
|
|
909
|
+
satelliteTimestamp: null,
|
|
910
|
+
date: null,
|
|
911
|
+
run: null,
|
|
912
|
+
forecastHour: 0
|
|
913
|
+
});
|
|
914
|
+
const manifest = await (0, _nexradTilts.fetchRadarTiltsManifestFromNetwork)();
|
|
915
|
+
if (manifest) (0, _nexradTilts.setRadarTiltsManifest)(manifest);
|
|
916
|
+
if (site) {
|
|
917
|
+
var _this$nexradTimesBySt3;
|
|
918
|
+
await this.refreshNexradTimes();
|
|
919
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(((_this$nexradTimesBySt3 = this.nexradTimesByStation[this._nexradTimesCacheKey()]) === null || _this$nexradTimesBySt3 === void 0 ? void 0 : _this$nexradTimesBySt3.unixTimes) || []);
|
|
920
|
+
let ts = options.nexradTimestamp != null ? Number(options.nexradTimestamp) : this.state.nexradTimestamp;
|
|
921
|
+
if (ts == null && filtered.length > 0) ts = filtered[filtered.length - 1];else if (ts != null && filtered.length > 0 && !filtered.includes(ts)) {
|
|
922
|
+
ts = filtered[filtered.length - 1];
|
|
923
|
+
}
|
|
924
|
+
await this.setState({
|
|
925
|
+
nexradTimestamp: ts
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
433
929
|
} else {
|
|
434
|
-
console.error(`Invalid mode specified in switchMode: '${mode}'`);
|
|
435
930
|
return;
|
|
436
931
|
}
|
|
437
932
|
await this.setState(targetState);
|
|
438
933
|
}
|
|
439
934
|
|
|
935
|
+
/**
|
|
936
|
+
* Keep `nexradTilt` on a coalesced elevation (same rules as aguacero-frontend tilt controls).
|
|
937
|
+
*/
|
|
938
|
+
async _snapNexradTiltToAvailableOptions() {
|
|
939
|
+
const s = this.state;
|
|
940
|
+
if (!s.isNexrad || !s.nexradSite) return;
|
|
941
|
+
const raw = (0, _nexrad_support.getRawNexradTiltsForCoalesce)(s.nexradSite, s.nexradDataSource || 'level2', s.nexradProduct || 'REF');
|
|
942
|
+
if (!raw.length) return;
|
|
943
|
+
const t = s.nexradTilt;
|
|
944
|
+
const target = t != null && Number.isFinite(Number(t)) ? Number(t) : (0, _nexradTilts.getDefaultRadarTilt)(s.nexradSite);
|
|
945
|
+
const canonical = (0, _nexradTiltCoalesce.nexradLayerTiltToDisplayOption)(target, raw);
|
|
946
|
+
const match = (a, b) => Math.abs(Number(a) - Number(b)) < 1e-4;
|
|
947
|
+
if (match(s.nexradTilt, canonical)) return;
|
|
948
|
+
await this.setState({
|
|
949
|
+
nexradTilt: canonical
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
async refreshNexradTimes() {
|
|
953
|
+
var _getNexradLevel3Entry2, _out$level3MotionUnix, _out$level3MotionUnix2;
|
|
954
|
+
const s0 = this.state;
|
|
955
|
+
if (!s0.isNexrad || !s0.nexradSite) return;
|
|
956
|
+
await this._snapNexradTiltToAvailableOptions();
|
|
957
|
+
const s = this.state;
|
|
958
|
+
const listingHours = this._nexradListingWindowHours();
|
|
959
|
+
const out = await (0, _nexrad_support.fetchNexradTimesListing)({
|
|
960
|
+
stationId: s.nexradSite,
|
|
961
|
+
variable: s.nexradProduct || 'REF',
|
|
962
|
+
elev: s.nexradTilt,
|
|
963
|
+
source: s.nexradDataSource || 'level2',
|
|
964
|
+
level3StormRelative: s.nexradStormRelative,
|
|
965
|
+
level3Product: (_getNexradLevel3Entry2 = (0, _nexrad_level3_catalog.getNexradLevel3EntryByRadarKey)(s.nexradProduct)) === null || _getNexradLevel3Entry2 === void 0 ? void 0 : _getNexradLevel3Entry2.product,
|
|
966
|
+
listingWindowHours: listingHours
|
|
967
|
+
});
|
|
968
|
+
const nk = this._nexradTimesCacheKey();
|
|
969
|
+
if (!nk) return;
|
|
970
|
+
this.nexradTimesByStation[nk] = {
|
|
971
|
+
unixTimes: out.unixTimes,
|
|
972
|
+
timeToKeyMap: out.timeToKeyMap,
|
|
973
|
+
level3MotionTimeToKeyMap: out.level3MotionTimeToKeyMap,
|
|
974
|
+
listWindowHours: listingHours
|
|
975
|
+
};
|
|
976
|
+
if (out.level3MotionKey && (_out$level3MotionUnix = out.level3MotionUnixTimes) !== null && _out$level3MotionUnix !== void 0 && _out$level3MotionUnix.length) {
|
|
977
|
+
this.nexradTimesByStation[out.level3MotionKey] = {
|
|
978
|
+
unixTimes: out.level3MotionUnixTimes,
|
|
979
|
+
timeToKeyMap: out.level3MotionTimeToKeyMap,
|
|
980
|
+
listWindowHours: listingHours
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
if (out.l2StormMotionListKey && (_out$level3MotionUnix2 = out.level3MotionUnixTimes) !== null && _out$level3MotionUnix2 !== void 0 && _out$level3MotionUnix2.length) {
|
|
984
|
+
this.nexradTimesByStation[out.l2StormMotionListKey] = {
|
|
985
|
+
unixTimes: out.level3MotionUnixTimes,
|
|
986
|
+
timeToKeyMap: out.level3MotionTimeToKeyMap,
|
|
987
|
+
listWindowHours: listingHours
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(out.unixTimes || []);
|
|
991
|
+
const map = out.timeToKeyMap || {};
|
|
992
|
+
if (filtered.length > 0) {
|
|
993
|
+
const cur = this.state.nexradTimestamp != null ? Number(this.state.nexradTimestamp) : null;
|
|
994
|
+
const hasKey = cur != null && Object.prototype.hasOwnProperty.call(map, String(cur));
|
|
995
|
+
const inWindow = cur != null && filtered.includes(cur);
|
|
996
|
+
if (cur == null || !inWindow || !hasKey) {
|
|
997
|
+
this.state.nexradTimestamp = filtered[filtered.length - 1];
|
|
998
|
+
}
|
|
999
|
+
} else if (this.state.nexradTimestamp != null) {
|
|
1000
|
+
this.state.nexradTimestamp = null;
|
|
1001
|
+
}
|
|
1002
|
+
this._emitStateChange();
|
|
1003
|
+
}
|
|
1004
|
+
async setNexradSite(siteId) {
|
|
1005
|
+
var _this$nexradTimesBySt4;
|
|
1006
|
+
if (!this.state.isNexrad) return;
|
|
1007
|
+
const tilt = siteId ? (0, _nexradTilts.getDefaultRadarTilt)(siteId) : null;
|
|
1008
|
+
await this.setState({
|
|
1009
|
+
nexradSite: siteId || null,
|
|
1010
|
+
nexradTilt: tilt,
|
|
1011
|
+
nexradTimestamp: null
|
|
1012
|
+
});
|
|
1013
|
+
await this.refreshNexradTimes();
|
|
1014
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(((_this$nexradTimesBySt4 = this.nexradTimesByStation[this._nexradTimesCacheKey()]) === null || _this$nexradTimesBySt4 === void 0 ? void 0 : _this$nexradTimesBySt4.unixTimes) || []);
|
|
1015
|
+
if (filtered.length > 0) {
|
|
1016
|
+
await this.setState({
|
|
1017
|
+
nexradTimestamp: filtered[filtered.length - 1]
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async setNexradProduct(product) {
|
|
1022
|
+
var _this$nexradTimesBySt5;
|
|
1023
|
+
if (!this.state.isNexrad) return;
|
|
1024
|
+
const p = (product || 'REF').toUpperCase();
|
|
1025
|
+
await this.setState({
|
|
1026
|
+
nexradProduct: p
|
|
1027
|
+
});
|
|
1028
|
+
await this.refreshNexradTimes();
|
|
1029
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(((_this$nexradTimesBySt5 = this.nexradTimesByStation[this._nexradTimesCacheKey()]) === null || _this$nexradTimesBySt5 === void 0 ? void 0 : _this$nexradTimesBySt5.unixTimes) || []);
|
|
1030
|
+
if (filtered.length > 0) {
|
|
1031
|
+
await this.setState({
|
|
1032
|
+
nexradTimestamp: filtered[filtered.length - 1]
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async setNexradTilt(tilt) {
|
|
1037
|
+
if (!this.state.isNexrad || !this.state.nexradSite) return;
|
|
1038
|
+
await this.setState({
|
|
1039
|
+
nexradTilt: tilt != null ? Number(tilt) : null
|
|
1040
|
+
});
|
|
1041
|
+
await this.refreshNexradTimes();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Opt-in storm-relative velocity (L3: N0G + N0S). When off (default), only base radial velocity is used (faster).
|
|
1046
|
+
* @param {boolean} enabled
|
|
1047
|
+
*/
|
|
1048
|
+
async setNexradStormRelative(enabled) {
|
|
1049
|
+
if (!this.state.isNexrad) return;
|
|
1050
|
+
await this.setState({
|
|
1051
|
+
nexradStormRelative: Boolean(enabled)
|
|
1052
|
+
});
|
|
1053
|
+
await this.refreshNexradTimes();
|
|
1054
|
+
}
|
|
1055
|
+
async setNexradTimestamp(ts) {
|
|
1056
|
+
if (!this.state.isNexrad) return;
|
|
1057
|
+
await this.setState({
|
|
1058
|
+
nexradTimestamp: ts != null ? Number(ts) : null
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
440
1062
|
// --- Data and Calculation Methods ---
|
|
441
1063
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
1064
|
+
_ensureGridDecodeWorker() {
|
|
1065
|
+
if (this._gridDecodeWorkerDisabled) {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
if (this._gridDecodeWorker) {
|
|
1069
|
+
return this._gridDecodeWorker;
|
|
1070
|
+
}
|
|
1071
|
+
if (typeof Worker === 'undefined') {
|
|
1072
|
+
this._gridDecodeWorkerDisabled = true;
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
try {
|
|
1076
|
+
this._gridDecodeWorker = new Worker(new URL('./gridDecodeWorker.js', import.meta.url), {
|
|
1077
|
+
type: 'module'
|
|
1078
|
+
});
|
|
1079
|
+
this._gridDecodeWorker.addEventListener('error', () => {
|
|
1080
|
+
this._gridDecodeWorkerDisabled = true;
|
|
1081
|
+
if (this._gridDecodeWorker) {
|
|
1082
|
+
this._gridDecodeWorker.terminate();
|
|
1083
|
+
this._gridDecodeWorker = null;
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
} catch {
|
|
1087
|
+
this._gridDecodeWorkerDisabled = true;
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
return this._gridDecodeWorker;
|
|
1091
|
+
}
|
|
448
1092
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1093
|
+
/**
|
|
1094
|
+
* Offloads zstd + delta decode + transform to a Worker when available; falls back to main-thread
|
|
1095
|
+
* {@link processCompressedGrid}. Uses a copy for postMessage transfer so the original `compressedData`
|
|
1096
|
+
* stays valid if the Worker path fails.
|
|
1097
|
+
*/
|
|
1098
|
+
_decodeGridPayload(compressedData, encoding) {
|
|
1099
|
+
const worker = this._ensureGridDecodeWorker();
|
|
1100
|
+
if (!worker) {
|
|
1101
|
+
return Promise.resolve((0, _gridDecodePipeline.processCompressedGrid)(compressedData, encoding));
|
|
454
1102
|
}
|
|
455
|
-
|
|
456
|
-
return new
|
|
1103
|
+
const payload = compressedData.slice();
|
|
1104
|
+
return new Promise((resolve, reject) => {
|
|
1105
|
+
const id = ++this._gridDecodeMsgId;
|
|
1106
|
+
const onMsg = e => {
|
|
1107
|
+
if (!e.data || e.data.id !== id) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
worker.removeEventListener('message', onMsg);
|
|
1111
|
+
worker.removeEventListener('error', onErr);
|
|
1112
|
+
if (e.data.error) {
|
|
1113
|
+
reject(new Error(e.data.error));
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const data = new Uint8Array(e.data.dataBuffer, e.data.dataByteOffset, e.data.dataByteLength);
|
|
1117
|
+
resolve({
|
|
1118
|
+
data,
|
|
1119
|
+
encoding: e.data.encoding
|
|
1120
|
+
});
|
|
1121
|
+
};
|
|
1122
|
+
const onErr = err => {
|
|
1123
|
+
worker.removeEventListener('message', onMsg);
|
|
1124
|
+
worker.removeEventListener('error', onErr);
|
|
1125
|
+
reject(err);
|
|
1126
|
+
};
|
|
1127
|
+
worker.addEventListener('message', onMsg);
|
|
1128
|
+
worker.addEventListener('error', onErr);
|
|
1129
|
+
try {
|
|
1130
|
+
worker.postMessage({
|
|
1131
|
+
id,
|
|
1132
|
+
encoding,
|
|
1133
|
+
compressedBuffer: payload.buffer,
|
|
1134
|
+
compressedByteOffset: payload.byteOffset,
|
|
1135
|
+
compressedByteLength: payload.byteLength
|
|
1136
|
+
}, [payload.buffer]);
|
|
1137
|
+
} catch (err) {
|
|
1138
|
+
worker.removeEventListener('message', onMsg);
|
|
1139
|
+
worker.removeEventListener('error', onErr);
|
|
1140
|
+
reject(err);
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
457
1143
|
}
|
|
458
1144
|
async _loadGridData(state) {
|
|
459
1145
|
if (this.isReactNative) {
|
|
460
|
-
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
if (state.isNexrad) {
|
|
461
1149
|
return null;
|
|
462
1150
|
}
|
|
463
1151
|
const {
|
|
@@ -491,17 +1179,6 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
491
1179
|
if (this.dataCache.has(dataUrlIdentifier)) {
|
|
492
1180
|
return this.dataCache.get(dataUrlIdentifier);
|
|
493
1181
|
}
|
|
494
|
-
|
|
495
|
-
// --- EDITED ---
|
|
496
|
-
// If we are in React Native, this function should NOT do any work.
|
|
497
|
-
// The native WeatherFrameProcessorModule is now responsible for all data loading.
|
|
498
|
-
// This function might still be called by a "cache miss" fallback, but it
|
|
499
|
-
// should not fetch data from JS anymore. We return null so the fallback knows
|
|
500
|
-
// that the native module is the only source of truth for new data.
|
|
501
|
-
if (this.isReactNative) {
|
|
502
|
-
console.warn(`_loadGridData was called in React Native for ${dataUrlIdentifier}. This should be handled by the native module. Returning null.`);
|
|
503
|
-
return null;
|
|
504
|
-
}
|
|
505
1182
|
const abortController = new AbortController();
|
|
506
1183
|
this.abortControllers.set(dataUrlIdentifier, abortController);
|
|
507
1184
|
const loadPromise = (async () => {
|
|
@@ -529,38 +1206,18 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
529
1206
|
encoding
|
|
530
1207
|
} = await response.json();
|
|
531
1208
|
const compressedData = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
resolve,
|
|
538
|
-
reject
|
|
539
|
-
});
|
|
540
|
-
});
|
|
541
|
-
this.worker.postMessage({
|
|
542
|
-
requestId,
|
|
543
|
-
compressedData,
|
|
544
|
-
encoding
|
|
545
|
-
}, [compressedData.buffer]);
|
|
546
|
-
const result = await workerPromise;
|
|
547
|
-
const finalData = result.data;
|
|
548
|
-
const transformedData = new Uint8Array(finalData.length);
|
|
549
|
-
for (let i = 0; i < finalData.length; i++) {
|
|
550
|
-
const signedValue = finalData[i] > 127 ? finalData[i] - 256 : finalData[i];
|
|
551
|
-
transformedData[i] = signedValue + 128;
|
|
1209
|
+
let gridPayload;
|
|
1210
|
+
try {
|
|
1211
|
+
gridPayload = await this._decodeGridPayload(compressedData, encoding);
|
|
1212
|
+
} catch {
|
|
1213
|
+
gridPayload = (0, _gridDecodePipeline.processCompressedGrid)(compressedData, encoding);
|
|
552
1214
|
}
|
|
553
1215
|
this.abortControllers.delete(dataUrlIdentifier);
|
|
554
1216
|
return {
|
|
555
|
-
data:
|
|
556
|
-
encoding
|
|
1217
|
+
data: gridPayload.data,
|
|
1218
|
+
encoding: gridPayload.encoding
|
|
557
1219
|
};
|
|
558
1220
|
} catch (error) {
|
|
559
|
-
if (error.name === 'AbortError') {
|
|
560
|
-
console.log(`Request cancelled for ${resourcePath}`);
|
|
561
|
-
} else {
|
|
562
|
-
console.error(`Failed to load data for path ${resourcePath}:`, error);
|
|
563
|
-
}
|
|
564
1221
|
this.dataCache.delete(dataUrlIdentifier);
|
|
565
1222
|
this.abortControllers.delete(dataUrlIdentifier);
|
|
566
1223
|
return null;
|
|
@@ -578,9 +1235,11 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
578
1235
|
// Clear both maps
|
|
579
1236
|
this.abortControllers.clear();
|
|
580
1237
|
this.dataCache.clear();
|
|
581
|
-
console.log('All pending requests cancelled');
|
|
582
1238
|
}
|
|
583
1239
|
async getValueAtLngLat(lng, lat) {
|
|
1240
|
+
if (this.state.isSatellite || this.state.isNexrad) {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
584
1243
|
const {
|
|
585
1244
|
variable,
|
|
586
1245
|
isMRMS,
|
|
@@ -968,7 +1627,6 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
968
1627
|
y
|
|
969
1628
|
};
|
|
970
1629
|
} catch (error) {
|
|
971
|
-
console.warn(`[GridAccessor] RGEM polar stereographic conversion failed for ${lat}, ${lon}:`, error);
|
|
972
1630
|
return {
|
|
973
1631
|
x: -1,
|
|
974
1632
|
y: -1
|
|
@@ -976,91 +1634,8 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
976
1634
|
}
|
|
977
1635
|
}
|
|
978
1636
|
|
|
979
|
-
// ---
|
|
980
|
-
|
|
981
|
-
createWorker() {
|
|
982
|
-
if (this.isReactNative) return null;
|
|
983
|
-
const workerCode = `
|
|
984
|
-
import { decompress } from 'https://cdn.skypack.dev/fzstd@0.1.1';
|
|
1637
|
+
// --- Status Methods ---
|
|
985
1638
|
|
|
986
|
-
function _reconstructData(decompressedDeltas, encoding) {
|
|
987
|
-
const expectedLength = encoding.length;
|
|
988
|
-
const reconstructedData = new Int8Array(expectedLength);
|
|
989
|
-
if (decompressedDeltas.length > 0 && expectedLength > 0) {
|
|
990
|
-
reconstructedData[0] = decompressedDeltas[0] > 127 ? decompressedDeltas[0] - 256 : decompressedDeltas[0];
|
|
991
|
-
for (let i = 1; i < expectedLength; i++) {
|
|
992
|
-
const delta = decompressedDeltas[i] > 127 ? decompressedDeltas[i] - 256 : decompressedDeltas[i];
|
|
993
|
-
reconstructedData[i] = reconstructedData[i - 1] + delta;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
return new Uint8Array(reconstructedData.buffer);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
self.onmessage = async (e) => {
|
|
1000
|
-
const { requestId, compressedData, encoding } = e.data;
|
|
1001
|
-
try {
|
|
1002
|
-
const decompressedDeltas = await decompress(compressedData);
|
|
1003
|
-
const finalData = _reconstructData(decompressedDeltas, encoding);
|
|
1004
|
-
self.postMessage({ success: true, requestId: requestId, decompressedData: finalData, encoding: encoding }, [finalData.buffer]);
|
|
1005
|
-
} catch (error) {
|
|
1006
|
-
self.postMessage({ success: false, requestId: requestId, error: error.message });
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
|
-
`;
|
|
1010
|
-
const blob = new Blob([workerCode], {
|
|
1011
|
-
type: 'application/javascript'
|
|
1012
|
-
});
|
|
1013
|
-
return new Worker(URL.createObjectURL(blob), {
|
|
1014
|
-
type: 'module'
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
_processResultQueue() {
|
|
1018
|
-
while (this.resultQueue.length > 0) {
|
|
1019
|
-
const {
|
|
1020
|
-
success,
|
|
1021
|
-
requestId,
|
|
1022
|
-
decompressedData,
|
|
1023
|
-
encoding,
|
|
1024
|
-
error
|
|
1025
|
-
} = this.resultQueue.shift();
|
|
1026
|
-
if (this.workerResolvers.has(requestId)) {
|
|
1027
|
-
const {
|
|
1028
|
-
resolve,
|
|
1029
|
-
reject
|
|
1030
|
-
} = this.workerResolvers.get(requestId);
|
|
1031
|
-
if (success) {
|
|
1032
|
-
resolve({
|
|
1033
|
-
data: decompressedData
|
|
1034
|
-
}); // Return as { data: ... }
|
|
1035
|
-
} else {
|
|
1036
|
-
reject(new Error(error));
|
|
1037
|
-
}
|
|
1038
|
-
this.workerResolvers.delete(requestId);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
this.isProcessingQueue = false;
|
|
1042
|
-
}
|
|
1043
|
-
_handleWorkerMessage(e) {
|
|
1044
|
-
if (this.isReactNative) return;
|
|
1045
|
-
const {
|
|
1046
|
-
success,
|
|
1047
|
-
requestId,
|
|
1048
|
-
decompressedData,
|
|
1049
|
-
encoding,
|
|
1050
|
-
error
|
|
1051
|
-
} = e.data;
|
|
1052
|
-
this.resultQueue.push({
|
|
1053
|
-
success,
|
|
1054
|
-
requestId,
|
|
1055
|
-
decompressedData,
|
|
1056
|
-
encoding,
|
|
1057
|
-
error
|
|
1058
|
-
});
|
|
1059
|
-
if (!this.isProcessingQueue) {
|
|
1060
|
-
this.isProcessingQueue = true;
|
|
1061
|
-
requestAnimationFrame(() => this._processResultQueue());
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
1639
|
async fetchModelStatus(force = false) {
|
|
1065
1640
|
if (!this.modelStatus || force) {
|
|
1066
1641
|
try {
|
|
@@ -1086,6 +1661,18 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1086
1661
|
}
|
|
1087
1662
|
return this.mrmsStatus;
|
|
1088
1663
|
}
|
|
1664
|
+
async fetchSatelliteListing(force = false) {
|
|
1665
|
+
if (!this.satelliteListing || force) {
|
|
1666
|
+
try {
|
|
1667
|
+
const response = await fetch(_satellite_support.SATELLITE_FRAMES_URL);
|
|
1668
|
+
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
|
1669
|
+
this.satelliteListing = await response.json();
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
this.satelliteListing = null;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return this.satelliteListing;
|
|
1675
|
+
}
|
|
1089
1676
|
startAutoRefresh(intervalSeconds) {
|
|
1090
1677
|
this.stopAutoRefresh();
|
|
1091
1678
|
this.autoRefreshIntervalId = setInterval(async () => {
|