expo-gaode-map 2.2.30-next.0 → 2.2.30

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 (82) hide show
  1. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapModule.kt +4 -2
  2. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +117 -57
  3. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +8 -15
  4. package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +20 -6
  5. package/android/src/main/java/expo/modules/gaodemap/overlays/ClusterView.kt +24 -13
  6. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerBitmapRenderer.kt +351 -0
  7. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +94 -310
  8. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerViewModule.kt +3 -3
  9. package/build/ExpoGaodeMapModule.d.ts +13 -5
  10. package/build/ExpoGaodeMapModule.d.ts.map +1 -1
  11. package/build/ExpoGaodeMapModule.js +166 -34
  12. package/build/ExpoGaodeMapModule.js.map +1 -1
  13. package/build/ExpoGaodeMapView.d.ts.map +1 -1
  14. package/build/ExpoGaodeMapView.js +12 -0
  15. package/build/ExpoGaodeMapView.js.map +1 -1
  16. package/build/components/AreaMaskOverlay.d.ts +5 -0
  17. package/build/components/AreaMaskOverlay.d.ts.map +1 -0
  18. package/build/components/AreaMaskOverlay.js +20 -0
  19. package/build/components/AreaMaskOverlay.js.map +1 -0
  20. package/build/components/FoldableMapView.d.ts.map +1 -1
  21. package/build/components/FoldableMapView.js +115 -104
  22. package/build/components/FoldableMapView.js.map +1 -1
  23. package/build/components/RouteOverlay.d.ts +5 -0
  24. package/build/components/RouteOverlay.d.ts.map +1 -0
  25. package/build/components/RouteOverlay.js +20 -0
  26. package/build/components/RouteOverlay.js.map +1 -0
  27. package/build/components/overlays/Cluster.d.ts.map +1 -1
  28. package/build/components/overlays/Cluster.js +12 -0
  29. package/build/components/overlays/Cluster.js.map +1 -1
  30. package/build/components/overlays/Marker.d.ts.map +1 -1
  31. package/build/components/overlays/Marker.js +86 -3
  32. package/build/components/overlays/Marker.js.map +1 -1
  33. package/build/hooks/useRoutePlayback.d.ts +4 -0
  34. package/build/hooks/useRoutePlayback.d.ts.map +1 -0
  35. package/build/hooks/useRoutePlayback.js +310 -0
  36. package/build/hooks/useRoutePlayback.js.map +1 -0
  37. package/build/index.d.ts +4 -1
  38. package/build/index.d.ts.map +1 -1
  39. package/build/index.js +4 -2
  40. package/build/index.js.map +1 -1
  41. package/build/types/common.types.d.ts +29 -5
  42. package/build/types/common.types.d.ts.map +1 -1
  43. package/build/types/common.types.js +5 -5
  44. package/build/types/common.types.js.map +1 -1
  45. package/build/types/index.d.ts +3 -2
  46. package/build/types/index.d.ts.map +1 -1
  47. package/build/types/index.js.map +1 -1
  48. package/build/types/location.types.d.ts +28 -0
  49. package/build/types/location.types.d.ts.map +1 -1
  50. package/build/types/location.types.js +5 -0
  51. package/build/types/location.types.js.map +1 -1
  52. package/build/types/map-view.types.d.ts +22 -22
  53. package/build/types/map-view.types.d.ts.map +1 -1
  54. package/build/types/map-view.types.js.map +1 -1
  55. package/build/types/native-module.types.d.ts +2 -2
  56. package/build/types/native-module.types.d.ts.map +1 -1
  57. package/build/types/native-module.types.js.map +1 -1
  58. package/build/types/overlays.types.d.ts +14 -0
  59. package/build/types/overlays.types.d.ts.map +1 -1
  60. package/build/types/overlays.types.js.map +1 -1
  61. package/build/types/route-playback.types.d.ts +118 -0
  62. package/build/types/route-playback.types.d.ts.map +1 -0
  63. package/build/types/route-playback.types.js +2 -0
  64. package/build/types/route-playback.types.js.map +1 -0
  65. package/build/utils/RouteUtils.d.ts +8 -0
  66. package/build/utils/RouteUtils.d.ts.map +1 -0
  67. package/build/utils/RouteUtils.js +140 -0
  68. package/build/utils/RouteUtils.js.map +1 -0
  69. package/ios/ExpoGaodeMapModule.swift +41 -22
  70. package/ios/ExpoGaodeMapView.swift +236 -241
  71. package/ios/ExpoGaodeMapViewModule.swift +16 -11
  72. package/ios/managers/UIManager.swift +5 -4
  73. package/ios/modules/LocationManager.swift +32 -9
  74. package/ios/overlays/ClusterView.swift +114 -12
  75. package/ios/overlays/ClusterViewModule.swift +5 -1
  76. package/ios/overlays/MarkerView.swift +195 -18
  77. package/ios/overlays/MarkerViewModule.swift +7 -7
  78. package/package.json +6 -6
  79. package/build/utils/throttle.d.ts +0 -10
  80. package/build/utils/throttle.d.ts.map +0 -1
  81. package/build/utils/throttle.js +0 -19
  82. package/build/utils/throttle.js.map +0 -1
