因为公司里需要做关于林区防火方面的项目,需要完成着火后山区路径的导航,但.....某德的功能似乎只能到达山区的边上,后边的路就需要自己完成导航了。搞了一个周终于有所效果了,也遇见了很多的坑,在此记录一下,希望以后不要踩坑。
需要上述的环境才能进行路径导航,环境的搭建可以参阅
<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>
注意:这里的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
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);
}
用于计算离起始点或终点最近的线上点,这样就不会出现点未点在线上时无法计算的问题。
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次(也可以是更多次)尝试获取最短路径,这样保证可以最大程度上获取路径数据。
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;
}
}
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.整体的查询速度还可以,但是有出现线无法连接的情况,要保证原始数据的连通性。