expo-gaode-map-navigation 2.0.12 → 2.0.14
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 +29 -16
- package/android/build.gradle +8 -4
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +83 -15
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +13 -3
- package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +36 -39
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +5 -2
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +122 -10
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
- package/build/index.d.ts +26 -126
- package/build/index.d.ts.map +1 -1
- package/build/index.js +11 -612
- package/build/index.js.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.d.ts +5 -0
- package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
- package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/map/components/overlays/HeatMap.js +21 -2
- package/build/map/components/overlays/HeatMap.js.map +1 -1
- package/build/map/index.d.ts +3 -0
- package/build/map/index.d.ts.map +1 -1
- package/build/map/index.js +3 -0
- package/build/map/index.js.map +1 -1
- package/build/map/search/ExpoGaodeMapSearch.types.d.ts +340 -0
- package/build/map/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
- package/build/map/search/ExpoGaodeMapSearch.types.js +19 -0
- package/build/map/search/ExpoGaodeMapSearch.types.js.map +1 -0
- package/build/map/search/ExpoGaodeMapSearchModule.d.ts +74 -0
- package/build/map/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
- package/build/map/search/ExpoGaodeMapSearchModule.js +47 -0
- package/build/map/search/ExpoGaodeMapSearchModule.js.map +1 -0
- package/build/map/search/index.d.ts +156 -0
- package/build/map/search/index.d.ts.map +1 -0
- package/build/map/search/index.js +171 -0
- package/build/map/search/index.js.map +1 -0
- package/build/map/types/map-view.types.d.ts +4 -2
- package/build/map/types/map-view.types.d.ts.map +1 -1
- package/build/map/types/map-view.types.js.map +1 -1
- package/build/map/utils/ErrorHandler.js +11 -11
- package/build/map/utils/ErrorHandler.js.map +1 -1
- package/build/map/utils/OfflineMapManager.d.ts +4 -0
- package/build/map/utils/OfflineMapManager.d.ts.map +1 -1
- package/build/map/utils/OfflineMapManager.js +6 -0
- package/build/map/utils/OfflineMapManager.js.map +1 -1
- package/build/route-geometry.d.ts +13 -0
- package/build/route-geometry.d.ts.map +1 -0
- package/build/route-geometry.js +154 -0
- package/build/route-geometry.js.map +1 -0
- package/build/route-planning.d.ts +21 -0
- package/build/route-planning.d.ts.map +1 -0
- package/build/route-planning.js +67 -0
- package/build/route-planning.js.map +1 -0
- package/build/web-api-fallback.d.ts +5 -0
- package/build/web-api-fallback.d.ts.map +1 -0
- package/build/web-api-fallback.js +160 -0
- package/build/web-api-fallback.js.map +1 -0
- package/build/web-route-following.d.ts +3 -0
- package/build/web-route-following.d.ts.map +1 -0
- package/build/web-route-following.js +178 -0
- package/build/web-route-following.js.map +1 -0
- package/expo-module.config.json +4 -2
- package/ios/ExpoGaodeMapNaviView.swift +16 -17
- package/ios/ExpoGaodeMapNavigation.podspec +2 -1
- package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
- package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
- package/ios/map/modules/LocationManager.swift +9 -3
- package/ios/map/overlays/PolylineView.swift +6 -12
- package/package.json +2 -2
- package/plugin/build/withGaodeMap.js +12 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/RouteCalculator.kt +0 -173
package/README.md
CHANGED
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
7
|
- 🗺️ **地图渲染**:内置完整地图能力,支持 Marker、Polyline、Polygon、Circle、Cluster、HeatMap 等覆盖物。
|
|
8
|
+
- 🔍 **原生搜索**:内置 POI 搜索、周边搜索、沿途搜索、输入提示、逆地理编码等搜索能力。
|
|
8
9
|
- 🚗 **多模式路径规划**:支持驾车、步行、骑行、电动车、货车、摩托车等多种出行方式。
|
|
9
|
-
- 🧭 **实时导航 UI**:提供 `
|
|
10
|
+
- 🧭 **实时导航 UI**:提供 `ExpoGaodeMapNaviView` 官方嵌入视图,并暴露完整事件与原生参数,方便你自行定制导航界面。
|
|
10
11
|
- 🛣️ **独立路径规划**:支持“先算路、再导航”的高级模式,可实现多路线对比与选择。
|
|
11
12
|
- ⚙️ **策略丰富**:支持速度优先、避让拥堵、少收费、不走高速等多种算路策略。
|
|
12
13
|
- ✅ **开箱即用**:封装了 Android/iOS 原生导航 SDK,统一 JS 接口。
|
|
13
14
|
|
|
14
15
|
## 安装
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
本模块已包含地图、搜索与导航能力,**不需要**、也不应同时安装 `expo-gaode-map` 或 `expo-gaode-map-search`。
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
20
|
# bun
|
|
@@ -29,6 +30,10 @@ npm install expo-gaode-map-navigation
|
|
|
29
30
|
**⚠️ 重要提示:**
|
|
30
31
|
如果项目中已安装 `expo-gaode-map`,请务必先卸载,否则会导致 Android 端二进制冲突(`3dmap` vs `navi-3dmap`)。`expo-gaode-map` 和 `expo-gaode-map-navigation` 由于 SDK 冲突不能同时安装,二选一使用。
|
|
31
32
|
|
|
33
|
+
`expo-gaode-map-search` 的独立集成只维护到 `2.2.33`。从 `2.0.13` 开始,搜索能力随 `expo-gaode-map` / `expo-gaode-map-navigation` 一起维护;在导航包中请直接从 `expo-gaode-map-navigation` 导入搜索 API。
|
|
34
|
+
|
|
35
|
+
高德官方 Android SDK 在 `10.0.700` 之后将远程依赖由“地图 + 定位”调整为“地图 + 定位 + 搜索”,依赖地址从 `com.amap.api:3dmap:latest.integration` 调整为 `com.amap.api:3dmap-location-search:latest.integration`。继续单独维护 search 模块会带来重复合包和依赖冲突成本,因此搜索能力改为随 core / navigation 一起维护。
|
|
36
|
+
|
|
32
37
|
> ⚠️ **版本兼容性说明**:
|
|
33
38
|
> - 如果你的项目使用 **Expo SDK 54 及以上**,请安装 默认的 版本。
|
|
34
39
|
> - 如果你的项目使用 **Expo SDK 53 及以下**(如 50, 51, 52, 53),请使用 **V1** 版本(Tag: `v1`)。
|
|
@@ -75,8 +80,8 @@ npx expo run:ios
|
|
|
75
80
|
- `enableBackgroundAudio` 仅 iOS 生效(默认随 `enableBackgroundLocation` 自动开启),用于注入 `UIBackgroundModes: audio`,保障后台导航语音持续播报。
|
|
76
81
|
- `enableIOSLiveActivity` 仅 iOS 生效,用于注入 `NSSupportsLiveActivities`。
|
|
77
82
|
- `enableIOSLiveActivityFrequentUpdates` 仅 iOS 生效,用于注入 `NSSupportsLiveActivitiesFrequentUpdates`。
|
|
78
|
-
- 运行时还需要在 `
|
|
79
|
-
- iOS 运行时还需要在 `
|
|
83
|
+
- 运行时还需要在 `ExpoGaodeMapNaviView` 里显式传 `androidBackgroundNavigationNotificationEnabled={true}` 才会在应用退到后台后显示导航常驻通知。
|
|
84
|
+
- iOS 运行时还需要在 `ExpoGaodeMapNaviView` 里显式传 `iosLiveActivityEnabled={true}` 才会持续更新 Live Activity。
|
|
80
85
|
|
|
81
86
|
## 示例工程
|
|
82
87
|
|
|
@@ -84,7 +89,7 @@ npx expo run:ios
|
|
|
84
89
|
|
|
85
90
|
推荐场景:
|
|
86
91
|
|
|
87
|
-
- 调试 `
|
|
92
|
+
- 调试 `ExpoGaodeMapNaviView` 与示例工程里的自定义 HUD / 车道 HUD / 路况光柱
|
|
88
93
|
- 对比官方黑盒页、官方嵌入式页、自绘嵌入式页
|
|
89
94
|
- 验证独立算路、多路线选择、近似跟线导航
|
|
90
95
|
|
|
@@ -165,15 +170,16 @@ export default function BasicMapScreen() {
|
|
|
165
170
|
|
|
166
171
|
### 2. 嵌入导航视图
|
|
167
172
|
|
|
168
|
-
使用 `
|
|
173
|
+
使用 `ExpoGaodeMapNaviView` 组件直接嵌入导航界面:
|
|
169
174
|
|
|
170
175
|
```tsx
|
|
171
176
|
import React, { useEffect, useRef } from 'react';
|
|
172
177
|
import { View } from 'react-native';
|
|
173
|
-
import {
|
|
178
|
+
import { ExpoGaodeMapNaviView } from 'expo-gaode-map-navigation';
|
|
179
|
+
import type { ExpoGaodeMapNaviViewRef } from 'expo-gaode-map-navigation';
|
|
174
180
|
|
|
175
181
|
export default function NavigationScreen() {
|
|
176
|
-
const naviRef = useRef<
|
|
182
|
+
const naviRef = useRef<ExpoGaodeMapNaviViewRef>(null);
|
|
177
183
|
|
|
178
184
|
useEffect(() => {
|
|
179
185
|
// 延迟 1 秒后开始导航
|
|
@@ -191,7 +197,7 @@ export default function NavigationScreen() {
|
|
|
191
197
|
|
|
192
198
|
return (
|
|
193
199
|
<View style={{ flex: 1 }}>
|
|
194
|
-
<
|
|
200
|
+
<ExpoGaodeMapNaviView
|
|
195
201
|
ref={naviRef}
|
|
196
202
|
style={{ flex: 1 }}
|
|
197
203
|
showCamera={true} // 显示摄像头
|
|
@@ -206,17 +212,17 @@ export default function NavigationScreen() {
|
|
|
206
212
|
|
|
207
213
|
### 3. 自定义嵌入式导航 UI
|
|
208
214
|
|
|
209
|
-
如果你要做“嵌入在自己页面里的导航页”,库本身提供的是底层 `
|
|
215
|
+
如果你要做“嵌入在自己页面里的导航页”,库本身提供的是底层 `ExpoGaodeMapNaviView`、导航事件和原生参数;完整的自定义 HUD / 车道 HUD / 路况光柱参考实现,已经迁移到仓库内的 [`example-navigation`](/Volumes/xinxin/expo-gaode-map/example-navigation/README.md)。
|
|
210
216
|
|
|
211
217
|
建议做法:
|
|
212
218
|
|
|
213
|
-
- 用 `
|
|
219
|
+
- 用 `ExpoGaodeMapNaviView` 负责底层导航地图、语音、车道事件、路况事件、路口大图事件
|
|
214
220
|
- 用 `onNaviInfoUpdate`、`onLaneInfoUpdate`、`onTrafficStatusesUpdate`、`onNaviVisualStateChange` 在业务侧自绘 HUD
|
|
215
221
|
- 直接参考 `example-navigation/lib/navigation-ui/EmbeddedNaviView.tsx` 及配套 UI 文件,按你的产品需求裁剪
|
|
216
222
|
|
|
217
223
|
注意:
|
|
218
224
|
|
|
219
|
-
- Android 官方嵌入式 `
|
|
225
|
+
- Android 官方嵌入式 `ExpoGaodeMapNaviView` 在部分 React Native / Expo 宿主中,顶部信息区、车道条、路口大图联动效果可能与高德官方 Demo 不完全一致
|
|
220
226
|
- 如果你要验证官方嵌入式 UI 本身,请直接跑 `example-navigation` 里的 `official-embedded` 示例页
|
|
221
227
|
- 如果你要交付稳定的嵌入式导航页,建议以示例工程里的“自定义 UI 导航界面”作为起点
|
|
222
228
|
|
|
@@ -489,6 +495,13 @@ const result = await calculateTransitRoute({
|
|
|
489
495
|
- 可共享的范围仅限纯 TS 的 route / AOI 数据适配工具、文档和测试思路
|
|
490
496
|
- 原生地图桥接、overlay 宿主逻辑、MapView facade 不会和核心包合并
|
|
491
497
|
|
|
498
|
+
### 导入入口
|
|
499
|
+
|
|
500
|
+
- 运行时能力优先使用命名导出,例如 `calculateRoute`、`ExpoGaodeMapNaviView`、`openOfficialNaviPage`
|
|
501
|
+
- 类型请用 `import type`,例如 `RouteOptions`、`FollowWebPlannedRouteResult`
|
|
502
|
+
- 默认导出保留给需要整包挂载的场景,内容与常用路径规划和导航函数一致
|
|
503
|
+
- `NaviView` / `NaviViewRef` 仍作为兼容别名保留,新代码建议使用 `ExpoGaodeMapNaviView` / `ExpoGaodeMapNaviViewRef`
|
|
504
|
+
|
|
492
505
|
## API 参考
|
|
493
506
|
|
|
494
507
|
### DriveStrategy (驾车策略)
|
|
@@ -522,7 +535,7 @@ const result = await calculateTransitRoute({
|
|
|
522
535
|
- 基于 `onTrafficStatusesUpdate` 的自绘路况光柱
|
|
523
536
|
- “全览 / 锁车”与路况开关等浮层控制按钮
|
|
524
537
|
|
|
525
|
-
###
|
|
538
|
+
### ExpoGaodeMapNaviView Props
|
|
526
539
|
|
|
527
540
|
| 属性 | 类型 | 说明 |
|
|
528
541
|
|---|---|---|
|
|
@@ -564,7 +577,7 @@ const result = await calculateTransitRoute({
|
|
|
564
577
|
| `onNaviInfoUpdate` | function | 导航信息更新(剩余距离、时间等) |
|
|
565
578
|
| `onLaneInfoUpdate` | function | Android / iOS 车道信息更新,用于自绘车道 HUD |
|
|
566
579
|
|
|
567
|
-
###
|
|
580
|
+
### ExpoGaodeMapNaviView UI 能力清单
|
|
568
581
|
|
|
569
582
|
已开放且两端都有实现:
|
|
570
583
|
|
|
@@ -636,14 +649,14 @@ const result = await calculateTransitRoute({
|
|
|
636
649
|
2. **Web API**:如果需要更灵活的 HTTP 算路(如公交跨城规划、Web端展示),推荐配合 `expo-gaode-map-web-api` 使用。
|
|
637
650
|
3. **权限**:使用导航功能前,请确保应用已获取定位权限(`ACCESS_FINE_LOCATION`)。
|
|
638
651
|
4. **Android 状态栏兼容性**:`naviStatusBarEnabled` 依赖高德 Android 导航 SDK 某些版本才提供的 `AMapNaviViewOptions.setNaviStatusBarEnabled(...)`。当前封装已做兼容处理:若宿主工程解析到的 SDK 不包含该方法,则不会再编译失败,而是在运行时跳过该设置并输出 warning。此时该 prop 在 Android 上等价于 no-op。
|
|
639
|
-
5. **嵌入式 UI 边界**:库导出的是底层 `
|
|
652
|
+
5. **嵌入式 UI 边界**:库导出的是底层 `ExpoGaodeMapNaviView` 能力;完整自定义导航界面请参考 `example-navigation` 里的示例实现,它也不是高德官方黑盒导航页的 UI 替代品。
|
|
640
653
|
|
|
641
654
|
|
|
642
655
|
## 📚 文档与资源
|
|
643
656
|
|
|
644
657
|
- [在线文档](https://tomwq.github.io/expo-gaode-map/api/navigation.html)
|
|
645
658
|
- [GitHub 仓库](https://github.com/TomWq/expo-gaode-map/packages/navigation)
|
|
646
|
-
- [
|
|
659
|
+
- [导航示例工程](../../example-navigation)
|
|
647
660
|
- [高德地图开放平台](https://lbs.amap.com/)
|
|
648
661
|
- [Expo Modules API](https://docs.expo.dev/modules/overview/)
|
|
649
662
|
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'expo.modules.gaodemap.navigation'
|
|
4
|
-
version = '2.0.
|
|
4
|
+
version = '2.0.13'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -34,7 +34,7 @@ android {
|
|
|
34
34
|
namespace "expo.modules.gaodemap.navigation"
|
|
35
35
|
defaultConfig {
|
|
36
36
|
versionCode 1
|
|
37
|
-
versionName "2.0.
|
|
37
|
+
versionName "2.0.13"
|
|
38
38
|
}
|
|
39
39
|
lintOptions {
|
|
40
40
|
abortOnError false
|
|
@@ -60,10 +60,14 @@ dependencies {
|
|
|
60
60
|
implementation files(sdkFile)
|
|
61
61
|
} else {
|
|
62
62
|
println "ExpoGaodeMapNavigation: Custom SDK path configured but file not found, falling back to Maven dependency."
|
|
63
|
-
implementation 'com.amap.api:navi-3dmap:
|
|
63
|
+
implementation 'com.amap.api:navi-3dmap-location-search:10.1.600_3dmap10.1.600_loc6.5.1_sea9.7.4'
|
|
64
|
+
|
|
64
65
|
}
|
|
65
66
|
} else {
|
|
66
|
-
|
|
67
|
+
// 10.1.600 is the last Android 3D map SDK line that supports offline
|
|
68
|
+
// map downloads without the separate 11.x offline-download permission.
|
|
69
|
+
implementation 'com.amap.api:navi-3dmap-location-search:10.1.600_3dmap10.1.600_loc6.5.1_sea9.7.4'
|
|
70
|
+
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
// Live Update APIs on NotificationCompat.Builder
|
|
@@ -21,4 +21,12 @@
|
|
|
21
21
|
<!-- 蓝牙权限 - 用于室内定位 (可选) -->
|
|
22
22
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
23
23
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
24
|
+
|
|
25
|
+
<application>
|
|
26
|
+
<!-- 高德 3D 地图 SDK 官方离线地图 UI 组件 -->
|
|
27
|
+
<activity
|
|
28
|
+
android:name="com.amap.api.maps.offlinemap.OfflineMapActivity"
|
|
29
|
+
android:screenOrientation="portrait"
|
|
30
|
+
android:exported="false" />
|
|
31
|
+
</application>
|
|
24
32
|
</manifest>
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
package expo.modules.gaodemap.map
|
|
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
|
|
6
9
|
import android.util.Log
|
|
10
|
+
import com.amap.api.maps.offlinemap.OfflineMapActivity
|
|
7
11
|
import com.amap.api.maps.offlinemap.OfflineMapCity
|
|
8
12
|
import com.amap.api.maps.offlinemap.OfflineMapManager
|
|
9
13
|
import com.amap.api.maps.offlinemap.OfflineMapProvince
|
|
10
14
|
import com.amap.api.maps.offlinemap.OfflineMapStatus
|
|
11
15
|
import expo.modules.gaodemap.map.modules.SDKInitializer
|
|
16
|
+
import expo.modules.kotlin.exception.CodedException
|
|
12
17
|
import expo.modules.kotlin.modules.Module
|
|
13
18
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
14
19
|
|
|
@@ -92,12 +97,18 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
92
97
|
offlineMapManager = null
|
|
93
98
|
downloadingCities.clear()
|
|
94
99
|
}
|
|
100
|
+
|
|
101
|
+
AsyncFunction("openOfflineMapUI") {
|
|
102
|
+
openOfflineMapUI()
|
|
103
|
+
}
|
|
95
104
|
|
|
96
105
|
// ==================== 地图列表管理 ====================
|
|
97
106
|
|
|
98
107
|
AsyncFunction("getAvailableCities") {
|
|
99
|
-
val
|
|
100
|
-
cities
|
|
108
|
+
val manager = getOfflineMapManager()
|
|
109
|
+
val cities = manager?.offlineMapCityList ?: emptyList()
|
|
110
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
111
|
+
cities.map { city -> convertCityToMap(city, downloadedCityKeys) }
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
AsyncFunction("getAvailableProvinces") {
|
|
@@ -106,15 +117,19 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
AsyncFunction("getCitiesByProvince") { provinceCode: String ->
|
|
109
|
-
val
|
|
110
|
-
|
|
120
|
+
val manager = getOfflineMapManager()
|
|
121
|
+
val province = manager?.offlineMapProvinceList?.find {
|
|
122
|
+
it.provinceCode == provinceCode
|
|
111
123
|
}
|
|
112
|
-
|
|
124
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
125
|
+
province?.cityList?.map { city -> convertCityToMap(city, downloadedCityKeys) } ?: emptyList()
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
AsyncFunction("getDownloadedMaps") {
|
|
116
|
-
val
|
|
117
|
-
cities
|
|
129
|
+
val manager = getOfflineMapManager()
|
|
130
|
+
val cities = manager?.downloadOfflineMapCityList ?: emptyList()
|
|
131
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
132
|
+
cities.map { city -> convertCityToMap(city, downloadedCityKeys) }
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
// ==================== 下载管理 ====================
|
|
@@ -208,14 +223,17 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
208
223
|
// ==================== 状态查询 ====================
|
|
209
224
|
|
|
210
225
|
AsyncFunction("isMapDownloaded") { cityCode: String ->
|
|
211
|
-
val
|
|
212
|
-
city
|
|
213
|
-
city?.state == OfflineMapStatus.
|
|
226
|
+
val manager = getOfflineMapManager()
|
|
227
|
+
val city = manager?.getItemByCityCode(cityCode)
|
|
228
|
+
city?.state == OfflineMapStatus.SUCCESS ||
|
|
229
|
+
city?.let { isDownloadedCity(it, getDownloadedCityKeys(manager)) } == true
|
|
214
230
|
}
|
|
215
231
|
|
|
216
232
|
AsyncFunction("getMapStatus") { cityCode: String ->
|
|
217
|
-
val
|
|
218
|
-
city
|
|
233
|
+
val manager = getOfflineMapManager()
|
|
234
|
+
val city = manager?.getItemByCityCode(cityCode)
|
|
235
|
+
val downloadedCityKeys = getDownloadedCityKeys(manager)
|
|
236
|
+
city?.let { convertCityToMap(it, downloadedCityKeys) } ?: Bundle()
|
|
219
237
|
}
|
|
220
238
|
|
|
221
239
|
AsyncFunction("getTotalProgress") {
|
|
@@ -463,13 +481,37 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
463
481
|
/**
|
|
464
482
|
* 转换城市对象为 Map
|
|
465
483
|
*/
|
|
466
|
-
private fun
|
|
484
|
+
private fun getDownloadedCityKeys(manager: OfflineMapManager?): Set<String> {
|
|
485
|
+
return manager?.downloadOfflineMapCityList
|
|
486
|
+
?.flatMap { getCityIdentityKeys(it) }
|
|
487
|
+
?.toSet()
|
|
488
|
+
?: emptySet()
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private fun getCityIdentityKeys(city: OfflineMapCity): List<String> {
|
|
492
|
+
return listOfNotNull(
|
|
493
|
+
city.code?.trim()?.takeIf { it.isNotEmpty() }?.let { "code:$it" },
|
|
494
|
+
city.adcode?.trim()?.takeIf { it.isNotEmpty() }?.let { "adcode:$it" },
|
|
495
|
+
city.city?.trim()?.takeIf { it.isNotEmpty() }?.let { "name:$it" }
|
|
496
|
+
)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private fun isDownloadedCity(city: OfflineMapCity, downloadedCityKeys: Set<String>): Boolean {
|
|
500
|
+
return getCityIdentityKeys(city).any { downloadedCityKeys.contains(it) }
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private fun convertCityToMap(
|
|
504
|
+
city: OfflineMapCity,
|
|
505
|
+
downloadedCityKeys: Set<String> = emptySet()
|
|
506
|
+
): Bundle {
|
|
467
507
|
val isPaused = synchronized(lock) { pausedCities.contains(city.code) }
|
|
468
508
|
val isDownloading = synchronized(lock) { downloadingCities.contains(city.code) }
|
|
509
|
+
val isDownloaded = isDownloadedCity(city, downloadedCityKeys)
|
|
469
510
|
|
|
470
511
|
val status = when {
|
|
471
512
|
isPaused -> "paused"
|
|
472
513
|
isDownloading -> "downloading"
|
|
514
|
+
isDownloaded -> "downloaded"
|
|
473
515
|
else -> getStatusString(city.state)
|
|
474
516
|
}
|
|
475
517
|
|
|
@@ -501,8 +543,9 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
501
543
|
}
|
|
502
544
|
|
|
503
545
|
/**
|
|
504
|
-
*
|
|
505
|
-
*
|
|
546
|
+
* 获取状态字符串。
|
|
547
|
+
* CHECKUPDATES / NEW_VERSION 在 10.1.600 冷启动时可能出现在普通城市列表,
|
|
548
|
+
* 不能单独作为已下载依据;已下载状态以 downloadOfflineMapCityList 为准。
|
|
506
549
|
*/
|
|
507
550
|
private fun getStatusString(state: Int): String {
|
|
508
551
|
return when (state) {
|
|
@@ -558,4 +601,29 @@ class ExpoGaodeMapOfflineModule : Module() {
|
|
|
558
601
|
null
|
|
559
602
|
}
|
|
560
603
|
}
|
|
604
|
+
|
|
605
|
+
private fun getActivityLaunchContext(): Context {
|
|
606
|
+
return appContext.currentActivity
|
|
607
|
+
?: appContext.reactContext
|
|
608
|
+
?: throw CodedException("NO_CONTEXT", "React context not available", null)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private fun openOfflineMapUI() {
|
|
612
|
+
val context = getActivityLaunchContext()
|
|
613
|
+
SDKInitializer.restorePersistedState(context.applicationContext)
|
|
614
|
+
|
|
615
|
+
if (!SDKInitializer.isPrivacyReady()) {
|
|
616
|
+
throw CodedException(
|
|
617
|
+
"PRIVACY_NOT_AGREED",
|
|
618
|
+
"隐私协议未完成确认,请先调用 setPrivacyConfig",
|
|
619
|
+
null
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
val intent = Intent(context, OfflineMapActivity::class.java)
|
|
624
|
+
if (context !is Activity) {
|
|
625
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
626
|
+
}
|
|
627
|
+
context.startActivity(intent)
|
|
628
|
+
}
|
|
561
629
|
}
|
|
@@ -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
|
|
|
@@ -351,10 +352,14 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
351
352
|
fun setInitialCameraPosition(position: Map<String, Any?>) {
|
|
352
353
|
initialCameraPosition = position
|
|
353
354
|
|
|
355
|
+
if (hasAppliedInitialCameraPosition) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
354
359
|
// 如果地图已加载,立即应用;否则缓存等待地图加载完成
|
|
355
360
|
if (isMapLoaded) {
|
|
356
361
|
mainHandler.post {
|
|
357
|
-
|
|
362
|
+
applyInitialCameraPositionIfNeeded(position)
|
|
358
363
|
}
|
|
359
364
|
} else {
|
|
360
365
|
pendingCameraPosition = position
|
|
@@ -365,8 +370,12 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
365
370
|
* 实际应用相机位置
|
|
366
371
|
* @param position 相机位置配置
|
|
367
372
|
*/
|
|
368
|
-
private fun
|
|
373
|
+
private fun applyInitialCameraPositionIfNeeded(position: Map<String, Any?>) {
|
|
374
|
+
if (hasAppliedInitialCameraPosition) {
|
|
375
|
+
return
|
|
376
|
+
}
|
|
369
377
|
cameraManager.setInitialCameraPosition(position)
|
|
378
|
+
hasAppliedInitialCameraPosition = true
|
|
370
379
|
}
|
|
371
380
|
|
|
372
381
|
// ==================== UI 控件和手势 ====================
|
|
@@ -643,6 +652,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
643
652
|
|
|
644
653
|
// 销毁地图实例
|
|
645
654
|
mapView.onDestroy()
|
|
655
|
+
hasAppliedInitialCameraPosition = false
|
|
646
656
|
} catch (e: Exception) {
|
|
647
657
|
// 静默处理异常,确保销毁流程不会中断
|
|
648
658
|
android.util.Log.e("ExpoGaodeMapView", "Error destroying map", e)
|
|
@@ -87,36 +87,46 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
|
|
|
87
87
|
// ==================== 图层显示 ====================
|
|
88
88
|
|
|
89
89
|
private var currentLocationStyle: MyLocationStyle? = null
|
|
90
|
+
private var currentFollowUserLocation: Boolean = false
|
|
91
|
+
private var explicitLocationType: Int? = null
|
|
92
|
+
|
|
93
|
+
private fun parseLocationType(locationType: String): Int? {
|
|
94
|
+
return when (locationType) {
|
|
95
|
+
"SHOW" -> MyLocationStyle.LOCATION_TYPE_SHOW
|
|
96
|
+
"LOCATE" -> MyLocationStyle.LOCATION_TYPE_LOCATE
|
|
97
|
+
"FOLLOW" -> MyLocationStyle.LOCATION_TYPE_FOLLOW
|
|
98
|
+
"MAP_ROTATE" -> MyLocationStyle.LOCATION_TYPE_MAP_ROTATE
|
|
99
|
+
"LOCATION_ROTATE" -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE
|
|
100
|
+
"LOCATION_ROTATE_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER
|
|
101
|
+
"FOLLOW_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER
|
|
102
|
+
"MAP_ROTATE_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_MAP_ROTATE_NO_CENTER
|
|
103
|
+
else -> null
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun resolveLocationType(followUserLocation: Boolean): Int {
|
|
108
|
+
explicitLocationType?.let { return it }
|
|
109
|
+
return if (followUserLocation) {
|
|
110
|
+
MyLocationStyle.LOCATION_TYPE_FOLLOW
|
|
111
|
+
} else {
|
|
112
|
+
MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER
|
|
113
|
+
}
|
|
114
|
+
}
|
|
90
115
|
|
|
91
116
|
/**
|
|
92
117
|
* 设置是否显示用户位置
|
|
93
118
|
*/
|
|
94
119
|
fun setShowsUserLocation(show: Boolean, followUserLocation: Boolean = false) {
|
|
120
|
+
currentFollowUserLocation = followUserLocation
|
|
95
121
|
if (show) {
|
|
96
|
-
// 创建默认的定位样式
|
|
97
122
|
if (currentLocationStyle == null) {
|
|
98
|
-
currentLocationStyle = MyLocationStyle()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
myLocationType(locationType)
|
|
106
|
-
interval(2000) // 2秒定位一次
|
|
107
|
-
showMyLocation(true)
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
// 更新定位类型
|
|
111
|
-
val locationType = if (followUserLocation) {
|
|
112
|
-
MyLocationStyle.LOCATION_TYPE_FOLLOW
|
|
113
|
-
} else {
|
|
114
|
-
MyLocationStyle.LOCATION_TYPE_SHOW
|
|
115
|
-
}
|
|
116
|
-
currentLocationStyle?.apply {
|
|
117
|
-
myLocationType(locationType)
|
|
118
|
-
interval(2000)
|
|
119
|
-
}
|
|
123
|
+
currentLocationStyle = MyLocationStyle()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
currentLocationStyle?.apply {
|
|
127
|
+
myLocationType(resolveLocationType(followUserLocation))
|
|
128
|
+
interval(2000) // 2秒定位一次
|
|
129
|
+
showMyLocation(true)
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
// 监听定位变化(用于通知 React Native)
|
|
@@ -171,9 +181,10 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
|
|
|
171
181
|
*/
|
|
172
182
|
@SuppressLint("DiscouragedApi")
|
|
173
183
|
fun setUserLocationRepresentation(config: Map<String, Any>) {
|
|
184
|
+
explicitLocationType = (config["locationType"] as? String)?.let(::parseLocationType)
|
|
185
|
+
|
|
174
186
|
if (currentLocationStyle == null) {
|
|
175
187
|
currentLocationStyle = MyLocationStyle().apply {
|
|
176
|
-
myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE)
|
|
177
188
|
interval(2000)
|
|
178
189
|
showMyLocation(true)
|
|
179
190
|
}
|
|
@@ -182,21 +193,7 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
|
|
|
182
193
|
val style = currentLocationStyle!!
|
|
183
194
|
|
|
184
195
|
// 定位蓝点展现模式 (locationType) - Android 支持8种模式
|
|
185
|
-
|
|
186
|
-
if (locationType != null) {
|
|
187
|
-
val locationTypeValue = when (locationType) {
|
|
188
|
-
"SHOW" -> MyLocationStyle.LOCATION_TYPE_SHOW
|
|
189
|
-
"LOCATE" -> MyLocationStyle.LOCATION_TYPE_LOCATE
|
|
190
|
-
"FOLLOW" -> MyLocationStyle.LOCATION_TYPE_FOLLOW
|
|
191
|
-
"MAP_ROTATE" -> MyLocationStyle.LOCATION_TYPE_MAP_ROTATE
|
|
192
|
-
"LOCATION_ROTATE" -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE
|
|
193
|
-
"LOCATION_ROTATE_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER
|
|
194
|
-
"FOLLOW_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER
|
|
195
|
-
"MAP_ROTATE_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_MAP_ROTATE_NO_CENTER
|
|
196
|
-
else -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE // 默认值
|
|
197
|
-
}
|
|
198
|
-
style.myLocationType(locationTypeValue)
|
|
199
|
-
}
|
|
196
|
+
style.myLocationType(resolveLocationType(currentFollowUserLocation))
|
|
200
197
|
|
|
201
198
|
// 是否显示定位蓝点 (showMyLocation) - Android 5.1.0+ 支持
|
|
202
199
|
// 注意:这个属性在 iOS 中没有对应项,是 Android 特有的
|
|
@@ -141,8 +141,11 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
141
141
|
* 设置最小聚合数量
|
|
142
142
|
*/
|
|
143
143
|
fun setMinClusterSize(size: Int) {
|
|
144
|
-
|
|
145
|
-
minClusterSize
|
|
144
|
+
val nextSize = size.coerceAtLeast(1)
|
|
145
|
+
if (minClusterSize != nextSize) {
|
|
146
|
+
minClusterSize = nextSize
|
|
147
|
+
styleChanged = true
|
|
148
|
+
}
|
|
146
149
|
updateClusters()
|
|
147
150
|
}
|
|
148
151
|
|