expo-gaode-map-navigation 2.0.12-next.0 → 2.0.13

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 (238) hide show
  1. package/README.md +296 -7
  2. package/android/build.gradle +12 -4
  3. package/android/src/main/AndroidManifest.xml +10 -1
  4. package/android/src/main/cpp/cluster_jni.cpp +56 -0
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +49 -8
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +83 -15
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +13 -3
  8. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +36 -39
  9. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +23 -17
  10. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +5 -2
  11. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +122 -10
  12. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +2 -2
  13. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +37 -25
  14. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +6 -6
  15. package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
  16. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +103 -0
  17. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +1408 -394
  18. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +121 -1
  19. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +14 -28
  20. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +28 -3
  21. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
  22. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +114 -15
  23. package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +24 -35
  24. package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +50 -36
  25. package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
  26. package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +21 -12
  27. package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
  28. package/android/src/main/res/drawable/landback_0.png +0 -0
  29. package/android/src/main/res/drawable/landback_1.png +0 -0
  30. package/android/src/main/res/drawable/landback_2.png +0 -0
  31. package/android/src/main/res/drawable/landback_3.png +0 -0
  32. package/android/src/main/res/drawable/landback_4.png +0 -0
  33. package/android/src/main/res/drawable/landback_5.png +0 -0
  34. package/android/src/main/res/drawable/landback_6.png +0 -0
  35. package/android/src/main/res/drawable/landback_7.png +0 -0
  36. package/android/src/main/res/drawable/landback_8.png +0 -0
  37. package/android/src/main/res/drawable/landback_9.png +0 -0
  38. package/android/src/main/res/drawable/landback_a.png +0 -0
  39. package/android/src/main/res/drawable/landback_b.png +0 -0
  40. package/android/src/main/res/drawable/landback_c.png +0 -0
  41. package/android/src/main/res/drawable/landback_d.png +0 -0
  42. package/android/src/main/res/drawable/landback_e.png +0 -0
  43. package/android/src/main/res/drawable/landback_f.png +0 -0
  44. package/android/src/main/res/drawable/landback_g.png +0 -0
  45. package/android/src/main/res/drawable/landback_h.png +0 -0
  46. package/android/src/main/res/drawable/landback_i.png +0 -0
  47. package/android/src/main/res/drawable/landback_j.png +0 -0
  48. package/android/src/main/res/drawable/landback_k.png +0 -0
  49. package/android/src/main/res/drawable/landback_l.png +0 -0
  50. package/android/src/main/res/drawable/landfront_0.png +0 -0
  51. package/android/src/main/res/drawable/landfront_00.png +0 -0
  52. package/android/src/main/res/drawable/landfront_1.png +0 -0
  53. package/android/src/main/res/drawable/landfront_11.png +0 -0
  54. package/android/src/main/res/drawable/landfront_20.png +0 -0
  55. package/android/src/main/res/drawable/landfront_21.png +0 -0
  56. package/android/src/main/res/drawable/landfront_22.png +0 -0
  57. package/android/src/main/res/drawable/landfront_3.png +0 -0
  58. package/android/src/main/res/drawable/landfront_33.png +0 -0
  59. package/android/src/main/res/drawable/landfront_40.png +0 -0
  60. package/android/src/main/res/drawable/landfront_43.png +0 -0
  61. package/android/src/main/res/drawable/landfront_44.png +0 -0
  62. package/android/src/main/res/drawable/landfront_5.png +0 -0
  63. package/android/src/main/res/drawable/landfront_55.png +0 -0
  64. package/android/src/main/res/drawable/landfront_61.png +0 -0
  65. package/android/src/main/res/drawable/landfront_63.png +0 -0
  66. package/android/src/main/res/drawable/landfront_66.png +0 -0
  67. package/android/src/main/res/drawable/landfront_70.png +0 -0
  68. package/android/src/main/res/drawable/landfront_71.png +0 -0
  69. package/android/src/main/res/drawable/landfront_73.png +0 -0
  70. package/android/src/main/res/drawable/landfront_77.png +0 -0
  71. package/android/src/main/res/drawable/landfront_8.png +0 -0
  72. package/android/src/main/res/drawable/landfront_88.png +0 -0
  73. package/android/src/main/res/drawable/landfront_90.png +0 -0
  74. package/android/src/main/res/drawable/landfront_95.png +0 -0
  75. package/android/src/main/res/drawable/landfront_99.png +0 -0
  76. package/android/src/main/res/drawable/landfront_a0.png +0 -0
  77. package/android/src/main/res/drawable/landfront_a8.png +0 -0
  78. package/android/src/main/res/drawable/landfront_aa.png +0 -0
  79. package/android/src/main/res/drawable/landfront_b1.png +0 -0
  80. package/android/src/main/res/drawable/landfront_b5.png +0 -0
  81. package/android/src/main/res/drawable/landfront_bb.png +0 -0
  82. package/android/src/main/res/drawable/landfront_c3.png +0 -0
  83. package/android/src/main/res/drawable/landfront_c8.png +0 -0
  84. package/android/src/main/res/drawable/landfront_cc.png +0 -0
  85. package/android/src/main/res/drawable/landfront_d.png +0 -0
  86. package/android/src/main/res/drawable/landfront_dd.png +0 -0
  87. package/android/src/main/res/drawable/landfront_e1.png +0 -0
  88. package/android/src/main/res/drawable/landfront_e5.png +0 -0
  89. package/android/src/main/res/drawable/landfront_ee.png +0 -0
  90. package/android/src/main/res/drawable/landfront_f0.png +0 -0
  91. package/android/src/main/res/drawable/landfront_f1.png +0 -0
  92. package/android/src/main/res/drawable/landfront_f5.png +0 -0
  93. package/android/src/main/res/drawable/landfront_ff.png +0 -0
  94. package/android/src/main/res/drawable/landfront_g3.png +0 -0
  95. package/android/src/main/res/drawable/landfront_g5.png +0 -0
  96. package/android/src/main/res/drawable/landfront_gg.png +0 -0
  97. package/android/src/main/res/drawable/landfront_h1.png +0 -0
  98. package/android/src/main/res/drawable/landfront_h3.png +0 -0
  99. package/android/src/main/res/drawable/landfront_h5.png +0 -0
  100. package/android/src/main/res/drawable/landfront_hh.png +0 -0
  101. package/android/src/main/res/drawable/landfront_i0.png +0 -0
  102. package/android/src/main/res/drawable/landfront_i3.png +0 -0
  103. package/android/src/main/res/drawable/landfront_i5.png +0 -0
  104. package/android/src/main/res/drawable/landfront_ii.png +0 -0
  105. package/android/src/main/res/drawable/landfront_j1.png +0 -0
  106. package/android/src/main/res/drawable/landfront_j8.png +0 -0
  107. package/android/src/main/res/drawable/landfront_jj.png +0 -0
  108. package/android/src/main/res/drawable/landfront_kk.png +0 -0
  109. package/android/src/main/res/drawable/landfront_ll.png +0 -0
  110. package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
  111. package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
  112. package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
  113. package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
  114. package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
  115. package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
  116. package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
  117. package/build/ExpoGaodeMapNaviView.d.ts +16 -0
  118. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  119. package/build/ExpoGaodeMapNaviView.js +74 -1
  120. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  121. package/build/index.d.ts +56 -8
  122. package/build/index.d.ts.map +1 -1
  123. package/build/index.js +452 -10
  124. package/build/index.js.map +1 -1
  125. package/build/map/ExpoGaodeMapModule.d.ts +15 -13
  126. package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
  127. package/build/map/ExpoGaodeMapModule.js +31 -39
  128. package/build/map/ExpoGaodeMapModule.js.map +1 -1
  129. package/build/map/ExpoGaodeMapOfflineModule.d.ts +5 -0
  130. package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
  131. package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
  132. package/build/map/ExpoGaodeMapView.d.ts +3 -4
  133. package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
  134. package/build/map/ExpoGaodeMapView.js +28 -25
  135. package/build/map/ExpoGaodeMapView.js.map +1 -1
  136. package/build/map/components/overlays/Circle.d.ts.map +1 -1
  137. package/build/map/components/overlays/Circle.js +1 -30
  138. package/build/map/components/overlays/Circle.js.map +1 -1
  139. package/build/map/components/overlays/Cluster.d.ts.map +1 -1
  140. package/build/map/components/overlays/Cluster.js +1 -42
  141. package/build/map/components/overlays/Cluster.js.map +1 -1
  142. package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
  143. package/build/map/components/overlays/HeatMap.js +21 -21
  144. package/build/map/components/overlays/HeatMap.js.map +1 -1
  145. package/build/map/components/overlays/Marker.d.ts.map +1 -1
  146. package/build/map/components/overlays/Marker.js +76 -80
  147. package/build/map/components/overlays/Marker.js.map +1 -1
  148. package/build/map/components/overlays/Polygon.d.ts.map +1 -1
  149. package/build/map/components/overlays/Polygon.js +1 -25
  150. package/build/map/components/overlays/Polygon.js.map +1 -1
  151. package/build/map/components/overlays/Polyline.d.ts.map +1 -1
  152. package/build/map/components/overlays/Polyline.js +1 -31
  153. package/build/map/components/overlays/Polyline.js.map +1 -1
  154. package/build/map/index.d.ts +9 -2
  155. package/build/map/index.d.ts.map +1 -1
  156. package/build/map/index.js +9 -2
  157. package/build/map/index.js.map +1 -1
  158. package/build/map/search/ExpoGaodeMapSearch.types.d.ts +340 -0
  159. package/build/map/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
  160. package/build/map/search/ExpoGaodeMapSearch.types.js +19 -0
  161. package/build/map/search/ExpoGaodeMapSearch.types.js.map +1 -0
  162. package/build/map/search/ExpoGaodeMapSearchModule.d.ts +74 -0
  163. package/build/map/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
  164. package/build/map/search/ExpoGaodeMapSearchModule.js +47 -0
  165. package/build/map/search/ExpoGaodeMapSearchModule.js.map +1 -0
  166. package/build/map/search/index.d.ts +156 -0
  167. package/build/map/search/index.d.ts.map +1 -0
  168. package/build/map/search/index.js +171 -0
  169. package/build/map/search/index.js.map +1 -0
  170. package/build/map/types/index.d.ts +2 -2
  171. package/build/map/types/index.d.ts.map +1 -1
  172. package/build/map/types/index.js.map +1 -1
  173. package/build/map/types/map-view.types.d.ts +4 -2
  174. package/build/map/types/map-view.types.d.ts.map +1 -1
  175. package/build/map/types/map-view.types.js.map +1 -1
  176. package/build/map/types/native-module.types.d.ts +11 -12
  177. package/build/map/types/native-module.types.d.ts.map +1 -1
  178. package/build/map/types/native-module.types.js.map +1 -1
  179. package/build/map/types/overlays.types.d.ts +9 -14
  180. package/build/map/types/overlays.types.d.ts.map +1 -1
  181. package/build/map/types/overlays.types.js.map +1 -1
  182. package/build/map/types/route-playback.types.d.ts +16 -0
  183. package/build/map/types/route-playback.types.d.ts.map +1 -1
  184. package/build/map/types/route-playback.types.js.map +1 -1
  185. package/build/map/utils/ErrorHandler.js +11 -11
  186. package/build/map/utils/ErrorHandler.js.map +1 -1
  187. package/build/map/utils/OfflineMapManager.d.ts +4 -0
  188. package/build/map/utils/OfflineMapManager.d.ts.map +1 -1
  189. package/build/map/utils/OfflineMapManager.js +6 -0
  190. package/build/map/utils/OfflineMapManager.js.map +1 -1
  191. package/build/types/coordinates.types.d.ts +3 -0
  192. package/build/types/coordinates.types.d.ts.map +1 -1
  193. package/build/types/coordinates.types.js.map +1 -1
  194. package/build/types/independent.types.d.ts +111 -12
  195. package/build/types/independent.types.d.ts.map +1 -1
  196. package/build/types/independent.types.js.map +1 -1
  197. package/build/types/native-module.types.d.ts +1 -1
  198. package/build/types/native-module.types.js.map +1 -1
  199. package/build/types/naviview.types.d.ts +304 -14
  200. package/build/types/naviview.types.d.ts.map +1 -1
  201. package/build/types/naviview.types.js.map +1 -1
  202. package/build/types/route.types.d.ts +12 -2
  203. package/build/types/route.types.d.ts.map +1 -1
  204. package/build/types/route.types.js.map +1 -1
  205. package/expo-module.config.json +4 -2
  206. package/ios/ExpoGaodeMapNaviView.swift +2331 -201
  207. package/ios/ExpoGaodeMapNaviViewModule.swift +109 -1
  208. package/ios/ExpoGaodeMapNavigation.podspec +2 -1
  209. package/ios/ExpoGaodeMapNavigationModule.swift +7 -5
  210. package/ios/managers/IndependentRouteManager.swift +90 -26
  211. package/ios/map/ExpoGaodeMapModule.swift +72 -21
  212. package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
  213. package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
  214. package/ios/map/ExpoGaodeMapView.swift +23 -5
  215. package/ios/map/GaodeMapPrivacyManager.swift +26 -18
  216. package/ios/map/cpp/GeometryEngine.cpp +112 -0
  217. package/ios/map/cpp/GeometryEngine.hpp +21 -0
  218. package/ios/map/modules/LocationManager.swift +37 -5
  219. package/ios/map/overlays/MarkerView.swift +11 -11
  220. package/ios/map/overlays/MarkerViewModule.swift +4 -4
  221. package/ios/map/overlays/PolylineView.swift +6 -12
  222. package/ios/map/utils/ClusterNative.h +8 -0
  223. package/ios/map/utils/ClusterNative.mm +27 -0
  224. package/ios/map/utils/PermissionManager.swift +115 -6
  225. package/ios/routes/drive/DriveTruckRouteCalculator.swift +165 -77
  226. package/ios/routes/walkride/WalkRideRouteCalculator.swift +127 -1
  227. package/ios/services/IndependentRouteService.swift +198 -39
  228. package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
  229. package/ios/services/NavigationLiveActivityManager.swift +359 -0
  230. package/package.json +22 -7
  231. package/plugin/build/withGaodeMap.d.ts +8 -0
  232. package/plugin/build/withGaodeMap.js +60 -4
  233. package/scripts/check-expo-modules.js +68 -0
  234. package/shared/cpp/GeometryEngine.cpp +112 -0
  235. package/shared/cpp/GeometryEngine.hpp +21 -0
  236. package/widget-template/README.md +46 -0
  237. package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
  238. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/RouteCalculator.kt +0 -173
