Cesium 中 WMS 图层点击查询(pickImageryLayerFeatures和GetFeatureInfo)

一、两种查询方式对比

特性 pickImageryLayerFeatures GetFeatureInfo (手动构造)
实现方式 Cesium 内置 API 手动构造 HTTP 请求
适用场景 简单查询、不需要筛选条件 需要筛选条件(CQL)、复杂查询
代码复杂度 低,几行代码 中,需要手动计算 BBOX 等参数
灵活性 低,不能自定义参数 高,可添加任意 WMS 参数
筛选条件 ❌ 不支持 ✅ 支持 (CQL_FILTER)
控制精度 Cesium 自动控制 手动控制 BBOX 范围、像素精度

二、pickImageryLayerFeatures 详解

2.1 工作原理
javascript 复制代码
用户点击 → Cesium 生成射线 → 球面交点计算 → 自动构造 GetFeatureInfo 请求 → 返回结果
2.2 核心代码
javascript 复制代码
// 1. 创建 WMS Provider 时**必须**开启拾取
const provider = new Cesium.WebMapServiceImageryProvider({
  url: '...',
  layers: '...',
  enablePickFeatures: true  // 🔥 关键:必须为 true
});

// 2. 点击时调用
const pickRay = viewer.camera.getPickRay(click.position);
const features = await viewer.scene.imageryLayers.pickImageryLayerFeatures(pickRay, viewer.scene);

// 3. 解析结果
features.forEach(feature => {
  console.log(feature.name, feature.description, feature.properties);
});
2.3 优点
  • 简单易用:不需要手动处理 BBOX、投影转换

  • 自动精确:Cesium 内部计算当前视图的精确范围和像素坐标

  • 射线驱动:直接通过三维射线获取点击位置的几何精度

2.4 缺点
  • 无法添加筛选条件enablePickFeatures: true 时,你添加的 parameters 中的 CQL_FILTER 会被 Cesium 忽略

  • 结果格式受限 :返回的是 Cesium 内部的 ImageryLayerFeatureInfo 对象,不一定包含所有属性

  • 版本适配问题:部分第三方封装可能会改写此方法

三、GetFeatureInfo (手动构造) 详解

3.1 工作原理
javascript 复制代码
用户点击 → 手动计算经纬度 → 手动计算 BBOX → 手动构造请求 URL → fetch 发送 → 解析结果
3.2 核心参数说明
参数 说明 示例
service 服务类型 WMS
request 请求类型 GetFeatureInfo
layers 查询的图层名 tb_list
query_layers 要查询的图层(通常同 layers) tb_list
bbox 查询范围 minLon,minLat,maxLon,maxLat
width 图片宽度(像素) 400
height 图片高度(像素) 400
x 点击点的 X 坐标(像素,0~width) 200
y 点击点的 Y 坐标(像素,0~height) 200
info_format 返回格式 application/jsontext/html
CQL_FILTER 筛选条件(GeoServer 特有) del_flag = 0 AND split_status = 0
3.3 关键:如何确定 BBOX 和 (x, y)
javascript 复制代码
// 获取当前视图范围
const rectangle = camera.computeViewRectangle();
const west = Cesium.Math.toDegrees(rectangle.west);
const east = Cesium.Math.toDegrees(rectangle.east);
const south = Cesium.Math.toDegrees(rectangle.south);
const north = Cesium.Math.toDegrees(rectangle.north);

// 方法一:使用整个视图范围
bbox = `${west},${south},${east},${north}`;
width = canvas.clientWidth;
height = canvas.clientHeight;
x = (longitude - west) / (east - west) * width;
y = height - ((latitude - south) / (north - south) * height); // Y 轴可能反转

// 方法二:以点击点为中心的小范围(推荐,更精确)
const lonRange = (east - west) * (queryWidth / canvas.clientWidth);
const latRange = (north - south) * (queryHeight / canvas.clientHeight);
bbox = `${longitude - lonRange/2},${latitude - latRange/2},${longitude + lonRange/2},${latitude + latRange/2}`;
x = queryWidth / 2;
y = queryHeight / 2;
3.4 两种 BBOX 策略对比
策略 优点 缺点
视图范围 一次覆盖整个屏幕,不会漏查 范围太大,可能返回多个要素,精度较差
点击点小范围 精度高,只返回附近要素 如果要素较大,可能点不中

四、常见问题与解决方案

问题 可能原因 解决方案
pickImageryLayerFeatures 返回空 enablePickFeatures 未设为 true 添加 enablePickFeatures: true
手动 GetFeatureInfo 无数据 BBOX 范围太小或太大 调整 queryWidth/Height 或改用视图范围模式
手动请求返回 HTML 而不是 JSON info_format 参数不对 设置为 application/json,服务端需支持
CQL_FILTER 不生效 GeoServer 版本或图层未配置查询 检查图层是否可查询,使用 CQL_FILTER 而非 cql_filter
跨域错误 CORS 未配置 配置 GeoServer CORS 或使用代理

