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.
Files changed (238) hide show
  1. package/README.md +296 -7
  2. package/android/build.gradle +12 -4
  3. package/android/src/main/AndroidManifest.xml +10 -1
  4. package/android/src/main/cpp/cluster_jni.cpp +56 -0
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +49 -8
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +83 -15
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +13 -3
  8. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +36 -39
  9. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +23 -17
  10. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +5 -2
  11. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +122 -10
  12. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +2 -2
  13. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +37 -25
  14. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +6 -6
  15. package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
  16. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +103 -0
  17. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +1408 -394
  18. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +121 -1
  19. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +14 -28
  20. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +28 -3
  21. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
  22. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +114 -15
  23. package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +24 -35
  24. package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +50 -36
  25. package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
  26. package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +21 -12
  27. package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
  28. package/android/src/main/res/drawable/landback_0.png +0 -0
  29. package/android/src/main/res/drawable/landback_1.png +0 -0
  30. package/android/src/main/res/drawable/landback_2.png +0 -0
  31. package/android/src/main/res/drawable/landback_3.png +0 -0
  32. package/android/src/main/res/drawable/landback_4.png +0 -0
  33. package/android/src/main/res/drawable/landback_5.png +0 -0
  34. package/android/src/main/res/drawable/landback_6.png +0 -0
  35. package/android/src/main/res/drawable/landback_7.png +0 -0
  36. package/android/src/main/res/drawable/landback_8.png +0 -0
  37. package/android/src/main/res/drawable/landback_9.png +0 -0
  38. package/android/src/main/res/drawable/landback_a.png +0 -0
  39. package/android/src/main/res/drawable/landback_b.png +0 -0
  40. package/android/src/main/res/drawable/landback_c.png +0 -0
  41. package/android/src/main/res/drawable/landback_d.png +0 -0
  42. package/android/src/main/res/drawable/landback_e.png +0 -0
  43. package/android/src/main/res/drawable/landback_f.png +0 -0
  44. package/android/src/main/res/drawable/landback_g.png +0 -0
  45. package/android/src/main/res/drawable/landback_h.png +0 -0
  46. package/android/src/main/res/drawable/landback_i.png +0 -0
  47. package/android/src/main/res/drawable/landback_j.png +0 -0
  48. package/android/src/main/res/drawable/landback_k.png +0 -0
  49. package/android/src/main/res/drawable/landback_l.png +0 -0
  50. package/android/src/main/res/drawable/landfront_0.png +0 -0
  51. package/android/src/main/res/drawable/landfront_00.png +0 -0
  52. package/android/src/main/res/drawable/landfront_1.png +0 -0
  53. package/android/src/main/res/drawable/landfront_11.png +0 -0
  54. package/android/src/main/res/drawable/landfront_20.png +0 -0
  55. package/android/src/main/res/drawable/landfront_21.png +0 -0
  56. package/android/src/main/res/drawable/landfront_22.png +0 -0
  57. package/android/src/main/res/drawable/landfront_3.png +0 -0
  58. package/android/src/main/res/drawable/landfront_33.png +0 -0
  59. package/android/src/main/res/drawable/landfront_40.png +0 -0
  60. package/android/src/main/res/drawable/landfront_43.png +0 -0
  61. package/android/src/main/res/drawable/landfront_44.png +0 -0
  62. package/android/src/main/res/drawable/landfront_5.png +0 -0
  63. package/android/src/main/res/drawable/landfront_55.png +0 -0
  64. package/android/src/main/res/drawable/landfront_61.png +0 -0
  65. package/android/src/main/res/drawable/landfront_63.png +0 -0
  66. package/android/src/main/res/drawable/landfront_66.png +0 -0
  67. package/android/src/main/res/drawable/landfront_70.png +0 -0
  68. package/android/src/main/res/drawable/landfront_71.png +0 -0
  69. package/android/src/main/res/drawable/landfront_73.png +0 -0
  70. package/android/src/main/res/drawable/landfront_77.png +0 -0
  71. package/android/src/main/res/drawable/landfront_8.png +0 -0
  72. package/android/src/main/res/drawable/landfront_88.png +0 -0
  73. package/android/src/main/res/drawable/landfront_90.png +0 -0
  74. package/android/src/main/res/drawable/landfront_95.png +0 -0
  75. package/android/src/main/res/drawable/landfront_99.png +0 -0
  76. package/android/src/main/res/drawable/landfront_a0.png +0 -0
  77. package/android/src/main/res/drawable/landfront_a8.png +0 -0
  78. package/android/src/main/res/drawable/landfront_aa.png +0 -0
  79. package/android/src/main/res/drawable/landfront_b1.png +0 -0
  80. package/android/src/main/res/drawable/landfront_b5.png +0 -0
  81. package/android/src/main/res/drawable/landfront_bb.png +0 -0
  82. package/android/src/main/res/drawable/landfront_c3.png +0 -0
  83. package/android/src/main/res/drawable/landfront_c8.png +0 -0
  84. package/android/src/main/res/drawable/landfront_cc.png +0 -0
  85. package/android/src/main/res/drawable/landfront_d.png +0 -0
  86. package/android/src/main/res/drawable/landfront_dd.png +0 -0
  87. package/android/src/main/res/drawable/landfront_e1.png +0 -0
  88. package/android/src/main/res/drawable/landfront_e5.png +0 -0
  89. package/android/src/main/res/drawable/landfront_ee.png +0 -0
  90. package/android/src/main/res/drawable/landfront_f0.png +0 -0
  91. package/android/src/main/res/drawable/landfront_f1.png +0 -0
  92. package/android/src/main/res/drawable/landfront_f5.png +0 -0
  93. package/android/src/main/res/drawable/landfront_ff.png +0 -0
  94. package/android/src/main/res/drawable/landfront_g3.png +0 -0
  95. package/android/src/main/res/drawable/landfront_g5.png +0 -0
  96. package/android/src/main/res/drawable/landfront_gg.png +0 -0
  97. package/android/src/main/res/drawable/landfront_h1.png +0 -0
  98. package/android/src/main/res/drawable/landfront_h3.png +0 -0
  99. package/android/src/main/res/drawable/landfront_h5.png +0 -0
  100. package/android/src/main/res/drawable/landfront_hh.png +0 -0
  101. package/android/src/main/res/drawable/landfront_i0.png +0 -0
  102. package/android/src/main/res/drawable/landfront_i3.png +0 -0
  103. package/android/src/main/res/drawable/landfront_i5.png +0 -0
  104. package/android/src/main/res/drawable/landfront_ii.png +0 -0
  105. package/android/src/main/res/drawable/landfront_j1.png +0 -0
  106. package/android/src/main/res/drawable/landfront_j8.png +0 -0
  107. package/android/src/main/res/drawable/landfront_jj.png +0 -0
  108. package/android/src/main/res/drawable/landfront_kk.png +0 -0
  109. package/android/src/main/res/drawable/landfront_ll.png +0 -0
  110. package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
  111. package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
  112. package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
  113. package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
  114. package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
  115. package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
  116. package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
  117. package/build/ExpoGaodeMapNaviView.d.ts +16 -0
  118. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  119. package/build/ExpoGaodeMapNaviView.js +74 -1
  120. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  121. package/build/index.d.ts +56 -8
  122. package/build/index.d.ts.map +1 -1
  123. package/build/index.js +452 -10
  124. package/build/index.js.map +1 -1
  125. package/build/map/ExpoGaodeMapModule.d.ts +15 -13
  126. package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
  127. package/build/map/ExpoGaodeMapModule.js +31 -39
  128. package/build/map/ExpoGaodeMapModule.js.map +1 -1
  129. package/build/map/ExpoGaodeMapOfflineModule.d.ts +5 -0
  130. package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
  131. package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
  132. package/build/map/ExpoGaodeMapView.d.ts +3 -4
  133. package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
  134. package/build/map/ExpoGaodeMapView.js +28 -25
  135. package/build/map/ExpoGaodeMapView.js.map +1 -1
  136. package/build/map/components/overlays/Circle.d.ts.map +1 -1
  137. package/build/map/components/overlays/Circle.js +1 -30
  138. package/build/map/components/overlays/Circle.js.map +1 -1
  139. package/build/map/components/overlays/Cluster.d.ts.map +1 -1
  140. package/build/map/components/overlays/Cluster.js +1 -42
  141. package/build/map/components/overlays/Cluster.js.map +1 -1
  142. package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
  143. package/build/map/components/overlays/HeatMap.js +21 -21
  144. package/build/map/components/overlays/HeatMap.js.map +1 -1
  145. package/build/map/components/overlays/Marker.d.ts.map +1 -1
  146. package/build/map/components/overlays/Marker.js +76 -80
  147. package/build/map/components/overlays/Marker.js.map +1 -1
  148. package/build/map/components/overlays/Polygon.d.ts.map +1 -1
  149. package/build/map/components/overlays/Polygon.js +1 -25
  150. package/build/map/components/overlays/Polygon.js.map +1 -1
  151. package/build/map/components/overlays/Polyline.d.ts.map +1 -1
  152. package/build/map/components/overlays/Polyline.js +1 -31
  153. package/build/map/components/overlays/Polyline.js.map +1 -1
  154. package/build/map/index.d.ts +9 -2
  155. package/build/map/index.d.ts.map +1 -1
  156. package/build/map/index.js +9 -2
  157. package/build/map/index.js.map +1 -1
  158. package/build/map/search/ExpoGaodeMapSearch.types.d.ts +340 -0
  159. package/build/map/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
  160. package/build/map/search/ExpoGaodeMapSearch.types.js +19 -0
  161. package/build/map/search/ExpoGaodeMapSearch.types.js.map +1 -0
  162. package/build/map/search/ExpoGaodeMapSearchModule.d.ts +74 -0
  163. package/build/map/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
  164. package/build/map/search/ExpoGaodeMapSearchModule.js +47 -0
  165. package/build/map/search/ExpoGaodeMapSearchModule.js.map +1 -0
  166. package/build/map/search/index.d.ts +156 -0
  167. package/build/map/search/index.d.ts.map +1 -0
  168. package/build/map/search/index.js +171 -0
  169. package/build/map/search/index.js.map +1 -0
  170. package/build/map/types/index.d.ts +2 -2
  171. package/build/map/types/index.d.ts.map +1 -1
  172. package/build/map/types/index.js.map +1 -1
  173. package/build/map/types/map-view.types.d.ts +4 -2
  174. package/build/map/types/map-view.types.d.ts.map +1 -1
  175. package/build/map/types/map-view.types.js.map +1 -1
  176. package/build/map/types/native-module.types.d.ts +11 -12
  177. package/build/map/types/native-module.types.d.ts.map +1 -1
  178. package/build/map/types/native-module.types.js.map +1 -1
  179. package/build/map/types/overlays.types.d.ts +9 -14
  180. package/build/map/types/overlays.types.d.ts.map +1 -1
  181. package/build/map/types/overlays.types.js.map +1 -1
  182. package/build/map/types/route-playback.types.d.ts +16 -0
  183. package/build/map/types/route-playback.types.d.ts.map +1 -1
  184. package/build/map/types/route-playback.types.js.map +1 -1
  185. package/build/map/utils/ErrorHandler.js +11 -11
  186. package/build/map/utils/ErrorHandler.js.map +1 -1
  187. package/build/map/utils/OfflineMapManager.d.ts +4 -0
  188. package/build/map/utils/OfflineMapManager.d.ts.map +1 -1
  189. package/build/map/utils/OfflineMapManager.js +6 -0
  190. package/build/map/utils/OfflineMapManager.js.map +1 -1
  191. package/build/types/coordinates.types.d.ts +3 -0
  192. package/build/types/coordinates.types.d.ts.map +1 -1
  193. package/build/types/coordinates.types.js.map +1 -1
  194. package/build/types/independent.types.d.ts +111 -12
  195. package/build/types/independent.types.d.ts.map +1 -1
  196. package/build/types/independent.types.js.map +1 -1
  197. package/build/types/native-module.types.d.ts +1 -1
  198. package/build/types/native-module.types.js.map +1 -1
  199. package/build/types/naviview.types.d.ts +304 -14
  200. package/build/types/naviview.types.d.ts.map +1 -1
  201. package/build/types/naviview.types.js.map +1 -1
  202. package/build/types/route.types.d.ts +12 -2
  203. package/build/types/route.types.d.ts.map +1 -1
  204. package/build/types/route.types.js.map +1 -1
  205. package/expo-module.config.json +4 -2
  206. package/ios/ExpoGaodeMapNaviView.swift +2331 -201
  207. package/ios/ExpoGaodeMapNaviViewModule.swift +109 -1
  208. package/ios/ExpoGaodeMapNavigation.podspec +2 -1
  209. package/ios/ExpoGaodeMapNavigationModule.swift +7 -5
  210. package/ios/managers/IndependentRouteManager.swift +90 -26
  211. package/ios/map/ExpoGaodeMapModule.swift +72 -21
  212. package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
  213. package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
  214. package/ios/map/ExpoGaodeMapView.swift +23 -5
  215. package/ios/map/GaodeMapPrivacyManager.swift +26 -18
  216. package/ios/map/cpp/GeometryEngine.cpp +112 -0
  217. package/ios/map/cpp/GeometryEngine.hpp +21 -0
  218. package/ios/map/modules/LocationManager.swift +37 -5
  219. package/ios/map/overlays/MarkerView.swift +11 -11
  220. package/ios/map/overlays/MarkerViewModule.swift +4 -4
  221. package/ios/map/overlays/PolylineView.swift +6 -12
  222. package/ios/map/utils/ClusterNative.h +8 -0
  223. package/ios/map/utils/ClusterNative.mm +27 -0
  224. package/ios/map/utils/PermissionManager.swift +115 -6
  225. package/ios/routes/drive/DriveTruckRouteCalculator.swift +165 -77
  226. package/ios/routes/walkride/WalkRideRouteCalculator.swift +127 -1
  227. package/ios/services/IndependentRouteService.swift +198 -39
  228. package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
  229. package/ios/services/NavigationLiveActivityManager.swift +359 -0
  230. package/package.json +22 -7
  231. package/plugin/build/withGaodeMap.d.ts +8 -0
  232. package/plugin/build/withGaodeMap.js +60 -4
  233. package/scripts/check-expo-modules.js +68 -0
  234. package/shared/cpp/GeometryEngine.cpp +112 -0
  235. package/shared/cpp/GeometryEngine.hpp +21 -0
  236. package/widget-template/README.md +46 -0
  237. package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
  238. 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
+ }