1. 查询概述
作为一名程序员,每天都在开发着大量的CRUD,而查询在CRUD中占有大多数。在前面的文章里介绍了对于shp的创建和修改i,今天我们就聊一聊GeoTools里的查询,准确的说是GeoTools对shp的查询。好了,我们先看一下GeoTools对于shp查询的基本语法结构:
SimpleFeatureSource source = dataStore.getFeatureSource(typeName);
FeatureType schema = source.getSchema();
Filter filter = CQL.toFilter(text.getText());
SimpleFeatureCollection features = source.getFeatures(filter);
上面的语句类似于select * from ...
,如果我们只要shp的几个字段如何做呢?此时我们就需要借助Query对象。通过使用Query
数据结构,您可以更好地控制您的请求,允许您只选择所需的属性;控制返回多少属性信息;并要求一些特定的处理步骤。通过下面的代码,我们发现只返回了默认的FeatureIdentifer和the_geom属性。
String typeName = (String) featureTypeCBox.getSelectedItem();
SimpleFeatureSource source = dataStore.getFeatureSource(typeName);
FeatureType schema = source.getSchema();
//name 一般为the_geom,表示记录shp的空间信息
String name = schema.getGeometryDescriptor().getLocalName();
Filter filter = CQL.toFilter(text.getText());
//关键是下面的代码
Query query = new Query(typeName, filter, new String[] {name});
SimpleFeatureCollection features = source.getFeatures(query);
不管是通过那种方法实现shp数据的查询,我们发现都离不开Filter。查询嘛,少不了where条件那么Filter有几种创建方式呢?我们可以回想一下操作数据库的框架。一类是类似于MyBatis的通过写SQL查询数据,而另一种方式类似于JPA,通过面向对象的方式查询数据。而对于GeoTools,也同样提供了两种创建Filter的方法,他们分别是CQL
和FilterFactory
2. CQL查询
2.1 概述
CQL工具类提供了一个很好的前端来从文本字符串生成过滤器和表达式,该工具类包含静态方法,您可以调用这些方法将文本字符串转换为表达式、过滤器或列表<过滤器>。它还能够采用这些项目并生成适当的文本表示。
2.2 示例
2.2.1 简单例子
Filter filter = CQL.toFilter("attrName >= 5");
2.2.2 解析过滤器列表
可以一次解析过滤器列表。这对于像 GeoServer 这样的应用程序很有用,例如,它允许为 WMS GetMap 请求中的每一层定义过滤谓词。
List filters = CQL.toFilterList("att1 > 5;ogc:name = 'river'");
在这个例子中,你会得到两个过滤器,因为“;” 字符充当分隔符。
2.2.3 按比较值过滤
Filter result = CQL.toFilter("ATTR1 < (1 + ((2 / 3) * 4))" );
Filter result = CQL.toFilter("ATTR1 < abs(ATTR2)" );
Filter result = CQL.toFilter("ATTR1 < 10 AND ATTR2 < 2 OR ATTR3 > 10" );
2.2.4 使用文本过滤
Filter result = CQL.toFilter( "ATTR1 LIKE 'abc%'" );
Filter result = CQL.toFilter( "ATTR1 NOT LIKE 'abc%'" );
2.2.5 过滤空值
Filter result = CQL.toFilter( "ATTR1 IS NULL" );
Filter result = CQL.toFilter( "ATTR1 IS NOT NULL" );
2.2.6 按比较时间值过滤
-
等于一个日期
Filter result = CQL.toFilter( "ATTR1 TEQUALS 2006-11-30T01:30:00Z" );
-
之前时间
Before filter = (Before) CQL.toFilter("lastEarthQuake BEFORE 2006-11-30T01:30:00Z");
-
之前一个经期
Filter result = CQL.toFilter( "ATTR1 BEFORE 2006-11-30T01:30:00Z/2006-12-31T01:30:00Z" );
-
之后时间
After filter = (After) CQL.toFilter("lastEarthQuake AFTER 2006-11-30T01:30:00Z");
-
使用GMT+3区的之后一个时间
After filter = (After) CQL.toFilter("lastEarthQuake AFTER 2006-11-30T01:30:00+03:00");
-
之后一个经期
Filter result = CQL.toFilter( "ATTR1 AFTER 2006-11-30T01:30:00Z/2006-12-31T01:30:00Z" );
-
具有持续时间的时间谓词(例如2006-11-30T01:30:00Z 之后的十天)
Filter result = CQL.toFilter( "ATTR1 AFTER 2006-11-30T01:30:00Z/P10D" );Filter result = CQL.toFilter( "ATTR1 AFTER 2006-11-30T01:30:00Z/T10H" );
-
在谓词期间
During filter =(During)CQL.toFilter("lastEarthQuake DURING 1700-01-01T00:00:00/2011-01-01T00:00:00");
2.2.7 基于存在的过滤器
//检查是否存在
Filter result = CQL.toFilter("ATTR1 EXISTS" );
//检查某些东西是否不存在
Filter result = CQL.toFilter("ATTR1 DOES-NOT-EXIST" );
2.2.8 通过检查值是否介于之间来过滤:
Filter result = CQL.toFilter("ATTR1 BETWEEN 10 AND 20" );
2.2.9 使用复合属性:
Filter result = CQL.toFilter("gmd:MD_Metadata.gmd:identificationInfo.gmd:MD_DataIdentification.gmd:abstract LIKE 'abc%'" );
2.2.10 使用几何关系过滤:
Filter result = CQL.toFilter("CONTAINS(ATTR1, POINT(1 2))" );
Filter result = CQL.toFilter("BBOX(ATTR1, 10,20,30,40)" );
Filter result = CQL.toFilter("DWITHIN(ATTR1, POINT(1 2), 10, kilometers)" );
Filter result = CQL.toFilter("CROSS(ATTR1, LINESTRING(1 2, 10 15))" );
Filter result = CQL.toFilter("INTERSECT(ATTR1, GEOMETRYCOLLECTION (POINT (10 10),POINT (30 30),LINESTRING (15 15, 20 20)) )" );
Filter result = CQL.toFilter("CROSSES(ATTR1, LINESTRING(1 2, 10 15))" );
Filter result = CQL.toFilter("INTERSECTS(ATTR1, GEOMETRYCOLLECTION (POINT (10 10),POINT (30 30),LINESTRING (15 15, 20 20)) )" );
2.2.11 九交模式判断过滤器
Filter filter =ECQL.toFilter("RELATE(geometry, LINESTRING (-134.921387 58.687767, -135.303391 59.092838), T*****FF*)");
2.3 CQL的扩展类–ECQL
ECQL 语言旨在作为 CQL 的扩展,因此您可以编写 CQL 支持的所有谓词并使用新语法规则中定义的新表达式可能性。
2.3.1 例子
2.3.1.1 按比较值过滤
CQL 语言将我们限制为propertyName
针对更一般的表达式引用 a 。ECQL 允许您在任何地方使用完整的表达式:
Filter filter = ECQL.toFilter("1000 <= population");
Filter filter =ECQL.toFilter("(under18YearsOld * 19541453 / 100 ) < (over65YearsOld * 19541453 / 100 )");
Filter filter = ECQL.toFilter("population BETWEEN 10000000 and 20000000");
Filter filter =ECQL.toFilter("area(Polygon((10 10, 20 10, 20 20, 10 10))) BETWEEN 10000 AND 30000");
2.3.1.2 按功能 ID 列表过滤
过滤器 XML 格式允许定义捕获一组 FeatureID(通常代表一个选择)的Id过滤器。
//使用字符串作为 id:
Filter filter = ECQL.toFilter("IN ('river.1', 'river.2')");
//使用整数作为 id:
Filter filter = ECQL.toFilter("IN (300, 301)");
//我们尝试了几个实验,但并非所有实验都有效,留下了以下已弃用的语法:
Filter filter = ECQL.toFilter("ID IN ('river.1', 'river.2')");
2.3.1.3 基于一组值的过滤器
以下过滤器选择以白银、石油或黄金为主要矿产资源的国家:
Filter filter = ECQL.toFilter("principalMineralResource IN ('silver','oil', 'gold' )");
2.3.1.4 使用文本模式过滤
//使用LIKE关键字过滤文本模式:
Filter filter = ECQL.toFilter("cityName LIKE 'New%'");
//带有ILIKE关键字的不区分大小写示例
Filter filter = ECQL.toFilter("cityName ILIKE 'new%'");
//ECQL 允许您测试任何两个表达式,包括文字:
Filter filter = ECQL.toFilter("'aabbcc' LIKE '%bb%'");
2.3.1.5 按空间关系过滤
使用完整表达式的能力也适用于空间操作,允许我们使用函数处理几何,如下例所示:
Filter filter = ECQL.toFilter("DISJOINT(the_geom, POINT(1 2))");
Filter filter = ECQL.toFilter("DISJOINT(buffer(the_geom, 10) , POINT(1 2))");
Filter filter = ECQL.toFilter("DWITHIN(buffer(the_geom,5), POINT(1 2), 10, kilometers)");
以下示例显示如何使用 RELATE 操作创建过滤器。在这种情况下,DE-9IM 模式对应于包含空间关系,如果第一个几何图形包含第二个,则为真。
Filter filter =ECQL.toFilter("RELATE(geometry, LINESTRING (-134.921387 58.687767, -135.303391 59.092838), T*****FF*)");
以下变体显示相同,但使用 EWKT 约定为几何图形提供坐标参考系统,即在其前面加上“SRID=epsgCode;”:
Filter filter =ECQL.toFilter("RELATE(geometry, SRID=4326;LINESTRING (-134.921387 58.687767, -135.303391 59.092838), T*****FF*)");
2.3.1.6 按时间关系过滤
时间谓词允许建立两个给定时刻之间或时刻和时间间隔之间的关系。在下一个示例中,使用 during 谓词过滤指定日期之间发生地震的城市:
During filter =(During)ECQL.toFilter("lastEarthQuake DURING 1700-01-01T00:00:00Z/2011-01-01T00:00:00Z");
在 ECQL 中,您可以在时间谓词的左侧编写日期时间表达式:
Filter filter = ECQL.toFilter("2006-11-30T01:00:00Z AFTER 2006-11-30T01:30:00Z");
在 Before 谓词中:
Filter filter = ECQL.toFilter("2006-11-30T01:00:00Z BEFORE 2006-11-30T01:30:00Z");
在期间谓词中:
Filter filter =ECQL.toFilter("2006-11-30T01:00:00Z DURING 2006-11-30T00:30:00Z/2006-11-30T01:30:00Z ");
以下示例显示了一个时间谓词,该谓词在日期时间表达式中包含 UTC 时区 (GMT +3):
Filter filter =ECQL.toFilter("2006-11-30T01:00:00+03:00 DURING 2006-11-30T00:30:00+03:00/2006-11-30T01:30:00+03:00 ");
2.3.1.7 过滤空值
Filter filter = ECQL.toFilter(" Name IS NULL");
Filter filter = ECQL.toFilter("centroid( the_geom ) IS NULL");
2.3.1.8 属性存在谓词
Filter resultFilter = ECQL.toFilter("aProperty EXISTS");
2.3.1.9 表达式
Expression expr = ECQL.toExpression("X + 1");
2.3.1.10 过滤器列表
//过滤器列表仍然支持使用“;” 分隔条目:
List<Filter> list = ECQL.toFilterList("X=1; Y<4");
2.3.1.11 使用日期文字过滤
Filter filter = ECQL.toFilter("foo = 1981-06-20");
Filter filter = ECQL.toFilter("foo <= 1981-06-20T12:30:01Z");
3. FilterFactory
通过使用FilterFactory
,您可以手动创建对象。该FilterFactory
接口仅限于严格的规范合规性。
3.1 工厂过滤器基础
核心过滤器抽象在这里。这组接口是关闭的(你不能创建一个新的过滤器类并期望它工作)。
3.2 比较
过滤器数据模型的核心是属性比较;这些过滤器允许您测试特征的属性并仅选择匹配的特征:
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();Filter filter;
// 与equals相同功能的查询
ff.equal(ff.property("land_use"), ff.literal("URBAN"));
// 判断值是否为null
filter = ff.isNull(ff.property("approved"));
filter = ff.less(ff.property("depth"), ff.literal(300));
filter = ff.lessOrEqual(ff.property("risk"), ff.literal(3.7));
filter = ff.greater(ff.property("name"), ff.literal("Smith"));
filter = ff.greaterOrEqual(ff.property("schedule"), ff.literal(new Date()));
// 判断两个数值之前的结果
filter = ff.between(ff.property("age"), ff.literal(20), ff.literal("29"));
filter = ff.between(ff.property("group"), ff.literal("A"), ff.literal("D"));
// 不等于的简写
filter = ff.notEqual(ff.property("type"), ff.literal("draft"));
// like模式的过滤器
filter = ff.like(ff.property("code"), "2300%");
// 您可以使用自定义的通配符
filter = ff.like(ff.property("code"), "2300?", "*", "?", "\\");
3.3 空 vs 空
比较属性值的一个相关主题是测试属性是否具有值。在简单的情况下PropertyIsNull
可用于检查属性是否存在;并且该值为空。我们也有允许一个属性出现零次或多次的情况;在这种情况下,我们需要一种清晰的方法来检查属性根本不存在(即发生 = 零)。
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
Filter filter;
// 如果approved等于“null”,正如前面的测试示例
filter = ff.isNull(ff.property("approved"));
// 此示例检查是否存在approved
filter = ff.isNil(ff.property("approved"), "no approval available");
3.4 匹配动作
实现该MultiValuedFilter
接口的所有过滤器都支持对在评估时返回多个值的操作数进行过滤。可以通过MatchAction
属性修改这些过滤器处理多个值的方式。可以通过一个简单的 getter 来检索该属性:
filter.getMatchAction()
MatchAction
有三个可能的值:
-
MatchAction.ANY
—当没有MatchAction
指定时,它被设置为默认值MatchAction.ANY
。如果任何可能的操作数组合的计算结果为真,则计算结果为真:List<Integer> ages = Arrays.asList(new Integer[] {7, 8, 10, 15}); Filter filter = ff.greater(ff.literal(ages), ff.literal(12), false, MatchAction.ANY); System.out.println("Any: " + filter.evaluate(null)); // prints Any: true
-
MatchAction.ALL
— 如果所有可能的操作数组合的计算结果为真,则计算结果为真。:List<Integer> ages = Arrays.asList(new Integer[] {7, 8, 10, 15}); Filter filter = ff.greater(ff.literal(ages), ff.literal(12), false, MatchAction.ALL); System.out.println("All: " + filter.evaluate(null)); // prints All: false
-
MatchAction.ONE
— 如果恰好一种可能的值组合评估为真,则评估为真:List<Integer> ages = Arrays.asList(new Integer[] {7, 8, 10, 15}); Filter filter = ff.greater(ff.literal(ages), ff.literal(12), false, MatchAction.ONE); System.out.println("One: " + filter.evaluate(null)); // prints One: true
在几种情况下可能有多个值:使用应用程序模式时,或直接使用 java 对象时。当针对这种性质的丰富内容评估表达式时,子引用可能会返回多值属性。
例如,此过滤器测试是否所有孩子都超过 12 岁:
Filter filter = ff.greater(ff.property("child/age"), ff.literal(12), true, MatchAction.ALL);
3.5 逻辑过滤器
可以使用通常的 AND、OR 和 NOT 二进制逻辑组合过滤器。
filter = ff.not(ff.like(ff.property("code"), "230%"));
//您还可以组合筛选器以缩小返回的结果范围
filter = ff.and(ff.greater(ff.property("rainfall"), ff.literal(70)),ff.equal(ff.property("land_use"), ff.literal("urban"), false));
filter = ff.or(ff.equal(ff.property("code"), ff.literal("approved")),ff.greater(ff.property("funding"), ff.literal(23000)));
3.6 标识符
在 GIS 意义上使用过滤器更多地作为“选择”的另一种有趣方式。在这种情况下,我们将直接匹配FeatureId
.
最常见的测试是针对FeatureId
:
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
Filter filter = ff.id(ff.featureId("CITY.98734597823459687235"), ff.featureId("CITY.98734592345235823474"));
从形式上讲,这种 Id 匹配风格不应与传统的基于属性的评估(例如边界框过滤器)混合使用。您还可以使用Set<FeatureId>
:
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
Set<FeatureId> selected = new HashSet<>();
selected.add(ff.featureId("CITY.98734597823459687235"));
selected.add(ff.featureId("CITY.98734592345235823474"));
Filter filter = ff.id(selected);
另一个使用标识符的地方是在处理版本信息时。在这种情况下ResourceId
,使用由 afid
和 a组成的a rid
。ResourceId
可用于探索版本信息:
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
Filter filter;
// 获取特定修订版本
filter = ff.id(ff.featureId("CITY.98734597823459687235", "A457"));
// 您还可以使用ResourceId获取特定的修订版本
filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version()));
//获取符合条件的上一个
filter =ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(Action.PREVIOUS)));
// 获取符合条件的下一个
filter =ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(Action.NEXT)));
// 获取第一个
filter =ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(Action.FIRST)));
// 获取第一个(即索引=1)
filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(1)));
// 获取序列中的第12条记录(即索引=12)
filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(12)));
// 获取接近1985年1月数据
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);df.setTimeZone(TimeZone.getTimeZone("GMT"));
filter =ff.id(ff.resourceId("CITY.98734597823459687235","A457",new Version(df.parse("1985-1-1"))));
// 获取90年代所有的实体对象
filter =ff.id(ff.resourceId("CITY.98734597823459687235",df.parse("1990-1-1"),df.parse("2000-1-1")));
3.7 空间过滤器
这是一个快速示例,展示了如何在边界框中请求特征。
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
ReferencedEnvelope bbox = new ReferencedEnvelope(x1, x2, y1, y2, DefaultGeographicCRS.WGS84);
Filter filter = ff.bbox(ff.property("the_geom"), bbox);
3.8 时间过滤器
时间过滤器最近由过滤器 2.0 规范定义,是 GeoTools 8.0 的新增功能。
gt-main 模块提供了一些我们需要的实现类:
DefaultIntant
:这是用于表示单个时间点的 Instant 的实现。DefaultPeriod
:这是Period的一个实现,用于表示时间范围。
这是一个示例,说明它们的构造和与时间过滤器的使用:
// 使用gt main的默认实现
DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date1 = FORMAT.parse("2001-07-05T12:08:56.235-0700");
Instant temporalInstant = new DefaultInstant(new DefaultPosition(date1));
// 简单检查属性是否在
Filter after = ff.after(ff.property("date"), ff.literal(temporalInstant));
// 也可以在一定期限内检查属性
Date date2 = FORMAT.parse("2001-07-04T12:08:56.235-0700");
Instant temporalInstant2 = new DefaultInstant(new DefaultPosition(date2));
Period period = new DefaultPeriod(temporalInstant, temporalInstant2);
Filter within = ff.toverlaps(ff.property("constructed_date"), ff.literal(period));
3.9 表达式
上面提到的许多过滤器都是作为两个(或多个)表达式之间的比较呈现的。表达式用于访问保存在 Feature(或 POJO、Record 或 …)中的数据。核心表达式抽象在这里 - 该集合是开放的,您可以定义新函数。
表达式非常有用,您会在 GeoTools 的许多部分中看到它们弹出。样式使用它们来选择用于描绘的数据等等。
-
PropertyName
该
PropertyName
表达式用于从您的数据模型中提取信息。最常见的用途是访问特征属性。FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() ); Expression expr = ff.property("name"); //计算 Object value = expr.evaluate( feature ); if( value instanceof String){ name = (String)value; }else{ name = "(invalid name)"; }
您还可以将值具体要求为字符串,如果无法将该值强制为字符串,则将返回 null:
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() ); Expression expr = ff.property("name"); String name = expr.evaluate( feature, String ); //evaluate if( name == null ){ name = "(invalid name)"; }
-
X 路径和命名空间
可以在过滤器中使用 XPath 表达式。这对于针对复杂特征评估嵌套属性特别有用。要评估 XPath 表达式,
org.xml.sax.helpers.NamespaceSupport
需要一个 对象来将前缀与名称空间 URI 相关联。FilterFactory2
支持创建PropertyName
具有关联命名空间上下文信息的表达式:FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() ); NamespaceSupport namespaceSupport = new NamespaceSupport(); namespaceSupport.declarePrefix("foo", "urn:cgi:xmlns:CGI:GeoSciML:2.0" ); Filter filter = ff.greater(ff.property("foo:city/foo:size",namespaceSupport),ff.literal(300000));
可以从现有
PropertyName
表达式中检索命名空间上下文信息:PropertyName propertyName = ff.property("foo:city/foo:size", namespaceSupport); NamespaceSupport namespaceSupport2 = propertyName.getNamespaceContext();
-
职能
您可以使用
FilterFactory2
以下方法创建函数:FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() ); PropertyName a = ff.property("testInteger"); Literal b = ff.literal( 1004.0 ); Function min = ff.function("min", a, b );
对于需要多个参数的函数,您需要使用数组:
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); PropertyName property = ff.property("name"); Literal search = ff.literal("foo"); Literal replace = ff.literal("bar"); Literal all = ff.literal( true ); Function f = ff.function("strReplace", new Expression[]{property,search,replace,all});
找不到函数时该怎么办 - 创建函数将失败!Symbology Encoding 2.0 规范的概念是
fallbackValue
- 虽然我们还没有通过工厂提供它,但您可以使用FunctionFinder
.FunctionFinder finder = new FunctionFinder(null); finder.findFunction("pi", Collections.emptyList(), ff.literal(Math.PI));
3.10 过滤器访问者
FilterVisitor
用于遍历过滤器数据结构。常见用途包括:
- 询问有关过滤器内容的问题
- 对过滤器执行分析和优化(比如用“2”替换“1+1”)
- 转换过滤器(想想搜索和替换)
当使用 XSLT 处理遍历树时,对 XML 文档(也形成树)使用类似的方法。
所有这些活动都有一个共同点:
- 需要检查过滤器的内容
- 需要建立结果或答案
这是一个快速代码示例,显示了使用访问者遍历数据结构:
class FindNames extends DefaultFilterVisitor {
public Set<String> found = new HashSet<String>();
//我们只感兴趣属性名称表达式
public Object visit( PropertyName expression, Object data ) {
found.add( expression.getPropertyName() );
return found;
}
}
// 将访问者传递到过滤器以开始遍历
FindNames visitor = new FindNames();
filter.accept( visitor, null );
System.out.println("Property Names found "+visitor.found );
4. 简单示例
本示例通过一个过滤器从Shape或者其他数据源选择一个要素集合。
4.1 引入pom
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<geotools.version>19.1</geotools.version>
</properties>
<dependencies>
<!-- Provides map projections -->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
<!-- Provides support for shapefiles -->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>${geotools.version}</version>
</dependency>
<!-- Provides GUI components -->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-swing</artifactId>
<version>${geotools.version}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>osgeo</id>
<name>OSGeo Release Repository</name>
<url>https://repo.osgeo.org/repository/release/</url>
<snapshots><enabled>false</enabled></snapshots>
<releases><enabled>true</enabled></releases>
</repository>
<repository>
<id>osgeo-snapshot</id>
<name>OSGeo Snapshot Repository</name>
<url>https://repo.osgeo.org/repository/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
</repositories>
4.2 创建一个启动类
package cn.surpass.geotools.tutorial.query;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.Query;
import org.geotools.data.postgis.PostgisNGDataStoreFactory;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.swing.action.SafeAction;
import org.geotools.swing.data.JDataStoreWizard;
import org.geotools.swing.table.FeatureCollectionTableModel;
import org.geotools.swing.wizard.JWizard;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Map;
/**
* @author SurpassLiang
* @version 1.0
* @ClassName cn.surpass.geotools.tutorial.query
* @date 2021/6/25 22:56
* @desc
*/
public class QueryLab extends JFrame {
private DataStore dataStore;
private JComboBox<String> featureTypeCBox;
private JTable table;
private JTextField text;
public static void main(String[] args) throws Exception {
JFrame frame = new QueryLab();
frame.setVisible(true);
}
}
4.3 初始化选择文件源的功能按钮
private void initFileAction(JMenu fileMenu){
fileMenu.add(new SafeAction("Open shapefile...") {
@Override
public void action(ActionEvent e) throws Throwable {
connect(new ShapefileDataStoreFactory());
}
});
fileMenu.add(new SafeAction("Connect to PostGIS database...") {
@Override
public void action(ActionEvent e) throws Throwable {
connect(new PostgisNGDataStoreFactory());
}
});
fileMenu.add(new SafeAction("Connect to DataStore...") {
public void action(ActionEvent e) throws Throwable {
connect(null);
}
});
fileMenu.addSeparator();
fileMenu.add(new SafeAction("Exit") {
public void action(ActionEvent e) throws Throwable {
System.exit(0);
}
});
}
4.4 初始化数据选择功能按钮
private void initDataAction(JMenu dataMenu){
dataMenu.add(new SafeAction("Get features") {
public void action(ActionEvent e) throws Throwable {
filterFeatures();
}
});
dataMenu.add(new SafeAction("Count") {
public void action(ActionEvent e) throws Throwable {
countFeatures();
}
});
dataMenu.add(new SafeAction("Geometry") {
public void action(ActionEvent e) throws Throwable {
queryFeatures();
}
});
}
4.5 初始化构造方法
public QueryLab() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
text = new JTextField(80);
text.setText("include"); // include selects everything!
getContentPane().add(text, BorderLayout.NORTH);
table = new JTable();
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setModel(new DefaultTableModel(5, 5));
table.setPreferredScrollableViewportSize(new Dimension(500, 200));
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
JMenuBar menubar = new JMenuBar();
setJMenuBar(menubar);
JMenu fileMenu = new JMenu("File");
menubar.add(fileMenu);
featureTypeCBox = new JComboBox<>();
menubar.add(featureTypeCBox);
JMenu dataMenu = new JMenu("Data");
menubar.add(dataMenu);
pack();
initFileAction(fileMenu);
initDataAction(dataMenu);
}
4.6 连接数据存储
在快速入门中,我们使用FileDataStoreFinder
连接到特定文件。这次我们将使用更通用DataStoreFinder
的连接参数映射。
请注意,相同的代码可用于连接到由DataStoreFactorySpi
( Service Provider Interface ) 参数指定的完全不同类型的数据存储 。文件菜单操作使用ShapefileDataStoreFactory
or 或的实例调用此方法PostgisNGDataStoreFactory
。
将JDataStoreWizard
显示一个对话框,其中包含适用于 shapefile 或 PostGIS 数据库的输入字段。它需要比JFileDataStoreChooser
Quickstart 中使用的代码多几行来提示用户输入 shapefile,但允许更好的控制。
-
文件菜单操作调用此方法进行连接。
private void connect(DataStoreFactorySpi format) throws Exception { JDataStoreWizard wizard = new JDataStoreWizard(format); int result = wizard.showModalDialog(); if (result == JWizard.FINISH) { Map<String, Object> connectionParameters = wizard.getConnectionParameters(); dataStore = DataStoreFinder.getDataStore(connectionParameters); if (dataStore == null) { JOptionPane.showMessageDialog(null, "Could not connect - check parameters"); } updateUI(); } }
-
更新用于选择特征类型的组合框的辅助方法:
private void updateUI() throws Exception { ComboBoxModel<String> cbm = new DefaultComboBoxModel<>(dataStore.getTypeNames()); featureTypeCBox.setModel(cbm); table.setModel(new DefaultTableModel(5, 5)); }
4.7 查询
下面是我们显示所选特征的策略:
- 获取用户选择的特征类型名称,并
FeatureSource
从DataStore
. - 获取在文本字段中输入的查询条件,并使用 CQL 类创建
Filter
对象。 - 将 传递
Filter
给getFeatures
方法,该方法将与查询匹配的特征作为FeatureCollection
. FeatureCollectionTableModel
为我们的对话框创建一个JTable
. 这个 GeoTools 类接受FeatureCollection
并检索每个要素的要素属性名称和数据。
4.7.1 使用featureSource.getFeatures(filter)
获取要素数据
private void filterFeatures() throws Exception {
String typeName = (String) featureTypeCBox.getSelectedItem();
SimpleFeatureSource source = dataStore.getFeatureSource(typeName);
Filter filter = CQL.toFilter(text.getText());
SimpleFeatureCollection features = source.getFeatures(filter);
FeatureCollectionTableModel model = new FeatureCollectionTableModel(features);
table.setModel(model);
}
4.7.2 获取数据数目
private void countFeatures() throws Exception {
String typeName = (String) featureTypeCBox.getSelectedItem();
SimpleFeatureSource source = dataStore.getFeatureSource(typeName);
Filter filter = CQL.toFilter(text.getText());
SimpleFeatureCollection features = source.getFeatures(filter);
int count = features.size();
JOptionPane.showMessageDialog(text, "Number of selected features:" + count);
}
4.7.3 重新映射查询结果信息
通过使用Query
数据结构,您可以更好地控制您的请求,允许您只选择所需的属性;控制返回多少特征;并要求一些特定的处理步骤,例如重投影。
private void queryFeatures() throws Exception {
String typeName = (String) featureTypeCBox.getSelectedItem();
SimpleFeatureSource source = dataStore.getFeatureSource(typeName);
FeatureType schema = source.getSchema();
String name = schema.getGeometryDescriptor().getLocalName();
Filter filter = CQL.toFilter(text.getText());
Query query = new Query(typeName, filter, new String[] {name});
SimpleFeatureCollection features = source.getFeatures(query);
FeatureCollectionTableModel model = new FeatureCollectionTableModel(features);
table.setModel(model);
}