Files
yjearth/geo.html
2025-09-08 17:01:50 +08:00

769 lines
34 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>路径规划系统</title>
<!-- 仅保留必要CDN -->
<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">
<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://cdn.jsdelivr.net/npm/axios@1.6.8/dist/axios.min.js"></script>
<!-- Tailwind配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
success: '#00B42A',
danger: '#F53F3F',
warning: '#FF7D00',
neutral: '#F5F7FA',
'neutral-dark': '#4E5969',
},
fontFamily: {
inter: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.map-height {
height: 100vh; /* 调整为占满视口高度 */
}
.sidebar-height {
height: 100vh; /* 调整为占满视口高度 */
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.custom-marker .fa {
font-size: 14px;
}
.input-error {
@apply border-danger focus:ring-danger/50 focus:border-danger;
}
.btn-disabled {
@apply bg-gray-300 text-gray-500 cursor-not-allowed hover:bg-gray-300;
}
}
</style>
</head>
<body class="font-inter bg-gray-50 text-gray-800 antialiased m-0">
<!-- 主内容区直接顶到顶部、删除原header -->
<main class="flex flex-col md:flex-row">
<!-- 左侧控制面板 -->
<aside class="w-full md:w-96 bg-white shadow-sm z-10 md:sidebar-height overflow-y-auto scrollbar-hide transition-all">
<div class="p-4 space-y-6">
<!-- 地图加载区域 -->
<div class="p-4 border border-gray-100 rounded-lg bg-neutral shadow-sm">
<h2 class="text-lg font-semibold mb-3 flex items-center text-neutral-dark">
<i class="fa fa-map text-primary mr-2"></i>地图管理
</h2>
<div class="flex items-center space-x-2 mb-2">
<input type="file" id="mapFile" accept=".pbf" class="hidden" multiple="false"/>
<button id="selectFileBtn"
class="flex-1 px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded text-sm font-medium transition-colors duration-200">
选择PBF文件
</button>
<button id="loadMapBtn"
class="px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded text-sm font-medium transition-colors duration-200">
加载地图
</button>
</div>
<div id="fileInfo" class="mt-1 text-sm hidden"></div>
<div id="loadProgress" class="mt-1 text-sm text-primary hidden flex items-center">
<i class="fa fa-spinner fa-spin mr-1.5"></i>
<span id="progressText">正在处理...</span>
</div>
</div>
<!-- 路径规划参数 -->
<div class="space-y-4">
<h2 class="text-lg font-semibold flex items-center text-neutral-dark">
<i class="fa fa-road text-primary mr-2"></i>路径参数
</h2>
<!-- 起点移除默认value、默认无数据 -->
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">起点 <span class="text-danger">*</span></label>
<div class="flex space-x-2">
<div class="flex-1 space-y-0.5">
<input type="text" id="startLat" placeholder="纬度"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all"
maxlength="10">
<span id="startLatError" class="text-danger text-xs hidden">请输入有效纬度</span>
</div>
<div class="flex-1 space-y-0.5">
<input type="text" id="startLng" placeholder="经度"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all"
maxlength="10">
<span id="startLngError" class="text-danger text-xs hidden">请输入有效经度</span>
</div>
<button id="setStartBtn" class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200"
title="在地图上选择起点">
<i class="fa fa-map-marker text-danger"></i>
</button>
</div>
</div>
<!-- 终点 -->
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">终点 <span class="text-danger">*</span></label>
<div class="flex space-x-2">
<div class="flex-1 space-y-0.5">
<input type="text" id="endLat" placeholder="纬度"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all"
maxlength="10">
<span id="endLatError" class="text-danger text-xs hidden">请输入有效纬度</span>
</div>
<div class="flex-1 space-y-0.5">
<input type="text" id="endLng" placeholder="经度"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all"
maxlength="10">
<span id="endLngError" class="text-danger text-xs hidden">请输入有效经度</span>
</div>
<button id="setEndBtn" class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200"
title="在地图上选择终点">
<i class="fa fa-flag text-success"></i>
</button>
</div>
</div>
<!-- 途经点 -->
<div class="space-y-1">
<div class="flex justify-between items-center">
<label class="block text-sm font-medium text-gray-700">途经点</label>
<button id="addWaypointBtn"
class="text-xs px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200 flex items-center">
<i class="fa fa-plus mr-1"></i>添加
</button>
</div>
<div id="waypointsContainer" class="space-y-2">
<!-- 途经点动态添加 -->
</div>
</div>
<!-- 交通方式 -->
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">交通方式 <span class="text-danger">*</span></label>
<select id="profile"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all bg-white">
<option value="car">驾车</option>
<option value="bike">骑行</option>
<option value="foot">步行</option>
</select>
</div>
<!-- 计算路径按钮 -->
<button id="calculateRouteBtn"
class="w-full py-2.5 bg-primary hover:bg-primary/90 text-white rounded-md font-medium transition-colors duration-200 flex items-center justify-center btn-disabled"
disabled>
<i class="fa fa-calculator mr-2"></i>请先加载地图
</button>
</div>
<!-- 结果展示区域 -->
<div id="resultContainer" class="p-4 border border-gray-100 rounded-lg bg-neutral shadow-sm hidden">
<h2 class="text-lg font-semibold mb-3 flex items-center text-neutral-dark">
<i class="fa fa-check-circle text-success mr-2"></i>路径结果
</h2>
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="bg-white p-3 rounded shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
<div class="text-xs text-gray-500 mb-1">总距离</div>
<div id="distanceResult" class="text-lg font-semibold text-gray-800">-</div>
</div>
<div class="bg-white p-3 rounded shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
<div class="text-xs text-gray-500 mb-1">预计时间</div>
<div id="timeResult" class="text-lg font-semibold text-gray-800">-</div>
</div>
</div>
<div class="grid grid-cols-2 gap-2">
<button id="clearRouteBtn"
class="py-2 border border-gray-300 bg-white hover:bg-gray-50 text-gray-700 rounded-md text-sm font-medium transition-colors duration-200">
<i class="fa fa-trash mr-1"></i>清除路径
</button>
<button id="clearAllBtn"
class="py-2 border border-gray-300 bg-white hover:bg-gray-50 text-gray-700 rounded-md text-sm font-medium transition-colors duration-200">
<i class="fa fa-refresh mr-1"></i>清空所有
</button>
</div>
</div>
</div>
</aside>
<!-- 右侧地图区域 -->
<section class="flex-1 relative">
<div id="map" class="w-full map-height z-0"></div>
<!-- 地图控件 -->
<div class="absolute top-4 right-4 z-10 flex flex-col space-y-2">
<button id="zoomInBtn"
class="w-10 h-10 bg-white rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200"
title="放大">
<i class="fa fa-plus"></i>
</button>
<button id="zoomOutBtn"
class="w-10 h-10 bg-white rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200"
title="缩小">
<i class="fa fa-minus"></i>
</button>
<button id="centerMapBtn"
class="w-10 h-10 bg-white rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200"
title="重置中心(成都)">
<i class="fa fa-crosshairs"></i>
</button>
</div>
<!-- 地图操作提示(移动端) -->
<div class="absolute bottom-4 left-4 z-10 md:hidden bg-white/90 px-3 py-2 rounded-full text-xs text-neutral-dark shadow-md">
<i class="fa fa-hand-pointer-o text-primary mr-1"></i>点击地图设起点/终点
</div>
</section>
</main>
<script>
// 大整数转字符串处理(保留原逻辑)
axios.defaults.transformResponse = [
function(data) {
if (typeof data !== 'string') return data;
const bigIntRegex = /(\s*"[^"]*"\s*:\s*)(\d{16,})(\s*)/g;
return data.replace(bigIntRegex, (match, keyPart, bigInt, endPart) => {
return `${keyPart}"${bigInt}"${endPart}`;
});
},
function(parsedData) {
try {
return JSON.parse(parsedData);
} catch (e) {
return parsedData;
}
}
];
// 全局变量
let map;
let startMarker = null;
let endMarker = null;
let waypointMarkers = [];
let routeLine = null;
let waypointCount = 0;
const API_BASE_URL = "http://127.0.0.1:8848";
const DEFAULT_CENTER = { lat: 30.6570, lng: 104.0650 }; // 成都默认坐标
// 地图初始化
function initMap() {
map = L.map('map', {
zoomControl: false,
attributionControl: true,
minZoom: 5,
maxZoom: 18
}).setView([DEFAULT_CENTER.lat, DEFAULT_CENTER.lng], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
tileSize: 256,
zoomOffset: 0
}).addTo(map);
map.on('click', handleMapClick);
}
// 表单验证
function validateCoord(value, type) {
const num = parseFloat(value);
if (isNaN(num)) return false;
return type === 'lat' ? (num >= -90 && num <= 90) : (num >= -180 && num <= 180);
}
function toggleInputError(inputId, errorId, show, inputEl = null, errorEl = null) {
inputEl = inputEl || document.getElementById(inputId);
errorEl = errorEl || document.getElementById(errorId);
if (show) {
inputEl.classList.add('input-error');
errorEl.classList.remove('hidden');
} else {
inputEl.classList.remove('input-error');
errorEl.classList.add('hidden');
}
}
// 绑定坐标验证
function bindCoordValidation() {
// 起点验证
document.getElementById('startLat').addEventListener('input', (e) => {
const isValid = validateCoord(e.target.value, 'lat');
toggleInputError('startLat', 'startLatError', !isValid);
});
document.getElementById('startLng').addEventListener('input', (e) => {
const isValid = validateCoord(e.target.value, 'lng');
toggleInputError('startLng', 'startLngError', !isValid);
});
// 终点验证
document.getElementById('endLat').addEventListener('input', (e) => {
const isValid = validateCoord(e.target.value, 'lat');
toggleInputError('endLat', 'endLatError', !isValid);
});
document.getElementById('endLng').addEventListener('input', (e) => {
const isValid = validateCoord(e.target.value, 'lng');
toggleInputError('endLng', 'endLngError', !isValid);
});
}
// 标记管理
function setStartPoint(lat, lng) {
const latEl = document.getElementById('startLat');
const lngEl = document.getElementById('startLng');
latEl.value = lat.toFixed(6);
lngEl.value = lng.toFixed(6);
latEl.dispatchEvent(new Event('input'));
lngEl.dispatchEvent(new Event('input'));
if (startMarker) {
startMarker.setLatLng([lat, lng]);
} else {
startMarker = L.marker([lat, lng], {
icon: L.divIcon({
className: 'custom-marker',
html: '<div class="w-6 h-6 bg-danger rounded-full flex items-center justify-center text-white shadow-md"><i class="fa fa-map-marker"></i></div>',
iconSize: [30, 30],
iconAnchor: [15, 30]
}),
draggable: true,
riseOnHover: true
}).addTo(map);
startMarker.on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng();
setStartPoint(lat, lng);
});
}
}
function setEndPoint(lat, lng) {
const latEl = document.getElementById('endLat');
const lngEl = document.getElementById('endLng');
latEl.value = lat.toFixed(6);
lngEl.value = lng.toFixed(6);
latEl.dispatchEvent(new Event('input'));
lngEl.dispatchEvent(new Event('input'));
if (endMarker) {
endMarker.setLatLng([lat, lng]);
} else {
endMarker = L.marker([lat, lng], {
icon: L.divIcon({
className: 'custom-marker',
html: '<div class="w-6 h-6 bg-success rounded-full flex items-center justify-center text-white shadow-md"><i class="fa fa-flag"></i></div>',
iconSize: [30, 30],
iconAnchor: [15, 30]
}),
draggable: true,
riseOnHover: true
}).addTo(map);
endMarker.on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng();
setEndPoint(lat, lng);
});
}
}
function addWaypoint(lat = '', lng = '') {
waypointCount++;
const container = document.getElementById('waypointsContainer');
const waypointDiv = document.createElement('div');
waypointDiv.className = 'flex space-x-2 waypoint-item';
waypointDiv.dataset.id = waypointCount;
waypointDiv.innerHTML = `
<div class="flex-1 space-y-0.5">
<input type="text" class="waypoint-lat w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all"
placeholder="纬度" value="${lat}" maxlength="10">
<span class="waypoint-lat-error text-danger text-xs hidden">请输入有效纬度</span>
</div>
<div class="flex-1 space-y-0.5">
<input type="text" class="waypoint-lng w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all"
placeholder="经度" value="${lng}" maxlength="10">
<span class="waypoint-lng-error text-danger text-xs hidden">请输入有效经度</span>
</div>
<button class="set-waypoint-btn p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" title="在地图上选择">
<i class="fa fa-map-pin text-primary"></i>
</button>
<button class="remove-waypoint-btn p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" title="删除途经点">
<i class="fa fa-times text-gray-500"></i>
</button>
`;
container.appendChild(waypointDiv);
const latInput = waypointDiv.querySelector('.waypoint-lat');
const lngInput = waypointDiv.querySelector('.waypoint-lng');
const latError = waypointDiv.querySelector('.waypoint-lat-error');
const lngError = waypointDiv.querySelector('.waypoint-lng-error');
latInput.addEventListener('input', (e) => {
const isValid = validateCoord(e.target.value, 'lat');
toggleInputError(null, null, !isValid, latInput, latError);
});
lngInput.addEventListener('input', (e) => {
const isValid = validateCoord(e.target.value, 'lng');
toggleInputError(null, null, !isValid, lngInput, lngError);
});
// 途经点地图选点
waypointDiv.querySelector('.set-waypoint-btn').addEventListener('click', () => {
const id = parseInt(waypointDiv.dataset.id);
map.once('click', (e) => {
const { lat, lng } = e.latlng;
latInput.value = lat.toFixed(6);
lngInput.value = lng.toFixed(6);
latInput.dispatchEvent(new Event('input'));
lngInput.dispatchEvent(new Event('input'));
if (waypointMarkers[id]) {
waypointMarkers[id].setLatLng([lat, lng]);
} else {
waypointMarkers[id] = L.marker([lat, lng], {
icon: L.divIcon({
className: 'custom-marker',
html: `<div class="w-5 h-5 bg-primary rounded-full flex items-center justify-center text-white text-xs shadow-md">${id}</div>`,
iconSize: [25, 25],
iconAnchor: [12, 25]
}),
draggable: true,
riseOnHover: true
}).addTo(map);
waypointMarkers[id].on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng();
latInput.value = lat.toFixed(6);
lngInput.value = lng.toFixed(6);
latInput.dispatchEvent(new Event('input'));
lngInput.dispatchEvent(new Event('input'));
});
}
});
});
// 删除途经点
waypointDiv.querySelector('.remove-waypoint-btn').addEventListener('click', () => {
const id = parseInt(waypointDiv.dataset.id);
if (waypointMarkers[id]) {
map.removeLayer(waypointMarkers[id]);
waypointMarkers[id] = null;
}
waypointDiv.remove();
});
}
// 地图点击处理
function handleMapClick(e) {
const { lat, lng } = e.latlng;
const startLat = document.getElementById('startLat').value;
const endLat = document.getElementById('endLat').value;
// 先设起点(空则设起点)→ 再设终点
if (!startLat.trim()) {
setStartPoint(lat, lng);
} else if (!endLat.trim()) {
setEndPoint(lat, lng);
}
}
// 路径操作
function clearRoute() {
if (routeLine) {
map.removeLayer(routeLine);
routeLine = null;
}
document.getElementById('resultContainer').classList.add('hidden');
}
// 清空所有:起点重置为空(而非默认坐标)
function clearAll() {
clearRoute();
// 清除起点(重置为空)
if (startMarker) {
map.removeLayer(startMarker);
startMarker = null;
}
document.getElementById('startLat').value = '';
document.getElementById('startLng').value = '';
toggleInputError('startLat', 'startLatError', false);
toggleInputError('startLng', 'startLngError', false);
// 清除终点
if (endMarker) {
map.removeLayer(endMarker);
endMarker = null;
}
document.getElementById('endLat').value = '';
document.getElementById('endLng').value = '';
toggleInputError('endLat', 'endLatError', false);
toggleInputError('endLng', 'endLngError', false);
// 清除途经点
waypointMarkers.forEach(marker => {
if (marker) map.removeLayer(marker);
});
waypointMarkers = [];
document.getElementById('waypointsContainer').innerHTML = '';
waypointCount = 0;
addWaypoint(); // 重置默认空途经点
}
// 地图加载进度
function updateLoadProgress(show, text = '正在处理...') {
const progressEl = document.getElementById('loadProgress');
const textEl = document.getElementById('progressText');
if (show) {
progressEl.classList.remove('hidden');
textEl.textContent = text;
} else {
progressEl.classList.add('hidden');
}
}
// 上传PBF文件
async function uploadPbfFile(file) {
try {
const formData = new FormData();
formData.append('files', file);
updateLoadProgress(true, '正在上传地图文件...');
const response = await axios.post(`${API_BASE_URL}/fileInfo/upload`, formData, {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
if (percent < 100) {
updateLoadProgress(true, `上传中... ${percent}%`);
}
}
});
if (response.data.code !== 200 || !response.data.data || !response.data.data[0]) {
throw new Error(`上传失败:${response.data.message || '未知错误'}`);
}
const fileId = response.data.data[0].id;
updateLoadProgress(true, `上传成功ID: ${fileId})、正在加载地图...`);
return fileId;
} catch (error) {
const errorMsg = error.response?.data?.message || error.message || '上传异常';
updateLoadProgress(false);
alert(`⚠️ 上传失败:${errorMsg}`);
throw error;
}
}
// 加载地图
async function loadMapByFileId(fileId) {
try {
const formData = new FormData();
formData.append('fileId', fileId);
const response = await axios.post(`${API_BASE_URL}/graphhopper/loadMap`, formData);
if (response.data.code !== 200) {
throw new Error(`加载失败:${response.data.message || '接口返回异常'}`);
}
updateLoadProgress(false);
const calcBtn = document.getElementById('calculateRouteBtn');
calcBtn.disabled = false;
calcBtn.classList.remove('btn-disabled');
calcBtn.innerHTML = '<i class="fa fa-calculator mr-2"></i>计算路径';
return true;
} catch (error) {
const errorMsg = error.response?.data?.message || error.message || '加载异常';
updateLoadProgress(false);
alert(`⚠️ 地图加载失败:${errorMsg}`);
throw error;
}
}
// 计算路径
async function calculateRoute() {
const startLat = parseFloat(document.getElementById('startLat').value);
const startLng = parseFloat(document.getElementById('startLng').value);
const endLat = parseFloat(document.getElementById('endLat').value);
const endLng = parseFloat(document.getElementById('endLng').value);
const profile = document.getElementById('profile').value;
// 基础验证
if ([startLat, startLng, endLat, endLng].some(isNaN)) {
alert('⚠️ 请确保起点/终点坐标为有效数字');
return;
}
if (!validateCoord(startLat, 'lat') || !validateCoord(startLng, 'lng')) {
alert('⚠️ 起点坐标超出有效范围(纬度-90~90、经度-180~180');
return;
}
if (!validateCoord(endLat, 'lat') || !validateCoord(endLng, 'lng')) {
alert('⚠️ 终点坐标超出有效范围(纬度-90~90、经度-180~180');
return;
}
// 处理途经点
const waypoints = [];
document.querySelectorAll('.waypoint-item').forEach(item => {
const lat = parseFloat(item.querySelector('.waypoint-lat').value);
const lng = parseFloat(item.querySelector('.waypoint-lng').value);
if (!isNaN(lat) && !isNaN(lng) && validateCoord(lat, 'lat') && validateCoord(lng, 'lng')) {
waypoints.push({ lat, lng });
}
});
// 发起请求
const calcBtn = document.getElementById('calculateRouteBtn');
calcBtn.disabled = true;
calcBtn.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i>计算中...';
try {
const response = await axios.post(
`${API_BASE_URL}/graphhopper/route`,
{ startLat, startLng, endLat, endLng, profile, waypoints },
{ headers: { 'Content-Type': 'application/json' } }
);
if (response.data.code !== 200 || !response.data.data) {
throw new Error(`计算失败:${response.data.message || '接口返回异常'}`);
}
handleRouteResponse(response.data.data);
} catch (error) {
const errorMsg = error.response?.data?.message || error.message || '计算异常';
alert(`⚠️ 路径计算失败:${errorMsg}`);
} finally {
calcBtn.disabled = false;
calcBtn.innerHTML = '<i class="fa fa-calculator mr-2"></i>计算路径';
}
}
// 处理路径结果
function handleRouteResponse(routeData) {
document.getElementById('distanceResult').textContent = `${routeData.distanceKm.toFixed(2)} 公里`;
document.getElementById('timeResult').textContent = `${routeData.timeMinutes} 分钟`;
document.getElementById('resultContainer').classList.remove('hidden');
if (routeLine) map.removeLayer(routeLine);
const latLngs = routeData.pathPoints.map(point => [point.lat, point.lng]);
const lineStyles = {
car: { color: '#165DFF', weight: 5, opacity: 0.8, dashArray: '' },
bike: { color: '#00B42A', weight: 4, opacity: 0.8, dashArray: '5,5' },
foot: { color: '#4b0c35', weight: 3, opacity: 0.8, dashArray: '2,2' }
};
routeLine = L.polyline(latLngs, lineStyles[document.getElementById('profile').value])
.addTo(map)
.bindPopup(`<div class="text-sm"><p>距离:${routeData.distanceKm.toFixed(2)} 公里</p><p>时间:${routeData.timeMinutes} 分钟</p></div>`);
map.fitBounds(routeLine.getBounds(), { padding: [50, 50], maxZoom: 14 });
}
// 事件绑定:移除起点按钮弹窗
function bindEvents() {
// 文件选择相关
document.getElementById('selectFileBtn').addEventListener('click', () => {
document.getElementById('mapFile').click();
});
document.getElementById('mapFile').addEventListener('change', (e) => {
const fileInfoEl = document.getElementById('fileInfo');
if (e.target.files.length === 0) {
fileInfoEl.classList.add('hidden');
return;
}
const file = e.target.files[0];
if (file.name.toLowerCase().endsWith('.pbf')) {
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(2);
fileInfoEl.textContent = `已选择:${file.name}${fileSizeMB} MB`;
fileInfoEl.className = 'mt-1 text-sm text-gray-600';
} else {
fileInfoEl.textContent = '❌ 请选择PBF格式的地图文件';
fileInfoEl.className = 'mt-1 text-sm text-danger';
e.target.value = '';
}
});
document.getElementById('loadMapBtn').addEventListener('click', async () => {
const fileInput = document.getElementById('mapFile');
if (fileInput.files.length === 0) {
alert('⚠️ 请先选择PBF格式地图文件');
return;
}
try {
const file = fileInput.files[0];
const fileId = await uploadPbfFile(file);
await loadMapByFileId(fileId);
fileInput.value = '';
document.getElementById('fileInfo').classList.add('hidden');
} catch (error) {
console.error('地图加载失败:', error);
}
});
// 起点按钮:移除弹窗、仅保留地图选点逻辑
document.getElementById('setStartBtn').addEventListener('click', () => {
map.once('click', (e) => setStartPoint(e.latlng.lat, e.latlng.lng));
});
// 终点按钮保留弹窗、如需统一移除可删除alert
document.getElementById('setEndBtn').addEventListener('click', () => {
map.once('click', (e) => setEndPoint(e.latlng.lat, e.latlng.lng));
});
// 途经点/路径操作
document.getElementById('addWaypointBtn').addEventListener('click', () => addWaypoint());
document.getElementById('calculateRouteBtn').addEventListener('click', calculateRoute);
document.getElementById('clearRouteBtn').addEventListener('click', clearRoute);
document.getElementById('clearAllBtn').addEventListener('click', clearAll);
// 地图控件
document.getElementById('zoomInBtn').addEventListener('click', () => map.zoomIn());
document.getElementById('zoomOutBtn').addEventListener('click', () => map.zoomOut());
document.getElementById('centerMapBtn').addEventListener('click', () => {
map.setView([DEFAULT_CENTER.lat, DEFAULT_CENTER.lng], 12);
});
// 表单验证绑定
bindCoordValidation();
}
// 应用初始化
function initApp() {
initMap();
bindEvents();
addWaypoint(); // 默认添加1个空途经点
}
// 页面加载完成后初始化
window.addEventListener('load', initApp);
</script>
</body>
</html>