首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Springboot集成PostGIS完成路径规划

Springboot集成PostGIS完成路径规划

作者头像
盹猫
发布2025-07-22 18:12:50
发布2025-07-22 18:12:50
12500
代码可运行
举报
运行总次数:0
代码可运行

简介

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

前置环境

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

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

详细内容

1.引入Maven所需依赖

  1. web依赖
  2. pg数据库依赖
  3. JPA依赖(这里没有限定,也可以用MyBatis)
代码语言:javascript
代码运行次数:0
运行
复制
         <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>

2.修改配置文件

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

代码语言:javascript
代码运行次数:0
运行
复制
spring:
  datasource:
      jdbc-url: jdbc:postgresql://数据库地址/数据库名?public
      username: 数据库用户名
      password: 数据库密码
  jpa:
      database-platform: org.hibernate.dialect.PostgreSQL9Dialect
      hibernate:
       ddl-auto: update
      show-sql: true

3.创建RouteRepostitory

代码语言:javascript
代码运行次数:0
运行
复制
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);

}

4.距离工具类

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

代码语言:javascript
代码运行次数:0
运行
复制
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;
    }
}

5.路径规划服务

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

代码语言:javascript
代码运行次数:0
运行
复制
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;
    }
}

6.Controller控制器

代码语言:javascript
代码运行次数:0
运行
复制
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("无法计算该路线信息");
    }
}

总结

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 前置环境
  • 详细内容
    • 1.引入Maven所需依赖
    • 2.修改配置文件
    • 3.创建RouteRepostitory
    • 4.距离工具类
    • 5.路径规划服务
  • 6.Controller控制器
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档