本文将基于开源GIS后端技术展示如何实现Shp数据的读取及写入数据库。
作者:后端小肥肠
1. 前言
在当前的软件开发和数据处理领域,地理信息系统(GIS)的应用变得越来越广泛。而对于GIS数据的读取和处理,GeoTools是一个强大而灵活的Java库,提供了丰富的功能和工具。与此同时,MyBatisPlus作为一种优秀的ORM框架,简化了与数据库的交互过程,提高了开发效率。而PostGIS是一个基于PostgreSQL数据库的空间数据库扩展,提供了许多地理空间函数和数据类型,使得数据库能够有效地存储和处理地理空间数据。在这篇博客中,我们将结合GeoTools、MyBatisPlus和PostGIS,探讨如何实现Shp数据的读取和写入数据库的过程。通过结合这三者的强大功能,我们将能够高效地处理GIS数据,并将其存储到数据库中,为地理信息系统的开发提供了可靠而高效的解决方案。
2. PostGIS简介
PostGIS是一个开源的空间数据库扩展,为PostgreSQL数据库提供了地理空间功能。它扩展了PostgreSQL的数据类型,使得数据库能够存储地理空间数据,并提供了一系列的地理空间函数和索引,以支持地理信息系统(GIS)应用的开发和分析。PostGIS允许用户在数据库中存储地理要素(如点、线、面)以及地理空间对象的属性信息,同时还提供了强大的查询和分析功能,如空间关系查询、空间分析、几何运算等。 主要特点包括:
- 空间数据类型支持: PostGIS扩展了PostgreSQL的数据类型系统,引入了几何类型(Geometry)和地理类型(Geography),可以存储各种地理空间对象,如点、线、面等。
- 空间索引支持: PostGIS提供了多种空间索引的实现,如R树索引、GiST索引和SP-GiST索引等,可以加快空间查询的速度,提高数据检索效率。
- 地理空间函数: PostGIS内置了大量的地理空间函数,用于进行空间分析、空间关系判断、几何操作等,如距离计算、相交判断、缓冲区分析等。
- 标准兼容性: PostGIS符合Open Geospatial Consortium(OGC)的空间数据规范,保证了与其他GIS软件的互操作性,可以轻松地导入和导出地理空间数据。
通过结合PostGIS与GeoTools和MyBatisPlus,我们能够实现更加灵活、高效的GIS数据处理和管理,为地理信息系统的开发提供了强大的支持和工具。
3. 开发环境搭建
3.1. 所需版本和工具
依赖 | 版本 |
---|---|
Spring Boot | 2.6.14 |
GeoTools | 27-SNAPSHOT |
java | 1.8以上 |
ArcGis | 10.8 |
postgres | 13.12 |
3.2. pom依赖
xml
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mybatis-plus-generator.version>3.3.2</mybatis-plus-generator.version>
<mybatis-plus-boot-starter.version>3.3.1</mybatis-plus-boot-starter.version>
<geotools.version>27-SNAPSHOT</geotools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-swing</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
<version>2021.1.0</version>
</dependency>
</dependencies>
4. 代码实现
4.1. Shp数据准备
Shp数据可以去网上找,也可以自己做,从来没接触过GIS相关知识的同学我建议就网上找一下,有一些GIS基础的我建议就自己做了,比网上找快得多。我是基于AcrMap(ArcGis)自己做了一份Shp数据(4326的几个面要素)。
4.2. 表设计实体类编写
1.PostGis建立空间表
建立空间表的前提是安装PostGis
扩展:
ini
create extension postgis;
安装扩展成功后,会在数据库下面生成一张spatial_ref_sys
表:
建表sql
语句:
sql
CREATE TABLE "xfc_geo_data" (
"id" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"extent" "public"."geometry",
"center" "public"."geometry",
CONSTRAINT "geo_data_pkey" PRIMARY KEY ("id")
);
COMMENT ON COLUMN "public"."xfc_geo_data"."id" IS 'id';
COMMENT ON COLUMN "public"."xfc_geo_data"."extent" IS '数据空间范围';
COMMENT ON COLUMN "public"."xfc_geo_data"."center" IS '数据中心点';
2. 基于MybatisPlus的代码生成器创建实体类代码
代码生成器
编写:
java
public class CodeGenerator {
// 生成的代码放到哪个工程中
private static String PROJECT_NAME="xfc-gis";
// 数据库名称
private static String DATABASE_NAME = "xfc_gis";
// 子包名
private static String MODULE_NAME = "gis";
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:postgresql://127.0.0.1:5432/"+DATABASE_NAME);
dsc.setDriverName("org.postgresql.Driver");
dsc.setUsername("postgres");
dsc.setPassword("postgres");
mpg.setDataSource(dsc);
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir") + "/";
gc.setOutputDir(projectPath + PROJECT_NAME +"/src/main/java");
// 分布式id
gc.setIdType(IdType.ASSIGN_ID);
gc.setAuthor("xfc");
//覆盖现有的
gc.setFileOverride(false);
//是否生成后打开
gc.setOpen(false);
gc.setDateType(DateType.ONLY_DATE);
//实体属性 Swagger2 注解
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 包配置
PackageConfig pc = new PackageConfig();
//父包名
pc.setParent("com.xfc");
pc.setController(MODULE_NAME+".controller");
pc.setService(MODULE_NAME+".service");
pc.setServiceImpl(MODULE_NAME+".service.impl");
pc.setMapper(MODULE_NAME+".mapper");
pc.setXml(MODULE_NAME+".mapper.xml");
pc.setEntity(MODULE_NAME+".entities");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//使用lombok
strategy.setEntityLombokModel(true);
// 实体类的实现接口Serializable
strategy.setEntitySerialVersionUID(true);
// @RestController
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
}
代码生成器生成的实体类
:
less
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class XfcGeoData implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
private String extent;
private String center;
}
4.3. Handler类编写及适配属性字段
1. 编写PgGeometry84TypeHandler 类
这个类主要是用于适配PostGis的geometry的数据类型。
java
@MappedTypes({String.class})
public class PgGeometry84TypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
PGgeometry pGgeometry = new PGgeometry(parameter);
Geometry geometry = pGgeometry.getGeometry();
geometry.setSrid(4326);
ps.setObject(i, pGgeometry);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String string = rs.getString(columnName);
return getResult(string);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String string = rs.getString(columnIndex);
return getResult(string);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String string = cs.getString(columnIndex);
return getResult(string);
}
2. 适配实体类空间字段
4.4. 读取数据及写入数据库
读取数据主要是基于GeoTools相关函数方法。
java
public void insertGeoData(AddGeoDTO addGeoDTO) throws IOException, ParseException {
String shpPath=addGeoDTO.getShpPath();
List<Map<String, Object>> proMaps = getProMapByFeatureCollection(shpPath);
for (Map<String, Object> proMap : proMaps) {
XfcGeoData xfcGeoData=new XfcGeoData();
xfcGeoData.setExtent(proMap.get("the_geom").toString());
xfcGeoData.setCenter(GisUtil.calculateCenter(proMap.get("the_geom").toString()));
baseMapper.insert(xfcGeoData);
}
}
/**
* 根据featureCollection获取属性Map
*/
public List<Map<String, Object>> getProMapByFeatureCollection(String shpPath) throws IOException {
File file = new File(shpPath);
ShapefileDataStore dataStore = (ShapefileDataStore) FileDataStoreFinder.getDataStore(file);
dataStore.setCharset(Charset.forName("GBK"));
SimpleFeatureSource simpleFeatureSource = dataStore.getFeatureSource();
SimpleFeatureCollection simpleFeatureCollection = simpleFeatureSource.getFeatures();
SimpleFeatureIterator featureIterator = simpleFeatureCollection.features();
List<Map<String, Object>>properMaps=new ArrayList<>();
while (featureIterator.hasNext()) {
// 要素对象
SimpleFeature feature = featureIterator.next();
Collection<Property> propertyCollection = (Collection<Property>) feature.getValue();
//填充属性map
Map<String, Object> properMap = new HashMap<>();
for (Property property : propertyCollection) {
properMap.put(property.getName().toString(), property.getValue());
}
properMaps.add(properMap);
}
return properMaps;
}
写入数据库成功(wkb格式): wkt格式:
4.6. 数据正确性验证
验证空间数据的正确性有很多方法,我用的是的一个JTS的工具:
由上图可知我们解析插入到PostGis数据库中的数据是没问题的,我这里只是提供一个宽泛的验证,详细的验证需要根据地理要素的id对比验证解析图形的正确性。
5. 结语
本文结合具体技术栈讲解了如何实现Shp空间数据的读取到写入数据库,下期会给大家分享一下空间数据的存储方案,涵盖2/3维数据,对GIS系列感兴趣的同学可动动小手点点关注哦~
6. 参考链接
基于Mybatis-Plus实现Geometry字段在PostGis空间数据库中的使用_mybatisplus postgis-CSDN博客