基于 Leaflet 与 Dijkstra 算法的路网最短路径分析系统
一、项目概述
在现代城市规划、物流调度和导航应用中,路网最短路径分析是一项核心技术。本文将详细介绍如何使用 Leaflet 地图库和 Dijkstra 算法构建一个完整的路网最短路径分析系统,包括前端界面设计、路网数据处理和路径计算算法实现。

在线预览地址:http://devmodels.oss-cn-shenzhen.aliyuncs.com/devtest/liubofang/dijkstra/index.html
二、技术实现
- 地图初始化与路网数据处理
- 使用 Leaflet 地图库加载 OpenStreetMap 底图
- 自定义路网数据结构,使用 GeoJSON 格式存储道路信息
- 实现了从 GeoJSON 数据构建网络图的算法,将路网抽象为节点和边的图结构
- 最短路径算法
- 实现了 Dijkstra 算法计算路网中的最短路径
- 针对路网特点进行了算法优化,提高了路径计算效率
- 处理了单行道、转弯限制等实际路网中的复杂情况
- 界面设计与交互
- 使用 Tailwind CSS 实现了现代化的 UI 设计
- 设计了响应式布局,确保在不同设备上都有良好的显示效果
- 添加了平滑过渡动画,提升用户体
三、经验总结
- 技术选型
- Leaflet 是一个轻量级、功能强大的地图库,非常适合开发路网分析应用
- Dijkstra 算法在小规模路网中表现良好,但在大规模路网中可能需要更高效的算法
四、核心算法
function dijkstra(graph, start, end) {
const distances = {};
const previous = {};
const queue = [];
// 初始化距离和前驱节点
for (const node in graph) {
distances[node] = Infinity;
previous[node] = null;
queue.push(node);
}
distances[start] = 0;
while (queue.length > 0) {
// 找到距离最小的节点
let minDistance = Infinity;
let minNode = null;
let minIndex = -1;
queue.forEach((node, index) => {
if (distances[node] < minDistance) {
minDistance = distances[node];
minNode = node;
minIndex = index;
}
});
// 如果找不到可达节点,退出循环
if (minNode === null) break;
// 从队列中移除当前节点
queue.splice(minIndex, 1);
// 如果到达终点,结束算法
if (minNode === end) break;
// 更新相邻节点的距离
for (const neighbor in graph[minNode]) {
const distance =
distances[minNode] + graph[minNode][neighbor].distance;
if (distance < distances[neighbor]) {
distances[neighbor] = distance;
previous[neighbor] = minNode;
}
}
}
// 构建最短路径
const path = [];
let current = end;
while (current !== null) {
path.unshift(current);
current = previous[current];
}
return {
path,
distance: distances[end],
};
}
五、完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Leaflet路网最短路径分析</title>
<script src="https://cdn.tailwindcss.com"></script>
<link
href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"
rel="stylesheet"
/>
<!-- Leaflet相关资源 -->
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
></script>
<!-- 路由算法库 -->
<script src="https://unpkg.com/osrm-text-instructions@6.8.0/dist/osrm-text-instructions.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: "#3B82F6",
secondary: "#10B981",
accent: "#8B5CF6",
dark: "#1F2937",
light: "#F3F4F6",
},
fontFamily: {
inter: ["Inter", "system-ui", "sans-serif"],
},
},
},
};
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.map-height {
height: calc(100vh - 4rem);
}
.sidebar-width {
width: clamp(280px, 30%, 400px);
}
.transition-all-300 {
transition: all 0.3s ease;
}
}
</style>
</head>
<body class="font-inter bg-gray-50 text-gray-800">
<!-- 顶部导航栏 -->
<header class="bg-white shadow-md fixed w-full z-50">
<div
class="container mx-auto px-4 py-3 flex justify-between items-center"
>
<div class="flex items-center space-x-2">
<i class="fa fa-map-o text-primary text-2xl"></i>
<h1 class="text-xl font-bold text-primary">路网分析系统</h1>
</div>
<div class="flex items-center space-x-4">
<button
id="helpBtn"
class="text-gray-600 hover:text-primary transition-all-300"
>
<i class="fa fa-question-circle text-lg"></i>
</button>
<button
id="aboutBtn"
class="text-gray-600 hover:text-primary transition-all-300"
>
<i class="fa fa-info-circle text-lg"></i>
</button>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="pt-16 flex flex-col md:flex-row">
<!-- 左侧控制面板 -->
<div
id="sidebar"
class="sidebar-width bg-white shadow-lg z-40 md:min-h-screen overflow-y-auto transition-all-300 transform -translate-x-full md:translate-x-0 fixed md:relative top-16 md:top-0 h-[calc(100vh-4rem)]"
>
<div class="p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-800">路径规划</h2>
<button
id="closeSidebar"
class="md:hidden text-gray-500 hover:text-gray-800"
>
<i class="fa fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div class="bg-gray-50 p-3 rounded-lg">
<div class="flex items-center mb-3">
<div
class="w-8 h-8 rounded-full bg-red-500 flex items-center justify-center text-white mr-3"
>
<i class="fa fa-map-marker"></i>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 mb-1"
>起点</label
>
<div class="relative">
<input
type="text"
id="startPoint"
placeholder="点击地图选择起点"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
/>
<button
id="clearStart"
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
<i class="fa fa-times-circle"></i>
</button>
</div>
</div>
</div>
<div class="flex items-center">
<div
class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white mr-3"
>
<i class="fa fa-flag"></i>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 mb-1"
>终点</label
>
<div class="relative">
<input
type="text"
id="endPoint"
placeholder="点击地图选择终点"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
/>
<button
id="clearEnd"
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
<i class="fa fa-times-circle"></i>
</button>
</div>
</div>
</div>
</div>
<div class="flex space-x-2">
<button
id="findRouteBtn"
class="flex-1 bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-md transition-all-300 flex items-center justify-center"
>
<i class="fa fa-road mr-2"></i>计算路径
</button>
<button
id="clearRouteBtn"
class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-2 px-4 rounded-md transition-all-300"
>
<i class="fa fa-refresh"></i>
</button>
</div>
<!-- 路径信息 -->
<div id="routeInfo" class="hidden">
<div class="bg-gray-50 p-3 rounded-lg border border-gray-200">
<div class="flex justify-between items-center mb-2">
<h3 class="font-medium text-gray-800">路径信息</h3>
<span
id="routeDistance"
class="text-sm font-medium text-green-600"
></span>
</div>
<div
id="routeInstructions"
class="space-y-2 max-h-64 overflow-y-auto text-sm"
>
<!-- 路径指引将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 地图区域 -->
<div class="flex-1 relative">
<div id="map" class="w-full map-height"></div>
<!-- 浮动按钮 -->
<div class="absolute bottom-4 right-4 flex flex-col space-y-2 z-30">
<button
id="toggleSidebar"
class="md:hidden bg-primary text-white w-10 h-10 rounded-full shadow-lg flex items-center justify-center hover:bg-primary/90 transition-all-300"
>
<i class="fa fa-bars"></i>
</button>
<button
id="locateMe"
class="bg-white text-primary w-10 h-10 rounded-full shadow-lg flex items-center justify-center hover:bg-gray-50 transition-all-300"
>
<i class="fa fa-location-arrow"></i>
</button>
<button
id="zoomToRoute"
class="bg-white text-primary w-10 h-10 rounded-full shadow-lg flex items-center justify-center hover:bg-gray-50 transition-all-300 opacity-50 cursor-not-allowed"
>
<i class="fa fa-search-plus"></i>
</button>
</div>
<!-- 帮助模态框 -->
<div
id="helpModal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center hidden"
style="z-index: 9999"
>
<div
class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 transform transition-all duration-300 scale-95 opacity-0"
id="modalContent"
>
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">使用帮助</h3>
<button
id="closeHelpModal"
class="text-gray-500 hover:text-gray-700"
>
<i class="fa fa-times"></i>
</button>
</div>
<div class="space-y-3 text-gray-700">
<p><strong>1. 选择起点和终点</strong></p>
<p class="text-sm ml-4">
点击地图上的任意位置选择起点和终点,或者在搜索框中输入地址
</p>
<p><strong>2. 计算路径</strong></p>
<p class="text-sm ml-4">
点击"计算路径"按钮,系统将在路网中寻找最短路径
</p>
<p><strong>3. 查看结果</strong></p>
<p class="text-sm ml-4">
地图上会显示最短路径,左侧面板会显示路径距离和详细指引
</p>
<p><strong>4. 清除路径</strong></p>
<p class="text-sm ml-4">
点击刷新按钮可以清除当前路径,重新选择起点和终点
</p>
</div>
<div class="mt-6">
<button
id="gotItBtn"
class="w-full bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-md transition-all-300"
>
我知道了
</button>
</div>
</div>
</div>
</div>
</div>
</main>
<script>
// 全局变量
let map, networkGraph, startMarker, endMarker, routeLine;
let startPoint = null,
endPoint = null;
let nodes = {}; // 存储路网节点
let edges = []; // 存储路网边
let roadNetwork = null; // 存储GeoJSON路网数据
let coordinateToNode = {}; // 坐标到节点ID的映射
// 初始化地图
function initMap() {
// 创建地图实例并设置中心点和缩放级别
map = L.map("map").setView([39.90923, 116.397428], 13);
// 添加底图图层
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
// 加载路网数据
loadNetworkData();
// 地图点击事件 - 选择起点和终点
map.on("click", function (e) {
if (!startPoint) {
// 设置起点
setStartPoint(e.latlng);
} else if (!endPoint) {
// 设置终点
setEndPoint(e.latlng);
}
});
// 初始化界面事件监听
initEventListeners();
// 显示帮助模态框
setTimeout(() => {
document.getElementById("helpBtn").click();
}, 500);
}
// 加载路网数据
async function loadNetworkData() {
// 使用GeoJSON格式的路网数据
await fetch("./bj.geojson")
.then((response) => response.json())
.then((data) => {
roadNetwork = data;
// 从GeoJSON构建网络图
buildNetworkFromGeoJSON();
// 在地图上绘制路网
drawNetwork();
});
}
// 从GeoJSON构建网络图
function buildNetworkFromGeoJSON() {
// 清空现有节点和边
nodes = {};
edges = [];
coordinateToNode = {};
// 为每个坐标点生成唯一的节点ID
roadNetwork.features.forEach((feature) => {
if (feature.geometry.type === "LineString") {
const coords = feature.geometry.coordinates;
// 为每个坐标点创建节点
coords.forEach((coord) => {
const coordKey = `${coord[0].toFixed(6)},${coord[1].toFixed(6)}`;
// 如果这个坐标还没有对应的节点ID,创建一个新的
if (!coordinateToNode[coordKey]) {
const nodeId = `node_${
Object.keys(coordinateToNode).length + 1
}`;
coordinateToNode[coordKey] = nodeId;
nodes[nodeId] = coord;
}
});
}
});
// 处理GeoJSON中的每条道路,创建边
roadNetwork.features.forEach((feature) => {
const roadId = feature.properties.osm_id;
const roadName = feature.properties.name;
const roadLength = feature.properties.length || 100; // 默认长度100米
const isOneWay = feature.properties.oneway || false; // 是否单行道
if (feature.geometry.type === "LineString") {
const coords = feature.geometry.coordinates;
// 为相邻节点创建边
for (let i = 0; i < coords.length - 1; i++) {
const fromCoord = coords[i];
const toCoord = coords[i + 1];
const fromCoordKey = `${fromCoord[0].toFixed(
6
)},${fromCoord[1].toFixed(6)}`;
const toCoordKey = `${toCoord[0].toFixed(6)},${toCoord[1].toFixed(
6
)}`;
const fromNode = coordinateToNode[fromCoordKey];
const toNode = coordinateToNode[toCoordKey];
// 添加正向边
edges.push([
fromNode,
toNode,
roadLength / (coords.length - 1),
roadName,
]);
// 如果不是单行道,添加反向边
if (!isOneWay) {
edges.push([
toNode,
fromNode,
roadLength / (coords.length - 1),
roadName,
]);
}
}
}
});
// 构建网络图
buildNetworkGraph();
}
// 构建网络图
function buildNetworkGraph() {
networkGraph = {};
// 初始化图结构
for (const nodeId in nodes) {
networkGraph[nodeId] = {};
}
// 添加边到图中
edges.forEach((edge) => {
const [from, to, distance, name] = edge;
// 添加双向边
networkGraph[from][to] = { distance, name };
networkGraph[to][from] = { distance, name };
});
}
// 在地图上绘制路网
function drawNetwork() {
// 绘制道路线段
if (roadNetwork) {
L.geoJSON(roadNetwork, {
style: function (feature) {
return {
color: "#ff6600",
weight: 3,
opacity: 0.7,
// dashArray: '5, 5',
lineJoin: "round",
};
},
onEachFeature: function (feature, layer) {
layer.bindTooltip(feature.properties.name, {
permanent: false,
direction: "auto",
});
},
}).addTo(map);
}
}
// 设置起点
function setStartPoint(latlng) {
// 清除现有起点标记
if (startMarker) {
map.removeLayer(startMarker);
}
// 创建新的起点标记
startPoint = latlng;
startMarker = L.marker(latlng, {
icon: L.divIcon({
html: '<div class="w-6 h-6 bg-red-500 rounded-full flex items-center justify-center text-white shadow-lg"><i class="fa fa-map-marker"></i></div>',
className: "custom-div-icon",
iconSize: [30, 30],
iconAnchor: [15, 30],
}),
draggable: true,
}).addTo(map);
// 更新输入框
document.getElementById("startPoint").value = `(${latlng.lat.toFixed(
6
)}, ${latlng.lng.toFixed(6)})`;
// 添加拖拽事件
startMarker.on("dragend", function (e) {
startPoint = e.target.getLatLng();
document.getElementById(
"startPoint"
).value = `(${startPoint.lat.toFixed(6)}, ${startPoint.lng.toFixed(
6
)})`;
});
// 启用查找路径按钮(如果起点和终点都已设置)
updateRouteButtonState();
}
// 设置终点
function setEndPoint(latlng) {
// 清除现有终点标记
if (endMarker) {
map.removeLayer(endMarker);
}
// 创建新的终点标记
endPoint = latlng;
endMarker = L.marker(latlng, {
icon: L.divIcon({
html: '<div class="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-white shadow-lg"><i class="fa fa-flag"></i></div>',
className: "custom-div-icon",
iconSize: [30, 30],
iconAnchor: [15, 30],
}),
draggable: true,
}).addTo(map);
// 更新输入框
document.getElementById("endPoint").value = `(${latlng.lat.toFixed(
6
)}, ${latlng.lng.toFixed(6)})`;
// 添加拖拽事件
endMarker.on("dragend", function (e) {
endPoint = e.target.getLatLng();
document.getElementById("endPoint").value = `(${endPoint.lat.toFixed(
6
)}, ${endPoint.lng.toFixed(6)})`;
});
// 启用查找路径按钮(如果起点和终点都已设置)
updateRouteButtonState();
}
// 更新路径按钮状态
function updateRouteButtonState() {
const findRouteBtn = document.getElementById("findRouteBtn");
if (startPoint && endPoint) {
findRouteBtn.removeAttribute("disabled");
findRouteBtn.classList.remove("opacity-50", "cursor-not-allowed");
} else {
findRouteBtn.setAttribute("disabled", "true");
findRouteBtn.classList.add("opacity-50", "cursor-not-allowed");
}
}
// 寻找最近的路网节点
function findNearestNode(latlng) {
let nearestNode = null;
let minDistance = Infinity;
for (const nodeId in nodes) {
const nodeLatLng = L.latLng(nodes[nodeId][1], nodes[nodeId][0]);
const distance = latlng.distanceTo(nodeLatLng);
if (distance < minDistance) {
minDistance = distance;
nearestNode = nodeId;
}
}
return nearestNode;
}
// Dijkstra算法实现
function dijkstra(graph, start, end) {
const distances = {};
const previous = {};
const queue = [];
// 初始化距离和前驱节点
for (const node in graph) {
distances[node] = Infinity;
previous[node] = null;
queue.push(node);
}
distances[start] = 0;
while (queue.length > 0) {
// 找到距离最小的节点
let minDistance = Infinity;
let minNode = null;
let minIndex = -1;
queue.forEach((node, index) => {
if (distances[node] < minDistance) {
minDistance = distances[node];
minNode = node;
minIndex = index;
}
});
// 如果找不到可达节点,退出循环
if (minNode === null) break;
// 从队列中移除当前节点
queue.splice(minIndex, 1);
// 如果到达终点,结束算法
if (minNode === end) break;
// 更新相邻节点的距离
for (const neighbor in graph[minNode]) {
const distance =
distances[minNode] + graph[minNode][neighbor].distance;
if (distance < distances[neighbor]) {
distances[neighbor] = distance;
previous[neighbor] = minNode;
}
}
}
// 构建最短路径
const path = [];
let current = end;
while (current !== null) {
path.unshift(current);
current = previous[current];
}
return {
path,
distance: distances[end],
};
}
// 计算最短路径
function calculateRoute() {
if (!startPoint || !endPoint) return;
// 找到起点和终点最近的路网节点
const startNode = findNearestNode(startPoint);
const endNode = findNearestNode(endPoint);
if (!startNode || !endNode) {
alert("无法找到最近的路网节点");
return;
}
// 使用Dijkstra算法计算最短路径
const result = dijkstra(networkGraph, startNode, endNode);
if (result.path.length === 0 || result.distance === Infinity) {
alert("无法找到从起点到终点的路径");
return;
}
// 显示路径
displayRoute(result.path, result.distance);
}
// 显示路径
function displayRoute(path, totalDistance) {
// 清除现有路径
if (routeLine) {
map.removeLayer(routeLine);
}
// 构建路径点数组
const routePoints = [];
// 添加起点
routePoints.push(startPoint);
// 添加路径中间点
for (let i = 0; i < path.length - 1; i++) {
const fromNode = path[i];
const toNode = path[i + 1];
const fromLatLng = L.latLng(nodes[fromNode][1], nodes[fromNode][0]);
const toLatLng = L.latLng(nodes[toNode][1], nodes[toNode][0]);
routePoints.push(fromLatLng);
routePoints.push(toLatLng);
}
// 添加终点
routePoints.push(endPoint);
// 创建路径线
routeLine = L.polyline(routePoints, {
color: "#3B82F6",
weight: 5,
opacity: 0.8,
lineCap: "round",
lineJoin: "round",
interactive: true,
}).addTo(map);
// 显示路径信息
showRouteInfo(path, totalDistance);
// 缩放地图以显示完整路径
map.fitBounds(routeLine.getBounds(), { padding: [50, 50] });
// 启用缩放至路径按钮
const zoomToRouteBtn = document.getElementById("zoomToRoute");
zoomToRouteBtn.classList.remove("opacity-50", "cursor-not-allowed");
zoomToRouteBtn.removeAttribute("disabled");
}
// 显示路径信息
function showRouteInfo(path, totalDistance) {
const routeInfo = document.getElementById("routeInfo");
const routeDistance = document.getElementById("routeDistance");
const routeInstructions = document.getElementById("routeInstructions");
// 显示距离信息
routeDistance.textContent = `总距离: ${(totalDistance / 1000).toFixed(
2
)} 公里`;
// 生成路径指引
let instructionsHTML = "";
// 添加起点说明
instructionsHTML += `
<div class="flex items-center p-2 bg-green-50 rounded">
<div class="w-6 h-6 rounded-full bg-red-500 flex items-center justify-center text-white mr-3">
<i class="fa fa-map-marker"></i>
</div>
<div>
<p class="font-medium">起点</p>
<p class="text-sm text-gray-600">从这里出发</p>
</div>
</div>
`;
// 添加路径说明
let currentRoadName = null;
let currentRoadDistance = 0;
for (let i = 0; i < path.length - 1; i++) {
const fromNode = path[i];
const toNode = path[i + 1];
const roadName = networkGraph[fromNode][toNode].name;
const roadDistance = networkGraph[fromNode][toNode].distance;
// 如果是同一条路,累计距离
if (roadName === currentRoadName) {
currentRoadDistance += roadDistance;
} else {
// 如果不是同一条路,添加指引
if (currentRoadName !== null) {
instructionsHTML += `
<div class="flex items-center p-2 hover:bg-gray-100 rounded transition-all-300">
<div class="w-6 h-6 rounded-full bg-primary/20 flex items-center justify-center text-primary mr-3">
${i}
</div>
<div>
<p class="font-medium">沿 ${currentRoadName} 行驶</p>
<p class="text-sm text-gray-600">${(
currentRoadDistance / 1000
).toFixed(2)} 公里</p>
</div>
</div>
`;
}
currentRoadName = roadName;
currentRoadDistance = roadDistance;
}
}
// 添加最后一条路的指引
if (currentRoadName !== null) {
instructionsHTML += `
<div class="flex items-center p-2 hover:bg-gray-100 rounded transition-all-300">
<div class="w-6 h-6 rounded-full bg-primary/20 flex items-center justify-center text-primary mr-3">
${path.length - 1}
</div>
<div>
<p class="font-medium">沿 ${currentRoadName} 行驶</p>
<p class="text-sm text-gray-600">${(
currentRoadDistance / 1000
).toFixed(2)} 公里</p>
</div>
</div>
`;
}
// 添加终点说明
instructionsHTML += `
<div class="flex items-center p-2 bg-blue-50 rounded">
<div class="w-6 h-6 rounded-full bg-blue-500 flex items-center justify-center text-white mr-3">
<i class="fa fa-flag"></i>
</div>
<div>
<p class="font-medium">终点</p>
<p class="text-sm text-gray-600">到达目的地</p>
</div>
</div>
`;
// 更新指引内容
routeInstructions.innerHTML = instructionsHTML;
// 显示路径信息面板
routeInfo.classList.remove("hidden");
// 确保侧边栏是打开的
if (window.innerWidth < 768) {
document
.getElementById("sidebar")
.classList.remove("-translate-x-full");
}
}
// 清除路径
function clearRoute() {
// 清除起点
if (startMarker) {
map.removeLayer(startMarker);
startMarker = null;
startPoint = null;
document.getElementById("startPoint").value = "";
}
// 清除终点
if (endMarker) {
map.removeLayer(endMarker);
endMarker = null;
endPoint = null;
document.getElementById("endPoint").value = "";
}
// 清除路径
if (routeLine) {
map.removeLayer(routeLine);
routeLine = null;
}
// 隐藏路径信息
document.getElementById("routeInfo").classList.add("hidden");
// 禁用查找路径按钮
updateRouteButtonState();
// 禁用缩放至路径按钮
const zoomToRouteBtn = document.getElementById("zoomToRoute");
zoomToRouteBtn.classList.add("opacity-50", "cursor-not-allowed");
zoomToRouteBtn.setAttribute("disabled", "true");
}
// 初始化事件监听
function initEventListeners() {
// 计算路径按钮
document
.getElementById("findRouteBtn")
.addEventListener("click", calculateRoute);
// 清除路径按钮
document
.getElementById("clearRouteBtn")
.addEventListener("click", clearRoute);
// 清除起点按钮
document
.getElementById("clearStart")
.addEventListener("click", function () {
if (startMarker) {
map.removeLayer(startMarker);
startMarker = null;
startPoint = null;
document.getElementById("startPoint").value = "";
updateRouteButtonState();
}
});
// 清除终点按钮
document
.getElementById("clearEnd")
.addEventListener("click", function () {
if (endMarker) {
map.removeLayer(endMarker);
endMarker = null;
endPoint = null;
document.getElementById("endPoint").value = "";
updateRouteButtonState();
}
});
// 移动端侧边栏切换
document
.getElementById("toggleSidebar")
.addEventListener("click", function () {
const sidebar = document.getElementById("sidebar");
sidebar.classList.toggle("-translate-x-full");
});
// 关闭侧边栏(移动端)
document
.getElementById("closeSidebar")
.addEventListener("click", function () {
document
.getElementById("sidebar")
.classList.add("-translate-x-full");
});
// 缩放至路径按钮
document
.getElementById("zoomToRoute")
.addEventListener("click", function () {
if (routeLine) {
map.fitBounds(routeLine.getBounds(), { padding: [50, 50] });
}
});
// 定位到当前位置
document
.getElementById("locateMe")
.addEventListener("click", function () {
map.locate({ setView: true, maxZoom: 16 });
});
map.on("locationfound", function (e) {
if (startPoint === null) {
setStartPoint(e.latlng);
}
});
map.on("locationerror", function (e) {
alert("无法获取当前位置,请确保您的浏览器允许位置访问。");
});
// 帮助按钮
document
.getElementById("helpBtn")
.addEventListener("click", function () {
const helpModal = document.getElementById("helpModal");
const modalContent = document.getElementById("modalContent");
helpModal.classList.remove("hidden");
setTimeout(() => {
modalContent.classList.remove("scale-95", "opacity-0");
modalContent.classList.add("scale-100", "opacity-100");
}, 10);
});
// 关闭帮助模态框
document
.getElementById("closeHelpModal")
.addEventListener("click", closeHelpModal);
document
.getElementById("gotItBtn")
.addEventListener("click", closeHelpModal);
function closeHelpModal() {
const helpModal = document.getElementById("helpModal");
const modalContent = document.getElementById("modalContent");
modalContent.classList.remove("scale-100", "opacity-100");
modalContent.classList.add("scale-95", "opacity-0");
setTimeout(() => {
helpModal.classList.add("hidden");
}, 300);
}
// 关于按钮
document
.getElementById("aboutBtn")
.addEventListener("click", function () {
alert("路网最短路径分析系统\n基于Leaflet和Dijkstra算法实现");
});
}
// 页面加载完成后初始化地图
document.addEventListener("DOMContentLoaded", initMap);
</script>
</body>
</html>
本站文章除注明转载外,均为原创文章。转载请注明:文章转载自:
葱爆GIS—刘博方GIS博客(
https://liubf.com )