expo-gaode-map-navigation 2.0.12-next.0 → 2.0.13
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 +296 -7
- package/android/build.gradle +12 -4
- package/android/src/main/AndroidManifest.xml +10 -1
- package/android/src/main/cpp/cluster_jni.cpp +56 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +49 -8
- 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/modules/SDKInitializer.kt +23 -17
- 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/overlays/MarkerView.kt +37 -25
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +6 -6
- package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +103 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +1408 -394
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +121 -1
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +14 -28
- package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +28 -3
- package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +114 -15
- package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +24 -35
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +50 -36
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +21 -12
- package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
- package/android/src/main/res/drawable/landback_0.png +0 -0
- package/android/src/main/res/drawable/landback_1.png +0 -0
- package/android/src/main/res/drawable/landback_2.png +0 -0
- package/android/src/main/res/drawable/landback_3.png +0 -0
- package/android/src/main/res/drawable/landback_4.png +0 -0
- package/android/src/main/res/drawable/landback_5.png +0 -0
- package/android/src/main/res/drawable/landback_6.png +0 -0
- package/android/src/main/res/drawable/landback_7.png +0 -0
- package/android/src/main/res/drawable/landback_8.png +0 -0
- package/android/src/main/res/drawable/landback_9.png +0 -0
- package/android/src/main/res/drawable/landback_a.png +0 -0
- package/android/src/main/res/drawable/landback_b.png +0 -0
- package/android/src/main/res/drawable/landback_c.png +0 -0
- package/android/src/main/res/drawable/landback_d.png +0 -0
- package/android/src/main/res/drawable/landback_e.png +0 -0
- package/android/src/main/res/drawable/landback_f.png +0 -0
- package/android/src/main/res/drawable/landback_g.png +0 -0
- package/android/src/main/res/drawable/landback_h.png +0 -0
- package/android/src/main/res/drawable/landback_i.png +0 -0
- package/android/src/main/res/drawable/landback_j.png +0 -0
- package/android/src/main/res/drawable/landback_k.png +0 -0
- package/android/src/main/res/drawable/landback_l.png +0 -0
- package/android/src/main/res/drawable/landfront_0.png +0 -0
- package/android/src/main/res/drawable/landfront_00.png +0 -0
- package/android/src/main/res/drawable/landfront_1.png +0 -0
- package/android/src/main/res/drawable/landfront_11.png +0 -0
- package/android/src/main/res/drawable/landfront_20.png +0 -0
- package/android/src/main/res/drawable/landfront_21.png +0 -0
- package/android/src/main/res/drawable/landfront_22.png +0 -0
- package/android/src/main/res/drawable/landfront_3.png +0 -0
- package/android/src/main/res/drawable/landfront_33.png +0 -0
- package/android/src/main/res/drawable/landfront_40.png +0 -0
- package/android/src/main/res/drawable/landfront_43.png +0 -0
- package/android/src/main/res/drawable/landfront_44.png +0 -0
- package/android/src/main/res/drawable/landfront_5.png +0 -0
- package/android/src/main/res/drawable/landfront_55.png +0 -0
- package/android/src/main/res/drawable/landfront_61.png +0 -0
- package/android/src/main/res/drawable/landfront_63.png +0 -0
- package/android/src/main/res/drawable/landfront_66.png +0 -0
- package/android/src/main/res/drawable/landfront_70.png +0 -0
- package/android/src/main/res/drawable/landfront_71.png +0 -0
- package/android/src/main/res/drawable/landfront_73.png +0 -0
- package/android/src/main/res/drawable/landfront_77.png +0 -0
- package/android/src/main/res/drawable/landfront_8.png +0 -0
- package/android/src/main/res/drawable/landfront_88.png +0 -0
- package/android/src/main/res/drawable/landfront_90.png +0 -0
- package/android/src/main/res/drawable/landfront_95.png +0 -0
- package/android/src/main/res/drawable/landfront_99.png +0 -0
- package/android/src/main/res/drawable/landfront_a0.png +0 -0
- package/android/src/main/res/drawable/landfront_a8.png +0 -0
- package/android/src/main/res/drawable/landfront_aa.png +0 -0
- package/android/src/main/res/drawable/landfront_b1.png +0 -0
- package/android/src/main/res/drawable/landfront_b5.png +0 -0
- package/android/src/main/res/drawable/landfront_bb.png +0 -0
- package/android/src/main/res/drawable/landfront_c3.png +0 -0
- package/android/src/main/res/drawable/landfront_c8.png +0 -0
- package/android/src/main/res/drawable/landfront_cc.png +0 -0
- package/android/src/main/res/drawable/landfront_d.png +0 -0
- package/android/src/main/res/drawable/landfront_dd.png +0 -0
- package/android/src/main/res/drawable/landfront_e1.png +0 -0
- package/android/src/main/res/drawable/landfront_e5.png +0 -0
- package/android/src/main/res/drawable/landfront_ee.png +0 -0
- package/android/src/main/res/drawable/landfront_f0.png +0 -0
- package/android/src/main/res/drawable/landfront_f1.png +0 -0
- package/android/src/main/res/drawable/landfront_f5.png +0 -0
- package/android/src/main/res/drawable/landfront_ff.png +0 -0
- package/android/src/main/res/drawable/landfront_g3.png +0 -0
- package/android/src/main/res/drawable/landfront_g5.png +0 -0
- package/android/src/main/res/drawable/landfront_gg.png +0 -0
- package/android/src/main/res/drawable/landfront_h1.png +0 -0
- package/android/src/main/res/drawable/landfront_h3.png +0 -0
- package/android/src/main/res/drawable/landfront_h5.png +0 -0
- package/android/src/main/res/drawable/landfront_hh.png +0 -0
- package/android/src/main/res/drawable/landfront_i0.png +0 -0
- package/android/src/main/res/drawable/landfront_i3.png +0 -0
- package/android/src/main/res/drawable/landfront_i5.png +0 -0
- package/android/src/main/res/drawable/landfront_ii.png +0 -0
- package/android/src/main/res/drawable/landfront_j1.png +0 -0
- package/android/src/main/res/drawable/landfront_j8.png +0 -0
- package/android/src/main/res/drawable/landfront_jj.png +0 -0
- package/android/src/main/res/drawable/landfront_kk.png +0 -0
- package/android/src/main/res/drawable/landfront_ll.png +0 -0
- package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
- package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
- package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
- package/build/ExpoGaodeMapNaviView.d.ts +16 -0
- package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
- package/build/ExpoGaodeMapNaviView.js +74 -1
- package/build/ExpoGaodeMapNaviView.js.map +1 -1
- package/build/index.d.ts +56 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +452 -10
- package/build/index.js.map +1 -1
- package/build/map/ExpoGaodeMapModule.d.ts +15 -13
- package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapModule.js +31 -39
- package/build/map/ExpoGaodeMapModule.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/ExpoGaodeMapView.d.ts +3 -4
- package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapView.js +28 -25
- package/build/map/ExpoGaodeMapView.js.map +1 -1
- package/build/map/components/overlays/Circle.d.ts.map +1 -1
- package/build/map/components/overlays/Circle.js +1 -30
- package/build/map/components/overlays/Circle.js.map +1 -1
- package/build/map/components/overlays/Cluster.d.ts.map +1 -1
- package/build/map/components/overlays/Cluster.js +1 -42
- package/build/map/components/overlays/Cluster.js.map +1 -1
- package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/map/components/overlays/HeatMap.js +21 -21
- package/build/map/components/overlays/HeatMap.js.map +1 -1
- package/build/map/components/overlays/Marker.d.ts.map +1 -1
- package/build/map/components/overlays/Marker.js +76 -80
- package/build/map/components/overlays/Marker.js.map +1 -1
- package/build/map/components/overlays/Polygon.d.ts.map +1 -1
- package/build/map/components/overlays/Polygon.js +1 -25
- package/build/map/components/overlays/Polygon.js.map +1 -1
- package/build/map/components/overlays/Polyline.d.ts.map +1 -1
- package/build/map/components/overlays/Polyline.js +1 -31
- package/build/map/components/overlays/Polyline.js.map +1 -1
- package/build/map/index.d.ts +9 -2
- package/build/map/index.d.ts.map +1 -1
- package/build/map/index.js +9 -2
- 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/index.d.ts +2 -2
- package/build/map/types/index.d.ts.map +1 -1
- package/build/map/types/index.js.map +1 -1
- 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/types/native-module.types.d.ts +11 -12
- package/build/map/types/native-module.types.d.ts.map +1 -1
- package/build/map/types/native-module.types.js.map +1 -1
- package/build/map/types/overlays.types.d.ts +9 -14
- package/build/map/types/overlays.types.d.ts.map +1 -1
- package/build/map/types/overlays.types.js.map +1 -1
- package/build/map/types/route-playback.types.d.ts +16 -0
- package/build/map/types/route-playback.types.d.ts.map +1 -1
- package/build/map/types/route-playback.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/types/coordinates.types.d.ts +3 -0
- package/build/types/coordinates.types.d.ts.map +1 -1
- package/build/types/coordinates.types.js.map +1 -1
- package/build/types/independent.types.d.ts +111 -12
- 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 +1 -1
- package/build/types/native-module.types.js.map +1 -1
- package/build/types/naviview.types.d.ts +304 -14
- package/build/types/naviview.types.d.ts.map +1 -1
- package/build/types/naviview.types.js.map +1 -1
- package/build/types/route.types.d.ts +12 -2
- package/build/types/route.types.d.ts.map +1 -1
- package/build/types/route.types.js.map +1 -1
- package/expo-module.config.json +4 -2
- package/ios/ExpoGaodeMapNaviView.swift +2331 -201
- package/ios/ExpoGaodeMapNaviViewModule.swift +109 -1
- package/ios/ExpoGaodeMapNavigation.podspec +2 -1
- package/ios/ExpoGaodeMapNavigationModule.swift +7 -5
- package/ios/managers/IndependentRouteManager.swift +90 -26
- package/ios/map/ExpoGaodeMapModule.swift +72 -21
- package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
- package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
- package/ios/map/ExpoGaodeMapView.swift +23 -5
- package/ios/map/GaodeMapPrivacyManager.swift +26 -18
- package/ios/map/cpp/GeometryEngine.cpp +112 -0
- package/ios/map/cpp/GeometryEngine.hpp +21 -0
- package/ios/map/modules/LocationManager.swift +37 -5
- package/ios/map/overlays/MarkerView.swift +11 -11
- package/ios/map/overlays/MarkerViewModule.swift +4 -4
- package/ios/map/overlays/PolylineView.swift +6 -12
- package/ios/map/utils/ClusterNative.h +8 -0
- package/ios/map/utils/ClusterNative.mm +27 -0
- package/ios/map/utils/PermissionManager.swift +115 -6
- package/ios/routes/drive/DriveTruckRouteCalculator.swift +165 -77
- package/ios/routes/walkride/WalkRideRouteCalculator.swift +127 -1
- package/ios/services/IndependentRouteService.swift +198 -39
- package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
- package/ios/services/NavigationLiveActivityManager.swift +359 -0
- package/package.json +22 -7
- package/plugin/build/withGaodeMap.d.ts +8 -0
- package/plugin/build/withGaodeMap.js +60 -4
- package/scripts/check-expo-modules.js +68 -0
- package/shared/cpp/GeometryEngine.cpp +112 -0
- package/shared/cpp/GeometryEngine.hpp +21 -0
- package/widget-template/README.md +46 -0
- package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/RouteCalculator.kt +0 -173
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <cmath>
|
|
4
4
|
#include <map>
|
|
5
5
|
#include <algorithm>
|
|
6
|
+
#include <limits>
|
|
6
7
|
|
|
7
8
|
namespace gaodemap {
|
|
8
9
|
|
|
@@ -19,6 +20,54 @@ static inline double geo_toDegrees(double radians) {
|
|
|
19
20
|
return radians * kRadiansToDegrees;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
static inline double clampMercatorLatitude(double lat) {
|
|
24
|
+
static constexpr double kMaxMercatorLatitude = 85.05112878;
|
|
25
|
+
if (lat > kMaxMercatorLatitude) return kMaxMercatorLatitude;
|
|
26
|
+
if (lat < -kMaxMercatorLatitude) return -kMaxMercatorLatitude;
|
|
27
|
+
return lat;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static inline double mercatorX01(double lon) {
|
|
31
|
+
double wrapped = std::fmod(lon + 180.0, 360.0);
|
|
32
|
+
if (wrapped < 0.0) {
|
|
33
|
+
wrapped += 360.0;
|
|
34
|
+
}
|
|
35
|
+
return wrapped / 360.0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static inline double mercatorY01(double lat) {
|
|
39
|
+
const double clampedLat = clampMercatorLatitude(lat);
|
|
40
|
+
const double latRad = geo_toRadians(clampedLat);
|
|
41
|
+
const double y = (1.0 - std::asinh(std::tan(latRad)) / kPi) * 0.5;
|
|
42
|
+
if (y < 0.0) return 0.0;
|
|
43
|
+
if (y > 1.0) return 1.0;
|
|
44
|
+
return y;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static double wrappedSpan01(std::vector<double> xs) {
|
|
48
|
+
if (xs.size() <= 1) {
|
|
49
|
+
return 0.0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
std::sort(xs.begin(), xs.end());
|
|
53
|
+
double maxGap = 0.0;
|
|
54
|
+
|
|
55
|
+
for (size_t i = 0; i + 1 < xs.size(); ++i) {
|
|
56
|
+
const double gap = xs[i + 1] - xs[i];
|
|
57
|
+
if (gap > maxGap) {
|
|
58
|
+
maxGap = gap;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const double endGap = xs.front() + 1.0 - xs.back();
|
|
63
|
+
if (endGap > maxGap) {
|
|
64
|
+
maxGap = endGap;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const double span = 1.0 - maxGap;
|
|
68
|
+
return span < 0.0 ? 0.0 : span;
|
|
69
|
+
}
|
|
70
|
+
|
|
22
71
|
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
|
23
72
|
const double radLat1 = geo_toRadians(lat1);
|
|
24
73
|
const double radLat2 = geo_toRadians(lat2);
|
|
@@ -512,6 +561,69 @@ GeoPoint pixelToLatLng(double x, double y, int zoom) {
|
|
|
512
561
|
return {lat, lon};
|
|
513
562
|
}
|
|
514
563
|
|
|
564
|
+
double calculateFitZoomForPoints(
|
|
565
|
+
const std::vector<GeoPoint>& points,
|
|
566
|
+
double viewportWidthPx,
|
|
567
|
+
double viewportHeightPx,
|
|
568
|
+
double paddingPx,
|
|
569
|
+
int minZoom,
|
|
570
|
+
int maxZoom
|
|
571
|
+
) {
|
|
572
|
+
if (minZoom > maxZoom) {
|
|
573
|
+
std::swap(minZoom, maxZoom);
|
|
574
|
+
}
|
|
575
|
+
if (points.empty()) {
|
|
576
|
+
return static_cast<double>(minZoom);
|
|
577
|
+
}
|
|
578
|
+
if (points.size() == 1) {
|
|
579
|
+
return static_cast<double>(maxZoom);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const double safeViewportWidth = viewportWidthPx > 1.0 ? viewportWidthPx : 390.0;
|
|
583
|
+
const double safeViewportHeight = viewportHeightPx > 1.0 ? viewportHeightPx : 844.0;
|
|
584
|
+
const double safePadding = std::max(0.0, paddingPx);
|
|
585
|
+
const double availableWidth = std::max(1.0, safeViewportWidth - safePadding * 2.0);
|
|
586
|
+
const double availableHeight = std::max(1.0, safeViewportHeight - safePadding * 2.0);
|
|
587
|
+
|
|
588
|
+
std::vector<double> projectedXs;
|
|
589
|
+
projectedXs.reserve(points.size());
|
|
590
|
+
|
|
591
|
+
double minY = std::numeric_limits<double>::max();
|
|
592
|
+
double maxY = -std::numeric_limits<double>::max();
|
|
593
|
+
|
|
594
|
+
for (const auto& p : points) {
|
|
595
|
+
projectedXs.push_back(mercatorX01(p.lon));
|
|
596
|
+
const double y = mercatorY01(p.lat);
|
|
597
|
+
if (y < minY) minY = y;
|
|
598
|
+
if (y > maxY) maxY = y;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const double spanX = wrappedSpan01(projectedXs);
|
|
602
|
+
const double spanY = std::max(0.0, maxY - minY);
|
|
603
|
+
static constexpr double kTileSize = 256.0;
|
|
604
|
+
static constexpr double kSpanEpsilon = 1e-12;
|
|
605
|
+
|
|
606
|
+
const double zoomX = spanX <= kSpanEpsilon
|
|
607
|
+
? static_cast<double>(maxZoom)
|
|
608
|
+
: std::log2(availableWidth / (kTileSize * spanX));
|
|
609
|
+
const double zoomY = spanY <= kSpanEpsilon
|
|
610
|
+
? static_cast<double>(maxZoom)
|
|
611
|
+
: std::log2(availableHeight / (kTileSize * spanY));
|
|
612
|
+
|
|
613
|
+
double fitZoom = std::min(zoomX, zoomY);
|
|
614
|
+
if (!std::isfinite(fitZoom)) {
|
|
615
|
+
fitZoom = static_cast<double>(minZoom);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (fitZoom < static_cast<double>(minZoom)) {
|
|
619
|
+
fitZoom = static_cast<double>(minZoom);
|
|
620
|
+
} else if (fitZoom > static_cast<double>(maxZoom)) {
|
|
621
|
+
fitZoom = static_cast<double>(maxZoom);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return fitZoom;
|
|
625
|
+
}
|
|
626
|
+
|
|
515
627
|
// --- 批量地理围栏与热力图 ---
|
|
516
628
|
|
|
517
629
|
int findPointInPolygons(double pointLat, double pointLon, const std::vector<std::vector<GeoPoint>>& polygons) {
|
|
@@ -125,6 +125,27 @@ PixelResult latLngToPixel(double lat, double lon, int zoom);
|
|
|
125
125
|
*/
|
|
126
126
|
GeoPoint pixelToLatLng(double x, double y, int zoom);
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* 根据一组坐标点和视口尺寸计算“可同时看到所有点”的推荐缩放级别。
|
|
130
|
+
* 使用 Web Mercator 投影,在跨经线场景下会自动取更小经度跨度。
|
|
131
|
+
*
|
|
132
|
+
* @param points 坐标点集合,至少 1 个
|
|
133
|
+
* @param viewportWidthPx 视口宽度(像素)
|
|
134
|
+
* @param viewportHeightPx 视口高度(像素)
|
|
135
|
+
* @param paddingPx 四周预留的内边距(像素)
|
|
136
|
+
* @param minZoom 最小缩放级别
|
|
137
|
+
* @param maxZoom 最大缩放级别
|
|
138
|
+
* @return 推荐 zoom(已在 [minZoom, maxZoom] 范围内)
|
|
139
|
+
*/
|
|
140
|
+
double calculateFitZoomForPoints(
|
|
141
|
+
const std::vector<GeoPoint>& points,
|
|
142
|
+
double viewportWidthPx,
|
|
143
|
+
double viewportHeightPx,
|
|
144
|
+
double paddingPx,
|
|
145
|
+
int minZoom,
|
|
146
|
+
int maxZoom
|
|
147
|
+
);
|
|
148
|
+
|
|
128
149
|
// --- 批量地理围栏与热力图 ---
|
|
129
150
|
|
|
130
151
|
/**
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# iOS Live Activity Widget Extension 模板
|
|
2
|
+
|
|
3
|
+
该目录提供 `ActivityConfiguration` 示例,用于展示 `expo-gaode-map-navigation` 在 iOS 锁屏和灵动岛的实时导航信息。
|
|
4
|
+
|
|
5
|
+
## 文件
|
|
6
|
+
|
|
7
|
+
- `ios/NavigationLiveActivityWidget.swift`:Widget Extension 入口与 UI。
|
|
8
|
+
|
|
9
|
+
## 接入步骤
|
|
10
|
+
|
|
11
|
+
1. 在 Xcode 中给你的 iOS App 新建 `Widget Extension`(iOS 16.1+)。
|
|
12
|
+
2. 把 `ios/NavigationLiveActivityWidget.swift` 拖入新 Extension target,并确保 target membership 只勾选 Extension。
|
|
13
|
+
3. Extension 代码需要 `import ExpoGaodeMapNavigation`,并使用库里提供的 `NavigationLiveActivityAttributes`。
|
|
14
|
+
4. 在 `app.json/app.config.ts` 开启插件配置:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
[
|
|
18
|
+
"expo-gaode-map-navigation",
|
|
19
|
+
{
|
|
20
|
+
"enableIOSLiveActivity": true,
|
|
21
|
+
"enableIOSLiveActivityFrequentUpdates": true
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
5. 导航组件开启运行时开关:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<ExpoGaodeMapNaviView iosLiveActivityEnabled />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Podfile(含 Extension 时)
|
|
33
|
+
|
|
34
|
+
如果 Extension 无法 import `ExpoGaodeMapNavigation`,在 `ios/Podfile` 中为 Extension target 加上 `use_expo_modules!`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
target 'YourNavigationWidgetExtension' do
|
|
38
|
+
use_expo_modules!
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
然后执行:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd ios && pod install
|
|
46
|
+
```
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import ActivityKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import UIKit
|
|
4
|
+
import WidgetKit
|
|
5
|
+
import ExpoGaodeMapNavigation
|
|
6
|
+
|
|
7
|
+
private struct TurnMeta {
|
|
8
|
+
let action: String
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private func turnMeta(for iconType: Int) -> TurnMeta {
|
|
12
|
+
switch iconType {
|
|
13
|
+
case 2:
|
|
14
|
+
return TurnMeta(action: "左转")
|
|
15
|
+
case 3:
|
|
16
|
+
return TurnMeta(action: "右转")
|
|
17
|
+
case 4:
|
|
18
|
+
return TurnMeta(action: "左前方")
|
|
19
|
+
case 5:
|
|
20
|
+
return TurnMeta(action: "右前方")
|
|
21
|
+
case 6:
|
|
22
|
+
return TurnMeta(action: "左后方")
|
|
23
|
+
case 7:
|
|
24
|
+
return TurnMeta(action: "右后方")
|
|
25
|
+
case 8:
|
|
26
|
+
return TurnMeta(action: "左转调头")
|
|
27
|
+
case 9, 20:
|
|
28
|
+
return TurnMeta(action: "直行")
|
|
29
|
+
case 11, 12:
|
|
30
|
+
return TurnMeta(action: "环岛")
|
|
31
|
+
case 15:
|
|
32
|
+
return TurnMeta(action: "到达目的地")
|
|
33
|
+
case 19:
|
|
34
|
+
return TurnMeta(action: "右转调头")
|
|
35
|
+
case 65:
|
|
36
|
+
return TurnMeta(action: "靠左行驶")
|
|
37
|
+
case 66:
|
|
38
|
+
return TurnMeta(action: "靠右行驶")
|
|
39
|
+
default:
|
|
40
|
+
return TurnMeta(action: "继续")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func formatDistanceCN(_ meters: Int, spacedUnit: Bool = false) -> String {
|
|
45
|
+
let safeMeters = max(meters, 0)
|
|
46
|
+
if safeMeters >= 1000 {
|
|
47
|
+
let kilometers = Double(safeMeters) / 1000.0
|
|
48
|
+
let value = kilometers >= 10 ? String(format: "%.0f", kilometers) : String(format: "%.1f", kilometers)
|
|
49
|
+
return spacedUnit ? "\(value) 公里" : "\(value)公里"
|
|
50
|
+
}
|
|
51
|
+
return spacedUnit ? "\(safeMeters) 米" : "\(safeMeters)米"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private func formatTimeCN(_ seconds: Int) -> String {
|
|
55
|
+
let safeSeconds = max(seconds, 0)
|
|
56
|
+
if safeSeconds >= 3600 {
|
|
57
|
+
return "约\(safeSeconds / 3600)小时\((safeSeconds % 3600) / 60)分钟"
|
|
58
|
+
}
|
|
59
|
+
return "约\(max(safeSeconds / 60, 1))分钟"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private func preferredRoadName(current: String, next: String) -> String {
|
|
63
|
+
if !next.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
64
|
+
return next.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
65
|
+
}
|
|
66
|
+
if !current.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
67
|
+
return current.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
68
|
+
}
|
|
69
|
+
return ""
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private func compactInstruction(distanceMeters: Int, roadName: String, action: String) -> String {
|
|
73
|
+
if distanceMeters > 0 {
|
|
74
|
+
if roadName.isEmpty {
|
|
75
|
+
return "\(formatDistanceCN(distanceMeters))后\(action)"
|
|
76
|
+
}
|
|
77
|
+
return "\(formatDistanceCN(distanceMeters))后进入\(roadName)"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if !roadName.isEmpty {
|
|
81
|
+
return "进入\(roadName)"
|
|
82
|
+
}
|
|
83
|
+
return "正在导航"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private func liveTurnIconImage(base64: String?) -> Image? {
|
|
87
|
+
guard let base64, !base64.isEmpty, let data = Data(base64Encoded: base64) else {
|
|
88
|
+
return nil
|
|
89
|
+
}
|
|
90
|
+
guard let image = UIImage(data: data) else {
|
|
91
|
+
return nil
|
|
92
|
+
}
|
|
93
|
+
return Image(uiImage: image).renderingMode(.original)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private func routeProgress(remainDistance: Int, totalDistance: Int) -> Double {
|
|
97
|
+
guard totalDistance > 0 else {
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
let safeRemain = min(max(remainDistance, 0), totalDistance)
|
|
101
|
+
let finished = totalDistance - safeRemain
|
|
102
|
+
return min(max(Double(finished) / Double(totalDistance), 0), 1)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private func clampedProgress(_ value: Double) -> Double {
|
|
106
|
+
min(max(value, 0), 1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private struct NavigationProgressTrack: View {
|
|
110
|
+
let progress: Double
|
|
111
|
+
var showsIndicator: Bool = true
|
|
112
|
+
var trackHeight: CGFloat = 7
|
|
113
|
+
|
|
114
|
+
var body: some View {
|
|
115
|
+
GeometryReader { proxy in
|
|
116
|
+
let width = max(proxy.size.width, 1)
|
|
117
|
+
let normalizedProgress = clampedProgress(progress)
|
|
118
|
+
let carWidth: CGFloat = 34
|
|
119
|
+
let trackYOffset: CGFloat = showsIndicator ? 5 : 0
|
|
120
|
+
let indicatorOffset = min(max(0, normalizedProgress * max(width - carWidth, 0)), max(width - carWidth, 0))
|
|
121
|
+
let fillWidth = min(width, max(trackHeight, indicatorOffset + (carWidth * 0.45)))
|
|
122
|
+
|
|
123
|
+
ZStack(alignment: .leading) {
|
|
124
|
+
Capsule(style: .continuous)
|
|
125
|
+
.fill(Color.white.opacity(0.18))
|
|
126
|
+
.frame(height: trackHeight)
|
|
127
|
+
.offset(y: trackYOffset)
|
|
128
|
+
|
|
129
|
+
Capsule(style: .continuous)
|
|
130
|
+
.fill(
|
|
131
|
+
LinearGradient(
|
|
132
|
+
colors: [
|
|
133
|
+
Color(red: 0.32, green: 0.78, blue: 0.98),
|
|
134
|
+
Color(red: 0.24, green: 0.70, blue: 0.98)
|
|
135
|
+
],
|
|
136
|
+
startPoint: .leading,
|
|
137
|
+
endPoint: .trailing
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
.frame(width: fillWidth, height: trackHeight)
|
|
141
|
+
.offset(y: trackYOffset)
|
|
142
|
+
|
|
143
|
+
if showsIndicator {
|
|
144
|
+
ZStack {
|
|
145
|
+
Circle()
|
|
146
|
+
.fill(
|
|
147
|
+
LinearGradient(
|
|
148
|
+
colors: [
|
|
149
|
+
Color(red: 0.08, green: 0.66, blue: 0.96),
|
|
150
|
+
Color(red: 0.18, green: 0.80, blue: 0.98)
|
|
151
|
+
],
|
|
152
|
+
startPoint: .topLeading,
|
|
153
|
+
endPoint: .bottomTrailing
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
Image(systemName: "car.side.fill")
|
|
157
|
+
.font(.system(size: 12, weight: .bold))
|
|
158
|
+
.foregroundStyle(.white)
|
|
159
|
+
}
|
|
160
|
+
.frame(width: 24, height: 24)
|
|
161
|
+
.shadow(color: Color.black.opacity(0.16), radius: 2, x: 0, y: 1)
|
|
162
|
+
.offset(x: indicatorOffset, y: trackYOffset)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
|
166
|
+
}
|
|
167
|
+
.frame(height: showsIndicator ? 24 : max(trackHeight, 4))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@main
|
|
172
|
+
struct NavigationLiveActivityWidgetBundle: WidgetBundle {
|
|
173
|
+
var body: some Widget {
|
|
174
|
+
NavigationLiveActivityWidget()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
struct NavigationLiveActivityWidget: Widget {
|
|
179
|
+
var body: some WidgetConfiguration {
|
|
180
|
+
ActivityConfiguration(for: NavigationLiveActivityAttributes.self) { context in
|
|
181
|
+
LockScreenNavigationLiveActivityView(context: context)
|
|
182
|
+
.activityBackgroundTint(.clear)
|
|
183
|
+
.activitySystemActionForegroundColor(.white)
|
|
184
|
+
} dynamicIsland: { context in
|
|
185
|
+
let meta = turnMeta(for: context.state.iconType)
|
|
186
|
+
let roadName = preferredRoadName(current: context.state.currentRoadName, next: context.state.nextRoadName)
|
|
187
|
+
let totalDistance = max(context.state.routeTotalDistance, max(context.state.pathRetainDistance, 0))
|
|
188
|
+
let progress = routeProgress(
|
|
189
|
+
remainDistance: context.state.pathRetainDistance,
|
|
190
|
+
totalDistance: totalDistance
|
|
191
|
+
)
|
|
192
|
+
let instruction = compactInstruction(
|
|
193
|
+
distanceMeters: max(context.state.curStepRetainDistance, 0),
|
|
194
|
+
roadName: roadName,
|
|
195
|
+
action: meta.action
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return DynamicIsland {
|
|
199
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
200
|
+
Group {
|
|
201
|
+
if let turnIconImage = liveTurnIconImage(base64: context.state.turnIconBase64) {
|
|
202
|
+
turnIconImage
|
|
203
|
+
.resizable()
|
|
204
|
+
.scaledToFit()
|
|
205
|
+
.padding(6)
|
|
206
|
+
} else {
|
|
207
|
+
Color.clear
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
.frame(width: 34, height: 34)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
DynamicIslandExpandedRegion(.center) {
|
|
214
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
215
|
+
Text(context.attributes.appName)
|
|
216
|
+
.font(.caption2)
|
|
217
|
+
.foregroundStyle(.white.opacity(0.75))
|
|
218
|
+
Text(instruction)
|
|
219
|
+
.font(.subheadline.weight(.semibold))
|
|
220
|
+
.foregroundStyle(.white)
|
|
221
|
+
.lineLimit(1)
|
|
222
|
+
NavigationProgressTrack(progress: progress, showsIndicator: false, trackHeight: 4)
|
|
223
|
+
.frame(height: 6)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
228
|
+
Text(formatDistanceCN(context.state.pathRetainDistance))
|
|
229
|
+
.font(.caption.weight(.semibold))
|
|
230
|
+
.foregroundStyle(.white.opacity(0.92))
|
|
231
|
+
}
|
|
232
|
+
} compactLeading: {
|
|
233
|
+
Group {
|
|
234
|
+
if let turnIconImage = liveTurnIconImage(base64: context.state.turnIconBase64) {
|
|
235
|
+
turnIconImage
|
|
236
|
+
.resizable()
|
|
237
|
+
.scaledToFit()
|
|
238
|
+
} else {
|
|
239
|
+
Color.clear
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} compactTrailing: {
|
|
243
|
+
Text(formatDistanceCN(context.state.curStepRetainDistance))
|
|
244
|
+
.font(.caption2)
|
|
245
|
+
.foregroundStyle(.white)
|
|
246
|
+
} minimal: {
|
|
247
|
+
Group {
|
|
248
|
+
if let turnIconImage = liveTurnIconImage(base64: context.state.turnIconBase64) {
|
|
249
|
+
turnIconImage
|
|
250
|
+
.resizable()
|
|
251
|
+
.scaledToFit()
|
|
252
|
+
} else {
|
|
253
|
+
Color.clear
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
.keylineTint(.white)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private struct LockScreenNavigationLiveActivityView: View {
|
|
263
|
+
let context: ActivityViewContext<NavigationLiveActivityAttributes>
|
|
264
|
+
|
|
265
|
+
private var meta: TurnMeta {
|
|
266
|
+
turnMeta(for: context.state.iconType)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private var roadName: String {
|
|
270
|
+
preferredRoadName(current: context.state.currentRoadName, next: context.state.nextRoadName)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private var stepDistanceText: String {
|
|
274
|
+
if context.state.iconType == 15 {
|
|
275
|
+
return "到达目的地"
|
|
276
|
+
}
|
|
277
|
+
let step = max(context.state.curStepRetainDistance, 0)
|
|
278
|
+
guard step > 0 else {
|
|
279
|
+
return meta.action == "继续" ? "继续前行" : meta.action
|
|
280
|
+
}
|
|
281
|
+
return formatDistanceCN(step, spacedUnit: true)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private var instructionText: String {
|
|
285
|
+
if context.state.iconType == 15 {
|
|
286
|
+
return ""
|
|
287
|
+
}
|
|
288
|
+
if !roadName.isEmpty {
|
|
289
|
+
return "进入 \(roadName)"
|
|
290
|
+
}
|
|
291
|
+
return meta.action == "继续" ? "正在导航" : meta.action
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private var remainText: String {
|
|
295
|
+
"剩余 \(formatDistanceCN(context.state.pathRetainDistance, spacedUnit: true)) · \(formatTimeCN(context.state.pathRetainTime))"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private var routeTotalDistance: Int {
|
|
299
|
+
max(context.state.routeTotalDistance, max(context.state.pathRetainDistance, 0))
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private var progressValue: Double {
|
|
303
|
+
routeProgress(remainDistance: context.state.pathRetainDistance, totalDistance: routeTotalDistance)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
var body: some View {
|
|
307
|
+
ZStack {
|
|
308
|
+
RoundedRectangle(cornerRadius: 24, style: .continuous)
|
|
309
|
+
.fill(
|
|
310
|
+
LinearGradient(
|
|
311
|
+
colors: [
|
|
312
|
+
Color(red: 0.10, green: 0.12, blue: 0.19),
|
|
313
|
+
Color(red: 0.08, green: 0.10, blue: 0.16)
|
|
314
|
+
],
|
|
315
|
+
startPoint: .topLeading,
|
|
316
|
+
endPoint: .bottomTrailing
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
321
|
+
HStack(spacing: 14) {
|
|
322
|
+
Group {
|
|
323
|
+
if let turnIconImage = liveTurnIconImage(base64: context.state.turnIconBase64) {
|
|
324
|
+
turnIconImage
|
|
325
|
+
.resizable()
|
|
326
|
+
.scaledToFit()
|
|
327
|
+
.padding(12)
|
|
328
|
+
} else {
|
|
329
|
+
Color.clear
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
.frame(width: 78, height: 78)
|
|
333
|
+
|
|
334
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
335
|
+
Text(stepDistanceText)
|
|
336
|
+
.font(.system(size: 38, weight: .heavy, design: .rounded))
|
|
337
|
+
.foregroundStyle(.white)
|
|
338
|
+
.lineLimit(1)
|
|
339
|
+
.minimumScaleFactor(0.65)
|
|
340
|
+
|
|
341
|
+
if !instructionText.isEmpty {
|
|
342
|
+
Text(instructionText)
|
|
343
|
+
.font(.system(size: 23, weight: .semibold, design: .rounded))
|
|
344
|
+
.foregroundStyle(.white.opacity(0.96))
|
|
345
|
+
.lineLimit(1)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
NavigationProgressTrack(progress: progressValue, showsIndicator: true, trackHeight: 8)
|
|
352
|
+
.frame(height: 24)
|
|
353
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
354
|
+
|
|
355
|
+
Text(remainText)
|
|
356
|
+
.font(.system(size: 13, weight: .medium))
|
|
357
|
+
.foregroundStyle(.white.opacity(0.72))
|
|
358
|
+
.lineLimit(1)
|
|
359
|
+
.frame(maxWidth: .infinity, alignment: .center)
|
|
360
|
+
}
|
|
361
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
362
|
+
.padding(.horizontal, 16)
|
|
363
|
+
.padding(.vertical, 14)
|
|
364
|
+
}
|
|
365
|
+
.padding(.horizontal, 2)
|
|
366
|
+
}
|
|
367
|
+
}
|