最近甲方国企因为换领导要裁撤所有外包员工(听来的早的同事说他们每隔一两年换一波外包,全裁后再根据需求招,满满的套路),我TM才干了四个月,回来西安的第一份工作就这么坑,西安的软件环境真是没得说,还是怀念北京老东家的那伙人,好吧,不得已又得开始找工作,各种工具框架什么的看着真是没劲啊,所谓的java基础是复习了一遍又一遍,忘了一边又一遍,之前勤快的时候敲了N多遍没事看个源码,现在懒了最多也是看看面试题。说起复习技术,最不喜欢看的就是工具,容器中间件等,跟着教程走,记着哪里怎么弄,原理是什么,出现问题怎么处理,话说出现问题大部分人又能怎么处理,现学看看原理+百度+灵活思辨处理呗怎么处理,都是遇到问题主要靠摸索攒经验而已,但这些问题平时基本不会遇到,遇到了也都是现学现用,真是么得意思。再就是框架(特别讨厌面试时问框架原理的三流面试官,这纯粹是在考验记忆力,跟编程没有毛线关系),框架有什么可以问的:大到场景应用,小到灵活巧用,无非就这稍有点用,它也就是个标准工具而已,也么意思的很。相对比个人还是更喜欢场景题,提出场景,怎么设计怎么优化怎么解决,没有标准答案,这多有意思,沟通-思考-商议-设计-开发,多有意思,哈哈。
说了好多,不过说起技术,个人更喜欢深究摸索算法类的,实现某个模型功能,优化提升效率等等。这篇文章内容是两年半前在上家公司做的,当时拿到的需求就五百字的word文档(关于应急疏散的最优策略和最短路径的设计,吧啦吧啦),写了些看不懂的公式和描述(最后发现是公司领导在网上抄的,他也看不懂),其他就没了。至于这玩意要做啥,做成什么样,没人说也没人会,老板说:“你自己就看着弄,我们项目是应急项目,应急疏散功能是合同上有的,只要最后有这个功能就行,具体什么样你自己定”。
好吧,万事开头难,既然要做那最起码得差不多点,自此,开启现学现用模式,个人的设计不讲究多复杂,拷贝地图软件最短路径的思路呗,项目有用到GIS地图,那就基于地图做最短路径,然后搜资料文档,摸索实现方案,最后就整了这么一套。
进入正文:该方式基于地图中路口和路线作为建模数据,在地图中找到一片样本区域,基于这片区域标注出所有的路口节点和路线,在百度API提供的接口中获取标注路口节点的经纬度;针对样本区域中的曲线路段或非路口拐点,将曲线颗粒化为多个直线,再依次获取每个直线截取点(未作为路段起始或结束点的节点),如下图中的黄色点,因为地图上是根据经纬度标绘直线的,曲线或拐点的路段需要分段成多个点标绘。我截取的区域如下:
为什么要这么设计呢,主要还是根据Dijkstra算法的数据需要,Dijkstra算法的详细讲解可以百度了解。根据Dijkstra算法的特点我们得先有符合算法的结构数据:每个点可到达的其他点的集合,集合中可到达的点信息包含点ID和距离,如下:
// 点2: {点3:110 ,点8:100 } // 点3:{点2:110} // 点8:{点2:100 ,点9:120} // 点9:{点8:120 , 点10:90 , 点13:89} // 点10:{点9:90 ,...} // 点13:{点9:89 ,... }
准备:
一、表的设计如下,总共两张表:
1.节点表(id,节点名称,经度,维度,是否为路口节点,是否为事故点,是否为安置点,所在路段ID);
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "CeNodeConfig对象", description = "路口节点信息表")
public class CeNodeConfig implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键")
@TableId(value = "ID", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "路口名称")
private String name;
@ApiModelProperty(value = "经度(负值表示西经,正值为东经)")
private String longitude;
@ApiModelProperty(value = "纬度(负值为南纬,正值为北纬)")
private String latitude;
@ApiModelProperty(value = "事故爆发点最近节点 0:不是,1:是")
private Integer sceneOfAction;
@ApiModelProperty(value = "安置点最近节点 0:不是,1:是")
private Integer temporaryShelter;
@ApiModelProperty(value = "路口节点(0:不是,1:是)")
private Integer crossingNode;
@ApiModelProperty(value = "所在路线ID(非路口节点)")
private Integer sectionId;
}
2.路段表(id,路段名称,路段物理距离,路段阻抗值,路段起始节点ID,路段结束节点ID,是否为双向路段);
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "CeSectionConfig对象", description = "路段信息表")
public class CeSectionConfig implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键")
@TableId(value = "ID", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "路段名")
private String name;
@ApiModelProperty(value = "路段物理距离")
private Double physicalDistance;
@ApiModelProperty(value = "风险阻抗值")
private Double riskImpedanceValue;
@ApiModelProperty(value = "路段起始节点ID")
private Integer startNodeId;
@ApiModelProperty(value = "路段结束节点ID")
private Integer endNodeId;
@ApiModelProperty(value = "是否为双向路段 0:不是,1:是")
private Integer bothway;
}
二、数据准备:
使用百度坐标系获取上图中这些节点的经纬度,依次给这些节点定义名称;节点与节点之间的蓝色线为路段,定义路段名称,根据百度API或经纬度计算方法计算每个路段的长度;
将节点和路段的相关数据插入到节点表和路段表中(之前公司的项目,现在只有代码连不到数据库,建表语句离职退了电脑没了,数据就不贴了);
三、数据准备工作完成,开始实现
Dijkstra算法需要的是当前点到其他点以及距离的集合数据,那么先需要数据封装,将我们的节点数据和路段数据封装成算法需要的:
1.先查询节点网数据,根据路段表递归查询并关联获取所有路段数据数据,查询sql如下:
/**
* 查询指定点可关联到的节点网集合:
* 1.指定节点作为结束节点递归查出符合双向路段条件的根节点,如果没有根节点则指定节点为根节点;
* 2.根据根节点查出所有子节点关联的路段信息;
* 3.根据路段关联查询对应的起始和结束节点数据
* @Param id 指定点ID
* @return 关联的节点网集合
*/
@Select("select d.ID cId, d.NAME cName, d.LONGITUDE cLongi, d.LATITUDE cLati, " +
" c.ID pId, c.NAME pName, c.LONGITUDE pLongi, c.LATITUDE pLati, " +
" a.ID sId, a.NAME sName, a.PHYSICAL_DISTANCE sDistance, " +
" a.RISK_IMPEDANCE_VALUE sRisk, a.BOTHWAY sBothway " +
" from CE_SECTION_CONFIG a " +
" right join ( " +
" SELECT distinct(ID) id " +
" FROM CE_SECTION_CONFIG START WITH START_NODE_ID = ( " +
" select nvl(max(id), #{id}) as id " +
" from ( " +
" SELECT distinct(START_NODE_ID) id " +
" FROM CE_SECTION_CONFIG where BOTHWAY = 1 START WITH END_NODE_ID = #{id} " +
" CONNECT BY END_NODE_ID = PRIOR START_NODE_ID order by ID asc " +
" ) a where rownum =1 ) " +
" CONNECT BY START_NODE_ID = PRIOR END_NODE_ID order by ID asc " +
" ) b " +
" on a.id = b.id " +
" inner join CE_NODE_CONFIG c on a.START_NODE_ID = c.ID " +
" inner join CE_NODE_CONFIG d on a.END_NODE_ID = d.ID " +
" order by c.ID asc ")
List<CeNextNodeConfig> selectNextCeNodeConfig(Integer id);
上面的sql可以这么理解:路段关联的起始节点当作父节点,关联的结束节点当作子节点,给定一个节点作为子节点然后向上递归查询根节点,再根据根节点查询所有子节点作为起始节点的路段,这样就可以查询出指定节点关联的所有路线网。这里的指定节点可以理解为事故点,当发生事故需要疏散时,将事故附近最近的节点作为指定节点,这样递归查询后的结果则是查询出事故点可以到达的所有路线网。
我这里只是用的小样本区域数据并不大,如果是大面积的地图区域数据,那关联出来的路线网络数据量会很大,这种情况下就不适合全量递归加载,因为条条大路通罗马,所有的路线都会串起来,最终的结果是只要不是封闭区域的其他区域路线都会被查询加载,那数据量就大的离谱了,这种情况下可以加一些限制条件,比如计算路线的起始或结束节点和指定节点的直线距离,是否大于某个距离就不查询,至于距离的计算可以使用触发器自写函数等方式,或者使用权重过滤等方式,这里没涉及就先不考虑了。
查询返回的节点网数据结构如下:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "CeNextNodeConfig对象", description = "路段起始节点到结束节点的数据信息")
public class CeNextNodeConfig implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "路段结束节点ID")
@TableId(value = "cId", type = IdType.AUTO)
private Integer cId;
@ApiModelProperty(value = "路段结束节点路口名称")
private String cName;
@ApiModelProperty(value = "路段结束节点经度(负值表示西经,正值为东经)")
private String cLongi;
@ApiModelProperty(value = "路段结束节点纬度(负值为南纬,正值为北纬)")
private String cLati;
@ApiModelProperty(value = "路段起始节点ID")
private Integer pId;
@ApiModelProperty(value = "路段起始节点路口名称")
private String pName;
@ApiModelProperty(value = "路段起始节点经度(负值表示西经,正值为东经)")
private String pLongi;
@ApiModelProperty(value = "路段起始节点纬度(负值为南纬,正值为北纬)")
private String pLati;
@ApiModelProperty(value = "路段ID")
private Integer sId;
@ApiModelProperty(value = "路段名称")
private String sName;
@ApiModelProperty(value = "路段物理距离")
private Double sDistance;
@ApiModelProperty(value = "路段风险阻抗值")
private Double sRisk;
@ApiModelProperty(value = "路段是否为双向路段(0:不是,1:是)")
private Integer sBothway;
}
这里查询出后的结果以每条路段为一条数据,数据中包含路段信息,起始和结束节点信息;
2、接下来对路段集合信息做封装处理,
我们现有的数据如下:
//路段S1信息..., 起始节点N2信息...,结束节点N3信息...; 单向路段,无3-2
//路段S2信息..., 起始节点N2信息...,结束节点N8信息...; 双向路段,有8-2
//路段S3信息..., 起始节点N3信息...,结束节点N8信息...;
//路段S4信息..., 起始节点N3信息...,结束节点N4信息...;
//路段S5信息..., 起始节点N8信息...,结束节点N9信息...;
//路段S6信息..., 起始节点N8信息...,结束节点N2信息...; 双向路段,有2-8
......
我们想要的结果如下:
// 点2: {点3:110 ,点8:100 }
// 点3:{点2:110}
// 点8:{点2:100 ,点9:120}
// 点9:{点8:120 ,点10:90 , 点13:89}
// 点10:{点9:90 ,...}
// 点13:{点9:89 ,... }
封装方法如下:
//节点数据分类
public static void nodeClassify(HashMap<String, HashMap<String,Double>> graph,
List<CeNextNodeConfig> startLD){
Map<String,List<CeNextNodeConfig>> resultMap = new HashMap<String,List<CeNextNodeConfig>>();
for(CeNextNodeConfig ce : startLD){
if(resultMap.containsKey(ce.getPName())){
//父节点已经存在
resultMap.get(ce.getPName()).add(ce);
}else{
List<CeNextNodeConfig> theCes = new ArrayList<CeNextNodeConfig>();
theCes.add(ce);
resultMap.put(ce.getPName(),theCes);
}
}// 点2:{2-3,2-8} 点8:{8-9} 点9:{9-10,9-13}
//调用数据封装方法
for(Map.Entry<String,List<CeNextNodeConfig>> entry : resultMap.entrySet()){
nodePackage(graph,entry.getValue());
}
}
//节点数据封装
public static void nodePackage(HashMap<String, HashMap<String,Double>> graph,
List<CeNextNodeConfig> startLD) {
HashMap<String, HashMap<String,Double>> graph1 = Dijkstra.clone(graph);
String pName = startLD.get(0).getPName();
int i = 0;
for(Map.Entry<String, HashMap<String,Double>> maps : graph1.entrySet()){
if (pName.equals(maps.getKey())) { // 父节点在结果集中已经存在
for(CeNextNodeConfig cs : startLD) {
graph.get(maps.getKey()).put(cs.getCName(), cs.getSDistance()); //存储父节点到子节点
if(cs.getSBothway() == 1){
//双向路段
int j = 0;
for(Map.Entry<String, HashMap<String,Double>> mapsz : graph1.entrySet()){
if(cs.getCName().equals(mapsz.getKey())){ // 子节点在结果集中已经存在
graph.get(mapsz.getKey()).put(pName,cs.getSDistance()); // 存储子节点到父节点
j = 1;
}
}
if(j == 0){ // 子节点在结果集中不存在
HashMap<String, Double> map = new HashMap<String, Double>();
map.put(pName,cs.getSDistance());
graph.put(cs.getCName(),map); // 新增子节点到结果集中
}
}
}
i = 1;
}
}
// 点2: {点3:110 ,点8:100 }
// 点3:{点2:110}
// 点8:{点2:100 ,点9:120}
// 点9:{点8:120 , 点10:90 , 点13:89}
// 点10:{点9:90 ,...}
// 点13:{点9:89 ,... }
// ...
HashMap<String, Double> map = new HashMap<String, Double>();
if(i == 0){ //父节点在结果集中不存在
HashMap<String, Double> map1 = new HashMap<String, Double>();
graph.put(pName,map1); //点2: {点3:110 ,点8:100 } 双向: 点3:{点2:110} 点8:{点2:100}
for(CeNextNodeConfig cs : startLD) {
map1.put(cs.getCName(),cs.getSDistance()); //存储父节点到子节点
if(cs.getSBothway() == 1){
//双向路段,存储子节点到父节点
map = new HashMap<String, Double>();
map.put(pName,cs.getSDistance());
graph.put(cs.getCName(),map);
}
}
}
}
在上述封装过程中,进行了双向路段判断,这个是我当时根据现实生活中存在单向行驶路段的情况加的,双向路段的封装处理在上述代码中也考虑到了
调用时如下:
HashMap<String, HashMap<String,Double>> graph = new HashMap<String, HashMap<String,Double>>();
nodeClassify(graph,nextNodes);//封装节点网数据,nextNodes为查询出的路段网数据
3、至此,节点网数据已经封装完成,接下来就开始使用Dijkstra算法进行最短路径计算,实现如下:
//计算最短路径长度和路线
public static Map<Double,List<String>> dijkstra(String start, String end, HashMap<String,HashMap<String,Double>> graph){
Map<Double,List<String>> result = new HashMap<Double,List<String>>();
List<String> pathL = new ArrayList<>();
Map<String,Double> costMap = graph.get(start); //{点3:110 ,点8:100 ,点2:220}
Map<String,String> parentMap = new HashMap<>();
HashSet<String> processed = new HashSet<>();
parentMap.put(start,null); //{点2:点3}
processed.add(start); //点2,点3,点8
while (!costMap.isEmpty()){
String lowest = getLowestCostNode(costMap,processed); //点8
if(lowest.equals(end)){
String Node = end;
Stack<String> stack = new Stack<>();
stack.push(Node);
while(parentMap.get(Node) != null){
stack.push(parentMap.get(Node));
Node = parentMap.get(Node);
}
pathL.add(start);
//System.out.print(start);
while(!stack.isEmpty()){
pathL.add(stack.pop());
//System.out.print("->" + stack.pop() ); //使用栈中当前节点且弹出
}
//System.out.println();
result.put(costMap.get(lowest), pathL);
return result;
}
processed.add(lowest); // 点2,点3,点8
HashMap<String,Double> map = graph.get(lowest); //{点2:100 ,点9:120}
if(map != null && !map.isEmpty()){
for(Map.Entry<String,Double> node : map.entrySet()){
double cost = costMap.get(lowest) + node.getValue(); // 100 + 100
if(!costMap.containsKey(node.getKey()) || (costMap.containsKey(node.getKey()) && cost < costMap.get(node.getKey()))){
costMap.put(node.getKey(),cost); //{点2:220}
parentMap.put(node.getKey(),lowest); //{点2:点3}
}
}
}
}
return result;
}
public static String getLowestCostNode(Map<String,Double> costMap,HashSet<String> processed ){
double min = Double.MAX_VALUE;
String res = null;
for(Map.Entry<String,Double> entry : costMap.entrySet()){
if(!processed.contains(entry.getKey()) && entry.getValue() <= min){
min = entry.getValue();
res = entry.getKey();
}
}
return res;
}
调用方式如下:
//start:起始节点名称
//end:结束节点名称
//封装后的节点网数据
Map<Double,List<String>> pathResult = dijkstra(start,end,grapha);
四、测试:这里我自己模拟了一小部分地图封装处理后的数据进行测试:
测试方式如下:
public static void main(String[] args){
HashMap<String,HashMap<String,Double>> graph = new HashMap<>();
HashMap<String,Double> jd0 = new HashMap<>();
jd0.put("点2",30D);
jd0.put("点12",220D);
graph.put("点1",jd0);
HashMap<String,Double> jd1 = new HashMap<>();
jd1.put("点1",30D);
jd1.put("点3",110D);
jd1.put("点8",210D);
graph.put("点2",jd1);
HashMap<String,Double> jd2 = new HashMap<>();
jd2.put("点2",110D);
jd2.put("点8",100D);
jd2.put("点4",180D);
graph.put("点3",jd2);
HashMap<String,Double> jd3 = new HashMap<>();
jd3.put("点2",210D);
jd3.put("点3",100D);
jd3.put("点9",30D);
graph.put("点8",jd3);
HashMap<String,Double> jd4 = new HashMap<>();
jd4.put("点8",30D);
jd4.put("点10",180D);
jd4.put("点13",90D);
graph.put("点9",jd4);
HashMap<String,Double> jd5 = new HashMap<>();
jd5.put("点4",130D);
jd5.put("点9",180D);
jd5.put("点15",100D);
jd5.put("点11",170D);
graph.put("点10",jd5);
HashMap<String,Double> jd6 = new HashMap<>();
jd6.put("点3",180D);
jd6.put("点5",170D);
jd6.put("点10",130D);
graph.put("点4",jd6);
HashMap<String,Double> jd7 = new HashMap<>();
jd7.put("点1",220D);
jd7.put("点13",140D);
jd7.put("点18",150D);
graph.put("点12",jd7);
HashMap<String,Double> jd8 = new HashMap<>();
jd8.put("点9",90D);
jd8.put("点12",140D);
jd8.put("点14",40D);
graph.put("点13",jd8);
HashMap<String,Double> jd9 = new HashMap<>();
jd9.put("点13",400D);
jd9.put("点15",160D);
jd9.put("点20",100D);
graph.put("点14",jd9);
HashMap<String,Double> jd10 = new HashMap<>();
jd10.put("点10",100D);
jd10.put("点14",160D);
jd10.put("点21",100D);
graph.put("点15",jd10);
HashMap<String,Double> jd11 = new HashMap<>();
jd11.put("点12",150D);
jd11.put("点20",160D);
graph.put("点18",jd11);
HashMap<String,Double> jd12 = new HashMap<>();
jd12.put("点14",100D);
jd12.put("点18",160D);
jd12.put("点21",160D);
graph.put("点20",jd12);
HashMap<String,Double> jd13 = new HashMap<>();
jd13.put("点15",100D);
jd13.put("点20",160D);
graph.put("点21",jd13);
HashMap<String,Double> jd14 = new HashMap<>();
jd14.put("点4",170D);
jd14.put("点11",130D);
graph.put("点5",jd14);
HashMap<String,Double> jd15 = new HashMap<>();
jd15.put("点5",130D);
jd15.put("点10",170D);
jd15.put("点22",200D);
graph.put("点11",jd15);
HashMap<String,Double> jd16 = new HashMap<>();
jd16.put("点11",200D);
jd16.put("点21",170D);
graph.put("点22",jd16);
Map<Double,List<String>> jg = dijkstra("点2","点9",graph);
print(jg);
}
//打印最短路径计算结果
public static void print(Map<Double,List<String>> pathResult){
Set<Double> set=pathResult.keySet();
List<String> ggList = new ArrayList<String>();
for(double aa : set){
ggList = pathResult.get(aa);
System.out.println("==== 路径长度: "+aa+" ; 路径节点数: "+ggList.size());
for(int i = 0;i<ggList.size();i++){
if(i==0){
System.out.print("==== 最短路径为: "+ggList.get(i));
}else if(i == ggList.size()-1){
System.out.println(" -> "+ggList.get(i));
}else{
System.out.print(" -> "+ggList.get(i));
}
}
}
}
测试结果如下:
==== 路径长度: 240.0 ; 路径节点数: 3
==== 最短路径为: 点2 -> 点8 -> 点9
由结果可见:点2到点9的最短路径长度为240,经过的路口节点为:点2 -> 点8 -> 点9
五、处理结果:现在得到了计算后的最短路径节点和最短路径距离,这样的数据给前端无法使用,前端需要的是包含经纬度的点,这样才能在地图上标绘使用,需要封装处理结果返回给前端:
处理结果封装实体类:
@Data
@ApiModel(value="查询路线信息", description = "查询路线信息")
public class ShortesSectionMessageRspVo {
@ApiModelProperty("路线ID")
private Integer sectionID;
@ApiModelProperty("路线名称")
private String sectionName;
@ApiModelProperty("起点信息")
private Map<String,String> originNode;
@ApiModelProperty("终点信息")
private Map<String,String> terminusNode;
@ApiModelProperty("其他节点信息")
private List<Map<String,String>> elseNodes;
@ApiModelProperty("路线物理距离")
private Double sectionDistance;
@ApiModelProperty("路线阻抗值")
private Double riskValue;
}
封装处理结果:
//路段信息封装
public List<ShortesSectionMessageRspVo> sectionPackage(Map<Double,List<String>> result, List<CeNextNodeConfig> nextNodesBF){
List<ShortesSectionMessageRspVo> sectionVOList = new ArrayList<ShortesSectionMessageRspVo>();
ShortesSectionMessageRspVo sectionM = new ShortesSectionMessageRspVo();
for(Map.Entry<Double,List<String>> entry : result.entrySet()){
Map<String,String> originNode=new HashMap<String,String>();
Map<String,String> terminusNode=new HashMap<String,String>();
List<String> resNode =entry.getValue();
//封装路径节点结果集
for(int i=0; i<resNode.size()-1; i++){
sectionM = new ShortesSectionMessageRspVo();
String qdName = resNode.get(i);
String zdName = resNode.get(i+1);
for(CeNextNodeConfig ceNext : nextNodesBF){
String cName = ceNext.getCName();
String pName = ceNext.getPName();
if(cName.equals(qdName) && pName.equals(zdName) ){
sectionM.setSectionID(ceNext.getSId());
sectionM.setSectionName(ceNext.getSName());
sectionM.setRiskValue(ceNext.getSRisk());
sectionM.setSectionDistance(ceNext.getSDistance());
//封装起点信息
originNode=new HashMap<String,String>();
originNode.put("id",String.valueOf(ceNext.getCId()));
originNode.put("name",ceNext.getCName());
originNode.put("jd",ceNext.getCLongi());
originNode.put("wd",ceNext.getCLati());
sectionM.setOriginNode(originNode);
//封装终点信息
terminusNode=new HashMap<String,String>();
terminusNode.put("id",String.valueOf(ceNext.getPId()));
terminusNode.put("name",ceNext.getPName());
terminusNode.put("jd",ceNext.getPLongi());
terminusNode.put("wd",ceNext.getPLati());
sectionM.setTerminusNode(terminusNode);
break;
}else if(cName.equals(zdName) && pName.equals(qdName)){
sectionM.setSectionID(ceNext.getSId());
sectionM.setSectionName(ceNext.getSName());
sectionM.setRiskValue(ceNext.getSRisk());
sectionM.setSectionDistance(ceNext.getSDistance());
//封装起点信息
originNode = new HashMap<String,String>();
originNode.put("id",String.valueOf(ceNext.getPId()));
originNode.put("name",ceNext.getPName());
originNode.put("jd",ceNext.getPLongi());
originNode.put("wd",ceNext.getPLati());
sectionM.setOriginNode(originNode);
//封装终点信息
terminusNode = new HashMap<String,String>();
terminusNode.put("id",String.valueOf(ceNext.getCId()));
terminusNode.put("name",ceNext.getCName());
terminusNode.put("jd",ceNext.getCLongi());
terminusNode.put("wd",ceNext.getCLati());
sectionM.setTerminusNode(terminusNode);
break;
}
}
//封装其他节点信息
int sectionID = sectionM.getSectionID();
if(sectionID == 0){
//没有路段信息
continue;
}
List<CeNodeConfig> cesectionNodes = iCeNodeConfigService.list(new QueryWrapper<CeNodeConfig>()
.eq("SECTION_ID",sectionID)
.eq("CROSSING_NODE",0).orderByAsc()); // 根据路段ID查询该路段上的其它非路口节点
List<Map<String,String>> elseNodes = new ArrayList<Map<String,String>>();
Map<String,String> elseNode =new HashMap<String,String>();
for(CeNodeConfig cenode : cesectionNodes){
elseNode =new HashMap<String,String>();
elseNode.put("id",String.valueOf(cenode.getId()));
elseNode.put("name",cenode.getName());
elseNode.put("jd",cenode.getLongitude());
elseNode.put("wd",cenode.getLatitude());
elseNodes.add(elseNode);
}
sectionM.setElseNodes(elseNodes);
sectionVOList.add(sectionM);
}
}
return sectionVOList;
}
上面封装计算结果时有个特别注意的地方:将非路口节点(曲线颗粒化节点或拐点节点,图中黄色点)封装进路线中,不然前端拿到的节点在地图上标绘时弯曲的路会直接标绘成直线,处理方式如上代码,根据路段关联查询每个路段的非路口节点并封装进路线结果集中。
剩下的就是将结果数据返回给前端上图,前端在地图上标绘出事故点、安置点、最短路线、距离等数据,还有一些封装事故点处理、起始点处理、路线阻抗值(某条路段拥堵情况、危险系数处理等)计算、路线等级计算等,跟最短路径算法无关我这里没写,如果要在真实场景使用,整个设计以及实现需要牵扯很多变量因素,这里的实现也只能算是个差很多的初始模板。这个功能最后客户没有用,只是在演示的时候使用我录入的区域数据在地图上进行演示,对于非专业地图公司实用性基本没有,最主要的原因是数据,非专业公司哪里来那么多路口节点和路段信息数据,总不可能人工去地图软件上手工扒取,之前想过用程序脚本爬取,可是看了各家地图软件以及和gis同事讨论后结果是爬取不可能,因为爬取到的数据和我要的数据不是一个路数,除非我的数据进行重新设计,所以实用性基本没有,被老板拿来给客户充数演示而已,因此后续的很多功能我最后也没再做。
整个设计实现测试等都是自己完成,大概10-15个工作日左右吧,主要是前期的设计和实现思路花了不少时间,只要想好了剩下开发的事都好说,拿到需求后我第一时间想到的是结合生活中地图软件的方式,老板的意思也是结合地图要一个疏散方案的实现,当时因为别的业务开发我和前端GIS开发同事交互了很多,也知道前端只需要数据点经纬度,基于这个前提,我开始上网搜索最短路径的实现方案,网上介绍最多也是最广泛被使用的算法就是Dijkstra算法,很多其他公司的最短路径也是基于Dijkstra算法的理念设计的,针对这个算法看了一些网上的代码案例,怎么说呢,算法这东西,你不清楚它的原理和模型思路,看代码再多都没用,最后无奈一心搜索Dijkstra算法的很多思路模型案例,大致明白以后才结合我要做的场景开始设计数据结构,算法要的是点以及点到点的距离作为计算条件,结合这个思路开始设计表结构和数据以及数据封装。