当前位置:首页 » 《关于电脑》 » 正文

在Java中使用GeoTools解析POI数据并存储到PostGIS实战

28 人参与  2024年11月11日 08:01  分类 : 《关于电脑》  评论

点击全文阅读


目录

前言

一、POI数据相关介绍

1、原始数据说明

2、空间数据库表设计

二、POI数据存储的设计与实现

1、对应的数据模型对象的设计

2、属性表数据和空间信息的读取

3、实际运行结果

三、总结


前言

        POI点,全称为Point of Interest(兴趣点),是指一切被抽象为点要素的空间地理实体,尤其是与人们生活密切相关的地理要素,如小区、餐馆、商场、车站等。POI是地理信息数据的一部分,用于丰富地图内容,提供用户所需的地理信息,POI数据在地图应用、导航系统、位置服务、市场营销、城市规划等领域有着广泛应用。

        POI数据涵盖了丰富的地理信息和属性特征,能够反映出一个地区的商业、文化、交通等各方面的特色,每个POI点主要包含四方面的信息:名称、类别、坐标、分类。(1)名称:POI的名称或标题。(2)地址:POI的详细地址。(3)经纬度坐标:POI的地理位置坐标,通过经纬度坐标将现实世界中的地点与数字世界进行关联。(4)分类:POI所属的类别。(5)描述:关于POI的详细描述。(6)联系方式:如电话、网址等。(7)附加信息:如营业时间、用户评价、图片等。可以看出,POI数据的核心要素在于其地理位置信息和属性描述。POI数据的特点有多样性(涵盖多种类型的地理实体)、动态性(POI信息会随时间变化而更新)、空间性(具有明确的地理位置)、可查询性(用户可以根据需求检索特定的POI)。

        因此,可以看出,学会管理并正确的使用POI数据,对于我们进行城市规划、导航服务、位置服务、智慧旅游、智慧应急等方面有重要的应用。在我们应用这些数据之前,需要先将POI数据管理起来。本文即在这样的场景下产生。与GDAL的shp数据处理方式不同,在GeoTools中的处理方法有一定的不同。文章分享的方法可以在分布式环境中利用Mybatis-Plus这种ORM框架进行快速的空间数据批量插入。与GeoTools官方提供的PostGIS数据读写相比,本文分享的方法将更加方便,易于与其它项目进行集成。

一、POI数据相关介绍

        在讲解如何利用GeoTools进行数据管理时,我们先对POI数据进行简单的说明。POI数据不仅包含丰富的空间位置信息,同时包含很丰富的分类,以餐饮类的POI为例,我们可以分为大类、中类和小类。

1、原始数据说明

按照大类分为餐饮服务、中类分为中餐厅、西餐、烧烤,小类可以分为湘菜、川菜等。

        在这里,从行政区划上,我们把POI按照其归属进行了划分。在后续的统计中可以充分的利用这些数据。 这里的数据也是从互联网上抓取的数据,大多数的POI数据都是有标准的大类、中类、小类。当然,在拿到的部分POI数据,比如商业住宅的数据,

        在商务住宅的POI数据中,所有的分类数据都集中到了大类这个字段,而另外两个字段比如中类和小类则是空的。其它的数据都是正确的,因此我们需要对商务住宅这个大类的数据进行简单的分拆。然后对应到具体的大类、中类、小类上面。 

2、空间数据库表设计

        在上一篇博客中,我们对POI信息表的空间数据属性字段有了具体的了解。与Shapefile等空间数据表相同,在PostGIS空间数据库中,我们也需要设计对应的空间表来存储对应的空间数据。

        以上就是Shapefile中的属性字段信息,按照一一映射的原则,我们在PostGIS当中也同样的来设计对应的空间表。 

        大家可以使用自己熟悉的工具来进行表结构的设计,然后在数据库客户端软件中进行创建表结构即可。这里同样将数据表的表结构贴出来,供大家参考:

