@aguacerowx/react-native 0.0.24 → 0.0.26

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.
@@ -251,10 +251,18 @@ public class GridRenderLayer implements CustomLayerHost {
251
251
  floatMatrix[i] = matrix.get(i).floatValue();
252
252
  }
253
253
 
254
- floatMatrix[0] *= scale;
255
- floatMatrix[1] *= scale;
256
- floatMatrix[4] *= scale;
257
- floatMatrix[5] *= scale;
254
+ floatMatrix[0] *= scale // X scale
255
+ floatMatrix[1] *= scale // X rotation
256
+ floatMatrix[2] *= scale // X Z-component
257
+ floatMatrix[3] *= scale // X translation
258
+ floatMatrix[4] *= scale // Y rotation
259
+ floatMatrix[5] *= scale // Y scale
260
+ floatMatrix[6] *= scale // Y Z-component
261
+ floatMatrix[7] *= scale // Y translation
262
+ floatMatrix[8] *= scale // Z X-component
263
+ floatMatrix[9] *= scale // Z Y-component
264
+ floatMatrix[10] *= scale // Z scale
265
+ floatMatrix[11] *= scale // Z translation
258
266
 
259
267
  GLES20.glUniformMatrix4fv(uMatrix, 1, false, floatMatrix, 0);
260
268
  GLES20.glUniform1f(uOpacity, opacity);
@@ -954,15 +954,23 @@ internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFor
954
954
  // Apply zoom scale transformation
955
955
  let zoom = parameters.zoom
956
956
  let scale = Float(pow(2.0, zoom))
957
-
957
+
958
958
  let matrixArray = parameters.projectionMatrix
959
959
  var floatArray = matrixArray.map { $0.floatValue }
960
-
961
- floatArray[0] *= scale // X scale
962
- floatArray[1] *= scale // X rotation
963
- floatArray[4] *= scale // Y rotation
964
- floatArray[5] *= scale
965
-
960
+
961
+ floatArray[0] *= scale // X scale
962
+ floatArray[1] *= scale // X rotation
963
+ floatArray[2] *= scale // X Z-component
964
+ floatArray[3] *= scale // X translation
965
+ floatArray[4] *= scale // Y rotation
966
+ floatArray[5] *= scale // Y scale
967
+ floatArray[6] *= scale // Y Z-component
968
+ floatArray[7] *= scale // Y translation
969
+ floatArray[8] *= scale // Z X-component
970
+ floatArray[9] *= scale // Z Y-component
971
+ floatArray[10] *= scale // Z scale
972
+ floatArray[11] *= scale // Z translation
973
+
966
974
  let mvp = matrix_float4x4(
967
975
  SIMD4<Float>(floatArray[0], floatArray[1], floatArray[2], floatArray[3]),
968
976
  SIMD4<Float>(floatArray[4], floatArray[5], floatArray[6], floatArray[7]),
@@ -993,6 +1001,25 @@ internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFor
993
1001
  encoder.endEncoding()
994
1002
  }
995
1003
 
1004
+ deinit {
1005
+ print("🔴 [GridRenderLayer] deinit called for layer: \(id)")
1006
+
1007
+ // Clean up all resources
1008
+ frameCache.removeAll()
1009
+ vertexBuffer = nil
1010
+ indexBuffer = nil
1011
+ dataTexture = nil
1012
+ colormapTexture = nil
1013
+ device = nil
1014
+ commandQueue = nil
1015
+ pipelineState = nil
1016
+ dataSamplerState = nil
1017
+ colormapSamplerState = nil
1018
+
1019
+ // Break the reference cycle
1020
+ hostWrapper = nil
1021
+ }
1022
+
996
1023
  internal func internalRenderingWillEnd() {
997
1024
  print("🟢 [GridRenderLayer] renderingWillEnd called")
998
1025
  vertexBuffer = nil
@@ -1001,3 +1028,16 @@ internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFor
1001
1028
  colormapTexture = nil
1002
1029
  }
1003
1030
  }
