目录
简介
因为公司里需要做关于林区防火方面的项目,需要完成着火后山区路径的导航,但.....某德的功能似乎只能到达山区的边上,后边的路就需要自己完成导航了。搞了一个周终于有所效果了,也遇见了很多的坑,在此记录一下,希望以后不要踩坑。
前置环境
- Postgresql数据库(PG数据库)
- PG数据库的PostGIS扩展
- PG数据库的Pgrouting扩展
需要上述的环境才能进行路径导航,环境的搭建可以参阅
详细内容
1.引入Maven所需依赖
- web依赖
- pg数据库依赖
- 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>
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
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); }
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; } }
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; } }
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("无法计算该路线信息"); } }
总结
1.算法上还可以在优化下
2.整体的查询速度还可以,但是有出现线无法连接的情况,要保证原始数据的连通性。
编辑