Springboot集成PostGIS完成路径规划

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
注册配置 MSE Nacos/ZooKeeper,182元/月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 因为公司里需要做关于林区防火方面的项目,需要完成着火后山区路径的导航,但.....某德的功能似乎只能到达山区的边上,后边的路就需要自己完成导航了。搞了一个周终于有所效果了,也遇见了很多的坑,在此记录一下,希望以后不要踩坑。需要上述的环境才能进行路径导航,环境的搭建可以参阅


目录

简介

前置环境

详细内容

1.引入Maven所需依赖

2.修改配置文件

3.创建RouteRepostitory

4.距离工具类

5.路径规划服务

6.Controller控制器

总结


简介

       因为公司里需要做关于林区防火方面的项目,需要完成着火后山区路径的导航,但.....某德的功能似乎只能到达山区的边上,后边的路就需要自己完成导航了。搞了一个周终于有所效果了,也遇见了很多的坑,在此记录一下,希望以后不要踩坑。

前置环境

  1. Postgresql数据库(PG数据库)
  2. PG数据库的PostGIS扩展
  3. PG数据库的Pgrouting扩展

需要上述的环境才能进行路径导航,环境的搭建可以参阅

详细内容

1.引入Maven所需依赖

  1. web依赖
  2. pg数据库依赖
  3. JPA依赖(这里没有限定,也可以用MyBatis)        
<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

image.gif

2.修改配置文件

注意:这里的pubulic一定要加上,否则无法调用pg数据库的扩展函数

spring:
  datasource:
      jdbc-url: jdbc:postgresql://数据库地址/数据库名?public
      username: 数据库用户名
      password: 数据库密码
  jpa:
      database-platform: org.hibernate.dialect.PostgreSQL9Dialect
      hibernate:
       ddl-auto: update
      show-sql: true

image.gif

3.创建RouteRepostitory

package com.seaua.dao.pgsql;
import com.seaua.entity.pg.Roads;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
 * @author seaua
 * @description: 路径查询->Repository
 * @date 2022/12/1 下午2:17
 * Implements{@link }
 * extend{@link JpaRepository<Roads,Integer>}
 **/
public interface RouteRepository extends JpaRepository<Roads, Integer> {
    /**
     * 取最近的一条线路数据
     *
     * @param lon  经度
     * @param lat  纬度
     * @param code 地理编码
     * @return java.lang.String
     * @date :2022/12/3 下午4:25
     * {@link [java.lang.Double, java.lang.Double, java.lang.Integer, java.lang.Integer]}
     */
    @Query(value = "select st_asgeojson(geom) from roads order by st_setsrid(st_makepoint(?1,?2),?3)<->geom limit 1", nativeQuery = true)
    String queryPointLine(Double lon, Double lat, Integer code);
    /**
     * 计算起点起始点和终点的路径(这里的起点和终点必须在线上)
     *
     * @param table     表名
     * @param start_lon 起点(经度)
     * @param start_lat 起点(纬度)
     * @param stop_lon  终点(经度)
     * @param stop_lat  终点(纬度)
     * @return java.lang.String
     * @date :2022/12/3 下午4:22
     * {@link [java.lang.String, java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Double]}
     */
    @Query(value = "select st_asGeoJson(pgr_fromatob) as geojson from pgr_fromatob(?1,?2,?3,?4,?5)", nativeQuery = true)
    String queryShortLine(String table, Double start_lon, Double start_lat, Double stop_lon, Double stop_lat);
    /**
     * 查询离起始点或终点最近的N条线的数据
     *
     * @param lon   经度
     * @param lat   纬度
     * @param code  地理编码
     * @param limit 条数范围
     * @return java.util.List<java.lang.String>
     * @date :2022/12/3 下午4:20
     * {@link [java.lang.Double, java.lang.Double, java.lang.Integer, java.lang.Integer]}
     */
    @Query(value = "select st_asgeojson(geom) from roads order by st_setsrid(st_makepoint(?1,?2),?3)<->geom limit ?4", nativeQuery = true)
    List<String> queryPointLines(Double lon, Double lat, Integer code, Integer limit);
}

image.gif

4.距离工具类

       用于计算离起始点或终点最近的线上点,这样就不会出现点未点在线上时无法计算的问题。

package com.seaua.utils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author seaua
 * @description: 距离工具类
 * @date 2022/12/1 下午3:07
 * Implements{@link }
 * extend{@link }
 **/
