目录
前言
一、在GQIS中浏览数据
1、关于空间参考
2、属性表格
二、GDAL的相关驱动及解析实战
1、GDAL中的KMZ驱动
2、GDAL实际解析
三、数据解析成果
1、KML解析结果
2、KMZ文件入库
四、总结
前言
在前面的博客中讲过纯Java实现Google地图的KMZ和KML文件的解析,不知道有没有小伙伴在工作中实用了。如果有了实战的朋友一定会发现,虽然在前面的博客中,能解决我们对KMZ和KML文件的解析,也能正确的构造WKT数据,也就是意味着我们可以将数据插入到数据库中或者是转为其它的数据格式。但是,使用纯Java解析的方式还是存在一些问题的。
下面我们首先来看看使用纯Java的解析模式会存在什么问题。这里我们先说结论,然后再使用针对性的解决办法。其实使用纯Java解析模式主要的问题是:1、没有完全识别出KMZ文件的属性值,有一些属性值丢失(当然,这里是通过桌面软件来进行属性的查看),如果想其实是可以解析到。但确实从之前的解析包中没有发现其它属性的获取方法。2、获取不到空间参考。3、对于KMZ文件,首先需要进行解压缩,然后再读取KML文件,解析代码比较繁琐。
针对上面的这些问题,本文主要采用GDAL组件来进行KMZ和KML文件的解析,在C站或者其它的技术博客中查找相关知识点会发现,使用JAVA和GDAL实现数据解析的相关介绍内容比较少,很多都是C++或者其它语言的示例,因此这里分享如何使用JAVA语言来进行开发。博文首先使用Qgis软件展示KMZ或者KML文件的全部属性和空间参考,然后介绍GDAL中对于读取KMZ和KML的驱动的讲解,基于GDAL的统一解析模式来实现对KMZ和KML文件的统一解析,避免了过多的代码,最后将KMZ数据进行了数据库插入的操作,从而实现从数据解析、转换、存储的一个完整过程。通过本文您可以掌握使用Java语言通过GDAL实现对KMZ和KML文件的统一处理和存储操作,如果您当前也有这样的需求,欢迎进行交流。
一、在GQIS中浏览数据
既然KMZ和KML也是一种空间矢量数据,那么QGIS也是一定提供了支持的。为了展示在空间矢量数据中的所有属性数据,这里我们采用QGIS来展示。当然您也可以采用Arcgis或者SuperMap等桌面端软件同样也是可以的。
1、关于空间参考
首先打开QGIS这款软件,在左边的菜单中选择需要展示的KMZ数据,如下图所示:
然后将所有的图层都加载到地图中,点击全选。然后点击OK,所有的图层就会自动添加到地图中。 在图层窗口中就可以看到这些子图层的数据和具体的信息。
使用鼠标右键,点击图层的属性,可以看到这个图层的属性信息,比如基础信息、属性信息等。 这里以空间参考信息为例:
序号 | 参数信息 | 参数值 |
1 | 存储 | LIBKML |
2 | 编码 | UTF-8 |
3 | 几何图形 | Point (PointZ) |
4 | 空间参考CRS | EPSG:4326 - WGS 84 - 地理的 |
5 | 单位 | 度 |
6 | 要素数目 | 457 |
2、属性表格
矢量数据中,属性表格是很重要的数据,因此十分有必要介绍一下属性信息。在QGIS中,可以查看空间数据的属性表格,同样的属性查看选项中。点击字段即可查看。
序号 | 参数名 | 字段类型 | 说明 |
1 | Name | String | 名称 |
2 | description | String | 说明 |
3 | timestamp | DateTime | 时间戳 |
4 | begin | DateTime | |
5 | end | DateTime | |
6 | altitudeMode | String | |
7 | tessellate | int | |
8 | extrude | int | |
9 | visibility | int | |
10 | drawOrder | int | |
11 | icon | String |
在QGIS中查看属性信息如下所示:
我们来实际看一下表中的数据大概是什么,打开数据属性表,如下所示:
如果您看过之前的文章,就会发现之前的数据解析,其实只解析到了description字段 ,其它字段基本上都是空值,哪怕实际上是有值的。
二、GDAL的相关驱动及解析实战
在使用Qgis软件对上述数据进行了深度解析之后,我们基本对数据有了一个大概的任务。接下来我们来看看GDAL中对于KMZ和KML文件解析驱动的说明相关的知识。为实际的文件解析提供坚实的基础。
1、GDAL中的KMZ驱动
既然KML和KMZ文件也是矢量文件的一种,那么首先我们来看看GDAL是否支持这两种文件。首先打开GDAL的矢量驱动连接GDAL矢量驱动器。界面如下图所示:
其实GDAL支持的矢量文件是非常多的,这里仅将我们关注的KMZ和KML文件进行展示,其它的文件格式在后续的博文中我们慢慢讲解。
JML | JML:OpenJUMP JML格式 | Yes | Yes | (读取支持需要libexpat) |
KML | 锁眼标记语言 | Yes | Yes | (读取支持需要libexpat) |
LIBKML | LIBKML驱动程序(.kml.kmz) | Yes | Yes | libkml语言 |
LVBAG | 荷兰卡达斯特LV包2.0提取物 | 不 | 不 | libexpat公司 |
MapML | 地图管理语言 | Yes | Yes | 默认内置 |
Memory | Memory | Yes | Yes | 默认内置 |
MITAB | MapInfo TAB和MIF/MID | Yes | Yes | 默认内置 |
MongoDBv3 | MongoDBv3 | Yes | Yes | Mongo CXX>=3.4.0客户端库 |
MSSQLSpatial | Microsoft SQL Server空间数据库 | Yes | Yes | ODBC库 |
MVT | MVT:地图框矢量平铺 | Yes | Yes | (需要SQLite和GEOS提供写支持) |
其中我们发现LIBKML,这个驱动是支持对应的KMZ和KML文件的读写的。关于这个驱动的说明,可以在GDAL的官网看得到。它的描述如下:
LIBKML驱动程序是 Libkml ,的参考实现 KML 阅读和写作,以跨平台C++的形式出现。必须生成并安装Libkml才能使用此OGR驱动程序。注意:您需要构建libkml 1.3或master。 注意,如果您构建并包含这个LIBKML驱动程序,它将成为ogr的KML的默认读取器,覆盖前面的 KML driver . 仍然可以通过命令行指定KML或LIBKML作为输出驱动程序 来自Google的Libkml为任何有效的KML文件提供读取服务。但是,请注意,一些KML设施并没有映射到OGR用作其内部结构的简单功能规范中。因此,驱动程序将尽最大努力理解libkml读入ogr的KML文件的内容,但是您的里程数可能会有所不同。请尝试一些KML文件作为示例,以了解理解的内容。特别是,多个深度的特征集嵌套将被展平以支持ogr的内部格式。
它的数据来源如下:
其它更多的属性信息,请大家及时的去GDAL的官网进行查询。有了上述的知识后,我们就可以采用GDAL来进行数据的解析。
2、GDAL实际解析
本小节将重点以代码的形式对GDAL如何解析KMZ和KML文件进行深入说明。在上面一个小节中我们已经完成了驱动包说明,GDAL是一个支持很多种格式的数据解析程序包,在程序运行的时候,它通过驱动的桥接口实现了不同的数据解析,然后给予统一的数据格式返回对应的属性数据。为我们下一步进行数据的存储和分析提供基础。
下面以KML文件为例,给出具体的针对KML的解析代码。
@Test
public void testReadKml() {
//指定文件的名字和路径
String strVectorFile ="C:/BaiduDownload/基地-地图数据(kmz)/usa.kml";
// 注册所有的驱动
ogr.RegisterAll();
// 为了支持中文路径,请添加下面这句代码
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8","YES");
// 为了使属性表字段支持中文,请添加下面这句
gdal.SetConfigOption("SHAPE_ENCODING","CP936");
//读取数据,这里以ESRI的shp文件为例
String strDriverName = "LIBKML";
//创建一个文件,根据strDriverName扩展名自动判断驱动类型
org.gdal.ogr.Driver oDriver =ogr.GetDriverByName(strDriverName);
if (oDriver == null) {
System.out.println(strDriverName+ " 驱动不可用!\n");
return;
}
DataSource dataSource = oDriver.Open(strVectorFile);
System.out.println("图层总数=="+dataSource.GetLayerCount());
//System.out.println(data);
for(int w =0;w<dataSource.GetLayerCount();w++) {
//Layer layer = dataSource.GetLayer("test");
Layer layer = dataSource.GetLayer(w);
String layerName = layer.GetName();
System.out.println("图层名称:"+layerName);
SpatialReference spatialReference = layer.GetSpatialRef();
FeatureDefn featureDefn = layer.GetLayerDefn();
int fieldCount = featureDefn.GetFieldCount();
Map<String,Object> fieldMap = new HashMap<String,Object>();
List<String> filedList = new ArrayList<String>(fieldCount);
for(int i=0; i<fieldCount; i++){
FieldDefn fieldDefn = featureDefn.GetFieldDefn(i);
//得到属性字段类型
int fieldType = fieldDefn.GetFieldType();
String fieldTypeName = fieldDefn.GetFieldTypeName(fieldType);
//得到属性字段名称
String fieldName = fieldDefn.GetName();
//fieldMap.put(fieldTypeName,fieldName);
filedList.add(fieldName);
}
System.out.println(filedList);
System.out.println("**************************************");
long featureCount = layer.GetFeatureCount();
System.out.println("图层要素个数:"+featureCount);
for(int i=0; i<featureCount; i++){
Feature feature = layer.GetFeature(i);
if(null == feature) continue;
for(int k=0; k<filedList.size(); k++){
String fvalue = feature.GetFieldAsString(filedList.get(k));
System.out.print(filedList.get(k) + ":"+fvalue + "\t");
}
System.out.println();
}
}
}
如果看过我之前的博客的话,对于GDAL如何实现空间矢量数据的解析应该比较了解。这里需要注意的只有一点,就是解析驱动。对于KML文件,一定要设置对应的驱动,如String strDriverName = "LIBKML";
三、数据解析成果
以上的代码重点演示了如何使用GDAL来解析KML数据,本节将把上面小节的程序运行起来,然后把运行结果反馈给用户。对于KML数据,仅将数据在控制台进行输出打印,而对于KMZ数据,我们需要将数据保存的空间数据库中。
1、KML解析结果
在程序IDE中运行上面的代码,在控制台中可以看到以下输出。
Name:NAS Keflavik IC description: timestamp: begin: end: altitudeMode: tessellate:-1 extrude:0 visibility:-1 drawOrder: icon:
Name:Campbell Barracks description: timestamp: begin: end: altitudeMode: tessellate:-1 extrude:0 visibility:-1 drawOrder: icon:
Name:Giebelstadt Army Airfield description: timestamp: begin: end: altitudeMode: tessellate:-1 extrude:0 visibility:-1 drawOrder: icon:
Name:NAF Atsugi JA description: timestamp: begin: end: altitudeMode: tessellate:-1 extrude:0 visibility:-1 drawOrder: icon:
Name:Leighton Barracks description: timestamp: begin: end: altitudeMode: tessellate:-1 extrude:0 visibility:-1 drawOrder: icon:
如果能看到以上的信息,说明已经正确的解析了KML文件。
2、KMZ文件入库
使用GDAL来解析KMZ文件,因为驱动类型是一样的,因此不需要更换驱动,值需要把文件名给修改下,关键代码如下(重复代码已删除):
String strVectorFile ="C:/BaiduDownload/基地-地图数据(kmz)/全球基地.kmz";
为了实现对Kmz保存到数据库中,因此这里我们需要创建对应的数据表,同时要基于MP新建业务处理实现类。首先将数据库的表结构分享给大家,需要的同学可以在这里复制。
CREATE TABLE "public"."biz_usa_military_base" (
"id" int8 NOT NULL,
"en_name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"en_desc" varchar(1024) COLLATE "pg_catalog"."default",
"cn_name" varchar(255) COLLATE "pg_catalog"."default",
"remark" varchar(255) COLLATE "pg_catalog"."default",
"geom" "public"."geometry",
"create_by" varchar(64) COLLATE "pg_catalog"."default",
"create_time" timestamp(6),
"update_by" varchar(64) COLLATE "pg_catalog"."default",
"update_time" timestamp(6),
CONSTRAINT "pk_biz_usa_military_topics" PRIMARY KEY ("id")
);
ALTER TABLE "public"."biz_usa_military_base"
OWNER TO "ghy01";
CREATE INDEX "idx_biz_usa_military_base_geom" ON "public"."biz_usa_military_base" USING gist (
"geom" "public"."gist_geometry_ops_2d"
);
COMMENT ON COLUMN "public"."biz_usa_military_base"."id" IS '主键';
COMMENT ON COLUMN "public"."biz_usa_military_base"."en_name" IS '英文名称';
COMMENT ON COLUMN "public"."biz_usa_military_base"."en_desc" IS '英文描述';
COMMENT ON COLUMN "public"."biz_usa_military_base"."cn_name" IS '英文名称';
COMMENT ON COLUMN "public"."biz_usa_military_base"."remark" IS '备注';
COMMENT ON COLUMN "public"."biz_usa_military_base"."geom" IS '空间信息';
COMMENT ON COLUMN "public"."biz_usa_military_base"."create_by" IS '创建人';
COMMENT ON COLUMN "public"."biz_usa_military_base"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."biz_usa_military_base"."update_by" IS '更新人';
COMMENT ON COLUMN "public"."biz_usa_military_base"."update_time" IS '更新时间';
COMMENT ON TABLE "public"."biz_usa_military_base" IS '基地信息表';
根据数据的表结构,我们需要定义Mapper和实体类来进行对应。
package com.yelang.project.extend.militarytopics.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yelang.framework.handler.PgGeometryTypeHandler;
import com.yelang.framework.web.domain.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* 基地实体类
* @author 夜郎king
*/
@TableName(value ="biz_usa_military_base",autoResultMap = true)
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class UsaMilitaryBase extends BaseEntity{
private static final long serialVersionUID = 9052078556566456025L;
@TableId
private Long id;//主键
@TableField(value = "en_name")
private String enName;
@TableField(value = "en_desc")
private String enDesc;
@TableField(value = "cn_name")
private String cnName;
private String remark;
@TableField(typeHandler = PgGeometryTypeHandler.class)
private String geom;
@TableField(exist=false)
private String geomJson;
}
其它的业务类代码比较简单,只是将批量提交的集合进行保存,这里不赘述。重点介绍一下就是GDAL解析完数据后,我们需要将属性信息保存到对象中,然后把每个对象添加到集合中,最后把集合保存到数据库中。获取信息后将数据设置到对象中的关键代码如下:
for(int i=0; i<featureCount; i++){
Feature feature = layer.GetFeature(i);
if(null == feature) continue;
String enName = feature.GetFieldAsString("Name");
String enDesc = feature.GetFieldAsString("description");
UsaMilitaryBase base = new UsaMilitaryBase();
base.setEnName(enName);
base.setEnDesc(enDesc);
base.setCreateTime(now);
Geometry geom = feature.GetGeometryRef();
//step 1、生成原始wkt
String wkt = geom.ExportToWkt();
wkt = "SRID=" + srid +";" + wkt;//拼接srid,实现动态写入
base.setGeom(wkt);
dataList.add(base);
}
最后调用Service的批量更新功能进行数据的插入。
if(dataList.size() >0) {
usaMilitaryBaseService.saveBatch(dataList, 300);
在测试用例中执行以上代码,在数据库中查询这张表的数据,可以看到以下信息。
到此就完成了KML数据的解析以及KMZ数据的解析及入库。这里没有复杂的业务逻辑,因此实现起来并不是很复杂。
四、总结
以上就是本文的主要内容,本文主要采用GDAL组件来进行KMZ和KML文件的解析,在C站或者其它的技术博客中查找相关知识点会发现,使用JAVA和GDAL实现数据解析的相关介绍内容比较少,很多都是C++或者其它语言的示例,因此这里分享如何使用JAVA语言来进行开发。博文首先使用Qgis软件展示KMZ或者KML文件的全部属性和空间参考,然后介绍GDAL中对于读取KMZ和KML的驱动的讲解,基于GDAL的统一解析模式来实现对KMZ和KML文件的统一解析,避免了过多的代码,最后将KMZ数据进行了数据库插入的操作,从而实现从数据解析、转换、存储的一个完整过程。通过本文您可以掌握使用Java语言通过GDAL实现对KMZ和KML文件的统一处理和存储操作,如果您当前也有这样的需求,欢迎进行交流。行文仓促,难免有不足之处,欢迎专家朋友们在评论区留言批评指正,不慎感激。