CREATE TABLE "public"."biz_poi_info" (  "pk_id" int8 NOT NULL,  "name" varchar(255) COLLATE "pg_catalog"."default",  "main_category" varchar(255) COLLATE "pg_catalog"."default",  "type" varchar(255) COLLATE "pg_catalog"."default",  "subtype" varchar(255) COLLATE "pg_catalog"."default",  "address" varchar(255) COLLATE "pg_catalog"."default",  "province_name" varchar(255) COLLATE "pg_catalog"."default",  "city_name" varchar(255) COLLATE "pg_catalog"."default",  "area_name" varchar(255) COLLATE "pg_catalog"."default",  "lon_wgs84" numeric(18,11),  "lat_wgs84" numeric(18,11),  "geom" "public"."geometry",  "year" int4,  "create_by" int8,  "create_time" timestamp(6),  "update_by" int8,  "update_time" timestamp(6),  CONSTRAINT "pk_biz_poi_info" PRIMARY KEY ("pk_id"));CREATE INDEX "idx_biz_poi_info_geom" ON "public"."biz_poi_info" USING gist (  "geom" "public"."gist_geometry_ops_2d");COMMENT ON COLUMN "public"."biz_poi_info"."pk_id" IS 'pk_id';COMMENT ON COLUMN "public"."biz_poi_info"."name" IS '名称';COMMENT ON COLUMN "public"."biz_poi_info"."main_category" IS '大类,比如:餐饮服务';COMMENT ON COLUMN "public"."biz_poi_info"."type" IS '中类,比如:中餐';COMMENT ON COLUMN "public"."biz_poi_info"."subtype" IS '小类';COMMENT ON COLUMN "public"."biz_poi_info"."address" IS '地址';COMMENT ON COLUMN "public"."biz_poi_info"."province_name" IS '省';COMMENT ON COLUMN "public"."biz_poi_info"."city_name" IS '市';COMMENT ON COLUMN "public"."biz_poi_info"."area_name" IS '区';COMMENT ON COLUMN "public"."biz_poi_info"."year" IS '年份';COMMENT ON COLUMN "public"."biz_poi_info"."create_by" IS '创建人';COMMENT ON COLUMN "public"."biz_poi_info"."create_time" IS '创建时间';COMMENT ON COLUMN "public"."biz_poi_info"."update_by" IS '更新人';COMMENT ON COLUMN "public"."biz_poi_info"."update_time" IS '更新时间';COMMENT ON TABLE "public"."biz_poi_info" IS '保存兴趣点信息表';

        以上就是对POI数据进行简单的介绍,以及对POI数据的时空数据表的物理模型和表结构进行了讲解。请注意,在进行空间数据库设计的时候,请务必安装PostGIS的扩展,否则上面的SQL将无法运行,为了在后面的空间分析和查询的服务中发挥出更好的性能,我们给Geometry字段建立空间索引。

二、POI数据存储的设计与实现

        在介绍完POI的属性表结构和空间数据库物理表模型之后,我们来具体讲解如何使用GeoTools来进行属性的读取,并调用Mybatis-Plus的批量入库代码,将2020年星城长沙的POI数据进行入库操作。

1、对应的数据模型对象的设计

        众所周知,在面向对象的设计中,我们需要给数据库模型的表设计一个对应的实体类,这里简称为实体类。一般字段与数据库物理表是一一对应的。这里我们直接给出原始的代码:

