expo-gaode-map-navigation 2.0.6 → 2.0.7
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 +25 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +71 -45
- package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +35 -0
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +0 -1
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +13 -2
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +385 -1
- package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +180 -4
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +182 -7
- package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +31 -1
- package/build/index.d.ts +7 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -0
- package/build/index.js.map +1 -1
- package/build/types/independent.types.d.ts +7 -0
- package/build/types/independent.types.d.ts.map +1 -1
- package/build/types/independent.types.js.map +1 -1
- package/build/types/native-module.types.d.ts +5 -1
- package/build/types/native-module.types.d.ts.map +1 -1
- package/build/types/native-module.types.js.map +1 -1
- package/build/types/route.types.d.ts +112 -0
- package/build/types/route.types.d.ts.map +1 -1
- package/build/types/route.types.js.map +1 -1
- package/ios/ExpoGaodeMapNaviView.swift +56 -4
- package/ios/ExpoGaodeMapNavigationModule.swift +585 -12
- package/ios/map/ExpoGaodeMapOfflineModule.swift +58 -34
- package/ios/map/GaodeMapPrivacyManager.swift +23 -1
- package/ios/map/overlays/MarkerView.swift +148 -11
- package/ios/services/IndependentRouteService.swift +186 -44
- package/package.json +1 -1
- package/plugin/build/withGaodeMap.js +28 -0
package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt
CHANGED
|
@@ -8,6 +8,7 @@ import com.amap.api.navi.model.AMapNaviPathGroup
|
|
|
8
8
|
import expo.modules.kotlin.Promise
|
|
9
9
|
import expo.modules.kotlin.modules.Module
|
|
10
10
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
11
|
+
import expo.modules.gaodemap.map.modules.SDKInitializer
|
|
11
12
|
import expo.modules.gaodemap.navigation.routes.drive.DriveTruckRouteCalculator
|
|
12
13
|
import expo.modules.gaodemap.navigation.routes.walkride.WalkRideRouteCalculator
|
|
13
14
|
import expo.modules.gaodemap.navigation.routes.ebike.EbikeRouteCalculator
|
|
@@ -15,6 +16,7 @@ import expo.modules.gaodemap.navigation.listeners.IndependentRouteListener
|
|
|
15
16
|
import expo.modules.gaodemap.navigation.utils.Converters
|
|
16
17
|
import expo.modules.gaodemap.navigation.managers.IndependentRouteManager
|
|
17
18
|
import expo.modules.gaodemap.navigation.services.IndependentRouteService
|
|
19
|
+
import java.util.Locale
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -48,6 +50,12 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
48
50
|
override fun definition() = ModuleDefinition {
|
|
49
51
|
Name("ExpoGaodeMapNavigation")
|
|
50
52
|
|
|
53
|
+
OnCreate {
|
|
54
|
+
appContext.reactContext?.let { context ->
|
|
55
|
+
SDKInitializer.restorePersistedState(context)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
// 仅保留算路相关事件(用于需要事件的场景;Promise 也会在 Listener 中完成)
|
|
52
60
|
Events(
|
|
53
61
|
"onCalculateRouteSuccess",
|
|
@@ -200,6 +208,7 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
200
208
|
*/
|
|
201
209
|
AsyncFunction("startNaviWithIndependentPath") { options: Map<String, Any?>, promise: Promise ->
|
|
202
210
|
try {
|
|
211
|
+
ensureNavigationPrivacyReady()
|
|
203
212
|
val token = (options["token"] as? Number)?.toInt() ?: throw Exception("token is required")
|
|
204
213
|
val routeId = (options["routeId"] as? Number)?.toInt()
|
|
205
214
|
val routeIndex = (options["routeIndex"] as? Number)?.toInt()
|
|
@@ -212,6 +221,29 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
212
221
|
}
|
|
213
222
|
}
|
|
214
223
|
|
|
224
|
+
/**
|
|
225
|
+
* 打开高德官方导航页面(AmapNaviPage.showRouteActivity)
|
|
226
|
+
* 参数:
|
|
227
|
+
* - from?: { latitude, longitude, name?, poiId? }(可选,不传则使用当前位置)
|
|
228
|
+
* - to: { latitude, longitude, name?, poiId? }(必填)
|
|
229
|
+
* - waypoints?: Array<{ latitude, longitude, name?, poiId? }>(可选)
|
|
230
|
+
* - needCalculateRouteWhenPresent?: Boolean(默认 true)
|
|
231
|
+
* - needDestroyDriveManagerInstanceWhenNaviExit?: Boolean(默认 false)
|
|
232
|
+
* - showExitNaviDialog?: Boolean(默认 true)
|
|
233
|
+
* - useInnerVoice?: Boolean(默认 true)
|
|
234
|
+
* - pageType?: String(可选,例如 ROUTE/NAVI)
|
|
235
|
+
* - officialNaviType?: String(可选,例如 DRIVER)
|
|
236
|
+
*/
|
|
237
|
+
AsyncFunction("openOfficialNaviPage") { options: Map<String, Any?>, promise: Promise ->
|
|
238
|
+
try {
|
|
239
|
+
ensureNavigationPrivacyReady()
|
|
240
|
+
openOfficialNaviPage(options)
|
|
241
|
+
promise.resolve(true)
|
|
242
|
+
} catch (e: Exception) {
|
|
243
|
+
promise.reject("OPEN_OFFICIAL_PAGE_ERROR", e.message, e)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
215
247
|
/**
|
|
216
248
|
* 清除独立路径组
|
|
217
249
|
* - token: Int
|
|
@@ -230,6 +262,7 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
230
262
|
// 注意:摩托车是收费能力;务必在算路前设置 carType=11 和排量,否则不生效
|
|
231
263
|
AsyncFunction("calculateMotorcycleRoute") { options: Map<String, Any?>, promise: Promise ->
|
|
232
264
|
try {
|
|
265
|
+
ensureNavigationPrivacyReady()
|
|
233
266
|
// 设置摩托车车辆信息(车牌可选、排量可选)
|
|
234
267
|
try {
|
|
235
268
|
val carNumber = options["carNumber"] as? String
|
|
@@ -283,6 +316,7 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
283
316
|
}
|
|
284
317
|
|
|
285
318
|
private fun ensureDriveTruck(): DriveTruckRouteCalculator {
|
|
319
|
+
ensureNavigationPrivacyReady()
|
|
286
320
|
if (driveTruckCalculator == null) {
|
|
287
321
|
driveTruckCalculator = DriveTruckRouteCalculator(context, this)
|
|
288
322
|
}
|
|
@@ -290,6 +324,7 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
290
324
|
}
|
|
291
325
|
|
|
292
326
|
private fun ensureWalkRide(): WalkRideRouteCalculator {
|
|
327
|
+
ensureNavigationPrivacyReady()
|
|
293
328
|
if (walkRideCalculator == null) {
|
|
294
329
|
walkRideCalculator = WalkRideRouteCalculator(context, this)
|
|
295
330
|
}
|
|
@@ -297,6 +332,7 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
297
332
|
}
|
|
298
333
|
|
|
299
334
|
private fun ensureEbike(): EbikeRouteCalculator {
|
|
335
|
+
ensureNavigationPrivacyReady()
|
|
300
336
|
if (ebikeCalculator == null) {
|
|
301
337
|
ebikeCalculator = EbikeRouteCalculator(context, this)
|
|
302
338
|
}
|
|
@@ -304,16 +340,364 @@ class ExpoGaodeMapNavigationModule : Module() {
|
|
|
304
340
|
}
|
|
305
341
|
|
|
306
342
|
private fun ensureIndependentService(): IndependentRouteService {
|
|
343
|
+
ensureNavigationPrivacyReady()
|
|
307
344
|
if (independentRouteService == null) {
|
|
308
345
|
independentRouteService = IndependentRouteService(context, this)
|
|
309
346
|
}
|
|
310
347
|
return independentRouteService!!
|
|
311
348
|
}
|
|
312
349
|
|
|
350
|
+
private fun ensureNavigationPrivacyReady() {
|
|
351
|
+
SDKInitializer.restorePersistedState(context)
|
|
352
|
+
if (!SDKInitializer.isPrivacyReady()) {
|
|
353
|
+
throw Exception(
|
|
354
|
+
"隐私协议未完成确认,请先调用 setPrivacyConfig(hasShow/hasContainsPrivacy/hasAgree 均为 true)"
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private fun openOfficialNaviPage(options: Map<String, Any?>) {
|
|
360
|
+
@Suppress("UNCHECKED_CAST")
|
|
361
|
+
val from = options["from"] as? Map<String, Any?>
|
|
362
|
+
@Suppress("UNCHECKED_CAST")
|
|
363
|
+
val to = options["to"] as? Map<String, Any?> ?: throw Exception("to is required")
|
|
364
|
+
@Suppress("UNCHECKED_CAST")
|
|
365
|
+
val waypointsRaw = options["waypoints"] as? List<Map<String, Any?>>
|
|
366
|
+
|
|
367
|
+
val startPoi = createOfficialPoi(from, "起点")
|
|
368
|
+
val endPoi = createOfficialPoi(to, "终点")
|
|
369
|
+
?: throw Exception("to is required and must contain latitude/longitude")
|
|
370
|
+
val waypointPois = waypointsRaw
|
|
371
|
+
?.mapIndexedNotNull { index, item -> createOfficialPoi(item, "途经点${index + 1}") }
|
|
372
|
+
?: emptyList()
|
|
373
|
+
|
|
374
|
+
val params = createAmapNaviParams(
|
|
375
|
+
startPoi = startPoi,
|
|
376
|
+
endPoi = endPoi,
|
|
377
|
+
waypointPois = waypointPois,
|
|
378
|
+
options = options
|
|
379
|
+
)
|
|
380
|
+
showOfficialNaviPage(params)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private fun createOfficialPoi(
|
|
384
|
+
coordinate: Map<String, Any?>?,
|
|
385
|
+
fallbackName: String
|
|
386
|
+
): Any? {
|
|
387
|
+
if (coordinate == null) return null
|
|
388
|
+
|
|
389
|
+
val latitude = (coordinate["latitude"] as? Number)?.toDouble()
|
|
390
|
+
?: throw Exception("coordinate.latitude is required")
|
|
391
|
+
val longitude = (coordinate["longitude"] as? Number)?.toDouble()
|
|
392
|
+
?: throw Exception("coordinate.longitude is required")
|
|
393
|
+
val name = (coordinate["name"] as? String)?.trim()?.takeIf { it.isNotEmpty() } ?: fallbackName
|
|
394
|
+
val poiId = (coordinate["poiId"] as? String).orEmpty()
|
|
395
|
+
|
|
396
|
+
val latLngClass = Class.forName("com.amap.api.maps.model.LatLng")
|
|
397
|
+
val latLng = latLngClass
|
|
398
|
+
.getConstructor(Double::class.javaPrimitiveType, Double::class.javaPrimitiveType)
|
|
399
|
+
.newInstance(latitude, longitude)
|
|
400
|
+
|
|
401
|
+
val poiClass = Class.forName("com.amap.api.maps.model.Poi")
|
|
402
|
+
val candidates = listOf<Array<Any?>>(
|
|
403
|
+
arrayOf(name, latLng, poiId),
|
|
404
|
+
arrayOf(name, latLng),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
for (args in candidates) {
|
|
408
|
+
instantiateIfMatch(poiClass, args)?.let { return it }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
throw Exception("当前 SDK 的 Poi 构造函数不兼容")
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private fun createAmapNaviParams(
|
|
415
|
+
startPoi: Any?,
|
|
416
|
+
endPoi: Any,
|
|
417
|
+
waypointPois: List<Any>,
|
|
418
|
+
options: Map<String, Any?>
|
|
419
|
+
): Any {
|
|
420
|
+
val paramsClass = Class.forName("com.amap.api.navi.AmapNaviParams")
|
|
421
|
+
val naviTypeClass = loadFirstAvailableClass(
|
|
422
|
+
"com.amap.api.navi.AmapNaviType",
|
|
423
|
+
"com.amap.api.navi.enums.AmapNaviType"
|
|
424
|
+
) ?: throw Exception("当前 SDK 缺少 AmapNaviType 枚举类")
|
|
425
|
+
val pageTypeClass = loadFirstAvailableClass(
|
|
426
|
+
"com.amap.api.navi.AmapPageType",
|
|
427
|
+
"com.amap.api.navi.enums.AmapPageType"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
val officialNaviType = resolveEnumConstant(
|
|
431
|
+
enumClass = naviTypeClass,
|
|
432
|
+
preferName = options["officialNaviType"] as? String,
|
|
433
|
+
fallbackNames = listOf("DRIVER", "DRIVE", "CAR")
|
|
434
|
+
)
|
|
435
|
+
val pageType = pageTypeClass?.let {
|
|
436
|
+
resolveEnumConstant(
|
|
437
|
+
enumClass = it,
|
|
438
|
+
preferName = options["pageType"] as? String,
|
|
439
|
+
fallbackNames = listOf("ROUTE", "NAVI")
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
val waypointValue: Any? = if (waypointPois.isEmpty()) null else ArrayList(waypointPois)
|
|
444
|
+
val candidates = mutableListOf<Array<Any?>>(
|
|
445
|
+
arrayOf(startPoi, waypointValue, endPoi, officialNaviType, pageType),
|
|
446
|
+
arrayOf(startPoi, waypointValue, endPoi, officialNaviType),
|
|
447
|
+
arrayOf(startPoi, null, endPoi, officialNaviType, pageType),
|
|
448
|
+
arrayOf(startPoi, null, endPoi, officialNaviType),
|
|
449
|
+
arrayOf(startPoi, endPoi, officialNaviType, pageType),
|
|
450
|
+
arrayOf(startPoi, endPoi, officialNaviType),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
for (args in candidates) {
|
|
454
|
+
val params = instantiateIfMatch(paramsClass, args) ?: continue
|
|
455
|
+
applyOptionalParamsByReflection(params, options)
|
|
456
|
+
return params
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
throw Exception("当前 SDK 的 AmapNaviParams 构造函数不兼容")
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private fun applyOptionalParamsByReflection(params: Any, options: Map<String, Any?>) {
|
|
463
|
+
// 保持旧行为:未传时仍沿用默认值
|
|
464
|
+
val needCalculate = options["needCalculateRouteWhenPresent"] as? Boolean ?: true
|
|
465
|
+
val needDestroy = options["needDestroyDriveManagerInstanceWhenNaviExit"] as? Boolean ?: false
|
|
466
|
+
val showExitDialog = options["showExitNaviDialog"] as? Boolean ?: true
|
|
467
|
+
val useInnerVoice = options["useInnerVoice"] as? Boolean ?: true
|
|
468
|
+
|
|
469
|
+
invokeSetterIfExists(params, "setNeedCalculateRouteWhenPresent", needCalculate)
|
|
470
|
+
invokeSetterIfExists(params, "setNeedDestroyDriveManagerInstanceWhenNaviExit", needDestroy)
|
|
471
|
+
invokeSetterIfExists(params, "setShowExitNaviDialog", showExitDialog)
|
|
472
|
+
invokeSetterIfExists(params, "setUseInnerVoice", useInnerVoice)
|
|
473
|
+
|
|
474
|
+
// 不依赖 Context 的参数
|
|
475
|
+
invokeSetterIfExists(params, "setMultipleRouteNaviMode", options["multipleRouteNaviMode"])
|
|
476
|
+
invokeSetterIfExists(params, "setTruckMultipleRouteNaviMode", options["truckMultipleRouteNaviMode"])
|
|
477
|
+
invokeSetterIfExists(params, "setSecondActionVisible", options["secondActionVisible"])
|
|
478
|
+
invokeSetterIfExists(params, "setShowCrossImage", options["showCrossImage"])
|
|
479
|
+
invokeSetterIfExists(params, "setShowRouteStrategyPreferenceView", options["showRouteStrategyPreferenceView"])
|
|
480
|
+
invokeSetterIfExists(params, "setShowVoiceSetings", options["showVoiceSettings"])
|
|
481
|
+
invokeSetterIfExists(params, "setTrafficEnabled", options["trafficEnabled"])
|
|
482
|
+
invokeSetterIfExists(params, "setRouteStrategy", options["routeStrategy"])
|
|
483
|
+
invokeSetterIfExists(params, "setNaviMode", options["naviMode"])
|
|
484
|
+
|
|
485
|
+
// 依赖 Context 的参数
|
|
486
|
+
invokeSetterWithContextIfExists(params, "setDayAndNightMode", options["dayAndNightMode"])
|
|
487
|
+
invokeSetterWithContextIfExists(params, "setBroadcastMode", options["broadcastMode"])
|
|
488
|
+
invokeSetterWithContextIfExists(params, "setCarDirectionMode", options["carDirectionMode"])
|
|
489
|
+
invokeSetterWithContextIfExists(params, "setScaleAutoChangeEnable", options["scaleAutoChangeEnable"])
|
|
490
|
+
invokeSetterWithContextIfExists(params, "showEagleMap", options["showEagleMap"])
|
|
491
|
+
|
|
492
|
+
// 主题(枚举)
|
|
493
|
+
val themeName = options["theme"] as? String
|
|
494
|
+
if (!themeName.isNullOrBlank()) {
|
|
495
|
+
val themeClass = runCatching {
|
|
496
|
+
Class.forName("com.amap.api.navi.AmapNaviTheme")
|
|
497
|
+
}.getOrNull()
|
|
498
|
+
if (themeClass != null) {
|
|
499
|
+
val theme = resolveEnumConstant(
|
|
500
|
+
enumClass = themeClass,
|
|
501
|
+
preferName = themeName,
|
|
502
|
+
fallbackNames = emptyList()
|
|
503
|
+
)
|
|
504
|
+
invokeSetterIfExists(params, "setTheme", theme)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 车辆信息(用于限行/货车)
|
|
509
|
+
@Suppress("UNCHECKED_CAST")
|
|
510
|
+
val carInfoMap = options["carInfo"] as? Map<String, Any?>
|
|
511
|
+
if (carInfoMap != null) {
|
|
512
|
+
val carInfo = buildCarInfo(carInfoMap)
|
|
513
|
+
invokeSetterIfExists(params, "setCarInfo", carInfo)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private fun buildCarInfo(options: Map<String, Any?>): AMapCarInfo {
|
|
518
|
+
val carInfo = AMapCarInfo()
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
val carType = (options["carType"] as? String)?.trim()?.takeIf { it.isNotEmpty() }
|
|
522
|
+
if (carType != null) carInfo.carType = carType
|
|
523
|
+
} catch (_: Exception) {}
|
|
524
|
+
try {
|
|
525
|
+
val carNumber = (options["carNumber"] as? String)?.trim()?.takeIf { it.isNotEmpty() }
|
|
526
|
+
if (carNumber != null) carInfo.setCarNumber(carNumber)
|
|
527
|
+
} catch (_: Exception) {}
|
|
528
|
+
try {
|
|
529
|
+
val restriction = options["restriction"] as? Boolean
|
|
530
|
+
if (restriction != null) carInfo.isRestriction = restriction
|
|
531
|
+
} catch (_: Exception) {}
|
|
532
|
+
try {
|
|
533
|
+
val motorcycleCC = (options["motorcycleCC"] as? Number)?.toInt()
|
|
534
|
+
if (motorcycleCC != null) carInfo.motorcycleCC = motorcycleCC
|
|
535
|
+
} catch (_: Exception) {}
|
|
536
|
+
|
|
537
|
+
// 货车相关可选参数(部分版本 SDK 可能不存在对应 setter,故用反射安全调用)
|
|
538
|
+
invokeSetterIfExists(carInfo, "setVehicleAxis", options["vehicleAxis"])
|
|
539
|
+
invokeSetterIfExists(carInfo, "setVehicleHeight", options["vehicleHeight"])
|
|
540
|
+
invokeSetterIfExists(carInfo, "setVehicleLength", options["vehicleLength"])
|
|
541
|
+
invokeSetterIfExists(carInfo, "setVehicleWidth", options["vehicleWidth"])
|
|
542
|
+
invokeSetterIfExists(carInfo, "setVehicleSize", options["vehicleSize"])
|
|
543
|
+
invokeSetterIfExists(carInfo, "setVehicleLoad", options["vehicleLoad"])
|
|
544
|
+
invokeSetterIfExists(carInfo, "setVehicleWeight", options["vehicleWeight"])
|
|
545
|
+
invokeSetterIfExists(carInfo, "setVehicleLoadSwitch", options["vehicleLoadSwitch"])
|
|
546
|
+
|
|
547
|
+
return carInfo
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private fun invokeSetterIfExists(target: Any, methodName: String, value: Any?) {
|
|
551
|
+
if (value == null) return
|
|
552
|
+
val methods = target.javaClass.methods.filter { method ->
|
|
553
|
+
method.name == methodName && method.parameterTypes.size == 1
|
|
554
|
+
}
|
|
555
|
+
for (method in methods) {
|
|
556
|
+
val converted = coerceValue(method.parameterTypes[0], value) ?: continue
|
|
557
|
+
try {
|
|
558
|
+
method.invoke(target, converted)
|
|
559
|
+
return
|
|
560
|
+
} catch (_: Exception) {
|
|
561
|
+
// try next overload
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private fun invokeSetterWithContextIfExists(target: Any, methodName: String, value: Any?) {
|
|
567
|
+
if (value == null) return
|
|
568
|
+
val appCtx = context.applicationContext ?: context
|
|
569
|
+
val methods = target.javaClass.methods.filter { method ->
|
|
570
|
+
method.name == methodName && method.parameterTypes.size == 2
|
|
571
|
+
}
|
|
572
|
+
for (method in methods) {
|
|
573
|
+
val params = method.parameterTypes
|
|
574
|
+
if (!params[0].isAssignableFrom(appCtx.javaClass)) continue
|
|
575
|
+
val converted = coerceValue(params[1], value) ?: continue
|
|
576
|
+
try {
|
|
577
|
+
method.invoke(target, appCtx, converted)
|
|
578
|
+
return
|
|
579
|
+
} catch (_: Exception) {
|
|
580
|
+
// try next overload
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private fun coerceValue(expectedType: Class<*>, value: Any): Any? {
|
|
586
|
+
val boxed = boxPrimitive(expectedType)
|
|
587
|
+
if (boxed.isAssignableFrom(value.javaClass)) return value
|
|
588
|
+
|
|
589
|
+
return when (boxed) {
|
|
590
|
+
java.lang.Integer::class.java -> (value as? Number)?.toInt()
|
|
591
|
+
java.lang.Long::class.java -> (value as? Number)?.toLong()
|
|
592
|
+
java.lang.Double::class.java -> (value as? Number)?.toDouble()
|
|
593
|
+
java.lang.Float::class.java -> (value as? Number)?.toFloat()
|
|
594
|
+
java.lang.Short::class.java -> (value as? Number)?.toShort()
|
|
595
|
+
java.lang.Byte::class.java -> (value as? Number)?.toByte()
|
|
596
|
+
java.lang.Boolean::class.java -> when (value) {
|
|
597
|
+
is Boolean -> value
|
|
598
|
+
is Number -> value.toInt() != 0
|
|
599
|
+
is String -> value.equals("true", ignoreCase = true) || value == "1"
|
|
600
|
+
else -> null
|
|
601
|
+
}
|
|
602
|
+
java.lang.String::class.java -> value.toString()
|
|
603
|
+
else -> {
|
|
604
|
+
if (boxed.isEnum && value is String) {
|
|
605
|
+
val byName = boxed.enumConstants
|
|
606
|
+
?.associateBy { (it as Enum<*>).name.uppercase(Locale.ROOT) }
|
|
607
|
+
byName?.get(value.trim().uppercase(Locale.ROOT))
|
|
608
|
+
} else {
|
|
609
|
+
null
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private fun showOfficialNaviPage(params: Any) {
|
|
616
|
+
val pageClass = Class.forName("com.amap.api.navi.AmapNaviPage")
|
|
617
|
+
val pageInstance = pageClass.getMethod("getInstance").invoke(null)
|
|
618
|
+
val showMethod = pageClass.methods.firstOrNull { method ->
|
|
619
|
+
method.name == "showRouteActivity" && method.parameterTypes.size == 3
|
|
620
|
+
} ?: throw Exception("未找到 showRouteActivity 方法")
|
|
621
|
+
|
|
622
|
+
val appCtx = context.applicationContext ?: context
|
|
623
|
+
showMethod.invoke(pageInstance, appCtx, params, null)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private fun resolveEnumConstant(
|
|
627
|
+
enumClass: Class<*>,
|
|
628
|
+
preferName: String?,
|
|
629
|
+
fallbackNames: List<String>
|
|
630
|
+
): Any {
|
|
631
|
+
val constants = enumClass.enumConstants
|
|
632
|
+
?: throw Exception("${enumClass.simpleName} is not enum")
|
|
633
|
+
val byName = constants.associateBy { (it as Enum<*>).name.uppercase(Locale.ROOT) }
|
|
634
|
+
|
|
635
|
+
val preferred = preferName?.trim()?.uppercase(Locale.ROOT)
|
|
636
|
+
if (!preferred.isNullOrEmpty()) {
|
|
637
|
+
byName[preferred]?.let { return it }
|
|
638
|
+
}
|
|
639
|
+
for (name in fallbackNames) {
|
|
640
|
+
byName[name.uppercase(Locale.ROOT)]?.let { return it }
|
|
641
|
+
}
|
|
642
|
+
return constants.first()
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private fun loadFirstAvailableClass(vararg classNames: String): Class<*>? {
|
|
646
|
+
for (name in classNames) {
|
|
647
|
+
val clazz = runCatching { Class.forName(name) }.getOrNull()
|
|
648
|
+
if (clazz != null) return clazz
|
|
649
|
+
}
|
|
650
|
+
return null
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private fun instantiateIfMatch(clazz: Class<*>, args: Array<Any?>): Any? {
|
|
654
|
+
val constructors = clazz.constructors.filter { constructor ->
|
|
655
|
+
constructor.parameterTypes.size == args.size
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
for (constructor in constructors) {
|
|
659
|
+
if (!areArgsCompatible(constructor.parameterTypes, args)) continue
|
|
660
|
+
try {
|
|
661
|
+
return constructor.newInstance(*args)
|
|
662
|
+
} catch (_: Exception) {
|
|
663
|
+
// try next constructor
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return null
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private fun areArgsCompatible(paramTypes: Array<Class<*>>, args: Array<Any?>): Boolean {
|
|
670
|
+
if (paramTypes.size != args.size) return false
|
|
671
|
+
for (index in paramTypes.indices) {
|
|
672
|
+
val expected = boxPrimitive(paramTypes[index])
|
|
673
|
+
val actual = args[index]
|
|
674
|
+
if (actual == null) {
|
|
675
|
+
if (expected.isPrimitive) return false
|
|
676
|
+
continue
|
|
677
|
+
}
|
|
678
|
+
if (!expected.isAssignableFrom(actual.javaClass)) return false
|
|
679
|
+
}
|
|
680
|
+
return true
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private fun boxPrimitive(type: Class<*>): Class<*> {
|
|
684
|
+
return when (type) {
|
|
685
|
+
java.lang.Integer.TYPE -> java.lang.Integer::class.java
|
|
686
|
+
java.lang.Long.TYPE -> java.lang.Long::class.java
|
|
687
|
+
java.lang.Boolean.TYPE -> java.lang.Boolean::class.java
|
|
688
|
+
java.lang.Float.TYPE -> java.lang.Float::class.java
|
|
689
|
+
java.lang.Double.TYPE -> java.lang.Double::class.java
|
|
690
|
+
java.lang.Short.TYPE -> java.lang.Short::class.java
|
|
691
|
+
java.lang.Byte.TYPE -> java.lang.Byte::class.java
|
|
692
|
+
java.lang.Character.TYPE -> java.lang.Character::class.java
|
|
693
|
+
else -> type
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
313
697
|
/**
|
|
314
698
|
* 发送事件到 JS 层
|
|
315
699
|
*/
|
|
316
700
|
internal fun sendEvent(name: String, params: Map<String, Any?>?) {
|
|
317
701
|
this@ExpoGaodeMapNavigationModule.sendEvent(name, params)
|
|
318
702
|
}
|
|
319
|
-
}
|
|
703
|
+
}
|
|
@@ -3,6 +3,8 @@ package expo.modules.gaodemap.navigation.routes.drive
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import com.amap.api.navi.AMapNavi
|
|
5
5
|
import com.amap.api.navi.model.AMapCarInfo
|
|
6
|
+
import com.amap.api.navi.model.NaviLatLng
|
|
7
|
+
import com.amap.api.navi.model.NaviPoi
|
|
6
8
|
import expo.modules.gaodemap.navigation.ExpoGaodeMapNavigationModule
|
|
7
9
|
import expo.modules.gaodemap.navigation.listeners.RouteCalculateListener
|
|
8
10
|
import expo.modules.gaodemap.navigation.utils.Converters
|
|
@@ -22,6 +24,10 @@ class DriveTruckRouteCalculator(
|
|
|
22
24
|
private val context: Context,
|
|
23
25
|
private val module: ExpoGaodeMapNavigationModule
|
|
24
26
|
) {
|
|
27
|
+
companion object {
|
|
28
|
+
private const val TAG = "DriveTruckRouteCalculator"
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
private var aMapNavi: AMapNavi? = null
|
|
26
32
|
private var routeListener: RouteCalculateListener? = null
|
|
27
33
|
private var isInitialized = false
|
|
@@ -97,7 +103,8 @@ class DriveTruckRouteCalculator(
|
|
|
97
103
|
val fromPoi = from?.let { Converters.parseNaviPoi(it) }
|
|
98
104
|
val toPoi = Converters.parseNaviPoi(to)
|
|
99
105
|
@Suppress("UNCHECKED_CAST")
|
|
100
|
-
val
|
|
106
|
+
val waypointsRaw = options["waypoints"] as? List<Map<String, Any?>>
|
|
107
|
+
val waypoints = Converters.parseWaypoints(waypointsRaw)
|
|
101
108
|
|
|
102
109
|
// 1) 直接传策略值,或 2) 通过 strategyConvert 计算
|
|
103
110
|
val strategy: Int = if (options.containsKey("strategy")) {
|
|
@@ -141,8 +148,25 @@ class DriveTruckRouteCalculator(
|
|
|
141
148
|
android.util.Log.d("DriveTruckRouteCalculator", "起点: $fromPoi")
|
|
142
149
|
android.util.Log.d("DriveTruckRouteCalculator", "终点: $toPoi")
|
|
143
150
|
android.util.Log.d("DriveTruckRouteCalculator", "策略: $strategy")
|
|
144
|
-
|
|
145
|
-
val
|
|
151
|
+
|
|
152
|
+
val fromLatLng = from?.let { Converters.parseNaviLatLng(it) }
|
|
153
|
+
val toLatLng = Converters.parseNaviLatLng(to)
|
|
154
|
+
val waypointsLatLng = Converters.parseWaypointsLatLng(waypointsRaw)
|
|
155
|
+
val avoidPolygons = Converters.parseAvoidPolygons(options["avoidPolygons"])
|
|
156
|
+
val avoidRoad = (options["avoidRoad"] as? String)?.trim()?.takeIf { it.isNotEmpty() }
|
|
157
|
+
|
|
158
|
+
val result = calculateDriveRouteWithOptionalAvoid(
|
|
159
|
+
fromPoi = fromPoi,
|
|
160
|
+
toPoi = toPoi,
|
|
161
|
+
waypointsPoi = waypoints,
|
|
162
|
+
fromLatLng = fromLatLng,
|
|
163
|
+
toLatLng = toLatLng,
|
|
164
|
+
waypointsLatLng = waypointsLatLng,
|
|
165
|
+
strategy = strategy,
|
|
166
|
+
avoidPolygons = avoidPolygons,
|
|
167
|
+
avoidRoad = avoidRoad
|
|
168
|
+
)
|
|
169
|
+
|
|
146
170
|
android.util.Log.d("DriveTruckRouteCalculator", "calculateDriveRoute 返回: $result")
|
|
147
171
|
}
|
|
148
172
|
|
|
@@ -193,4 +217,156 @@ class DriveTruckRouteCalculator(
|
|
|
193
217
|
routeListener = null
|
|
194
218
|
}
|
|
195
219
|
}
|
|
196
|
-
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 兼容不同版本 SDK 的避让参数能力:
|
|
223
|
+
* - 若传了 avoidPolygons / avoidRoad,优先尝试反射调用带避让参数的重载;
|
|
224
|
+
* - 不可用时回退到普通算路,保证兼容性。
|
|
225
|
+
*/
|
|
226
|
+
private fun calculateDriveRouteWithOptionalAvoid(
|
|
227
|
+
fromPoi: NaviPoi?,
|
|
228
|
+
toPoi: NaviPoi,
|
|
229
|
+
waypointsPoi: List<NaviPoi>,
|
|
230
|
+
fromLatLng: NaviLatLng?,
|
|
231
|
+
toLatLng: NaviLatLng,
|
|
232
|
+
waypointsLatLng: List<NaviLatLng>,
|
|
233
|
+
strategy: Int,
|
|
234
|
+
avoidPolygons: List<List<NaviLatLng>>,
|
|
235
|
+
avoidRoad: String?
|
|
236
|
+
): Boolean {
|
|
237
|
+
val navi = aMapNavi ?: return false
|
|
238
|
+
val hasAvoidPolygon = avoidPolygons.isNotEmpty()
|
|
239
|
+
val hasAvoidRoad = !avoidRoad.isNullOrBlank()
|
|
240
|
+
|
|
241
|
+
if (hasAvoidPolygon || hasAvoidRoad) {
|
|
242
|
+
val reflected = invokeDriveRouteWithAvoidByReflection(
|
|
243
|
+
navi = navi,
|
|
244
|
+
fromPoi = fromPoi,
|
|
245
|
+
toPoi = toPoi,
|
|
246
|
+
waypointsPoi = waypointsPoi,
|
|
247
|
+
fromLatLng = fromLatLng,
|
|
248
|
+
toLatLng = toLatLng,
|
|
249
|
+
waypointsLatLng = waypointsLatLng,
|
|
250
|
+
strategy = strategy,
|
|
251
|
+
avoidPolygons = avoidPolygons,
|
|
252
|
+
avoidRoad = avoidRoad.orEmpty()
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if (reflected != null) {
|
|
256
|
+
return reflected
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
android.util.Log.w(
|
|
260
|
+
TAG,
|
|
261
|
+
"avoidPolygons/avoidRoad 已传入,但当前 SDK 重载不可用,回退到普通算路"
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return navi.calculateDriveRoute(fromPoi, toPoi, waypointsPoi, strategy)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private fun invokeDriveRouteWithAvoidByReflection(
|
|
269
|
+
navi: AMapNavi,
|
|
270
|
+
fromPoi: NaviPoi?,
|
|
271
|
+
toPoi: NaviPoi,
|
|
272
|
+
waypointsPoi: List<NaviPoi>,
|
|
273
|
+
fromLatLng: NaviLatLng?,
|
|
274
|
+
toLatLng: NaviLatLng,
|
|
275
|
+
waypointsLatLng: List<NaviLatLng>,
|
|
276
|
+
strategy: Int,
|
|
277
|
+
avoidPolygons: List<List<NaviLatLng>>,
|
|
278
|
+
avoidRoad: String
|
|
279
|
+
): Boolean? {
|
|
280
|
+
val startList = fromLatLng?.let { listOf(it) }
|
|
281
|
+
val endList = listOf(toLatLng)
|
|
282
|
+
|
|
283
|
+
val avoidPolygonArgs: List<Any> = buildList {
|
|
284
|
+
if (avoidPolygons.isNotEmpty()) {
|
|
285
|
+
add(avoidPolygons)
|
|
286
|
+
add(avoidPolygons.first())
|
|
287
|
+
add(avoidPolygons.flatten())
|
|
288
|
+
} else {
|
|
289
|
+
add(emptyList<NaviLatLng>())
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 依次尝试常见重载:List 版本、POI 版本;以及是否包含 avoidRoad。
|
|
294
|
+
for (avoidArg in avoidPolygonArgs) {
|
|
295
|
+
if (startList != null) {
|
|
296
|
+
invokeBooleanMethodIfMatch(
|
|
297
|
+
target = navi,
|
|
298
|
+
methodName = "calculateDriveRoute",
|
|
299
|
+
args = arrayOf(startList, endList, waypointsLatLng, avoidArg, avoidRoad, strategy)
|
|
300
|
+
)?.let { return it }
|
|
301
|
+
|
|
302
|
+
invokeBooleanMethodIfMatch(
|
|
303
|
+
target = navi,
|
|
304
|
+
methodName = "calculateDriveRoute",
|
|
305
|
+
args = arrayOf(startList, endList, waypointsLatLng, avoidArg, strategy)
|
|
306
|
+
)?.let { return it }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (fromPoi != null) {
|
|
310
|
+
invokeBooleanMethodIfMatch(
|
|
311
|
+
target = navi,
|
|
312
|
+
methodName = "calculateDriveRoute",
|
|
313
|
+
args = arrayOf(fromPoi, toPoi, waypointsPoi, avoidArg, avoidRoad, strategy)
|
|
314
|
+
)?.let { return it }
|
|
315
|
+
|
|
316
|
+
invokeBooleanMethodIfMatch(
|
|
317
|
+
target = navi,
|
|
318
|
+
methodName = "calculateDriveRoute",
|
|
319
|
+
args = arrayOf(fromPoi, toPoi, waypointsPoi, avoidArg, strategy)
|
|
320
|
+
)?.let { return it }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private fun invokeBooleanMethodIfMatch(
|
|
328
|
+
target: Any,
|
|
329
|
+
methodName: String,
|
|
330
|
+
args: Array<Any>
|
|
331
|
+
): Boolean? {
|
|
332
|
+
val methods = target.javaClass.methods.filter { method ->
|
|
333
|
+
method.name == methodName && method.parameterTypes.size == args.size
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
for (method in methods) {
|
|
337
|
+
if (!areArgsCompatible(method.parameterTypes, args)) continue
|
|
338
|
+
try {
|
|
339
|
+
val value = method.invoke(target, *args)
|
|
340
|
+
if (value is Boolean) return value
|
|
341
|
+
} catch (_: Exception) {
|
|
342
|
+
// 尝试下一个重载
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return null
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private fun areArgsCompatible(paramTypes: Array<Class<*>>, args: Array<Any>): Boolean {
|
|
350
|
+
if (paramTypes.size != args.size) return false
|
|
351
|
+
for (index in paramTypes.indices) {
|
|
352
|
+
val expected = boxPrimitive(paramTypes[index])
|
|
353
|
+
val actual = args[index].javaClass
|
|
354
|
+
if (!expected.isAssignableFrom(actual)) return false
|
|
355
|
+
}
|
|
356
|
+
return true
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private fun boxPrimitive(type: Class<*>): Class<*> {
|
|
360
|
+
return when (type) {
|
|
361
|
+
java.lang.Integer.TYPE -> java.lang.Integer::class.java
|
|
362
|
+
java.lang.Long.TYPE -> java.lang.Long::class.java
|
|
363
|
+
java.lang.Boolean.TYPE -> java.lang.Boolean::class.java
|
|
364
|
+
java.lang.Float.TYPE -> java.lang.Float::class.java
|
|
365
|
+
java.lang.Double.TYPE -> java.lang.Double::class.java
|
|
366
|
+
java.lang.Short.TYPE -> java.lang.Short::class.java
|
|
367
|
+
java.lang.Byte.TYPE -> java.lang.Byte::class.java
|
|
368
|
+
java.lang.Character.TYPE -> java.lang.Character::class.java
|
|
369
|
+
else -> type
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|