@@ -1,13 +1,6 @@
1
1
  import ExpoModulesCore
2
2
  import MAMapKit
3
-
4
- enum MapType: Int, Enumerable {
5
- case standard = 0
6
- case satellite = 1
7
- case night = 2
8
- case navi = 3
9
- case bus = 4
10
- }
3
+ import CoreLocation
11
4
 
12
5
  /**
13
6
  * 高德地图视图 Module
@@ -17,10 +10,10 @@ public class ExpoGaodeMapViewModule: Module {
17
10
  Name("ExpoGaodeMapView")
18
11
 
19
12
  View(ExpoGaodeMapView.self) {
20
- Events("onMapPress", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
13
+ Events("onMapPress", "onPressPoi", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
21
14
 
22
- Prop("mapType") { (view: ExpoGaodeMapView, type: MapType) in
23
- view.mapType = type.rawValue
15
+ Prop("mapType") { (view: ExpoGaodeMapView, type: Int) in
16
+ view.mapType = type
24
17
  }
25
18
 
26
19
  Prop("initialCameraPosition") { (view: ExpoGaodeMapView, position: [String: Any]?) in
@@ -66,6 +59,18 @@ public class ExpoGaodeMapViewModule: Module {
66
59
  Prop("myLocationEnabled") { (view: ExpoGaodeMapView, show: Bool) in
67
60
  view.setShowsUserLocation(show)
68
61
  }
62
+
63
+ Prop("distanceFilter") { (view: ExpoGaodeMapView, distance: Double?) in
64
+ view.distanceFilter = distance ?? kCLDistanceFilterNone
65
+ }
66
+
67
+ Prop("headingFilter") { (view: ExpoGaodeMapView, heading: Double?) in
68
+ view.headingFilter = heading ?? kCLHeadingFilterNone
69
+ }
70
+
71
+ Prop("cameraEventThrottleMs") { (view: ExpoGaodeMapView, throttleMs: Int?) in
72
+ view.cameraEventThrottleMs = max(throttleMs ?? 32, 0)
73
+ }
69
74
 
70
75
  Prop("followUserLocation") { (view: ExpoGaodeMapView, follow: Bool) in
71
76
  view.setFollowUserLocation(follow)
@@ -29,14 +29,15 @@ class UIManager: NSObject, MAMapViewDelegate {
29
29
 
30
30
  /**
31
31
  * 设置地图类型
32
- * @param type 0:标准 1:卫星 2:夜间 3:导航
32
+ * @param type 1:标准 2:卫星 3:夜间 4:导航 5:公交
33
33
  */
