基于SpringBoot和PostGIS的OSM时空路网数据入库实践

基于SpringBoot和PostGIS的OSM时空路网数据入库实践

目录

前言

一、空间表的设计

1、属性信息

2、空间表结构设计

二、路网数据入库

1、实体类设计

2、路网数据写入

3、pgAdmin数据查询

三、总结


前言

        在当今数字化时代,随着信息技术的飞速发展,地理空间数据的应用范围越来越广泛,尤其是在交通领域,路网数据的管理和分析对于智能交通系统、城市规划、导航服务等方面具有至关重要的作用。而时空路网数据作为交通领域的核心数据类型,其不仅包含空间位置信息,还涵盖了时间维度的变化特征,对于准确地反映交通动态、优化交通资源配置以及提高交通管理效率等方面都具有不可或缺的价值。然而,时空路网数据的处理和管理面临着诸多挑战。一方面,路网数据规模庞大,且随着时间的推移不断累积,传统的数据处理和存储方式难以满足高效存储和快速查询的需求。另一方面,数据来源多样,包括卫星遥感、道路传感器、车辆GPS等,不同来源的数据在格式、精度、更新频率等方面存在很大差异,如何对这些异构数据进行有效整合和规范化处理,是构建高质量路网数据库的关键问题。同时,时空数据具有复杂的空间和关系时间序列特性,需要具备强大的空间分析能力和时间序列数据处理能力,才能充分发挥其在交通分析和决策中的作用。

        在此背景下,本实践旨在探索基于SpringBoot和PostGIS的时空路网数据入库解决方案。SpringBoot作为一个流行的Java开发框架,以其简洁、高效的开发模式和丰富的生态体系,为后端应用开发提供了强大的支持,能够方便地进行数据接入、业务逻辑处理以及与前端的交互PostGIS作为PostgreSQL的空间数据库扩展,具备强大的空间数据存储和分析能力,能够有效地处理地理空间数据的复杂几何关系和拓扑结构,为时空路网数据的存储、查询和分析提供可靠的支撑。通过结合SpringBoot和PostGIS的优势,可以构建一个高效、稳定且易于扩展的时空路网数据管理系统,实现对海量时空路网数据的高效入库、存储和管理,为后续的交通分析、模拟和决策提供坚实的数据基础,推动交通领域的数字化转型和智能化发展。

        本文以OSM的路网数据为例,详细讲解如何在PostGIS数据库中进行空间表的设计,以及基于SpringBoot实现路网数据的空间导入,通过Geotools来实现数据的读取,通过实战型的代码讲解让大家对路网的入库和空间检索有一定的了解。

一、空间表的设计

        本节将重点介绍针对OSM路网数据的空间表设计。不仅再次对路网关键属性信息进行介绍,同时给出了路网空间表的物理结构和建表语句。

1、属性信息

序号名称数据类型长度说明备注
1osm_idString12oms标识
2codeInteger4code
3fclassString28道路类型这个字段是最重要的字段,他表示的是道路的类型,一共有27个分类,比如高速路、自行车道等,我们在下文fclass道路类型会详细介绍这27个分类,一般情况下我们都是根据道路
分类来进行数据可视化和数据分析的
4nameString100道路名称道路的名称,比如大广高速,该字段数据缺失比较多,name道路名称。大部分道路没有名字
5refString20道路编号道路的编号,例如大广高速的编号是G45,该字段数据缺ref道路编号失比较多,也就是大部分道路没有编号
6onewayString1是否为单行道有F和T两个值,其中F代表不是单行道,T代表是单行道
7maxspeedInteger3最大速度
8layerInteger12
9bridgeString1是否为桥梁有F和T两个值,其中F代表不是桥梁,T代表是桥梁
10tunnelString1是否为隧道有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"; @Autowired private IRoadNetworkService roadNetworkService; @Test public 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(); //遍历featurecollection List<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的优势,可以构建一个高效、稳定且易于扩展的时空路网数据管理系统,实现对海量时空路网数据的高效入库、存储和管理,为后续的交通分析、模拟和决策提供坚实的数据基础,推动交通领域的数字化转型和智能化发展。本文通过空间数据库相关表的设计与后台代码实现,为大家详细介绍了如何进行路网数据的入库。行文仓促,难免有许多不足之处,如有不足,还恳请各位专家博主在评论区批评指出,不胜感激。

