expo-gaode-map-navigation 1.1.5-next.1 → 1.1.5-next.2

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 (131) hide show
  1. package/android/build.gradle +10 -0
  2. package/android/src/main/cpp/CMakeLists.txt +24 -0
  3. package/android/src/main/cpp/cluster_jni.cpp +848 -0
  4. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +616 -92
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +493 -0
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +230 -14
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +37 -27
  8. package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +494 -0
  9. package/android/src/main/java/expo/modules/gaodemap/map/companion/BitmapDescriptorCache.kt +30 -0
  10. package/android/src/main/java/expo/modules/gaodemap/map/companion/IconBitmapCache.kt +37 -0
  11. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +76 -0
  12. package/android/src/main/java/expo/modules/gaodemap/map/modules/LocationManager.kt +15 -3
  13. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +4 -59
  14. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleView.kt +9 -12
  15. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleViewModule.kt +5 -6
  16. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +539 -66
  17. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterViewModule.kt +17 -1
  18. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +165 -33
  19. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +15 -3
  20. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +1249 -672
  21. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +40 -17
  22. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointView.kt +177 -22
  23. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointViewModule.kt +11 -3
  24. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonView.kt +57 -14
  25. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonViewModule.kt +9 -5
  26. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineView.kt +90 -63
  27. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineViewModule.kt +7 -3
  28. package/android/src/main/java/expo/modules/gaodemap/map/services/LocationForegroundService.kt +3 -2
  29. package/android/src/main/java/expo/modules/gaodemap/map/utils/BitmapDescriptorCache.kt +20 -0
  30. package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +13 -0
  31. package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +20 -0
  32. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +515 -0
  33. package/android/src/main/java/expo/modules/gaodemap/map/utils/LatLngParser.kt +91 -0
  34. package/android/src/main/java/expo/modules/gaodemap/map/utils/PermissionHelper.kt +248 -0
  35. package/build/ExpoGaodeMapNaviView.d.ts +7 -7
  36. package/build/ExpoGaodeMapNaviView.js +8 -8
  37. package/build/index.d.ts +1 -1
  38. package/build/index.js +2 -2
  39. package/build/map/ExpoGaodeMapModule.d.ts +2 -201
  40. package/build/map/ExpoGaodeMapModule.js +584 -14
  41. package/build/map/ExpoGaodeMapOfflineModule.d.ts +139 -0
  42. package/build/map/ExpoGaodeMapOfflineModule.js +8 -0
  43. package/build/map/ExpoGaodeMapView.js +66 -58
  44. package/build/map/components/FoldableMapView.d.ts +38 -0
  45. package/build/map/components/FoldableMapView.js +209 -0
  46. package/build/map/components/MapContext.d.ts +12 -0
  47. package/build/map/components/MapContext.js +54 -0
  48. package/build/map/components/MapUI.d.ts +18 -0
  49. package/build/map/components/MapUI.js +29 -0
  50. package/build/map/components/overlays/Circle.js +34 -3
  51. package/build/map/components/overlays/Cluster.d.ts +3 -1
  52. package/build/map/components/overlays/Cluster.js +31 -2
  53. package/build/map/components/overlays/HeatMap.d.ts +3 -1
  54. package/build/map/components/overlays/HeatMap.js +33 -3
  55. package/build/map/components/overlays/Marker.d.ts +1 -1
  56. package/build/map/components/overlays/Marker.js +37 -32
  57. package/build/map/components/overlays/MultiPoint.js +1 -1
  58. package/build/map/components/overlays/Polygon.js +30 -3
  59. package/build/map/components/overlays/Polyline.js +36 -3
  60. package/build/map/index.d.ts +25 -5
  61. package/build/map/index.js +59 -18
  62. package/build/map/types/common.types.d.ts +40 -0
  63. package/build/map/types/common.types.js +0 -4
  64. package/build/map/types/index.d.ts +3 -2
  65. package/build/map/types/map-view.types.d.ts +108 -3
  66. package/build/map/types/native-module.types.d.ts +363 -0
  67. package/build/map/types/native-module.types.js +5 -0
  68. package/build/map/types/offline.types.d.ts +132 -0
  69. package/build/map/types/offline.types.js +5 -0
  70. package/build/map/types/overlays.types.d.ts +137 -24
  71. package/build/map/utils/ErrorHandler.d.ts +110 -0
  72. package/build/map/utils/ErrorHandler.js +421 -0
  73. package/build/map/utils/GeoUtils.d.ts +20 -0
  74. package/build/map/utils/GeoUtils.js +76 -0
  75. package/build/map/utils/OfflineMapManager.d.ts +148 -0
  76. package/build/map/utils/OfflineMapManager.js +217 -0
  77. package/build/map/utils/PermissionUtils.d.ts +91 -0
  78. package/build/map/utils/PermissionUtils.js +255 -0
  79. package/build/map/utils/PlatformDetector.d.ts +102 -0
  80. package/build/map/utils/PlatformDetector.js +186 -0
  81. package/build/types/naviview.types.d.ts +1 -1
  82. package/expo-module.config.json +12 -10
  83. package/ios/ExpoGaodeMapNavigation.podspec +9 -0
  84. package/ios/map/ExpoGaodeMapModule.swift +485 -75
  85. package/ios/map/ExpoGaodeMapOfflineModule.swift +479 -0
  86. package/ios/map/ExpoGaodeMapView.swift +611 -62
  87. package/ios/map/ExpoGaodeMapViewModule.swift +48 -26
  88. package/ios/map/MapPreloadManager.swift +348 -0
  89. package/ios/map/cpp/ClusterEngine.cpp +110 -0
  90. package/ios/map/cpp/ClusterEngine.hpp +20 -0
  91. package/ios/map/cpp/ColorParser.cpp +135 -0
  92. package/ios/map/cpp/ColorParser.hpp +14 -0
  93. package/ios/map/cpp/GeometryEngine.cpp +574 -0
  94. package/ios/map/cpp/GeometryEngine.hpp +159 -0
  95. package/ios/map/cpp/QuadTree.cpp +92 -0
  96. package/ios/map/cpp/QuadTree.hpp +42 -0
  97. package/ios/map/cpp/README.md +55 -0
  98. package/ios/map/cpp/tests/benchmark_js.js +41 -0
  99. package/ios/map/cpp/tests/run.sh +17 -0
  100. package/ios/map/cpp/tests/test_main.cpp +276 -0
  101. package/ios/map/managers/UIManager.swift +72 -1
  102. package/ios/map/modules/LocationManager.swift +114 -165
  103. package/ios/map/overlays/CircleView.swift +16 -32
  104. package/ios/map/overlays/CircleViewModule.swift +12 -12
  105. package/ios/map/overlays/ClusterAnnotation.swift +32 -0
  106. package/ios/map/overlays/ClusterView.swift +331 -45
  107. package/ios/map/overlays/ClusterViewModule.swift +20 -6
  108. package/ios/map/overlays/HeatMapView.swift +135 -32
  109. package/ios/map/overlays/HeatMapViewModule.swift +20 -8
  110. package/ios/map/overlays/MarkerView.swift +613 -130
  111. package/ios/map/overlays/MarkerViewModule.swift +38 -18
  112. package/ios/map/overlays/MultiPointView.swift +168 -10
  113. package/ios/map/overlays/MultiPointViewModule.swift +27 -5
  114. package/ios/map/overlays/PolygonView.swift +62 -23
  115. package/ios/map/overlays/PolygonViewModule.swift +18 -12
  116. package/ios/map/overlays/PolylineView.swift +21 -13
  117. package/ios/map/overlays/PolylineViewModule.swift +18 -12
  118. package/ios/map/utils/ClusterNative.h +96 -0
  119. package/ios/map/utils/ClusterNative.mm +377 -0
  120. package/ios/map/utils/ColorParser.swift +12 -1
  121. package/ios/map/utils/CppBridging.mm +13 -0
  122. package/ios/map/utils/GeometryUtils.swift +34 -0
  123. package/ios/map/utils/LatLngParser.swift +87 -0
  124. package/ios/map/utils/PermissionManager.swift +135 -6
  125. package/package.json +1 -1
  126. package/build/map/ExpoGaodeMap.types.d.ts +0 -41
  127. package/build/map/ExpoGaodeMap.types.js +0 -24
  128. package/build/map/utils/EventManager.d.ts +0 -10
  129. package/build/map/utils/EventManager.js +0 -26
  130. package/build/map/utils/ModuleLoader.d.ts +0 -73
  131. package/build/map/utils/ModuleLoader.js +0 -112