五、选择建议

javascript 复制代码
需要筛选条件(CQL)?
    ├── 是 → 手动构造 GetFeatureInfo
    └── 否 → 优先用 pickImageryLayerFeatures
                ├── 成功 → 简单省事
                └── 失败/不准确 → 回退到手动构造

六、示例源码

加载

javascript 复制代码
  public async addWMS(Bounds?: number[], CQL?: any) {
    this.clearAllSelections(); //清理选中
    this.removeWmsLayer(); //清理图层
    const url =
      process.env.NODE_ENV === 'development' ? '地址' : `${window.location.origin}/地址`;
    const layers = '图层名';

    let rectangle: Cesium.Rectangle | undefined = undefined;
    // 只有传了 Bounds 并且长度为4,才赋值 rectangle
    if (Bounds && Bounds.length === 4) {
      rectangle = Cesium.Rectangle.fromDegrees(Bounds[0], Bounds[1], Bounds[2], Bounds[3]);
      this.rectangle = rectangle;
      console.log('使用自定义范围', rectangle);
    }
    let parameters: any = {
      format: 'image/png',
      version: '1.1.1',
      wmslayers: layers,
      transparent: true,
    };
    let finalCqlFilter: string;
    if (CQL && CQL.trim() !== '') {
      finalCqlFilter = `${CQL} AND del_flag = 0 AND split_status = 0`;
    } else {
      finalCqlFilter = 'del_flag = 0 AND split_status = 0';
    }
    parameters.CQL_FILTER = finalCqlFilter;
    // 👇 加这一行:保存当前 CQL
    this.currentWmsCql = finalCqlFilter;
    this.wmsImageryProvider = new Cesium.WebMapServiceImageryProvider({
      url,
      layers,
      parameters,
      crs: 'EPSG:4326',
      srs: 'EPSG:4326',
      tilingScheme: new Cesium.GeographicTilingScheme(),
      rectangle: rectangle!,
      minimumLevel: 0,
      maximumLevel: 26,
      enablePickFeatures: false  // 关闭默认拾取,完全手动控制
    });
    this.wmsLayer = this.viewer.imageryLayers.addImageryProvider(this.wmsImageryProvider);
    this.wmsLayer.terrainClamp = true;
    this.viewer.imageryLayers.raiseToTop(this.wmsLayer);
    this.wmsLayer.samplingHeight = 100;
    this.wmsImageryProvider.readyPromise.then(() => {
      if (rectangle) {
        this.viewer.zoomTo(
          this.wmsLayer,
          new Cesium.HeadingPitchRange(
            Cesium.Math.toRadians(-50),
            Cesium.Math.toRadians(-25),
            1000
          )
        );
      }
      // this.viewer.zoomTo(this.wmsLayer, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(-50), Cesium.Math.toRadians(-25), 1000));
    });
  }

点击查询

