WMS + WFS 框选查询完整知识点总结

WFS (Web Feature Service) 查询完整知识点


一、WFS 核心概念

概念说明
WFSOGC 标准服务,用于返回矢量要素数据(GeoJSON/GML),而非图片
WMS vs WFSWMS 返回图片(显示用),WFS 返回数据(查询/分析用)
GeoJSONWFS 最常用的输出格式,Cesium 可直接加载
GMLWFS 默认输出格式(XML 结构),体积大,不推荐前端使用

二、WFS 核心操作

操作说明是否必需
GetCapabilities获取服务的元信息(支持的操作、图层、输出格式等)✅ 必需
DescribeFeatureType获取要素类型的字段结构(列名、类型)✅ 必需
GetFeature查询并返回要素数据(核心操作)✅ 必需
Transaction创建、更新、删除要素(WFS-T 事务操作)❌ 可选

三、WFS 请求参数详解 (GetFeature)

const params = {
    // 基础参数
    service: 'WFS',           // 固定值,服务类型
    version: '1.1.0',         // WFS 版本(1.0.0 / 1.1.0 / 2.0.0)
    request: 'GetFeature',    // 请求类型
    
    // 图层参数
    typeName: 'workspace:layer',  // 工作区:图层名
    
    // 输出参数
    outputFormat: 'json',     // 输出格式(json / application/json / gml)
    srsName: 'EPSG:4326',     // 坐标系
    
    // 过滤参数
    CQL_FILTER: "...",        // CQL 过滤条件
    bbox: "...",              // 矩形过滤(与 CQL_FILTER 互斥!)
    propertyName: "id,name",  // 指定返回的字段
    
    // 分页参数
    maxFeatures: 100,         // 最大返回数量
    startIndex: 0,            // 起始索引(分页用)
    sortBy: "id ASC",         // 排序
};

四、WFS 版本对比

特性WFS 1.0.0WFS 1.1.0WFS 2.0.0
默认输出格式GML2GML3GML3.2
GeoJSON 支持需插件需插件需插件
坐标顺序(经度, 纬度)(纬度, 经度) ⚠️可配置
BBOX 参数支持支持支持
CQL_FILTER支持(GeoServer 扩展)支持支持
分页参数maxFeaturesmaxFeaturescount + startIndex

⚠️ 重要:WFS 1.1.0 的坐标顺序是 (纬度, 经度),这是最常见的坑!


五、CQL 空间查询函数

函数语法说明适用场景
BBOXBBOX(geom, minY, minX, maxY, maxX)矩形范围查询框选查询(推荐)
INTERSECTSINTERSECTS(geom, POLYGON(...))相交查询精确空间相交
DWITHINDWITHIN(geom, POINT(...), distance, units)距离范围内查询圆形范围、附近查询
CONTAINSCONTAINS(geom, POINT(...))包含查询判断点是否在多边形内
WITHINWITHIN(geom, POLYGON(...))被包含查询判断要素是否在范围内
TOUCHESTOUCHES(geom1, geom2)边界接触相邻要素查询

六、WFS 请求完整示例

// URL 方式
const url = '/geoserver/jiesen/wfs?service=WFS&version=1.1.0&request=GetFeature&typeName=jiesen:tb_list&outputFormat=json';

// 代码方式
const params = new URLSearchParams();
params.append('service', 'WFS');
params.append('version', '1.1.0');
params.append('request', 'GetFeature');
params.append('typeName', 'jiesen:tb_list');
params.append('outputFormat', 'json');
示例 2:属性过滤(CQL)
// 过滤:task_id 等于某个值,且 del_flag = 0
const cql = "task_id='fc9cb975-8570-4249-9454-3703c54bb65f' AND del_flag = 0";
params.append('CQL_FILTER', cql);
示例 3:空间过滤(BBOX - 正确坐标顺序)
// ⚠️ 注意:BBOX 参数顺序是 (纬度, 经度)
const bboxCondition = `BBOX(tb_shape, ${minLat}, ${minLon}, ${maxLat}, ${maxLon})`;
params.append('CQL_FILTER', bboxCondition);
示例 4:混合过滤(属性 + 空间)
const cqlFilter = "task_id='xxx' AND del_flag = 0";
const bboxCondition = `BBOX(tb_shape, ${minLat}, ${minLon}, ${maxLat}, ${maxLon})`;
params.append('CQL_FILTER', `${cqlFilter} AND ${bboxCondition}`);
示例 5:完整 WFS 查询代码
async function queryWFS(minLon, minLat, maxLon, maxLat, taskId) {
    const wfsUrl = '/geoserver/jiesen/wfs';
    
    const params = new URLSearchParams();
    params.append('service', 'WFS');
    params.append('version', '1.1.0');
    params.append('request', 'GetFeature');
    params.append('typeName', 'jiesen:tb_list');
    params.append('outputFormat', 'json');
    params.append('srsName', 'EPSG:4326');
    params.append('CQL_FILTER', `task_id='${taskId}' AND BBOX(tb_shape, ${minLat}, ${minLon}, ${maxLat}, ${maxLon})`);
    
    const response = await fetch(`${wfsUrl}?${params.toString()}`);
    const geojson = await response.json();
    return geojson;
}

