expo-gaode-map 2.2.33 → 2.2.34
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 +20 -14
- package/android/build.gradle +8 -4
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapModule.kt +7 -8
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapOfflineModule.kt +150 -27
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +24 -14
- package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +38 -41
- package/android/src/main/java/expo/modules/gaodemap/modules/SDKInitializer.kt +18 -17
- package/android/src/main/java/expo/modules/gaodemap/overlays/CircleView.kt +3 -1
- package/android/src/main/java/expo/modules/gaodemap/overlays/ClusterView.kt +6 -1
- package/android/src/main/java/expo/modules/gaodemap/overlays/HeatMapView.kt +124 -10
- package/android/src/main/java/expo/modules/gaodemap/overlays/HeatMapViewModule.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerBitmapRenderer.kt +10 -9
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +7 -11
- package/android/src/main/java/expo/modules/gaodemap/overlays/MultiPointView.kt +3 -1
- package/android/src/main/java/expo/modules/gaodemap/overlays/PolygonView.kt +2 -1
- package/android/src/main/java/expo/modules/gaodemap/overlays/PolylineView.kt +1 -0
- package/android/src/main/java/expo/modules/gaodemap/search/ExpoGaodeMapSearchModule.kt +751 -0
- package/android/src/main/java/expo/modules/gaodemap/utils/GeometryUtils.kt +5 -5
- package/android/src/main/java/expo/modules/gaodemap/utils/PermissionHelper.kt +13 -16
- package/build/ExpoGaodeMapOfflineModule.d.ts +5 -0
- package/build/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
- package/build/ExpoGaodeMapOfflineModule.js.map +1 -1
- package/build/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/components/overlays/HeatMap.js +21 -2
- package/build/components/overlays/HeatMap.js.map +1 -1
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/search/ExpoGaodeMapSearch.types.d.ts +340 -0
- package/build/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
- package/build/search/ExpoGaodeMapSearch.types.js +19 -0
- package/build/search/ExpoGaodeMapSearch.types.js.map +1 -0
- package/build/search/ExpoGaodeMapSearchModule.d.ts +74 -0
- package/build/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
- package/build/search/ExpoGaodeMapSearchModule.js +47 -0
- package/build/search/ExpoGaodeMapSearchModule.js.map +1 -0
- package/build/search/index.d.ts +156 -0
- package/build/search/index.d.ts.map +1 -0
- package/build/search/index.js +171 -0
- package/build/search/index.js.map +1 -0
- package/build/types/map-view.types.d.ts +4 -2
- package/build/types/map-view.types.d.ts.map +1 -1
- package/build/types/map-view.types.js.map +1 -1
- package/build/utils/OfflineMapManager.d.ts +4 -0
- package/build/utils/OfflineMapManager.d.ts.map +1 -1
- package/build/utils/OfflineMapManager.js +6 -0
- package/build/utils/OfflineMapManager.js.map +1 -1
- package/expo-module.config.json +4 -2
- package/ios/ExpoGaodeMap.podspec +2 -2
- package/ios/ExpoGaodeMapOfflineModule.swift +60 -0
- package/ios/ExpoGaodeMapSearchModule.swift +773 -0
- package/ios/modules/LocationManager.swift +9 -3
- package/ios/overlays/PolylineView.swift +6 -12
- package/package.json +1 -1
- package/plugin/build/withGaodeMap.js +12 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 📖 完整文档
|
|
8
8
|
|
|
9
|
-
**👉 [在线文档网站](https://TomWq.github.io/expo-gaode-map/)** · **👉 [
|
|
9
|
+
**👉 [在线文档网站](https://TomWq.github.io/expo-gaode-map/)** · **👉 本地示例:[`example/`](../../example) / [`example-navigation/`](../../example-navigation)**
|
|
10
10
|
|
|
11
11
|
包含完整的 API 文档、使用指南和示例代码:
|
|
12
12
|
- [快速开始](https://TomWq.github.io/expo-gaode-map/guide/getting-started.html)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- [导航功能](https://TomWq.github.io/expo-gaode-map/guide/navigation.html)
|
|
16
16
|
- [Web API](https://TomWq.github.io/expo-gaode-map/guide/web-api.html)
|
|
17
17
|
- [API 参考](https://TomWq.github.io/expo-gaode-map/api/)
|
|
18
|
-
- [
|
|
18
|
+
- [本地示例工程](../../example) / [导航示例工程](../../example-navigation)
|
|
19
19
|
|
|
20
20
|
## ✨ 主要特性
|
|
21
21
|
|
|
@@ -37,10 +37,16 @@
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
### 可选模块
|
|
40
|
-
- 🔍
|
|
40
|
+
- 🔍 **搜索功能** - 已内置在 `expo-gaode-map` 中,提供 POI 搜索、周边搜索、关键字搜索、地理编码等
|
|
41
41
|
- 🧭 **导航功能**(expo-gaode-map-navigation)- 驾车、步行、骑行、货车路径规划,实时导航
|
|
42
42
|
- 🌐 **Web API**(expo-gaode-map-web-api)- 纯 JavaScript 实现的路径规划、地理编码、POI 搜索等
|
|
43
43
|
|
|
44
|
+
> ⚠️ **Search 模块维护说明**
|
|
45
|
+
>
|
|
46
|
+
> 从下个版本开始,搜索能力已经集成到 `expo-gaode-map`(core)和 `expo-gaode-map-navigation` 的 map 能力中,`expo-gaode-map-search` 将不再作为独立模块继续维护。`2.2.33` 是最后一个支持 `expo-gaode-map-search` 单独集成的版本;新项目请直接从 `expo-gaode-map` 或 `expo-gaode-map-navigation` 导入搜索 API。
|
|
47
|
+
>
|
|
48
|
+
> 高德官方 Android SDK 在 `10.0.700` 之后将远程依赖由“地图 + 定位”调整为“地图 + 定位 + 搜索”,依赖地址从 `com.amap.api:3dmap:latest.integration` 调整为 `com.amap.api:3dmap-location-search:latest.integration`。继续单独维护 search 模块会带来重复合包和依赖冲突成本,因此搜索能力改为随 core / navigation 一起维护。
|
|
49
|
+
|
|
44
50
|
## 📦 安装
|
|
45
51
|
|
|
46
52
|
> ⚠️ **版本兼容性说明**:
|
|
@@ -57,7 +63,6 @@
|
|
|
57
63
|
npm install expo-gaode-map
|
|
58
64
|
|
|
59
65
|
# 可选模块
|
|
60
|
-
npm install expo-gaode-map-search # 搜索功能
|
|
61
66
|
npm install expo-gaode-map-web-api # Web API
|
|
62
67
|
```
|
|
63
68
|
|
|
@@ -104,7 +109,7 @@ npx expo run:ios
|
|
|
104
109
|
|
|
105
110
|
详细的初始化和使用指南请查看:
|
|
106
111
|
- 📖 [快速开始文档](https://TomWq.github.io/expo-gaode-map/guide/getting-started.html)
|
|
107
|
-
- 💻 [
|
|
112
|
+
- 💻 [地图示例工程](../../example) / [导航示例工程](../../example-navigation)
|
|
108
113
|
|
|
109
114
|
## 📚 功能模块对比
|
|
110
115
|
|
|
@@ -113,8 +118,8 @@ npx expo run:ios
|
|
|
113
118
|
| 地图显示 | ✅ | ❌ | ✅ | ❌ |
|
|
114
119
|
| 定位 | ✅ | ❌ | ✅ | ❌ |
|
|
115
120
|
| 覆盖物 | ✅ | ❌ | ✅ | ❌ |
|
|
116
|
-
| POI 搜索 |
|
|
117
|
-
| 地理编码 |
|
|
121
|
+
| POI 搜索 | ✅ | ⚠️ 2.2.33 及以下 | ✅ | ✅ |
|
|
122
|
+
| 地理编码 | ✅ | ⚠️ 2.2.33 及以下 | ✅ | ✅ |
|
|
118
123
|
| 路径规划 | ❌ | ❌ | ✅ | ✅ |
|
|
119
124
|
| 实时导航 | ❌ | ❌ | ✅ | ❌ |
|
|
120
125
|
| 平台 | 原生 | 原生 | 原生 | Web/原生 |
|
|
@@ -125,11 +130,11 @@ npx expo run:ios
|
|
|
125
130
|
expo-gaode-map/
|
|
126
131
|
├── packages/
|
|
127
132
|
│ ├── core/ # expo-gaode-map(核心包)
|
|
128
|
-
│ │ └──
|
|
129
|
-
│ ├── search/ # expo-gaode-map-search
|
|
130
|
-
│ │ └── POI
|
|
133
|
+
│ │ └── 地图显示、定位、覆盖物、搜索
|
|
134
|
+
│ ├── search/ # expo-gaode-map-search(独立搜索包,2.2.33 后不再维护)
|
|
135
|
+
│ │ └── POI 搜索、地理编码(历史兼容)
|
|
131
136
|
│ ├── navigation/ # expo-gaode-map-navigation(导航包)
|
|
132
|
-
│ │ └──
|
|
137
|
+
│ │ └── 地图+搜索+导航(替代 core)
|
|
133
138
|
│ └── web-api/ # expo-gaode-map-web-api(Web API)
|
|
134
139
|
│ └── 纯 JS 实现的POI 搜索、地理编码、路径规划等
|
|
135
140
|
└── 注意:core 和 navigation 不能同时安装
|
|
@@ -145,7 +150,8 @@ expo-gaode-map/
|
|
|
145
150
|
|
|
146
151
|
### 2. 搜索功能和 Web API 有什么区别?
|
|
147
152
|
|
|
148
|
-
-
|
|
153
|
+
- **内置原生搜索**(`expo-gaode-map` / `expo-gaode-map-navigation`):原生实现,性能更好,无需网络请求,需要配置原生环境
|
|
154
|
+
- **独立搜索包**(`expo-gaode-map-search`):仅建议历史项目固定 `2.2.33` 使用,后续不再单独维护
|
|
149
155
|
- **Web API**(`expo-gaode-map-web-api`):纯 JavaScript,无需原生配置,跨平台更好,需要网络请求,但功能更加强大和完善
|
|
150
156
|
|
|
151
157
|
### 3. 如何配置 API Key?
|
|
@@ -231,8 +237,8 @@ MIT
|
|
|
231
237
|
- [错误处理指南](./ERROR_HANDLING_GUIDE.md) 🆕
|
|
232
238
|
- [性能优化指南](./PERFORMANCE_GUIDE.md) 🆕
|
|
233
239
|
- [GitHub 仓库](https://github.com/TomWq/expo-gaode-map)
|
|
234
|
-
- [
|
|
235
|
-
- [
|
|
240
|
+
- [地图示例工程](../../example)
|
|
241
|
+
- [导航示例工程](../../example-navigation)
|
|
236
242
|
- [高德地图开放平台](https://lbs.amap.com/)
|
|
237
243
|
- [Expo Modules API](https://docs.expo.dev/modules/overview/)
|
|
238
244
|
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'expo.modules.gaodemap'
|
|
4
|
-
version = '2.2.
|
|
4
|
+
version = '2.2.34'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -38,7 +38,7 @@ android {
|
|
|
38
38
|
namespace "expo.modules.gaodemap"
|
|
39
39
|
defaultConfig {
|
|
40
40
|
versionCode 1
|
|
41
|
-
versionName "2.2.
|
|
41
|
+
versionName "2.2.34"
|
|
42
42
|
externalNativeBuild {
|
|
43
43
|
cmake {
|
|
44
44
|
cppFlags "-std=c++17"
|
|
@@ -55,8 +55,10 @@ android {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
|
|
59
|
+
|
|
58
60
|
dependencies {
|
|
59
|
-
//
|
|
61
|
+
// 高德地图组合 SDK:地图 + 定位 + 搜索。
|
|
60
62
|
def customSdkPath = null
|
|
61
63
|
println "ExpoGaodeMap: Checking for custom SDK property in rootProject: ${rootProject.name}"
|
|
62
64
|
if (rootProject.hasProperty("EXPO_GAODE_MAP_CUSTOM_SDK_PATH")) {
|
|
@@ -80,6 +82,8 @@ dependencies {
|
|
|
80
82
|
throw new FileNotFoundException("ExpoGaodeMap: Could not find custom SDK at ${customSdkPath}. Please check your customMapSdkPath configuration.")
|
|
81
83
|
}
|
|
82
84
|
} else {
|
|
83
|
-
|
|
85
|
+
// 10.1.600 is the last Android 3D map SDK line that supports offline
|
|
86
|
+
// map downloads without the separate 11.x offline-download permission.
|
|
87
|
+
implementation('com.amap.api:3dmap-location-search:10.1.700_loc6.5.1_sea9.7.4')
|
|
84
88
|
}
|
|
85
89
|
}
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
6
6
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
7
7
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
8
|
+
|
|
9
|
+
<!-- 离线地图读写本地地图包 (Android 12 及以下) -->
|
|
10
|
+
<uses-permission
|
|
11
|
+
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
12
|
+
android:maxSdkVersion="32" />
|
|
13
|
+
<uses-permission
|
|
14
|
+
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
15
|
+
android:maxSdkVersion="32" />
|
|
8
16
|
|
|
9
17
|
<application allowBackup="false" >
|
|
10
18
|
|
|
@@ -13,6 +21,12 @@
|
|
|
13
21
|
android:name="com.amap.api.location.APSService"
|
|
14
22
|
android:enabled="true"
|
|
15
23
|
android:exported="false" />
|
|
24
|
+
|
|
25
|
+
<!-- 高德 3D 地图 SDK 官方离线地图 UI 组件 -->
|
|
26
|
+
<activity
|
|
27
|
+
android:name="com.amap.api.maps.offlinemap.OfflineMapActivity"
|
|
28
|
+
android:screenOrientation="portrait"
|
|
29
|
+
android:exported="false" />
|
|
16
30
|
|
|
17
31
|
</application>
|
|
18
32
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package expo.modules.gaodemap
|
|
2
2
|
|
|
3
3
|
import com.amap.api.maps.MapsInitializer
|
|
4
|
-
import com.amap.api.maps.model.LatLng
|
|
5
4
|
import expo.modules.kotlin.modules.Module
|
|
6
5
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
7
6
|
import expo.modules.gaodemap.modules.SDKInitializer
|
|
@@ -170,8 +169,8 @@ class ExpoGaodeMapModule : Module() {
|
|
|
170
169
|
AsyncFunction("coordinateConvert") { coordinate: Map<String, Any>?, type: Int, promise: expo.modules.kotlin.Promise ->
|
|
171
170
|
val latLng = LatLngParser.parseLatLng(coordinate)
|
|
172
171
|
if (latLng != null) {
|
|
173
|
-
val
|
|
174
|
-
getLocationManager().coordinateConvert(
|
|
172
|
+
val cordMap = mapOf("latitude" to latLng.latitude, "longitude" to latLng.longitude)
|
|
173
|
+
getLocationManager().coordinateConvert(cordMap, type, promise)
|
|
175
174
|
} else {
|
|
176
175
|
promise.reject("INVALID_COORDINATE", "Invalid coordinate format", null)
|
|
177
176
|
}
|
|
@@ -186,10 +185,10 @@ class ExpoGaodeMapModule : Module() {
|
|
|
186
185
|
* @returns 两点之间的距离(单位:米)
|
|
187
186
|
*/
|
|
188
187
|
Function("distanceBetweenCoordinates") { p1: Map<String, Any>?, p2: Map<String, Any>? ->
|
|
189
|
-
val
|
|
190
|
-
val
|
|
191
|
-
if (
|
|
192
|
-
GeometryUtils.calculateDistance(
|
|
188
|
+
val cord1 = LatLngParser.parseLatLng(p1)
|
|
189
|
+
val cord2 = LatLngParser.parseLatLng(p2)
|
|
190
|
+
if (cord1 != null && cord2 != null) {
|
|
191
|
+
GeometryUtils.calculateDistance(cord1, cord2)
|
|
193
192
|
} else {
|
|
194
193
|
0.0
|
|
195
194
|
}
|
|
@@ -580,7 +579,7 @@ class ExpoGaodeMapModule : Module() {
|
|
|
580
579
|
* @param gridSizeMeters 网格大小(米)
|
|
581
580
|
*/
|
|
582
581
|
Function("generateHeatmapGrid") { points: List<Map<String, Any>>?, gridSizeMeters: Double ->
|
|
583
|
-
if (points
|
|
582
|
+
if (points.isNullOrEmpty()) return@Function emptyList<Map<String, Any>>()
|
|
584
583
|
|
|
585
584
|
val count = points.size
|
|
586
585
|
val latitudes = DoubleArray(count)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
package expo.modules.gaodemap
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
3
6
|
import android.os.Bundle
|
|
4
7
|
import android.os.StatFs
|
|
5
8
|
import android.os.Environment
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import com.amap.api.maps.offlinemap.OfflineMapActivity
|
|
6
11
|
import com.amap.api.maps.offlinemap.OfflineMapCity
|
|
7
12
|
import com.amap.api.maps.offlinemap.OfflineMapManager
|
|
8
13
|
import com.amap.api.maps.offlinemap.OfflineMapProvince
|
|
@@ -17,6 +22,7 @@ import expo.modules.kotlin.modules.ModuleDefinition
|
|
|
17
22
|
*/
|
|
18
23
|
class ExpoGaodeMapOfflineModule : Module() {
|
|
19
24
|
|
|
25
|
+
private val logTag = "ExpoGaodeMapOffline"
|
|
20
26
|
private var offlineMapManager: OfflineMapManager? = null
|
|
21
27
|
private val downloadingCities = mutableSetOf<String>()
|
|
22
28
|
private val pausedCities = mutableSetOf<String>()
|
|
@@ -39,6 +45,11 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
private fun getOfflineMapManager(): OfflineMapManager {
|
|
48
|
+
val reactContext = appContext.reactContext
|
|
49
|
+
?: throw CodedException("NO_CONTEXT", "React context not available", null)
|
|
50
|
+
|
|
51
|
+
SDKInitializer.restorePersistedState(reactContext.applicationContext)
|
|
52
|
+
|
|
42
53
|
if (!SDKInitializer.isPrivacyReady()) {
|
|
43
54
|
throw CodedException(
|
|
44
55
|
"PRIVACY_NOT_AGREED",
|
|
@@ -47,9 +58,6 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
47
58
|
)
|
|
48
59
|
}
|
|
49
60
|
|
|
50
|
-
val reactContext = appContext.reactContext
|
|
51
|
-
?: throw CodedException("NO_CONTEXT", "React context not available", null)
|
|
52
|
-
|
|
53
61
|
if (offlineMapManager == null) {
|
|
54
62
|
offlineMapManager = OfflineMapManager(
|
|
55
63
|
reactContext.applicationContext,
|
|
@@ -59,6 +67,31 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
59
67
|
|
|
60
68
|
return offlineMapManager!!
|
|
61
69
|
}
|
|
70
|
+
|
|
71
|
+
private fun getActivityLaunchContext(): Context {
|
|
72
|
+
return appContext.currentActivity
|
|
73
|
+
?: appContext.reactContext
|
|
74
|
+
?: throw CodedException("NO_CONTEXT", "React context not available", null)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private fun openOfflineMapUI() {
|
|
78
|
+
val context = getActivityLaunchContext()
|
|
79
|
+
SDKInitializer.restorePersistedState(context.applicationContext)
|
|
80
|
+
|
|
81
|
+
if (!SDKInitializer.isPrivacyReady()) {
|
|
82
|
+
throw CodedException(
|
|
83
|
+
"PRIVACY_NOT_AGREED",
|
|
84
|
+
"隐私协议未完成确认,请先调用 setPrivacyConfig",
|
|
85
|
+
null
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
val intent = Intent(context, OfflineMapActivity::class.java)
|
|
90
|
+
if (context !is Activity) {
|
|
91
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
92
|
+
}
|
|
93
|
+
context.startActivity(intent)
|
|
94
|
+
}
|
|
62
95
|
|
|
63
96
|
override fun definition() = ModuleDefinition {
|
|
64
97
|
Name("ExpoGaodeMapOffline")
|
|
@@ -79,12 +112,18 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
79
112
|
offlineMapManager = null
|
|
80
113
|
downloadingCities.clear()
|
|
81
114
|
}
|
|
115
|
+
|
|
116
|
+
AsyncFunction("openOfflineMapUI") {
|
|
117
|
+
openOfflineMapUI()
|
|
118
|
+
}
|
|
82
119
|
|
|
83
120
|
// ==================== 地图列表管理 ====================
|
|
84
121
|
|
|
85
122
|
AsyncFunction("getAvailableCities") {
|
|
86
|
-
val
|
|
87
|
-
cities
|
|
123
|
+
val manager = getOfflineMapManager()
|
|
124
|
+
val cities = manager.offlineMapCityList ?: emptyList()
|
|
125
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
126
|
+
cities.map { city -> convertCityToMap(city, downloadedCityKeys) }
|
|
88
127
|
}
|
|
89
128
|
|
|
90
129
|
AsyncFunction("getAvailableProvinces") {
|
|
@@ -93,15 +132,19 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
93
132
|
}
|
|
94
133
|
|
|
95
134
|
AsyncFunction("getCitiesByProvince") { provinceCode: String ->
|
|
96
|
-
val
|
|
135
|
+
val manager = getOfflineMapManager()
|
|
136
|
+
val province = manager.offlineMapProvinceList?.find {
|
|
97
137
|
it.provinceCode == provinceCode
|
|
98
138
|
}
|
|
99
|
-
|
|
139
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
140
|
+
province?.cityList?.map { city -> convertCityToMap(city, downloadedCityKeys) } ?: emptyList()
|
|
100
141
|
}
|
|
101
142
|
|
|
102
143
|
AsyncFunction("getDownloadedMaps") {
|
|
103
|
-
val
|
|
104
|
-
cities
|
|
144
|
+
val manager = getOfflineMapManager()
|
|
145
|
+
val cities = manager.downloadOfflineMapCityList ?: emptyList()
|
|
146
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
147
|
+
cities.map { city -> convertCityToMap(city, downloadedCityKeys) }
|
|
105
148
|
}
|
|
106
149
|
|
|
107
150
|
// ==================== 下载管理 ====================
|
|
@@ -114,7 +157,7 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
114
157
|
downloadingCities.add(cityCode)
|
|
115
158
|
pausedCities.remove(cityCode)
|
|
116
159
|
}
|
|
117
|
-
getOfflineMapManager()
|
|
160
|
+
startCityDownload(getOfflineMapManager(), cityCode, "startDownload")
|
|
118
161
|
}
|
|
119
162
|
|
|
120
163
|
AsyncFunction("pauseDownload") { cityCode: String ->
|
|
@@ -145,7 +188,7 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
145
188
|
}
|
|
146
189
|
// Android SDK 没有针对单个城市的恢复方法
|
|
147
190
|
// 需要重新调用 downloadByCityCode 来继续下载
|
|
148
|
-
getOfflineMapManager()
|
|
191
|
+
startCityDownload(getOfflineMapManager(), cityCode, "resumeDownload")
|
|
149
192
|
}
|
|
150
193
|
|
|
151
194
|
AsyncFunction("cancelDownload") { cityCode: String ->
|
|
@@ -195,14 +238,17 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
195
238
|
// ==================== 状态查询 ====================
|
|
196
239
|
|
|
197
240
|
AsyncFunction("isMapDownloaded") { cityCode: String ->
|
|
198
|
-
val
|
|
199
|
-
city
|
|
200
|
-
city?.state == OfflineMapStatus.
|
|
241
|
+
val manager = getOfflineMapManager()
|
|
242
|
+
val city = manager.getItemByCityCode(cityCode)
|
|
243
|
+
city?.state == OfflineMapStatus.SUCCESS ||
|
|
244
|
+
city?.let { isDownloadedCity(it, getDownloadedCityKeys(manager)) } == true
|
|
201
245
|
}
|
|
202
246
|
|
|
203
247
|
AsyncFunction("getMapStatus") { cityCode: String ->
|
|
204
|
-
val
|
|
205
|
-
city
|
|
248
|
+
val manager = getOfflineMapManager()
|
|
249
|
+
val city = manager.getItemByCityCode(cityCode)
|
|
250
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
251
|
+
city?.let { convertCityToMap(it, downloadedCityKeys) } ?: Bundle()
|
|
206
252
|
}
|
|
207
253
|
|
|
208
254
|
AsyncFunction("getTotalProgress") {
|
|
@@ -279,7 +325,7 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
279
325
|
}
|
|
280
326
|
}
|
|
281
327
|
cityCodes.forEach { cityCode ->
|
|
282
|
-
getOfflineMapManager()
|
|
328
|
+
startCityDownload(getOfflineMapManager(), cityCode, "batchDownload")
|
|
283
329
|
}
|
|
284
330
|
}
|
|
285
331
|
|
|
@@ -340,17 +386,53 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
340
386
|
downloadingCities.add(cityCode)
|
|
341
387
|
pausedCities.remove(cityCode)
|
|
342
388
|
}
|
|
343
|
-
getOfflineMapManager()
|
|
389
|
+
startCityDownload(getOfflineMapManager(), cityCode, "resumeAllDownloads")
|
|
344
390
|
}
|
|
345
391
|
}
|
|
346
392
|
}
|
|
347
393
|
|
|
348
394
|
// ==================== 辅助方法 ====================
|
|
395
|
+
|
|
396
|
+
private fun findCity(manager: OfflineMapManager, cityCode: String): OfflineMapCity? {
|
|
397
|
+
return manager.getItemByCityCode(cityCode)
|
|
398
|
+
?: manager.offlineMapCityList?.find { it.code == cityCode || it.adcode == cityCode }
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private fun startCityDownload(manager: OfflineMapManager, cityCode: String, action: String) {
|
|
402
|
+
val city = findCity(manager, cityCode)
|
|
403
|
+
?: throw IllegalArgumentException("City not found: $cityCode")
|
|
404
|
+
|
|
405
|
+
logCityBeforeDownload(city, cityCode, action)
|
|
406
|
+
downloadCity(manager, city)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private fun downloadCity(manager: OfflineMapManager, city: OfflineMapCity) {
|
|
410
|
+
try {
|
|
411
|
+
manager.downloadByCityCode(city.code)
|
|
412
|
+
return
|
|
413
|
+
} catch (codeError: Exception) {
|
|
414
|
+
Log.w(logTag, "downloadByCityCode failed cityCode=${city.code} cityName=${city.city} error=${codeError.message}")
|
|
415
|
+
try {
|
|
416
|
+
manager.downloadByCityName(city.city)
|
|
417
|
+
} catch (nameError: Exception) {
|
|
418
|
+
throw IllegalStateException(
|
|
419
|
+
"离线地图下载失败: cityCode=${city.code}, cityName=${city.city}, codeError=${codeError.message}, nameError=${nameError.message}",
|
|
420
|
+
nameError
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private fun logCityBeforeDownload(city: OfflineMapCity?, cityCode: String, action: String) {
|
|
427
|
+
Log.i(logTag, "$action cityCode=$cityCode cityName=${city?.city} code=${city?.code} adcode=${city?.adcode} state=${city?.state} progress=${city?.let { getDownloadProgress(it) }} size=${city?.size} url=${city?.url}")
|
|
428
|
+
}
|
|
349
429
|
|
|
350
430
|
/**
|
|
351
431
|
* 处理下载状态回调
|
|
352
432
|
*/
|
|
353
433
|
private fun handleDownloadStatus(status: Int, completeCode: Int, downName: String?) {
|
|
434
|
+
Log.i(logTag, "onDownload raw status=$status completeCode=$completeCode downName=$downName")
|
|
435
|
+
|
|
354
436
|
if (downName == null) return
|
|
355
437
|
|
|
356
438
|
// downName 可能是城市代码或城市名称,尝试两种方式查找
|
|
@@ -360,10 +442,14 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
360
442
|
city = manager.offlineMapCityList?.find { it.city == downName }
|
|
361
443
|
}
|
|
362
444
|
|
|
363
|
-
if (city == null)
|
|
445
|
+
if (city == null) {
|
|
446
|
+
Log.w(logTag, "onDownload city not found for downName=$downName status=$status completeCode=$completeCode")
|
|
447
|
+
return
|
|
448
|
+
}
|
|
364
449
|
|
|
365
450
|
val cityCode = city.code
|
|
366
451
|
val cityName = city.city
|
|
452
|
+
Log.i(logTag, "onDownload city cityCode=$cityCode cityName=$cityName adcode=${city.adcode} state=${city.state} progress=${getDownloadProgress(city)} size=${city.size} url=${city.url} status=$status completeCode=$completeCode")
|
|
367
453
|
|
|
368
454
|
when (status) {
|
|
369
455
|
OfflineMapStatus.SUCCESS -> {
|
|
@@ -404,6 +490,18 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
404
490
|
putString("error", "解压失败,数据可能有问题")
|
|
405
491
|
})
|
|
406
492
|
}
|
|
493
|
+
|
|
494
|
+
startDownloadFailedCode -> {
|
|
495
|
+
synchronized(lock) {
|
|
496
|
+
downloadingCities.remove(cityCode)
|
|
497
|
+
}
|
|
498
|
+
sendEvent("onDownloadError", Bundle().apply {
|
|
499
|
+
putString("cityCode", cityCode)
|
|
500
|
+
putString("cityName", cityName)
|
|
501
|
+
putString("error", "下载地址为空或离线包不可用")
|
|
502
|
+
putInt("errorCode", status)
|
|
503
|
+
})
|
|
504
|
+
}
|
|
407
505
|
|
|
408
506
|
OfflineMapStatus.EXCEPTION_NETWORK_LOADING -> {
|
|
409
507
|
sendEvent("onDownloadError", Bundle().apply {
|
|
@@ -451,13 +549,37 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
451
549
|
/**
|
|
452
550
|
* 转换城市对象为 Map
|
|
453
551
|
*/
|
|
454
|
-
private fun
|
|
552
|
+
private fun getDownloadedCityKeys(manager: OfflineMapManager): Set<String> {
|
|
553
|
+
return manager.downloadOfflineMapCityList
|
|
554
|
+
?.flatMap { getCityIdentityKeys(it) }
|
|
555
|
+
?.toSet()
|
|
556
|
+
?: emptySet()
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private fun getCityIdentityKeys(city: OfflineMapCity): List<String> {
|
|
560
|
+
return listOfNotNull(
|
|
561
|
+
city.code?.trim()?.takeIf { it.isNotEmpty() }?.let { "code:$it" },
|
|
562
|
+
city.adcode?.trim()?.takeIf { it.isNotEmpty() }?.let { "adcode:$it" },
|
|
563
|
+
city.city?.trim()?.takeIf { it.isNotEmpty() }?.let { "name:$it" }
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private fun isDownloadedCity(city: OfflineMapCity, downloadedCityKeys: Set<String>): Boolean {
|
|
568
|
+
return getCityIdentityKeys(city).any { downloadedCityKeys.contains(it) }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private fun convertCityToMap(
|
|
572
|
+
city: OfflineMapCity,
|
|
573
|
+
downloadedCityKeys: Set<String> = emptySet()
|
|
574
|
+
): Bundle {
|
|
455
575
|
val isPaused = synchronized(lock) { pausedCities.contains(city.code) }
|
|
456
576
|
val isDownloading = synchronized(lock) { downloadingCities.contains(city.code) }
|
|
577
|
+
val isDownloaded = isDownloadedCity(city, downloadedCityKeys)
|
|
457
578
|
|
|
458
579
|
val status = when {
|
|
459
580
|
isPaused -> "paused"
|
|
460
581
|
isDownloading -> "downloading"
|
|
582
|
+
isDownloaded -> "downloaded"
|
|
461
583
|
else -> getStatusString(city.state)
|
|
462
584
|
}
|
|
463
585
|
|
|
@@ -495,10 +617,10 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
495
617
|
private val startDownloadFailedCode: Int by lazy {
|
|
496
618
|
try {
|
|
497
619
|
OfflineMapStatus::class.java.getField("START_DOWNLOAD_FAILED").getInt(null)
|
|
498
|
-
} catch (
|
|
620
|
+
} catch (_: Exception) {
|
|
499
621
|
try {
|
|
500
622
|
OfflineMapStatus::class.java.getField("START_DOWNLOAD_FAILD").getInt(null)
|
|
501
|
-
} catch (
|
|
623
|
+
} catch (_: Exception) {
|
|
502
624
|
-1
|
|
503
625
|
}
|
|
504
626
|
}
|
|
@@ -513,17 +635,17 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
513
635
|
// 尝试标准版的命名 (getcompleteCode)
|
|
514
636
|
val method = obj.javaClass.getMethod("getcompleteCode")
|
|
515
637
|
return method.invoke(obj) as Int
|
|
516
|
-
} catch (
|
|
638
|
+
} catch (_: Exception) {
|
|
517
639
|
try {
|
|
518
640
|
// 尝试修正后的命名 (getCompleteCode) - Google Play 版本可能使用此命名
|
|
519
641
|
val method = obj.javaClass.getMethod("getCompleteCode")
|
|
520
642
|
return method.invoke(obj) as Int
|
|
521
|
-
} catch (
|
|
643
|
+
} catch (_: Exception) {
|
|
522
644
|
// 如果都失败了,尝试直接访问 completeCode 字段
|
|
523
645
|
try {
|
|
524
646
|
val field = obj.javaClass.getField("completeCode")
|
|
525
647
|
return field.getInt(obj)
|
|
526
|
-
} catch (
|
|
648
|
+
} catch (_: Exception) {
|
|
527
649
|
return 0
|
|
528
650
|
}
|
|
529
651
|
}
|
|
@@ -531,8 +653,9 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
531
653
|
}
|
|
532
654
|
|
|
533
655
|
/**
|
|
534
|
-
*
|
|
535
|
-
*
|
|
656
|
+
* 获取状态字符串。
|
|
657
|
+
* CHECKUPDATES / NEW_VERSION 在 10.1.600 冷启动时可能出现在普通城市列表,
|
|
658
|
+
* 不能单独作为已下载依据;已下载状态以 downloadOfflineMapCityList 为准。
|
|
536
659
|
*/
|
|
537
660
|
private fun getStatusString(state: Int): String {
|
|
538
661
|
return when (state) {
|
|
@@ -7,7 +7,6 @@ import android.view.View
|
|
|
7
7
|
import android.view.ViewGroup
|
|
8
8
|
import com.amap.api.maps.AMap
|
|
9
9
|
import com.amap.api.maps.TextureMapView
|
|
10
|
-
import com.amap.api.maps.MapsInitializer
|
|
11
10
|
import com.amap.api.maps.model.LatLng
|
|
12
11
|
import expo.modules.kotlin.AppContext
|
|
13
12
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
@@ -19,6 +18,7 @@ import expo.modules.gaodemap.overlays.*
|
|
|
19
18
|
import androidx.core.graphics.createBitmap
|
|
20
19
|
import androidx.core.view.isVisible
|
|
21
20
|
import androidx.core.graphics.withTranslation
|
|
21
|
+
import androidx.core.view.isGone
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* 高德地图视图组件
|
|
@@ -50,20 +50,20 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
53
|
-
val measuredWidth =
|
|
54
|
-
val measuredHeight =
|
|
53
|
+
val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
|
|
54
|
+
val measuredHeight = MeasureSpec.getSize(heightMeasureSpec)
|
|
55
55
|
|
|
56
56
|
setMeasuredDimension(measuredWidth, measuredHeight)
|
|
57
57
|
|
|
58
58
|
for (i in 0 until childCount) {
|
|
59
59
|
val child = getChildAt(i) ?: continue
|
|
60
|
-
if (child.
|
|
60
|
+
if (child.isGone) {
|
|
61
61
|
continue
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
if (child === mapView) {
|
|
65
|
-
val childWidthSpec =
|
|
66
|
-
val childHeightSpec =
|
|
65
|
+
val childWidthSpec = MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY)
|
|
66
|
+
val childHeightSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
|
|
67
67
|
child.measure(childWidthSpec, childHeightSpec)
|
|
68
68
|
continue
|
|
69
69
|
}
|
|
@@ -71,15 +71,15 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
71
71
|
val lp = child.layoutParams
|
|
72
72
|
val childWidthSpec = when {
|
|
73
73
|
lp?.width != null && lp.width > 0 ->
|
|
74
|
-
|
|
74
|
+
MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY)
|
|
75
75
|
else ->
|
|
76
|
-
|
|
76
|
+
MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.AT_MOST)
|
|
77
77
|
}
|
|
78
78
|
val childHeightSpec = when {
|
|
79
79
|
lp?.height != null && lp.height > 0 ->
|
|
80
|
-
|
|
80
|
+
MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)
|
|
81
81
|
else ->
|
|
82
|
-
|
|
82
|
+
MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST)
|
|
83
83
|
}
|
|
84
84
|
child.measure(childWidthSpec, childHeightSpec)
|
|
85
85
|
}
|
|
@@ -91,7 +91,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
91
91
|
|
|
92
92
|
for (i in 0 until childCount) {
|
|
93
93
|
val child = getChildAt(i) ?: continue
|
|
94
|
-
if (child.
|
|
94
|
+
if (child.isGone) {
|
|
95
95
|
continue
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -150,6 +150,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
150
150
|
// 缓存初始相机位置,等待地图加载完成后设置
|
|
151
151
|
private var pendingCameraPosition: Map<String, Any?>? = null
|
|
152
152
|
private var isMapLoaded = false
|
|
153
|
+
private var hasAppliedInitialCameraPosition = false
|
|
153
154
|
|
|
154
155
|
init {
|
|
155
156
|
try {
|
|
@@ -192,7 +193,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
192
193
|
|
|
193
194
|
val positionToApply = initialCameraPosition ?: pendingCameraPosition
|
|
194
195
|
positionToApply?.let { position ->
|
|
195
|
-
|
|
196
|
+
applyInitialCameraPositionIfNeeded(position)
|
|
196
197
|
pendingCameraPosition = null
|
|
197
198
|
}
|
|
198
199
|
|
|
@@ -352,10 +353,14 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
352
353
|
fun setInitialCameraPosition(position: Map<String, Any?>) {
|
|
353
354
|
initialCameraPosition = position
|
|
354
355
|
|
|
356
|
+
if (hasAppliedInitialCameraPosition) {
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
355
360
|
// 如果地图已加载,立即应用;否则缓存等待地图加载完成
|
|
356
361
|
if (isMapLoaded) {
|
|
357
362
|
mainHandler.post {
|
|
358
|
-
|
|
363
|
+
applyInitialCameraPositionIfNeeded(position)
|
|
359
364
|
}
|
|
360
365
|
} else {
|
|
361
366
|
pendingCameraPosition = position
|
|
@@ -366,8 +371,12 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
366
371
|
* 实际应用相机位置
|
|
367
372
|
* @param position 相机位置配置
|
|
368
373
|
*/
|
|
369
|
-
private fun
|
|
374
|
+
private fun applyInitialCameraPositionIfNeeded(position: Map<String, Any?>) {
|
|
375
|
+
if (hasAppliedInitialCameraPosition) {
|
|
376
|
+
return
|
|
377
|
+
}
|
|
370
378
|
cameraManager.setInitialCameraPosition(position)
|
|
379
|
+
hasAppliedInitialCameraPosition = true
|
|
371
380
|
}
|
|
372
381
|
|
|
373
382
|
// ==================== UI 控件和手势 ====================
|
|
@@ -649,6 +658,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
649
658
|
|
|
650
659
|
// 销毁地图实例
|
|
651
660
|
mapView.onDestroy()
|
|
661
|
+
hasAppliedInitialCameraPosition = false
|
|
652
662
|
} catch (e: Exception) {
|
|
653
663
|
// 静默处理异常,确保销毁流程不会中断
|
|
654
664
|
android.util.Log.e("ExpoGaodeMapView", "Error destroying map", e)
|