javascript 复制代码
  // 监听点击 WMS 获取属性信息
  public addWmsClickEvent() {
    const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);

    handler.setInputAction((event: any) => {
      // 1. 获取点击的笛卡尔坐标
      const cartesian = this.viewer.camera.pickEllipsoid(
        event.position,
        this.viewer.scene.globe.ellipsoid
      );
      if (!cartesian) return;

      // 2. 转经纬度
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      const lon = Cesium.Math.toDegrees(cartographic.longitude);
      const lat = Cesium.Math.toDegrees(cartographic.latitude);
      this.getWmsFeatureInfo(lon, lat, event.position, false);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  }
javascript 复制代码
  /**
 * 获取 WMS 图层的要素信息(带筛选条件)
 * @param longitude 经度
 * @param latitude 纬度
 * @param position 屏幕点击位置
 * @param useViewBounds 最后执行的方法
 */
  private async getWmsFeatureInfo(
    longitude: number,
    latitude: number,
    position: any,
    useViewBounds: boolean = false
  ) {
    try {
      // 1. 检查 WMS 图层是否存在
      if (!this.wmsImageryProvider || !this.wmsLayer) {
        console.warn('WMS 图层未加载');
        return null;
      }

      // 2. 获取当前视图范围用于计算 BBOX
      const scene = this.viewer.scene;
      const camera = scene.camera;
      const canvas = this.viewer.canvas;

      // 3. 动态计算查询窗口大小和 BBOX
      let bbox: string;
      let queryWidth: number;
      let queryHeight: number;
      let x: number;
      let y: number;


      console.log('采用的这边');

      // 方式2:以点击点为中心,动态计算小范围 BBOX(推荐,更精确)
      const rectangle = camera.computeViewRectangle();
      if (!rectangle) {
        console.warn('无法获取视图范围');
        return null;
      }

      const west = Cesium.Math.toDegrees(rectangle.west);
      const east = Cesium.Math.toDegrees(rectangle.east);
      const south = Cesium.Math.toDegrees(rectangle.south);
      const north = Cesium.Math.toDegrees(rectangle.north);
      const lonSpan = east - west;
      const latSpan = north - south;

      // 查询窗口大小
      queryWidth = 400;
      queryHeight = 400;

      // 计算查询范围:以点击点为中心
      const lonRange = lonSpan * (queryWidth / canvas.clientWidth);
      const latRange = latSpan * (queryHeight / canvas.clientHeight);

      const minLon = longitude - lonRange / 2;
      const maxLon = longitude + lonRange / 2;
      const minLat = latitude - latRange / 2;
      const maxLat = latitude + latRange / 2;

      bbox = `${minLon},${minLat},${maxLon},${maxLat}`;

      // 点击点位于图片中心
      x = queryWidth / 2;
      y = queryHeight / 2;
      // 4. 执行查询
      return this.executeWmsQuery(bbox, queryWidth, queryHeight, Math.round(x), Math.round(y), useViewBounds);

    } catch (error) {
      console.error('获取 WMS 要素信息失败:', error);
      return null;
    }
  }

  /**
   * 执行实际的 WMS GetFeatureInfo 请求
   */
  private async executeWmsQuery(
    bbox: string,
    width: number,
    height: number,
    x: number,
    y: number,
    useViewBounds: boolean
  ): Promise<any> {

    // 获取 WMS 服务配置
    const url = process.env.NODE_ENV === 'development'
      ? '地址'
      : `${window.location.origin}地址`;
    const layers = '图层名';

    // 构建 GetFeatureInfo 请求参数
    const params: any = {
      service: 'WMS',
      version: '1.1.1',
      request: 'GetFeatureInfo',
      layers: layers,
      query_layers: layers,
      bbox: bbox,
      width: width,
      height: height,
      x: x,
      y: y,
      info_format: 'application/json',
      srs: 'EPSG:4326'
    };

    // 关键:添加保存的 CQL 筛选条件
    if (this.currentWmsCql) {
      params.CQL_FILTER = this.currentWmsCql;
    }

    // 构建请求 URL
    const queryString = Object.entries(params)
      .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
      .join('&');
    const fullUrl = `${url}?${queryString}`;

    console.log('WMS GetFeatureInfo 请求:', fullUrl);

    try {
      const response = await fetch(fullUrl);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const contentType = response.headers.get('content-type');
      let data: any;

      if (contentType?.includes('application/json')) {
        data = await response.json();
      } else {
        data = await response.text();
      }

      // 打印查询结果
      console.log('WMS 查询结果:', data);

      // 如果是 GeoJSON 格式,打印要素数量
      if (data && data.type === 'FeatureCollection' && data.features) {
        console.log(`查询到 ${data.features.length} 个要素`);
        if (data.features.length > 0) {
          console.log('第一个要素属性:', data.features[0].properties);
          if (useViewBounds) {
            this.getDouBle(data.features[0]);
          } else {
            this.getClickLonLat(data.features[0]);
          }
        }
      }

      return data;

    } catch (error) {
      console.error('WMS GetFeatureInfo 请求失败:', error);
      return null;
    }
  }
相关推荐
byte轻骑兵3 天前
【HID】规范精讲[10]: 蓝牙HID设备的连接基石——GAP协议如何掌控发现、连接与安全
人工智能·人机交互·蓝牙·键盘·鼠标·hid
爱看书的小沐5 天前
【小沐学WebGIS】基于Cesium.JS与jsbsim联动三维飞行仿真(OpenGL、Cesium.js、Three.js)
c++·qt·three.js·opengl·cesium·jsbsim
byte轻骑兵5 天前
【HID】规范精讲[9]: SDP协议深度解析与实战应用
人工智能·人机交互·键盘·鼠标·hid
Vwms10 天前
2026 医药制造 WMS 选型指南:GMP 合规仓储管理系统怎么选
大数据·wms·wms选型
byte轻骑兵11 天前
【HID】规范精讲[6]: 蓝牙HID系统设计指南——从合规到体验的全维度要求
人机交互·蓝牙·键盘·鼠标·遥控·hid
白嫖叫上我12 天前
Cesium广告牌之自定义封装label
cesium
byte轻骑兵13 天前
【HID】规范精讲[5]: 蓝牙 HID Boot Protocol Requirements 详解
人机交互·蓝牙·键盘·鼠标·hid
byte轻骑兵15 天前
【HID】规范精讲[4]: 蓝牙HID传输机制——无线数据的传递规则与底层逻辑
人工智能·人机交互·键盘·鼠标·hid
ximagine21 天前
【26年4月外设鼠标推荐清单】教父级游戏鼠标选购指南!18款鼠标从竞技上分到拯救鼠标手!
科技·游戏·计算机外设·智能路由器·鼠标·ximagine