GeoTools 实战(四):空间查询与 CQL 过滤全解析(查询篇)
目录一、前言为什么需要空间查询二、核心概念速览2.1 CQL vs ECQL2.2 FilterFactory22.3 查询是懒加载的三、Maven 依赖四、完整可运行代码4.1 完整代码五、代码逐段讲解5.1 属性查询ECQL.toFilter5.2 BBOX 查询地图框选5.3 INTERSECTS 查询空间关系5.4 DWITHIN 查询距离查询5.5 组合查询AND / OR六、运行结果示例七、常见坑位与注意事项7.1 DWITHIN 的单位陷阱7.2 BBOX 与 INTERSECTS 的区别7.3 属性名大小写敏感7.4 ECQL 字符串中的引号转义7.5 查询结果为空但明明有数据八、工程实践建议九、小结一、前言为什么需要空间查询在前面的文章中我们已经掌握了Shapefile 的读取与写入空间数据格式互转Shp / GeoJSON / WKT坐标系与投影转换但在实际工程中仅仅“能读写”是不够的。你一定会遇到这些需求查询某个范围内的所有要素地图框选查询距离某点 500 米内的所有 POI周边搜索查询人口大于 1000 万的城市属性过滤查询与某条道路相交的所有地块空间关系如果只用 Java 循环遍历 if 判断不仅代码丑陋而且性能极差。GeoTools 提供了 CQL / ECQL 查询语言让你可以像写 SQL 一样做空间查询而且查询是由 DataStore 引擎优化的性能远超内存遍历。二、核心概念速览2.1 CQL vs ECQLCQLCommon Query Language是 OGC 定义的一种面向文本的空间查询语言。GeoTools 对其做了扩展称为 ECQLExtended CQL支持更丰富的语法。简单对比// CQL属性过滤FilterfilterCQL.toFilter(POPULATION 10000000);// ECQL支持更多空间函数FilterfilterECQL.toFilter(DWITHIN(the_geom, POINT(116.4 39.9), 500, kilometers));2.2 FilterFactory2除了用字符串写 CQLGeoTools 还提供了 FilterFactory2 这个 API可以编程式构建查询条件避免字符串拼接的麻烦也更类型安全。FilterFactory2ffCommonFactoryFinder.getFilterFactory2();Filterfilterff.bbox(ff.property(the_geom),115,38,120,42,EPSG:4326);2.3 查询是懒加载的GeoTools 的 FeatureCollection 是“懒加载”的调用getFeatures(filter)时并不会立刻把所有数据读入内存而是返回一个游标真正遍历时才会从数据源读取。这对大文件非常重要。三、Maven 依赖除了之前的基础依赖外本篇需要额外引入gt-cqldependencygroupIdorg.geotools/groupIdartifactIdgt-cql/artifactIdversion29.3/version/dependencydependencygroupIdorg.geotools/groupIdartifactIdgt-shapefile/artifactIdversion29.3/version/dependencydependencygroupIdorg.geotools/groupIdartifactIdgt-epsg-hsql/artifactIdversion29.3/version/dependencydependencygroupIdorg.locationtech.jts/groupIdartifactIdjts-core/artifactIdversion1.19.0/version/dependency四、完整可运行代码以下代码基于 GeoTools 29.3 JTS 1.19.0使用你提供的测试数据包含 5 种典型查询场景。4.1 完整代码importorg.geotools.data.DataStore;importorg.geotools.data.DataStoreFinder;importorg.geotools.data.FeatureSource;importorg.geotools.factory.CommonFactoryFinder;importorg.geotools.feature.FeatureCollection;importorg.geotools.feature.FeatureIterator;importorg.geotools.filter.text.ecql.ECQL;importorg.locationtech.jts.geom.*;importorg.locationtech.jts.io.WKTReader;importorg.opengis.feature.simple.SimpleFeature;importorg.opengis.feature.simple.SimpleFeatureType;importorg.opengis.filter.Filter;importorg.opengis.filter.FilterFactory2;importjava.io.File;importjava.io.Serializable;importjava.nio.charset.StandardCharsets;importjava.util.HashMap;importjava.util.Map;publicclassGeoToolsSpatialQueryDemo{privatestaticfinalGeometryFactoryGFnewGeometryFactory();privatestaticfinalFilterFactory2FFCommonFactoryFinder.getFilterFactory2();publicstaticvoidmain(String[]args)throwsException{StringshpPathD:/testdata/testshp/ne_110m_admin_0_tiny_countries/ne_110m_admin_0_tiny_countries.shp;DataStoredataStorenull;try{dataStoreopenShapefile(shpPath);StringtypeNamedataStore.getTypeNames()[0];FeatureSourceorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturesourcedataStore.getFeatureSource(typeName);// 1. 属性查询CQLqueryByAttribute(source);// 2. BBOX 查询地图框选queryByBBox(source);// 3. 空间关系查询INTERSECTSqueryByIntersects(source);// 4. 距离查询DWITHINqueryByDistance(source);// 5. 组合查询ANDqueryByCombined(source);}finally{if(dataStore!null){dataStore.dispose();}}}// 工具方法 privatestaticDataStoreopenShapefile(Stringpath)throwsException{MapString,SerializableparamsnewHashMap();params.put(url,newFile(path).toURI().toURL());params.put(charset,StandardCharsets.UTF_8.name());returnDataStoreFinder.getDataStore(params);}// 查询示例 // 1. 属性查询privatestaticvoidqueryByAttribute(FeatureSourceorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturesource)throwsException{FilterfilterECQL.toFilter(NAME LIKE %Bah%);FeatureCollectionorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturefcsource.getFeatures(filter);System.out.println(【属性查询】NAME 包含 Bah 的数量: fc.size());printFeatures(fc);}// 2. BBOX 查询地图框选privatestaticvoidqueryByBBox(FeatureSourceorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturesource)throwsException{FilterfilterFF.bbox(FF.property(the_geom),50,-30,120,30,// minX, minY, maxX, maxYEPSG:4326);FeatureCollectionorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturefcsource.getFeatures(filter);System.out.println(\n【BBOX 查询】包围盒内要素数量: fc.size());printFeatures(fc);}// 3. INTERSECTS 查询privatestaticvoidqueryByIntersects(FeatureSourceorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturesource)throwsException{Polygonpolygon(Polygon)newWKTReader(GF).read(POLYGON ((-24.30650713186804524 11.15202787725273481, -24.01438682373617439 -26.82361217989013369, 22.43274216923086328 -30.91329649373626864, 23.60122340175831823 9.10718572032966733, 23.60122340175831823 9.10718572032966733, -24.30650713186804524 11.15202787725273481)));FilterfilterFF.intersects(FF.property(the_geom),FF.literal(polygon));FeatureCollectionorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturefcsource.getFeatures(filter);System.out.println(\n【INTERSECTS 查询】相交要素数量: fc.size());printFeatures(fc);}// 4. 距离查询DWITHINprivatestaticvoidqueryByDistance(FeatureSourceorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturesource)throwsException{PointcenterGF.createPoint(newCoordinate(116.4,39.9));FilterfilterFF.dwithin(FF.property(the_geom),FF.literal(center),500,// 500 公里kilometers);FeatureCollectionorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturefcsource.getFeatures(filter);System.out.println(\n【DWITHIN 查询】500km 内要素数量: fc.size());printFeatures(fc);}// 5. 组合查询privatestaticvoidqueryByCombined(FeatureSourceSimpleFeatureType,SimpleFeaturesource)throwsException{FilterspatialFF.bbox(FF.property(the_geom),50,-30,120,30,EPSG:4326);FilterattributeECQL.toFilter(NAME LIKE %s%);FiltercombinedFF.and(spatial,attribute);FeatureCollectionorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturefcsource.getFeatures(combined);System.out.println(\n【组合查询】BBOX NAME 数量: fc.size());printFeatures(fc);}// 打印辅助privatestaticvoidprintFeatures(FeatureCollectionorg.opengis.feature.simple.SimpleFeatureType,SimpleFeaturefc){try(FeatureIteratorSimpleFeatureitfc.features()){inti0;while(it.hasNext()i3){SimpleFeaturefit.next();System.out.println( - f.getAttribute(NAME));i;}}}}五、代码逐段讲解5.1 属性查询ECQL.toFilterECQL 语法示例FilterfilterECQL.toFilter(NAME LIKE %Bah%);常用 ECQL 属性操作符NAME China // 精确匹配 NAME LIKE %Chi% // 模糊匹配 POPULATION 10000000 // 数值比较 AREA BETWEEN 100 AND 500 // 范围查询 NAME IN (China, India) // IN 查询5.2 BBOX 查询地图框选FilterfilterFF.bbox(FF.property(the_geom),50,-30,120,30,EPSG:4326);BBOX 参数说明参数 1几何字段名通常是the_geom参数 2‑5minX,minY,maxX,maxY参数 6坐标系必须是 EPSG 编码BBOX 本质上是一个矩形范围查询等价于the_geom ST_MakeEnvelope(...)5.3 INTERSECTS 查询空间关系FilterfilterFF.intersects(FF.property(the_geom),FF.literal(polygon));除了 INTERSECTSGeoTools 还支持以下空间关系判断FF.contains(a, b) // a 包含 b FF.within(a, b) // a 被 b 包含 FF.intersects(a, b) // a 与 b 相交 FF.disjoint(a, b) // a 与 b 不相交 FF.touches(a, b) // a 与 b 相切 FF.crosses(a, b) // a 与 b 交叉 FF.overlaps(a, b) // a 与 b 重叠5.4 DWITHIN 查询距离查询FilterfilterFF.dwithin(FF.property(the_geom),FF.literal(center),500,kilometers);DWITHIN 参数说明参数 1几何字段名参数 2参考几何点/线/面参数 3距离值参数 4距离单位kilometers/meters/miles/degrees注意如果使用 EPSG:4326度单位是kilometers或meters时GeoTools 会自动进行内部转换。但如果直接写数值不加单位默认是度极易出错。5.5 组合查询AND / ORFilterspatialFF.bbox(FF.property(the_geom),50,-30,120,30,EPSG:4326);FilterattributeECQL.toFilter(NAME LIKE %s%);FiltercombinedFF.and(spatial,attribute);支持的逻辑组合FF.and(filter1, filter2) // 与 FF.or(filter1, filter2) // 或 FF.not(filter) // 非六、运行结果示例这里使用测试数据ne_110m_admin_0_tiny_countries.shp运行结果如下七、常见坑位与注意事项7.1 DWITHIN 的单位陷阱这是最容易踩的坑。如果你写的是FF.dwithin(prop,geom,500,kilometers)// 正确但如果写成FF.dwithin(prop,geom,500,degrees)// 等价于 500 度约 55500 公里或者FF.dwithin(prop,geom,500)// 默认单位取决于 CRS建议始终显式指定单位不要依赖默认值。7.2 BBOX 与 INTERSECTS 的区别BBOX 是“包围盒相交”INTERSECTS 是“几何相交”。在绝大多数情况下应该用 INTERSECTSBBOX 只适合做第一层粗筛。7.3 属性名大小写敏感Shapefile 的属性名在 DBF 中是大小写敏感的。如果字段名是NAME写name会匹配不到。建议先用source.getSchema()查看实际字段名。7.4 ECQL 字符串中的引号转义在 Java 字符串中写 ECQL单引号需要转义ECQL.toFilter(NAME China)// 正确ECQL.toFilter(NAME China)// 错误7.5 查询结果为空但明明有数据常见原因坐标系不一致BBOX 的 CRS 与数据的 CRS 不同字段名拼写错误DWITHIN 距离单位搞错数据本身的几何字段不叫the_geom八、工程实践建议优先使用 ECQL 做属性过滤FilterFactory2 做空间过滤各取所长。组合查询时先空间后属性空间过滤通常能砍掉更多数据。DWITHIN 的距离单位永远显式指定不要依赖默认。查询结果用 try-with-resources 或 finally 关闭 FeatureIterator防止资源泄露。对于超大 Shapefile先用 BBOX 粗筛再用 INTERSECTS 精算。生产环境中建议将常用查询条件封装为方法避免重复拼接 ECQL 字符串。九、小结本篇我们系统学习了 GeoTools 的空间查询能力CQL / ECQL 是 GeoTools 的“空间 SQL”远比 Java 循环高效。FilterFactory2 提供了类型安全的编程式查询 API。BBOX 适合粗筛INTERSECTS 适合精算DWITHIN 适合周边搜索。组合查询AND/OR可以实现复杂的业务条件。单位陷阱、大小写敏感、坐标系不一致是最常见的问题来源。掌握空间查询后你的 GeoTools 工具箱已经覆盖了“读写 格式转换 坐标转换 查询”下一篇我们将进入更激动人心的领域基于 GeoTools 的空间运算与叠加分析。

