expo-gaode-map-navigation 1.1.5 → 1.1.6
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/README.md +213 -73
- 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 +10 -11
- 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 +586 -18
- 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/managers/UIManager.swift +72 -1
- package/ios/map/modules/LocationManager.swift +123 -166
- 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 +3 -2
- package/shared/cpp/ClusterEngine.cpp +110 -0
- package/shared/cpp/ClusterEngine.hpp +20 -0
- package/shared/cpp/ColorParser.cpp +135 -0
- package/shared/cpp/ColorParser.hpp +14 -0
- package/shared/cpp/GeometryEngine.cpp +574 -0
- package/shared/cpp/GeometryEngine.hpp +159 -0
- package/shared/cpp/QuadTree.cpp +92 -0
- package/shared/cpp/QuadTree.hpp +42 -0
- package/shared/cpp/README.md +55 -0
- package/shared/cpp/tests/benchmark_js.js +41 -0
- package/shared/cpp/tests/run.sh +17 -0
- package/shared/cpp/tests/test_main.cpp +276 -0
- 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
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
package expo.modules.gaodemap.map
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import android.os.Handler
|
|
6
|
-
import android.os.Looper
|
|
7
|
-
import android.util.Log
|
|
8
|
-
import androidx.core.app.ActivityCompat
|
|
9
|
-
import androidx.core.content.ContextCompat
|
|
3
|
+
import com.amap.api.maps.MapsInitializer
|
|
4
|
+
import com.amap.api.maps.model.LatLng
|
|
10
5
|
import expo.modules.kotlin.modules.Module
|
|
11
6
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
12
7
|
import expo.modules.gaodemap.map.modules.SDKInitializer
|
|
13
8
|
import expo.modules.gaodemap.map.modules.LocationManager
|
|
14
|
-
import expo.modules.
|
|
15
|
-
import
|
|
16
|
-
import
|
|
9
|
+
import expo.modules.gaodemap.map.utils.GeometryUtils
|
|
10
|
+
import kotlin.math.max
|
|
11
|
+
import kotlin.math.abs
|
|
12
|
+
import expo.modules.gaodemap.map.utils.LatLngParser
|
|
13
|
+
|
|
14
|
+
import expo.modules.gaodemap.map.utils.PermissionHelper
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* 高德地图 Expo 模块
|
|
@@ -25,36 +23,54 @@ import java.lang.ref.WeakReference
|
|
|
25
23
|
*/
|
|
26
24
|
class ExpoGaodeMapModule : Module() {
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
|
|
29
27
|
/** 定位管理器实例 */
|
|
30
28
|
private var locationManager: LocationManager? = null
|
|
31
29
|
|
|
32
30
|
override fun definition() = ModuleDefinition {
|
|
33
|
-
Name("
|
|
31
|
+
Name("ExpoGaodeMap")
|
|
34
32
|
|
|
35
33
|
// 在模块加载时尝试从本地缓存恢复隐私同意状态,避免每次启动都必须 JS 调用
|
|
36
34
|
try {
|
|
37
|
-
|
|
35
|
+
val context = appContext.reactContext!!
|
|
36
|
+
SDKInitializer.restorePrivacyState(context)
|
|
37
|
+
|
|
38
|
+
// 初始化预加载管理器(注册内存监听)
|
|
39
|
+
MapPreloadManager.initialize(context)
|
|
40
|
+
|
|
41
|
+
// 尝试从 AndroidManifest.xml 读取并设置 API Key
|
|
42
|
+
val apiKey = context.packageManager
|
|
43
|
+
.getApplicationInfo(context.packageName, android.content.pm.PackageManager.GET_META_DATA)
|
|
44
|
+
.metaData?.getString("com.amap.api.v2.apikey")
|
|
45
|
+
|
|
46
|
+
if (!apiKey.isNullOrEmpty()) {
|
|
47
|
+
try {
|
|
48
|
+
MapsInitializer.setApiKey(apiKey)
|
|
49
|
+
com.amap.api.location.AMapLocationClient.setApiKey(apiKey)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// 只有在 API Key 已设置的情况下才启动预加载
|
|
53
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
54
|
+
android.util.Log.i("ExpoGaodeMap", "🚀 自动启动地图预加载")
|
|
55
|
+
MapPreloadManager.startPreload(context, poolSize = 1)
|
|
56
|
+
}, 2000)
|
|
57
|
+
} catch (e: Exception) {
|
|
58
|
+
android.util.Log.w("ExpoGaodeMap", "设置 API Key 失败: ${e.message}")
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
android.util.Log.w("ExpoGaodeMap", "⚠️ AndroidManifest.xml 未找到 API Key,跳过自动预加载")
|
|
62
|
+
}
|
|
63
|
+
|
|
38
64
|
} catch (e: Exception) {
|
|
39
65
|
android.util.Log.w("ExpoGaodeMap", "恢复隐私状态时出现问题: ${e.message}")
|
|
40
66
|
}
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 更新隐私合规状态
|
|
46
|
-
* 必须在用户同意隐私协议后调用
|
|
47
|
-
* @param hasAgreed 用户是否已同意隐私协议
|
|
48
|
-
*/
|
|
49
|
-
Function("updatePrivacyCompliance") { hasAgreed: Boolean ->
|
|
50
|
-
SDKInitializer.updatePrivacyCompliance(appContext.reactContext!!, hasAgreed)
|
|
51
|
-
}
|
|
52
|
-
|
|
68
|
+
|
|
53
69
|
// ==================== SDK 初始化 ====================
|
|
54
70
|
|
|
55
71
|
/**
|
|
56
72
|
* 初始化 SDK(地图 + 定位)
|
|
57
|
-
* @
|
|
73
|
+
* @config 配置对象,包含 androidKey
|
|
58
74
|
*/
|
|
59
75
|
Function("initSDK") { config: Map<String, String> ->
|
|
60
76
|
val androidKey = config["androidKey"]
|
|
@@ -62,16 +78,27 @@ class ExpoGaodeMapModule : Module() {
|
|
|
62
78
|
try {
|
|
63
79
|
SDKInitializer.initSDK(appContext.reactContext!!, androidKey)
|
|
64
80
|
getLocationManager() // 初始化定位管理器
|
|
81
|
+
|
|
82
|
+
// 初始化成功后自动触发一次预加载
|
|
83
|
+
MapPreloadManager.startPreload(appContext.reactContext!!, poolSize = 1)
|
|
65
84
|
} catch (e: SecurityException) {
|
|
66
|
-
Log.e("ExpoGaodeMap", "隐私协议未同意: ${e.message}")
|
|
67
|
-
throw e
|
|
85
|
+
android.util.Log.e("ExpoGaodeMap", "隐私协议未同意: ${e.message}")
|
|
86
|
+
throw expo.modules.kotlin.exception.CodedException("PRIVACY_NOT_AGREED", e.message ?: "用户未同意隐私协议", e)
|
|
68
87
|
} catch (e: Exception) {
|
|
69
|
-
Log.e("ExpoGaodeMap", "SDK 初始化失败: ${e.message}")
|
|
70
|
-
throw e
|
|
88
|
+
android.util.Log.e("ExpoGaodeMap", "SDK 初始化失败: ${e.message}")
|
|
89
|
+
throw expo.modules.kotlin.exception.CodedException("INIT_FAILED", e.message ?: "SDK 初始化失败", e)
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
/**
|
|
95
|
+
* 设置是否加载世界向量地图
|
|
96
|
+
* @param enable 是否开启
|
|
97
|
+
*/
|
|
98
|
+
Function("setLoadWorldVectorMap") { enable: Boolean ->
|
|
99
|
+
MapsInitializer.loadWorldVectorMap(enable)
|
|
100
|
+
}
|
|
101
|
+
|
|
75
102
|
/**
|
|
76
103
|
* 获取 SDK 版本
|
|
77
104
|
* @return SDK 版本号
|
|
@@ -80,21 +107,31 @@ class ExpoGaodeMapModule : Module() {
|
|
|
80
107
|
SDKInitializer.getVersion()
|
|
81
108
|
}
|
|
82
109
|
|
|
110
|
+
/**
|
|
111
|
+
* 检查原生 SDK 是否已配置 API Key
|
|
112
|
+
*/
|
|
113
|
+
Function("isNativeSDKConfigured") {
|
|
114
|
+
try {
|
|
115
|
+
val context = appContext.reactContext!!
|
|
116
|
+
val apiKey = context.packageManager
|
|
117
|
+
.getApplicationInfo(context.packageName, android.content.pm.PackageManager.GET_META_DATA)
|
|
118
|
+
.metaData?.getString("com.amap.api.v2.apikey")
|
|
119
|
+
!apiKey.isNullOrEmpty()
|
|
120
|
+
} catch (_: Exception) {
|
|
121
|
+
false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
83
126
|
// ==================== 定位功能 ====================
|
|
84
127
|
|
|
85
128
|
/**
|
|
86
129
|
* 开始连续定位
|
|
87
130
|
*/
|
|
88
131
|
Function("start") {
|
|
89
|
-
// 检查隐私协议状态
|
|
90
|
-
if (!SDKInitializer.isPrivacyAgreed()) {
|
|
91
|
-
Log.w("ExpoGaodeMap", "用户未同意隐私协议,无法开始定位")
|
|
92
|
-
throw CodedException("用户未同意隐私协议,无法开始定位")
|
|
93
|
-
}
|
|
94
|
-
|
|
95
132
|
getLocationManager().start()
|
|
96
133
|
}
|
|
97
|
-
|
|
134
|
+
|
|
98
135
|
/**
|
|
99
136
|
* 停止定位
|
|
100
137
|
*/
|
|
@@ -106,7 +143,7 @@ class ExpoGaodeMapModule : Module() {
|
|
|
106
143
|
* 检查是否正在定位
|
|
107
144
|
* @return 是否正在定位
|
|
108
145
|
*/
|
|
109
|
-
AsyncFunction("isStarted") { promise: Promise ->
|
|
146
|
+
AsyncFunction("isStarted") { promise: expo.modules.kotlin.Promise ->
|
|
110
147
|
promise.resolve(getLocationManager().isStarted())
|
|
111
148
|
}
|
|
112
149
|
|
|
@@ -114,13 +151,7 @@ class ExpoGaodeMapModule : Module() {
|
|
|
114
151
|
* 获取当前位置(单次定位)
|
|
115
152
|
* @return 位置信息对象
|
|
116
153
|
*/
|
|
117
|
-
AsyncFunction("getCurrentLocation") { promise: Promise ->
|
|
118
|
-
// 检查隐私协议状态
|
|
119
|
-
if (!SDKInitializer.isPrivacyAgreed()) {
|
|
120
|
-
promise.reject("PRIVACY_NOT_AGREED", "用户未同意隐私协议,无法获取位置", null)
|
|
121
|
-
return@AsyncFunction
|
|
122
|
-
}
|
|
123
|
-
|
|
154
|
+
AsyncFunction("getCurrentLocation") { promise: expo.modules.kotlin.Promise ->
|
|
124
155
|
getLocationManager().getCurrentLocation(promise)
|
|
125
156
|
}
|
|
126
157
|
|
|
@@ -130,15 +161,416 @@ class ExpoGaodeMapModule : Module() {
|
|
|
130
161
|
* @param type 坐标类型
|
|
131
162
|
* @return 转换后的坐标
|
|
132
163
|
*/
|
|
133
|
-
AsyncFunction("coordinateConvert") { coordinate: Map<String,
|
|
134
|
-
|
|
164
|
+
AsyncFunction("coordinateConvert") { coordinate: Map<String, Any>?, type: Int, promise: expo.modules.kotlin.Promise ->
|
|
165
|
+
val latLng = LatLngParser.parseLatLng(coordinate)
|
|
166
|
+
if (latLng != null) {
|
|
167
|
+
val coordMap = mapOf("latitude" to latLng.latitude, "longitude" to latLng.longitude)
|
|
168
|
+
getLocationManager().coordinateConvert(coordMap, type, promise)
|
|
169
|
+
} else {
|
|
170
|
+
promise.reject("INVALID_COORDINATE", "Invalid coordinate format", null)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ==================== 几何计算 ====================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 计算两个坐标点之间的距离
|
|
178
|
+
* @param coordinate1 第一个坐标点
|
|
179
|
+
* @param coordinate2 第二个坐标点
|
|
180
|
+
* @returns 两点之间的距离(单位:米)
|
|
181
|
+
*/
|
|
182
|
+
Function("distanceBetweenCoordinates") { p1: Map<String, Any>?, p2: Map<String, Any>? ->
|
|
183
|
+
val coord1 = LatLngParser.parseLatLng(p1)
|
|
184
|
+
val coord2 = LatLngParser.parseLatLng(p2)
|
|
185
|
+
if (coord1 != null && coord2 != null) {
|
|
186
|
+
GeometryUtils.calculateDistance(coord1, coord2)
|
|
187
|
+
} else {
|
|
188
|
+
0.0
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 计算多边形面积
|
|
194
|
+
* @param points 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
195
|
+
* @return 面积(平方米)
|
|
196
|
+
*/
|
|
197
|
+
Function("calculatePolygonArea") { points: List<Any>? ->
|
|
198
|
+
val rings = LatLngParser.parseLatLngListList(points)
|
|
199
|
+
if (rings.isEmpty()) return@Function 0.0
|
|
200
|
+
|
|
201
|
+
// 第一项是外轮廓
|
|
202
|
+
var totalArea = GeometryUtils.calculatePolygonArea(rings[0])
|
|
203
|
+
|
|
204
|
+
// 后续项是内孔,需要减去面积
|
|
205
|
+
if (rings.size > 1) {
|
|
206
|
+
for (i in 1 until rings.size) {
|
|
207
|
+
totalArea -= GeometryUtils.calculatePolygonArea(rings[i])
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 确保面积不为负数
|
|
212
|
+
max(0.0, totalArea)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 判断点是否在多边形内
|
|
217
|
+
* @param point 待判断点
|
|
218
|
+
* @param polygon 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
219
|
+
* @return 是否在多边形内
|
|
220
|
+
*/
|
|
221
|
+
Function("isPointInPolygon") { point: Map<String, Any>?, polygon: List<Any>? ->
|
|
222
|
+
val pt = LatLngParser.parseLatLng(point) ?: return@Function false
|
|
223
|
+
val rings = LatLngParser.parseLatLngListList(polygon)
|
|
224
|
+
if (rings.isEmpty()) return@Function false
|
|
225
|
+
|
|
226
|
+
// 点必须在外轮廓内
|
|
227
|
+
val inOuter = GeometryUtils.isPointInPolygon(pt, rings[0])
|
|
228
|
+
if (!inOuter) return@Function false
|
|
229
|
+
|
|
230
|
+
// 点不能在任何内孔内
|
|
231
|
+
if (rings.size > 1) {
|
|
232
|
+
for (i in 1 until rings.size) {
|
|
233
|
+
if (GeometryUtils.isPointInPolygon(pt, rings[i])) {
|
|
234
|
+
return@Function false
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 判断点是否在圆内
|
|
244
|
+
* @param point 待判断点
|
|
245
|
+
* @param center 圆心坐标
|
|
246
|
+
* @param radius 圆半径(米)
|
|
247
|
+
* @return 是否在圆内
|
|
248
|
+
*/
|
|
249
|
+
Function("isPointInCircle") { point: Map<String, Any>?, center: Map<String, Any>?, radius: Double ->
|
|
250
|
+
val pt = LatLngParser.parseLatLng(point)
|
|
251
|
+
val cn = LatLngParser.parseLatLng(center)
|
|
252
|
+
if (pt != null && cn != null) {
|
|
253
|
+
GeometryUtils.isPointInCircle(pt, cn, radius)
|
|
254
|
+
} else {
|
|
255
|
+
false
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 计算矩形面积
|
|
261
|
+
* @param southWest 西南角
|
|
262
|
+
* @param northEast 东北角
|
|
263
|
+
* @return 面积(平方米)
|
|
264
|
+
*/
|
|
265
|
+
Function("calculateRectangleArea") { southWest: Map<String, Any>?, northEast: Map<String, Any>? ->
|
|
266
|
+
val sw = LatLngParser.parseLatLng(southWest)
|
|
267
|
+
val ne = LatLngParser.parseLatLng(northEast)
|
|
268
|
+
if (sw != null && ne != null) {
|
|
269
|
+
GeometryUtils.calculateRectangleArea(sw, ne)
|
|
270
|
+
} else {
|
|
271
|
+
0.0
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 获取路径上距离目标点最近的点
|
|
277
|
+
* @param path 路径点集合
|
|
278
|
+
* @param target 目标点
|
|
279
|
+
* @return 最近点结果
|
|
280
|
+
*/
|
|
281
|
+
Function("getNearestPointOnPath") { path: List<Any>?, target: Map<String, Any>? ->
|
|
282
|
+
val pathPoints = LatLngParser.parseLatLngList(path)
|
|
283
|
+
val targetPoint = LatLngParser.parseLatLng(target)
|
|
284
|
+
|
|
285
|
+
if (targetPoint != null && pathPoints.isNotEmpty()) {
|
|
286
|
+
val result = GeometryUtils.getNearestPointOnPath(pathPoints, targetPoint)
|
|
287
|
+
if (result != null) {
|
|
288
|
+
mapOf(
|
|
289
|
+
"latitude" to result.point.latitude,
|
|
290
|
+
"longitude" to result.point.longitude,
|
|
291
|
+
"index" to result.index,
|
|
292
|
+
"distanceMeters" to result.distanceMeters
|
|
293
|
+
)
|
|
294
|
+
} else {
|
|
295
|
+
null
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
null
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 计算多边形质心
|
|
304
|
+
* @param polygon 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
305
|
+
* @return 质心坐标
|
|
306
|
+
*/
|
|
307
|
+
Function("calculateCentroid") { polygon: List<Any>? ->
|
|
308
|
+
val rings = LatLngParser.parseLatLngListList(polygon)
|
|
309
|
+
if (rings.isEmpty()) return@Function null
|
|
310
|
+
|
|
311
|
+
if (rings.size == 1) {
|
|
312
|
+
val result = GeometryUtils.calculateCentroid(rings[0])
|
|
313
|
+
return@Function result?.let {
|
|
314
|
+
mapOf(
|
|
315
|
+
"latitude" to it.latitude,
|
|
316
|
+
"longitude" to it.longitude
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 带孔多边形的质心计算: Σ(Area_i * Centroid_i) / Σ(Area_i)
|
|
322
|
+
// 注意: 这里的 Area 是带符号的,或者我们手动减去孔的贡献
|
|
323
|
+
var totalArea = 0.0
|
|
324
|
+
var sumLat = 0.0
|
|
325
|
+
var sumLon = 0.0
|
|
326
|
+
|
|
327
|
+
for (i in rings.indices) {
|
|
328
|
+
val ring = rings[i]
|
|
329
|
+
val area = GeometryUtils.calculatePolygonArea(ring)
|
|
330
|
+
val centroid = GeometryUtils.calculateCentroid(ring)
|
|
331
|
+
|
|
332
|
+
if (centroid != null) {
|
|
333
|
+
// 第一项是外轮廓(正),后续是内孔(负)
|
|
334
|
+
val factor = if (i == 0) 1.0 else -1.0
|
|
335
|
+
val signedArea = area * factor
|
|
336
|
+
|
|
337
|
+
totalArea += signedArea
|
|
338
|
+
sumLat += centroid.latitude * signedArea
|
|
339
|
+
sumLon += centroid.longitude * signedArea
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (abs(totalArea) > 1e-9) {
|
|
344
|
+
mapOf(
|
|
345
|
+
"latitude" to sumLat / totalArea,
|
|
346
|
+
"longitude" to sumLon / totalArea
|
|
347
|
+
)
|
|
348
|
+
} else {
|
|
349
|
+
null
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 计算路径边界
|
|
355
|
+
* @param pointsList 路径点集合
|
|
356
|
+
* @return 边界信息
|
|
357
|
+
*/
|
|
358
|
+
Function("calculatePathBounds") { pointsList: List<Any>? ->
|
|
359
|
+
val points = LatLngParser.parseLatLngList(pointsList)
|
|
360
|
+
if (points.isEmpty()) return@Function null
|
|
361
|
+
|
|
362
|
+
val result = GeometryUtils.calculatePathBounds(points)
|
|
363
|
+
result?.let {
|
|
364
|
+
mapOf(
|
|
365
|
+
"north" to it.north,
|
|
366
|
+
"south" to it.south,
|
|
367
|
+
"east" to it.east,
|
|
368
|
+
"west" to it.west,
|
|
369
|
+
"center" to mapOf(
|
|
370
|
+
"latitude" to it.centerLat,
|
|
371
|
+
"longitude" to it.centerLon
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* GeoHash 编码
|
|
379
|
+
* @param coordinate 坐标点
|
|
380
|
+
* @param precision 精度 (1-12)
|
|
381
|
+
* @return GeoHash 字符串
|
|
382
|
+
*/
|
|
383
|
+
Function("encodeGeoHash") { coordinate: Map<String, Any>?, precision: Int ->
|
|
384
|
+
val latLng = LatLngParser.parseLatLng(coordinate)
|
|
385
|
+
if (latLng != null) {
|
|
386
|
+
GeometryUtils.encodeGeoHash(latLng, precision)
|
|
387
|
+
} else {
|
|
388
|
+
""
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 轨迹抽稀 (RDP 算法)
|
|
394
|
+
* @param points 原始轨迹点
|
|
395
|
+
* @param tolerance 允许误差(米)
|
|
396
|
+
* @return 简化后的轨迹点
|
|
397
|
+
*/
|
|
398
|
+
Function("simplifyPolyline") { points: List<Any>?, tolerance: Double ->
|
|
399
|
+
val poly = LatLngParser.parseLatLngList(points)
|
|
400
|
+
val simplified = GeometryUtils.simplifyPolyline(poly, tolerance)
|
|
401
|
+
simplified.map {
|
|
402
|
+
mapOf(
|
|
403
|
+
"latitude" to it.latitude,
|
|
404
|
+
"longitude" to it.longitude
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 计算路径总长度
|
|
411
|
+
* @param points 路径点
|
|
412
|
+
* @return 长度(米)
|
|
413
|
+
*/
|
|
414
|
+
Function("calculatePathLength") { points: List<Any>? ->
|
|
415
|
+
val poly = LatLngParser.parseLatLngList(points)
|
|
416
|
+
GeometryUtils.calculatePathLength(poly)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* 解析高德地图 API 返回的 Polyline 字符串
|
|
421
|
+
* @param polylineStr 高德原始 polyline 字符串
|
|
422
|
+
* @return 坐标点列表
|
|
423
|
+
*/
|
|
424
|
+
Function("parsePolyline") { polylineStr: String? ->
|
|
425
|
+
val result = GeometryUtils.parsePolyline(polylineStr)
|
|
426
|
+
result.map {
|
|
427
|
+
mapOf(
|
|
428
|
+
"latitude" to it.latitude,
|
|
429
|
+
"longitude" to it.longitude
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 获取路径上指定距离的点
|
|
436
|
+
* @param points 路径点
|
|
437
|
+
* @param distance 距离起点的米数
|
|
438
|
+
* @return 点信息(坐标+角度)
|
|
439
|
+
*/
|
|
440
|
+
Function("getPointAtDistance") { points: List<Any>?, distance: Double ->
|
|
441
|
+
val poly = LatLngParser.parseLatLngList(points)
|
|
442
|
+
val result = GeometryUtils.getPointAtDistance(poly, distance)
|
|
443
|
+
if (result != null) {
|
|
444
|
+
mapOf(
|
|
445
|
+
"latitude" to result.point.latitude,
|
|
446
|
+
"longitude" to result.point.longitude,
|
|
447
|
+
"angle" to result.angle
|
|
448
|
+
)
|
|
449
|
+
} else {
|
|
450
|
+
null
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* 经纬度转瓦片坐标
|
|
456
|
+
* @param coordinate 坐标
|
|
457
|
+
* @param zoom 缩放级别
|
|
458
|
+
* @return 瓦片坐标 [x, y]
|
|
459
|
+
*/
|
|
460
|
+
Function("latLngToTile") { coordinate: Map<String, Any>?, zoom: Int ->
|
|
461
|
+
val latLng = LatLngParser.parseLatLng(coordinate)
|
|
462
|
+
if (latLng != null) {
|
|
463
|
+
val result = GeometryUtils.latLngToTile(latLng, zoom)
|
|
464
|
+
if (result != null && result.size >= 2) {
|
|
465
|
+
mapOf("x" to result[0], "y" to result[1])
|
|
466
|
+
} else {
|
|
467
|
+
null
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
null
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* 瓦片坐标转经纬度
|
|
476
|
+
* @param tile 瓦片坐标 {x, y, z}
|
|
477
|
+
* @return 坐标
|
|
478
|
+
*/
|
|
479
|
+
Function("tileToLatLng") { tile: Map<String, Any>? ->
|
|
480
|
+
val x = (tile?.get("x") as? Number)?.toInt() ?: 0
|
|
481
|
+
val y = (tile?.get("y") as? Number)?.toInt() ?: 0
|
|
482
|
+
val zoom = (tile?.get("z") as? Number)?.toInt() ?: (tile?.get("zoom") as? Number)?.toInt() ?: 0
|
|
483
|
+
val result = GeometryUtils.tileToLatLng(x, y, zoom)
|
|
484
|
+
result?.let {
|
|
485
|
+
mapOf("latitude" to it.latitude, "longitude" to it.longitude)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 经纬度转像素坐标
|
|
491
|
+
* @param coordinate 坐标
|
|
492
|
+
* @param zoom 缩放级别
|
|
493
|
+
* @return 像素坐标 [x, y]
|
|
494
|
+
*/
|
|
495
|
+
Function("latLngToPixel") { coordinate: Map<String, Any>?, zoom: Int ->
|
|
496
|
+
val latLng = LatLngParser.parseLatLng(coordinate)
|
|
497
|
+
if (latLng != null) {
|
|
498
|
+
val result = GeometryUtils.latLngToPixel(latLng, zoom)
|
|
499
|
+
if (result != null && result.size >= 2) {
|
|
500
|
+
mapOf("x" to result[0], "y" to result[1])
|
|
501
|
+
} else {
|
|
502
|
+
null
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
null
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* 像素坐标转经纬度
|
|
511
|
+
* @param pixel 像素坐标 {x, y}
|
|
512
|
+
* @param zoom 缩放级别
|
|
513
|
+
* @return 坐标
|
|
514
|
+
*/
|
|
515
|
+
Function("pixelToLatLng") { pixel: Map<String, Any>?, zoom: Int ->
|
|
516
|
+
val x = (pixel?.get("x") as? Number)?.toDouble() ?: 0.0
|
|
517
|
+
val y = (pixel?.get("y") as? Number)?.toDouble() ?: 0.0
|
|
518
|
+
val result = GeometryUtils.pixelToLatLng(x, y, zoom)
|
|
519
|
+
result?.let {
|
|
520
|
+
mapOf("latitude" to it.latitude, "longitude" to it.longitude)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* 批量判断点在哪个多边形内
|
|
526
|
+
* @param point 待判断点
|
|
527
|
+
* @param polygons 多边形列表
|
|
528
|
+
* @return 所在多边形的索引,不在任何多边形内返回 -1
|
|
529
|
+
*/
|
|
530
|
+
Function("findPointInPolygons") { point: Map<String, Any>?, polygons: List<List<Any>>? ->
|
|
531
|
+
val pt = LatLngParser.parseLatLng(point)
|
|
532
|
+
val polys = polygons?.map { LatLngParser.parseLatLngList(it) }
|
|
533
|
+
if (pt != null && polys != null) {
|
|
534
|
+
GeometryUtils.findPointInPolygons(pt, polys)
|
|
535
|
+
} else {
|
|
536
|
+
-1
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* 生成网格聚合数据 (常用于展示网格聚合图或大规模点数据处理)
|
|
542
|
+
* @param points 包含经纬度和权重的点数组
|
|
543
|
+
* @param gridSizeMeters 网格大小(米)
|
|
544
|
+
*/
|
|
545
|
+
Function("generateHeatmapGrid") { points: List<Map<String, Any>>?, gridSizeMeters: Double ->
|
|
546
|
+
if (points == null || points.isEmpty()) return@Function emptyList<Map<String, Any>>()
|
|
547
|
+
|
|
548
|
+
val count = points.size
|
|
549
|
+
val latitudes = DoubleArray(count)
|
|
550
|
+
val longitudes = DoubleArray(count)
|
|
551
|
+
val weights = DoubleArray(count)
|
|
552
|
+
|
|
553
|
+
points.forEachIndexed { index, map ->
|
|
554
|
+
latitudes[index] = (map["latitude"] as? Number)?.toDouble() ?: 0.0
|
|
555
|
+
longitudes[index] = (map["longitude"] as? Number)?.toDouble() ?: 0.0
|
|
556
|
+
weights[index] = (map["weight"] as? Number)?.toDouble() ?: 1.0
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
val result = GeometryUtils.generateHeatmapGrid(latitudes, longitudes, weights, gridSizeMeters)
|
|
560
|
+
result.map {
|
|
561
|
+
mapOf(
|
|
562
|
+
"latitude" to it.latitude,
|
|
563
|
+
"longitude" to it.longitude,
|
|
564
|
+
"intensity" to it.intensity
|
|
565
|
+
)
|
|
566
|
+
}
|
|
135
567
|
}
|
|
136
568
|
|
|
137
569
|
// ==================== 定位配置 ====================
|
|
138
570
|
|
|
139
571
|
/**
|
|
140
572
|
* 设置是否返回逆地理信息
|
|
141
|
-
* @param isReGeocode
|
|
573
|
+
* @param isReGeocode 是否返回逆地理信息+
|
|
142
574
|
*/
|
|
143
575
|
Function("setLocatingWithReGeocode") { isReGeocode: Boolean ->
|
|
144
576
|
getLocationManager().setLocatingWithReGeocode(isReGeocode)
|
|
@@ -281,54 +713,70 @@ class ExpoGaodeMapModule : Module() {
|
|
|
281
713
|
// 未实现
|
|
282
714
|
}
|
|
283
715
|
|
|
716
|
+
/**
|
|
717
|
+
* 开始更新设备方向 (iOS 专用,Android 空实现)
|
|
718
|
+
* Android 不支持此功能
|
|
719
|
+
*/
|
|
720
|
+
Function("startUpdatingHeading") {
|
|
721
|
+
// Android 不支持罗盘方向更新
|
|
722
|
+
android.util.Log.d("ExpoGaodeMap", "startUpdatingHeading: iOS 专用功能,Android 不支持")
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* 停止更新设备方向 (iOS 专用,Android 空实现)
|
|
727
|
+
* Android 不支持此功能
|
|
728
|
+
*/
|
|
729
|
+
Function("stopUpdatingHeading") {
|
|
730
|
+
// Android 不支持罗盘方向更新
|
|
731
|
+
android.util.Log.d("ExpoGaodeMap", "stopUpdatingHeading: iOS 专用功能,Android 不支持")
|
|
732
|
+
}
|
|
733
|
+
|
|
284
734
|
// ==================== 权限管理 ====================
|
|
285
|
-
|
|
735
|
+
|
|
286
736
|
/**
|
|
287
|
-
*
|
|
288
|
-
* @return
|
|
737
|
+
* 检查位置权限状态(增强版,支持 Android 14+ 适配)
|
|
738
|
+
* @return 权限状态对象,包含详细的权限信息
|
|
289
739
|
*/
|
|
290
|
-
AsyncFunction("checkLocationPermission") { promise: Promise ->
|
|
740
|
+
AsyncFunction("checkLocationPermission") { promise: expo.modules.kotlin.Promise ->
|
|
291
741
|
val context = appContext.reactContext!!
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
val
|
|
296
|
-
|
|
297
|
-
val hasCoarse = ContextCompat.checkSelfPermission(context, coarseLocation) ==
|
|
298
|
-
PackageManager.PERMISSION_GRANTED
|
|
299
|
-
|
|
742
|
+
|
|
743
|
+
// 使用增强的权限检查
|
|
744
|
+
val foregroundStatus = PermissionHelper.checkForegroundLocationPermission(context)
|
|
745
|
+
val backgroundStatus = PermissionHelper.checkBackgroundLocationPermission(context)
|
|
746
|
+
|
|
300
747
|
promise.resolve(mapOf(
|
|
301
|
-
"granted" to
|
|
302
|
-
"
|
|
303
|
-
"
|
|
748
|
+
"granted" to foregroundStatus.granted,
|
|
749
|
+
"status" to if (foregroundStatus.granted) "granted" else if (foregroundStatus.isPermanentlyDenied) "denied" else "notDetermined",
|
|
750
|
+
"fineLocation" to foregroundStatus.fineLocation,
|
|
751
|
+
"coarseLocation" to foregroundStatus.coarseLocation,
|
|
752
|
+
"backgroundLocation" to backgroundStatus.backgroundLocation,
|
|
753
|
+
"shouldShowRationale" to foregroundStatus.shouldShowRationale,
|
|
754
|
+
"isPermanentlyDenied" to foregroundStatus.isPermanentlyDenied,
|
|
755
|
+
"isAndroid14Plus" to PermissionHelper.isAndroid14Plus()
|
|
304
756
|
))
|
|
305
757
|
}
|
|
306
|
-
|
|
758
|
+
|
|
307
759
|
/**
|
|
308
|
-
*
|
|
760
|
+
* 请求前台位置权限(增强版,支持 Android 14+ 适配)
|
|
309
761
|
* 注意: Android 权限请求是异步的,使用轮询方式检查权限状态
|
|
310
762
|
* @return 权限请求结果
|
|
311
763
|
*/
|
|
312
|
-
AsyncFunction("requestLocationPermission") { promise: Promise ->
|
|
764
|
+
AsyncFunction("requestLocationPermission") { promise: expo.modules.kotlin.Promise ->
|
|
313
765
|
val activity = appContext.currentActivity
|
|
314
766
|
if (activity == null) {
|
|
315
767
|
promise.reject("NO_ACTIVITY", "Activity not available", null)
|
|
316
768
|
return@AsyncFunction
|
|
317
769
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
ActivityCompat.requestPermissions(activity, permissions, 1001)
|
|
325
|
-
|
|
770
|
+
|
|
771
|
+
// 使用增强的权限请求方法
|
|
772
|
+
PermissionHelper.requestForegroundLocationPermission(activity, 1001)
|
|
773
|
+
|
|
326
774
|
// 使用 WeakReference 避免内存泄露
|
|
327
|
-
val contextRef = WeakReference(appContext.reactContext)
|
|
328
|
-
val handler = Handler(Looper.getMainLooper())
|
|
329
|
-
|
|
330
|
-
val maxAttempts =
|
|
331
|
-
|
|
775
|
+
val contextRef = java.lang.ref.WeakReference(appContext.reactContext)
|
|
776
|
+
val handler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
777
|
+
val attempts = 0
|
|
778
|
+
val maxAttempts = 50 // 增加到 5 秒 / 100ms,给用户足够时间操作
|
|
779
|
+
|
|
332
780
|
val checkPermission = object : Runnable {
|
|
333
781
|
override fun run() {
|
|
334
782
|
val context = contextRef.get()
|
|
@@ -336,37 +784,113 @@ class ExpoGaodeMapModule : Module() {
|
|
|
336
784
|
promise.reject("CONTEXT_LOST", "Context was garbage collected", null)
|
|
337
785
|
return
|
|
338
786
|
}
|
|
339
|
-
|
|
340
|
-
val
|
|
341
|
-
|
|
342
|
-
) == PackageManager.PERMISSION_GRANTED
|
|
343
|
-
val hasCoarse = ContextCompat.checkSelfPermission(
|
|
344
|
-
context, Manifest.permission.ACCESS_COARSE_LOCATION
|
|
345
|
-
) == PackageManager.PERMISSION_GRANTED
|
|
346
|
-
|
|
787
|
+
|
|
788
|
+
val status = PermissionHelper.checkForegroundLocationPermission(context)
|
|
789
|
+
|
|
347
790
|
// 如果权限已授予或达到最大尝试次数,返回结果并清理 Handler
|
|
348
|
-
if (
|
|
791
|
+
if (status.granted || attempts >= maxAttempts) {
|
|
349
792
|
handler.removeCallbacks(this)
|
|
350
793
|
promise.resolve(mapOf(
|
|
351
|
-
"granted" to
|
|
352
|
-
"
|
|
353
|
-
"
|
|
794
|
+
"granted" to status.granted,
|
|
795
|
+
"status" to if (status.granted) "granted" else if (status.isPermanentlyDenied) "denied" else "notDetermined",
|
|
796
|
+
"fineLocation" to status.fineLocation,
|
|
797
|
+
"coarseLocation" to status.coarseLocation,
|
|
798
|
+
"shouldShowRationale" to status.shouldShowRationale,
|
|
799
|
+
"isPermanentlyDenied" to status.isPermanentlyDenied
|
|
354
800
|
))
|
|
355
801
|
} else {
|
|
356
|
-
|
|
802
|
+
|
|
357
803
|
handler.postDelayed(this, 100)
|
|
358
804
|
}
|
|
359
805
|
}
|
|
360
806
|
}
|
|
361
|
-
|
|
807
|
+
|
|
808
|
+
// 延迟更长时间开始轮询,给权限对话框弹出的时间
|
|
809
|
+
handler.postDelayed(checkPermission, 500)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* 请求后台位置权限(Android 10+ 支持)
|
|
814
|
+
* 注意: 必须在前台权限已授予后才能请求
|
|
815
|
+
* @return 权限请求结果
|
|
816
|
+
*/
|
|
817
|
+
AsyncFunction("requestBackgroundLocationPermission") { promise: expo.modules.kotlin.Promise ->
|
|
818
|
+
val activity = appContext.currentActivity
|
|
819
|
+
if (activity == null) {
|
|
820
|
+
promise.reject("NO_ACTIVITY", "Activity not available", null)
|
|
821
|
+
return@AsyncFunction
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 检查是否支持后台位置权限
|
|
825
|
+
if (!PermissionHelper.isAndroid10Plus()) {
|
|
826
|
+
promise.resolve(mapOf(
|
|
827
|
+
"granted" to true,
|
|
828
|
+
"backgroundLocation" to true,
|
|
829
|
+
"message" to "Android 10 以下不需要单独请求后台位置权限"
|
|
830
|
+
))
|
|
831
|
+
return@AsyncFunction
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// 尝试请求后台位置权限
|
|
835
|
+
val canRequest = PermissionHelper.requestBackgroundLocationPermission(activity, 1002)
|
|
836
|
+
if (!canRequest) {
|
|
837
|
+
promise.reject(
|
|
838
|
+
"FOREGROUND_PERMISSION_REQUIRED",
|
|
839
|
+
"必须先授予前台位置权限才能请求后台位置权限",
|
|
840
|
+
null
|
|
841
|
+
)
|
|
842
|
+
return@AsyncFunction
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// 轮询检查权限状态
|
|
846
|
+
val contextRef = java.lang.ref.WeakReference(appContext.reactContext)
|
|
847
|
+
val handler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
848
|
+
val attempts = 0
|
|
849
|
+
val maxAttempts = 30
|
|
850
|
+
|
|
851
|
+
val checkPermission = object : Runnable {
|
|
852
|
+
override fun run() {
|
|
853
|
+
val context = contextRef.get()
|
|
854
|
+
if (context == null) {
|
|
855
|
+
promise.reject("CONTEXT_LOST", "Context was garbage collected", null)
|
|
856
|
+
return
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
val status = PermissionHelper.checkBackgroundLocationPermission(context)
|
|
860
|
+
|
|
861
|
+
if (status.granted || attempts >= maxAttempts) {
|
|
862
|
+
handler.removeCallbacks(this)
|
|
863
|
+
promise.resolve(mapOf(
|
|
864
|
+
"granted" to status.granted,
|
|
865
|
+
"status" to if (status.granted) "granted" else if (status.isPermanentlyDenied) "denied" else "notDetermined",
|
|
866
|
+
"backgroundLocation" to status.backgroundLocation,
|
|
867
|
+
"shouldShowRationale" to status.shouldShowRationale,
|
|
868
|
+
"isPermanentlyDenied" to status.isPermanentlyDenied
|
|
869
|
+
))
|
|
870
|
+
} else {
|
|
871
|
+
|
|
872
|
+
handler.postDelayed(this, 100)
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
362
877
|
handler.postDelayed(checkPermission, 100)
|
|
363
878
|
}
|
|
364
879
|
|
|
880
|
+
/**
|
|
881
|
+
* 打开应用设置页面(引导用户手动授予权限)
|
|
882
|
+
*/
|
|
883
|
+
Function("openAppSettings") {
|
|
884
|
+
val context = appContext.reactContext!!
|
|
885
|
+
PermissionHelper.openAppSettings(context)
|
|
886
|
+
}
|
|
887
|
+
|
|
365
888
|
Events("onLocationUpdate")
|
|
366
889
|
|
|
367
890
|
OnDestroy {
|
|
368
891
|
locationManager?.destroy()
|
|
369
892
|
locationManager = null
|
|
893
|
+
MapPreloadManager.cleanup()
|
|
370
894
|
}
|
|
371
895
|
}
|
|
372
896
|
|