基于SpringBoot和PostGIS的OSM时空路网数据入库实践
目录
前言
一、空间表的设计
1、属性信息
2、空间表结构设计
二、路网数据入库
1、实体类设计
2、路网数据写入
3、pgAdmin数据查询
三、总结
前言
在当今数字化时代,随着信息技术的飞速发展,地理空间数据的应用范围越来越广泛,尤其是在交通领域,路网数据的管理和分析对于智能交通系统、城市规划、导航服务等方面具有至关重要的作用。而时空路网数据作为交通领域的核心数据类型,其不仅包含空间位置信息,还涵盖了时间维度的变化特征,对于准确地反映交通动态、优化交通资源配置以及提高交通管理效率等方面都具有不可或缺的价值。然而,时空路网数据的处理和管理面临着诸多挑战。一方面,路网数据规模庞大,且随着时间的推移不断累积,传统的数据处理和存储方式难以满足高效存储和快速查询的需求。另一方面,数据来源多样,包括卫星遥感、道路传感器、车辆GPS等,不同来源的数据在格式、精度、更新频率等方面存在很大差异,如何对这些异构数据进行有效整合和规范化处理,是构建高质量路网数据库的关键问题。同时,时空数据具有复杂的空间和关系时间序列特性,需要具备强大的空间分析能力和时间序列数据处理能力,才能充分发挥其在交通分析和决策中的作用。
在此背景下,本实践旨在探索基于SpringBoot和PostGIS的时空路网数据入库解决方案。SpringBoot作为一个流行的Java开发框架,以其简洁、高效的开发模式和丰富的生态体系,为后端应用开发提供了强大的支持,能够方便地进行数据接入、业务逻辑处理以及与前端的交互PostGIS作为PostgreSQL的空间数据库扩展,具备强大的空间数据存储和分析能力,能够有效地处理地理空间数据的复杂几何关系和拓扑结构,为时空路网数据的存储、查询和分析提供可靠的支撑。通过结合SpringBoot和PostGIS的优势,可以构建一个高效、稳定且易于扩展的时空路网数据管理系统,实现对海量时空路网数据的高效入库、存储和管理,为后续的交通分析、模拟和决策提供坚实的数据基础,推动交通领域的数字化转型和智能化发展。
本文以OSM的路网数据为例,详细讲解如何在PostGIS数据库中进行空间表的设计,以及基于SpringBoot实现路网数据的空间导入,通过Geotools来实现数据的读取,通过实战型的代码讲解让大家对路网的入库和空间检索有一定的了解。
一、空间表的设计
本节将重点介绍针对OSM路网数据的空间表设计。不仅再次对路网关键属性信息进行介绍,同时给出了路网空间表的物理结构和建表语句。
1、属性信息
序号 | 名称 | 数据类型 | 长度 | 说明 | 备注 |
1 | osm_id | String | 12 | oms标识 | |
2 | code | Integer | 4 | code | |
3 | fclass | String | 28 | 道路类型 | 这个字段是最重要的字段,他表示的是道路的类型,一共有27个分类,比如高速路、自行车道等,我们在下文fclass道路类型会详细介绍这27个分类,一般情况下我们都是根据道路 分类来进行数据可视化和数据分析的 |
4 | name | String | 100 | 道路名称 | 道路的名称,比如大广高速,该字段数据缺失比较多,name道路名称。大部分道路没有名字 |
5 | ref | String | 20 | 道路编号 | 道路的编号,例如大广高速的编号是G45,该字段数据缺ref道路编号失比较多,也就是大部分道路没有编号 |
6 | oneway | String | 1 | 是否为单行道 | 有F和T两个值,其中F代表不是单行道,T代表是单行道 |
7 | maxspeed | Integer | 3 | 最大速度 | |
8 | layer | Integer | 12 | ||
9 | bridge | String | 1 | 是否为桥梁 | 有F和T两个值,其中F代表不是桥梁,T代表是桥梁 |
10 | tunnel | String | 1 | 是否为隧道 | 有F和T两个值,其中F代表不是隧道,T代表是隧道 |
请注意,上面的这些字段和具体的字段的含义非常有意义,以后在进行数据分析时会重点用得到。 当然,由于数据较多,在OSM的路网数据中,道路分类不一定都有这些数据。
2、空间表结构设计
依据前面的的矢量数据属性信息,为了在空间表里也能实现相应的数据模型查询,我们设计的路网表的属性与上面表几乎是一致,仅仅是多了两个字段,第一个是业务主键,第二个是空间属性geom。如下图所示:
对应的路网物理表结构SQL语句如下:
CREATE TABLE "biz_road_network" ("pk_id" int8 NOT NULL,"osm_id" varchar(12) COLLATE "default" NOT NULL,"code" int4,"fclass" varchar(28) COLLATE "default" NOT NULL DEFAULT ''::character varying,"name" varchar(100) COLLATE "default" NOT NULL DEFAULT ''::character varying,"ref" varchar(20) COLLATE "default" NOT NULL DEFAULT ''::character varying,"oneway" char(1) COLLATE "default" NOT NULL,"maxspeed" int4 NOT NULL DEFAULT 0,"layer" int8,"bridge" char(1) COLLATE "default" DEFAULT ''::bpchar,"tunnel" char(1) COLLATE "default" DEFAULT ''::bpchar,"geom" geometry,CONSTRAINT "pk_biz_road_network" PRIMARY KEY ("pk_id")
);
CREATE INDEX "biz_road_network_fclass" ON "public"."biz_road_network" USING btree ("fclass" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
);
CREATE INDEX "idx_biz_road_network_geom" ON "public"."biz_road_network" USING gist ("geom" "public"."gist_geometry_ops_2d"
);
CREATE INDEX "idx_biz_road_network_name" ON "public"."biz_road_network" USING btree ("name" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
);
COMMENT ON COLUMN "public"."biz_road_network"."pk_id" IS '主键';
COMMENT ON COLUMN "public"."biz_road_network"."osm_id" IS 'OSM主键';
COMMENT ON COLUMN "public"."biz_road_network"."code" IS 'code';
COMMENT ON COLUMN "public"."biz_road_network"."fclass" IS '道路类型,这个字段是最重要的字段,他表示的是道路的类型,一共有27个分类,比如高速路、自行车道等';
COMMENT ON COLUMN "public"."biz_road_network"."name" IS '道路名称';
COMMENT ON COLUMN "public"."biz_road_network"."ref" IS '道路的编号,例如大广高速的编号是G45';
COMMENT ON COLUMN "public"."biz_road_network"."oneway" IS '是否为单行道';
COMMENT ON COLUMN "public"."biz_road_network"."maxspeed" IS '最大速度';
COMMENT ON COLUMN"public"."biz_road_network"."layer" IS 'layer';
COMMENT ON COLUMN "public"."biz_road_network"."bridge" IS '是否为桥梁';
COMMENT ON COLUMN "public"."biz_road_network"."tunnel" IS '是否为隧道';
COMMENT ON COLUMN "public"."biz_road_network"."geom" IS 'geom';
COMMENT ON TABLE "public"."biz_road_network" IS '路网信息表';
以上就是对路网的空间表的结构和物理表语句进行简单的说明,这是实现数据入库的基础。
二、路网数据入库
在前面的内容中已经对路网表的设计,接下来我们在SpringBoot环境中使用Geotools来读取路网矢量数据,并将数据插入到PostGIS中。最后为了验证数据是否成功插入,基于pgAdmin来对岳麓区的所有路网进行查询并进行可视化。让大家对如何使用Java程序来进行GIS应用开发有更好的了解。Java应用程序采用MVC模式开发,这里讲解数据的读取和写入,演示模型层和业务层的业务逻辑。
1、实体类设计
实体类比较简单,主要采用的是MybatiesPlus的方式来操作,因此只需要定义一个跟数据库物理表结构类似的实体类,核心代码如下:
package com.yelang.project.extend.earthquake.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 夜郎king*/
@TableName(value = "biz_road_network", autoResultMap = true)
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class RoadNetwork implements Serializable{private static final long serialVersionUID = -6520680287497074840L;@TableId(value="pk_id")private Long pkId;//@TableField(value="osm_id")private String osmId;private Integer code;private String fclass;//道路类型private String name;//道路类型@TableField(value="ref")private String ref;private String oneway;//private Integer maxspeed;//限速private Long layer;private String bridge;private String tunnel;@TableField(typeHandler = PgGeometryTypeHandler.class)private String geom;@TableField(exist=false)private String geomJson;public RoadNetwork(String osmId, Integer code, String fclass, String name, String ref, String oneway,Integer maxspeed, Long layer, String bridge, String tunnel, String geom) {super();this.osmId = osmId;this.code = code;this.fclass = fclass;this.name = name;this.ref = ref;this.oneway = oneway;this.maxspeed = maxspeed;this.layer = layer;this.bridge = bridge;this.tunnel = tunnel;this.geom = geom;}
}
2、路网数据写入
众所周知,在MybatisPlus中要实现数据的写入,除了有实体类,还需要有Mapper和Servcie类。目前的路网相关Mapper和Service类都比较简单,没有复杂的自定义方法。因此这里暂且忽略,如果确实需要实例代码的,可以单独在评论区留言或者发私信,仅限于Mapper和Service类。为了验证数据的导入性,这里我们使用Junit测试程序来进行导入。核心测试方法如下:
package com.yelang.project.geotools.vectorroad;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.referencing.CRS;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.locationtech.jts.io.WKTWriter;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
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.earthquake.domain.RoadNetwork;
import com.yelang.project.extend.earthquake.service.IRoadNetworkService;
@SpringBootTest
@RunWith(SpringRunner.class)
public class RoadToPostGIS {// 指定Shapefile的文件路径private static final String ROAD_SHP_FILE = "F:/shpfilepath/湖南路网2024.shp";@Autowiredprivate IRoadNetworkService roadNetworkService;@Testpublic void shpData2DB() throws IOException, FactoryException {ShapefileDataStore shapefileDataStore = new ShapefileDataStore(new File(ROAD_SHP_FILE).toURI().toURL());shapefileDataStore.setCharset(Charset.forName("GBK"));// 设置中文字符编码// 获取特征类型SimpleFeatureType featureType = shapefileDataStore.getSchema(shapefileDataStore.getTypeNames()[0]);CoordinateReferenceSystem crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();Integer epsgCode = 0;if(crs != null) {epsgCode = CRS.lookupEpsgCode(crs, true);}SimpleFeatureSource featureSource = shapefileDataStore.getFeatureSource();SimpleFeatureCollection simpleFeatureCollection=featureSource.getFeatures();SimpleFeatureIterator itertor = simpleFeatureCollection.features();//遍历featurecollectionList<RoadNetwork> list = new ArrayList<RoadNetwork>();while (itertor.hasNext()){SimpleFeature feature = itertor.next();Property osmIdProperty = feature.getProperty("osm_id");String osmId = (String)osmIdProperty.getValue();Property codeProperty = feature.getProperty("code");Integer code = (Integer)codeProperty.getValue();Property fclassProperty = feature.getProperty("fclass");String fclass = (String) fclassProperty.getValue();Property nameProperty = feature.getProperty("name");String name = (String)nameProperty.getValue();Property refProperty = feature.getProperty("ref");String ref = (String)refProperty.getValue();Property onewayProperty = feature.getProperty("oneway");String oneway = (String)onewayProperty.getValue();Property maxspeedProperty = feature.getProperty("maxspeed");Integer maxspeed = (Integer)maxspeedProperty.getValue();Property layerProperty = feature.getProperty("layer");Long layer = (Long)layerProperty.getValue();Property bridgeProperty = feature.getProperty("bridge");String bridge = (String)bridgeProperty.getValue();Property tunnelProperty = feature.getProperty("tunnel");String tunnel = (String)tunnelProperty.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,实现动态写入RoadNetwork road = new RoadNetwork(osmId, code, fclass, name, ref, oneway, maxspeed, layer, bridge, tunnel, geom);list.add(road);}if(list.size() > 0) {roadNetworkService.saveBatch(list,500);}}
}
3、pgAdmin数据查询
运行上面的程序就实现了OSM路网数据的导入,路网的范围是湖南省范围内的,为了验证数据是否完全导入,这里我们使用pgAdmin查询界面来验证,之所以要用这个工具,并不是navicat不能用,而是这个工具自带了一个地图可视化的界面,可以辅助查看数据的可视化效果。下面以查询长沙市岳麓区的路网信息为例,执行以下sql:
select r.* from biz_road_network r,biz_area t
where st_contains(t.geom, r.geom) and t.area_name = '岳麓区';
执行之后在客户端可以看到以下界面:
发现岳麓区有 5876条路线信息,拖动滚动条到最后,点击预览按钮可以查看地图的可视化效果,界面如下所示:
说明我们的路网数据已经成功的导入到PostGIS中。
三、总结
以上就是本文的主要内容,本实践旨在探索基于SpringBoot和PostGIS的时空路网数据入库解决方案。通过结合SpringBoot和PostGIS的优势,可以构建一个高效、稳定且易于扩展的时空路网数据管理系统,实现对海量时空路网数据的高效入库、存储和管理,为后续的交通分析、模拟和决策提供坚实的数据基础,推动交通领域的数字化转型和智能化发展。本文通过空间数据库相关表的设计与后台代码实现,为大家详细介绍了如何进行路网数据的入库。行文仓促,难免有许多不足之处,如有不足,还恳请各位专家博主在评论区批评指出,不胜感激。