/**
 * OpenLayers 适配器
 *
 * 作用：
 * 1. 封装 OpenLayers 地图库的具体实现
 * 2. 实现 MapAdapter 抽象接口，提供统一的地图操作能力
 * 3. 处理坐标转换、图层管理、底图切换等 OpenLayers 特有功能
 *
 * 特性：
 * - 支持多种底图：高德矢量/卫星、天地图矢量/影像
 * - 支持 WMS 标准协议图层
 * - 支持坐标投影转换（EPSG:4326 <-> EPSG:3857）
 * - 支持 GCJ-02（火星坐标系）投影
 * - 内置比例尺控件
 *
 * @class OLAdapter
 * @extends MapAdapter
 */

// OpenLayers 核心模块
import Map from 'ol/Map'
import View from 'ol/View'
import { DragBox } from 'ol/interaction'
import Draw, { createBox, createRegularPolygon } from 'ol/interaction/Draw'
import TileLayer from 'ol/layer/Tile'
import VectorLayer from 'ol/layer/Vector'
import 'ol/ol.css' // OpenLayers 样式文件
import VectorSource from 'ol/source/Vector'
import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
import Polygon from 'ol/geom/Polygon'
import LineString from 'ol/geom/LineString'
import { fromLonLat, transform, toLonLat } from 'ol/proj'
import Style from 'ol/style/Style'
import Icon from 'ol/style/Icon'
import Fill from 'ol/style/Fill'
import Stroke from 'ol/style/Stroke'
import CircleStyle from 'ol/style/Circle'
import Text from 'ol/style/Text'
import Overlay from 'ol/Overlay'
import { XYZ } from 'ol/source'
import TileWMS from 'ol/source/TileWMS'
import { ScaleLine } from 'ol/control'
import { getLength as getSphereLength, getArea as getSphereArea } from 'ol/sphere'
import Geolocation from 'ol/Geolocation'

// 项目模块
import MapAdapter from './MapAdapter'
import { wgs84ToGcj02Projection } from '@vue2-client/base-client/components/common/AfMap/map/utils/wgs84ToGcj02'
import { gcj02Mecator } from '@vue2-client/base-client/components/common/AfMap/map/utils/gcj02Mecator'

export default class OLAdapter extends MapAdapter {
  /**
   * 构造函数
   *
   * 初始化适配器状态，包括地图实例、图层管理和底图配置
   */
  constructor () {
    super()

    /**
     * OpenLayers 地图实例
     * @type {ol.Map|null}
     */
    this.map = null

    /**
     * 业务图层映射表
     * 以图层 ID 为 key，存储业务层创建的图层实例
     *
     * @type {Object<string, ol.layer.Layer>}
     */
    this.layers = {}

    /**
     * 组数据映射表
     * 以组 ID 为 key，存储组数据信息
     * 格式：{ id, name, type, visible, items: [] }
     *
     * @type {Object<string, object>}
     */
    this.groups = {}

    /**
     * 底图图层集合
     * 存储所有可用的底图图层（高德矢量、高德卫星、天地图矢量、天地图影像等）
     *
     * @type {Object<string, ol.layer.Tile>}
     */
    this.baseMaps = {}

    /**
     * 当前使用的底图类型
     * @type {string}
     */
    this.currentBase = 'gaode'

    /**
     * 框选放大交互实例
     * @type {ol.interaction.Draw|null}
     */
    this.zoomToBoxInteraction = null

    // 测距
    // 专用源/图层/交互，避免干扰业务图层
    this.measureSource = null
    this.measureLayer = null
    this.measureDraw = null
    this.measureResults = []
    this.measureResultCb = null
    this.measureOverlays = []
    this.currentViewMode = '2D' // OpenLayers 使用 2D，但保留状态以便统一接口

    /**
     * 框选交互
     */
    this.selectInteraction = null
    this.selectSource = null
    this.selectLayer = null

    /**
     * WMS点击查询相关
     */
    this.wmsClickCallback = null
    this.wmsClickEnabled = false
    this.wmsClickHandler = null

    /**
     * 组闪烁定时器
     */
    this.blinkTimers = {}

    /**
     * 组数据刷新定时器
     * 以组 ID 为 key，存储定时刷新标记点数据的定时器
     * @type {Object<string, number>}
     */
    this.refreshTimers = {}

    /**
     * 分析画线交互与图层
     */
    this.lineDrawInteraction = null
    this.lineDrawSource = null
    this.lineDrawLayer = null

    /**
     * 通用地图点击相关
     */
    this.mapClickCallback = null
    this.mapClickEnabled = false
    this.mapClickHandler = null

    /**
     * 组图层点击事件处理器映射
     * @type {Object<string, Function>}
     */
    this.layerClickHandlers = {}

    /**
     * WMS 鼠标样式监听器
     * @type {Function|null}
     */
    this.wmsCursorHandler = null

    /**
     * 信息窗口覆盖物
     * @type {ol.Overlay|null}
     */
    this.infoWindowOverlay = null

    /**
     * 信息窗口关闭点击处理器
     * @type {Function|null}
     */
    this._infoWindowCloseHandler = null

    /**
     * 轨迹播放状态对象
     * 所有轨迹播放相关的状态都集中在这里，便于管理和序列化
     */
    this.trajectoryState = {
      // 数据相关
      segments: [], // 轨迹段数据
      config: null, // 轨迹配置

      // 播放状态
      currentIndex: 0, // 当前播放段索引
      progress: 0, // 当前段进度 0-1
      status: 'idle', // 播放状态: 'idle' | 'playing' | 'paused' | 'finished'
      speed: 1, // 当前播放速度倍数

      // 内部状态（不对外暴露，但保留在 state 中便于管理）
      _distanceTraveled: 0, // 当前段已行驶距离
      _segmentLengths: [], // 当前段各线段长度
      _totalLength: 0, // 当前段总长度
      _coordinates: [], // 当前段坐标(3857)
      _lastTime: 0, // 上次动画时间

      // 地图元素引用（不序列化）
      _layers: {
        route: null, // 轨迹线图层
        traveled: null, // 已播放轨迹图层
        car: null, // 小车图层
        start: null, // 起点图层
        end: null // 终点图层
      },
      _carFeature: null, // 小车要素
      _animationFrameId: null, // 动画帧ID
      _traveledFeatures: [] // 已播放轨迹要素
    }
  }

  /**
   * 初始化 OpenLayers 地图实例
   *
   * @param {string|HTMLElement} container - 地图容器 DOM 或选择器
   * @param {object} options - 地图初始化配置
   * @param {Array<number>} [options.center=[116.397428, 39.90923]] - 中心点坐标 [经度, 纬度]（WGS84）
   * @param {number} [options.zoom=12] - 初始缩放级别
   * @param {Array<number>} [options.zooms=[4, 20]] - 缩放范围 [最小级别, 最大级别]
   * @param {number} [options.defaultMaxZoom=18] - 默认底图最大缩放级别
   * @param {number} [options.satelliteMaxZoom=18] - 卫星底图最大缩放级别
   * @param {string} [options.baseType] - 初始底图类型（可选）
   * @returns {Promise<ol.Map>} OpenLayers 地图实例
   */
  async initMap (container, options = {}) {
    // 处理容器参数：支持字符串选择器或 DOM 元素
    const target = typeof container === 'string'
      ? document.querySelector(container)
      : container

    // 参数处理，设置默认值
    const {
      center = [116.397428, 39.90923], // 默认北京天安门
      zoom = 12,
      zooms = [4, 20],
      defaultMaxZoom = 18,
      satelliteMaxZoom = 18
    } = options

    // 坐标转换：WGS84 经纬度 -> Web Mercator (EPSG:3857)
    // OpenLayers 默认使用 EPSG:3857 投影坐标系
    const initialCenter = transform(center, 'EPSG:4326', 'EPSG:3857')

    // 创建所有底图图层（高德矢量、高德卫星、天地图矢量、天地图影像等）
    // 所有底图预先创建，通过可见性控制切换
    this.baseMaps = this.createBaseMaps('EPSG:3857', options.TIANDITUKEY, defaultMaxZoom, satelliteMaxZoom)

    // 设置底图可见性：默认只显示高德矢量地图
    Object.keys(this.baseMaps).forEach(key => {
      const layer = this.baseMaps[key]
      layer.setVisible(key === 'gaode')
    })

    // 创建地图实例
    const mapLayers = [...Object.values(this.baseMaps)]
    this.map = new Map({
      target, // 地图容器
      layers: mapLayers, // 所有图层（包括底图）
      view: new View({
        center: initialCenter, // 地图中心点（EPSG:3857 坐标）
        zoom: zoom, // 初始缩放级别
        projection: 'EPSG:3857', // 投影坐标系
        maxZoom: zooms[1], // 最大缩放级别
        minZoom: zooms[0], // 最小缩放级别
      }),
      controls: [
        // 添加比例尺控件
        new ScaleLine({
          units: 'metric' // 使用公制单位（米、千米）
        })
      ],
    })

    // 如果调用方指定了初始底图类型，则切换到对应底图
    if (options.baseType) {
      this.switchBaseMap(options.baseType)
    }

    return this.map
  }

  /**
   * 初始化WMS点击查询事件监听
   */
  initWmsClickHandler () {
    if (!this.map) return

    // 移除旧的事件监听
    if (this.wmsClickHandler) {
      this.map.un('singleclick', this.wmsClickHandler)
    }

    // 创建新的事件监听
    this.wmsClickHandler = (evt) => {
      if (!this.wmsClickEnabled || !this.wmsClickCallback) return

      const coordinate = evt.coordinate
      // 将EPSG:3857坐标转换为WGS84经纬度
      const lonLat = toLonLat(coordinate, 'EPSG:3857')
      // 调用回调函数，传递坐标 [lng, lat]
      if (this.wmsClickCallback) {
        this.wmsClickCallback(lonLat)
      }
    }

    // 绑定点击事件
    this.map.on('singleclick', this.wmsClickHandler)
  }

  /**
   * 初始化 WMS 鼠标样式监听器
   * 当鼠标悬浮在 WMS 图层要素上时，将鼠标样式改为十字
   */
  initWmsCursorHandler () {
    if (!this.map) return

    // 如果已经初始化过，不再重复初始化
    if (this.wmsCursorHandler) return

    // 创建鼠标移动事件监听器
    this.wmsCursorHandler = (event) => {
      if (event.dragging) {
        return // 如果正在拖拽地图，则不改变鼠标样式
      }

      // 使用 getEventPixel 获取事件相对于地图容器的像素坐标
      const pixel = this.map.getEventPixel(event.originalEvent)
      let cursor = '' // 初始化鼠标样式为空字符串（默认样式）
      let foundWmsData = false // 标记是否找到了 WMS 图层的可见数据

      // 获取地图上的所有图层，并转换为数组
      const layers = this.map.getLayers().getArray()

      // 手动遍历图层数组
      for (const layer of layers) {
        // 检查图层是否是 TileLayer，是否标记为 'wms'，并且是可见的
        if (layer instanceof TileLayer && layer.get('type') === 'wms' && layer.getVisible()) {
          // 确保图层上有 getData 方法
          if (typeof layer.getData === 'function') {
            const data = layer.getData(pixel) // 获取当前像素的 RGBA 数据
            // 检查 data 是否有效 (非 null/undefined) 并且 alpha 通道 (data[3]) > 0
            if (data && data[3] > 0) {
              foundWmsData = true // 找到了非透明的 WMS 数据
              break // 找到后就跳出循环，不再检查其他图层
            }
          } else {
            // 如果 WMS 图层没有 getData 方法，可能存在问题，打印警告
            console.warn('当前 WMS 图层不支持 getData(pixel) 方法:', layer)
          }
        }
      }

      if (foundWmsData) {
        // 如果找到了 WMS 图层的可见数据，设置鼠标样式为十字准星
        cursor = 'crosshair'
      } else {
        // 如果没有在当前像素找到 WMS 数据，再检查是否有其他矢量要素
        const featureHit = this.map.hasFeatureAtPixel(pixel, {
          hitTolerance: 5, // 为矢量要素设置一些像素容差
          // 图层过滤器：只检查那些 *不是* WMS TileLayer 的图层
          layerFilter: (layerToCheck) => {
            return !(layerToCheck instanceof TileLayer && layerToCheck.get('type') === 'wms')
          }
        })
        // 如果命中了矢量要素，设置鼠标样式为指针
        cursor = featureHit ? 'pointer' : ''
      }

      // 更新地图容器元素的鼠标样式
      this.map.getTargetElement().style.cursor = cursor
    }

    // 绑定鼠标移动事件
    this.map.on('pointermove', this.wmsCursorHandler)
  }

