最近项目中需要实现这样一个功能:从当前位置,规划一条最优线路到附近多个工厂的最优线路。必须要有详细的路径坐标点信息。
思路:1.首先 使用汉密尔顿回路算法,求解出当前位置坐标点到附近工厂最短线路的路径。2.借助百度地图API,实现2点间坐标点的详细路径。话不多说,直接整代码:
项目使用的框架是springboot: 参数path格式为:40.465,116.314|40.232,116.352|40.121,116.453|41.121,118.453 经纬度直接用“,”隔开,2个坐标点之间用"|"隔开。这样做的目的是方便前端传值与后台取值。
@GetMapping("/dynamicProgramming")
public AjaxResult dynamicProgramming(HttpServletRequest request,
@RequestParam("path") String path) throws Exception{
//所有经纬度数组
String[] strings = path.split("\\|");
double [] x = new double[strings.length];
double [] y = new double[strings.length];
for (int i = 0; i < strings.length; i++) {
String str = strings[i];
String latitude = str.substring(0, str.indexOf(","));
String longitude = str.substring(str.lastIndexOf(",") + 1);
x[i] = Double.parseDouble(latitude);
y[i] = Double.parseDouble(longitude);
}
//汉密尔顿回路算法求解最短路线的最优路线
List<Integer> sort = DynamicProgramming.sort(x, y, strings.length);;
List<Map> list = new ArrayList<>();
//将返回的规划路线分段调用百度地图api,获取详细坐标
for (int i = 0; i < sort.size()-1; i++) {
Map<String, Object> steps = new HashMap<>();
int qidian = sort.get(i);
int zongdian = sort.get(i+1);
//出发地
String origin = strings[qidian-1];
//目的地
String destination = strings[zongdian-1];
//调用百度地图驾车路线api生成具体路径
String url = " http://api.map.baidu.com/directionlite/v1/driving?" +
"origin="+origin+"&destination="+destination+
"&ak="+baiduAk;
String result = BaiduHttpUtils.httpGet(url);
List<Map> maps = BaiduHttpUtils.getResult(result);
steps.put("step",maps);
list.add(steps);
}
return success(list);
}
算法工具类:DynamicProgramming
package com.chaosting.kiwifruit.web.utils.weixin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: zhangweixia
* @Description: 汉密尔顿回路算法最短路径动态规划
* @Date:Created in 10:35 2020/4/3
* @Modified:
*/
public class DynamicProgramming {
private int citynumbers;//城市数目
double s = 0;//总距离
int path[];//存放路径,方便计算距离
private double[][] distance;
private double[][] optimalvalue;//阶段最短路径值矩阵
private int[][] optimalchoice;//阶段最优策略矩阵
public DynamicProgramming(int citynumbers) {
this.citynumbers = citynumbers;
}
public void readData(double [] x,double [] y) throws IOException {
// int [] x = {2066,935,1270,1389,984,2253};
// int [] y = {2333,1304,200,700,2810,478};
distance = new double[citynumbers][citynumbers];//距离矩阵
//计算距离矩阵
for (int i = 0; i < citynumbers; i++) {
for (int j = 0; j < citynumbers; j++) {
distance[i][j] = Math.sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));//计算欧式距离
}
}
int h = (int) Math.pow(2, citynumbers - 1);
optimalvalue = new double[citynumbers][h];
optimalchoice = new int[citynumbers][h];
}
public List solve() {
double min = Double.MAX_VALUE;//确保会更新
int mink = 0;
//计算第一列地值
for (int i = 0; i < citynumbers; i++) {
optimalvalue[i][0] = distance[i][0];
}
for (int i = 1; i < (Math.pow(2, citynumbers - 1)); i++) {
for (int j = 1; j < citynumbers; j++) {
int k = 0;
if (judge(i, j) == 0) {//确定j不包含在i代表的集合中
String a = com.chaosting.fireStation.service.util.DynamicProgramming.binary(i, citynumbers - 1);
for (int w = a.length(); w > 0; w--) {
k = a.charAt(a.length() - w) - 48;//k为0或者1
if (k == 1) {
k = k * w;//此时的k为选择的集合中的某个值
double y = (distance[j][k] + optimalvalue[k][(int) (i - Math.pow(2, k - 1))]);
if (y < min) {
min = (distance[j][k] + optimalvalue[k][(int) (i - Math.pow(2, k - 1))]);
mink = k;
}
}
}
if (min < Double.MAX_VALUE) {//确定min是否变化,有变化再写入矩阵
optimalvalue[j][i] = min;
optimalchoice[j][i] = mink;
min = Double.MAX_VALUE;
}
}
}
}
min = Double.MAX_VALUE;//更新min
int i = (int) (Math.pow(2, citynumbers - 1) - 1);//更新最后一列
for (int k = citynumbers - 1; k > 0; k--) {
double x = (distance[0][k] + optimalvalue[k][(int) (i - Math.pow(2, k - 1))]);
if (x < min) {
min = x;
mink = k;
}
}
optimalvalue[0][i] = min;
optimalchoice[0][i] = mink;
path = new int[citynumbers + 1];
path[0] = 1;
int c = 1;
for (int j = 0; i > 0; ) {
j = optimalchoice[j][i];
i = (int) (i - Math.pow(2, j - 1));
path[c++] = j + 1;
}
path[c++] = 1;
List<Integer> list = new ArrayList<>();
for (i = 0; i < citynumbers; i++) {
//System.out.print(path[i] + "->");
list.add(path[i]);
s = s + distance[path[i] - 1][path[i + 1] - 1];
}
return list;
}
//判断j是否在i表示的集合中
public int judge(int i, int j) {
String a = com.chaosting.fireStation.service.util.DynamicProgramming.binary(i, citynumbers - 1);
int b = a.charAt(a.length() - j) - 48;
return b;
}
//给定一个十进制数,输出一个指定位数的二进制形式字符串
public static String binary(int decNum, int digit) {
String binStr = "";
for (int i = digit - 1; i >= 0; i--) {
binStr += (decNum >> i) & 1;
}
return binStr;
}
public static void main(String[] args) throws Exception {
double [] x = {0,93.568};
double [] y = {0,13.865};
List sort = sort(x, y, 2);
System.out.println(sort);
}
public static List<Integer> sort(double [] x,double [] y,int number) throws Exception{
com.chaosting.fireStation.service.util.DynamicProgramming tsp = new com.chaosting.fireStation.service.util.DynamicProgramming(number);//建立对象
tsp.readData(x,y);
List<Integer> list = tsp.solve();
return list;
}
}
百度工具类:BaiduHttpUtils
package com.chaosting.kiwifruit.web.utils.weixin;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.util.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
/**
* @Author: zhangweixia
* @Description: 请求百度开发接口工具类
* @Date:Created in 14:34 2020/2/26
* @Modified:
*/
public class BaiduHttpUtils {
/**
* GET请求
*/
public static String httpGet(String httpUrl) throws Exception {
//创建一个url对象
URL url = new URL(httpUrl);
//打开链接,强转为httpUrlConnection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//设置链接方式
connection.setRequestMethod("GET");
//设置超时时间
connection.setConnectTimeout(3000);
//设置读取超时时间
connection.setReadTimeout(5000);
//发送请求
connection.connect();
//获取输入流
InputStream inputStream = connection.getInputStream();
//封装输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
//接收返回的数据
StringBuffer stringBuffer = new StringBuffer();
String line = null;
while ((line = bufferedReader.readLine()) != null){
stringBuffer.append(line);
}
//关流
bufferedReader.close();
inputStream.close();
//关闭链接
connection.disconnect();
return stringBuffer.toString();
}
/**
* 百度驾车路径解析
*/
public static List<Map> getResult(String baiduResult){
JSONObject jsonObject = JSON.parseObject(baiduResult);
//0.获得结果集json对象
Object result = jsonObject.get("result");
JSONObject resultJsonObject = JSON.parseObject(result.toString());
//4.获得第一页路线routes
String routes = resultJsonObject.getString("routes");
JSONArray routesArray = JSON.parseArray(routes);
System.out.println("routes总共有="+routesArray.size());
//默认取第一条路线
JSONObject routeJsonObject = JSON.parseObject(routesArray.get(0).toString());
//本条路线有几个step 步骤
String steps = routeJsonObject.getString("steps");
JSONArray stepsArray = JSON.parseArray(steps);
System.out.println("steps总共有="+stepsArray.size());
List<Map> maps = new ArrayList<>();
for (int i = 0; i < stepsArray.size(); i++) {
Map map1 = new HashMap();
JSONObject jsonStep = JSON.parseObject(stepsArray.get(i).toString());
String start_location = jsonStep.getString("start_location");
JSONObject startObject = JSON.parseObject(start_location);
String longitude = startObject.getString("lng");
String latitude = startObject.getString("lat");
map1.put("latitude",latitude);
map1.put("longitude",longitude);
maps.add(map1);
}
return maps;
}
/**
* 百度地址解析
*/
public static Map getJson(String baiduResult) {
JSONObject jsonObject = JSON.parseObject(baiduResult);
String status = jsonObject.getString("status");//0
String message = jsonObject.getString("message");//成功
Map<Object, Object> hashMap = new HashMap<>();
hashMap.put("status", status);
hashMap.put("message", message);
if (!"0".equals(status)) return hashMap;
//0.获得结果集json对象
Object result = jsonObject.get("result");
JSONObject resultJsonObject = JSON.parseObject(result.toString());
//1.获得出发地城市名称
Object origin = resultJsonObject.get("origin");
JSONObject originJsonObject = JSON.parseObject(origin.toString());
String origin_city_name = originJsonObject.get("city_name").toString();
//2.获得目的地城市名称
Object destination = resultJsonObject.get("destination");
JSONObject destinationJsonObject = JSON.parseObject(destination.toString());
String destination_city_name = destinationJsonObject.getString("city_name");
//3.获得所有路线的总数
String total = resultJsonObject.getString("total");
//计算总页数
int totalInt = Integer.parseInt(total);
//4.获得第一页路线routes
String routes = resultJsonObject.getString("routes");
JSONArray routesArray = JSON.parseArray(routes);
Map<String, Object> map = new LinkedHashMap<>();
Map<String, Object> dataMap = new LinkedHashMap<>();
dataMap.put("total", totalInt);
//当前页数量
dataMap.put("pageSize", routesArray.size());
dataMap.put("origin", origin_city_name);
dataMap.put("destination", destination_city_name);
map.put("status", status);
map.put("message", message);
//5.解析第一页的所有路线
List<Map> routeList = new ArrayList<>();
for (int i = 0; i < routesArray.size(); i++) {
int m = 1;
//Map<String, String> sunMap = new HashMap<>();
Map<String, Object> routeMap = new LinkedHashMap<>();
Map sunMap = new LinkedHashMap<String, String>();
List<Map> sunMapList = new ArrayList<>();
JSONObject routeJsonObject = JSON.parseObject(routesArray.get(i).toString());
//本条路线的总距离(米)
String distanceType = routeJsonObject.getString("distance");
//将米转换成公里,保留小数点后一位,四舍五入
String distance = getKM(distanceType);
//本条路线的总耗时(秒)
String duration = routeJsonObject.getString("duration");
//将秒转换成时分秒
duration = DateUtil.getDuratiom(duration);
routeMap.put("distance", distance);
routeMap.put("duration", duration);
//6.本条路线的总票价
String price = routeJsonObject.getString("price");
routeMap.put("price", price);
//本条路线有几个step 步骤
String steps = routeJsonObject.getString("steps");
JSONArray stepsArray = JSON.parseArray(steps);
//System.out.println("第"+i+"条路线:"+"总距离="+distance+"。总耗时="+duration+"。步骤="+steps);
//每个子步骤的具体方案
for (int j = 0; j < stepsArray.size(); j++) {
//每个子步骤 schemes
String schemes = stepsArray.get(j).toString();
//解析每个子步骤下面的孙子步骤
JSONArray schemesArray = JSON.parseArray(schemes);
for (int k = 0; k < schemesArray.size(); k++) {
String sun = schemesArray.get(k).toString();
JSONObject sunJsonObject = JSON.parseObject(sun);
//1.获取孙子步骤的描述信息
String instructions = sunJsonObject.getString("instructions");
//2.孙子步骤的距离
String sunDistanceType = sunJsonObject.getString("distance");
String sunDistance = getKM(sunDistanceType);
//3.孙子步骤的耗时
String sunDuration = sunJsonObject.getString("duration");
sunDuration = DateUtil.getDuratiom(sunDuration);
//.交通方式
String vehicle_info = sunJsonObject.getString("vehicle_info");
JSONObject vehicleJsonObject = JSON.parseObject(vehicle_info);
String type = vehicleJsonObject.getString("type");
//存入步骤信息,因机场有些描述信息没有,但还是有步骤,当没描述信息就不存了,跳出此次循环
if (StringUtils.isEmpty(instructions)) {
break;
}
//type为1是火车,type为2是飞机
if (Integer.parseInt(type) == 1 || Integer.parseInt(type) == 2) {
String detail = vehicleJsonObject.getString("detail");
JSONObject detailJsonObject = JSON.parseObject(detail);
//火车车次
String name = detailJsonObject.getString("name");
//总票价
String detailPrice = detailJsonObject.getString("price");
//上车火车站名
String departureStation = detailJsonObject.getString("departure_station");
//下火车站名
String arriveStation = detailJsonObject.getString("arrive_station");
//发车时间
String departureTime = detailJsonObject.getString("departure_time");
//到站时间
String arriveTime = detailJsonObject.getString("arrive_time");
sunMap.put("第" + m + "步", instructions + "|距离=" + sunDistance + "|耗时=" + sunDuration + "|班次" + name + "|出发时间=" + departureTime + "|到达时间=" + arriveTime);
} else {
sunMap.put("第" + m + "步", instructions + "|距离=" + sunDistance + "|耗时=" + sunDuration);
}
System.out.println("第" + (i + 1) + "条路线:" + "第" + m + "步=" + instructions);
m++;
}
}
sunMapList.add(sunMap);
routeMap.put("route", sunMapList);
routeList.add(routeMap);
}
dataMap.put("routes", routeList);
map.put("result", dataMap);
return map;
}
/**
* 米转换成公里,保留小数点后一位
*/
public static String getKM(String distance) {
//换算成公里,保留小数点后一位,四舍五入
double distanceDouble = Double.parseDouble(distance);
double b = Math.rint(distanceDouble / 100) / 10;
distance = b + "公里";
return distance;
}
}
返回给前端的路径格式:
"code": 0,
"data": [
{
"step": [
{
"latitude": "40.479965606385",
"longitude": "116.31932607972"
},
{
"latitude": "40.479900370146",
"longitude": "116.32667969845"
}
]
},
{
"step": [
{
"latitude": "41.129388293691",
"longitude": "118.447649255"
},
{
"latitude": "41.122830549249",
"longitude": "118.43334984823"
}
]
},
{
"step": [
{
"latitude": "40.120992095059",
"longitude": "116.45298917858"
},
{
"latitude": "40.124614014625",
"longitude": "116.45278705984"
}
]
}
]