1031
+
1032
+ extension GridRenderLayer {
1033
+ // Override to prevent KVC crashes during cleanup
1034
+ open override func value(forUndefinedKey key: String) -> Any? {
1035
+ print("⚠️ [GridRenderLayer] Attempted to access undefined key: \(key)")
1036
+ return nil
1037
+ }
1038
+
1039
+ open override func setValue(_ value: Any?, forUndefinedKey key: String) {
1040
+ print("⚠️ [GridRenderLayer] Attempted to set undefined key: \(key)")
1041
+ // Silently ignore instead of crashing
1042
+ }
1043
+ }
@@ -239,4 +239,19 @@
239
239
  [super removeFromSuperview];
240
240
  }
241
241
 
242
+ - (void)dealloc {
243
+ RCTLogInfo(@"🔴 [GridRenderLayerView] dealloc called");
244
+
245
+ // Remove notification observer
246
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
247
+
248
+ // Clear the layer instance
249
+ if (_layerInstance) {
250
+ [_layerInstance clear];
251
+ _layerInstance = nil;
252
+ }
253
+
254
+ _mapView = nil;
255
+ }
256
+
242
257
  @end
@@ -251,10 +251,18 @@ public class GridRenderLayer implements CustomLayerHost {
251
251
  floatMatrix[i] = matrix.get(i).floatValue();
252
252
  }
253
253
 
254
- floatMatrix[0] *= scale;
255
- floatMatrix[1] *= scale;
256
- floatMatrix[4] *= scale;
257
- floatMatrix[5] *= scale;
254
+ floatMatrix[0] *= scale // X scale
255
+ floatMatrix[1] *= scale // X rotation
256
+ floatMatrix[2] *= scale // X Z-component
257
+ floatMatrix[3] *= scale // X translation
258
+ floatMatrix[4] *= scale // Y rotation
259
+ floatMatrix[5] *= scale // Y scale
260
+ floatMatrix[6] *= scale // Y Z-component
261
+ floatMatrix[7] *= scale // Y translation
262
+ floatMatrix[8] *= scale // Z X-component
263
+ floatMatrix[9] *= scale // Z Y-component
264
+ floatMatrix[10] *= scale // Z scale
265
+ floatMatrix[11] *= scale // Z translation
258
266
 
259
267
  GLES20.glUniformMatrix4fv(uMatrix, 1, false, floatMatrix, 0);
260
268
  GLES20.glUniform1f(uOpacity, opacity);
@@ -954,15 +954,23 @@ internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFor
954
954
  // Apply zoom scale transformation
955
955
  let zoom = parameters.zoom
956
956
  let scale = Float(pow(2.0, zoom))
957
-
957
+
958
958
  let matrixArray = parameters.projectionMatrix
959
959
  var floatArray = matrixArray.map { $0.floatValue }
960
-
961
- floatArray[0] *= scale // X scale
962
- floatArray[1] *= scale // X rotation
963
- floatArray[4] *= scale // Y rotation
964
- floatArray[5] *= scale
965
-
960
+
961
+ floatArray[0] *= scale // X scale
962
+ floatArray[1] *= scale // X rotation
963
+ floatArray[2] *= scale // X Z-component
964
+ floatArray[3] *= scale // X translation
965
+ floatArray[4] *= scale // Y rotation
966
+ floatArray[5] *= scale // Y scale
967
+ floatArray[6] *= scale // Y Z-component
968
+ floatArray[7] *= scale // Y translation
969
+ floatArray[8] *= scale // Z X-component
970
+ floatArray[9] *= scale // Z Y-component
971
+ floatArray[10] *= scale // Z scale
972
+ floatArray[11] *= scale // Z translation
973
+
966
974
  let mvp = matrix_float4x4(
967
975
  SIMD4<Float>(floatArray[0], floatArray[1], floatArray[2], floatArray[3]),
968
976
  SIMD4<Float>(floatArray[4], floatArray[5], floatArray[6], floatArray[7]),
@@ -993,6 +1001,25 @@ internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFor
993
1001
  encoder.endEncoding()
994
1002
  }
995
1003
 
