之前在《GIS开源框架:ArcGIS文件地理数据库(GDB)解析与入库》中,从地理数据库的角度对Feature要素进行了解释,接下来,我们将从GeoTools库的角度,重新认识Feature要素,并通过GeoTools实现Shapefile文件在Feature要素层面的CRUD操作。
目录
Feature要素
FeatureClass要素类
Geometry
DataStore数据源
读取Shapefile文件
添加Feature到Shapefile文件中
修改Shapefile文件中的Feature要素属性
删除Shapefile文件中的Feature要素
Feature要素
所谓Feature要素,实质上就是在map地图上展示出来的东西。严格意义上讲:Feature要素是对现实世界客观实体的抽象表达。
但是对于Java开发者而言,最简明的解释就是:一个Feature就是一个对象。像Java对象一样,Feature要素可以用于表达客观实体与现实世界相关的信息。这些信息将被组织成attributes属性,然后被写入field字段中保存。
有时候,对于两个拥有共性的Feature要素,我们就可以对其进行抽象——转为用一个Class类来描述这一类要素。例如:对于两个机场A、B,我们可以创建一个Airport类来描述其公共属性,但是在Map地图上,我们将创建一个FeatureType来进行表达。
那么,如何来概括Java和Map地图之间的关系呢?我们可以通过下表进行理解,
FeatureClass要素类
GeoTools中,通过GeoAPI项目提供了Feature、FeatureType、Attribute接口。通常地,GeoAPI提供了十分严格的接口,而GeoTools提供了对应的class类。
对于一个Feature要素来讲,仅仅有一些简单属性Attributes(例如:String、Integer、Date等类型的)是十分常见的。为了满足这种需求,GeoTools提供了SimpleFeature子类。
Geometry
Object对象和Feature要素之间的另一个不同之处是:Feature包含一些位置信息。这些位置信息可以通过Geometry几何图形进行收集,并作为Attribute属性字段保存。
GeoTools提供了 JTS Topology Suite(JTS)模块来描述Geometry几何图形,基于JTS,可以实现对于任何几何图形数据的高效处理操作。
以下为使用JTS库,基于Well-Known-Text (WKT)格式创建Point点的示例代码,
@Test
public void crateGeometryFromWKT() throws ParseException {
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory( null );
WKTReader reader = new WKTReader( geometryFactory );
Point point = (Point) reader.read("POINT (1 1)");
System.out.println(point);
}
以下为直接使用GeometryFactory工厂类创建Point的示例代码,
@Test
public void crateGeometryByGeometryFactory() throws ParseException {
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory( null );
Coordinate coordinate = new Coordinate(1,1);
Point point = geometryFactory.createPoint(coordinate);
System.out.println(point);
}
DataStore数据源
GeoTools提供了DataStore接口,用于表示一个包含空间数据(spatial data)的File文件、DataBase数据库、Service服务——即:空间数据源。API结构如下所示,
FeatureSource被用于读取数据源中的Feature要素数据,其子类FeatureStore
拥有对数据源的读写权限。
读取Shapefile文件
例如:读取上图所示的out.shp文件中的要素数据示例代码如下,
@Test
public void readShapefile() throws IOException {
String filePath = "C:\\Users\\13241\\Documents\\data\\out.shp";
File file = new File(filePath);
DataStore dataStore = new ShapefileDataStore(file.toURI().toURL());//创建ShapefileDataStore实例
String[] typeNames = dataStore.getTypeNames();//获取数据源中所有可获取的图层名称
System.out.println(Arrays.toString(typeNames));
//逐个解析图层数据
for (int i = 0; i < typeNames.length; i++) {
String typeName = typeNames[i];
//获取FeatureSource
SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);
SimpleFeatureCollection features = featureSource.getFeatures();//获取FeatureSource中的Feature集合
SimpleFeatureIterator iterator = features.features();//获取集合迭代器
while (iterator.hasNext()){
SimpleFeature next = iterator.next();
SimpleFeatureType featureType = next.getFeatureType();
List<AttributeDescriptor> attributeDescriptors = featureType.getAttributeDescriptors();
for (int i1 = 0; i1 < attributeDescriptors.size(); i1++) {
Name name = attributeDescriptors.get(i1).getName();
System.out.print(name+":"+next.getAttribute(name)+"\t");
}
Object defaultGeometry = next.getDefaultGeometry();
System.out.println();
}
}
}
添加Feature到Shapefile文件中
添加Feature到Shapefile文件中的示例代码如下,
@Test
public void addFeaturesToShp() throws SchemaException, IOException {
//Geometry工厂类
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
/*
* FeatureType-定义Feature的字段结构
* TYPE is used as a template to describe the file contents
*/
final SimpleFeatureType TYPE = DataUtilities.createType(
"Location",
"the_geom:Point:srid=4326,"
+ // <- the geometry attribute: Point type
"name:String,"
+ // <- a String attribute
"number:Integer" // a number attribute
);
String name = "dsandjkadmskladakndsaldmalkdmaldkas";
Random random = new Random();
//创建10个新的Feature要素-[带有属性信息的简单Feature-SimpleFeature]
List<SimpleFeature> features = new ArrayList<>();
for (int i = 0; i < 10; i++) {
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
Point point = geometryFactory.createPoint(new Coordinate(120.0+Math.random(),32.0+Math.random()));
//注意字段添加顺序
featureBuilder.add(point);
featureBuilder.add(name.substring(0,random.nextInt(name.length())));
featureBuilder.add(10+random.nextInt(20));
SimpleFeature simpleFeature = featureBuilder.buildFeature(null);//创建Feature实例
features.add(simpleFeature);
}
//向shp文件中添加新的Feature要素
SimpleFeatureCollection collection = new ListFeatureCollection(TYPE,features);
//获取Shapefile数据源
String filePath = "C:\\Users\\13241\\Documents\\data\\out.shp";
File file = new File(filePath);
DataStore dataStore = new ShapefileDataStore(file.toURI().toURL());//创建ShapefileDataStore实例
SimpleFeatureSource featureSource = dataStore.getFeatureSource("out");//获取FeatureSource
if( featureSource instanceof SimpleFeatureStore){
SimpleFeatureStore store = (SimpleFeatureStore) featureSource; // write access!
store.addFeatures(collection);
}
}
添加之后,可以看到out.shp文件中共计为25个Feature要素。
修改Shapefile文件中的Feature要素属性
可以看到,上面执行完添加操作之后的out.shp属性表中某些记录的name属性为null。下面,我们执行更新操作,将name=null的记录进行修改——统一将name属性修改为xxx。
考虑到name属性值可能为null,也可能为空白字符串。因此,先通过Filter过滤器,获取到对应的记录,示例代码如下,
@Test
public void modifyFeatureFromShp() throws IOException, CQLException {
//获取数据源
String filePath = "C:\\Users\\13241\\Documents\\data\\out.shp";
File file = new File(filePath);
DataStore dataStore = new ShapefileDataStore(file.toURI().toURL());//创建ShapefileDataStore实例
SimpleFeatureSource featureSource = dataStore.getFeatureSource("out");//获取FeatureSource
if( featureSource instanceof SimpleFeatureStore){
SimpleFeatureStore store = (SimpleFeatureStore) featureSource; // write access!
// store.modifyFeatures("name", "xxx", CQL.toFilter("name is null"));
Filter filter = CQL.toFilter("name = '' OR name IS NULL");
// Query query = new Query(filter);
SimpleFeatureCollection features = featureSource.getFeatures(filter);
SimpleFeatureIterator iterator = features.features();
System.out.println(features.size());
while (iterator.hasNext()) {
SimpleFeature next = iterator.next();
SimpleFeatureType featureType = next.getFeatureType();
List<AttributeDescriptor> attributeDescriptors = featureType.getAttributeDescriptors();
for (int i1 = 0; i1 < attributeDescriptors.size(); i1++) {
Name name = attributeDescriptors.get(i1).getName();
System.out.print(name + ":" + next.getAttribute(name) + "\t");
}
System.out.println();
}
}
}
获取结果如下,
接着,我们继续使用同样的Filter过滤器,将目标记录的name字段值改为xxx。示例代码如下,
//根据条件修改记录
@Test
public void modifyFeatureFromShp() throws IOException, CQLException {
//获取数据源
String filePath = "C:\\Users\\13241\\Documents\\data\\out.shp";
File file = new File(filePath);
DataStore dataStore = new ShapefileDataStore(file.toURI().toURL());//创建ShapefileDataStore实例
SimpleFeatureSource featureSource = dataStore.getFeatureSource("out");//获取FeatureSource
if( featureSource instanceof SimpleFeatureStore){
SimpleFeatureStore store = (SimpleFeatureStore) featureSource; // write access!
// store.modifyFeatures("name", "xxx", CQL.toFilter("name is null"));
Filter filter = CQL.toFilter("name = '' OR name IS NULL");
store.modifyFeatures("name","xxx",filter);
}
}
再次查看属性表,可以看到name为空的记录已经被修改成功,
删除Shapefile文件中的Feature要素
接下来,我们尝试将name='xxx‘的Feature删除掉,示例代码如下,
//根据条件删除记录
@Test
public void deleteFeatureFromShp() throws IOException, CQLException {
//获取数据源
String filePath = "C:\\Users\\13241\\Documents\\data\\out.shp";
File file = new File(filePath);
DataStore dataStore = new ShapefileDataStore(file.toURI().toURL());//创建ShapefileDataStore实例
SimpleFeatureSource featureSource = dataStore.getFeatureSource("out");//获取FeatureSource
if( featureSource instanceof SimpleFeatureStore){
SimpleFeatureStore store = (SimpleFeatureStore) featureSource; // write access!
// store.modifyFeatures("name", "xxx", CQL.toFilter("name is null"));
Filter filter = CQL.toFilter("name = 'xxx'");
store.removeFeatures(filter);
}
}
可以看到,我们总的Feature要素数量已经由25变为23,并且目标Feature要素也已经被删除掉。