@aguacerowx/javascript-sdk 0.0.27 → 0.0.28
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 +207 -794
- package/dist/default-colormaps.js +437 -556
- package/dist/dictionaries.js +2413 -2071
- package/dist/events.js +2 -2
- package/dist/getBundleId.js +20 -9
- package/dist/index.js +1 -172
- 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 +128 -128
- package/src/nexrad_level3_catalog.js +26 -26
- 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/getBundleId.native.js +0 -18
- package/dist/gridDecodePipeline.js +0 -37
- package/dist/gridDecodeWorker.js +0 -31
- package/dist/nexradTiltCoalesce.js +0 -95
- package/dist/nexradTilts.js +0 -129
- package/dist/nexrad_level3_catalog.js +0 -56
- package/dist/nexrad_support.js +0 -269
- package/dist/satellite_support.js +0 -395
package/dist/AguaceroCore.js
CHANGED
|
@@ -10,13 +10,7 @@ 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");
|
|
14
13
|
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");
|
|
20
14
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
15
|
// AguaceroCore.js - The Headless "Engine"
|
|
22
16
|
|
|
@@ -60,80 +54,26 @@ function findLatestModelRun(modelsData, modelName) {
|
|
|
60
54
|
}
|
|
61
55
|
return null;
|
|
62
56
|
}
|
|
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
|
-
}
|
|
118
57
|
class AguaceroCore extends _events.EventEmitter {
|
|
119
58
|
constructor(options = {}) {
|
|
120
59
|
super();
|
|
121
60
|
this.isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
122
61
|
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';
|
|
125
62
|
this.bundleId = (0, _getBundleId.getBundleId)();
|
|
126
63
|
this.baseGridUrl = 'https://d3dc62msmxkrd7.cloudfront.net';
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
64
|
+
if (!this.isReactNative) {
|
|
65
|
+
this.worker = this.createWorker();
|
|
66
|
+
this.workerRequestId = 0;
|
|
67
|
+
this.workerResolvers = new Map();
|
|
68
|
+
this.worker.addEventListener('message', this._handleWorkerMessage.bind(this));
|
|
69
|
+
this.resultQueue = [];
|
|
70
|
+
this.isProcessingQueue = false;
|
|
71
|
+
} else {
|
|
72
|
+
this.worker = null;
|
|
73
|
+
}
|
|
132
74
|
this.statusUrl = 'https://d3dc62msmxkrd7.cloudfront.net/model-status';
|
|
133
75
|
this.modelStatus = null;
|
|
134
76
|
this.mrmsStatus = null;
|
|
135
|
-
/** @type {{ objects?: Array<{ key: string }> } | null} */
|
|
136
|
-
this.satelliteListing = null;
|
|
137
77
|
this.dataCache = new Map();
|
|
138
78
|
this.abortControllers = new Map();
|
|
139
79
|
this.isPlaying = false;
|
|
@@ -144,207 +84,30 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
144
84
|
// EDIT: Determine initial mode from options
|
|
145
85
|
const initialMode = userLayerOptions.mode || 'model';
|
|
146
86
|
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
|
-
}
|
|
160
87
|
this.state = {
|
|
161
88
|
model: userLayerOptions.model || 'gfs',
|
|
162
89
|
// EDIT: Set isMRMS based on the initial mode
|
|
163
|
-
isMRMS: initialMode === 'mrms'
|
|
90
|
+
isMRMS: initialMode === 'mrms',
|
|
164
91
|
mrmsTimestamp: null,
|
|
165
|
-
variable:
|
|
92
|
+
variable: initialVariable,
|
|
166
93
|
date: null,
|
|
167
94
|
run: null,
|
|
168
95
|
forecastHour: 0,
|
|
169
96
|
visible: true,
|
|
170
97
|
opacity: userLayerOptions.opacity ?? 1,
|
|
171
98
|
units: options.initialUnit || 'imperial',
|
|
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
|
|
99
|
+
shaderSmoothingEnabled: options.shaderSmoothingEnabled ?? true
|
|
191
100
|
};
|
|
192
101
|
this.autoRefreshEnabled = options.autoRefresh ?? false;
|
|
193
102
|
this.autoRefreshIntervalSeconds = options.autoRefreshInterval ?? 60;
|
|
194
103
|
this.autoRefreshIntervalId = null;
|
|
195
|
-
|
|
196
|
-
/** @type {Record<string, { unixTimes?: number[]; timeToKeyMap?: Record<string, string>; listWindowHours?: number }>} */
|
|
197
|
-
this.nexradTimesByStation = {};
|
|
198
104
|
}
|
|
199
105
|
async setState(newState) {
|
|
200
|
-
|
|
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);
|
|
106
|
+
Object.assign(this.state, newState);
|
|
249
107
|
this._emitStateChange();
|
|
250
108
|
}
|
|
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
|
-
}
|
|
346
109
|
_emitStateChange() {
|
|
347
|
-
var _this$modelStatus;
|
|
110
|
+
var _this$modelStatus, _this$modelStatus2;
|
|
348
111
|
const {
|
|
349
112
|
colormap,
|
|
350
113
|
baseUnit
|
|
@@ -353,45 +116,23 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
353
116
|
const displayColormap = this._convertColormapUnits(colormap, baseUnit, toUnit);
|
|
354
117
|
let availableTimestamps = [];
|
|
355
118
|
if (this.state.isMRMS && this.state.variable && this.mrmsStatus) {
|
|
356
|
-
|
|
119
|
+
const timestamps = this.mrmsStatus[this.state.variable] || [];
|
|
120
|
+
availableTimestamps = [...timestamps].reverse();
|
|
357
121
|
}
|
|
358
|
-
let
|
|
359
|
-
|
|
360
|
-
|
|
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);
|
|
122
|
+
let availableHours = this.state.isMRMS ? [] : ((_this$modelStatus = this.modelStatus) === null || _this$modelStatus === void 0 || (_this$modelStatus = _this$modelStatus[this.state.model]) === null || _this$modelStatus === void 0 || (_this$modelStatus = _this$modelStatus[this.state.date]) === null || _this$modelStatus === void 0 ? void 0 : _this$modelStatus[this.state.run]) || [];
|
|
123
|
+
if (!this.state.isMRMS && this.state.variable === 'ptypeRefl' && this.state.model === 'hrrr' && availableHours.length > 0) {
|
|
124
|
+
availableHours = availableHours.filter(hour => hour !== 0);
|
|
364
125
|
}
|
|
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();
|
|
379
126
|
const eventPayload = {
|
|
380
127
|
...this.state,
|
|
381
128
|
availableModels: this.modelStatus ? Object.keys(this.modelStatus).sort() : [],
|
|
382
|
-
availableRuns: ((_this$
|
|
129
|
+
availableRuns: ((_this$modelStatus2 = this.modelStatus) === null || _this$modelStatus2 === void 0 ? void 0 : _this$modelStatus2[this.state.model]) || {},
|
|
383
130
|
availableHours: availableHours,
|
|
384
131
|
// <-- Changed from inline calculation
|
|
385
132
|
availableVariables: this.getAvailableVariables(this.state.isMRMS ? 'mrms' : this.state.model),
|
|
386
133
|
// We need to confirm this line is working as expected.
|
|
387
134
|
availableMRMSVariables: this.getAvailableVariables('mrms'),
|
|
388
135
|
availableTimestamps: availableTimestamps,
|
|
389
|
-
availableSatelliteTimestamps,
|
|
390
|
-
satelliteTimeToFileMap,
|
|
391
|
-
availableNexradTimestamps,
|
|
392
|
-
nexradTimeToKeyMap,
|
|
393
|
-
nexradLevel3MotionTimeToKeyMap,
|
|
394
|
-
availableNexradTilts,
|
|
395
136
|
isPlaying: this.isPlaying,
|
|
396
137
|
colormap: displayColormap,
|
|
397
138
|
colormapBaseUnit: toUnit
|
|
@@ -401,34 +142,28 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
401
142
|
async initialize(options = {}) {
|
|
402
143
|
await this.fetchModelStatus(true);
|
|
403
144
|
await this.fetchMRMSStatus(true);
|
|
404
|
-
await this.fetchSatelliteListing(true);
|
|
405
145
|
let initialState = {
|
|
406
146
|
...this.state
|
|
407
147
|
};
|
|
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
|
-
}
|
|
415
148
|
|
|
416
149
|
// ADD: Logic to handle an initial MRMS state
|
|
417
150
|
if (initialState.isMRMS) {
|
|
418
151
|
const variable = initialState.variable;
|
|
419
152
|
if (variable && this.mrmsStatus && this.mrmsStatus[variable]) {
|
|
420
|
-
const sortedTimestamps = this.
|
|
421
|
-
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[
|
|
153
|
+
const sortedTimestamps = [...(this.mrmsStatus[variable] || [])].sort((a, b) => b - a);
|
|
154
|
+
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[0] : null;
|
|
422
155
|
} else {
|
|
156
|
+
// Fallback if the provided variable is not valid
|
|
157
|
+
console.warn(`Initial MRMS variable '${variable}' not found. Using default.`);
|
|
423
158
|
const availableMRMSVars = this.getAvailableVariables('mrms');
|
|
424
159
|
if (availableMRMSVars.length > 0) {
|
|
425
160
|
const firstVar = availableMRMSVars[0];
|
|
426
161
|
initialState.variable = firstVar;
|
|
427
|
-
const sortedTimestamps = this.
|
|
428
|
-
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[
|
|
162
|
+
const sortedTimestamps = [...(this.mrmsStatus[firstVar] || [])].sort((a, b) => b - a);
|
|
163
|
+
initialState.mrmsTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[0] : null;
|
|
429
164
|
}
|
|
430
165
|
}
|
|
431
|
-
} else
|
|
166
|
+
} else {
|
|
432
167
|
// EDIT: This is the existing logic, now in an else block
|
|
433
168
|
const latestRun = findLatestModelRun(this.modelStatus, initialState.model);
|
|
434
169
|
if (latestRun) {
|
|
@@ -446,13 +181,6 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
446
181
|
}
|
|
447
182
|
}
|
|
448
183
|
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
|
-
}
|
|
456
184
|
if (options.autoRefresh ?? this.autoRefreshEnabled) {
|
|
457
185
|
this.startAutoRefresh(options.refreshInterval ?? this.autoRefreshIntervalSeconds);
|
|
458
186
|
}
|
|
@@ -461,11 +189,11 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
461
189
|
this.pause();
|
|
462
190
|
this.stopAutoRefresh();
|
|
463
191
|
this.dataCache.clear();
|
|
464
|
-
this.
|
|
465
|
-
|
|
466
|
-
this._gridDecodeWorker.terminate();
|
|
467
|
-
this._gridDecodeWorker = null;
|
|
192
|
+
if (this.worker) {
|
|
193
|
+
this.worker.terminate();
|
|
468
194
|
}
|
|
195
|
+
this.callbacks = {};
|
|
196
|
+
console.log(`AguaceroCore has been destroyed.`);
|
|
469
197
|
}
|
|
470
198
|
|
|
471
199
|
// --- Public API Methods ---
|
|
@@ -494,21 +222,6 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
494
222
|
this.isPlaying ? this.pause() : this.play();
|
|
495
223
|
}
|
|
496
224
|
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
|
-
}
|
|
512
225
|
// --- THIS IS THE CORRECTED MRMS LOGIC ---
|
|
513
226
|
if (this.state.isMRMS) {
|
|
514
227
|
const {
|
|
@@ -516,16 +229,19 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
516
229
|
mrmsTimestamp
|
|
517
230
|
} = this.state;
|
|
518
231
|
if (!this.mrmsStatus || !this.mrmsStatus[variable]) {
|
|
232
|
+
console.warn('[Core.step] MRMS status or variable not available.');
|
|
519
233
|
return;
|
|
520
234
|
}
|
|
521
|
-
|
|
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();
|
|
522
239
|
if (availableTimestamps.length === 0) return;
|
|
523
|
-
const
|
|
524
|
-
const currentIndex = ts == null ? -1 : availableTimestamps.indexOf(ts);
|
|
240
|
+
const currentIndex = availableTimestamps.indexOf(mrmsTimestamp);
|
|
525
241
|
if (currentIndex === -1) {
|
|
526
|
-
// If not found, reset to the
|
|
242
|
+
// If not found, reset to the first (newest) frame
|
|
527
243
|
this.setState({
|
|
528
|
-
mrmsTimestamp: availableTimestamps[
|
|
244
|
+
mrmsTimestamp: availableTimestamps[0]
|
|
529
245
|
});
|
|
530
246
|
return;
|
|
531
247
|
}
|
|
@@ -539,29 +255,17 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
539
255
|
this.setState({
|
|
540
256
|
mrmsTimestamp: newTimestamp
|
|
541
257
|
});
|
|
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
|
-
});
|
|
557
258
|
} else {
|
|
259
|
+
var _this$modelStatus3;
|
|
558
260
|
const {
|
|
261
|
+
model,
|
|
262
|
+
date,
|
|
263
|
+
run,
|
|
559
264
|
forecastHour
|
|
560
265
|
} = this.state;
|
|
561
|
-
const forecastHours = this.
|
|
266
|
+
const forecastHours = (_this$modelStatus3 = this.modelStatus) === null || _this$modelStatus3 === void 0 || (_this$modelStatus3 = _this$modelStatus3[model]) === null || _this$modelStatus3 === void 0 || (_this$modelStatus3 = _this$modelStatus3[date]) === null || _this$modelStatus3 === void 0 ? void 0 : _this$modelStatus3[run];
|
|
562
267
|
if (!forecastHours || forecastHours.length === 0) return;
|
|
563
|
-
const
|
|
564
|
-
const currentIndex = forecastHours.indexOf(fh);
|
|
268
|
+
const currentIndex = forecastHours.indexOf(forecastHour);
|
|
565
269
|
if (currentIndex === -1) return;
|
|
566
270
|
const maxIndex = forecastHours.length - 1;
|
|
567
271
|
let nextIndex = currentIndex + direction;
|
|
@@ -595,7 +299,8 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
595
299
|
async setVariable(variable) {
|
|
596
300
|
// --- NEW CODE: Handle switching TO ptypeRefl on HRRR ---
|
|
597
301
|
if (variable === 'ptypeRefl' && this.state.model === 'hrrr' && this.state.forecastHour === 0) {
|
|
598
|
-
|
|
302
|
+
var _this$modelStatus4;
|
|
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]) || [];
|
|
599
304
|
const firstValidHour = availableHours.find(hour => hour !== 0) || 0;
|
|
600
305
|
await this.setState({
|
|
601
306
|
variable,
|
|
@@ -610,17 +315,8 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
610
315
|
});
|
|
611
316
|
}
|
|
612
317
|
async setModel(modelName) {
|
|
613
|
-
var _this$
|
|
614
|
-
if (modelName === this.state.model || !((_this$
|
|
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
|
-
}
|
|
318
|
+
var _this$modelStatus5;
|
|
319
|
+
if (modelName === this.state.model || !((_this$modelStatus5 = this.modelStatus) !== null && _this$modelStatus5 !== void 0 && _this$modelStatus5[modelName])) return;
|
|
624
320
|
const latestRun = findLatestModelRun(this.modelStatus, modelName);
|
|
625
321
|
if (latestRun) {
|
|
626
322
|
// --- NEW CODE: Determine initial forecast hour ---
|
|
@@ -628,7 +324,9 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
628
324
|
|
|
629
325
|
// If switching to HRRR with ptypeRefl, start at hour 1 instead of 0
|
|
630
326
|
if (modelName === 'hrrr' && this.state.variable === 'ptypeRefl') {
|
|
631
|
-
|
|
327
|
+
var _this$modelStatus6;
|
|
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)
|
|
632
330
|
initialHour = availableHours.find(hour => hour !== 0) || 0;
|
|
633
331
|
}
|
|
634
332
|
// --- END NEW CODE ---
|
|
@@ -658,16 +356,11 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
658
356
|
});
|
|
659
357
|
}
|
|
660
358
|
async setMRMSVariable(variable) {
|
|
661
|
-
const sortedTimestamps = this.
|
|
662
|
-
const initialTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[
|
|
359
|
+
const sortedTimestamps = [...(this.mrmsStatus[variable] || [])].sort((a, b) => b - a);
|
|
360
|
+
const initialTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[0] : null;
|
|
663
361
|
await this.setState({
|
|
664
362
|
variable,
|
|
665
363
|
isMRMS: true,
|
|
666
|
-
isSatellite: false,
|
|
667
|
-
satelliteInstrumentId: null,
|
|
668
|
-
satelliteSectorLabel: null,
|
|
669
|
-
satelliteChannel: null,
|
|
670
|
-
satelliteTimestamp: null,
|
|
671
364
|
mrmsTimestamp: initialTimestamp
|
|
672
365
|
});
|
|
673
366
|
}
|
|
@@ -677,184 +370,42 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
677
370
|
mrmsTimestamp: timestamp
|
|
678
371
|
});
|
|
679
372
|
}
|
|
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
|
-
}
|
|
770
373
|
async switchMode(options) {
|
|
771
|
-
|
|
374
|
+
const {
|
|
772
375
|
mode,
|
|
773
376
|
variable,
|
|
774
377
|
model,
|
|
775
378
|
forecastHour,
|
|
776
|
-
mrmsTimestamp
|
|
777
|
-
satelliteTimestamp
|
|
379
|
+
mrmsTimestamp
|
|
778
380
|
} = options;
|
|
779
|
-
if (!mode) {
|
|
381
|
+
if (!mode || !variable) {
|
|
382
|
+
console.error("switchMode requires 'mode' ('mrms' | 'model') and 'variable' properties.");
|
|
780
383
|
return;
|
|
781
384
|
}
|
|
782
385
|
if (mode === 'model' && !model) {
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
if ((mode === 'mrms' || mode === 'model') && !variable) {
|
|
386
|
+
console.error("switchMode with mode 'model' requires a 'model' property.");
|
|
786
387
|
return;
|
|
787
388
|
}
|
|
788
389
|
let targetState = {};
|
|
789
|
-
if (mode === '
|
|
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);
|
|
390
|
+
if (mode === 'mrms') {
|
|
831
391
|
let finalTimestamp = mrmsTimestamp;
|
|
832
392
|
if (finalTimestamp === undefined) {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const n = Number(finalTimestamp);
|
|
836
|
-
if (!sortedTimestamps.includes(n)) {
|
|
837
|
-
finalTimestamp = sortedTimestamps[sortedTimestamps.length - 1];
|
|
838
|
-
}
|
|
393
|
+
const sortedTimestamps = [...(this.mrmsStatus[variable] || [])].sort((a, b) => b - a);
|
|
394
|
+
finalTimestamp = sortedTimestamps.length > 0 ? sortedTimestamps[0] : null;
|
|
839
395
|
}
|
|
840
396
|
targetState = {
|
|
841
397
|
isMRMS: true,
|
|
842
|
-
isSatellite: false,
|
|
843
|
-
isNexrad: false,
|
|
844
398
|
variable: variable,
|
|
845
399
|
mrmsTimestamp: finalTimestamp,
|
|
846
400
|
model: this.state.model,
|
|
847
401
|
date: null,
|
|
848
402
|
run: null,
|
|
849
|
-
forecastHour: 0
|
|
850
|
-
satelliteInstrumentId: null,
|
|
851
|
-
satelliteSectorLabel: null,
|
|
852
|
-
satelliteChannel: null,
|
|
853
|
-
satelliteTimestamp: null
|
|
403
|
+
forecastHour: 0
|
|
854
404
|
};
|
|
855
405
|
} else if (mode === 'model') {
|
|
856
406
|
const latestRun = findLatestModelRun(this.modelStatus, model);
|
|
857
407
|
if (!latestRun) {
|
|
408
|
+
console.error(`Could not find a valid run for model: ${model}`);
|
|
858
409
|
return;
|
|
859
410
|
}
|
|
860
411
|
|
|
@@ -863,289 +414,50 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
863
414
|
|
|
864
415
|
// If switching to HRRR with ptypeRefl and hour is 0, use hour 1
|
|
865
416
|
if (model === 'hrrr' && variable === 'ptypeRefl' && initialHour === 0) {
|
|
866
|
-
|
|
417
|
+
var _this$modelStatus7;
|
|
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]) || [];
|
|
867
419
|
initialHour = availableHours.find(hour => hour !== 0) || 0;
|
|
868
420
|
}
|
|
869
421
|
// --- END NEW CODE ---
|
|
870
422
|
|
|
871
423
|
targetState = {
|
|
872
424
|
isMRMS: false,
|
|
873
|
-
isSatellite: false,
|
|
874
|
-
isNexrad: false,
|
|
875
425
|
model: model,
|
|
876
426
|
variable: variable,
|
|
877
427
|
date: latestRun.date,
|
|
878
428
|
run: latestRun.run,
|
|
879
429
|
forecastHour: initialHour,
|
|
880
430
|
// <-- Changed
|
|
881
|
-
mrmsTimestamp: null
|
|
882
|
-
satelliteInstrumentId: null,
|
|
883
|
-
satelliteSectorLabel: null,
|
|
884
|
-
satelliteChannel: null,
|
|
885
|
-
satelliteTimestamp: null
|
|
431
|
+
mrmsTimestamp: null
|
|
886
432
|
};
|
|
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;
|
|
929
433
|
} else {
|
|
434
|
+
console.error(`Invalid mode specified in switchMode: '${mode}'`);
|
|
930
435
|
return;
|
|
931
436
|
}
|
|
932
437
|
await this.setState(targetState);
|
|
933
438
|
}
|
|
934
439
|
|
|
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
|
-
|
|
1062
440
|
// --- Data and Calculation Methods ---
|
|
1063
441
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
-
}
|
|
442
|
+
_reconstructData(decompressedDeltas, encoding) {
|
|
443
|
+
const expectedLength = encoding.length;
|
|
444
|
+
const reconstructedData = new Int8Array(expectedLength);
|
|
445
|
+
if (decompressedDeltas.length > 0 && expectedLength > 0) {
|
|
446
|
+
// First value is absolute
|
|
447
|
+
reconstructedData[0] = decompressedDeltas[0] > 127 ? decompressedDeltas[0] - 256 : decompressedDeltas[0];
|
|
1092
448
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
*/
|
|
1098
|
-
_decodeGridPayload(compressedData, encoding) {
|
|
1099
|
-
const worker = this._ensureGridDecodeWorker();
|
|
1100
|
-
if (!worker) {
|
|
1101
|
-
return Promise.resolve((0, _gridDecodePipeline.processCompressedGrid)(compressedData, encoding));
|
|
1102
|
-
}
|
|
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);
|
|
449
|
+
// Subsequent values are deltas from the previous one
|
|
450
|
+
for (let i = 1; i < expectedLength; i++) {
|
|
451
|
+
const delta = decompressedDeltas[i] > 127 ? decompressedDeltas[i] - 256 : decompressedDeltas[i];
|
|
452
|
+
reconstructedData[i] = reconstructedData[i - 1] + delta;
|
|
1141
453
|
}
|
|
1142
|
-
}
|
|
454
|
+
}
|
|
455
|
+
// Return as a Uint8Array, which is what the rest of the code expects
|
|
456
|
+
return new Uint8Array(reconstructedData.buffer);
|
|
1143
457
|
}
|
|
1144
458
|
async _loadGridData(state) {
|
|
1145
459
|
if (this.isReactNative) {
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
if (state.isNexrad) {
|
|
460
|
+
console.warn(`[AguaceroCore] _loadGridData was called in React Native. This is a bypass. Data loading is handled natively.`);
|
|
1149
461
|
return null;
|
|
1150
462
|
}
|
|
1151
463
|
const {
|
|
@@ -1179,6 +491,17 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1179
491
|
if (this.dataCache.has(dataUrlIdentifier)) {
|
|
1180
492
|
return this.dataCache.get(dataUrlIdentifier);
|
|
1181
493
|
}
|
|
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
|
+
}
|
|
1182
505
|
const abortController = new AbortController();
|
|
1183
506
|
this.abortControllers.set(dataUrlIdentifier, abortController);
|
|
1184
507
|
const loadPromise = (async () => {
|
|
@@ -1206,18 +529,38 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1206
529
|
encoding
|
|
1207
530
|
} = await response.json();
|
|
1208
531
|
const compressedData = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
532
|
+
|
|
533
|
+
// This path is now ONLY for the web worker
|
|
534
|
+
const requestId = this.workerRequestId++;
|
|
535
|
+
const workerPromise = new Promise((resolve, reject) => {
|
|
536
|
+
this.workerResolvers.set(requestId, {
|
|
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;
|
|
1214
552
|
}
|
|
1215
553
|
this.abortControllers.delete(dataUrlIdentifier);
|
|
1216
554
|
return {
|
|
1217
|
-
data:
|
|
1218
|
-
encoding
|
|
555
|
+
data: transformedData,
|
|
556
|
+
encoding
|
|
1219
557
|
};
|
|
1220
558
|
} 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
|
+
}
|
|
1221
564
|
this.dataCache.delete(dataUrlIdentifier);
|
|
1222
565
|
this.abortControllers.delete(dataUrlIdentifier);
|
|
1223
566
|
return null;
|
|
@@ -1235,11 +578,9 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1235
578
|
// Clear both maps
|
|
1236
579
|
this.abortControllers.clear();
|
|
1237
580
|
this.dataCache.clear();
|
|
581
|
+
console.log('All pending requests cancelled');
|
|
1238
582
|
}
|
|
1239
583
|
async getValueAtLngLat(lng, lat) {
|
|
1240
|
-
if (this.state.isSatellite || this.state.isNexrad) {
|
|
1241
|
-
return null;
|
|
1242
|
-
}
|
|
1243
584
|
const {
|
|
1244
585
|
variable,
|
|
1245
586
|
isMRMS,
|
|
@@ -1627,6 +968,7 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1627
968
|
y
|
|
1628
969
|
};
|
|
1629
970
|
} catch (error) {
|
|
971
|
+
console.warn(`[GridAccessor] RGEM polar stereographic conversion failed for ${lat}, ${lon}:`, error);
|
|
1630
972
|
return {
|
|
1631
973
|
x: -1,
|
|
1632
974
|
y: -1
|
|
@@ -1634,8 +976,91 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1634
976
|
}
|
|
1635
977
|
}
|
|
1636
978
|
|
|
1637
|
-
// --- Status Methods ---
|
|
979
|
+
// --- Worker and Status Methods ---
|
|
980
|
+
|
|
981
|
+
createWorker() {
|
|
982
|
+
if (this.isReactNative) return null;
|
|
983
|
+
const workerCode = `
|
|
984
|
+
import { decompress } from 'https://cdn.skypack.dev/fzstd@0.1.1';
|
|
1638
985
|
|
|
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
|
+
}
|
|
1639
1064
|
async fetchModelStatus(force = false) {
|
|
1640
1065
|
if (!this.modelStatus || force) {
|
|
1641
1066
|
try {
|
|
@@ -1661,18 +1086,6 @@ class AguaceroCore extends _events.EventEmitter {
|
|
|
1661
1086
|
}
|
|
1662
1087
|
return this.mrmsStatus;
|
|
1663
1088
|
}
|
|
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
|
-
}
|
|
1676
1089
|
startAutoRefresh(intervalSeconds) {
|
|
1677
1090
|
this.stopAutoRefresh();
|
|
1678
1091
|
this.autoRefreshIntervalId = setInterval(async () => {
|