基于Java的GeoTools对Shapefile文件属性信息深度解析

举报
夜郎king 发表于 2025/06/27 10:00:39 2025/06/27
【摘要】 本文重点讲解在Java当中如何使用GeoTools来进行属性表格的解析,首先在Qgis中重点介绍属性表格的字段信息,然后介绍一种常见的属性字段信息解析方式,其次介绍基于dbf的属性信息解析方法,直接解析dbf文件的方法在很多网络知识中很少见,但是却非常有用,比如在获取double类型的数值时,想获取精度值,就可以通过这种方式来获取。学习本文,不仅更加熟悉GeoTools对属性表格的解析,同时对于如

目录


前言

一、Shapefile的属性列表信息

1、属性表格信息

2、属性表格包含的要素 

二、GeoTools对属性表格的解析

1、常规解析方法

2、基于dbf文件的属性信息读取

三、总结



前言

        ESRI Shapefile(shp),或简称shapefile,是美国环境系统研究所公司(ESRI)开发的一种空间数据开放格式。该文件格式已经成为了地理信息软件界的一个开放标准,这表明ESRI公司在全球的地理信息系统市场的重要性。Shapefile也是一种重要的交换格式,它能够在ESRI与其他公司的产品之间进行数据互操作。Shapefile文件用于描述几何体对象:点,折线与多边形。例如,Shapefile文件可以存储井、河流、湖泊等空间对象的几何位置。除了几何位置,shp文件也可以存储这些空间对象的属性,例如一条河流的名字,一个城市的温度等等。

Shapefile属于一种矢量图形格式,它能够保存几何图形的位置及相关属性。但这种格式没法存储地理数据的拓扑信息。Shapefile在九十年代初的ArcView GIS的第二个版本被首次应用。许多自由的程序或商业的程序都可以读取Shapefile。Shapefile是一种比较原始的矢量数据存储方式,它仅仅能够存储几何体的位置数据,而无法在一个文件之中同时存储这些几何体的属性数据。因此,Shapefile还必须附带一个二维表用于存储Shapefile中每个几何体的属性信息。Shapefile中许多几何体能够代表复杂的地理事物,并为他们提供强大而精确的计算能力。
        Shapefile文件指的是一种文件存储的方法,实际上该种文件格式是由多个文件组成的。其中,要组成一个Shapefile,有三个文件是必不可少的,它们分别是".shp", ".shx"与 ".dbf"文件。表示同一数据的一组文件其文件名前缀应该相同。例如,存储一个关于湖的几何与属性数据,就必须有lake.shp,lake.shx与lake.dbf三个文件。而其中“真正”的Shapefile的后缀为shp,然而仅有这个文件数据是不完整的,必须要把其他两个附带上才能构成一组完整的地理数据。除了这三个必须的文件以外,还有八个可选的文件,使用它们可以增强空间数据的表达能力。所有的文件名都必须遵循MS DOS的8.3文件名标准(文件前缀名8个字符,后缀名3个字符,如shapefil.shp),以方便与一些老的应用程序保持兼容性,尽管现在许多新的程序都能够支持长文件名。此外,所有的文件都必须位于同一个目录之中。

        由于Shapefile有着广泛的适用性,是很多公共地理服务所采用的矢量文件规范之一。因此有必要对Shapefile文件进行深度的解析。如果将shapefile类比为一个数据库,那么它就是一个database,而其中的属性表则是一张一张的物理表。他们两存在天然的对应关系。虽然在前面的博客中,我们已经对shapefile的解析有了基本的认识。但是对属性表格的解析并没有介绍得很详细。本文则重点讲解在Java当中,如何使用GeoTools来进行属性表格的解析,首先在Qgis中重点介绍属性表格的字段信息,然后介绍一种常见的属性字段信息解析方式,其次介绍基于dbf的属性信息解析方法,直接解析dbf文件的方法在很多网络知识中很少见,但是却非常有用,比如在获取double类型的数值时,想获取精度值,就可以通过这种方式来获取。学习本文,不仅更加熟悉GeoTools对属性表格的解析,同时对于如何快速的将Shapefile的属性模型映射成空间库模型有更进一步的认识。希望对您有一定的帮助。

