@aguacerowx/react-native 0.0.53 → 0.0.54

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