七、常见错误及解决方案

错误信息原因解决方案
Illegal property name: geom几何字段名错误使用正确的字段名(如 tb_shape
bbox and cql_filter both specified同时使用 bbox 参数和 CQL_FILTER将 bbox 条件合并到 CQL_FILTER 中
Could not parse CQL filter listCQL 语法错误检查函数名、括号、坐标顺序
InvalidParameterValue + Illegal property name字段名不存在先调用 DescribeFeatureType 确认字段
Operation on mixed SRID geometriesSRID 不一致使用 BBOX 而非 DWITHIN
Function not found使用了不支持的函数使用标准 CQL 函数,不加 ST_ 前缀
outputFormat not supported输出格式不支持检查 GetCapabilities 中的 AcceptFormats
返回 XML 错误但不是 JSON请求参数错误检查 URL 参数是否正确编码

八、WFS vs WMS 选择指南

需求推荐方案说明
显示地图底图WMS返回图片,性能好
点击查询属性WMS GetFeatureInfo简单点查询,返回属性
框选查询WFS返回完整矢量数据,可高亮
获取几何数据进行分析WFS返回 GeoJSON 几何
数据下载/导出WFS返回矢量格式
查询后高亮显示WFS可加载为 GeoJsonDataSource
编辑/更新数据WFS-T事务操作(需要配置)

九、GeoServer 配置检查清单

检查项操作预期结果
WFS 服务启用服务 → WFS → 配置 → 勾选"启用服务"✅ 服务状态已启用
输出格式检查 GetCapabilities 中的 outputFormat✅ 包含 json
图层 WFS 发布数据 → 图层 → 检查 WFS 服务已勾选✅ WFS 复选框已勾选
几何字段类型要素类型详细信息中查看几何字段✅ 类型为 Geometry/Point/LineString/Polygon
空间索引PostGIS 中检查 GIST 索引✅ 存在 tb_shape 的索引
CORS 配置web.xml 中配置 CORS 过滤器✅ 允许跨域请求

十、最佳实践总结

  1. 版本选择:优先使用 WFS 1.1.0 + outputFormat=json

  2. 坐标顺序:牢记 WFS 1.1.0 的 BBOX 是 (纬度, 经度) 顺序

  3. 字段确认:必先用 DescribeFeatureType 确认几何字段名

  4. CQL 一致性:WMS 和 WFS 使用完全相同的 CQL_FILTER

  5. 分步测试:先测试无过滤 → 属性过滤 → 空间过滤 → 混合过滤

  6. 错误处理:捕获 JSON 解析异常,检查是否为 XML 错误报告

  7. 性能优化:使用 BBOX 而非复杂几何函数,添加 maxFeatures 限制

  8. 数据转换:WFS 返回的 GeoJSON 属性名是下划线格式(task_id),需转换为驼峰(taskId

WMS + WFS 框选查询完整知识点总结

一、核心概念

概念说明
WMS (Web Map Service)返回图片格式的地图数据,用于显示
WFS (Web Feature Service)返回矢量格式的要素数据,用于查询
GeoJSONWFS 可返回的矢量数据格式,Cesium 可直接加载
CQL (Contextual Query Language)扩展的查询语言,用于在 WMS/WFS 中过滤数据
BBOX矩形空间查询函数,用于框选

二、架构设计

text

┌─────────────────────────────────────────────────────────────┐
│                      前端 Cesium 应用                        │
├─────────────────────────────────────────────────────────────┤
│  WMS 图层显示              │  WFS 框选查询                   │
│  ┌─────────────────┐      │  ┌─────────────────┐           │
│  │ addWMS()        │      │  │ initBoxSelect() │           │
│  │ - 加载瓦片图层   │      │  │ - 绘制选框      │           │
│  │ - 应用 CQL 过滤 │      │  │ - 获取矩形范围  │           │
│  │ - 显示地图      │      │  │ - 调用 WFS 查询 │           │
│  └────────┬────────┘      │  └────────┬────────┘           │
│           │                │           │                     │
│           ▼                │           ▼                     │
│  ┌─────────────────┐      │  ┌─────────────────┐           │
│  │   相同 CQL 过滤  │◄─────┼──│ queryWFS...()   │           │
│  └─────────────────┘      │  │ - BBOX 空间过滤 │           │
│                           │  │ - 返回 GeoJSON  │           │
│                           │  └─────────────────┘           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │    GeoServer    │
                    │  /jiesen/wms    │
                    │  /jiesen/wfs    │
                    └─────────────────┘

三、WMS 图层加载代码

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

    // 设置矩形范围
    let rectangle: Cesium.Rectangle | undefined = undefined;
    if (Bounds && Bounds.length === 4) {
        rectangle = Cesium.Rectangle.fromDegrees(Bounds[0], Bounds[1], Bounds[2], Bounds[3]);
        this.rectangle = rectangle;
    }

    // 构建 CQL 过滤条件
    let parameters: any = {
        format: 'image/png',
        version: '1.1.1',
        layers: layers,
        transparent: true,
    };
    
    let finalCqlFilter: string;
 
    parameters.CQL_FILTER = finalCqlFilter;
    
    // ✅ 保存当前 CQL,供 WFS 查询使用
    this.currentWmsCql = finalCqlFilter;

    this.wmsImageryProvider = new Cesium.WebMapServiceImageryProvider({
        url,
        layers,
        parameters,
        crs: 'EPSG:4326',   // WMS 1.3.0 使用 crs
        srs: 'EPSG:4326',   // WMS 1.1.1 使用 srs
        tilingScheme: new Cesium.GeographicTilingScheme(),
        rectangle: rectangle!,
        minimumLevel: 0,
        maximumLevel: 26,
        enablePickFeatures: false   // 关闭默认拾取,使用 WFS 查询
    });
    
    this.wmsLayer = this.viewer.imageryLayers.addImageryProvider(this.wmsImageryProvider);
    this.wmsLayer.terrainClamp = true;
    this.viewer.imageryLayers.raiseToTop(this.wmsLayer);
    this.wmsLayer.samplingHeight = 100;
    
}

四、框选交互代码(带了一个框,可以自行去掉)

  public initBoxSelect(callback?: (rect: { minLon: number; minLat: number; maxLon: number; maxLat: number; corners: any }) => void) {
    this.handler != null && this.handler.destroy();
    console.log('this.doubleHandler', this.doubleHandler);
    this.offGon();
    // 销毁旧的框选处理器
    this.boxHandler && this.boxHandler.destroy();
    this.boxHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);

    let isDrawing = false;
    let startCarto: Cesium.Cartographic | null = null;

    // 移除上次临时显示的矩形
    if (this.activeShape) {
      this.viewer.entities.remove(this.activeShape);
      this.activeShape = null;
    }

    // 左键:第一次左键记录起点,第二次左键结束并回调
    this.boxHandler.setInputAction(async (e: Cesium.ScreenSpaceEventHandler.PositionedEvent) => {
      const position = e?.position;
      if (!position) return;
      const ray = this.viewer.camera.getPickRay(position);
      if (!ray) return;
      const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
      if (!cartesian) return;
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      const lon = Cesium.Math.toDegrees(cartographic.longitude);
      const lat = Cesium.Math.toDegrees(cartographic.latitude);

      if (!isDrawing) {
        // 开始绘制
        startCarto = cartographic;
        isDrawing = true;
        const coords = [lon, lat, lon, lat, lon, lat, lon, lat, lon, lat];
        this.activeShape = this.viewer.entities.add({
          polygon: {
            hierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(coords)),
            material: Cesium.Color.YELLOW.withAlpha(0.25),
            outline: true,
            outlineColor: Cesium.Color.YELLOW,
          },
        });
      } else {
        // 结束绘制
        isDrawing = false;
        const startLon = Cesium.Math.toDegrees(startCarto!.longitude);
        const startLat = Cesium.Math.toDegrees(startCarto!.latitude);
        const minLon = Math.min(startLon, lon);
        const maxLon = Math.max(startLon, lon);
        const minLat = Math.min(startLat, lat);
        const maxLat = Math.max(startLat, lat);
        const corners = [
          { lon: minLon, lat: minLat },
          { lon: maxLon, lat: minLat },
          { lon: maxLon, lat: maxLat },
          { lon: minLon, lat: maxLat },
        ];
        // 回调结果
        try {
          if (this.activeShape) {
            this.viewer.entities.remove(this.activeShape);
            this.activeShape = null;
          }
          console.log('点到了', { minLon, minLat, maxLon, maxLat, corners });
          // 在框选结束的回调位置,添加这行
          const data = await this.queryWFSByRectangle(minLon, minLat, maxLon, maxLat);
          callback && callback(data);
        } catch (err) {
          console.error('框选回调错误', err);
        }
        // 销毁事件处理器(如需保留图形,可注释掉下面两行)
        this.boxHandler && this.boxHandler.destroy();
        this.boxHandler = null;
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    // 鼠标移动时更新矩形显示
    this.boxHandler.setInputAction((e: Cesium.ScreenSpaceEventHandler.MotionEvent) => {
      if (!isDrawing || !startCarto) return;
      const pos = e.endPosition;
      const ray = this.viewer.camera.getPickRay(pos);
      if (!ray) return;
      const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
      if (!cartesian) return;
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      const lon = Cesium.Math.toDegrees(cartographic.longitude);
      const lat = Cesium.Math.toDegrees(cartographic.latitude);
      const startLon = Cesium.Math.toDegrees(startCarto.longitude);
      const startLat = Cesium.Math.toDegrees(startCarto.latitude);
      const minLon = Math.min(startLon, lon);
      const maxLon = Math.max(startLon, lon);
      const minLat = Math.min(startLat, lat);
      const maxLat = Math.max(startLat, lat);
      const coords = [minLon, minLat, maxLon, minLat, maxLon, maxLat, minLon, maxLat, minLon, minLat];
      if (this.activeShape) {
        (this.activeShape.polygon as any).hierarchy = new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(coords));
      } else {
        this.activeShape = this.viewer.entities.add({
          polygon: {
            hierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(coords)),
            material: Cesium.Color.YELLOW.withAlpha(0.25),
            outline: true,
            outlineColor: Cesium.Color.YELLOW,
          },
        });
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }

重点是   

 const data = await this.queryWFSByRectangle(minLon, minLat, maxLon, maxLat);
          callback && callback(data);

五、WFS 框选查询代码

async queryWFSByRectangle(minLon: number, minLat: number, maxLon: number, maxLat: number) {
    const wfsUrl = process.env.NODE_ENV === 'development'
        ? '/地址/wfs'
        : `${window.location.origin}/地址/wfs`;

    // ✅ 使用与 WMS 相同的 CQL 过滤条件
    const cqlFilter = this.currentWmsCql;

    // ✅ 关键:BBOX 的正确顺序是 (纬度, 经度)
    const bboxCondition = `BBOX(tb_shape, ${minLat}, ${minLon}, ${maxLat}, ${maxLon})`;

    const params = new URLSearchParams();
    params.append('service', 'WFS');
    params.append('version', '1.1.0');
    params.append('request', 'GetFeature');
    params.append('typeName', 'jiesen:tb_list');
    params.append('outputFormat', 'json');
    params.append('srsName', 'EPSG:4326');
    params.append('CQL_FILTER', `${cqlFilter} AND ${bboxCondition}`);

    const url = `${wfsUrl}?${params.toString()}`;

    try {
        const response = await fetch(url);
        const geojson = await response.json();
        console.log(`✅ 框选范围内找到 ${geojson.features?.length || 0} 个要素`);
        return geojson;
    } catch (error) {
        console.error('WFS请求失败:', error);
        return { features: [] };
    }
}

七、调用方代码

实例类后

		wayLineShowMap.initBoxSelect(async (rect: { minLon: number; minLat: number; maxLon: number; maxLat: number; corners: number[][] }) => {
			// 调用 WFS 查询获取 GeoJSON
			console.log('rect', rect);}}

八、关键注意事项

序号注意事项说明
1CQL 一致性WMS 和 WFS 必须使用完全相同的 CQL_FILTER,否则查询结果不一致
2BBOX 坐标顺序WFS 1.1.0 要求 BBOX(geom, minY, minX, maxY, maxX) 即 纬度在前,经度在后
3几何字段名几何字段名不是固定的 geom,需根据实际配置确定(如 tb_shape
4事件清理每次 initBoxSelect 前必须销毁旧的 boxHandler,回调后也要销毁
5坐标系使用 EPSG:4326,确保 WMS 和 WFS 的 srsName/crs 一致
6CORS 配置GeoServer 需要配置跨域,否则前端无法直接请求
7防抖处理框选回调可能重复触发,需要添加防抖或立即销毁事件
8错误处理WFS 返回 XML 错误时,需要解析错误信息并妥善处理
9输出格式WFS 请求需指定 outputFormat=json 以返回 GeoJSON
10版本兼容WMS 1.1.1 使用 srs,WMS 1.3.0 使用 crs
BBOX 坐标顺序WFS 1.1.0 要求 BBOX(geom, minY, minX, maxY, maxX) 即 纬度在前,经度在后
geom 的含义

在你的代码中,geom 是一个占位符,需要替换成你实际的几何字段名


一、geom 是什么?
概念说明
geom数据库表中存储几何数据的列名(字段名)
几何字段存储点、线、面等空间数据的字段
不是固定名称每个图层的几何字段名可能不同

二、如何找到你实际的几何字段名?

方法1:通过 GeoServer 界面查看

  1. 登录 GeoServer → 数据 → 图层

  2. 找到 你的图层名,点击图层名称

  3. 向下滚动到 "要素类型详细信息" 表格

  4. 找到类型为 Geometry 的行,"属性"列的值就是几何字段名


九、常见错误及解决方案

错误信息原因解决方案
Illegal property name: geom几何字段名错误使用正确的字段名(如 tb_shape
bbox and cql_filter both specified同时使用 bbox 和 CQL 参数将 bbox 条件合并到 CQL_FILTER 中
Could not parse CQL filterCQL 语法错误检查 BBOX 坐标顺序和函数名称
SRID mismatch几何 SRID 不一致使用 BBOX 而非 DWITHIN
URL is not validDemo requests 工具使用错误直接用浏览器 URL 测试
回调执行两次事件未正确销毁回调后立即 destroy()

十、数据流向图

text

用户点击地图
    │
    ▼
initBoxSelect() 开启框选
    │
    ├── 第一次点击 ──► 记录起点,显示黄色半透明矩形
    │
    ├── 鼠标移动 ────► 实时更新矩形大小
    │
    └── 第二次点击 ──► 计算矩形范围
              │
              ▼
        callback({ minLon, minLat, maxLon, maxLat })
              │
              ▼
        queryWFSByRectangle()
              │
              ├── 使用 currentWmsCql(与 WMS 一致)
              ├── 构建 BBOX 条件(纬度在前)
              └── 发送 WFS 请求
                      │
                      ▼
                GeoServer
                      │
                      ▼
                GeoJSON 数据
                      │
                      ▼
        convertWfsToDataFormat()
                      │
                      ▼
        后端统一格式数据
                      │
                      ▼
        更新 selectedAreas,显示选中结果

内容概要:本文档详细介绍了基于直驱永磁同步发电机(PMSG)的1.5MW风力发电系统在Simulink环境下的建模与仿真全过程,涵盖了风力机空气动力学模型、PMSG电磁特性建模、不可控整流与逆变电路、直流环节、空间矢量脉宽调制(SVPWM)技术以及核心控制策略的设计。重点实现了最大功率点跟踪(MPPT)控制以提升风能捕获效率,并构建了电压外环与电流内环协同工作的双闭环控制系统,通过仿真验证了系统在不同风速条件下稳定运行的能力及动态响应性能。; 适合人群:适用于具备电力系统、电机控制理论基础及Simulink仿真操作经验的研究生、科研人员和从事新能源发电系统开发的工程技术人员;特别适合正在进行风电系统建模、控制算法研究或完成相关毕业设计的专业人士。; 使用场景及目标:①深入理解直驱式PMSG风力发电系统的整体架构与工作机理;②掌握从物理部件建模到控制策略实现的完整Simulink仿真流程;③学习并复现MPPT控制、双闭环控制等关键技术方案;④为后续开展低电压穿越、并网稳定性分析、故障诊断等高级课题提供可靠的仿真平台支撑。; 阅读建议:建议结合Matlab/Simulink软件动手实践,逐模块搭建模型,重点关注各控制环节的参数设计与调试方法,同时可参照文中提供的其他风电相关资源进行拓展学习与对比分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值