一、Shapefile的属性列表信息

        本文主要是讲解如何来深度解析shapefile的属性表,因此就有必要对属性列表信息进行一个详细的讲解。

1、属性表格信息

        shapefile文件其实是文件数据库的一种,它将数据的信息写入到shapefile文件中,然后在其它的电脑终端中可以直接共享这些数据,解析这些属性信息。为了介绍方便,我们将shapefile与常见的关系型数据库进行对比,可以这么进行理解。一个shapefile就是一个database,而一张表就是一个属性表。属性表格中的字段、数据类型、长度、精度等就是与关系型数据库中的数据列是一个意思,而数据行基本就是数据库中一行数据的意思。下面是以某年度某城市POI的矢量数据为例进行讲解,在Qgis中打开字段表如下:

2、属性表格包含的要素 

        既然与关系型数据库的数据定义类似,那么就有必要对属性表格的每一列的信息进行精准的定义。包括数据类型、数据长度、精度等,下面结合示例数据来进行一个深入讲解。

序号 参数 参数
1 名称 具体的字段名称,如name
2 别名 类比于关系数据库的字段别名
3 类型 数据类型,比如QString
4 类型名 数据类型名,比如String,Real
5 长度 字段的长度,比如:255
6 精度 double等类型的精度,如11
7 注解 其它辅助信息

        有了上面的信息之后,我们也可以根据属性表格来进行空间表的创建,这样管理起来也比较方便。在数据库中定义好每一个列的数据信息之后,就可以将具体的空间信息填充到每一行中,这时候可能就需要外业和内业的工作人员通力配合。

在了解上面的基本信息后,在下面的章节中,我们就要进行属性表格的深度解析操作。 

二、GeoTools对属性表格的解析

        本节重点使用两种方法来进行属性表格解析的实战,第一种方法是常规的通过featureType的方式来获取属性表格的信息。第二种是基于GeoTools来读取dbf文件来获取元数据信息。这里不仅说明两种方式的优缺点,也说明两者的具体开发代码,帮助大家来深度掌握GeoTools的属性表解析操作。

1、常规解析方法

        首先讲解普通的解析方法,同时这种解析的方式在互联网或者很多博客当中是最常见的。也就是直接解析shp文件。具体的解析步骤流程如下所示:

这里给出详细的遍历代码,如下所示:

