java使用Dijkstra算法实现地图最短路径模型

最近甲方国企因为换领导要裁撤所有外包员工(听来的早的同事说他们每隔一两年换一波外包,全裁后再根据需求招,满满的套路),我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算法的很多思路模型案例,大致明白以后才结合我要做的场景开始设计数据结构,算法要的是点以及点到点的距离作为计算条件,结合这个思路开始设计表结构和数据以及数据封装。

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值