@aguacerowx/react-native 0.0.36 → 0.0.37

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 (39) hide show
  1. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  2. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  3. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  4. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  5. package/android/.gradle/8.9/gc.properties +0 -0
  6. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  7. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  8. package/android/.gradle/vcs-1/gc.properties +0 -0
  9. package/android/build/.transforms/8f329a9571a96a1c1c0869d49784e448/results.bin +1 -0
  10. package/android/build/.transforms/8f329a9571a96a1c1c0869d49784e448/transformed/classes/classes_dex/classes.dex +0 -0
  11. package/android/build/.transforms/f95abdfc98a7a06fc247f75cdd74def9/results.bin +1 -0
  12. package/android/build/.transforms/f95abdfc98a7a06fc247f75cdd74def9/transformed/classes/classes_dex/classes.dex +0 -0
  13. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  14. package/ios/GridRenderLayer.swift +191 -58
  15. package/ios/GridRenderLayerBridge.swift +8 -0
  16. package/ios/GridRenderLayerView.m +1 -17
  17. package/lib/commonjs/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  18. package/lib/commonjs/ios/GridRenderLayer.swift +191 -58
  19. package/lib/commonjs/ios/GridRenderLayerBridge.swift +8 -0
  20. package/lib/commonjs/ios/GridRenderLayerView.m +1 -17
  21. package/lib/commonjs/package.json +1 -1
  22. package/lib/commonjs/src/WeatherLayerManager.js +34 -12
  23. package/lib/commonjs/src/WeatherLayerManager.js.map +1 -1
  24. package/lib/module/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  25. package/lib/module/ios/GridRenderLayer.swift +191 -58
  26. package/lib/module/ios/GridRenderLayerBridge.swift +8 -0
  27. package/lib/module/ios/GridRenderLayerView.m +1 -17
  28. package/lib/module/lib/commonjs/ios/GridRenderLayer.swift +191 -58
  29. package/lib/module/lib/commonjs/ios/GridRenderLayerBridge.swift +8 -0
  30. package/lib/module/lib/commonjs/ios/GridRenderLayerView.m +1 -17
  31. package/lib/module/lib/commonjs/package.json +1 -1
  32. package/lib/module/lib/commonjs/src/WeatherLayerManager.js +34 -12
  33. package/lib/module/lib/commonjs/src/WeatherLayerManager.js.map +1 -1
  34. package/lib/module/package.json +1 -1
  35. package/lib/module/src/WeatherLayerManager.js +35 -13
  36. package/lib/module/src/WeatherLayerManager.js.map +1 -1
  37. package/lib/typescript/src/WeatherLayerManager.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/WeatherLayerManager.js +151 -121
@@ -1,11 +1,11 @@
1
1
  // packages/react-native/src/WeatherLayerManager.js
2
2
 
