@aguacerowx/react-native 0.0.52 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/LICENSE +21 -0
  2. package/android/src/main/cpp/satellite_ktx_jni.cpp +6 -1
  3. package/android/src/main/java/com/aguacerowx/reactnative/NexradRadarLayerView.java +14 -2
  4. package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayer.java +121 -1
  5. package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayerView.java +556 -392
  6. package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +77 -0
  7. package/index.js +1 -1
  8. package/ios/SatelliteLayerView.swift +517 -517
  9. package/ios/WeatherFrameProcessorModule.m +19 -15
  10. package/ios/WeatherFrameProcessorModule.swift +65 -0
  11. package/lib/commonjs/AguaceroContext.js +0 -4
  12. package/lib/commonjs/AguaceroContext.js.map +1 -1
  13. package/lib/commonjs/GridRenderLayer.js +62 -76
  14. package/lib/commonjs/GridRenderLayer.js.map +1 -1
  15. package/lib/commonjs/MapManager.js +110 -224
  16. package/lib/commonjs/MapManager.js.map +1 -1
  17. package/lib/commonjs/MapRegistry.js +21 -33
  18. package/lib/commonjs/MapRegistry.js.map +1 -1
  19. package/lib/commonjs/NexradRadarLayer.android.js +28 -100
  20. package/lib/commonjs/NexradRadarLayer.android.js.map +1 -1
  21. package/lib/commonjs/NexradRadarLayer.ios.js +26 -97
  22. package/lib/commonjs/NexradRadarLayer.ios.js.map +1 -1
  23. package/lib/commonjs/NexradSitesMapLayer.js +41 -61
  24. package/lib/commonjs/NexradSitesMapLayer.js.map +1 -1
  25. package/lib/commonjs/SatelliteLayer.android.js +26 -38
  26. package/lib/commonjs/SatelliteLayer.android.js.map +1 -1
  27. package/lib/commonjs/SatelliteLayer.ios.js +30 -42
  28. package/lib/commonjs/SatelliteLayer.ios.js.map +1 -1
  29. package/lib/commonjs/StyleApplicator.js +129 -175
  30. package/lib/commonjs/StyleApplicator.js.map +1 -1
  31. package/lib/commonjs/WeatherLayerManager.js +996 -1627
  32. package/lib/commonjs/WeatherLayerManager.js.map +1 -1
  33. package/lib/commonjs/aguaceroCoreDebugHooks.js +58 -130
  34. package/lib/commonjs/aguaceroCoreDebugHooks.js.map +1 -1
  35. package/lib/commonjs/aguaceroRnDebug.js +147 -287
  36. package/lib/commonjs/aguaceroRnDebug.js.map +1 -1
  37. package/lib/commonjs/cdnAuthenticatedFetch.js +104 -0
  38. package/lib/commonjs/cdnAuthenticatedFetch.js.map +1 -0
  39. package/lib/commonjs/dispatchViewManagerCommandCompat.js +51 -88
  40. package/lib/commonjs/dispatchViewManagerCommandCompat.js.map +1 -1
  41. package/lib/commonjs/gridCdnAuth.js +41 -50
  42. package/lib/commonjs/gridCdnAuth.js.map +1 -1
  43. package/lib/commonjs/index.js +51 -0
  44. package/lib/commonjs/index.js.map +1 -1
  45. package/lib/commonjs/nexrad/nexradAndroidController.js +851 -863
  46. package/lib/commonjs/nexrad/nexradAndroidController.js.map +1 -1
  47. package/lib/commonjs/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +62 -85
  48. package/lib/commonjs/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js.map +1 -1
  49. package/lib/commonjs/nexrad/nexradDiag.js +32 -148
  50. package/lib/commonjs/nexrad/nexradDiag.js.map +1 -1
  51. package/lib/commonjs/nexrad/nexradLevel2Keys.js +261 -0
  52. package/lib/commonjs/nexrad/nexradLevel2Keys.js.map +1 -0
  53. package/lib/commonjs/nexrad/nexradLutBuild.js +64 -111
  54. package/lib/commonjs/nexrad/nexradLutBuild.js.map +1 -1
  55. package/lib/commonjs/nexrad/nexradMapboxFrameOpts.bundled.js +136 -164
  56. package/lib/commonjs/nexrad/nexradMapboxFrameOpts.bundled.js.map +1 -1
  57. package/lib/commonjs/nexrad/nexradSdkImports.js +51 -0
  58. package/lib/commonjs/nexrad/nexradSdkImports.js.map +1 -0
  59. package/lib/commonjs/nexrad/radarArchiveCore.bundled.js +2848 -4455
  60. package/lib/commonjs/nexrad/radarArchiveCore.bundled.js.map +1 -1
  61. package/lib/commonjs/nexrad/radarDecode.worker.bundled.js +445 -648
  62. package/lib/commonjs/nexrad/radarDecode.worker.bundled.js.map +1 -1
  63. package/lib/commonjs/nexrad/radarFrameGpuMatch.bundled.js +52 -68
  64. package/lib/commonjs/nexrad/radarFrameGpuMatch.bundled.js.map +1 -1
  65. package/lib/commonjs/nexradNativeCommandIds.js +24 -0
  66. package/lib/commonjs/nexradNativeCommandIds.js.map +1 -0
  67. package/lib/commonjs/nws/NwsAlertsOverlay.android.js.map +1 -1
  68. package/lib/commonjs/nws/NwsAlertsOverlay.ios.js.map +1 -1
  69. package/lib/commonjs/nws/NwsAlertsOverlay.js +3 -3
  70. package/lib/commonjs/nws/NwsAlertsOverlay.js.map +1 -1
  71. package/lib/commonjs/nws/NwsAlertsOverlay.native.js +235 -361
  72. package/lib/commonjs/nws/NwsAlertsOverlay.native.js.map +1 -1
  73. package/lib/commonjs/nws/eventSourceRnPolyfill.js +92 -146
  74. package/lib/commonjs/nws/eventSourceRnPolyfill.js.map +1 -1
  75. package/lib/commonjs/nws/nwsAndroidConstants.js +2 -8
  76. package/lib/commonjs/nws/nwsAndroidConstants.js.map +1 -1
  77. package/lib/commonjs/satellite/satelliteAndroidController.js +122 -199
  78. package/lib/commonjs/satellite/satelliteAndroidController.js.map +1 -1
  79. package/lib/commonjs/satelliteBridgeDiag.js +3 -13
  80. package/lib/commonjs/satelliteBridgeDiag.js.map +1 -1
  81. package/lib/commonjs/satelliteRnDebug.js +160 -0
  82. package/lib/commonjs/satelliteRnDebug.js.map +1 -0
  83. package/lib/module/AguaceroContext.js +2 -7
  84. package/lib/module/AguaceroContext.js.map +1 -1
  85. package/lib/module/GridRenderLayer.js +66 -80
  86. package/lib/module/GridRenderLayer.js.map +1 -1
  87. package/lib/module/MapManager.js +125 -239
  88. package/lib/module/MapManager.js.map +1 -1
  89. package/lib/module/MapRegistry.js +21 -33
  90. package/lib/module/MapRegistry.js.map +1 -1
  91. package/lib/module/NexradRadarLayer.android.js +32 -104
  92. package/lib/module/NexradRadarLayer.android.js.map +1 -1
  93. package/lib/module/NexradRadarLayer.ios.js +30 -101
  94. package/lib/module/NexradRadarLayer.ios.js.map +1 -1
  95. package/lib/module/NexradSitesMapLayer.js +44 -63
  96. package/lib/module/NexradSitesMapLayer.js.map +1 -1
  97. package/lib/module/SatelliteLayer.android.js +32 -44
  98. package/lib/module/SatelliteLayer.android.js.map +1 -1
  99. package/lib/module/SatelliteLayer.ios.js +36 -48
  100. package/lib/module/SatelliteLayer.ios.js.map +1 -1
  101. package/lib/module/StyleApplicator.js +144 -191
  102. package/lib/module/StyleApplicator.js.map +1 -1
  103. package/lib/module/WeatherLayerManager.js +1024 -1655
  104. package/lib/module/WeatherLayerManager.js.map +1 -1
  105. package/lib/module/aguaceroCoreDebugHooks.js +59 -130
  106. package/lib/module/aguaceroCoreDebugHooks.js.map +1 -1
  107. package/lib/module/aguaceroRnDebug.js +151 -291
  108. package/lib/module/aguaceroRnDebug.js.map +1 -1
  109. package/lib/module/cdnAuthenticatedFetch.js +97 -0
  110. package/lib/module/cdnAuthenticatedFetch.js.map +1 -0
  111. package/lib/module/dispatchViewManagerCommandCompat.js +52 -90
  112. package/lib/module/dispatchViewManagerCommandCompat.js.map +1 -1
  113. package/lib/module/gridCdnAuth.js +38 -50
  114. package/lib/module/gridCdnAuth.js.map +1 -1
  115. package/lib/module/index.js +9 -6
  116. package/lib/module/index.js.map +1 -1
  117. package/lib/module/nexrad/nexradAndroidController.js +865 -876
  118. package/lib/module/nexrad/nexradAndroidController.js.map +1 -1
  119. package/lib/module/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +62 -85
  120. package/lib/module/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js.map +1 -1
  121. package/lib/module/nexrad/nexradDiag.js +31 -145
  122. package/lib/module/nexrad/nexradDiag.js.map +1 -1
  123. package/lib/module/nexrad/nexradLevel2Keys.js +245 -0
  124. package/lib/module/nexrad/nexradLevel2Keys.js.map +1 -0
  125. package/lib/module/nexrad/nexradLutBuild.js +64 -110
  126. package/lib/module/nexrad/nexradLutBuild.js.map +1 -1
  127. package/lib/module/nexrad/nexradMapboxFrameOpts.bundled.js +136 -163
  128. package/lib/module/nexrad/nexradMapboxFrameOpts.bundled.js.map +1 -1
  129. package/lib/module/nexrad/nexradSdkImports.js +4 -0
  130. package/lib/module/nexrad/nexradSdkImports.js.map +1 -0
  131. package/lib/module/nexrad/radarArchiveCore.bundled.js +2839 -4448
  132. package/lib/module/nexrad/radarArchiveCore.bundled.js.map +1 -1
  133. package/lib/module/nexrad/radarDecode.worker.bundled.js +445 -648
  134. package/lib/module/nexrad/radarDecode.worker.bundled.js.map +1 -1
  135. package/lib/module/nexrad/radarFrameGpuMatch.bundled.js +50 -66
  136. package/lib/module/nexrad/radarFrameGpuMatch.bundled.js.map +1 -1
  137. package/lib/module/nexradNativeCommandIds.js +18 -0
  138. package/lib/module/nexradNativeCommandIds.js.map +1 -0
  139. package/lib/module/nws/NwsAlertsOverlay.android.js +1 -1
  140. package/lib/module/nws/NwsAlertsOverlay.android.js.map +1 -1
  141. package/lib/module/nws/NwsAlertsOverlay.ios.js +1 -1
  142. package/lib/module/nws/NwsAlertsOverlay.ios.js.map +1 -1
  143. package/lib/module/nws/NwsAlertsOverlay.js +5 -5
  144. package/lib/module/nws/NwsAlertsOverlay.js.map +1 -1
  145. package/lib/module/nws/NwsAlertsOverlay.native.js +248 -373
  146. package/lib/module/nws/NwsAlertsOverlay.native.js.map +1 -1
  147. package/lib/module/nws/eventSourceRnPolyfill.js +92 -146
  148. package/lib/module/nws/eventSourceRnPolyfill.js.map +1 -1
  149. package/lib/module/nws/nwsAndroidConstants.js +2 -8
  150. package/lib/module/nws/nwsAndroidConstants.js.map +1 -1
  151. package/lib/module/satellite/satelliteAndroidController.js +125 -202
  152. package/lib/module/satellite/satelliteAndroidController.js.map +1 -1
  153. package/lib/module/satelliteBridgeDiag.js +3 -13
  154. package/lib/module/satelliteBridgeDiag.js.map +1 -1
  155. package/lib/module/satelliteRnDebug.js +148 -0
  156. package/lib/module/satelliteRnDebug.js.map +1 -0
  157. package/lib/typescript/AguaceroContext.d.ts +0 -4
  158. package/lib/typescript/AguaceroContext.d.ts.map +1 -1
  159. package/lib/typescript/GridRenderLayer.d.ts.map +1 -1
  160. package/lib/typescript/MapManager.d.ts +0 -12
  161. package/lib/typescript/MapManager.d.ts.map +1 -1
  162. package/lib/typescript/MapRegistry.d.ts +10 -12
  163. package/lib/typescript/MapRegistry.d.ts.map +1 -1
  164. package/lib/typescript/NexradRadarLayer.android.d.ts.map +1 -1
  165. package/lib/typescript/NexradRadarLayer.ios.d.ts.map +1 -1
  166. package/lib/typescript/NexradSitesMapLayer.d.ts +4 -10
  167. package/lib/typescript/NexradSitesMapLayer.d.ts.map +1 -1
  168. package/lib/typescript/SatelliteLayer.android.d.ts.map +1 -1
  169. package/lib/typescript/SatelliteLayer.ios.d.ts.map +1 -1
  170. package/lib/typescript/StyleApplicator.d.ts +1 -1
  171. package/lib/typescript/StyleApplicator.d.ts.map +1 -1
  172. package/lib/typescript/WeatherLayerManager.d.ts.map +1 -1
  173. package/lib/typescript/aguaceroCoreDebugHooks.d.ts +2 -9
  174. package/lib/typescript/aguaceroCoreDebugHooks.d.ts.map +1 -1
  175. package/lib/typescript/aguaceroRnDebug.d.ts +47 -66
  176. package/lib/typescript/aguaceroRnDebug.d.ts.map +1 -1
  177. package/lib/typescript/cdnAuthenticatedFetch.d.ts +10 -0
  178. package/lib/typescript/cdnAuthenticatedFetch.d.ts.map +1 -0
  179. package/lib/typescript/dispatchViewManagerCommandCompat.d.ts +1 -17
  180. package/lib/typescript/dispatchViewManagerCommandCompat.d.ts.map +1 -1
  181. package/lib/typescript/gridCdnAuth.d.ts +16 -21
  182. package/lib/typescript/gridCdnAuth.d.ts.map +1 -1
  183. package/lib/typescript/index.d.ts +2 -0
  184. package/lib/typescript/nexrad/nexradAndroidController.d.ts +39 -89
  185. package/lib/typescript/nexrad/nexradAndroidController.d.ts.map +1 -1
  186. package/lib/typescript/nexrad/nexradCrossSectionSampleAtLatLon.bundled.d.ts +2 -1
  187. package/lib/typescript/nexrad/nexradCrossSectionSampleAtLatLon.bundled.d.ts.map +1 -1
  188. package/lib/typescript/nexrad/nexradDiag.d.ts +13 -101
  189. package/lib/typescript/nexrad/nexradDiag.d.ts.map +1 -1
  190. package/lib/typescript/nexrad/nexradLevel2Keys.d.ts +36 -0
  191. package/lib/typescript/nexrad/nexradLevel2Keys.d.ts.map +1 -0
  192. package/lib/typescript/nexrad/nexradLutBuild.d.ts +3 -10
  193. package/lib/typescript/nexrad/nexradLutBuild.d.ts.map +1 -1
  194. package/lib/typescript/nexrad/nexradMapboxFrameOpts.bundled.d.ts +4 -3
  195. package/lib/typescript/nexrad/nexradMapboxFrameOpts.bundled.d.ts.map +1 -1
  196. package/lib/typescript/nexrad/nexradSdkImports.d.ts +2 -0
  197. package/lib/typescript/nexrad/nexradSdkImports.d.ts.map +1 -0
  198. package/lib/typescript/nexrad/radarArchiveCore.bundled.d.ts +12 -7
  199. package/lib/typescript/nexrad/radarArchiveCore.bundled.d.ts.map +1 -1
  200. package/lib/typescript/nexrad/radarDecode.worker.bundled.d.ts +20 -20
  201. package/lib/typescript/nexrad/radarDecode.worker.bundled.d.ts.map +1 -1
  202. package/lib/typescript/nexrad/radarFrameGpuMatch.bundled.d.ts +4 -3
  203. package/lib/typescript/nexrad/radarFrameGpuMatch.bundled.d.ts.map +1 -1
  204. package/lib/typescript/nexradNativeCommandIds.d.ts +2 -0
  205. package/lib/typescript/nexradNativeCommandIds.d.ts.map +1 -0
  206. package/lib/typescript/nws/NwsAlertsOverlay.native.d.ts +6 -17
  207. package/lib/typescript/nws/NwsAlertsOverlay.native.d.ts.map +1 -1
  208. package/lib/typescript/nws/eventSourceRnPolyfill.d.ts +0 -3
  209. package/lib/typescript/nws/eventSourceRnPolyfill.d.ts.map +1 -1
  210. package/lib/typescript/nws/nwsAndroidConstants.d.ts +0 -5
  211. package/lib/typescript/nws/nwsAndroidConstants.d.ts.map +1 -1
  212. package/lib/typescript/satellite/satelliteAndroidController.d.ts +9 -47
  213. package/lib/typescript/satellite/satelliteAndroidController.d.ts.map +1 -1
  214. package/lib/typescript/satelliteBridgeDiag.d.ts +1 -5
  215. package/lib/typescript/satelliteBridgeDiag.d.ts.map +1 -1
  216. package/lib/typescript/satelliteRnDebug.d.ts +75 -0
  217. package/lib/typescript/satelliteRnDebug.d.ts.map +1 -0
  218. package/package.json +75 -74
  219. package/src/AguaceroContext.js +1 -7
  220. package/src/GridRenderLayer.js +1 -128
  221. package/src/MapManager.js +1 -277
  222. package/src/MapRegistry.js +1 -56
  223. package/src/NexradRadarLayer.android.js +1 -121
  224. package/src/NexradRadarLayer.ios.js +1 -115
  225. package/src/NexradSitesMapLayer.js +1 -75
  226. package/src/SatelliteLayer.android.js +1 -63
  227. package/src/SatelliteLayer.ios.js +1 -70
  228. package/src/StyleApplicator.js +1 -241
  229. package/src/WeatherLayerManager.js +1 -2025
  230. package/src/aguaceroCoreDebugHooks.js +1 -142
  231. package/src/aguaceroRnDebug.js +1 -335
  232. package/src/cdnAuthenticatedFetch.js +1 -0
  233. package/src/dispatchViewManagerCommandCompat.js +1 -100
  234. package/src/gridCdnAuth.js +1 -56
  235. package/src/index.js +1 -19
  236. package/src/nexrad/nexradAndroidController.js +1 -1078
  237. package/src/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +1 -91
  238. package/src/nexrad/nexradDiag.js +1 -150
  239. package/src/nexrad/nexradLevel2Keys.js +1 -0
  240. package/src/nexrad/nexradLutBuild.js +1 -126
  241. package/src/nexrad/nexradMapboxFrameOpts.bundled.js +1 -245
  242. package/src/nexrad/nexradSdkImports.js +1 -0
  243. package/src/nexrad/radarArchiveCore.bundled.js +1 -7085
  244. package/src/nexrad/radarDecode.worker.bundled.js +1 -813
  245. package/src/nexrad/radarFrameGpuMatch.bundled.js +1 -79
  246. package/src/nexradNativeCommandIds.js +1 -0
  247. package/src/nws/NwsAlertsOverlay.android.js +1 -1
  248. package/src/nws/NwsAlertsOverlay.ios.js +1 -1
  249. package/src/nws/NwsAlertsOverlay.js +1 -7
  250. package/src/nws/NwsAlertsOverlay.native.js +1 -463
  251. package/src/nws/eventSourceRnPolyfill.js +7 -193
  252. package/src/nws/nwsAndroidConstants.js +1 -8
  253. package/src/satellite/satelliteAndroidController.js +1 -245
  254. package/src/satelliteBridgeDiag.js +1 -15
  255. package/src/satelliteRnDebug.js +1 -0
  256. package/lib/commonjs/nexrad/nexradNativeCommandIds.js +0 -51
  257. package/lib/commonjs/nexrad/nexradNativeCommandIds.js.map +0 -1
  258. package/lib/module/nexrad/nexradNativeCommandIds.js +0 -44
  259. package/lib/module/nexrad/nexradNativeCommandIds.js.map +0 -1
  260. package/lib/typescript/nexrad/nexradNativeCommandIds.d.ts +0 -9
  261. package/lib/typescript/nexrad/nexradNativeCommandIds.d.ts.map +0 -1
  262. package/src/nexrad/nexradNativeCommandIds.js +0 -44
  263. /package/lib/commonjs/{nexrad/nexradSitesUs.json → nexradSitesUs.json} +0 -0
  264. /package/lib/module/{nexrad/nexradSitesUs.json → nexradSitesUs.json} +0 -0
  265. /package/src/{nexrad/nexradSitesUs.json → nexradSitesUs.json} +0 -0
