@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.
@@ -8,13 +8,18 @@ import { fromByteArray } from 'base64-js';
8
8
  import { Platform, NativeModules } from 'react-native';
9
9
  import { mapRegistry } from './MapRegistry';
10
10
 
11
- console.log("--- Aguacero Native Module Initial State Check ---");
12
- console.log("All NativeModules:", Object.keys(NativeModules));
13
- console.log("WeatherFrameProcessorModule available?:", !!NativeModules.WeatherFrameProcessorModule);
14
- console.log("InspectorModule available?:", !!NativeModules.InspectorModule);
15
- console.log("GridRenderLayerManager available?:", !!NativeModules.GridRenderLayerManager);
16
- console.log("-------------------------------------------------");
17
-
11
+ function findLatestModelRun(modelsData, modelName) {
12
+ const model = modelsData?.[modelName];
13
+ if (!model) return null;
14
+ const availableDates = Object.keys(model).sort((a, b) => b.localeCompare(a));
15
+ for (const date of availableDates) {
16
+ const runs = model[date];
17
+ if (!runs) continue;
18
+ const availableRuns = Object.keys(runs).sort((a, b) => b.localeCompare(a));
19
+ if (availableRuns.length > 0) return { date: date, run: availableRuns[0] };
20
+ }
21
+ return null;
22
+ }
18
23
  const { WeatherFrameProcessorModule, InspectorModule } = NativeModules;
19
24
 
