expo-gaode-map-navigation 1.1.5 → 1.1.6

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 (146) hide show
  1. package/README.md +213 -73
  2. package/android/build.gradle +10 -0
  3. package/android/src/main/cpp/CMakeLists.txt +24 -0
  4. package/android/src/main/cpp/cluster_jni.cpp +848 -0
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +616 -92
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +493 -0
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +230 -14
  8. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +37 -27
  9. package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +494 -0
  10. package/android/src/main/java/expo/modules/gaodemap/map/companion/BitmapDescriptorCache.kt +30 -0
  11. package/android/src/main/java/expo/modules/gaodemap/map/companion/IconBitmapCache.kt +37 -0
  12. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +76 -0
  13. package/android/src/main/java/expo/modules/gaodemap/map/modules/LocationManager.kt +15 -3
  14. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +4 -59
  15. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleView.kt +9 -12
  16. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleViewModule.kt +5 -6
  17. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +539 -66
  18. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterViewModule.kt +17 -1
  19. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +165 -33
  20. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +15 -3
  21. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +1249 -672
  22. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +40 -17
  23. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointView.kt +177 -22
  24. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointViewModule.kt +11 -3
  25. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonView.kt +57 -14
  26. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonViewModule.kt +9 -5
  27. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineView.kt +90 -63
  28. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineViewModule.kt +7 -3
  29. package/android/src/main/java/expo/modules/gaodemap/map/services/LocationForegroundService.kt +3 -2
  30. package/android/src/main/java/expo/modules/gaodemap/map/utils/BitmapDescriptorCache.kt +20 -0
  31. package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +13 -0
  32. package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +20 -0
  33. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +515 -0
  34. package/android/src/main/java/expo/modules/gaodemap/map/utils/LatLngParser.kt +91 -0
  35. package/android/src/main/java/expo/modules/gaodemap/map/utils/PermissionHelper.kt +248 -0
  36. package/build/ExpoGaodeMapNaviView.d.ts +7 -7
  37. package/build/ExpoGaodeMapNaviView.js +10 -11
  38. package/build/ExpoGaodeMapNavigationModule.d.ts +2 -1
  39. package/build/index.d.ts +35 -33
  40. package/build/index.js +70 -106
  41. package/build/map/ExpoGaodeMapModule.d.ts +2 -201
  42. package/build/map/ExpoGaodeMapModule.js +586 -18
  43. package/build/map/ExpoGaodeMapOfflineModule.d.ts +139 -0
  44. package/build/map/ExpoGaodeMapOfflineModule.js +8 -0
  45. package/build/map/ExpoGaodeMapView.js +66 -58
  46. package/build/map/components/FoldableMapView.d.ts +38 -0
  47. package/build/map/components/FoldableMapView.js +209 -0
  48. package/build/map/components/MapContext.d.ts +12 -0
  49. package/build/map/components/MapContext.js +54 -0
  50. package/build/map/components/MapUI.d.ts +18 -0
  51. package/build/map/components/MapUI.js +29 -0
  52. package/build/map/components/overlays/Circle.js +34 -3
  53. package/build/map/components/overlays/Cluster.d.ts +3 -1
  54. package/build/map/components/overlays/Cluster.js +31 -2
  55. package/build/map/components/overlays/HeatMap.d.ts +3 -1
  56. package/build/map/components/overlays/HeatMap.js +33 -3
  57. package/build/map/components/overlays/Marker.d.ts +1 -1
  58. package/build/map/components/overlays/Marker.js +37 -32
  59. package/build/map/components/overlays/MultiPoint.js +1 -1
  60. package/build/map/components/overlays/Polygon.js +30 -3
  61. package/build/map/components/overlays/Polyline.js +36 -3
  62. package/build/map/index.d.ts +25 -5
  63. package/build/map/index.js +59 -18
  64. package/build/map/types/common.types.d.ts +40 -0
  65. package/build/map/types/common.types.js +0 -4
  66. package/build/map/types/index.d.ts +3 -2
  67. package/build/map/types/map-view.types.d.ts +108 -3
  68. package/build/map/types/native-module.types.d.ts +363 -0
  69. package/build/map/types/native-module.types.js +5 -0
  70. package/build/map/types/offline.types.d.ts +132 -0
  71. package/build/map/types/offline.types.js +5 -0
  72. package/build/map/types/overlays.types.d.ts +137 -24
  73. package/build/map/utils/ErrorHandler.d.ts +110 -0
  74. package/build/map/utils/ErrorHandler.js +421 -0
  75. package/build/map/utils/GeoUtils.d.ts +20 -0
  76. package/build/map/utils/GeoUtils.js +76 -0
  77. package/build/map/utils/OfflineMapManager.d.ts +148 -0
  78. package/build/map/utils/OfflineMapManager.js +217 -0
  79. package/build/map/utils/PermissionUtils.d.ts +91 -0
  80. package/build/map/utils/PermissionUtils.js +255 -0
  81. package/build/map/utils/PlatformDetector.d.ts +102 -0
  82. package/build/map/utils/PlatformDetector.js +186 -0
  83. package/build/types/index.d.ts +1 -0
  84. package/build/types/index.js +1 -0
  85. package/build/types/native-module.types.d.ts +69 -0
  86. package/build/types/native-module.types.js +2 -0
  87. package/build/types/naviview.types.d.ts +1 -1
  88. package/expo-module.config.json +12 -10
  89. package/ios/ExpoGaodeMapNavigation.podspec +9 -0
  90. package/ios/map/ExpoGaodeMapModule.swift +485 -75
  91. package/ios/map/ExpoGaodeMapOfflineModule.swift +479 -0
  92. package/ios/map/ExpoGaodeMapView.swift +611 -62
  93. package/ios/map/ExpoGaodeMapViewModule.swift +48 -26
  94. package/ios/map/MapPreloadManager.swift +348 -0
  95. package/ios/map/cpp/ClusterEngine.cpp +110 -0
  96. package/ios/map/cpp/ClusterEngine.hpp +20 -0
  97. package/ios/map/cpp/ColorParser.cpp +135 -0
  98. package/ios/map/cpp/ColorParser.hpp +14 -0
  99. package/ios/map/cpp/GeometryEngine.cpp +574 -0
  100. package/ios/map/cpp/GeometryEngine.hpp +159 -0
  101. package/ios/map/cpp/QuadTree.cpp +92 -0
  102. package/ios/map/cpp/QuadTree.hpp +42 -0
  103. package/ios/map/cpp/README.md +55 -0
  104. package/ios/map/managers/UIManager.swift +72 -1
  105. package/ios/map/modules/LocationManager.swift +123 -166
  106. package/ios/map/overlays/CircleView.swift +16 -32
  107. package/ios/map/overlays/CircleViewModule.swift +12 -12
  108. package/ios/map/overlays/ClusterAnnotation.swift +32 -0
  109. package/ios/map/overlays/ClusterView.swift +331 -45
  110. package/ios/map/overlays/ClusterViewModule.swift +20 -6
  111. package/ios/map/overlays/HeatMapView.swift +135 -32
  112. package/ios/map/overlays/HeatMapViewModule.swift +20 -8
  113. package/ios/map/overlays/MarkerView.swift +613 -130
  114. package/ios/map/overlays/MarkerViewModule.swift +38 -18
  115. package/ios/map/overlays/MultiPointView.swift +168 -10
  116. package/ios/map/overlays/MultiPointViewModule.swift +27 -5
  117. package/ios/map/overlays/PolygonView.swift +62 -23
  118. package/ios/map/overlays/PolygonViewModule.swift +18 -12
  119. package/ios/map/overlays/PolylineView.swift +21 -13
  120. package/ios/map/overlays/PolylineViewModule.swift +18 -12
  121. package/ios/map/utils/ClusterNative.h +96 -0
  122. package/ios/map/utils/ClusterNative.mm +377 -0
  123. package/ios/map/utils/ColorParser.swift +12 -1
  124. package/ios/map/utils/CppBridging.mm +13 -0
  125. package/ios/map/utils/GeometryUtils.swift +34 -0
  126. package/ios/map/utils/LatLngParser.swift +87 -0
  127. package/ios/map/utils/PermissionManager.swift +135 -6
  128. package/package.json +3 -2
  129. package/shared/cpp/ClusterEngine.cpp +110 -0
  130. package/shared/cpp/ClusterEngine.hpp +20 -0
  131. package/shared/cpp/ColorParser.cpp +135 -0
  132. package/shared/cpp/ColorParser.hpp +14 -0
  133. package/shared/cpp/GeometryEngine.cpp +574 -0
  134. package/shared/cpp/GeometryEngine.hpp +159 -0
  135. package/shared/cpp/QuadTree.cpp +92 -0
  136. package/shared/cpp/QuadTree.hpp +42 -0
  137. package/shared/cpp/README.md +55 -0
  138. package/shared/cpp/tests/benchmark_js.js +41 -0
  139. package/shared/cpp/tests/run.sh +17 -0
  140. package/shared/cpp/tests/test_main.cpp +276 -0
  141. package/build/map/ExpoGaodeMap.types.d.ts +0 -41
  142. package/build/map/ExpoGaodeMap.types.js +0 -24
  143. package/build/map/utils/EventManager.d.ts +0 -10
  144. package/build/map/utils/EventManager.js +0 -26
  145. package/build/map/utils/ModuleLoader.d.ts +0 -73
  146. package/build/map/utils/ModuleLoader.js +0 -112