相关新闻

终极鼠标性能检测指南:5分钟量化你的设备真实表现

终极鼠标性能检测指南:5分钟量化你的设备真实表现

终极鼠标性能检测指南:5分钟量化你的设备真实表现 【免费下载链接】MouseTester 项目地址: https://gitcode.com/gh_mirrors/mo/MouseTester 你是否在游戏中总感觉瞄准不够精准?或在设计工作中发现光标移动不够流畅?这些看似主观的感…

2026/7/9 3:14:48阅读更多 →
锂离子电池组电压平衡系统设计与优化实践

锂离子电池组电压平衡系统设计与优化实践

1. 项目背景与核心需求锂离子电池因其高能量密度和长循环寿命,已成为便携式设备和储能系统的首选电源方案。但在串联应用中,单体电池间的电压差异会导致容量损失甚至安全隐患。我曾在一个电动工具项目中遇到过这样的问题:两块串联的18650电池…

2026/7/9 3:14:48阅读更多 →
openEuler/python-multi_key_dict核心功能解析:从基础操作到高级迭代技巧

openEuler/python-multi_key_dict核心功能解析:从基础操作到高级迭代技巧

openEuler/python-multi_key_dict核心功能解析:从基础操作到高级迭代技巧 【免费下载链接】python-multi_key_dict Multi key dictionary implementation 项目地址: https://gitcode.com/openeuler/python-multi_key_dict 前往项目官网免费下载:h…