1004
+ deinit {
1005
+ print("🔴 [GridRenderLayer] deinit called for layer: \(id)")
1006
+
1007
+ // Clean up all resources
1008
+ frameCache.removeAll()
1009
+ vertexBuffer = nil
1010
+ indexBuffer = nil
1011
+ dataTexture = nil
1012
+ colormapTexture = nil
1013
+ device = nil
1014
+ commandQueue = nil
1015
+ pipelineState = nil
1016
+ dataSamplerState = nil
1017
+ colormapSamplerState = nil
1018
+
1019
+ // Break the reference cycle
1020
+ hostWrapper = nil
1021
+ }
1022
+
996
1023
  internal func internalRenderingWillEnd() {
997
1024
  print("🟢 [GridRenderLayer] renderingWillEnd called")
998
1025
  vertexBuffer = nil
@@ -1001,3 +1028,16 @@ internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFor
1001
1028
  colormapTexture = nil
1002
1029
  }
1003
1030
  }
1031
+
1032
+ extension GridRenderLayer {
1033
+ // Override to prevent KVC crashes during cleanup
1034
+ open override func value(forUndefinedKey key: String) -> Any? {
1035
+ print("⚠️ [GridRenderLayer] Attempted to access undefined key: \(key)")
1036
+ return nil
1037
+ }
1038
+
1039
+ open override func setValue(_ value: Any?, forUndefinedKey key: String) {
1040
+ print("⚠️ [GridRenderLayer] Attempted to set undefined key: \(key)")
1041
+ // Silently ignore instead of crashing
1042
+ }
1043
+ }
@@ -239,4 +239,19 @@
239
239
  [super removeFromSuperview];
240
240
  }
241
241
 
242
+ - (void)dealloc {
243
+ RCTLogInfo(@"🔴 [GridRenderLayerView] dealloc called");
244
+
245
+ // Remove notification observer
246
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
247
+
248
+ // Clear the layer instance
249
+ if (_layerInstance) {
250
+ [_layerInstance clear];
251
+ _layerInstance = nil;
252
+ }
253
+
254
+ _mapView = nil;
255
+ }
256
+
242
257
  @end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aguacerowx/react-native",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Native weather rendering for React Native",
5
5
  "license": "ISC",
6
6
  "author": "Michael Barletta",
@@ -14,12 +14,21 @@ var _MapRegistry = require("./MapRegistry");
14
14
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
15
  // packages/react-native/src/WeatherLayerManager.js
16
16
 
17
- console.log("--- Aguacero Native Module Initial State Check ---");
18
- console.log("All NativeModules:", Object.keys(_reactNative.NativeModules));
19
- console.log("WeatherFrameProcessorModule available?:", !!_reactNative.NativeModules.WeatherFrameProcessorModule);
20
- console.log("InspectorModule available?:", !!_reactNative.NativeModules.InspectorModule);
21
- console.log("GridRenderLayerManager available?:", !!_reactNative.NativeModules.GridRenderLayerManager);
22
- console.log("-------------------------------------------------");
17
+ function findLatestModelRun(modelsData, modelName) {
18
+ const model = modelsData?.[modelName];
19
+ if (!model) return null;
20
+ const availableDates = Object.keys(model).sort((a, b) => b.localeCompare(a));
21
+ for (const date of availableDates) {
22
+ const runs = model[date];
23
+ if (!runs) continue;
24
+ const availableRuns = Object.keys(runs).sort((a, b) => b.localeCompare(a));
25
+ if (availableRuns.length > 0) return {
26
+ date: date,
27
+ run: availableRuns[0]
28
+ };
29
+ }
30
+ return null;
31
+ }
23
32
  const {
24
33
  WeatherFrameProcessorModule,
25
34
  InspectorModule
@@ -71,7 +80,6 @@ _javascriptSdk.AguaceroCore.prototype.setMapCenter = function (center) {
71
80
  this.emit('map:move', center);
72
81
  };
73
82
  const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
74
- // EDIT: Destructure the new props
75
83
  const {
76
84
  inspectorEnabled,
77
85
  onInspect,
@@ -79,6 +87,8 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
79
87
  customColormaps,
80
88
  initialMode,
81
89
  initialVariable,
90
+ autoRefresh,
91
+ autoRefreshInterval,
82
92
  ...restProps
83
93
  } = props;
84
94
  const context = (0, _react.useContext)(_AguaceroContext.AguaceroContext);
@@ -95,13 +105,11 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
95
105
  }), [apiKey]);
96
106
  const gridLayerRef = (0, _react.useRef)(null);
97
107
  const currentGridDataRef = (0, _react.useRef)(null);
