Dijkstra+GeoTools实现最短路径计算服务

2019年到现在还没更过一篇,确实是这阵子赶项目,自个也一直处于打杂之中(前期项目调研、公司网络调整、项目地图制作等等)。乘着五一空闲更一篇最近做的小栗子,希望能够帮助到各位同学。

为什么会有这篇

作者在给一个园区做地图时候,甲方有需求能够针对这个园区进行路径规划服务,也即告知起止点(甚至途径点),计算出一条路径。
这是一个典型的路径规划问题,各个地图大厂都有路径规划服务,但是针对园区内还没有。也有很多前辈基于pgRouting实现过,例如:基于pgrouting的任意两点间的最短路径查询函数二
,但是需要借助PostgreSQL、Postgis、GeoServer等。针对一个园区,这些有点重,工程施工也比较繁琐。园区内道路简单,节点也不算多,可以把Postgis替我们干的活我们自己做。
如果你正在做一个园区的最短路径服务,并且希望服务轻量(不需要那么多依赖工具)这篇文章适合你。

准备工作

1、你有一个要做最短路径规划的园区平面图(可能是甲方给你的CAD图纸、或者你从OSM亦或百度高德谷歌地图上临摹的道路中心线Shp文件),进行空间配准后将其导出为shp文件或geojson文件。


image.png

2、对这个文件进行处理,完成一部分Postgis替我们干的活。主要是给道路进行编号、给道路节点进行编号、给道路添加起止节点(引用道路节点)。生成两个文件,暂且给它命名为(roadline、roadpoint)。
其属性表如下


roadline.png

roadpoint.png

roadpoint字段单一,这里只给出了一个节点编号,重点解释一下roadline。

roadline是道路中心线的分段,每条线段有由线段ID(ID)、起点编号(start)、终点编号(target)、是否双向(twoway)、权重(weight)构成,其权重值即为路段长度。


image.png

PS:在对道路进行分段的时候,遵循的技巧是在岔道口分段。
将数据手动填充完,把两个文件保存为geojson格式,放置在项目resource文件夹内备用。
完成上述工作,基本上数据就准备完毕了。

算法开发

Dijkstra是成熟的最短路径算法,可以到github上找一个脚手架算法。把代码下载并在本地调试,它会帮助你理解该算法。
但是,github上的算法基本上是告诉你这条路径应该按什么样的节点顺序走,而我们的应用中希望是获得一条或者多条道路中心线(前端页面最想获得的就是一串道路中心线坐标或者linestring 这样的wkt)。所以我们得把算法改动一下,增加途径的路段。

构造实体对象

首先是节点对象,它包含这样几个字段,分别为节点编号、节点相连的路段列表、节点与目标节点途经路径(由节点列表构成)、节点与目标节点途经的路段(由路段ID列表构成)

public class Vertex implements Comparable<Vertex> {
    public String id; 
    public ArrayList<Edge> neighbours;
    public LinkedList<Vertex> path;
    public LinkedList<String> linkedgeid;
}

其次是路段对象,它由起始节点、终止节点、权重、双向标志位、路段编号、路段Geometry对象构成

public class Edge {
    public Vertex start;
    public Vertex target;
    public double weight;
    public String twoway;
    public String edgeid;
    public LineString lineString;
}

最后是图,它就是由节点列表和路段列表构成

public class Graph {
    private ArrayList<Vertex> vertices;
    private ArrayList<Edge> edges;
}

图的初始化

Dijkstra算法最重要一步是图的初始化工作。
首先,读取节点对应的geojson文件,来初始化图中的节点列表。
其次,读取路段对应的geojson文件,构建节点的连接关系,并填充图中的路段列表。(这里,根据路段双向标志物进行判断,如果是双向起止节点要添加两次,如果是单向只添加一次)

算法实现

这部分可以参照postgis实现,把sql语句改写为java代码即可。作者在这就直接上代码。

    //输入起点终点坐标,计算最短路径
    public static List calculateMinRoute(double startlon, double startlat, double endlon, double endlat, Graph graph) {
        List resultls = new ArrayList();
        Edge startEdge = calculateEdge(startlon, startlat, graph.getEdges());
        Edge endEdge = calculateEdge(endlon, endlat, graph.getEdges());
        List ss = calculatePath(startEdge.start, endEdge.start, graph);
        List st = calculatePath(startEdge.start, endEdge.target, graph);
        List ts = calculatePath(startEdge.target, endEdge.start, graph);
        List tt = calculatePath(startEdge.target, endEdge.target, graph);
        List<Edge> minls = ss;
        double minlenss = calculateWeight(ss);
        double minlenst = calculateWeight(st);
        double minlents = calculateWeight(ts);
        double minlentt = calculateWeight(tt);
        if(minlenss > minlenst) {
            minls = st;
        }
        if(minlenst > minlents) {
            minls = ts;
        }
        if(minlents > minlentt) {
            minls = tt;
        }

        LineMerger lineMerger = new LineMerger();
        List<Geometry> list = new ArrayList<Geometry>();
        list.add(startEdge.getLineString());
        for(Edge edge:minls) {
            if(edge.start.getId() != startEdge.start.getId() && edge.target.getId() != startEdge.target.getId()) {
                list.add(edge.getLineString());
            }
        }
        if(startEdge.start.getId() != endEdge.start.getId() && startEdge.target.getId() != endEdge.target.getId()) {
            list.add(endEdge.getLineString());
        }
        lineMerger.add(list);
        WKTWriter writer = new WKTWriter();
        //将所有线段进行合并(如若首位坐标不匹配,可能合并后不止一条线段)
        Collection<Geometry> mergerLineStrings = lineMerger.getMergedLineStrings();
        if(mergerLineStrings.size() == 1) {
            LocationIndexedLine mergeline=new LocationIndexedLine(mergerLineStrings.iterator().next());
            Coordinate spoint = new Coordinate(startlon, startlat);
            LinearLocation shere=mergeline.project(spoint);

            Coordinate epoint = new Coordinate(endlon, endlat);
            LinearLocation ehere=mergeline.project(epoint);

            LinearLocation temp;
            if(shere.compareTo(ehere) > 0) {
                temp = shere;
                shere = ehere;
                ehere = temp;
            }

            LineString result = (LineString)mergeline.extractLine(shere, ehere);
            resultls.add(writer.write(result));
        }
    }

这里用到来geotools中的linemerge功能(在此特别感谢polong,每每遇到问题都能从他那找到答案)

结果

让前端帮我测试了一下,效果还不错。


image.png

image.png

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容