public void testAttrLength() throws Exception {
		// 指定Shapefile的文件路径
		String shpFile = "C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/风景名胜.shp";
		FileDataStore dataStore = FileDataStoreFinder.getDataStore(new File(shpFile));
		ShapefileDataStore shapefileDataStore = new ShapefileDataStore(new File(shpFile).toURI().toURL());
		System.out.println(shapefileDataStore.getCharset());
		// 打开数据存储
		String[] typeNames = dataStore.getTypeNames();
		System.out.println(typeNames.length);
		for (String type : typeNames) {
			System.out.println(type);
		}
		// 获取特征类型
		SimpleFeatureType featureType = dataStore.getSchema(dataStore.getTypeNames()[0]);
		CoordinateReferenceSystem crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();
		System.out.println("坐标参考系统:" + crs);
		// 获取属性名
		List<String> attributeNames = featureType.getAttributeDescriptors().stream().map(attr -> attr.getLocalName())
				.collect(Collectors.toList());
		System.out.println("Attributes: " + attributeNames);
		System.out.println("以下是属性信息的深度解析:----------------------------------------");
		List<AttributeDescriptor> attrDescList = featureType.getAttributeDescriptors();
		System.out.println(attrDescList.size());
		for (AttributeDescriptor attrDesc : attrDescList) {
			System.out.println(attrDesc);
			System.out.println("属性详情:");
			AttributeType attrType = attrDesc.getType();
			System.out.println("userData=========>" + attrType.getUserData());
			System.out.println("数据库数据类型:" + convertShpFieldType2H2GISOrPG(attrType.getBinding()));
			System.out.println(attrType);
			System.out.println("name=" + attrDesc.getName() + "\tLocalName=" + attrDesc.getLocalName() + "\t"
					+ attrDesc.getMaxOccurs() + "\t" + attrDesc.getMinOccurs());
			List<Filter> restrictions = attrType.getRestrictions();
			System.out.println("过滤器为 : " + restrictions);
			for (Filter filter : restrictions) {
				if (filter instanceof IsLessThenOrEqualToImpl) {
					IsLessThenOrEqualToImpl impl = (IsLessThenOrEqualToImpl) filter;
					Expression exp1 = impl.getExpression1();
					Expression exp2 = impl.getExpression2();
					System.out.println(exp1.getClass());
					System.out.println(exp2.getClass());
					if (exp1 instanceof LengthFunction && exp2 instanceof LiteralExpressionImpl) {
						LengthFunction length = (LengthFunction) exp1;
						LiteralExpressionImpl literal = (LiteralExpressionImpl) exp2;
						System.out.println( length.getName() + ":\t" + literal.getValue());
					}
				}
			}
			System.out.println("**************************************************");
		}
	}

可以看到,这里关于数据的属性列表信息,尤其是获取字段的长度和精度时,是通过在过滤器中的具体子类来实现的。在Filter列表中,一定会用到两个表达式对象,其中的一个是:IsLessThenOrEqualToImpl,另一个则是LiteralExpressionImpl,通过这两个类来实现长度的控制。因此如果需要在代码中获取具体的长度,可以通过这两个表达式对象来获取。同时,在进行数据的解析时,虽然我们在前面对两者进行了简单的类比,但毕竟两者不是一个东西,因此在进行数据的存储时,具体的设置还是有一定的区分的,比如对应的具体数据类型,这里我们来提供一个类来进行转换。

/**
* convert shpFileType to db field type
** 备注:目前arcgis的字段类型有:短整型 长整型 浮点型 双精度 文本 日期
* @param value
* @return
*/
public static String convertShpFieldType2H2GISOrPG(Class<?> value) throws Exception {
   if (value == String.class) {//文本
        return "varchar";
   }
   if (value == Integer.class) {//短整型
       return "int";
   }
   if (value == Long.class) {//长整型
       return "bigint";
   }
   if (value == Double.class || value == Float.class) {//浮点型 双精度 保留精度,比如在金币上运算更安全
      return "numeric";
   }
   if (value == Date.class) {
      return "TIMESTAMP WITH TIME ZONE ";//日期, 使用包含时区的来处理
   }
   if (Geometry.class.isAssignableFrom(value)) {
       return "geometry";
   }
   //除了上述,真不知道还会有啥了。除非arcgis的shp又增加了新类型?!那无能为力了,抛出异常吧
   throw new Exception("不支持的类型!" + value.getName());
   //
   //if (value.getSuperclass().getName().equals(String.class))
   // return null;
}

在GeoTools中,关于Filter的相关类,非常的庞大,这里不全部展开,以防止抓不住重点。先给一个Filter的类图给朋友们,感兴趣的可以先了解一下。

通过以上的步骤和示例代码,我们可以在控制台中输出以下信息:

为了方便展示字段的解析,这里特意将复制一段处理的输出信息:

AttributeDescriptorImpl WGS84_纬 <Double:Double> nillable 0:1
属性详情:
userData=========>{}
数据库数据类型:numeric
AttributeTypeImpl Double<Double>
restrictions=[ length([.]) <= 19 ]
name=WGS84_纬	LocalName=WGS84_纬	1	0
过滤器为 : [[ length([.]) <= 19 ]]
class org.geotools.filter.LengthFunction
class org.geotools.filter.LiteralExpressionImpl
length:	19

