Files
yjearth/geo.html

769 lines
34 KiB
HTML
Raw Normal View History

2025-09-08 17:01:50 +08:00
<!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>