@@ -96,6 +96,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
96
96
  private var uiManager: UIManager!
97
97
  /// 地图是否已加载完成
98
98
  private var isMapLoaded = false
99
+ /// 初始相机是否已应用(仅应用一次,避免与运行时相机控制冲突)
100
+ private var hasAppliedInitialCameraPosition = false
99
101
  /// 是否正在处理 annotation 选择事件
100
102
  private var isHandlingAnnotationSelect = false
101
103
  /// MarkerView 的隐藏容器(用于渲染 children)
@@ -496,9 +498,10 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
496
498
 
497
499
  uiManager.setMapType(mapType)
498
500
 
499
- // 如果有初始位置,设置相机位置
500
- if let position = initialCameraPosition {
501
+ // initialCameraPosition 只应用一次,避免每次 props 更新重置相机导致操作延迟感
502
+ if !hasAppliedInitialCameraPosition, let position = initialCameraPosition {
501
503
  cameraManager.setInitialCameraPosition(position)
504
+ hasAppliedInitialCameraPosition = true
502
505
  }
503
506
 
504
507
  uiManager.setShowsScale(showsScale)
@@ -969,6 +972,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
969
972
 
970
973
  mapView = resolvedMapView
971
974
  super.addSubview(resolvedMapView)
975
+ isMapLoaded = false
976
+ hasAppliedInitialCameraPosition = false
972
977
 
973
978
  cameraManager = CameraManager(mapView: resolvedMapView)
974
979
  uiManager = UIManager(mapView: resolvedMapView)
@@ -1026,6 +1031,12 @@ extension ExpoGaodeMapView {
1026
1031
  public func mapViewDidFinishLoadingMap(_ mapView: MAMapView) {
1027
1032
  guard !isMapLoaded else { return }
1028
1033
  isMapLoaded = true
1034
+
1035
+ // 兜底:若初始化阶段尚未生效,在加载完成后应用一次初始相机
1036
+ if !hasAppliedInitialCameraPosition, let position = initialCameraPosition {
1037
+ cameraManager?.setInitialCameraPosition(position)
1038
+ hasAppliedInitialCameraPosition = true
1039
+ }
1029
1040
 
1030
1041
  // 地图加载完成后,应用自定义样式
1031
1042
  if let styleData = customMapStyleData {
@@ -1443,17 +1454,24 @@ extension ExpoGaodeMapView {
1443
1454
  isHandlingAnnotationSelect = true
1444
1455
 
1445
1456
  // 🔑 统一从 overlayViews 查找 MarkerView(新旧架构统一)
1446
- for view in overlayViews {
1447
- if let markerView = view as? MarkerView {
1457
+ for overlayView in overlayViews {
1458
+ if let markerView = overlayView as? MarkerView {
1448
1459
  if markerView.annotation === annotation {
1449
1460
  let eventData: [String: Any] = [
1450
1461
  "latitude": annotation.coordinate.latitude,
1451
1462
  "longitude": annotation.coordinate.longitude
1452
1463
  ]
1453
1464
  markerView.onMarkerPress(eventData)
1465
+ // iOS 对“已选中 annotation”再次点击通常不会重复触发 didSelect。
1466
+ // 对自定义 children marker(无系统 callout)这里主动取消选中,
1467
+ // 以保证同一 marker 可以连续触发点击(例如关闭 sheet 后再次点击)。
1468
+ if !markerView.subviews.isEmpty {
1469
+ mapView.deselectAnnotation(annotation, animated: false)
1470
+ isHandlingAnnotationSelect = false
1471
+ }
1454
1472
  return
1455
1473
  }
1456
- } else if let clusterView = view as? ClusterView {
1474
+ } else if let clusterView = overlayView as? ClusterView {
1457
1475
  if clusterView.containsAnnotation(annotation) {
1458
1476
  clusterView.handleAnnotationTap(annotation)
1459
1477
  return
@@ -46,24 +46,6 @@ enum GaodeMapPrivacyManager {
46
46
  applyPrivacyState()
47
47
  }
48
48
 
49
- static func setPrivacyShow(_ show: Bool, hasContainsPrivacy: Bool) {
50
- let previousStatus = status()
51
- hasShow = show
52
- self.hasContainsPrivacy = hasContainsPrivacy
53
- persistState()
54
- applyPrivacyState()
55
- notifyIfNeeded(previousStatus: previousStatus)
56
- }
57
-
58
- static func setPrivacyAgree(_ agree: Bool) {
59
- let previousStatus = status()
60
- hasAgree = agree
61
- agreedPrivacyVersion = agree ? privacyVersion : nil
62
- persistState()
63
- applyPrivacyState()
64
- notifyIfNeeded(previousStatus: previousStatus)
65
- }
66
-
67
49
  static func setPrivacyVersion(_ version: String) {
68
50
  let previousStatus = status()
69
51
  privacyVersion = version.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -83,6 +65,32 @@ enum GaodeMapPrivacyManager {
83
65
  notifyIfNeeded(previousStatus: previousStatus)
84
66
  }
85
67
 
68
+ static func setPrivacyConfig(
69
+ hasShow: Bool,
70
+ hasContainsPrivacy: Bool,
71
+ hasAgree: Bool,
72
+ privacyVersion newPrivacyVersion: String?,
73
+ updatesPrivacyVersion: Bool
74
+ ) {
75
+ let previousStatus = status()
76
+
77
+ if updatesPrivacyVersion {
78
+ privacyVersion = newPrivacyVersion?.trimmingCharacters(in: .whitespacesAndNewlines)
79
+ if privacyVersion?.isEmpty == true {
80
+ privacyVersion = nil
81
+ }
82
+ }
83
+
84
+ self.hasShow = hasShow
85
+ self.hasContainsPrivacy = hasContainsPrivacy
86
+ self.hasAgree = hasAgree
87
+ agreedPrivacyVersion = hasAgree ? privacyVersion : nil
88
+
89
+ persistState()
90
+ applyPrivacyState()
91
+ notifyIfNeeded(previousStatus: previousStatus)
92
+ }
93
+
86
94
  static func resetPrivacyConsent() {
87
95
  let previousStatus = status()
88
96
  clearConsentPersistedState(keepCurrentVersion: false)
@@ -3,6 +3,7 @@
3
3
  #include <cmath>
4
4
  #include <map>
5
5
  #include <algorithm>
6
+ #include <limits>
6
7
 
7
8
  namespace gaodemap {
8
9
 
@@ -19,6 +20,54 @@ static inline double geo_toDegrees(double radians) {
19
20
  return radians * kRadiansToDegrees;
20
21
  }
21
22
 
23
+ static inline double clampMercatorLatitude(double lat) {
24
+ static constexpr double kMaxMercatorLatitude = 85.05112878;
25
+ if (lat > kMaxMercatorLatitude) return kMaxMercatorLatitude;
26
+ if (lat < -kMaxMercatorLatitude) return -kMaxMercatorLatitude;
27
+ return lat;
28
+ }
29
+
30
+ static inline double mercatorX01(double lon) {
31
+ double wrapped = std::fmod(lon + 180.0, 360.0);
32
+ if (wrapped < 0.0) {
33
+ wrapped += 360.0;
34
+ }
35
+ return wrapped / 360.0;
36
+ }
37
+
38
+ static inline double mercatorY01(double lat) {
39
+ const double clampedLat = clampMercatorLatitude(lat);
40
+ const double latRad = geo_toRadians(clampedLat);
41
+ const double y = (1.0 - std::asinh(std::tan(latRad)) / kPi) * 0.5;
42
+ if (y < 0.0) return 0.0;
43
+ if (y > 1.0) return 1.0;
44
+ return y;
45
+ }
46
+
47
+ static double wrappedSpan01(std::vector<double> xs) {
48
+ if (xs.size() <= 1) {
49
+ return 0.0;
50
+ }
51
+
52
+ std::sort(xs.begin(), xs.end());
53
+ double maxGap = 0.0;
54
+
55
+ for (size_t i = 0; i + 1 < xs.size(); ++i) {
56
+ const double gap = xs[i + 1] - xs[i];
57
+ if (gap > maxGap) {
58
+ maxGap = gap;
59
+ }
60
+ }
61
+
62
+ const double endGap = xs.front() + 1.0 - xs.back();
63
+ if (endGap > maxGap) {
64
+ maxGap = endGap;
65
+ }
66
+
67
+ const double span = 1.0 - maxGap;
68
+ return span < 0.0 ? 0.0 : span;
69
+ }
70
+
22
71
  double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
23
72
  const double radLat1 = geo_toRadians(lat1);
24
73
  const double radLat2 = geo_toRadians(lat2);
@@ -512,6 +561,69 @@ GeoPoint pixelToLatLng(double x, double y, int zoom) {
512
561
  return {lat, lon};
513
562
  }
514
563
 
564
+ double calculateFitZoomForPoints(
565
+ const std::vector<GeoPoint>& points,
566
+ double viewportWidthPx,
567
+ double viewportHeightPx,
568
+ double paddingPx,
569
+ int minZoom,
570
+ int maxZoom
571
+ ) {
572
+ if (minZoom > maxZoom) {
573
+ std::swap(minZoom, maxZoom);
574
+ }
575
+ if (points.empty()) {
576
+ return static_cast<double>(minZoom);
577
+ }
578
+ if (points.size() == 1) {
579
+ return static_cast<double>(maxZoom);
580
+ }
581
+
582
+ const double safeViewportWidth = viewportWidthPx > 1.0 ? viewportWidthPx : 390.0;
583
+ const double safeViewportHeight = viewportHeightPx > 1.0 ? viewportHeightPx : 844.0;
584
+ const double safePadding = std::max(0.0, paddingPx);
585
+ const double availableWidth = std::max(1.0, safeViewportWidth - safePadding * 2.0);
586
+ const double availableHeight = std::max(1.0, safeViewportHeight - safePadding * 2.0);
587
+
588
+ std::vector<double> projectedXs;
589
+ projectedXs.reserve(points.size());
590
+
591
+ double minY = std::numeric_limits<double>::max();
592
+ double maxY = -std::numeric_limits<double>::max();
593
+
594
+ for (const auto& p : points) {
595
+ projectedXs.push_back(mercatorX01(p.lon));
596
+ const double y = mercatorY01(p.lat);
597
+ if (y < minY) minY = y;
598
+ if (y > maxY) maxY = y;
599
+ }
600
+
601
+ const double spanX = wrappedSpan01(projectedXs);
602
+ const double spanY = std::max(0.0, maxY - minY);
603
+ static constexpr double kTileSize = 256.0;
604
+ static constexpr double kSpanEpsilon = 1e-12;
605
+
606
+ const double zoomX = spanX <= kSpanEpsilon
607
+ ? static_cast<double>(maxZoom)
608
+ : std::log2(availableWidth / (kTileSize * spanX));
609
+ const double zoomY = spanY <= kSpanEpsilon
610
+ ? static_cast<double>(maxZoom)
611
+ : std::log2(availableHeight / (kTileSize * spanY));
612
+
613
+ double fitZoom = std::min(zoomX, zoomY);
614
+ if (!std::isfinite(fitZoom)) {
615
+ fitZoom = static_cast<double>(minZoom);
616
+ }
617
+
618
+ if (fitZoom < static_cast<double>(minZoom)) {
619
+ fitZoom = static_cast<double>(minZoom);
620
+ } else if (fitZoom > static_cast<double>(maxZoom)) {
621
+ fitZoom = static_cast<double>(maxZoom);
622
+ }
623
+
624
+ return fitZoom;
625
+ }
626
+
515
627
  // --- 批量地理围栏与热力图 ---
516
628
 
517
629
  int findPointInPolygons(double pointLat, double pointLon, const std::vector<std::vector<GeoPoint>>& polygons) {
@@ -125,6 +125,27 @@ PixelResult latLngToPixel(double lat, double lon, int zoom);
125
125
  */
126
126
  GeoPoint pixelToLatLng(double x, double y, int zoom);
127
127
 
128
+ /**
129
+ * 根据一组坐标点和视口尺寸计算“可同时看到所有点”的推荐缩放级别。
130
+ * 使用 Web Mercator 投影,在跨经线场景下会自动取更小经度跨度。
131
+ *
132
+ * @param points 坐标点集合,至少 1 个
133
+ * @param viewportWidthPx 视口宽度(像素)
134
+ * @param viewportHeightPx 视口高度(像素)
135
+ * @param paddingPx 四周预留的内边距(像素)
136
+ * @param minZoom 最小缩放级别
137
+ * @param maxZoom 最大缩放级别
138
+ * @return 推荐 zoom(已在 [minZoom, maxZoom] 范围内)
139
+ */
140
+ double calculateFitZoomForPoints(
141
+ const std::vector<GeoPoint>& points,
142
+ double viewportWidthPx,
143
+ double viewportHeightPx,
144
+ double paddingPx,
145
+ int minZoom,
146
+ int maxZoom
147
+ );
148
+
128
149
  // --- 批量地理围栏与热力图 ---
129
150
 
130
151
  /**
@@ -90,14 +90,29 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
90
90
  return
91
91
  }
92
92
  }
93
- ensureLocationManager()?.allowsBackgroundLocationUpdates = allows
93
+ guard let manager = ensureLocationManager() else {
94
+ return
95
+ }
96
+ manager.allowsBackgroundLocationUpdates = allows
97
+ if #available(iOS 11.0, *) {
98
+ let indicatorSelector = NSSelectorFromString("setShowsBackgroundLocationIndicator:")
99
+ if manager.responds(to: indicatorSelector) {
100
+ manager.setValue(allows, forKey: "showsBackgroundLocationIndicator")
101
+ }
102
+ }
103
+ }
104
+
105
+ private func setReGeocodeLanguage(_ rawValue: AMapRegionLanguageType.RawValue) {
106
+ if let language = AMapRegionLanguageType(rawValue: rawValue) {
107
+ ensureLocationManager()?.reGeocodeLanguage = language
108
+ }
94
109
  }
95
110
 
96
111
  func setGeoLanguage(_ language: Int) {
97
112
  switch language {
98
- case 0: ensureLocationManager()?.reGeocodeLanguage = .default
99
- case 1: ensureLocationManager()?.reGeocodeLanguage = .chinse
100
- case 2: ensureLocationManager()?.reGeocodeLanguage = .english
113
+ case 0: setReGeocodeLanguage(0)
114
+ case 1: setReGeocodeLanguage(0)
115
+ case 2: setReGeocodeLanguage(2)
101
116
  default: break
102
117
  }
103
118
  }
@@ -112,6 +127,23 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
112
127
  ensureLocationManager()?.stopUpdatingHeading()
113
128
  }
114
129
 
130
+ func requestSingleLocation(completion: @escaping (_ location: CLLocation?, _ reGeocode: AMapLocationReGeocode?, _ error: Error?) -> Void) {
131
+ guard let manager = ensureLocationManager() else {
132
+ let error = NSError(
133
+ domain: "ExpoGaodeMap",
134
+ code: -1,
135
+ userInfo: [NSLocalizedDescriptionKey: "定位管理器未初始化,请先完成隐私协议确认"]
136
+ )
137
+ completion(nil, nil, error)
138
+ return
139
+ }
140
+
141
+ manager.requestLocation(
142
+ withReGeocode: manager.locatingWithReGeocode,
143
+ completionBlock: completion
144
+ )
145
+ }
146
+
115
147
  // MARK: - 初始化
116
148
 
117
149
  @discardableResult
@@ -199,7 +231,7 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
199
231
  */
200
232
  func coordinateConvert(_ coordinate: [String: Double], type: Int, promise: Promise) {
201
233
  guard GaodeMapPrivacyManager.isReady else {
202
- promise.reject("PRIVACY_NOT_AGREED", "隐私协议未完成确认,请先调用 setPrivacyShow/setPrivacyAgree")
234
+ promise.reject("PRIVACY_NOT_AGREED", "隐私协议未完成确认,请先调用 setPrivacyConfig")
203
235
  return
204
236
  }
205
237
 
@@ -37,9 +37,9 @@ class MarkerView: ExpoView {
37
37
  /// 图标高度(用于自定义图标 icon 属性)
38
38
  var iconHeight: Double = 40
39
39
  /// 自定义视图宽度(用于 children 属性)
40
- var customViewWidth: Double = 0
40
+ var contentWidth: Double = 0
41
41
  /// 自定义视图高度(用于 children 属性)
42
- var customViewHeight: Double = 0
42
+ var contentHeight: Double = 0
43
43
  /// 中心偏移
44
44
  var centerOffset: [String: Double]?
45
45
  /// 是否显示动画
@@ -585,9 +585,9 @@ class MarkerView: ExpoView {
585
585
  return defaultSize
586
586
  }
587
587
 
588
- if customViewWidth > 0 || customViewHeight > 0 {
589
- let width = customViewWidth > 0 ? CGFloat(customViewWidth) : defaultSize.width
590
- let height = customViewHeight > 0 ? CGFloat(customViewHeight) : defaultSize.height
588
+ if contentWidth > 0 || contentHeight > 0 {
589
+ let width = contentWidth > 0 ? CGFloat(contentWidth) : defaultSize.width
590
+ let height = contentHeight > 0 ? CGFloat(contentHeight) : defaultSize.height
591
591
  return CGSize(width: width, height: height)
592
592
  }
593
593
 
@@ -1000,17 +1000,17 @@ class MarkerView: ExpoView {
1000
1000
  }
1001
1001
  }
1002
1002
 
1003
- func setCustomViewWidth(_ width: Double) {
1004
- guard customViewWidth != width else { return }
1005
- customViewWidth = width
1003
+ func setContentWidth(_ width: Double) {
1004
+ guard contentWidth != width else { return }
1005
+ contentWidth = width
1006
1006
  if !subviews.isEmpty {
1007
1007
  refreshAnnotationAppearance(invalidateChildrenCache: true)
1008
1008
  }
1009
1009
  }
1010
1010
 
1011
- func setCustomViewHeight(_ height: Double) {
1012
- guard customViewHeight != height else { return }
1013
- customViewHeight = height
1011
+ func setContentHeight(_ height: Double) {
1012
+ guard contentHeight != height else { return }
1013
+ contentHeight = height
1014
1014
  if !subviews.isEmpty {
1015
1015
  refreshAnnotationAppearance(invalidateChildrenCache: true)
1016
1016
  }
@@ -41,12 +41,12 @@ public class MarkerViewModule: Module {
41
41
  view.iconHeight = height
42
42
  }
43
43
 
44
- Prop("customViewWidth") { (view: MarkerView, width: Double) in
45
- view.customViewWidth = width
44
+ Prop("contentWidth") { (view: MarkerView, width: Double) in
45
+ view.setContentWidth(width)
46
46
  }
47
47
 
48
- Prop("customViewHeight") { (view: MarkerView, height: Double) in
49
- view.customViewHeight = height
48
+ Prop("contentHeight") { (view: MarkerView, height: Double) in
49
+ view.setContentHeight(height)
50
50
  }
51
51
 
52
52
  Prop("centerOffset") { (view: MarkerView, offset: [String: Double]) in
@@ -71,6 +71,9 @@ class PolylineView: ExpoView {
71
71
  func setMap(_ map: MAMapView) {
72
72
  // 🔑 关键优化:如果是同一个地图引用,跳过重复设置
73
73
  if lastSetMapView === map {
74
+ if polyline == nil {
75
+ updatePolyline()
76
+ }
74
77
  return
75
78
  }
76
79
 
@@ -105,10 +108,9 @@ class PolylineView: ExpoView {
105
108
  // 🔑 至少需要2个点才能绘制折线
106
109
  guard coords.count >= 2 else { return }
107
110
 
111
+ renderer = nil
108
112
  polyline = MAPolyline(coordinates: &coords, count: UInt(coords.count))
109
113
  mapView.add(polyline!)
110
-
111
- renderer = nil
112
114
  }
113
115
 
114
116
  /**
@@ -221,18 +223,10 @@ class PolylineView: ExpoView {
221
223
 
222
224
  /**
223
225
  * 强制重新渲染折线
224
- * 通过移除并重新添加 overlay 来触发地图重新请求 renderer
226
+ * 通过重建 overlay 来触发地图重新请求 renderer
225
227
  */
226
228
  private func forceRerender() {
227
- guard let mapView = mapView, let polyline = polyline else {
228
- return
229
- }
230
-
231
- // 移除旧的 overlay
232
- mapView.remove(polyline)
233
-
234
- // 重新添加(地图会调用 rendererFor overlay)
235
- mapView.add(polyline)
229
+ updatePolyline()
236
230
  }
237
231
 
238
232
  func setDotted(_ dotted: Bool) {
@@ -55,6 +55,14 @@ NS_ASSUME_NONNULL_BEGIN
55
55
  + (NSDictionary * _Nullable)calculatePathBoundsWithLatitudes:(NSArray<NSNumber *> *)latitudes
56
56
  longitudes:(NSArray<NSNumber *> *)longitudes NS_SWIFT_NAME(calculatePathBounds(latitudes:longitudes:));
57
57
 
58
+ + (double)calculateFitZoomWithLatitudes:(NSArray<NSNumber *> *)latitudes
59
+ longitudes:(NSArray<NSNumber *> *)longitudes
60
+ viewportWidthPx:(double)viewportWidthPx
61
+ viewportHeightPx:(double)viewportHeightPx
62
+ paddingPx:(double)paddingPx
63
+ minZoom:(int)minZoom
64
+ maxZoom:(int)maxZoom NS_SWIFT_NAME(calculateFitZoom(latitudes:longitudes:viewportWidthPx:viewportHeightPx:paddingPx:minZoom:maxZoom:));
65
+
58
66
  + (NSString *)encodeGeoHashWithLat:(double)lat
59
67
  lon:(double)lon
60
68
  precision:(int)precision NS_SWIFT_NAME(encodeGeoHash(lat:lon:precision:));
@@ -242,6 +242,33 @@
242
242
  };
243
243
  }
244
244
 
245
+ + (double)calculateFitZoomWithLatitudes:(NSArray<NSNumber *> *)latitudes
246
+ longitudes:(NSArray<NSNumber *> *)longitudes
247
+ viewportWidthPx:(double)viewportWidthPx
248
+ viewportHeightPx:(double)viewportHeightPx
249
+ paddingPx:(double)paddingPx
250
+ minZoom:(int)minZoom
251
+ maxZoom:(int)maxZoom {
252
+ if (latitudes.count == 0 || latitudes.count != longitudes.count) {
253
+ return (double)minZoom;
254
+ }
255
+
256
+ std::vector<gaodemap::GeoPoint> points;
257
+ points.reserve(latitudes.count);
258
+ for (NSUInteger i = 0; i < latitudes.count; i++) {
259
+ points.push_back({[latitudes[i] doubleValue], [longitudes[i] doubleValue]});
260
+ }
261
+
262
+ return gaodemap::calculateFitZoomForPoints(
263
+ points,
264
+ viewportWidthPx,
265
+ viewportHeightPx,
266
+ paddingPx,
267
+ minZoom,
268
+ maxZoom
269
+ );
270
+ }
271
+
245
272
  + (NSString *)encodeGeoHashWithLat:(double)lat
246
273
  lon:(double)lon
247
274
  precision:(int)precision {
@@ -39,6 +39,39 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
39
39
  private var locationManager: CLLocationManager?
40
40
  /// 权限请求回调
41
41
  private var permissionCallback: ((Bool, String) -> Void)?
42
+ /// 后台(始终)权限请求回调
43
+ private var backgroundPermissionCallback: ((Bool, String) -> Void)?
44
+ /// 在 notDetermined 状态下,先申请前台权限,再升级为始终权限
45
+ private var pendingAlwaysAuthorizationUpgrade = false
46
+
47
+ private func resolveBackgroundPermissionIfNeeded(_ status: CLAuthorizationStatus) {
48
+ guard let backgroundPermissionCallback else {
49
+ return
50
+ }
51
+ let backgroundGranted = status == .authorizedAlways
52
+ backgroundPermissionCallback(backgroundGranted, getAuthorizationStatusString(status))
53
+ self.backgroundPermissionCallback = nil
54
+ }
55
+
56
+ private func scheduleAlwaysAuthorizationFallback(_ manager: CLLocationManager) {
57
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { [weak self] in
58
+ guard let self else {
59
+ return
60
+ }
61
+ guard self.backgroundPermissionCallback != nil else {
62
+ return
63
+ }
64
+
65
+ let latestStatus: CLAuthorizationStatus
66
+ if #available(iOS 14.0, *) {
67
+ latestStatus = manager.authorizationStatus
68
+ } else {
69
+ latestStatus = CLLocationManager.authorizationStatus()
70
+ }
71
+ self.pendingAlwaysAuthorizationUpgrade = false
72
+ self.resolveBackgroundPermissionIfNeeded(latestStatus)
73
+ }
74
+ }
42
75
 
43
76
  /**
44
77
  * 请求位置权限
@@ -48,17 +81,22 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
48
81
  self.permissionCallback = callback
49
82
 
50
83
  // 确保在主线程操作
51
- DispatchQueue.main.async { [weak self] in
52
- guard let self = self else { return }
53
-
84
+ DispatchQueue.main.async {
54
85
  if self.locationManager == nil {
55
86
  self.locationManager = CLLocationManager()
56
- self.locationManager?.delegate = self
57
87
  }
88
+
89
+ guard let locationManager = self.locationManager else {
90
+ self.permissionCallback?(false, "unknown")
91
+ self.permissionCallback = nil
92
+ return
93
+ }
94
+
95
+ locationManager.delegate = self
58
96
 
59
97
  var currentStatus: CLAuthorizationStatus
60
98
  if #available(iOS 14.0, *) {
61
- currentStatus = self.locationManager?.authorizationStatus ?? .notDetermined
99
+ currentStatus = locationManager.authorizationStatus
62
100
  } else {
63
101
  currentStatus = CLLocationManager.authorizationStatus()
64
102
  }
@@ -84,7 +122,62 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
84
122
  return
85
123
  }
86
124
 
87
- self.locationManager?.requestWhenInUseAuthorization()
125
+ locationManager.requestWhenInUseAuthorization()
126
+ }
127
+ }
128
+
129
+ /**
130
+ * 请求后台(始终)位置权限
131
+ * @param callback 权限结果回调 (granted, status)
132
+ */
133
+ func requestAlwaysPermission(callback: @escaping (Bool, String) -> Void) {
134
+ self.backgroundPermissionCallback = callback
135
+
136
+ DispatchQueue.main.async {
137
+ if self.locationManager == nil {
138
+ self.locationManager = CLLocationManager()
139
+ }
140
+
141
+ guard let locationManager = self.locationManager else {
142
+ self.backgroundPermissionCallback?(false, "unknown")
143
+ self.backgroundPermissionCallback = nil
144
+ self.pendingAlwaysAuthorizationUpgrade = false
145
+ return
146
+ }
147
+
148
+ locationManager.delegate = self
149
+
150
+ let currentStatus: CLAuthorizationStatus
151
+ if #available(iOS 14.0, *) {
152
+ currentStatus = locationManager.authorizationStatus
153
+ } else {
154
+ currentStatus = CLLocationManager.authorizationStatus()
155
+ }
156
+
157
+ if currentStatus == .authorizedAlways {
158
+ self.backgroundPermissionCallback?(true, self.getAuthorizationStatusString(currentStatus))
159
+ self.backgroundPermissionCallback = nil
160
+ self.pendingAlwaysAuthorizationUpgrade = false
161
+ return
162
+ }
163
+
164
+ if currentStatus == .denied || currentStatus == .restricted {
165
+ self.backgroundPermissionCallback?(false, self.getAuthorizationStatusString(currentStatus))
166
+ self.backgroundPermissionCallback = nil
167
+ self.pendingAlwaysAuthorizationUpgrade = false
168
+ return
169
+ }
170
+
171
+ if currentStatus == .authorizedWhenInUse {
172
+ self.pendingAlwaysAuthorizationUpgrade = false
173
+ locationManager.requestAlwaysAuthorization()
174
+ self.scheduleAlwaysAuthorizationFallback(locationManager)
175
+ return
176
+ }
177
+
178
+ // notDetermined: 先请求前台定位,再在回调中升级到始终权限
179
+ self.pendingAlwaysAuthorizationUpgrade = true
180
+ locationManager.requestWhenInUseAuthorization()
88
181
  }
89
182
  }
90
183
 
@@ -110,6 +203,18 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
110
203
  if status == .notDetermined {
111
204
  return
112
205
  }
206
+
207
+ if pendingAlwaysAuthorizationUpgrade && status == .authorizedWhenInUse {
208
+ pendingAlwaysAuthorizationUpgrade = false
209
+ if let locationManager {
210
+ locationManager.requestAlwaysAuthorization()
211
+ scheduleAlwaysAuthorizationFallback(locationManager)
212
+ } else {
213
+ resolveBackgroundPermissionIfNeeded(status)
214
+ }
215
+ return
216
+ }
217
+ pendingAlwaysAuthorizationUpgrade = false
113
218
 
114
219
  // 状态已确定(授予或拒绝),返回结果
115
220
  var granted = false
@@ -120,6 +225,8 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
120
225
  }
121
226
  let statusString = getAuthorizationStatusString(status)
122
227
 
228
+ resolveBackgroundPermissionIfNeeded(status)
229
+
123
230
  permissionCallback?(granted, statusString)
124
231
  permissionCallback = nil
125
232
  }
@@ -231,6 +338,8 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
231
338
  locationManager?.delegate = nil
232
339
  locationManager = nil
233
340
  permissionCallback = nil
341
+ backgroundPermissionCallback = nil
342
+ pendingAlwaysAuthorizationUpgrade = false
234
343
  }
235
344
  }
236
345
  #endif