@aguacerowx/react-native 0.0.50 → 0.0.52
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/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayerView.java +11 -3
- package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +315 -275
- package/ios/SatelliteLayerView.swift +11 -4
- package/ios/WeatherFrameProcessorModule.swift +222 -188
- package/lib/commonjs/WeatherLayerManager.js +112 -48
- package/lib/commonjs/WeatherLayerManager.js.map +1 -1
- package/lib/commonjs/aguaceroCoreDebugHooks.js +144 -0
- package/lib/commonjs/aguaceroCoreDebugHooks.js.map +1 -0
- package/lib/commonjs/aguaceroRnDebug.js +358 -0
- package/lib/commonjs/aguaceroRnDebug.js.map +1 -0
- package/lib/commonjs/gridCdnAuth.js +64 -0
- package/lib/commonjs/gridCdnAuth.js.map +1 -0
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/nexrad/nexradAndroidController.js +38 -25
- package/lib/commonjs/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/commonjs/nexrad/nexradDiag.js +31 -25
- package/lib/commonjs/nexrad/nexradDiag.js.map +1 -1
- package/lib/commonjs/satellite/satelliteAndroidController.js +24 -15
- package/lib/commonjs/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/module/WeatherLayerManager.js +112 -48
- package/lib/module/WeatherLayerManager.js.map +1 -1
- package/lib/module/aguaceroCoreDebugHooks.js +136 -0
- package/lib/module/aguaceroCoreDebugHooks.js.map +1 -0
- package/lib/module/aguaceroRnDebug.js +341 -0
- package/lib/module/aguaceroRnDebug.js.map +1 -0
- package/lib/module/gridCdnAuth.js +56 -0
- package/lib/module/gridCdnAuth.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/nexrad/nexradAndroidController.js +38 -25
- package/lib/module/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/module/nexrad/nexradDiag.js +31 -25
- package/lib/module/nexrad/nexradDiag.js.map +1 -1
- package/lib/module/satellite/satelliteAndroidController.js +24 -15
- package/lib/module/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/typescript/WeatherLayerManager.d.ts.map +1 -1
- package/lib/typescript/aguaceroCoreDebugHooks.d.ts +10 -0
- package/lib/typescript/aguaceroCoreDebugHooks.d.ts.map +1 -0
- package/lib/typescript/aguaceroRnDebug.d.ts +97 -0
- package/lib/typescript/aguaceroRnDebug.d.ts.map +1 -0
- package/lib/typescript/gridCdnAuth.d.ts +24 -0
- package/lib/typescript/gridCdnAuth.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/nexrad/nexradAndroidController.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradDiag.d.ts.map +1 -1
- package/lib/typescript/satellite/satelliteAndroidController.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/WeatherLayerManager.js +2024 -1947
- package/src/aguaceroCoreDebugHooks.js +142 -0
- package/src/aguaceroRnDebug.js +335 -0
- package/src/gridCdnAuth.js +56 -0
- package/src/index.js +19 -7
- package/src/nexrad/nexradAndroidController.js +1078 -1068
- package/src/nexrad/nexradDiag.js +150 -144
- package/src/satellite/satelliteAndroidController.js +245 -236
|
@@ -1,1068 +1,1078 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AguaceroCore NEXRAD → Android native custom layer (parity with mapsgl {@link NexradWeatherController}).
|
|
3
|
-
*/
|
|
4
|
-
import { getUnitConversionFunction, getDefaultRadarTilt, nexradBinGroupIdForKey, variableToNexradGroup } from '@aguacerowx/javascript-sdk';
|
|
5
|
-
import { fromByteArray } from 'base64-js';
|
|
6
|
-
import {
|
|
7
|
-
fetchAndParseArchive,
|
|
8
|
-
objectKeyToUrl,
|
|
9
|
-
setNexradArchiveApiKey,
|
|
10
|
-
setNexradArchiveBundleId,
|
|
11
|
-
setNexradArchiveSiteOrigin,
|
|
12
|
-
setNexradSitesJsonUrl,
|
|
13
|
-
setNexradSitesFetchAuth,
|
|
14
|
-
} from './radarArchiveCore.bundled.js';
|
|
15
|
-
import { prepareRadarFrameForGpuReadout } from './radarFrameGpuMatch.bundled.js';
|
|
16
|
-
import { mapboxFrameUploadOptionsForNexradState } from './nexradMapboxFrameOpts.bundled.js';
|
|
17
|
-
import { sampleNexradFrameAtLatLon } from './nexradCrossSectionSampleAtLatLon.bundled.js';
|
|
18
|
-
import { buildNexradLutRgba } from './nexradLutBuild.js';
|
|
19
|
-
import { Platform } from 'react-native';
|
|
20
|
-
import { nexradDiagBootSnapshot, nexradDiagGateTextureSummary, nexradPerfSpan } from './nexradDiag.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
typeof
|
|
36
|
-
typeof
|
|
37
|
-
typeof
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (hi
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
'
|
|
125
|
-
'
|
|
126
|
-
'
|
|
127
|
-
'
|
|
128
|
-
'
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
132
|
-
'
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (idx >=
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
let
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let
|
|
192
|
-
let
|
|
193
|
-
let
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
out[outIdx++] =
|
|
204
|
-
out[outIdx++] =
|
|
205
|
-
out[outIdx++] =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
* @param {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
this.
|
|
221
|
-
this.
|
|
222
|
-
this.
|
|
223
|
-
this.
|
|
224
|
-
this.
|
|
225
|
-
this.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
this.
|
|
300
|
-
this.
|
|
301
|
-
this.
|
|
302
|
-
this.
|
|
303
|
-
this.
|
|
304
|
-
this.
|
|
305
|
-
this.
|
|
306
|
-
this.
|
|
307
|
-
this.
|
|
308
|
-
this.
|
|
309
|
-
this.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
rawFrame.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
this._preloadTimelineSig
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
this.
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const
|
|
983
|
-
if (!
|
|
984
|
-
return null;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AguaceroCore NEXRAD → Android native custom layer (parity with mapsgl {@link NexradWeatherController}).
|
|
3
|
+
*/
|
|
4
|
+
import { getUnitConversionFunction, getDefaultRadarTilt, nexradBinGroupIdForKey, variableToNexradGroup } from '@aguacerowx/javascript-sdk';
|
|
5
|
+
import { fromByteArray } from 'base64-js';
|
|
6
|
+
import {
|
|
7
|
+
fetchAndParseArchive,
|
|
8
|
+
objectKeyToUrl,
|
|
9
|
+
setNexradArchiveApiKey,
|
|
10
|
+
setNexradArchiveBundleId,
|
|
11
|
+
setNexradArchiveSiteOrigin,
|
|
12
|
+
setNexradSitesJsonUrl,
|
|
13
|
+
setNexradSitesFetchAuth,
|
|
14
|
+
} from './radarArchiveCore.bundled.js';
|
|
15
|
+
import { prepareRadarFrameForGpuReadout } from './radarFrameGpuMatch.bundled.js';
|
|
16
|
+
import { mapboxFrameUploadOptionsForNexradState } from './nexradMapboxFrameOpts.bundled.js';
|
|
17
|
+
import { sampleNexradFrameAtLatLon } from './nexradCrossSectionSampleAtLatLon.bundled.js';
|
|
18
|
+
import { buildNexradLutRgba } from './nexradLutBuild.js';
|
|
19
|
+
import { Platform } from 'react-native';
|
|
20
|
+
import { nexradDiagBootSnapshot, nexradDiagGateTextureSummary, nexradPerfSpan } from './nexradDiag.js';
|
|
21
|
+
import { aguaceroDebug, getAguaceroAuthDiagnosticSnapshot, isAguaceroRnDebugEnabled } from '../aguaceroRnDebug';
|
|
22
|
+
|
|
23
|
+
nexradDiagBootSnapshot({
|
|
24
|
+
archiveExports: {
|
|
25
|
+
setNexradArchiveApiKey: typeof setNexradArchiveApiKey,
|
|
26
|
+
setNexradArchiveBundleId: typeof setNexradArchiveBundleId,
|
|
27
|
+
objectKeyToUrl: typeof objectKeyToUrl,
|
|
28
|
+
fetchAndParseArchive: typeof fetchAndParseArchive,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/** @returns {boolean} */
|
|
33
|
+
function ensureRadarArchiveBindings(label) {
|
|
34
|
+
const ok =
|
|
35
|
+
typeof setNexradArchiveApiKey === 'function' &&
|
|
36
|
+
typeof setNexradArchiveBundleId === 'function' &&
|
|
37
|
+
typeof objectKeyToUrl === 'function' &&
|
|
38
|
+
typeof fetchAndParseArchive === 'function';
|
|
39
|
+
return ok;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pickNearestLevel3ObjectKey(unixTime, timeToKeyMap, maxDeltaSec = 600) {
|
|
43
|
+
const direct = timeToKeyMap[String(unixTime)];
|
|
44
|
+
if (direct) return direct;
|
|
45
|
+
const entries = Object.entries(timeToKeyMap || {});
|
|
46
|
+
if (entries.length === 0) return null;
|
|
47
|
+
let bestKey = null;
|
|
48
|
+
let bestDelta = Infinity;
|
|
49
|
+
for (const [tStr, key] of entries) {
|
|
50
|
+
const t = Number(tStr);
|
|
51
|
+
if (!Number.isFinite(t)) continue;
|
|
52
|
+
const d = Math.abs(t - unixTime);
|
|
53
|
+
if (d < bestDelta) {
|
|
54
|
+
bestDelta = d;
|
|
55
|
+
bestKey = key;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (bestKey == null) return null;
|
|
59
|
+
if (bestDelta > maxDeltaSec && Number.isFinite(maxDeltaSec)) return null;
|
|
60
|
+
return bestKey;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isVelocityStyleRadarVar(radarVariable) {
|
|
64
|
+
return radarVariable === 'VEL' || radarVariable === 'SW' || radarVariable === 'N0G' || radarVariable === 'N0W';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function velocityRangeToMs(rangeMin, rangeMax, displayUnit) {
|
|
68
|
+
if (rangeMin == null || rangeMax == null || !displayUnit || displayUnit.toLowerCase() === 'm/s' || displayUnit.toLowerCase() === 'ms') {
|
|
69
|
+
return [rangeMin, rangeMax];
|
|
70
|
+
}
|
|
71
|
+
const toMs = getUnitConversionFunction(displayUnit, 'm/s', 'nexrad_vel');
|
|
72
|
+
if (!toMs) return [rangeMin, rangeMax];
|
|
73
|
+
return [toMs(rangeMin), toMs(rangeMax)];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function colormapToMs(colormap, radarVariable, displayUnit) {
|
|
77
|
+
if (!colormap || !Array.isArray(colormap) || colormap.length < 2) return colormap;
|
|
78
|
+
if (!isVelocityStyleRadarVar(radarVariable)) return colormap;
|
|
79
|
+
if (!displayUnit || displayUnit.toLowerCase() === 'm/s' || displayUnit.toLowerCase() === 'ms') return colormap;
|
|
80
|
+
const toMs = getUnitConversionFunction(
|
|
81
|
+
displayUnit,
|
|
82
|
+
'm/s',
|
|
83
|
+
radarVariable === 'SW' || radarVariable === 'N0W' ? 'nexrad_sw' : 'nexrad_vel',
|
|
84
|
+
);
|
|
85
|
+
if (!toMs) return colormap;
|
|
86
|
+
const out = [...colormap];
|
|
87
|
+
for (let i = 0; i < out.length; i += 2) {
|
|
88
|
+
const v = out[i];
|
|
89
|
+
if (typeof v === 'number' && Number.isFinite(v)) out[i] = toMs(v);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function radarShaderValueRange(rangeMin, rangeMax, radarVariable, units, nexradDataSource) {
|
|
95
|
+
const v = (radarVariable || '').toUpperCase();
|
|
96
|
+
if (isVelocityStyleRadarVar(v)) {
|
|
97
|
+
const [vmin, vmax] = velocityRangeToMs(rangeMin, rangeMax, units);
|
|
98
|
+
return [vmin ?? 0, vmax ?? 80];
|
|
99
|
+
}
|
|
100
|
+
if (nexradDataSource === 'level3' && (v === 'N0H' || v === 'HHC')) {
|
|
101
|
+
const lo = rangeMin ?? 0;
|
|
102
|
+
let hi = rangeMax ?? 11;
|
|
103
|
+
if (hi > 11) hi = 11;
|
|
104
|
+
if (hi < lo) hi = lo;
|
|
105
|
+
return [lo, hi];
|
|
106
|
+
}
|
|
107
|
+
if (v === 'KDP') {
|
|
108
|
+
const lo = rangeMin ?? -2;
|
|
109
|
+
let hi = rangeMax ?? 8;
|
|
110
|
+
if (hi < lo) hi = lo;
|
|
111
|
+
return [lo, hi];
|
|
112
|
+
}
|
|
113
|
+
return [rangeMin ?? 0, rangeMax ?? 80];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function nexradLevel3IsHydrometeorClassification(radarVariable) {
|
|
117
|
+
const v = (radarVariable || '').toUpperCase();
|
|
118
|
+
return v === 'N0H' || v === 'HHC';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const NEXRAD_HYDROMETEOR_CLASS_LABELS = [
|
|
122
|
+
'Biological',
|
|
123
|
+
'Ground clutter',
|
|
124
|
+
'Ice crystals',
|
|
125
|
+
'Dry snow',
|
|
126
|
+
'Wet snow',
|
|
127
|
+
'Light rain',
|
|
128
|
+
'Heavy rain',
|
|
129
|
+
'Big drops',
|
|
130
|
+
'Graupel',
|
|
131
|
+
'Hail/rain',
|
|
132
|
+
'Large hail',
|
|
133
|
+
'Giant hail',
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
function nexradHydrometeorLabelForClassIndex(value) {
|
|
137
|
+
const idx = Math.round(Number(value));
|
|
138
|
+
if (idx >= 12) return null;
|
|
139
|
+
if (idx >= 0 && idx < NEXRAD_HYDROMETEOR_CLASS_LABELS.length) {
|
|
140
|
+
return NEXRAD_HYDROMETEOR_CLASS_LABELS[idx];
|
|
141
|
+
}
|
|
142
|
+
return `Class ${idx}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function velocityMsToDisplay(valueMs, displayUnit) {
|
|
146
|
+
if (!displayUnit || displayUnit.toLowerCase() === 'm/s' || displayUnit.toLowerCase() === 'ms') {
|
|
147
|
+
return valueMs;
|
|
148
|
+
}
|
|
149
|
+
const fromMs = getUnitConversionFunction('m/s', displayUnit, 'nexrad_vel');
|
|
150
|
+
return fromMs ? fromMs(valueMs) : valueMs;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function beamHeightKm(slantRangeKm, elevDeg) {
|
|
154
|
+
const el = (elevDeg * Math.PI) / 180;
|
|
155
|
+
const ReKm = (6371000 * 4) / (3 * 1000);
|
|
156
|
+
const cosEl = Math.max(Math.cos(el), 0.05);
|
|
157
|
+
const s = slantRangeKm / cosEl;
|
|
158
|
+
return Math.sqrt(s * s + ReKm * ReKm + 2 * s * ReKm * Math.sin(el)) - ReKm;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function formatNexradInspectNumeric(raw, radarVariable) {
|
|
162
|
+
const vu = (radarVariable || '').toUpperCase();
|
|
163
|
+
const vl = radarVariable || '';
|
|
164
|
+
if (vu.includes('RATE') || vl.toLowerCase().includes('rate')) {
|
|
165
|
+
return Number(raw.toFixed(3).replace(/\.?0+$/, ''));
|
|
166
|
+
}
|
|
167
|
+
if (vu === 'RHO' || vu === 'ZDR' || vl.includes('RhoHV') || vl.includes('Zdr')) {
|
|
168
|
+
return Number(raw.toFixed(2));
|
|
169
|
+
}
|
|
170
|
+
if (Math.abs(raw) < 10) return Number(raw.toFixed(1));
|
|
171
|
+
return Math.round(raw);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Keep low so Level-2 decode stays off the critical path for touches/animations (was 4). */
|
|
175
|
+
/** iOS Metal uploads benefit from overlapping fetches; keep Android at 1 for steadier radio/CPU. */
|
|
176
|
+
const PRELOAD_CONCURRENCY = Platform.OS === 'ios' ? 2 : 1;
|
|
177
|
+
|
|
178
|
+
function yieldToJsEventLoop() {
|
|
179
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Match mapsgl {@link MapboxRadarLayer} geometry LRU — reuse serialized native payloads when scrubbing. */
|
|
183
|
+
const NATIVE_UPLOAD_JSON_LRU_MAX = 12;
|
|
184
|
+
|
|
185
|
+
function rleCompressGateData(gateData) {
|
|
186
|
+
const len = gateData.length;
|
|
187
|
+
const out = new Uint8Array(len * 2);
|
|
188
|
+
let outIdx = 0;
|
|
189
|
+
let i = 0;
|
|
190
|
+
while (i < len) {
|
|
191
|
+
let val0 = gateData[i];
|
|
192
|
+
let val1 = gateData[i + 1];
|
|
193
|
+
let count = 1;
|
|
194
|
+
let maxCount = (len - i) >> 1;
|
|
195
|
+
if (maxCount > 65535) maxCount = 65535;
|
|
196
|
+
|
|
197
|
+
let j = i + 2;
|
|
198
|
+
while (count < maxCount && gateData[j] === val0 && gateData[j + 1] === val1) {
|
|
199
|
+
count++;
|
|
200
|
+
j += 2;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
out[outIdx++] = count & 0xFF;
|
|
204
|
+
out[outIdx++] = (count >> 8) & 0xFF;
|
|
205
|
+
out[outIdx++] = val0;
|
|
206
|
+
out[outIdx++] = val1;
|
|
207
|
+
|
|
208
|
+
i = j;
|
|
209
|
+
}
|
|
210
|
+
return out.subarray(0, outIdx);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export class NexradAndroidController {
|
|
214
|
+
/**
|
|
215
|
+
* @param {*} core - AguaceroCore instance
|
|
216
|
+
* @param {React.MutableRefObject<{ uploadNexradFrame?: (s: string) => void; uploadNexradStyleOnly?: (s: string) => void; clearNexrad?: () => void; activateNexradCachedFrame?: (k: string) => void } | null>} layerRef
|
|
217
|
+
* @param {object} [options]
|
|
218
|
+
*/
|
|
219
|
+
constructor(core, layerRef, options = {}) {
|
|
220
|
+
this.core = core;
|
|
221
|
+
this._layerRef = layerRef;
|
|
222
|
+
this._lastSyncKey = null;
|
|
223
|
+
this._abort = null;
|
|
224
|
+
this._interpolateColormap = options.interpolateNexradColormap !== false;
|
|
225
|
+
this._gateSmoothing = options.nexradGateSmoothing === true;
|
|
226
|
+
this._frameCache = new Map();
|
|
227
|
+
/** WeakMap: decoded frame → Map(uploadOptsKey → prepared frame). Avoids O(n²) canonical re-bin every map move. */
|
|
228
|
+
this._gpuReadoutPrep = new WeakMap();
|
|
229
|
+
this._preloadAbort = null;
|
|
230
|
+
this._preloadTimelineSig = null;
|
|
231
|
+
this._nativeFrameUploaded = false;
|
|
232
|
+
/** @type {{ valueScale: number; valueOffset: number } | null} */
|
|
233
|
+
this._lastUploadMeta = null;
|
|
234
|
+
/** @type {Map<string, { json: string; valueScale: number; valueOffset: number }>} */
|
|
235
|
+
this._nativeUploadJsonLru = new Map();
|
|
236
|
+
/** @type {Set<string>} Sync keys with gate texture registered in Android GL cache (AguaceroNexradGpuFrameReady). */
|
|
237
|
+
this._nativeGpuReadyKeys = new Set();
|
|
238
|
+
/** @type {Map<string, { valueScale: number; valueOffset: number }>} */
|
|
239
|
+
this._uploadMetaBySyncKey = new Map();
|
|
240
|
+
/** @type {{ remove: () => void } | null} */
|
|
241
|
+
this._gpuReadyEventSub = null;
|
|
242
|
+
try {
|
|
243
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
244
|
+
if (DeviceEventEmitter && typeof DeviceEventEmitter.addListener === 'function') {
|
|
245
|
+
this._gpuReadyEventSub = DeviceEventEmitter.addListener('AguaceroNexradGpuFrameReady', (e) => {
|
|
246
|
+
const k = e && e.nativeGpuCacheKey;
|
|
247
|
+
if (typeof k === 'string' && k.length > 0) {
|
|
248
|
+
this._nativeGpuReadyKeys.add(k);
|
|
249
|
+
this._trimNativeGpuReadyKeys(96);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
/* ignore */
|
|
255
|
+
}
|
|
256
|
+
const base = typeof core?.baseGridUrl === 'string' ? core.baseGridUrl.replace(/\/$/, '') : '';
|
|
257
|
+
if (base && typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
|
258
|
+
setNexradSitesJsonUrl(`${base}/data/nexrad.json`);
|
|
259
|
+
setNexradSitesFetchAuth(core.apiKey || '', core.bundleId || '');
|
|
260
|
+
}
|
|
261
|
+
setNexradArchiveSiteOrigin(core.gridRequestSiteOrigin || 'https://localhost');
|
|
262
|
+
if (isAguaceroRnDebugEnabled()) {
|
|
263
|
+
aguaceroDebug('nexrad.authConfigured', getAguaceroAuthDiagnosticSnapshot(core, { phase: 'NexradAndroidController.constructor' }));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_trimNativeGpuReadyKeys(max) {
|
|
268
|
+
while (this._nativeGpuReadyKeys.size > max) {
|
|
269
|
+
const first = this._nativeGpuReadyKeys.values().next().value;
|
|
270
|
+
if (first === undefined) break;
|
|
271
|
+
this._nativeGpuReadyKeys.delete(first);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_rememberUploadMetaForSyncKey(syncKey, valueScale, valueOffset) {
|
|
276
|
+
this._uploadMetaBySyncKey.set(syncKey, { valueScale, valueOffset });
|
|
277
|
+
while (this._uploadMetaBySyncKey.size > 40) {
|
|
278
|
+
const k = this._uploadMetaBySyncKey.keys().next().value;
|
|
279
|
+
if (k === undefined) break;
|
|
280
|
+
this._uploadMetaBySyncKey.delete(k);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
updateStyleOptions(options) {
|
|
285
|
+
if (options.interpolateNexradColormap != null) {
|
|
286
|
+
this._interpolateColormap = options.interpolateNexradColormap !== false;
|
|
287
|
+
}
|
|
288
|
+
if (options.nexradGateSmoothing != null) {
|
|
289
|
+
this._gateSmoothing = options.nexradGateSmoothing === true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
destroy() {
|
|
294
|
+
try {
|
|
295
|
+
this._gpuReadyEventSub?.remove();
|
|
296
|
+
} catch (_) {
|
|
297
|
+
/* ignore */
|
|
298
|
+
}
|
|
299
|
+
this._gpuReadyEventSub = null;
|
|
300
|
+
this._nativeGpuReadyKeys.clear();
|
|
301
|
+
this._uploadMetaBySyncKey.clear();
|
|
302
|
+
this._abort?.abort();
|
|
303
|
+
this._abort = null;
|
|
304
|
+
this._preloadAbort?.abort();
|
|
305
|
+
this._preloadAbort = null;
|
|
306
|
+
this._frameCache.clear();
|
|
307
|
+
this._gpuReadoutPrep = new WeakMap();
|
|
308
|
+
this._preloadTimelineSig = null;
|
|
309
|
+
this._lastSyncKey = null;
|
|
310
|
+
this._nativeFrameUploaded = false;
|
|
311
|
+
this._lastUploadMeta = null;
|
|
312
|
+
this._nativeUploadJsonLru.clear();
|
|
313
|
+
this._layerRef?.current?.clearNexrad?.();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_getPreparedReadoutFrame(frame, uploadOpts) {
|
|
317
|
+
let byOpts = this._gpuReadoutPrep.get(frame);
|
|
318
|
+
if (!byOpts) {
|
|
319
|
+
byOpts = new Map();
|
|
320
|
+
this._gpuReadoutPrep.set(frame, byOpts);
|
|
321
|
+
}
|
|
322
|
+
const optKey =
|
|
323
|
+
uploadOpts && typeof uploadOpts === 'object'
|
|
324
|
+
? JSON.stringify(uploadOpts)
|
|
325
|
+
: String(uploadOpts);
|
|
326
|
+
let prepared = byOpts.get(optKey);
|
|
327
|
+
if (!prepared) {
|
|
328
|
+
prepared = prepareRadarFrameForGpuReadout(frame, uploadOpts);
|
|
329
|
+
byOpts.set(optKey, prepared);
|
|
330
|
+
}
|
|
331
|
+
return prepared;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
_native() {
|
|
335
|
+
return this._layerRef?.current;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
_lutPack(state) {
|
|
339
|
+
const radarVar = (state.nexradProduct || 'REF').toUpperCase();
|
|
340
|
+
const colormapFlat = state.colormap;
|
|
341
|
+
const colormapForShader = colormapToMs(colormapFlat, radarVar, state.units);
|
|
342
|
+
const [shaderMin, shaderMax] = radarShaderValueRange(
|
|
343
|
+
colormapFlat?.[0],
|
|
344
|
+
colormapFlat?.[colormapFlat.length - 2],
|
|
345
|
+
radarVar,
|
|
346
|
+
state.units,
|
|
347
|
+
state.nexradDataSource,
|
|
348
|
+
);
|
|
349
|
+
const { bytes, discreteIntegerLut } = buildNexradLutRgba(
|
|
350
|
+
colormapForShader,
|
|
351
|
+
shaderMin,
|
|
352
|
+
shaderMax,
|
|
353
|
+
this._interpolateColormap,
|
|
354
|
+
);
|
|
355
|
+
return {
|
|
356
|
+
lutB64: fromByteArray(bytes),
|
|
357
|
+
lutMin: shaderMin,
|
|
358
|
+
lutMax: shaderMax,
|
|
359
|
+
discreteIntegerLut,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* LRU for full native JSON payloads (parity with mapsgl gate-texture reuse — avoids base64 + stringify on repeat scrubs).
|
|
365
|
+
* @param {string} syncKey
|
|
366
|
+
* @returns {{ json: string; valueScale: number; valueOffset: number } | null}
|
|
367
|
+
*/
|
|
368
|
+
_nativeUploadJsonLruTouch(syncKey) {
|
|
369
|
+
const ent = this._nativeUploadJsonLru.get(syncKey);
|
|
370
|
+
if (!ent) return null;
|
|
371
|
+
this._nativeUploadJsonLru.delete(syncKey);
|
|
372
|
+
this._nativeUploadJsonLru.set(syncKey, ent);
|
|
373
|
+
return ent;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** @param {string} syncKey @param {{ json: string; valueScale: number; valueOffset: number }} entry */
|
|
377
|
+
_nativeUploadJsonLruPut(syncKey, entry) {
|
|
378
|
+
if (this._nativeUploadJsonLru.has(syncKey)) this._nativeUploadJsonLru.delete(syncKey);
|
|
379
|
+
this._nativeUploadJsonLru.set(syncKey, entry);
|
|
380
|
+
while (this._nativeUploadJsonLru.size > NATIVE_UPLOAD_JSON_LRU_MAX) {
|
|
381
|
+
const oldest = this._nativeUploadJsonLru.keys().next().value;
|
|
382
|
+
if (oldest === undefined) break;
|
|
383
|
+
this._nativeUploadJsonLru.delete(oldest);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Full-frame JSON includes LUT + opacity + shader toggles; keep LRU key aligned so scrubbing
|
|
389
|
+
* cannot resurrect an outdated style after the user changes only presentation options.
|
|
390
|
+
*/
|
|
391
|
+
_nativeUploadLruKey(state, syncKey) {
|
|
392
|
+
return `${syncKey}|o:${state.opacity ?? 1}|g:${this._gateSmoothing ? 1 : 0}|i:${this._interpolateColormap ? 1 : 0}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* After JS decoded a frame into `_frameCache`, optionally push it through the native bridge.
|
|
397
|
+
* During timeline preload we **only** upload the **currently displayed** unix to Metal; other
|
|
398
|
+
* volumes stay in the JS cache until {@link sync} needs them (avoids 40+ full decodes on load).
|
|
399
|
+
*/
|
|
400
|
+
_primeNativeGpuUploadIfNeeded(snapshot, unix, p, frame, abortSignal) {
|
|
401
|
+
if (!frame || !p || (abortSignal && abortSignal.aborted)) return;
|
|
402
|
+
try {
|
|
403
|
+
const displayUx =
|
|
404
|
+
snapshot.nexradTimestamp != null && Number.isFinite(Number(snapshot.nexradTimestamp))
|
|
405
|
+
? Number(snapshot.nexradTimestamp)
|
|
406
|
+
: NaN;
|
|
407
|
+
if (Number.isFinite(displayUx) && Number(unix) !== displayUx) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const syncKey = this._syncIdentity(snapshot, p, unix);
|
|
411
|
+
if (this._nativeGpuReadyKeys.has(syncKey)) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (!this._native()?.uploadNexradFrame) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this._uploadFrameToNative(snapshot, frame, p, unix);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
/* ignore */
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @param {*} state
|
|
425
|
+
* @param {*} frame - decoded archive frame
|
|
426
|
+
* @param {{ fetchKey: string }} p - from {@link _buildFetchParamsForUnix}
|
|
427
|
+
* @param {number} unix
|
|
428
|
+
*/
|
|
429
|
+
_uploadFrameToNative(state, frame, p, unix) {
|
|
430
|
+
const uploadTotal = nexradPerfSpan(
|
|
431
|
+
`uploadFrame.total site=${state.nexradSite} unix=${unix} var=${p?.radarVar ?? state.nexradProduct}`,
|
|
432
|
+
);
|
|
433
|
+
const n = this._native();
|
|
434
|
+
if (!n?.uploadNexradFrame) {
|
|
435
|
+
uploadTotal.end({ outcome: 'skippedNoNative' });
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const syncKey = this._syncIdentity(state, p, unix);
|
|
440
|
+
const lruKey = this._nativeUploadLruKey(state, syncKey);
|
|
441
|
+
const lruHit = this._nativeUploadJsonLruTouch(lruKey);
|
|
442
|
+
if (lruHit) {
|
|
443
|
+
this._lastUploadMeta = { valueScale: lruHit.valueScale, valueOffset: lruHit.valueOffset };
|
|
444
|
+
this._rememberUploadMetaForSyncKey(syncKey, lruHit.valueScale, lruHit.valueOffset);
|
|
445
|
+
if (n.activateNexradCachedFrame && this._nativeGpuReadyKeys.has(syncKey)) {
|
|
446
|
+
const tAct = nexradPerfSpan('uploadFrame.nativeGpuActivateOnly');
|
|
447
|
+
n.activateNexradCachedFrame(syncKey);
|
|
448
|
+
tAct.end({ keyLen: syncKey.length });
|
|
449
|
+
const tStyle = nexradPerfSpan('uploadFrame.styleAfterGpuActivate');
|
|
450
|
+
this.applyStyleFromState(state);
|
|
451
|
+
tStyle.end({});
|
|
452
|
+
this._nativeFrameUploaded = true;
|
|
453
|
+
uploadTotal.end({ outcome: 'gpuActivateOnly', jsonCharsSkipped: lruHit.json.length });
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const bridge = nexradPerfSpan('uploadFrame.nativeJsonLru.bridgeDispatch');
|
|
457
|
+
n.uploadNexradFrame(lruHit.json);
|
|
458
|
+
bridge.end({ jsonChars: lruHit.json.length, path: 'jsonLruHit' });
|
|
459
|
+
this._nativeFrameUploaded = true;
|
|
460
|
+
uploadTotal.end({ outcome: 'jsonLruHit', jsonChars: lruHit.json.length });
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const tOpts = nexradPerfSpan('uploadFrame.mapboxFrameUploadOptions');
|
|
465
|
+
const uploadOpts = mapboxFrameUploadOptionsForNexradState(state);
|
|
466
|
+
tOpts.end({});
|
|
467
|
+
|
|
468
|
+
// Skip JS sorting for Android native upload
|
|
469
|
+
// We pass the raw frame and let Android do the sorting/canonicalization
|
|
470
|
+
const rawFrame = frame;
|
|
471
|
+
|
|
472
|
+
this._lastUploadMeta = {
|
|
473
|
+
valueScale: rawFrame.valueScale,
|
|
474
|
+
valueOffset: rawFrame.valueOffset,
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const tLut = nexradPerfSpan('uploadFrame.lutPack_buildNexradLutRgba');
|
|
478
|
+
const style = this._lutPack(state);
|
|
479
|
+
tLut.end({
|
|
480
|
+
lutB64Chars: style.lutB64?.length ?? 0,
|
|
481
|
+
discreteIntegerLut: style.discreteIntegerLut,
|
|
482
|
+
lutMin: style.lutMin,
|
|
483
|
+
lutMax: style.lutMax,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const tRay = nexradPerfSpan('uploadFrame.rayBoundariesToJsArray');
|
|
487
|
+
const rayBoundaries = rawFrame.rayBoundariesDeg?.slice ? Array.from(rawFrame.rayBoundariesDeg) : [...(rawFrame.rayBoundariesDeg || [])];
|
|
488
|
+
tRay.end({ len: rayBoundaries.length });
|
|
489
|
+
|
|
490
|
+
const tB64 = nexradPerfSpan('uploadFrame.gateData_toBase64');
|
|
491
|
+
const rleGateData = rleCompressGateData(rawFrame.gateData);
|
|
492
|
+
const gateB64 = fromByteArray(rleGateData);
|
|
493
|
+
tB64.end({ gateB64Chars: gateB64.length, rleBytes: rleGateData.length, rawBytes: rawFrame.gateData.length });
|
|
494
|
+
|
|
495
|
+
const payload = {
|
|
496
|
+
gateB64,
|
|
497
|
+
gateIsRle: true,
|
|
498
|
+
nGates: rawFrame.nGates,
|
|
499
|
+
nRays: rawFrame.nRays,
|
|
500
|
+
stationLat: rawFrame.stationLat,
|
|
501
|
+
stationLon: rawFrame.stationLon,
|
|
502
|
+
firstGateKm: rawFrame.firstGateKm,
|
|
503
|
+
gateWidthKm: rawFrame.gateWidthKm,
|
|
504
|
+
rayBoundaries,
|
|
505
|
+
lutB64: style.lutB64,
|
|
506
|
+
valueScale: rawFrame.valueScale,
|
|
507
|
+
valueOffset: rawFrame.valueOffset,
|
|
508
|
+
lutMin: style.lutMin,
|
|
509
|
+
lutMax: style.lutMax,
|
|
510
|
+
discreteIntegerLut: style.discreteIntegerLut,
|
|
511
|
+
opacity: state.opacity ?? 1,
|
|
512
|
+
gateSmoothPolar: this._gateSmoothing ? 1 : 0,
|
|
513
|
+
interpolateLut: this._interpolateColormap ? 1 : 0,
|
|
514
|
+
nativeGpuCacheKey: syncKey,
|
|
515
|
+
uploadOpts: uploadOpts,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const tJson = nexradPerfSpan('uploadFrame.JSON_stringify_payload');
|
|
519
|
+
const json = JSON.stringify(payload);
|
|
520
|
+
tJson.end({
|
|
521
|
+
jsonChars: json.length,
|
|
522
|
+
rayBoundaryLen: rayBoundaries.length,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const tLruPut = nexradPerfSpan('uploadFrame.nativeUploadJsonLruPut');
|
|
526
|
+
this._nativeUploadJsonLruPut(lruKey, {
|
|
527
|
+
json,
|
|
528
|
+
valueScale: rawFrame.valueScale,
|
|
529
|
+
valueOffset: rawFrame.valueOffset,
|
|
530
|
+
});
|
|
531
|
+
tLruPut.end({ lruKeyTail: lruKey.slice(-96), lruSize: this._nativeUploadJsonLru.size });
|
|
532
|
+
|
|
533
|
+
const gateSummary = nexradDiagGateTextureSummary(
|
|
534
|
+
rawFrame.gateData,
|
|
535
|
+
rawFrame.nGates,
|
|
536
|
+
rawFrame.nRays,
|
|
537
|
+
rawFrame.valueScale,
|
|
538
|
+
rawFrame.valueOffset,
|
|
539
|
+
style.lutMin,
|
|
540
|
+
style.lutMax,
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
const tBridge = nexradPerfSpan('uploadFrame.nativeBridge_uploadNexradFrame');
|
|
544
|
+
n.uploadNexradFrame(json);
|
|
545
|
+
tBridge.end({ jsonChars: json.length, path: 'fullRebuild' });
|
|
546
|
+
|
|
547
|
+
this._rememberUploadMetaForSyncKey(syncKey, rawFrame.valueScale, rawFrame.valueOffset);
|
|
548
|
+
this._nativeFrameUploaded = true;
|
|
549
|
+
uploadTotal.end({
|
|
550
|
+
outcome: 'fullPipeline',
|
|
551
|
+
jsonChars: json.length,
|
|
552
|
+
nGates: rawFrame.nGates,
|
|
553
|
+
nRays: rawFrame.nRays,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
applyStyleFromState(state) {
|
|
558
|
+
const perf = nexradPerfSpan(
|
|
559
|
+
`applyStyleFromState site=${state.nexradSite} product=${state.nexradProduct} tilt=${state.nexradTilt}`,
|
|
560
|
+
);
|
|
561
|
+
const n = this._native();
|
|
562
|
+
if (!this._nativeFrameUploaded || !this._lastUploadMeta || !n?.uploadNexradStyleOnly) {
|
|
563
|
+
perf.end({
|
|
564
|
+
outcome: 'skipped',
|
|
565
|
+
nativeFrameUploaded: this._nativeFrameUploaded,
|
|
566
|
+
hasUploadMeta: !!this._lastUploadMeta,
|
|
567
|
+
hasStyleMethod: !!n?.uploadNexradStyleOnly,
|
|
568
|
+
});
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const tLut = nexradPerfSpan('applyStyleFromState.lutPack');
|
|
573
|
+
const style = this._lutPack(state);
|
|
574
|
+
tLut.end({ lutB64Chars: style.lutB64?.length ?? 0 });
|
|
575
|
+
|
|
576
|
+
const payload = {
|
|
577
|
+
lutB64: style.lutB64,
|
|
578
|
+
valueScale: this._lastUploadMeta.valueScale,
|
|
579
|
+
valueOffset: this._lastUploadMeta.valueOffset,
|
|
580
|
+
lutMin: style.lutMin,
|
|
581
|
+
lutMax: style.lutMax,
|
|
582
|
+
discreteIntegerLut: style.discreteIntegerLut,
|
|
583
|
+
opacity: state.opacity ?? 1,
|
|
584
|
+
gateSmoothPolar: this._gateSmoothing ? 1 : 0,
|
|
585
|
+
interpolateLut: this._interpolateColormap ? 1 : 0,
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const tJson = nexradPerfSpan('applyStyleFromState.JSON_stringify');
|
|
589
|
+
const json = JSON.stringify(payload);
|
|
590
|
+
tJson.end({ jsonChars: json.length });
|
|
591
|
+
|
|
592
|
+
const tBridge = nexradPerfSpan('applyStyleFromState.nativeBridge');
|
|
593
|
+
n.uploadNexradStyleOnly(json);
|
|
594
|
+
tBridge.end({ jsonChars: json.length });
|
|
595
|
+
|
|
596
|
+
perf.end({ outcome: 'dispatched' });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
_resolveListingContext(state) {
|
|
600
|
+
const nk = this.core._nexradTimesCacheKey?.();
|
|
601
|
+
const ent = nk ? this.core.nexradTimesByStation?.[nk] : null;
|
|
602
|
+
// Merge so we still resolve object keys if the last state event omitted maps but the core cache is populated.
|
|
603
|
+
const timeToKeyMap = { ...(ent?.timeToKeyMap || {}), ...(state.nexradTimeToKeyMap || {}) };
|
|
604
|
+
const motionMap = {
|
|
605
|
+
...(ent?.level3MotionTimeToKeyMap || {}),
|
|
606
|
+
...(state.nexradLevel3MotionTimeToKeyMap || {}),
|
|
607
|
+
};
|
|
608
|
+
return { nk, ent, timeToKeyMap, motionMap };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
_buildFetchParamsForUnix(state, unix) {
|
|
612
|
+
const radarVar = (state.nexradProduct || 'REF').toUpperCase();
|
|
613
|
+
const radarSource = state.nexradDataSource === 'level3' ? 'level3' : 'level2';
|
|
614
|
+
const groupId = nexradBinGroupIdForKey(variableToNexradGroup(radarVar));
|
|
615
|
+
const { timeToKeyMap, motionMap } = this._resolveListingContext(state);
|
|
616
|
+
const objectKey = timeToKeyMap[String(unix)];
|
|
617
|
+
if (!objectKey) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
let motionObjectKey = null;
|
|
622
|
+
if (radarVar === 'VEL' && state.nexradStormRelative) {
|
|
623
|
+
motionObjectKey = pickNearestLevel3ObjectKey(unix, motionMap);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const url = objectKeyToUrl(objectKey, radarSource);
|
|
627
|
+
const fetchKey = `${url}|${radarVar}|${radarSource}|${motionObjectKey || ''}`;
|
|
628
|
+
return { objectKey, url, fetchKey, motionObjectKey, radarVar, radarSource, groupId };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async _fetchFrame(state, unix, { signal, priority }) {
|
|
632
|
+
const fetchPerf = nexradPerfSpan(
|
|
633
|
+
`fetchAndParseArchive priority=${priority} site=${state.nexradSite} unix=${unix} product=${state.nexradProduct}`,
|
|
634
|
+
);
|
|
635
|
+
const p = this._buildFetchParamsForUnix(state, unix);
|
|
636
|
+
if (!p) {
|
|
637
|
+
fetchPerf.end({ ok: false, reason: 'noFetchParams' });
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
const frame = await fetchAndParseArchive(p.url, p.objectKey, p.radarVar, p.groupId, p.radarSource, {
|
|
642
|
+
signal,
|
|
643
|
+
priority,
|
|
644
|
+
level3MotionObjectKey: p.motionObjectKey,
|
|
645
|
+
});
|
|
646
|
+
fetchPerf.end({
|
|
647
|
+
ok: !!frame,
|
|
648
|
+
radarVar: p.radarVar,
|
|
649
|
+
radarSource: p.radarSource,
|
|
650
|
+
objectKeyTail: (p.objectKey || '').slice(-48),
|
|
651
|
+
motionKeyTail: (p.motionObjectKey || '').slice(-48),
|
|
652
|
+
});
|
|
653
|
+
return frame;
|
|
654
|
+
} catch (err) {
|
|
655
|
+
fetchPerf.end({
|
|
656
|
+
ok: false,
|
|
657
|
+
error: err?.message || String(err),
|
|
658
|
+
radarVar: p.radarVar,
|
|
659
|
+
radarSource: p.radarSource,
|
|
660
|
+
});
|
|
661
|
+
throw err;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
_preloadTimelineSignature(state) {
|
|
666
|
+
const nk = this.core._nexradTimesCacheKey?.() || '';
|
|
667
|
+
const ts = [...(state.availableNexradTimestamps || [])]
|
|
668
|
+
.map(Number)
|
|
669
|
+
.filter((t) => Number.isFinite(t))
|
|
670
|
+
.sort((a, b) => a - b)
|
|
671
|
+
.join(',');
|
|
672
|
+
const tilt =
|
|
673
|
+
state.nexradTilt != null && Number.isFinite(Number(state.nexradTilt))
|
|
674
|
+
? Number(state.nexradTilt).toFixed(3)
|
|
675
|
+
: '';
|
|
676
|
+
return `${nk}|${ts}|sr:${state.nexradStormRelative ? 1 : 0}|tilt:${tilt}`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
_syncIdentity(state, p, unix) {
|
|
680
|
+
const tilt =
|
|
681
|
+
state.nexradTilt != null && Number.isFinite(Number(state.nexradTilt))
|
|
682
|
+
? Number(state.nexradTilt).toFixed(3)
|
|
683
|
+
: '';
|
|
684
|
+
return `${p.fetchKey}|tilt:${tilt}|u:${unix}`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
preloadAllAvailable(state) {
|
|
688
|
+
if (!state.isNexrad || !state.nexradSite) return;
|
|
689
|
+
|
|
690
|
+
if (!ensureRadarArchiveBindings('preloadAllAvailable')) return;
|
|
691
|
+
|
|
692
|
+
const times = [...(state.availableNexradTimestamps || [])]
|
|
693
|
+
.map(Number)
|
|
694
|
+
.filter((t) => Number.isFinite(t));
|
|
695
|
+
if (!times.length) {
|
|
696
|
+
// Important: do not touch `_preloadTimelineSig` or the frame cache here — callers
|
|
697
|
+
// sometimes pass `core.state` before `availableNexradTimestamps` is committed; an
|
|
698
|
+
// empty list must not poison the signature or wipe the LRU (see WeatherLayerManager
|
|
699
|
+
// preload kick merging subscription snapshot).
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const sig = this._preloadTimelineSignature(state);
|
|
704
|
+
if (sig === this._preloadTimelineSig) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
this._preloadTimelineSig = sig;
|
|
709
|
+
this._preloadAbort?.abort();
|
|
710
|
+
this._preloadAbort = new AbortController();
|
|
711
|
+
const signal = this._preloadAbort.signal;
|
|
712
|
+
|
|
713
|
+
// Keep the volume currently on screen in the JS cache so getInspectPayload (mapsgl parity readouts)
|
|
714
|
+
// still works while we clear and rebuild the prefetch LRU — otherwise readouts stay null until
|
|
715
|
+
// this unix is fetched again (often last in the timeline).
|
|
716
|
+
const curUnix =
|
|
717
|
+
state.nexradTimestamp != null && Number.isFinite(Number(state.nexradTimestamp))
|
|
718
|
+
? Number(state.nexradTimestamp)
|
|
719
|
+
: NaN;
|
|
720
|
+
|
|
721
|
+
// Keep frames that are still in the new timeline (or the currently displayed one)
|
|
722
|
+
// to avoid re-fetching them when expanding the duration window.
|
|
723
|
+
const validFetchKeys = new Set();
|
|
724
|
+
for (const unix of times) {
|
|
725
|
+
const p = this._buildFetchParamsForUnix(state, unix);
|
|
726
|
+
if (p) validFetchKeys.add(p.fetchKey);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const curP = Number.isFinite(curUnix) ? this._buildFetchParamsForUnix(state, curUnix) : null;
|
|
730
|
+
if (curP) validFetchKeys.add(curP.fetchKey);
|
|
731
|
+
|
|
732
|
+
for (const key of this._frameCache.keys()) {
|
|
733
|
+
if (!validFetchKeys.has(key)) {
|
|
734
|
+
this._frameCache.delete(key);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// Do NOT clear `_nativeGpuReadyKeys` / JSON LRU / upload meta here: that forced a full
|
|
738
|
+
// ~1MB JSON parse + native decode on every timeline scrub after prefetch. GPU cache keys
|
|
739
|
+
// stay valid while Metal still holds the slot; `sync` falls back to full upload if needed.
|
|
740
|
+
// (Controller `destroy()` still clears everything on site/mode teardown.)
|
|
741
|
+
|
|
742
|
+
setNexradArchiveApiKey(this.core.apiKey || '');
|
|
743
|
+
setNexradArchiveBundleId(this.core.bundleId || '');
|
|
744
|
+
setNexradArchiveSiteOrigin(this.core.gridRequestSiteOrigin || 'https://localhost');
|
|
745
|
+
setNexradSitesFetchAuth(this.core.apiKey || '', this.core.bundleId || '');
|
|
746
|
+
if (isAguaceroRnDebugEnabled()) {
|
|
747
|
+
aguaceroDebug('nexrad.authConfigured', getAguaceroAuthDiagnosticSnapshot(this.core, {
|
|
748
|
+
phase: 'NexradAndroidController.preload',
|
|
749
|
+
site: state.nexradSite,
|
|
750
|
+
product: state.nexradProduct,
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const snapshot = { ...state };
|
|
755
|
+
const preloadBatch = nexradPerfSpan(`preload.batch site=${state.nexradSite} times=${times.length}`);
|
|
756
|
+
let preloadCompleted = 0;
|
|
757
|
+
let preloadFailed = 0;
|
|
758
|
+
let preloadSkippedCached = 0;
|
|
759
|
+
|
|
760
|
+
const run = async () => {
|
|
761
|
+
// Prime the visible unix first so the active frame hits GPU before background slots.
|
|
762
|
+
const curUx =
|
|
763
|
+
snapshot.nexradTimestamp != null && Number.isFinite(Number(snapshot.nexradTimestamp))
|
|
764
|
+
? Number(snapshot.nexradTimestamp)
|
|
765
|
+
: NaN;
|
|
766
|
+
if (Number.isFinite(curUx)) {
|
|
767
|
+
const p0 = this._buildFetchParamsForUnix(snapshot, curUx);
|
|
768
|
+
if (p0) {
|
|
769
|
+
const fr0 = this._frameCache.get(p0.fetchKey);
|
|
770
|
+
if (fr0) {
|
|
771
|
+
this._primeNativeGpuUploadIfNeeded(snapshot, curUx, p0, fr0, signal);
|
|
772
|
+
await yieldToJsEventLoop();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
let cursor = 0;
|
|
778
|
+
const work = async () => {
|
|
779
|
+
while (cursor < times.length && !signal.aborted) {
|
|
780
|
+
const i = cursor++;
|
|
781
|
+
const unix = times[i];
|
|
782
|
+
const slot = nexradPerfSpan(`preload.slot idx=${i + 1}/${times.length} unix=${unix}`);
|
|
783
|
+
const p = this._buildFetchParamsForUnix(snapshot, unix);
|
|
784
|
+
if (!p || signal.aborted) {
|
|
785
|
+
slot.end({ outcome: 'noParamsOrAborted' });
|
|
786
|
+
} else if (this._frameCache.has(p.fetchKey)) {
|
|
787
|
+
preloadSkippedCached++;
|
|
788
|
+
const cachedFrame = this._frameCache.get(p.fetchKey);
|
|
789
|
+
this._primeNativeGpuUploadIfNeeded(snapshot, unix, p, cachedFrame, signal);
|
|
790
|
+
await yieldToJsEventLoop();
|
|
791
|
+
slot.end({ outcome: 'alreadyCached', fetchKeyTail: p.fetchKey.slice(-64) });
|
|
792
|
+
} else {
|
|
793
|
+
try {
|
|
794
|
+
const frame = await this._fetchFrame(snapshot, unix, {
|
|
795
|
+
signal,
|
|
796
|
+
priority: 'prefetch',
|
|
797
|
+
});
|
|
798
|
+
if (frame && !signal.aborted) {
|
|
799
|
+
this._frameCache.set(p.fetchKey, frame);
|
|
800
|
+
preloadCompleted++;
|
|
801
|
+
this._primeNativeGpuUploadIfNeeded(snapshot, unix, p, frame, signal);
|
|
802
|
+
await yieldToJsEventLoop();
|
|
803
|
+
slot.end({
|
|
804
|
+
outcome: 'cachedFrame',
|
|
805
|
+
fetchKeyTail: p.fetchKey.slice(-64),
|
|
806
|
+
cacheSizeAfter: this._frameCache.size,
|
|
807
|
+
});
|
|
808
|
+
} else {
|
|
809
|
+
slot.end({ outcome: 'noFrame', aborted: signal.aborted });
|
|
810
|
+
}
|
|
811
|
+
} catch (err) {
|
|
812
|
+
preloadFailed++;
|
|
813
|
+
slot.end({ outcome: 'error', message: err?.message || String(err) });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
await yieldToJsEventLoop();
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
await Promise.all(Array.from({ length: PRELOAD_CONCURRENCY }, () => work()));
|
|
820
|
+
|
|
821
|
+
// Catch-up: any unix still missing a GPU slot (e.g. layer mounted mid-preload) gets one upload pass.
|
|
822
|
+
if (!signal.aborted) {
|
|
823
|
+
for (let i = 0; i < times.length && !signal.aborted; i++) {
|
|
824
|
+
const unix = times[i];
|
|
825
|
+
const p = this._buildFetchParamsForUnix(snapshot, unix);
|
|
826
|
+
if (!p) continue;
|
|
827
|
+
const frame = this._frameCache.get(p.fetchKey);
|
|
828
|
+
if (!frame) continue;
|
|
829
|
+
const syncKey = this._syncIdentity(snapshot, p, unix);
|
|
830
|
+
if (this._nativeGpuReadyKeys.has(syncKey)) continue;
|
|
831
|
+
this._primeNativeGpuUploadIfNeeded(snapshot, unix, p, frame, signal);
|
|
832
|
+
await yieldToJsEventLoop();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
preloadBatch.end({
|
|
837
|
+
outcome: signal.aborted ? 'aborted' : 'complete',
|
|
838
|
+
completed: preloadCompleted,
|
|
839
|
+
failed: preloadFailed,
|
|
840
|
+
skippedCached: preloadSkippedCached,
|
|
841
|
+
finalCacheSize: this._frameCache.size,
|
|
842
|
+
gpuReadyCount: this._nativeGpuReadyKeys.size,
|
|
843
|
+
});
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
void run();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
async sync(state) {
|
|
850
|
+
const unixEarly = state.nexradTimestamp != null ? Number(state.nexradTimestamp) : NaN;
|
|
851
|
+
const syncTotal = nexradPerfSpan(
|
|
852
|
+
`sync.total site=${state.nexradSite} unix=${unixEarly} product=${state.nexradProduct} source=${state.nexradDataSource} tilt=${state.nexradTilt}`,
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
if (!state.isNexrad || !state.nexradSite || state.nexradTimestamp == null) {
|
|
856
|
+
this.destroy();
|
|
857
|
+
syncTotal.end({ outcome: 'skippedNotNexradOrMissing' });
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (!ensureRadarArchiveBindings('sync')) {
|
|
862
|
+
syncTotal.end({ outcome: 'skippedMissingArchiveBindings' });
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const tAuth = nexradPerfSpan('sync.setArchiveAuth');
|
|
867
|
+
setNexradArchiveApiKey(this.core.apiKey || '');
|
|
868
|
+
setNexradArchiveBundleId(this.core.bundleId || '');
|
|
869
|
+
setNexradArchiveSiteOrigin(this.core.gridRequestSiteOrigin || 'https://localhost');
|
|
870
|
+
setNexradSitesFetchAuth(this.core.apiKey || '', this.core.bundleId || '');
|
|
871
|
+
tAuth.end({});
|
|
872
|
+
|
|
873
|
+
const unix = Number(state.nexradTimestamp);
|
|
874
|
+
|
|
875
|
+
const tParams = nexradPerfSpan('sync.buildFetchParams');
|
|
876
|
+
const p = this._buildFetchParamsForUnix(state, unix);
|
|
877
|
+
tParams.end({ ok: !!p });
|
|
878
|
+
if (!p) {
|
|
879
|
+
const ctx = this._resolveListingContext(state);
|
|
880
|
+
syncTotal.end({ outcome: 'noFetchParams' });
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const tIdentity = nexradPerfSpan('sync.syncIdentity');
|
|
885
|
+
const syncKey = this._syncIdentity(state, p, unix);
|
|
886
|
+
tIdentity.end({ syncKeyTail: syncKey.slice(-96), lastSyncKeyMatch: syncKey === this._lastSyncKey });
|
|
887
|
+
|
|
888
|
+
const jsCachedForShortCircuit = this._frameCache.get(p.fetchKey);
|
|
889
|
+
if (syncKey === this._lastSyncKey && this._nativeFrameUploaded && jsCachedForShortCircuit) {
|
|
890
|
+
const tStyle = nexradPerfSpan('sync.shortCircuit_styleOnly');
|
|
891
|
+
this.applyStyleFromState(state);
|
|
892
|
+
tStyle.end({});
|
|
893
|
+
syncTotal.end({ outcome: 'styleOnlyShortCircuit' });
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const tCacheLookup = nexradPerfSpan('sync.memoryCacheLookup');
|
|
898
|
+
const cached = this._frameCache.get(p.fetchKey);
|
|
899
|
+
tCacheLookup.end({ hit: !!cached, cacheSize: this._frameCache.size, fetchKeyTail: p.fetchKey.slice(-80) });
|
|
900
|
+
|
|
901
|
+
const tGpuTry = nexradPerfSpan('sync.tryGpuCacheActivate');
|
|
902
|
+
if (cached && this._nativeGpuReadyKeys.has(syncKey)) {
|
|
903
|
+
const n = this._native();
|
|
904
|
+
if (n?.activateNexradCachedFrame) {
|
|
905
|
+
const tAct = nexradPerfSpan('sync.dispatchActivateCachedFrame');
|
|
906
|
+
n.activateNexradCachedFrame(syncKey);
|
|
907
|
+
tAct.end({ keyLen: syncKey.length });
|
|
908
|
+
const tStyle = nexradPerfSpan('sync.applyStyleAfterGpuActivate');
|
|
909
|
+
this.applyStyleFromState(state);
|
|
910
|
+
tStyle.end({});
|
|
911
|
+
const meta = this._uploadMetaBySyncKey.get(syncKey);
|
|
912
|
+
if (meta) {
|
|
913
|
+
this._lastUploadMeta = { valueScale: meta.valueScale, valueOffset: meta.valueOffset };
|
|
914
|
+
}
|
|
915
|
+
this._lastSyncKey = syncKey;
|
|
916
|
+
this._nativeFrameUploaded = true;
|
|
917
|
+
tGpuTry.end({ outcome: 'gpuCacheActivate', gpuReadySetSize: this._nativeGpuReadyKeys.size });
|
|
918
|
+
syncTotal.end({ outcome: 'gpuCacheActivate' });
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
tGpuTry.end({
|
|
923
|
+
outcome: 'noGpuActivate',
|
|
924
|
+
hasJsCachedFrame: !!cached,
|
|
925
|
+
gpuReadyForSyncKey: this._nativeGpuReadyKeys.has(syncKey),
|
|
926
|
+
hasActivateMethod: !!this._native()?.activateNexradCachedFrame,
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
if (cached) {
|
|
930
|
+
const tUpload = nexradPerfSpan('sync.uploadFromMemoryCache_toNative');
|
|
931
|
+
this._uploadFrameToNative(state, cached, p, unix);
|
|
932
|
+
tUpload.end({});
|
|
933
|
+
this._lastSyncKey = syncKey;
|
|
934
|
+
syncTotal.end({ outcome: 'memoryCacheHit' });
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
this._abort?.abort();
|
|
939
|
+
this._abort = new AbortController();
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
let frame;
|
|
943
|
+
try {
|
|
944
|
+
frame = await this._fetchFrame(state, unix, {
|
|
945
|
+
signal: this._abort.signal,
|
|
946
|
+
priority: 'display',
|
|
947
|
+
});
|
|
948
|
+
} catch (err) {
|
|
949
|
+
syncTotal.end({ outcome: 'fetchThrew', message: err?.message || String(err) });
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (!frame || this._abort.signal.aborted) {
|
|
954
|
+
syncTotal.end({
|
|
955
|
+
outcome: 'fetchNoFrame',
|
|
956
|
+
aborted: this._abort.signal.aborted,
|
|
957
|
+
hadFrame: !!frame,
|
|
958
|
+
});
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const tPut = nexradPerfSpan('sync.frameCacheSet');
|
|
963
|
+
this._frameCache.set(p.fetchKey, frame);
|
|
964
|
+
tPut.end({ cacheSizeAfter: this._frameCache.size });
|
|
965
|
+
|
|
966
|
+
const tUploadFetched = nexradPerfSpan('sync.uploadFetchedFrame_toNative');
|
|
967
|
+
this._uploadFrameToNative(state, frame, p, unix);
|
|
968
|
+
tUploadFetched.end({});
|
|
969
|
+
this._lastSyncKey = syncKey;
|
|
970
|
+
syncTotal.end({ outcome: 'fetchedAndUploaded' });
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
getInspectPayload(lng, lat, state) {
|
|
974
|
+
if (!state?.isNexrad || !state.nexradSite || state.nexradTimestamp == null) {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
if (state.visible === false || (state.opacity ?? 1) <= 0) {
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const unix = Number(state.nexradTimestamp);
|
|
982
|
+
const p = this._buildFetchParamsForUnix(state, unix);
|
|
983
|
+
if (!p) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const frame = this._frameCache.get(p.fetchKey);
|
|
988
|
+
if (!frame) {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const colormapFlat = state.colormap;
|
|
993
|
+
if (!colormapFlat || colormapFlat.length < 2) {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const rangeMin = colormapFlat[0];
|
|
998
|
+
const rangeMax = colormapFlat[colormapFlat.length - 2];
|
|
999
|
+
|
|
1000
|
+
const radarVariable = (state.nexradProduct || 'REF').toUpperCase();
|
|
1001
|
+
const radarSource = state.nexradDataSource === 'level3' ? 'level3' : 'level2';
|
|
1002
|
+
const displayUnits =
|
|
1003
|
+
state.units && String(state.units).toLowerCase() !== 'none'
|
|
1004
|
+
? state.units
|
|
1005
|
+
: isVelocityStyleRadarVar(radarVariable)
|
|
1006
|
+
? 'm/s'
|
|
1007
|
+
: '';
|
|
1008
|
+
|
|
1009
|
+
const uploadOptsForReadout = mapboxFrameUploadOptionsForNexradState(state);
|
|
1010
|
+
const gpuFrame = this._getPreparedReadoutFrame(frame, uploadOptsForReadout);
|
|
1011
|
+
const sample = sampleNexradFrameAtLatLon(gpuFrame, lat, lng, { smoothPolar: this._gateSmoothing });
|
|
1012
|
+
if (!sample) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const valueMs = sample.value;
|
|
1017
|
+
const isHydro = radarSource === 'level3' && nexradLevel3IsHydrometeorClassification(radarVariable);
|
|
1018
|
+
|
|
1019
|
+
let valueOut;
|
|
1020
|
+
let unitOut = displayUnits || '';
|
|
1021
|
+
|
|
1022
|
+
if (isVelocityStyleRadarVar(radarVariable)) {
|
|
1023
|
+
valueOut = velocityMsToDisplay(valueMs, displayUnits);
|
|
1024
|
+
} else if (isHydro) {
|
|
1025
|
+
const label = nexradHydrometeorLabelForClassIndex(valueMs);
|
|
1026
|
+
if (label == null) {
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
valueOut = label;
|
|
1030
|
+
unitOut = '';
|
|
1031
|
+
} else {
|
|
1032
|
+
valueOut = formatNexradInspectNumeric(valueMs, radarVariable);
|
|
1033
|
+
const bu = state.colormapBaseUnit;
|
|
1034
|
+
if (bu != null && String(bu).length > 0 && String(bu).toLowerCase() !== 'none') {
|
|
1035
|
+
unitOut = bu;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
let inRange;
|
|
1040
|
+
if (isHydro) {
|
|
1041
|
+
const idx = Math.round(Number(valueMs));
|
|
1042
|
+
inRange =
|
|
1043
|
+
(rangeMin == null || idx >= rangeMin) && (rangeMax == null || idx <= rangeMax);
|
|
1044
|
+
} else if (isVelocityStyleRadarVar(radarVariable)) {
|
|
1045
|
+
inRange =
|
|
1046
|
+
(rangeMin == null || valueOut >= rangeMin) && (rangeMax == null || valueOut <= rangeMax);
|
|
1047
|
+
} else {
|
|
1048
|
+
// Do not apply the colormap *lower* stop as a hard readout floor: weak echoes below the first
|
|
1049
|
+
// legend tick can still be drawn (LUT / GL), and users expect a number there.
|
|
1050
|
+
inRange = rangeMax == null || valueOut <= rangeMax;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!inRange) {
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const tiltDeg = Number.isFinite(state.nexradTilt) ? state.nexradTilt : getDefaultRadarTilt(state.nexradSite);
|
|
1058
|
+
const bhKm = beamHeightKm(sample.groundRangeKm, tiltDeg);
|
|
1059
|
+
const metric = state.units === 'metric';
|
|
1060
|
+
const beamHeightDisplay = metric ? bhKm : bhKm * 3.280839895013123;
|
|
1061
|
+
const beamUnit = metric ? 'km' : 'kft';
|
|
1062
|
+
|
|
1063
|
+
const fldKey = state.variable;
|
|
1064
|
+
return {
|
|
1065
|
+
lngLat: { lng, lat },
|
|
1066
|
+
variable: {
|
|
1067
|
+
code: fldKey,
|
|
1068
|
+
name: this.core.getVariableDisplayName(fldKey),
|
|
1069
|
+
},
|
|
1070
|
+
value: valueOut,
|
|
1071
|
+
unit: unitOut,
|
|
1072
|
+
beamHeight:
|
|
1073
|
+
Number.isFinite(beamHeightDisplay) && Number.isFinite(bhKm)
|
|
1074
|
+
? { value: beamHeightDisplay, unit: beamUnit }
|
|
1075
|
+
: null,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
}
|