可以看到,通过上述的代码就可以获取属性表格的字段名、字段类型、字段的长度等信息,有了这些信息,我们其实可以做的事情很多了。不知道大家发现一个问题没有,在上述的代码中,并没有返回精度,即11位小数的长度。上述代码是无法实现的。那么有没有什么办法来实现精度值的获取呢。

2、基于dbf文件的属性信息读取

        众所周知,在shapefile文件夹中,除了shp文件之外,另外一个prj和dbf文件也是缺一不可的。既然我们无法通过读取shp来实现精度的精确读取,那么是否可以从这两个文件当中来获取呢?shp文件有我们定义好的空间信息,而大量的属性表格,我们是存放在dbf中,因此我们来测试读取dbf文件看能否找到另外的一条大路。

        幸运的是,在Geotools当中,官方提供了DbaseFileReader这个类,通过这个类实现对dbf文件的读取,之前也查找了一些相关的资料,但是很少有博主写这种实现模式。这里提供基于DbaseFileReader的实现方式,代码实现更简单,性能更好。其原理非常简单,通过DbaseFileReader来获取DbaseFileHeader,然后就可以遍历DbaseFileHeader中的信息,就可以实现所有信息的识别。代码如下:

 /**
 * - 读取dbf文件
 * @throws IOException
 */
 @Test
 public void testReadDbf() throws IOException {
	File dbfFile = new File("C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/风景名胜.shp");
	// 打开 DBF 文件
	ShpFiles shpFile = new ShpFiles(dbfFile);
	System.out.println(Charset.defaultCharset().toString());
	DbaseFileReader dbfReader = new DbaseFileReader(shpFile, true, Charset.defaultCharset());
	// 读取 DBF 文件的头信息
	DbaseFileHeader header = dbfReader.getHeader();
	System.out.println(header.getNumFields());
	System.out.println(header.getNumRecords());
	for (int i = 0; i < header.getNumFields(); i++) {
		System.out.println(header.getFieldName(i) + "\t" + header.getFieldLength(i) + "\t" + header.getFieldType(i)
		+ "\t" + header.getFieldDecimalCount(i));

	}
	// 关闭 DBF 文件读取器
	dbfReader.close();
}

 想要获取属性就是通过获取Head对象即可实现属性列表的深度解析。返回DbaseFileHead,它的方法如下表所示

 在Heade对象中,要想获取字段的长度和精度,主要是使用下列两个方法,分别是:getFieldLength(index)和getFieldDecimalCount(index)。在IDE中运行以上程序后,可以在控制台中看到如下输出:

名称	254	C	0
大类	254	C	0
中类	254	C	0
小类	254	C	0
地址	254	C	0
省	254	C	0
市	254	C	0
区	254	C	0
WGS84_经	19	F	11
WGS84_纬	19	F	11

可以看到,这里不仅输出了数据的长度,同时还输出了数据的精度。到此,使用第二种方式来进行dbf文件的解析已经成功实现。

三、总结

        以上就是本文的主要内容,本文则重点讲解在Java当中,如何使用GeoTools来进行属性表格的解析,首先在Qgis中重点介绍属性表格的字段信息,然后介绍一种常见的属性字段信息解析方式,其次介绍基于dbf的属性信息解析方法,直接解析dbf文件的方法在很多网络知识中很少见,但是却非常有用,比如在获取double类型的数值时,想获取精度值,就可以通过这种方式来获取。学习本文,不仅更加熟悉GeoTools对属性表格的解析,同时对于如何快速的将Shapefile的属性模型映射成空间库模型有更进一步的认识。行文仓促,难免有许多不足之处,如有不足还请各位专家博主在评论区留言指出,不胜感激。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。