108
+ const autoRefreshIntervalId = (0, _react.useRef)(null);
98
109
 
99
110
  // Cache for preloaded grid data - stores the processed data ready for GPU upload
100
111
  const preloadedDataCache = (0, _react.useRef)(new Map());
101
112
 
102
- // Track what we're currently preloading to avoid duplicates
103
- const preloadingSet = (0, _react.useRef)(new Set());
104
-
105
113
  // Store geometry and colormap that don't change with forecast hour
106
114
  const cachedGeometry = (0, _react.useRef)(null);
107
115
  const cachedColormap = (0, _react.useRef)(null);
@@ -119,6 +127,18 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
119
127
  dataRange: [0, 1]
120
128
  });
121
129
  (0, _react.useImperativeHandle)(ref, () => {
130
+ const setAutoRefresh = (enabled, intervalSeconds) => {
131
+ if (autoRefreshIntervalId.current) {
132
+ clearInterval(autoRefreshIntervalId.current);
133
+ autoRefreshIntervalId.current = null;
134
+ }
135
+ if (enabled) {
136
+ const effectiveInterval = (intervalSeconds || autoRefreshInterval || 30) * 1000;
137
+ // Run once immediately, then start the interval
138
+ _checkForUpdates();
139
+ autoRefreshIntervalId.current = setInterval(_checkForUpdates, effectiveInterval);
140
+ }
141
+ };
122
142
  return {
123
143
  play: () => {
124
144
  core.play();
@@ -147,9 +167,10 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
147
167
  if (gridLayerRef.current) {
148
168
  gridLayerRef.current.setSmoothing(enabled);
149
169
  }
150
- }
170
+ },
171
+ setAutoRefresh
151
172
  };
152
- }, [core]);
173
+ }, [core, autoRefreshInterval, _checkForUpdates]);
153
174
  const preloadAllFramesToDisk = state => {
154
175
  if (hasPreloadedRef.current) {
155
176
  console.log('✅ [Preload] Gating preload; already initiated for this dataset.');
@@ -352,6 +373,22 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
352
373
  });
353
374
  });
354
375
  };
376
+ (0, _react.useEffect)(() => {
377
+ // This effect manages the auto-refresh based on props
378
+ if (autoRefresh) {
379
+ const interval = (autoRefreshInterval || 30) * 1000;
380
+ _checkForUpdates(); // Run immediately on enable
381
+ autoRefreshIntervalId.current = setInterval(_checkForUpdates, interval);
382
+ }
383
+
384
+ // Cleanup function: this runs when the component unmounts or props change
385
+ return () => {
386
+ if (autoRefreshIntervalId.current) {
387
+ clearInterval(autoRefreshIntervalId.current);
388
+ autoRefreshIntervalId.current = null;
389
+ }
390
+ };
391
+ }, [autoRefresh, autoRefreshInterval, _checkForUpdates]);
355
392
  const updateGPUWithCachedData = state => {
356
393
  const {
357
394
  model,
@@ -554,12 +591,112 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
554
591
  return null;
555
592
  }
556
593
  };