  /**
   * 设置WMS点击查询回调
   * @param {Function} callback - 回调函数，参数为 (coordinate: Array<number>)
   */
  setWmsClickCallback (callback) {
    this.wmsClickCallback = callback
  }

  /**
   * 设置WMS点击查询启用状态
   * @param {boolean} enabled
   */
  setWmsClickEnabled (enabled) {
    this.wmsClickEnabled = enabled
  }

  /**
   * 设置WMS鼠标悬停样式启用状态
   * 注意：OpenLayers 暂不支持此功能，此方法为空实现
   * @param {boolean} enabled
   */
  setWmsHoverEnabled (enabled) {
    // OpenLayers 暂不支持此功能
    // 如果需要实现，可以通过监听地图的 pointermove 事件来实现
  }

  /**
   * 设置通用地图点击回调
   */
  setMapClickCallback (callback) {
    this.mapClickCallback = callback
    if (!this.mapClickHandler && this.map) {
      this.initMapClickHandler()
    }
  }

  /**
   * 设置通用地图点击启用状态
   */
  setMapClickEnabled (enabled) {
    this.mapClickEnabled = enabled
  }

  /**
   * 初始化通用地图点击监听
   */
  initMapClickHandler () {
    if (!this.map) return
    if (this.mapClickHandler) {
      this.map.un('singleclick', this.mapClickHandler)
    }
    this.mapClickHandler = (evt) => {
      if (!this.mapClickEnabled || !this.mapClickCallback) return
      const lonLat = transform(evt.coordinate, 'EPSG:3857', 'EPSG:4326')
      this.mapClickCallback(lonLat)
    }
    this.map.on('singleclick', this.mapClickHandler)
  }

  /**
   * 获取WMS图层的GetFeatureInfo URL
   * @param {string} layerId - 图层ID
   * @param {Array<number>} coordinate - 点击坐标 [lng, lat] (WGS84)
   * @returns {string|null} GetFeatureInfo URL，如果图层不存在或不可见则返回null
   */
  getWmsFeatureInfoUrl (layerId, coordinate) {
    if (!this.map || !coordinate || coordinate.length < 2) {
      return null
    }

    const layer = this.layers[layerId]
    if (!layer || layer.getVisible() !== true) {
      return null
    }

    const source = layer.getSource()
    if (!source || !(source instanceof TileWMS)) {
      return null
    }

    try {
      const view = this.map.getView()
      const viewResolution = view.getResolution()
      const projection = view.getProjection()

      // 将WGS84坐标转换为地图投影坐标
      const mapCoordinate = fromLonLat(coordinate, projection)

      // 获取GetFeatureInfo URL
      const url = source.getFeatureInfoUrl(
        mapCoordinate,
        viewResolution,
        projection,
        {
          INFO_FORMAT: 'application/json',
          REQUEST: 'GetFeatureInfo' // 指定获取功能信息
        }
      )

      return url || null
    } catch (error) {
      console.error(`获取 WMS 功能信息 URL 时出现错误，该错误与特定图层有关。 ${layerId}:`, error)
      return null
    }
  }

  bindViewChange (callback) {
    this.viewChangeCb = callback
    if (!this.map) return
    this.map.on('moveend', () => {
      const state = this.getMapState()
      if (state && state.center && this.viewChangeCb) {
        this.viewChangeCb(state.center)
      }
    })
  }

  /**
   * 批量添加标记点
   *
   * @param {Function} getDataFn - 获取标记点数据的函数，返回 Promise<Array<object>>
   * @param {object} options - 组配置选项
   * @param {string} options.groupId - 组数据唯一标识（必填）
   * @param {string} options.groupName - 组数据名称（必填）
   * @param {boolean} [options.visible=true] - 默认是否显示，默认 true
   * @returns {Promise<Array<ol.layer.Vector>>} 标记点图层实例数组
   */
  /**
   * 批量添加标记点，并支持组级点击回调
   * @param {Function} getDataFn 获取标记点数据
   * @param {object} options 组配置
   * @param {boolean} [options.isAutoRefresh=false] 是否定时刷新，默认 false
   * @param {number} [options.refreshInterval=10] 定时间隔（秒），默认 10 秒
   * @param {boolean} [options.showLabel=false] 是否显示文本标签，默认 false
   * @param {string} [options.labelPosition='bottom'] 标签位置，默认 'bottom'，可选值：'top'|'right'|'bottom'|'left'|'center'
   * @param {Function} [onClick] 点位点击回调 (payload) => void
   */
  async addMarker (getDataFn, options = {}, onClick) {
    if (!this.map) throw new Error('Map not initialized')

    const {
      groupId,
      groupName,
      visible = true,
      isAutoRefresh = false,
      refreshInterval = 10,
      showLabel = false,
      labelPosition = 'bottom'
    } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    // 如果组已存在，先清理旧的图层和定时器
    if (this.groups[groupId] && this.layers[groupId]) {
      this.stopMarkerRefresh(groupId)
      const oldLayer = this.layers[groupId]
      this.map.removeLayer(oldLayer)
      if (oldLayer.getSource) {
        const source = oldLayer.getSource()
        if (source && source.dispose) {
          source.dispose()
        }
      }
      delete this.layers[groupId]
      // 解绑旧的组点击事件
      if (this.layerClickHandlers[groupId]) {
        this.map.un('singleclick', this.layerClickHandlers[groupId])
        delete this.layerClickHandlers[groupId]
      }
    }

    // 调用数据获取函数获取最新数据
    const markerOptionsList = await getDataFn()

    const { features, items } = this.buildMarkerFeatures(markerOptionsList, { showLabel, labelPosition })

    // 性能优化：将同一组的所有标记点放在同一个 VectorLayer 中
    // 这样只需要创建一个图层，而不是每个标记点一个图层
    const layer = new VectorLayer({
      source: new VectorSource({
        features: features, // 一次性添加所有要素
      }),
      visible: visible // 设置初始可见性
    })

    // 添加到地图（只添加一次）
    this.map.addLayer(layer)

    // 记录组图层到映射表（使用 groupId 作为 key）
    this.layers[groupId] = layer

    // 创建或更新组数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'marker',
      groupType: options.groupType || '点位',
      visible: visible,
      items: items,
      layer: layer, // 保存图层引用
      getDataFn: getDataFn, // 保存数据获取函数，用于后续更新
      onClick: onClick, // 保存点击事件回调
      isAutoRefresh: isAutoRefresh, // 是否定时刷新
      refreshInterval: refreshInterval, // 刷新间隔（秒）
      showLabel: showLabel, // 是否显示标签
      labelPosition: labelPosition // 标签位置
    }

    // 绑定组级点击事件：仅触发该组图层中的要素
    if (typeof onClick === 'function') {
      const handler = (evt) => {
        const feat = this.map.forEachFeatureAtPixel(
          evt.pixel,
          (f) => f,
          { layerFilter: (l) => l === layer }
        )
        if (feat) {
          const id = feat.get('markerId')
          const name = feat.get('markerName')
          const coordinate3857 = feat.getGeometry().getCoordinates()
          const coordinate = toLonLat(coordinate3857, 'EPSG:3857')
          try {
            onClick({ id, name, coordinate, feature: feat, event: evt })
          } catch (e) {}
        }
      }
      this.map.on('singleclick', handler)
      this.layerClickHandlers[groupId] = handler
    }

    // 如果启用定时刷新且图层可见，启动定时器
    if (isAutoRefresh && visible) {
      this.startMarkerRefresh(groupId)
    }