3
- import React, { useState, useRef, useContext, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
4
- import { AguaceroCore, getUnitConversionFunction, DICTIONARIES } from '@aguacerowx/javascript-sdk';
3
+ import { AguaceroCore, DICTIONARIES, getUnitConversionFunction } from '@aguacerowx/javascript-sdk';
4
+ import { fromByteArray } from 'base64-js';
5
+ import React, { forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
6
+ import { NativeModules, Platform } from 'react-native';
5
7
  import { AguaceroContext } from './AguaceroContext';
6
8
  import { GridRenderLayer } from './GridRenderLayer';
7
- import { fromByteArray } from 'base64-js';
8
- import { Platform, NativeModules } from 'react-native';
9
9
  import { mapRegistry } from './MapRegistry';
10
10
 
11
11
  function findLatestModelRun(modelsData, modelName) {
@@ -67,27 +67,27 @@ const _generateColormapBytes = (colormap) => {
67
67
  return data;
68
68
  };
69
69
 
70
- AguaceroCore.prototype.setMapCenter = function(center) {
70
+ AguaceroCore.prototype.setMapCenter = function (center) {
71
71
  this.emit('map:move', center);
72
72
  };
73
73
 
74
74
  export const WeatherLayerManager = forwardRef((props, ref) => {
75
- const {
76
- inspectorEnabled,
77
- onInspect,
78
- apiKey,
79
- customColormaps,
80
- initialMode,
75
+ const {
76
+ inspectorEnabled,
77
+ onInspect,
78
+ apiKey,
79
+ customColormaps,
80
+ initialMode,
81
81
  initialVariable,
82
- autoRefresh,
83
- autoRefreshInterval,
82
+ autoRefresh,
83
+ autoRefreshInterval,
84
84
  initialModel,
85
- ...restProps
85
+ ...restProps
86
86
  } = props;
87
87
  const context = useContext(AguaceroContext);
88
-
88
+
89
89
  // Create the core here instead of getting it from context
90
- const core = useMemo(() => new AguaceroCore({
90
+ const core = useMemo(() => new AguaceroCore({
91
91
  apiKey: apiKey,
92
92
  customColormaps: customColormaps,
93
93
  // ADD: Pass layerOptions to the core's constructor
@@ -97,27 +97,27 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
97
97
  model: initialModel
98
98
  }
99
99
  }), [apiKey]);
100
-
100
+
101
101
  const gridLayerRef = useRef(null);
102
102
  const currentGridDataRef = useRef(null);
103
103
  const autoRefreshIntervalId = useRef(null);
104
-
104
+
105
105
  // Cache for preloaded grid data - stores the processed data ready for GPU upload
106
106
  const preloadedDataCache = useRef(new Map());
107
-
107
+
108
108
  // Store geometry and colormap that don't change with forecast hour
109
109
  const cachedGeometry = useRef(null);
110
110
  const cachedColormap = useRef(null);
111
111
  const cachedDataRange = useRef([0, 1]);
112
-
112
+
113
113
  // Track if we've done the initial load
114
114
  const hasInitialLoad = useRef(false);
115
115
  const hasPreloadedRef = useRef(false);
116
-
116
+
117
117
  // Track the last state we processed to avoid redundant updates
118
118
  const lastProcessedState = useRef(null);
119
119
  const previousStateRef = useRef(null);
120
-
120
+
121
121
  const [renderProps, setRenderProps] = useState({
122
122
  opacity: 1,
123
123
  dataRange: [0, 1]
@@ -223,11 +223,11 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
223
223
  }
224
224
  const colormapBytes = _generateColormapBytes(finalColormap);
225
225
  const colormapAsBase64 = fromByteArray(colormapBytes);
226
-
226
+
227
227
  gridLayerRef.current.updateColormapTexture(colormapAsBase64);
228
228
  cachedColormap.current = { key: `${variable}-${units}` };
229
229
  cachedDataRange.current = dataRange;
230
-
230
+
231
231
  setRenderProps({ opacity: state.opacity, dataRange: dataRange });
232
232
  hasInitialLoad.current = true;
233
233
  }
@@ -259,7 +259,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
259
259
 
260
260
  // Load the current frame FIRST and WAIT for it before continuing
261
261
  const currentCacheKey = isMRMS ? `mrms-${currentFrame}-${variable}` : `${model}-${date}-${run}-${currentFrame}-${variable}`;
262
-
262
+
263
263
  if (!preloadedDataCache.current.has(currentCacheKey)) {
264
264
  let resourcePath;
265
265
  if (isMRMS) {
@@ -277,13 +277,13 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
277
277
 
278
278
  try {
279
279
  const result = await WeatherFrameProcessorModule.processFrame(options);
280
-
280
+
281
281
  if (!result || !result.filePath) {
282
282
  return;
283
283
  }
284
-
284
+
285
285
  const { baseUnit } = core._getColormapForVariable(variable);
286
-
286
+
287
287
  const toUnit = core._getTargetUnit(baseUnit, units);
288
288
 
289
289
  const fieldInfo = DICTIONARIES?.fld?.[variable] || {};
@@ -315,7 +315,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
315
315
  dataOffset = convertedOffset;
316
316
  }
317
317
  }
318
-
318
+
319
319
  if (baseUnit !== toUnit) {
320
320
  const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
321
321
  if (conversionFunc) {
@@ -333,21 +333,21 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
333
333
  convertedScale = convertedOffsetPlusScale - convertedOffset;
334
334
  }
335
335
  }
336
- }
337
-
336
+ }
337
+
338
338
  const frameData = {
339
339
  filePath: result.filePath,
340
340
  nx, ny,
341
341
  scale: convertedScale,
342
342
  offset: convertedOffset,
343
343
  missing: result.missing,
344
- corners,
344
+ corners,
345
345
  gridDef,
346
346
  scaleType: result.scaleType,
347
347
  originalScale: result.scale,
348
348
  originalOffset: result.offset
349
349
  };
350
-
350
+
351
351
  preloadedDataCache.current.set(currentCacheKey, frameData);
352
352
 
353
353
  // Update the GPU with the current frame
@@ -402,19 +402,19 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
402
402
  console.warn(`⚠️ [preloadAllFramesToDisk] Failed frame ${frame}: No filePath`);
403
403
  return;
404
404
  }
405
-
405
+
406
406
  // ADD: Same two-step conversion as the current frame
407
407
  const { baseUnit } = core._getColormapForVariable(variable);
408
408
  const toUnit = core._getTargetUnit(baseUnit, units);
409
409
  const fieldInfo = DICTIONARIES?.fld?.[variable] || {};
410
410
  const serverDataUnit = fieldInfo.defaultUnit || baseUnit;
411
-
411
+
412
412
  let dataScale = result.scale;
413
413
  let dataOffset = result.offset;
414
-
414
+
415
415
  let convertedScale = dataScale;
416
416
  let convertedOffset = dataOffset;
417
-
417
+
418
418
  // Step 1: Convert from server unit to colormap base unit
419
419
  if (serverDataUnit !== baseUnit) {
420
420
  const conversionFunc = getUnitConversionFunction(serverDataUnit, baseUnit);
@@ -436,7 +436,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
436
436
  dataOffset = convertedOffset;
437
437
  }
438
438
  }
439
-
439
+
440
440
  // Step 2: Convert from colormap base unit to target display unit
441
441
  if (baseUnit !== toUnit) {
442
442
  const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
@@ -456,27 +456,27 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
456
456
  }
457
457
  }
458
458
  }
459
-
459
+
460
460
  const frameData = {
461
461
  filePath: result.filePath,
462
462
  nx, ny,
463
463
  scale: convertedScale,
464
464
  offset: convertedOffset,
465
465
  missing: result.missing,
466
- corners,
466
+ corners,
467
467
  gridDef,
468
468
  scaleType: result.scaleType,
469
469
  originalScale: result.scale,
470
470
  originalOffset: result.offset
471
471
  };
472
-
472
+
473
473
  preloadedDataCache.current.set(cacheKey, frameData);
474
474
 
475
475
  if (Platform.OS === 'ios' && gridLayerRef.current.primeGpuCache) {
476
476
  const frameInfoForGpu = {
477
477
  [cacheKey]: {
478
- filePath: frameData.filePath,
479
- nx: frameData.nx,
478
+ filePath: frameData.filePath,
479
+ nx: frameData.nx,
480
480
  ny: frameData.ny,
481
481
  scale: frameData.scale,
482
482
  offset: frameData.offset,
@@ -514,7 +514,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
514
514
 
515
515
  const updateGPUWithCachedData = (state) => {
516
516
  const { model, date, run, forecastHour, variable, units, isMRMS, mrmsTimestamp } = state;
517
-
517
+
518
518
  const cacheKey = isMRMS
519
519
  ? `mrms-${mrmsTimestamp}-${variable}`
520
520
  : `${model}-${date}-${run}-${forecastHour}-${variable}`;
@@ -522,7 +522,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
522
522
  if (Platform.OS === 'ios' && gridLayerRef.current.setActiveFrame) {
523
523
  // Get the cached data BEFORE calling setActiveFrame
524
524
  const cachedData = preloadedDataCache.current.get(cacheKey);
525
-
525
+
526
526
  if (cachedData) {
527
527
  currentGridDataRef.current = {
528
528
  nx: cachedData.nx,
@@ -536,28 +536,30 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
536
536
  scaleType: cachedData.scaleType
537
537
  };
538
538
  }
539
-
540
- // Now call setActiveFrame (which will async update the native cache)
539
+
540
+ if (__DEV__) {
541
+ console.log(`[WeatherLayerManager] setActiveFrame: ${cacheKey}`);
542
+ }
541
543
  gridLayerRef.current.setActiveFrame(cacheKey);
542
544
  return true;
543
545
  }
544
546
 
545
547
  const cachedData = preloadedDataCache.current.get(cacheKey);
546
-
548
+
547
549
  if (!cachedData) {
548
550
  return false;
549
551
  }
550
-
552
+
551
553
  if (!gridLayerRef.current) {
552
554
  console.warn(`⚠️ [updateGPUWithCachedData] GridLayer ref not available`); // CHANGED
553
555
  return false;
554
556
  }
555
-
557
+
556
558
  if (!cachedGeometry.current || cachedGeometry.current.model !== (isMRMS ? 'mrms' : model) || cachedGeometry.current.variable !== variable) {
557
559
  gridLayerRef.current.updateGeometry(cachedData.corners, cachedData.gridDef);
558
560
  cachedGeometry.current = { model: (isMRMS ? 'mrms' : model), variable };
559
- }
560
-
561
+ }
562
+
561
563
  const colormapKey = `${variable}-${units}`;
562
564
  if (!cachedColormap.current || cachedColormap.current.key !== colormapKey) {
563
565
  const { colormap, baseUnit } = core._getColormapForVariable(variable);
@@ -575,14 +577,14 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
575
577
  }
576
578
  const colormapBytes = _generateColormapBytes(finalColormap);
577
579
  const colormapAsBase64 = fromByteArray(colormapBytes);
578
-
580
+
579
581
  gridLayerRef.current.updateColormapTexture(colormapAsBase64);
580
582
  cachedColormap.current = { key: colormapKey };
581
583
  cachedDataRange.current = dataRange;
582
-
584
+
583
585
  setRenderProps(prev => ({ ...prev, dataRange }));
584
- }
585
-
586
+ }
587
+
586
588
  if (cachedData.filePath) {
587
589
  gridLayerRef.current.updateDataTextureFromFile(
588
590
  cachedData.filePath,
@@ -590,7 +592,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
590
592
  cachedData.scale, cachedData.offset, cachedData.missing,
591
593
  cachedData.scaleType
592
594
  );
593
-
595
+
594
596
  // ADD THIS: Update inspector cache for file-based data too
595
597
  currentGridDataRef.current = {
596
598
  nx: cachedData.nx,
@@ -609,15 +611,15 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
609
611
  cachedData.scale, cachedData.offset, cachedData.missing,
610
612
  cachedData.scaleType
611
613
  );
612
-
614
+
613
615
  // Update the inspector cache when using dataAsBase64
614
616
  const binaryString = atob(cachedData.dataAsBase64);
615
617
  const uint8Array = new Uint8Array(binaryString.length);
616
-
618
+
617
619
  for (let i = 0; i < binaryString.length; i++) {
618
620
  uint8Array[i] = binaryString.charCodeAt(i);
619
621
  }
620
-
622
+
621
623
  currentGridDataRef.current = {
622
624
  data: uint8Array,
623
625
  nx: cachedData.nx,
@@ -634,11 +636,11 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
634
636
  console.error('❌ [updateGPUWithCachedData] Cached data keys:', Object.keys(cachedData));
635
637
  return false;
636
638
  }
637
-
639
+
638
640
  // Update inspector parameters for file-based data too
639
641
  if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
640
642
  gridLayerRef.current.updateDataParameters(cachedData.scale, cachedData.offset, cachedData.missing);
641
- }
643
+ }
642
644
  return true;
643
645
  };
644
646
 
@@ -660,16 +662,16 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
660
662
  console.warn('🔍 [Inspector] Core not available');
661
663
  return null;
662
664
  }
663
-
665
+
664
666
  // ADD THIS: Check if we have valid data before attempting inspection
665
667
  if (!currentGridDataRef.current) {
666
668
  return null;
667
669
  }
668
-
670
+
669
671
  try {
670
672
  const gridIndices = core._getGridIndexFromLngLat(lng, lat);
671
673
  if (!gridIndices) return null;
672
-
674
+
673
675
  const { i, j } = gridIndices;
674
676
 
675
677
  const value = await InspectorModule.getValueAtGridIndex(i, j);
@@ -677,12 +679,12 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
677
679
  if (value === null) {
678
680
  return null;
679
681
  }
680
-
682
+
681
683
  const { colormap, baseUnit } = core._getColormapForVariable(core.state.variable);
682
684
  const displayUnit = core._getTargetUnit(baseUnit, core.state.units);
683
685
  const finalColormap = core._convertColormapUnits(colormap, baseUnit, displayUnit);
684
686
  const minThreshold = finalColormap[0];
685
-
687
+
686
688
  if (value < minThreshold) {
687
689
  return null;
688
690
  }
@@ -691,12 +693,12 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
691
693
  if (value < minThreshold) {
692
694
  return null;
693
695
  }
694
-
696
+
695
697
  // Also check if value is NaN or effectively missing
696
698
  if (!isFinite(value)) {
697
699
  return null;
698
700
  }
699
-
701
+
700
702
  return {
701
703
  value: value,
702
704
  unit: displayUnit,
@@ -736,7 +738,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
736
738
  newTimestampsToPreload.forEach(frame => {
737
739
  const cacheKey = `mrms-${frame}-${currentVariable}`;
738
740
  if (preloadedDataCache.current.has(cacheKey)) return;
739
-
741
+
740
742
  const frameDate = new Date(frame * 1000);
741
743
  const y = frameDate.getUTCFullYear(), m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0'), d = frameDate.getUTCDate().toString().padStart(2, '0');
742
744
  const resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${currentVariable}/0`;
@@ -752,7 +754,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
752
754
  }
753
755
  }).catch(error => console.warn(`[Auto-Refresh] Failed to preload frame ${frame}:`, error));
754
756
  });
755
-
757
+
756
758
  const newTimestampsSet = new Set(newTimestamps);
757
759
  oldTimestamps.forEach(oldTs => {
758
760
  if (!newTimestampsSet.has(oldTs)) {
@@ -776,31 +778,29 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
776
778
  return;
777
779
  }
778
780
 
779
- const handleStateChange = async (newState) => {
780
- props.onStateChange?.(newState);
781
-
781
+ const handleStateChange = (newState) => {
782
782
  if (!previousStateRef.current) {
783
783
  previousStateRef.current = core.state;
784
- }
784
+ }
785
785
 
786
786
  const variableChanged = !previousStateRef.current || newState.variable !== previousStateRef.current.variable;
787
787
 
788
788
  if (variableChanged && gridLayerRef.current?.setVariable) {
789
789
  gridLayerRef.current.setVariable(newState.variable);
790
790
  }
791
-
791
+
792
792
  const stateKey = `${newState.model}-${newState.variable}-${newState.date}-${newState.run}-${newState.forecastHour}-${newState.units}-${newState.mrmsTimestamp}`;
793
793
 
794
- const isOpacityOnlyChange =
794
+ const isOpacityOnlyChange =
795
795
  hasInitialLoad.current &&
796
- newState.opacity !== renderProps.opacity &&
796
+ newState.opacity !== renderProps.opacity &&
797
797
  newState.variable === previousStateRef.current?.variable &&
798
798
  newState.forecastHour === previousStateRef.current?.forecastHour &&
799
799
  newState.mrmsTimestamp === previousStateRef.current?.mrmsTimestamp &&
800
800
  newState.model === previousStateRef.current?.model &&
801
801
  newState.units === previousStateRef.current?.units;
802
802
 
803
- const isPlayStateOnlyChange =
803
+ const isPlayStateOnlyChange =
804
804
  hasInitialLoad.current &&
805
805
  newState.isPlaying !== previousStateRef.current?.isPlaying &&
806
806
  newState.variable === previousStateRef.current?.variable &&
@@ -814,11 +814,11 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
814
814
  previousStateRef.current = newState;
815
815
  return;
816
816
  }
817
-
817
+
818
818
  if (!isOpacityOnlyChange && !isPlayStateOnlyChange) {
819
819
  lastProcessedState.current = stateKey;
820
820
  }
821
-
821
+
822
822
  if (isOpacityOnlyChange) {
823
823
  setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
824
824
  previousStateRef.current = newState;
@@ -830,7 +830,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
830
830
  return;
831
831
  }
832
832
 
833
- const isUnitsOnlyChange =
833
+ const isUnitsOnlyChange =
834
834
  hasInitialLoad.current &&
835
835
  newState.model === previousStateRef.current.model &&
836
836
  newState.isMRMS === previousStateRef.current.isMRMS &&
@@ -843,21 +843,21 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
843
843
 
844
844
  if (isUnitsOnlyChange) {
845
845
  const { variable, units, isMRMS, mrmsTimestamp, model, date, run, forecastHour } = newState;
846
- const oldCacheKey = isMRMS
846
+ const oldCacheKey = isMRMS
847
847
  ? `mrms-${mrmsTimestamp}-${variable}`
848
848
  : `${model}-${date}-${run}-${forecastHour}-${variable}`;
849
-
849
+
850
850
  const cachedData = preloadedDataCache.current.get(oldCacheKey);
851
-
851
+
852
852
  if (cachedData && cachedData.originalScale !== undefined && cachedData.originalOffset !== undefined) {
853
853
  const { baseUnit } = core._getColormapForVariable(variable);
854
854
  const toUnit = core._getTargetUnit(baseUnit, units);
855
855
  const fieldInfo = DICTIONARIES?.fld?.[variable] || {};
856
856
  const serverDataUnit = fieldInfo.defaultUnit || baseUnit;
857
-
857
+
858
858
  let dataScale = cachedData.originalScale;
859
859
  let dataOffset = cachedData.originalOffset;
860
-
860
+
861
861
  if (serverDataUnit !== baseUnit) {
862
862
  const conversionFunc = getUnitConversionFunction(serverDataUnit, baseUnit);
863
863
  if (conversionFunc) {
@@ -878,7 +878,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
878
878
  }
879
879
  }
880
880
  }
881
-
881
+
882
882
  if (baseUnit !== toUnit) {
883
883
  const conversionFunc = getUnitConversionFunction(baseUnit, toUnit);
884
884
  if (conversionFunc) {
@@ -898,19 +898,19 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
898
898
  dataOffset = convertedOffset;
899
899
  }
900
900
  }
901
- }
902
-
901
+ }
902
+
903
903
  const { colormap } = core._getColormapForVariable(variable);
904
904
  const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
905
905
  let dataRange = (variable === 'ptypeRefl' || variable === 'ptypeRate') ? [5, 380] : [finalColormap[0], finalColormap[finalColormap.length - 2]];
906
906
  const colormapBytes = _generateColormapBytes(finalColormap);
907
907
  const colormapAsBase64 = fromByteArray(colormapBytes);
908
-
908
+
909
909
  gridLayerRef.current.updateColormapTexture(colormapAsBase64);
910
910
  cachedColormap.current = { key: `${variable}-${units}` };
911
911
  cachedDataRange.current = dataRange;
912
912
  setRenderProps(prev => ({ ...prev, dataRange, opacity: newState.opacity }));
913
-
913
+
914
914
  if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
915
915
  const scaleTypeValue = cachedData.scaleType === 'sqrt' ? 1 : 0;
916
916
  gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing, scaleTypeValue);
@@ -919,11 +919,11 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
919
919
  const newCacheKey = isMRMS ? `mrms-${mrmsTimestamp}-${variable}` : `${model}-${date}-${run}-${forecastHour}-${variable}`;
920
920
  preloadedDataCache.current.set(newCacheKey, { ...cachedData, scale: dataScale, offset: dataOffset });
921
921
  }
922
-
922
+
923
923
  previousStateRef.current = newState;
924
924
  return;
925
925
  }
926
-
926
+
927
927
  const needsFullLoad =
928
928
  !hasInitialLoad.current ||
929
929
  newState.model !== previousStateRef.current.model ||
@@ -958,42 +958,72 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
958
958
  setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
959
959
  }
960
960
  }
961
-
961
+
962
962
  previousStateRef.current = newState;
963
963
  };
964
- // --- END REPLACEMENT ---
965
964
 
966
965
  handleStateChangeRef.current = handleStateChange;
967
-
966
+
968
967
  const stableHandler = (newState) => {
969
- const isOpacityOnlyChange =
968
+ // OPTIMIZATION: If playing (high speed), prioritize MAP update and skip debounce
969
+ if (newState.isPlaying) {
970
+ // 1. Update Map FIRST (Native Enqueue)
971
+ if (handleStateChangeRef.current) {
972
+ handleStateChangeRef.current(newState);
973
+ }
974
+
975
+ // 2. Update UI Slider SECOND
976
+ // This ensures the heavy map frame is processing while React reconciles the slider
977
+ props.onStateChange?.(newState);
978
+
979
+ if (debounceTimeoutRef.current) {
980
+ clearTimeout(debounceTimeoutRef.current);
981
+ debounceTimeoutRef.current = null;
982
+ }
983
+ return;
984
+ }
985
+
986
+ // --- Existing Logic for scrubbing/paused ---
987
+
988
+ // 1. Immediate Slider Update for responsiveness
989
+ props.onStateChange?.(newState);
990
+
991
+ if (debounceTimeoutRef.current) {
992
+ clearTimeout(debounceTimeoutRef.current);
993
+ }
994
+
995
+ // Opacity and Play state changes should be immediate for the native layer too
996
+ const isOpacityOnlyChange =
970
997
  previousStateRef.current &&
971
998
  newState.opacity !== previousStateRef.current.opacity &&
972
999
  newState.variable === previousStateRef.current.variable &&
973
1000
  newState.forecastHour === previousStateRef.current.forecastHour &&
974
1001
  newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp &&
975
1002
  newState.model === previousStateRef.current.model &&
976
- newState.units === previousStateRef.current.units &&
977
- newState.date === previousStateRef.current.date &&
978
- newState.run === previousStateRef.current.run;
979
-
980
- if (isOpacityOnlyChange) {
1003
+ newState.units === previousStateRef.current.units;
1004
+
1005
+ const isPlayStateOnlyChange =
1006
+ previousStateRef.current &&
1007
+ newState.isPlaying !== previousStateRef.current.isPlaying &&
1008
+ newState.variable === previousStateRef.current.variable &&
1009
+ newState.forecastHour === previousStateRef.current.forecastHour &&
1010
+ newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp &&
1011
+ newState.model === previousStateRef.current.model &&
1012
+ newState.units === previousStateRef.current.units;
1013
+
1014
+ if (isOpacityOnlyChange || isPlayStateOnlyChange || !previousStateRef.current) {
981
1015
  if (handleStateChangeRef.current) {
982
1016
  handleStateChangeRef.current(newState);
983
1017
  }
984
1018
  return;
985
1019
  }
986
-
987
- if (debounceTimeoutRef.current) {
988
- clearTimeout(debounceTimeoutRef.current);
989
- }
990
1020
 
991
1021
  debounceTimeoutRef.current = setTimeout(() => {
992
1022
  if (handleStateChangeRef.current) {
993
1023
  handleStateChangeRef.current(newState);
994
1024
  }
995
1025
  debounceTimeoutRef.current = null;
996
- }, 50);
1026
+ }, 16); // ~60fps map updates
997
1027
  };
998
1028
 
999
1029
  core.on('state:change', stableHandler);
@@ -1005,7 +1035,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
1005
1035
  }
1006
1036
  };
1007
1037
  }, [core]);
1008
-
1038
+
1009
1039
  useEffect(() => {
1010
1040
  return () => {
1011
1041
  preloadedDataCache.current.clear();
@@ -1013,9 +1043,9 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
1013
1043
  lastProcessedState.current = null;
1014
1044
  };
1015
1045
  }, []);
1016
-
1046
+
1017
1047
  const lastInspectorUpdateRef = useRef(0);
1018
- const INSPECTOR_THROTTLE_MS = 50;
1048
+ const INSPECTOR_THROTTLE_MS = 50;
1019
1049
 
1020
1050
  useEffect(() => {
1021
1051
  if (!core || !inspectorEnabled) {
@@ -1026,16 +1056,16 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
1026
1056
  if (!center || !Array.isArray(center) || center.length !== 2) {
1027
1057
  return;
1028
1058
  }
1029
-
1059
+
1030
1060
  // Throttle updates
1031
1061
  const now = Date.now();
1032
1062
  if (now - lastInspectorUpdateRef.current < INSPECTOR_THROTTLE_MS) {
1033
1063
  return;
1034
1064
  }
1035
1065
  lastInspectorUpdateRef.current = now;
1036
-
1066
+
1037
1067
  const [longitude, latitude] = center;
1038
-
1068
+
1039
1069
  const payload = await getValueAtPoint(longitude, latitude);
1040
1070
  onInspect?.(payload);
1041
1071
  };
@@ -1062,10 +1092,10 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
1062
1092
  const triggerReinspection = () => {
1063
1093
  const mapRef = mapRegistry.getMap();
1064
1094
  const center = mapRef?._currentCenter;
1065
-
1095
+
1066
1096
  if (center && Array.isArray(center) && center.length === 2) {
1067
1097
  const [longitude, latitude] = center;
1068
-
1098
+
1069
1099
  getValueAtPoint(longitude, latitude).then(payload => {
1070
1100
  onInspect?.(payload);
1071
1101
  });
@@ -1078,7 +1108,7 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
1078
1108
  return () => clearTimeout(timer);
1079
1109
  }, [
1080
1110
  core?.state?.variable,
1081
- core?.state?.model,
1111
+ core?.state?.model,
1082
1112
  core?.state?.forecastHour,
1083
1113
  core?.state?.mrmsTimestamp,
1084
1114
  core?.state?.units,
@@ -1090,22 +1120,22 @@ export const WeatherLayerManager = forwardRef((props, ref) => {
1090
1120
  if (!core) {
1091
1121
  return;
1092
1122
  }
1093
-
1123
+
1094
1124
  const handleCameraChange = (center) => {
1095
1125
  if (core && center) {
1096
1126
  core.setMapCenter(center);
1097
1127
  }
1098
1128
  };
1099
-
1129
+
1100
1130
  // Register with the global registry
1101
1131
  mapRegistry.addCameraListener(handleCameraChange);
1102
-
1132
+
1103
1133
  // Try to get initial center
1104
1134
  const mapRef = mapRegistry.getMap();
1105
1135
  if (mapRef?._currentCenter) {
1106
1136
  handleCameraChange(mapRef._currentCenter);
1107
1137
  }
1108
-
1138
+
1109
1139
  return () => {
1110
1140
  mapRegistry.removeCameraListener(handleCameraChange);
1111
1141
  };