public class DistanceUtils {
    /**
     * PARAM:{@param EARTH_RADIUS} [] TYPE: {@link Double}
     */
    private static double EARTH_RADIUS = 6378.137;
    /**
     * @param d 
     * @return double
     * @date :2022/12/3 上午8:21
     * {@link [double]}
     */
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }
    /**
     * 通过经纬度获取距离(单位:米)
     *
     * @param lat1 纬度
     * @param lng1 经度
     * @param lat2 纬度
     * @param lng2 经度
     * @return 距离
     */
    public static double getDistance(double lng1, double lat1, double lng2, double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2)
                * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000d) / 10000d;
        s = s * 1000;
        return s;
    }
    
    /**
     * 遍历获取线上的距离最近的点
     * @param req_lon 经度
     * @param req_lat 纬度
     * @param line  线(字符串->GeoJson)
     * @return java.util.Map<java.lang.String, java.lang.Double>
     * @date :2022/12/3 上午8:21
     * {@link [java.lang.Double, java.lang.Double, java.util.List<java.util.List<java.lang.Double>>]}
     */
    public static Map<String, Double> getNearestPoint(Double req_lon, Double req_lat, List<List<Double>> line) {
        Map<String, Double> nearestMaps = new HashMap<>(3);
        double minDistance = 0;
        if (line.size() > 0) {
            Double lon = line.get(0).get(0);
            Double lat = line.get(0).get(1);
            minDistance = getDistance(req_lon, req_lat, lon, lat);
            nearestMaps.put("lon", lon);
            nearestMaps.put("lat", lat);
            nearestMaps.put("distance", minDistance);
        }
        for (List<Double> arrayLonALat : line) {
            Double lon = arrayLonALat.get(0);
            Double lat = arrayLonALat.get(1);
            double distance = getDistance(req_lon, req_lat, lon, lat);
            if (distance < minDistance) {
                nearestMaps.put("lon", lon);
                nearestMaps.put("lat", lat);
                nearestMaps.put("distance", distance);
            }
        }
        return nearestMaps;
    }
}

image.gif

5.路径规划服务

       这里的两个方法,第一个方法进行了尝试一次获取最短路径,但获取最短路径若发生断连情况,该算法无法返回有效的参数,会报错。第二个方法进行了5次(也可以是更多次)尝试获取最短路径,这样保证可以最大程度上获取路径数据。

package com.seaua.service;
import com.alibaba.fastjson.JSON;
import com.seaua.dao.pgsql.RouteRepository;
import com.seaua.entity.GeoJson;
import com.seaua.utils.DistanceUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
 * @author seaua
 * @description: 路径规划服务
 * @date 2022/12/1 下午2:51
 * Implements{@link }
 * extend{@link }
 **/
@Component
public class RouteService {
    /**
     * PARAM:{@param routeRepository} [RouteRepository] TYPE: {@link RouteRepository}
     */
    @Autowired
    RouteRepository routeRepository;
    /**
     * @param start_lon 起点经度
     * @param start_lat 起点纬度
     * @param stop_lon  终点经度
     * @param stop_lat  终点纬度
     * @param code      地理编码
     * @return java.lang.String
     * @date :2022/12/3 上午8:22
     * {@link [java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Integer]}
     */
    //获取路径
    public String getRoads(Double start_lon, Double start_lat, Double stop_lon, Double stop_lat, Integer code) {
        //TODO:获取离两个点最近的两条线
        String line_start = routeRepository.queryPointLine(start_lon, start_lat, code);
        String line_stop = routeRepository.queryPointLine(stop_lon, stop_lat, code);
        return getLatelyLine(line_start, line_stop, start_lon, start_lat, stop_lon, stop_lat);
    }
    /**
     * @param line_start 离起点最近的线
     * @param line_stop  离终点最近的线
     * @param start_lon  起点经度
     * @param start_lat  起点纬度
     * @param stop_lon   终点经度
     * @param stop_lat   终点纬度
     * @return java.lang.String
     * @date :2022/12/3 下午4:46
     * {@link [java.lang.String, java.lang.String, java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Double]}
     */
    public String getLatelyLine(String line_start, String line_stop, Double start_lon, Double start_lat, Double stop_lon, Double stop_lat) {
        List<List<Double>> startLine = JSON.parseObject(line_start, GeoJson.class).getValue();
        List<List<Double>> stopLine = JSON.parseObject(line_stop, GeoJson.class).getValue();
        Map<String, Double> startPoint = DistanceUtils.getNearestPoint(start_lon, start_lat, startLine);
        Map<String, Double> stopPoint = DistanceUtils.getNearestPoint(stop_lon, stop_lat, stopLine);
        return routeRepository.queryShortLine(
                "roads",
                startPoint.get("lon"),
                startPoint.get("lat"),
                stopPoint.get("lon"),
                stopPoint.get("lat")
        );
    }
    /**
     * 多条路径
     *
     * @param start_lon 起始点经度
     * @param start_lat 起始点纬度
     * @param stop_lon  终点经度
     * @param stop_lat  终点纬度
     * @param code      地理编码
     * @return java.lang.String
     * @date :2022/12/3 上午8:22
     * {@link [java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Integer]}
     */
    //获取路径
    public String getRoadsAgain(Double start_lon, Double start_lat, Double stop_lon, Double stop_lat, Integer code) {
        List<String> startLines = routeRepository.queryPointLines(start_lon, start_lat, code, 5);
        List<String> stopLines = routeRepository.queryPointLines(stop_lon, stop_lat, code, 5);
        for (String line_start : startLines) {
            for (String line_stop : stopLines) {
                try {
                    return getLatelyLine(line_start, line_stop, start_lon, start_lat, stop_lon, stop_lat);
                } catch (Exception e) {
                    System.err.println("这两个位置无法连接...,尝试下一个最短路径");
                }
            }
        }
        return null;
    }
}

