@aguacerowx/react-native 0.0.0
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.
- package/README.md +16 -0
- package/android/build/.transforms/78b892a9dae44f36e51ff0649e9c6e36/results.bin +1 -0
- package/android/build/.transforms/78b892a9dae44f36e51ff0649e9c6e36/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/results.bin +1 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/AguaceroPackage.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/BuildConfig.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/GridRenderLayer$VertexInfo.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/GridRenderLayer.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/GridRenderLayerView.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/GridRenderManager.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/InspectorModule.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/ShaderUtils.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/aguacerowx/reactnative/WeatherFrameProcessorModule.dex +0 -0
- package/android/build/.transforms/f4de14556a82e99f0d27ddcab762b219/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
- package/android/build/generated/source/buildConfig/debug/com/aguacerowx/reactnative/BuildConfig.java +10 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +8 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +4 -0
- package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/raw_debug_fragment_shader.glsl.flat +0 -0
- package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/raw_debug_vertex_shader.glsl.flat +0 -0
- package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/raw_fragment_shader.glsl.flat +0 -0
- package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/raw_vertex_shader.glsl.flat +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +5 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/AguaceroPackage.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/BuildConfig.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/GridRenderLayer$VertexInfo.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/GridRenderLayer.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/GridRenderLayerView.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/GridRenderManager.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/InspectorModule.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/ShaderUtils.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/aguacerowx/reactnative/WeatherFrameProcessorModule.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +6 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +8 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +8 -0
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/packaged_res/debug/packageDebugResources/raw/debug_fragment_shader.glsl +13 -0
- package/android/build/intermediates/packaged_res/debug/packageDebugResources/raw/debug_vertex_shader.glsl +13 -0
- package/android/build/intermediates/packaged_res/debug/packageDebugResources/raw/fragment_shader.glsl +87 -0
- package/android/build/intermediates/packaged_res/debug/packageDebugResources/raw/vertex_shader.glsl +20 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/AguaceroPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/GridRenderLayer$VertexInfo.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/GridRenderLayer.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/GridRenderLayerView.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/GridRenderManager.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/InspectorModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/ShaderUtils.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/aguacerowx/reactnative/WeatherFrameProcessorModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +5 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +17 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/AguaceroPackage.class.uniqueId1 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/GridRenderLayerView.class.uniqueId2 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/GridRenderManager.class.uniqueId3 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/InspectorModule.class.uniqueId0 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +47 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/com/aguacerowx/reactnative/AguaceroPackage.java +34 -0
- package/android/src/main/java/com/aguacerowx/reactnative/GridRenderLayer.java +639 -0
- package/android/src/main/java/com/aguacerowx/reactnative/GridRenderLayerView.java +287 -0
- package/android/src/main/java/com/aguacerowx/reactnative/GridRenderManager.java +111 -0
- package/android/src/main/java/com/aguacerowx/reactnative/InspectorModule.java +64 -0
- package/android/src/main/java/com/aguacerowx/reactnative/ShaderUtils.java +107 -0
- package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +145 -0
- package/android/src/main/res/raw/debug_fragment_shader.glsl +13 -0
- package/android/src/main/res/raw/debug_vertex_shader.glsl +13 -0
- package/android/src/main/res/raw/fragment_shader.glsl +87 -0
- package/android/src/main/res/raw/vertex_shader.glsl +20 -0
- package/index.js +2 -0
- package/package.json +35 -0
- package/src/AguaceroContext.js +4 -0
- package/src/GridRenderLayer.js +121 -0
- package/src/MapManager.js +158 -0
- package/src/MapRegistry.js +35 -0
- package/src/StyleApplicator.js +241 -0
- package/src/WeatherLayerManager.js +754 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
// packages/react-native/src/WeatherLayerManager.js
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef, useContext, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
|
|
4
|
+
import { AguaceroCore, getUnitConversionFunction } from '@aguacerowx/javascript-sdk';
|
|
5
|
+
import { AguaceroContext } from './AguaceroContext';
|
|
6
|
+
import { GridRenderLayer } from './GridRenderLayer';
|
|
7
|
+
import { fromByteArray } from 'base64-js';
|
|
8
|
+
import { NativeModules } from 'react-native';
|
|
9
|
+
import { mapRegistry } from './MapRegistry';
|
|
10
|
+
const { WeatherFrameProcessorModule, InspectorModule } = NativeModules;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A helper function to generate the raw RGBA byte buffer for the colormap texture.
|
|
14
|
+
*/
|
|
15
|
+
const _generateColormapBytes = (colormap) => {
|
|
16
|
+
const width = 256;
|
|
17
|
+
const data = new Uint8Array(width * 4);
|
|
18
|
+
const stops = colormap.reduce((acc, _, i) => (i % 2 === 0 ? [...acc, { value: colormap[i], color: colormap[i + 1] }] : acc), []);
|
|
19
|
+
|
|
20
|
+
if (stops.length === 0) return data;
|
|
21
|
+
|
|
22
|
+
const minVal = stops[0].value;
|
|
23
|
+
const maxVal = stops[stops.length - 1].value;
|
|
24
|
+
|
|
25
|
+
const hexToRgb = (hex) => {
|
|
26
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
27
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
28
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
29
|
+
return [r, g, b];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < width; i++) {
|
|
33
|
+
const val = minVal + (i / (width - 1)) * (maxVal - minVal);
|
|
34
|
+
let lower = stops[0];
|
|
35
|
+
let upper = stops[stops.length - 1];
|
|
36
|
+
for (let j = 0; j < stops.length - 1; j++) {
|
|
37
|
+
if (val >= stops[j].value && val <= stops[j + 1].value) {
|
|
38
|
+
lower = stops[j];
|
|
39
|
+
upper = stops[j + 1];
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const t = (val - lower.value) / (upper.value - lower.value || 1);
|
|
44
|
+
const lowerRgb = hexToRgb(lower.color);
|
|
45
|
+
const upperRgb = hexToRgb(upper.color);
|
|
46
|
+
const rgb = lowerRgb.map((c, idx) => c * (1 - t) + upperRgb[idx] * t);
|
|
47
|
+
|
|
48
|
+
const offset = i * 4;
|
|
49
|
+
data[offset + 0] = Math.round(rgb[0]);
|
|
50
|
+
data[offset + 1] = Math.round(rgb[1]);
|
|
51
|
+
data[offset + 2] = Math.round(rgb[2]);
|
|
52
|
+
data[offset + 3] = 255;
|
|
53
|
+
}
|
|
54
|
+
return data;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
AguaceroCore.prototype.setMapCenter = function(center) {
|
|
58
|
+
this.emit('map:move', center);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const WeatherLayerManager = forwardRef((props, ref) => {
|
|
62
|
+
const { inspectorEnabled, onInspect, apiKey, customColormaps, ...restProps } = props;
|
|
63
|
+
const context = useContext(AguaceroContext);
|
|
64
|
+
|
|
65
|
+
// Create the core here instead of getting it from context
|
|
66
|
+
const core = useMemo(() => new AguaceroCore({
|
|
67
|
+
apiKey: apiKey,
|
|
68
|
+
customColormaps: customColormaps
|
|
69
|
+
}), [apiKey]);
|
|
70
|
+
|
|
71
|
+
const gridLayerRef = useRef(null);
|
|
72
|
+
const currentGridDataRef = useRef(null);
|
|
73
|
+
|
|
74
|
+
// Cache for preloaded grid data - stores the processed data ready for GPU upload
|
|
75
|
+
const preloadedDataCache = useRef(new Map());
|
|
76
|
+
|
|
77
|
+
// Track what we're currently preloading to avoid duplicates
|
|
78
|
+
const preloadingSet = useRef(new Set());
|
|
79
|
+
|
|
80
|
+
// Store geometry and colormap that don't change with forecast hour
|
|
81
|
+
const cachedGeometry = useRef(null);
|
|
82
|
+
const cachedColormap = useRef(null);
|
|
83
|
+
const cachedDataRange = useRef([0, 1]);
|
|
84
|
+
|
|
85
|
+
// Track if we've done the initial load
|
|
86
|
+
const hasInitialLoad = useRef(false);
|
|
87
|
+
|
|
88
|
+
// Track the last state we processed to avoid redundant updates
|
|
89
|
+
const lastProcessedState = useRef(null);
|
|
90
|
+
const previousStateRef = useRef(null);
|
|
91
|
+
|
|
92
|
+
const [renderProps, setRenderProps] = useState({
|
|
93
|
+
opacity: 1,
|
|
94
|
+
dataRange: [0, 1]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
useImperativeHandle(ref, () => ({
|
|
98
|
+
play: () => core.play(),
|
|
99
|
+
pause: () => core.pause(),
|
|
100
|
+
togglePlay: () => core.togglePlay(),
|
|
101
|
+
step: (direction) => core.step(direction),
|
|
102
|
+
setPlaybackSpeed: (speed) => core.setPlaybackSpeed(speed),
|
|
103
|
+
setOpacity: (opacity) => core.setOpacity(opacity),
|
|
104
|
+
setUnits: (units) => core.setUnits(units),
|
|
105
|
+
switchMode: (options) => core.switchMode(options),
|
|
106
|
+
getAvailableVariables: (model) => core.getAvailableVariables(model),
|
|
107
|
+
getVariableDisplayName: (code) => core.getVariableDisplayName(code),
|
|
108
|
+
setRun: (runString) => core.setState({ run: runString.split(':')[1] }),
|
|
109
|
+
setState: (newState) => core.setState(newState),
|
|
110
|
+
setMRMSTimestamp: (timestamp) => core.setMRMSTimestamp(timestamp),
|
|
111
|
+
setSmoothing: (enabled) => {
|
|
112
|
+
if (gridLayerRef.current) {
|
|
113
|
+
gridLayerRef.current.setSmoothing(enabled);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
const preloadAllFramesToDisk = (state) => {
|
|
119
|
+
const { isMRMS, model, date, run, variable, units, availableHours, availableTimestamps } = state;
|
|
120
|
+
|
|
121
|
+
const allFrames = isMRMS ? availableTimestamps : availableHours;
|
|
122
|
+
if (!allFrames || allFrames.length === 0) {
|
|
123
|
+
console.warn('🟡 [Preload To Disk] No frames available to download.');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { corners, gridDef } = core._getGridCornersAndDef(isMRMS ? 'mrms' : model);
|
|
128
|
+
const { nx, ny } = gridDef.grid_params;
|
|
129
|
+
|
|
130
|
+
for (const frame of allFrames) {
|
|
131
|
+
const cacheKey = isMRMS ? `mrms-${frame}-${variable}-${units}` : `${model}-${date}-${run}-${frame}-${variable}-${units}`;
|
|
132
|
+
if (preloadedDataCache.current.has(cacheKey)) {
|
|
133
|
+
continue; // Skip if already cached
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let resourcePath;
|
|
137
|
+
if (isMRMS) {
|
|
138
|
+
const frameDate = new Date(frame * 1000);
|
|
139
|
+
const y = frameDate.getUTCFullYear();
|
|
140
|
+
const m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
141
|
+
const d = frameDate.getUTCDate().toString().padStart(2, '0');
|
|
142
|
+
resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${variable}/0`;
|
|
143
|
+
} else {
|
|
144
|
+
resourcePath = `/grids/${model}/${date}/${run}/${frame}/${variable}/0`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const url = `${core.baseGridUrl}${resourcePath}?apiKey=${core.apiKey}`;
|
|
148
|
+
const options = { url, apiKey: core.apiKey, bundleId: core.bundleId };
|
|
149
|
+
|
|
150
|
+
// Assumes your native module has a method 'processFrameAndSave' that saves to a file
|
|
151
|
+
// and returns a { filePath, scale, offset, missing } object in the callback.
|
|
152
|
+
WeatherFrameProcessorModule.processFrame(options, (error, result) => {
|
|
153
|
+
if (error || !result.filePath) {
|
|
154
|
+
console.error(`❌ [Native Save Error] for frame ${frame}:`, error || "Result has no filePath");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Cache is now extremely lightweight, just storing metadata and a path.
|
|
159
|
+
preloadedDataCache.current.set(cacheKey, {
|
|
160
|
+
filePath: result.filePath,
|
|
161
|
+
nx, ny,
|
|
162
|
+
scale: result.scale, offset: result.offset, missing: result.missing,
|
|
163
|
+
corners, gridDef
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* FINAL VERSION: Updates the GPU by reading from the cache and calling the correct
|
|
171
|
+
* native render function (either from a file path or from a Base64 string).
|
|
172
|
+
*/
|
|
173
|
+
const updateGPUWithCachedData = (state) => {
|
|
174
|
+
const { model, date, run, forecastHour, variable, units, isMRMS, mrmsTimestamp } = state;
|
|
175
|
+
|
|
176
|
+
const cacheKey = isMRMS
|
|
177
|
+
? `mrms-${mrmsTimestamp}-${variable}-${units}`
|
|
178
|
+
: `${model}-${date}-${run}-${forecastHour}-${variable}-${units}`;
|
|
179
|
+
|
|
180
|
+
const cachedData = preloadedDataCache.current.get(cacheKey);
|
|
181
|
+
|
|
182
|
+
if (!cachedData) {
|
|
183
|
+
const timeKey = isMRMS ? `timestamp ${mrmsTimestamp}` : `hour +${forecastHour}`;
|
|
184
|
+
console.warn(`⚠️ [GPU Update] No cached data for ${timeKey}. Key not found: ${cacheKey}`);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!gridLayerRef.current) {
|
|
189
|
+
console.error(`❌ [GPU Update] GridLayer ref not available`);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!cachedGeometry.current || cachedGeometry.current.model !== (isMRMS ? 'mrms' : model) || cachedGeometry.current.variable !== variable) {
|
|
194
|
+
gridLayerRef.current.updateGeometry(cachedData.corners, cachedData.gridDef);
|
|
195
|
+
cachedGeometry.current = { model: (isMRMS ? 'mrms' : model), variable };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const colormapKey = `${variable}-${units}`;
|
|
199
|
+
if (!cachedColormap.current || cachedColormap.current.key !== colormapKey) {
|
|
200
|
+
const { colormap, baseUnit } = core._getColormapForVariable(variable);
|
|
201
|
+
const toUnit = core._getTargetUnit(baseUnit, units);
|
|
202
|
+
const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
|
|
203
|
+
const dataRange = [finalColormap[0], finalColormap[finalColormap.length - 2]];
|
|
204
|
+
const colormapBytes = _generateColormapBytes(finalColormap);
|
|
205
|
+
const colormapAsBase64 = fromByteArray(colormapBytes);
|
|
206
|
+
|
|
207
|
+
gridLayerRef.current.updateColormapTexture(colormapAsBase64);
|
|
208
|
+
cachedColormap.current = { key: colormapKey };
|
|
209
|
+
cachedDataRange.current = dataRange;
|
|
210
|
+
|
|
211
|
+
setRenderProps(prev => ({ ...prev, dataRange }));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// UPDATE GPU
|
|
215
|
+
if (cachedData.filePath) {
|
|
216
|
+
gridLayerRef.current.updateDataTextureFromFile(
|
|
217
|
+
cachedData.filePath,
|
|
218
|
+
cachedData.nx, cachedData.ny,
|
|
219
|
+
cachedData.scale, cachedData.offset, cachedData.missing
|
|
220
|
+
);
|
|
221
|
+
} else if (cachedData.dataAsBase64) {
|
|
222
|
+
gridLayerRef.current.updateDataTexture(
|
|
223
|
+
cachedData.dataAsBase64,
|
|
224
|
+
cachedData.nx, cachedData.ny,
|
|
225
|
+
cachedData.scale, cachedData.offset, cachedData.missing
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// ADD THIS: Update the inspector cache when using dataAsBase64
|
|
229
|
+
const binaryString = atob(cachedData.dataAsBase64);
|
|
230
|
+
const uint8Array = new Uint8Array(binaryString.length);
|
|
231
|
+
|
|
232
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
233
|
+
uint8Array[i] = binaryString.charCodeAt(i);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
currentGridDataRef.current = {
|
|
237
|
+
data: uint8Array,
|
|
238
|
+
nx: cachedData.nx,
|
|
239
|
+
ny: cachedData.ny,
|
|
240
|
+
scale: cachedData.scale,
|
|
241
|
+
offset: cachedData.offset,
|
|
242
|
+
missing: cachedData.missing,
|
|
243
|
+
gridDef: cachedData.gridDef,
|
|
244
|
+
variable: variable,
|
|
245
|
+
units: units
|
|
246
|
+
};
|
|
247
|
+
} else {
|
|
248
|
+
console.error(`❌ [GPU Update] Cached data for key ${cacheKey} has no filePath or dataAsBase64.`);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ADD THIS: Update inspector parameters for file-based data too
|
|
253
|
+
if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
|
|
254
|
+
gridLayerRef.current.updateDataParameters(cachedData.scale, cachedData.offset, cachedData.missing);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return true;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const handleStateChangeRef = useRef(null);
|
|
261
|
+
const debounceTimeoutRef = useRef(null);
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (core && props.customColormaps) {
|
|
265
|
+
core.customColormaps = props.customColormaps;
|
|
266
|
+
// Trigger a re-render if we already have data loaded
|
|
267
|
+
if (hasInitialLoad.current) {
|
|
268
|
+
core._emitStateChange();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}, [core, props.customColormaps]);
|
|
272
|
+
|
|
273
|
+
const getValueAtPoint = async (lng, lat) => {
|
|
274
|
+
if (!core) {
|
|
275
|
+
console.warn('🔍 [Inspector] Core not available');
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const gridIndices = core._getGridIndexFromLngLat(lng, lat);
|
|
281
|
+
if (!gridIndices) return null;
|
|
282
|
+
|
|
283
|
+
const { i, j } = gridIndices;
|
|
284
|
+
|
|
285
|
+
// The native module uses the cached scale/offset which we update via updateDataParameters
|
|
286
|
+
const value = await InspectorModule.getValueAtGridIndex(i, j);
|
|
287
|
+
|
|
288
|
+
if (value === null) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { colormap, baseUnit } = core._getColormapForVariable(core.state.variable);
|
|
293
|
+
const displayUnit = core._getTargetUnit(baseUnit, core.state.units);
|
|
294
|
+
|
|
295
|
+
// Get the converted colormap to check min threshold
|
|
296
|
+
const finalColormap = core._convertColormapUnits(colormap, baseUnit, displayUnit);
|
|
297
|
+
const minThreshold = finalColormap[0]; // First value in colormap is the minimum
|
|
298
|
+
|
|
299
|
+
// Filter out values below the minimum threshold (matching shader behavior)
|
|
300
|
+
if (value < minThreshold) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Also check if value is NaN or effectively missing
|
|
305
|
+
if (!isFinite(value)) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
value: value,
|
|
311
|
+
unit: displayUnit,
|
|
312
|
+
variable: {
|
|
313
|
+
code: core.state.variable,
|
|
314
|
+
name: core.getVariableDisplayName(core.state.variable)
|
|
315
|
+
},
|
|
316
|
+
lngLat: { lng, lat }
|
|
317
|
+
};
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('🔍 [Inspector] Error:', error);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
if (!core) {
|
|
326
|
+
console.warn('⚠️ [useEffect] Core is not available yet');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const handleStateChange = async (newState) => {
|
|
331
|
+
if (!previousStateRef.current) {
|
|
332
|
+
previousStateRef.current = core.state;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const stateKey = `${newState.model}-${newState.variable}-${newState.date}-${newState.run}-${newState.forecastHour}-${newState.units}-${newState.mrmsTimestamp}`;
|
|
336
|
+
|
|
337
|
+
const isOpacityOnlyChange =
|
|
338
|
+
hasInitialLoad.current &&
|
|
339
|
+
newState.opacity !== renderProps.opacity &&
|
|
340
|
+
newState.variable === previousStateRef.current?.variable &&
|
|
341
|
+
newState.forecastHour === previousStateRef.current?.forecastHour &&
|
|
342
|
+
newState.mrmsTimestamp === previousStateRef.current?.mrmsTimestamp &&
|
|
343
|
+
newState.model === previousStateRef.current?.model &&
|
|
344
|
+
newState.units === previousStateRef.current?.units;
|
|
345
|
+
|
|
346
|
+
if (!isOpacityOnlyChange && lastProcessedState.current === stateKey) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!isOpacityOnlyChange) {
|
|
351
|
+
lastProcessedState.current = stateKey;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
props.onStateChange?.(newState);
|
|
355
|
+
|
|
356
|
+
if (isOpacityOnlyChange) {
|
|
357
|
+
setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
|
|
358
|
+
previousStateRef.current = newState;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Check if only units changed
|
|
362
|
+
const isUnitsOnlyChange =
|
|
363
|
+
hasInitialLoad.current &&
|
|
364
|
+
newState.model === previousStateRef.current.model &&
|
|
365
|
+
newState.isMRMS === previousStateRef.current.isMRMS &&
|
|
366
|
+
newState.variable === previousStateRef.current.variable &&
|
|
367
|
+
newState.date === previousStateRef.current.date &&
|
|
368
|
+
newState.run === previousStateRef.current.run &&
|
|
369
|
+
newState.forecastHour === previousStateRef.current.forecastHour &&
|
|
370
|
+
newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp &&
|
|
371
|
+
newState.units !== previousStateRef.current.units;
|
|
372
|
+
|
|
373
|
+
if (isUnitsOnlyChange) {
|
|
374
|
+
const { variable, units, isMRMS, mrmsTimestamp, model, date, run, forecastHour } = newState;
|
|
375
|
+
|
|
376
|
+
const oldCacheKey = isMRMS
|
|
377
|
+
? `mrms-${mrmsTimestamp}-${variable}-${previousStateRef.current.units}`
|
|
378
|
+
: `${model}-${date}-${run}-${forecastHour}-${variable}-${previousStateRef.current.units}`;
|
|
379
|
+
|
|
380
|
+
const cachedData = preloadedDataCache.current.get(oldCacheKey);
|
|
381
|
+
|
|
382
|
+
if (cachedData && cachedData.originalScale !== undefined && cachedData.originalOffset !== undefined) {
|
|
383
|
+
const { baseUnit } = core._getColormapForVariable(variable);
|
|
384
|
+
const toUnit = core._getTargetUnit(baseUnit, units);
|
|
385
|
+
|
|
386
|
+
let dataScale = cachedData.originalScale;
|
|
387
|
+
let dataOffset = cachedData.originalOffset;
|
|
388
|
+
|
|
389
|
+
if (baseUnit !== toUnit) {
|
|
390
|
+
const conversionFunc = getUnitConversionFunction(baseUnit, toUnit); // ✅ Use SDK function
|
|
391
|
+
if (conversionFunc) {
|
|
392
|
+
const convertedOffset = conversionFunc(dataOffset);
|
|
393
|
+
const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
|
|
394
|
+
dataScale = convertedOffsetPlusScale - convertedOffset;
|
|
395
|
+
dataOffset = convertedOffset;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ONLY update the inspector cache parameters - don't touch GPU or colormap!
|
|
400
|
+
if (gridLayerRef.current && gridLayerRef.current.updateDataParameters) {
|
|
401
|
+
gridLayerRef.current.updateDataParameters(dataScale, dataOffset, cachedData.missing);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Create new cache entry with new units
|
|
405
|
+
const newCacheKey = isMRMS
|
|
406
|
+
? `mrms-${mrmsTimestamp}-${variable}-${units}`
|
|
407
|
+
: `${model}-${date}-${run}-${forecastHour}-${variable}-${units}`;
|
|
408
|
+
|
|
409
|
+
preloadedDataCache.current.set(newCacheKey, {
|
|
410
|
+
...cachedData,
|
|
411
|
+
scale: dataScale,
|
|
412
|
+
offset: dataOffset
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
previousStateRef.current = newState;
|
|
417
|
+
|
|
418
|
+
if (newState.opacity !== renderProps.opacity) {
|
|
419
|
+
setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const needsFullLoad =
|
|
426
|
+
!hasInitialLoad.current ||
|
|
427
|
+
newState.model !== previousStateRef.current.model ||
|
|
428
|
+
newState.isMRMS !== previousStateRef.current.isMRMS ||
|
|
429
|
+
newState.variable !== previousStateRef.current.variable ||
|
|
430
|
+
newState.date !== previousStateRef.current.date ||
|
|
431
|
+
newState.run !== previousStateRef.current.run;
|
|
432
|
+
// REMOVED: || newState.units !== previousStateRef.current.units;
|
|
433
|
+
|
|
434
|
+
if (needsFullLoad) {
|
|
435
|
+
preloadedDataCache.current.clear();
|
|
436
|
+
cachedGeometry.current = null;
|
|
437
|
+
cachedColormap.current = null;
|
|
438
|
+
WeatherFrameProcessorModule.cancelAllFrames();
|
|
439
|
+
|
|
440
|
+
if (!newState.variable) {
|
|
441
|
+
previousStateRef.current = newState;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const { model, date, run, forecastHour, variable, units, isMRMS, mrmsTimestamp } = newState;
|
|
447
|
+
|
|
448
|
+
const gridModel = isMRMS ? 'mrms' : model;
|
|
449
|
+
const { corners, gridDef } = core._getGridCornersAndDef(gridModel);
|
|
450
|
+
const { nx, ny } = gridDef.grid_params;
|
|
451
|
+
|
|
452
|
+
let resourcePath;
|
|
453
|
+
if (isMRMS) {
|
|
454
|
+
if (!mrmsTimestamp) {
|
|
455
|
+
previousStateRef.current = newState;
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const mrmsDate = new Date(mrmsTimestamp * 1000);
|
|
459
|
+
const y = mrmsDate.getUTCFullYear();
|
|
460
|
+
const m = (mrmsDate.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
461
|
+
const d = mrmsDate.getUTCDate().toString().padStart(2, '0');
|
|
462
|
+
resourcePath = `/grids/mrms/${y}${m}${d}/${mrmsTimestamp}/0/${variable}/0`;
|
|
463
|
+
} else {
|
|
464
|
+
resourcePath = `/grids/${model}/${date}/${run}/${forecastHour}/${variable}/0`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const url = `${core.baseGridUrl}${resourcePath}?apiKey=${core.apiKey}`;
|
|
468
|
+
const options = { url, apiKey: core.apiKey, bundleId: core.bundleId };
|
|
469
|
+
|
|
470
|
+
const result = await new Promise((resolve, reject) => {
|
|
471
|
+
WeatherFrameProcessorModule.processFrame(options, (error, res) => {
|
|
472
|
+
if (error) reject(new Error(error));
|
|
473
|
+
else resolve(res);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
if (result && gridLayerRef.current) {
|
|
478
|
+
gridLayerRef.current.updateGeometry(corners, gridDef);
|
|
479
|
+
cachedGeometry.current = { model: gridModel, variable };
|
|
480
|
+
|
|
481
|
+
const { colormap, baseUnit } = core._getColormapForVariable(variable);
|
|
482
|
+
const toUnit = core._getTargetUnit(baseUnit, units);
|
|
483
|
+
|
|
484
|
+
let dataScale = result.scale;
|
|
485
|
+
let dataOffset = result.offset;
|
|
486
|
+
|
|
487
|
+
// Store original scale/offset for unit conversion later
|
|
488
|
+
const originalScale = result.scale;
|
|
489
|
+
const originalOffset = result.offset;
|
|
490
|
+
|
|
491
|
+
if (baseUnit !== toUnit) {
|
|
492
|
+
const conversionFunc = getUnitConversionFunction(baseUnit, toUnit); // ✅ Use SDK function
|
|
493
|
+
if (conversionFunc) {
|
|
494
|
+
const convertedOffset = conversionFunc(dataOffset);
|
|
495
|
+
const convertedOffsetPlusScale = conversionFunc(dataOffset + dataScale);
|
|
496
|
+
dataScale = convertedOffsetPlusScale - convertedOffset;
|
|
497
|
+
dataOffset = convertedOffset;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (result.dataAsBase64) {
|
|
502
|
+
const binaryString = atob(result.dataAsBase64);
|
|
503
|
+
const uint8Array = new Uint8Array(binaryString.length);
|
|
504
|
+
|
|
505
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
506
|
+
uint8Array[i] = binaryString.charCodeAt(i);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
currentGridDataRef.current = {
|
|
510
|
+
data: uint8Array,
|
|
511
|
+
nx, ny,
|
|
512
|
+
scale: dataScale,
|
|
513
|
+
offset: dataOffset,
|
|
514
|
+
missing: result.missing,
|
|
515
|
+
gridDef: gridDef,
|
|
516
|
+
variable: variable,
|
|
517
|
+
units: units
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Now upload to GPU
|
|
522
|
+
if (result.filePath) {
|
|
523
|
+
gridLayerRef.current.updateDataTextureFromFile(
|
|
524
|
+
result.filePath, nx, ny,
|
|
525
|
+
dataScale, dataOffset, result.missing
|
|
526
|
+
);
|
|
527
|
+
} else if (result.dataAsBase64) {
|
|
528
|
+
gridLayerRef.current.updateDataTexture(
|
|
529
|
+
result.dataAsBase64, nx, ny,
|
|
530
|
+
dataScale, dataOffset, result.missing
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const finalColormap = core._convertColormapUnits(colormap, baseUnit, toUnit);
|
|
535
|
+
const dataRange = [finalColormap[0], finalColormap[finalColormap.length - 2]];
|
|
536
|
+
const colormapBytes = _generateColormapBytes(finalColormap);
|
|
537
|
+
const colormapAsBase64 = fromByteArray(colormapBytes);
|
|
538
|
+
gridLayerRef.current.updateColormapTexture(colormapAsBase64);
|
|
539
|
+
cachedColormap.current = { key: `${variable}-${units}` };
|
|
540
|
+
cachedDataRange.current = dataRange;
|
|
541
|
+
|
|
542
|
+
setRenderProps({ opacity: newState.opacity, dataRange: dataRange });
|
|
543
|
+
|
|
544
|
+
hasInitialLoad.current = true;
|
|
545
|
+
|
|
546
|
+
const cacheKey = isMRMS
|
|
547
|
+
? `mrms-${mrmsTimestamp}-${variable}-${units}`
|
|
548
|
+
: `${model}-${date}-${run}-${forecastHour}-${variable}-${units}`;
|
|
549
|
+
|
|
550
|
+
const dataToCache = {
|
|
551
|
+
nx, ny,
|
|
552
|
+
scale: dataScale,
|
|
553
|
+
offset: dataOffset,
|
|
554
|
+
originalScale: originalScale, // Store original for unit conversion
|
|
555
|
+
originalOffset: originalOffset, // Store original for unit conversion
|
|
556
|
+
missing: result.missing,
|
|
557
|
+
corners, gridDef
|
|
558
|
+
};
|
|
559
|
+
if (result.filePath) dataToCache.filePath = result.filePath;
|
|
560
|
+
if (result.dataAsBase64) dataToCache.dataAsBase64 = result.dataAsBase64;
|
|
561
|
+
|
|
562
|
+
preloadedDataCache.current.set(cacheKey, dataToCache);
|
|
563
|
+
|
|
564
|
+
preloadAllFramesToDisk(newState);
|
|
565
|
+
}
|
|
566
|
+
} catch (error) {
|
|
567
|
+
console.error("❌ [State Change] Failed to load initial frame via native module:", error);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
} else if (newState.forecastHour !== previousStateRef.current.forecastHour || (newState.isMRMS && newState.mrmsTimestamp !== previousStateRef.current.mrmsTimestamp)) {
|
|
571
|
+
updateGPUWithCachedData(newState);
|
|
572
|
+
|
|
573
|
+
if (newState.opacity !== renderProps.opacity) {
|
|
574
|
+
setRenderProps(prev => ({ ...prev, opacity: newState.opacity }));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
previousStateRef.current = newState;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
handleStateChangeRef.current = handleStateChange;
|
|
581
|
+
|
|
582
|
+
const stableHandler = (newState) => {
|
|
583
|
+
const isOpacityOnlyChange =
|
|
584
|
+
previousStateRef.current &&
|
|
585
|
+
newState.opacity !== previousStateRef.current.opacity &&
|
|
586
|
+
newState.variable === previousStateRef.current.variable &&
|
|
587
|
+
newState.forecastHour === previousStateRef.current.forecastHour &&
|
|
588
|
+
newState.mrmsTimestamp === previousStateRef.current.mrmsTimestamp &&
|
|
589
|
+
newState.model === previousStateRef.current.model &&
|
|
590
|
+
newState.units === previousStateRef.current.units &&
|
|
591
|
+
newState.date === previousStateRef.current.date &&
|
|
592
|
+
newState.run === previousStateRef.current.run;
|
|
593
|
+
|
|
594
|
+
if (isOpacityOnlyChange) {
|
|
595
|
+
if (handleStateChangeRef.current) {
|
|
596
|
+
handleStateChangeRef.current(newState);
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (debounceTimeoutRef.current) {
|
|
602
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
debounceTimeoutRef.current = setTimeout(() => {
|
|
606
|
+
if (handleStateChangeRef.current) {
|
|
607
|
+
handleStateChangeRef.current(newState);
|
|
608
|
+
}
|
|
609
|
+
debounceTimeoutRef.current = null;
|
|
610
|
+
}, 50);
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
core.on('state:change', stableHandler);
|
|
614
|
+
|
|
615
|
+
return () => {
|
|
616
|
+
core.off('state:change', stableHandler);
|
|
617
|
+
if (debounceTimeoutRef.current) {
|
|
618
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
}, [core]);
|
|
622
|
+
|
|
623
|
+
useEffect(() => {
|
|
624
|
+
return () => {
|
|
625
|
+
preloadedDataCache.current.clear();
|
|
626
|
+
hasInitialLoad.current = false;
|
|
627
|
+
lastProcessedState.current = null;
|
|
628
|
+
};
|
|
629
|
+
}, []);
|
|
630
|
+
|
|
631
|
+
const lastInspectorUpdateRef = useRef(0);
|
|
632
|
+
const INSPECTOR_THROTTLE_MS = 50;
|
|
633
|
+
|
|
634
|
+
useEffect(() => {
|
|
635
|
+
if (!core || !inspectorEnabled) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const handleMapMove = async (center) => {
|
|
640
|
+
if (!center || !Array.isArray(center) || center.length !== 2) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Throttle updates
|
|
645
|
+
const now = Date.now();
|
|
646
|
+
if (now - lastInspectorUpdateRef.current < INSPECTOR_THROTTLE_MS) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
lastInspectorUpdateRef.current = now;
|
|
650
|
+
|
|
651
|
+
const [longitude, latitude] = center;
|
|
652
|
+
|
|
653
|
+
const payload = await getValueAtPoint(longitude, latitude);
|
|
654
|
+
onInspect?.(payload);
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
core.on('map:move', handleMapMove);
|
|
658
|
+
|
|
659
|
+
if (context && context.getCenter) {
|
|
660
|
+
const center = context.getCenter();
|
|
661
|
+
if (center) {
|
|
662
|
+
handleMapMove(center);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return () => {
|
|
667
|
+
core.off('map:move', handleMapMove);
|
|
668
|
+
};
|
|
669
|
+
}, [inspectorEnabled, onInspect, core, context]);
|
|
670
|
+
|
|
671
|
+
// Add this new useEffect after the existing inspector useEffect
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
// Trigger re-inspection when state changes (variable, model, forecast hour, etc.)
|
|
674
|
+
if (!core || !inspectorEnabled) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const triggerReinspection = () => {
|
|
679
|
+
const mapRef = mapRegistry.getMap();
|
|
680
|
+
const center = mapRef?._currentCenter;
|
|
681
|
+
|
|
682
|
+
if (center && Array.isArray(center) && center.length === 2) {
|
|
683
|
+
const [longitude, latitude] = center;
|
|
684
|
+
|
|
685
|
+
getValueAtPoint(longitude, latitude).then(payload => {
|
|
686
|
+
onInspect?.(payload);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// Small delay to ensure data is loaded before re-inspecting
|
|
692
|
+
const timer = setTimeout(triggerReinspection, 100);
|
|
693
|
+
|
|
694
|
+
return () => clearTimeout(timer);
|
|
695
|
+
}, [
|
|
696
|
+
core?.state?.variable,
|
|
697
|
+
core?.state?.model,
|
|
698
|
+
core?.state?.forecastHour,
|
|
699
|
+
core?.state?.mrmsTimestamp,
|
|
700
|
+
core?.state?.units,
|
|
701
|
+
inspectorEnabled,
|
|
702
|
+
onInspect
|
|
703
|
+
]);
|
|
704
|
+
|
|
705
|
+
useEffect(() => {
|
|
706
|
+
if (!core) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const handleCameraChange = (center) => {
|
|
711
|
+
if (core && center) {
|
|
712
|
+
core.setMapCenter(center);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// Register with the global registry
|
|
717
|
+
mapRegistry.addCameraListener(handleCameraChange);
|
|
718
|
+
|
|
719
|
+
// Try to get initial center
|
|
720
|
+
const mapRef = mapRegistry.getMap();
|
|
721
|
+
if (mapRef?._currentCenter) {
|
|
722
|
+
handleCameraChange(mapRef._currentCenter);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return () => {
|
|
726
|
+
mapRegistry.removeCameraListener(handleCameraChange);
|
|
727
|
+
};
|
|
728
|
+
}, [core]);
|
|
729
|
+
|
|
730
|
+
useEffect(() => {
|
|
731
|
+
core.initialize();
|
|
732
|
+
return () => {
|
|
733
|
+
core.destroy();
|
|
734
|
+
};
|
|
735
|
+
}, [core]);
|
|
736
|
+
|
|
737
|
+
return (
|
|
738
|
+
<GridRenderLayer
|
|
739
|
+
ref={gridLayerRef}
|
|
740
|
+
opacity={renderProps.opacity}
|
|
741
|
+
dataRange={renderProps.dataRange}
|
|
742
|
+
belowID="AML_-_terrain"
|
|
743
|
+
/>
|
|
744
|
+
);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
WeatherLayerManager.getAvailableVariables = (options) => {
|
|
748
|
+
if (!options || !options.apiKey) {
|
|
749
|
+
console.error("API key must be provided to get available variables.");
|
|
750
|
+
return [];
|
|
751
|
+
}
|
|
752
|
+
const core = new AguaceroCore({ apiKey: options.apiKey });
|
|
753
|
+
return core.getAvailableVariables('mrms');
|
|
754
|
+
};
|