@aguacerowx/react-native 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/android/src/main/java/com/aguacerowx/reactnative/GridRenderLayer.java +42 -5
  2. package/android/src/main/res/raw/fragment_shader.glsl +18 -8
  3. package/ios/FragmentUniforms.swift +2 -0
  4. package/ios/GridRenderLayer.swift +24 -4
  5. package/ios/GridRenderLayerManager.mm +12 -0
  6. package/ios/GridRenderLayerView.h +1 -7
  7. package/ios/GridRenderLayerView.m +7 -1
  8. package/ios/Shaders.metal +131 -71
  9. package/ios/compiled-shaders/Shaders-device.metallib +0 -0
  10. package/ios/compiled-shaders/Shaders-simulator.metallib +0 -0
  11. package/lib/commonjs/android/src/main/java/com/aguacerowx/reactnative/GridRenderLayer.java +42 -5
  12. package/lib/commonjs/android/src/main/res/raw/fragment_shader.glsl +18 -8
  13. package/lib/commonjs/ios/FragmentUniforms.swift +2 -0
  14. package/lib/commonjs/ios/GridRenderLayer.swift +24 -4
  15. package/lib/commonjs/ios/GridRenderLayerManager.mm +12 -0
  16. package/lib/commonjs/ios/GridRenderLayerView.h +1 -7
  17. package/lib/commonjs/ios/GridRenderLayerView.m +7 -1
  18. package/lib/commonjs/ios/Shaders.metal +131 -71
  19. package/lib/commonjs/ios/compiled-shaders/Shaders-device.metallib +0 -0
  20. package/lib/commonjs/ios/compiled-shaders/Shaders-simulator.metallib +0 -0
  21. package/lib/commonjs/package.json +3 -3
  22. package/lib/commonjs/scripts/compile-shaders.js +62 -0
  23. package/lib/commonjs/scripts/compile-shaders.js.map +1 -0
  24. package/lib/commonjs/scripts/compile-shaders.sh +12 -0
  25. package/lib/commonjs/src/WeatherLayerManager.js +51 -17
  26. package/lib/commonjs/src/WeatherLayerManager.js.map +1 -1
  27. package/lib/module/android/src/main/java/com/aguacerowx/reactnative/GridRenderLayer.java +42 -5
  28. package/lib/module/android/src/main/res/raw/fragment_shader.glsl +18 -8
  29. package/lib/module/ios/FragmentUniforms.swift +2 -0
  30. package/lib/module/ios/GridRenderLayer.swift +24 -4
  31. package/lib/module/ios/GridRenderLayerManager.mm +12 -0
  32. package/lib/module/ios/GridRenderLayerView.h +1 -7
  33. package/lib/module/ios/GridRenderLayerView.m +7 -1
  34. package/lib/module/ios/Shaders.metal +131 -71
  35. package/lib/module/ios/compiled-shaders/Shaders-device.metallib +0 -0
  36. package/lib/module/ios/compiled-shaders/Shaders-simulator.metallib +0 -0
  37. package/lib/module/lib/commonjs/package.json +3 -3
  38. package/lib/module/lib/commonjs/scripts/compile-shaders.js +62 -0
  39. package/lib/module/lib/commonjs/scripts/compile-shaders.js.map +1 -0
  40. package/lib/module/lib/commonjs/src/WeatherLayerManager.js +51 -17
  41. package/lib/module/lib/commonjs/src/WeatherLayerManager.js.map +1 -1
  42. package/lib/module/package.json +3 -3
  43. package/lib/module/scripts/compile-shaders.js +60 -0
  44. package/lib/module/scripts/compile-shaders.js.map +1 -0
  45. package/lib/module/scripts/compile-shaders.sh +12 -0
  46. package/lib/module/src/WeatherLayerManager.js +51 -17
  47. package/lib/module/src/WeatherLayerManager.js.map +1 -1
  48. package/lib/typescript/scripts/compile-shaders.d.ts +3 -0
  49. package/lib/typescript/scripts/compile-shaders.d.ts.map +1 -0
  50. package/lib/typescript/src/WeatherLayerManager.d.ts.map +1 -1
  51. package/package.json +3 -3
  52. package/src/WeatherLayerManager.js +192 -151
  53. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/AguaceroCore.js +0 -1063
  54. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/AguaceroCore.js.map +0 -1
  55. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/coordinate_configs.js +0 -381
  56. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/coordinate_configs.js.map +0 -1
  57. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/default-colormaps.js +0 -1256
  58. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/default-colormaps.js.map +0 -1
  59. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/dictionaries.js +0 -4041
  60. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/dictionaries.js.map +0 -1
  61. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/events.js +0 -38
  62. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/events.js.map +0 -1
  63. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/fill-layer-worker.js +0 -30
  64. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/fill-layer-worker.js.map +0 -1
  65. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/getBundleId.js +0 -27
  66. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/getBundleId.js.map +0 -1
  67. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/index.js +0 -41
  68. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/index.js.map +0 -1
  69. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/map-styles.js +0 -301
  70. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/map-styles.js.map +0 -1
  71. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/unitConversions.js +0 -126
  72. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/unitConversions.js.map +0 -1
  73. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/package.json +0 -48
  74. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/AguaceroCore.js +0 -1055
  75. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/AguaceroCore.js.map +0 -1
  76. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/coordinate_configs.js +0 -381
  77. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/coordinate_configs.js.map +0 -1
  78. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/default-colormaps.js +0 -1256
  79. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/default-colormaps.js.map +0 -1
  80. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/dictionaries.js +0 -4041
  81. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/dictionaries.js.map +0 -1
  82. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/events.js +0 -38
  83. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/events.js.map +0 -1
  84. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/fill-layer-worker.js +0 -30
  85. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/fill-layer-worker.js.map +0 -1
  86. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/getBundleId.js +0 -27
  87. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/getBundleId.js.map +0 -1
  88. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/index.js +0 -41
  89. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/index.js.map +0 -1
  90. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/map-styles.js +0 -301
  91. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/map-styles.js.map +0 -1
  92. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/unitConversions.js +0 -126
  93. package/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/unitConversions.js.map +0 -1
  94. package/lib/module/lib/commonjs/android/build.gradle +0 -108
  95. package/lib/module/lib/commonjs/android/src/main/AndroidManifest.xml +0 -7
  96. package/lib/module/lib/commonjs/ios/FragmentUniforms.swift +0 -14
  97. package/lib/module/lib/commonjs/ios/GridRenderLayer.swift +0 -983
  98. package/lib/module/lib/commonjs/ios/GridRenderLayerBridge.swift +0 -30
  99. package/lib/module/lib/commonjs/ios/GridRenderLayerManager.mm +0 -146
  100. package/lib/module/lib/commonjs/ios/GridRenderLayerView.h +0 -23
  101. package/lib/module/lib/commonjs/ios/GridRenderLayerView.m +0 -199
  102. package/lib/module/lib/commonjs/ios/InspectorDataCache.swift +0 -66
  103. package/lib/module/lib/commonjs/ios/InspectorModule.m +0 -10
  104. package/lib/module/lib/commonjs/ios/InspectorModule.swift +0 -64
  105. package/lib/module/lib/commonjs/ios/Shaders.metal +0 -131
  106. package/lib/module/lib/commonjs/ios/WeatherFrameProcessorModule.m +0 -16
  107. package/lib/module/lib/commonjs/ios/WeatherFrameProcessorModule.swift +0 -104
  108. package/lib/module/lib/commonjs/ios/compiled-shaders/Shaders-device.metallib +0 -0
  109. package/lib/module/lib/commonjs/ios/compiled-shaders/Shaders-simulator.metallib +0 -0
  110. package/lib/module/lib/commonjs/ios/compiled-shaders/Shaders.metallib +0 -0
  111. package/lib/module/lib/commonjs/ios/generated/AguaceroWxReactNativeSpec-generated.mm +0 -0
  112. package/lib/module/lib/commonjs/ios/generated/AguaceroWxReactNativeSpec.h +0 -0
  113. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/AguaceroCore.js +0 -1063
  114. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/AguaceroCore.js.map +0 -1
  115. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/coordinate_configs.js +0 -381
  116. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/coordinate_configs.js.map +0 -1
  117. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/default-colormaps.js +0 -1256
  118. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/default-colormaps.js.map +0 -1
  119. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/dictionaries.js +0 -4041
  120. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/dictionaries.js.map +0 -1
  121. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/events.js +0 -38
  122. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/events.js.map +0 -1
  123. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/fill-layer-worker.js +0 -30
  124. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/fill-layer-worker.js.map +0 -1
  125. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/getBundleId.js +0 -27
  126. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/getBundleId.js.map +0 -1
  127. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/index.js +0 -41
  128. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/index.js.map +0 -1
  129. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/map-styles.js +0 -301
  130. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/map-styles.js.map +0 -1
  131. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/unitConversions.js +0 -126
  132. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/dist/unitConversions.js.map +0 -1
  133. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/package.json +0 -48
  134. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/AguaceroCore.js +0 -1059
  135. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/AguaceroCore.js.map +0 -1
  136. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/coordinate_configs.js +0 -381
  137. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/coordinate_configs.js.map +0 -1
  138. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/default-colormaps.js +0 -1256
  139. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/default-colormaps.js.map +0 -1
  140. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/dictionaries.js +0 -4041
  141. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/dictionaries.js.map +0 -1
  142. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/events.js +0 -38
  143. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/events.js.map +0 -1
  144. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/fill-layer-worker.js +0 -30
  145. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/fill-layer-worker.js.map +0 -1
  146. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/getBundleId.js +0 -27
  147. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/getBundleId.js.map +0 -1
  148. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/index.js +0 -41
  149. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/index.js.map +0 -1
  150. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/map-styles.js +0 -301
  151. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/map-styles.js.map +0 -1
  152. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/unitConversions.js +0 -126
  153. package/lib/module/lib/commonjs/node_modules/@aguacerowx/javascript-sdk/src/unitConversions.js.map +0 -1
  154. package/lib/module/lib/commonjs/scripts/compile-shaders.sh +0 -27
  155. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/AguaceroCore.js +0 -1063
  156. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/AguaceroCore.js.map +0 -1
  157. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/coordinate_configs.js +0 -381
  158. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/coordinate_configs.js.map +0 -1
  159. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/default-colormaps.js +0 -1256
  160. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/default-colormaps.js.map +0 -1
  161. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/dictionaries.js +0 -4041
  162. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/dictionaries.js.map +0 -1
  163. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/events.js +0 -38
  164. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/events.js.map +0 -1
  165. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/fill-layer-worker.js +0 -30
  166. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/fill-layer-worker.js.map +0 -1
  167. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/getBundleId.js +0 -27
  168. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/getBundleId.js.map +0 -1
  169. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/index.js +0 -41
  170. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/index.js.map +0 -1
  171. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/map-styles.js +0 -301
  172. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/map-styles.js.map +0 -1
  173. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/unitConversions.js +0 -126
  174. package/lib/module/node_modules/@aguacerowx/javascript-sdk/dist/unitConversions.js.map +0 -1
  175. package/lib/module/node_modules/@aguacerowx/javascript-sdk/package.json +0 -48
  176. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/AguaceroCore.js +0 -1047
  177. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/AguaceroCore.js.map +0 -1
  178. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/coordinate_configs.js +0 -375
  179. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/coordinate_configs.js.map +0 -1
  180. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/default-colormaps.js +0 -1250
  181. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/default-colormaps.js.map +0 -1
  182. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/dictionaries.js +0 -4035
  183. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/dictionaries.js.map +0 -1
  184. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/events.js +0 -31
  185. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/events.js.map +0 -1
  186. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/fill-layer-worker.js +0 -29
  187. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/fill-layer-worker.js.map +0 -1
  188. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/getBundleId.js +0 -21
  189. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/getBundleId.js.map +0 -1
  190. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/index.js +0 -12
  191. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/index.js.map +0 -1
  192. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/map-styles.js +0 -295
  193. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/map-styles.js.map +0 -1
  194. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/unitConversions.js +0 -119
  195. package/lib/module/node_modules/@aguacerowx/javascript-sdk/src/unitConversions.js.map +0 -1