image.gif

6.Controller控制器

package com.seaua.controller;
import com.seaua.entity.Result;
import com.seaua.service.RouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author seaua
 * @description: 路径规划控制器
 * @date 2022/12/1 下午1:54
 * Implements{@link }
 * extend{@link }
 **/
@RestController
public class RouteController {
    /**
     * PARAM:{@param routeService} [路径服务] TYPE: {@link RouteService}
     */
    @Autowired
    RouteService routeService;
    /**
     * @param slon 起点经度
     * @param slat 起点纬度
     * @param tlon 终点经度
     * @param tlat 终点纬度
     * @param code 地理编码
     * @return com.seaua.entity.Result
     * @date :2022/12/3 下午4:56
     * {@link [java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Double, java.lang.Integer]}
     */
    @GetMapping("/nearest")
    public Result getNearestPath(Double slon, Double slat, Double tlon, Double tlat, Integer code) {
        String again = routeService.getRoadsAgain(slon, slat, tlon, tlat, code);
        return again != null ? Result.success(again) : Result.error("无法计算该路线信息");
    }
}

image.gif


总结

       1.算法上还可以在优化下

       2.整体的查询速度还可以,但是有出现线无法连接的情况,要保证原始数据的连通性。

image.gif 编辑



目录
相关文章
|
2月前
|
监控 Java 开发工具
Springboot秒集成-视频推拉流
在工作中需要用到视频的推拉流服务,刚开始准备使用netty服务自己实现RTSP推拉流服务,但在RTSP解包时卡住,自己实现难度确实有点大,后来在网上找到了Zlm4j库,它是基于ZLMediaKit服务实现的Jna版本,可以很容易的集成到Springboot中,在此也。希望本篇博客可以帮助到想快速实现视频推拉流服务的朋友。
196 10
|
2月前
|
存储 Java 区块链
Springboot应用开发:工具类整理
在实际的Springboot应用开发中,有很多类可作为工具类,这些类将实际开发中可能用到的重复性代码进行提取,方便在后续的开发中使用,在这里我对在开发中经常用到的工具类进行整理,方便自己之后查找,同时希望可以帮助到有实现相关功能的朋友。
134 0
|
2月前
|
Java 关系型数据库 数据库连接
Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
178 3
|
存储 Java 定位技术
gis利器之Gdal(二)shp数据读取
本文首先简单介绍了空间数据shp数据的基本知识,其常见的文件组成形式。使用qgis软件对数据进行常规预览,最后重点介绍了使用gdal对矢量信息进行读取,​包括空间信息和属性信息
1541 0
gis利器之Gdal(二)shp数据读取
|
2月前
|
缓存 前端开发 Java
SpringBoot 实现动态菜单功能完整指南
本文介绍了一个动态菜单系统的实现方案,涵盖数据库设计、SpringBoot后端实现、Vue前端展示及权限控制等内容,适用于中后台系统的权限管理。
184 1
|
2月前
|
监控 Linux 网络安全
Linux命令大全:从入门到精通
日常使用的linux命令整理
592 13
|
2月前
|
安全 Java 数据安全/隐私保护
Springboot应用开发-SpringBootSecurity
Spring Boot Security 是 Spring 提供的安全框架,集成了身份认证和授权功能,帮助开发者快速构建安全的应用程序。本篇博客将从功能介绍到配置和实践,带您全面了解 Spring Boot Security。
559 1
Springboot应用开发-SpringBootSecurity
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习模型、算法与应用的全方位解析
深度学习,作为人工智能(AI)的一个重要分支,已经在多个领域产生了革命性的影响。从图像识别到自然语言处理,从语音识别到自动驾驶,深度学习无处不在。本篇博客将深入探讨深度学习的模型、算法及其在各个领域的应用。
310 3
|
2月前
|
Java 区块链 Maven
关于引入maven项目后出现‘parent.relativePath’ of POM错误时的解决方法
关于引入maven项目后出现‘parent.relativePath’ of POM错误时的解决方法
190 3
|
2月前
|
传感器 定位技术 数据格式
常用通信协议及数据格式
常用通信协议和格式总结
184 2