20
25
  /**
@@ -67,8 +72,17 @@ AguaceroCore.prototype.setMapCenter = function(center) {
67
72
  };
68
73
 
69
74
  export const WeatherLayerManager = forwardRef((props, ref) => {
70
- // EDIT: Destructure the new props
71
- const { inspectorEnabled, onInspect, apiKey, customColormaps, initialMode, initialVariable, ...restProps } = props;
75
+ const {
76
+ inspectorEnabled,
77
+ onInspect,
78
+ apiKey,
79
+ customColormaps,
80
+ initialMode,
81
+ initialVariable,
82
+ autoRefresh,
83
+ autoRefreshInterval,
84
+ ...restProps
85
+ } = props;
72
86
  const context = useContext(AguaceroContext);
73
87
 
74
88
  // Create the core here instead of getting it from context
@@ -84,13 +98,11 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
84
98
 
85
99
  const gridLayerRef = useRef(null);
86
100
  const currentGridDataRef = useRef(null);
101
+ const autoRefreshIntervalId = useRef(null);
87
102
 
88
103
  // Cache for preloaded grid data - stores the processed data ready for GPU upload
89
104
  const preloadedDataCache = useRef(new Map());
90
105
 
91
- // Track what we're currently preloading to avoid duplicates
92
- const preloadingSet = useRef(new Set());
93
-
94
106
  // Store geometry and colormap that don't change with forecast hour
95
107
  const cachedGeometry = useRef(null);
96
108
  const cachedColormap = useRef(null);
@@ -110,6 +122,18 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
110
122
  });
111
123
 
112
124
  useImperativeHandle(ref, () => {
125
+ const setAutoRefresh = (enabled, intervalSeconds) => {
126
+ if (autoRefreshIntervalId.current) {
127
+ clearInterval(autoRefreshIntervalId.current);
128
+ autoRefreshIntervalId.current = null;
129
+ }
130
+ if (enabled) {
131
+ const effectiveInterval = (intervalSeconds || autoRefreshInterval || 30) * 1000;
132
+ // Run once immediately, then start the interval
133
+ _checkForUpdates();
134
+ autoRefreshIntervalId.current = setInterval(_checkForUpdates, effectiveInterval);
135
+ }
136
+ };
113
137
  return {
114
138
  play: () => {
115
139
  core.play();
@@ -137,8 +161,9 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
137
161
  gridLayerRef.current.setSmoothing(enabled);
138
162
  }
139
163
  },
164
+ setAutoRefresh,
140
165
  };
141
- }, [core]);
166
+ }, [core, autoRefreshInterval, _checkForUpdates]);
142
167
 
143
168
  const preloadAllFramesToDisk = (state) => {
144
169
 
@@ -337,6 +362,23 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
337
362
  });
338
363
  };
339
364
 
365
+ useEffect(() => {
366
+ // This effect manages the auto-refresh based on props
367
+ if (autoRefresh) {
368
+ const interval = (autoRefreshInterval || 30) * 1000;
369
+ _checkForUpdates(); // Run immediately on enable
370
+ autoRefreshIntervalId.current = setInterval(_checkForUpdates, interval);
371
+ }
372
+
373
+ // Cleanup function: this runs when the component unmounts or props change
374
+ return () => {
375
+ if (autoRefreshIntervalId.current) {
376
+ clearInterval(autoRefreshIntervalId.current);
377
+ autoRefreshIntervalId.current = null;
378
+ }
379
+ };
380
+ }, [autoRefresh, autoRefreshInterval, _checkForUpdates]);
381
+
340
382
  const updateGPUWithCachedData = (state) => {
341
383
  const { model, date, run, forecastHour, variable, units, isMRMS, mrmsTimestamp } = state;
342
384
 
@@ -547,13 +589,92 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
547
589
  }
548
590
  };
549
591
 
592
+ const _checkForUpdates = useMemo(() => async () => {
593
+ if (!core) return;
594
+ const { isMRMS, model: currentModel, variable: currentVariable, date, run } = core.state;
595
+
596
+ if (isMRMS) {
597
+ // --- MRMS LOGIC WITH PRE-LOADING ---
598
+ const oldTimestamps = new Set(core.mrmsStatus?.[currentVariable] || []);
599
+ const mrmsStatus = await core.fetchMRMSStatus(true);
600
+ const newTimestamps = mrmsStatus?.[currentVariable] || [];
601
+ if (newTimestamps.length === 0) return;
602
+
603
+ const newTimestampsToPreload = newTimestamps.filter(ts => !oldTimestamps.has(ts));
604
+
605
+ if (newTimestampsToPreload.length > 0) {
606
+ core.mrmsStatus = mrmsStatus;
607
+ core._emitStateChange(); // Update UI slider without changing selection
608
+
609
+ console.log(`[Auto-Refresh] Preloading ${newTimestampsToPreload.length} new MRMS frames.`);
610
+ const { corners, gridDef } = core._getGridCornersAndDef('mrms');
611
+ const { nx, ny } = gridDef.grid_params;
612
+
613
+ newTimestampsToPreload.forEach(frame => {
614
+ const cacheKey = `mrms-${frame}-${currentVariable}`;
615
+ if (preloadedDataCache.current.has(cacheKey)) return;
616
+
617
+ const frameDate = new Date(frame * 1000);
618
+ const y = frameDate.getUTCFullYear(), m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0'), d = frameDate.getUTCDate().toString().padStart(2, '0');
619
+
620
+ // --- THIS IS THE FIX ---
621
+ // Changed 'variable' to 'currentVariable'
622
+ const resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${currentVariable}/0`;
623
+ // --- END FIX ---
624
+
625
+ const url = `${core.baseGridUrl}${resourcePath}?apiKey=${core.apiKey}`;
626
+
627
+ WeatherFrameProcessorModule.processFrame({ url, apiKey: core.apiKey, bundleId: core.bundleId })
628
+ .then(result => {
629
+ if (!result || !result.filePath) return;
630
+ 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 };
631
+ preloadedDataCache.current.set(cacheKey, frameData);
632
+ if (Platform.OS === 'ios' && gridLayerRef.current?.primeGpuCache) {
633
+ gridLayerRef.current.primeGpuCache({ [cacheKey]: frameData });
634
+ }
635
+ }).catch(error => console.warn(`[Auto-Refresh] Failed to preload frame ${frame}:`, error));
636
+ });
637
+
638
+ const newTimestampsSet = new Set(newTimestamps);
639
+ oldTimestamps.forEach(oldTs => {
640
+ if (!newTimestampsSet.has(oldTs)) {
641
+ const cacheKey = `mrms-${oldTs}-${currentVariable}`;
642
+ preloadedDataCache.current.delete(cacheKey);
643
+ }
644
+ });
645
+ }
646
+ } else {
647
+ // --- MODEL LOGIC ---
648
+ const modelStatus = await core.fetchModelStatus(true);
649
+ const latestRun = findLatestModelRun(modelStatus, currentModel);
650
+ if (!latestRun) return;
651
+
652
+ const currentRunKey = `${date}:${run}`;
653
+ const latestRunKey = `${latestRun.date}:${latestRun.run}`;
654
+
655
+ if (latestRunKey > currentRunKey) {
656
+ // This preserves the current forecastHour while changing the run
657
+ await core.setState({
658
+ date: latestRun.date,
659
+ run: latestRun.run,
660
+ });
661
+ }
662
+ }
663
+ }, [core]);
664
+
550
665
  useEffect(() => {
551
666
  if (!core) {
552
667
  console.warn('⚠️ [useEffect] Core is not available yet');
553
668
  return;
554
669
  }
555
670
 
671
+ // --- REPLACE THIS ENTIRE FUNCTION ---
556
672
  const handleStateChange = async (newState) => {
673
+ // --- FIX 1: Notify the parent component IMMEDIATELY ---
674
+ // This ensures the parent UI (like sliders) always gets the latest state,
675
+ // including updated lists of available hours or timestamps.
676
+ props.onStateChange?.(newState);
677
+
557
678
  if (!previousStateRef.current) {
558
679
  previousStateRef.current = core.state;
559
680
  }
@@ -575,7 +696,6 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
575
696
  newState.model === previousStateRef.current?.model &&
576
697
  newState.units === previousStateRef.current?.units;
577
698
 
578
- // ADD: Check for play state only change
579
699
  const isPlayStateOnlyChange =
580
700
  hasInitialLoad.current &&
581
701
  newState.isPlaying !== previousStateRef.current?.isPlaying &&
@@ -586,7 +706,12 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
586
706
  newState.units === previousStateRef.current?.units &&
587
707
  newState.opacity === previousStateRef.current?.opacity;
588
708
 
709
+ // This gate now correctly prevents only redundant *internal* processing.
710
+ // The parent has already been notified.
589
711
  if (!isOpacityOnlyChange && !isPlayStateOnlyChange && lastProcessedState.current === stateKey) {
712
+ // --- FIX 2: Update the previous state ref before returning ---
713
+ // This is critical to prevent stale comparisons on the next state change event.
714
+ previousStateRef.current = newState;
590
715
  return;
591
716
  }
592
717
 
@@ -594,22 +719,17 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
594
719
  lastProcessedState.current = stateKey;
595
720
  }
596
721
 
597
- // MOVE THIS TO THE TOP - Always notify the parent component of state changes
598
- props.onStateChange?.(newState);
599
-
600
722
  if (isOpacityOnlyChange) {
601
723
  setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
602
724
  previousStateRef.current = newState;
603
725
  return;
604
726
  }
605
727
 
606
- // ADD: Handle play state only change
607
728
  if (isPlayStateOnlyChange) {
608
729
  previousStateRef.current = newState;
609
730
  return;
610
731
  }
611
732
 
612
- // Check if only units changed
613
733
  const isUnitsOnlyChange =
614
734
  hasInitialLoad.current &&
615
735
  newState.model === previousStateRef.current.model &&
@@ -632,88 +752,48 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
632
752
  if (cachedData && cachedData.originalScale !== undefined && cachedData.originalOffset !== undefined) {
633
753
  const { baseUnit } = core._getColormapForVariable(variable);
634
754
  const toUnit = core._getTargetUnit(baseUnit, units);
635
-
636
755
  let dataScale = cachedData.originalScale;
637
756
  let dataOffset = cachedData.originalOffset;
638
757
 
639
758
  if (baseUnit !== toUnit) {
640
759
  const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
641
- console.log('🔧 [Unit Conversion] Conversion function exists:', !!conversionFunc);
642
-
643
760
  if (conversionFunc) {
644
761
  if (cachedData.scaleType === 'sqrt') {
645
- // Calculate what the physical values would be at offset and offset+scale
646
762
  const physicalAtOffset = dataOffset * dataOffset;
647
763
  const physicalAtOffsetPlusScale = (dataOffset + dataScale) * (dataOffset + dataScale);
648
-
649
- // Convert these physical values to the new unit
650
764
  const convertedPhysicalAtOffset = conversionFunc(physicalAtOffset);
651
765
  const convertedPhysicalAtOffsetPlusScale = conversionFunc(physicalAtOffsetPlusScale);
652
-
653
- // Take sqrt to get back to intermediate values
654
766
  const newOffset = Math.sqrt(Math.abs(convertedPhysicalAtOffset)) * Math.sign(convertedPhysicalAtOffset);
655
767
  const newOffsetPlusScale = Math.sqrt(Math.abs(convertedPhysicalAtOffsetPlusScale)) * Math.sign(convertedPhysicalAtOffsetPlusScale);
656
-
657
768
  dataScale = newOffsetPlusScale - newOffset;
658
769
  dataOffset = newOffset;
659
770
  } else {
660
771
  const convertedOffset = conversionFunc(dataOffset);
661
772
  const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
662
-
663
773
  dataScale = convertedOffsetPlusScale - convertedOffset;
664
774
  dataOffset = convertedOffset;
665
775
  }
666
776
  }
667
777
  }
668
778
 
669
- // Update the colormap AND data range
670
779
  const { colormap } = core._getColormapForVariable(variable);
671
780
  const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
672
- let dataRange;
673
- if (variable === 'ptypeRefl' || variable === 'ptypeRate') {
674
- if (isMRMS) {
675
- dataRange = [5, 380];
676
- } else {
677
- dataRange = [5, 380];
678
- }
679
- } else {
680
- dataRange = [finalColormap[0], finalColormap[finalColormap.length - 2]];
681
- }
682
-
781
+ let dataRange = (variable === 'ptypeRefl' || variable === 'ptypeRate') ? [5, 380] : [finalColormap[0], finalColormap[finalColormap.length - 2]];
683
782
  const colormapBytes = _generateColormapBytes(finalColormap);
684
783
  const colormapAsBase64 = fromByteArray(colormapBytes);
685
784
 
686
785
  gridLayerRef.current.updateColormapTexture(colormapAsBase64);
687
786
  cachedColormap.current = { key: `${variable}-${units}` };
688
787
  cachedDataRange.current = dataRange;
689
-
690
788
  setRenderProps(prev => ({ ...prev, dataRange, opacity: newState.opacity }));
691
789
 
692
790
  if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
693
- const scaleTypeValue = cachedData.scaleType === 'sqrt' ? 1 : 0; // Convert to number
694
-
695
- console.log('🔄 [Unit Conversion] Calling updateDataParameters with:', {
696
- scale: dataScale,
697
- offset: dataOffset,
698
- missing: cachedData.missing,
699
- scaleType: scaleTypeValue
700
- });
701
-
702
- gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing, scaleTypeValue); // Pass scaleType
791
+ const scaleTypeValue = cachedData.scaleType === 'sqrt' ? 1 : 0;
792
+ gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing, scaleTypeValue);
703
793
  }
704
794
 
705
- const newCacheKey = isMRMS
706
- ? `mrms-${mrmsTimestamp}-${variable}`
707
- : `${model}-${date}-${run}-${forecastHour}-${variable}`;
708
-
709
- preloadedDataCache.current.set(newCacheKey, {
710
- ...cachedData,
711
- scale: dataScale,
712
- offset: dataOffset
713
- });
714
-
715
- } else {
716
- console.warn('⚠️ [Unit Conversion] No cached data found for key:', oldCacheKey);
795
+ const newCacheKey = isMRMS ? `mrms-${mrmsTimestamp}-${variable}` : `${model}-${date}-${run}-${forecastHour}-${variable}`;
796
+ preloadedDataCache.current.set(newCacheKey, { ...cachedData, scale: dataScale, offset: dataOffset });
717
797
  }
718
798
 
719
799
  previousStateRef.current = newState;
@@ -729,51 +809,35 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
729
809
  newState.run !== previousStateRef.current.run;
730
810
 
731
811
  if (needsFullLoad) {
732
-
733
812
  if (gridLayerRef.current) {
734
813
  gridLayerRef.current.setVariable(newState.variable);
735
814
  gridLayerRef.current.clear();
736
- // --- OPTIMIZATION: Clear the native GPU cache on iOS ---
737
815
  if (Platform.OS === 'ios' && gridLayerRef.current.clearGpuCache) {
738
816
  gridLayerRef.current.clearGpuCache();
739
817
  }
740
818
  }
741
819
  hasPreloadedRef.current = false;
742
-
743
820
  preloadedDataCache.current.clear();
744
821
  cachedGeometry.current = null;
745
822
  cachedColormap.current = null;
746
-
747
- // ADD THIS: Clear the inspector cache too
748
823
  currentGridDataRef.current = null;
749
-
750
824
  WeatherFrameProcessorModule.cancelAllFrames();
751
825
 
752
826
  if (!newState.variable) {
753
827
  previousStateRef.current = newState;
754
828
  return;
755
829
  }
756
-
757
830
  preloadAllFramesToDisk(newState);
758
831
  } else if (newState.forecastHour !== previousStateRef.current.forecastHour || (newState.isMRMS && newState.mrmsTimestamp !== previousStateRef.current.mrmsTimestamp)) {
759
832
  const success = updateGPUWithCachedData(newState);
760
-
761
- if (!success) {
762
- // CHANGED: Don't error, just log and wait for preload
763
- const timeKey = newState.isMRMS ? `timestamp ${newState.mrmsTimestamp}` : `hour +${newState.forecastHour}`;
764
- console.log(`⏳ [handleStateChange] Frame ${timeKey} not ready yet, waiting for preload...`);
765
- // Don't clear the layer - keep showing the previous frame until new one is ready
766
- // The preload will eventually complete and trigger a re-render
767
- } else {
768
- // Only update inspector cache when we successfully loaded new data
769
- if (newState.opacity !== renderProps.opacity) {
770
- setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
771
- }
833
+ if (success && newState.opacity !== renderProps.opacity) {
834
+ setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
772
835
  }
773
836
  }
774
837
 
775
838
  previousStateRef.current = newState;
776
839
  };
840
+ // --- END REPLACEMENT ---
777
841
 
778
842
  handleStateChangeRef.current = handleStateChange;
779
843