2026/7/9 3:09:48阅读更多 →
OpenResty 1.25.3.1 升级实战:CentOS 7.9 平滑迁移与 5 个 Lua 模块配置验证

OpenResty 1.25.3.1 升级实战:CentOS 7.9 平滑迁移与 5 个 Lua 模块配置验证

OpenResty 1.25.3.1 生产环境升级指南:CentOS 7.9 平滑迁移与核心模块验证1. 升级前的战略准备在开始升级前,我们需要建立一个完整的风险评估和回滚方案。不同于全新安装,生产环境的升级需要特别关注业务连续性保障。以下是关键准备步骤&…

2026/7/9 4:19:52阅读更多 →
Claude 中转站合规怎么做?数据脱敏、权限和审查边界

Claude 中转站合规怎么做?数据脱敏、权限和审查边界

🔐 合规不是大公司的专属问题 只要你把代码、日志、接口说明、客户字段或业务流程提交给 AI 工具,合规问题就已经出现了。它不是等公司很大才需要考虑,也不是写一句“注意安全”就能解决。 Claude 中转站能提升效率,但也会把数据…

2026/7/9 4:19:52阅读更多 →
AI时代的技术面试,不能还停留在“刷题”和“背答案”

AI时代的技术面试,不能还停留在“刷题”和“背答案”

