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
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import ExpoGaodeMapNavigationModule from './ExpoGaodeMapNavigationModule';
|
|
2
|
+
import { buildAnchorWaypointsFromWebRoute, calculatePathLengthSafe, dedupeAdjacentPoints, getDistanceToPathSafe, normalizeWebRoutePolyline, samplePolyline } from './route-geometry';
|
|
3
|
+
function extractRoutePolyline(route) {
|
|
4
|
+
if (Array.isArray(route.polyline) && route.polyline.length > 0) {
|
|
5
|
+
return route.polyline;
|
|
6
|
+
}
|
|
7
|
+
const segments = Array.isArray(route.segments) ? route.segments : [];
|
|
8
|
+
if (segments.length > 0) {
|
|
9
|
+
return dedupeAdjacentPoints(segments.flatMap((segment) => segment.polyline ?? []));
|
|
10
|
+
}
|
|
11
|
+
const steps = Array.isArray(route.steps) ? route.steps : [];
|
|
12
|
+
if (steps.length > 0) {
|
|
13
|
+
return dedupeAdjacentPoints(steps.flatMap((step) => step.polyline ?? []));
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
function resolveIndependentRouteId(result, route, routeIndex) {
|
|
18
|
+
if (typeof route.id === 'number') {
|
|
19
|
+
return route.id;
|
|
20
|
+
}
|
|
21
|
+
if (typeof route.routeId === 'number') {
|
|
22
|
+
return route.routeId;
|
|
23
|
+
}
|
|
24
|
+
return result.routeIds?.[routeIndex];
|
|
25
|
+
}
|
|
26
|
+
function scoreIndependentRouteAgainstWebPolyline(result, route, routeIndex, webPolyline, anchorWaypoints, thresholdMeters) {
|
|
27
|
+
// 评分思路很简单:看原生独立路线与 Web 折线有多接近,
|
|
28
|
+
// 再把平均偏差、最大偏差、漏掉的锚点一起折成一个排序分数。
|
|
29
|
+
const nativePolyline = extractRoutePolyline(route);
|
|
30
|
+
if (nativePolyline.length === 0 || webPolyline.length === 0) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const sampledNativePoints = samplePolyline(nativePolyline);
|
|
34
|
+
const pointDistances = sampledNativePoints.map((point) => getDistanceToPathSafe(webPolyline, point));
|
|
35
|
+
const averageDeviationMeters = pointDistances.reduce((total, distance) => total + distance, 0) / pointDistances.length;
|
|
36
|
+
const maxDeviationMeters = Math.max(...pointDistances);
|
|
37
|
+
const missedAnchorCount = anchorWaypoints.reduce((count, point) => (getDistanceToPathSafe(nativePolyline, point) > thresholdMeters ? count + 1 : count), 0);
|
|
38
|
+
return {
|
|
39
|
+
routeId: resolveIndependentRouteId(result, route, routeIndex),
|
|
40
|
+
routeIndex,
|
|
41
|
+
averageDeviationMeters,
|
|
42
|
+
maxDeviationMeters,
|
|
43
|
+
missedAnchorCount,
|
|
44
|
+
score: averageDeviationMeters +
|
|
45
|
+
maxDeviationMeters * 0.35 +
|
|
46
|
+
missedAnchorCount * thresholdMeters * 0.5,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function evaluateIndependentResultAgainstWebRoute(independentResult, webPolyline, anchorWaypoints, maxDeviationMeters) {
|
|
50
|
+
// 先给每条原生候选路线打分,再把最接近的一条拿出来判断是 matched / approximate / preview_only。
|
|
51
|
+
const candidateMatches = independentResult.routes
|
|
52
|
+
.map((route, routeIndex) => scoreIndependentRouteAgainstWebPolyline(independentResult, route, routeIndex, webPolyline, anchorWaypoints, maxDeviationMeters))
|
|
53
|
+
.filter((candidate) => candidate !== null)
|
|
54
|
+
.sort((routeA, routeB) => routeA.score - routeB.score);
|
|
55
|
+
const bestMatch = candidateMatches[0];
|
|
56
|
+
const selectedRoute = bestMatch
|
|
57
|
+
? independentResult.routes[bestMatch.routeIndex]
|
|
58
|
+
: undefined;
|
|
59
|
+
const nativePolyline = selectedRoute ? extractRoutePolyline(selectedRoute) : [];
|
|
60
|
+
let mode = 'preview_only';
|
|
61
|
+
let reason = '未找到足够接近 Web 规划线的原生路线';
|
|
62
|
+
if (bestMatch) {
|
|
63
|
+
if (bestMatch.averageDeviationMeters <= maxDeviationMeters / 2 &&
|
|
64
|
+
bestMatch.maxDeviationMeters <= maxDeviationMeters &&
|
|
65
|
+
bestMatch.missedAnchorCount === 0) {
|
|
66
|
+
mode = 'matched';
|
|
67
|
+
reason = '原生路线与 Web 规划线高度接近,可直接按近似结果导航';
|
|
68
|
+
}
|
|
69
|
+
else if (bestMatch.averageDeviationMeters <= maxDeviationMeters &&
|
|
70
|
+
bestMatch.maxDeviationMeters <= maxDeviationMeters * 2) {
|
|
71
|
+
mode = 'approximate';
|
|
72
|
+
reason = '原生路线与 Web 规划线接近,但仍存在可见偏差';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
candidateMatches,
|
|
77
|
+
bestMatch,
|
|
78
|
+
selectedRoute,
|
|
79
|
+
nativePolyline,
|
|
80
|
+
mode,
|
|
81
|
+
reason,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function runIndependentDriveRoute(options) {
|
|
85
|
+
return ExpoGaodeMapNavigationModule.independentDriveRoute(options);
|
|
86
|
+
}
|
|
87
|
+
export async function followWebPlannedRoute(options) {
|
|
88
|
+
const { from, to, webRoute, strategy, carNumber, restriction, maxDeviationMeters = 120, startNavigation = false, naviType = 0, } = options;
|
|
89
|
+
const webPolyline = normalizeWebRoutePolyline(webRoute);
|
|
90
|
+
if (webPolyline.length < 2) {
|
|
91
|
+
throw new Error('webRoute.polyline 至少需要 2 个点');
|
|
92
|
+
}
|
|
93
|
+
// 第一步:从 Web 线路提取少量锚点,让原生独立算路先尽量往这条线靠。
|
|
94
|
+
const anchorWaypoints = buildAnchorWaypointsFromWebRoute(options);
|
|
95
|
+
const anchoredIndependentResult = await runIndependentDriveRoute({
|
|
96
|
+
from,
|
|
97
|
+
to,
|
|
98
|
+
strategy,
|
|
99
|
+
carNumber,
|
|
100
|
+
restriction,
|
|
101
|
+
waypoints: anchorWaypoints,
|
|
102
|
+
});
|
|
103
|
+
let independentResult = anchoredIndependentResult;
|
|
104
|
+
let evaluation = evaluateIndependentResultAgainstWebRoute(anchoredIndependentResult, webPolyline, anchorWaypoints, maxDeviationMeters);
|
|
105
|
+
let navigationUsesAnchorWaypoints = anchorWaypoints.length > 0;
|
|
106
|
+
// 如果锚点路线已经足够接近,再尝试去掉锚点重算一次。
|
|
107
|
+
// 这样可以避免“为了贴线而被锚点拖偏”的情况。
|
|
108
|
+
if (evaluation.bestMatch && evaluation.mode !== 'preview_only' && anchorWaypoints.length > 0) {
|
|
109
|
+
try {
|
|
110
|
+
const directIndependentResult = await runIndependentDriveRoute({
|
|
111
|
+
from,
|
|
112
|
+
to,
|
|
113
|
+
strategy,
|
|
114
|
+
carNumber,
|
|
115
|
+
restriction,
|
|
116
|
+
});
|
|
117
|
+
const directEvaluation = evaluateIndependentResultAgainstWebRoute(directIndependentResult, webPolyline, [], maxDeviationMeters);
|
|
118
|
+
const anchoredBest = evaluation.bestMatch;
|
|
119
|
+
const directBest = directEvaluation.bestMatch;
|
|
120
|
+
const canSwitchToDirectNavigation = Boolean(directBest) &&
|
|
121
|
+
directEvaluation.mode !== 'preview_only' &&
|
|
122
|
+
directBest.averageDeviationMeters <= Math.max(anchoredBest.averageDeviationMeters + 45, anchoredBest.averageDeviationMeters * 1.45) &&
|
|
123
|
+
directBest.maxDeviationMeters <= Math.max(anchoredBest.maxDeviationMeters + 90, anchoredBest.maxDeviationMeters * 1.45);
|
|
124
|
+
if (canSwitchToDirectNavigation) {
|
|
125
|
+
// 直连结果更自然,就切到直连结果,并清掉刚才的锚点算路缓存。
|
|
126
|
+
ExpoGaodeMapNavigationModule.clearIndependentRoute({
|
|
127
|
+
token: anchoredIndependentResult.token,
|
|
128
|
+
}).catch(() => { });
|
|
129
|
+
independentResult = directIndependentResult;
|
|
130
|
+
evaluation = directEvaluation;
|
|
131
|
+
navigationUsesAnchorWaypoints = false;
|
|
132
|
+
evaluation.reason =
|
|
133
|
+
directEvaluation.mode === 'matched'
|
|
134
|
+
? '已切换为无途经点导航结果,且与 Web 规划线高度接近'
|
|
135
|
+
: '已切换为无途经点导航结果,但与 Web 规划线仍存在轻微偏差';
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// 直连结果不如锚点方案,就保留锚点方案作为最终导航依据。
|
|
139
|
+
ExpoGaodeMapNavigationModule.clearIndependentRoute({
|
|
140
|
+
token: directIndependentResult.token,
|
|
141
|
+
}).catch(() => { });
|
|
142
|
+
evaluation.reason = `${evaluation.reason};最终导航仍需依赖锚点途经点逼近 Web 线路`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
evaluation.reason = `${evaluation.reason};无途经点重算失败,最终导航仍需依赖锚点途经点`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
let navigationStarted = false;
|
|
150
|
+
if (startNavigation && evaluation.bestMatch && evaluation.mode !== 'preview_only') {
|
|
151
|
+
// 只有当评估结果足够接近时才真正启动导航,避免把偏差过大的结果直接交给导航 SDK。
|
|
152
|
+
navigationStarted = await ExpoGaodeMapNavigationModule.startNaviWithIndependentPath({
|
|
153
|
+
token: independentResult.token,
|
|
154
|
+
naviType,
|
|
155
|
+
routeId: evaluation.bestMatch.routeId,
|
|
156
|
+
routeIndex: evaluation.bestMatch.routeId == null ? evaluation.bestMatch.routeIndex : undefined,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
mode: evaluation.mode,
|
|
161
|
+
token: independentResult.token,
|
|
162
|
+
anchorWaypoints,
|
|
163
|
+
webDistance: calculatePathLengthSafe(webPolyline),
|
|
164
|
+
nativeDistance: evaluation.nativePolyline.length > 1
|
|
165
|
+
? calculatePathLengthSafe(evaluation.nativePolyline)
|
|
166
|
+
: undefined,
|
|
167
|
+
selectedRouteId: evaluation.bestMatch?.routeId,
|
|
168
|
+
selectedRouteIndex: evaluation.bestMatch?.routeIndex,
|
|
169
|
+
averageDeviationMeters: evaluation.bestMatch?.averageDeviationMeters,
|
|
170
|
+
maxDeviationMeters: evaluation.bestMatch?.maxDeviationMeters,
|
|
171
|
+
navigationStarted,
|
|
172
|
+
navigationUsesAnchorWaypoints,
|
|
173
|
+
independentResult,
|
|
174
|
+
candidateMatches: evaluation.candidateMatches,
|
|
175
|
+
reason: evaluation.reason,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=web-route-following.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-route-following.js","sourceRoot":"","sources":["../src/web-route-following.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,gCAAgC,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAerL,SAAS,oBAAoB,CAAC,KAAgB;IAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/D,OAAO,KAAK,CAAC,QAAQ,CAAC;IACxB,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACrE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,oBAAoB,CACzB,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CACtD,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,oBAAoB,CACzB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAC7C,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,yBAAyB,CAChC,MAA8B,EAC9B,KAAgB,EAChB,UAAkB;IAElB,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,EAAE,CAAC;IAClB,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,uCAAuC,CAC9C,MAA8B,EAC9B,KAAgB,EAChB,UAAkB,EAClB,WAAwB,EACxB,eAA4B,EAC5B,eAAuB;IAEvB,+BAA+B;IAC/B,+BAA+B;IAC/B,MAAM,cAAc,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,mBAAmB,GAAG,cAAc,CAAC,cAAc,CAAC,CAAC;IAC3D,MAAM,cAAc,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACvD,qBAAqB,CAAC,WAAW,EAAE,KAAK,CAAC,CAC1C,CAAC;IACF,MAAM,sBAAsB,GAC1B,cAAc,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,GAAG,QAAQ,EAAE,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC;IAC1F,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC;IAEvD,MAAM,iBAAiB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CACjE,qBAAqB,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CACnF,EAAE,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC;QAC7D,UAAU;QACV,sBAAsB;QACtB,kBAAkB;QAClB,iBAAiB;QACjB,KAAK,EACH,sBAAsB;YACtB,kBAAkB,GAAG,IAAI;YACzB,iBAAiB,GAAG,eAAe,GAAG,GAAG;KAC5C,CAAC;AACJ,CAAC;AAED,SAAS,wCAAwC,CAC/C,iBAAyC,EACzC,WAAwB,EACxB,eAA4B,EAC5B,kBAA0B;IAE1B,oEAAoE;IACpE,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,MAAM;SAC9C,GAAG,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CACzB,uCAAuC,CACrC,iBAAiB,EACjB,KAAkB,EAClB,UAAU,EACV,WAAW,EACX,eAAe,EACf,kBAAkB,CACnB,CACF;SACA,MAAM,CAAC,CAAC,SAAS,EAA+C,EAAE,CAAC,SAAS,KAAK,IAAI,CAAC;SACtF,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,SAAS;QAC7B,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAc;QAC7D,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,cAAc,GAAG,aAAa,CAAC,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEhF,IAAI,IAAI,GAAwC,cAAc,CAAC;IAC/D,IAAI,MAAM,GAAG,sBAAsB,CAAC;IAEpC,IAAI,SAAS,EAAE,CAAC;QACd,IACE,SAAS,CAAC,sBAAsB,IAAI,kBAAkB,GAAG,CAAC;YAC1D,SAAS,CAAC,kBAAkB,IAAI,kBAAkB;YAClD,SAAS,CAAC,iBAAiB,KAAK,CAAC,EACjC,CAAC;YACD,IAAI,GAAG,SAAS,CAAC;YACjB,MAAM,GAAG,8BAA8B,CAAC;QAC1C,CAAC;aAAM,IACL,SAAS,CAAC,sBAAsB,IAAI,kBAAkB;YACtD,SAAS,CAAC,kBAAkB,IAAI,kBAAkB,GAAG,CAAC,EACtD,CAAC;YACD,IAAI,GAAG,aAAa,CAAC;YACrB,MAAM,GAAG,0BAA0B,CAAC;QACtC,CAAC;IACH,CAAC;IAED,OAAO;QACL,gBAAgB;QAChB,SAAS;QACT,aAAa;QACb,cAAc;QACd,IAAI;QACJ,MAAM;KACP,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,wBAAwB,CAAC,OAOvC;IACC,OAAO,4BAA4B,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAAqC;IAErC,MAAM,EACJ,IAAI,EACJ,EAAE,EACF,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,WAAW,EACX,kBAAkB,GAAG,GAAG,EACxB,eAAe,GAAG,KAAK,EACvB,QAAQ,GAAG,CAAC,GACb,GAAG,OAAO,CAAC;IAEZ,MAAM,WAAW,GAAG,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IACxD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,sCAAsC;IACtC,MAAM,eAAe,GAAG,gCAAgC,CAAC,OAAO,CAAC,CAAC;IAClE,MAAM,yBAAyB,GAAG,MAAM,wBAAwB,CAAC;QAC/D,IAAI;QACJ,EAAE;QACF,QAAQ;QACR,SAAS;QACT,WAAW;QACX,SAAS,EAAE,eAAe;KAC3B,CAAC,CAAC;IAEH,IAAI,iBAAiB,GAAG,yBAAyB,CAAC;IAClD,IAAI,UAAU,GAAG,wCAAwC,CACvD,yBAAyB,EACzB,WAAW,EACX,eAAe,EACf,kBAAkB,CACnB,CAAC;IACF,IAAI,6BAA6B,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC;IAE/D,4BAA4B;IAC5B,yBAAyB;IACzB,IAAI,UAAU,CAAC,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,cAAc,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7F,IAAI,CAAC;YACH,MAAM,uBAAuB,GAAG,MAAM,wBAAwB,CAAC;gBAC7D,IAAI;gBACJ,EAAE;gBACF,QAAQ;gBACR,SAAS;gBACT,WAAW;aACZ,CAAC,CAAC;YAEH,MAAM,gBAAgB,GAAG,wCAAwC,CAC/D,uBAAuB,EACvB,WAAW,EACX,EAAE,EACF,kBAAkB,CACnB,CAAC;YACF,MAAM,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC;YAC1C,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC;YAE9C,MAAM,2BAA2B,GAC/B,OAAO,CAAC,UAAU,CAAC;gBACnB,gBAAgB,CAAC,IAAI,KAAK,cAAc;gBACxC,UAAW,CAAC,sBAAsB,IAAI,IAAI,CAAC,GAAG,CAC5C,YAAY,CAAC,sBAAsB,GAAG,EAAE,EACxC,YAAY,CAAC,sBAAsB,GAAG,IAAI,CAC3C;gBACD,UAAW,CAAC,kBAAkB,IAAI,IAAI,CAAC,GAAG,CACxC,YAAY,CAAC,kBAAkB,GAAG,EAAE,EACpC,YAAY,CAAC,kBAAkB,GAAG,IAAI,CACvC,CAAC;YAEJ,IAAI,2BAA2B,EAAE,CAAC;gBAChC,gCAAgC;gBAChC,4BAA4B,CAAC,qBAAqB,CAAC;oBACjD,KAAK,EAAE,yBAAyB,CAAC,KAAK;iBACvC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACnB,iBAAiB,GAAG,uBAAuB,CAAC;gBAC5C,UAAU,GAAG,gBAAgB,CAAC;gBAC9B,6BAA6B,GAAG,KAAK,CAAC;gBACtC,UAAU,CAAC,MAAM;oBACf,gBAAgB,CAAC,IAAI,KAAK,SAAS;wBACjC,CAAC,CAAC,6BAA6B;wBAC/B,CAAC,CAAC,gCAAgC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,8BAA8B;gBAC9B,4BAA4B,CAAC,qBAAqB,CAAC;oBACjD,KAAK,EAAE,uBAAuB,CAAC,KAAK;iBACrC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACnB,UAAU,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,yBAAyB,CAAC;YACpE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,UAAU,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,yBAAyB,CAAC;QACpE,CAAC;IACH,CAAC;IAED,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAC9B,IAAI,eAAe,IAAI,UAAU,CAAC,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAClF,4CAA4C;QAC5C,iBAAiB,GAAG,MAAM,4BAA4B,CAAC,4BAA4B,CAAC;YAClF,KAAK,EAAE,iBAAiB,CAAC,KAAK;YAC9B,QAAQ;YACR,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,OAAO;YACrC,UAAU,EACR,UAAU,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;SACrF,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,KAAK,EAAE,iBAAiB,CAAC,KAAK;QAC9B,eAAe;QACf,WAAW,EAAE,uBAAuB,CAAC,WAAW,CAAC;QACjD,cAAc,EACZ,UAAU,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;YAClC,CAAC,CAAC,uBAAuB,CAAC,UAAU,CAAC,cAAc,CAAC;YACpD,CAAC,CAAC,SAAS;QACf,eAAe,EAAE,UAAU,CAAC,SAAS,EAAE,OAAO;QAC9C,kBAAkB,EAAE,UAAU,CAAC,SAAS,EAAE,UAAU;QACpD,sBAAsB,EAAE,UAAU,CAAC,SAAS,EAAE,sBAAsB;QACpE,kBAAkB,EAAE,UAAU,CAAC,SAAS,EAAE,kBAAkB;QAC5D,iBAAiB;QACjB,6BAA6B;QAC7B,iBAAiB;QACjB,gBAAgB,EAAE,UAAU,CAAC,gBAAgB;QAC7C,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CAAC;AACJ,CAAC","sourcesContent":["import ExpoGaodeMapNavigationModule from './ExpoGaodeMapNavigationModule';\nimport { buildAnchorWaypointsFromWebRoute, calculatePathLengthSafe, dedupeAdjacentPoints, getDistanceToPathSafe, normalizeWebRoutePolyline, samplePolyline } from './route-geometry';\nimport type {\n FollowWebPlannedRouteCandidate,\n FollowWebPlannedRouteOptions,\n FollowWebPlannedRouteResult,\n IndependentRouteResult,\n NaviPoint,\n RouteResult,\n} from './types';\n\ntype RouteLike = RouteResult & {\n routeId?: number;\n steps?: Array<{ polyline?: NaviPoint[] }>;\n};\n\nfunction extractRoutePolyline(route: RouteLike): NaviPoint[] {\n if (Array.isArray(route.polyline) && route.polyline.length > 0) {\n return route.polyline;\n }\n\n const segments = Array.isArray(route.segments) ? route.segments : [];\n if (segments.length > 0) {\n return dedupeAdjacentPoints(\n segments.flatMap((segment) => segment.polyline ?? [])\n );\n }\n\n const steps = Array.isArray(route.steps) ? route.steps : [];\n if (steps.length > 0) {\n return dedupeAdjacentPoints(\n steps.flatMap((step) => step.polyline ?? [])\n );\n }\n\n return [];\n}\n\nfunction resolveIndependentRouteId(\n result: IndependentRouteResult,\n route: RouteLike,\n routeIndex: number\n): number | undefined {\n if (typeof route.id === 'number') {\n return route.id;\n }\n if (typeof route.routeId === 'number') {\n return route.routeId;\n }\n return result.routeIds?.[routeIndex];\n}\n\nfunction scoreIndependentRouteAgainstWebPolyline(\n result: IndependentRouteResult,\n route: RouteLike,\n routeIndex: number,\n webPolyline: NaviPoint[],\n anchorWaypoints: NaviPoint[],\n thresholdMeters: number\n): FollowWebPlannedRouteCandidate | null {\n // 评分思路很简单:看原生独立路线与 Web 折线有多接近,\n // 再把平均偏差、最大偏差、漏掉的锚点一起折成一个排序分数。\n const nativePolyline = extractRoutePolyline(route);\n if (nativePolyline.length === 0 || webPolyline.length === 0) {\n return null;\n }\n\n const sampledNativePoints = samplePolyline(nativePolyline);\n const pointDistances = sampledNativePoints.map((point) =>\n getDistanceToPathSafe(webPolyline, point)\n );\n const averageDeviationMeters =\n pointDistances.reduce((total, distance) => total + distance, 0) / pointDistances.length;\n const maxDeviationMeters = Math.max(...pointDistances);\n\n const missedAnchorCount = anchorWaypoints.reduce((count, point) => (\n getDistanceToPathSafe(nativePolyline, point) > thresholdMeters ? count + 1 : count\n ), 0);\n\n return {\n routeId: resolveIndependentRouteId(result, route, routeIndex),\n routeIndex,\n averageDeviationMeters,\n maxDeviationMeters,\n missedAnchorCount,\n score:\n averageDeviationMeters +\n maxDeviationMeters * 0.35 +\n missedAnchorCount * thresholdMeters * 0.5,\n };\n}\n\nfunction evaluateIndependentResultAgainstWebRoute(\n independentResult: IndependentRouteResult,\n webPolyline: NaviPoint[],\n anchorWaypoints: NaviPoint[],\n maxDeviationMeters: number\n) {\n // 先给每条原生候选路线打分,再把最接近的一条拿出来判断是 matched / approximate / preview_only。\n const candidateMatches = independentResult.routes\n .map((route, routeIndex) =>\n scoreIndependentRouteAgainstWebPolyline(\n independentResult,\n route as RouteLike,\n routeIndex,\n webPolyline,\n anchorWaypoints,\n maxDeviationMeters\n )\n )\n .filter((candidate): candidate is FollowWebPlannedRouteCandidate => candidate !== null)\n .sort((routeA, routeB) => routeA.score - routeB.score);\n\n const bestMatch = candidateMatches[0];\n const selectedRoute = bestMatch\n ? independentResult.routes[bestMatch.routeIndex] as RouteLike\n : undefined;\n const nativePolyline = selectedRoute ? extractRoutePolyline(selectedRoute) : [];\n\n let mode: FollowWebPlannedRouteResult['mode'] = 'preview_only';\n let reason = '未找到足够接近 Web 规划线的原生路线';\n\n if (bestMatch) {\n if (\n bestMatch.averageDeviationMeters <= maxDeviationMeters / 2 &&\n bestMatch.maxDeviationMeters <= maxDeviationMeters &&\n bestMatch.missedAnchorCount === 0\n ) {\n mode = 'matched';\n reason = '原生路线与 Web 规划线高度接近,可直接按近似结果导航';\n } else if (\n bestMatch.averageDeviationMeters <= maxDeviationMeters &&\n bestMatch.maxDeviationMeters <= maxDeviationMeters * 2\n ) {\n mode = 'approximate';\n reason = '原生路线与 Web 规划线接近,但仍存在可见偏差';\n }\n }\n\n return {\n candidateMatches,\n bestMatch,\n selectedRoute,\n nativePolyline,\n mode,\n reason,\n };\n}\n\nasync function runIndependentDriveRoute(options: {\n from: FollowWebPlannedRouteOptions['from'];\n to: FollowWebPlannedRouteOptions['to'];\n strategy?: FollowWebPlannedRouteOptions['strategy'];\n carNumber?: FollowWebPlannedRouteOptions['carNumber'];\n restriction?: FollowWebPlannedRouteOptions['restriction'];\n waypoints?: NaviPoint[];\n}): Promise<IndependentRouteResult> {\n return ExpoGaodeMapNavigationModule.independentDriveRoute(options);\n}\n\nexport async function followWebPlannedRoute(\n options: FollowWebPlannedRouteOptions\n): Promise<FollowWebPlannedRouteResult> {\n const {\n from,\n to,\n webRoute,\n strategy,\n carNumber,\n restriction,\n maxDeviationMeters = 120,\n startNavigation = false,\n naviType = 0,\n } = options;\n\n const webPolyline = normalizeWebRoutePolyline(webRoute);\n if (webPolyline.length < 2) {\n throw new Error('webRoute.polyline 至少需要 2 个点');\n }\n\n // 第一步:从 Web 线路提取少量锚点,让原生独立算路先尽量往这条线靠。\n const anchorWaypoints = buildAnchorWaypointsFromWebRoute(options);\n const anchoredIndependentResult = await runIndependentDriveRoute({\n from,\n to,\n strategy,\n carNumber,\n restriction,\n waypoints: anchorWaypoints,\n });\n\n let independentResult = anchoredIndependentResult;\n let evaluation = evaluateIndependentResultAgainstWebRoute(\n anchoredIndependentResult,\n webPolyline,\n anchorWaypoints,\n maxDeviationMeters\n );\n let navigationUsesAnchorWaypoints = anchorWaypoints.length > 0;\n\n // 如果锚点路线已经足够接近,再尝试去掉锚点重算一次。\n // 这样可以避免“为了贴线而被锚点拖偏”的情况。\n if (evaluation.bestMatch && evaluation.mode !== 'preview_only' && anchorWaypoints.length > 0) {\n try {\n const directIndependentResult = await runIndependentDriveRoute({\n from,\n to,\n strategy,\n carNumber,\n restriction,\n });\n\n const directEvaluation = evaluateIndependentResultAgainstWebRoute(\n directIndependentResult,\n webPolyline,\n [],\n maxDeviationMeters\n );\n const anchoredBest = evaluation.bestMatch;\n const directBest = directEvaluation.bestMatch;\n\n const canSwitchToDirectNavigation =\n Boolean(directBest) &&\n directEvaluation.mode !== 'preview_only' &&\n directBest!.averageDeviationMeters <= Math.max(\n anchoredBest.averageDeviationMeters + 45,\n anchoredBest.averageDeviationMeters * 1.45\n ) &&\n directBest!.maxDeviationMeters <= Math.max(\n anchoredBest.maxDeviationMeters + 90,\n anchoredBest.maxDeviationMeters * 1.45\n );\n\n if (canSwitchToDirectNavigation) {\n // 直连结果更自然,就切到直连结果,并清掉刚才的锚点算路缓存。\n ExpoGaodeMapNavigationModule.clearIndependentRoute({\n token: anchoredIndependentResult.token,\n }).catch(() => {});\n independentResult = directIndependentResult;\n evaluation = directEvaluation;\n navigationUsesAnchorWaypoints = false;\n evaluation.reason =\n directEvaluation.mode === 'matched'\n ? '已切换为无途经点导航结果,且与 Web 规划线高度接近'\n : '已切换为无途经点导航结果,但与 Web 规划线仍存在轻微偏差';\n } else {\n // 直连结果不如锚点方案,就保留锚点方案作为最终导航依据。\n ExpoGaodeMapNavigationModule.clearIndependentRoute({\n token: directIndependentResult.token,\n }).catch(() => {});\n evaluation.reason = `${evaluation.reason};最终导航仍需依赖锚点途经点逼近 Web 线路`;\n }\n } catch {\n evaluation.reason = `${evaluation.reason};无途经点重算失败,最终导航仍需依赖锚点途经点`;\n }\n }\n\n let navigationStarted = false;\n if (startNavigation && evaluation.bestMatch && evaluation.mode !== 'preview_only') {\n // 只有当评估结果足够接近时才真正启动导航,避免把偏差过大的结果直接交给导航 SDK。\n navigationStarted = await ExpoGaodeMapNavigationModule.startNaviWithIndependentPath({\n token: independentResult.token,\n naviType,\n routeId: evaluation.bestMatch.routeId,\n routeIndex:\n evaluation.bestMatch.routeId == null ? evaluation.bestMatch.routeIndex : undefined,\n });\n }\n\n return {\n mode: evaluation.mode,\n token: independentResult.token,\n anchorWaypoints,\n webDistance: calculatePathLengthSafe(webPolyline),\n nativeDistance:\n evaluation.nativePolyline.length > 1\n ? calculatePathLengthSafe(evaluation.nativePolyline)\n : undefined,\n selectedRouteId: evaluation.bestMatch?.routeId,\n selectedRouteIndex: evaluation.bestMatch?.routeIndex,\n averageDeviationMeters: evaluation.bestMatch?.averageDeviationMeters,\n maxDeviationMeters: evaluation.bestMatch?.maxDeviationMeters,\n navigationStarted,\n navigationUsesAnchorWaypoints,\n independentResult,\n candidateMatches: evaluation.candidateMatches,\n reason: evaluation.reason,\n };\n}\n"]}
|
package/expo-module.config.json
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"MultiPointViewModule",
|
|
15
15
|
"HeatMapViewModule",
|
|
16
16
|
"ClusterViewModule",
|
|
17
|
-
"ExpoGaodeMapOfflineModule"
|
|
17
|
+
"ExpoGaodeMapOfflineModule",
|
|
18
|
+
"ExpoGaodeMapSearchModule"
|
|
18
19
|
]
|
|
19
20
|
},
|
|
20
21
|
"android": {
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"expo.modules.gaodemap.map.overlays.MultiPointViewModule",
|
|
31
32
|
"expo.modules.gaodemap.map.overlays.HeatMapViewModule",
|
|
32
33
|
"expo.modules.gaodemap.map.overlays.ClusterViewModule",
|
|
33
|
-
"expo.modules.gaodemap.map.ExpoGaodeMapOfflineModule"
|
|
34
|
+
"expo.modules.gaodemap.map.ExpoGaodeMapOfflineModule",
|
|
35
|
+
"expo.modules.gaodemap.map.search.ExpoGaodeMapSearchModule"
|
|
34
36
|
]
|
|
35
37
|
}
|
|
36
38
|
}
|
|
@@ -442,6 +442,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
442
442
|
private var hasLoggedMissingBackgroundAudioMode: Bool = false
|
|
443
443
|
private var renderedCustomWaypointAnnotations: [AMapNaviCompositeCustomAnnotation] = []
|
|
444
444
|
private var customWaypointMarkers: [NaviCustomWaypointMarkerModel] = []
|
|
445
|
+
private let customSpeechSynthesizer = AVSpeechSynthesizer()
|
|
445
446
|
|
|
446
447
|
private enum LaneStringKind {
|
|
447
448
|
case background
|
|
@@ -1231,10 +1232,6 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
1231
1232
|
}
|
|
1232
1233
|
|
|
1233
1234
|
private func resolveTravelTurnIconImage(iconType: Int) -> UIImage? {
|
|
1234
|
-
let iconEnum = AMapNaviIconType(rawValue: iconType) ?? .none
|
|
1235
|
-
if activeScene == .ride {
|
|
1236
|
-
return AMapNaviRideView.rideViewTurnIconImage(with: iconEnum)
|
|
1237
|
-
}
|
|
1238
1235
|
return fallbackTurnIconImage(iconType: iconType)
|
|
1239
1236
|
}
|
|
1240
1237
|
|
|
@@ -1637,11 +1634,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
1637
1634
|
let resizedImage = self?.resizeImageIfNeeded(image, targetSize: self?.carImageSize) ?? image
|
|
1638
1635
|
driveView?.setCarImage(resizedImage)
|
|
1639
1636
|
walkView?.setCarImage(resizedImage)
|
|
1640
|
-
|
|
1641
|
-
rideView?.setCarImageWithSize(resizedImage)
|
|
1642
|
-
} else {
|
|
1643
|
-
rideView?.setCarImage(nil)
|
|
1644
|
-
}
|
|
1637
|
+
rideView?.setCarImage(resizedImage)
|
|
1645
1638
|
}
|
|
1646
1639
|
}
|
|
1647
1640
|
|
|
@@ -2323,18 +2316,24 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
2323
2316
|
}
|
|
2324
2317
|
|
|
2325
2318
|
func playCustomTTS(text: String, forcePlay: Bool, promise: Promise) {
|
|
2326
|
-
guard
|
|
2319
|
+
guard currentNaviManager() != nil else {
|
|
2327
2320
|
promise.reject("NAVI_MANAGER_UNAVAILABLE", "导航管理器尚未初始化")
|
|
2328
2321
|
return
|
|
2329
2322
|
}
|
|
2330
|
-
|
|
2331
|
-
if
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
promise.reject("PLAY_TTS_FAILED", "当前场景暂不支持或正在播报其他导航语音")
|
|
2323
|
+
|
|
2324
|
+
if forcePlay {
|
|
2325
|
+
customSpeechSynthesizer.stopSpeaking(at: .immediate)
|
|
2326
|
+
} else if customSpeechSynthesizer.isSpeaking {
|
|
2327
|
+
promise.reject("PLAY_TTS_FAILED", "当前正在播报其他导航语音")
|
|
2328
|
+
return
|
|
2337
2329
|
}
|
|
2330
|
+
|
|
2331
|
+
let utterance = AVSpeechUtterance(string: text)
|
|
2332
|
+
utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
|
|
2333
|
+
customSpeechSynthesizer.speak(utterance)
|
|
2334
|
+
promise.resolve([
|
|
2335
|
+
"success": true
|
|
2336
|
+
])
|
|
2338
2337
|
}
|
|
2339
2338
|
|
|
2340
2339
|
// MARK: - Lifecycle
|
|
@@ -24,9 +24,10 @@ Pod::Spec.new do |s|
|
|
|
24
24
|
s.static_framework = true
|
|
25
25
|
|
|
26
26
|
s.dependency 'ExpoModulesCore'
|
|
27
|
-
s.dependency 'AMapNavi'
|
|
27
|
+
s.dependency 'AMapNavi','= 11.1.200'
|
|
28
28
|
s.dependency 'AMapFoundation'
|
|
29
29
|
s.dependency 'AMapLocation'
|
|
30
|
+
s.dependency 'AMapSearch'
|
|
30
31
|
|
|
31
32
|
s.library = 'c++'
|
|
32
33
|
|
|
@@ -2,6 +2,8 @@ import ExpoModulesCore
|
|
|
2
2
|
import AMapFoundationKit
|
|
3
3
|
import AMapNaviKit
|
|
4
4
|
import Foundation
|
|
5
|
+
import AMapNaviKit
|
|
6
|
+
import UIKit
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* 高德地图离线地图模块 (iOS)
|
|
@@ -66,6 +68,12 @@ public class ExpoGaodeMapOfflineModule: Module {
|
|
|
66
68
|
self.downloadingCities.removeAll()
|
|
67
69
|
self.pausedCities.removeAll()
|
|
68
70
|
}
|
|
71
|
+
|
|
72
|
+
AsyncFunction("openOfflineMapUI") { (promise: Promise) in
|
|
73
|
+
DispatchQueue.main.async {
|
|
74
|
+
self.openOfflineMapUI(promise: promise)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
69
77
|
|
|
70
78
|
// ==================== 1. 地图列表管理 ====================
|
|
71
79
|
|
|
@@ -383,6 +391,59 @@ public class ExpoGaodeMapOfflineModule: Module {
|
|
|
383
391
|
|
|
384
392
|
return offlineMapManager
|
|
385
393
|
}
|
|
394
|
+
|
|
395
|
+
private func openOfflineMapUI(promise: Promise) {
|
|
396
|
+
guard getOfflineMapManager() != nil else {
|
|
397
|
+
promise.reject("ERR_SETUP", "Setup failed")
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
guard let detailViewController = MAOfflineMapViewController.sharedInstance() else {
|
|
402
|
+
promise.reject("ERR_OFFLINE_MAP_UI", "Offline map UI is unavailable")
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
guard let presenter = currentPresenterViewController() else {
|
|
407
|
+
promise.reject("ERR_NO_VIEW_CONTROLLER", "No active view controller")
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if let navigationController = presenter as? UINavigationController {
|
|
412
|
+
navigationController.pushViewController(detailViewController, animated: true)
|
|
413
|
+
} else if let navigationController = presenter.navigationController {
|
|
414
|
+
navigationController.pushViewController(detailViewController, animated: true)
|
|
415
|
+
} else {
|
|
416
|
+
let navigationController = UINavigationController(rootViewController: detailViewController)
|
|
417
|
+
presenter.present(navigationController, animated: true)
|
|
418
|
+
}
|
|
419
|
+
promise.resolve(nil)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private func currentPresenterViewController() -> UIViewController? {
|
|
423
|
+
guard let scene = UIApplication.shared.connectedScenes
|
|
424
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
425
|
+
.first(where: { $0.activationState == .foregroundActive }) else {
|
|
426
|
+
return topViewController(from: UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let root = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController
|
|
430
|
+
?? scene.windows.first?.rootViewController
|
|
431
|
+
return topViewController(from: root)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private func topViewController(from root: UIViewController?) -> UIViewController? {
|
|
435
|
+
guard let root else { return nil }
|
|
436
|
+
if let presented = root.presentedViewController {
|
|
437
|
+
return topViewController(from: presented)
|
|
438
|
+
}
|
|
439
|
+
if let navigationController = root as? UINavigationController {
|
|
440
|
+
return topViewController(from: navigationController.visibleViewController)
|
|
441
|
+
}
|
|
442
|
+
if let tabBarController = root as? UITabBarController {
|
|
443
|
+
return topViewController(from: tabBarController.selectedViewController)
|
|
444
|
+
}
|
|
445
|
+
return root
|
|
446
|
+
}
|
|
386
447
|
|
|
387
448
|
private func ensureSetup(completion: @escaping (Bool) -> Void) {
|
|
388
449
|
guard let manager = getOfflineMapManager() else {
|