当前位置: 首页 > news >正文

Leaflet 综合案例 - 路径规划

看过的知识不等于学会。唯有用心总结、系统记录,并通过温故知新反复实践,才能真正掌握一二
作为一名摸爬滚打三年的前端开发,开源社区给了我饭碗,我也将所学的知识体系回馈给大家,助你少走弯路!
OpenLayers、Leaflet 快速入门 ,每周保持更新 2 个案例
Cesium 快速入门,每周保持更新 4 个案例

Leaflet 综合案例 - 路径规划

Vue 3 + Leaflet 实现的 WebGIS 应用提供了完整的路径规划功能

主要功能

这里调用了OpenStreetMap 的 OSRM (Open Source Routing Machine) 公共服务。OSRM 的公共服务是用于演示和测试的,不适合高并发或生产环境使用,因为它有严格的速率限制。

注意:

  1. 需要使用 OSM 底图,高德底图存在坐标偏差。
  2. OSM 底图需要挂一个梯子访问

在这里插入图片描述

MP4效果动画链接地址

技术栈

该环境下代码即拿即用

Vue 3.5.13+
Leaflet 1.9.4
Vite 6.3.5+
<template><div class="map-wrapper"><div class="control-panel"><h2>真实路径规划 (OSRM)</h2><p>点击地图选择起点和终点,然后点击“规划路径”。</p><div class="info-display"><p><strong>起点:</strong>{{startPointCoords.lat? startPointCoords.lat.toFixed(6) +", " +startPointCoords.lng.toFixed(6): "未选择"}}</p><p><strong>终点:</strong>{{endPointCoords.lat? endPointCoords.lat.toFixed(6) +", " +endPointCoords.lng.toFixed(6): "未选择"}}</p><p v-if="routeDistance"><strong>距离:</strong> {{ (routeDistance / 1000).toFixed(2) }} km</p><p v-if="routeDuration"><strong>预计时间:</strong> {{ formatDuration(routeDuration) }}</p><p v-if="loading" class="loading-text">规划中...</p><p v-if="error" class="error-text">{{ error }}</p></div><button@click="planRoute":disabled="!startPointCoords.lat || !endPointCoords.lat || loading"class="action-button route-button">规划路径</button><button @click="clearRoute" class="action-button clear-button">清除路径</button><button @click="resetMapView" class="reset-button">重置地图视图</button></div><div id="map-real-route" class="map-container"></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from "vue";
import "leaflet/dist/leaflet.css";
import L from "leaflet";let map = null;
let startMarker = null;
let endMarker = null;
let routePolyline = null;const startPointCoords = ref({ lat: null, lng: null });
const endPointCoords = ref({ lat: null, lng: null });
const routeDistance = ref(null);
const routeDuration = ref(null);
const loading = ref(false);
const error = ref(null);const initialView = [39.909186, 116.397479];
const initialZoom = 12;onMounted(() => {map = L.map("map-real-route").setView(initialView, initialZoom);L.tileLayer(// osm底图"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",{maxZoom: 18,minZoom: 3,attribution: '&copy; <a href="https://www.amap.com/">高德地图</a>',}).addTo(map);// 监听地图点击事件,用于选择起点和终点map.on("click", (e) => {if (!startPointCoords.value.lat) {startPointCoords.value = { lat: e.latlng.lat, lng: e.latlng.lng };if (startMarker) map.removeLayer(startMarker);startMarker = L.marker(e.latlng).addTo(map).bindPopup("起点").openPopup();} else if (!endPointCoords.value.lat) {endPointCoords.value = { lat: e.latlng.lat, lng: e.latlng.lng };if (endMarker) map.removeLayer(endMarker);endMarker = L.marker(e.latlng).addTo(map).bindPopup("终点").openPopup();} else {// 如果起点终点都已选择,则重置起点startPointCoords.value = { lat: e.latlng.lat, lng: e.latlng.lng };endPointCoords.value = { lat: null, lng: null };if (startMarker) map.removeLayer(startMarker);if (endMarker) map.removeLayer(endMarker);startMarker = L.marker(e.latlng).addTo(map).bindPopup("起点").openPopup();clearRoute();}});
});onUnmounted(() => {if (map) {map.off("click");map.remove();map = null;startMarker = null;endMarker = null;routePolyline = null;}
});const planRoute = async () => {if (!startPointCoords.value.lat || !endPointCoords.value.lat) {error.value = "请先选择起点和终点。";return;}loading.value = true;error.value = null;routeDistance.value = null;routeDuration.value = null;clearRoute(); // 清除旧路径const startLngLat = `${startPointCoords.value.lng},${startPointCoords.value.lat}`;const endLngLat = `${endPointCoords.value.lng},${endPointCoords.value.lat}`;// OSRM 路由 API URL// profile 可以是 'car', 'bike', 'foot'const osrmApiUrl = `https://router.project-osrm.org/route/v1/driving/${startLngLat};${endLngLat}?overview=full&geometries=geojson`;try {const response = await fetch(osrmApiUrl);if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}const data = await response.json();if (data.routes && data.routes.length > 0) {const route = data.routes[0];const routeGeoJSON = route.geometry; // OSRM 返回的 GeoJSON LineString// 绘制路径routePolyline = L.geoJSON(routeGeoJSON, {style: {color: "#007bff", // 蓝色weight: 6,opacity: 0.7,},}).addTo(map);// 调整地图视图以适应路径map.fitBounds(routePolyline.getBounds());routeDistance.value = route.distance; // 距离,单位米routeDuration.value = route.duration; // 时间,单位秒console.log("路径规划成功:", route);} else {error.value = "未能找到路径。";}} catch (e) {console.error("路径规划失败:", e);error.value = `路径规划失败: ${e.message}. 请检查网络或稍后再试。`;} finally {loading.value = false;}
};const clearRoute = () => {if (routePolyline) {map.removeLayer(routePolyline);routePolyline = null;}routeDistance.value = null;routeDuration.value = null;error.value = null;console.log("路径已清除。");
};const resetMapView = () => {if (map) {map.setView(initialView, initialZoom);if (startMarker) map.removeLayer(startMarker);if (endMarker) map.removeLayer(endMarker);startMarker = null;endMarker = null;startPointCoords.value = { lat: null, lng: null };endPointCoords.value = { lat: null, lng: null };clearRoute();}
};const formatDuration = (seconds) => {const minutes = Math.floor(seconds / 60);const hours = Math.floor(minutes / 60);const remainingMinutes = minutes % 60;const remainingSeconds = Math.floor(seconds % 60);let result = "";if (hours > 0) {result += `${hours}小时`;}if (remainingMinutes > 0) {result += `${remainingMinutes}分钟`;}if (remainingSeconds > 0 && hours === 0) {// 如果不足一小时,显示秒result += `${remainingSeconds}秒`;}return result || "不足1秒";
};
</script><style scoped>
.map-wrapper {display: flex;flex-direction: column;height: 100vh;width: 100vw;font-family: sans-serif;box-sizing: border-box;
}@media (min-width: 768px) {.map-wrapper {flex-direction: row;}
}.control-panel {flex-shrink: 0;width: 100%;background-color: #f8f8f8;border-right: 1px solid #eee;box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);display: flex;flex-direction: column;gap: 15px;color: #495057;
}@media (min-width: 768px) {.control-panel {width: 280px;height: 100%;}
}.control-panel h2 {margin-top: 0;margin-bottom: 15px;color: #333;font-size: 1.2em;
}.info-display {background-color: #e9ecef;padding: 15px;border-radius: 8px;border: 1px solid #dee2e6;color: #495057;
}.info-display p {margin: 5px 0;font-size: 0.95em;
}.info-display strong {color: #007bff;
}.loading-text {color: #007bff;font-weight: bold;
}.error-text {color: #dc3545;font-weight: bold;
}.map-container {flex-grow: 1;height: 100%;min-height: 300px;background-color: #e0e0e0;
}.action-button {padding: 10px 15px;border: none;border-radius: 5px;cursor: pointer;font-size: 1em;transition: background-color 0.3s ease;
}.action-button:disabled {background-color: #cccccc;cursor: not-allowed;
}.route-button {background-color: #28a745; /* 绿色 */color: white;
}.route-button:hover:not(:disabled) {background-color: #218838;
}.clear-button {background-color: #dc3545;color: white;
}.clear-button:hover:not(:disabled) {background-color: #c82333;
}.reset-button {background-color: #007bff;color: white;margin-top: auto;
}.reset-button:hover:not(:disabled) {background-color: #0056b3;
}.reset-button:active:not(:disabled) {background-color: #004085;
}
</style>
http://www.lryc.cn/news/603128.html

相关文章:

  • 3. 卷积网络代码参数解读分析
  • 前端高级综合搜索组件 SearchBox 使用详解!
  • 4.DRF 认证--Authentication4.DRF 认证--Authentication
  • django ManyToManyField 如何添加数据
  • python-内存管理
  • Kubernetes --存储入门
  • 基于FPGA和DDS原理的任意波形发生器(含仿真)
  • 做了一款小而美的本地校验器
  • 【0基础PS】PS工具详解--选择工具--对象选择工具
  • 天学网面试 —— 中级前端开发岗位
  • B树、B+树、红黑树区别
  • 安宝特案例丨AR+AI+SOP?3大技术融合革新军工航天领域
  • windows部署ACE-Step记录
  • 语音识别数据增强
  • Redis实战(3)-- 高级数据结构zset
  • C++现代Redis客户端库redis-plus-plus详解
  • 第四章:分析 Redis 性能高原因和核心字符串类型命令
  • 散点图(散点矩阵)相关介绍
  • 3. Socket 编程 TCP
  • 群晖Synology Drive:打造高效安全的私有云协作平台
  • TDengine 中 TDgpt 用于异常检测
  • 【51单片机2位数码管跑马灯】2022-9-25
  • 04动手学深度学习(下)
  • C++ 哈希算法、贪心算法
  • 【硬件】LVGL
  • 六轴机械臂cad【11张】三维图+设计说明书
  • 用latex+vscode+ctex写毕业论文
  • node后端-JWT认证
  • 使用Ettus USRP X440对雷达和EW系统进行原型验证
  • 自定义spring-boot-starter