444 lines
16 KiB
HTML
444 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
||
<title>路径规划</title>
|
||
|
||
<style>
|
||
/* --- 全局样式 --- */
|
||
html,
|
||
body,
|
||
#container {
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0;
|
||
font-family: sans-serif;
|
||
}
|
||
|
||
/* --- 暴力隐藏高德自带的导流链接和Logo (关键) --- */
|
||
.amap-callamap,
|
||
.amap-lib-driving-callBtn,
|
||
.amap-copyright,
|
||
.amap-logo {
|
||
display: none !important;
|
||
}
|
||
|
||
#panel .amap-call {
|
||
display: none;
|
||
}
|
||
|
||
/* --- 搜索栏样式 --- */
|
||
#search-box {
|
||
position: absolute;
|
||
top: 10px;
|
||
left: 10px;
|
||
right: 10px;
|
||
z-index: 100;
|
||
background: #fff;
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.input-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
input {
|
||
flex: 1;
|
||
padding: 10px;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
background-color: #f9f9f9;
|
||
font-size: 14px;
|
||
}
|
||
|
||
button {
|
||
padding: 0 15px;
|
||
height: 38px;
|
||
background: #3366FF;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* --- 导航结果面板 (底部弹出) --- */
|
||
#panel {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 35%;
|
||
/* 面板高度 */
|
||
background-color: white;
|
||
overflow-y: auto;
|
||
border-top: 1px solid #eee;
|
||
border-top-left-radius: 12px;
|
||
border-top-right-radius: 12px;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||
z-index: 99;
|
||
display: none;
|
||
/* 默认隐藏 */
|
||
}
|
||
|
||
/* --- 自定义定位按钮样式 --- */
|
||
#location-btn {
|
||
position: fixed;
|
||
right: 10px;
|
||
bottom: 50px;
|
||
/* 默认位置 */
|
||
width: 44px;
|
||
height: 44px;
|
||
background-color: #fff;
|
||
border-radius: 50%;
|
||
/* 圆形更符合现代审美 */
|
||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||
z-index: 150;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: bottom 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||
/* 平滑动画 */
|
||
}
|
||
|
||
#location-btn:active {
|
||
background-color: #f2f2f2;
|
||
}
|
||
|
||
#location-btn svg {
|
||
width: 24px;
|
||
height: 24px;
|
||
fill: #555;
|
||
}
|
||
|
||
/* --- 关键:当 body 有 panel-active 类时,按钮上移 --- */
|
||
body.panel-active #location-btn {
|
||
bottom: 38%;
|
||
/* 对应 #panel 的 height + 一点间距 */
|
||
}
|
||
</style>
|
||
|
||
<!-- 1. 配置安全密钥 -->
|
||
<script type="text/javascript">
|
||
window._AMapSecurityConfig = {
|
||
securityJsCode: 'aa3a22c19ed76b27f8a587555d6981c8',
|
||
}
|
||
</script>
|
||
|
||
<!-- 2. 加载地图和插件 (去掉了 Geolocation 插件,避免弹窗) -->
|
||
<script
|
||
src="https://webapi.amap.com/maps?v=2.0&key=ecd74ece8cb14c9dad67675f83c3274d&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete,AMap.ToolBar,AMap.Scale,AMap.Geocoder">
|
||
</script>
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<div id="search-box">
|
||
<div class="input-row">
|
||
<input id="startInput" placeholder="起点: 请输入当前地点" />
|
||
</div>
|
||
<div class="input-row">
|
||
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||
<button onclick="startRouteSearch()">路径规划</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="container"></div>
|
||
|
||
<div id="panel"></div>
|
||
|
||
<!-- 自定义定位按钮 -->
|
||
<div id="location-btn" onclick="backToLocation()">
|
||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||
<path
|
||
d="M512 85.333333a426.666667 426.666667 0 1 0 0 853.333334 426.666667 426.666667 0 0 0 0-853.333334z m0 149.333334a277.333333 277.333333 0 1 1 0 554.666666 277.333333 277.333333 0 0 1 0-554.666666z m0 149.333333a128 128 0 1 0 0 256 128 128 0 0 0 0-256z"
|
||
fill="#666666"></path>
|
||
</svg>
|
||
</div>
|
||
|
||
<script>
|
||
var map, marker, driving, truckDriving, geocoder;
|
||
var currentLat, currentLng;
|
||
var isTruckMode = false;
|
||
var isInitialLocationSet = false;
|
||
|
||
|
||
function initMap() {
|
||
map = new AMap.Map('container', {
|
||
resizeEnable: true,
|
||
zoom: 15,
|
||
viewMode: '3D'
|
||
});
|
||
|
||
// --- 2. 初始化 geocoder ---
|
||
geocoder = new AMap.Geocoder({
|
||
city: "全国" // 设置地理编码范围
|
||
});
|
||
|
||
// 通知 Flutter 地图加载完毕
|
||
map.on('complete', function () {
|
||
console.log("JS->: Map is ready.");
|
||
if (window.flutter_inappwebview) {
|
||
window.flutter_inappwebview.callHandler('mapReady');
|
||
}
|
||
});
|
||
|
||
// 添加基础控件
|
||
map.addControl(new AMap.Scale());
|
||
map.addControl(new AMap.ToolBar({
|
||
visible: true,
|
||
position: {
|
||
top: '200px',
|
||
right: '10px'
|
||
} // 稍微避开定位按钮
|
||
}));
|
||
|
||
// --- 初始化轿车规划 ---
|
||
driving = new AMap.Driving({
|
||
map: map,
|
||
panel: "panel",
|
||
policy: AMap.DrivingPolicy.LEAST_TIME
|
||
});
|
||
|
||
// --- 初始化货车规划 ---
|
||
if (AMap.TruckDriving) {
|
||
truckDriving = new AMap.TruckDriving({
|
||
map: map,
|
||
panel: "panel",
|
||
size: 2,
|
||
policy: 0,
|
||
width: 2.5,
|
||
height: 2,
|
||
load: 1,
|
||
weight: 10,
|
||
axlesNum: 2,
|
||
});
|
||
}
|
||
|
||
// --- 输入提示 ---
|
||
new AMap.AutoComplete({
|
||
input: "startInput"
|
||
});
|
||
new AMap.AutoComplete({
|
||
input: "endInput"
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 核心功能 1: 接收 Flutter 传来的定位数据
|
||
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
||
* 经度 维度
|
||
*/
|
||
function updateMyLocation(lat, lng, angle) {
|
||
var rawLat = parseFloat(lat);
|
||
var rawLng = parseFloat(lng);
|
||
var rawAngle = parseFloat(angle);
|
||
var gps = [rawLng, rawLat];
|
||
|
||
AMap.convertFrom(gps, 'gps', function (status, result) {
|
||
if (result.info === 'ok') {
|
||
var mPoint = result.locations[0];
|
||
currentLng = mPoint.lng;
|
||
currentLat = mPoint.lat;
|
||
var position = [currentLng, currentLat];
|
||
|
||
// 更新车辆标记位置 (保持不变)
|
||
if (!marker) {
|
||
marker = new AMap.Marker({
|
||
map: map,
|
||
position: position,
|
||
icon: "car.png",
|
||
offset: new AMap.Pixel(-23.5, -15),
|
||
autoRotation: true,
|
||
angle: isNaN(rawAngle) ? 0 : rawAngle,
|
||
});
|
||
map.setCenter(position);
|
||
} else {
|
||
marker.moveTo(position, {
|
||
duration: 1000,
|
||
autoRotation: true
|
||
});
|
||
if (!isNaN(rawAngle)) marker.setAngle(rawAngle);
|
||
}
|
||
|
||
// --- 4. 逆地理编码并设置默认起点 ---
|
||
// 只有在第一次获取到位置时,才设置默认起点,避免覆盖用户手动输入的起点
|
||
if (!isInitialLocationSet) {
|
||
geocoder.getAddress(position, function (status, result) {
|
||
if (status === 'complete' && result.regeocode) {
|
||
let shortAddress = '';
|
||
const regeo = result.regeocode;
|
||
const addressComponent = regeo.addressComponent;
|
||
const pois = regeo.pois;
|
||
|
||
console.log("地理:"+JSON.stringify(result));
|
||
|
||
// 策略1: 优先使用最近的、类型合适的POI的名称
|
||
if (pois && pois.length > 0) {
|
||
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名
|
||
const significantPoi = pois.find(p => p.type.indexOf('商务住宅') === -
|
||
1 && p.type.indexOf('地名地址信息') === -1);
|
||
if (significantPoi) {
|
||
shortAddress = significantPoi.name;
|
||
} else {
|
||
// 如果找不到,就用第一个POI的名字
|
||
shortAddress = pois[0].name;
|
||
}
|
||
}
|
||
// 策略2: 如果没有POI,使用"道路+门牌号"
|
||
else if (addressComponent.street && addressComponent.streetNumber) {
|
||
shortAddress = addressComponent.district+
|
||
addressComponent.township+
|
||
addressComponent.street + addressComponent.streetNumber;
|
||
}
|
||
// 策略3: 如果还没有,使用"区+乡镇"
|
||
else if (addressComponent.district) {
|
||
shortAddress = addressComponent.district + (addressComponent
|
||
.township || '');
|
||
}
|
||
// 策略4: 降级到使用完整的、但可能很长的地址
|
||
else {
|
||
shortAddress = regeo.formattedAddress;
|
||
}
|
||
|
||
// 如果拼接出的地址过长,可以再做一次截断
|
||
if (shortAddress.length > 20) {
|
||
// 可以在这里添加更复杂的截断逻辑,比如按关键字
|
||
shortAddress = regeo.formattedAddress.substring(0, 20) + '...';
|
||
}
|
||
|
||
|
||
// 将获取到的地址填充到起点输入框
|
||
document.getElementById('startInput').value = shortAddress;
|
||
isInitialLocationSet = true; // 标记为已设置,不再更新
|
||
} else {
|
||
// 如果逆地理编码失败,依然使用“当前位置”作为提示
|
||
document.getElementById('startInput').placeholder = "当前位置";
|
||
console.error('逆地理编码失败:', result);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 核心功能 2: 点击按钮回到当前位置
|
||
*/
|
||
function backToLocation() {
|
||
if (currentLng && currentLat) {
|
||
map.panTo([currentLng, currentLat]);
|
||
map.setZoom(17);
|
||
console.log("JS: 已回到当前位置");
|
||
} else {
|
||
console.log("JS: 暂无定位数据,向 Flutter 请求...");
|
||
if (window.flutter_inappwebview) {
|
||
window.flutter_inappwebview.callHandler('requestLocation');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 核心功能 3: 路径规划
|
||
*/
|
||
function startRouteSearch() {
|
||
// 获取输入框的文字
|
||
var startKw = document.getElementById('startInput').value;
|
||
var endKw = document.getElementById('endInput').value;
|
||
|
||
if (!startKw) {
|
||
alert("请输入起点");
|
||
return;
|
||
}
|
||
|
||
if (!endKw) {
|
||
alert("请输入终点");
|
||
return;
|
||
}
|
||
|
||
// 清除旧路线
|
||
if (driving) driving.clear();
|
||
|
||
// 收起键盘
|
||
document.getElementById('startInput').blur();
|
||
document.getElementById('endInput').blur();
|
||
|
||
// --- 构造路径规划的点 (使用数组方式,更灵活) ---
|
||
var points = [];
|
||
|
||
// 1. 处理起点逻辑
|
||
// 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标
|
||
if (!startKw || startKw === '我的位置') {
|
||
if (!currentLng || !currentLat) {
|
||
// 如果还没获取到定位
|
||
if (window.flutter_inappwebview) {
|
||
window.flutter_inappwebview.callHandler('requestLocation');
|
||
}
|
||
alert("正在获取定位,请稍后...");
|
||
return;
|
||
}
|
||
// 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词)
|
||
points.push({
|
||
keyword: '我的位置', // 用于显示的名字
|
||
location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标
|
||
});
|
||
} else {
|
||
// 如果用户手动输入了地点 (例如 "北京南站")
|
||
// 直接存入关键词,让高德自己去搜
|
||
points.push({
|
||
keyword: startKw
|
||
});
|
||
}
|
||
|
||
// 2. 处理终点逻辑 (通常是关键词)
|
||
points.push({
|
||
keyword: endKw
|
||
});
|
||
|
||
// 3. 发起搜索
|
||
// points 数组里现在是一个起点对象和一个终点对象
|
||
driving.search(points, function (status, result) {
|
||
if (status === 'complete') {
|
||
console.log('JS: 规划成功');
|
||
var panel = document.getElementById('panel');
|
||
panel.style.display = 'block';
|
||
document.body.classList.add('panel-active');
|
||
} else {
|
||
console.log('JS: 规划失败', result);
|
||
alert("规划失败,请检查起终点名称");
|
||
}
|
||
});
|
||
}
|
||
|
||
// 辅助功能:手动绘制路线 (Flutter 可能用到)
|
||
function drawCustomRoute(pointsJsonString) {
|
||
var path = typeof pointsJsonString === 'string' ? JSON.parse(pointsJsonString) : pointsJsonString;
|
||
if (driving) driving.clear();
|
||
var polyline = new AMap.Polyline({
|
||
path: path,
|
||
isOutline: true,
|
||
outlineColor: '#ffeeee',
|
||
borderWeight: 3,
|
||
strokeColor: "#3366FF",
|
||
strokeWeight: 6,
|
||
lineJoin: 'round'
|
||
});
|
||
map.add(polyline);
|
||
map.setFitView([polyline]);
|
||
}
|
||
|
||
window.onload = initMap;
|
||
</script>
|
||
</body>
|
||
|
||
</html> |