34
34
  func setMapType(_ type: Int) {
35
35
  guard let mapView = mapView else { return }
36
36
  switch type {
37
- case 1: mapView.mapType = .satellite
38
- case 2: mapView.mapType = .standardNight
39
- case 3: mapView.mapType = .navi
37
+ case 2: mapView.mapType = .satellite
38
+ case 3: mapView.mapType = .standardNight
39
+ case 4: mapView.mapType = .navi
40
+ case 5: mapView.mapType = .bus
40
41
  default: mapView.mapType = .standard
41
42
  }
42
43
 
@@ -25,6 +25,8 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
25
25
  // 连续定位 event 回调(给 JS map listener 用)
26
26
  var onLocationUpdate: (([String: Any]) -> Void)?
27
27
  var onHeadingUpdate: (([String: Any]) -> Void)?
28
+ private var locationIntervalMillis: Int = 2000
29
+ private var lastLocationEventTimestampMillis: Double?
28
30
 
29
31
  override init() {
30
32
  super.init()
@@ -33,6 +35,7 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
33
35
  // MARK: - 连续定位控制
34
36
 
35
37
  func start() {
38
+ lastLocationEventTimestampMillis = nil
36
39
  ensureLocationManager()?.startUpdatingLocation()
37
40
  isLocationStarted = true
38
41
  }
@@ -40,6 +43,7 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
40
43
  func stop() {
41
44
  ensureLocationManager()?.stopUpdatingLocation()
42
45
  isLocationStarted = false
46
+ lastLocationEventTimestampMillis = nil
43
47
  }
44
48
 
45
49
  func isStarted() -> Bool {
@@ -56,6 +60,10 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
56
60
  ensureLocationManager()?.distanceFilter = distance
57
61
  }
58
62
 
63
+ func setInterval(_ interval: Int) {
64
+ locationIntervalMillis = max(interval, 0)
65
+ }
66
+
59
67
  func setLocationTimeout(_ timeout: Int) {
60
68
  ensureLocationManager()?.locationTimeout = timeout
61
69
  }
@@ -93,12 +101,14 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
93
101
  ensureLocationManager()?.allowsBackgroundLocationUpdates = allows
94
102
  }
95
103
 
96
- func setGeoLanguage(_ language: Int) {
97
- switch language {
98
- case 0: ensureLocationManager()?.reGeocodeLanguage = .default
99
- case 1: ensureLocationManager()?.reGeocodeLanguage = .chinse
100
- case 2: ensureLocationManager()?.reGeocodeLanguage = .english
101
- default: break
104
+ func setGeoLanguage(_ language: String) {
105
+ switch language.uppercased() {
106
+ case "ZH":
107
+ ensureLocationManager()?.reGeocodeLanguage = .chinse
108
+ case "EN":
109
+ ensureLocationManager()?.reGeocodeLanguage = .english
110
+ default:
111
+ ensureLocationManager()?.reGeocodeLanguage = .default
102
112
  }
103
113
  }
104
114
 
@@ -155,6 +165,7 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
155
165
  "longitude": location.coordinate.longitude,
156
166
  "accuracy": location.horizontalAccuracy,
157
167
  "altitude": location.altitude,
168
+ "heading": location.course,
158
169
  "bearing": location.course,
159
170
  "speed": location.speed,
160
171
  "timestamp": location.timestamp.timeIntervalSince1970 * 1000
@@ -172,14 +183,25 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
172
183
  data["adCode"] = geo.adcode
173
184
  }
174
185
 
175
- // 触发连续定位回调
186
+ let timestamp = location.timestamp.timeIntervalSince1970 * 1000
187
+ if let lastTimestamp = lastLocationEventTimestampMillis,
188
+ locationIntervalMillis > 0,
189
+ timestamp - lastTimestamp < Double(locationIntervalMillis) {
190
+ return
191
+ }
192
+
193
+ lastLocationEventTimestampMillis = timestamp
176
194
  onLocationUpdate?(data)
177
195
  }
178
196
 
179
197
  func amapLocationManager(_ manager: AMapLocationManager!, didUpdate heading: CLHeading!) {
180
198
  let headingData: [String: Any] = [
181
- "heading": heading.trueHeading,
182
- "accuracy": heading.headingAccuracy,
199
+ "magneticHeading": heading.magneticHeading,
200
+ "trueHeading": heading.trueHeading,
201
+ "headingAccuracy": heading.headingAccuracy,
202
+ "x": heading.x,
203
+ "y": heading.y,
204
+ "z": heading.z,
183
205
  "timestamp": heading.timestamp.timeIntervalSince1970 * 1000
184
206
  ]
185
207
  onHeadingUpdate?(headingData)
@@ -237,5 +259,6 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
237
259
  locationManager = nil
238
260
  onLocationUpdate = nil
239
261
  onHeadingUpdate = nil
262
+ lastLocationEventTimestampMillis = nil
240
263
  }
241
264
  }