AI时代的技术面试,不能还停留在“刷题”和“背答案” 技术面试过去很长一段时间都围绕几个固定问题展开:基础知识掌握得怎么样,算法题能不能写出来,项目经历讲得清不清楚,框架原理是否熟悉。这些内容当然有价值&#x…

2026/7/9 4:19:52阅读更多 →
OpenCV摄像头参数优化指南:提升画质300%的实用技巧

OpenCV摄像头参数优化指南:提升画质300%的实用技巧

1. 为什么需要优化摄像头参数 第一次用OpenCV调用摄像头时,我拍出来的照片总是灰蒙蒙的,要么过曝要么欠曝,跟手机拍的完全没法比。后来才发现,默认参数下的摄像头就像没调好焦距的显微镜,空有好硬件却发挥不出实力。通…

2026/7/9 4:19:52阅读更多 →
Certum 开源代码签名证书 2024 申请实战:3 步身份验证与 1 年 25 欧元成本详解

Certum 开源代码签名证书 2024 申请实战:3 步身份验证与 1 年 25 欧元成本详解

Certum 开源代码签名证书 2024 全流程指南:从身份验证到成本优化 对于独立开发者和开源贡献者而言,代码签名证书曾是难以企及的专业工具。Certum 提供的开源开发者专属方案,以 25 欧元/年的亲民价格打破了这一门槛。本文将深入解析 2024 年最…

2026/7/9 4:19:52阅读更多 →
2026世界人工智能大会7月启幕,上海人工智能产业规模超6370亿!

2026世界人工智能大会7月启幕,上海人工智能产业规模超6370亿!

2026世界人工智能大会:聚焦全球智慧与前沿成果7月7日,市政府新闻办举行新闻发布会,上海市委常委、副市长陈杰介绍了2026世界人工智能大会暨人工智能全球治理高级别会议筹备进展。该会议由多部门共同主办,将于7月17日至20日在世博、…

2026/7/9 4:14:52阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/8 5:12:14阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/8 7:00:12阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/9 2:47:07阅读更多 →
Three.js 着色器光效教程

Three.js 着色器光效教程

着色器光效 Shader Light ▶ 在线运行案例 案例合集: 三维可视化功能案例(threehub.cn)开源仓库github地址: https://github.com/z2586300277/three-cesium-examples400个案例代码: 网盘链接 你将学到什么 ShaderMaterial 自…

2026/7/9 0:04:37阅读更多 →
如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南

如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南

如何5分钟掌握CS2智能库存管理:开源工具CASEMOVE终极指南 【免费下载链接】casemove A dedicated desktop app that enables you to move items in and out of storage units in CS2. 项目地址: https://gitcode.com/gh_mirrors/ca/casemove 还在为CS2存储单…

2026/7/9 0:04:37阅读更多 →
GBase 8a vs MySQL 8.0:ALTER TABLE语法与限制的5点关键差异对比

GBase 8a vs MySQL 8.0:ALTER TABLE语法与限制的5点关键差异对比

GBase 8a与MySQL 8.0:ALTER TABLE语法差异深度解析与实战指南1. 两种数据库的ALTER TABLE能力全景对比在数据库架构设计和运维过程中,表结构变更(DDL操作)是不可避免的需求。GBase 8a作为国产分析型数据库代表,与开源M…

2026/7/9 0:04:37阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/8 6:59:54阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/8 13:42:39阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/8 13:42:39阅读更多 →