You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在vue-leaflet中实现共享电单车多段路线规划?

解决Vue-Leaflet结合Leaflet-Routing-Machine实现三段式共享电单车路线规划

一、修复Vue-Leaflet与Leaflet-Routing-Machine的兼容问题

核心问题是要拿到Vue-Leaflet封装的原生Leaflet实例,因为Leaflet-Routing-Machine是基于原生Leaflet开发的,无法直接操作Vue组件。

  1. 确保依赖完整安装:
npm install leaflet @vue-leaflet/vue-leaflet leaflet-routing-machine
  1. 在Vue组件中正确导入并初始化:
<template>
  <l-map ref="mapRef" :zoom="13" :center="[40.7128, -74.0060]">
    <l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"></l-tile-layer>
  </l-map>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import L from 'leaflet'
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css'
import 'leaflet-routing-machine'
import { LMap, LTileLayer } from '@vue-leaflet/vue-leaflet'

const mapRef = ref(null)
let mapInstance = null

onMounted(() => {
  if (mapRef.value) {
    // 获取底层原生Leaflet实例
    mapInstance = mapRef.value.mapObject
  }
})
</script>

<style>
@import 'leaflet/dist/leaflet.css';
</style>

二、地址/坐标转地理坐标

如果用户输入的是地址,需要先解析为经纬度,用leaflet-control-geocoder实现:

npm install leaflet-control-geocoder

组件内导入并编写解析函数:

import 'leaflet-control-geocoder/dist/Control.Geocoder.css'
import 'leaflet-control-geocoder'

async function geocodeAddress(address) {
  const geocoder = L.Control.Geocoder.nominatim()
  return new Promise((resolve, reject) => {
    geocoder.geocode(address, (results) => {
      results.length > 0 ? resolve(results[0].center) : reject(new Error('地址解析失败'))
    })
  })
}

三、匹配最近的充电站

用Haversine公式计算距离,从给定的充电站JSON数据中筛选最近站点:

// 计算两点间距离(单位:米)
function calculateDistance(lat1, lon1, lat2, lon2) {
  const R = 6371000 // 地球半径(米)
  const dLat = (lat2 - lat1) * Math.PI / 180
  const dLon = (lon2 - lon1) * Math.PI / 180
  const a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
  return R * c
}

// 找到目标坐标最近的充电站
function findNearestStation(targetCoords, stations) {
  return stations.reduce((nearest, station) => {
    const distance = calculateDistance(targetCoords.lat, targetCoords.lng, station.lat, station.lng)
    return !nearest || distance < nearest.distance ? { station, distance } : nearest
  }, null)
}

四、生成三段式路线

基于起点、起点充电站、终点充电站、终点四个坐标,分别生成步行-骑行-步行三段路线:

// 清除地图上已有路线
function clearRoutes() {
  mapInstance?.eachLayer(layer => {
    if (layer instanceof L.Routing.Control) mapInstance.removeControl(layer)
  })
}

// 生成完整路线
async function generateRoute(startInput, endInput, stations) {
  // 1. 解析起点/终点坐标(支持地址字符串或直接传入坐标)
  const startCoords = typeof startInput === 'string' ? await geocodeAddress(startInput) : startInput
  const endCoords = typeof endInput === 'string' ? await geocodeAddress(endInput) : endInput

  // 2. 匹配最近充电站
  const startStation = findNearestStation(startCoords, stations).station
  const endStation = findNearestStation(endCoords, stations).station

  // 3. 清除旧路线
  clearRoutes()

  // 第一段:步行从起点到起点充电站
  L.Routing.control({
    waypoints: [L.latLng(startCoords), L.latLng(startStation)],
    routeWhileDragging: false,
    lineOptions: { styles: [{ color: '#007AFF', weight: 4 }] },
    router: L.Routing.osrmv1({
      serviceUrl: 'https://router.project-osrm.org/route/v1',
      profile: 'foot'
    })
  }).addTo(mapInstance)

  // 第二段:骑行从起点充电站到终点充电站
  L.Routing.control({
    waypoints: [L.latLng(startStation), L.latLng(endStation)],
    routeWhileDragging: false,
    lineOptions: { styles: [{ color: '#34C759', weight: 6 }] },
    router: L.Routing.osrmv1({
      serviceUrl: 'https://router.project-osrm.org/route/v1',
      profile: 'bike'
    })
  }).addTo(mapInstance)

  // 第三段:步行从终点充电站到终点
  L.Routing.control({
    waypoints: [L.latLng(endStation), L.latLng(endCoords)],
    routeWhileDragging: false,
    lineOptions: { styles: [{ color: '#007AFF', weight: 4 }] },
    router: L.Routing.osrmv1({
      serviceUrl: 'https://router.project-osrm.org/route/v1',
      profile: 'foot'
    })
  }).addTo(mapInstance)

  // 调整地图视野覆盖所有路线
  const bounds = L.latLngBounds([startCoords, startStation, endStation, endCoords])
  mapInstance.fitBounds(bounds, { padding: [50, 50] })
}

五、常见问题排查

  • 路由控件不显示:检查是否导入了Leaflet-Routing-Machine的CSS文件,以及是否正确获取了原生Leaflet实例。
  • 路线请求失败:确认OSRM服务地址可用,或替换为其他路由服务(如Mapbox,需API密钥)。
  • 大数据量卡顿:若充电站数量过多,可将距离计算逻辑移至后端,或在前端用空间索引优化。

内容的提问来源于stack exchange,提问作者UnLegitCombos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 10:08:16