OpenLayers WFS 添加 CQL 过滤条件

WebGIS
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

在 OpenLayers 中给 WFS 添加 CQL 过滤条件,核心做法是手动把 GeoServer 的 CQL_FILTER(或 cql_filter)参数拼进 GetFeature 请求,而不是依赖原生过滤器对象。截至 2026 年 11 月,OpenLayers 稳定版 v10.11.0 通过 ol/format/filter 支持 OGC Filter Encoding(XML),但不直接把 CQL 字符串当过滤器;面对 GeoServer 时,自定义 VectorSource 的 loader 拼接 URL 或 POST 参数即可完成服务端过滤。这样写出来比完整 XML Filter 更短,也便于动态改条件。

CQL 过滤后只返回符合条件的 WFS 要素示意

为什么优先用 CQL_FILTER 而不是原生 Filter

要解决的问题很实际:图层数据量大,客户端拉全量再过滤既慢又费流量,必须让服务器只返回符合条件的要素。GeoServer 把 CQL/ECQL 做成 WFS 的 vendor 参数,一条类似 SQL 的字符串就能覆盖属性比较、LIKE、AND/OR、BBOX、INTERSECTS、DWITHIN 等常见需求。OpenLayers 官方示例走的是 writeGetFeature + XML Filter,兼容性更好,但语法冗长;如果你的服务确定是 GeoServer(或兼容实现),CQL 更省事。非 GeoServer 服务器可能直接忽略该参数,这时应改回标准 OGC Filter。

  • 适合场景:属性条件多、需要动态切换、空间与属性组合过滤、结果要立刻刷新。
  • 不适合场景:跨多种 WFS 实现、必须严格遵守标准 Filter Encoding、过滤器极长且只能用 GET。
  • 与客户端过滤对比:服务端 CQL 能配合索引,通常比拉完再在浏览器里筛快得多。

若你同时在做要素交互,过滤后的图层仍可正常绑定点击与坐标拾取,具体写法可参考本站OpenLayers 图层点击事件和坐标拾取。

用自定义 loader 拼接 CQL_FILTER 的步骤

目标:让 VectorSource 在每次加载(含 bbox strategy)时都带上你的 CQL 字符串,并用 GeoJSON 读回要素。

  1. 准备基础 WFS 地址与固定参数:service=WFS、version(常用 1.1.0 或 2.0.0)、request=GetFeature、typename、outputFormat=application/json、srsname(与地图视图一致,如 EPSG:3857)。
  2. 把业务条件写成 ECQL 字符串,例如 status=’正常’ AND name LIKE ‘城%’,或空间条件 BBOX(the_geom, xmin,ymin,xmax,ymax,’EPSG:3857’)。字符串值加单引号,点几何写成 POINT(x y)(坐标之间空格,不要逗号)。
  3. 在 loader 里用 encodeURIComponent 编码整段 CQL,拼到 URL:&CQL_FILTER= + 编码结果。若同时用了标准 bbox 参数,容易触发“只能有一个空间过滤器”的限制,建议把空间条件写进 CQL,或干脆去掉额外的 bbox 参数。
  4. 用 fetch 拿到响应后,new GeoJSON().readFeatures(data, {featureProjection: map.getView().getProjection()}),再 source.addFeatures。strategy 可继续用 bbox 或 all,按数据量选择。
  5. 验收:打开浏览器网络面板,确认请求里确实出现编码后的 CQL_FILTER;地图上只应出现符合条件的要素;控制台无解析错误。

长条件或特殊字符多时,改用 POST + URLSearchParams 把 CQL_FILTER 放进 body,避免 URL 长度限制。官方用 XML Filter 的写法见 OpenLayers WFS GetFeature 示例,可作对照。

OpenLayers 官方 vector-wfs-getfeature 示例页面截图
OpenLayers 官方 vector-wfs-getfeature 示例页面截图(截图来源:openlayers.org)

动态改条件后如何刷新图层

过滤条件常来自下拉框、输入框或地图交互。改完字符串后必须触发源重新加载,否则画面仍是旧数据。

  1. 把当前 CQL 存在变量或闭包里,loader 每次读取最新值。
  2. 用户确认条件后执行 vectorSource.clear(true)。第二个参数 true 会清空并重新触发 loader。
  3. 若用了 tile/bbox strategy,平移缩放也会再次请求;确认新请求里的 CQL 已更新。
  4. 可选:加 loading 监听,过滤刷新时给用户明确反馈,避免误以为卡死。

结果很密时,可在过滤后再做点聚合,减少绘制压力,做法见本站OpenLayers 点聚合怎么实现及代码逻辑。

语法、冲突与性能上要注意什么

CQL 不生效时,优先查这几类问题,而不是先怀疑 OpenLayers 版本。

问题类型 常见表现 处理办法
与 BBOX 冲突 服务返回异常或忽略过滤 去掉标准 bbox,或把范围写进 CQL_FILTER
几何写法错误 解析失败 / 空结果 POINT 用空格分隔坐标;检查 CRS
未 URL 编码 AND、空格、中文被截断 整段 CQL 使用 encodeURIComponent
字段名或类型不对 条件看似正确却无要素 对照 DescribeFeatureType;字符串加引号
结果过大 加载慢、浏览器卡顿 加 maxFeatures/count;用 PropertyName 限字段

GeoServer 对 CQL/ECQL 的说明与 vendor 参数列表可在其文档核对:GeoServer WFS vendor 参数说明。属性一般要放在比较符左侧;FeatureID 类过滤通常不适合直接塞进 CQL,应换标准 Filter。投影、typename、outputFormat 三者任一不匹配,都会表现为“过滤成功但看不见”。

  • 验收标准:网络请求含正确 CQL;返回要素数量与预期一致;改条件后 clear 能立刻反映;平移地图不会把过滤条件冲掉(除非你故意按视口重写 BBOX)。
  • 跨域:优先让服务开 CORS;旧环境才考虑 JSONP(format_options=callback)。
  • 版本:本文写法面向 OpenLayers 10.x(含 v10.11.0);OL2 时代的 Format.CQL 已不适用。

常见问题

CQL_FILTER 写了为什么完全没效果?

先看服务是否支持该 vendor 参数(主要是 GeoServer)。再检查是否同时带了标准 BBOX/FILTER,导致服务按规范只认其中一个。最后看编码和字段名是否正确。

过滤后平移地图又变回全量数据?

loader 里如果每次都重新拼 URL,却把 CQL 写死成旧值,或 clear 后没有读到最新变量,就会回退。把 CQL 放到可更新的变量,并在改条件后 clear(true)。

POINT 或 DWITHIN 总是报解析错误?

坐标之间必须是空格而不是逗号,例如 POINT(120.1 30.2)。距离单位、几何字段名也要和图层定义一致。

能不能不用 CQL,改用官方 Filter 模块?

能。用 ol/format/filter 组装 equalTo、like、and、bbox 等,再 WFS.writeGetFeature 生成 XML 后 POST。跨服务器兼容更好,但代码更长;GeoServer 场景下 CQL 通常更省事。

给 WFS 加 CQL,本质是自己掌控 GetFeature 参数,让 GeoServer 在服务端完成筛选,再由 OpenLayers 用 GeoJSON 读入。优先用自定义 loader 拼 CQL_FILTER,改条件后 clear(true) 刷新,并避开与标准 BBOX 的冲突。建议先在固定条件上验收请求与要素数量,再接到界面控件做动态过滤;若服务不是 GeoServer,直接改用官方 OGC Filter 写法更稳妥。