594
+ const _checkForUpdates = (0, _react.useMemo)(() => async () => {
595
+ if (!core) return;
596
+ const {
597
+ isMRMS,
598
+ model: currentModel,
599
+ variable: currentVariable,
600
+ date,
601
+ run
602
+ } = core.state;
603
+ if (isMRMS) {
604
+ // --- MRMS LOGIC WITH PRE-LOADING ---
605
+ const oldTimestamps = new Set(core.mrmsStatus?.[currentVariable] || []);
606
+ const mrmsStatus = await core.fetchMRMSStatus(true);
607
+ const newTimestamps = mrmsStatus?.[currentVariable] || [];
608
+ if (newTimestamps.length === 0) return;
609
+ const newTimestampsToPreload = newTimestamps.filter(ts => !oldTimestamps.has(ts));
610
+ if (newTimestampsToPreload.length > 0) {
611
+ core.mrmsStatus = mrmsStatus;
612
+ core._emitStateChange(); // Update UI slider without changing selection
613
+
614
+ console.log(`[Auto-Refresh] Preloading ${newTimestampsToPreload.length} new MRMS frames.`);
615
+ const {
616
+ corners,
617
+ gridDef
618
+ } = core._getGridCornersAndDef('mrms');
619
+ const {
620
+ nx,
621
+ ny
622
+ } = gridDef.grid_params;
623
+ newTimestampsToPreload.forEach(frame => {
624
+ const cacheKey = `mrms-${frame}-${currentVariable}`;
625
+ if (preloadedDataCache.current.has(cacheKey)) return;
626
+ const frameDate = new Date(frame * 1000);
627
+ const y = frameDate.getUTCFullYear(),
628
+ m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0'),
629
+ d = frameDate.getUTCDate().toString().padStart(2, '0');
630
+
631
+ // --- THIS IS THE FIX ---
632
+ // Changed 'variable' to 'currentVariable'
633
+ const resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${currentVariable}/0`;
634
+ // --- END FIX ---
635
+
636
+ const url = `${core.baseGridUrl}${resourcePath}?apiKey=${core.apiKey}`;
637
+ WeatherFrameProcessorModule.processFrame({
638
+ url,
639
+ apiKey: core.apiKey,
640
+ bundleId: core.bundleId
641
+ }).then(result => {
642
+ if (!result || !result.filePath) return;
643
+ const frameData = {
644
+ filePath: result.filePath,
645
+ nx,
646
+ ny,
647
+ scale: result.scale,
648
+ offset: result.offset,
649
+ missing: result.missing,
650
+ corners,
651
+ gridDef,
652
+ scaleType: result.scaleType,
653
+ originalScale: result.scale,
654
+ originalOffset: result.offset
655
+ };
656
+ preloadedDataCache.current.set(cacheKey, frameData);
657
+ if (_reactNative.Platform.OS === 'ios' && gridLayerRef.current?.primeGpuCache) {
658
+ gridLayerRef.current.primeGpuCache({
659
+ [cacheKey]: frameData
660
+ });
661
+ }
662
+ }).catch(error => console.warn(`[Auto-Refresh] Failed to preload frame ${frame}:`, error));
663
+ });
664
+ const newTimestampsSet = new Set(newTimestamps);
665
+ oldTimestamps.forEach(oldTs => {
666
+ if (!newTimestampsSet.has(oldTs)) {
667
+ const cacheKey = `mrms-${oldTs}-${currentVariable}`;
668
+ preloadedDataCache.current.delete(cacheKey);
669
+ }
670
+ });
671
+ }
672
+ } else {
673
+ // --- MODEL LOGIC ---
674
+ const modelStatus = await core.fetchModelStatus(true);
675
+ const latestRun = findLatestModelRun(modelStatus, currentModel);
676
+ if (!latestRun) return;
677
+ const currentRunKey = `${date}:${run}`;
678
+ const latestRunKey = `${latestRun.date}:${latestRun.run}`;
679
+ if (latestRunKey > currentRunKey) {
680
+ // This preserves the current forecastHour while changing the run
681
+ await core.setState({
682
+ date: latestRun.date,
683
+ run: latestRun.run
684
+ });
685
+ }
686
+ }
687
+ }, [core]);
557
688
  (0, _react.useEffect)(() => {
558
689
  if (!core) {
559
690
  console.warn('⚠️ [useEffect] Core is not available yet');
560
691
  return;
561
692
  }
693
+
694
+ // --- REPLACE THIS ENTIRE FUNCTION ---
562
695
  const handleStateChange = async newState => {
696
+ // --- FIX 1: Notify the parent component IMMEDIATELY ---
697
+ // This ensures the parent UI (like sliders) always gets the latest state,
698
+ // including updated lists of available hours or timestamps.
699
+ props.onStateChange?.(newState);
563
700
  if (!previousStateRef.current) {
564
701
  previousStateRef.current = core.state;
565
702
  }
@@ -569,18 +706,19 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
569
706
  }
570
707
  const stateKey = `${newState.model}-${newState.variable}-${newState.date}-${newState.run}-${newState.forecastHour}-${newState.units}-${newState.mrmsTimestamp}`;
571
708
  const isOpacityOnlyChange = hasInitialLoad.current && newState.opacity !== renderProps.opacity && newState.variable === previousStateRef.current?.variable && newState.forecastHour === previousStateRef.current?.forecastHour && newState.mrmsTimestamp === previousStateRef.current?.mrmsTimestamp && newState.model === previousStateRef.current?.model && newState.units === previousStateRef.current?.units;
572
-
573
- // ADD: Check for play state only change
574
709
  const isPlayStateOnlyChange = hasInitialLoad.current && newState.isPlaying !== previousStateRef.current?.isPlaying && newState.variable === previousStateRef.current?.variable && newState.forecastHour === previousStateRef.current?.forecastHour && newState.mrmsTimestamp === previousStateRef.current?.mrmsTimestamp && newState.model === previousStateRef.current?.model && newState.units === previousStateRef.current?.units && newState.opacity === previousStateRef.current?.opacity;
710
+
711
+ // This gate now correctly prevents only redundant *internal* processing.
712
+ // The parent has already been notified.
575
713
  if (!isOpacityOnlyChange && !isPlayStateOnlyChange && lastProcessedState.current === stateKey) {
714
+ // --- FIX 2: Update the previous state ref before returning ---
715
+ // This is critical to prevent stale comparisons on the next state change event.
716
+ previousStateRef.current = newState;
576
717
  return;
577
718
  }
578
719
  if (!isOpacityOnlyChange && !isPlayStateOnlyChange) {
579
720
  lastProcessedState.current = stateKey;
580
721
  }
581
-
582
- // MOVE THIS TO THE TOP - Always notify the parent component of state changes
583
- props.onStateChange?.(newState);
584
722
  if (isOpacityOnlyChange) {
585
723
  setRenderProps(prev => ({
586
724
  ...prev,
@@ -589,14 +727,10 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
589
727
  previousStateRef.current = newState;
590
728
  return;
591
729
  }
592
-
593
- // ADD: Handle play state only change
594
730
  if (isPlayStateOnlyChange) {
595
731
  previousStateRef.current = newState;
596
732
  return;
597
733
  }
598
-
599
- // Check if only units changed
600
734
  const isUnitsOnlyChange = hasInitialLoad.current && newState.model === previousStateRef.current.model && newState.isMRMS === previousStateRef.current.isMRMS && newState.variable === previousStateRef.current.variable && newState.date === previousStateRef.current.date && newState.run === previousStateRef.current.run && newState.forecastHour === previousStateRef.current.forecastHour && newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp && newState.units !== previousStateRef.current.units;
601
735
  if (isUnitsOnlyChange) {
602
736
  const {
@@ -620,18 +754,12 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
620
754
  let dataOffset = cachedData.originalOffset;
621
755
  if (baseUnit !== toUnit) {
622
756
  const conversionFunc = (0, _javascriptSdk.getUnitConversionFunction)(baseUnit, toUnit);
623
- console.log('🔧 [Unit Conversion] Conversion function exists:', !!conversionFunc);
624
757
  if (conversionFunc) {
625
758
  if (cachedData.scaleType === 'sqrt') {
626
- // Calculate what the physical values would be at offset and offset+scale
627
759
  const physicalAtOffset = dataOffset * dataOffset;
628
760
  const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
629
-
630
- // Convert these physical values to the new unit
631
761
  const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
632
762
  const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
633
-
634
- // Take sqrt to get back to intermediate values
635
763
  const newOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
636
764
  const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
637
765
  dataScale = newOffsetPlusScale - newOffset;
@@ -644,22 +772,11 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
644
772
  }
645
773
  }
646
774
  }
647
-
648
- // Update the colormap AND data range
649
775
  const {
650
776
  colormap
651
777
  } = core._getColormapForVariable(variable);
652
778
  const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
653
- let dataRange;
654
- if (variable === 'ptypeRefl' || variable === 'ptypeRate') {
655
- if (isMRMS) {
656
- dataRange = [5, 380];
657
- } else {
658
- dataRange = [5, 380];
659
- }
660
- } else {
661
- dataRange = [finalColormap[0], finalColormap[finalColormap.length - 2]];
662
- }
779
+ let dataRange = variable === 'ptypeRefl' || variable === 'ptypeRate' ? [5, 380] : [finalColormap[0], finalColormap[finalColormap.length - 2]];
663
780
  const colormapBytes = _generateColormapBytes(finalColormap);
664
781
  const colormapAsBase64 = (0, _base64Js.fromByteArray)(colormapBytes);
665
782
  gridLayerRef.current.updateColormapTexture(colormapAsBase64);
@@ -673,15 +790,8 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
673
790
  opacity: newState.opacity
674
791
  }));
675
792
  if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
676
- const scaleTypeValue = cachedData.scaleType === 'sqrt' ? 1 : 0; // Convert to number
677
-
678
- console.log('🔄 [Unit Conversion] Calling updateDataParameters with:', {
679
- scale: dataScale,
680
- offset: dataOffset,
681
- missing: cachedData.missing,
682
- scaleType: scaleTypeValue
683
- });
684
- gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing, scaleTypeValue); // Pass scaleType
793
+ const scaleTypeValue = cachedData.scaleType === 'sqrt' ? 1 : 0;
794
+ gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing, scaleTypeValue);
685
795
  }
686
796
  const newCacheKey = isMRMS ? `mrms-${mrmsTimestamp}-${variable}` : `${model}-${date}-${run}-${forecastHour}-${variable}`;
687
797
  preloadedDataCache.current.set(newCacheKey, {
@@ -689,8 +799,6 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
689
799
  scale: dataScale,
690
800
  offset: dataOffset
691
801
  });
692
- } else {
693
- console.warn('⚠️ [Unit Conversion] No cached data found for key:', oldCacheKey);
694
802
  }
695
803
  previousStateRef.current = newState;
696
804
  return;
@@ -700,7 +808,6 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
700
808
  if (gridLayerRef.current) {
701
809
  gridLayerRef.current.setVariable(newState.variable);
702
810
  gridLayerRef.current.clear();
703
- // --- OPTIMIZATION: Clear the native GPU cache on iOS ---
704
811
  if (_reactNative.Platform.OS === 'ios' && gridLayerRef.current.clearGpuCache) {
705
812
  gridLayerRef.current.clearGpuCache();
706
813
  }
@@ -709,8 +816,6 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
709
816
  preloadedDataCache.current.clear();
710
817
  cachedGeometry.current = null;
711
818
  cachedColormap.current = null;
712
-
713
- // ADD THIS: Clear the inspector cache too
714
819
  currentGridDataRef.current = null;
715
820
  WeatherFrameProcessorModule.cancelAllFrames();
716
821
  if (!newState.variable) {
@@ -720,24 +825,17 @@ const WeatherLayerManager = exports.WeatherLayerManager = /*#__PURE__*/(0, _reac
720
825
  preloadAllFramesToDisk(newState);
721
826
  } else if (newState.forecastHour !== previousStateRef.current.forecastHour || newState.isMRMS && newState.mrmsTimestamp !== previousStateRef.current.mrmsTimestamp) {
722
827
  const success = updateGPUWithCachedData(newState);
723
- if (!success) {
724
- // CHANGED: Don't error, just log and wait for preload
725
- const timeKey = newState.isMRMS ? `timestamp ${newState.mrmsTimestamp}` : `hour +${newState.forecastHour}`;
726
- console.log(`⏳ [handleStateChange] Frame ${timeKey} not ready yet, waiting for preload...`);
727
- // Don't clear the layer - keep showing the previous frame until new one is ready
728
- // The preload will eventually complete and trigger a re-render
729
- } else {
730
- // Only update inspector cache when we successfully loaded new data
731
- if (newState.opacity !== renderProps.opacity) {
732
- setRenderProps(prev => ({
733
- ...prev,
734
- opacity: newState.opacity
735
- }));
736
- }
828
+ if (success && newState.opacity !== renderProps.opacity) {
829
+ setRenderProps(prev => ({
830
+ ...prev,
831
+ opacity: newState.opacity
832
+ }));
737
833
  }
738
834
  }
739
835
  previousStateRef.current = newState;
740
836
  };
837
+ // --- END REPLACEMENT ---
838
+
741
839
  handleStateChangeRef.current = handleStateChange;
742
840
  const stableHandler = newState => {
743
841
  const isOpacityOnlyChange = previousStateRef.current && newState.opacity !== previousStateRef.current.opacity && newState.variable === previousStateRef.current.variable && newState.forecastHour === previousStateRef.current.forecastHour && newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp && newState.model === previousStateRef.current.model && newState.units === previousStateRef.current.units && newState.date === previousStateRef.current.date && newState.run === previousStateRef.current.run;