程序会在以下情况下平移地图:

当定位点不在当前视野范围内时,自动平移地图,确保定位点可见。

平移时保持缩放级别不变,避免干扰用户的操作体验。

<!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>

发表评论