expo-gaode-map-navigation 1.1.5-next.1 → 1.1.5-next.3
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.
- package/android/build.gradle +10 -0
- package/android/src/main/cpp/CMakeLists.txt +24 -0
- package/android/src/main/cpp/cluster_jni.cpp +848 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +616 -92
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +493 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +230 -14
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +37 -27
- package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +494 -0
- package/android/src/main/java/expo/modules/gaodemap/map/companion/BitmapDescriptorCache.kt +30 -0
- package/android/src/main/java/expo/modules/gaodemap/map/companion/IconBitmapCache.kt +37 -0
- package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +76 -0
- package/android/src/main/java/expo/modules/gaodemap/map/modules/LocationManager.kt +15 -3
- package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +4 -59
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleView.kt +9 -12
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleViewModule.kt +5 -6
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +539 -66
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterViewModule.kt +17 -1
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +165 -33
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +15 -3
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +1249 -672
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +40 -17
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointView.kt +177 -22
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointViewModule.kt +11 -3
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonView.kt +57 -14
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonViewModule.kt +9 -5
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineView.kt +90 -63
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineViewModule.kt +7 -3
- package/android/src/main/java/expo/modules/gaodemap/map/services/LocationForegroundService.kt +3 -2
- package/android/src/main/java/expo/modules/gaodemap/map/utils/BitmapDescriptorCache.kt +20 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +13 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +20 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +515 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/LatLngParser.kt +91 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/PermissionHelper.kt +248 -0
- package/build/ExpoGaodeMapNaviView.d.ts +7 -7
- package/build/ExpoGaodeMapNaviView.js +8 -8
- package/build/ExpoGaodeMapNavigationModule.d.ts +2 -1
- package/build/index.d.ts +35 -33
- package/build/index.js +70 -106
- package/build/map/ExpoGaodeMapModule.d.ts +2 -201
- package/build/map/ExpoGaodeMapModule.js +584 -14
- package/build/map/ExpoGaodeMapOfflineModule.d.ts +139 -0
- package/build/map/ExpoGaodeMapOfflineModule.js +8 -0
- package/build/map/ExpoGaodeMapView.js +66 -58
- package/build/map/components/FoldableMapView.d.ts +38 -0
- package/build/map/components/FoldableMapView.js +209 -0
- package/build/map/components/MapContext.d.ts +12 -0
- package/build/map/components/MapContext.js +54 -0
- package/build/map/components/MapUI.d.ts +18 -0
- package/build/map/components/MapUI.js +29 -0
- package/build/map/components/overlays/Circle.js +34 -3
- package/build/map/components/overlays/Cluster.d.ts +3 -1
- package/build/map/components/overlays/Cluster.js +31 -2
- package/build/map/components/overlays/HeatMap.d.ts +3 -1
- package/build/map/components/overlays/HeatMap.js +33 -3
- package/build/map/components/overlays/Marker.d.ts +1 -1
- package/build/map/components/overlays/Marker.js +37 -32
- package/build/map/components/overlays/MultiPoint.js +1 -1
- package/build/map/components/overlays/Polygon.js +30 -3
- package/build/map/components/overlays/Polyline.js +36 -3
- package/build/map/index.d.ts +25 -5
- package/build/map/index.js +59 -18
- package/build/map/types/common.types.d.ts +40 -0
- package/build/map/types/common.types.js +0 -4
- package/build/map/types/index.d.ts +3 -2
- package/build/map/types/map-view.types.d.ts +108 -3
- package/build/map/types/native-module.types.d.ts +363 -0
- package/build/map/types/native-module.types.js +5 -0
- package/build/map/types/offline.types.d.ts +132 -0
- package/build/map/types/offline.types.js +5 -0
- package/build/map/types/overlays.types.d.ts +137 -24
- package/build/map/utils/ErrorHandler.d.ts +110 -0
- package/build/map/utils/ErrorHandler.js +421 -0
- package/build/map/utils/GeoUtils.d.ts +20 -0
- package/build/map/utils/GeoUtils.js +76 -0
- package/build/map/utils/OfflineMapManager.d.ts +148 -0
- package/build/map/utils/OfflineMapManager.js +217 -0
- package/build/map/utils/PermissionUtils.d.ts +91 -0
- package/build/map/utils/PermissionUtils.js +255 -0
- package/build/map/utils/PlatformDetector.d.ts +102 -0
- package/build/map/utils/PlatformDetector.js +186 -0
- package/build/types/index.d.ts +1 -0
- package/build/types/index.js +1 -0
- package/build/types/native-module.types.d.ts +69 -0
- package/build/types/native-module.types.js +2 -0
- package/build/types/naviview.types.d.ts +1 -1
- package/expo-module.config.json +12 -10
- package/ios/ExpoGaodeMapNavigation.podspec +9 -0
- package/ios/map/ExpoGaodeMapModule.swift +485 -75
- package/ios/map/ExpoGaodeMapOfflineModule.swift +479 -0
- package/ios/map/ExpoGaodeMapView.swift +611 -62
- package/ios/map/ExpoGaodeMapViewModule.swift +48 -26
- package/ios/map/MapPreloadManager.swift +348 -0
- package/ios/map/cpp/ClusterEngine.cpp +110 -0
- package/ios/map/cpp/ClusterEngine.hpp +20 -0
- package/ios/map/cpp/ColorParser.cpp +135 -0
- package/ios/map/cpp/ColorParser.hpp +14 -0
- package/ios/map/cpp/GeometryEngine.cpp +574 -0
- package/ios/map/cpp/GeometryEngine.hpp +159 -0
- package/ios/map/cpp/QuadTree.cpp +92 -0
- package/ios/map/cpp/QuadTree.hpp +42 -0
- package/ios/map/cpp/README.md +55 -0
- package/ios/map/cpp/tests/benchmark_js.js +41 -0
- package/ios/map/cpp/tests/run.sh +17 -0
- package/ios/map/cpp/tests/test_main.cpp +276 -0
- package/ios/map/managers/UIManager.swift +72 -1
- package/ios/map/modules/LocationManager.swift +114 -165
- package/ios/map/overlays/CircleView.swift +16 -32
- package/ios/map/overlays/CircleViewModule.swift +12 -12
- package/ios/map/overlays/ClusterAnnotation.swift +32 -0
- package/ios/map/overlays/ClusterView.swift +331 -45
- package/ios/map/overlays/ClusterViewModule.swift +20 -6
- package/ios/map/overlays/HeatMapView.swift +135 -32
- package/ios/map/overlays/HeatMapViewModule.swift +20 -8
- package/ios/map/overlays/MarkerView.swift +613 -130
- package/ios/map/overlays/MarkerViewModule.swift +38 -18
- package/ios/map/overlays/MultiPointView.swift +168 -10
- package/ios/map/overlays/MultiPointViewModule.swift +27 -5
- package/ios/map/overlays/PolygonView.swift +62 -23
- package/ios/map/overlays/PolygonViewModule.swift +18 -12
- package/ios/map/overlays/PolylineView.swift +21 -13
- package/ios/map/overlays/PolylineViewModule.swift +18 -12
- package/ios/map/utils/ClusterNative.h +96 -0
- package/ios/map/utils/ClusterNative.mm +377 -0
- package/ios/map/utils/ColorParser.swift +12 -1
- package/ios/map/utils/CppBridging.mm +13 -0
- package/ios/map/utils/GeometryUtils.swift +34 -0
- package/ios/map/utils/LatLngParser.swift +87 -0
- package/ios/map/utils/PermissionManager.swift +135 -6
- package/package.json +1 -1
- package/build/map/ExpoGaodeMap.types.d.ts +0 -41
- package/build/map/ExpoGaodeMap.types.js +0 -24
- package/build/map/utils/EventManager.d.ts +0 -10
- package/build/map/utils/EventManager.js +0 -26
- package/build/map/utils/ModuleLoader.d.ts +0 -73
- package/build/map/utils/ModuleLoader.js +0 -112
|
@@ -4,104 +4,126 @@ import AMapNaviKit
|
|
|
4
4
|
/**
|
|
5
5
|
* 高德地图视图 Module
|
|
6
6
|
*/
|
|
7
|
-
public class
|
|
7
|
+
public class ExpoGaodeMapViewModule: Module {
|
|
8
8
|
public func definition() -> ModuleDefinition {
|
|
9
|
-
Name("
|
|
9
|
+
Name("ExpoGaodeMapView")
|
|
10
10
|
|
|
11
|
-
View(
|
|
11
|
+
View(ExpoGaodeMapView.self) {
|
|
12
12
|
Events("onMapPress", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
|
|
13
13
|
|
|
14
|
-
Prop("mapType") { (view:
|
|
14
|
+
Prop("mapType") { (view: ExpoGaodeMapView, type: Int) in
|
|
15
15
|
view.mapType = type
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
Prop("initialCameraPosition") { (view:
|
|
18
|
+
Prop("initialCameraPosition") { (view: ExpoGaodeMapView, position: [String: Any]?) in
|
|
19
19
|
view.initialCameraPosition = position
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
Prop("maxZoom") { (view:
|
|
22
|
+
Prop("maxZoom") { (view: ExpoGaodeMapView, zoom: Double) in
|
|
23
23
|
view.setMaxZoom(zoom)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
Prop("minZoom") { (view:
|
|
26
|
+
Prop("minZoom") { (view: ExpoGaodeMapView, zoom: Double) in
|
|
27
27
|
view.setMinZoom(zoom)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
Prop("zoomControlsEnabled") { (view:
|
|
30
|
+
Prop("zoomControlsEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
31
31
|
view.showsZoomControls = show
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
Prop("compassEnabled") { (view:
|
|
34
|
+
Prop("compassEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
35
35
|
view.showsCompass = show
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
Prop("scaleControlsEnabled") { (view:
|
|
38
|
+
Prop("scaleControlsEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
39
39
|
view.showsScale = show
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
Prop("zoomGesturesEnabled") { (view:
|
|
42
|
+
Prop("zoomGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
|
|
43
43
|
view.isZoomEnabled = enabled
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
Prop("scrollGesturesEnabled") { (view:
|
|
46
|
+
Prop("scrollGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
|
|
47
47
|
view.isScrollEnabled = enabled
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
Prop("rotateGesturesEnabled") { (view:
|
|
50
|
+
Prop("rotateGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
|
|
51
51
|
view.isRotateEnabled = enabled
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
Prop("tiltGesturesEnabled") { (view:
|
|
54
|
+
Prop("tiltGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
|
|
55
55
|
view.isTiltEnabled = enabled
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
Prop("myLocationEnabled") { (view:
|
|
58
|
+
Prop("myLocationEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
59
59
|
view.setShowsUserLocation(show)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
Prop("followUserLocation") { (view:
|
|
62
|
+
Prop("followUserLocation") { (view: ExpoGaodeMapView, follow: Bool) in
|
|
63
63
|
view.setFollowUserLocation(follow)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
Prop("userLocationRepresentation") { (view:
|
|
66
|
+
Prop("userLocationRepresentation") { (view: ExpoGaodeMapView, config: [String: Any]) in
|
|
67
67
|
view.setUserLocationRepresentation(config)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
Prop("trafficEnabled") { (view:
|
|
70
|
+
Prop("trafficEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
71
71
|
view.setShowsTraffic(show)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
Prop("buildingsEnabled") { (view:
|
|
74
|
+
Prop("buildingsEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
75
75
|
view.setShowsBuildings(show)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
Prop("indoorViewEnabled") { (view:
|
|
78
|
+
Prop("indoorViewEnabled") { (view: ExpoGaodeMapView, show: Bool) in
|
|
79
79
|
view.setShowsIndoorMap(show)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
+
}
|