Read more

【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论 : 本章将提到Qt中的网络部分,在看这篇文章之前需要有一定的网络基础也就是TCP/HTTP、本篇文章主要讲到的是Qt中基础的Udp、Tcp、Http的使用方法,并附有了多个小demo方便实操练习,并且其中还在每章最后进行了小总结回顾重要接口和函数方便回顾。 ———————— 早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。 网络编程主要依赖于操作系统提供的Socket API。需要注意的是,C++标准库本身并未封装网络编程相关的API。 关于Qt网络编程的几个要点: 1. 网络应用开发本质上是编写应用层代码,需要传输层协议(如TCP/UDP)的支持 2. 为此,Qt提供了两套专门的网络编程API(QUDPSocket和QTcpSocket) 3. 使用Qt网络编程API时,需先在.pro文件中添加network模块 4. 之前学习的Qt控件和核心功能都属于QtCore模块(默认已包含) 为什么Qt要划分出这些模块呢? Qt 本身是一个非常庞

By Ne0inhk
毕设项目·SpringBoot学校访客管理系统\02-06(白嫖源码+演示录像)可做计算机毕设JAVA、PHP、爬虫、APP、小程序、C#、C++、python、数据可视化、文案

毕设项目·SpringBoot学校访客管理系统\02-06(白嫖源码+演示录像)可做计算机毕设JAVA、PHP、爬虫、APP、小程序、C#、C++、python、数据可视化、文案

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统流程分析 2.2.1 数据新增流程 2.2.2 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.

By Ne0inhk

Pinocchio 3.5.0版本:C++可视化引擎与模仿关节技术带来机器人动力学计算革命

Pinocchio 3.5.0版本:C++可视化引擎与模仿关节技术带来机器人动力学计算革命 【免费下载链接】pinocchioA fast and flexible implementation of Rigid Body Dynamics algorithms and their analytical derivatives 项目地址: https://gitcode.com/gh_mirrors/pi/pinocchio 项目概述 Pinocchio作为机器人动力学领域的标杆性C++库,以其毫秒级计算效率和模块化架构著称。该库专注于提供刚体动力学算法及其解析导数的快速实现,广泛应用于工业机器人仿真、人形机器人控制、医疗康复设备开发等前沿领域。最新发布的3.5.0版本通过五大技术革新,将机器人建模与计算能力提升至全新高度,特别在复杂机构仿真和实时控制场景中展现出突破性价值。 图:Pinocchio在不同机器人模型上的动力学计算性能对比,展示了其在逆动力学、质量矩阵和正向动力学任务中的高效表现 核心升级亮点 🔧 C++原生可视化引擎:告别Python依赖的开发新范

By Ne0inhk
【C++动态规划】1547. 切棍子的最小成本|2116

【C++动态规划】1547. 切棍子的最小成本|2116

本文涉及知识点 C++动态规划 LeetCode1547. 切棍子的最小成本 有一根长度为 n 个单位的木棍,棍上从 0 到 n 标记了若干位置。例如,长度为 6 的棍子可以标记如下: 给你一个整数数组 cuts ,其中 cuts[i] 表示你需要将棍子切开的位置。 你可以按顺序完成切割,也可以根据需要更改切割的顺序。 每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根木棍的长度和就是切割前木棍的长度)。请参阅第一个示例以获得更直观的解释。 返回切棍子的 最小总成本 。 示例 1: 输入:n = 7, cuts = [1,3,4,5] 输出:16 解释:按 [1, 3, 4, 5]

By Ne0inhk