@@ -1,3 +1,4 @@
1
+ import Foundation
1
2
  import ExpoModulesCore
2
3
  import MAMapKit
3
4
 
@@ -28,6 +29,9 @@ class ClusterView: ExpoView {
28
29
  private var currentAnnotations: [MAAnnotation] = []
29
30
  private let quadTreeQueue = DispatchQueue(label: "com.expo.gaode.quadtree")
30
31
  private var isInvalidated = false
32
+ private let imageCache = NSCache<NSString, UIImage>()
33
+ private var baseIconImage: UIImage?
34
+ private var iconIdentifier: String?
31
35
 
32
36
  // 缓存坐标数据以加速 C++ 调用
33
37
  private var latitudes: [Double] = []
@@ -104,9 +108,23 @@ class ClusterView: ExpoView {
104
108
  self.clusterSize.width = CGFloat(height)
105
109
  }
106
110
  }
107
-
111
+
112
+ clearImageCache()
108
113
  updateClusters()
109
114
  }
115
+
116
+ func setIcon(_ icon: String?) {
117
+ iconIdentifier = icon
118
+ baseIconImage = nil
119
+ clearImageCache()
120
+
121
+ guard let icon, !icon.isEmpty else {
122
+ updateClusters()
123
+ return
124
+ }
125
+
126
+ loadIcon(from: icon)
127
+ }
110
128
 