package com.yelang.project.extend.earthquake.domain;import java.io.Serializable;import java.math.BigDecimal;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;@TableName(value = "biz_poi_info", autoResultMap = true)@NoArgsConstructor@AllArgsConstructor@Setter@Getter@ToString/** * - 兴趣点信息表实体类 * @author 夜郎king * */public class PoiInfo extends BaseEntity implements Serializable {private static final long serialVersionUID = -9163178655131959272L;@TableId(value = "pk_id")private Long pkId;// 主键private String name;// 名称@TableField(value = "main_category")private String mainCategory;// 大类,比如:餐饮服务private String type;// 中类,比如:中餐private String subtype;// 小类private String address;// 地址@TableField(value = "province_name")private String provinceName;// 省@TableField(value = "city_name")private String cityName;// 市@TableField(value = "area_name")private String areaName;// 区@TableField(value = "lon_wgs84")private BigDecimal lonWgs84;@TableField(value = "lat_wgs84")private BigDecimal latWgs84;@TableField(typeHandler = PgGeometryTypeHandler.class)private String geom;@TableField(exist = false)private String geomJson;private Integer year;// 年份public PoiInfo(String name, String mainCategory, String type, String subtype, String address, String provinceName,String cityName, String areaName, BigDecimal lonWgs84, BigDecimal latWgs84, String geom, Integer year) {super();this.name = name;this.mainCategory = mainCategory;this.type = type;this.subtype = subtype;this.address = address;this.provinceName = provinceName;this.cityName = cityName;this.areaName = areaName;this.lonWgs84 = lonWgs84;this.latWgs84 = latWgs84;this.geom = geom;this.year = year;}}

        熟悉博主代码风格的小伙伴一定知道,在处理空间数据时,我们需要将Wkt格式的字符数据转为PostGIS认识的Geometry字段,因此在这里就需要自定义typeHandler来进行处理。在上面的代码中标识符如下:

@TableField(typeHandler = PgGeometryTypeHandler.class)

2、属性表数据和空间信息的读取

        在定义好模型实体之后,我们将介绍如何使用GeoTools来进行属性数据和空间信息的读取。通过这两个信息要素,构成完成的一条空间基本信息。在本文的例子中,我们需要指定属性来进行解析,比如需要将“地址”这个属性对应到address中,因此我们需要类似于Jdbc的ResultSet的处理方式,需要手动的进行数据的对应。下面贴出具体的解析代码:

@Testpublic void Read2PostGIS() throws IOException, FactoryException {// 指定Shapefile的文件路径//String shpFile = "C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/风景名胜.shp";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());shapefileDataStore.setCharset(Charset.forName("UTF-8"));// 设置中文字符编码// 获取特征类型SimpleFeatureType featureType = shapefileDataStore.getSchema(shapefileDataStore.getTypeNames()[0]);CoordinateReferenceSystem crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();System.out.println("坐标参考系统:" + crs);Integer epsgCode = CRS.lookupEpsgCode(crs, true);SimpleFeatureSource featureSource = shapefileDataStore.getFeatureSource();SimpleFeatureCollection simpleFeatureCollection=featureSource.getFeatures();    SimpleFeatureIterator itertor = simpleFeatureCollection.features();    //遍历featurecollection    List<PoiInfo> list = new ArrayList<PoiInfo>();    Date now = new Date();    while (itertor.hasNext()){        SimpleFeature feature = itertor.next();        Property nameProperty = feature.getProperty("名称");        String name = (String)nameProperty.getValue();        Property mainCategoryProperty = feature.getProperty("大类");        String mainCategory = (String) mainCategoryProperty.getValue();        Property typeProperty = feature.getProperty("中类");        String type = (String)typeProperty.getValue();        Property subtypeProperty = feature.getProperty("小类");      String subtype = subtypeProperty != null ? (String)subtypeProperty.getValue() : "";       Property addressProperty = feature.getProperty("地址");       String address = (String)addressProperty.getValue();       Property provinceNameProperty = feature.getProperty("省");       String provinceName = (String)provinceNameProperty.getValue();       Property cityNameProperty = feature.getProperty("市");       String cityName = (String)cityNameProperty.getValue();       Property areaNameProperty = feature.getProperty("区");       String areaName = (String)areaNameProperty.getValue();        Property lonWgs84Property = feature.getProperty("WGS84_经");        BigDecimal lonWgs84 = new BigDecimal(String.valueOf(lonWgs84Property.getValue()));         Property latWgs84Property = feature.getProperty("WGS84_纬");         BigDecimal latWgs84 = new BigDecimal(String.valueOf(latWgs84Property.getValue()));         // 获取空间字段         org.locationtech.jts.geom.Geometry geometry = (org.locationtech.jts.geom.Geometry) feature.getDefaultGeometry();         // 创建WKTWriter对象        WKTWriter wktWriter = new WKTWriter();        // 将Geometry对象转换为WKT格式的字符串        String wkt = wktWriter.write(geometry);        String geom = "SRID=" + epsgCode +";" + wkt;//拼接srid,实现动态写入        PoiInfo poi = new PoiInfo(name, mainCategory, type, subtype, address, provinceName, cityName, areaName, lonWgs84, latWgs84, geom, 2020);        poi.setCreateTime(now);        poi.setUpdateTime(now);        list.add(poi);  }   if(list.size() > 0) {        // poiService.saveBatch(list,500);    }    System.out.println(list.size());}

        在上面的代码中,请注意在Geometry字段的属性设置时,我们为了能动态的设置空间对象的SRID,需要动态将解析出来的空间参考编码设置到WKT字符串中,方便在数据处理时可以动态的设置。看到很多朋友在介绍相关的博客值,总是在设置方法将4326这个SRID设置为静态的,这样的处理方式不够灵活。

        在第一节中我们曾将讲过,在商务住宅这类POI中,数据的制作方将大类、中类、小类进行了合并,也因此导致了在数据中只有一列,这里举一个合并的例子:

商务住宅;楼宇;商住两用楼宇|商务住宅;楼宇;商务写字楼

        在上面的例子当中,我们就需要特殊处理,正常的大类、中类、小类三类组合起来都是三个长度的标准分类,上面的分类就不是,因此我们将最后的字符全部合并起来,当成当前分类的小类。毕竟这种情况不多,当然我们后续可以对数据进行一个集中的清理。数据转换的逻辑:

String mainCategory = (String) mainCategoryProperty.getValue();;String [] splitMainCategory = mainCategory.split(";");String type = "";String subtype ="";//商务住宅的POI要特殊处理、从大类中分解出中类和小类if(splitMainCategory.length == 3) {     mainCategory = splitMainCategory[0];     type = splitMainCategory[1];     subtype = splitMainCategory[2];}else if(splitMainCategory.length > 3) {     mainCategory = splitMainCategory[0];     type = splitMainCategory[1];     for(int i = 2;i <splitMainCategory.length;i++ ) {            subtype += splitMainCategory[i];     }}else {     Property typeProperty = feature.getProperty("中类");     type = (String)typeProperty.getValue();     Property subtypeProperty = feature.getProperty("小类");     subtype = subtypeProperty != null ? (String)subtypeProperty.getValue() : "";}

3、实际运行结果

        最后我们使用Junit来调用上述的代码实现POI数据的批量插入,由于篇幅有限,关于在Mybatis-Plus中如何批量插入数据的代码不再赘述。读取时的数据显示如下:

        可以看到数据已经成功的加载到内存中,等待批量录入的空间数据库中。下面我们来看下空间数据库中的情况。 在PgAdmin中执行查询语句可以看到如下的结果:

        如果能看到以上结果说明,数据已经成功的插入到数据库中,在PgAdmin当中,还可以直接看到空间数据的分布,可以点击查看属性信息。

        以上步骤就是如何在Java中调用GeoTools进行POI数据入库实例。

三、总结

        以上就是本文的主要内容,本文主要讲解在Java开发环境,如何使用Geotools来进行数据的解析与存储,与GDAL的shp数据处理方式不同,在GeoTools中的处理方法有一定的不同。文章分享的方法可以在分布式环境中利用Mybatis-Plus这种ORM框架进行快速的空间数据批量插入。与GeoTools官方提供的PostGIS数据读写相比,本文分享的方法将更加方便,易于与其它项目进行集成。行文仓促,定有不足之处,还恳请各位专家朋友不吝赐教,万分感谢。


点击全文阅读


本文链接:http://zhangshiyu.com/post/184386.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1