@aguacerowx/react-native 0.0.52 → 0.0.54
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/LICENSE +21 -0
- package/android/src/main/cpp/satellite_ktx_jni.cpp +6 -1
- package/android/src/main/java/com/aguacerowx/reactnative/NexradRadarLayerView.java +14 -2
- package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayer.java +121 -1
- package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayerView.java +556 -392
- package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +77 -0
- package/index.js +1 -1
- package/ios/SatelliteLayerView.swift +517 -517
- package/ios/WeatherFrameProcessorModule.m +19 -15
- package/ios/WeatherFrameProcessorModule.swift +65 -0
- package/lib/commonjs/AguaceroContext.js +0 -4
- package/lib/commonjs/AguaceroContext.js.map +1 -1
- package/lib/commonjs/GridRenderLayer.js +62 -76
- package/lib/commonjs/GridRenderLayer.js.map +1 -1
- package/lib/commonjs/MapManager.js +110 -224
- package/lib/commonjs/MapManager.js.map +1 -1
- package/lib/commonjs/MapRegistry.js +21 -33
- package/lib/commonjs/MapRegistry.js.map +1 -1
- package/lib/commonjs/NexradRadarLayer.android.js +28 -100
- package/lib/commonjs/NexradRadarLayer.android.js.map +1 -1
- package/lib/commonjs/NexradRadarLayer.ios.js +26 -97
- package/lib/commonjs/NexradRadarLayer.ios.js.map +1 -1
- package/lib/commonjs/NexradSitesMapLayer.js +41 -61
- package/lib/commonjs/NexradSitesMapLayer.js.map +1 -1
- package/lib/commonjs/SatelliteLayer.android.js +26 -38
- package/lib/commonjs/SatelliteLayer.android.js.map +1 -1
- package/lib/commonjs/SatelliteLayer.ios.js +30 -42
- package/lib/commonjs/SatelliteLayer.ios.js.map +1 -1
- package/lib/commonjs/StyleApplicator.js +129 -175
- package/lib/commonjs/StyleApplicator.js.map +1 -1
- package/lib/commonjs/WeatherLayerManager.js +996 -1627
- package/lib/commonjs/WeatherLayerManager.js.map +1 -1
- package/lib/commonjs/aguaceroCoreDebugHooks.js +58 -130
- package/lib/commonjs/aguaceroCoreDebugHooks.js.map +1 -1
- package/lib/commonjs/aguaceroRnDebug.js +147 -287
- package/lib/commonjs/aguaceroRnDebug.js.map +1 -1
- package/lib/commonjs/cdnAuthenticatedFetch.js +104 -0
- package/lib/commonjs/cdnAuthenticatedFetch.js.map +1 -0
- package/lib/commonjs/dispatchViewManagerCommandCompat.js +51 -88
- package/lib/commonjs/dispatchViewManagerCommandCompat.js.map +1 -1
- package/lib/commonjs/gridCdnAuth.js +41 -50
- package/lib/commonjs/gridCdnAuth.js.map +1 -1
- package/lib/commonjs/index.js +51 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/nexrad/nexradAndroidController.js +851 -863
- package/lib/commonjs/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/commonjs/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +62 -85
- package/lib/commonjs/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js.map +1 -1
- package/lib/commonjs/nexrad/nexradDiag.js +32 -148
- package/lib/commonjs/nexrad/nexradDiag.js.map +1 -1
- package/lib/commonjs/nexrad/nexradLevel2Keys.js +261 -0
- package/lib/commonjs/nexrad/nexradLevel2Keys.js.map +1 -0
- package/lib/commonjs/nexrad/nexradLutBuild.js +64 -111
- package/lib/commonjs/nexrad/nexradLutBuild.js.map +1 -1
- package/lib/commonjs/nexrad/nexradMapboxFrameOpts.bundled.js +136 -164
- package/lib/commonjs/nexrad/nexradMapboxFrameOpts.bundled.js.map +1 -1
- package/lib/commonjs/nexrad/nexradSdkImports.js +51 -0
- package/lib/commonjs/nexrad/nexradSdkImports.js.map +1 -0
- package/lib/commonjs/nexrad/radarArchiveCore.bundled.js +2848 -4455
- package/lib/commonjs/nexrad/radarArchiveCore.bundled.js.map +1 -1
- package/lib/commonjs/nexrad/radarDecode.worker.bundled.js +445 -648
- package/lib/commonjs/nexrad/radarDecode.worker.bundled.js.map +1 -1
- package/lib/commonjs/nexrad/radarFrameGpuMatch.bundled.js +52 -68
- package/lib/commonjs/nexrad/radarFrameGpuMatch.bundled.js.map +1 -1
- package/lib/commonjs/nexradNativeCommandIds.js +24 -0
- package/lib/commonjs/nexradNativeCommandIds.js.map +1 -0
- package/lib/commonjs/nws/NwsAlertsOverlay.android.js.map +1 -1
- package/lib/commonjs/nws/NwsAlertsOverlay.ios.js.map +1 -1
- package/lib/commonjs/nws/NwsAlertsOverlay.js +3 -3
- package/lib/commonjs/nws/NwsAlertsOverlay.js.map +1 -1
- package/lib/commonjs/nws/NwsAlertsOverlay.native.js +235 -361
- package/lib/commonjs/nws/NwsAlertsOverlay.native.js.map +1 -1
- package/lib/commonjs/nws/eventSourceRnPolyfill.js +92 -146
- package/lib/commonjs/nws/eventSourceRnPolyfill.js.map +1 -1
- package/lib/commonjs/nws/nwsAndroidConstants.js +2 -8
- package/lib/commonjs/nws/nwsAndroidConstants.js.map +1 -1
- package/lib/commonjs/satellite/satelliteAndroidController.js +122 -199
- package/lib/commonjs/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/commonjs/satelliteBridgeDiag.js +3 -13
- package/lib/commonjs/satelliteBridgeDiag.js.map +1 -1
- package/lib/commonjs/satelliteRnDebug.js +160 -0
- package/lib/commonjs/satelliteRnDebug.js.map +1 -0
- package/lib/module/AguaceroContext.js +2 -7
- package/lib/module/AguaceroContext.js.map +1 -1
- package/lib/module/GridRenderLayer.js +66 -80
- package/lib/module/GridRenderLayer.js.map +1 -1
- package/lib/module/MapManager.js +125 -239
- package/lib/module/MapManager.js.map +1 -1
- package/lib/module/MapRegistry.js +21 -33
- package/lib/module/MapRegistry.js.map +1 -1
- package/lib/module/NexradRadarLayer.android.js +32 -104
- package/lib/module/NexradRadarLayer.android.js.map +1 -1
- package/lib/module/NexradRadarLayer.ios.js +30 -101
- package/lib/module/NexradRadarLayer.ios.js.map +1 -1
- package/lib/module/NexradSitesMapLayer.js +44 -63
- package/lib/module/NexradSitesMapLayer.js.map +1 -1
- package/lib/module/SatelliteLayer.android.js +32 -44
- package/lib/module/SatelliteLayer.android.js.map +1 -1
- package/lib/module/SatelliteLayer.ios.js +36 -48
- package/lib/module/SatelliteLayer.ios.js.map +1 -1
- package/lib/module/StyleApplicator.js +144 -191
- package/lib/module/StyleApplicator.js.map +1 -1
- package/lib/module/WeatherLayerManager.js +1024 -1655
- package/lib/module/WeatherLayerManager.js.map +1 -1
- package/lib/module/aguaceroCoreDebugHooks.js +59 -130
- package/lib/module/aguaceroCoreDebugHooks.js.map +1 -1
- package/lib/module/aguaceroRnDebug.js +151 -291
- package/lib/module/aguaceroRnDebug.js.map +1 -1
- package/lib/module/cdnAuthenticatedFetch.js +97 -0
- package/lib/module/cdnAuthenticatedFetch.js.map +1 -0
- package/lib/module/dispatchViewManagerCommandCompat.js +52 -90
- package/lib/module/dispatchViewManagerCommandCompat.js.map +1 -1
- package/lib/module/gridCdnAuth.js +38 -50
- package/lib/module/gridCdnAuth.js.map +1 -1
- package/lib/module/index.js +9 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/nexrad/nexradAndroidController.js +865 -876
- package/lib/module/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/module/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +62 -85
- package/lib/module/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js.map +1 -1
- package/lib/module/nexrad/nexradDiag.js +31 -145
- package/lib/module/nexrad/nexradDiag.js.map +1 -1
- package/lib/module/nexrad/nexradLevel2Keys.js +245 -0
- package/lib/module/nexrad/nexradLevel2Keys.js.map +1 -0
- package/lib/module/nexrad/nexradLutBuild.js +64 -110
- package/lib/module/nexrad/nexradLutBuild.js.map +1 -1
- package/lib/module/nexrad/nexradMapboxFrameOpts.bundled.js +136 -163
- package/lib/module/nexrad/nexradMapboxFrameOpts.bundled.js.map +1 -1
- package/lib/module/nexrad/nexradSdkImports.js +4 -0
- package/lib/module/nexrad/nexradSdkImports.js.map +1 -0
- package/lib/module/nexrad/radarArchiveCore.bundled.js +2839 -4448
- package/lib/module/nexrad/radarArchiveCore.bundled.js.map +1 -1
- package/lib/module/nexrad/radarDecode.worker.bundled.js +445 -648
- package/lib/module/nexrad/radarDecode.worker.bundled.js.map +1 -1
- package/lib/module/nexrad/radarFrameGpuMatch.bundled.js +50 -66
- package/lib/module/nexrad/radarFrameGpuMatch.bundled.js.map +1 -1
- package/lib/module/nexradNativeCommandIds.js +18 -0
- package/lib/module/nexradNativeCommandIds.js.map +1 -0
- package/lib/module/nws/NwsAlertsOverlay.android.js +1 -1
- package/lib/module/nws/NwsAlertsOverlay.android.js.map +1 -1
- package/lib/module/nws/NwsAlertsOverlay.ios.js +1 -1
- package/lib/module/nws/NwsAlertsOverlay.ios.js.map +1 -1
- package/lib/module/nws/NwsAlertsOverlay.js +5 -5
- package/lib/module/nws/NwsAlertsOverlay.js.map +1 -1
- package/lib/module/nws/NwsAlertsOverlay.native.js +248 -373
- package/lib/module/nws/NwsAlertsOverlay.native.js.map +1 -1
- package/lib/module/nws/eventSourceRnPolyfill.js +92 -146
- package/lib/module/nws/eventSourceRnPolyfill.js.map +1 -1
- package/lib/module/nws/nwsAndroidConstants.js +2 -8
- package/lib/module/nws/nwsAndroidConstants.js.map +1 -1
- package/lib/module/satellite/satelliteAndroidController.js +125 -202
- package/lib/module/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/module/satelliteBridgeDiag.js +3 -13
- package/lib/module/satelliteBridgeDiag.js.map +1 -1
- package/lib/module/satelliteRnDebug.js +148 -0
- package/lib/module/satelliteRnDebug.js.map +1 -0
- package/lib/typescript/AguaceroContext.d.ts +0 -4
- package/lib/typescript/AguaceroContext.d.ts.map +1 -1
- package/lib/typescript/GridRenderLayer.d.ts.map +1 -1
- package/lib/typescript/MapManager.d.ts +0 -12
- package/lib/typescript/MapManager.d.ts.map +1 -1
- package/lib/typescript/MapRegistry.d.ts +10 -12
- package/lib/typescript/MapRegistry.d.ts.map +1 -1
- package/lib/typescript/NexradRadarLayer.android.d.ts.map +1 -1
- package/lib/typescript/NexradRadarLayer.ios.d.ts.map +1 -1
- package/lib/typescript/NexradSitesMapLayer.d.ts +4 -10
- package/lib/typescript/NexradSitesMapLayer.d.ts.map +1 -1
- package/lib/typescript/SatelliteLayer.android.d.ts.map +1 -1
- package/lib/typescript/SatelliteLayer.ios.d.ts.map +1 -1
- package/lib/typescript/StyleApplicator.d.ts +1 -1
- package/lib/typescript/StyleApplicator.d.ts.map +1 -1
- package/lib/typescript/WeatherLayerManager.d.ts.map +1 -1
- package/lib/typescript/aguaceroCoreDebugHooks.d.ts +2 -9
- package/lib/typescript/aguaceroCoreDebugHooks.d.ts.map +1 -1
- package/lib/typescript/aguaceroRnDebug.d.ts +47 -66
- package/lib/typescript/aguaceroRnDebug.d.ts.map +1 -1
- package/lib/typescript/cdnAuthenticatedFetch.d.ts +10 -0
- package/lib/typescript/cdnAuthenticatedFetch.d.ts.map +1 -0
- package/lib/typescript/dispatchViewManagerCommandCompat.d.ts +1 -17
- package/lib/typescript/dispatchViewManagerCommandCompat.d.ts.map +1 -1
- package/lib/typescript/gridCdnAuth.d.ts +16 -21
- package/lib/typescript/gridCdnAuth.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/nexrad/nexradAndroidController.d.ts +39 -89
- package/lib/typescript/nexrad/nexradAndroidController.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradCrossSectionSampleAtLatLon.bundled.d.ts +2 -1
- package/lib/typescript/nexrad/nexradCrossSectionSampleAtLatLon.bundled.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradDiag.d.ts +13 -101
- package/lib/typescript/nexrad/nexradDiag.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradLevel2Keys.d.ts +36 -0
- package/lib/typescript/nexrad/nexradLevel2Keys.d.ts.map +1 -0
- package/lib/typescript/nexrad/nexradLutBuild.d.ts +3 -10
- package/lib/typescript/nexrad/nexradLutBuild.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradMapboxFrameOpts.bundled.d.ts +4 -3
- package/lib/typescript/nexrad/nexradMapboxFrameOpts.bundled.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradSdkImports.d.ts +2 -0
- package/lib/typescript/nexrad/nexradSdkImports.d.ts.map +1 -0
- package/lib/typescript/nexrad/radarArchiveCore.bundled.d.ts +12 -7
- package/lib/typescript/nexrad/radarArchiveCore.bundled.d.ts.map +1 -1
- package/lib/typescript/nexrad/radarDecode.worker.bundled.d.ts +20 -20
- package/lib/typescript/nexrad/radarDecode.worker.bundled.d.ts.map +1 -1
- package/lib/typescript/nexrad/radarFrameGpuMatch.bundled.d.ts +4 -3
- package/lib/typescript/nexrad/radarFrameGpuMatch.bundled.d.ts.map +1 -1
- package/lib/typescript/nexradNativeCommandIds.d.ts +2 -0
- package/lib/typescript/nexradNativeCommandIds.d.ts.map +1 -0
- package/lib/typescript/nws/NwsAlertsOverlay.native.d.ts +6 -17
- package/lib/typescript/nws/NwsAlertsOverlay.native.d.ts.map +1 -1
- package/lib/typescript/nws/eventSourceRnPolyfill.d.ts +0 -3
- package/lib/typescript/nws/eventSourceRnPolyfill.d.ts.map +1 -1
- package/lib/typescript/nws/nwsAndroidConstants.d.ts +0 -5
- package/lib/typescript/nws/nwsAndroidConstants.d.ts.map +1 -1
- package/lib/typescript/satellite/satelliteAndroidController.d.ts +9 -47
- package/lib/typescript/satellite/satelliteAndroidController.d.ts.map +1 -1
- package/lib/typescript/satelliteBridgeDiag.d.ts +1 -5
- package/lib/typescript/satelliteBridgeDiag.d.ts.map +1 -1
- package/lib/typescript/satelliteRnDebug.d.ts +75 -0
- package/lib/typescript/satelliteRnDebug.d.ts.map +1 -0
- package/package.json +75 -74
- package/src/AguaceroContext.js +1 -7
- package/src/GridRenderLayer.js +1 -128
- package/src/MapManager.js +1 -277
- package/src/MapRegistry.js +1 -56
- package/src/NexradRadarLayer.android.js +1 -121
- package/src/NexradRadarLayer.ios.js +1 -115
- package/src/NexradSitesMapLayer.js +1 -75
- package/src/SatelliteLayer.android.js +1 -63
- package/src/SatelliteLayer.ios.js +1 -70
- package/src/StyleApplicator.js +1 -241
- package/src/WeatherLayerManager.js +1 -2025
- package/src/aguaceroCoreDebugHooks.js +1 -142
- package/src/aguaceroRnDebug.js +1 -335
- package/src/cdnAuthenticatedFetch.js +1 -0
- package/src/dispatchViewManagerCommandCompat.js +1 -100
- package/src/gridCdnAuth.js +1 -56
- package/src/index.js +1 -19
- package/src/nexrad/nexradAndroidController.js +1 -1078
- package/src/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +1 -91
- package/src/nexrad/nexradDiag.js +1 -150
- package/src/nexrad/nexradLevel2Keys.js +1 -0
- package/src/nexrad/nexradLutBuild.js +1 -126
- package/src/nexrad/nexradMapboxFrameOpts.bundled.js +1 -245
- package/src/nexrad/nexradSdkImports.js +1 -0
- package/src/nexrad/radarArchiveCore.bundled.js +1 -7085
- package/src/nexrad/radarDecode.worker.bundled.js +1 -813
- package/src/nexrad/radarFrameGpuMatch.bundled.js +1 -79
- package/src/nexradNativeCommandIds.js +1 -0
- package/src/nws/NwsAlertsOverlay.android.js +1 -1
- package/src/nws/NwsAlertsOverlay.ios.js +1 -1
- package/src/nws/NwsAlertsOverlay.js +1 -7
- package/src/nws/NwsAlertsOverlay.native.js +1 -463
- package/src/nws/eventSourceRnPolyfill.js +7 -193
- package/src/nws/nwsAndroidConstants.js +1 -8
- package/src/satellite/satelliteAndroidController.js +1 -245
- package/src/satelliteBridgeDiag.js +1 -15
- package/src/satelliteRnDebug.js +1 -0
- package/lib/commonjs/nexrad/nexradNativeCommandIds.js +0 -51
- package/lib/commonjs/nexrad/nexradNativeCommandIds.js.map +0 -1
- package/lib/module/nexrad/nexradNativeCommandIds.js +0 -44
- package/lib/module/nexrad/nexradNativeCommandIds.js.map +0 -1
- package/lib/typescript/nexrad/nexradNativeCommandIds.d.ts +0 -9
- package/lib/typescript/nexrad/nexradNativeCommandIds.d.ts.map +0 -1
- package/src/nexrad/nexradNativeCommandIds.js +0 -44
- /package/lib/commonjs/{nexrad/nexradSitesUs.json → nexradSitesUs.json} +0 -0
- /package/lib/module/{nexrad/nexradSitesUs.json → nexradSitesUs.json} +0 -0
- /package/src/{nexrad/nexradSitesUs.json → nexradSitesUs.json} +0 -0
|
@@ -1,517 +1,517 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import MapboxMaps
|
|
3
|
-
import React
|
|
4
|
-
import UIKit
|
|
5
|
-
|
|
6
|
-
@objc(SatelliteLayerView)
|
|
7
|
-
public final class SatelliteLayerView: UIView {
|
|
8
|
-
|
|
9
|
-
/// Xcode / device console — grep `[AguaceroWX][SatelliteLayerView]` or `SatelliteLayerView`
|
|
10
|
-
private static func satLog(_ message: String) {
|
|
11
|
-
NSLog("[AguaceroWX][SatelliteLayerView] %@", message)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
public static let satelliteLayerId = "aguacero-satellite-layer"
|
|
15
|
-
private static let weatherGridLayerId = "aguacero-weather-grid-custom"
|
|
16
|
-
|
|
17
|
-
@objc public var layerInstance: AguaceroSatelliteRenderer!
|
|
18
|
-
@objc public weak var mapView: MapView?
|
|
19
|
-
@objc public var layerId: String = SatelliteLayerView.satelliteLayerId
|
|
20
|
-
@objc public var belowID: String?
|
|
21
|
-
|
|
22
|
-
@objc public weak var bridge: RCTBridge?
|
|
23
|
-
|
|
24
|
-
private var isLayerAdded = false
|
|
25
|
-
private var satReparentScheduled = false
|
|
26
|
-
|
|
27
|
-
@objc public var satelliteLayerAdded: Bool { isLayerAdded }
|
|
28
|
-
|
|
29
|
-
private let decodeQueue = DispatchQueue(label: "com.aguacero.satellite-decode", qos: .utility)
|
|
30
|
-
private var decodeGeneration = 0
|
|
31
|
-
private var lastRunKey: String?
|
|
32
|
-
|
|
33
|
-
private var fetchInFlight = Set<Int64>()
|
|
34
|
-
private let fetchLock = NSLock()
|
|
35
|
-
|
|
36
|
-
private lazy var urlSession: URLSession = {
|
|
37
|
-
let c = URLSessionConfiguration.ephemeral
|
|
38
|
-
c.timeoutIntervalForRequest = 120
|
|
39
|
-
return URLSession(configuration: c)
|
|
40
|
-
}()
|
|
41
|
-
|
|
42
|
-
public override init(frame: CGRect) {
|
|
43
|
-
layerInstance = AguaceroSatelliteRenderer(id: SatelliteLayerView.satelliteLayerId)
|
|
44
|
-
super.init(frame: frame)
|
|
45
|
-
isOpaque = false
|
|
46
|
-
Self.satLog("init(frame:) SatelliteLayerView=\(Unmanaged.passUnretained(self).toOpaque())")
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
required init?(coder: NSCoder) {
|
|
50
|
-
layerInstance = AguaceroSatelliteRenderer(id: SatelliteLayerView.satelliteLayerId)
|
|
51
|
-
super.init(coder: coder)
|
|
52
|
-
isOpaque = false
|
|
53
|
-
Self.satLog("init(coder:) SatelliteLayerView=\(Unmanaged.passUnretained(self).toOpaque())")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
public override func didMoveToWindow() {
|
|
57
|
-
super.didMoveToWindow()
|
|
58
|
-
if window != nil && !isLayerAdded {
|
|
59
|
-
findMapViewAndAddLayer(in: window!)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public override func layoutSubviews() {
|
|
64
|
-
super.layoutSubviews()
|
|
65
|
-
if !isLayerAdded {
|
|
66
|
-
let root = window ?? superview
|
|
67
|
-
if let root {
|
|
68
|
-
findMapViewAndAddLayer(in: root)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private func findMapViewAndAddLayer(in view: UIView) {
|
|
74
|
-
if isLayerAdded { return }
|
|
75
|
-
if let map = findMapViewRecursive(view) {
|
|
76
|
-
Self.satLog("findMapView: OK mapView attached, belowID=\(belowID ?? "nil")")
|
|
77
|
-
self.mapView = map
|
|
78
|
-
waitForStyleLoadAndAddLayer()
|
|
79
|
-
} else {
|
|
80
|
-
Self.satLog("findMapView: MapView not found under RNMBX hierarchy yet (will retry from layout/window)")
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private func findMapViewRecursive(_ view: UIView) -> MapView? {
|
|
85
|
-
let name = NSStringFromClass(type(of: view))
|
|
86
|
-
if name == "RNMBXMapView", view.subviews.count > 0 {
|
|
87
|
-
let first = view.subviews[0]
|
|
88
|
-
let subName = NSStringFromClass(type(of: first))
|
|
89
|
-
if subName.contains("MapboxMaps.MapView") || subName == "MapView" {
|
|
90
|
-
return first as? MapView
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
for sub in view.subviews {
|
|
94
|
-
if let found = findMapViewRecursive(sub) {
|
|
95
|
-
return found
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return nil
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private func waitForStyleLoadAndAddLayer() {
|
|
102
|
-
guard let mapView else { return }
|
|
103
|
-
let anchor = belowID ?? "AML_-_terrain"
|
|
104
|
-
let anchorExists = GridRenderLayerBridge.layerExists(in: mapView, layerId: anchor)
|
|
105
|
-
Self.satLog("waitForStyle: anchor=\(anchor) exists=\(anchorExists) gridLayer=\(GridRenderLayerBridge.layerExists(in: mapView, layerId: Self.weatherGridLayerId))")
|
|
106
|
-
if anchorExists {
|
|
107
|
-
addLayerToMap()
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
pollForLayerExistence(anchor: anchor, attempts: 0)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private func pollForLayerExistence(anchor: String, attempts: Int) {
|
|
114
|
-
guard let mapView, !isLayerAdded else { return }
|
|
115
|
-
if attempts > 80 {
|
|
116
|
-
Self.satLog("pollAnchor: GAVE UP after ~\(attempts * 100)ms — layer id \"\(anchor)\" never appeared. Satellite custom layer was NOT added. Check Mapbox style / belowID prop.")
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
if attempts % 20 == 0, attempts > 0 {
|
|
120
|
-
Self.satLog("pollAnchor: still waiting for \"\(anchor)\" attempt=\(attempts)")
|
|
121
|
-
}
|
|
122
|
-
if GridRenderLayerBridge.layerExists(in: mapView, layerId: anchor) {
|
|
123
|
-
Self.satLog("pollAnchor: anchor \"\(anchor)\" appeared after \(attempts) attempts")
|
|
124
|
-
addLayerToMap()
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
128
|
-
self?.pollForLayerExistence(anchor: anchor, attempts: attempts + 1)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private func addLayerToMap() {
|
|
133
|
-
guard let mapView, !isLayerAdded else { return }
|
|
134
|
-
guard let host = layerInstance.getHostWrapper() as? CustomLayerHost else {
|
|
135
|
-
Self.satLog("addLayerToMap: ABORT — CustomLayer host missing")
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
let success = GridRenderLayerBridge.addSatelliteCustomLayer(
|
|
140
|
-
to: mapView,
|
|
141
|
-
layerHost: host,
|
|
142
|
-
layerId: layerId,
|
|
143
|
-
weatherGridLayerId: Self.weatherGridLayerId,
|
|
144
|
-
belowLayerId: belowID
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
if success {
|
|
148
|
-
isLayerAdded = true
|
|
149
|
-
let gridThere = GridRenderLayerBridge.layerExists(in: mapView, layerId: Self.weatherGridLayerId)
|
|
150
|
-
Self.satLog("addLayerToMap: OK layerId=\(layerId) aboveGrid=\(gridThere) belowID=\(belowID ?? "nil")")
|
|
151
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
152
|
-
scheduleReparentAboveGridOnce()
|
|
153
|
-
} else {
|
|
154
|
-
Self.satLog("addLayerToMap: FAILED Mapbox addLayer for layerId=\(layerId) — see GridRenderLayerBridge logs")
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private func scheduleReparentAboveGridOnce() {
|
|
159
|
-
if satReparentScheduled { return }
|
|
160
|
-
satReparentScheduled = true
|
|
161
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
|
|
162
|
-
self?.satReparentScheduled = false
|
|
163
|
-
self?.reparentAboveGridIfNeeded()
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private func reparentAboveGridIfNeeded() {
|
|
168
|
-
guard let mapView else { return }
|
|
169
|
-
let gridOk = GridRenderLayerBridge.layerExists(in: mapView, layerId: Self.weatherGridLayerId)
|
|
170
|
-
let satOk = GridRenderLayerBridge.layerExists(in: mapView, layerId: layerId)
|
|
171
|
-
guard gridOk, satOk else {
|
|
172
|
-
Self.satLog("reparentAboveGrid: skip grid=\(gridOk) satellite=\(satOk)")
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
guard let host = layerInstance.getHostWrapper() as? CustomLayerHost else { return }
|
|
176
|
-
|
|
177
|
-
GridRenderLayerBridge.removeCustomLayer(from: mapView, layerId: layerId)
|
|
178
|
-
_ = GridRenderLayerBridge.addSatelliteCustomLayer(
|
|
179
|
-
to: mapView,
|
|
180
|
-
layerHost: host,
|
|
181
|
-
layerId: layerId,
|
|
182
|
-
weatherGridLayerId: Self.weatherGridLayerId,
|
|
183
|
-
belowLayerId: belowID
|
|
184
|
-
)
|
|
185
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private struct FrameClass {
|
|
189
|
-
let trueColor: Bool
|
|
190
|
-
let colormapKind: String
|
|
191
|
-
let useColormap: Bool
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private static func classifyShaderFileName(_ shaderFileName: String?) -> FrameClass {
|
|
195
|
-
let fn = shaderFileName ?? ""
|
|
196
|
-
let isTrueColor =
|
|
197
|
-
fn.contains("_truecolor_")
|
|
198
|
-
|| fn.contains("_geocolor_")
|
|
199
|
-
|| fn.contains("_firetemperature_")
|
|
200
|
-
|| fn.contains("_dust_")
|
|
201
|
-
|| fn.contains("_simplewatervapor_")
|
|
202
|
-
|| fn.contains("_ntmicro_")
|
|
203
|
-
|| fn.contains("_daycloudphase_")
|
|
204
|
-
|| fn.contains("_daylandcloudfire_")
|
|
205
|
-
|| fn.contains("_airmass_")
|
|
206
|
-
|| fn.contains("_sandwich_")
|
|
207
|
-
|
|
208
|
-
var colormapType = "none"
|
|
209
|
-
if fn.contains("C13") || fn.contains("C14") || fn.contains("C15") || fn.contains("C16") {
|
|
210
|
-
colormapType = "ir"
|
|
211
|
-
} else if fn.contains("C08") || fn.contains("C09") || fn.contains("C10") {
|
|
212
|
-
colormapType = "wv"
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
let useColormap = !isTrueColor && colormapType != "none"
|
|
216
|
-
return FrameClass(trueColor: isTrueColor, colormapKind: colormapType, useColormap: useColormap)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/// Match `WeatherLayerManager` grid `buildGridFrameProcessOptions`: authoritative `apiKey` in the query string
|
|
220
|
-
/// plus `x-api-key` on the request (same pattern as `AguaceroCore` grid / NEXRAD CDN).
|
|
221
|
-
private static func satelliteFetchURL(from url: URL, apiKey: String) -> URL {
|
|
222
|
-
guard var c = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }
|
|
223
|
-
var q = c.queryItems ?? []
|
|
224
|
-
q.removeAll { $0.name == "apiKey" }
|
|
225
|
-
q.append(URLQueryItem(name: "apiKey", value: apiKey))
|
|
226
|
-
if !q.contains(where: { $0.name == "userId" }) {
|
|
227
|
-
q.append(URLQueryItem(name: "userId", value: "sdk-user"))
|
|
228
|
-
}
|
|
229
|
-
c.queryItems = q
|
|
230
|
-
return c.url ?? url
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
@objc public func clearSatellite() {
|
|
234
|
-
Self.satLog("clearSatellite: bump decodeGeneration (was \(decodeGeneration)) → \(decodeGeneration + 1), cancel in-flight fetches")
|
|
235
|
-
decodeGeneration += 1
|
|
236
|
-
fetchLock.lock()
|
|
237
|
-
fetchInFlight.removeAll()
|
|
238
|
-
fetchLock.unlock()
|
|
239
|
-
layerInstance.clearAll()
|
|
240
|
-
if let mapView {
|
|
241
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
@objc public func syncFromJson(_ json: String?) {
|
|
246
|
-
guard let json, !json.isEmpty,
|
|
247
|
-
let data = json.data(using: .utf8),
|
|
248
|
-
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
249
|
-
else {
|
|
250
|
-
Self.satLog("syncFromJson: SKIP empty or invalid JSON (nil=\(json == nil) len=\(json?.count ?? 0))")
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
let runKey = (obj["runKey"] as? String) ?? ""
|
|
255
|
-
if !runKey.isEmpty, runKey != lastRunKey {
|
|
256
|
-
Self.satLog("syncFromJson: new runKey=\(runKey) (invalidate caches + bump generation)")
|
|
257
|
-
lastRunKey = runKey
|
|
258
|
-
decodeGeneration += 1
|
|
259
|
-
fetchLock.lock()
|
|
260
|
-
fetchInFlight.removeAll()
|
|
261
|
-
fetchLock.unlock()
|
|
262
|
-
layerInstance.clearAll()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
layerInstance.setVisible((obj["visible"] as? Bool) ?? true)
|
|
266
|
-
layerInstance.setOpacity(Float((obj["opacity"] as? Double) ?? 1.0))
|
|
267
|
-
layerInstance.setFillSmoothing(obj["fillSmoothing"] as? Int ?? 0)
|
|
268
|
-
|
|
269
|
-
if let tu = obj["targetUnix"] as? Int64 {
|
|
270
|
-
layerInstance.setActiveUnix(NSNumber(value: tu))
|
|
271
|
-
} else if let tu = obj["targetUnix"] as? Int {
|
|
272
|
-
layerInstance.setActiveUnix(NSNumber(value: Int64(tu)))
|
|
273
|
-
} else if let tu = obj["targetUnix"] as? Double {
|
|
274
|
-
layerInstance.setActiveUnix(NSNumber(value: Int64(tu)))
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
let apiKey = (obj["apiKey"] as? String) ?? ""
|
|
278
|
-
let bundleId = (obj["bundleId"] as? String) ?? ""
|
|
279
|
-
var gridSiteOrigin = (obj["gridRequestSiteOrigin"] as? String) ?? ""
|
|
280
|
-
gridSiteOrigin = gridSiteOrigin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
281
|
-
while gridSiteOrigin.hasSuffix("/") {
|
|
282
|
-
gridSiteOrigin.removeLast()
|
|
283
|
-
}
|
|
284
|
-
let frames = obj["frames"] as? [[String: Any]]
|
|
285
|
-
let frameCount = frames?.count ?? 0
|
|
286
|
-
let tgt = obj["targetUnix"]
|
|
287
|
-
Self.satLog(
|
|
288
|
-
"syncFromJson: runKey=\(runKey.isEmpty ? "(none)" : runKey) frames=\(frameCount) apiKey.len=\(apiKey.count) bundleId.len=\(bundleId.count) gridOrigin.len=\(gridSiteOrigin.count) visible=\((obj["visible"] as? Bool) ?? true) targetUnix=\(String(describing: tgt)) layerOnMap=\(isLayerAdded)"
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
guard let frames, !frames.isEmpty, !apiKey.isEmpty else {
|
|
292
|
-
if frames == nil {
|
|
293
|
-
Self.satLog("syncFromJson: EARLY EXIT — \"frames\" missing or not an array (no KTX URLs to fetch)")
|
|
294
|
-
} else if frames?.isEmpty == true {
|
|
295
|
-
Self.satLog("syncFromJson: EARLY EXIT — frames[] empty (JS timeline map likely not loaded yet; nothing to fetch)")
|
|
296
|
-
} else {
|
|
297
|
-
Self.satLog("syncFromJson: EARLY EXIT — apiKey empty (cannot authorize satellite CDN)")
|
|
298
|
-
}
|
|
299
|
-
if let mapView {
|
|
300
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
301
|
-
}
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let gen = decodeGeneration
|
|
306
|
-
|
|
307
|
-
for (idx, item) in frames.enumerated() {
|
|
308
|
-
guard let unixNum = item["unix"],
|
|
309
|
-
let urlStr = item["url"] as? String,
|
|
310
|
-
let url = URL(string: urlStr)
|
|
311
|
-
else { continue }
|
|
312
|
-
let unix: Int64
|
|
313
|
-
if let i = unixNum as? Int64 {
|
|
314
|
-
unix = i
|
|
315
|
-
} else if let i = unixNum as? Int {
|
|
316
|
-
unix = Int64(i)
|
|
317
|
-
} else if let d = unixNum as? Double {
|
|
318
|
-
unix = Int64(d)
|
|
319
|
-
} else {
|
|
320
|
-
continue
|
|
321
|
-
}
|
|
322
|
-
let shaderFileName = item["shaderFileName"] as? String ?? ""
|
|
323
|
-
if idx < 5 {
|
|
324
|
-
Self.satLog("scheduleFetch[\(idx)]: unix=\(unix) host=\(url.host ?? "?") shader=\(shaderFileName.prefix(48))…")
|
|
325
|
-
}
|
|
326
|
-
scheduleFetchIfNeeded(
|
|
327
|
-
gen: gen,
|
|
328
|
-
unix: unix,
|
|
329
|
-
url: url,
|
|
330
|
-
shaderFileName: shaderFileName,
|
|
331
|
-
apiKey: apiKey,
|
|
332
|
-
bundleId: bundleId,
|
|
333
|
-
gridRequestSiteOrigin: gridSiteOrigin
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
|
-
if frames.count > 5 {
|
|
337
|
-
Self.satLog("scheduleFetch: … and \(frames.count - 5) more frame(s) not listed")
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if let mapView {
|
|
341
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private func scheduleFetchIfNeeded(
|
|
346
|
-
gen: Int,
|
|
347
|
-
unix: Int64,
|
|
348
|
-
url: URL,
|
|
349
|
-
shaderFileName: String,
|
|
350
|
-
apiKey: String,
|
|
351
|
-
bundleId: String,
|
|
352
|
-
gridRequestSiteOrigin: String
|
|
353
|
-
) {
|
|
354
|
-
if layerInstance.isFrameCached(unix) {
|
|
355
|
-
Self.satLog("fetch: unix=\(unix) already cached — skip download")
|
|
356
|
-
return
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
fetchLock.lock()
|
|
360
|
-
if fetchInFlight.contains(unix) {
|
|
361
|
-
fetchLock.unlock()
|
|
362
|
-
Self.satLog("fetch: unix=\(unix) already in flight — skip duplicate")
|
|
363
|
-
return
|
|
364
|
-
}
|
|
365
|
-
fetchInFlight.insert(unix)
|
|
366
|
-
fetchLock.unlock()
|
|
367
|
-
|
|
368
|
-
decodeQueue.async { [weak self] in
|
|
369
|
-
guard let self else { return }
|
|
370
|
-
if gen != self.decodeGeneration {
|
|
371
|
-
Self.satLog("fetch: unix=\(unix) aborted — decodeGeneration changed (stale run)")
|
|
372
|
-
self.fetchLock.lock()
|
|
373
|
-
self.fetchInFlight.remove(unix)
|
|
374
|
-
self.fetchLock.unlock()
|
|
375
|
-
return
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
let fetchURL = Self.satelliteFetchURL(from: url, apiKey: apiKey)
|
|
379
|
-
var req = URLRequest(url: fetchURL)
|
|
380
|
-
req.setValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
381
|
-
if !bundleId.isEmpty {
|
|
382
|
-
req.setValue(bundleId, forHTTPHeaderField: "x-app-identifier")
|
|
383
|
-
}
|
|
384
|
-
// Match WeatherFrameProcessorModule + AguaceroCore grid fetch (CloudFront returns 403 without Origin on RN).
|
|
385
|
-
var gridOrigin = gridRequestSiteOrigin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
386
|
-
if gridOrigin.isEmpty {
|
|
387
|
-
gridOrigin = "https://localhost"
|
|
388
|
-
}
|
|
389
|
-
while gridOrigin.hasSuffix("/") {
|
|
390
|
-
gridOrigin.removeLast()
|
|
391
|
-
}
|
|
392
|
-
if !gridOrigin.isEmpty {
|
|
393
|
-
req.setValue(gridOrigin, forHTTPHeaderField: "Origin")
|
|
394
|
-
req.setValue("\(gridOrigin)/", forHTTPHeaderField: "Referer")
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
let sem = DispatchSemaphore(value: 0)
|
|
398
|
-
var outData: Data?
|
|
399
|
-
var httpStatus = -1
|
|
400
|
-
let task = self.urlSession.dataTask(with: req) { data, response, _ in
|
|
401
|
-
if let http = response as? HTTPURLResponse {
|
|
402
|
-
httpStatus = http.statusCode
|
|
403
|
-
}
|
|
404
|
-
outData = data
|
|
405
|
-
sem.signal()
|
|
406
|
-
}
|
|
407
|
-
task.resume()
|
|
408
|
-
sem.wait()
|
|
409
|
-
|
|
410
|
-
if gen != self.decodeGeneration {
|
|
411
|
-
Self.satLog("fetch: unix=\(unix) post-network abort — generation bumped")
|
|
412
|
-
DispatchQueue.main.async {
|
|
413
|
-
self.fetchLock.lock()
|
|
414
|
-
self.fetchInFlight.remove(unix)
|
|
415
|
-
self.fetchLock.unlock()
|
|
416
|
-
}
|
|
417
|
-
return
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
guard httpStatus == 200, let ktxBytes = outData, !ktxBytes.isEmpty else {
|
|
421
|
-
let bodyLen = outData?.count ?? 0
|
|
422
|
-
Self.satLog("fetch: unix=\(unix) HTTP \(httpStatus) bodyBytes=\(bodyLen) — expected 200 + non-empty KTX2 body")
|
|
423
|
-
DispatchQueue.main.async {
|
|
424
|
-
self.fetchLock.lock()
|
|
425
|
-
self.fetchInFlight.remove(unix)
|
|
426
|
-
self.fetchLock.unlock()
|
|
427
|
-
}
|
|
428
|
-
return
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if gen != self.decodeGeneration {
|
|
432
|
-
DispatchQueue.main.async {
|
|
433
|
-
self.fetchLock.lock()
|
|
434
|
-
self.fetchInFlight.remove(unix)
|
|
435
|
-
self.fetchLock.unlock()
|
|
436
|
-
}
|
|
437
|
-
return
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
let packedOpt = AguaceroSatelliteKtxBridge.transcodeKtx2(ktxBytes)
|
|
441
|
-
let parsedOpt = packedOpt.flatMap { AguaceroSatelliteRenderer.parsePacked($0) }
|
|
442
|
-
let meshOpt: SatelliteMeshBuilder.MeshResult? =
|
|
443
|
-
parsedOpt.flatMap { p in
|
|
444
|
-
SatelliteMeshBuilder.buildMesh(shaderFileName: shaderFileName, width: p.width, height: p.height)
|
|
445
|
-
}
|
|
446
|
-
guard packedOpt != nil,
|
|
447
|
-
let parsed = parsedOpt,
|
|
448
|
-
let mesh = meshOpt
|
|
449
|
-
else {
|
|
450
|
-
Self.satLog(
|
|
451
|
-
"fetch: unix=\(unix) DECODE FAIL — ktxIn=\(ktxBytes.count)B packed=\(packedOpt?.count ?? -1) parsed=\(parsedOpt != nil) mesh=\(meshOpt != nil) shader=\(shaderFileName.prefix(40))"
|
|
452
|
-
)
|
|
453
|
-
DispatchQueue.main.async {
|
|
454
|
-
self.fetchLock.lock()
|
|
455
|
-
self.fetchInFlight.remove(unix)
|
|
456
|
-
self.fetchLock.unlock()
|
|
457
|
-
}
|
|
458
|
-
return
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
let fc = Self.classifyShaderFileName(shaderFileName)
|
|
462
|
-
let pending = AguaceroSatelliteRenderer.PendingGpuUpload(
|
|
463
|
-
unix: unix,
|
|
464
|
-
mesh: mesh,
|
|
465
|
-
decoded: parsed,
|
|
466
|
-
trueColor: fc.trueColor,
|
|
467
|
-
colormapKind: fc.colormapKind,
|
|
468
|
-
useColormap: fc.useColormap
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
DispatchQueue.main.async {
|
|
472
|
-
self.fetchLock.lock()
|
|
473
|
-
self.fetchInFlight.remove(unix)
|
|
474
|
-
self.fetchLock.unlock()
|
|
475
|
-
|
|
476
|
-
if gen != self.decodeGeneration {
|
|
477
|
-
return
|
|
478
|
-
}
|
|
479
|
-
if !self.isLayerAdded {
|
|
480
|
-
if let w = self.window {
|
|
481
|
-
self.findMapViewAndAddLayer(in: w)
|
|
482
|
-
} else if let sv = self.superview {
|
|
483
|
-
self.findMapViewAndAddLayer(in: sv)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
Self.satLog(
|
|
487
|
-
"GPU queue: unix=\(unix) \(parsed.width)x\(parsed.height) tc=\(fc.trueColor) cmap=\(fc.colormapKind) layerOnMap=\(self.isLayerAdded)"
|
|
488
|
-
)
|
|
489
|
-
self.layerInstance.queueGpuUpload(pending)
|
|
490
|
-
if let mv = self.mapView {
|
|
491
|
-
GridRenderLayerBridge.triggerRepaint(on: mv)
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
@objc public func activateSatelliteCachedUnix(_ unix: Int64) {
|
|
498
|
-
Self.satLog("activateSatelliteCachedUnix: unix=\(unix) cached=\(layerInstance.isFrameCached(unix))")
|
|
499
|
-
layerInstance.activateCachedUnix(unix)
|
|
500
|
-
if let mapView {
|
|
501
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
@objc public func updateStyleFromJson(_ json: String?) {
|
|
506
|
-
guard let json, !json.isEmpty,
|
|
507
|
-
let data = json.data(using: .utf8),
|
|
508
|
-
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
509
|
-
else { return }
|
|
510
|
-
layerInstance.setVisible((obj["visible"] as? Bool) ?? true)
|
|
511
|
-
layerInstance.setOpacity(Float((obj["opacity"] as? Double) ?? 1.0))
|
|
512
|
-
layerInstance.setFillSmoothing(obj["fillSmoothing"] as? Int ?? 0)
|
|
513
|
-
if let mapView {
|
|
514
|
-
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
1
|
+
import Foundation
|
|
2
|
+
import MapboxMaps
|
|
3
|
+
import React
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
@objc(SatelliteLayerView)
|
|
7
|
+
public final class SatelliteLayerView: UIView {
|
|
8
|
+
|
|
9
|
+
/// Xcode / device console — grep `[AguaceroWX][SatelliteLayerView]` or `SatelliteLayerView`
|
|
10
|
+
private static func satLog(_ message: String) {
|
|
11
|
+
NSLog("[AguaceroWX][SatelliteLayerView] %@", message)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static let satelliteLayerId = "aguacero-satellite-layer"
|
|
15
|
+
private static let weatherGridLayerId = "aguacero-weather-grid-custom"
|
|
16
|
+
|
|
17
|
+
@objc public var layerInstance: AguaceroSatelliteRenderer!
|
|
18
|
+
@objc public weak var mapView: MapView?
|
|
19
|
+
@objc public var layerId: String = SatelliteLayerView.satelliteLayerId
|
|
20
|
+
@objc public var belowID: String?
|
|
21
|
+
|
|
22
|
+
@objc public weak var bridge: RCTBridge?
|
|
23
|
+
|
|
24
|
+
private var isLayerAdded = false
|
|
25
|
+
private var satReparentScheduled = false
|
|
26
|
+
|
|
27
|
+
@objc public var satelliteLayerAdded: Bool { isLayerAdded }
|
|
28
|
+
|
|
29
|
+
private let decodeQueue = DispatchQueue(label: "com.aguacero.satellite-decode", qos: .utility)
|
|
30
|
+
private var decodeGeneration = 0
|
|
31
|
+
private var lastRunKey: String?
|
|
32
|
+
|
|
33
|
+
private var fetchInFlight = Set<Int64>()
|
|
34
|
+
private let fetchLock = NSLock()
|
|
35
|
+
|
|
36
|
+
private lazy var urlSession: URLSession = {
|
|
37
|
+
let c = URLSessionConfiguration.ephemeral
|
|
38
|
+
c.timeoutIntervalForRequest = 120
|
|
39
|
+
return URLSession(configuration: c)
|
|
40
|
+
}()
|
|
41
|
+
|
|
42
|
+
public override init(frame: CGRect) {
|
|
43
|
+
layerInstance = AguaceroSatelliteRenderer(id: SatelliteLayerView.satelliteLayerId)
|
|
44
|
+
super.init(frame: frame)
|
|
45
|
+
isOpaque = false
|
|
46
|
+
Self.satLog("init(frame:) SatelliteLayerView=\(Unmanaged.passUnretained(self).toOpaque())")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
required init?(coder: NSCoder) {
|
|
50
|
+
layerInstance = AguaceroSatelliteRenderer(id: SatelliteLayerView.satelliteLayerId)
|
|
51
|
+
super.init(coder: coder)
|
|
52
|
+
isOpaque = false
|
|
53
|
+
Self.satLog("init(coder:) SatelliteLayerView=\(Unmanaged.passUnretained(self).toOpaque())")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public override func didMoveToWindow() {
|
|
57
|
+
super.didMoveToWindow()
|
|
58
|
+
if window != nil && !isLayerAdded {
|
|
59
|
+
findMapViewAndAddLayer(in: window!)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public override func layoutSubviews() {
|
|
64
|
+
super.layoutSubviews()
|
|
65
|
+
if !isLayerAdded {
|
|
66
|
+
let root = window ?? superview
|
|
67
|
+
if let root {
|
|
68
|
+
findMapViewAndAddLayer(in: root)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private func findMapViewAndAddLayer(in view: UIView) {
|
|
74
|
+
if isLayerAdded { return }
|
|
75
|
+
if let map = findMapViewRecursive(view) {
|
|
76
|
+
Self.satLog("findMapView: OK mapView attached, belowID=\(belowID ?? "nil")")
|
|
77
|
+
self.mapView = map
|
|
78
|
+
waitForStyleLoadAndAddLayer()
|
|
79
|
+
} else {
|
|
80
|
+
Self.satLog("findMapView: MapView not found under RNMBX hierarchy yet (will retry from layout/window)")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private func findMapViewRecursive(_ view: UIView) -> MapView? {
|
|
85
|
+
let name = NSStringFromClass(type(of: view))
|
|
86
|
+
if name == "RNMBXMapView", view.subviews.count > 0 {
|
|
87
|
+
let first = view.subviews[0]
|
|
88
|
+
let subName = NSStringFromClass(type(of: first))
|
|
89
|
+
if subName.contains("MapboxMaps.MapView") || subName == "MapView" {
|
|
90
|
+
return first as? MapView
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for sub in view.subviews {
|
|
94
|
+
if let found = findMapViewRecursive(sub) {
|
|
95
|
+
return found
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func waitForStyleLoadAndAddLayer() {
|
|
102
|
+
guard let mapView else { return }
|
|
103
|
+
let anchor = belowID ?? "AML_-_terrain"
|
|
104
|
+
let anchorExists = GridRenderLayerBridge.layerExists(in: mapView, layerId: anchor)
|
|
105
|
+
Self.satLog("waitForStyle: anchor=\(anchor) exists=\(anchorExists) gridLayer=\(GridRenderLayerBridge.layerExists(in: mapView, layerId: Self.weatherGridLayerId))")
|
|
106
|
+
if anchorExists {
|
|
107
|
+
addLayerToMap()
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
pollForLayerExistence(anchor: anchor, attempts: 0)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func pollForLayerExistence(anchor: String, attempts: Int) {
|
|
114
|
+
guard let mapView, !isLayerAdded else { return }
|
|
115
|
+
if attempts > 80 {
|
|
116
|
+
Self.satLog("pollAnchor: GAVE UP after ~\(attempts * 100)ms — layer id \"\(anchor)\" never appeared. Satellite custom layer was NOT added. Check Mapbox style / belowID prop.")
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
if attempts % 20 == 0, attempts > 0 {
|
|
120
|
+
Self.satLog("pollAnchor: still waiting for \"\(anchor)\" attempt=\(attempts)")
|
|
121
|
+
}
|
|
122
|
+
if GridRenderLayerBridge.layerExists(in: mapView, layerId: anchor) {
|
|
123
|
+
Self.satLog("pollAnchor: anchor \"\(anchor)\" appeared after \(attempts) attempts")
|
|
124
|
+
addLayerToMap()
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
128
|
+
self?.pollForLayerExistence(anchor: anchor, attempts: attempts + 1)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func addLayerToMap() {
|
|
133
|
+
guard let mapView, !isLayerAdded else { return }
|
|
134
|
+
guard let host = layerInstance.getHostWrapper() as? CustomLayerHost else {
|
|
135
|
+
Self.satLog("addLayerToMap: ABORT — CustomLayer host missing")
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let success = GridRenderLayerBridge.addSatelliteCustomLayer(
|
|
140
|
+
to: mapView,
|
|
141
|
+
layerHost: host,
|
|
142
|
+
layerId: layerId,
|
|
143
|
+
weatherGridLayerId: Self.weatherGridLayerId,
|
|
144
|
+
belowLayerId: belowID
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if success {
|
|
148
|
+
isLayerAdded = true
|
|
149
|
+
let gridThere = GridRenderLayerBridge.layerExists(in: mapView, layerId: Self.weatherGridLayerId)
|
|
150
|
+
Self.satLog("addLayerToMap: OK layerId=\(layerId) aboveGrid=\(gridThere) belowID=\(belowID ?? "nil")")
|
|
151
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
152
|
+
scheduleReparentAboveGridOnce()
|
|
153
|
+
} else {
|
|
154
|
+
Self.satLog("addLayerToMap: FAILED Mapbox addLayer for layerId=\(layerId) — see GridRenderLayerBridge logs")
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private func scheduleReparentAboveGridOnce() {
|
|
159
|
+
if satReparentScheduled { return }
|
|
160
|
+
satReparentScheduled = true
|
|
161
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
|
|
162
|
+
self?.satReparentScheduled = false
|
|
163
|
+
self?.reparentAboveGridIfNeeded()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private func reparentAboveGridIfNeeded() {
|
|
168
|
+
guard let mapView else { return }
|
|
169
|
+
let gridOk = GridRenderLayerBridge.layerExists(in: mapView, layerId: Self.weatherGridLayerId)
|
|
170
|
+
let satOk = GridRenderLayerBridge.layerExists(in: mapView, layerId: layerId)
|
|
171
|
+
guard gridOk, satOk else {
|
|
172
|
+
Self.satLog("reparentAboveGrid: skip grid=\(gridOk) satellite=\(satOk)")
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
guard let host = layerInstance.getHostWrapper() as? CustomLayerHost else { return }
|
|
176
|
+
|
|
177
|
+
GridRenderLayerBridge.removeCustomLayer(from: mapView, layerId: layerId)
|
|
178
|
+
_ = GridRenderLayerBridge.addSatelliteCustomLayer(
|
|
179
|
+
to: mapView,
|
|
180
|
+
layerHost: host,
|
|
181
|
+
layerId: layerId,
|
|
182
|
+
weatherGridLayerId: Self.weatherGridLayerId,
|
|
183
|
+
belowLayerId: belowID
|
|
184
|
+
)
|
|
185
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private struct FrameClass {
|
|
189
|
+
let trueColor: Bool
|
|
190
|
+
let colormapKind: String
|
|
191
|
+
let useColormap: Bool
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private static func classifyShaderFileName(_ shaderFileName: String?) -> FrameClass {
|
|
195
|
+
let fn = shaderFileName ?? ""
|
|
196
|
+
let isTrueColor =
|
|
197
|
+
fn.contains("_truecolor_")
|
|
198
|
+
|| fn.contains("_geocolor_")
|
|
199
|
+
|| fn.contains("_firetemperature_")
|
|
200
|
+
|| fn.contains("_dust_")
|
|
201
|
+
|| fn.contains("_simplewatervapor_")
|
|
202
|
+
|| fn.contains("_ntmicro_")
|
|
203
|
+
|| fn.contains("_daycloudphase_")
|
|
204
|
+
|| fn.contains("_daylandcloudfire_")
|
|
205
|
+
|| fn.contains("_airmass_")
|
|
206
|
+
|| fn.contains("_sandwich_")
|
|
207
|
+
|
|
208
|
+
var colormapType = "none"
|
|
209
|
+
if fn.contains("C13") || fn.contains("C14") || fn.contains("C15") || fn.contains("C16") {
|
|
210
|
+
colormapType = "ir"
|
|
211
|
+
} else if fn.contains("C08") || fn.contains("C09") || fn.contains("C10") {
|
|
212
|
+
colormapType = "wv"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let useColormap = !isTrueColor && colormapType != "none"
|
|
216
|
+
return FrameClass(trueColor: isTrueColor, colormapKind: colormapType, useColormap: useColormap)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Match `WeatherLayerManager` grid `buildGridFrameProcessOptions`: authoritative `apiKey` in the query string
|
|
220
|
+
/// plus `x-api-key` on the request (same pattern as `AguaceroCore` grid / NEXRAD CDN).
|
|
221
|
+
private static func satelliteFetchURL(from url: URL, apiKey: String) -> URL {
|
|
222
|
+
guard var c = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }
|
|
223
|
+
var q = c.queryItems ?? []
|
|
224
|
+
q.removeAll { $0.name == "apiKey" }
|
|
225
|
+
q.append(URLQueryItem(name: "apiKey", value: apiKey))
|
|
226
|
+
if !q.contains(where: { $0.name == "userId" }) {
|
|
227
|
+
q.append(URLQueryItem(name: "userId", value: "sdk-user"))
|
|
228
|
+
}
|
|
229
|
+
c.queryItems = q
|
|
230
|
+
return c.url ?? url
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@objc public func clearSatellite() {
|
|
234
|
+
Self.satLog("clearSatellite: bump decodeGeneration (was \(decodeGeneration)) → \(decodeGeneration + 1), cancel in-flight fetches")
|
|
235
|
+
decodeGeneration += 1
|
|
236
|
+
fetchLock.lock()
|
|
237
|
+
fetchInFlight.removeAll()
|
|
238
|
+
fetchLock.unlock()
|
|
239
|
+
layerInstance.clearAll()
|
|
240
|
+
if let mapView {
|
|
241
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@objc public func syncFromJson(_ json: String?) {
|
|
246
|
+
guard let json, !json.isEmpty,
|
|
247
|
+
let data = json.data(using: .utf8),
|
|
248
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
249
|
+
else {
|
|
250
|
+
Self.satLog("syncFromJson: SKIP empty or invalid JSON (nil=\(json == nil) len=\(json?.count ?? 0))")
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let runKey = (obj["runKey"] as? String) ?? ""
|
|
255
|
+
if !runKey.isEmpty, runKey != lastRunKey {
|
|
256
|
+
Self.satLog("syncFromJson: new runKey=\(runKey) (invalidate caches + bump generation)")
|
|
257
|
+
lastRunKey = runKey
|
|
258
|
+
decodeGeneration += 1
|
|
259
|
+
fetchLock.lock()
|
|
260
|
+
fetchInFlight.removeAll()
|
|
261
|
+
fetchLock.unlock()
|
|
262
|
+
layerInstance.clearAll()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
layerInstance.setVisible((obj["visible"] as? Bool) ?? true)
|
|
266
|
+
layerInstance.setOpacity(Float((obj["opacity"] as? Double) ?? 1.0))
|
|
267
|
+
layerInstance.setFillSmoothing(obj["fillSmoothing"] as? Int ?? 0)
|
|
268
|
+
|
|
269
|
+
if let tu = obj["targetUnix"] as? Int64 {
|
|
270
|
+
layerInstance.setActiveUnix(NSNumber(value: tu))
|
|
271
|
+
} else if let tu = obj["targetUnix"] as? Int {
|
|
272
|
+
layerInstance.setActiveUnix(NSNumber(value: Int64(tu)))
|
|
273
|
+
} else if let tu = obj["targetUnix"] as? Double {
|
|
274
|
+
layerInstance.setActiveUnix(NSNumber(value: Int64(tu)))
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let apiKey = (obj["apiKey"] as? String) ?? ""
|
|
278
|
+
let bundleId = (obj["bundleId"] as? String) ?? ""
|
|
279
|
+
var gridSiteOrigin = (obj["gridRequestSiteOrigin"] as? String) ?? ""
|
|
280
|
+
gridSiteOrigin = gridSiteOrigin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
281
|
+
while gridSiteOrigin.hasSuffix("/") {
|
|
282
|
+
gridSiteOrigin.removeLast()
|
|
283
|
+
}
|
|
284
|
+
let frames = obj["frames"] as? [[String: Any]]
|
|
285
|
+
let frameCount = frames?.count ?? 0
|
|
286
|
+
let tgt = obj["targetUnix"]
|
|
287
|
+
Self.satLog(
|
|
288
|
+
"syncFromJson: runKey=\(runKey.isEmpty ? "(none)" : runKey) frames=\(frameCount) apiKey.len=\(apiKey.count) bundleId.len=\(bundleId.count) gridOrigin.len=\(gridSiteOrigin.count) visible=\((obj["visible"] as? Bool) ?? true) targetUnix=\(String(describing: tgt)) layerOnMap=\(isLayerAdded)"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
guard let frames, !frames.isEmpty, !apiKey.isEmpty else {
|
|
292
|
+
if frames == nil {
|
|
293
|
+
Self.satLog("syncFromJson: EARLY EXIT — \"frames\" missing or not an array (no KTX URLs to fetch)")
|
|
294
|
+
} else if frames?.isEmpty == true {
|
|
295
|
+
Self.satLog("syncFromJson: EARLY EXIT — frames[] empty (JS timeline map likely not loaded yet; nothing to fetch)")
|
|
296
|
+
} else {
|
|
297
|
+
Self.satLog("syncFromJson: EARLY EXIT — apiKey empty (cannot authorize satellite CDN)")
|
|
298
|
+
}
|
|
299
|
+
if let mapView {
|
|
300
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
301
|
+
}
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let gen = decodeGeneration
|
|
306
|
+
|
|
307
|
+
for (idx, item) in frames.enumerated() {
|
|
308
|
+
guard let unixNum = item["unix"],
|
|
309
|
+
let urlStr = item["url"] as? String,
|
|
310
|
+
let url = URL(string: urlStr)
|
|
311
|
+
else { continue }
|
|
312
|
+
let unix: Int64
|
|
313
|
+
if let i = unixNum as? Int64 {
|
|
314
|
+
unix = i
|
|
315
|
+
} else if let i = unixNum as? Int {
|
|
316
|
+
unix = Int64(i)
|
|
317
|
+
} else if let d = unixNum as? Double {
|
|
318
|
+
unix = Int64(d)
|
|
319
|
+
} else {
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
322
|
+
let shaderFileName = item["shaderFileName"] as? String ?? ""
|
|
323
|
+
if idx < 5 {
|
|
324
|
+
Self.satLog("scheduleFetch[\(idx)]: unix=\(unix) host=\(url.host ?? "?") shader=\(shaderFileName.prefix(48))…")
|
|
325
|
+
}
|
|
326
|
+
scheduleFetchIfNeeded(
|
|
327
|
+
gen: gen,
|
|
328
|
+
unix: unix,
|
|
329
|
+
url: url,
|
|
330
|
+
shaderFileName: shaderFileName,
|
|
331
|
+
apiKey: apiKey,
|
|
332
|
+
bundleId: bundleId,
|
|
333
|
+
gridRequestSiteOrigin: gridSiteOrigin
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
if frames.count > 5 {
|
|
337
|
+
Self.satLog("scheduleFetch: … and \(frames.count - 5) more frame(s) not listed")
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if let mapView {
|
|
341
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private func scheduleFetchIfNeeded(
|
|
346
|
+
gen: Int,
|
|
347
|
+
unix: Int64,
|
|
348
|
+
url: URL,
|
|
349
|
+
shaderFileName: String,
|
|
350
|
+
apiKey: String,
|
|
351
|
+
bundleId: String,
|
|
352
|
+
gridRequestSiteOrigin: String
|
|
353
|
+
) {
|
|
354
|
+
if layerInstance.isFrameCached(unix) {
|
|
355
|
+
Self.satLog("fetch: unix=\(unix) already cached — skip download")
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fetchLock.lock()
|
|
360
|
+
if fetchInFlight.contains(unix) {
|
|
361
|
+
fetchLock.unlock()
|
|
362
|
+
Self.satLog("fetch: unix=\(unix) already in flight — skip duplicate")
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
fetchInFlight.insert(unix)
|
|
366
|
+
fetchLock.unlock()
|
|
367
|
+
|
|
368
|
+
decodeQueue.async { [weak self] in
|
|
369
|
+
guard let self else { return }
|
|
370
|
+
if gen != self.decodeGeneration {
|
|
371
|
+
Self.satLog("fetch: unix=\(unix) aborted — decodeGeneration changed (stale run)")
|
|
372
|
+
self.fetchLock.lock()
|
|
373
|
+
self.fetchInFlight.remove(unix)
|
|
374
|
+
self.fetchLock.unlock()
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let fetchURL = Self.satelliteFetchURL(from: url, apiKey: apiKey)
|
|
379
|
+
var req = URLRequest(url: fetchURL)
|
|
380
|
+
req.setValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
381
|
+
if !bundleId.isEmpty {
|
|
382
|
+
req.setValue(bundleId, forHTTPHeaderField: "x-app-identifier")
|
|
383
|
+
}
|
|
384
|
+
// Match WeatherFrameProcessorModule + AguaceroCore grid fetch (CloudFront returns 403 without Origin on RN).
|
|
385
|
+
var gridOrigin = gridRequestSiteOrigin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
386
|
+
if gridOrigin.isEmpty {
|
|
387
|
+
gridOrigin = "https://localhost"
|
|
388
|
+
}
|
|
389
|
+
while gridOrigin.hasSuffix("/") {
|
|
390
|
+
gridOrigin.removeLast()
|
|
391
|
+
}
|
|
392
|
+
if !gridOrigin.isEmpty {
|
|
393
|
+
req.setValue(gridOrigin, forHTTPHeaderField: "Origin")
|
|
394
|
+
req.setValue("\(gridOrigin)/", forHTTPHeaderField: "Referer")
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let sem = DispatchSemaphore(value: 0)
|
|
398
|
+
var outData: Data?
|
|
399
|
+
var httpStatus = -1
|
|
400
|
+
let task = self.urlSession.dataTask(with: req) { data, response, _ in
|
|
401
|
+
if let http = response as? HTTPURLResponse {
|
|
402
|
+
httpStatus = http.statusCode
|
|
403
|
+
}
|
|
404
|
+
outData = data
|
|
405
|
+
sem.signal()
|
|
406
|
+
}
|
|
407
|
+
task.resume()
|
|
408
|
+
sem.wait()
|
|
409
|
+
|
|
410
|
+
if gen != self.decodeGeneration {
|
|
411
|
+
Self.satLog("fetch: unix=\(unix) post-network abort — generation bumped")
|
|
412
|
+
DispatchQueue.main.async {
|
|
413
|
+
self.fetchLock.lock()
|
|
414
|
+
self.fetchInFlight.remove(unix)
|
|
415
|
+
self.fetchLock.unlock()
|
|
416
|
+
}
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
guard httpStatus == 200, let ktxBytes = outData, !ktxBytes.isEmpty else {
|
|
421
|
+
let bodyLen = outData?.count ?? 0
|
|
422
|
+
Self.satLog("fetch: unix=\(unix) HTTP \(httpStatus) bodyBytes=\(bodyLen) — expected 200 + non-empty KTX2 body")
|
|
423
|
+
DispatchQueue.main.async {
|
|
424
|
+
self.fetchLock.lock()
|
|
425
|
+
self.fetchInFlight.remove(unix)
|
|
426
|
+
self.fetchLock.unlock()
|
|
427
|
+
}
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if gen != self.decodeGeneration {
|
|
432
|
+
DispatchQueue.main.async {
|
|
433
|
+
self.fetchLock.lock()
|
|
434
|
+
self.fetchInFlight.remove(unix)
|
|
435
|
+
self.fetchLock.unlock()
|
|
436
|
+
}
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let packedOpt = AguaceroSatelliteKtxBridge.transcodeKtx2(ktxBytes)
|
|
441
|
+
let parsedOpt = packedOpt.flatMap { AguaceroSatelliteRenderer.parsePacked($0) }
|
|
442
|
+
let meshOpt: SatelliteMeshBuilder.MeshResult? =
|
|
443
|
+
parsedOpt.flatMap { p in
|
|
444
|
+
SatelliteMeshBuilder.buildMesh(shaderFileName: shaderFileName, width: p.width, height: p.height)
|
|
445
|
+
}
|
|
446
|
+
guard packedOpt != nil,
|
|
447
|
+
let parsed = parsedOpt,
|
|
448
|
+
let mesh = meshOpt
|
|
449
|
+
else {
|
|
450
|
+
Self.satLog(
|
|
451
|
+
"fetch: unix=\(unix) DECODE FAIL — ktxIn=\(ktxBytes.count)B packed=\(packedOpt?.count ?? -1) parsed=\(parsedOpt != nil) mesh=\(meshOpt != nil) shader=\(shaderFileName.prefix(40))"
|
|
452
|
+
)
|
|
453
|
+
DispatchQueue.main.async {
|
|
454
|
+
self.fetchLock.lock()
|
|
455
|
+
self.fetchInFlight.remove(unix)
|
|
456
|
+
self.fetchLock.unlock()
|
|
457
|
+
}
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let fc = Self.classifyShaderFileName(shaderFileName)
|
|
462
|
+
let pending = AguaceroSatelliteRenderer.PendingGpuUpload(
|
|
463
|
+
unix: unix,
|
|
464
|
+
mesh: mesh,
|
|
465
|
+
decoded: parsed,
|
|
466
|
+
trueColor: fc.trueColor,
|
|
467
|
+
colormapKind: fc.colormapKind,
|
|
468
|
+
useColormap: fc.useColormap
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
DispatchQueue.main.async {
|
|
472
|
+
self.fetchLock.lock()
|
|
473
|
+
self.fetchInFlight.remove(unix)
|
|
474
|
+
self.fetchLock.unlock()
|
|
475
|
+
|
|
476
|
+
if gen != self.decodeGeneration {
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
if !self.isLayerAdded {
|
|
480
|
+
if let w = self.window {
|
|
481
|
+
self.findMapViewAndAddLayer(in: w)
|
|
482
|
+
} else if let sv = self.superview {
|
|
483
|
+
self.findMapViewAndAddLayer(in: sv)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
Self.satLog(
|
|
487
|
+
"GPU queue: unix=\(unix) \(parsed.width)x\(parsed.height) tc=\(fc.trueColor) cmap=\(fc.colormapKind) layerOnMap=\(self.isLayerAdded)"
|
|
488
|
+
)
|
|
489
|
+
self.layerInstance.queueGpuUpload(pending)
|
|
490
|
+
if let mv = self.mapView {
|
|
491
|
+
GridRenderLayerBridge.triggerRepaint(on: mv)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@objc public func activateSatelliteCachedUnix(_ unix: Int64) {
|
|
498
|
+
Self.satLog("activateSatelliteCachedUnix: unix=\(unix) cached=\(layerInstance.isFrameCached(unix))")
|
|
499
|
+
layerInstance.activateCachedUnix(unix)
|
|
500
|
+
if let mapView {
|
|
501
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@objc public func updateStyleFromJson(_ json: String?) {
|
|
506
|
+
guard let json, !json.isEmpty,
|
|
507
|
+
let data = json.data(using: .utf8),
|
|
508
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
509
|
+
else { return }
|
|
510
|
+
layerInstance.setVisible((obj["visible"] as? Bool) ?? true)
|
|
511
|
+
layerInstance.setOpacity(Float((obj["opacity"] as? Double) ?? 1.0))
|
|
512
|
+
layerInstance.setFillSmoothing(obj["fillSmoothing"] as? Int ?? 0)
|
|
513
|
+
if let mapView {
|
|
514
|
+
GridRenderLayerBridge.triggerRepaint(on: mapView)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|