    return [layer] // 返回图层数组（只有一个图层）
  }

  /**
   * 添加热力图
   *
   * @param {Function} getDataFn 获取热力图数据的函数，返回 Promise<Array<object>>
   *  返回的数据格式：[{ lng, lat, count }]
   * @param {object} options 组配置选项
   * @param {string} options.groupId 组数据唯一标识（必填）
   * @param {string} options.groupName 组数据名称（必填）
   * @param {boolean} [options.visible=true] 默认是否显示，默认 true
   * @returns {Promise<ol.layer.Heatmap>} 热力图图层实例
   */
  async addHeatmap (getDataFn, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    const { groupId, groupName, visible = true } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    // 如果组已存在，先清理旧的热力图
    if (this.groups[groupId] && this.layers[groupId]) {
      const oldLayer = this.layers[groupId]
      this.map.removeLayer(oldLayer)
      delete this.layers[groupId]
    }

    // 调用数据获取函数获取最新数据
    const heatmapData = await getDataFn()

    // OpenLayers 使用 VectorLayer 配合热力图样式
    // 创建要素数组
    const features = heatmapData.map(item => {
      const point = new Point(fromLonLat([item.lng, item.lat], 'EPSG:3857'))
      const feature = new Feature({
        geometry: point,
        weight: item.count || 1
      })
      return feature
    })

    // 创建热力图图层（使用 VectorLayer，通过样式实现热力图效果）
    // 注意：OpenLayers 没有内置热力图图层，这里使用点图层配合样式模拟
    const heatmapLayer = new VectorLayer({
      source: new VectorSource({
        features: features
      }),
      style: (feature) => {
        const weight = feature.get('weight') || 1
        const radius = Math.min(20, 5 + weight * 2) // 根据权重调整半径
        const opacity = Math.min(0.8, 0.3 + weight * 0.1) // 根据权重调整透明度

        return new Style({
          image: new CircleStyle({
            radius: radius,
            fill: new Fill({
              color: `rgba(255, 77, 79, ${opacity})` // 红色渐变
            }),
            stroke: new Stroke({
              color: 'rgba(255, 77, 79, 0.6)',
              width: 1
            })
          })
        })
      },
      visible: visible
    })

    // 添加到地图
    this.map.addLayer(heatmapLayer)

    // 保存到图层映射
    this.layers[groupId] = heatmapLayer

    // 创建或更新组数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'heatmap',
      visible: visible,
      groupType: options.groupType || '热力图',
      layer: heatmapLayer,
      getDataFn: getDataFn // 保存数据获取函数，用于后续更新
    }

    return heatmapLayer
  }

  /**
   * 批量绘制多边形
   *
   * @param {Function} getDataFn - 获取多边形数据的函数，返回 Promise<Array<object>>
   * @param {object} options - 组配置选项
   * @param {string} options.groupId - 组数据唯一标识（必填）
   * @param {string} options.groupName - 组数据名称（必填）
   * @param {boolean} [options.visible=true] - 默认是否显示，默认 true
   * @returns {Promise<Array<ol.layer.Vector>>} 多边形图层实例数组
   */
  async drawPolygon (getDataFn, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    const { groupId, groupName, visible = true } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    // 调用数据获取函数获取最新数据
    const polygonOptionsList = await getDataFn()

    // 如果组已存在，先清理旧的图层
    if (this.groups[groupId] && this.layers[groupId]) {
      const oldLayer = this.layers[groupId]
      this.map.removeLayer(oldLayer)
      if (oldLayer.getSource) {
        const source = oldLayer.getSource()
        if (source && source.dispose) {
          source.dispose()
        }
      }
      delete this.layers[groupId]
    }

    const { features, items } = this.buildPolygonFeatures(polygonOptionsList)

    // 性能优化：将同一组的所有多边形放在同一个 VectorLayer 中
    // 这样只需要创建一个图层，而不是每个多边形一个图层
    const layer = new VectorLayer({
      source: new VectorSource({
        features: features, // 一次性添加所有要素
      }),
      visible: visible // 设置初始可见性
    })

    // 添加到地图（只添加一次）
    this.map.addLayer(layer)

    // 记录组图层到映射表（使用 groupId 作为 key）
    this.layers[groupId] = layer

    // 创建或更新组数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'polygon',
      groupType: options.groupType || '片区',
      visible: visible,
      items: items,
      layer: layer, // 保存图层引用
      getDataFn: getDataFn // 保存数据获取函数，用于后续更新
    }

    return [layer] // 返回图层数组（只有一个图层）
  }

  /**
   * 批量绘制折线
   * @param {Function} getDataFn 获取折线数据
   * @param {object} options 组配置
   */
  async addPolyline (getDataFn, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    const { groupId, groupName, visible = true } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    const polylineOptions = await getDataFn()

    // 如果组已存在，移除旧图层
    if (this.groups[groupId] && this.layers[groupId]) {
      const oldLayer = this.layers[groupId]
      this.map.removeLayer(oldLayer)
      if (oldLayer.getSource) {
        const source = oldLayer.getSource()
        if (source && source.dispose) {
          source.dispose()
        }
      }
      delete this.layers[groupId]
    }

    const { features, items } = this.buildPolylineFeatures(polylineOptions)

    const layer = new VectorLayer({
      source: new VectorSource({
        features
      }),
      visible
    })

    this.map.addLayer(layer)
    this.layers[groupId] = layer
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'polyline',
      groupType: options.groupType || '线',
      visible,
      items,
      layer,
      getDataFn
    }
    return [layer]
  }

  /**
   * 添加单个 WMS 图层
   *
   * OpenLayers 原生支持 WMS 标准协议，可以直接加载 WMS 服务
   *
   * @param {object} layerOptions - WMS 图层配置对象
   * @param {string} layerOptions.id - 图层唯一标识（必填）
   * @param {string} layerOptions.name - 图层名称（必填）
   * @param {string} layerOptions.url - WMS 服务地址
   * @param {object} layerOptions.params - WMS 请求参数，如 { LAYERS: 'layer_name', VERSION: '1.1.0' }
   * @param {boolean} [layerOptions.visible=true] - 初始显隐
   * @param {string} groupId - 组数据唯一标识（必填）
   * @param {string} groupName - 组数据名称（必填）
   * @param {string} [groupType='wms'] - 组类型标识
   * @returns {Promise<TileLayer>} WMS 图层实例数组（单元素）
   */
  async addWmsLayer (layerOptions, groupId, groupName, groupType = 'wms') {
    if (!this.map) throw new Error('Map not initialized')
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }
    if (!layerOptions || !layerOptions.id || !layerOptions.name) {
      throw new Error('layerOptions.id and layerOptions.name are required')
    }

    // 如果已有同 ID 的图层，先移除再更新
    const exist = this.layers[layerOptions.id]
    if (exist) {
      this.map.removeLayer(exist)
      if (exist.getSource) {
        const source = exist.getSource()
        if (source && source.dispose) {
          source.dispose()
        }
      }
      delete this.layers[layerOptions.id]
    }

    // ✅ 工程化：版本号由外部注入（回调）或兜底使用时间戳
    let layerVersion
    try {
      layerVersion = typeof layerOptions.getLayerVersion === 'function'
        ? await layerOptions.getLayerVersion(layerOptions.id)
        : Date.now()
    } catch (e) {
      layerVersion = Date.now()
    }

    const params = {
      ...(layerOptions.params || {}),
      LAYERVERSION: String(layerVersion ?? Date.now())
    }

    // 创建 WMS 图层，使用瓦片模式以提升交互体验
    const layer = new TileLayer({
      source: new TileWMS({
        url: layerOptions.url,
        params,
      }),
      visible: layerOptions.visible
    })

    // 标记为 WMS 图层，用于鼠标样式检测
    layer.set('type', 'wms')

    // 添加到地图并记录
    this.map.addLayer(layer)
    this.layers[layerOptions.id] = layer

    // 初始化 WMS 鼠标样式监听（如果还没有初始化）
    this.initWmsCursorHandler()

    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'wms',
      groupType: groupType || 'wms',
      visible: layerOptions.visible === true,
      items: [],
      // 保存WMS配置信息，便于刷新/查询
      wmsConfig: {
        url: layerOptions.url,
        params: params,
        // 缓存版本号回调，供 refreshWmsLayers 直接调用（无需额外传参）
        getLayerVersion: layerOptions.getLayerVersion
      }
    }
    return layer
  }

  /**
   * 刷新当前显示的WMS图层（强制刷新缓存）
   * 只更新时间戳参数，不移除图层
   */
  async refreshWmsLayers (key = null) {
    try {
      let groupsByWms = this.getGroupsByType('wms')
      if (key) {
        groupsByWms = groupsByWms.filter(g => key.includes(g.id))
      }
      for (const group of groupsByWms) {
        if (group.visible) {
          const layer = this.layers[group.id]
          const source = layer.getSource()
          let layerVersion
          try {
            layerVersion = typeof group.wmsConfig?.getLayerVersion === 'function'
              ? await group.wmsConfig.getLayerVersion(group.id)
              : Date.now()
          } catch (e) {
            layerVersion = Date.now()
          }
          source.updateParams({
            ...(group.wmsConfig?.params || {}),
            LAYERVERSION: String(layerVersion ?? Date.now())
          })
        }
      }
    } catch (e) {
      console.error('>>>>刷新当前显示的WMS图层', e)
    }
  }

  /**
   * 清理图层
   *
   * @param {string} [layerId] - 要清理的图层 ID；不传则清理所有业务图层
   * @returns {Promise<void>}
   */
  async clearLayer (layerId) {
    if (!this.map) return

    // 如果指定了图层 ID，只清理该图层
    if (layerId) {
      const target = this.layers[layerId]
      if (target) {
        this.map.removeLayer(target) // 从地图移除

        // 清理图层资源
        if (target.getSource) {
          const source = target.getSource()
          if (source && source.dispose) {
            source.dispose()
          }
        }

        delete this.layers[layerId] // 从映射表删除

        // 同时从组数据中删除
        if (this.groups[layerId]) {
          delete this.groups[layerId]
        }
        // 同时解绑组点击事件
        if (this.layerClickHandlers[layerId]) {
          this.map.un('singleclick', this.layerClickHandlers[layerId])
          delete this.layerClickHandlers[layerId]
        }
      }
      return
    }

    // 未指定 ID 时，清理所有业务图层（不包括底图）
    Object.values(this.layers).forEach((layer) => {
      this.map.removeLayer(layer)

      // 清理图层资源
      if (layer.getSource) {
        const source = layer.getSource()
        if (source && source.dispose) {
          source.dispose()
        }
      }
    })
    this.layers = {} // 清空映射表
    this.groups = {} // 清空组数据
    // 解绑所有组点击事件
    Object.keys(this.layerClickHandlers).forEach((gid) => {
      const h = this.layerClickHandlers[gid]
      if (h) {
        try { this.map.un('singleclick', h) } catch (e) {}
      }
    })
    this.layerClickHandlers = {}
  }

  /**
   * 切换底图类型
   *
   * OpenLayers 支持多种底图：
   * - 'gaode' / 'gaodeVec': 高德矢量地图
   * - 'gaodeSat': 高德卫星地图 + 标注
   * - 'tdtVec': 天地图矢量地图 + 标注
   * - 'tdtImg': 天地图影像地图 + 标注
   *
   * 实现方式：通过控制底图图层的可见性实现切换
   * 所有底图在初始化时已创建，切换时只需显示/隐藏对应图层
   *
   * @param {string} mapType - 底图类型，默认 'gaode'
   * @returns {void}
   */
  switchBaseMap (mapType = 'gaode') {
    if (!this.map) return

    // 更新当前底图类型
    this.currentBase = mapType

    // 先隐藏所有基础图层
    Object.values(this.baseMaps).forEach((layer) => {
      layer.setVisible(false)
    })

    // 根据类型显示对应的底图组合
    switch (mapType) {
      case 'gaode':
        // 高德矢量地图
        this.baseMaps.gaode.setVisible(true)
        break

      case 'gaodeSat':
        // 高德卫星地图 + 标注图层
        this.baseMaps.gaodeSatellite.setVisible(true) // 卫星影像
        this.baseMaps.gaodelabelLayer.setVisible(true) // 标注图层
        break

      case 'tdtVec':
        // 天地图矢量地图 + 标注图层
        this.baseMaps.tianditu.setVisible(true) // 矢量底图
        this.baseMaps.tianditulabel.setVisible(true) // 标注图层
        break

      case 'tdtImg':
        // 天地图影像地图 + 标注图层
        this.baseMaps.tianditusatellite.setVisible(true) // 卫星影像
        this.baseMaps.tianditusatlabel.setVisible(true) // 标注图层
        break

      default:
        // 默认使用高德矢量地图
        this.baseMaps.gaode.setVisible(true)
        break
    }
  }

  /**
   * 设置组数据显隐状态
   *
   * @param {string} groupId - 组数据唯一标识
   * @param {boolean} visible - 是否显示
   * @returns {Promise<void>}
   */
  async setGroupVisible (groupId, visible) {
    if (!this.map) return

    const group = this.groups[groupId]
    if (!group) {
      console.warn(`Group ${groupId} not found`)
      return
    }
    if (group.type === 'wms') {
      // 性能优化：直接控制组图层显隐（同一组数据共享一个图层）

      const layer = this.layers[groupId]
      if (layer) {
        // OpenLayers 通过 setVisible 方法控制显隐
        layer.setVisible(visible)
      }
      group.visible = visible
      return
    }

    // 标记点类型：控制定时刷新
    if (group.type === 'marker') {
      if (visible) {
        // 显示时：先停止定时器，获取最新数据并更新，再启动定时器
        this.stopMarkerRefresh(groupId)
        if (group.getDataFn) {
          await this.refreshMarkerData(groupId)
        }
        // 如果启用定时刷新，启动定时器
        if (group.isAutoRefresh) {
          this.startMarkerRefresh(groupId)
        }
      } else {
        // 隐藏时：停止定时刷新
        this.stopMarkerRefresh(groupId)
      }
      // 更新显隐状态
      group.visible = visible
      const layer = this.layers[groupId]
      if (layer && layer.setVisible) {
        layer.setVisible(visible)
      }
      return
    }

    // 如果设置为显示，且存在数据获取函数，则重新获取最新数据并更新
    if (visible && group.getDataFn) {
      // 只刷新数据，不重新创建图层，避免闪烁
      const layer = this.layers[groupId]
      const source = layer ? layer.getSource() : null

      // 兜底：如果之前没有图层（理论上不会），退回到重建逻辑
      if (!layer || !source) {
        if (group.type === 'polygon') {
          await this.drawPolygon(group.getDataFn, {
            groupId: group.id,
            groupName: group.name,
            visible: true
          })
        }
        return
      }

      // 重新获取数据并更新已有图层数据
      const data = await group.getDataFn()
      const { features, items } = group.type === 'polygon'
        ? this.buildPolygonFeatures(data)
        : this.buildPolylineFeatures(data)

      // 用新数据刷新源，保持同一个图层
      source.clear()
      if (features.length) {
        source.addFeatures(features)
      }

      // 更新组内缓存
      group.items = items
      group.visible = true
      layer.setVisible(true)
      return
    }

    // 更新组数据显隐状态
    group.visible = visible
    const layer = this.layers[groupId]
    if (layer && layer.setVisible) {
      layer.setVisible(visible)
    }
  }

  /**
   * 启动标记点定时刷新
   * @param {string} groupId - 组 ID
   * @private
   */
  startMarkerRefresh (groupId) {
    const group = this.groups[groupId]
    if (!group || !group.isAutoRefresh || !group.getDataFn) {
      return
    }

    // 如果已有定时器，先清除
    this.stopMarkerRefresh(groupId)

    const interval = (group.refreshInterval || 10) * 1000 // 转换为毫秒
    const timer = setInterval(async () => {
      // 只有在图层可见时才刷新
      if (group.visible) {
        await this.refreshMarkerData(groupId)
      }
    }, interval)

    this.refreshTimers[groupId] = timer
  }

  /**
   * 停止标记点定时刷新
   * @param {string} groupId - 组 ID
   * @private
   */
  stopMarkerRefresh (groupId) {
    const timer = this.refreshTimers[groupId]
    if (timer) {
      clearInterval(timer)
      delete this.refreshTimers[groupId]
    }
  }

  /**
   * 刷新标记点数据
   * @param {string} groupId - 组 ID
   * @private
   */
  async refreshMarkerData (groupId) {
    const group = this.groups[groupId]
    if (!group || !group.getDataFn || !this.map) {
      return
    }

    try {
      // 获取最新数据
      const markerOptionsList = await group.getDataFn()

      // 重新构建要素
      const { features, items } = this.buildMarkerFeatures(markerOptionsList, {
        showLabel: group.showLabel || false,
        labelPosition: group.labelPosition || 'bottom'
      })

      // 更新图层数据源
      const layer = this.layers[groupId]
      if (layer && layer.getSource) {
        const source = layer.getSource()
        source.clear()
        if (features.length) {
          source.addFeatures(features)
        }
      }

      // 更新组数据
      group.items = items
    } catch (error) {
      console.error(`刷新标记点数据失败，组 ID: ${groupId}`, error)
    }
  }

  /**
   * 启动组闪烁，通过显隐切换
   */
  startGroupBlink (groupId, interval = 600) {
    const layer = this.layers[groupId]
    if (!layer) return
    this.stopGroupBlink(groupId)
    let visible = true
    this.blinkTimers[groupId] = setInterval(() => {
      visible = !visible
      if (layer.setVisible) {
        layer.setVisible(visible)
      }
    }, interval)
  }

  /**
   * 停止组闪烁并恢复显示
   */
  stopGroupBlink (groupId) {
    const timer = this.blinkTimers[groupId]
    if (timer) {
      clearInterval(timer)
      delete this.blinkTimers[groupId]
    }
    const layer = this.layers[groupId]
    if (layer && layer.setVisible) {
      layer.setVisible(true)
    }
  }

  /**
   * 获取所有组数据信息
   *
   * @returns {Promise<Array<Object>>} 组数据信息数组
   */
  getGroups () {
    const groups = []
    Object.values(this.groups).forEach((group) => {
      groups.push({
        id: group.id,
        name: group.name,
        type: group.type,
        groupType: group.groupType,
        visible: group.visible,
        count: group.items.length
      })
    })
    return groups
  }

  /**
   * 根据 ID 获取组数据信息
   *
   * @param {string} groupId - 组数据唯一标识
   * @returns {Object|null} 组数据信息对象，如果不存在则返回 null
   */
  getGroupById (groupId) {
    return this.groups[groupId] || null
  }

  /**
   * 根据 type 获取图层组，如果不传 type 则返回所有组
   * @param {string} [type] - 可选，要过滤的图层类型 (如 'marker', 'polygon')
   * @returns {Array<Object>} 过滤后的图层组数组
   */
  getGroupsByType (type) {
    // 先获取所有组的统一格式数据
    const allGroups = this.getGroups()

    // 如果传入了 type 参数，则进行过滤
    if (type) {
      return allGroups.filter(group => group.type === type)
    }

    // 如果没有传参数，直接返回所有组
    return allGroups
  }

  /**
   * 将图层组按 groupType 分类，或获取指定分类的图层组
   * @param {string} [specificGroupType] - 可选，指定要获取的 groupType
   * @returns {Array<Object>|Array<{groupType: string, groups: Array<Object>}>}
   *          如果传入了 specificGroupType，返回该分类的组数组；
   *          否则，返回所有分类的对象数组。
   */
  getGroupsByGroupType (specificGroupType) {
    // 先获取一维数组
    const allGroups = this.getGroups()

    // 使用 reduce 进行分组
    const grouped = allGroups.reduce((accumulator, currentGroup) => {
      const existingCategory = accumulator.find(item => item.groupType === currentGroup.groupType)

      if (existingCategory) {
        existingCategory.groups.push(currentGroup)
      } else {
        accumulator.push({
          groupType: currentGroup.groupType,
          groups: [currentGroup]
        })
      }

      return accumulator
    }, [])

    // 如果传入了 specificGroupType 参数
    if (specificGroupType) {
      // 在分组结果中查找对应的分类
      const foundCategory = grouped.find(item => item.groupType === specificGroupType)
      // 如果找到了，返回它的 groups 数组；如果没找到，返回空数组
      return foundCategory ? foundCategory.groups : []
    }

    // 如果没有传参数，返回完整的分组结果
    return grouped
  }

  /**
   * 获取地图当前状态
   * 用于切换引擎时保存状态
   *
   * @returns {object} 地图状态对象
   */
  getMapState () {
    if (!this.map) return null

    const view = this.map.getView()
    const center = view.getCenter()
    const zoom = view.getZoom()

    // 将 EPSG:3857 坐标转换为 WGS84
    let centerLonLat = null
    if (center) {
      const [lon, lat] = transform(center, 'EPSG:3857', 'EPSG:4326')
      centerLonLat = [lon, lat]
    }

    return {
      center: centerLonLat,
      zoom: zoom || null,
      baseMapType: this.currentBase,
    }
  }

  /**
   * 销毁地图实例
   * 用于切换引擎时清理资源
   */
  destroy () {
    // 清理所有定时刷新器
    Object.keys(this.refreshTimers).forEach(groupId => {
      this.stopMarkerRefresh(groupId)
    })
    this.refreshTimers = {}

    if (this.map) {
      // 移除 WMS 鼠标样式监听器
      if (this.wmsCursorHandler) {
        this.map.un('pointermove', this.wmsCursorHandler)
        this.wmsCursorHandler = null
      }

      if (this.measureDraw) {
        this.map.removeInteraction(this.measureDraw)
        this.measureDraw = null
      }
      if (this.measureOverlays?.length) {
        this.measureOverlays.forEach(ov => this.map.removeOverlay(ov))
        this.measureOverlays = []
      }
      if (this.zoomToBoxInteraction) {
        this.map.removeInteraction(this.zoomToBoxInteraction)
        this.zoomToBoxInteraction = null
      }
      // 清理所有图层
      const layers = this.map.getLayers().getArray()
      layers.forEach(layer => {
        this.map.removeLayer(layer)
        if (layer.getSource) {
          const source = layer.getSource()
          if (source && source.dispose) {
            source.dispose()
          }
        }
      })

      // 设置 target 为 null 以解除 DOM 绑定
      this.map.setTarget(null)
      this.map = null
    }
    this.layers = {}
    this.groups = {}
    this.baseMaps = {}
    this.measureSource = null
    this.measureLayer = null
    this.measureResults = []
    this.measureResultCb = null
    this.measureOverlays = []
  }

  // ===== 私有工具 =====
  /**
   * 高德 anchor 转 OpenLayers anchor
   *
   * 高德：
   *  'top-left' | 'top-center' | 'top-right'
   *  'middle-left' | 'center' | 'middle-right'
   *  'bottom-left' | 'bottom-center' | 'bottom-right'
   *
   * OpenLayers：
   *  [x, y]，范围 0 ~ 1
   *
   * @param {string} gdAnchor 高德 anchor 字符串
   * @returns {[number, number]} OpenLayers anchor 数组
   */
  convertGdAnchorToOlAnchor (gdAnchor) {
    const map = {
      'top-left': [0, 0],
      'top-center': [0.5, 0],
      'top-right': [1, 0],

      'middle-left': [0, 0.5],
      center: [0.5, 0.5],
      'middle-right': [1, 0.5],

      'bottom-left': [0, 1],
      'bottom-center': [0.5, 1],
      'bottom-right': [1, 1]
    }

    // 兜底：防止传入非法值
    return map[gdAnchor] || [0.5, 1]
  }

  buildMarkerFeatures (markerOptionsList, labelOptions = {}) {
    const features = []
    const items = []
    const { showLabel = false, labelPosition = 'bottom' } = labelOptions

    for (const markerOptions of markerOptionsList) {
      if (!markerOptions.id || !markerOptions.name) {
        throw new Error('Each marker must have id and name')
      }

      const feature = new Feature({
        geometry: new Point(fromLonLat([markerOptions.lng, markerOptions.lat])),
      })

      feature.set('markerId', markerOptions.id)
      feature.set('markerName', markerOptions.name)

      // 构建样式
      const styles = [
        new Style({
          image: new Icon({
            src: markerOptions.icon,
            scale: 0.2,
            anchor: this.convertGdAnchorToOlAnchor(markerOptions.anchor || 'bottom-center'),
            anchorXUnits: 'fraction',
            anchorYUnits: 'fraction'
          }),
        })
      ]

      // 如果需要显示标签，添加文本样式
      if (showLabel && markerOptions.name) {
        // 根据位置计算偏移量
        const offsetMap = {
          top: [0, -20],
          right: [15, 0],
          bottom: [0, 20],
          left: [-15, 0],
          center: [0, 0]
        }
        const offset = offsetMap[labelPosition] || offsetMap.bottom

        // 计算文本对齐方式
        const textAlignMap = {
          top: 'center',
          right: 'left',
          bottom: 'center',
          left: 'right',
          center: 'center'
        }
        const textAlign = textAlignMap[labelPosition] || 'center'

        const textBaselineMap = {
          top: 'bottom',
          right: 'middle',
          bottom: 'top',
          left: 'middle',
          center: 'middle'
        }
        const textBaseline = textBaselineMap[labelPosition] || 'top'

        styles.push(
          new Style({
            text: new Text({
              text: markerOptions.name,
              offsetY: offset[1],
              offsetX: offset[0],
              textAlign: textAlign,
              textBaseline: textBaseline,
              font: '12px sans-serif',
              fill: new Fill({
                color: '#333'
              }),
              stroke: new Stroke({
                color: '#fff',
                width: 3
              })
            })
          })
        )
      }

      feature.setStyle(styles)

      features.push(feature)
      items.push({
        id: markerOptions.id,
        name: markerOptions.name,
        feature: feature
      })
    }

    return { features, items }
  }

  buildPolygonFeatures (polygonOptionsList) {
    const features = []
    const items = []

    for (const polygonOptions of polygonOptionsList) {
      if (!polygonOptions.id || !polygonOptions.name) {
        throw new Error('Each polygon must have id and name')
      }

      const olPoints = polygonOptions.points.map((item) => fromLonLat(item))

      const polygon = new Feature({
        geometry: new Polygon([olPoints]),
      })

      polygon.set('polygonId', polygonOptions.id)
      polygon.set('polygonName', polygonOptions.name)

      polygon.setStyle(
        new Style({
          stroke: new Stroke({
            color: polygonOptions.strokeColor || '#1890ff',
            width: polygonOptions.strokeWidth || 2,
          }),
          fill: new Fill({
            color: polygonOptions.fillColor || 'rgba(24,144,255,0.3)',
          }),
        }),
      )

      features.push(polygon)
      items.push({
        id: polygonOptions.id,
        name: polygonOptions.name,
        feature: polygon
      })
    }

    return { features, items }
  }

  buildPolylineFeatures (polylineOptionsList) {
    const features = []
    const items = []

    for (const lineOptions of polylineOptionsList) {
      if (!lineOptions.id || !lineOptions.name || !Array.isArray(lineOptions.points)) {
        throw new Error('Each polyline must have id, name and points')
      }

      const olPoints = lineOptions.points.map((item) => fromLonLat(item))
      const line = new Feature({
        geometry: new LineString(olPoints)
      })

      line.set('lineId', lineOptions.id)
      line.set('lineName', lineOptions.name)

      line.setStyle(
        new Style({
          stroke: new Stroke({
            color: lineOptions.strokeColor || '#ff0000',
            width: lineOptions.strokeWidth || 4
          })
        })
      )

      features.push(line)
      items.push({
        id: lineOptions.id,
        name: lineOptions.name,
        feature: line
      })
    }

    return { features, items }
  }

  /**
   * 创建基础底图图层集合
   *
   * 预先创建所有可用的底图图层，包括：
   * - 高德矢量地图
   * - 高德卫星地图 + 标注
   * - 天地图矢量地图 + 标注
   * - 天地图影像地图 + 标注
   *
   * 注意：
   * - 天地图使用 WGS84 转 GCJ-02 投影，以匹配高德地图坐标系
   * - 高德地图使用 EPSG:3857 投影
   * - 所有底图默认隐藏，通过 switchBaseMap 控制显示
   *
   * @param {string} projection - 投影坐标系标识
   * @param TIANDITUKEY
   * @param {number} defaultMaxZoom - 默认底图最大缩放级别
   * @param {number} satelliteMaxZoom - 卫星底图最大缩放级别
   * @returns {Object<string, ol.layer.Tile>} 底图图层对象集合
   * @private
   */
  createBaseMaps (projection, TIANDITUKEY, defaultMaxZoom = 18, satelliteMaxZoom = 18) {
    // 处理投影坐标系
    let projectionInstance = projection
    if (projection === 'GCJ-02') {
      projectionInstance = gcj02Mecator
    }

    return {
      // 高德矢量地图
      gaode: new TileLayer({
        source: new XYZ({
          url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
          projection: projectionInstance,
          maxZoom: defaultMaxZoom
        }),
        properties: { isBaseMap: true } // 标识为底图
      }),

      // 高德卫星影像图层
      gaodeSatellite: new TileLayer({
        source: new XYZ({
          url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
          projection: projectionInstance,
          maxZoom: satelliteMaxZoom
        }),
        properties: { isBaseMap: true }
      }),

      // 高德卫星标注图层（用于叠加在卫星图上）
      gaodelabelLayer: new TileLayer({
        visible: false, // 默认隐藏
        source: new XYZ({
          projection: projectionInstance,
          url: 'http://webst02.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}',
          maxZoom: satelliteMaxZoom,
        }),
      }),

      // 天地图矢量图层
      tianditu: new TileLayer({
        source: new XYZ({
          url: 'http://t0.tianditu.gov.cn/vec_w/wmts?' +
            'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&' +
            `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITUKEY}`,
          // 天地图使用 WGS84 坐标系，需要转换为 GCJ-02 以匹配高德地图
          projection: wgs84ToGcj02Projection,
          maxZoom: defaultMaxZoom
        }),
        properties: { isBaseMap: true }
      }),

      // 天地图矢量标注图层
      tianditulabel: new TileLayer({
        source: new XYZ({
          url: 'http://t0.tianditu.gov.cn/cva_w/wmts?' +
            'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&' +
            `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITUKEY}`,
          projection: wgs84ToGcj02Projection,
          maxZoom: defaultMaxZoom
        }),
        properties: { isBaseMap: false }
      }),

      // 天地图卫星影像图层
      tianditusatellite: new TileLayer({
        source: new XYZ({
          url: 'https://t{0-7}.tianditu.gov.cn/img_w/wmts?' +
            'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&' +
            `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITUKEY}`,
          projection: wgs84ToGcj02Projection,
          maxZoom: defaultMaxZoom,
          tilePixelRatio: 2, // 提高瓦片清晰度（高分辨率屏幕）
          crossOrigin: 'anonymous' // 跨域支持
        }),
        properties: { isBaseMap: true }
      }),

      // 天地图卫星影像标注图层
      tianditusatlabel: new TileLayer({
        source: new XYZ({
          url: 'https://t{0-7}.tianditu.gov.cn/cia_w/wmts?' +
            'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&' +
            `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITUKEY}`,
          projection: wgs84ToGcj02Projection,
          maxZoom: defaultMaxZoom,
          tilePixelRatio: 2, // 提高瓦片清晰度
          crossOrigin: 'anonymous' // 跨域支持
        }),
        properties: { isBaseMap: false }
      }),
    }
  }

  /**
   * 地图放大
   */
  zoomIn () {
    if (!this.map) return
    const view = this.map.getView()
    const currentZoom = view.getZoom()
    if (currentZoom !== undefined) {
      view.animate({ zoom: currentZoom + 1, duration: 300 })
    }
  }

  /**
   * 地图缩小
   */
  zoomOut () {
    if (!this.map) return
    const view = this.map.getView()
    const currentZoom = view.getZoom()
    if (currentZoom !== undefined) {
      view.animate({ zoom: currentZoom - 1, duration: 300 })
    }
  }

  /**
   * 设置地图视图
   * @param {number} zoom - 缩放级别
   * @param center
   */
  fitView (zoom = 15, center = null) {
    if (!this.map) return
    const view = this.map.getView()
    const mapState = this.getMapState()
    if (!center) {
      center = mapState.center
      center = transform(center, 'EPSG:4326', 'EPSG:3857')
    }
    if (!zoom) {
      zoom = mapState.zoom
    }
    view.animate({ zoom: zoom, center: center, duration: 300 })
  }

  /**
   * 启用框选放大功能
   * @param {Function} onComplete - 框选完成回调
   */

  enableZoomToBox () {
    if (!this.map) return

    // 防止重复添加
    if (this.zoomToBoxInteraction) {
      this.map.removeInteraction(this.zoomToBoxInteraction)
      this.zoomToBoxInteraction = null
    }

    const dragBox = new DragBox({
      condition: () => true,
      // 不生效
      // style: new Style({
      //   stroke: new Stroke({
      //     color: 'rgba(24,144,255,1)', // 边框颜色
      //     width: 2,
      //   }),
      //   fill: new Fill({
      //     color: 'rgba(24,144,255,0.2)', // 内部填充
      //   }),
      // }),
    })

    dragBox.on('boxend', () => {
      const extent = dragBox.getGeometry().getExtent()

      this.map.getView().fit(extent, {
        padding: [20, 20, 20, 20],
        duration: 300,
      })
    })

    this.map.addInteraction(dragBox)
    this.zoomToBoxInteraction = dragBox
  }

  /**
   * 禁用框选放大功能
   */
  disableZoomToBox () {
    if (!this.map) return

    if (this.zoomToBoxInteraction) {
      this.map.removeInteraction(this.zoomToBoxInteraction)
      this.zoomToBoxInteraction = null
    }
  }

  /**
   * 距离测量（OpenLayers MouseTool 等价实现）
   */
  startDistanceMeasure (onResultChange) {
    if (!this.map) return

    // 记录回调与结果；每次开启前重置
    this.measureResultCb = onResultChange || null
    this.measureResults = []

    this.ensureMeasureLayer()
    // 清理旧的覆盖物与图层数据
    if (this.measureSource) {
      this.measureSource.clear()
    }
    if (this.measureOverlays.length) {
      this.measureOverlays.forEach(ov => this.map.removeOverlay(ov))
      this.measureOverlays = []
    }

    // 清理旧的交互
    if (this.measureDraw) {
      this.map.removeInteraction(this.measureDraw)
      this.measureDraw = null
    }

    const draw = new Draw({
      source: this.measureSource,
      type: 'LineString',
      style: new Style({
        // 与高德示例接近的紫色 2px 线样式
        stroke: new Stroke({
          color: '#FF33FF',
          width: 2
        }),
        image: new CircleStyle({
          radius: 5,
          fill: new Fill({ color: '#FF33FF' })
        })
      })
    })

    const createLabelElement = (text, type = 'mid') => {
      const el = document.createElement('div')
      el.style.cssText = [
        'background:#fff',
        'border:1px solid #9c6ade',
        'color:#4b2ea0',
        'font-size:12px',
        'padding:4px 8px',
        'border-radius:12px',
        'box-shadow:0 2px 6px rgba(0,0,0,0.15)',
        'white-space:nowrap'
      ].join(';')
      if (type === 'start') {
        el.style.borderColor = '#47c9ef'
        el.style.color = '#176782'
      } else if (type === 'end') {
        el.style.borderColor = '#ff4d4f'
        el.style.color = '#a8071a'
      }
      el.innerText = text
      return el
    }

    const addOverlay = (coord, text, type = 'mid') => {
      const element = createLabelElement(text, type)
      const overlay = new Overlay({
        element,
        position: coord,
        offset: [0, -10],
        positioning: 'bottom-center',
        stopEvent: false
      })
      this.map.addOverlay(overlay)
      this.measureOverlays.push(overlay)
    }

    const formatDistance = (meters) => {
      if (!meters && meters !== 0) return ''
      if (meters >= 1000) {
        return `${(meters / 1000).toFixed(2)} km`
      }
      return `${meters.toFixed(2)} m`
    }

    draw.on('drawstart', (evt) => {
      let lastFixedCount = 0
      const geom = evt.feature.getGeometry()

      geom.on('change', (gevt) => {
        const coords = gevt.target.getCoordinates()
        // 绘制过程中，最后一个点为鼠标位置，不计入已落点
        const fixedCoords = coords.slice(0, Math.max(coords.length - 1, 1))
        if (fixedCoords.length > lastFixedCount) {
          const current = fixedCoords[fixedCoords.length - 1]
          const line = new LineString(fixedCoords)
          const distance = Number(getSphereLength(line, { projection: 'EPSG:3857' }).toFixed(2))
          const type = fixedCoords.length === 1 ? 'start' : 'mid'
          addOverlay(current, formatDistance(distance), type)
          lastFixedCount = fixedCoords.length
        }
      })
    })

    draw.on('drawend', (evt) => {
      const geom = evt.feature.getGeometry()
      let distance = 0
      if (geom && geom.getLength) {
        // 使用地球测地长度
        distance = Number(getSphereLength(geom, { projection: 'EPSG:3857' }).toFixed(2))
      }

      const coords = geom?.getCoordinates?.() || []
      if (coords.length) {
        const endCoord = coords[coords.length - 1]
        addOverlay(endCoord, formatDistance(distance), 'end')
      }

      const result = {
        id: `measure-${Date.now()}-${this.measureResults.length + 1}`,
        distance,
        color: '#FF33FF'
      }
      this.measureResults.push(result)
      if (typeof this.measureResultCb === 'function') {
        this.measureResultCb([...this.measureResults])
      }
    })

    this.map.addInteraction(draw)
    this.measureDraw = draw
  }

  clearMeasureResults () {
    if (this.measureDraw) {
      this.map.removeInteraction(this.measureDraw)
      this.measureDraw = null
    }
    if (this.measureSource) {
      this.measureSource.clear()
    }
    if (this.measureOverlays.length) {
      this.measureOverlays.forEach(ov => this.map.removeOverlay(ov))
      this.measureOverlays = []
    }
    this.measureResults = []
    if (typeof this.measureResultCb === 'function') {
      this.measureResultCb([])
    }
  }

  /**
   * 面积测量（绘制多边形，双击结束并在终点显示面积）
   */
  startAreaMeasure () {
    if (!this.map) return

    this.ensureMeasureLayer()

    // 清理旧的交互/覆盖物/几何
    if (this.measureDraw) {
      this.map.removeInteraction(this.measureDraw)
      this.measureDraw = null
    }
    if (this.measureSource) {
      this.measureSource.clear()
    }
    if (this.measureOverlays.length) {
      this.measureOverlays.forEach(ov => this.map.removeOverlay(ov))
      this.measureOverlays = []
    }

    const draw = new Draw({
      source: this.measureSource,
      type: 'Polygon',
      style: new Style({
        stroke: new Stroke({
          color: '#FED151',
          width: 2
        }),
        fill: new Fill({
          color: 'rgba(254, 189, 94, 0.35)'
        }),
        image: new CircleStyle({
          radius: 5,
          fill: new Fill({ color: '#FED151' })
        })
      })
    })

    const polygonStyle = new Style({
      stroke: new Stroke({
        color: '#FED151',
        width: 2
      }),
      fill: new Fill({
        color: 'rgba(254, 189, 94, 0.35)'
      }),
      image: new CircleStyle({
        radius: 5,
        fill: new Fill({ color: '#FED151' })
      })
    })

    const formatArea = (value) => {
      if (!Number.isFinite(value)) return ''
      if (value >= 1e6) return `${(value / 1e6).toFixed(2)} 平方公里`
      if (value >= 1e4) return `${(value / 1e4).toFixed(2)} 万平方米`
      return `${Math.round(value)} m²`
    }

    const addAreaOverlay = (coord, areaText) => {
      const el = document.createElement('div')
      el.style.cssText = [
        'background:#fff',
        'border:1px solid #d48806',
        'color:#ad6800',
        'font-size:12px',
        'padding:4px 8px',
        'border-radius:12px',
        'box-shadow:0 2px 6px rgba(0,0,0,0.15)',
        'white-space:nowrap'
      ].join(';')
      el.innerText = areaText
      const overlay = new Overlay({
        element: el,
        position: coord,
        offset: [0, -10],
        positioning: 'bottom-center',
        stopEvent: false
      })
      this.map.addOverlay(overlay)
      this.measureOverlays.push(overlay)
    }

    draw.on('drawend', (evt) => {
      const geom = evt.feature.getGeometry()
      // 给最终面要素设置填充样式，确保内部有背景色
      evt.feature.setStyle(polygonStyle)

      let area = 0
      if (geom && geom.getArea) {
        area = Math.abs(getSphereArea(geom, { projection: 'EPSG:3857' }))
      }

      const coords = geom?.getCoordinates?.() || []
      const ring = coords[0] || []
      if (ring.length) {
        const endCoord = ring[ring.length - 2] || ring[ring.length - 1]
        addAreaOverlay(endCoord, formatArea(area))
      }
    })

    this.map.addInteraction(draw)
    this.measureDraw = draw
  }

  ensureMeasureLayer () {
    if (this.measureLayer && this.measureSource) return

    // 测距覆盖物单独图层，避免污染业务层
    this.measureSource = new VectorSource()
    this.measureLayer = new VectorLayer({
      source: this.measureSource,
      style: new Style({
        stroke: new Stroke({
          color: '#FF33FF',
          width: 2
        }),
        image: new CircleStyle({
          radius: 5,
          fill: new Fill({ color: '#FF33FF' })
        })
      })
    })
    this.map.addLayer(this.measureLayer)
  }

  /**
   * 懒创建选择图层
   */
  ensureSelectLayer () {
    if (!this.map) return
    // if (!this.selectSource) {
      this.selectSource = new VectorSource()
    // }
    // if (!this.selectLayer) {
      this.selectLayer = new VectorLayer({
        source: this.selectSource,
        style: new Style({
          stroke: new Stroke({
            color: 'rgba(24,144,255,1)',
            width: 2,
          }),
          fill: new Fill({
            color: 'rgba(24,144,255,0.25)',
          }),
        })
      })
      this.map.addLayer(this.selectLayer)
    // }
  }

  stopSelectInteraction () {
    if (!this.map) return
    if (this.selectInteraction) {
      this.map.removeInteraction(this.selectInteraction)
      this.selectInteraction = null
    }
  }

  /**
   * 启动矩形框选（回调返回 WGS84 坐标）
   * @param {Function} onComplete 回调函数，参数为坐标数组
   */
  startRectangleSelect (onComplete) {
    return this.startDrawSelect({
      type: 'Circle',
      geometryFunction: createBox(),
    }, 'rectangle', onComplete)
  }

  /**
   * 启动圆形框选（回调返回 WGS84 坐标）
   * @param {Function} onComplete 回调函数，参数为坐标数组
   */
  startCircleSelect (onComplete) {
    return this.startDrawSelect({
      type: 'Circle',
      geometryFunction: createRegularPolygon(64),
    }, 'circle', onComplete)
  }

  /**
   * 启动多边形框选（回调返回 WGS84 坐标）
   * @param {Function} onComplete 回调函数，参数为坐标数组
   */
  startPolygonSelect (onComplete) {
    return this.startDrawSelect({
      type: 'Polygon',
    }, 'polygon', onComplete)
  }

  /**
   * 启动框选绘制通用方法
   * @param {object} drawOptions OpenLayers Draw 配置
   * @param {('rectangle'|'circle'|'polygon')} type 框选类型
   * @param {Function} onComplete 回调函数，参数为坐标数组
   */
  startDrawSelect (drawOptions, type, onComplete) {
    if (!this.map) return
    this.stopSelectInteraction()
    this.ensureSelectLayer()
    if (this.selectSource) {
      this.selectSource.clear()
    }
    const draw = new Draw({
      source: this.selectSource,
      ...drawOptions,
    })
    draw.on('drawend', (evt) => {
      // 标记类型，便于按类型清理
      try {
        evt.feature && evt.feature.set && evt.feature.set('selectType', type)
      } catch (e) {}
      const geometry = evt.feature.getGeometry()
      const coords = this.extractLonLatCoordinates(geometry)
      if (typeof onComplete === 'function') {
        onComplete(coords)
      }
      // 结束后退出绘制：仅移除绘制交互，不清除 selectSource
      // 这样绘制结果会保留在图层上，供 clearSelectResults 显式清除
      try {
        if (this.map && draw) {
          this.map.removeInteraction(draw)
        }
      } catch (e) {}
      // 确保 selectInteraction 引用被清理（但不清空 selectSource）
      if (this.selectInteraction === draw) {
        this.selectInteraction = null
      }
    })
    this.map.addInteraction(draw)
    this.selectInteraction = draw
  }

  /**
   * 清理框选结果（不再按 type 区分，始终清除绘制结果）
   */
  clearSelectResults () {
    console.warn('clearSelectResults (clear all select drawings)')
    try {
      // 先停止交互，避免残留状态
      this.stopSelectInteraction()
      console.warn('ensureSelectLayer (clear all select drawings)', this.selectLayer)
      console.warn('ensureSelectLayer (clear all select drawings)', this.selectSource)
      // 确保选择图层和数据源存在再清空
      if (this.selectSource) {
        try {
          // this.selectSource.clear()
          this.selectLayer.getSource().clear()
          this.map.removeLayer(this.selectLayer)
        } catch (e) {
          console.error('clearSelectResults error', e)
        }
      }

      // 如果需要可同时移除选择图层，这里保留图层以便后续继续使用
    } catch (e) {
      console.error('clearSelectResults error', e)
    }
  }

  /**
   * 开启自由画线（LineString）
   */
  startLineDraw (onComplete) {
    if (!this.map) return
    this.stopLineDraw()

    this.lineDrawSource = new VectorSource()
    this.lineDrawLayer = new VectorLayer({
      source: this.lineDrawSource,
      style: new Style({
        stroke: new Stroke({
          color: '#ff6600',
          width: 3
        })
      })
    })
    this.map.addLayer(this.lineDrawLayer)

    this.lineDrawInteraction = new Draw({
      source: this.lineDrawSource,
      type: 'LineString'
    })

    this.lineDrawInteraction.on('drawend', (e) => {
      try {
        const coords = e.feature.getGeometry().getCoordinates().map(item => toLonLat(item))
        if (typeof onComplete === 'function') {
          onComplete(coords)
        }
      } catch (err) {
        console.error('line draw parse error', err)
      } finally {
        this.stopLineDraw()
      }
    })

    this.map.addInteraction(this.lineDrawInteraction)
  }

  /**
   * 停止自由画线并清理临时资源
   */
  stopLineDraw () {
    if (!this.map) return
    if (this.lineDrawInteraction) {
      this.map.removeInteraction(this.lineDrawInteraction)
      this.lineDrawInteraction = null
    }
    if (this.lineDrawLayer) {
      this.map.removeLayer(this.lineDrawLayer)
      this.lineDrawLayer = null
    }
    this.lineDrawSource = null
  }

  /**
   * 开启单条直线绘制（目前仅用于切面分析）
   * 只能绘制一条直线（两个点），绘制完成后自动结束交互
   */
  startStraightLineDraw (onComplete) {
    if (!this.map) return
    this.stopStraightLineDraw()

    this.straightLineDrawSource = new VectorSource()
    this.straightLineDrawLayer = new VectorLayer({
      source: this.straightLineDrawSource,
      style: new Style({
        stroke: new Stroke({
          color: '#ff6600',
          width: 3
        })
      })
    })
    this.map.addLayer(this.straightLineDrawLayer)

    // 创建画线交互，限制为两点（直线）
    this.straightLineDrawInteraction = new Draw({
      source: this.straightLineDrawSource,
      type: 'LineString',
      maxPoints: 2 // 限制为两点，只能画直线
    })

    // 监听画线完成事件
    this.straightLineDrawInteraction.once('drawend', (e) => {
      try {
        // 获取坐标（OpenLayers内部使用3857，转换为4326）
        const coords3857 = e.feature.getGeometry().getCoordinates()
        const coords4326 = coords3857.map(item => toLonLat(item))

        // 确保只有两个点
        if (coords4326.length >= 2) {
          const lineCoords = [coords4326[0], coords4326[coords4326.length - 1]]
          if (typeof onComplete === 'function') {
            onComplete(lineCoords)
          }
        }
      } catch (err) {
        console.error('straight line draw parse error', err)
      } finally {
        // 绘制完成后自动结束交互
        this.stopStraightLineDraw()
      }
    })

    this.map.addInteraction(this.straightLineDrawInteraction)
  }

  /**
   * 停止单条直线绘制并清理临时资源
   */
  stopStraightLineDraw () {
    if (!this.map) return
    if (this.straightLineDrawInteraction) {
      this.map.removeInteraction(this.straightLineDrawInteraction)
      this.straightLineDrawInteraction = null
    }
    if (this.straightLineDrawLayer) {
      this.map.removeLayer(this.straightLineDrawLayer)
      this.straightLineDrawLayer = null
    }
    this.straightLineDrawSource = null
  }

  /**
   * 当前定位
   * 使用 OpenLayers Geolocation 工具获取用户当前位置坐标
   * @returns {Promise<{longitude: number, latitude: number}>} 返回定位坐标 {longitude, latitude} (WGS84)
   */
  async locateCurrent () {
    if (!this.map) {
      throw new Error('Map not initialized')
    }

    return new Promise((resolve, reject) => {
      let timeoutId = null
      let resolved = false

      // 清理函数
      const cleanup = () => {
        if (timeoutId) {
          clearTimeout(timeoutId)
          timeoutId = null
        }
        if (geolocation) {
          geolocation.setTracking(false) // 停止跟踪
        }
      }

      // 创建 Geolocation 实例
      const geolocation = new Geolocation({
        tracking: false, // 初始不跟踪
        projection: 'EPSG:3857', // 使用 Web Mercator 投影
        enableHighAccuracy: true // 启用高精度定位
      })

      // 监听位置变化事件（只触发一次）
      const positionHandler = () => {
        if (resolved) return
        const position = geolocation.getPosition()
        if (position) {
          resolved = true
          cleanup()
          // 将 EPSG:3857 坐标转换为 WGS84 (EPSG:4326)
          const [longitude, latitude] = toLonLat(position)
          resolve({ longitude, latitude })
        }
      }

      // 监听错误事件
      const errorHandler = (error) => {
        if (resolved) return
        resolved = true
        cleanup()
        let errorMessage = '定位失败，请检查浏览器定位权限和网络连接'
        // OpenLayers Geolocation 的错误对象可能包含 code 属性
        if (error && error.code === 1) {
          errorMessage = '定位失败：浏览器定位权限被拒绝。请在浏览器设置中允许此网站访问您的位置信息，然后重试。'
        } else if (error && error.code === 2) {
          errorMessage = '定位失败：无法获取位置信息，请检查设备定位功能是否开启'
        } else if (error && error.code === 3) {
          errorMessage = '定位超时，请重试'
        }
        reject(new Error(errorMessage))
      }

      // 绑定事件监听（使用 once 确保只触发一次）
      geolocation.once('change:position', positionHandler)
      geolocation.once('error', errorHandler)

      // 开始定位（设置 tracking 为 true）
      geolocation.setTracking(true)

      // 设置超时处理
      timeoutId = setTimeout(() => {
        if (!resolved && geolocation.getPosition() === null) {
          resolved = true
          cleanup()
          reject(new Error('定位超时，请重试'))
        }
      }, 10000) // 10 秒超时
    })
  }

  extractLonLatCoordinates (geometry) {
    if (!geometry) return []
    const type = geometry.getType()
    if (type === 'Polygon') {
      const clone = geometry.clone().transform('EPSG:3857', 'EPSG:4326')
      const rings = clone.getCoordinates()
      return rings && rings.length > 0 ? rings[0] : []
    }
    if (type === 'Circle') {
      const poly = Polygon.fromCircle(geometry, 64)
      const clone = poly.transform('EPSG:3857', 'EPSG:4326')
      const rings = clone.getCoordinates()
      return rings && rings.length > 0 ? rings[0] : []
    }
    return []
  }

  /**
   * 打开信息窗口
   * @param {Array<number>} coordinate - 坐标 [lng, lat] (WGS84)
   * @param {HTMLElement} content - 窗口内容（DOM元素）
   *  注意：Vue 组件在调用层（如 index.vue）中已挂载为 DOM 元素，适配器只接收 DOM 元素
   * @param {object} [options] - 可选配置
   * @param {number} [options.width] - 窗口宽度（像素）
   * @param {number} [options.height] - 窗口高度（像素）
   * @param {boolean} [options.closeOnClick] - 点击地图是否关闭窗口
   * @returns {Promise<void>}
   */
  async openInfoWindow (coordinate, content, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    // 关闭已存在的信息窗口
    this.closeInfoWindow()

    const [lng, lat] = coordinate
    // 转换为地图投影坐标（EPSG:3857）
    const mapCoordinate = fromLonLat([lng, lat])

    // 如果 overlay 不存在，创建它
    if (!this.infoWindowOverlay) {
      this.infoWindowOverlay = new Overlay({
        element: content,
        positioning: 'bottom-center',
        stopEvent: false,
        offset: [0, -10]
      })
      // 添加到地图
      this.map.addOverlay(this.infoWindowOverlay)
    } else {
      // 如果已存在，更新元素
      this.infoWindowOverlay.setElement(content)
    }

    // 设置位置
    this.infoWindowOverlay.setPosition(mapCoordinate)

    // 如果设置了点击地图关闭，添加地图点击事件
    if (options.closeOnClick) {
      this._infoWindowCloseHandler = (evt) => {
        // 检查点击是否在信息窗口内
        const pixel = evt.pixel
        const targetElement = this.map.getTargetElement()
        const mapRect = targetElement.getBoundingClientRect()
        const x = pixel[0] + mapRect.left
        const y = pixel[1] + mapRect.top

        const rect = content.getBoundingClientRect()
        // 如果点击不在信息窗口内，则关闭
        if (!(x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom)) {
          this.closeInfoWindow()
        }
      }
      // 延迟添加事件，避免立即触发
      setTimeout(() => {
        if (this._infoWindowCloseHandler) {
          this.map.on('click', this._infoWindowCloseHandler)
        }
      }, 100)
    }
  }

  /**
   * 关闭信息窗口
   * @returns {Promise<void>}
   */
  async closeInfoWindow () {
    if (this.infoWindowOverlay) {
      // 只移除位置，不删除 overlay（保留以便重用）
      this.infoWindowOverlay.setPosition(undefined)
    }

    // 移除地图点击关闭事件
    if (this._infoWindowCloseHandler) {
      this.map.un('click', this._infoWindowCloseHandler)
      this._infoWindowCloseHandler = null
    }
  }

  // ===== 轨迹播放相关方法 =====

  /**
   * 添加轨迹
   * @param {Array<{key: string, path: Array<{x: number, y: number}>}>} segments - 轨迹段数组
   * @param {object} config - 轨迹配置
   */
  async addTrajectory (segments, config = {}) {
    if (!this.map) throw new Error('Map not initialized')
    if (!segments || segments.length === 0) {
      throw new Error('Trajectory segments cannot be empty')
    }

    // 清除旧的轨迹
    this.clearTrajectory()

    // 保存轨迹数据和配置到 state
    this.trajectoryState.segments = segments
    this.trajectoryState.config = {
      speed: config.speed || 1,
      baseSpeed: config.baseSpeed || 1,
      unplayedColor: config.unplayedColor || '#ff0000',
      playedColor: config.playedColor || '#00ff00',
      startIcon: config.startIcon || require('@vue2-client/assets/img/map/start.png'),
      endIcon: config.endIcon || require('@vue2-client/assets/img/map/terminus.png'),
      carIcon: config.carIcon || require('@vue2-client/assets/img/map/car.png')
    }
    this.trajectoryState.speed = this.trajectoryState.config.speed

    // 转换坐标并创建图层
    this.initTrajectoryLayers()
  }

  /**
   * 初始化轨迹图层
   * @private
   */
  initTrajectoryLayers () {
    if (!this.map) return

    // 转换所有轨迹段坐标
    const allCoordinates = this.trajectoryState.segments.map(segment => {
      return segment.path.map(point => fromLonLat([point.x, point.y]))
    })

    // 创建轨迹线图层（未播放部分）
    const routeFeatures = allCoordinates.map((coords, index) => {
      if (coords.length < 2) return null
      const route = new LineString(coords)
      return new Feature({
        geometry: route,
        segmentIndex: index
      })
    }).filter(Boolean)

    this.trajectoryState._layers.route = new VectorLayer({
      source: new VectorSource({
        features: routeFeatures
      }),
      style: new Style({
        stroke: new Stroke({
          width: 6,
          color: this.trajectoryState.config.unplayedColor
        })
      })
    })
    this.map.addLayer(this.trajectoryState._layers.route)

    // 创建已播放轨迹图层
    this.trajectoryState._layers.traveled = new VectorLayer({
      source: new VectorSource(),
      style: new Style({
        stroke: new Stroke({
          width: 6,
          color: this.trajectoryState.config.playedColor
        })
      })
    })
    this.map.addLayer(this.trajectoryState._layers.traveled)

    // 创建起点和终点图层
    const startFeatures = []
    const endFeatures = []
    allCoordinates.forEach((coords, index) => {
      if (coords.length > 0) {
        startFeatures.push(new Feature({
          geometry: new Point(coords[0]),
          segmentIndex: index
        }))
        endFeatures.push(new Feature({
          geometry: new Point(coords[coords.length - 1]),
          segmentIndex: index
        }))
      }
    })

    this.trajectoryState._layers.start = new VectorLayer({
      source: new VectorSource({ features: startFeatures }),
      style: new Style({
        image: new Icon({
          anchor: [0.5, 1],
          src: this.trajectoryState.config.startIcon,
          scale: 0.2
        })
      })
    })
    this.map.addLayer(this.trajectoryState._layers.start)

    this.trajectoryState._layers.end = new VectorLayer({
      source: new VectorSource({ features: endFeatures }),
      style: new Style({
        image: new Icon({
          anchor: [0.5, 1],
          src: this.trajectoryState.config.endIcon,
          scale: 0.2
        })
      })
    })
    this.map.addLayer(this.trajectoryState._layers.end)

    // 创建小车图层
    const firstCoord = allCoordinates[0] && allCoordinates[0][0]
    if (firstCoord) {
      this.trajectoryState._carFeature = new Feature({
        geometry: new Point(firstCoord)
      })
      this.trajectoryState._layers.car = new VectorLayer({
        source: new VectorSource({
          features: [this.trajectoryState._carFeature]
        }),
        style: new Style({
          image: new Icon({
            anchor: [0.5, 0.5],
            src: this.trajectoryState.config.carIcon,
            scale: 1,
            rotateWithView: true
          })
        })
      })
      this.map.addLayer(this.trajectoryState._layers.car)
    }

    // 初始化第一段数据
    this.trajectoryState.currentIndex = 0
    this.trajectoryState.progress = 0
    this.trajectoryState._distanceTraveled = 0
    this.updateCurrentSegmentData()
  }

  /**
   * 更新当前段数据
   * @private
   */
  updateCurrentSegmentData () {
    if (this.trajectoryState.currentIndex >= this.trajectoryState.segments.length) {
      return
    }

    const segment = this.trajectoryState.segments[this.trajectoryState.currentIndex]
    this.trajectoryState._coordinates = segment.path.map(point => fromLonLat([point.x, point.y]))

    // 计算线段长度
    this.trajectoryState._segmentLengths = []
    this.trajectoryState._totalLength = 0
    if (this.trajectoryState._coordinates.length >= 2) {
      for (let i = 0; i < this.trajectoryState._coordinates.length - 1; i++) {
        const start = this.trajectoryState._coordinates[i]
        const end = this.trajectoryState._coordinates[i + 1]
        const dx = end[0] - start[0]
        const dy = end[1] - start[1]
        const length = Math.sqrt(dx * dx + dy * dy)
        this.trajectoryState._segmentLengths.push(length)
        this.trajectoryState._totalLength += length
      }
    }
  }

  /**
   * 根据距离比例获取位置
   * @private
   */
  getPositionByDistanceRatio (distanceRatio) {
    if (this.trajectoryState._coordinates.length < 2) {
      return { index: 0, position: this.trajectoryState._coordinates[0] || [0, 0] }
    }

    const targetDistance = distanceRatio * this.trajectoryState._totalLength
    let accumulatedDistance = 0

    for (let i = 0; i < this.trajectoryState._segmentLengths.length; i++) {
      const segmentLength = this.trajectoryState._segmentLengths[i]

      if (accumulatedDistance + segmentLength >= targetDistance) {
        const segmentRatio = (targetDistance - accumulatedDistance) / segmentLength
        const start = this.trajectoryState._coordinates[i]
        const end = this.trajectoryState._coordinates[i + 1]

        const position = [
          start[0] + segmentRatio * (end[0] - start[0]),
          start[1] + segmentRatio * (end[1] - start[1])
        ]

        return { index: i, position }
      }

      accumulatedDistance += segmentLength
    }

    return {
      index: this.trajectoryState._coordinates.length - 2,
      position: this.trajectoryState._coordinates[this.trajectoryState._coordinates.length - 1]
    }
  }

  /**
   * 轨迹动画帧处理
   * @private
   */
  trajectoryAnimationFrame (e) {
    if (this.trajectoryState.status !== 'playing') return

    const time = e.frameState.time
    const deltaSeconds = Math.max((time - this.trajectoryState._lastTime) / 1000, 0)
    this.trajectoryState._lastTime = time

    // 计算移动距离
    const actualSpeed = this.trajectoryState.config.baseSpeed * this.trajectoryState.config.speed
    const incrementDistance = actualSpeed * deltaSeconds
    this.trajectoryState._distanceTraveled += incrementDistance

    // 更新进度
    this.trajectoryState.progress = this.trajectoryState._totalLength > 0
      ? this.trajectoryState._distanceTraveled / this.trajectoryState._totalLength
      : 1

    // 检查是否完成当前段
    if (this.trajectoryState.progress >= 1) {
      this.trajectoryState.progress = 1

      // 添加完整已播放轨迹
      const traveledRoute = new LineString(this.trajectoryState._coordinates)
      const traveledFeature = new Feature({
        geometry: traveledRoute,
        segmentIndex: this.trajectoryState.currentIndex
      })
      this.trajectoryState._layers.traveled.getSource().addFeature(traveledFeature)
      this.trajectoryState._traveledFeatures.push(traveledFeature)

      // 检查是否有下一段
      if (this.trajectoryState.currentIndex < this.trajectoryState.segments.length - 1) {
        // 移动到下一段
        this.trajectoryState.currentIndex++
        this.trajectoryState.progress = 0
        this.trajectoryState._distanceTraveled = 0
        this.updateCurrentSegmentData()

        // 移动小车到下一段起点
        if (this.trajectoryState._carFeature && this.trajectoryState._coordinates[0]) {
          this.trajectoryState._carFeature.getGeometry().setCoordinates(this.trajectoryState._coordinates[0])
        }
      } else {
        // 所有段播放完成
        this.trajectoryState.status = 'finished'
        this.trajectoryState._layers.route.un('postrender', this.trajectoryAnimationFrame)
        // 移动小车到终点
        if (this.trajectoryState._carFeature && this.trajectoryState._coordinates.length > 0) {
          const lastCoord = this.trajectoryState._coordinates[this.trajectoryState._coordinates.length - 1]
          this.trajectoryState._carFeature.getGeometry().setCoordinates(lastCoord)
        }
        return
      }
    }

    // 更新小车位置和已播放轨迹
    const { index, position } = this.getPositionByDistanceRatio(this.trajectoryState.progress)

    // 更新小车位置
    if (this.trajectoryState._carFeature) {
      this.trajectoryState._carFeature.getGeometry().setCoordinates(position)

      // 更新小车旋转角度
      const nextIndex = Math.min(index + 1, this.trajectoryState._coordinates.length - 1)
      const nextCoord = this.trajectoryState._coordinates[nextIndex]
      const dx = nextCoord[0] - position[0]
      const dy = nextCoord[1] - position[1]
      const angle = Math.atan2(dy, dx) - Math.PI / 2

      const iconStyle = new Icon({
        anchor: [0.5, 0.5],
        anchorXUnits: 'fraction',
        anchorYUnits: 'fraction',
        src: this.trajectoryState.config.carIcon,
        scale: 1,
        rotateWithView: true,
        rotation: -angle
      })
      this.trajectoryState._carFeature.setStyle(new Style({ image: iconStyle }))
    }

    // 更新已播放轨迹（临时）
    // 清除当前段的临时轨迹
    const currentTempFeatures = this.trajectoryState._traveledFeatures.filter(f => {
      return f.get('segmentIndex') === this.trajectoryState.currentIndex && !f.get('permanent')
    })
    currentTempFeatures.forEach(f => {
      this.trajectoryState._layers.traveled.getSource().removeFeature(f)
    })
    this.trajectoryState._traveledFeatures = this.trajectoryState._traveledFeatures.filter(f => {
      return !(f.get('segmentIndex') === this.trajectoryState.currentIndex && !f.get('permanent'))
    })

    // 添加新的临时轨迹
    const splitIndex = index + 1
    const traveledCoords = this.trajectoryState._coordinates.slice(0, splitIndex)
    traveledCoords.push(position)

    if (traveledCoords.length >= 2) {
      const traveledRoute = new LineString(traveledCoords)
      const traveledFeature = new Feature({
        geometry: traveledRoute,
        segmentIndex: this.trajectoryState.currentIndex,
        permanent: false
      })
      this.trajectoryState._layers.traveled.getSource().addFeature(traveledFeature)
      this.trajectoryState._traveledFeatures.push(traveledFeature)
    }

    this.map.render()
  }

  /**
   * 开始播放轨迹
   */
  async startTrajectory () {
    if (!this.map || !this.trajectoryState.segments.length) {
      throw new Error('Trajectory not added')
    }

    if (this.trajectoryState.status === 'finished' || (this.trajectoryState.progress === 0 && this.trajectoryState.currentIndex === 0)) {
      return this.replayTrajectory()
    } else {
      return this.resumeTrajectory()
    }
  }

  /**
   * 重新播放轨迹
   */
  async replayTrajectory () {
    if (!this.map || !this.trajectoryState.segments.length) {
      throw new Error('Trajectory not added')
    }

    // 暂停当前播放
    this.pauseTrajectory()

    // 重置状态
    this.trajectoryState.currentIndex = 0
    this.trajectoryState.progress = 0
    this.trajectoryState._distanceTraveled = 0
    this.trajectoryState.status = 'idle'

    // 清除已播放轨迹
    if (this.trajectoryState._layers.traveled) {
      this.trajectoryState._layers.traveled.getSource().clear()
    }
    this.trajectoryState._traveledFeatures = []

    // 更新当前段数据
    this.updateCurrentSegmentData()

    // 移动小车到起点
    if (this.trajectoryState._carFeature && this.trajectoryState._coordinates[0]) {
      this.trajectoryState._carFeature.getGeometry().setCoordinates(this.trajectoryState._coordinates[0])
    }

    // 开始播放
    this.resumeTrajectory()
  }

  /**
   * 暂停播放轨迹
   */
  async pauseTrajectory () {
    if (this.trajectoryState.status !== 'playing') return

    this.trajectoryState.status = 'paused'

    // 移除动画监听
    if (this.trajectoryState._layers.route) {
      this.trajectoryState._layers.route.un('postrender', this.trajectoryAnimationFrame)
    }

    // 将临时轨迹标记为永久
    this.trajectoryState._traveledFeatures.forEach(f => {
      f.set('permanent', true)
    })

    // 取消动画帧
    if (this.trajectoryState._animationFrameId) {
      cancelAnimationFrame(this.trajectoryState._animationFrameId)
      this.trajectoryState._animationFrameId = null
    }
  }

  /**
   * 继续播放轨迹
   */
  async resumeTrajectory () {
    if (!this.map || !this.trajectoryState.segments.length) {
      throw new Error('Trajectory not added')
    }

    if (this.trajectoryState.status === 'finished') {
      return this.replayTrajectory()
    }

    this.trajectoryState.status = 'playing'
    this.trajectoryState._lastTime = Date.now()

    // 确保当前段数据已更新
    this.updateCurrentSegmentData()

    // 绑定动画帧
    if (this.trajectoryState._layers.route) {
      this.trajectoryState._layers.route.on('postrender', this.trajectoryAnimationFrame.bind(this))
    }

    this.map.render()
  }

  /**
   * 清除轨迹
   */
  async clearTrajectory () {
    // 暂停播放
    this.pauseTrajectory()

    // 移除图层
    if (this.map) {
      Object.values(this.trajectoryState._layers).forEach(layer => {
        if (layer) {
          this.map.removeLayer(layer)
        }
      })
    }

    // 重置状态
    this.trajectoryState = {
      segments: [],
      config: null,
      currentIndex: 0,
      progress: 0,
      status: 'idle',
      speed: 1,
      _distanceTraveled: 0,
      _segmentLengths: [],
      _totalLength: 0,
      _coordinates: [],
      _lastTime: 0,
      _layers: {
        route: null,
        traveled: null,
        car: null,
        start: null,
        end: null
      },
      _carFeature: null,
      _animationFrameId: null,
      _traveledFeatures: []
    }
  }

  /**
   * 设置轨迹播放状态
   * @param {object|string} stateOrKey - 完整状态对象或字段名
   * @param {*} value - 字段值（当第一个参数为字段名时）
   */
  setTrajectoryState (stateOrKey, value) {
    if (typeof stateOrKey === 'string') {
      // 设置单个字段
      if (stateOrKey.startsWith('_')) {
        throw new Error(`Cannot set internal field: ${stateOrKey}`)
      }
      this.trajectoryState[stateOrKey] = value
      // 如果更新了 speed，同步到 config
      if (stateOrKey === 'speed' && this.trajectoryState.config) {
        this.trajectoryState.config.speed = value
      }
    } else {
      // 设置整个状态对象（只更新提供的字段，不覆盖整个对象）
      Object.keys(stateOrKey).forEach(key => {
        if (key.startsWith('_')) {
          throw new Error(`Cannot set internal field: ${key}`)
        }
        this.trajectoryState[key] = stateOrKey[key]
      })
      // 同步 speed 到 config
      if (stateOrKey.speed && this.trajectoryState.config) {
        this.trajectoryState.config.speed = stateOrKey.speed
      }
    }
  }

  /**
   * 获取轨迹播放状态
   * @param {string} [key] - 可选，获取指定字段
   * @returns {object|*} 完整状态对象或指定字段值
   */
  getTrajectoryState (key) {
    if (key) {
      return this.trajectoryState[key]
    }
    // 返回可序列化的状态（排除内部字段和地图元素引用）
    return {
      segments: this.trajectoryState.segments || [],
      currentIndex: this.trajectoryState.currentIndex || 0,
      progress: this.trajectoryState.progress || 0,
      speed: this.trajectoryState.speed || 1,
      config: this.trajectoryState.config || {},
      status: this.trajectoryState.status || 'idle'
    }
  }
}
