程序会在以下情况下平移地图:
当定位点不在当前视野范围内时,自动平移地图,确保定位点可见。
平移时保持缩放级别不变,避免干扰用户的操作体验。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
<title>高德地图 MQTT GPS 定位</title>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<style>
html, body, #container {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#device-selector {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: rgba(255, 255, 255, 0.8); /* 半透明背景 */
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
#device-selector select {
width: 100%;
padding: 5px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 3px;
background: rgba(255, 255, 255, 0.8); /* 半透明背景 */
}
</style>
</head>
<body>
<div id="container"></div>
<!-- 设备选择器 -->
<div id="device-selector">
<select id="device-list">
<option value="all">显示所有设备</option>
<!-- 设备选项将动态添加到这里 -->
</select>
</div>
<!-- 安全密钥配置 -->
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: "你的安全密钥", // 替换为申请的安全密钥
};
</script>
<!-- 引入高德地图 JS API Loader -->
<script src="https://webapi.amap.com/loader.js"></script>
<!-- 引入 MQTT.js -->
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script type="text/javascript">
// WGS84 转 GCJ02 坐标转换函数
const pi = 3.1415926535897932384626;
const a = 6378245.0; // 椭球长半径
const ee = 0.00669342162296594323; // 第一偏心率的平方
function outOfChina(lat, lon) {
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
function transformLat(x, y) {
let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLon(x, y) {
let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
return ret;
}
function wgs84ToGcj02(lat, lon) {
if (outOfChina(lat, lon)) {
return { lat, lon };
}
let dLat = transformLat(lon - 105.0, lat - 35.0);
let dLon = transformLon(lon - 105.0, lat - 35.0);
const radLat = lat / 180.0 * pi;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
const mgLat = lat + dLat;
const mgLon = lon + dLon;
return { lat: mgLat, lon: mgLon };
}
// 检查点是否在视野范围内
function isPointInView(map, point) {
const bounds = map.getBounds(); // 获取当前地图的视野范围
return bounds.contains(point); // 判断点是否在视野范围内
}
// 加载高德地图 JS API
AMapLoader.load({
key: "94af36b1b75bcda68807bd2684941e79", // 替换为申请的 Web 端 Key
version: "2.0",
})
.then((AMap) => {
// 1. 初始化高德地图
const map = new AMap.Map("container", {
zoom: 13, // 地图缩放级别
center: [121.594, 29.8706], // 初始化中心位置
});
// 2. 存储设备标记、轨迹和颜色
const markers = {};
const paths = {}; // 存储每个设备的轨迹点
const polylines = {}; // 存储每个设备的轨迹线
const colors = { // 为不同设备分配不同颜色
XinEr: "#FF0000", // 红色
CAR1: "#0000FF", // 蓝色
};
// 3. 设备选择器逻辑
const deviceList = document.getElementById("device-list");
// 显示所有设备
deviceList.addEventListener("change", (event) => {
const selectedDevice = event.target.value;
// 隐藏所有标记和轨迹
Object.values(markers).forEach(marker => marker.hide());
Object.values(polylines).forEach(polyline => polyline.hide());
if (selectedDevice === "all") {
// 显示所有设备
Object.values(markers).forEach(marker => marker.show());
Object.values(polylines).forEach(polyline => polyline.show());
adjustViewToAllDevices(); // 调整视野以包含所有设备
} else {
// 显示当前设备的标记和轨迹
markers[selectedDevice].show();
polylines[selectedDevice].show();
// 调整视野以包含当前设备的标记和轨迹
adjustViewToDevice(selectedDevice);
}
});
// 动态添加设备选项
function addDeviceOption(deviceName) {
const option = document.createElement("option");
option.value = deviceName;
option.textContent = deviceName;
deviceList.appendChild(option);
}
// 调整视野以包含所有设备
function adjustViewToAllDevices() {
const allMarkers = Object.values(markers);
const allPolylines = Object.values(polylines);
if (allMarkers.length > 0 || allPolylines.length > 0) {
map.setFitView([...allMarkers, ...allPolylines]);
}
}
// 调整视野以包含单个设备
function adjustViewToDevice(deviceName) {
if (markers[deviceName] && polylines[deviceName]) {
map.setFitView([markers[deviceName], polylines[deviceName]]);
}
}
// 4. 添加标志位,控制是否自动调整视野
let shouldAutoAdjustView = true; // 默认自动调整视野
// 监听地图视野变化
map.on('movestart', function () {
shouldAutoAdjustView = false; // 用户手动调整视野时,停止自动调整
});
// 5. MQTT 客户端连接
const MQTT_BROKER = "wss://broker.emqx.io:8084/mqtt"; // 替换为 MQTT Broker 地址
const MQTT_TOPIC = "sunxuefei/gps/location"; // 替换为主题
const MQTT_LAST_LOCATION_TOPIC = "sunxuefei/gps/lastLocation"; // 新增:请求最后位置的Topic
const client = mqtt.connect(MQTT_BROKER);
client.on("connect", function () {
console.log("MQTT 已连接");
client.subscribe(MQTT_TOPIC, function (err) {
if (!err) {
console.log("订阅主题成功:", MQTT_TOPIC);
// 在连接成功后,立即请求最后位置
client.publish(MQTT_LAST_LOCATION_TOPIC, '{"name":"edge","msg":"request"}', function (err) {
if (!err) {
console.log("已发送请求最后位置的消息");
} else {
console.error("发送请求最后位置的消息失败:", err);
}
});
} else {
console.error("订阅主题失败:", err);
}
});
});
client.on("message", function (topic, message) {
console.log(`收到消息 [${topic}]:`, message.toString());
// 解析消息并更新地图位置
try {
const payload = JSON.parse(message.toString());
const deviceName = payload.name; // 设备名称
const rawLat = parseFloat(payload.lat); // WGS84 纬度
const rawLon = parseFloat(payload.lon); // WGS84 经度
// 进行 WGS84 到 GCJ02 转换
const { lat, lon } = wgs84ToGcj02(rawLat, rawLon);
if (!isNaN(lat) && !isNaN(lon)) {
const position = [lon, lat];
// 更新或创建标记
if (markers[deviceName]) {
markers[deviceName].setPosition(position);
} else {
const marker = new AMap.Marker({
position: position,
title: deviceName,
map: map,
});
markers[deviceName] = marker;
addDeviceOption(deviceName); // 添加设备选项
deviceList.value = "all"; // 切换到“显示所有设备”模式
}
// 更新轨迹
if (!paths[deviceName]) {
paths[deviceName] = [];
}
paths[deviceName].push(position);
// 绘制轨迹
if (paths[deviceName].length >= 2) {
if (polylines[deviceName]) {
polylines[deviceName].setPath(paths[deviceName]);
} else {
const polyline = new AMap.Polyline({
path: paths[deviceName],
strokeColor: colors[deviceName] || "#00FF00", // 默认绿色
strokeWeight: 3,
map: map,
});
polylines[deviceName] = polyline;
}
}
// 检查定位点是否在视野范围内
if (!isPointInView(map, position)) {
map.panTo(position); // 平移地图,保持缩放级别不变
}
// 根据当前模式调整视野
if (deviceList.value === "all") {
// 显示所有设备的标记和轨迹
Object.values(markers).forEach(marker => marker.show());
Object.values(polylines).forEach(polyline => polyline.show());
if (shouldAutoAdjustView) {
adjustViewToAllDevices(); // 仅在自动调整模式下调整视野
}
} else if (deviceList.value === deviceName) {
if (shouldAutoAdjustView) {
adjustViewToDevice(deviceName); // 仅在自动调整模式下调整视野
}
}
} else {
console.error("无效的经纬度数据");
}
} catch (e) {
console.error("消息解析错误:", e);
}
});
client.on("error", function (error) {
console.error("MQTT 连接错误:", error);
});
})
.catch((e) => {
console.error("高德地图加载失败:", e); // 加载错误提示
});
</script>
</body>
</html>