@aguacerowx/react-native 0.0.51 → 0.0.53
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/android/src/main/cpp/satellite_ktx_jni.cpp +6 -1
- package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayer.java +121 -1
- package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayerView.java +556 -384
- package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +315 -311
- package/ios/SatelliteLayerView.swift +517 -510
- package/ios/WeatherFrameProcessorModule.swift +222 -219
- package/lib/commonjs/WeatherLayerManager.js +82 -46
- package/lib/commonjs/WeatherLayerManager.js.map +1 -1
- package/lib/commonjs/aguaceroRnDebug.js +9 -1
- package/lib/commonjs/aguaceroRnDebug.js.map +1 -1
- package/lib/commonjs/gridCdnAuth.js +64 -0
- package/lib/commonjs/gridCdnAuth.js.map +1 -0
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/nexrad/nexradAndroidController.js +25 -25
- package/lib/commonjs/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/commonjs/nexrad/nexradDiag.js +24 -24
- package/lib/commonjs/satellite/satelliteAndroidController.js +32 -24
- package/lib/commonjs/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/commonjs/satelliteRnDebug.js +261 -0
- package/lib/commonjs/satelliteRnDebug.js.map +1 -0
- package/lib/module/WeatherLayerManager.js +82 -46
- package/lib/module/WeatherLayerManager.js.map +1 -1
- package/lib/module/aguaceroRnDebug.js +9 -1
- package/lib/module/aguaceroRnDebug.js.map +1 -1
- package/lib/module/gridCdnAuth.js +56 -0
- package/lib/module/gridCdnAuth.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/nexrad/nexradAndroidController.js +25 -25
- package/lib/module/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/module/nexrad/nexradDiag.js +24 -24
- package/lib/module/satellite/satelliteAndroidController.js +32 -24
- package/lib/module/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/module/satelliteRnDebug.js +248 -0
- package/lib/module/satelliteRnDebug.js.map +1 -0
- package/lib/typescript/WeatherLayerManager.d.ts.map +1 -1
- package/lib/typescript/aguaceroRnDebug.d.ts.map +1 -1
- package/lib/typescript/gridCdnAuth.d.ts +24 -0
- package/lib/typescript/gridCdnAuth.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/satellite/satelliteAndroidController.d.ts.map +1 -1
- package/lib/typescript/satelliteRnDebug.d.ts +81 -0
- package/lib/typescript/satelliteRnDebug.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/WeatherLayerManager.js +2044 -2004
- package/src/aguaceroRnDebug.js +9 -1
- package/src/gridCdnAuth.js +56 -0
- package/src/index.js +27 -15
- package/src/nexrad/nexradAndroidController.js +1078 -1078
- package/src/nexrad/nexradDiag.js +150 -150
- package/src/satellite/satelliteAndroidController.js +257 -245
- package/src/satelliteRnDebug.js +269 -0
|
@@ -1,384 +1,556 @@
|
|
|
1
|
-
package com.aguacerowx.reactnative;
|
|
2
|
-
|
|
3
|
-
import android.app.Activity;
|
|
4
|
-
import android.os.Handler;
|
|
5
|
-
import android.os.Looper;
|
|
6
|
-
import android.util.Log;
|
|
7
|
-
import android.view.View;
|
|
8
|
-
import android.view.ViewGroup;
|
|
9
|
-
import android.widget.FrameLayout;
|
|
10
|
-
|
|
11
|
-
import androidx.annotation.NonNull;
|
|
12
|
-
import androidx.annotation.Nullable;
|
|
13
|
-
|
|
14
|
-
import com.facebook.react.
|
|
15
|
-
import com.
|
|
16
|
-
import com.
|
|
17
|
-
import com.
|
|
18
|
-
import com.
|
|
19
|
-
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
|
|
27
|
-
import java.
|
|
28
|
-
import java.util.
|
|
29
|
-
import java.util.
|
|
30
|
-
import java.util.
|
|
31
|
-
|
|
32
|
-
import
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
|
|
36
|
-
import okhttp3.
|
|
37
|
-
import okhttp3.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
private
|
|
51
|
-
private String
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
private final
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
private final
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
private
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
1
|
+
package com.aguacerowx.reactnative;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.os.Handler;
|
|
5
|
+
import android.os.Looper;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
import android.view.View;
|
|
8
|
+
import android.view.ViewGroup;
|
|
9
|
+
import android.widget.FrameLayout;
|
|
10
|
+
|
|
11
|
+
import androidx.annotation.NonNull;
|
|
12
|
+
import androidx.annotation.Nullable;
|
|
13
|
+
|
|
14
|
+
import com.facebook.react.bridge.Arguments;
|
|
15
|
+
import com.facebook.react.bridge.ReactContext;
|
|
16
|
+
import com.facebook.react.bridge.WritableMap;
|
|
17
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
18
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
19
|
+
import com.mapbox.maps.LayerPosition;
|
|
20
|
+
import com.mapbox.maps.MapView;
|
|
21
|
+
import com.mapbox.maps.MapboxMap;
|
|
22
|
+
import com.mapbox.maps.Style;
|
|
23
|
+
|
|
24
|
+
import org.json.JSONArray;
|
|
25
|
+
import org.json.JSONObject;
|
|
26
|
+
|
|
27
|
+
import java.io.IOException;
|
|
28
|
+
import java.util.Collections;
|
|
29
|
+
import java.util.Locale;
|
|
30
|
+
import java.util.Set;
|
|
31
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
32
|
+
import java.util.concurrent.ExecutorService;
|
|
33
|
+
import java.util.concurrent.Executors;
|
|
34
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
35
|
+
|
|
36
|
+
import okhttp3.Call;
|
|
37
|
+
import okhttp3.HttpUrl;
|
|
38
|
+
import okhttp3.OkHttpClient;
|
|
39
|
+
import okhttp3.Request;
|
|
40
|
+
import okhttp3.Response;
|
|
41
|
+
import okhttp3.ResponseBody;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hosts {@link SatelliteLayer} on the app {@link MapView} (Android satellite path — parity with mapsgl
|
|
45
|
+
* {@code WeatherLayerManager} + {@code SatelliteShaderManager}).
|
|
46
|
+
*/
|
|
47
|
+
public class SatelliteLayerView extends FrameLayout {
|
|
48
|
+
|
|
49
|
+
private static final String TAG = "AguaceroSatelliteView";
|
|
50
|
+
private static final String DIAG_TAG = "AguaceroRN";
|
|
51
|
+
private static final String DIAG_EVENT = "AguaceroSatelliteDiagnostic";
|
|
52
|
+
|
|
53
|
+
private final ThemedReactContext reactContext;
|
|
54
|
+
private final SatelliteLayer satelliteLayer;
|
|
55
|
+
private final String layerId;
|
|
56
|
+
private MapView mapView;
|
|
57
|
+
private boolean isLayerAdded = false;
|
|
58
|
+
private String belowLayerID = null;
|
|
59
|
+
private volatile boolean debugDiagnostics = false;
|
|
60
|
+
private volatile long lastTargetUnix = Long.MIN_VALUE;
|
|
61
|
+
private final java.util.concurrent.atomic.AtomicInteger fetchOkCount =
|
|
62
|
+
new java.util.concurrent.atomic.AtomicInteger(0);
|
|
63
|
+
private final java.util.concurrent.atomic.AtomicInteger fetchFailCount =
|
|
64
|
+
new java.util.concurrent.atomic.AtomicInteger(0);
|
|
65
|
+
|
|
66
|
+
/** Concurrent fetch+decode so the priority frame is not blocked behind the rest of the timeline. */
|
|
67
|
+
private final ExecutorService satDecodeExecutor =
|
|
68
|
+
Executors.newFixedThreadPool(
|
|
69
|
+
4,
|
|
70
|
+
r -> {
|
|
71
|
+
Thread t = new Thread(r, "aguacero-satellite-decode");
|
|
72
|
+
t.setPriority(Thread.NORM_PRIORITY - 1);
|
|
73
|
+
return t;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
77
|
+
private final AtomicInteger decodeGeneration = new AtomicInteger(0);
|
|
78
|
+
|
|
79
|
+
private volatile String lastRunKey = null;
|
|
80
|
+
|
|
81
|
+
private final Set<Long> fetchInFlight =
|
|
82
|
+
Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
|
|
83
|
+
|
|
84
|
+
private static final OkHttpClient HTTP =
|
|
85
|
+
new OkHttpClient.Builder()
|
|
86
|
+
.followRedirects(true)
|
|
87
|
+
.followSslRedirects(true)
|
|
88
|
+
.build();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Match grid {@code buildGridFrameProcessOptions}: authoritative {@code apiKey} in the query string
|
|
92
|
+
* plus {@code x-api-key} header (same pattern as AguaceroCore grid / NEXRAD CDN).
|
|
93
|
+
*/
|
|
94
|
+
private static String satelliteUrlWithQueryApiKey(String url, String apiKey) {
|
|
95
|
+
HttpUrl parsed = HttpUrl.parse(url);
|
|
96
|
+
if (parsed == null) {
|
|
97
|
+
return url;
|
|
98
|
+
}
|
|
99
|
+
HttpUrl.Builder b = parsed.newBuilder();
|
|
100
|
+
b.setQueryParameter("apiKey", apiKey);
|
|
101
|
+
if (parsed.queryParameter("userId") == null) {
|
|
102
|
+
b.setQueryParameter("userId", "sdk-user");
|
|
103
|
+
}
|
|
104
|
+
return b.build().toString();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public SatelliteLayerView(@NonNull ThemedReactContext context) {
|
|
108
|
+
super(context);
|
|
109
|
+
this.reactContext = context;
|
|
110
|
+
this.satelliteLayer = new SatelliteLayer();
|
|
111
|
+
this.layerId = AguaceroStyleLayerIds.SATELLITE_CUSTOM_LAYER;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private void emitDiagnostic(@NonNull String phase, @Nullable WritableMap extra) {
|
|
115
|
+
if (!debugDiagnostics) return;
|
|
116
|
+
try {
|
|
117
|
+
WritableMap m = Arguments.createMap();
|
|
118
|
+
m.putString("phase", phase);
|
|
119
|
+
m.putBoolean("mapLayerAdded", isLayerAdded);
|
|
120
|
+
m.putBoolean("mapViewFound", mapView != null);
|
|
121
|
+
if (lastTargetUnix != Long.MIN_VALUE) {
|
|
122
|
+
m.putDouble("targetUnix", lastTargetUnix);
|
|
123
|
+
}
|
|
124
|
+
m.putInt("fetchOkCount", fetchOkCount.get());
|
|
125
|
+
m.putInt("fetchFailCount", fetchFailCount.get());
|
|
126
|
+
java.util.HashMap<String, Object> snap = new java.util.HashMap<>();
|
|
127
|
+
satelliteLayer.fillDebugSnapshot(snap);
|
|
128
|
+
for (java.util.Map.Entry<String, Object> e : snap.entrySet()) {
|
|
129
|
+
Object v = e.getValue();
|
|
130
|
+
if (v == null) continue;
|
|
131
|
+
String k = e.getKey();
|
|
132
|
+
if (v instanceof Boolean) m.putBoolean(k, (Boolean) v);
|
|
133
|
+
else if (v instanceof Integer) m.putInt(k, (Integer) v);
|
|
134
|
+
else if (v instanceof Long) m.putDouble(k, ((Long) v).doubleValue());
|
|
135
|
+
else if (v instanceof Float) m.putDouble(k, ((Float) v).doubleValue());
|
|
136
|
+
else if (v instanceof Double) m.putDouble(k, (Double) v);
|
|
137
|
+
}
|
|
138
|
+
if (extra != null) {
|
|
139
|
+
com.facebook.react.bridge.ReadableMapKeySetIterator it = extra.keySetIterator();
|
|
140
|
+
while (it.hasNextKey()) {
|
|
141
|
+
String key = it.nextKey();
|
|
142
|
+
switch (extra.getType(key)) {
|
|
143
|
+
case String:
|
|
144
|
+
m.putString(key, extra.getString(key));
|
|
145
|
+
break;
|
|
146
|
+
case Number:
|
|
147
|
+
m.putDouble(key, extra.getDouble(key));
|
|
148
|
+
break;
|
|
149
|
+
case Boolean:
|
|
150
|
+
m.putBoolean(key, extra.getBoolean(key));
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
Log.i(DIAG_TAG, "[satellite.native] " + phase + " " + m.toString());
|
|
158
|
+
ReactContext ctx = reactContext.getReactApplicationContext();
|
|
159
|
+
if (ctx != null && ctx.hasActiveReactInstance()) {
|
|
160
|
+
reactContext.runOnUiQueueThread(
|
|
161
|
+
() -> {
|
|
162
|
+
try {
|
|
163
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
164
|
+
.emit(DIAG_EVENT, m);
|
|
165
|
+
} catch (Throwable t) {
|
|
166
|
+
Log.w(TAG, "emit " + DIAG_EVENT + " failed", t);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
} catch (Throwable t) {
|
|
171
|
+
Log.w(TAG, "emitDiagnostic failed phase=" + phase, t);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private static void putMapValue(@NonNull WritableMap m, @NonNull String k, @Nullable Object v) {
|
|
176
|
+
if (v == null) return;
|
|
177
|
+
if (v instanceof String) m.putString(k, (String) v);
|
|
178
|
+
else if (v instanceof Integer) m.putInt(k, (Integer) v);
|
|
179
|
+
else if (v instanceof Long) m.putDouble(k, ((Long) v).doubleValue());
|
|
180
|
+
else if (v instanceof Boolean) m.putBoolean(k, (Boolean) v);
|
|
181
|
+
else if (v instanceof Double) m.putDouble(k, (Double) v);
|
|
182
|
+
else if (v instanceof Float) m.putDouble(k, ((Float) v).doubleValue());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Alternating keys and values: {@code mapOf("a", 1, "b", true)} */
|
|
186
|
+
private static WritableMap mapOf(Object... keysAndValues) {
|
|
187
|
+
WritableMap m = Arguments.createMap();
|
|
188
|
+
if (keysAndValues == null) return m;
|
|
189
|
+
for (int i = 0; i + 1 < keysAndValues.length; i += 2) {
|
|
190
|
+
putMapValue(m, String.valueOf(keysAndValues[i]), keysAndValues[i + 1]);
|
|
191
|
+
}
|
|
192
|
+
return m;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public void setBelowID(String belowID) {
|
|
196
|
+
this.belowLayerID = belowID;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@Override
|
|
200
|
+
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
201
|
+
super.onLayout(changed, left, top, right, bottom);
|
|
202
|
+
findMapViewAndAddLayer();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private void findMapViewAndAddLayer() {
|
|
206
|
+
if (mapView != null) {
|
|
207
|
+
Style existing = mapView.getMapboxMap().getStyle();
|
|
208
|
+
if (existing != null && existing.isStyleLoaded() && existing.styleLayerExists(layerId)) {
|
|
209
|
+
isLayerAdded = true;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Activity activity = ((ThemedReactContext) getContext()).getCurrentActivity();
|
|
215
|
+
if (activity == null) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
View rootView = activity.findViewById(android.R.id.content);
|
|
219
|
+
if (rootView == null) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
MapView foundMap = findMapViewRecursive(rootView);
|
|
223
|
+
if (foundMap == null) {
|
|
224
|
+
emitDiagnostic("map.viewNotFound", null);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.mapView = foundMap;
|
|
228
|
+
MapboxMap mapboxMap = mapView.getMapboxMap();
|
|
229
|
+
Style.OnStyleLoaded styleLoadedCallback =
|
|
230
|
+
style -> {
|
|
231
|
+
if (!style.styleLayerExists(layerId)) {
|
|
232
|
+
LayerPosition position = null;
|
|
233
|
+
if (style.styleLayerExists(AguaceroStyleLayerIds.WEATHER_GRID_CUSTOM_LAYER)) {
|
|
234
|
+
position =
|
|
235
|
+
new LayerPosition(
|
|
236
|
+
AguaceroStyleLayerIds.WEATHER_GRID_CUSTOM_LAYER, null, null);
|
|
237
|
+
} else if (this.belowLayerID != null && style.styleLayerExists(this.belowLayerID)) {
|
|
238
|
+
position = new LayerPosition(null, this.belowLayerID, null);
|
|
239
|
+
}
|
|
240
|
+
style.addStyleCustomLayer(layerId, satelliteLayer, position);
|
|
241
|
+
isLayerAdded = true;
|
|
242
|
+
emitDiagnostic("map.layerAdded", mapOf("layerId", layerId));
|
|
243
|
+
triggerRepaint();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
Style currentStyle = mapboxMap.getStyle();
|
|
247
|
+
if (currentStyle != null && currentStyle.isStyleLoaded()) {
|
|
248
|
+
styleLoadedCallback.onStyleLoaded(currentStyle);
|
|
249
|
+
} else {
|
|
250
|
+
mapboxMap.getStyle(styleLoadedCallback);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private MapView findMapViewRecursive(View view) {
|
|
255
|
+
if (view instanceof MapView) return (MapView) view;
|
|
256
|
+
if (view instanceof ViewGroup) {
|
|
257
|
+
ViewGroup vg = (ViewGroup) view;
|
|
258
|
+
for (int i = 0; i < vg.getChildCount(); i++) {
|
|
259
|
+
MapView f = findMapViewRecursive(vg.getChildAt(i));
|
|
260
|
+
if (f != null) return f;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private void triggerRepaint() {
|
|
267
|
+
if (mapView != null) mapView.post(mapView.getMapboxMap()::triggerRepaint);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static final class FrameClass {
|
|
271
|
+
final boolean trueColor;
|
|
272
|
+
@NonNull final String colormapKind;
|
|
273
|
+
final boolean useColormap;
|
|
274
|
+
|
|
275
|
+
FrameClass(boolean trueColor, @NonNull String colormapKind, boolean useColormap) {
|
|
276
|
+
this.trueColor = trueColor;
|
|
277
|
+
this.colormapKind = colormapKind;
|
|
278
|
+
this.useColormap = useColormap;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static FrameClass classifyShaderFileName(@Nullable String shaderFileName) {
|
|
283
|
+
String fn = shaderFileName != null ? shaderFileName : "";
|
|
284
|
+
boolean isTrueColor =
|
|
285
|
+
fn.contains("_truecolor_")
|
|
286
|
+
|| fn.contains("_geocolor_")
|
|
287
|
+
|| fn.contains("_firetemperature_")
|
|
288
|
+
|| fn.contains("_dust_")
|
|
289
|
+
|| fn.contains("_simplewatervapor_")
|
|
290
|
+
|| fn.contains("_ntmicro_")
|
|
291
|
+
|| fn.contains("_daycloudphase_")
|
|
292
|
+
|| fn.contains("_daylandcloudfire_")
|
|
293
|
+
|| fn.contains("_airmass_")
|
|
294
|
+
|| fn.contains("_sandwich_");
|
|
295
|
+
|
|
296
|
+
String colormapType = "none";
|
|
297
|
+
if (fn.contains("C13") || fn.contains("C14") || fn.contains("C15") || fn.contains("C16")) {
|
|
298
|
+
colormapType = "ir";
|
|
299
|
+
} else if (fn.contains("C08") || fn.contains("C09") || fn.contains("C10")) {
|
|
300
|
+
colormapType = "wv";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
boolean useColormap = !isTrueColor && !"none".equals(colormapType);
|
|
304
|
+
return new FrameClass(isTrueColor, colormapType, useColormap);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public void clearSatellite() {
|
|
308
|
+
decodeGeneration.incrementAndGet();
|
|
309
|
+
fetchInFlight.clear();
|
|
310
|
+
satelliteLayer.clearAll();
|
|
311
|
+
triggerRepaint();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Full sync from JS: run key, optional style, target unix, and frame descriptors (small strings —
|
|
316
|
+
* no image bytes on the bridge).
|
|
317
|
+
*/
|
|
318
|
+
public void syncFromJson(@Nullable String json) {
|
|
319
|
+
if (json == null || json.isEmpty()) return;
|
|
320
|
+
try {
|
|
321
|
+
JSONObject o = new JSONObject(json);
|
|
322
|
+
debugDiagnostics = o.optBoolean("debug", false);
|
|
323
|
+
SatelliteLayer.setDebugLogging(debugDiagnostics);
|
|
324
|
+
|
|
325
|
+
String runKey = o.optString("runKey", "");
|
|
326
|
+
if (!runKey.isEmpty() && !runKey.equals(lastRunKey)) {
|
|
327
|
+
lastRunKey = runKey;
|
|
328
|
+
decodeGeneration.incrementAndGet();
|
|
329
|
+
fetchInFlight.clear();
|
|
330
|
+
satelliteLayer.clearAll();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
boolean visible = o.optBoolean("visible", true);
|
|
334
|
+
float opacity = (float) o.optDouble("opacity", 1.0);
|
|
335
|
+
int fillSmoothing = o.optInt("fillSmoothing", 0);
|
|
336
|
+
satelliteLayer.setVisible(visible);
|
|
337
|
+
satelliteLayer.setOpacity(opacity);
|
|
338
|
+
satelliteLayer.setFillSmoothing(fillSmoothing);
|
|
339
|
+
|
|
340
|
+
long targetUnix = Long.MIN_VALUE;
|
|
341
|
+
if (o.has("targetUnix") && !o.isNull("targetUnix")) {
|
|
342
|
+
targetUnix = o.getLong("targetUnix");
|
|
343
|
+
}
|
|
344
|
+
if (targetUnix != Long.MIN_VALUE) {
|
|
345
|
+
lastTargetUnix = targetUnix;
|
|
346
|
+
satelliteLayer.setActiveUnix(targetUnix);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
String apiKey = o.optString("apiKey", "");
|
|
350
|
+
String bundleId = o.optString("bundleId", "");
|
|
351
|
+
String gridSiteOrigin = o.optString("gridRequestSiteOrigin", "").trim();
|
|
352
|
+
while (gridSiteOrigin.endsWith("/")) {
|
|
353
|
+
gridSiteOrigin = gridSiteOrigin.substring(0, gridSiteOrigin.length() - 1);
|
|
354
|
+
}
|
|
355
|
+
JSONArray frames = o.optJSONArray("frames");
|
|
356
|
+
if (frames == null || apiKey.isEmpty()) {
|
|
357
|
+
WritableMap err = Arguments.createMap();
|
|
358
|
+
err.putBoolean("apiKeyEmpty", apiKey.isEmpty());
|
|
359
|
+
err.putBoolean("framesMissing", frames == null);
|
|
360
|
+
emitDiagnostic("sync.aborted", err);
|
|
361
|
+
triggerRepaint();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
WritableMap syncInfo = Arguments.createMap();
|
|
366
|
+
syncInfo.putString("runKey", runKey);
|
|
367
|
+
syncInfo.putInt("frameCount", frames.length());
|
|
368
|
+
syncInfo.putBoolean("visible", visible);
|
|
369
|
+
syncInfo.putDouble("opacity", opacity);
|
|
370
|
+
syncInfo.putBoolean("apiKeyPresent", !apiKey.isEmpty());
|
|
371
|
+
syncInfo.putBoolean("bundleIdPresent", bundleId != null && !bundleId.isEmpty());
|
|
372
|
+
syncInfo.putString("gridRequestSiteOrigin", gridSiteOrigin);
|
|
373
|
+
if (targetUnix != Long.MIN_VALUE) syncInfo.putDouble("targetUnix", targetUnix);
|
|
374
|
+
emitDiagnostic("sync.received", syncInfo);
|
|
375
|
+
|
|
376
|
+
final int gen = decodeGeneration.get();
|
|
377
|
+
for (int i = 0; i < frames.length(); i++) {
|
|
378
|
+
JSONObject f = frames.getJSONObject(i);
|
|
379
|
+
long unix = f.getLong("unix");
|
|
380
|
+
String url = f.getString("url");
|
|
381
|
+
String shaderFileName = f.optString("shaderFileName", "");
|
|
382
|
+
scheduleFetchIfNeeded(gen, unix, url, shaderFileName, apiKey, bundleId, gridSiteOrigin);
|
|
383
|
+
}
|
|
384
|
+
triggerRepaint();
|
|
385
|
+
if (debugDiagnostics) {
|
|
386
|
+
mainHandler.postDelayed(() -> emitDiagnostic("render.poll", null), 2500);
|
|
387
|
+
mainHandler.postDelayed(() -> emitDiagnostic("render.poll", null), 6000);
|
|
388
|
+
}
|
|
389
|
+
} catch (Exception e) {
|
|
390
|
+
Log.e(TAG, "syncFromJson failed", e);
|
|
391
|
+
WritableMap err = Arguments.createMap();
|
|
392
|
+
err.putString("error", e.getMessage() != null ? e.getMessage() : e.toString());
|
|
393
|
+
emitDiagnostic("sync.error", err);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private void scheduleFetchIfNeeded(
|
|
398
|
+
int gen,
|
|
399
|
+
long unix,
|
|
400
|
+
@NonNull String url,
|
|
401
|
+
@NonNull String shaderFileName,
|
|
402
|
+
@NonNull String apiKey,
|
|
403
|
+
@NonNull String bundleId,
|
|
404
|
+
@NonNull String gridRequestSiteOrigin) {
|
|
405
|
+
if (satelliteLayer.isFrameCached(unix)) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (!fetchInFlight.add(unix)) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
satDecodeExecutor.execute(
|
|
413
|
+
() -> {
|
|
414
|
+
try {
|
|
415
|
+
if (gen != decodeGeneration.get()) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
String fetchUrl = satelliteUrlWithQueryApiKey(url, apiKey);
|
|
419
|
+
Request.Builder reqBuilder =
|
|
420
|
+
new Request.Builder().url(fetchUrl).header("x-api-key", apiKey);
|
|
421
|
+
if (bundleId != null && !bundleId.isEmpty()) {
|
|
422
|
+
reqBuilder.header("x-app-identifier", bundleId);
|
|
423
|
+
}
|
|
424
|
+
// Match WeatherFrameProcessorModule + AguaceroCore grid fetch (CloudFront allowlists on RN).
|
|
425
|
+
String gridOrigin = gridRequestSiteOrigin;
|
|
426
|
+
if (gridOrigin == null || gridOrigin.trim().isEmpty()) {
|
|
427
|
+
gridOrigin = "https://localhost";
|
|
428
|
+
}
|
|
429
|
+
gridOrigin = gridOrigin.trim();
|
|
430
|
+
while (gridOrigin.endsWith("/")) {
|
|
431
|
+
gridOrigin = gridOrigin.substring(0, gridOrigin.length() - 1);
|
|
432
|
+
}
|
|
433
|
+
if (!gridOrigin.isEmpty()) {
|
|
434
|
+
reqBuilder.header("Origin", gridOrigin);
|
|
435
|
+
reqBuilder.header("Referer", gridOrigin + "/");
|
|
436
|
+
}
|
|
437
|
+
Request req = reqBuilder.build();
|
|
438
|
+
Call call = HTTP.newCall(req);
|
|
439
|
+
try (Response resp = call.execute()) {
|
|
440
|
+
if (!resp.isSuccessful() || resp.body() == null) {
|
|
441
|
+
int code = resp != null ? resp.code() : -1;
|
|
442
|
+
fetchFailCount.incrementAndGet();
|
|
443
|
+
Log.w(
|
|
444
|
+
TAG,
|
|
445
|
+
"Satellite frame fetch failed HTTP "
|
|
446
|
+
+ code
|
|
447
|
+
+ " unix="
|
|
448
|
+
+ unix
|
|
449
|
+
+ (code == 403
|
|
450
|
+
? " — check apiKey, gridRequestSiteOrigin (Origin/Referer), and x-app-identifier bundle allowlist"
|
|
451
|
+
: ""));
|
|
452
|
+
WritableMap fail = Arguments.createMap();
|
|
453
|
+
fail.putDouble("unix", unix);
|
|
454
|
+
fail.putInt("httpCode", code);
|
|
455
|
+
mainHandler.post(
|
|
456
|
+
() -> {
|
|
457
|
+
fetchInFlight.remove(unix);
|
|
458
|
+
emitDiagnostic("fetch.fail", fail);
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
ResponseBody body = resp.body();
|
|
463
|
+
byte[] ktxBytes = body.bytes();
|
|
464
|
+
fetchOkCount.incrementAndGet();
|
|
465
|
+
if (gen != decodeGeneration.get()) {
|
|
466
|
+
mainHandler.post(() -> fetchInFlight.remove(unix));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
byte[] packed = SatelliteKtxDecoder.transcodeKtx2(ktxBytes);
|
|
470
|
+
SatelliteKtxDecoder.Parsed parsed = SatelliteKtxDecoder.parsePacked(packed);
|
|
471
|
+
if (parsed == null) {
|
|
472
|
+
Log.w(TAG, "KTX2 transcode failed unix=" + unix);
|
|
473
|
+
WritableMap tf = Arguments.createMap();
|
|
474
|
+
tf.putDouble("unix", unix);
|
|
475
|
+
tf.putInt("ktxBytes", ktxBytes.length);
|
|
476
|
+
mainHandler.post(
|
|
477
|
+
() -> {
|
|
478
|
+
fetchInFlight.remove(unix);
|
|
479
|
+
emitDiagnostic("transcode.fail", tf);
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
WritableMap ok = Arguments.createMap();
|
|
484
|
+
ok.putDouble("unix", unix);
|
|
485
|
+
ok.putInt("httpBytes", ktxBytes.length);
|
|
486
|
+
ok.putInt("basisFormatOrdinal", parsed.basisFormatOrdinal);
|
|
487
|
+
ok.putInt("width", parsed.width);
|
|
488
|
+
ok.putInt("height", parsed.height);
|
|
489
|
+
ok.putString("shaderFileName", shaderFileName);
|
|
490
|
+
if (parsed.basisFormatOrdinal
|
|
491
|
+
== SatelliteKtxDecoder.TF_ASTC_LDR_4x4_RGBA) {
|
|
492
|
+
ok.putString(
|
|
493
|
+
"astcWarning",
|
|
494
|
+
"ASTC selected — black footprint if GPU cannot sample ASTC; prefer ETC2-first SDK build");
|
|
495
|
+
}
|
|
496
|
+
SatelliteMeshBuilder.MeshResult mesh =
|
|
497
|
+
SatelliteMeshBuilder.buildMesh(shaderFileName, parsed.width, parsed.height);
|
|
498
|
+
FrameClass fc = classifyShaderFileName(shaderFileName);
|
|
499
|
+
SatelliteLayer.PendingGpuUpload pending =
|
|
500
|
+
new SatelliteLayer.PendingGpuUpload(
|
|
501
|
+
unix,
|
|
502
|
+
mesh,
|
|
503
|
+
parsed,
|
|
504
|
+
fc.trueColor,
|
|
505
|
+
fc.colormapKind,
|
|
506
|
+
fc.useColormap);
|
|
507
|
+
|
|
508
|
+
mainHandler.post(
|
|
509
|
+
() -> {
|
|
510
|
+
fetchInFlight.remove(unix);
|
|
511
|
+
if (gen != decodeGeneration.get()) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (!isLayerAdded) {
|
|
515
|
+
findMapViewAndAddLayer();
|
|
516
|
+
}
|
|
517
|
+
satelliteLayer.queueGpuUpload(pending);
|
|
518
|
+
emitDiagnostic("fetch.ok", ok);
|
|
519
|
+
triggerRepaint();
|
|
520
|
+
if (lastTargetUnix == unix || fetchOkCount.get() == 1) {
|
|
521
|
+
emitDiagnostic("gpu.queued", ok);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
} catch (IOException e) {
|
|
526
|
+
Log.e(TAG, "fetch satellite frame unix=" + unix, e);
|
|
527
|
+
mainHandler.post(() -> fetchInFlight.remove(unix));
|
|
528
|
+
} catch (Exception e) {
|
|
529
|
+
Log.e(TAG, "decode satellite frame unix=" + unix, e);
|
|
530
|
+
mainHandler.post(() -> fetchInFlight.remove(unix));
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
public void activateSatelliteCachedUnix(long unix) {
|
|
536
|
+
lastTargetUnix = unix;
|
|
537
|
+
satelliteLayer.activateCachedUnix(unix);
|
|
538
|
+
emitDiagnostic(
|
|
539
|
+
"scrub.activate",
|
|
540
|
+
mapOf("unix", unix, "wasCached", satelliteLayer.isFrameCached(unix)));
|
|
541
|
+
triggerRepaint();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
public void updateStyleFromJson(@Nullable String json) {
|
|
545
|
+
if (json == null || json.isEmpty()) return;
|
|
546
|
+
try {
|
|
547
|
+
JSONObject o = new JSONObject(json);
|
|
548
|
+
satelliteLayer.setVisible(o.optBoolean("visible", true));
|
|
549
|
+
satelliteLayer.setOpacity((float) o.optDouble("opacity", 1.0));
|
|
550
|
+
satelliteLayer.setFillSmoothing(o.optInt("fillSmoothing", 0));
|
|
551
|
+
triggerRepaint();
|
|
552
|
+
} catch (Exception e) {
|
|
553
|
+
Log.e(TAG, "updateStyleFromJson failed", e);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|