一、两种查询方式对比
| 特性 | 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/json 或 text/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;
}
}