111
129
  func setClusterTextStyle(_ style: [String: Any]) {
112
130
  if let color = ColorParser.parseColor(style["color"]) {
@@ -115,11 +133,13 @@ class ClusterView: ExpoView {
115
133
  if let fontSize = style["fontSize"] as? Double {
116
134
  self.clusterTextSize = CGFloat(fontSize)
117
135
  }
136
+ clearImageCache()
118
137
  updateClusters()
119
138
  }
120
139
 
121
140
  func setClusterBuckets(_ buckets: [[String: Any]]) {
122
141
  self.clusterBuckets = buckets
142
+ clearImageCache()
123
143
  updateClusters()
124
144
  }
125
145
 
@@ -278,10 +298,15 @@ class ClusterView: ExpoView {
278
298
  }
279
299
 
280
300
  private func image(for count: Int) -> UIImage? {
301
+ let cacheKey = clusterImageCacheKey(for: count)
302
+ if let cachedImage = imageCache.object(forKey: cacheKey as NSString) {
303
+ return cachedImage
304
+ }
305
+
281
306
  let size = self.clusterSize
282
307
  let renderer = UIGraphicsImageRenderer(size: size)
283
-
284
- return renderer.image { context in
308
+
309
+ let renderedImage = renderer.image { context in
285
310
  let rect = CGRect(origin: .zero, size: size)
286
311
 
287
312
  // 基础样式
@@ -316,15 +341,25 @@ class ClusterView: ExpoView {
316
341
  }
317
342
  }
318
343
 
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()
344
+ if let baseIconImage {
345
+ baseIconImage.draw(in: rect)
346
+ if borderWidth > 0 {
347
+ borderColor.setStroke()
348
+ let path = UIBezierPath(ovalIn: rect.insetBy(dx: borderWidth / 2, dy: borderWidth / 2))
349
+ path.lineWidth = borderWidth
350
+ path.stroke()
351
+ }
352
+ } else {
353
+ bgColor.setFill()
354
+ UIBezierPath(ovalIn: rect).fill()
355
+
356
+ // 绘制边框
357
+ if borderWidth > 0 {
358
+ borderColor.setStroke()
359
+ let path = UIBezierPath(ovalIn: rect.insetBy(dx: borderWidth / 2, dy: borderWidth / 2))
360
+ path.lineWidth = borderWidth
361
+ path.stroke()
362
+ }
328
363
  }
329
364
 
330
365
  // 绘制文字
@@ -340,6 +375,72 @@ class ClusterView: ExpoView {
340
375
  height: textSize.height)
341
376
  text.draw(in: textRect, withAttributes: attributes)
342
377
  }
378
+
379
+ imageCache.setObject(renderedImage, forKey: cacheKey as NSString)
380
+ return renderedImage
381
+ }
382
+
383
+ private func clusterImageCacheKey(for count: Int) -> String {
384
+ let bucketKey: String
385
+ if let buckets = clusterBuckets {
386
+ let minPoints = buckets
387
+ .compactMap { $0["minPoints"] as? Int }
388
+ .filter { $0 <= count }
389
+ .max() ?? 0
390
+ bucketKey = "bucket:\(minPoints)"
391
+ } else {
392
+ bucketKey = "bucket:none"
393
+ }
394
+
395
+ return [
396
+ "count:\(count)",
397
+ bucketKey,
398
+ "size:\(Int(clusterSize.width.rounded()))x\(Int(clusterSize.height.rounded()))",
399
+ "bg:\(clusterBackgroundColor.description)",
400
+ "border:\(clusterBorderColor.description)",
401
+ "borderWidth:\(clusterBorderWidth)",
402
+ "textColor:\(clusterTextColor.description)",
403
+ "textSize:\(clusterTextSize)",
404
+ "icon:\(iconIdentifier ?? "none")"
405
+ ].joined(separator: "|")
406
+ }
407
+
408
+ private func clearImageCache() {
409
+ imageCache.removeAllObjects()
410
+ }
411
+
412
+ private func loadIcon(from icon: String) {
413
+ let requestedIcon = icon
414
+
415
+ DispatchQueue.global().async { [weak self] in
416
+ guard let self else { return }
417
+
418
+ let image: UIImage? = {
419
+ if requestedIcon.hasPrefix("http://") || requestedIcon.hasPrefix("https://") {
420
+ guard let url = URL(string: requestedIcon),
421
+ let data = try? Data(contentsOf: url) else {
422
+ return nil
423
+ }
424
+ return UIImage(data: data)
425
+ }
426
+
427
+ if requestedIcon.hasPrefix("file://") {
428
+ let path = String(requestedIcon.dropFirst(7))
429
+ return UIImage(contentsOfFile: path)
430
+ }
431
+
432
+ return UIImage(named: requestedIcon) ?? UIImage(contentsOfFile: requestedIcon)
433
+ }()
434
+
435
+ guard let image else { return }
436
+
437
+ DispatchQueue.main.async { [weak self] in
438
+ guard let self, self.iconIdentifier == requestedIcon else { return }
439
+ self.baseIconImage = image
440
+ self.clearImageCache()
441
+ self.updateClusters()
442
+ }
443
+ }
343
444
  }
344
445
 
345
446
  // MARK: - Event Handling
@@ -365,6 +466,7 @@ class ClusterView: ExpoView {
365
466
  override func removeFromSuperview() {
366
467
  isInvalidated = true
367
468
  updateTimer?.invalidate() // 清理定时器
469
+ clearImageCache()
368
470
  super.removeFromSuperview()
369
471
  mapView?.removeAnnotations(currentAnnotations)
370
472
  }
@@ -22,6 +22,10 @@ public class ClusterViewModule: Module {
22
22
  Prop("clusterStyle") { (view: ClusterView, style: [String: Any]) in
23
23
  view.setClusterStyle(style)
24
24
  }
25
+
26
+ Prop("icon") { (view: ClusterView, icon: String?) in
27
+ view.setIcon(icon)
28
+ }
25
29
 
26
30
  Prop("clusterTextStyle") { (view: ClusterView, style: [String: Any]) in
27
31
  view.setClusterTextStyle(style)
@@ -32,4 +36,4 @@ public class ClusterViewModule: Module {
32
36
  }
33
37
  }
34
38
  }
35
- }
39
+ }