@@ -1,2025 +1 @@
1
- // packages/react-native/src/WeatherLayerManager.js
2
-
3
- import {
4
- AguaceroCore,
5
- DICTIONARIES,
6
- formatTimelineDurationValue,
7
- getUnitConversionFunction,
8
- parseTimelineDurationHours,
9
- } from '@aguacerowx/javascript-sdk';
10
- import { fromByteArray } from 'base64-js';
11
- import React, { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
12
- import { NativeModules, Platform, UIManager } from 'react-native';
13
- import { AguaceroContext } from './AguaceroContext';
14
- import { GridRenderLayer } from './GridRenderLayer';
15
- import NexradRadarLayer from './NexradRadarLayer';
16
- import SatelliteLayer from './SatelliteLayer';
17
- import { NexradSitesMapLayer } from './NexradSitesMapLayer';
18
- import { NexradAndroidController } from './nexrad/nexradAndroidController';
19
- import { SatelliteAndroidController } from './satellite/satelliteAndroidController';
20
- import NwsAlertsOverlay from './nws/NwsAlertsOverlay';
21
- import { mapRegistry } from './MapRegistry';
22
- import { satBridgeWarn } from './satelliteBridgeDiag';
23
- import {
24
- augmentProcessFrameOptionsForDebug,
25
- aguaceroDebugWarn,
26
- configureAguaceroRnDebug,
27
- getAguaceroAuthDiagnosticSnapshot,
28
- isAguaceroRnDebugEnabled,
29
- } from './aguaceroRnDebug';
30
- import { installAguaceroCoreDebugHooks, logProcessFrameAuthMismatch } from './aguaceroCoreDebugHooks';
31
- import { resolveGridRequestSiteOrigin } from './gridCdnAuth';
32
-
33
- const NEXRAD_NATIVE = Platform.OS === 'android' || Platform.OS === 'ios';
34
- const SATELLITE_NATIVE = Platform.OS === 'android' || Platform.OS === 'ios';
35
-
36
- satBridgeWarn('SDK fingerprint', {
37
- platform: Platform.OS,
38
- SATELLITE_NATIVE,
39
- note: 'If you never see sat-bridge logs, the app bundle is not loading this WeatherLayerManager build.',
40
- });
41
-
42
- /**
43
- * Same filtering as {@link AguaceroCore#_getFilteredMrmsTimestampsForVariable} for older cores
44
- * where that helper (or {@code setMRMSDurationValue}) is missing from the prototype chain.
45
- */
46
- function getFilteredMrmsTimestampsCompat(core, variable) {
47
- const raw = core.mrmsStatus?.[variable];
48
- if (!raw || !raw.length) return [];
49
- const hours = parseTimelineDurationHours(core.state.mrmsDurationValue);
50
- let list = [...raw]
51
- .map((t) => Number(t))
52
- .filter((t) => !Number.isNaN(t))
53
- .sort((a, b) => a - b);
54
- if (hours > 0 && list.length > 0) {
55
- const latest = list[list.length - 1];
56
- const cutoff = latest - hours * 3600;
57
- list = list.filter((t) => t >= cutoff);
58
- }
59
- return list;
60
- }
61
-
62
- /**
63
- * Older npm installs may resolve an {@link AguaceroCore} without {@code setMRMSDurationValue} /
64
- * {@code setNexradDurationValue}; mirror those methods here using the same logic as the SDK.
65
- */
66
- async function applyMrmsDurationValue(core, value) {
67
- if (typeof core.setMRMSDurationValue === 'function') {
68
- await core.setMRMSDurationValue(value);
69
- return;
70
- }
71
- const v = formatTimelineDurationValue(value);
72
- await core.setState({ mrmsDurationValue: v });
73
- if (!core.state.isMRMS || !core.state.variable) return;
74
- const filtered =
75
- typeof core._getFilteredMrmsTimestampsForVariable === 'function'
76
- ? core._getFilteredMrmsTimestampsForVariable(core.state.variable)
77
- : getFilteredMrmsTimestampsCompat(core, core.state.variable);
78
- if (!filtered || filtered.length === 0) return;
79
- const curN = core.state.mrmsTimestamp == null ? null : Number(core.state.mrmsTimestamp);
80
- if (curN == null || !filtered.includes(curN)) {
81
- await core.setState({ mrmsTimestamp: filtered[filtered.length - 1] });
82
- }
83
- }
84
-
85
- async function applyNexradDurationValue(core, value) {
86
- if (typeof core.setNexradDurationValue === 'function') {
87
- await core.setNexradDurationValue(value);
88
- return;
89
- }
90
- const v = formatTimelineDurationValue(value);
91
- await core.setState({ nexradDurationValue: v });
92
- if (!core.state.isNexrad || !core.state.nexradSite) return;
93
- if (typeof core.refreshNexradTimes === 'function') {
94
- await core.refreshNexradTimes();
95
- }
96
- const nk = typeof core._nexradTimesCacheKey === 'function' ? core._nexradTimesCacheKey() : null;
97
- const raw = nk ? core.nexradTimesByStation?.[nk]?.unixTimes || [] : [];
98
- const filtered =
99
- typeof core._getFilteredNexradTimestampsForVariable === 'function'
100
- ? core._getFilteredNexradTimestampsForVariable(raw)
101
- : [...raw].map((t) => Number(t)).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
102
- if (!filtered || filtered.length === 0) return;
103
- const curN = core.state.nexradTimestamp == null ? null : Number(core.state.nexradTimestamp);
104
- if (curN == null || !filtered.includes(curN)) {
105
- await core.setState({ nexradTimestamp: filtered[filtered.length - 1] });
106
- }
107
- }
108
-
109
- function findLatestModelRun(modelsData, modelName) {
110
- const model = modelsData?.[modelName];
111
- if (!model) return null;
112
- const availableDates = Object.keys(model).sort((a, b) => b.localeCompare(a));
113
- for (const date of availableDates) {
114
- const runs = model[date];
115
- if (!runs) continue;
116
- const availableRuns = Object.keys(runs).sort((a, b) => b.localeCompare(a));
117
- if (availableRuns.length > 0) return { date: date, run: availableRuns[0] };
118
- }
119
- return null;
120
- }
121
- const { WeatherFrameProcessorModule, InspectorModule } = NativeModules;
122
-
123
- /**
124
- * `state:change` payloads can briefly have {@code isNexrad} before {@code nexradSite} / {@code nexradTimestamp}
125
- * are populated; {@code core.state} is updated first. Merge so readouts match the active radar.
126
- *
127
- * @param {object | null} emitted
128
- * @param {object | null} coreState
129
- */
130
- function mergeNexradEmittedWithCore(emitted, coreState) {
131
- if (!emitted || !coreState) return null;
132
- return {
133
- ...emitted,
134
- nexradSite: coreState.nexradSite ?? emitted.nexradSite,
135
- nexradTimestamp: coreState.nexradTimestamp ?? emitted.nexradTimestamp,
136
- nexradProduct: coreState.nexradProduct ?? emitted.nexradProduct,
137
- nexradTilt: coreState.nexradTilt ?? emitted.nexradTilt,
138
- nexradDataSource: coreState.nexradDataSource ?? emitted.nexradDataSource,
139
- nexradStormRelative: coreState.nexradStormRelative ?? emitted.nexradStormRelative,
140
- };
141
- }
142
-
143
- /**
144
- * Compact timeline identity for deduping {@code state:change}: extending the NEXRAD/satellite window
145
- * often keeps the same selected unix — without this, {@link WeatherLayerManager} short-circuits and
146
- * never calls native {@code sync} / preload with the expanded frame list.
147
- */
148
- function nexradObsTimelineSig(state) {
149
- const arr = [...(state?.availableNexradTimestamps || [])]
150
- .map(Number)
151
- .filter((t) => Number.isFinite(t))
152
- .sort((a, b) => a - b);
153
- if (!arr.length) return '0';
154
- return `${arr.length}:${arr[0]}:${arr[arr.length - 1]}`;
155
- }
156
-
157
- function satelliteObsTimelineSig(state) {
158
- const keys = Object.keys(state?.satelliteTimeToFileMap || {})
159
- .map(Number)
160
- .filter((t) => Number.isFinite(t))
161
- .sort((a, b) => a - b);
162
- if (!keys.length) return '0';
163
- return `${keys.length}:${keys[0]}:${keys[keys.length - 1]}`;
164
- }
165
-
166
- /** True when {@link WeatherLayerManager} `debug` / {@link configureAguaceroRnDebug}, or legacy `__AGUACERO_WX_GRID_DEBUG__`. */
167
- function wxGridDebugEnabled() {
168
- if (isAguaceroRnDebugEnabled()) return true;
169
- try {
170
- if (typeof __DEV__ !== 'undefined' && __DEV__) return true;
171
- return Boolean(typeof globalThis !== 'undefined' && globalThis.__AGUACERO_WX_GRID_DEBUG__);
172
- } catch {
173
- return false;
174
- }
175
- }
176
-
177
- function wxGridVerbose(tag, detail) {
178
- if (!wxGridDebugEnabled()) return;
179
- if (detail !== undefined) {
180
- console.log(`[AguaceroWX][grid][${tag}]`, detail);
181
- } else {
182
- console.log(`[AguaceroWX][grid][${tag}]`);
183
- }
184
- }
185
-
186
- /** Always logs (Metro / device logs) — use for failures and unusual early exits. */
187
- function wxGridWarn(tag, detail) {
188
- if (detail !== undefined) {
189
- console.warn(`[AguaceroWX][grid][${tag}]`, detail);
190
- } else {
191
- console.warn(`[AguaceroWX][grid][${tag}]`);
192
- }
193
- }
194
-
195
- /**
196
- * Native {@code processFrame} must mirror browser/AguaceroCore grid auth: encoded {@code apiKey}
197
- * in the query string and optional {@code Origin} / {@code Referer} (many CloudFront setups require them).
198
- *
199
- * @param {string} baseGridUrl
200
- * @param {string} resourcePath
201
- * @param {string} apiKey
202
- * @param {string | null | undefined} bundleId
203
- * @param {string | undefined} gridRequestSiteOrigin - prop or core origin (see {@link resolveGridRequestSiteOrigin})
204
- * @param {import('@aguacerowx/javascript-sdk').AguaceroCore | null | undefined} core
205
- */
206
- function buildGridFrameProcessOptions(baseGridUrl, resourcePath, apiKey, bundleId, gridRequestSiteOrigin, core) {
207
- const trimmedKey = typeof apiKey === 'string' ? apiKey.trim() : apiKey;
208
- const url = `${baseGridUrl}${resourcePath}?apiKey=${encodeURIComponent(trimmedKey || '')}`;
209
- const options = { url, apiKey: trimmedKey, bundleId };
210
- const origin = resolveGridRequestSiteOrigin(gridRequestSiteOrigin, core);
211
- if (origin) {
212
- options.gridRequestSiteOrigin = origin;
213
- }
214
- return options;
215
- }
216
-
217
- /**
218
- * A helper function to generate the raw RGBA byte buffer for the colormap texture.
219
- */
220
- const _generateColormapBytes = (colormap) => {
221
- const width = 256;
222
- const data = new Uint8Array(width * 4);
223
- const stops = colormap.reduce((acc, _, i) => (i % 2 === 0 ? [...acc, { value: colormap[i], color: colormap[i + 1] }] : acc), []);
224
-
225
- if (stops.length === 0) return data;
226
-
227
- const minVal = stops[0].value;
228
- const maxVal = stops[stops.length - 1].value;
229
-
230
- const hexToRgb = (hex) => {
231
- const r = parseInt(hex.slice(1, 3), 16);
232
- const g = parseInt(hex.slice(3, 5), 16);
233
- const b = parseInt(hex.slice(5, 7), 16);
234
- return [r, g, b];
235
- };
236
-
237
- for (let i = 0; i < width; i++) {
238
- const val = minVal + (i / (width - 1)) * (maxVal - minVal);
239
- let lower = stops[0];
240
- let upper = stops[stops.length - 1];
241
- for (let j = 0; j < stops.length - 1; j++) {
242
- if (val >= stops[j].value && val <= stops[j + 1].value) {
243
- lower = stops[j];
244
- upper = stops[j + 1];
245
- break;
246
- }
247
- }
248
- const t = (val - lower.value) / (upper.value - lower.value || 1);
249
- const lowerRgb = hexToRgb(lower.color);
250
- const upperRgb = hexToRgb(upper.color);
251
- const rgb = lowerRgb.map((c, idx) => c * (1 - t) + upperRgb[idx] * t);
252
-
253
- const offset = i * 4;
254
- data[offset + 0] = Math.round(rgb[0]);
255
- data[offset + 1] = Math.round(rgb[1]);
256
- data[offset + 2] = Math.round(rgb[2]);
257
- data[offset + 3] = 255;
258
- }
259
- return data;
260
- };
261
-
262
- AguaceroCore.prototype.setMapCenter = function (center) {
263
- this.emit('map:move', center);
264
- };
265
-
266
- export const WeatherLayerManager = forwardRef((props, ref) => {
267
- const {
268
- inspectorEnabled,
269
- onInspect,
270
- apiKey,
271
- customColormaps,
272
- initialMode,
273
- initialVariable,
274
- autoRefresh,
275
- autoRefreshInterval,
276
- initialModel,
277
- belowID: belowIDFromProps,
278
- interpolateNexradColormap = true,
279
- nexradGateSmoothing = false,
280
- watchesWarnings: watchesWarningsProp,
281
- onNwsAlertClick,
282
- /** Same semantics as mapsgl / AguaceroCore: Origin + Referer for grid CDN (required by many CloudFront rules). */
283
- gridRequestSiteOrigin,
284
- /** When true, logs auth/HTTP diagnostics under `[AguaceroRN][debug]` (Metro / Logcat / Xcode). */
285
- debug = false,
286
- ...restProps
287
- } = props;
288
-
289
- useEffect(() => {
290
- configureAguaceroRnDebug({ enabled: Boolean(debug) });
291
- }, [debug]);
292
- const context = useContext(AguaceroContext);
293
-
294
- // Create the core here instead of getting it from context
295
- const core = useMemo(
296
- () =>
297
- new AguaceroCore({
298
- apiKey: apiKey,
299
- customColormaps: customColormaps,
300
- gridRequestSiteOrigin: gridRequestSiteOrigin,
301
- layerOptions: {
302
- mode: initialMode,
303
- variable: initialVariable,
304
- model: initialModel,
305
- },
306
- autoRefresh: false, // <-- add this
307
- }),
308
- [apiKey, gridRequestSiteOrigin],
309
- );
310
-
311
- useEffect(() => {
312
- if (!core) return;
313
- installAguaceroCoreDebugHooks(core, {
314
- gridRequestSiteOriginProp: gridRequestSiteOrigin ?? null,
315
- debugProp: Boolean(debug),
316
- });
317
- if (isAguaceroRnDebugEnabled() && !apiKey) {
318
- aguaceroDebugWarn('WeatherLayerManager.missingApiKey', {
319
- hint: 'apiKey prop is empty — all CDN requests will fail or return 403',
320
- });
321
- }
322
- if (isAguaceroRnDebugEnabled() && apiKey && !gridRequestSiteOrigin) {
323
- aguaceroDebugWarn('WeatherLayerManager.missingGridOrigin', {
324
- hint: 'gridRequestSiteOrigin is not set — CloudFront often returns 403 without Origin/Referer on React Native',
325
- snapshot: getAguaceroAuthDiagnosticSnapshot(core),
326
- });
327
- }
328
- }, [core, debug, gridRequestSiteOrigin, apiKey]);
329
-
330
- const [watchesWarningsOptions, setWatchesWarningsOptions] = useState(() => ({
331
- alertInteractionEnabled: true,
332
- ...(watchesWarningsProp ?? {}),
333
- }));
334
-
335
- useEffect(() => {
336
- setWatchesWarningsOptions((prev) => ({
337
- ...prev,
338
- ...(watchesWarningsProp ?? {}),
339
- }));
340
- }, [watchesWarningsProp]);
341
-
342
- useEffect(() => {
343
- setNexradSitesMapVisible(Boolean(core.state.isNexrad && core.state.nexradShowSitesPicker !== false));
344
- }, [core]);
345
-
346
- const gridLayerRef = useRef(null);
347
- const nexradLayerRef = useRef(null);
348
- const nexradControllerRef = useRef(null);
349
- /** Latest {@code state:change} payload (includes {@code colormap}, NEXRAD maps) — required for readouts; {@code core.state} omits those. */
350
- const lastEmittedStateForInspectRef = useRef(null);
351
- const satelliteLayerRef = useRef(null);
352
- const satelliteControllerRef = useRef(null);
353
- /** @type {React.MutableRefObject<{ cancel?: () => void } | null>} */
354
- const nexradPreloadInteractionRef = useRef(null);
355
- const currentGridDataRef = useRef(null);
356
- const autoRefreshIntervalId = useRef(null);
357
-
358
- const ensureNexradController = useCallback(() => {
359
- if (!NEXRAD_NATIVE) return null;
360
- if (!nexradControllerRef.current) {
361
- nexradControllerRef.current = new NexradAndroidController(core, nexradLayerRef, {
362
- interpolateNexradColormap,
363
- nexradGateSmoothing,
364
- });
365
- }
366
- return nexradControllerRef.current;
367
- }, [core, interpolateNexradColormap, nexradGateSmoothing]);
368
-
369
- /** Synchronous NEXRAD readout (mapsgl-style); no await — avoids jank on map move. */
370
- const getNexradInspectPayloadAt = useCallback(
371
- (lng, lat) => {
372
- if (!NEXRAD_NATIVE || !core?.state?.isNexrad) return null;
373
- const ctl = ensureNexradController();
374
- if (!ctl) {
375
- return null;
376
- }
377
- const emitted = lastEmittedStateForInspectRef.current;
378
- if (!emitted) {
379
- return null;
380
- }
381
- const st = mergeNexradEmittedWithCore(emitted, core.state);
382
- if (!st?.nexradSite || st.nexradTimestamp == null) {
383
- return null;
384
- }
385
- return ctl.getInspectPayload(lng, lat, st) ?? null;
386
- },
387
- [core, ensureNexradController],
388
- );
389
-
390
- const ensureSatelliteController = useCallback(() => {
391
- if (!SATELLITE_NATIVE) return null;
392
- if (!satelliteControllerRef.current) {
393
- satelliteControllerRef.current = new SatelliteAndroidController(core, satelliteLayerRef);
394
- }
395
- return satelliteControllerRef.current;
396
- }, [core]);
397
-
398
- useEffect(() => {
399
- if (!SATELLITE_NATIVE) return;
400
- let gridCmdKeys = [];
401
- let satCmdKeys = [];
402
- try {
403
- gridCmdKeys = Object.keys(UIManager.getViewManagerConfig?.('GridRenderLayer')?.Commands ?? {});
404
- } catch {
405
- gridCmdKeys = ['error'];
406
- }
407
- try {
408
- satCmdKeys = Object.keys(UIManager.getViewManagerConfig?.('SatelliteLayer')?.Commands ?? {});
409
- } catch {
410
- satCmdKeys = ['error'];
411
- }
412
- satBridgeWarn('mount UIManager command registration', {
413
- GridRenderLayerCommands: gridCmdKeys,
414
- SatelliteLayerCommands: satCmdKeys,
415
- satelliteMissing: satCmdKeys.length === 0,
416
- });
417
- }, []);
418
-
419
- useEffect(() => {
420
- const ctl = nexradControllerRef.current;
421
- if (NEXRAD_NATIVE && ctl) {
422
- ctl.updateStyleOptions({
423
- interpolateNexradColormap,
424
- nexradGateSmoothing,
425
- });
426
- if (core?.state?.isNexrad) {
427
- ctl.applyStyleFromState(core.state);
428
- }
429
- }
430
- }, [interpolateNexradColormap, nexradGateSmoothing, core]);
431
-
432
- // Cache for preloaded grid data - stores the processed data ready for GPU upload
433
- const preloadedDataCache = useRef(new Map());
434
-
435
- // Store geometry and colormap that don't change with forecast hour
436
- const cachedGeometry = useRef(null);
437
- const cachedColormap = useRef(null);
438
- const cachedDataRange = useRef([0, 1]);
439
-
440
- // Track if we've done the initial load
441
- const hasInitialLoad = useRef(false);
442
- const hasPreloadedRef = useRef(false);
443
- /** Bumped on {@code cancelAllFrames} / full reload so stale {@code processFrame} results are ignored. */
444
- const preloadGenerationRef = useRef(0);
445
-
446
- // Track the last state we processed to avoid redundant updates
447
- const lastProcessedState = useRef(null);
448
- const previousStateRef = useRef(null);
449
-
450
- const [renderProps, setRenderProps] = useState({
451
- opacity: 1,
452
- dataRange: [0, 1]
453
- });
454
- /** Drives {@link NexradSitesMapLayer}; must re-render when core NEXRAD / picker flags change. */
455
- const [nexradSitesMapVisible, setNexradSitesMapVisible] = useState(false);
456
-
457
- useImperativeHandle(ref, () => {
458
- const setAutoRefresh = (enabled, intervalSeconds) => {
459
- if (autoRefreshIntervalId.current) {
460
- clearInterval(autoRefreshIntervalId.current);
461
- autoRefreshIntervalId.current = null;
462
- }
463
- if (enabled) {
464
- const effectiveInterval = (intervalSeconds || autoRefreshInterval || 30) * 1000;
465
- // Run once immediately, then start the interval
466
- _checkForUpdates();
467
- autoRefreshIntervalId.current = setInterval(_checkForUpdates, effectiveInterval);
468
- }
469
- };
470
- return {
471
- play: () => {
472
- core.play();
473
- },
474
- pause: () => {
475
- core.pause();
476
- },
477
- togglePlay: () => {
478
- core.togglePlay();
479
- },
480
- step: (direction) => {
481
- core.step(direction);
482
- },
483
- setPlaybackSpeed: (speed) => {
484
- if (speed > 0) {
485
- core.playbackSpeed = speed;
486
- if (core.isPlaying) {
487
- core.pause();
488
- core.play();
489
- }
490
- }
491
- },
492
- setOpacity: (opacity) => core.setOpacity(opacity),
493
- setUnits: (units) => core.setUnits(units),
494
- switchMode: (options) => core.switchMode(options),
495
- getAvailableVariables: (model) => core.getAvailableVariables(model),
496
- getVariableDisplayName: (code) => core.getVariableDisplayName(code),
497
- setRun: (runString) => core.setState({ run: runString.split(':')[1] }),
498
- setState: (newState) => core.setState(newState),
499
- setMRMSTimestamp: (timestamp) => core.setMRMSTimestamp(timestamp),
500
- setMRMSDurationValue: (value) => applyMrmsDurationValue(core, value),
501
- setNexradSite: (siteId) => core.setNexradSite(siteId),
502
- setNexradProduct: (product) => core.setNexradProduct(product),
503
- setNexradTilt: (tilt) => core.setNexradTilt(tilt),
504
- setNexradStormRelative: (enabled) => core.setNexradStormRelative(enabled),
505
- setNexradTimestamp: (ts) => core.setNexradTimestamp(ts),
506
- setNexradDurationValue: (value) => applyNexradDurationValue(core, value),
507
- setSatelliteTimestamp: (timestamp) => core.setSatelliteTimestamp(timestamp),
508
- setSatelliteDurationValue: (value) => core.setSatelliteDurationValue(value),
509
- setSatelliteSelection: (opts) => core.setSatelliteSelection(opts),
510
- setShaderSmoothing: async (enabled) => {
511
- await core.setShaderSmoothing(enabled);
512
- if (gridLayerRef.current) {
513
- gridLayerRef.current.setSmoothing(enabled);
514
- }
515
- },
516
- setSmoothing: (enabled) => {
517
- if (gridLayerRef.current) {
518
- gridLayerRef.current.setSmoothing(enabled);
519
- }
520
- },
521
- setAutoRefresh,
522
- refreshData: () => {
523
- _checkForUpdates();
524
- },
525
- /**
526
- * NWS watches/warnings (native map, iOS + Android): same options as mapsgl {@link WeatherLayerManager#configureWatchesWarnings}.
527
- * @param {object} partial
528
- */
529
- configureWatchesWarnings: (partial) => {
530
- setWatchesWarningsOptions((prev) => ({ ...prev, ...partial }));
531
- },
532
- };
533
- }, [core, autoRefreshInterval, _checkForUpdates]);
534
-
535
- const preloadAllFramesToDisk = async (state) => {
536
- if (state.isNexrad || state.isSatellite) {
537
- hasPreloadedRef.current = false;
538
- wxGridVerbose('preloadSkip', { reason: 'nexrad_or_satellite' });
539
- return;
540
- }
541
-
542
- if (hasPreloadedRef.current) {
543
- wxGridVerbose('preloadSkip', { reason: 'hasPreloadedRef_gate', isMRMS: state.isMRMS, variable: state.variable });
544
- return;
545
- }
546
-
547
- const { isMRMS, model, date, run, variable, units, availableHours, availableTimestamps, forecastHour, mrmsTimestamp } = state;
548
-
549
- // CRITICAL: Don't start preloading if we don't have a valid current frame
550
- if (isMRMS && (mrmsTimestamp == null || !availableTimestamps || availableTimestamps.length === 0)) {
551
- hasPreloadedRef.current = false;
552
- wxGridWarn('preloadAbort', {
553
- reason: 'mrms_missing_frame_or_timestamps',
554
- mrmsTimestamp,
555
- timestampCount: availableTimestamps?.length ?? 0,
556
- variable,
557
- });
558
- return;
559
- }
560
-
561
- if (!isMRMS && (forecastHour == null || !availableHours || availableHours.length === 0)) {
562
- hasPreloadedRef.current = false;
563
- wxGridWarn('preloadAbort', {
564
- reason: 'model_missing_hour_or_hours',
565
- forecastHour,
566
- hourCount: availableHours?.length ?? 0,
567
- model,
568
- variable,
569
- });
570
- return;
571
- }
572
-
573
- // Only mark as "has preloaded" after validation passes
574
- hasPreloadedRef.current = true;
575
- const generation = preloadGenerationRef.current;
576
- const isStaleGeneration = () => generation !== preloadGenerationRef.current;
577
-
578
- wxGridVerbose('preloadStart', {
579
- isMRMS,
580
- model,
581
- date,
582
- run,
583
- variable,
584
- mrmsTimestamp: isMRMS ? mrmsTimestamp : undefined,
585
- forecastHour: !isMRMS ? forecastHour : undefined,
586
- baseGridUrl: core?.baseGridUrl,
587
- hasApiKey: Boolean(core?.apiKey),
588
- bundleId: core?.bundleId ?? null,
589
- gridRequestSiteOrigin: gridRequestSiteOrigin || null,
590
- });
591
-
592
- // Fix the current forecast hour if it's invalid for this variable/model combo
593
- let effectiveForecastHour = forecastHour;
594
- if (!isMRMS && variable === 'ptypeRefl' && model === 'hrrr' && forecastHour === 0) {
595
- const validHours = availableHours.filter(hour => hour !== 0);
596
- effectiveForecastHour = validHours.length > 0 ? validHours[0] : 0;
597
- }
598
-
599
- if (!cachedGeometry.current || !cachedColormap.current) {
600
- const gridModel = isMRMS ? 'mrms' : model;
601
- const { corners, gridDef } = core._getGridCornersAndDef(gridModel);
602
- gridLayerRef.current.updateGeometry(corners, gridDef);
603
- cachedGeometry.current = { model: gridModel, variable };
604
-
605
- wxGridVerbose('geometryColormapInit', {
606
- gridModel,
607
- variable,
608
- nx: gridDef?.grid_params?.nx,
609
- ny: gridDef?.grid_params?.ny,
610
- });
611
-
612
- const { colormap, baseUnit } = core._getColormapForVariable(variable);
613
- const toUnit = core._getTargetUnit(baseUnit, units);
614
- const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
615
- let dataRange;
616
- if (variable === 'ptypeRefl' || variable === 'ptypeRate') {
617
- dataRange = isMRMS ? [5, 380] : [5, 380];
618
- } else {
619
- dataRange = [finalColormap[0], finalColormap[finalColormap.length - 2]];
620
- }
621
- const colormapBytes = _generateColormapBytes(finalColormap);
622
- const colormapAsBase64 = fromByteArray(colormapBytes);
623
-
624
- gridLayerRef.current.updateColormapTexture(colormapAsBase64);
625
- cachedColormap.current = { key: `${variable}-${units}` };
626
- cachedDataRange.current = dataRange;
627
-
628
- setRenderProps({ opacity: state.opacity, dataRange: dataRange });
629
- hasInitialLoad.current = true;
630
- }
631
-
632
- // Apply the same filtering logic as in AguaceroCore._emitStateChange
633
- let filteredHours = availableHours;
634
- if (!isMRMS && variable === 'ptypeRefl' && model === 'hrrr' && availableHours && availableHours.length > 0) {
635
- filteredHours = availableHours.filter(hour => hour !== 0);
636
- }
637
-
638
- const allFrames = isMRMS ? availableTimestamps : filteredHours;
639
- if (!allFrames || allFrames.length === 0) {
640
- hasPreloadedRef.current = false;
641
- wxGridWarn('preloadAbort', { reason: 'no_frames_after_filter', isMRMS, variable });
642
- return;
643
- }
644
-
645
- const currentFrame = isMRMS ? mrmsTimestamp : effectiveForecastHour;
646
-
647
- // Double-check currentFrame is valid
648
- if (currentFrame == null) {
649
- hasPreloadedRef.current = false;
650
- wxGridWarn('preloadAbort', { reason: 'currentFrame_null', isMRMS, variable });
651
- return;
652
- }
653
-
654
- // Reverse the frame order to load from last to first
655
- const reversedFrames = [...allFrames].reverse();
656
- const framesToPreload = reversedFrames.filter(frame => frame !== currentFrame);
657
-
658
- const { corners, gridDef } = core._getGridCornersAndDef(isMRMS ? 'mrms' : model);
659
- const { nx, ny } = gridDef.grid_params;
660
-
661
- // Load the current frame FIRST and WAIT for it before continuing
662
- const currentCacheKey = isMRMS ? `mrms-${currentFrame}-${variable}` : `${model}-${date}-${run}-${currentFrame}-${variable}`;
663
-
664
- if (!preloadedDataCache.current.has(currentCacheKey)) {
665
- let resourcePath;
666
- if (isMRMS) {
667
- const frameDate = new Date(currentFrame * 1000);
668
- const y = frameDate.getUTCFullYear();
669
- const m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0');
670
- const d = frameDate.getUTCDate().toString().padStart(2, '0');
671
- resourcePath = `/grids/mrms/${y}${m}${d}/${currentFrame}/0/${variable}/0`;
672
- } else {
673
- resourcePath = `/grids/${model}/${date}/${run}/${currentFrame}/${variable}/0`;
674
- }
675
-
676
- const options = augmentProcessFrameOptionsForDebug(
677
- buildGridFrameProcessOptions(
678
- core.baseGridUrl,
679
- resourcePath,
680
- core.apiKey,
681
- core.bundleId,
682
- gridRequestSiteOrigin,
683
- core,
684
- ),
685
- core,
686
- );
687
- logProcessFrameAuthMismatch(core, options, { phase: 'preloadCurrent', currentCacheKey });
688
-
689
- try {
690
- wxGridVerbose('processFrameRequest', { currentCacheKey, resourcePath, frame: currentFrame });
691
- const result = await WeatherFrameProcessorModule.processFrame(options);
692
-
693
- if (isStaleGeneration()) {
694
- wxGridVerbose('preloadCurrentFrameStale', { currentCacheKey, generation });
695
- return;
696
- }
697
-
698
- if (!result || !result.filePath) {
699
- hasPreloadedRef.current = false;
700
- wxGridWarn('preloadAbort', {
701
- reason: 'processFrame_empty_result',
702
- currentCacheKey,
703
- resultKeys: result ? Object.keys(result) : [],
704
- });
705
- return;
706
- }
707
-
708
- const { baseUnit } = core._getColormapForVariable(variable);
709
-
710
- const toUnit = core._getTargetUnit(baseUnit, units);
711
-
712
- const fieldInfo = DICTIONARIES?.fld?.[variable] || {};
713
- const serverDataUnit = fieldInfo.defaultUnit || baseUnit;
714
-
715
- let dataScale = result.scale;
716
- let dataOffset = result.offset;
717
-
718
- let convertedScale = dataScale;
719
- let convertedOffset = dataOffset;
720
-
721
- if (serverDataUnit !== baseUnit) {
722
- const conversionFunc = getUnitConversionFunction(serverDataUnit, baseUnit);
723
- if (conversionFunc) {
724
- if (result.scaleType === 'sqrt') {
725
- const physicalAtOffset = dataOffset * dataOffset;
726
- const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
727
- const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
728
- const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
729
- convertedOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
730
- const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
731
- convertedScale = newOffsetPlusScale - convertedOffset;
732
- } else {
733
- convertedOffset = conversionFunc(dataOffset);
734
- const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
735
- convertedScale = convertedOffsetPlusScale - convertedOffset;
736
- }
737
- dataScale = convertedScale;
738
- dataOffset = convertedOffset;
739
- }
740
- }
741
-
742
- if (baseUnit !== toUnit) {
743
- const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
744
- if (conversionFunc) {
745
- if (result.scaleType === 'sqrt') {
746
- const physicalAtOffset = dataOffset * dataOffset;
747
- const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
748
- const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
749
- const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
750
- convertedOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
751
- const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
752
- convertedScale = newOffsetPlusScale - convertedOffset;
753
- } else {
754
- convertedOffset = conversionFunc(dataOffset);
755
- const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
756
- convertedScale = convertedOffsetPlusScale - convertedOffset;
757
- }
758
- }
759
- }
760
-
761
- const frameData = {
762
- filePath: result.filePath,
763
- nx, ny,
764
- scale: convertedScale,
765
- offset: convertedOffset,
766
- missing: result.missing,
767
- corners,
768
- gridDef,
769
- scaleType: result.scaleType,
770
- originalScale: result.scale,
771
- originalOffset: result.offset
772
- };
773
-
774
- preloadedDataCache.current.set(currentCacheKey, frameData);
775
-
776
- // Update the GPU with the current frame
777
- gridLayerRef.current.updateDataTextureFromFile(
778
- frameData.filePath,
779
- frameData.nx, frameData.ny,
780
- frameData.scale, frameData.offset, frameData.missing,
781
- frameData.scaleType
782
- );
783
-
784
- currentGridDataRef.current = {
785
- nx: frameData.nx,
786
- ny: frameData.ny,
787
- scale: frameData.scale,
788
- offset: frameData.offset,
789
- missing: frameData.missing,
790
- gridDef: frameData.gridDef,
791
- variable: variable,
792
- units: units,
793
- scaleType: frameData.scaleType
794
- };
795
-
796
- wxGridVerbose('preloadCurrentFrameOk', {
797
- currentCacheKey,
798
- nx: frameData.nx,
799
- ny: frameData.ny,
800
- scale: frameData.scale,
801
- offset: frameData.offset,
802
- filePathSuffix: String(frameData.filePath).split('/').pop(),
803
- });
804
- } catch (e) {
805
- const cancelled =
806
- e &&
807
- (e.code === 'E_CANCELLED' ||
808
- e?.userInfo?.code === 'E_CANCELLED' ||
809
- (typeof e?.message === 'string' && e.message.includes('superseded')));
810
- if (!cancelled) {
811
- hasPreloadedRef.current = false;
812
- const errDetail = {
813
- currentCacheKey,
814
- code: e?.code,
815
- message: e?.message,
816
- userInfo: e?.userInfo,
817
- };
818
- wxGridWarn('preloadProcessFrameError', errDetail);
819
- if (isAguaceroRnDebugEnabled()) {
820
- aguaceroDebugWarn('preloadProcessFrameError', {
821
- ...errDetail,
822
- auth: getAguaceroAuthDiagnosticSnapshot(core),
823
- is403:
824
- e?.code === 'HTTP_ERROR' &&
825
- typeof e?.message === 'string' &&
826
- e.message.includes('403'),
827
- });
828
- }
829
- } else {
830
- wxGridVerbose('preloadProcessFrameCancelled', { currentCacheKey });
831
- }
832
- }
833
- }
834
-
835
- // NOW preload the rest of the frames asynchronously
836
- framesToPreload.forEach((frame) => {
837
- const cacheKey = isMRMS ? `mrms-${frame}-${variable}` : `${model}-${date}-${run}-${frame}-${variable}`;
838
- if (preloadedDataCache.current.has(cacheKey)) {
839
- return;
840
- }
841
-
842
- let resourcePath;
843
- if (isMRMS) {
844
- const frameDate = new Date(frame * 1000);
845
- const y = frameDate.getUTCFullYear();
846
- const m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0');
847
- const d = frameDate.getUTCDate().toString().padStart(2, '0');
848
- resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${variable}/0`;
849
- } else {
850
- resourcePath = `/grids/${model}/${date}/${run}/${frame}/${variable}/0`;
851
- }
852
-
853
- const options = augmentProcessFrameOptionsForDebug(
854
- buildGridFrameProcessOptions(
855
- core.baseGridUrl,
856
- resourcePath,
857
- core.apiKey,
858
- core.bundleId,
859
- gridRequestSiteOrigin,
860
- core,
861
- ),
862
- core,
863
- );
864
- logProcessFrameAuthMismatch(core, options, { phase: 'preloadBackground', cacheKey });
865
-
866
- WeatherFrameProcessorModule.processFrame(options)
867
- .then(result => {
868
- if (isStaleGeneration()) {
869
- return;
870
- }
871
- if (!result || !result.filePath) {
872
- return;
873
- }
874
-
875
- // ADD: Same two-step conversion as the current frame
876
- const { baseUnit } = core._getColormapForVariable(variable);
877
- const toUnit = core._getTargetUnit(baseUnit, units);
878
- const fieldInfo = DICTIONARIES?.fld?.[variable] || {};
879
- const serverDataUnit = fieldInfo.defaultUnit || baseUnit;
880
-
881
- let dataScale = result.scale;
882
- let dataOffset = result.offset;
883
-
884
- let convertedScale = dataScale;
885
- let convertedOffset = dataOffset;
886
-
887
- // Step 1: Convert from server unit to colormap base unit
888
- if (serverDataUnit !== baseUnit) {
889
- const conversionFunc = getUnitConversionFunction(serverDataUnit, baseUnit);
890
- if (conversionFunc) {
891
- if (result.scaleType === 'sqrt') {
892
- const physicalAtOffset = dataOffset * dataOffset;
893
- const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
894
- const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
895
- const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
896
- convertedOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
897
- const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
898
- convertedScale = newOffsetPlusScale - convertedOffset;
899
- } else {
900
- convertedOffset = conversionFunc(dataOffset);
901
- const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
902
- convertedScale = convertedOffsetPlusScale - convertedOffset;
903
- }
904
- dataScale = convertedScale;
905
- dataOffset = convertedOffset;
906
- }
907
- }
908
-
909
- // Step 2: Convert from colormap base unit to target display unit
910
- if (baseUnit !== toUnit) {
911
- const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
912
- if (conversionFunc) {
913
- if (result.scaleType === 'sqrt') {
914
- const physicalAtOffset = dataOffset * dataOffset;
915
- const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
916
- const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
917
- const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
918
- convertedOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
919
- const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
920
- convertedScale = newOffsetPlusScale - convertedOffset;
921
- } else {
922
- convertedOffset = conversionFunc(dataOffset);
923
- const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
924
- convertedScale = convertedOffsetPlusScale - convertedOffset;
925
- }
926
- }
927
- }
928
-
929
- const frameData = {
930
- filePath: result.filePath,
931
- nx, ny,
932
- scale: convertedScale,
933
- offset: convertedOffset,
934
- missing: result.missing,
935
- corners,
936
- gridDef,
937
- scaleType: result.scaleType,
938
- originalScale: result.scale,
939
- originalOffset: result.offset
940
- };
941
-
942
- preloadedDataCache.current.set(cacheKey, frameData);
943
-
944
- if (Platform.OS === 'ios' && gridLayerRef.current.primeGpuCache) {
945
- const frameInfoForGpu = {
946
- [cacheKey]: {
947
- filePath: frameData.filePath,
948
- nx: frameData.nx,
949
- ny: frameData.ny,
950
- scale: frameData.scale,
951
- offset: frameData.offset,
952
- missing: frameData.missing,
953
- scaleType: frameData.scaleType || 'linear',
954
- originalScale: frameData.originalScale,
955
- originalOffset: frameData.originalOffset
956
- }
957
- };
958
- gridLayerRef.current.primeGpuCache(frameInfoForGpu);
959
- }
960
- })
961
- .catch((e) => {
962
- const cancelled =
963
- e &&
964
- (e.code === 'E_CANCELLED' ||
965
- e?.userInfo?.code === 'E_CANCELLED' ||
966
- (typeof e?.message === 'string' && e.message.includes('superseded')));
967
- if (!cancelled) {
968
- wxGridWarn('backgroundFrameError', { cacheKey, code: e?.code, message: e?.message });
969
- }
970
- });
971
- });
972
- };
973
-
974
- useEffect(() => {
975
- // This effect manages the auto-refresh based on props
976
- if (autoRefresh) {
977
- const interval = (autoRefreshInterval || 30) * 1000;
978
- _checkForUpdates(); // Run immediately on enable
979
- autoRefreshIntervalId.current = setInterval(_checkForUpdates, interval);
980
- }
981
-
982
- // Cleanup function: this runs when the component unmounts or props change
983
- return () => {
984
- if (autoRefreshIntervalId.current) {
985
- clearInterval(autoRefreshIntervalId.current);
986
- autoRefreshIntervalId.current = null;
987
- }
988
- };
989
- }, [autoRefresh, autoRefreshInterval, _checkForUpdates]);
990
-
991
- const updateGPUWithCachedData = (state) => {
992
- const { model, date, run, forecastHour, variable, units, isMRMS, mrmsTimestamp } = state;
993
-
994
- const cacheKey = isMRMS
995
- ? `mrms-${mrmsTimestamp}-${variable}`
996
- : `${model}-${date}-${run}-${forecastHour}-${variable}`;
997
-
998
- if (Platform.OS === 'ios' && gridLayerRef.current.setActiveFrame) {
999
- // Get the cached data BEFORE calling setActiveFrame
1000
- const cachedData = preloadedDataCache.current.get(cacheKey);
1001
-
1002
- if (cachedData) {
1003
- currentGridDataRef.current = {
1004
- nx: cachedData.nx,
1005
- ny: cachedData.ny,
1006
- scale: cachedData.scale,
1007
- offset: cachedData.offset,
1008
- missing: cachedData.missing,
1009
- gridDef: cachedData.gridDef,
1010
- variable: variable,
1011
- units: units,
1012
- scaleType: cachedData.scaleType
1013
- };
1014
- } else {
1015
- wxGridVerbose('gpuIOS_setActiveFrame_no_row', {
1016
- cacheKey,
1017
- cacheSize: preloadedDataCache.current.size,
1018
- });
1019
- }
1020
-
1021
- gridLayerRef.current.setActiveFrame(cacheKey);
1022
- wxGridVerbose('gpuIOS_setActiveFrame', { cacheKey, hadCachedRow: Boolean(cachedData) });
1023
- return true;
1024
- }
1025
-
1026
- const cachedData = preloadedDataCache.current.get(cacheKey);
1027
-
1028
- if (!cachedData) {
1029
- wxGridWarn('updateGPUNoCache', {
1030
- cacheKey,
1031
- cacheSize: preloadedDataCache.current.size,
1032
- sampleKeys: Array.from(preloadedDataCache.current.keys()).slice(0, 5),
1033
- });
1034
- return false;
1035
- }
1036
-
1037
- if (!gridLayerRef.current) {
1038
- wxGridWarn('updateGPUNoGridLayerRef', { cacheKey });
1039
- return false;
1040
- }
1041
-
1042
- if (!cachedGeometry.current || cachedGeometry.current.model !== (isMRMS ? 'mrms' : model) || cachedGeometry.current.variable !== variable) {
1043
- gridLayerRef.current.updateGeometry(cachedData.corners, cachedData.gridDef);
1044
- cachedGeometry.current = { model: (isMRMS ? 'mrms' : model), variable };
1045
- }
1046
-
1047
- const colormapKey = `${variable}-${units}`;
1048
- if (!cachedColormap.current || cachedColormap.current.key !== colormapKey) {
1049
- const { colormap, baseUnit } = core._getColormapForVariable(variable);
1050
- const toUnit = core._getTargetUnit(baseUnit, units);
1051
- const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
1052
- let dataRange;
1053
- if (variable === 'ptypeRefl' || variable === 'ptypeRate') {
1054
- if (isMRMS) {
1055
- dataRange = [5, 380];
1056
- } else {
1057
- dataRange = [5, 380];
1058
- }
1059
- } else {
1060
- dataRange = [finalColormap[0], finalColormap[finalColormap.length - 2]];
1061
- }
1062
- const colormapBytes = _generateColormapBytes(finalColormap);
1063
- const colormapAsBase64 = fromByteArray(colormapBytes);
1064
-
1065
- gridLayerRef.current.updateColormapTexture(colormapAsBase64);
1066
- cachedColormap.current = { key: colormapKey };
1067
- cachedDataRange.current = dataRange;
1068
-
1069
- setRenderProps(prev => ({ ...prev, dataRange }));
1070
- }
1071
-
1072
- if (cachedData.filePath) {
1073
- gridLayerRef.current.updateDataTextureFromFile(
1074
- cachedData.filePath,
1075
- cachedData.nx, cachedData.ny,
1076
- cachedData.scale, cachedData.offset, cachedData.missing,
1077
- cachedData.scaleType
1078
- );
1079
- currentGridDataRef.current = {
1080
- nx: cachedData.nx,
1081
- ny: cachedData.ny,
1082
- scale: cachedData.scale,
1083
- offset: cachedData.offset,
1084
- missing: cachedData.missing,
1085
- gridDef: cachedData.gridDef,
1086
- variable: variable,
1087
- units: units,
1088
- scaleType: cachedData.scaleType
1089
- };
1090
- wxGridVerbose('gpuUploadFromFile', {
1091
- cacheKey,
1092
- nx: cachedData.nx,
1093
- ny: cachedData.ny,
1094
- filePathSuffix: String(cachedData.filePath).split('/').pop(),
1095
- });
1096
- } else {
1097
- wxGridWarn('updateGPUNoFilePath', { cacheKey });
1098
- return false;
1099
- }
1100
-
1101
- // Update inspector parameters for file-based data too
1102
- if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
1103
- gridLayerRef.current.updateDataParameters(cachedData.scale, cachedData.offset, cachedData.missing);
1104
- }
1105
- return true;
1106
- };
1107
-
1108
- const handleStateChangeRef = useRef(null);
1109
- const debounceTimeoutRef = useRef(null);
1110
-
1111
- useEffect(() => {
1112
- if (core && props.customColormaps) {
1113
- core.customColormaps = props.customColormaps;
1114
- // Trigger a re-render if we already have data loaded
1115
- if (hasInitialLoad.current) {
1116
- core._emitStateChange();
1117
- }
1118
- }
1119
- }, [core, props.customColormaps]);
1120
-
1121
- const getValueAtPoint = async (lng, lat) => {
1122
- if (!core) {
1123
- return null;
1124
- }
1125
-
1126
- if (NEXRAD_NATIVE && core.state?.isNexrad) {
1127
- return getNexradInspectPayloadAt(lng, lat);
1128
- }
1129
-
1130
- // ADD THIS: Check if we have valid data before attempting inspection
1131
- if (!currentGridDataRef.current) {
1132
- return null;
1133
- }
1134
-
1135
- try {
1136
- const gridIndices = core._getGridIndexFromLngLat(lng, lat);
1137
- if (!gridIndices) return null;
1138
-
1139
- const { i, j } = gridIndices;
1140
-
1141
- const value = await InspectorModule.getValueAtGridIndex(i, j);
1142
-
1143
- if (value === null) {
1144
- return null;
1145
- }
1146
-
1147
- const { colormap, baseUnit } = core._getColormapForVariable(core.state.variable);
1148
- const displayUnit = core._getTargetUnit(baseUnit, core.state.units);
1149
- const finalColormap = core._convertColormapUnits(colormap, baseUnit, displayUnit);
1150
- const minThreshold = finalColormap[0];
1151
-
1152
- if (value < minThreshold) {
1153
- return null;
1154
- }
1155
-
1156
- // Filter out values below the minimum threshold (matching shader behavior)
1157
- if (value < minThreshold) {
1158
- return null;
1159
- }
1160
-
1161
- // Also check if value is NaN or effectively missing
1162
- if (!isFinite(value)) {
1163
- return null;
1164
- }
1165
-
1166
- return {
1167
- value: value,
1168
- unit: displayUnit,
1169
- variable: {
1170
- code: core.state.variable,
1171
- name: core.getVariableDisplayName(core.state.variable)
1172
- },
1173
- lngLat: { lng, lat }
1174
- };
1175
- } catch {
1176
- return null;
1177
- }
1178
- };
1179
-
1180
- const getValueAtPointRef = useRef(getValueAtPoint);
1181
- getValueAtPointRef.current = getValueAtPoint;
1182
- const getNexradInspectPayloadAtRef = useRef(getNexradInspectPayloadAt);
1183
- getNexradInspectPayloadAtRef.current = getNexradInspectPayloadAt;
1184
-
1185
- const _checkForUpdates = useMemo(() => async () => {
1186
- if (!core) return;
1187
- const s = core.state;
1188
- const { isMRMS, isSatellite, isNexrad, model: currentModel, variable: currentVariable, date, run } = s;
1189
-
1190
- if (isSatellite) {
1191
- const prevTimeline = core._computeSatelliteTimeline();
1192
- const prevTimes = [...(prevTimeline.unixTimes || [])]
1193
- .map((t) => Number(t))
1194
- .filter((t) => !Number.isNaN(t))
1195
- .sort((a, b) => a - b);
1196
- const prevMax = prevTimes.length ? prevTimes[prevTimes.length - 1] : null;
1197
- const curSat = s.satelliteTimestamp == null ? null : Number(s.satelliteTimestamp);
1198
-
1199
- await core.fetchSatelliteListing(true);
1200
- core._emitStateChange();
1201
-
1202
- const nextTimeline = core._computeSatelliteTimeline();
1203
- const nextTimes = [...(nextTimeline.unixTimes || [])]
1204
- .map((t) => Number(t))
1205
- .filter((t) => !Number.isNaN(t))
1206
- .sort((a, b) => a - b);
1207
- const newMax = nextTimes.length ? nextTimes[nextTimes.length - 1] : null;
1208
-
1209
- if (prevMax != null && curSat != null && curSat === prevMax && newMax != null && newMax > prevMax) {
1210
- await core.setSatelliteTimestamp(newMax);
1211
- } else if (curSat != null && newMax != null && nextTimes.length && !nextTimes.includes(curSat)) {
1212
- await core.setSatelliteTimestamp(newMax);
1213
- }
1214
- return;
1215
- }
1216
-
1217
- if (isNexrad && s.nexradSite) {
1218
- const nk = core._nexradTimesCacheKey();
1219
- const rawBefore = nk ? core.nexradTimesByStation[nk]?.unixTimes : [];
1220
- const filteredBefore = core._getFilteredNexradTimestampsForVariable(rawBefore || []);
1221
- const prevMax = filteredBefore.length ? filteredBefore[filteredBefore.length - 1] : null;
1222
- const curNx = s.nexradTimestamp == null ? null : Number(s.nexradTimestamp);
1223
-
1224
- await core.refreshNexradTimes();
1225
-
1226
- const rawAfter = nk ? core.nexradTimesByStation[nk]?.unixTimes : [];
1227
- const filteredAfter = core._getFilteredNexradTimestampsForVariable(rawAfter || []);
1228
- const newMax = filteredAfter.length ? filteredAfter[filteredAfter.length - 1] : null;
1229
-
1230
- if (prevMax != null && curNx != null && curNx === prevMax && newMax != null && newMax > prevMax) {
1231
- await core.setNexradTimestamp(newMax);
1232
- } else if (curNx != null && newMax != null && filteredAfter.length && !filteredAfter.includes(curNx)) {
1233
- await core.setNexradTimestamp(newMax);
1234
- }
1235
- return;
1236
- }
1237
-
1238
- if (isMRMS) {
1239
- // --- MRMS LOGIC (Keep existing logic) ---
1240
- const oldTimestamps = new Set(core.mrmsStatus?.[currentVariable] || []);
1241
- const mrmsStatus = await core.fetchMRMSStatus(true);
1242
- const newTimestamps = mrmsStatus?.[currentVariable] || [];
1243
- if (newTimestamps.length === 0) return;
1244
-
1245
- const newTimestampsToPreload = newTimestamps.filter(ts => !oldTimestamps.has(ts));
1246
-
1247
- if (newTimestampsToPreload.length > 0) {
1248
- core.mrmsStatus = mrmsStatus;
1249
- core._emitStateChange(); // Update UI slider without changing selection
1250
-
1251
- // ... (Keep your existing preloading logic here) ...
1252
- const { corners, gridDef } = core._getGridCornersAndDef('mrms');
1253
- const { nx, ny } = gridDef.grid_params;
1254
-
1255
- newTimestampsToPreload.forEach(frame => {
1256
- const cacheKey = `mrms-${frame}-${currentVariable}`;
1257
- if (preloadedDataCache.current.has(cacheKey)) return;
1258
-
1259
- const frameDate = new Date(frame * 1000);
1260
- const y = frameDate.getUTCFullYear(), m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0'), d = frameDate.getUTCDate().toString().padStart(2, '0');
1261
- const resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${currentVariable}/0`;
1262
- const options = augmentProcessFrameOptionsForDebug(
1263
- buildGridFrameProcessOptions(
1264
- core.baseGridUrl,
1265
- resourcePath,
1266
- core.apiKey,
1267
- core.bundleId,
1268
- gridRequestSiteOrigin,
1269
- core,
1270
- ),
1271
- core,
1272
- );
1273
- logProcessFrameAuthMismatch(core, options, { phase: 'mrmsRefresh', cacheKey });
1274
-
1275
- WeatherFrameProcessorModule.processFrame(options)
1276
- .then(result => {
1277
- if (!result || !result.filePath) return;
1278
- const frameData = { filePath: result.filePath, nx, ny, scale: result.scale, offset: result.offset, missing: result.missing, corners, gridDef, scaleType: result.scaleType, originalScale: result.scale, originalOffset: result.offset };
1279
- preloadedDataCache.current.set(cacheKey, frameData);
1280
- if (Platform.OS === 'ios' && gridLayerRef.current?.primeGpuCache) {
1281
- gridLayerRef.current.primeGpuCache({ [cacheKey]: frameData });
1282
- }
1283
- })
1284
- .catch((e) => {
1285
- const cancelled =
1286
- e &&
1287
- (e.code === 'E_CANCELLED' ||
1288
- e?.userInfo?.code === 'E_CANCELLED' ||
1289
- (typeof e?.message === 'string' && e.message.includes('superseded')));
1290
- if (!cancelled) {
1291
- wxGridWarn('mrmsRefreshFrameError', { cacheKey, code: e?.code, message: e?.message });
1292
- }
1293
- });
1294
- });
1295
-
1296
- const newTimestampsSet = new Set(newTimestamps);
1297
- oldTimestamps.forEach(oldTs => {
1298
- if (!newTimestampsSet.has(oldTs)) {
1299
- const cacheKey = `mrms-${oldTs}-${currentVariable}`;
1300
- preloadedDataCache.current.delete(cacheKey);
1301
- }
1302
- });
1303
- }
1304
- } else {
1305
- const previousStatus = core.modelStatus;
1306
- const modelStatus = await core.fetchModelStatus(true);
1307
- const statusChanged = JSON.stringify(previousStatus) !== JSON.stringify(modelStatus);
1308
- if (statusChanged) {
1309
- core._emitStateChange();
1310
- }
1311
-
1312
- const latestRun = findLatestModelRun(modelStatus, currentModel);
1313
- if (!latestRun) return;
1314
- }
1315
- }, [core]);
1316
-
1317
- useEffect(() => {
1318
- if (!core) {
1319
- return;
1320
- }
1321
-
1322
- const handleStateChange = (newState) => {
1323
- if (!previousStateRef.current) {
1324
- previousStateRef.current = core.state;
1325
- }
1326
-
1327
- const variableChanged = !previousStateRef.current || newState.variable !== previousStateRef.current.variable;
1328
-
1329
- if (variableChanged && gridLayerRef.current?.setVariable) {
1330
- gridLayerRef.current.setVariable(newState.variable);
1331
- }
1332
-
1333
- const stateKey = newState.isNexrad
1334
- ? `nx-${newState.nexradSite}-${newState.nexradProduct}-${newState.nexradDataSource}-${newState.nexradTimestamp}-${newState.nexradTilt}-${newState.units}-${newState.model}-${newState.variable}-${newState.mrmsTimestamp}-${newState.forecastHour}-dur:${newState.nexradDurationValue ?? ''}-tl:${nexradObsTimelineSig(newState)}`
1335
- : newState.isSatellite
1336
- ? `sat-${newState.satelliteInstrumentId}-${newState.satelliteSectorLabel}-${newState.satelliteChannel}-${newState.satelliteTimestamp}-${newState.variable}-${newState.units}-${newState.opacity}-dur:${newState.satelliteDurationValue ?? ''}-tl:${satelliteObsTimelineSig(newState)}`
1337
- : `${newState.model}-${newState.variable}-${newState.date}-${newState.run}-${newState.forecastHour}-${newState.units}-${newState.mrmsTimestamp}`;
1338
-
1339
- const sameTimeline = newState.isNexrad
1340
- ? newState.nexradTimestamp === previousStateRef.current?.nexradTimestamp &&
1341
- newState.nexradSite === previousStateRef.current?.nexradSite &&
1342
- newState.nexradProduct === previousStateRef.current?.nexradProduct &&
1343
- Number(newState.nexradTilt) === Number(previousStateRef.current?.nexradTilt) &&
1344
- newState.nexradDurationValue === previousStateRef.current?.nexradDurationValue &&
1345
- nexradObsTimelineSig(newState) === nexradObsTimelineSig(previousStateRef.current)
1346
- : newState.isSatellite
1347
- ? newState.satelliteTimestamp === previousStateRef.current?.satelliteTimestamp &&
1348
- newState.satelliteInstrumentId === previousStateRef.current?.satelliteInstrumentId &&
1349
- newState.satelliteSectorLabel === previousStateRef.current?.satelliteSectorLabel &&
1350
- newState.satelliteChannel === previousStateRef.current?.satelliteChannel &&
1351
- newState.satelliteDurationValue === previousStateRef.current?.satelliteDurationValue &&
1352
- satelliteObsTimelineSig(newState) === satelliteObsTimelineSig(previousStateRef.current)
1353
- : newState.forecastHour === previousStateRef.current?.forecastHour &&
1354
- newState.mrmsTimestamp === previousStateRef.current?.mrmsTimestamp;
1355
-
1356
- const sameModeAnchor =
1357
- newState.isSatellite
1358
- ? newState.satelliteInstrumentId === previousStateRef.current?.satelliteInstrumentId &&
1359
- newState.satelliteSectorLabel === previousStateRef.current?.satelliteSectorLabel &&
1360
- newState.satelliteChannel === previousStateRef.current?.satelliteChannel
1361
- : newState.model === previousStateRef.current?.model &&
1362
- newState.isMRMS === previousStateRef.current?.isMRMS;
1363
-
1364
- const isOpacityOnlyChange =
1365
- hasInitialLoad.current &&
1366
- newState.opacity !== renderProps.opacity &&
1367
- newState.variable === previousStateRef.current?.variable &&
1368
- sameTimeline &&
1369
- sameModeAnchor &&
1370
- newState.units === previousStateRef.current?.units;
1371
-
1372
- const isPlayStateOnlyChange =
1373
- hasInitialLoad.current &&
1374
- newState.isPlaying !== previousStateRef.current?.isPlaying &&
1375
- newState.variable === previousStateRef.current?.variable &&
1376
- sameTimeline &&
1377
- sameModeAnchor &&
1378
- newState.units === previousStateRef.current?.units &&
1379
- newState.opacity === previousStateRef.current?.opacity;
1380
-
1381
- if (!isOpacityOnlyChange && !isPlayStateOnlyChange && lastProcessedState.current === stateKey) {
1382
- previousStateRef.current = newState;
1383
- return;
1384
- }
1385
-
1386
- if (
1387
- wxGridDebugEnabled() &&
1388
- Platform.OS === 'ios' &&
1389
- ((newState.isNexrad && previousStateRef.current?.isNexrad) ||
1390
- (newState.isSatellite && previousStateRef.current?.isSatellite))
1391
- ) {
1392
- const prev = previousStateRef.current;
1393
- if (newState.isNexrad && prev?.isNexrad) {
1394
- if (
1395
- newState.nexradDurationValue !== prev.nexradDurationValue ||
1396
- nexradObsTimelineSig(newState) !== nexradObsTimelineSig(prev)
1397
- ) {
1398
- wxGridVerbose('iosNexradTimelineWindowChanged', {
1399
- duration: { from: prev.nexradDurationValue, to: newState.nexradDurationValue },
1400
- timelineSig: { from: nexradObsTimelineSig(prev), to: nexradObsTimelineSig(newState) },
1401
- willSyncNative: true,
1402
- });
1403
- }
1404
- }
1405
- if (newState.isSatellite && prev?.isSatellite) {
1406
- if (
1407
- newState.satelliteDurationValue !== prev.satelliteDurationValue ||
1408
- satelliteObsTimelineSig(newState) !== satelliteObsTimelineSig(prev)
1409
- ) {
1410
- wxGridVerbose('iosSatelliteTimelineWindowChanged', {
1411
- duration: { from: prev.satelliteDurationValue, to: newState.satelliteDurationValue },
1412
- timelineSig: { from: satelliteObsTimelineSig(prev), to: satelliteObsTimelineSig(newState) },
1413
- willSyncNative: true,
1414
- });
1415
- }
1416
- }
1417
- }
1418
-
1419
- if (!isOpacityOnlyChange && !isPlayStateOnlyChange) {
1420
- lastProcessedState.current = stateKey;
1421
- }
1422
-
1423
- if (isOpacityOnlyChange) {
1424
- setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
1425
- if (NEXRAD_NATIVE && newState.isNexrad) {
1426
- nexradControllerRef.current?.applyStyleFromState?.(newState);
1427
- }
1428
- if (SATELLITE_NATIVE && newState.isSatellite) {
1429
- satelliteLayerRef.current?.updateSatelliteStyle?.(
1430
- JSON.stringify({
1431
- visible: newState.visible !== false,
1432
- opacity: newState.opacity ?? 1,
1433
- fillSmoothing: 0,
1434
- }),
1435
- );
1436
- }
1437
- previousStateRef.current = newState;
1438
- return;
1439
- }
1440
-
1441
- if (isPlayStateOnlyChange) {
1442
- previousStateRef.current = newState;
1443
- return;
1444
- }
1445
-
1446
- const isUnitsOnlyChange =
1447
- hasInitialLoad.current &&
1448
- newState.model === previousStateRef.current.model &&
1449
- newState.isMRMS === previousStateRef.current.isMRMS &&
1450
- newState.variable === previousStateRef.current.variable &&
1451
- newState.date === previousStateRef.current.date &&
1452
- newState.run === previousStateRef.current.run &&
1453
- (newState.isNexrad
1454
- ? newState.nexradTimestamp === previousStateRef.current.nexradTimestamp &&
1455
- newState.nexradSite === previousStateRef.current.nexradSite &&
1456
- newState.nexradProduct === previousStateRef.current.nexradProduct
1457
- : newState.forecastHour === previousStateRef.current.forecastHour &&
1458
- newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp) &&
1459
- newState.units !== previousStateRef.current.units;
1460
-
1461
- if (isUnitsOnlyChange) {
1462
- if (NEXRAD_NATIVE && newState.isNexrad) {
1463
- setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
1464
- nexradControllerRef.current?.applyStyleFromState?.(newState);
1465
- previousStateRef.current = newState;
1466
- return;
1467
- }
1468
- const { variable, units, isMRMS, mrmsTimestamp, model, date, run, forecastHour } = newState;
1469
- const oldCacheKey = isMRMS
1470
- ? `mrms-${mrmsTimestamp}-${variable}`
1471
- : `${model}-${date}-${run}-${forecastHour}-${variable}`;
1472
-
1473
- const cachedData = preloadedDataCache.current.get(oldCacheKey);
1474
-
1475
- if (cachedData && cachedData.originalScale !== undefined && cachedData.originalOffset !== undefined) {
1476
- const { baseUnit } = core._getColormapForVariable(variable);
1477
- const toUnit = core._getTargetUnit(baseUnit, units);
1478
- const fieldInfo = DICTIONARIES?.fld?.[variable] || {};
1479
- const serverDataUnit = fieldInfo.defaultUnit || baseUnit;
1480
-
1481
- let dataScale = cachedData.originalScale;
1482
- let dataOffset = cachedData.originalOffset;
1483
-
1484
- if (serverDataUnit !== baseUnit) {
1485
- const conversionFunc = getUnitConversionFunction(serverDataUnit, baseUnit);
1486
- if (conversionFunc) {
1487
- if (cachedData.scaleType === 'sqrt') {
1488
- const physicalAtOffset = dataOffset * dataOffset;
1489
- const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
1490
- const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
1491
- const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
1492
- const newOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
1493
- const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
1494
- dataScale = newOffsetPlusScale - newOffset;
1495
- dataOffset = newOffset;
1496
- } else {
1497
- const convertedOffset = conversionFunc(dataOffset);
1498
- const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
1499
- dataScale = convertedOffsetPlusScale - convertedOffset;
1500
- dataOffset = convertedOffset;
1501
- }
1502
- }
1503
- }
1504
-
1505
- if (baseUnit !== toUnit) {
1506
- const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
1507
- if (conversionFunc) {
1508
- if (cachedData.scaleType === 'sqrt') {
1509
- const physicalAtOffset = dataOffset * dataOffset;
1510
- const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
1511
- const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
1512
- const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
1513
- const newOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
1514
- const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
1515
- dataScale = newOffsetPlusScale - newOffset;
1516
- dataOffset = newOffset;
1517
- } else {
1518
- const convertedOffset = conversionFunc(dataOffset);
1519
- const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
1520
- dataScale = convertedOffsetPlusScale - convertedOffset;
1521
- dataOffset = convertedOffset;
1522
- }
1523
- }
1524
- }
1525
-
1526
- const { colormap } = core._getColormapForVariable(variable);
1527
- const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
1528
- let dataRange = (variable === 'ptypeRefl' || variable === 'ptypeRate') ? [5, 380] : [finalColormap[0], finalColormap[finalColormap.length - 2]];
1529
- const colormapBytes = _generateColormapBytes(finalColormap);
1530
- const colormapAsBase64 = fromByteArray(colormapBytes);
1531
-
1532
- gridLayerRef.current.updateColormapTexture(colormapAsBase64);
1533
- cachedColormap.current = { key: `${variable}-${units}` };
1534
- cachedDataRange.current = dataRange;
1535
- setRenderProps(prev => ({ ...prev, dataRange, opacity: newState.opacity }));
1536
-
1537
- if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
1538
- const scaleTypeValue = cachedData.scaleType === 'sqrt' ? 1 : 0;
1539
- gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing, scaleTypeValue);
1540
- }
1541
-
1542
- const newCacheKey = isMRMS ? `mrms-${mrmsTimestamp}-${variable}` : `${model}-${date}-${run}-${forecastHour}-${variable}`;
1543
- preloadedDataCache.current.set(newCacheKey, { ...cachedData, scale: dataScale, offset: dataOffset });
1544
- }
1545
-
1546
- previousStateRef.current = newState;
1547
- return;
1548
- }
1549
-
1550
- const prev = previousStateRef.current;
1551
- const needsFullLoad =
1552
- !hasInitialLoad.current ||
1553
- newState.isNexrad !== prev?.isNexrad ||
1554
- newState.isSatellite !== prev?.isSatellite ||
1555
- (newState.isNexrad &&
1556
- prev?.isNexrad &&
1557
- (newState.nexradSite !== prev.nexradSite ||
1558
- newState.nexradDataSource !== prev.nexradDataSource ||
1559
- newState.nexradProduct !== prev.nexradProduct)) ||
1560
- (newState.isSatellite &&
1561
- prev?.isSatellite &&
1562
- (newState.satelliteInstrumentId !== prev.satelliteInstrumentId ||
1563
- newState.satelliteSectorLabel !== prev.satelliteSectorLabel ||
1564
- newState.satelliteChannel !== prev.satelliteChannel)) ||
1565
- (!newState.isNexrad &&
1566
- !newState.isSatellite &&
1567
- (newState.model !== prev?.model ||
1568
- newState.isMRMS !== prev?.isMRMS ||
1569
- newState.variable !== prev?.variable ||
1570
- newState.date !== prev?.date ||
1571
- newState.run !== prev?.run));
1572
-
1573
- if (needsFullLoad) {
1574
- wxGridVerbose('needsFullLoad', {
1575
- variable: newState.variable,
1576
- isMRMS: newState.isMRMS,
1577
- model: newState.model,
1578
- isInitial: !hasInitialLoad.current,
1579
- prevVariable: prev?.variable,
1580
- prevModel: prev?.model,
1581
- });
1582
- const nexradIdentityChange =
1583
- NEXRAD_NATIVE &&
1584
- (newState.isNexrad !== prev?.isNexrad ||
1585
- (newState.isNexrad &&
1586
- prev?.isNexrad &&
1587
- (newState.nexradSite !== prev.nexradSite ||
1588
- newState.nexradDataSource !== prev.nexradDataSource ||
1589
- newState.nexradProduct !== prev.nexradProduct)));
1590
- const satelliteIdentityChange =
1591
- SATELLITE_NATIVE &&
1592
- (newState.isSatellite !== prev?.isSatellite ||
1593
- (newState.isSatellite &&
1594
- prev?.isSatellite &&
1595
- (newState.satelliteInstrumentId !== prev.satelliteInstrumentId ||
1596
- newState.satelliteSectorLabel !== prev.satelliteSectorLabel ||
1597
- newState.satelliteChannel !== prev.satelliteChannel)));
1598
- if (nexradIdentityChange) {
1599
- nexradPreloadInteractionRef.current?.cancel?.();
1600
- nexradPreloadInteractionRef.current = null;
1601
- nexradControllerRef.current?.destroy();
1602
- nexradControllerRef.current = null;
1603
- }
1604
- if (satelliteIdentityChange) {
1605
- satelliteControllerRef.current?.destroy();
1606
- satelliteControllerRef.current = null;
1607
- }
1608
- if (gridLayerRef.current) {
1609
- gridLayerRef.current.setVariable(newState.variable);
1610
- gridLayerRef.current.clear();
1611
- if (Platform.OS === 'ios' && gridLayerRef.current.clearGpuCache) {
1612
- gridLayerRef.current.clearGpuCache();
1613
- }
1614
- }
1615
- hasPreloadedRef.current = false;
1616
- preloadGenerationRef.current += 1;
1617
- preloadedDataCache.current.clear();
1618
- cachedGeometry.current = null;
1619
- cachedColormap.current = null;
1620
- currentGridDataRef.current = null;
1621
- WeatherFrameProcessorModule.cancelAllFrames();
1622
- wxGridVerbose('cancelAllFrames', {
1623
- reason: 'needsFullLoad',
1624
- variable: newState.variable,
1625
- generation: preloadGenerationRef.current,
1626
- });
1627
-
1628
- if (!newState.variable) {
1629
- previousStateRef.current = newState;
1630
- return;
1631
- }
1632
- if (!newState.isNexrad && !newState.isSatellite) {
1633
- preloadAllFramesToDisk(newState);
1634
- } else if (NEXRAD_NATIVE && newState.isNexrad) {
1635
- hasInitialLoad.current = true;
1636
- } else if (SATELLITE_NATIVE && newState.isSatellite) {
1637
- hasInitialLoad.current = true;
1638
- }
1639
- } else if (
1640
- !newState.isNexrad &&
1641
- (newState.forecastHour !== previousStateRef.current.forecastHour ||
1642
- (newState.isMRMS && newState.mrmsTimestamp !== previousStateRef.current.mrmsTimestamp))
1643
- ) {
1644
- const success = updateGPUWithCachedData(newState);
1645
- if (!success) {
1646
- wxGridWarn('timelineGpuUpdateMiss', {
1647
- isMRMS: newState.isMRMS,
1648
- variable: newState.variable,
1649
- mrmsTimestamp: newState.mrmsTimestamp,
1650
- forecastHour: newState.forecastHour,
1651
- });
1652
- hasPreloadedRef.current = false;
1653
- void preloadAllFramesToDisk(newState);
1654
- }
1655
- if (success && newState.opacity !== renderProps.opacity) {
1656
- setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
1657
- }
1658
- }
1659
-
1660
- if (NEXRAD_NATIVE && newState.isNexrad && newState.nexradSite && newState.nexradTimestamp != null) {
1661
- const ctl = ensureNexradController();
1662
- if (ctl) {
1663
- hasInitialLoad.current = true;
1664
- void ctl.sync(newState);
1665
- nexradPreloadInteractionRef.current?.cancel?.();
1666
- const preloadCancelled = { v: false };
1667
- nexradPreloadInteractionRef.current = {
1668
- cancel: () => {
1669
- preloadCancelled.v = true;
1670
- },
1671
- };
1672
- const nexradTimelineSnapshot = Array.isArray(newState.availableNexradTimestamps)
1673
- ? newState.availableNexradTimestamps
1674
- : [];
1675
- const kickPreload = () => {
1676
- nexradPreloadInteractionRef.current = null;
1677
- if (preloadCancelled.v) {
1678
- return;
1679
- }
1680
- const cur = nexradControllerRef.current;
1681
- if (!cur) {
1682
- return;
1683
- }
1684
- const s = core.state;
1685
- if (!s.isNexrad || !s.nexradSite || s.nexradTimestamp == null) {
1686
- return;
1687
- }
1688
- const coreTs = Array.isArray(s.availableNexradTimestamps) ? s.availableNexradTimestamps : [];
1689
- const mergedTimestamps =
1690
- coreTs.length > 0 ? coreTs : nexradTimelineSnapshot.length > 0 ? nexradTimelineSnapshot : [];
1691
- const preloadState = { ...s, availableNexradTimestamps: mergedTimestamps };
1692
- cur.preloadAllAvailable(preloadState);
1693
- };
1694
- if (typeof queueMicrotask === 'function') {
1695
- queueMicrotask(kickPreload);
1696
- } else {
1697
- setTimeout(kickPreload, 0);
1698
- }
1699
- }
1700
- } else if (NEXRAD_NATIVE && (!newState.isNexrad || !newState.nexradSite)) {
1701
- nexradPreloadInteractionRef.current?.cancel?.();
1702
- nexradPreloadInteractionRef.current = null;
1703
- nexradControllerRef.current?.destroy();
1704
- nexradControllerRef.current = null;
1705
- }
1706
-
1707
- if (SATELLITE_NATIVE && newState.isSatellite) {
1708
- satBridgeWarn('handleStateChange → satellite', {
1709
- satelliteInstrumentId: newState.satelliteInstrumentId ?? null,
1710
- satelliteSectorLabel: newState.satelliteSectorLabel ?? null,
1711
- satelliteChannel: newState.satelliteChannel ?? null,
1712
- timelineKeys: Object.keys(newState.satelliteTimeToFileMap || {}).length,
1713
- refHasSync: Boolean(satelliteLayerRef.current?.syncSatellite),
1714
- });
1715
- const satCtl = ensureSatelliteController();
1716
- if (satCtl) {
1717
- hasInitialLoad.current = true;
1718
- void satCtl.sync(newState);
1719
- } else {
1720
- satBridgeWarn('handleStateChange satellite: ensureSatelliteController returned null', {});
1721
- }
1722
- if (!newState.satelliteInstrumentId && typeof __DEV__ !== 'undefined' && __DEV__) {
1723
- console.warn(
1724
- '[AguaceroWX][satellite] isSatellite is true but satelliteInstrumentId is missing — native sync still runs; timeline may be empty until core sets instrument.',
1725
- );
1726
- }
1727
- } else if (SATELLITE_NATIVE && !newState.isSatellite) {
1728
- satelliteControllerRef.current?.destroy();
1729
- satelliteControllerRef.current = null;
1730
- }
1731
-
1732
- previousStateRef.current = newState;
1733
- };
1734
-
1735
- handleStateChangeRef.current = handleStateChange;
1736
-
1737
- const stableHandler = (newState) => {
1738
- lastEmittedStateForInspectRef.current = newState;
1739
- setNexradSitesMapVisible(Boolean(newState.isNexrad && newState.nexradShowSitesPicker !== false));
1740
- // OPTIMIZATION: If playing (high speed), prioritize MAP update and skip debounce
1741
- if (newState.isPlaying) {
1742
- // 1. Update Map FIRST (Native Enqueue)
1743
- if (handleStateChangeRef.current) {
1744
- handleStateChangeRef.current(newState);
1745
- }
1746
-
1747
- // 2. Update UI Slider SECOND
1748
- // This ensures the heavy map frame is processing while React reconciles the slider
1749
- props.onStateChange?.(newState);
1750
-
1751
- if (debounceTimeoutRef.current) {
1752
- clearTimeout(debounceTimeoutRef.current);
1753
- debounceTimeoutRef.current = null;
1754
- }
1755
- return;
1756
- }
1757
-
1758
- // --- Existing Logic for scrubbing/paused ---
1759
-
1760
- // 1. Immediate Slider Update for responsiveness
1761
- props.onStateChange?.(newState);
1762
-
1763
- if (debounceTimeoutRef.current) {
1764
- clearTimeout(debounceTimeoutRef.current);
1765
- }
1766
-
1767
- const prevStable = previousStateRef.current;
1768
- const sameTimelineStable =
1769
- prevStable &&
1770
- (prevStable.isNexrad
1771
- ? newState.nexradTimestamp === prevStable.nexradTimestamp &&
1772
- newState.nexradSite === prevStable.nexradSite &&
1773
- newState.nexradProduct === prevStable.nexradProduct &&
1774
- Number(newState.nexradTilt) === Number(prevStable.nexradTilt) &&
1775
- newState.nexradDurationValue === prevStable.nexradDurationValue &&
1776
- nexradObsTimelineSig(newState) === nexradObsTimelineSig(prevStable)
1777
- : prevStable.isSatellite
1778
- ? newState.satelliteTimestamp === prevStable.satelliteTimestamp &&
1779
- newState.satelliteInstrumentId === prevStable.satelliteInstrumentId &&
1780
- newState.satelliteSectorLabel === prevStable.satelliteSectorLabel &&
1781
- newState.satelliteChannel === prevStable.satelliteChannel &&
1782
- newState.satelliteDurationValue === prevStable.satelliteDurationValue &&
1783
- satelliteObsTimelineSig(newState) === satelliteObsTimelineSig(prevStable)
1784
- : newState.forecastHour === prevStable.forecastHour &&
1785
- newState.mrmsTimestamp === prevStable.mrmsTimestamp);
1786
-
1787
- const sameModeStable =
1788
- prevStable &&
1789
- (newState.isSatellite
1790
- ? newState.satelliteInstrumentId === prevStable.satelliteInstrumentId &&
1791
- newState.satelliteSectorLabel === prevStable.satelliteSectorLabel &&
1792
- newState.satelliteChannel === prevStable.satelliteChannel
1793
- : newState.model === prevStable.model && newState.isMRMS === prevStable.isMRMS);
1794
-
1795
- // Opacity and Play state changes should be immediate for the native layer too
1796
- const isOpacityOnlyChange =
1797
- prevStable &&
1798
- sameTimelineStable &&
1799
- sameModeStable &&
1800
- newState.opacity !== prevStable.opacity &&
1801
- newState.variable === prevStable.variable &&
1802
- newState.units === prevStable.units;
1803
-
1804
- const isPlayStateOnlyChange =
1805
- prevStable &&
1806
- sameTimelineStable &&
1807
- sameModeStable &&
1808
- newState.isPlaying !== prevStable.isPlaying &&
1809
- newState.variable === prevStable.variable &&
1810
- newState.units === prevStable.units &&
1811
- newState.opacity === prevStable.opacity;
1812
-
1813
- if (isOpacityOnlyChange || isPlayStateOnlyChange || !previousStateRef.current) {
1814
- if (handleStateChangeRef.current) {
1815
- handleStateChangeRef.current(newState);
1816
- }
1817
- return;
1818
- }
1819
-
1820
- debounceTimeoutRef.current = setTimeout(() => {
1821
- if (handleStateChangeRef.current) {
1822
- handleStateChangeRef.current(newState);
1823
- }
1824
- debounceTimeoutRef.current = null;
1825
- }, 16); // ~60fps map updates
1826
- };
1827
-
1828
- core.on('state:change', stableHandler);
1829
-
1830
- return () => {
1831
- core.off('state:change', stableHandler);
1832
- if (debounceTimeoutRef.current) {
1833
- clearTimeout(debounceTimeoutRef.current);
1834
- }
1835
- if (NEXRAD_NATIVE) {
1836
- nexradPreloadInteractionRef.current?.cancel?.();
1837
- nexradPreloadInteractionRef.current = null;
1838
- nexradControllerRef.current?.destroy();
1839
- nexradControllerRef.current = null;
1840
- }
1841
- if (SATELLITE_NATIVE) {
1842
- satelliteControllerRef.current?.destroy();
1843
- satelliteControllerRef.current = null;
1844
- }
1845
- };
1846
- }, [core, ensureNexradController, ensureSatelliteController]);
1847
-
1848
- useEffect(() => {
1849
- return () => {
1850
- preloadedDataCache.current.clear(); // This drops JS references
1851
- hasInitialLoad.current = false;
1852
- lastProcessedState.current = null;
1853
- // Native cleanup
1854
- if (gridLayerRef.current && Platform.OS === 'ios') {
1855
- gridLayerRef.current.clearGpuCache();
1856
- }
1857
- if (NEXRAD_NATIVE) {
1858
- nexradPreloadInteractionRef.current?.cancel?.();
1859
- nexradPreloadInteractionRef.current = null;
1860
- nexradControllerRef.current?.destroy();
1861
- nexradControllerRef.current = null;
1862
- }
1863
- if (SATELLITE_NATIVE) {
1864
- satelliteControllerRef.current?.destroy();
1865
- satelliteControllerRef.current = null;
1866
- }
1867
- };
1868
- }, []);
1869
-
1870
- const lastInspectorUpdateRef = useRef(0);
1871
- const INSPECTOR_THROTTLE_MS = 50;
1872
-
1873
- useEffect(() => {
1874
- if (!core || !inspectorEnabled) {
1875
- return;
1876
- }
1877
-
1878
- const handleMapMove = (center) => {
1879
- if (!center || !Array.isArray(center) || center.length !== 2) {
1880
- return;
1881
- }
1882
-
1883
- // Throttle updates
1884
- const now = Date.now();
1885
- if (now - lastInspectorUpdateRef.current < INSPECTOR_THROTTLE_MS) {
1886
- return;
1887
- }
1888
- lastInspectorUpdateRef.current = now;
1889
-
1890
- const [longitude, latitude] = center;
1891
-
1892
- if (NEXRAD_NATIVE && core.state?.isNexrad) {
1893
- onInspect?.(getNexradInspectPayloadAtRef.current(longitude, latitude));
1894
- return;
1895
- }
1896
-
1897
- void getValueAtPointRef.current(longitude, latitude).then((payload) => {
1898
- onInspect?.(payload);
1899
- });
1900
- };
1901
-
1902
- core.on('map:move', handleMapMove);
1903
-
1904
- if (context && context.getCenter) {
1905
- const center = context.getCenter();
1906
- if (center) {
1907
- handleMapMove(center);
1908
- }
1909
- }
1910
-
1911
- return () => {
1912
- core.off('map:move', handleMapMove);
1913
- };
1914
- }, [inspectorEnabled, onInspect, core, context]);
1915
-
1916
- useEffect(() => {
1917
- if (!core || !inspectorEnabled) {
1918
- return;
1919
- }
1920
-
1921
- const triggerReinspection = () => {
1922
- const mapRef = mapRegistry.getMap();
1923
- const center = mapRef?._currentCenter;
1924
-
1925
- if (center && Array.isArray(center) && center.length === 2) {
1926
- const [longitude, latitude] = center;
1927
-
1928
- if (NEXRAD_NATIVE && core?.state?.isNexrad) {
1929
- onInspect?.(getNexradInspectPayloadAtRef.current(longitude, latitude));
1930
- } else {
1931
- getValueAtPointRef.current(longitude, latitude).then((payload) => {
1932
- onInspect?.(payload);
1933
- });
1934
- }
1935
- }
1936
- };
1937
-
1938
- // Small delay to ensure data is loaded before re-inspecting
1939
- const timer = setTimeout(triggerReinspection, 100);
1940
-
1941
- return () => clearTimeout(timer);
1942
- }, [
1943
- core?.state?.nexradTimestamp,
1944
- core?.state?.isNexrad,
1945
- core?.state?.nexradSite,
1946
- core?.state?.nexradProduct,
1947
- core?.state?.nexradTilt,
1948
- core?.state?.nexradDataSource,
1949
- core?.state?.nexradStormRelative,
1950
- core?.state?.variable,
1951
- core?.state?.model,
1952
- core?.state?.forecastHour,
1953
- core?.state?.mrmsTimestamp,
1954
- core?.state?.units,
1955
- core?.state?.opacity,
1956
- inspectorEnabled,
1957
- onInspect,
1958
- ]);
1959
-
1960
- useEffect(() => {
1961
- if (!core) {
1962
- return;
1963
- }
1964
-
1965
- const handleCameraChange = (center) => {
1966
- if (core && center) {
1967
- core.setMapCenter(center);
1968
- }
1969
- };
1970
-
1971
- // Register with the global registry
1972
- mapRegistry.addCameraListener(handleCameraChange);
1973
-
1974
- // Try to get initial center
1975
- const mapRef = mapRegistry.getMap();
1976
- if (mapRef?._currentCenter) {
1977
- handleCameraChange(mapRef._currentCenter);
1978
- }
1979
-
1980
- return () => {
1981
- mapRegistry.removeCameraListener(handleCameraChange);
1982
- };
1983
- }, [core]);
1984
-
1985
- useEffect(() => {
1986
- core.initialize({ autoRefresh: false }); // <-- add the argument
1987
- return () => {
1988
- core.destroy();
1989
- };
1990
- }, [core]);
1991
-
1992
- const gridBelowId =
1993
- belowIDFromProps ??
1994
- context?.weatherBeforeLayerId ??
1995
- 'AML_-_terrain';
1996
-
1997
- return (
1998
- <>
1999
- <GridRenderLayer
2000
- ref={gridLayerRef}
2001
- opacity={renderProps.opacity}
2002
- dataRange={renderProps.dataRange}
2003
- belowID={gridBelowId}
2004
- />
2005
- {NEXRAD_NATIVE ? <NexradRadarLayer ref={nexradLayerRef} belowID={gridBelowId} /> : null}
2006
- {SATELLITE_NATIVE ? <SatelliteLayer ref={satelliteLayerRef} belowID={gridBelowId} /> : null}
2007
- <NwsAlertsOverlay core={core} watchesWarnings={watchesWarningsOptions} onNwsAlertClick={onNwsAlertClick} />
2008
- {nexradSitesMapVisible ? (
2009
- <NexradSitesMapLayer
2010
- visible
2011
- belowLayerID={gridBelowId}
2012
- onSelectSite={(siteId) => void core.setNexradSite(siteId)}
2013
- />
2014
- ) : null}
2015
- </>
2016
- );
2017
- });
2018
-
2019
- WeatherLayerManager.getAvailableVariables = (options) => {
2020
- if (!options || !options.apiKey) {
2021
- return [];
2022
- }
2023
- const core = new AguaceroCore({ apiKey: options.apiKey });
2024
- return core.getAvailableVariables('mrms');
2025
- };
1
+ import{AguaceroCore as je,DICTIONARIES as Be,formatTimelineDurationValue as ct,getUnitConversionFunction as ve,parseTimelineDurationHours as xt}from"@aguacerowx/javascript-sdk";import{fromByteArray as ze}from"base64-js";import be,{forwardRef as Tt,useCallback as we,useContext as Ct,useEffect as j,useImperativeHandle as Mt,useMemo as ut,useRef as V,useState as Je}from"react";import{NativeModules as vt,Platform as se,UIManager as mt}from"react-native";import{AguaceroContext as $t}from"./AguaceroContext";import{GridRenderLayer as _t}from"./GridRenderLayer";import Nt from"./NexradRadarLayer";import Dt from"./SatelliteLayer";import{NexradSitesMapLayer as It}from"./NexradSitesMapLayer";import{SatelliteAndroidController as Rt}from"./satellite/satelliteAndroidController";import{NexradAndroidController as Ft}from"./nexrad/nexradAndroidController";import{createNexradCloudFrontFetch as At}from"./cdnAuthenticatedFetch";import{setNexradLevel2CloudFrontFetch as dt}from"./nexrad/radarArchiveCore.bundled.js";import Pt from"./nws/NwsAlertsOverlay";import{mapRegistry as Ue}from"./MapRegistry";import{satBridgeWarn as Ee}from"./satelliteBridgeDiag";import{augmentProcessFrameOptionsForDebug as Xe,aguaceroDebugWarn as Ye,configureAguaceroRnDebug as Lt,getAguaceroAuthDiagnosticSnapshot as ft,isAguaceroRnDebugEnabled as Ke}from"./aguaceroRnDebug";import{installAguaceroCoreDebugHooks as Vt,logProcessFrameAuthMismatch as Qe}from"./aguaceroCoreDebugHooks";import{buildNativeCloudFrontFetchOptions as Ot,resolveGridRequestSiteOrigin as wt}from"./gridCdnAuth";import{auditSatelliteIntegration as Ut,installSatelliteDiagnosticListener as Et}from"./satelliteRnDebug";const Q=se.OS==="android"||se.OS==="ios",ie=se.OS==="android"||se.OS==="ios";Ee("SDK fingerprint",{platform:se.OS,SATELLITE_NATIVE:ie,note:"If you never see sat-bridge logs, the app bundle is not loading this WeatherLayerManager build."});function Kt(m,x){const g=m.mrmsStatus?.[x];if(!g||!g.length)return[];const I=xt(m.state.mrmsDurationValue);let v=[...g].map(O=>Number(O)).filter(O=>!Number.isNaN(O)).sort((O,_)=>O-_);if(I>0&&v.length>0){const O=v[v.length-1]-I*3600;v=v.filter(_=>_>=O)}return v}async function qt(m,x){if(typeof m.setMRMSDurationValue=="function"){await m.setMRMSDurationValue(x);return}const g=ct(x);if(await m.setState({mrmsDurationValue:g}),!m.state.isMRMS||!m.state.variable)return;const I=typeof m._getFilteredMrmsTimestampsForVariable=="function"?m._getFilteredMrmsTimestampsForVariable(m.state.variable):Kt(m,m.state.variable);if(!I||I.length===0)return;const v=m.state.mrmsTimestamp==null?null:Number(m.state.mrmsTimestamp);(v==null||!I.includes(v))&&await m.setState({mrmsTimestamp:I[I.length-1]})}function Gt(m,x){const g=m?.[x];if(!g)return null;const I=Object.keys(g).sort((v,O)=>O.localeCompare(v));for(const v of I){const O=g[v];if(!O)continue;const _=Object.keys(O).sort((ee,B)=>B.localeCompare(ee));if(_.length>0)return{date:v,run:_[0]}}return null}const{WeatherFrameProcessorModule:qe,InspectorModule:kt}=vt;function ue(m){const x=Object.keys(m?.satelliteTimeToFileMap||{}).map(Number).filter(g=>Number.isFinite(g)).sort((g,I)=>g-I);return x.length?`${x.length}:${x[0]}:${x[x.length-1]}`:"0"}function pt(){if(Ke())return!0;try{return typeof __DEV__<"u"&&__DEV__?!0:!!(typeof globalThis<"u"&&globalThis.__AGUACERO_WX_GRID_DEBUG__)}catch{return!1}}function z(m,x){pt()&&(x!==void 0?console.log(`[AguaceroWX][grid][${m}]`,x):console.log(`[AguaceroWX][grid][${m}]`))}function Z(m,x){x!==void 0?console.warn(`[AguaceroWX][grid][${m}]`,x):console.warn(`[AguaceroWX][grid][${m}]`)}function Ze(m,x,g,I,v,O){const _=`${m}${x}`;return Ot({url:_,apiKey:g,bundleId:I,gridRequestSiteOrigin:v,core:O})}const et=m=>{const x=new Uint8Array(1024),g=m.reduce((_,ee,B)=>B%2===0?[..._,{value:m[B],color:m[B+1]}]:_,[]);if(g.length===0)return x;const I=g[0].value,v=g[g.length-1].value,O=_=>{const ee=parseInt(_.slice(1,3),16),B=parseInt(_.slice(3,5),16),fe=parseInt(_.slice(5,7),16);return[ee,B,fe]};for(let _=0;_<256;_++){const ee=I+_/255*(v-I);let B=g[0],fe=g[g.length-1];for(let J=0;J<g.length-1;J++)if(ee>=g[J].value&&ee<=g[J+1].value){B=g[J],fe=g[J+1];break}const Ae=(ee-B.value)/(fe.value-B.value||1),Pe=O(B.color),xe=O(fe.color),$e=Pe.map((J,Ge)=>J*(1-Ae)+xe[Ge]*Ae),Te=_*4;x[Te+0]=Math.round($e[0]),x[Te+1]=Math.round($e[1]),x[Te+2]=Math.round($e[2]),x[Te+3]=255}return x};je.prototype.setMapCenter=function(m){this.emit("map:move",m)};export const WeatherLayerManager=Tt((m,x)=>{const{inspectorEnabled:g,onInspect:I,apiKey:v,customColormaps:O,initialMode:_,initialVariable:ee,initialSatelliteId:B,initialSatelliteSector:fe,initialSatelliteProduct:Ae,autoRefresh:Pe,autoRefreshInterval:xe,initialModel:$e,belowID:Te,watchesWarnings:J,onNwsAlertClick:Ge,gridRequestSiteOrigin:te,debug:Ce=!1,...Ht}=m;j(()=>{Lt({enabled:!!Ce})},[Ce]),j(()=>{if(Ce)return Ut({core:t,satelliteLayerRef:Ne}),Et()},[Ce,t]);const _e=Ct($t),t=ut(()=>new je({apiKey:v,customColormaps:O,gridRequestSiteOrigin:te,layerOptions:{mode:_,variable:ee,model:$e,..._==="satellite"?{satelliteId:B,sector:fe,satelliteProduct:Ae??ee}:{}},autoRefresh:!1}),[v,te]);j(()=>{t&&(Vt(t,{gridRequestSiteOriginProp:te??null,debugProp:!!Ce}),Ke()&&!v&&Ye("WeatherLayerManager.missingApiKey",{hint:"apiKey prop is empty \u2014 all CDN requests will fail or return 403"}),Ke()&&v&&!te&&Ye("WeatherLayerManager.missingGridOrigin",{hint:"gridRequestSiteOrigin is not set \u2014 CloudFront often returns 403 without Origin/Referer on React Native",snapshot:ft(t)}))},[t,Ce,te,v]);const[gt,tt]=Je(()=>({alertInteractionEnabled:!0,...J??{}}));j(()=>{tt(r=>({...r,...J??{}}))},[J]),j(()=>{st(!!(t.state.isNexrad&&t.state.nexradShowSitesPicker!==!1))},[t]);const p=V(null),rt=V(null),X=V(null),Ne=V(null),re=V(null),De=V(null),me=V(null),at=we(()=>ie?(re.current||(re.current=new Rt(t,Ne)),re.current):null,[t]),nt=we(()=>!Q||!t?null:(X.current||(X.current=new Ft(t,rt,{interpolateNexradColormap:t.state?.shaderSmoothingEnabled!==!1})),X.current),[t]),ht=we(r=>{const{colormap:a,baseUnit:e}=t._getColormapForVariable(r.variable),o=t._getTargetUnit(e,r.units);return{...r,colormap:t._convertColormapUnits(a,e,o),colormapBaseUnit:o}},[t]),Ie=we(r=>{if(!Q||!r?.isNexrad)return;const a=nt();a&&(ae.current=!0,a.preloadAllAvailable(r),a.sync(r))},[nt]);j(()=>{if(!Q||!t)return;const r=wt(te,t),a=At(()=>({apiKey:t.apiKey,bundleId:t.bundleId,gridRequestSiteOrigin:r}));return dt(a),()=>dt(null)},[t,te]),j(()=>{if(!ie)return;let r=[],a=[];try{r=Object.keys(mt.getViewManagerConfig?.("GridRenderLayer")?.Commands??{})}catch{r=["error"]}try{a=Object.keys(mt.getViewManagerConfig?.("SatelliteLayer")?.Commands??{})}catch{a=["error"]}Ee("mount UIManager command registration",{GridRenderLayerCommands:r,SatelliteLayerCommands:a,satelliteMissing:a.length===0})},[]);const W=V(new Map),pe=V(null),ge=V(null),ke=V([0,1]),ae=V(!1),ne=V(!1),Le=V(0),He=V(null),l=V(null),[Ve,Me]=Je({opacity:1,dataRange:[0,1]}),[St,st]=Je(!1);Mt(x,()=>({play:()=>{t.play()},pause:()=>{t.pause()},togglePlay:()=>{t.togglePlay()},step:r=>{t.step(r)},setPlaybackSpeed:r=>{r>0&&(t.playbackSpeed=r,t.isPlaying&&(t.pause(),t.play()))},setOpacity:r=>t.setOpacity(r),setUnits:r=>t.setUnits(r),switchMode:r=>t.switchMode(r),getAvailableVariables:r=>t.getAvailableVariables(r),getVariableDisplayName:r=>t.getVariableDisplayName(r),setRun:r=>t.setState({run:r.split(":")[1]}),setState:r=>t.setState(r),setMRMSTimestamp:r=>t.setMRMSTimestamp(r),setMRMSDurationValue:r=>qt(t,r),setNexradSite:r=>t.setNexradSite(r),setNexradProduct:r=>t.setNexradProduct(r),setNexradTilt:r=>t.setNexradTilt(r),setNexradStormRelative:r=>t.setNexradStormRelative(r),setNexradTimestamp:r=>t.setNexradTimestamp(r),setNexradDurationValue:r=>typeof t.setNexradDurationValue=="function"?t.setNexradDurationValue(r):t.setState({nexradDurationValue:ct(r)}),setSatelliteTimestamp:r=>t.setSatelliteTimestamp(r),setSatelliteDurationValue:r=>t.setSatelliteDurationValue(r),setSatelliteSelection:r=>t.setSatelliteSelection(r),setShaderSmoothing:async r=>{await t.setShaderSmoothing(r),p.current&&p.current.setSmoothing(r)},setSmoothing:r=>{p.current&&p.current.setSmoothing(r)},setAutoRefresh:(r,a)=>{if(me.current&&(clearInterval(me.current),me.current=null),r){const e=(a||xe||30)*1e3;Se(),me.current=setInterval(Se,e)}},refreshData:()=>{Se()},configureWatchesWarnings:r=>{tt(a=>({...a,...r}))}}),[t,xe,Se]);const it=async r=>{if(r.isNexrad||r.isSatellite){ne.current=!1,z("preloadSkip",{reason:"nexrad_or_satellite"});return}if(ne.current){z("preloadSkip",{reason:"hasPreloadedRef_gate",isMRMS:r.isMRMS,variable:r.variable});return}const{isMRMS:a,model:e,date:o,run:F,variable:s,units:w,availableHours:N,availableTimestamps:c,forecastHour:i,mrmsTimestamp:u}=r;if(a&&(u==null||!c||c.length===0)){ne.current=!1,Z("preloadAbort",{reason:"mrms_missing_frame_or_timestamps",mrmsTimestamp:u,timestampCount:c?.length??0,variable:s});return}if(!a&&(i==null||!N||N.length===0)){ne.current=!1,Z("preloadAbort",{reason:"model_missing_hour_or_hours",forecastHour:i,hourCount:N?.length??0,model:e,variable:s});return}ne.current=!0;const A=Le.current,y=()=>A!==Le.current;z("preloadStart",{isMRMS:a,model:e,date:o,run:F,variable:s,mrmsTimestamp:a?u:void 0,forecastHour:a?void 0:i,baseGridUrl:t?.baseGridUrl,hasApiKey:!!t?.apiKey,bundleId:t?.bundleId??null,gridRequestSiteOrigin:te||null});let M=i;if(!a&&s==="ptypeRefl"&&e==="hrrr"&&i===0){const d=N.filter(G=>G!==0);M=d.length>0?d[0]:0}if(!pe.current||!ge.current){const d=a?"mrms":e,{corners:G,gridDef:n}=t._getGridCornersAndDef(d);p.current.updateGeometry(G,n),pe.current={model:d,variable:s},z("geometryColormapInit",{gridModel:d,variable:s,nx:n?.grid_params?.nx,ny:n?.grid_params?.ny});const{colormap:R,baseUnit:h}=t._getColormapForVariable(s),k=t._getTargetUnit(h,w),P=t._convertColormapUnits(R,h,k);let f;s==="ptypeRefl"||s==="ptypeRate"?f=a?[5,380]:[5,380]:f=[P[0],P[P.length-2]];const C=et(P),b=ze(C);p.current.updateColormapTexture(b),ge.current={key:`${s}-${w}`},ke.current=f,Me({opacity:r.opacity,dataRange:f}),ae.current=!0}let U=N;!a&&s==="ptypeRefl"&&e==="hrrr"&&N&&N.length>0&&(U=N.filter(d=>d!==0));const E=a?c:U;if(!E||E.length===0){ne.current=!1,Z("preloadAbort",{reason:"no_frames_after_filter",isMRMS:a,variable:s});return}const K=a?u:M;if(K==null){ne.current=!1,Z("preloadAbort",{reason:"currentFrame_null",isMRMS:a,variable:s});return}const Y=[...E].reverse().filter(d=>d!==K),{corners:D,gridDef:q}=t._getGridCornersAndDef(a?"mrms":e),{nx:de,ny:ye}=q.grid_params,$=a?`mrms-${K}-${s}`:`${e}-${o}-${F}-${K}-${s}`;if(!W.current.has($)){let d;if(a){const n=new Date(K*1e3),R=n.getUTCFullYear(),h=(n.getUTCMonth()+1).toString().padStart(2,"0"),k=n.getUTCDate().toString().padStart(2,"0");d=`/grids/mrms/${R}${h}${k}/${K}/0/${s}/0`}else d=`/grids/${e}/${o}/${F}/${K}/${s}/0`;const G=Xe(Ze(t.baseGridUrl,d,t.apiKey,t.bundleId,te,t),t);Qe(t,G,{phase:"preloadCurrent",currentCacheKey:$});try{z("processFrameRequest",{currentCacheKey:$,resourcePath:d,frame:K});const n=await qe.processFrame(G);if(y()){z("preloadCurrentFrameStale",{currentCacheKey:$,generation:A});return}if(!n||!n.filePath){ne.current=!1,Z("preloadAbort",{reason:"processFrame_empty_result",currentCacheKey:$,resultKeys:n?Object.keys(n):[]});return}const{baseUnit:R}=t._getColormapForVariable(s),h=t._getTargetUnit(R,w),k=(Be?.fld?.[s]||{}).defaultUnit||R;let P=n.scale,f=n.offset,C=P,b=f;if(k!==R){const T=ve(k,R);if(T){if(n.scaleType==="sqrt"){const L=f*f,H=(f+P)*(f+P),oe=T(L),ce=T(H);b=Math.sqrt(Math.abs(oe))*Math.sign(oe),C=Math.sqrt(Math.abs(ce))*Math.sign(ce)-b}else b=T(f),C=T(f+P)-b;P=C,f=b}}if(R!==h){const T=ve(R,h);if(T)if(n.scaleType==="sqrt"){const L=f*f,H=(f+P)*(f+P),oe=T(L),ce=T(H);b=Math.sqrt(Math.abs(oe))*Math.sign(oe),C=Math.sqrt(Math.abs(ce))*Math.sign(ce)-b}else b=T(f),C=T(f+P)-b}const S={filePath:n.filePath,nx:de,ny:ye,scale:C,offset:b,missing:n.missing,corners:D,gridDef:q,scaleType:n.scaleType,originalScale:n.scale,originalOffset:n.offset};W.current.set($,S),p.current.updateDataTextureFromFile(S.filePath,S.nx,S.ny,S.scale,S.offset,S.missing,S.scaleType),De.current={nx:S.nx,ny:S.ny,scale:S.scale,offset:S.offset,missing:S.missing,gridDef:S.gridDef,variable:s,units:w,scaleType:S.scaleType},z("preloadCurrentFrameOk",{currentCacheKey:$,nx:S.nx,ny:S.ny,scale:S.scale,offset:S.offset,filePathSuffix:String(S.filePath).split("/").pop()})}catch(n){if(n&&(n.code==="E_CANCELLED"||n?.userInfo?.code==="E_CANCELLED"||typeof n?.message=="string"&&n.message.includes("superseded")))z("preloadProcessFrameCancelled",{currentCacheKey:$});else{ne.current=!1;const R={currentCacheKey:$,code:n?.code,message:n?.message,userInfo:n?.userInfo};Z("preloadProcessFrameError",R),Ke()&&Ye("preloadProcessFrameError",{...R,auth:ft(t),is403:n?.code==="HTTP_ERROR"&&typeof n?.message=="string"&&n.message.includes("403")})}}}Y.forEach(d=>{const G=a?`mrms-${d}-${s}`:`${e}-${o}-${F}-${d}-${s}`;if(W.current.has(G))return;let n;if(a){const h=new Date(d*1e3),k=h.getUTCFullYear(),P=(h.getUTCMonth()+1).toString().padStart(2,"0"),f=h.getUTCDate().toString().padStart(2,"0");n=`/grids/mrms/${k}${P}${f}/${d}/0/${s}/0`}else n=`/grids/${e}/${o}/${F}/${d}/${s}/0`;const R=Xe(Ze(t.baseGridUrl,n,t.apiKey,t.bundleId,te,t),t);Qe(t,R,{phase:"preloadBackground",cacheKey:G}),qe.processFrame(R).then(h=>{if(y()||!h||!h.filePath)return;const{baseUnit:k}=t._getColormapForVariable(s),P=t._getTargetUnit(k,w),f=(Be?.fld?.[s]||{}).defaultUnit||k;let C=h.scale,b=h.offset,S=C,T=b;if(f!==k){const H=ve(f,k);if(H){if(h.scaleType==="sqrt"){const oe=b*b,ce=(b+C)*(b+C),Re=H(oe),Fe=H(ce);T=Math.sqrt(Math.abs(Re))*Math.sign(Re),S=Math.sqrt(Math.abs(Fe))*Math.sign(Fe)-T}else T=H(b),S=H(b+C)-T;C=S,b=T}}if(k!==P){const H=ve(k,P);if(H)if(h.scaleType==="sqrt"){const oe=b*b,ce=(b+C)*(b+C),Re=H(oe),Fe=H(ce);T=Math.sqrt(Math.abs(Re))*Math.sign(Re),S=Math.sqrt(Math.abs(Fe))*Math.sign(Fe)-T}else T=H(b),S=H(b+C)-T}const L={filePath:h.filePath,nx:de,ny:ye,scale:S,offset:T,missing:h.missing,corners:D,gridDef:q,scaleType:h.scaleType,originalScale:h.scale,originalOffset:h.offset};if(W.current.set(G,L),se.OS==="ios"&&p.current.primeGpuCache){const H={[G]:{filePath:L.filePath,nx:L.nx,ny:L.ny,scale:L.scale,offset:L.offset,missing:L.missing,scaleType:L.scaleType||"linear",originalScale:L.originalScale,originalOffset:L.originalOffset}};p.current.primeGpuCache(H)}}).catch(h=>{h&&(h.code==="E_CANCELLED"||h?.userInfo?.code==="E_CANCELLED"||typeof h?.message=="string"&&h.message.includes("superseded"))||Z("backgroundFrameError",{cacheKey:G,code:h?.code,message:h?.message})})})};j(()=>{if(Pe){const r=(xe||30)*1e3;Se(),me.current=setInterval(Se,r)}return()=>{me.current&&(clearInterval(me.current),me.current=null)}},[Pe,xe,Se]);const yt=r=>{const{model:a,date:e,run:o,forecastHour:F,variable:s,units:w,isMRMS:N,mrmsTimestamp:c}=r,i=N?`mrms-${c}-${s}`:`${a}-${e}-${o}-${F}-${s}`;if(se.OS==="ios"&&p.current.setActiveFrame){const y=W.current.get(i);return y?De.current={nx:y.nx,ny:y.ny,scale:y.scale,offset:y.offset,missing:y.missing,gridDef:y.gridDef,variable:s,units:w,scaleType:y.scaleType}:z("gpuIOS_setActiveFrame_no_row",{cacheKey:i,cacheSize:W.current.size}),p.current.setActiveFrame(i),z("gpuIOS_setActiveFrame",{cacheKey:i,hadCachedRow:!!y}),!0}const u=W.current.get(i);if(!u)return Z("updateGPUNoCache",{cacheKey:i,cacheSize:W.current.size,sampleKeys:Array.from(W.current.keys()).slice(0,5)}),!1;if(!p.current)return Z("updateGPUNoGridLayerRef",{cacheKey:i}),!1;(!pe.current||pe.current.model!==(N?"mrms":a)||pe.current.variable!==s)&&(p.current.updateGeometry(u.corners,u.gridDef),pe.current={model:N?"mrms":a,variable:s});const A=`${s}-${w}`;if(!ge.current||ge.current.key!==A){const{colormap:y,baseUnit:M}=t._getColormapForVariable(s),U=t._getTargetUnit(M,w),E=t._convertColormapUnits(y,M,U);let K;s==="ptypeRefl"||s==="ptypeRate"?N?K=[5,380]:K=[5,380]:K=[E[0],E[E.length-2]];const Y=et(E),D=ze(Y);p.current.updateColormapTexture(D),ge.current={key:A},ke.current=K,Me(q=>({...q,dataRange:K}))}if(u.filePath)p.current.updateDataTextureFromFile(u.filePath,u.nx,u.ny,u.scale,u.offset,u.missing,u.scaleType),De.current={nx:u.nx,ny:u.ny,scale:u.scale,offset:u.offset,missing:u.missing,gridDef:u.gridDef,variable:s,units:w,scaleType:u.scaleType},z("gpuUploadFromFile",{cacheKey:i,nx:u.nx,ny:u.ny,filePathSuffix:String(u.filePath).split("/").pop()});else return Z("updateGPUNoFilePath",{cacheKey:i}),!1;return p.current&&p.current.updateDataParameters&&p.current.updateDataParameters(u.scale,u.offset,u.missing),!0},he=V(null),le=V(null);j(()=>{t&&m.customColormaps&&(t.customColormaps=m.customColormaps,ae.current&&t._emitStateChange())},[t,m.customColormaps]);const lt=async(r,a)=>{if(!t)return null;if(t.state?.isNexrad){const e=X.current;return e?e.getInspectPayload(r,a,ht(t.state)):null}if(!De.current)return null;try{const e=t._getGridIndexFromLngLat(r,a);if(!e)return null;const{i:o,j:F}=e,s=await kt.getValueAtGridIndex(o,F);if(s===null)return null;const{colormap:w,baseUnit:N}=t._getColormapForVariable(t.state.variable),c=t._getTargetUnit(N,t.state.units),i=t._convertColormapUnits(w,N,c)[0];return s<i||s<i||!isFinite(s)?null:{value:s,unit:c,variable:{code:t.state.variable,name:t.getVariableDisplayName(t.state.variable)},lngLat:{lng:r,lat:a}}}catch{return null}},We=V(lt);We.current=lt;const Se=ut(()=>async()=>{if(!t)return;const r=t.state,{isMRMS:a,isSatellite:e,isNexrad:o,model:F,variable:s,date:w,run:N}=r;if(e){const c=[...t._computeSatelliteTimeline().unixTimes||[]].map(M=>Number(M)).filter(M=>!Number.isNaN(M)).sort((M,U)=>M-U),i=c.length?c[c.length-1]:null,u=r.satelliteTimestamp==null?null:Number(r.satelliteTimestamp);await t.fetchSatelliteListing(!0),t._emitStateChange();const A=[...t._computeSatelliteTimeline().unixTimes||[]].map(M=>Number(M)).filter(M=>!Number.isNaN(M)).sort((M,U)=>M-U),y=A.length?A[A.length-1]:null;i!=null&&u!=null&&u===i&&y!=null&&y>i?await t.setSatelliteTimestamp(y):u!=null&&y!=null&&A.length&&!A.includes(u)&&await t.setSatelliteTimestamp(y);return}if(o&&r.nexradSite){const c=t._nexradTimesCacheKey(),i=c?t.nexradTimesByStation[c]?.unixTimes:[],u=t._getFilteredNexradTimestampsForVariable(i||[]),A=u.length?u[u.length-1]:null,y=r.nexradTimestamp==null?null:Number(r.nexradTimestamp);await t.refreshNexradTimes(),t._emitStateChange();const M=c?t.nexradTimesByStation[c]?.unixTimes:[],U=t._getFilteredNexradTimestampsForVariable(M||[]),E=U.length?U[U.length-1]:null;A!=null&&y!=null&&y===A&&E!=null&&E>A?await t.setNexradTimestamp(E):y!=null&&E!=null&&U.length&&!U.includes(y)&&await t.setNexradTimestamp(E);return}if(a){const c=new Set(t.mrmsStatus?.[s]||[]),i=await t.fetchMRMSStatus(!0),u=i?.[s]||[];if(u.length===0)return;const A=u.filter(y=>!c.has(y));if(A.length>0){t.mrmsStatus=i,t._emitStateChange();const{corners:y,gridDef:M}=t._getGridCornersAndDef("mrms"),{nx:U,ny:E}=M.grid_params;A.forEach(Y=>{const D=`mrms-${Y}-${s}`;if(W.current.has(D))return;const q=new Date(Y*1e3),de=q.getUTCFullYear(),ye=(q.getUTCMonth()+1).toString().padStart(2,"0"),$=q.getUTCDate().toString().padStart(2,"0"),d=`/grids/mrms/${de}${ye}${$}/${Y}/0/${s}/0`,G=Xe(Ze(t.baseGridUrl,d,t.apiKey,t.bundleId,te,t),t);Qe(t,G,{phase:"mrmsRefresh",cacheKey:D}),qe.processFrame(G).then(n=>{if(!n||!n.filePath)return;const R={filePath:n.filePath,nx:U,ny:E,scale:n.scale,offset:n.offset,missing:n.missing,corners:y,gridDef:M,scaleType:n.scaleType,originalScale:n.scale,originalOffset:n.offset};W.current.set(D,R),se.OS==="ios"&&p.current?.primeGpuCache&&p.current.primeGpuCache({[D]:R})}).catch(n=>{n&&(n.code==="E_CANCELLED"||n?.userInfo?.code==="E_CANCELLED"||typeof n?.message=="string"&&n.message.includes("superseded"))||Z("mrmsRefreshFrameError",{cacheKey:D,code:n?.code,message:n?.message})})});const K=new Set(u);c.forEach(Y=>{if(!K.has(Y)){const D=`mrms-${Y}-${s}`;W.current.delete(D)}})}}else{const c=t.modelStatus,i=await t.fetchModelStatus(!0);if(JSON.stringify(c)!==JSON.stringify(i)&&t._emitStateChange(),!Gt(i,F))return}},[t]);j(()=>{if(!t)return;const r=e=>{l.current||(l.current=t.state),(!l.current||e.variable!==l.current.variable)&&p.current?.setVariable&&p.current.setVariable(e.variable);const o=e.isNexrad?`nx-${e.nexradSite}-${e.nexradProduct}-${e.nexradDataSource}-${e.nexradTimestamp}-${e.nexradTilt}-${e.opacity}`:e.isSatellite?`sat-${e.satelliteInstrumentId}-${e.satelliteSectorLabel}-${e.satelliteChannel}-${e.satelliteTimestamp}-${e.variable}-${e.units}-${e.opacity}-dur:${e.satelliteDurationValue??""}-tl:${ue(e)}`:`${e.model}-${e.variable}-${e.date}-${e.run}-${e.forecastHour}-${e.units}-${e.mrmsTimestamp}`,F=e.isNexrad?e.nexradTimestamp===l.current?.nexradTimestamp&&e.nexradSite===l.current?.nexradSite&&e.nexradProduct===l.current?.nexradProduct&&Number(e.nexradTilt)===Number(l.current?.nexradTilt):e.isSatellite?e.satelliteTimestamp===l.current?.satelliteTimestamp&&e.satelliteInstrumentId===l.current?.satelliteInstrumentId&&e.satelliteSectorLabel===l.current?.satelliteSectorLabel&&e.satelliteChannel===l.current?.satelliteChannel&&e.satelliteDurationValue===l.current?.satelliteDurationValue&&ue(e)===ue(l.current):e.forecastHour===l.current?.forecastHour&&e.mrmsTimestamp===l.current?.mrmsTimestamp,s=e.isSatellite?e.satelliteInstrumentId===l.current?.satelliteInstrumentId&&e.satelliteSectorLabel===l.current?.satelliteSectorLabel&&e.satelliteChannel===l.current?.satelliteChannel:e.model===l.current?.model&&e.isMRMS===l.current?.isMRMS,w=ae.current&&e.opacity!==Ve.opacity&&e.variable===l.current?.variable&&F&&s&&e.units===l.current?.units,N=ae.current&&e.isPlaying!==l.current?.isPlaying&&e.variable===l.current?.variable&&F&&s&&e.units===l.current?.units&&e.opacity===l.current?.opacity;if(!w&&!N&&He.current===o){l.current=e;return}if(pt()&&se.OS==="ios"&&e.isSatellite&&l.current?.isSatellite){const i=l.current;e.isSatellite&&i?.isSatellite&&(e.satelliteDurationValue!==i.satelliteDurationValue||ue(e)!==ue(i))&&z("iosSatelliteTimelineWindowChanged",{duration:{from:i.satelliteDurationValue,to:e.satelliteDurationValue},timelineSig:{from:ue(i),to:ue(e)},willSyncNative:!0})}if(!w&&!N&&(He.current=o),w){Me(i=>({...i,opacity:e.opacity})),ie&&e.isSatellite&&Ne.current?.updateSatelliteStyle?.(JSON.stringify({visible:e.visible!==!1,opacity:e.opacity??1,fillSmoothing:0})),Q&&e.isNexrad&&Ie(e),l.current=e;return}if(N){l.current=e;return}if(ae.current&&e.model===l.current.model&&e.isMRMS===l.current.isMRMS&&e.variable===l.current.variable&&e.date===l.current.date&&e.run===l.current.run&&(e.isNexrad?e.nexradTimestamp===l.current.nexradTimestamp&&e.nexradSite===l.current.nexradSite&&e.nexradProduct===l.current.nexradProduct:e.forecastHour===l.current.forecastHour&&e.mrmsTimestamp===l.current.mrmsTimestamp)&&e.units!==l.current.units){if(Q&&e.isNexrad){Me(q=>({...q,opacity:e.opacity})),Ie(e),l.current=e;return}const{variable:i,units:u,isMRMS:A,mrmsTimestamp:y,model:M,date:U,run:E,forecastHour:K}=e,Y=A?`mrms-${y}-${i}`:`${M}-${U}-${E}-${K}-${i}`,D=W.current.get(Y);if(D&&D.originalScale!==void 0&&D.originalOffset!==void 0){const{baseUnit:q}=t._getColormapForVariable(i),de=t._getTargetUnit(q,u),ye=(Be?.fld?.[i]||{}).defaultUnit||q;let $=D.originalScale,d=D.originalOffset;if(ye!==q){const f=ve(ye,q);if(f)if(D.scaleType==="sqrt"){const C=d*d,b=(d+$)*(d+$),S=f(C),T=f(b),L=Math.sqrt(Math.abs(S))*Math.sign(S);$=Math.sqrt(Math.abs(T))*Math.sign(T)-L,d=L}else{const C=f(d);$=f(d+$)-C,d=C}}if(q!==de){const f=ve(q,de);if(f)if(D.scaleType==="sqrt"){const C=d*d,b=(d+$)*(d+$),S=f(C),T=f(b),L=Math.sqrt(Math.abs(S))*Math.sign(S);$=Math.sqrt(Math.abs(T))*Math.sign(T)-L,d=L}else{const C=f(d);$=f(d+$)-C,d=C}}const{colormap:G}=t._getColormapForVariable(i),n=t._convertColormapUnits(G,q,de);let R=i==="ptypeRefl"||i==="ptypeRate"?[5,380]:[n[0],n[n.length-2]];const h=et(n),k=ze(h);if(p.current.updateColormapTexture(k),ge.current={key:`${i}-${u}`},ke.current=R,Me(f=>({...f,dataRange:R,opacity:e.opacity})),p.current&&p.current.updateDataParameters){const f=D.scaleType==="sqrt"?1:0;p.current.updateDataParameters($,d,D.missing,f)}const P=A?`mrms-${y}-${i}`:`${M}-${U}-${E}-${K}-${i}`;W.current.set(P,{...D,scale:$,offset:d})}l.current=e;return}const c=l.current;if(!ae.current||e.isNexrad!==c?.isNexrad||e.isSatellite!==c?.isSatellite||e.isNexrad&&c?.isNexrad&&(e.nexradSite!==c.nexradSite||e.nexradDataSource!==c.nexradDataSource||e.nexradProduct!==c.nexradProduct)||e.isSatellite&&c?.isSatellite&&(e.satelliteInstrumentId!==c.satelliteInstrumentId||e.satelliteSectorLabel!==c.satelliteSectorLabel||e.satelliteChannel!==c.satelliteChannel)||!e.isNexrad&&!e.isSatellite&&(e.model!==c?.model||e.isMRMS!==c?.isMRMS||e.variable!==c?.variable||e.date!==c?.date||e.run!==c?.run)){z("needsFullLoad",{variable:e.variable,isMRMS:e.isMRMS,model:e.model,isInitial:!ae.current,prevVariable:c?.variable,prevModel:c?.model});const i=Q&&(e.isNexrad!==c?.isNexrad||e.isNexrad&&c?.isNexrad&&(e.nexradSite!==c.nexradSite||e.nexradDataSource!==c.nexradDataSource||e.nexradProduct!==c.nexradProduct)),u=ie&&(e.isSatellite!==c?.isSatellite||e.isSatellite&&c?.isSatellite&&(e.satelliteInstrumentId!==c.satelliteInstrumentId||e.satelliteSectorLabel!==c.satelliteSectorLabel||e.satelliteChannel!==c.satelliteChannel));if(i&&(X.current?.destroy(),X.current=null),u&&(re.current?.destroy(),re.current=null),p.current&&(p.current.setVariable(e.variable),p.current.clear(),se.OS==="ios"&&p.current.clearGpuCache&&p.current.clearGpuCache()),ne.current=!1,Le.current+=1,W.current.clear(),pe.current=null,ge.current=null,De.current=null,qe.cancelAllFrames(),z("cancelAllFrames",{reason:"needsFullLoad",variable:e.variable,generation:Le.current}),!e.variable){l.current=e;return}!e.isNexrad&&!e.isSatellite?it(e):Q&&e.isNexrad?Ie(e):ie&&e.isSatellite&&(ae.current=!0)}else if(!e.isNexrad&&(e.forecastHour!==l.current.forecastHour||e.isMRMS&&e.mrmsTimestamp!==l.current.mrmsTimestamp)){const i=yt(e);i||(Z("timelineGpuUpdateMiss",{isMRMS:e.isMRMS,variable:e.variable,mrmsTimestamp:e.mrmsTimestamp,forecastHour:e.forecastHour}),ne.current=!1,it(e)),i&&e.opacity!==Ve.opacity&&Me(u=>({...u,opacity:e.opacity}))}if(Q&&e.isNexrad?Ie(e):Q&&!e.isNexrad&&(X.current?.destroy(),X.current=null),ie&&e.isSatellite){Ee("handleStateChange \u2192 satellite",{satelliteInstrumentId:e.satelliteInstrumentId??null,satelliteSectorLabel:e.satelliteSectorLabel??null,satelliteChannel:e.satelliteChannel??null,timelineKeys:Object.keys(e.satelliteTimeToFileMap||{}).length,refHasSync:!!Ne.current?.syncSatellite});const i=at();i?(ae.current=!0,i.sync(e)):Ee("handleStateChange satellite: ensureSatelliteController returned null",{}),!e.satelliteInstrumentId&&typeof __DEV__<"u"&&__DEV__&&console.warn("[AguaceroWX][satellite] isSatellite is true but satelliteInstrumentId is missing \u2014 native sync still runs; timeline may be empty until core sets instrument.")}else ie&&!e.isSatellite&&(re.current?.destroy(),re.current=null);l.current=e};he.current=r;const a=e=>{if(st(!!(e.isNexrad&&e.nexradShowSitesPicker!==!1)),e.isPlaying){he.current&&he.current(e),m.onStateChange?.(e),le.current&&(clearTimeout(le.current),le.current=null);return}m.onStateChange?.(e),le.current&&clearTimeout(le.current);const o=l.current,F=o&&(o.isNexrad?e.nexradTimestamp===o.nexradTimestamp&&e.nexradSite===o.nexradSite&&e.nexradProduct===o.nexradProduct&&Number(e.nexradTilt)===Number(o.nexradTilt):o.isSatellite?e.satelliteTimestamp===o.satelliteTimestamp&&e.satelliteInstrumentId===o.satelliteInstrumentId&&e.satelliteSectorLabel===o.satelliteSectorLabel&&e.satelliteChannel===o.satelliteChannel&&e.satelliteDurationValue===o.satelliteDurationValue&&ue(e)===ue(o):e.forecastHour===o.forecastHour&&e.mrmsTimestamp===o.mrmsTimestamp),s=o&&(e.isSatellite?e.satelliteInstrumentId===o.satelliteInstrumentId&&e.satelliteSectorLabel===o.satelliteSectorLabel&&e.satelliteChannel===o.satelliteChannel:e.model===o.model&&e.isMRMS===o.isMRMS),w=o&&F&&s&&e.opacity!==o.opacity&&e.variable===o.variable&&e.units===o.units,N=o&&F&&s&&e.isPlaying!==o.isPlaying&&e.variable===o.variable&&e.units===o.units&&e.opacity===o.opacity;if(w||N||!l.current){he.current&&he.current(e);return}le.current=setTimeout(()=>{he.current&&he.current(e),le.current=null},16)};return t.on("state:change",a),()=>{t.off("state:change",a),le.current&&clearTimeout(le.current),Q&&(X.current?.destroy(),X.current=null),ie&&(re.current?.destroy(),re.current=null)}},[t,at,Ie]),j(()=>()=>{W.current.clear(),ae.current=!1,He.current=null,p.current&&se.OS==="ios"&&p.current.clearGpuCache(),Q&&(X.current?.destroy(),X.current=null),ie&&(re.current?.destroy(),re.current=null)},[]);const ot=V(0),bt=50;j(()=>{if(!t||!g)return;const r=a=>{if(!a||!Array.isArray(a)||a.length!==2)return;const e=Date.now();if(e-ot.current<bt)return;ot.current=e;const[o,F]=a;We.current(o,F).then(s=>{I?.(s)})};if(t.on("map:move",r),_e&&_e.getCenter){const a=_e.getCenter();a&&r(a)}return()=>{t.off("map:move",r)}},[g,I,t,_e]),j(()=>{if(!t||!g)return;const r=setTimeout(()=>{const a=Ue.getMap()?._currentCenter;if(a&&Array.isArray(a)&&a.length===2){const[e,o]=a;We.current(e,o).then(F=>{I?.(F)})}},100);return()=>clearTimeout(r)},[t?.state?.nexradTimestamp,t?.state?.isNexrad,t?.state?.nexradSite,t?.state?.nexradProduct,t?.state?.nexradTilt,t?.state?.nexradDataSource,t?.state?.nexradStormRelative,t?.state?.variable,t?.state?.model,t?.state?.forecastHour,t?.state?.mrmsTimestamp,t?.state?.units,t?.state?.opacity,g,I]),j(()=>{if(!t)return;const r=e=>{t&&e&&t.setMapCenter(e)};Ue.addCameraListener(r);const a=Ue.getMap();return a?._currentCenter&&r(a._currentCenter),()=>{Ue.removeCameraListener(r)}},[t]),j(()=>(t.initialize({autoRefresh:!1}),()=>{t.destroy()}),[t]);const Oe=Te??_e?.weatherBeforeLayerId??"AML_-_terrain";return be.createElement(be.Fragment,null,be.createElement(_t,{ref:p,opacity:Ve.opacity,dataRange:Ve.dataRange,belowID:Oe}),Q?be.createElement(Nt,{ref:rt,belowID:Oe}):null,ie?be.createElement(Dt,{ref:Ne,belowID:Oe}):null,be.createElement(Pt,{core:t,watchesWarnings:gt,onNwsAlertClick:Ge}),St?be.createElement(It,{visible:!0,belowLayerID:Oe,onSelectSite:r=>void t.setNexradSite(r)}):null)});WeatherLayerManager.getAvailableVariables=m=>!m||!m.apiKey?[]:new je({apiKey:m.apiKey}).getAvailableVariables("mrms");