openNavMap.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <template>
  2. <view style="height:100vh;">
  3. <view id="map"></view>
  4. <view class="info-box">
  5. <text>{{currentStep}}</text>
  6. </view>
  7. </view>
  8. </template>
  9. <script>
  10. export default {
  11. data() {
  12. return {
  13. map: null,
  14. routeLatLngs: [],
  15. marker: null,
  16. watchId: null,
  17. currentStep: "正在规划路线...",
  18. instructions: [],
  19. instrIndex: 0,
  20. start: [39.916527, 116.397128],
  21. end: [39.904211, 116.407395]
  22. };
  23. },
  24. onReady() {
  25. this.initMap();
  26. this.calcRoute();
  27. this.startGPS();
  28. },
  29. methods: {
  30. /** 1. 初始化地图 */
  31. initMap() {
  32. this.map = L.map("map").setView(this.start, 15);
  33. L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
  34. maxZoom: 19,
  35. }).addTo(this.map);
  36. },
  37. /** 2. GraphHopper 路线规划 */
  38. calcRoute() {
  39. const url =
  40. `https://graphhopper.com/api/1/route?` +
  41. `point=${this.start[0]},${this.start[1]}` +
  42. `&point=${this.end[0]},${this.end[1]}` +
  43. `&vehicle=car&instructions=true&locale=cn&key=fb959050-3a0c-4c6b-b722-5c322b2085c9`;
  44. uni.request({
  45. url,
  46. success: (res) => {
  47. const path = res.data.paths[0];
  48. this.instructions = path.instructions;
  49. const coords = path.points.coordinates;
  50. this.routeLatLngs = coords.map(c => [c[1], c[0]]);
  51. // 绘路线
  52. L.polyline(this.routeLatLngs, { color: "blue", weight: 5 }).addTo(this.map);
  53. this.map.fitBounds(this.routeLatLngs);
  54. // 设置起点终点 Marker
  55. L.marker(this.start).addTo(this.map).bindPopup("起点");
  56. L.marker(this.end).addTo(this.map).bindPopup("终点");
  57. this.currentStep = "路线规划完成,开始导航...";
  58. }
  59. });
  60. },
  61. /** 3. 开始实时 GPS 监听 */
  62. startGPS() {
  63. const _this = this;
  64. this.watchId = navigator.geolocation.watchPosition(
  65. (pos) => {
  66. const lat = pos.coords.latitude;
  67. const lng = pos.coords.longitude;
  68. _this.updatePosition(lat, lng);
  69. _this.checkNextInstruction(lat, lng);
  70. },
  71. (err) => { console.log("GPS error:", err); },
  72. { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }
  73. );
  74. },
  75. /** 4. 更新用户位置 + 地图跟随 */
  76. updatePosition(lat, lng) {
  77. if (!this.marker) {
  78. this.marker = L.marker([lat, lng]).addTo(this.map);
  79. } else {
  80. this.marker.setLatLng([lat, lng]);
  81. }
  82. // 地图跟随
  83. this.map.panTo([lat, lng]);
  84. },
  85. /** 5. 判断导航步骤 */
  86. checkNextInstruction(lat, lng) {
  87. if (!this.instructions.length) return;
  88. const step = this.instructions[this.instrIndex];
  89. const [fromIdx, toIdx] = step.interval;
  90. const stepCoords = this.routeLatLngs[fromIdx];
  91. const dist = this.getDistance(lat, lng, stepCoords[0], stepCoords[1]);
  92. if (dist < 20) { // 距离下一个步骤 < 20 米时触发
  93. this.currentStep = step.text;
  94. // 进入下一步
  95. if (this.instrIndex < this.instructions.length - 1) {
  96. this.instrIndex++;
  97. }
  98. }
  99. },
  100. /** 工具:两点间距离 */
  101. getDistance(lat1, lon1, lat2, lon2) {
  102. const R = 6378137;
  103. const dLat = (lat2 - lat1) * Math.PI / 180;
  104. const dLon = (lon2 - lon1) * Math.PI / 180;
  105. const a =
  106. Math.sin(dLat/2) * Math.sin(dLat/2) +
  107. Math.cos(lat1 * Math.PI/180) *
  108. Math.cos(lat2 * Math.PI/180) *
  109. Math.sin(dLon/2) * Math.sin(dLon/2);
  110. return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  111. }
  112. },
  113. onUnload() {
  114. if (this.watchId) {
  115. navigator.geolocation.clearWatch(this.watchId);
  116. }
  117. }
  118. };
  119. </script>
  120. <style>
  121. #map { width: 100%; height: 75vh; }
  122. .info-box {
  123. padding: 20rpx;
  124. font-size: 32rpx;
  125. }
  126. </style>
  127. <!-- Leaflet -->
  128. <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
  129. <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">