@@ -1,85 +1,371 @@
1
1
  import ExpoModulesCore
2
2
  import AMapNaviKit
3
3
 
4
- class NaviClusterView: ExpoView {
5
- var points: [[String: Any]] = []
6
- var radius: Int = 100
7
- var minClusterSize: Int = 2
4
+ class ClusterView: ExpoView {
5
+ // 属性
6
+ var points: [[String: Any]] = [] {
7
+ didSet {
8
+ parsePoints()
9
+ updateClusters()
10
+ }
11
+ }
12
+ var radius: Int = 100 // 聚合范围 (screen points)
13
+ var minClusterSize: Int = 1
14
+ var clusterBuckets: [[String: Any]]?
15
+
16
+ // 样式属性
17
+ private var clusterBackgroundColor: UIColor = .systemBlue
18
+ private var clusterBorderColor: UIColor = .white
19
+ private var clusterBorderWidth: CGFloat = 2.0
20
+ private var clusterTextColor: UIColor = .white
21
+ private var clusterTextSize: CGFloat = 14.0
22
+ private var clusterSize: CGSize = CGSize(width: 40, height: 40)
8
23
 
9
- private var mapView: MAMapView?
10
- private var annotations: [MAPointAnnotation] = []
24
+ let onClusterPress = EventDispatcher()
25
+
26
+ private weak var mapView: MAMapView?
27
+ // private var quadTree = CoordinateQuadTree() // Removed: using C++ ClusterNative
28
+ private var currentAnnotations: [MAAnnotation] = []
29
+ private let quadTreeQueue = DispatchQueue(label: "com.expo.gaode.quadtree")
30
+ private var isInvalidated = false
31
+
32
+ // 缓存坐标数据以加速 C++ 调用
33
+ private var latitudes: [Double] = []
34
+ private var longitudes: [Double] = []
11
35
 
12
36
  required init(appContext: AppContext? = nil) {
13
37
  super.init(appContext: appContext)
14
38
  }
15
39
 
16
- func setMap(_ map: MAMapView) {
17
- self.mapView = map
18
- updateCluster()
40
+ private func parsePoints() {
41
+ var lats: [Double] = []
42
+ var lons: [Double] = []
43
+
44
+ for point in points {
45
+ if let coord = LatLngParser.parseLatLng(point) {
46
+ lats.append(coord.latitude)
47
+ lons.append(coord.longitude)
48
+ } else {
49
+ // 保持索引一致,无效点填 0
50
+ lats.append(0)
51
+ lons.append(0)
52
+ }
53
+ }
54
+
55
+ self.latitudes = lats
56
+ self.longitudes = lons
19
57
  }
20
58
 
59
+ // MARK: - Setters for Expo Module
60
+
21
61
  func setPoints(_ points: [[String: Any]]) {
22
62
  self.points = points
23
- updateCluster()
24
63
  }
25
64
 
26
65
  func setRadius(_ radius: Int) {
27
66
  self.radius = radius
28
- updateCluster()
67
+ updateClusters()
29
68
  }
30
69
 
31
70
  func setMinClusterSize(_ size: Int) {
32
71
  self.minClusterSize = size
33
- updateCluster()
72
+ updateClusters()
73
+ }
74
+
75
+ func setMap(_ map: MAMapView) {
76
+ self.mapView = map
77
+ updateClusters()
78
+ }
79
+
80
+ func setClusterStyle(_ style: [String: Any]) {
81
+ if let color = ColorParser.parseColor(style["backgroundColor"]) {
82
+ self.clusterBackgroundColor = color
83
+ }
84
+ if let borderColor = ColorParser.parseColor(style["borderColor"]) {
85
+ self.clusterBorderColor = borderColor
86
+ }
87
+ if let borderWidth = style["borderWidth"] as? Double {
88
+ self.clusterBorderWidth = CGFloat(borderWidth)
89
+ }
90
+
91
+ // 尺寸设置
92
+ if let width = style["width"] as? Double {
93
+ self.clusterSize.width = CGFloat(width)
94
+ // 如果只设置了宽度,默认高度等于宽度(正圆)
95
+ if style["height"] == nil {
96
+ self.clusterSize.height = CGFloat(width)
97
+ }
98
+ }
99
+
100
+ if let height = style["height"] as? Double {
101
+ self.clusterSize.height = CGFloat(height)
102
+ // 如果只设置了高度,默认宽度等于高度
103
+ if style["width"] == nil {
104
+ self.clusterSize.width = CGFloat(height)
105
+ }
106
+ }
107
+
108
+ updateClusters()
109
+ }
110
+
111
+ func setClusterTextStyle(_ style: [String: Any]) {
112
+ if let color = ColorParser.parseColor(style["color"]) {
113
+ self.clusterTextColor = color
114
+ }
115
+ if let fontSize = style["fontSize"] as? Double {
116
+ self.clusterTextSize = CGFloat(fontSize)
117
+ }
118
+ updateClusters()
119
+ }
120
+
121
+ func setClusterBuckets(_ buckets: [[String: Any]]) {
122
+ self.clusterBuckets = buckets
123
+ updateClusters()
124
+ }
125
+
126
+ func mapRegionDidChange() {
127
+ updateClusters()
128
+ }
129
+
130
+ // MARK: - Update Logic
131
+
132
+ private var updateTimer: Timer?
133
+ private let throttleInterval: TimeInterval = 0.3 // 300ms 节流
134
+
135
+ func updateClusters() {
136
+ if isInvalidated { return }
137
+
138
+ // 节流逻辑:取消上一次的 Timer,重新计时
139
+ updateTimer?.invalidate()
140
+ updateTimer = Timer.scheduledTimer(withTimeInterval: throttleInterval, repeats: false) { [weak self] _ in
141
+ self?.performUpdate()
142
+ }
34
143
  }
35
144
 
36
- private func updateCluster() {
145
+ private func performUpdate() {
146
+ if isInvalidated { return }
37
147
  guard let mapView = mapView else { return }
38
148
 
39
- // 先移除旧的注释
40
- removeAllAnnotations()
149
+ // 确保地图已布局
150
+ if mapView.bounds.size.width == 0 { return }
41
151
 
42
- // 验证数据有效性
43
- guard !points.isEmpty else { return }
152
+ let visibleRect = mapView.visibleMapRect
153
+ let zoomScale = visibleRect.size.width / Double(mapView.bounds.size.width)
44
154
 
45
- for point in points {
46
- guard let latitude = point["latitude"] as? Double,
47
- let longitude = point["longitude"] as? Double else {
48
- continue
155
+ // 计算当前缩放级别下的物理半径(米)
156
+ // MAMapView 单位投影:1 map point 1 meter (at equator)
157
+ // 实际上需要根据纬度计算 metersPerMapPoint
158
+ let centerLat = mapView.centerCoordinate.latitude
159
+ let metersPerMapPoint = MAMetersPerMapPointAtLatitude(centerLat)
160
+ let mapPointsPerScreenPoint = zoomScale
161
+ let metersPerScreenPoint = metersPerMapPoint * mapPointsPerScreenPoint
162
+ let radiusMeters = Double(self.radius) * metersPerScreenPoint
163
+
164
+ // 在后台串行队列计算聚合
165
+ quadTreeQueue.async { [weak self] in
166
+ guard let self = self else { return }
167
+
168
+ // 转换为 NSNumber 数组以传递给 Obj-C++
169
+ let latNums = self.latitudes.map { NSNumber(value: $0) }
170
+ let lonNums = self.longitudes.map { NSNumber(value: $0) }
171
+
172
+ // 调用 C++ 聚类算法
173
+ let clusterData = ClusterNative.clusterPoints(latitudes: latNums, longitudes: lonNums, radiusMeters: radiusMeters)
174
+
175
+ var annotations: [ClusterAnnotation] = []
176
+
177
+ if clusterData.count > 0 {
178
+ let clusterCount = clusterData[0].intValue
179
+ var offset = 1
180
+
181
+ for _ in 0..<clusterCount {
182
+ if offset >= clusterData.count { break }
183
+
184
+ let centerIndex = clusterData[offset].intValue
185
+ offset += 1
186
+
187
+ let count = clusterData[offset].intValue
188
+ offset += 1
189
+
190
+ var pois: [[String: Any]] = []
191
+
192
+ for _ in 0..<count {
193
+ if offset < clusterData.count {
194
+ let idx = clusterData[offset].intValue
195
+ if idx >= 0 && idx < self.points.count {
196
+ pois.append(self.points[idx])
197
+ }
198
+ offset += 1
199
+ }
200
+ }
201
+
202
+ if centerIndex >= 0 && centerIndex < self.points.count {
203
+ let centerPoint = self.points[centerIndex]
204
+ if let lat = centerPoint["latitude"] as? Double,
205
+ let lon = centerPoint["longitude"] as? Double {
206
+ let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
207
+ // 只有当聚类点数量 >= minClusterSize 时才显示聚类
208
+ // 但通常 ClusterView 负责显示所有点(聚合或非聚合)
209
+ // 这里如果 count == 1,也是一个 ClusterAnnotation,只是显示样式可能不同
210
+ let annotation = ClusterAnnotation(coordinate: coordinate, count: count, pois: pois)
211
+ annotations.append(annotation)
212
+ }
213
+ }
214
+ }
49
215
  }
50
216
 
51
- let annotation = MAPointAnnotation()
52
- annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
53
- mapView.addAnnotation(annotation)
54
- annotations.append(annotation)
217
+ DispatchQueue.main.async {
218
+ if self.isInvalidated { return }
219
+ self.updateMapViewAnnotations(with: annotations as [MAAnnotation])
220
+ }
55
221
  }
56
222
  }
57
223
 
58
- /**
59
- * 移除所有标注
60
- */
61
- private func removeAllAnnotations() {
224
+ private func updateMapViewAnnotations(with newAnnotations: [MAAnnotation]) {
62
225
  guard let mapView = mapView else { return }
63
226
 
64
- for annotation in annotations {
65
- mapView.removeAnnotation(annotation)
227
+ // Diff 算法:找出新增、移除和保留的标注
228
+ // 注意:ClusterAnnotation 需要实现 isEqual 和 hash
229
+
230
+ let before = Set(currentAnnotations.compactMap { $0 as? ClusterAnnotation })
231
+ let after = Set(newAnnotations.compactMap { $0 as? ClusterAnnotation })
232
+
233
+ // intersection 返回 before 中的元素(如果相等),这正是我们需要保留的已经在地图上的实例
234
+ let toKeep = before.intersection(after)
235
+ let toAdd = after.subtracting(toKeep)
236
+ let toRemove = before.subtracting(toKeep)
237
+
238
+ // 只有当有变化时才操作
239
+ if !toRemove.isEmpty {
240
+ mapView.removeAnnotations(Array(toRemove))
241
+ }
242
+
243
+ if !toAdd.isEmpty {
244
+ mapView.addAnnotations(Array(toAdd))
66
245
  }
67
- annotations.removeAll()
246
+
247
+ // 更新 currentAnnotations
248
+ // 关键:必须保留已经在地图上的实例 (toKeep),加上新增的实例 (toAdd)
249
+ // 这样可以保证 currentAnnotations 中的对象始终与地图上的对象一致,避免 KVO 崩溃
250
+ var nextAnnotations: [MAAnnotation] = []
251
+ nextAnnotations.append(contentsOf: Array(toKeep) as [MAAnnotation])
252
+ nextAnnotations.append(contentsOf: Array(toAdd) as [MAAnnotation])
253
+
254
+ currentAnnotations = nextAnnotations
68
255
  }
69
256
 
70
- /**
71
- * 从父视图移除时清理标注
72
- */
73
- override func removeFromSuperview() {
74
- super.removeFromSuperview()
75
- removeAllAnnotations()
257
+ // MARK: - View Provider
258
+
259
+ func viewForAnnotation(_ annotation: MAAnnotation) -> MAAnnotationView? {
260
+ guard let clusterAnnotation = annotation as? ClusterAnnotation else { return nil }
261
+
262
+ let reuseIdentifier = "ClusterAnnotation"
263
+ var annotationView = mapView?.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
264
+
265
+ if annotationView == nil {
266
+ annotationView = MAAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
267
+ }
268
+
269
+ annotationView?.annotation = annotation
270
+ annotationView?.canShowCallout = false
271
+
272
+ // 生成图标
273
+ annotationView?.image = image(for: clusterAnnotation.count)
274
+ annotationView?.centerOffset = CGPoint(x: 0, y: 0)
275
+ annotationView?.zIndex = 100
276
+
277
+ return annotationView
76
278
  }
77
279
 
78
- /**
79
- * 析构时移除标注
80
- */
81
- deinit {
82
- removeAllAnnotations()
83
- mapView = nil
280
+ private func image(for count: Int) -> UIImage? {
281
+ let size = self.clusterSize
282
+ let renderer = UIGraphicsImageRenderer(size: size)
283
+
284
+ return renderer.image { context in
285
+ let rect = CGRect(origin: .zero, size: size)
286
+
287
+ // 基础样式
288
+ var bgColor = self.clusterBackgroundColor
289
+ var borderColor = self.clusterBorderColor
290
+ var borderWidth = self.clusterBorderWidth
291
+
292
+ // 应用分级样式
293
+ if let buckets = self.clusterBuckets {
294
+ var bestBucket: [String: Any]?
295
+ var maxMinPoints = -1
296
+
297
+ for bucket in buckets {
298
+ if let minPoints = bucket["minPoints"] as? Int, minPoints <= count {
299
+ if minPoints > maxMinPoints {
300
+ maxMinPoints = minPoints
301
+ bestBucket = bucket
302
+ }
303
+ }
304
+ }
305
+
306
+ if let bucket = bestBucket {
307
+ if let c = ColorParser.parseColor(bucket["backgroundColor"]) {
308
+ bgColor = c
309
+ }
310
+ if let c = ColorParser.parseColor(bucket["borderColor"]) {
311
+ borderColor = c
312
+ }
313
+ if let w = bucket["borderWidth"] as? Double {
314
+ borderWidth = CGFloat(w)
315
+ }
316
+ }
317
+ }
318
+
319
+ bgColor.setFill()
320
+ UIBezierPath(ovalIn: rect).fill()
321
+
322
+ // 绘制边框
323
+ if borderWidth > 0 {
324
+ borderColor.setStroke()
325
+ let path = UIBezierPath(ovalIn: rect.insetBy(dx: borderWidth / 2, dy: borderWidth / 2))
326
+ path.lineWidth = borderWidth
327
+ path.stroke()
328
+ }
329
+
330
+ // 绘制文字
331
+ let text = "\(count)"
332
+ let attributes: [NSAttributedString.Key: Any] = [
333
+ .font: UIFont.boldSystemFont(ofSize: self.clusterTextSize),
334
+ .foregroundColor: self.clusterTextColor
335
+ ]
336
+ let textSize = text.size(withAttributes: attributes)
337
+ let textRect = CGRect(x: (size.width - textSize.width) / 2,
338
+ y: (size.height - textSize.height) / 2,
339
+ width: textSize.width,
340
+ height: textSize.height)
341
+ text.draw(in: textRect, withAttributes: attributes)
342
+ }
343
+ }
344
+
345
+ // MARK: - Event Handling
346
+
347
+ func containsAnnotation(_ annotation: MAAnnotation) -> Bool {
348
+ guard let clusterAnnotation = annotation as? ClusterAnnotation else { return false }
349
+ return currentAnnotations.contains { $0.isEqual(clusterAnnotation) }
350
+ }
351
+
352
+ func handleAnnotationTap(_ annotation: MAAnnotation) {
353
+ guard let clusterAnnotation = annotation as? ClusterAnnotation else { return }
354
+
355
+ onClusterPress([
356
+ "count": clusterAnnotation.count,
357
+ "latitude": clusterAnnotation.coordinate.latitude,
358
+ "longitude": clusterAnnotation.coordinate.longitude,
359
+ "pois": clusterAnnotation.pois
360
+ ])
361
+ }
362
+
363
+ // MARK: - Lifecycle
364
+
365
+ override func removeFromSuperview() {
366
+ isInvalidated = true
367
+ updateTimer?.invalidate() // 清理定时器
368
+ super.removeFromSuperview()
369
+ mapView?.removeAnnotations(currentAnnotations)
84
370
  }
85
- }
371
+ }
@@ -1,21 +1,35 @@
1
1
  import ExpoModulesCore
2
2
 
3
- public class NaviClusterViewModule: Module {
3
+ public class ClusterViewModule: Module {
4
4
  public func definition() -> ModuleDefinition {
5
- Name("NaviClusterView")
5
+ Name("ClusterView")
6
6
 
7
- View(NaviClusterView.self) {
8
- Prop("points") { (view: NaviClusterView, points: [[String: Any]]) in
7
+ View(ClusterView.self) {
8
+ Events("onClusterPress")
9
+
10
+ Prop("points") { (view: ClusterView, points: [[String: Any]]) in
9
11
  view.setPoints(points)
10
12
  }
11
13
 
12
- Prop("radius") { (view: NaviClusterView, radius: Int) in
14
+ Prop("radius") { (view: ClusterView, radius: Int) in
13
15
  view.setRadius(radius)
14
16
  }
15
17
 
16
- Prop("minClusterSize") { (view: NaviClusterView, size: Int) in
18
+ Prop("minClusterSize") { (view: ClusterView, size: Int) in
17
19
  view.setMinClusterSize(size)
18
20
  }
21
+
22
+ Prop("clusterStyle") { (view: ClusterView, style: [String: Any]) in
23
+ view.setClusterStyle(style)
24
+ }
25
+
26
+ Prop("clusterTextStyle") { (view: ClusterView, style: [String: Any]) in
27
+ view.setClusterTextStyle(style)
28
+ }
29
+
30
+ Prop("clusterBuckets") { (view: ClusterView, buckets: [[String: Any]]) in
31
+ view.setClusterBuckets(buckets)
32
+ }
19
33
  }
20
34
  }
21
35
  }