@@ -1,983 +0,0 @@
1
- import Foundation
2
- import MapboxMaps
3
- import Metal
4
- import libzstd
5
- import simd
6
-
7
- // MARK: - Swift-only wrapper for CustomLayerHost conformance
8
- @objc internal final class GridRenderLayerHost: NSObject, CustomLayerHost {
9
- weak var layer: GridRenderLayer?
10
-
11
- init(layer: GridRenderLayer) {
12
- self.layer = layer
13
- super.init()
14
- }
15
-
16
- func renderingWillStart(_ metalDevice: MTLDevice, colorPixelFormat: UInt, depthStencilPixelFormat: UInt) {
17
- layer?.internalRenderingWillStart(metalDevice, colorPixelFormat: colorPixelFormat, depthStencilPixelFormat: depthStencilPixelFormat)
18
- }
19
-
20
- func render(_ parameters: CustomLayerRenderParameters, mtlCommandBuffer: MTLCommandBuffer, mtlRenderPassDescriptor: MTLRenderPassDescriptor) {
21
- layer?.internalRender(parameters, mtlCommandBuffer: mtlCommandBuffer, mtlRenderPassDescriptor: mtlRenderPassDescriptor)
22
- }
23
-
24
- func renderingWillEnd() {
25
- layer?.internalRenderingWillEnd()
26
- }
27
- }
28
-
29
- @objc(GridRenderLayer)
30
- public class GridRenderLayer: NSObject {
31
-
32
- // MARK: - Properties
33
-
34
- public var id: String
35
- @objc internal var hostWrapper: GridRenderLayerHost!
36
-
37
- // Metal objects
38
- private var device: MTLDevice!
39
- private var commandQueue: MTLCommandQueue!
40
- private var pipelineState: MTLRenderPipelineState!
41
- private var vertexBuffer: MTLBuffer?
42
- private var indexBuffer: MTLBuffer?
43
- private var dataTexture: MTLTexture?
44
- private var colormapTexture: MTLTexture?
45
- private var dataSamplerState: MTLSamplerState!
46
- private var colormapSamplerState: MTLSamplerState!
47
- private var isDataSamplerLinear: Bool = false
48
-
49
- private var pendingColormapUpdate: String?
50
- private var pendingDataUpdate: (data: String, nx: NSNumber, ny: NSNumber, scale: NSNumber, offset: NSNumber, missing: NSNumber, scaleType: String)?
51
- private var pendingGeometryUpdate: (corners: [String: Any], gridDef: [String: Any])?
52
-
53
- // Layer state
54
- private var indexCount: Int = 0
55
- private var uniforms = FragmentUniforms(
56
- opacity: 1.0,
57
- dataRange: SIMD2<Float>(0.0, 1.0),
58
- scale: 1.0,
59
- offset: 0.0,
60
- missingQuantized: 127.0,
61
- textureSize: SIMD2<Float>(0.0, 0.0),
62
- smoothing: 1,
63
- scaleType: 0
64
- )
65
- private var isVisible = false
66
- private var pendingActiveFrameKey: String?
67
- private let inspectorCache = InspectorDataCache.shared
68
- private struct FrameMetadata {
69
- let texture: MTLTexture
70
- let scale: Float
71
- let offset: Float
72
- let missing: Float
73
- let scaleType: Int
74
- let nx: Float
75
- let ny: Float
76
- let filePath: String
77
- let originalScale: Float // ADD THIS
78
- let originalOffset: Float // ADD THIS
79
- }
80
- private var frameCache: [String: FrameMetadata] = [:]
81
- private let highPriorityTextureQueue = DispatchQueue(label: "com.aguacero.texture-processing-high-priority", qos: .userInitiated)
82
-
83
- // The concurrent queue for background preloading all other frames.
84
- private let backgroundTextureQueue = DispatchQueue(label: "com.aguacero.texture-processing-background", qos: .utility, attributes: .concurrent)
85
- // MARK: - Initializer
86
- private struct VertexInfo {
87
- var mercX: Float
88
- var mercY: Float
89
- var texU: Float
90
- var texV: Float
91
- var index: UInt16
92
- }
93
- @objc
94
- public init(id: String) {
95
- self.id = id
96
- super.init()
97
- self.hostWrapper = GridRenderLayerHost(layer: self)
98
- print("🟢 [GridRenderLayer] Initialized with ID: \(id)")
99
- }
100
-
101
- @objc public func getHostWrapper() -> Any {
102
- return self.hostWrapper as Any
103
- }
104
-
105
- // MARK: - Public Methods (called from Manager)
106
-
107
- @objc public func setOpacity(value: Float) {
108
- self.uniforms.opacity = value
109
- print("🟢 [GridRenderLayer] Set opacity: \(value)")
110
- // ADD THIS: Force the map to repaint to show the opacity change immediately.
111
- NotificationCenter.default.post(name: NSNotification.Name("TriggerMapRepaint"), object: nil)
112
- }
113
-
114
- @objc public func setDataRange(value: [NSNumber]) {
115
- self.uniforms.dataRange = SIMD2<Float>(value[0].floatValue, value[1].floatValue)
116
- print("🟢 [GridRenderLayer] Set data range: \(value)")
117
- // ADD THIS: Force the map to repaint to show the data range change immediately.
118
- NotificationCenter.default.post(name: NSNotification.Name("TriggerMapRepaint"), object: nil)
119
- }
120
-
121
- @objc public func setSmoothing(value: Bool) {
122
- self.uniforms.smoothing = value ? 1 : 0
123
- print("🟢 [GridRenderLayer] Set smoothing: \(value)")
124
- // ADD THIS: Force the map to repaint to show the smoothing change immediately.
125
- NotificationCenter.default.post(name: NSNotification.Name("TriggerMapRepaint"), object: nil)
126
- }
127
-
128
- @objc public func clear() {
129
- self.isVisible = false
130
- self.inspectorCache.clear()
131
- print("🟢 [GridRenderLayer] Cleared")
132
- }
133
-
134
- @objc public func updateDataParameters(scale: NSNumber, offset: NSNumber, missing: NSNumber, scaleType: NSNumber) { // ADD scaleType parameter
135
- // Update both the inspector cache AND the rendering uniforms
136
- self.uniforms.scale = scale.floatValue
137
- self.uniforms.offset = offset.floatValue
138
- self.uniforms.missingQuantized = missing.floatValue
139
- self.uniforms.scaleType = Int32(scaleType.intValue) // ADD THIS
140
-
141
- inspectorCache.update(
142
- data: inspectorCache.lastDecompressedData,
143
- nx: inspectorCache.nx,
144
- ny: inspectorCache.ny,
145
- scale: scale.floatValue,
146
- offset: offset.floatValue,
147
- missing: missing.floatValue,
148
- scaleType: scaleType.intValue // ADD THIS
149
- )
150
-
151
- print("🟢 [GridRenderLayer] Updated data parameters - scale: \(scale), offset: \(offset), missing: \(missing), scaleType: \(scaleType)")
152
- }
153
-
154
- @objc public func updateColormapTexture(colormapAsBase64: String) {
155
- guard let device = self.device else {
156
- pendingColormapUpdate = colormapAsBase64
157
- return
158
- }
159
- guard let data = Data(base64Encoded: colormapAsBase64) else { return }
160
- let textureDescriptor = MTLTextureDescriptor()
161
- textureDescriptor.pixelFormat = .rgba8Unorm
162
- textureDescriptor.width = 256
163
- textureDescriptor.height = 1
164
- textureDescriptor.usage = .shaderRead
165
- guard let texture = device.makeTexture(descriptor: textureDescriptor) else { return }
166
- texture.replace(
167
- region: MTLRegionMake2D(0, 0, 256, 1),
168
- mipmapLevel: 0,
169
- withBytes: (data as NSData).bytes,
170
- bytesPerRow: 256 * 4
171
- )
172
- self.colormapTexture = texture
173
- }
174
-
175
- @objc(clearGpuCache) // Explicitly name the Obj-C selector
176
- public func clearGpuCache() {
177
- backgroundTextureQueue.async {
178
- self.frameCache.removeAll()
179
- print("🟢 [GridRenderLayer] GPU frame cache cleared.")
180
- }
181
- }
182
-
183
- private func processRawData(fileData: Data) -> Data? {
184
- guard let decompressedDeltas = self.decompressZstd(data: fileData) else {
185
- print("❌ [GridRenderLayer] Failed to decompress zstd data")
186
- return nil
187
- }
188
- let reconstructedData = self.reconstructData(decompressedDeltas: decompressedDeltas)
189
- let finalTextureBytes = self.transformData(finalData: reconstructedData)
190
- return finalTextureBytes
191
- }
192
-
193
- private func createTextureFromBytes(bytes: Data, nx: Int, ny: Int) -> MTLTexture? {
194
- guard let device = self.device else { return nil }
195
- let textureDescriptor = MTLTextureDescriptor()
196
- textureDescriptor.pixelFormat = .r8Unorm
197
- textureDescriptor.width = nx
198
- textureDescriptor.height = ny
199
- textureDescriptor.usage = .shaderRead
200
-
201
- guard let texture = device.makeTexture(descriptor: textureDescriptor) else { return nil }
202
- texture.replace(
203
- region: MTLRegionMake2D(0, 0, nx, ny),
204
- mipmapLevel: 0,
205
- withBytes: (bytes as NSData).bytes,
206
- bytesPerRow: nx
207
- )
208
- return texture
209
- }
210
-
211
- private func updateInspectorCache(filePath: String, nx: Int, ny: Int, scale: Float, offset: Float, missing: Float, scaleType: Int) {
212
- highPriorityTextureQueue.async {
213
-
214
- guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
215
- print("❌ [Inspector] FATAL: Failed to read file data from path.")
216
- self.inspectorCache.clear()
217
- return
218
- }
219
- print("💡 [Inspector] 2. Successfully read \(fileData.count) bytes from file.")
220
-
221
- guard let decompressedDeltas = self.decompressZstd(data: fileData) else {
222
- print("❌ [Inspector] FATAL: Failed to decompress Zstd data.")
223
- self.inspectorCache.clear()
224
- return
225
- }
226
- print("💡 [Inspector] 3. Successfully decompressed data.")
227
-
228
- let reconstructedData = self.reconstructData(decompressedDeltas: decompressedDeltas)
229
- print("💡 [Inspector] 4. Reconstructed data with \(reconstructedData.count) bytes.")
230
-
231
- self.inspectorCache.update(
232
- data: reconstructedData,
233
- nx: nx,
234
- ny: ny,
235
- scale: scale,
236
- offset: offset,
237
- missing: missing,
238
- scaleType: scaleType
239
- )
240
-
241
- print("💡 [Inspector] 5. ✅ Cache updated successfully with scaleType=\(scaleType)")
242
- }
243
- }
244
-
245
- @objc(setActiveFrameWithCacheKey:)
246
- public func setActiveFrame(cacheKey: String) {
247
- // First, check if the frame is already in the cache.
248
- if let frame = frameCache[cacheKey] {
249
- print("⚡️ [GridRenderLayer] Cache HIT for key: \(cacheKey). Swapping texture.")
250
-
251
- self.dataTexture = frame.texture
252
- self.uniforms.scale = frame.scale
253
- self.uniforms.offset = frame.offset
254
- self.uniforms.missingQuantized = frame.missing
255
- self.uniforms.textureSize = SIMD2<Float>(frame.nx, frame.ny)
256
- self.uniforms.scaleType = Int32(frame.scaleType)
257
-
258
- self.isVisible = true
259
- self.pendingActiveFrameKey = nil // Clear any pending key
260
- NotificationCenter.default.post(name: NSNotification.Name("TriggerMapRepaint"), object: nil)
261
-
262
- // --- ADD THIS ---
263
- // Now that the frame is active, update the inspector cache with its data.
264
- self.updateInspectorCache(
265
- filePath: frame.filePath,
266
- nx: Int(frame.nx),
267
- ny: Int(frame.ny),
268
- scale: frame.scale,
269
- offset: frame.offset,
270
- missing: frame.missing,
271
- scaleType: frame.scaleType
272
- )
273
-
274
- } else {
275
- // --- FIX: If it's a miss, store the key and wait for it to be primed. ---
276
- print("⚠️ [GridRenderLayer] setActiveFrame cache MISS for key: \(cacheKey). Will apply when primed.")
277
- self.pendingActiveFrameKey = cacheKey
278
- self.isVisible = false // Hide the layer until the frame is ready
279
- }
280
- }
281
-
282
- @objc(primeGpuCacheWithFrameInfo:)
283
- public func primeGpuCache(frameInfo: [String: [String: Any]]) {
284
- let group = DispatchGroup()
285
-
286
- if (frameInfo.count > 1) {
287
- print("🟡 [GridRenderLayer] Starting to prime \(frameInfo.count) textures concurrently...")
288
- }
289
-
290
- for (cacheKey, info) in frameInfo {
291
- group.enter()
292
-
293
- backgroundTextureQueue.async {
294
- defer { group.leave() }
295
- if self.frameCache[cacheKey] != nil { return }
296
-
297
- guard let filePath = info["filePath"] as? String,
298
- let nx = info["nx"] as? NSNumber,
299
- let ny = info["ny"] as? NSNumber,
300
- let scale = info["scale"] as? NSNumber,
301
- let offset = info["offset"] as? NSNumber,
302
- let missing = info["missing"] as? NSNumber,
303
- let scaleTypeStr = info["scaleType"] as? String,
304
- let originalScale = info["originalScale"] as? NSNumber, // ADD THIS
305
- let originalOffset = info["originalOffset"] as? NSNumber, // ADD THIS
306
- let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
307
- let finalTextureBytes = self.processRawData(fileData: fileData)
308
- else {
309
- print("❌ [GridRenderLayer] Skipping prime for \(cacheKey), missing data.")
310
- return
311
- }
312
-
313
- DispatchQueue.main.sync {
314
- if let texture = self.createTextureFromBytes(bytes: finalTextureBytes, nx: nx.intValue, ny: ny.intValue) {
315
- let metadata = FrameMetadata(
316
- texture: texture,
317
- scale: scale.floatValue,
318
- offset: offset.floatValue,
319
- missing: missing.floatValue,
320
- scaleType: (scaleTypeStr == "sqrt") ? 1 : 0,
321
- nx: nx.floatValue,
322
- ny: ny.floatValue,
323
- filePath: filePath,
324
- originalScale: originalScale.floatValue, // ADD THIS
325
- originalOffset: originalOffset.floatValue // ADD THIS
326
- )
327
- self.frameCache[cacheKey] = metadata
328
- print(" ✅ [GridRenderLayer] Primed and cached frame for key: \(cacheKey)")
329
-
330
- if let pendingKey = self.pendingActiveFrameKey, pendingKey == cacheKey {
331
- print("👍 [GridRenderLayer] The pending frame is now ready. Activating it.")
332
- self.setActiveFrame(cacheKey: pendingKey)
333
- }
334
- }
335
- }
336
- }
337
- }
338
-
339
- group.notify(queue: .main) {
340
- if (frameInfo.count > 1) {
341
- print("🎉 [GridRenderLayer] All \(frameInfo.count) frames have been processed and cached to the GPU.")
342
- }
343
- }
344
- }
345
-
346
- @objc public func updateDataTexture(data: String, nx: NSNumber, ny: NSNumber, scale: NSNumber, offset: NSNumber, missing: NSNumber, scaleType: String) {
347
- print("🚀 [GridRenderLayer] FAST LANE: updateDataTexture called for initial frame...")
348
-
349
- let filePath = data // Assuming 'data' is always the file path for this method.
350
-
351
- // USE THE HIGH-PRIORITY QUEUE
352
- highPriorityTextureQueue.async {
353
- guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else { return }
354
-
355
- guard let finalTextureBytes = self.processRawData(fileData: fileData) else {
356
- print("❌ [GridRenderLayer] FAST LANE: Failed to process initial frame data.")
357
- return
358
- }
359
-
360
- DispatchQueue.main.async {
361
- guard let texture = self.createTextureFromBytes(bytes: finalTextureBytes, nx: nx.intValue, ny: ny.intValue) else {
362
- return
363
- }
364
-
365
- self.dataTexture = texture
366
- self.uniforms.scale = scale.floatValue
367
- self.uniforms.offset = offset.floatValue
368
- self.uniforms.missingQuantized = missing.floatValue
369
- self.uniforms.scaleType = Int32((scaleType == "sqrt") ? 1 : 0)
370
- self.uniforms.textureSize = SIMD2<Float>(nx.floatValue, ny.floatValue)
371
-
372
- self.isVisible = true
373
- print("✅ [GridRenderLayer] FAST LANE: Initial frame processed and displayed.")
374
- NotificationCenter.default.post(name: NSNotification.Name("TriggerMapRepaint"), object: nil)
375
-
376
- // --- ADD THIS ---
377
- // Update the inspector cache for the initial frame.
378
- self.updateInspectorCache(
379
- filePath: filePath,
380
- nx: nx.intValue,
381
- ny: ny.intValue,
382
- scale: scale.floatValue,
383
- offset: offset.floatValue,
384
- missing: missing.floatValue,
385
- scaleType: (scaleType == "sqrt") ? 1 : 0
386
- )
387
- }
388
- }
389
- }
390
-
391
- @objc public func updateGeometry(corners: [String: Any], gridDef: [String: Any]) {
392
- print("🟢 [GridRenderLayer] updateGeometry called")
393
- print(" device: \(self.device != nil)")
394
-
395
- guard self.device != nil else {
396
- print("⚠️ [GridRenderLayer] Device not ready yet, storing geometry for later")
397
- pendingGeometryUpdate = (corners: corners, gridDef: gridDef)
398
- return
399
- }
400
-
401
- print(" Device is ready, processing geometry immediately")
402
-
403
- DispatchQueue.global(qos: .userInitiated).async {
404
- print(" 🔄 Generating geometry on background thread")
405
-
406
- var vertices: [Float] = []
407
- var indices: [UInt16] = []
408
-
409
- self.generateGeometryData(gridDef: gridDef, vertices: &vertices, indices: &indices)
410
-
411
- if vertices.isEmpty || indices.isEmpty {
412
- print("❌ [GridRenderLayer] No geometry generated")
413
- return
414
- }
415
-
416
- print(" ✅ Generated \(vertices.count/4) vertices, \(indices.count/3) triangles")
417
-
418
- self.indexCount = indices.count
419
-
420
- DispatchQueue.main.async {
421
- print(" 🎨 Creating buffers on main thread")
422
-
423
- self.vertexBuffer = self.device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Float>.size, options: [])
424
- self.indexBuffer = self.device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: [])
425
-
426
- print("🟢 [GridRenderLayer] Geometry updated: \(vertices.count/4) vertices, \(indices.count/3) triangles")
427
- print(" vertexBuffer: \(self.vertexBuffer != nil)")
428
- print(" indexBuffer: \(self.indexBuffer != nil)")
429
- print(" indexCount: \(self.indexCount)")
430
-
431
- // TRIGGER A REPAINT - ADD THIS:
432
- NotificationCenter.default.post(name: NSNotification.Name("TriggerMapRepaint"), object: nil)
433
- }
434
- }
435
- }
436
-
437
- private func decompressZstd(data: Data) -> Data? {
438
- let decompressedSize = data.withUnsafeBytes { ptr in
439
- ZSTD_getFrameContentSize(ptr.baseAddress, data.count)
440
- }
441
-
442
- guard Int64(bitPattern: decompressedSize) > 0 else {
443
- print("❌ [GridRenderLayer] Could not determine decompressed size")
444
- return nil
445
- }
446
-
447
- var decompressedData = Data(count: Int(decompressedSize))
448
-
449
- let result = decompressedData.withUnsafeMutableBytes { decompressedPtr -> Int in
450
- data.withUnsafeBytes { compressedPtr -> Int in
451
- let returnValue = ZSTD_decompress(
452
- decompressedPtr.baseAddress,
453
- Int(decompressedSize),
454
- compressedPtr.baseAddress,
455
- data.count
456
- )
457
- return Int(returnValue)
458
- }
459
- }
460
-
461
- guard result > 0 && result == decompressedData.count else {
462
- if result > 0 {
463
- let size_t_result = size_t(result)
464
- if let errorName = ZSTD_getErrorName(size_t_result) {
465
- let errorString = String(cString: errorName)
466
- print("❌ [GridRenderLayer] Zstd decompression failed: \(errorString)")
467
- }
468
- }
469
- return nil
470
- }
471
- return decompressedData
472
- }
473
-
474
- // MARK: - Private Helper Methods
475
-
476
- private func reconstructData(decompressedDeltas: Data) -> Data {
477
- guard !decompressedDeltas.isEmpty else { return Data() }
478
- var reconstructedData = Data(count: decompressedDeltas.count)
479
- reconstructedData[0] = decompressedDeltas[0]
480
- for i in 1..<decompressedDeltas.count {
481
- reconstructedData[i] = UInt8(Int8(bitPattern: reconstructedData[i-1]) &+ Int8(bitPattern: decompressedDeltas[i]))
482
- }
483
- return reconstructedData
484
- }
485
-
486
- private func transformData(finalData: Data) -> Data {
487
- var transformedData = Data(count: finalData.count)
488
- for i in 0..<finalData.count {
489
- transformedData[i] = UInt8(Int16(Int8(bitPattern: finalData[i])) + 128)
490
- }
491
- return transformedData
492
- }
493
-
494
- private func isLCCType(gridDef: [String: Any]) -> Bool {
495
- if let type = gridDef["type"] as? String {
496
- return type == "lambert_conformal_conic"
497
- }
498
- return false
499
- }
500
-
501
- private func generateLCCGeometry(gridDef: [String: Any], vertices: inout [Float], indices: inout [UInt16]) {
502
- guard let gridParams = gridDef["grid_params"] as? [String: Any],
503
- let projParams = gridDef["proj_params"] as? [String: Any],
504
- let nx = gridParams["nx"] as? Int,
505
- let ny = gridParams["ny"] as? Int,
506
- let dx = gridParams["dx"] as? Double,
507
- let dy = gridParams["dy"] as? Double,
508
- let x_origin = gridParams["x_origin"] as? Double,
509
- let y_origin = gridParams["y_origin"] as? Double else {
510
- return
511
- }
512
-
513
- let subdivisions = 60
514
- let TILE_SIZE: Double = 512.0
515
-
516
- let x_min = x_origin
517
- let y_max = y_origin
518
- let x_max = x_origin + Double(nx - 1) * dx
519
- let y_min = y_origin + Double(ny - 1) * dy
520
-
521
- var vertexGrid: [[VertexInfo?]] = Array(repeating: Array(repeating: nil, count: subdivisions + 1), count: subdivisions + 1)
522
- var validVertexCount: UInt16 = 0
523
-
524
- // Generate vertices
525
- for row in 0...subdivisions {
526
- for col in 0...subdivisions {
527
- let t_x = Double(col) / Double(subdivisions)
528
- let t_y = Double(row) / Double(subdivisions)
529
-
530
- let proj_x = x_min + t_x * (x_max - x_min)
531
- let proj_y = y_max + t_y * (y_min - y_max)
532
-
533
- // Convert LCC projection coordinates to lat/lon
534
- if let (lon, lat) = lccToLonLat(i: proj_x, j: proj_y, gridDef: gridDef) {
535
- // Convert lat/lon to Mercator
536
- let mercX_normalized = (lon + 180.0) / 360.0
537
- let clampedLat = max(-85.05112878, min(85.05112878, lat))
538
- let sinLatitude = sin(clampedLat * .pi / 180.0)
539
- let mercY_normalized = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * .pi)
540
-
541
- let mercX = mercX_normalized * TILE_SIZE
542
- let mercY = mercY_normalized * TILE_SIZE
543
-
544
- // Check for invalid values
545
- if !mercX.isFinite || !mercY.isFinite {
546
- vertexGrid[row][col] = nil
547
- continue
548
- }
549
-
550
- let tex_u = Float(t_x)
551
- let tex_v = Float(t_y)
552
-
553
- let vInfo = VertexInfo(
554
- mercX: Float(mercX),
555
- mercY: Float(mercY),
556
- texU: tex_u,
557
- texV: tex_v,
558
- index: validVertexCount
559
- )
560
-
561
- vertexGrid[row][col] = vInfo
562
-
563
- vertices.append(Float(mercX))
564
- vertices.append(Float(mercY))
565
- vertices.append(tex_u)
566
- vertices.append(tex_v)
567
-
568
- validVertexCount += 1
569
- } else {
570
- vertexGrid[row][col] = nil
571
- }
572
- }
573
- }
574
-
575
- if vertices.isEmpty {
576
- print("❌ [LCC Geometry] No valid vertices generated")
577
- return
578
- }
579
-
580
- // Generate indices
581
- for row in 0..<subdivisions {
582
- for col in 0..<subdivisions {
583
- guard let topLeft = vertexGrid[row][col],
584
- let topRight = vertexGrid[row][col + 1],
585
- let bottomLeft = vertexGrid[row + 1][col],
586
- let bottomRight = vertexGrid[row + 1][col + 1] else {
587
- continue
588
- }
589
-
590
- indices.append(topLeft.index)
591
- indices.append(bottomLeft.index)
592
- indices.append(topRight.index)
593
-
594
- indices.append(topRight.index)
595
- indices.append(bottomLeft.index)
596
- indices.append(bottomRight.index)
597
- }
598
- }
599
- }
600
-
601
- private func lccToLonLat(i: Double, j: Double, gridDef: [String: Any]) -> (lon: Double, lat: Double)? {
602
- guard let projParams = gridDef["proj_params"] as? [String: Any],
603
- let lat_0 = projParams["lat_0"] as? Double, // latitude of origin
604
- let lon_0 = projParams["lon_0"] as? Double, // longitude of origin
605
- let lat_1 = projParams["lat_1"] as? Double, // first standard parallel
606
- let lat_2 = projParams["lat_2"] as? Double, // second standard parallel (HRRR uses two!)
607
- let r_earth = projParams["R"] as? Double else {
608
- print("❌ [LCC Geometry] Failed to extract LCC parameters.")
609
- return nil
610
- }
611
-
612
- let π = Double.pi
613
- let toRad = π / 180.0
614
- let toDeg = 180.0 / π
615
-
616
- let lat1_rad = lat_1 * toRad
617
- let lat2_rad = lat_2 * toRad
618
- let lat0_rad = lat_0 * toRad
619
- let lon0_rad = lon_0 * toRad
620
-
621
- // Calculate cone constant (n) using two standard parallels
622
- let n: Double
623
- if abs(lat_1 - lat_2) < 1e-10 {
624
- n = sin(lat1_rad) // Single parallel case
625
- } else {
626
- n = log(cos(lat1_rad) / cos(lat2_rad)) / log(tan(π/4.0 + lat2_rad/2.0) / tan(π/4.0 + lat1_rad/2.0))
627
- }
628
-
629
- let F = cos(lat1_rad) * pow(tan(π/4.0 + lat1_rad/2.0), n) / n
630
- let rho_0 = r_earth * F * pow(tan(π/4.0 + lat0_rad/2.0), -n)
631
-
632
- // i, j are the x, y projection coordinates
633
- let x = i
634
- let y = j
635
-
636
- let rho = sqrt(x * x + (rho_0 - y) * (rho_0 - y))
637
- if rho < 1e-10 {
638
- return (lon: lon_0, lat: lat_0)
639
- }
640
-
641
- let theta = atan2(x, rho_0 - y)
642
-
643
- let lon = lon0_rad + theta / n
644
- let lat = 2.0 * atan(pow(r_earth * F / rho, 1.0 / n)) - π/2.0
645
-
646
- let lonDeg = lon * toDeg
647
- let latDeg = lat * toDeg
648
-
649
- if !lonDeg.isFinite || !latDeg.isFinite {
650
- return nil
651
- }
652
-
653
- return (lon: lonDeg, lat: latDeg)
654
- }
655
-
656
- private func generateGeometryData(gridDef: [String: Any], vertices: inout [Float], indices: inout [UInt16]) {
657
- guard let gridParams = gridDef["grid_params"] as? [String: Any] else {
658
- return
659
- }
660
-
661
- // GFS Global Grid Path
662
- if isGFSType(gridParams: gridParams) {
663
- let subdivisions = 120
664
- let verticesPerRow = (subdivisions * 3) + 1
665
- let TILE_SIZE: Double = 512.0
666
-
667
- for row in 0...subdivisions {
668
- for col in 0...(subdivisions * 3) {
669
- let v_interp = Float(row) / Float(subdivisions)
670
- let u_interp = Float(col) / Float(subdivisions)
671
- let lon = -540.0 + Double(u_interp) * 1080.0
672
- let lat = -90.0 + Double(v_interp) * 180.0
673
-
674
- let merc = lonLatToMercator(lon: lon, lat: lat, tileSize: TILE_SIZE)
675
- vertices.append(contentsOf: [merc.x, merc.y])
676
-
677
- let tex_u = Float((lon + 180.0) / 360.0)
678
- let tex_v = 1.0 - v_interp
679
- vertices.append(contentsOf: [tex_u, tex_v])
680
- }
681
- }
682
-
683
- for row in 0..<subdivisions {
684
- for col in 0..<(subdivisions * 3) {
685
- let tl = UInt16(row * verticesPerRow + col)
686
- let tr = tl + 1
687
- let bl = UInt16((row + 1) * verticesPerRow + col)
688
- let br = bl + 1
689
- indices.append(contentsOf: [tl, bl, tr, tr, bl, br])
690
- }
691
- }
692
- return
693
- }
694
- if isLCCType(gridDef: gridDef) {
695
- generateLCCGeometry(gridDef: gridDef, vertices: &vertices, indices: &indices)
696
- return
697
- }
698
-
699
- // Generic Grid Path (MRMS, etc.)
700
- let nx = gridParams["nx"] as? Int ?? 0
701
- let ny = gridParams["ny"] as? Int ?? 0
702
- let lon_first = gridParams["lon_first"] as? Double ?? 0.0
703
- let lat_first = gridParams["lat_first"] as? Double ?? 90.0
704
- let dx = gridParams["dx_degrees"] as? Double ?? 0.0
705
- let dy = gridParams["dy_degrees"] as? Double ?? 0.0
706
- let lon_last = gridParams["lon_last"] as? Double ?? (lon_first + Double(nx - 1) * dx)
707
- let lat_last = gridParams["lat_last"] as? Double ?? (lat_first + Double(ny - 1) * dy)
708
- let lat_span = lat_last - lat_first
709
- let isSouthToNorth = lat_span > 0
710
-
711
- let data_lon_first_180 = lon_first > 180 ? lon_first - 360 : lon_first
712
- let data_lon_last_180 = lon_last > 180 ? lon_last - 360 : lon_last
713
- let data_lon_range = data_lon_last_180 - data_lon_first_180
714
-
715
- let subdivisions_x = 120
716
- let subdivisions_y = 60
717
- let verticesPerRow = subdivisions_x + 1
718
- let TILE_SIZE: Double = 512.0
719
-
720
- // 🔑 FIX: Only create ONE world copy for regional data, THREE for global data
721
- let worldCopies = (data_lon_range > 300) ? [-1, 0, 1] : [0] // Only wrap if nearly global
722
-
723
- for world_copy in worldCopies {
724
- let vertexStartIndex = UInt16(vertices.count / 4)
725
- let lon_offset = Double(world_copy) * 360.0
726
-
727
- // Generate vertices for this world copy
728
- for row in 0...subdivisions_y {
729
- for col in 0...subdivisions_x {
730
- let v_interp = Float(row) / Float(subdivisions_y)
731
- let u_interp = Float(col) / Float(subdivisions_x)
732
-
733
- let vertex_lon = data_lon_first_180 + (Double(u_interp) * data_lon_range)
734
- let vertex_lat = lat_first + (Double(v_interp) * lat_span)
735
-
736
- let mercX_normalized = ((vertex_lon + lon_offset) + 180.0) / 360.0
737
- let clampedLat = max(-85.05112878, min(85.05112878, vertex_lat))
738
- let sinLatitude = sin(clampedLat * .pi / 180.0)
739
- let mercY_normalized = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * .pi)
740
- let mercX = mercX_normalized * TILE_SIZE
741
- let mercY = mercY_normalized * TILE_SIZE
742
-
743
- vertices.append(contentsOf: [Float(mercX), Float(mercY)])
744
-
745
- let tex_u = u_interp
746
- let tex_v = isSouthToNorth ? (1.0 - v_interp) : v_interp
747
-
748
- vertices.append(contentsOf: [tex_u, tex_v])
749
- }
750
- }
751
-
752
- // Generate indices (same as before)
753
- for row in 0..<UInt16(subdivisions_y) {
754
- for col in 0..<UInt16(subdivisions_x) {
755
- let tl = vertexStartIndex + row * UInt16(verticesPerRow) + col
756
- let tr = tl + 1
757
- let bl = vertexStartIndex + (row + 1) * UInt16(verticesPerRow) + col
758
- let br = bl + 1
759
-
760
- if isSouthToNorth {
761
- indices.append(contentsOf: [tl, bl, tr, tr, bl, br])
762
- } else {
763
- indices.append(contentsOf: [tl, tr, bl, bl, tr, br])
764
- }
765
- }
766
- }
767
- }
768
- }
769
-
770
- private func isGFSType(gridParams: [String: Any]) -> Bool {
771
- return (gridParams["lon_first"] as? Double) == 0.0 &&
772
- abs((gridParams["lat_first"] as? Double) ?? -1) == 90.0
773
- }
774
-
775
- private func lonLatToMercator(lon: Double, lat: Double, tileSize: Double) -> (x: Float, y: Float) {
776
- let mercX_normalized = (lon + 180.0) / 360.0
777
- let clampedLat = max(-85.05112878, min(85.05112878, lat))
778
- let sinLatitude = sin(clampedLat * .pi / 180.0)
779
- let mercY_normalized = 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * .pi)
780
-
781
- return (x: Float(mercX_normalized * tileSize), y: Float(mercY_normalized * tileSize))
782
- }
783
-
784
- // MARK: - Internal methods for CustomLayerHost (called by wrapper)
785
-
786
- internal func internalRenderingWillStart(_ metalDevice: MTLDevice, colorPixelFormat: UInt, depthStencilPixelFormat: UInt) {
787
- print("🟢 [GridRenderLayer] renderingWillStart called")
788
- self.device = metalDevice
789
- self.commandQueue = metalDevice.makeCommandQueue()
790
-
791
- let bundle = Bundle(for: GridRenderLayer.self)
792
-
793
- // Determine if we're running on simulator or device
794
- #if targetEnvironment(simulator)
795
- let metallibName = "Shaders-simulator"
796
- #else
797
- let metallibName = "Shaders-device"
798
- #endif
799
-
800
- // Try to load pre-compiled metallib for the current platform
801
- let defaultLibrary: MTLLibrary?
802
- if let metallibUrl = bundle.url(forResource: metallibName, withExtension: "metallib"),
803
- let library = try? metalDevice.makeLibrary(URL: metallibUrl) {
804
- print("✅ [GridRenderLayer] Loaded pre-compiled metallib (\(metallibName))")
805
- defaultLibrary = library
806
- }
807
- // Fall back to compiling from .metal source (for development with npm link)
808
- else if let metalUrl = bundle.url(forResource: "Shaders", withExtension: "metal"),
809
- let source = try? String(contentsOf: metalUrl),
810
- let library = try? metalDevice.makeLibrary(source: source, options: nil) {
811
- print("✅ [GridRenderLayer] Compiled Metal shader from source")
812
- defaultLibrary = library
813
- }
814
- // Neither worked
815
- else {
816
- print("❌ [GridRenderLayer] Could not find or compile Metal shaders")
817
- print(" Bundle path: \(bundle.bundlePath)")
818
- return
819
- }
820
-
821
- guard let library = defaultLibrary else {
822
- print("❌ [GridRenderLayer] Failed to create Metal library")
823
- return
824
- }
825
-
826
- let vertexFunction = library.makeFunction(name: "vertex_main")
827
- let fragmentFunction = library.makeFunction(name: "fragment_main")
828
-
829
- // Set up vertex descriptor
830
- let vertexDescriptor = MTLVertexDescriptor()
831
- vertexDescriptor.attributes[0].format = .float2 // position (x, y)
832
- vertexDescriptor.attributes[0].offset = 0
833
- vertexDescriptor.attributes[0].bufferIndex = 0
834
-
835
- vertexDescriptor.attributes[1].format = .float2 // texCoord (u, v)
836
- vertexDescriptor.attributes[1].offset = MemoryLayout<Float>.size * 2
837
- vertexDescriptor.attributes[1].bufferIndex = 0
838
-
839
- vertexDescriptor.layouts[0].stride = MemoryLayout<Float>.size * 4 // 2 floats for pos + 2 for texCoord
840
- vertexDescriptor.layouts[0].stepFunction = .perVertex
841
-
842
- let pipelineDescriptor = MTLRenderPipelineDescriptor()
843
- pipelineDescriptor.vertexDescriptor = vertexDescriptor
844
- pipelineDescriptor.vertexFunction = vertexFunction
845
- pipelineDescriptor.fragmentFunction = fragmentFunction
846
-
847
- pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormat(rawValue: colorPixelFormat) ?? .bgra8Unorm
848
- pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormat(rawValue: depthStencilPixelFormat) ?? .depth32Float_stencil8
849
- pipelineDescriptor.stencilAttachmentPixelFormat = MTLPixelFormat(rawValue: depthStencilPixelFormat) ?? .depth32Float_stencil8
850
-
851
- pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
852
- pipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add
853
- pipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add
854
- pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
855
- pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
856
- pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
857
- pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
858
-
859
- do {
860
- self.pipelineState = try metalDevice.makeRenderPipelineState(descriptor: pipelineDescriptor)
861
- } catch {
862
- print("❌ [GridRenderLayer] Failed to create pipeline state: \(error)")
863
- return
864
- }
865
-
866
- let dataSamplerDesc = MTLSamplerDescriptor()
867
- dataSamplerDesc.minFilter = .nearest
868
- dataSamplerDesc.magFilter = .nearest
869
- self.dataSamplerState = metalDevice.makeSamplerState(descriptor: dataSamplerDesc)
870
-
871
- let colormapSamplerDesc = MTLSamplerDescriptor()
872
- colormapSamplerDesc.minFilter = .nearest
873
- colormapSamplerDesc.magFilter = .nearest
874
- colormapSamplerDesc.sAddressMode = .clampToEdge
875
- self.colormapSamplerState = metalDevice.makeSamplerState(descriptor: colormapSamplerDesc)
876
-
877
- print("🟢 [GridRenderLayer] Metal setup complete")
878
-
879
- // Process any pending updates that came in before Metal was ready
880
- if let pendingGeometry = pendingGeometryUpdate {
881
- print("🟡 [GridRenderLayer] Processing pending geometry update")
882
- updateGeometry(corners: pendingGeometry.corners, gridDef: pendingGeometry.gridDef)
883
- pendingGeometryUpdate = nil
884
- }
885
-
886
- if let pendingColormap = pendingColormapUpdate {
887
- print("🟡 [GridRenderLayer] Processing pending colormap update")
888
- updateColormapTexture(colormapAsBase64: pendingColormap)
889
- pendingColormapUpdate = nil
890
- }
891
-
892
- if let pendingData = pendingDataUpdate {
893
- print("🟡 [GridRenderLayer] Processing pending data update")
894
- updateDataTexture(data: pendingData.data, nx: pendingData.nx, ny: pendingData.ny,
895
- scale: pendingData.scale, offset: pendingData.offset,
896
- missing: pendingData.missing, scaleType: pendingData.scaleType)
897
- pendingDataUpdate = nil
898
- }
899
- }
900
- internal func internalRender(_ parameters: CustomLayerRenderParameters, mtlCommandBuffer: MTLCommandBuffer, mtlRenderPassDescriptor: MTLRenderPassDescriptor) {
901
- guard isVisible,
902
- let pipeline = pipelineState,
903
- let vertices = vertexBuffer,
904
- let indices = indexBuffer,
905
- let dataTex = dataTexture,
906
- let colormapTex = colormapTexture,
907
- indexCount > 0,
908
- let encoder = mtlCommandBuffer.makeRenderCommandEncoder(descriptor: mtlRenderPassDescriptor) else {
909
-
910
- // OPTIMIZATION: Change log message to be more informative and less alarming.
911
- // This is an expected state during initial load.
912
- if (pipelineState != nil) { // Only log if Metal is set up
913
- print("🟡 [GridRenderLayer] internalRender: Waiting for all resources to be ready before drawing.")
914
- }
915
- return
916
- }
917
-
918
- print("✅ [GridRenderLayer] Past guards, about to render!")
919
- print(" 🎨 About to draw \(indexCount) indices (\(indexCount/3) triangles)")
920
-
921
- let needsLinear = (uniforms.smoothing != 0)
922
- if isDataSamplerLinear != needsLinear {
923
- let samplerDesc = MTLSamplerDescriptor()
924
- samplerDesc.minFilter = needsLinear ? .linear : .nearest
925
- samplerDesc.magFilter = needsLinear ? .linear : .nearest
926
- dataSamplerState = device.makeSamplerState(descriptor: samplerDesc)
927
- isDataSamplerLinear = needsLinear
928
- }
929
-
930
- // Apply zoom scale transformation
931
- let zoom = parameters.zoom
932
- let scale = Float(pow(2.0, zoom))
933
-
934
- let matrixArray = parameters.projectionMatrix
935
- var floatArray = matrixArray.map { $0.floatValue }
936
-
937
- floatArray[0] *= scale // X scale
938
- floatArray[5] *= scale // Y scale
939
-
940
- let mvp = matrix_float4x4(
941
- SIMD4<Float>(floatArray[0], floatArray[1], floatArray[2], floatArray[3]),
942
- SIMD4<Float>(floatArray[4], floatArray[5], floatArray[6], floatArray[7]),
943
- SIMD4<Float>(floatArray[8], floatArray[9], floatArray[10], floatArray[11]),
944
- SIMD4<Float>(floatArray[12], floatArray[13], floatArray[14], floatArray[15])
945
- )
946
-
947
- print(" 📐 MVP matrix after scaling: \(mvp)")
948
- print(" 📍 Zoom: \(zoom), Scale factor: \(scale)")
949
-
950
- // Add depth state
951
- let depthStencilDescriptor = MTLDepthStencilDescriptor()
952
- depthStencilDescriptor.depthCompareFunction = .always
953
- depthStencilDescriptor.isDepthWriteEnabled = false
954
- let depthStencilState = device.makeDepthStencilState(descriptor: depthStencilDescriptor)
955
-
956
- encoder.setRenderPipelineState(pipeline)
957
- encoder.setDepthStencilState(depthStencilState!)
958
- encoder.setVertexBuffer(vertices, offset: 0, index: 0)
959
- encoder.setVertexBytes([mvp], length: MemoryLayout<matrix_float4x4>.size, index: 1)
960
-
961
- var mutableUniforms = uniforms
962
- encoder.setFragmentBytes(&mutableUniforms, length: MemoryLayout<FragmentUniforms>.stride, index: 0)
963
- encoder.setFragmentTexture(dataTex, index: 0)
964
- encoder.setFragmentTexture(colormapTex, index: 1)
965
- encoder.setFragmentSamplerState(dataSamplerState, index: 0)
966
- encoder.setFragmentSamplerState(colormapSamplerState, index: 1)
967
-
968
- print(" 🎨 Drawing indexed primitives now...")
969
- encoder.drawIndexedPrimitives(type: .triangle, indexCount: indexCount, indexType: .uint16, indexBuffer: indices, indexBufferOffset: 0)
970
- print(" ✅ Draw call completed")
971
-
972
- encoder.endEncoding()
973
- print(" ✅ Encoder ended")
974
- }
975
-
976
- internal func internalRenderingWillEnd() {
977
- print("🟢 [GridRenderLayer] renderingWillEnd called")
978
- vertexBuffer = nil
979
- indexBuffer = nil
980
- dataTexture = nil
981
- colormapTexture = nil
982
- }
983
- }