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
|
@@ -2,6 +2,7 @@ import ExpoModulesCore
|
|
|
2
2
|
import AMapFoundationKit
|
|
3
3
|
import AMapLocationKit
|
|
4
4
|
import AMapNaviKit
|
|
5
|
+
import CoreLocation
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* 高德地图 Expo 模块
|
|
@@ -11,61 +12,77 @@ import AMapNaviKit
|
|
|
11
12
|
* - 定位功能管理
|
|
12
13
|
* - 权限管理
|
|
13
14
|
*/
|
|
14
|
-
public class
|
|
15
|
+
public class ExpoGaodeMapModule: Module {
|
|
15
16
|
/// 定位管理器实例
|
|
16
17
|
private var locationManager: LocationManager?
|
|
17
18
|
/// 权限管理器实例
|
|
18
19
|
private var permissionManager: PermissionManager?
|
|
19
|
-
/// 隐私协议是否已同意(模块级别跟踪)
|
|
20
|
-
private static var privacyAgreed: Bool = false
|
|
21
|
-
/// 隐私同意持久化 Key
|
|
22
|
-
private static let privacyDefaultsKey = "expo_gaode_map_privacy_agreed"
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
// MARK: - 私有辅助方法
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 尝试从 Info.plist 读取并设置 API Key
|
|
25
|
+
* @return 是否成功设置 API Key
|
|
26
|
+
*/
|
|
27
|
+
@discardableResult
|
|
28
|
+
private func trySetupApiKeyFromPlist() -> Bool {
|
|
29
|
+
if AMapServices.shared().apiKey == nil || AMapServices.shared().apiKey?.isEmpty == true {
|
|
30
|
+
if let plistKey = Bundle.main.infoDictionary?["AMapApiKey"] as? String, !plistKey.isEmpty {
|
|
31
|
+
AMapServices.shared().apiKey = plistKey
|
|
32
|
+
AMapServices.shared().enableHTTPS = true
|
|
33
|
+
print("✅ ExpoGaodeMap: 从 Info.plist 读取并设置 AMapApiKey 成功")
|
|
34
|
+
return true
|
|
38
35
|
} else {
|
|
36
|
+
print("⚠️ ExpoGaodeMap: Info.plist 未找到 AMapApiKey")
|
|
37
|
+
return false
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if hasAgreed {
|
|
55
|
-
// 同步到 SDK
|
|
56
|
-
MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.didAgree)
|
|
40
|
+
return true // 已经设置过了
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 尝试启动预加载(检查 API Key 后)
|
|
45
|
+
* @param delay 延迟时间(秒)
|
|
46
|
+
* @param poolSize 预加载池大小
|
|
47
|
+
*/
|
|
48
|
+
private func tryStartPreload(delay: Double = 1.0, poolSize: Int = 1) {
|
|
49
|
+
if let apiKey = AMapServices.shared().apiKey, !apiKey.isEmpty {
|
|
50
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
51
|
+
let status = MapPreloadManager.shared.getStatus()
|
|
52
|
+
let isPreloading = (status["isPreloading"] as? Bool) ?? false
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
AMapServices.shared().apiKey = plistKey
|
|
62
|
-
AMapServices.shared().enableHTTPS = true
|
|
63
|
-
} else {
|
|
64
|
-
}
|
|
54
|
+
if !MapPreloadManager.shared.hasPreloadedMapView() && !isPreloading {
|
|
55
|
+
|
|
56
|
+
MapPreloadManager.shared.startPreload(poolSize: poolSize)
|
|
65
57
|
}
|
|
66
|
-
} else {
|
|
67
|
-
MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.notAgree)
|
|
68
58
|
}
|
|
59
|
+
} else {
|
|
60
|
+
print("⚠️ ExpoGaodeMap: API Key 未设置,跳过自动预加载")
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public func definition() -> ModuleDefinition {
|
|
65
|
+
Name("ExpoGaodeMap")
|
|
66
|
+
|
|
67
|
+
// 模块初始化:尝试从本地缓存恢复隐私同意状态
|
|
68
|
+
OnCreate {
|
|
69
|
+
|
|
70
|
+
// 1. 告知 SDK:隐私协议已展示且包含隐私内容
|
|
71
|
+
MAMapView.updatePrivacyShow(
|
|
72
|
+
AMapPrivacyShowStatus.didShow,
|
|
73
|
+
privacyInfo: AMapPrivacyInfoStatus.didContain
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// 2. 告知 SDK:用户已同意隐私协议(关键)
|
|
77
|
+
MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.didAgree)
|
|
78
|
+
|
|
79
|
+
print("✅ ExpoGaodeMap: 原生侧已默认同意隐私协议")
|
|
80
|
+
|
|
81
|
+
// 3. 自动设置 API Key(Info.plist)
|
|
82
|
+
self.trySetupApiKeyFromPlist()
|
|
83
|
+
|
|
84
|
+
// 4. 自动启动预加载(可选)
|
|
85
|
+
self.tryStartPreload(delay: 2.0, poolSize: 1)
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
// ==================== SDK 初始化 ====================
|
|
@@ -75,22 +92,19 @@ public class NaviMapModule: Module {
|
|
|
75
92
|
* @param config 配置字典,包含 iosKey
|
|
76
93
|
*/
|
|
77
94
|
Function("initSDK") { (config: [String: String]) in
|
|
78
|
-
// 检查是否已同意隐私协议
|
|
79
|
-
if !NaviMapModule.privacyAgreed {
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
95
|
// 1) 优先使用传入的 iosKey;2) 否则回退读取 Info.plist 的 AMapApiKey
|
|
84
96
|
let providedKey = config["iosKey"]?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
85
97
|
var finalKey: String? = (providedKey?.isEmpty == false) ? providedKey : nil
|
|
86
98
|
if finalKey == nil {
|
|
87
99
|
if let plistKey = Bundle.main.infoDictionary?["AMapApiKey"] as? String, !plistKey.isEmpty {
|
|
88
100
|
finalKey = plistKey
|
|
101
|
+
print("ℹ️ ExpoGaodeMap: initSDK 未提供 iosKey,已从 Info.plist 使用 AMapApiKey")
|
|
89
102
|
}
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
guard let keyToUse = finalKey, !keyToUse.isEmpty else {
|
|
93
|
-
|
|
106
|
+
print("⚠️ ExpoGaodeMap: 未提供 iosKey 且 Info.plist 中也无 AMapApiKey,无法初始化 SDK")
|
|
107
|
+
throw Exception(name: "INIT_FAILED", description: "未提供 API Key")
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
// 设置 API Key(若与现有不同或尚未设置)
|
|
@@ -102,6 +116,15 @@ public class NaviMapModule: Module {
|
|
|
102
116
|
// 初始化定位管理器(触发原生侧懒加载)
|
|
103
117
|
self.getLocationManager()
|
|
104
118
|
|
|
119
|
+
print("✅ ExpoGaodeMap: 已设置 API Key 并完成初始化(来源:\(providedKey != nil ? "入参 iosKey" : "Info.plist" ))")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 设置是否加载世界向量地图
|
|
124
|
+
* @param enable 是否开启
|
|
125
|
+
*/
|
|
126
|
+
Function("setLoadWorldVectorMap") { (enable: Bool) in
|
|
127
|
+
MAMapView.loadWorldVectorMap = enable
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
/**
|
|
@@ -111,19 +134,26 @@ public class NaviMapModule: Module {
|
|
|
111
134
|
"iOS SDK Version"
|
|
112
135
|
}
|
|
113
136
|
|
|
137
|
+
/**
|
|
138
|
+
* 检查原生 SDK 是否已配置 API Key
|
|
139
|
+
*/
|
|
140
|
+
Function("isNativeSDKConfigured") { () -> Bool in
|
|
141
|
+
if let apiKey = AMapServices.shared().apiKey, !apiKey.isEmpty {
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
114
147
|
// ==================== 定位功能 ====================
|
|
115
148
|
|
|
116
149
|
/**
|
|
117
150
|
* 开始连续定位
|
|
118
151
|
*/
|
|
119
152
|
Function("start") {
|
|
120
|
-
// 检查隐私协议状态
|
|
121
|
-
if !NaviMapModule.privacyAgreed {
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
153
|
|
|
125
154
|
// 检查是否已设置 API Key
|
|
126
155
|
if AMapServices.shared().apiKey == nil || AMapServices.shared().apiKey?.isEmpty == true {
|
|
156
|
+
print("⚠️ ExpoGaodeMap: 未设置 API Key,无法开始定位")
|
|
127
157
|
return
|
|
128
158
|
}
|
|
129
159
|
|
|
@@ -149,11 +179,7 @@ public class NaviMapModule: Module {
|
|
|
149
179
|
* 返回位置信息和逆地理编码结果
|
|
150
180
|
*/
|
|
151
181
|
AsyncFunction("getCurrentLocation") { (promise: Promise) in
|
|
152
|
-
|
|
153
|
-
if !NaviMapModule.privacyAgreed {
|
|
154
|
-
promise.reject("PRIVACY_NOT_AGREED", "用户未同意隐私协议,无法获取位置")
|
|
155
|
-
return
|
|
156
|
-
}
|
|
182
|
+
|
|
157
183
|
|
|
158
184
|
// 检查是否已设置 API Key
|
|
159
185
|
if AMapServices.shared().apiKey == nil || AMapServices.shared().apiKey?.isEmpty == true {
|
|
@@ -161,7 +187,7 @@ public class NaviMapModule: Module {
|
|
|
161
187
|
return
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
let status =
|
|
190
|
+
let status = self.currentAuthorizationStatus()
|
|
165
191
|
|
|
166
192
|
if status == .authorizedAlways || status == .authorizedWhenInUse {
|
|
167
193
|
let manager = self.getLocationManager()
|
|
@@ -205,23 +231,352 @@ public class NaviMapModule: Module {
|
|
|
205
231
|
}
|
|
206
232
|
}
|
|
207
233
|
|
|
234
|
+
/**
|
|
235
|
+
* 解析高德折线字符串 (Polyline)
|
|
236
|
+
* @param polylineStr 折线字符串
|
|
237
|
+
* @return 坐标点数组
|
|
238
|
+
*/
|
|
239
|
+
Function("parsePolyline") { (polylineStr: String?) -> [[String: Double]] in
|
|
240
|
+
guard let polylineStr = polylineStr, !polylineStr.isEmpty else {
|
|
241
|
+
return []
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let flatCoords = ClusterNative.parsePolyline(polylineStr: polylineStr)
|
|
245
|
+
var result: [[String: Double]] = []
|
|
246
|
+
|
|
247
|
+
// flatCoords 是 [lat1, lon1, lat2, lon2, ...]
|
|
248
|
+
for i in stride(from: 0, to: flatCoords.count, by: 2) {
|
|
249
|
+
if i + 1 < flatCoords.count {
|
|
250
|
+
result.append([
|
|
251
|
+
"latitude": flatCoords[i].doubleValue,
|
|
252
|
+
"longitude": flatCoords[i+1].doubleValue
|
|
253
|
+
])
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return result
|
|
258
|
+
}
|
|
259
|
+
|
|
208
260
|
/**
|
|
209
261
|
* 坐标转换
|
|
210
|
-
*
|
|
262
|
+
* @param coordinate 原始坐标
|
|
263
|
+
* @param type 坐标类型 (0: GPS/Google, 1: MapBar, 2: Baidu, 3: MapABC/SoSo)
|
|
264
|
+
* @return 转换后的坐标
|
|
211
265
|
*/
|
|
212
|
-
AsyncFunction("coordinateConvert") { (coordinate: [String: Double]
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
266
|
+
AsyncFunction("coordinateConvert") { (coordinate: [String: Double]?, type: Int, promise: Promise) in
|
|
267
|
+
if let coord = LatLngParser.parseLatLng(coordinate) {
|
|
268
|
+
let coordDict = ["latitude": coord.latitude, "longitude": coord.longitude]
|
|
269
|
+
self.getLocationManager().coordinateConvert(coordDict, type: type, promise: promise)
|
|
270
|
+
} else {
|
|
271
|
+
promise.reject("INVALID_COORDINATE", "Invalid coordinate format")
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ==================== 几何计算 ====================
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 计算两点之间的距离
|
|
279
|
+
*/
|
|
280
|
+
Function("distanceBetweenCoordinates") { (p1: [String: Double]?, p2: [String: Double]?) -> Double in
|
|
281
|
+
guard let coord1 = LatLngParser.parseLatLng(p1),
|
|
282
|
+
let coord2 = LatLngParser.parseLatLng(p2) else {
|
|
283
|
+
return 0.0
|
|
284
|
+
}
|
|
285
|
+
return ClusterNative.calculateDistance(lat1: coord1.latitude, lon1: coord1.longitude, lat2: coord2.latitude, lon2: coord2.longitude)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 计算多边形面积
|
|
290
|
+
* @param points 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
291
|
+
*/
|
|
292
|
+
Function("calculatePolygonArea") { (points: [Any]?) -> Double in
|
|
293
|
+
let rings = LatLngParser.parseLatLngListList(points)
|
|
294
|
+
if rings.isEmpty { return 0.0 }
|
|
295
|
+
|
|
296
|
+
// 第一项是外轮廓
|
|
297
|
+
let outerCoords = rings[0]
|
|
298
|
+
var totalArea = ClusterNative.calculatePolygonArea(
|
|
299
|
+
latitudes: outerCoords.map { NSNumber(value: $0.latitude) },
|
|
300
|
+
longitudes: outerCoords.map { NSNumber(value: $0.longitude) }
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
// 后续项是内孔,需要减去面积
|
|
304
|
+
if rings.count > 1 {
|
|
305
|
+
for i in 1..<rings.count {
|
|
306
|
+
let ring = rings[i]
|
|
307
|
+
totalArea -= ClusterNative.calculatePolygonArea(
|
|
308
|
+
latitudes: ring.map { NSNumber(value: $0.latitude) },
|
|
309
|
+
longitudes: ring.map { NSNumber(value: $0.longitude) }
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return Swift.max(0.0, totalArea)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 判断点是否在多边形内
|
|
319
|
+
* @param point 待判断点
|
|
320
|
+
* @param polygon 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
321
|
+
*/
|
|
322
|
+
Function("isPointInPolygon") { (point: [String: Double]?, polygon: [Any]?) -> Bool in
|
|
323
|
+
guard let coord = LatLngParser.parseLatLng(point) else {
|
|
324
|
+
return false
|
|
217
325
|
}
|
|
218
326
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
]
|
|
224
|
-
|
|
327
|
+
let rings = LatLngParser.parseLatLngListList(polygon)
|
|
328
|
+
if rings.isEmpty { return false }
|
|
329
|
+
|
|
330
|
+
// 点必须在外轮廓内
|
|
331
|
+
let outerCoords = rings[0]
|
|
332
|
+
let inOuter = ClusterNative.isPointInPolygon(
|
|
333
|
+
pointLat: coord.latitude,
|
|
334
|
+
pointLon: coord.longitude,
|
|
335
|
+
latitudes: outerCoords.map { NSNumber(value: $0.latitude) },
|
|
336
|
+
longitudes: outerCoords.map { NSNumber(value: $0.longitude) }
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if !inOuter { return false }
|
|
340
|
+
|
|
341
|
+
// 点不能在任何内孔内
|
|
342
|
+
if rings.count > 1 {
|
|
343
|
+
for i in 1..<rings.count {
|
|
344
|
+
let ring = rings[i]
|
|
345
|
+
let inHole = ClusterNative.isPointInPolygon(
|
|
346
|
+
pointLat: coord.latitude,
|
|
347
|
+
pointLon: coord.longitude,
|
|
348
|
+
latitudes: ring.map { NSNumber(value: $0.latitude) },
|
|
349
|
+
longitudes: ring.map { NSNumber(value: $0.longitude) }
|
|
350
|
+
)
|
|
351
|
+
if inHole { return false }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return true
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* 判断点是否在圆内
|
|
360
|
+
*/
|
|
361
|
+
Function("isPointInCircle") { (point: [String: Double]?, center: [String: Double]?, radius: Double) -> Bool in
|
|
362
|
+
guard let coord = LatLngParser.parseLatLng(point),
|
|
363
|
+
let centerCoord = LatLngParser.parseLatLng(center) else {
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
return ClusterNative.isPointInCircle(pointLat: coord.latitude, pointLon: coord.longitude, centerLat: centerCoord.latitude, centerLon: centerCoord.longitude, radiusMeters: radius)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 计算矩形面积
|
|
371
|
+
*/
|
|
372
|
+
Function("calculateRectangleArea") { (southWest: [String: Double]?, northEast: [String: Double]?) -> Double in
|
|
373
|
+
guard let sw = LatLngParser.parseLatLng(southWest),
|
|
374
|
+
let ne = LatLngParser.parseLatLng(northEast) else {
|
|
375
|
+
return 0.0
|
|
376
|
+
}
|
|
377
|
+
return ClusterNative.calculateRectangleArea(swLat: sw.latitude, swLon: sw.longitude, neLat: ne.latitude, neLon: ne.longitude)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 计算路径上距离目标点最近的点
|
|
382
|
+
*/
|
|
383
|
+
Function("getNearestPointOnPath") { (path: [[String: Double]]?, target: [String: Double]?) -> [String: Any]? in
|
|
384
|
+
guard let targetCoord = LatLngParser.parseLatLng(target) else {
|
|
385
|
+
return nil
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let coords = LatLngParser.parseLatLngList(path)
|
|
389
|
+
if coords.isEmpty {
|
|
390
|
+
return nil
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let lats = coords.map { NSNumber(value: $0.latitude) }
|
|
394
|
+
let lons = coords.map { NSNumber(value: $0.longitude) }
|
|
395
|
+
|
|
396
|
+
return ClusterNative.getNearestPointOnPath(latitudes: lats, longitudes: lons, targetLat: targetCoord.latitude, targetLon: targetCoord.longitude) as? [String: Any]
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 计算多边形质心
|
|
401
|
+
* @param polygon 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
402
|
+
*/
|
|
403
|
+
Function("calculateCentroid") { (polygon: [Any]?) -> [String: Double]? in
|
|
404
|
+
let rings = LatLngParser.parseLatLngListList(polygon)
|
|
405
|
+
if rings.isEmpty { return nil }
|
|
406
|
+
|
|
407
|
+
if rings.count == 1 {
|
|
408
|
+
let coords = rings[0]
|
|
409
|
+
let lats = coords.map { NSNumber(value: $0.latitude) }
|
|
410
|
+
let lons = coords.map { NSNumber(value: $0.longitude) }
|
|
411
|
+
return ClusterNative.calculateCentroid(latitudes: lats, longitudes: lons) as? [String: Double]
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 带孔多边形的质心计算: Σ(Area_i * Centroid_i) / Σ(Area_i)
|
|
415
|
+
var totalArea = 0.0
|
|
416
|
+
var sumLat = 0.0
|
|
417
|
+
var sumLon = 0.0
|
|
418
|
+
|
|
419
|
+
for i in 0..<rings.count {
|
|
420
|
+
let coords = rings[i]
|
|
421
|
+
let lats = coords.map { NSNumber(value: $0.latitude) }
|
|
422
|
+
let lons = coords.map { NSNumber(value: $0.longitude) }
|
|
423
|
+
|
|
424
|
+
let area = ClusterNative.calculatePolygonArea(latitudes: lats, longitudes: lons)
|
|
425
|
+
if let centroid = ClusterNative.calculateCentroid(latitudes: lats, longitudes: lons) as? [String: Double],
|
|
426
|
+
let cLat = centroid["latitude"], let cLon = centroid["longitude"] {
|
|
427
|
+
|
|
428
|
+
// 第一项是外轮廓(正),后续是内孔(负)
|
|
429
|
+
let factor = (i == 0) ? 1.0 : -1.0
|
|
430
|
+
let signedArea = area * factor
|
|
431
|
+
|
|
432
|
+
totalArea += signedArea
|
|
433
|
+
sumLat += cLat * signedArea
|
|
434
|
+
sumLon += cLon * signedArea
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if abs(totalArea) > 1e-9 {
|
|
439
|
+
return [
|
|
440
|
+
"latitude": sumLat / totalArea,
|
|
441
|
+
"longitude": sumLon / totalArea
|
|
442
|
+
]
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return nil
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* 计算路径边界
|
|
450
|
+
* @param points 路径点集合
|
|
451
|
+
* @return 边界信息
|
|
452
|
+
*/
|
|
453
|
+
Function("calculatePathBounds") { (points: [Any]?) -> [String: Any]? in
|
|
454
|
+
let coords = LatLngParser.parseLatLngList(points)
|
|
455
|
+
if coords.isEmpty { return nil }
|
|
456
|
+
|
|
457
|
+
return ClusterNative.calculatePathBounds(
|
|
458
|
+
latitudes: coords.map { NSNumber(value: $0.latitude) },
|
|
459
|
+
longitudes: coords.map { NSNumber(value: $0.longitude) }
|
|
460
|
+
) as? [String: Any]
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* GeoHash 编码
|
|
465
|
+
*/
|
|
466
|
+
Function("encodeGeoHash") { (coordinate: [String: Double]?, precision: Int) -> String in
|
|
467
|
+
guard let coord = LatLngParser.parseLatLng(coordinate) else {
|
|
468
|
+
return ""
|
|
469
|
+
}
|
|
470
|
+
return ClusterNative.encodeGeoHash(lat: coord.latitude, lon: coord.longitude, precision: Int32(precision))
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* 轨迹抽稀 (RDP 算法)
|
|
475
|
+
*/
|
|
476
|
+
Function("simplifyPolyline") { (points: [[String: Double]]?, tolerance: Double) -> [[String: Double]] in
|
|
477
|
+
let coords = LatLngParser.parseLatLngList(points)
|
|
478
|
+
let simplified = GeometryUtils.simplifyPolyline(coords, tolerance: tolerance)
|
|
479
|
+
|
|
480
|
+
return simplified.map {
|
|
481
|
+
[
|
|
482
|
+
"latitude": $0.latitude,
|
|
483
|
+
"longitude": $0.longitude
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 计算路径总长度
|
|
490
|
+
*/
|
|
491
|
+
Function("calculatePathLength") { (points: [[String: Double]]?) -> Double in
|
|
492
|
+
let coords = LatLngParser.parseLatLngList(points)
|
|
493
|
+
let lats = coords.map { NSNumber(value: $0.latitude) }
|
|
494
|
+
let lons = coords.map { NSNumber(value: $0.longitude) }
|
|
495
|
+
return ClusterNative.calculatePathLength(latitudes: lats, longitudes: lons)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* 获取路径上指定距离的点
|
|
500
|
+
*/
|
|
501
|
+
Function("getPointAtDistance") { (points: [[String: Double]]?, distance: Double) -> [String: Any]? in
|
|
502
|
+
let coords = LatLngParser.parseLatLngList(points)
|
|
503
|
+
let lats = coords.map { NSNumber(value: $0.latitude) }
|
|
504
|
+
let lons = coords.map { NSNumber(value: $0.longitude) }
|
|
505
|
+
return ClusterNative.getPointAtDistance(latitudes: lats, longitudes: lons, distanceMeters: distance) as? [String: Any]
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// --- 瓦片与坐标转换 ---
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* 瓦片坐标转换:经纬度 -> 瓦片坐标
|
|
512
|
+
*/
|
|
513
|
+
Function("latLngToTile") { (coordinate: [String: Double]?, zoom: Int) -> [String: Any]? in
|
|
514
|
+
guard let coord = LatLngParser.parseLatLng(coordinate) else { return nil }
|
|
515
|
+
return ClusterNative.latLngToTile(lat: coord.latitude, lon: coord.longitude, zoom: Int32(zoom)) as? [String: Any]
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 瓦片坐标转换:瓦片坐标 -> 经纬度
|
|
520
|
+
*/
|
|
521
|
+
Function("tileToLatLng") { (tile: [String: Int]) -> [String: Double]? in
|
|
522
|
+
guard let x = tile["x"], let y = tile["y"], let z = tile["z"] else { return nil }
|
|
523
|
+
return ClusterNative.tileToLatLng(x: Int32(x), y: Int32(y), zoom: Int32(z)) as? [String: Double]
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* 像素坐标转换:经纬度 -> 像素坐标
|
|
528
|
+
*/
|
|
529
|
+
Function("latLngToPixel") { (coordinate: [String: Double]?, zoom: Int) -> [String: Any]? in
|
|
530
|
+
guard let coord = LatLngParser.parseLatLng(coordinate) else { return nil }
|
|
531
|
+
return ClusterNative.latLngToPixel(lat: coord.latitude, lon: coord.longitude, zoom: Int32(zoom)) as? [String: Any]
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 像素坐标转换:像素坐标 -> 经纬度
|
|
536
|
+
*/
|
|
537
|
+
Function("pixelToLatLng") { (pixel: [String: Double], zoom: Int) -> [String: Double]? in
|
|
538
|
+
guard let x = pixel["x"], let y = pixel["y"] else { return nil }
|
|
539
|
+
return ClusterNative.pixelToLatLng(x: x, y: y, zoom: Int32(zoom)) as? [String: Double]
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// --- 批量地理围栏与热力图 ---
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 批量地理围栏检测
|
|
546
|
+
*/
|
|
547
|
+
Function("findPointInPolygons") { (point: [String: Double]?, polygons: [Any]?) -> Int in
|
|
548
|
+
guard let coord = LatLngParser.parseLatLng(point) else { return -1 }
|
|
549
|
+
|
|
550
|
+
// 解析多边形集合,统一处理为 [[String: Any]] 格式供 ClusterNative 遍历
|
|
551
|
+
let rings = LatLngParser.parseLatLngListList(polygons)
|
|
552
|
+
if rings.isEmpty { return -1 }
|
|
553
|
+
|
|
554
|
+
let nsPolygons = rings.map { ring in
|
|
555
|
+
ring.map { ["latitude": $0.latitude, "longitude": $0.longitude] }
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return Int(ClusterNative.findPointInPolygons(pointLat: coord.latitude, pointLon: coord.longitude, polygons: nsPolygons))
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 生成网格聚合数据
|
|
563
|
+
*/
|
|
564
|
+
Function("generateHeatmapGrid") { (points: [[String: Any]]?, gridSizeMeters: Double) -> [[String: Any]] in
|
|
565
|
+
guard let points = points, !points.isEmpty else { return [] }
|
|
566
|
+
|
|
567
|
+
var lats: [NSNumber] = []
|
|
568
|
+
var lons: [NSNumber] = []
|
|
569
|
+
var weights: [NSNumber] = []
|
|
570
|
+
|
|
571
|
+
for p in points {
|
|
572
|
+
if let lat = p["latitude"] as? Double, let lon = p["longitude"] as? Double {
|
|
573
|
+
lats.append(NSNumber(value: lat))
|
|
574
|
+
lons.append(NSNumber(value: lon))
|
|
575
|
+
weights.append(NSNumber(value: (p["weight"] as? Double) ?? 1.0))
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return (ClusterNative.generateHeatmapGrid(latitudes: lats, longitudes: lons, weights: weights, gridSizeMeters: gridSizeMeters) as? [[String: Any]]) ?? []
|
|
225
580
|
}
|
|
226
581
|
|
|
227
582
|
// ==================== 定位配置 ====================
|
|
@@ -258,6 +613,11 @@ public class NaviMapModule: Module {
|
|
|
258
613
|
self.getLocationManager().setPausesLocationUpdatesAutomatically(pauses)
|
|
259
614
|
}
|
|
260
615
|
|
|
616
|
+
Property("isBackgroundLocationEnabled") { () -> Bool in
|
|
617
|
+
let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
|
|
618
|
+
return backgroundModes?.contains("location") == true
|
|
619
|
+
}
|
|
620
|
+
|
|
261
621
|
Function("setAllowsBackgroundLocationUpdates") { (allows: Bool) in
|
|
262
622
|
self.getLocationManager().setAllowsBackgroundLocationUpdates(allows)
|
|
263
623
|
}
|
|
@@ -339,7 +699,7 @@ public class NaviMapModule: Module {
|
|
|
339
699
|
* 检查位置权限状态
|
|
340
700
|
*/
|
|
341
701
|
AsyncFunction("checkLocationPermission") { (promise: Promise) in
|
|
342
|
-
let status =
|
|
702
|
+
let status = self.currentAuthorizationStatus()
|
|
343
703
|
let granted = status == .authorizedAlways || status == .authorizedWhenInUse
|
|
344
704
|
|
|
345
705
|
promise.resolve([
|
|
@@ -359,7 +719,7 @@ public class NaviMapModule: Module {
|
|
|
359
719
|
self.permissionManager?.requestPermission { granted, status in
|
|
360
720
|
// 无论结果如何,都延迟后再次检查最终状态
|
|
361
721
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
362
|
-
let finalStatus =
|
|
722
|
+
let finalStatus = self.currentAuthorizationStatus()
|
|
363
723
|
let finalGranted = finalStatus == .authorizedAlways || finalStatus == .authorizedWhenInUse
|
|
364
724
|
let finalStatusString = self.getAuthorizationStatusString(finalStatus)
|
|
365
725
|
|
|
@@ -371,21 +731,60 @@ public class NaviMapModule: Module {
|
|
|
371
731
|
}
|
|
372
732
|
}
|
|
373
733
|
|
|
734
|
+
/**
|
|
735
|
+
* 请求后台位置权限(iOS)
|
|
736
|
+
* 注意:必须在前台权限已授予后才能请求
|
|
737
|
+
*/
|
|
738
|
+
AsyncFunction("requestBackgroundLocationPermission") { (promise: Promise) in
|
|
739
|
+
let status = self.currentAuthorizationStatus()
|
|
740
|
+
|
|
741
|
+
// 检查前台权限是否已授予
|
|
742
|
+
if status != .authorizedWhenInUse && status != .authorizedAlways {
|
|
743
|
+
promise.reject("FOREGROUND_PERMISSION_REQUIRED", "必须先授予前台位置权限才能请求后台位置权限")
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// iOS 上后台权限通过 Info.plist 配置 + 系统设置
|
|
748
|
+
// 这里返回当前状态
|
|
749
|
+
let hasBackground = status == .authorizedAlways
|
|
750
|
+
|
|
751
|
+
promise.resolve([
|
|
752
|
+
"granted": hasBackground,
|
|
753
|
+
"backgroundLocation": hasBackground,
|
|
754
|
+
"status": self.getAuthorizationStatusString(status),
|
|
755
|
+
"message": hasBackground ? "已授予后台权限" : "需要在系统设置中手动授予'始终'权限"
|
|
756
|
+
])
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* 打开应用设置页面(引导用户手动授予权限)
|
|
762
|
+
*/
|
|
763
|
+
Function("openAppSettings") {
|
|
764
|
+
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
|
|
765
|
+
UIApplication.shared.open(settingsUrl)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
|
|
374
771
|
Events("onHeadingUpdate")
|
|
375
772
|
Events("onLocationUpdate")
|
|
376
773
|
|
|
377
774
|
OnDestroy {
|
|
378
775
|
self.locationManager?.destroy()
|
|
379
776
|
self.locationManager = nil
|
|
777
|
+
MapPreloadManager.shared.cleanup()
|
|
380
778
|
}
|
|
381
779
|
}
|
|
382
780
|
|
|
383
|
-
// MARK: -
|
|
781
|
+
// MARK: - 定位管理器
|
|
384
782
|
|
|
385
783
|
/**
|
|
386
784
|
* 获取或创建定位管理器实例
|
|
387
785
|
* 使用懒加载模式,并设置事件回调
|
|
388
786
|
*/
|
|
787
|
+
@discardableResult
|
|
389
788
|
private func getLocationManager() -> LocationManager {
|
|
390
789
|
if locationManager == nil {
|
|
391
790
|
locationManager = LocationManager()
|
|
@@ -399,6 +798,17 @@ public class NaviMapModule: Module {
|
|
|
399
798
|
return locationManager!
|
|
400
799
|
}
|
|
401
800
|
|
|
801
|
+
/**
|
|
802
|
+
* 获取当前的权限状态(兼容 iOS 14+)
|
|
803
|
+
*/
|
|
804
|
+
private func currentAuthorizationStatus() -> CLAuthorizationStatus {
|
|
805
|
+
if #available(iOS 14.0, *) {
|
|
806
|
+
return CLLocationManager().authorizationStatus
|
|
807
|
+
} else {
|
|
808
|
+
return CLLocationManager.authorizationStatus()
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
402
812
|
/**
|
|
403
813
|
* 将权限状态转换为字符串
|
|
404
814
|
*/
|