@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,145 @@
|
|
|
1
|
+
package com.aguacerowx.reactnative;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import android.util.Base64;
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.bridge.Arguments;
|
|
7
|
+
import com.facebook.react.bridge.Callback;
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
9
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
10
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
11
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
12
|
+
import com.facebook.react.bridge.WritableMap;
|
|
13
|
+
|
|
14
|
+
import org.json.JSONObject;
|
|
15
|
+
|
|
16
|
+
import java.io.ByteArrayOutputStream;
|
|
17
|
+
import java.io.File;
|
|
18
|
+
import java.io.FileOutputStream;
|
|
19
|
+
import java.io.IOException;
|
|
20
|
+
import java.net.HttpURLConnection;
|
|
21
|
+
import java.net.URL;
|
|
22
|
+
import java.nio.charset.StandardCharsets;
|
|
23
|
+
import java.util.concurrent.ExecutorService;
|
|
24
|
+
import java.util.concurrent.Executors;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
public class WeatherFrameProcessorModule extends ReactContextBaseJavaModule {
|
|
28
|
+
|
|
29
|
+
private final ExecutorService executorService;
|
|
30
|
+
private volatile int currentRunToken = 0;
|
|
31
|
+
private final ReactApplicationContext reactContext;
|
|
32
|
+
|
|
33
|
+
WeatherFrameProcessorModule(ReactApplicationContext context) {
|
|
34
|
+
super(context);
|
|
35
|
+
this.reactContext = context;
|
|
36
|
+
|
|
37
|
+
int coreCount = Runtime.getRuntime().availableProcessors();
|
|
38
|
+
int optimalWorkerCount = Math.max(4, Math.min(coreCount - 1, 8));
|
|
39
|
+
|
|
40
|
+
this.executorService = Executors.newFixedThreadPool(optimalWorkerCount);
|
|
41
|
+
android.util.Log.d("AguaceroWX", "Initialized with " + optimalWorkerCount + " workers (" + coreCount + " cores available)");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@ReactMethod
|
|
45
|
+
public void cancelAllFrames() {
|
|
46
|
+
currentRunToken++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@NonNull
|
|
50
|
+
@Override
|
|
51
|
+
public String getName() {
|
|
52
|
+
return "WeatherFrameProcessorModule";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@ReactMethod
|
|
56
|
+
public void processFrame(ReadableMap options, Callback callback) {
|
|
57
|
+
final int taskToken = this.currentRunToken;
|
|
58
|
+
executorService.execute(() -> {
|
|
59
|
+
FileOutputStream fos = null;
|
|
60
|
+
try {
|
|
61
|
+
if (taskToken != this.currentRunToken) {
|
|
62
|
+
// Task was cancelled, do nothing.
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 1. Network Request (This part is perfect, no changes needed)
|
|
67
|
+
String urlString = options.getString("url");
|
|
68
|
+
String apiKey = options.getString("apiKey");
|
|
69
|
+
String bundleId = options.getString("bundleId");
|
|
70
|
+
URL requestUrl = new URL(urlString);
|
|
71
|
+
HttpURLConnection conn = (HttpURLConnection) requestUrl.openConnection();
|
|
72
|
+
conn.setRequestMethod("GET");
|
|
73
|
+
conn.setRequestProperty("x-api-key", apiKey);
|
|
74
|
+
if (bundleId != null) {
|
|
75
|
+
conn.setRequestProperty("x-app-identifier", bundleId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (conn.getResponseCode() != 200) {
|
|
79
|
+
throw new IOException("HTTP Error: " + conn.getResponseCode());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
|
83
|
+
byte[] buffer = new byte[8192];
|
|
84
|
+
int length;
|
|
85
|
+
while ((length = conn.getInputStream().read(buffer)) != -1) {
|
|
86
|
+
result.write(buffer, 0, length);
|
|
87
|
+
}
|
|
88
|
+
String responseString = result.toString(StandardCharsets.UTF_8.name());
|
|
89
|
+
JSONObject jsonResponse = new JSONObject(responseString);
|
|
90
|
+
conn.disconnect();
|
|
91
|
+
|
|
92
|
+
String b64CompressedData = jsonResponse.getString("data");
|
|
93
|
+
JSONObject encoding = jsonResponse.getJSONObject("encoding");
|
|
94
|
+
|
|
95
|
+
// --- START OF EDITS ---
|
|
96
|
+
|
|
97
|
+
// 2. Decode Base64 into the RAW COMPRESSED bytes.
|
|
98
|
+
// DO NOT decompress it here.
|
|
99
|
+
byte[] compressedData = Base64.decode(b64CompressedData, Base64.DEFAULT);
|
|
100
|
+
|
|
101
|
+
// 3. Write the SMALL, COMPRESSED data to a file.
|
|
102
|
+
File cacheDir = reactContext.getCacheDir();
|
|
103
|
+
// Use a predictable filename to avoid creating duplicate files for the same frame
|
|
104
|
+
String fileName = "frame_" + urlString.hashCode() + ".zst";
|
|
105
|
+
File dataFile = new File(cacheDir, fileName);
|
|
106
|
+
|
|
107
|
+
fos = new FileOutputStream(dataFile);
|
|
108
|
+
fos.write(compressedData); // Write the compressed bytes
|
|
109
|
+
fos.flush();
|
|
110
|
+
fos.close();
|
|
111
|
+
fos = null; // Set to null after successful close
|
|
112
|
+
|
|
113
|
+
// 4. Prepare Success Response with the file path and metadata
|
|
114
|
+
WritableMap responseMap = Arguments.createMap();
|
|
115
|
+
responseMap.putString("filePath", dataFile.getAbsolutePath());
|
|
116
|
+
responseMap.putDouble("scale", encoding.getDouble("scale"));
|
|
117
|
+
responseMap.putDouble("offset", encoding.getDouble("offset"));
|
|
118
|
+
responseMap.putDouble("missing", encoding.getDouble("missing_quantized"));
|
|
119
|
+
|
|
120
|
+
// We still pass this for the very first frame to load instantly without a flicker.
|
|
121
|
+
// The preloader logic on the JS side will ignore this and just use the filePath.
|
|
122
|
+
responseMap.putString("dataAsBase64", b64CompressedData);
|
|
123
|
+
|
|
124
|
+
callback.invoke(null, responseMap);
|
|
125
|
+
|
|
126
|
+
// --- END OF EDITS ---
|
|
127
|
+
|
|
128
|
+
} catch (Exception e) {
|
|
129
|
+
// Don't invoke callback if task was cancelled, to avoid spamming logs
|
|
130
|
+
if (taskToken == this.currentRunToken) {
|
|
131
|
+
android.util.Log.e("AguaceroWX", "Error processing frame", e);
|
|
132
|
+
callback.invoke(e.getMessage(), null);
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
if (fos != null) {
|
|
136
|
+
try {
|
|
137
|
+
fos.close();
|
|
138
|
+
} catch (IOException e) {
|
|
139
|
+
android.util.Log.e("AguaceroWX", "Error closing file stream", e);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
precision highp float;
|
|
2
|
+
|
|
3
|
+
varying vec2 v_texCoord;
|
|
4
|
+
uniform sampler2D u_data_texture;
|
|
5
|
+
|
|
6
|
+
void main() {
|
|
7
|
+
// Read the raw normalized value [0.0, 1.0] from the texture's alpha channel.
|
|
8
|
+
float raw_normalized_value = texture2D(u_data_texture, v_texCoord).a;
|
|
9
|
+
|
|
10
|
+
// Output this value directly as a grayscale color.
|
|
11
|
+
// If the data is valid, this will produce a black and white weather map.
|
|
12
|
+
gl_FragColor = vec4(raw_normalized_value, raw_normalized_value, raw_normalized_value, 1.0);
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
attribute vec2 a_position; // Vertex position in Mercator coordinates (0.0 to 1.0)
|
|
2
|
+
attribute vec2 a_texCoord;
|
|
3
|
+
|
|
4
|
+
varying vec2 v_texCoord;
|
|
5
|
+
|
|
6
|
+
void main() {
|
|
7
|
+
// This is a "passthrough" shader. It IGNORES the u_matrix from the map camera.
|
|
8
|
+
// It converts the input position (0..1) directly to OpenGL clip space (-1..1).
|
|
9
|
+
// This should paint a giant square over the entire screen, regardless of map pan/zoom.
|
|
10
|
+
gl_Position = vec4(a_position.x * 2.0 - 1.0, (1.0 - a_position.y) * 2.0 - 1.0, 0.0, 1.0);
|
|
11
|
+
|
|
12
|
+
v_texCoord = a_texCoord;
|
|
13
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// packages/react-native/android/src/main/res/raw/fragment_shader.glsl
|
|
2
|
+
|
|
3
|
+
precision highp float;
|
|
4
|
+
varying vec2 v_texCoord;
|
|
5
|
+
|
|
6
|
+
uniform sampler2D u_data_texture;
|
|
7
|
+
uniform sampler2D u_colormap_texture;
|
|
8
|
+
uniform float u_scale;
|
|
9
|
+
uniform float u_offset;
|
|
10
|
+
uniform float u_missing_quantized;
|
|
11
|
+
uniform float u_opacity;
|
|
12
|
+
uniform vec2 u_data_range;
|
|
13
|
+
uniform vec2 u_texture_size;
|
|
14
|
+
uniform int u_smoothing;
|
|
15
|
+
|
|
16
|
+
// Custom bilinear filtering that respects NaN boundaries
|
|
17
|
+
float sampleDataTextureSmooth(vec2 uv) {
|
|
18
|
+
float missing_in_texture_range = u_missing_quantized + 128.0;
|
|
19
|
+
|
|
20
|
+
// Get the four corner samples for bilinear interpolation
|
|
21
|
+
vec2 pixel_coord = uv * u_texture_size;
|
|
22
|
+
vec2 pixel_floor = floor(pixel_coord - 0.5) + 0.5;
|
|
23
|
+
vec2 f = pixel_coord - pixel_floor;
|
|
24
|
+
|
|
25
|
+
// Sample the 2x2 grid
|
|
26
|
+
float s00 = texture2D(u_data_texture, (pixel_floor + vec2(0.0, 0.0)) / u_texture_size).a * 255.0;
|
|
27
|
+
float s10 = texture2D(u_data_texture, (pixel_floor + vec2(1.0, 0.0)) / u_texture_size).a * 255.0;
|
|
28
|
+
float s01 = texture2D(u_data_texture, (pixel_floor + vec2(0.0, 1.0)) / u_texture_size).a * 255.0;
|
|
29
|
+
float s11 = texture2D(u_data_texture, (pixel_floor + vec2(1.0, 1.0)) / u_texture_size).a * 255.0;
|
|
30
|
+
|
|
31
|
+
// Check which samples are valid (not NaN)
|
|
32
|
+
bool valid00 = abs(s00 - missing_in_texture_range) >= 0.5;
|
|
33
|
+
bool valid10 = abs(s10 - missing_in_texture_range) >= 0.5;
|
|
34
|
+
bool valid01 = abs(s01 - missing_in_texture_range) >= 0.5;
|
|
35
|
+
bool valid11 = abs(s11 - missing_in_texture_range) >= 0.5;
|
|
36
|
+
|
|
37
|
+
// If ANY sample is NaN, return NaN (this pixel is on an edge)
|
|
38
|
+
if (!valid00 || !valid10 || !valid01 || !valid11) {
|
|
39
|
+
return missing_in_texture_range;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// All samples are valid - do proper bilinear interpolation
|
|
43
|
+
float s0 = mix(s00, s10, f.x);
|
|
44
|
+
float s1 = mix(s01, s11, f.x);
|
|
45
|
+
return mix(s0, s1, f.y);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Simple nearest-neighbor sampling
|
|
49
|
+
float sampleDataTextureNearest(vec2 uv) {
|
|
50
|
+
// Snap to exact pixel center
|
|
51
|
+
vec2 snapped_coord = floor(uv * u_texture_size + 0.5) / u_texture_size;
|
|
52
|
+
return texture2D(u_data_texture, snapped_coord).a * 255.0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void main() {
|
|
56
|
+
// Choose sampling method based on smoothing setting
|
|
57
|
+
float value_in_texture;
|
|
58
|
+
if (u_smoothing == 1) {
|
|
59
|
+
value_in_texture = sampleDataTextureSmooth(v_texCoord);
|
|
60
|
+
} else {
|
|
61
|
+
value_in_texture = sampleDataTextureNearest(v_texCoord);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
float missing_in_texture_range = u_missing_quantized + 128.0;
|
|
65
|
+
|
|
66
|
+
// Check if this pixel is a predefined missing value
|
|
67
|
+
if (abs(value_in_texture - missing_in_texture_range) < 0.5) {
|
|
68
|
+
discard;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// De-quantize the texture value to its actual physical value
|
|
72
|
+
float quantized_value = value_in_texture - 128.0;
|
|
73
|
+
float raw_value = quantized_value * u_scale + u_offset;
|
|
74
|
+
|
|
75
|
+
// If the data is below the specified minimum range, make it invisible.
|
|
76
|
+
if (raw_value < u_data_range.x) {
|
|
77
|
+
discard;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Normalize the value to a 0-1 coordinate for colormap lookup
|
|
81
|
+
float colormap_coord = (raw_value - u_data_range.x) / (u_data_range.y - u_data_range.x);
|
|
82
|
+
colormap_coord = clamp(colormap_coord, 0.0, 1.0);
|
|
83
|
+
|
|
84
|
+
// Sample the colormap and apply opacity
|
|
85
|
+
vec4 color = texture2D(u_colormap_texture, vec2(colormap_coord, 0.5));
|
|
86
|
+
gl_FragColor = vec4(color.rgb, color.a * u_opacity);
|
|
87
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The view-projection matrix from the Mapbox camera
|
|
2
|
+
uniform mat4 u_matrix;
|
|
3
|
+
|
|
4
|
+
// Attributes passed from the vertex buffer
|
|
5
|
+
attribute vec2 a_position; // Vertex position in Mercator coordinates (0.0 to 1.0)
|
|
6
|
+
attribute vec2 a_texCoord; // Texture coordinate (0.0 to 1.0)
|
|
7
|
+
|
|
8
|
+
// Passed to the fragment shader
|
|
9
|
+
varying vec2 v_texCoord;
|
|
10
|
+
|
|
11
|
+
void main() {
|
|
12
|
+
// This is the intended use.
|
|
13
|
+
// The u_matrix correctly transforms the 2D Mercator coordinate (a_position)
|
|
14
|
+
// into the final 4D clip space position for the GPU.
|
|
15
|
+
// We construct a vec4 with z=0.0 and w=1.0 to treat it as a point on the map's surface.
|
|
16
|
+
gl_Position = u_matrix * vec4(a_position, 0.0, 1.0);
|
|
17
|
+
|
|
18
|
+
// Pass the texture coordinate to the fragment shader
|
|
19
|
+
v_texCoord = a_texCoord;
|
|
20
|
+
}
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aguacerowx/react-native",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Native weather rendering for React Native",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"react-native": "index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"android",
|
|
10
|
+
"ios",
|
|
11
|
+
"*.podspec"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react-native",
|
|
15
|
+
"android",
|
|
16
|
+
"weather",
|
|
17
|
+
"map"
|
|
18
|
+
],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@rnmapbox/maps": "*",
|
|
21
|
+
"react": "*",
|
|
22
|
+
"react-native": "*"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@aguacerowx/javascript-sdk": "^0.0.4",
|
|
26
|
+
"base64-js": "^1.5.1",
|
|
27
|
+
"fzstd": "^0.1.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
32
|
+
},
|
|
33
|
+
"author": "Michael Barletta",
|
|
34
|
+
"license": "ISC"
|
|
35
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// packages/react-native/src/GridRenderLayer.js
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
|
|
4
|
+
import { UIManager, findNodeHandle, requireNativeComponent, StyleSheet } from 'react-native';
|
|
5
|
+
|
|
6
|
+
// This must match getName() in GridRenderManager.java
|
|
7
|
+
const NATIVE_COMPONENT_NAME = 'GridRenderLayer';
|
|
8
|
+
const GridRenderView = requireNativeComponent(NATIVE_COMPONENT_NAME);
|
|
9
|
+
|
|
10
|
+
export const GridRenderLayer = forwardRef((props, ref) => {
|
|
11
|
+
const nativeRef = useRef(null);
|
|
12
|
+
|
|
13
|
+
const getCommandId = (commandName) => {
|
|
14
|
+
const config = UIManager.getViewManagerConfig(NATIVE_COMPONENT_NAME);
|
|
15
|
+
return config?.Commands?.[commandName];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
useImperativeHandle(ref, () => ({
|
|
19
|
+
updateGeometry: (corners, gridDef) => {
|
|
20
|
+
if (nativeRef.current) {
|
|
21
|
+
const commandId = getCommandId('updateGeometry');
|
|
22
|
+
if (commandId != null) {
|
|
23
|
+
UIManager.dispatchViewManagerCommand(
|
|
24
|
+
findNodeHandle(nativeRef.current),
|
|
25
|
+
commandId,
|
|
26
|
+
[corners, gridDef]
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
console.warn('⚠️ [GridRenderLayer] nativeRef not available for updateGeometry');
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
updateDataTextureFromFile: (filePath, nx, ny, scale, offset, missing) => {
|
|
35
|
+
if (nativeRef.current) {
|
|
36
|
+
const commandId = getCommandId('updateDataTexture');
|
|
37
|
+
if (commandId != null) {
|
|
38
|
+
UIManager.dispatchViewManagerCommand(
|
|
39
|
+
findNodeHandle(nativeRef.current),
|
|
40
|
+
commandId,
|
|
41
|
+
[filePath, Number(nx), Number(ny), Number(scale), Number(offset), Number(missing)]
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
console.warn('⚠️ [GridRenderLayer] nativeRef not available for updateDataTextureFromFile');
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
updateDataTexture: (base64Data, nx, ny, scale, offset, missing) => {
|
|
50
|
+
if (nativeRef.current) {
|
|
51
|
+
const commandId = getCommandId('updateDataTexture');
|
|
52
|
+
if (commandId != null) {
|
|
53
|
+
UIManager.dispatchViewManagerCommand(
|
|
54
|
+
findNodeHandle(nativeRef.current),
|
|
55
|
+
commandId,
|
|
56
|
+
[base64Data, Number(nx), Number(ny), Number(scale), Number(offset), Number(missing)]
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
console.warn('⚠️ [GridRenderLayer] nativeRef not available for updateDataTexture');
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
updateColormapTexture: (colormapArray) => {
|
|
65
|
+
if (nativeRef.current) {
|
|
66
|
+
const commandId = getCommandId('updateColormapTexture');
|
|
67
|
+
if (commandId != null) {
|
|
68
|
+
UIManager.dispatchViewManagerCommand(
|
|
69
|
+
findNodeHandle(nativeRef.current),
|
|
70
|
+
commandId,
|
|
71
|
+
[colormapArray]
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
console.warn('⚠️ [GridRenderLayer] nativeRef not available for updateColormapTexture');
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
updateDataParameters: (scale, offset, missing) => {
|
|
80
|
+
if (nativeRef.current) {
|
|
81
|
+
const commandId = getCommandId('updateDataParameters');
|
|
82
|
+
if (commandId != null) {
|
|
83
|
+
UIManager.dispatchViewManagerCommand(
|
|
84
|
+
findNodeHandle(nativeRef.current),
|
|
85
|
+
commandId,
|
|
86
|
+
[Number(scale), Number(offset), Number(missing)]
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
console.warn('⚠️ [GridRenderLayer] nativeRef not available for updateDataParameters');
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
setSmoothing: (enabled) => {
|
|
95
|
+
if (nativeRef.current) {
|
|
96
|
+
// Smoothing can be set via props
|
|
97
|
+
nativeRef.current.setNativeProps({ smoothing: enabled });
|
|
98
|
+
} else {
|
|
99
|
+
console.warn('⚠️ [GridRenderLayer] nativeRef not available for setSmoothing');
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<GridRenderView
|
|
106
|
+
ref={nativeRef}
|
|
107
|
+
opacity={props.opacity}
|
|
108
|
+
dataRange={props.dataRange}
|
|
109
|
+
belowID={props.belowID}
|
|
110
|
+
smoothing={true}
|
|
111
|
+
style={styles.overlay}
|
|
112
|
+
pointerEvents="none"
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const styles = StyleSheet.create({
|
|
118
|
+
overlay: {
|
|
119
|
+
...StyleSheet.absoluteFillObject,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// packages/react-native/src/MapManager.js
|
|
2
|
+
|
|
3
|
+
import React, { useMemo, useEffect, useState, useRef } from 'react';
|
|
4
|
+
import { View, StyleSheet, Text } from 'react-native';
|
|
5
|
+
import Mapbox from '@rnmapbox/maps';
|
|
6
|
+
import { THEME_CONFIGS } from '@aguacerowx/javascript-sdk';
|
|
7
|
+
import { StyleApplicator } from './StyleApplicator';
|
|
8
|
+
import { AguaceroContext } from './AguaceroContext';
|
|
9
|
+
import { mapRegistry } from './MapRegistry';
|
|
10
|
+
|
|
11
|
+
const TERRAIN_LAYER_ID = 'AML_-_terrain';
|
|
12
|
+
|
|
13
|
+
// --- UTILITIES ---
|
|
14
|
+
function isObject(item) {
|
|
15
|
+
return (item && typeof item === 'object' && !Array.isArray(item));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function deepMerge(target, source) {
|
|
19
|
+
const output = { ...target };
|
|
20
|
+
if (isObject(target) && isObject(source)) {
|
|
21
|
+
Object.keys(source).forEach(key => {
|
|
22
|
+
if (isObject(source[key])) {
|
|
23
|
+
if (!(key in target)) {
|
|
24
|
+
Object.assign(output, { [key]: source[key] });
|
|
25
|
+
} else {
|
|
26
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
Object.assign(output, { [key]: source[key] });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return output;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const MapManager = ({ mapboxToken, customStyles, theme = 'light', mapOptions = {}, children }) => {
|
|
37
|
+
const mapRef = useRef(null);
|
|
38
|
+
const [mapStyle, setMapStyle] = useState(null);
|
|
39
|
+
|
|
40
|
+
// Register map with the global registry when it's ready
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (mapRef.current) {
|
|
43
|
+
mapRegistry.setMap(mapRef.current);
|
|
44
|
+
console.log('📍 [MapManager] Registered map with registry');
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const contextValue = useMemo(() => ({
|
|
49
|
+
getMap: () => mapRef.current,
|
|
50
|
+
getCenter: () => {
|
|
51
|
+
return mapRef.current?._currentCenter || [-95.7129, 37.0902];
|
|
52
|
+
},
|
|
53
|
+
}), []);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
let lightTheme = JSON.parse(JSON.stringify(THEME_CONFIGS.light));
|
|
57
|
+
let darkTheme = JSON.parse(JSON.stringify(THEME_CONFIGS.dark));
|
|
58
|
+
|
|
59
|
+
if (customStyles) {
|
|
60
|
+
if (customStyles.light) {
|
|
61
|
+
lightTheme = deepMerge(lightTheme, customStyles.light);
|
|
62
|
+
}
|
|
63
|
+
if (customStyles.dark) {
|
|
64
|
+
darkTheme = deepMerge(darkTheme, customStyles.dark);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setMapStyle(theme === 'dark' ? darkTheme : lightTheme);
|
|
69
|
+
}, [customStyles, theme]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (mapboxToken) {
|
|
73
|
+
Mapbox.setAccessToken(mapboxToken);
|
|
74
|
+
} else {
|
|
75
|
+
console.error("MapManager: A 'mapboxToken' prop is required but was not provided.");
|
|
76
|
+
}
|
|
77
|
+
}, [mapboxToken]);
|
|
78
|
+
|
|
79
|
+
if (!mapboxToken) {
|
|
80
|
+
return (
|
|
81
|
+
<View style={[styles.container, styles.centerContent]}>
|
|
82
|
+
<Text style={styles.errorText}>Mapbox Token Missing</Text>
|
|
83
|
+
<Text style={styles.errorSubText}>Please provide the 'mapboxToken' prop.</Text>
|
|
84
|
+
</View>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handleCameraChange = (event) => {
|
|
89
|
+
if (event?.properties?.center) {
|
|
90
|
+
const center = event.properties.center;
|
|
91
|
+
|
|
92
|
+
// Store the current center in the ref for immediate access
|
|
93
|
+
if (mapRef.current) {
|
|
94
|
+
mapRef.current._currentCenter = center;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Notify all listeners through the registry
|
|
98
|
+
console.log('📍 [MapManager] Camera changed, notifying registry:', center);
|
|
99
|
+
mapRegistry.notifyCameraChange(center);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const BASE_STYLE_URL = 'mapbox://styles/aguacerowx/cmfvox8mq004u01qm5nlg7qkt';
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<View style={styles.container}>
|
|
107
|
+
<AguaceroContext.Provider value={contextValue}>
|
|
108
|
+
<Mapbox.MapView
|
|
109
|
+
ref={mapRef}
|
|
110
|
+
style={styles.map}
|
|
111
|
+
styleURL={BASE_STYLE_URL}
|
|
112
|
+
scaleBarEnabled={false}
|
|
113
|
+
onCameraChanged={handleCameraChange}
|
|
114
|
+
>
|
|
115
|
+
<Mapbox.Camera
|
|
116
|
+
defaultSettings={{
|
|
117
|
+
centerCoordinate: mapOptions.center || [-95.7129, 37.0902],
|
|
118
|
+
zoomLevel: mapOptions.zoom || 4,
|
|
119
|
+
pitch: mapOptions.pitch || 0,
|
|
120
|
+
bearing: mapOptions.bearing || 0,
|
|
121
|
+
minZoomLevel: mapOptions.minZoom || 2,
|
|
122
|
+
maxZoomLevel: mapOptions.maxZoom || 12,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
{mapStyle && <StyleApplicator styles={mapStyle} />}
|
|
127
|
+
|
|
128
|
+
<Mapbox.VectorSource id="mapbox-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1"/>
|
|
129
|
+
{children}
|
|
130
|
+
</Mapbox.MapView>
|
|
131
|
+
</AguaceroContext.Provider>
|
|
132
|
+
</View>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const styles = StyleSheet.create({
|
|
137
|
+
container: {
|
|
138
|
+
flex: 1,
|
|
139
|
+
backgroundColor: '#1a1a2e',
|
|
140
|
+
},
|
|
141
|
+
centerContent: {
|
|
142
|
+
justifyContent: 'center',
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
},
|
|
145
|
+
map: {
|
|
146
|
+
flex: 1,
|
|
147
|
+
},
|
|
148
|
+
errorText: {
|
|
149
|
+
color: '#FF6B6B',
|
|
150
|
+
fontSize: 22,
|
|
151
|
+
fontWeight: 'bold',
|
|
152
|
+
},
|
|
153
|
+
errorSubText: {
|
|
154
|
+
color: 'white',
|
|
155
|
+
fontSize: 16,
|
|
156
|
+
marginTop: 8,
|
|
157
|
+
}
|
|
158
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Simple registry to share the map instance
|
|
2
|
+
class MapRegistry {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.mapRef = null;
|
|
5
|
+
this.listeners = [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
setMap(mapRef) {
|
|
9
|
+
console.log('📍 [MapRegistry] Map registered:', !!mapRef);
|
|
10
|
+
this.mapRef = mapRef;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getMap() {
|
|
14
|
+
return this.mapRef;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
addCameraListener(listener) {
|
|
18
|
+
this.listeners.push(listener);
|
|
19
|
+
console.log('📍 [MapRegistry] Listener added, total:', this.listeners.length);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
removeCameraListener(listener) {
|
|
23
|
+
const index = this.listeners.indexOf(listener);
|
|
24
|
+
if (index > -1) {
|
|
25
|
+
this.listeners.splice(index, 1);
|
|
26
|
+
console.log('📍 [MapRegistry] Listener removed, total:', this.listeners.length);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
notifyCameraChange(center) {
|
|
31
|
+
this.listeners.forEach(listener => listener(center));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const mapRegistry = new MapRegistry();
|