@@ -4,104 +4,126 @@ import AMapNaviKit
4
4
  /**
5
5
  * 高德地图视图 Module
6
6
  */
7
- public class NaviMapViewModule: Module {
7
+ public class ExpoGaodeMapViewModule: Module {
8
8
  public func definition() -> ModuleDefinition {
9
- Name("NaviMapView")
9
+ Name("ExpoGaodeMapView")
10
10
 
11
- View(NaviMapView.self) {
11
+ View(ExpoGaodeMapView.self) {
12
12
  Events("onMapPress", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
13
13
 
14
- Prop("mapType") { (view: NaviMapView, type: Int) in
14
+ Prop("mapType") { (view: ExpoGaodeMapView, type: Int) in
15
15
  view.mapType = type
16
16
  }
17
17
 
18
- Prop("initialCameraPosition") { (view: NaviMapView, position: [String: Any]?) in
18
+ Prop("initialCameraPosition") { (view: ExpoGaodeMapView, position: [String: Any]?) in
19
19
  view.initialCameraPosition = position
20
20
  }
21
21
 
22
- Prop("maxZoom") { (view: NaviMapView, zoom: Double) in
22
+ Prop("maxZoom") { (view: ExpoGaodeMapView, zoom: Double) in
23
23
  view.setMaxZoom(zoom)
24
24
  }
25
25
 
26
- Prop("minZoom") { (view: NaviMapView, zoom: Double) in
26
+ Prop("minZoom") { (view: ExpoGaodeMapView, zoom: Double) in
27
27
  view.setMinZoom(zoom)
28
28
  }
29
29
 
30
- Prop("zoomControlsEnabled") { (view: NaviMapView, show: Bool) in
30
+ Prop("zoomControlsEnabled") { (view: ExpoGaodeMapView, show: Bool) in
31
31
  view.showsZoomControls = show
32
32
  }
33
33
 
34
- Prop("compassEnabled") { (view: NaviMapView, show: Bool) in
34
+ Prop("compassEnabled") { (view: ExpoGaodeMapView, show: Bool) in
35
35
  view.showsCompass = show
36
36
  }
37
37
 
38
- Prop("scaleControlsEnabled") { (view: NaviMapView, show: Bool) in
38
+ Prop("scaleControlsEnabled") { (view: ExpoGaodeMapView, show: Bool) in
39
39
  view.showsScale = show
40
40
  }
41
41
 
42
- Prop("zoomGesturesEnabled") { (view: NaviMapView, enabled: Bool) in
42
+ Prop("zoomGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
43
43
  view.isZoomEnabled = enabled
44
44
  }
45
45
 
46
- Prop("scrollGesturesEnabled") { (view: NaviMapView, enabled: Bool) in
46
+ Prop("scrollGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
47
47
  view.isScrollEnabled = enabled
48
48
  }
49
49
 
50
- Prop("rotateGesturesEnabled") { (view: NaviMapView, enabled: Bool) in
50
+ Prop("rotateGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
51
51
  view.isRotateEnabled = enabled
52
52
  }
53
53
 
54
- Prop("tiltGesturesEnabled") { (view: NaviMapView, enabled: Bool) in
54
+ Prop("tiltGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
55
55
  view.isTiltEnabled = enabled
56
56
  }
57
57
 
58
- Prop("myLocationEnabled") { (view: NaviMapView, show: Bool) in
58
+ Prop("myLocationEnabled") { (view: ExpoGaodeMapView, show: Bool) in
59
59
  view.setShowsUserLocation(show)
60
60
  }
61
61
 
62
- Prop("followUserLocation") { (view: NaviMapView, follow: Bool) in
62
+ Prop("followUserLocation") { (view: ExpoGaodeMapView, follow: Bool) in
63
63
  view.setFollowUserLocation(follow)
64
64
  }
65
65
 
66
- Prop("userLocationRepresentation") { (view: NaviMapView, config: [String: Any]) in
66
+ Prop("userLocationRepresentation") { (view: ExpoGaodeMapView, config: [String: Any]) in
67
67
  view.setUserLocationRepresentation(config)
68
68
  }
69
69
 
70
- Prop("trafficEnabled") { (view: NaviMapView, show: Bool) in
70
+ Prop("trafficEnabled") { (view: ExpoGaodeMapView, show: Bool) in
71
71
  view.setShowsTraffic(show)
72
72
  }
73
73
 
74
- Prop("buildingsEnabled") { (view: NaviMapView, show: Bool) in
74
+ Prop("buildingsEnabled") { (view: ExpoGaodeMapView, show: Bool) in
75
75
  view.setShowsBuildings(show)
76
76
  }
77
77
 
78
- Prop("indoorViewEnabled") { (view: NaviMapView, show: Bool) in
78
+ Prop("indoorViewEnabled") { (view: ExpoGaodeMapView, show: Bool) in
79
79
  view.setShowsIndoorMap(show)
80
80
  }
81
81
 
82
- OnViewDidUpdateProps { (view: NaviMapView) in
82
+ Prop("customMapStyle") { (view: ExpoGaodeMapView, styleData: [String: Any]?) in
83
+ if let styleData = styleData {
84
+ view.setCustomMapStyle(styleData)
85
+ }
86
+ }
87
+
88
+ Prop("worldMapSwitchEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
89
+ view.enableWorldMapSwitch = enabled
90
+ }
91
+
92
+ OnViewDidUpdateProps { (view: ExpoGaodeMapView) in
83
93
  view.applyProps()
84
94
  }
85
95
 
86
- AsyncFunction("moveCamera") { (view: NaviMapView, position: [String: Any], duration: Int) in
96
+ AsyncFunction("moveCamera") { (view: ExpoGaodeMapView, position: [String: Any], duration: Int) in
87
97
  view.moveCamera(position: position, duration: duration)
88
98
  }
89
99
 
90
- AsyncFunction("getLatLng") { (view: NaviMapView, point: [String: Double]) -> [String: Double] in
100
+ AsyncFunction("getLatLng") { (view: ExpoGaodeMapView, point: [String: Double]) -> [String: Double] in
91
101
  return view.getLatLng(point: point)
92
102
  }
93
103
 
94
- AsyncFunction("setCenter") { (view: NaviMapView, center: [String: Double], animated: Bool) in
104
+ AsyncFunction("setCenter") { (view: ExpoGaodeMapView, center: [String: Double], animated: Bool) in
95
105
  view.setCenter(center: center, animated: animated)
96
106
  }
97
107
 
98
- AsyncFunction("setZoom") { (view: NaviMapView, zoom: Double, animated: Bool) in
108
+ AsyncFunction("setZoom") { (view: ExpoGaodeMapView, zoom: Double, animated: Bool) in
99
109
  view.setZoom(zoom: zoom, animated: animated)
100
110
  }
101
111
 
102
- AsyncFunction("getCameraPosition") { (view: NaviMapView) -> [String: Any] in
112
+ AsyncFunction("getCameraPosition") { (view: ExpoGaodeMapView) -> [String: Any] in
103
113
  return view.getCameraPosition()
104
114
  }
115
+
116
+ AsyncFunction("takeSnapshot") { (view: ExpoGaodeMapView, promise: Promise) in
117
+ DispatchQueue.main.async {
118
+ view.takeSnapshot { path, error in
119
+ if let path = path {
120
+ promise.resolve(path)
121
+ } else {
122
+ promise.reject("SNAPSHOT_FAILED", error?.localizedDescription ?? "Failed to take snapshot")
123
+ }
124
+ }
125
+ }
126
+ }
105
127
 
106
128
  }
107
129
  }
@@ -0,0 +1,348 @@
1
+ /**
2
+ * 地图预加载管理器 (iOS)
3
+ * 在后台预先初始化地图实例,提升首次显示速度
4
+ */
5
+
6
+ import Foundation
7
+ import AMapFoundationKit
8
+ import AMapNaviKit
9
+
10
+ /// 预加载实例数据
11
+ struct PreloadedMapInstance {
12
+ let mapView: MAMapView
13
+ let timestamp: Date
14
+ }
15
+
16
+ /// 地图预加载管理器
17
+ ///
18
+ class MapPreloadManager {
19
+ static let shared = MapPreloadManager()
20
+
21
+ // 动态池大小配置(根据内存自适应)
22
+ private let maxPoolSizeHighMemory = 3 // 高内存设备:>= 500MB
23
+ private let maxPoolSizeMediumMemory = 2 // 中等内存:300-500MB
24
+ private let maxPoolSizeLowMemory = 1 // 低内存设备:150-300MB
25
+ private let minMemoryThresholdMB: UInt64 = 150 // 最低内存要求
26
+
27
+ // 动态 TTL 配置(根据内存压力自适应)
28
+ private let ttlNormal: TimeInterval = 5 * 60 // 正常:5分钟
29
+ private let ttlMemoryPressure: TimeInterval = 2 * 60 // 内存压力:2分钟
30
+ private let ttlLowMemory: TimeInterval = 1 * 60 // 低内存:1分钟
31
+
32
+ private var preloadedMapInstances: [PreloadedMapInstance] = []
33
+ private var isPreloading = false
34
+ private let preloadQueue = DispatchQueue(label: "com.expo.gaodemap.preload", qos: .background)
35
+ private var preloadGroup = DispatchGroup()
36
+
37
+ // 动态配置
38
+ private var currentMaxPoolSize = 2
39
+ private var currentTTL: TimeInterval = 5 * 60
40
+ private var memoryPressureLevel = 0 // 0=正常, 1=压力, 2=严重
41
+
42
+ // 定期清理任务
43
+ private var cleanupTimer: Timer?
44
+
45
+ private init() {
46
+
47
+
48
+ // 注册内存警告监听
49
+ NotificationCenter.default.addObserver(
50
+ self,
51
+ selector: #selector(handleMemoryWarning),
52
+ name: UIApplication.didReceiveMemoryWarningNotification,
53
+ object: nil
54
+ )
55
+
56
+ // 启动定期清理任务
57
+ startPeriodicCleanup()
58
+ }
59
+
60
+ /// 启动定期清理过期实例的任务
61
+ private func startPeriodicCleanup() {
62
+ cleanupTimer?.invalidate()
63
+ cleanupTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [weak self] _ in
64
+ self?.cleanupExpiredInstances()
65
+ }
66
+ }
67
+
68
+ /// 清理过期的预加载实例(使用动态 TTL)
69
+ private func cleanupExpiredInstances() {
70
+ let now = Date()
71
+ var expiredCount = 0
72
+
73
+ preloadedMapInstances.removeAll { instance in
74
+ let isExpired = now.timeIntervalSince(instance.timestamp) > currentTTL
75
+ if isExpired {
76
+ expiredCount += 1
77
+ }
78
+ return isExpired
79
+ }
80
+
81
+ if expiredCount > 0 {
82
+ }
83
+ }
84
+
85
+ /// 处理内存警告
86
+ @objc private func handleMemoryWarning() {
87
+
88
+ clearPool()
89
+ }
90
+
91
+ /// 开始预加载地图实例(自适应版本)
92
+ /// - Parameter poolSize: 预加载的地图实例数量(会根据内存自适应调整)
93
+ func startPreload(poolSize: Int) {
94
+ guard !isPreloading else {
95
+
96
+ return
97
+ }
98
+
99
+ // 动态计算最优池大小
100
+ let adaptiveMaxPoolSize = calculateAdaptivePoolSize()
101
+ currentMaxPoolSize = adaptiveMaxPoolSize
102
+
103
+ // 动态调整 TTL
104
+ currentTTL = calculateAdaptiveTTL()
105
+
106
+ // 检查内存是否充足
107
+ guard hasEnoughMemory() else {
108
+
109
+ return
110
+ }
111
+
112
+ isPreloading = true
113
+ let targetSize = min(poolSize, adaptiveMaxPoolSize)
114
+
115
+ for _ in 0..<targetSize {
116
+ preloadGroup.enter() // 进入预加载队列
117
+
118
+ preloadQueue.async { [weak self] in
119
+ guard let self = self else { return }
120
+ autoreleasepool {
121
+ // 创建地图视图
122
+ let mapView = self.createPreloadedMapView()
123
+
124
+ // 将地图实例添加到池中(带时间戳)
125
+ DispatchQueue.main.async {
126
+ let instance = PreloadedMapInstance(mapView: mapView, timestamp: Date())
127
+ self.preloadedMapInstances.append(instance)
128
+ self.preloadGroup.leave()
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ // 等待所有实例加载完成
135
+ preloadGroup.notify(queue: DispatchQueue.main) {
136
+ self.isPreloading = false
137
+ }
138
+ }
139
+
140
+ /// 创建预加载的地图视图
141
+ /// - Returns: 预加载的地图视图实例
142
+ private func createPreloadedMapView() -> MAMapView {
143
+ var mapView: MAMapView!
144
+
145
+ // 检查是否已在主线程,避免死锁
146
+ if Thread.isMainThread {
147
+ mapView = MAMapView()
148
+ configureMapView(mapView)
149
+ } else {
150
+ // 确保在主线程中创建 MAMapView
151
+ DispatchQueue.main.sync {
152
+ mapView = MAMapView()
153
+ self.configureMapView(mapView)
154
+ }
155
+ }
156
+ return mapView
157
+ }
158
+
159
+ /// 配置地图视图的基础属性
160
+ /// - Parameter mapView: 需要配置的地图视图
161
+ private func configureMapView(_ mapView: MAMapView) {
162
+ // 基础配置
163
+ mapView.mapType = .standard
164
+ mapView.showsUserLocation = false
165
+ mapView.showsCompass = false
166
+ mapView.showsScale = false
167
+ mapView.isZoomEnabled = true
168
+ mapView.isScrollEnabled = true
169
+ mapView.isRotateEnabled = true
170
+
171
+ // 预加载中心区域(北京天安门)
172
+ let centerCoordinate = CLLocationCoordinate2D(latitude: 39.9042, longitude: 116.4074)
173
+ mapView.setCenter(centerCoordinate, animated: false)
174
+ mapView.setZoomLevel(12, animated: false)
175
+
176
+ // 设置一个最小的 frame 以触发地图渲染
177
+ mapView.frame = CGRect(x: 0, y: 0, width: 1, height: 1)
178
+
179
+ // 触发地图初始化
180
+ mapView.layoutIfNeeded()
181
+ }
182
+
183
+ /// 计算自适应池大小
184
+ /// 根据可用内存动态调整池大小
185
+ private func calculateAdaptivePoolSize() -> Int {
186
+ let availableMB = getAvailableMemoryMB()
187
+
188
+ switch availableMB {
189
+ case 500...:
190
+ return maxPoolSizeHighMemory
191
+ case 300..<500:
192
+ return maxPoolSizeMediumMemory
193
+ case 150..<300:
194
+ return maxPoolSizeLowMemory
195
+ default:
196
+ return 0
197
+ }
198
+ }
199
+
200
+ /// 计算自适应 TTL
201
+ /// 根据内存压力动态调整过期时间
202
+ private func calculateAdaptiveTTL() -> TimeInterval {
203
+ let availableMB = getAvailableMemoryMB()
204
+ let totalMB = getTotalMemoryMB()
205
+ let usagePercent = Int((Double(totalMB - availableMB) / Double(totalMB)) * 100)
206
+
207
+ switch usagePercent {
208
+ case 0..<60:
209
+ memoryPressureLevel = 0
210
+ return ttlNormal
211
+ case 60..<80:
212
+ memoryPressureLevel = 1
213
+ return ttlMemoryPressure
214
+ default:
215
+ memoryPressureLevel = 2
216
+ return ttlLowMemory
217
+ }
218
+ }
219
+
220
+ /// 获取可用内存(MB)
221
+ private func getAvailableMemoryMB() -> UInt64 {
222
+ var info = mach_task_basic_info()
223
+ var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
224
+
225
+ let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
226
+ $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
227
+ task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
228
+ }
229
+ }
230
+
231
+ if kerr == KERN_SUCCESS {
232
+ let usedMB = UInt64(info.resident_size) / 1024 / 1024
233
+ let totalMB = ProcessInfo.processInfo.physicalMemory / 1024 / 1024
234
+ return totalMB - usedMB
235
+ }
236
+ return 0
237
+ }
238
+
239
+ /// 获取总内存(MB)
240
+ private func getTotalMemoryMB() -> UInt64 {
241
+ return ProcessInfo.processInfo.physicalMemory / 1024 / 1024
242
+ }
243
+
244
+ /// 检查是否有足够的内存
245
+ private func hasEnoughMemory() -> Bool {
246
+ return getAvailableMemoryMB() > minMemoryThresholdMB
247
+ }
248
+
249
+
250
+ /// 清空预加载池
251
+ func clearPool() {
252
+ _ = preloadedMapInstances.count
253
+ preloadedMapInstances.removeAll()
254
+ }
255
+
256
+ /// 获取预加载状态(包含动态配置信息)
257
+ /// - Returns: 预加载状态信息
258
+ func getStatus() -> [String: Any] {
259
+ return [
260
+ "poolSize": preloadedMapInstances.count,
261
+ "isPreloading": isPreloading,
262
+ "maxPoolSize": currentMaxPoolSize,
263
+ "currentTTL": currentTTL,
264
+ "memoryPressureLevel": memoryPressureLevel,
265
+ "isAdaptive": true
266
+ ]
267
+ }
268
+
269
+ /// 获取性能统计信息(包含内存信息)
270
+ func getPerformanceMetrics() -> [String: Any] {
271
+ let availableMB = getAvailableMemoryMB()
272
+ let totalMB = getTotalMemoryMB()
273
+ let usagePercent = Int((Double(totalMB - availableMB) / Double(totalMB)) * 100)
274
+
275
+ return [
276
+ "currentMaxPoolSize": currentMaxPoolSize,
277
+ "currentTTL": currentTTL,
278
+ "memoryPressureLevel": memoryPressureLevel,
279
+ "availableMemoryMB": availableMB,
280
+ "totalMemoryMB": totalMB,
281
+ "memoryUsagePercent": usagePercent,
282
+ "poolSize": preloadedMapInstances.count
283
+ ]
284
+ }
285
+
286
+ /// 获取一个预加载的地图实例(使用动态 TTL)
287
+ /// - Returns: 预加载的地图视图,如果池为空则返回 null
288
+ func getPreloadedMapView() -> MAMapView? {
289
+ let now = Date()
290
+
291
+ // 检查并移除过期实例
292
+ while let instance = preloadedMapInstances.first {
293
+ if now.timeIntervalSince(instance.timestamp) > currentTTL {
294
+ preloadedMapInstances.removeFirst()
295
+ // 可以在这里做一些清理工作,比如清理 delegate
296
+ } else {
297
+ break
298
+ }
299
+ }
300
+
301
+ if !preloadedMapInstances.isEmpty {
302
+ let instance = preloadedMapInstances.removeFirst()
303
+
304
+ // 触发自动补充(延迟执行,避免影响当前页面渲染)
305
+ triggerRefill()
306
+
307
+ return instance.mapView
308
+ }
309
+
310
+ // 尝试触发补充,以便下次使用
311
+ triggerRefill()
312
+
313
+ return nil
314
+ }
315
+
316
+ /// 触发自动补充机制
317
+ private func triggerRefill() {
318
+ guard !isPreloading else { return }
319
+
320
+ // 延迟 5 秒后尝试补充,避免抢占当前 UI 资源
321
+ DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
322
+ guard let self = self else { return }
323
+
324
+ if !self.isPreloading && self.preloadedMapInstances.count < self.currentMaxPoolSize {
325
+
326
+ self.startPreload(poolSize: self.currentMaxPoolSize)
327
+ }
328
+ }
329
+ }
330
+
331
+ /// 检查是否有可用的预加载实例
332
+ /// - Returns: 是否有可用实例
333
+ func hasPreloadedMapView() -> Bool {
334
+ return !preloadedMapInstances.isEmpty
335
+ }
336
+
337
+ func cleanup() {
338
+ cleanupTimer?.invalidate()
339
+ cleanupTimer = nil
340
+ clearPool()
341
+ }
342
+
343
+ deinit {
344
+ cleanupTimer?.invalidate()
345
+ NotificationCenter.default.removeObserver(self)
346
+ }
347
+ }
348
+
@@ -0,0 +1,110 @@
1
+ #include "ClusterEngine.hpp"
2
+ #include "QuadTree.hpp"
3
+ #include <cmath>
4
+ #include <algorithm>
5
+
6
+ namespace gaodemap {
7
+
8
+ // Removed duplicate toRadians definition, using inline or shared utility in future
9
+ // For now, let's keep it static but rename to avoid conflict in unity build
10
+ static inline double cluster_toRadians(double degrees) {
11
+ return degrees * 0.017453292519943295;
12
+ }
13
+
14
+ static double haversineMeters(const ClusterPoint& a, const ClusterPoint& b) {
15
+ const double lat1 = cluster_toRadians(a.lat);
16
+ const double lat2 = cluster_toRadians(b.lat);
17
+ const double dLat = lat2 - lat1;
18
+ const double dLon = cluster_toRadians(b.lon - a.lon);
19
+
20
+ const double sinHalfLat = std::sin(dLat * 0.5);
21
+ const double sinHalfLon = std::sin(dLon * 0.5);
22
+
23
+ const double h = sinHalfLat * sinHalfLat + std::cos(lat1) * std::cos(lat2) * sinHalfLon * sinHalfLon;
24
+ const double c = 2.0 * std::atan2(std::sqrt(h), std::sqrt(1.0 - h));
25
+
26
+ return 6371000.0 * c;
27
+ }
28
+
29
+ std::vector<ClusterOutput> clusterPoints(const std::vector<ClusterPoint>& points, double radiusMeters) {
30
+ std::vector<ClusterOutput> clusters;
31
+
32
+ if (points.empty() || radiusMeters <= 0.0) {
33
+ return clusters;
34
+ }
35
+
36
+ // 1. Build QuadTree
37
+ // Determine bounds
38
+ double minLat = 90.0, maxLat = -90.0, minLon = 180.0, maxLon = -180.0;
39
+ int maxIndex = -1;
40
+
41
+ for (const auto& p : points) {
42
+ if (p.lat < minLat) minLat = p.lat;
43
+ if (p.lat > maxLat) maxLat = p.lat;
44
+ if (p.lon < minLon) minLon = p.lon;
45
+ if (p.lon > maxLon) maxLon = p.lon;
46
+ if (p.index > maxIndex) maxIndex = p.index;
47
+ }
48
+
49
+ // Add some buffer
50
+ BoundingBox worldBounds{minLat - 1.0, minLon - 1.0, maxLat + 1.0, maxLon + 1.0};
51
+ QuadTree tree(worldBounds);
52
+
53
+ for (const auto& p : points) {
54
+ tree.insert(p);
55
+ }
56
+
57
+ // 2. Cluster
58
+ // Use maxIndex to size the visited array correctly.
59
+ // If points is empty, maxIndex is -1, but we checked empty above.
60
+ // Ensure vector size is at least maxIndex + 1
61
+ std::vector<bool> globalVisited((maxIndex >= 0 ? maxIndex + 1 : 0), false);
62
+
63
+ // Approximate degrees for radius (very rough, improves performance)
64
+ // 1 degree lat ~= 111km.
65
+ double latDegree = radiusMeters / 111000.0;
66
+
67
+ for (const auto& p : points) {
68
+ if (p.index < 0 || p.index >= globalVisited.size()) continue; // Safety check
69
+ if (globalVisited[p.index]) continue;
70
+
71
+ ClusterOutput cluster;
72
+ cluster.centerIndex = p.index;
73
+ cluster.indices.push_back(p.index);
74
+ globalVisited[p.index] = true;
75
+
76
+ // Query QuadTree
77
+ // Adjust lonDegree based on current latitude
78
+ // cos can be 0 at poles, handle it
79
+ double cosLat = std::cos(cluster_toRadians(p.lat));
80
+ double lonDegree;
81
+ if (std::abs(cosLat) < 0.00001) {
82
+ lonDegree = 360.0; // At poles, everything is close in longitude
83
+ } else {
84
+ lonDegree = radiusMeters / (111000.0 * std::abs(cosLat));
85
+ }
86
+
87
+ // Bounding box for query
88
+ BoundingBox range{p.lat - latDegree, p.lon - lonDegree, p.lat + latDegree, p.lon + lonDegree};
89
+
90
+ std::vector<ClusterPoint> neighbors;
91
+ tree.query(range, neighbors);
92
+
93
+ for (const auto& neighbor : neighbors) {
94
+ if (neighbor.index < 0 || neighbor.index >= globalVisited.size()) continue;
95
+ if (globalVisited[neighbor.index]) continue;
96
+
97
+ // Precise check
98
+ if (haversineMeters(p, neighbor) <= radiusMeters) {
99
+ cluster.indices.push_back(neighbor.index);
100
+ globalVisited[neighbor.index] = true;
101
+ }
102
+ }
103
+
104
+ clusters.push_back(std::move(cluster));
105
+ }
106
+
107
+ return clusters;
108
+ }
109
+
110
+ }
@@ -0,0 +1,20 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+
5
+ namespace gaodemap {
6
+
7
+ struct ClusterPoint {
8
+ double lat;
9
+ double lon;
10
+ int index;
11
+ };
12
+
13
+ struct ClusterOutput {
14
+ int centerIndex;
15
+ std::vector<int> indices;
16
+ };
17
+
18
+ std::vector<ClusterOutput> clusterPoints(const std::vector<ClusterPoint>& points, double radiusMeters);
19
+
20
+ }