目录
前言
一、数据介绍
1、空间数据
2、属性表说明
3、QGIS数据预览
二、PostGIS空间数据库设计
1、空间表结构
三、Java空间入库
1、实体定义
2、数据操作Mapper
3、业务层实现
4、入库
5、数据入库验证
总结
前言
星垂平野阔,月涌大江流”“晴川历历汉阳树,芳草萋萋鹦鹉洲”……祖国的每一寸土地,都饱含着诗情画意。旅行一定是很多朋友的爱好,有人说:“人生至少要有两次冲动:一场奋不顾身的爱情和一段走就走的旅行。”不知道在看博文的朋友,是否也是一位旅行爱好者呢。
随着后疫情时代的到来,许多人又开始踏上旅行。旅游可以放松自己身心,缓解工作和生活方面的压力.旅行的过程中是不需要有任何的心理负担的,可以使人达到一种完全放松的状况.感受最原始的快乐。旅行的时候不仅可以观赏风景,还可以尝美食,住宾馆,听故事,感受全国各地的饮食文化.增长自己的见识,可以看到更多的人.了解更多的民俗文化,看到更多的景色使自己的内心感到充实。旅游可以开阔眼界,观察到丰富的人文景观,了解各地的文化风俗,饮食习惯和宗教信仰,广交朋友。旅游还可以锤炼人的意志,增加人的智慧,尤其是去一些有挑战性的景区游玩,征服一座山,跨过一条河学会一项新技能,都可以使自己变的更加勇敢。
中华大地,旅游景点众多,那我们国家到底有多少旅游景区呢?在前面的很多博文中,我们讲过如何将空间数据进行入库。那么文本将采用Java语言,重点讲述如何将全国A级风景区数据导入到PostGis数据库中,为后续我们进行旅游资源和旅游路线的推荐和展示打下坚实的基础。如果您也是WebGis的爱好者,可以从本文了解空间数据的入库开发方式,知道空间数据库的设计和操作。
一、数据介绍
数据说明,本文下载的数据是朋友分享的2023年全国A级风景区数据。数据大小为36MB左右,数据格式shapfile,下面是数据展示。
1、空间数据
这里我们采用Qgis对空间数据进行导入前查看。其基础信息如下表所示:
序号 | 参数 | 说明 |
1 | 文件格式 | ESRI Shapefile |
2 | 文件编码 | GBK |
3 | 元素类型 | Point |
4 | 坐标参考系 | EPSG:4326 - WGS 84 |
5 | 数据单位 | 度 |
6 | 数据总数 | 14,847 |
2、属性表说明
在shp文件中,除了有空间数据的定义,还有属性数据的定义,A级景区shp数据一共有15个字段,详情请看下面的表定义。
序号 | 字段名 | 数据类型 | 长度 |
1 | 景区名称 | String | 254 |
2 | 等级 | String | 254 |
3 | 所属省份 | String | 254 |
4 | 地址 | String | 254 |
5 | 评定时间 | String | 254 |
6 | 发布时间 | String | 254 |
7 | 发布链接 | String | 254 |
8 | lng_GCJ02(高德经度) | Double | 18 |
9 | lat_GCJ02(高德纬度) | Double | 18 |
10 | lng_BD09(百度经度) | Double | 18 |
11 | lat_BD09(百度纬度) | Double | 18 |
12 | lng_WGS84 | Double | 18 |
13 | lat_WGS84 | Double | 18 |
14 | 所属城市 | String | 254 |
15 | 所属区县 | String | 254 |
3、QGIS数据预览
我们使用qgis对数据进行简单标绘,使用景区名字进行标注,使用景区等级做分类。可以看到如下的分类结果展示。
二、PostGIS空间数据库设计
空间数据库我们采用PostGIS进行存储,这里来简单设计一下如何存储景区数据。完整的景区数据条数在1万5千条左右。因此我们只需要设计一张景区表即可。与其空间属性保持一一对应的关系,我们来看一下数据表的表结构信息。
1、空间表结构
下面给出风景区空间数据库表的表结构:
/*==============================================================*/
/* Table: biz_scenic_spot */
/*==============================================================*/
create table biz_scenic_spot (
id INT8 not null,
name VARCHAR(255) null,
level VARCHAR(4) null,
province VARCHAR(255) null,
city VARCHAR(255) null,
area VARCHAR(255) null,
address VARCHAR(255) null,
evaluation_time VARCHAR(255) null,
publish_time VARCHAR(255) null,
publish_link VARCHAR(255) null,
lng_GCJ02 VARCHAR(30) null,
lat_GCJ02 VARCHAR(30) null,
lng_BD09 VARCHAR(30) null,
lat_BD09 VARCHAR(30) null,
lng_WGS84 VARCHAR(30) null,
lat_WGS84 VARCHAR(30) null,
geom geometry null,
constraint PK_BIZ_SCENIC_SPOT primary key (id)
);
comment on table biz_scenic_spot is
'全国风景区信息表';
comment on column biz_scenic_spot.id is
'主键';
comment on column biz_scenic_spot.name is
'景区名称';
comment on column biz_scenic_spot.level is
'景区级别';
comment on column biz_scenic_spot.province is
'所属省份';
comment on column biz_scenic_spot.city is
'所属城市';
comment on column biz_scenic_spot.area is
'所属区县';
comment on column biz_scenic_spot.address is
'地址';
comment on column biz_scenic_spot.evaluation_time is
'评定时间';
comment on column biz_scenic_spot.publish_time is
'发布时间';
comment on column biz_scenic_spot.publish_link is
'发布链接';
comment on column biz_scenic_spot.lng_GCJ02 is
'lng_GCJ02';
comment on column biz_scenic_spot.lat_GCJ02 is
'lat_GCJ02';
comment on column biz_scenic_spot.lng_BD09 is
'lng_BD09';
comment on column biz_scenic_spot.lat_BD09 is
'lat_BD09';
comment on column biz_scenic_spot.lng_WGS84 is
'lng_WGS84';
comment on column biz_scenic_spot.lat_WGS84 is
'lat_WGS84';
为了在后面的应用中应用空间索引,我们在geom字段上创建空间索引,创建语句如下:
-- ----------------------------
CREATE INDEX "idx_biz_scenic_spot_geom" ON "public"."biz_scenic_spot" USING gist (
"geom" "public"."gist_geometry_ops_2d"
);
三、Java空间入库
这里主要讲解如何使用Java语言将shp数据进行导入到PostGis数据库中,主要采用的组件还是Gdal,如果大家对gdal不太熟悉,可以翻看博主以前的博客,有关于gdal的部署和具体使用方法。下面从代码实现来详细讲解具体的入库过程。后台开发框架采用Springboot,ORM框架采用Mybatis-plus,都是熟悉的组件。如果看博客的朋友对上述框架不是很熟悉,可以先学习一下相关的知识,对于理解和代码掌握有很大的帮助。
1、实体定义
示例工程采用MVC三层开发模式,这里只讲解M层,V和C在后续博文中讲解。
package com.yelang.project.extend.scenicspot.domain;
import java.io.Serializable;
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 lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* 全国风景区信息表
* @author wzh
*
*/
@TableName(value = "biz_scenic_spot", autoResultMap = true)
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class ScenicSpot implements Serializable{
private static final long serialVersionUID = 1830004907219610805L;
@TableId
private Long id;
private String name;//景区名称
private String level;//景区级别
private String province;//所属省份
private String city;//所属城市
private String area;//所属区县
private String address;//地址
@TableField(value="evaluation_time")
private String evaluationTime;//评定时间
@TableField(value="publish_time")
private String publishTime;//发布时间
@TableField(value="publish_link")
private String publishLink;//发布时间
@TableField(value="lng_GCJ02")
private String lngGCJ02;
@TableField(value="lat_GCJ02")
private String latGCJ02;
@TableField(value="lng_BD09")
private String lngBD09;
@TableField(value="lat_BD09")
private String latBD09;
@TableField(value="lng_WGS84")
private String lngWGS84;
@TableField(value="lat_WGS84")
private String latWGS84;
@TableField(typeHandler = PgGeometryTypeHandler.class)
private String geom;
@TableField(exist=false)
private String geomJson;
public ScenicSpot(String name, String level, String province, String city, String area, String address,
String evaluationTime, String publishTime, String publishLink,String lngGCJ02, String latGCJ02, String lngBD09, String latBD09,
String lngWGS84, String latWGS84, String geom) {
super();
this.name = name;
this.level = level;
this.province = province;
this.city = city;
this.area = area;
this.address = address;
this.evaluationTime = evaluationTime;
this.publishTime = publishTime;
this.publishLink = publishLink;
this.lngGCJ02 = lngGCJ02;
this.latGCJ02 = latGCJ02;
this.lngBD09 = lngBD09;
this.latBD09 = latBD09;
this.lngWGS84 = lngWGS84;
this.latWGS84 = latWGS84;
this.geom = geom;
}
}
这里有几个地方要注意的就是,在类最开始定义的地方,@TableName(value = "biz_scenic_spot", autoResultMap = true),这里一定要这么写,否则后续将无法操作geometry数据。其次是@TableField(typeHandler = PgGeometryTypeHandler.class),通过绑定typehandler来设置具体的处理函数。
2、数据操作Mapper
熟悉Mybatis-Plus(mp)的朋友一定了解ORM操作的三个重要对象之一就是Mapper,相当与对jdbc的封装。下面是mapper的实现:
package com.yelang.project.extend.scenicspot.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yelang.project.extend.scenicspot.domain.ScenicSpot;
public interface ScenicSpotMapper extends BaseMapper<ScenicSpot>{
static final String FIND_GEOJSON_SQL="<script>"
+ "select st_asgeojson(geom) as geomJson from biz_scenic_spot "
+ "where id = #{id} "
+ "<if test='null != name'>and name like concat('%', #{name}, '%')</if>"
+ "</script>";
@Select(FIND_GEOJSON_SQL)
ScenicSpot findGeoJsonById(@Param("id")Long id,@Param("name")String name);
}
3、业务层实现
为了方便做景区数据的批量入库,我们在Mp之上实现serviceimpl,好直接调用其的批量处理方法,示例代码如下:
package com.yelang.project.extend.scenicspot.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yelang.project.extend.scenicspot.domain.ScenicSpot;
import com.yelang.project.extend.scenicspot.mapper.ScenicSpotMapper;
import com.yelang.project.extend.scenicspot.service.IScenicSpotService;
@Service
public class ScenicSpotServiceImpl extends ServiceImpl<ScenicSpotMapper, ScenicSpot> implements IScenicSpotService{
@Override
public ScenicSpot findGeoJsonById(Long id) {
return this.baseMapper.findGeoJsonById(id, null);
}
}
4、入库
这里采用junit测试组件来进行数据入库,首先将调用gdal进行shp数据解析,然后调用service方法进行空间数据入库。这里需要注意的是,要在junit测试方法中注入bean对象,因此,需要在测试bean中使用下面的注解。
@SpringBootTest
@RunWith(SpringRunner.class)
package com.yelang.project;
import java.util.ArrayList;
import java.util.List;
import org.gdal.gdal.gdal;
import org.gdal.ogr.DataSource;
import org.gdal.ogr.Feature;
import org.gdal.ogr.Geometry;
import org.gdal.ogr.Layer;
import org.gdal.ogr.ogr;
import org.gdal.osr.SpatialReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.yelang.project.extend.scenicspot.domain.ScenicSpot;
import com.yelang.project.extend.scenicspot.service.IScenicSpotService;
@SpringBootTest
@RunWith(SpringRunner.class)
public class ImportScenicSpot {
@Autowired
private IScenicSpotService scenicSpotService;
@Test
public void importData() {
// 指定文件的名字和路径
String strVectorFile = "C:/BaiduDownload/20230726 A级景点、宗教分布、与非遗空间分布\\2023年全国A级景区数据/2023年全国A级景区数据.shp";
// 注册所有的驱动
ogr.RegisterAll();
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
gdal.SetConfigOption("SHAPE_ENCODING", "CP936");
String strDriverName = "ESRI Shapefile";
org.gdal.ogr.Driver oDriver = ogr.GetDriverByName(strDriverName);
if (oDriver == null) {
System.out.println(strDriverName + " 驱动不可用!\n");
return;
}
DataSource dataSource = oDriver.Open(strVectorFile);
Layer layer = dataSource.GetLayer(0);
SpatialReference spatialReference = layer.GetSpatialRef();
String srid = spatialReference.GetAttrValue("AUTHORITY", 1);
long featureCount = layer.GetFeatureCount();
List<ScenicSpot> list = new ArrayList<ScenicSpot>(3000);
for (int i = 0; i < featureCount; i++) {
Feature feature = layer.GetFeature(i);
String name = feature.GetFieldAsString("景区名称");
String level = feature.GetFieldAsString("等级");
String province = feature.GetFieldAsString("所属省份");
String city = feature.GetFieldAsString("所属城市");
String area = feature.GetFieldAsString("所属区县");
String address = feature.GetFieldAsString("地址");
String evaluationTime = feature.GetFieldAsString("评定时间");
String publishTime = feature.GetFieldAsString("发布时间");
String publishLink = feature.GetFieldAsString("发布链接");
String lngGCJ02 = feature.GetFieldAsString("lng_GCJ02");
String latGCJ02 = feature.GetFieldAsString("lat_GCJ02");
String lngBD09 = feature.GetFieldAsString("lng_BD09");
String latBD09 = feature.GetFieldAsString("lat_BD09");
String lngWGS84 = feature.GetFieldAsString("lng_WGS84");
String latWGS84 = feature.GetFieldAsString("lat_WGS84");
Geometry geom = feature.GetGeometryRef();
//step 1、生成原始wkt
String wkt = geom.ExportToWkt();
wkt = "SRID=" + srid +";" + wkt;//拼接srid,实现动态写入
list.add(new ScenicSpot(name, level, province, city, area, address, evaluationTime, publishTime, publishLink, lngGCJ02, latGCJ02, lngBD09, latBD09, lngWGS84, latWGS84, wkt));
if(list.size() == 2000) {
System.out.println("00000000000000");
scenicSpotService.saveBatch(list,2000);
list.clear();
}
}
if(list.size() >0) {
scenicSpotService.saveBatch(list,1000);
}
System.out.println("完成!!!");
dataSource.delete();
gdal.GDALDestroyDriverManager();
}
}
5、数据入库验证
下面我们来运行一下测试代码,试着将数据导入到数据库中,鼠标右键运行。
运行后在控制台可以看到以下输出:
20:29:30.821 [main] DEBUG c.y.p.e.s.m.S.insert - [debug,137] - ==> Parameters: 1762817301184356354(Long), 八面山景区(String), 3A(String), 湖南(String), 湘西土家族苗族自治州(String), 龙山县(String), 湖南湘西自治州八面山景区(String), -(String), 发布时间:2022-08-03;统计截至时间:2021年底(String), (String), 109.25780600000(String), 28.83474400000(String), 109.26441070000(String), 28.84042333000(String), 109.25318500000(String), 28.83790594000(String), SRID=4326;POINT(109.253185 28.83790594)(PGgeometry)
20:29:30.822 [main] DEBUG c.y.p.e.s.m.S.insert - [debug,137] - ==> Parameters: 1762817301184356355(Long), 湘西自治州花垣县古苗河百瀑大峡谷景区(String), 3A(String), 湖南(String), 湘西土家族苗族自治州(String), 花垣县(String), 湖南湘西自治州湘西自治州花垣县古苗河百瀑大峡谷景区(String), -(String), 发布时间:2022-08-03;统计截至时间:2021年底(String), (String), 109.48161300000(String), 28.58698200000(String), 109.48818110000(String), 28.59279936000(String), 109.47683150000(String), 28.59022625000(String), SRID=4326;POINT(109.4768315 28.59022625)(PGgeometry)
20:29:30.822 [main] DEBUG c.y.p.e.s.m.S.insert - [debug,137] - ==> Parameters: 1762817301188550658(Long), 浏阳古风洞(String), 2A(String), 湖南(String), 长沙市(String), 浏阳市(String), 湖南长沙市浏阳古风洞(String), -(String), 发布时间:2022-08-03;统计截至时间:2021年底(String), (String), 113.79441900000(String), 28.21724500000(String), 113.80095370000(String), 28.22317049000(String), 113.78889760000(String), 28.22061438000(String), SRID=4326;POINT(113.7888976 28.22061438)(PGgeometry)
20:29:30.822 [main] DEBUG c.y.p.e.s.m.S.insert - [debug,137] - ==> Parameters: 1762817301188550659(Long), 耒阳市农耕文化博物馆旅游景区(String), 2A(String), 湖南(String), 衡阳市(String), 耒阳市(String), 湖南衡阳市耒阳市农耕文化博物馆旅游景区(String), -(String), 发布时间:2022-08-03;统计截至时间:2021年底(String), (String), 112.86089200000(String), 26.42240400000(String), 112.86748910000(String), 26.42806999000(String), 112.85559400000(String), 26.42594269000(String), SRID=4326;POINT(112.855594 26.42594269)(PGgeometry)
20:29:30.822 [main] DEBUG c.y.p.e.s.m.S.insert - [debug,137] - ==> Parameters: 1762817301188550660(Long), 耒阳市党史陈列馆(String), 2A(String), 湖南(String), 衡阳市(String), 耒阳市(String), 湖南衡阳市耒阳市党史陈列馆(String), -(String), 发布时间:2022-08-03;统计截至时间:2021年底(String), (String), 112.85569800000(String), 26.40719700000(String), 112.86228240000(String), 26.41286866000(String), 112.85039320000(String), 26.41072176000(String), SRID=4326;POINT(112.8503932 26.41072176)(PGgeometry)
完成!!!
最后到数据库中验证数据是否已经成功导入,在客户端中运行以下语句。可以看到数据全部导入,数据总条数是14847条。
总结
以上就是本文的主要内容,那么文本将采用Java语言,重点讲述如何将全国A级风景区数据导入到PostGis数据库中,为后续我们进行旅游资源和旅游路线的推荐和展示打下坚实的基础。如果您也是WebGis的爱好者,可以从本文了解空间数据的入库开发方式,知道空间数据库的设计和操作。