• 室内导航简单应用方案【Dijkstra+zrender】


    简单的室内导航,就是在没有传感器或者说外部硬件设施辅助(WIFI或者蓝牙组网点整)的情况下,基于相对位置实现。

    思路很简单,在室内地图上,将能走的路上关键点(能够产生分叉的路口)上打点,然后将能走通的点间,用线连接起来(这个线就是相邻两个点之间的路径,路径的长度由打出来的点的坐标,依据勾股定理计算出来),这样,就可以构建出一个限定地图(楼层)范围内的路线网络,这个打点连线的过程,就有点类似百度地图或者高德地图之类的,绘制地图中的道路的过程,只是我这里,相对来说,比较简单而已,但是,核心的思想其实大同小异。即:导航前,必须有一个地图,关键就是有一个路线网络。

    接下来,当有人需要用导航的时候,就需要选择自己在那个门口,然后选择自己要去那个地方,这套方案就可以给选定出一个最短路线。后台计算最短路径的算法,就是基于dijkstra算法,思路简单清晰。

    也就是说,这里的室内简单导航方案,主要是前端绘图,然后,后端基于客户请求,算出最短路径所经过的点,将这些点以及边的信息,告知前端,前端绘制出这个最短的路径,用户就可以基于自己所在的起点,沿着这个路线,找到自己所要去的目的地。这里之所以说是个简单的方案,原因在于,用户离开起点后,在行进的过程中,失去了自己当前所在位置信息,即没有了参考。当然,结合硬件设备,即可将用户实时的位置信息反映到地图上,就解决了实时位置参考信息。

    前端的打点和绘图工作,主要依据zrender.js这个插件实现(是个非常不错的绘图工具),后台数据处理,主要基于springboot+mysql完成。

    这里不做过多的介绍,直接上代码:

    1. 前端HTML

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>DijMap</title>
    </head>
    <style>
    #container{
        height: 700px;
        border: 3px dashed #ccc;
        margin: 0 auto;
    }
    #clearBtn, span{
        margin-left: 12px;
    }
    </style>
    <body>
    <h1>找最短路径游戏</h1>
    <span>前端技术参考资料:https://ecomfe.github.io/zrender-doc/public/api.html</span><br/>
    <span style="color: #44bb99;">说明:1)左键单击创建节点,左键按下拖动到终点实现划线;2)右键单击删除节点/边;3)选择起点/终点状态后,中键选择起点/终点</span><br/>
    <button id="clearBtn">清除所有点</button>
    <label><input name="demo" type="radio" value="st"/>选起点</label>
    <label><input name="demo" type="radio" value="ed"/>选终点</label>
    <button id="nearest">开始游戏</button>
    <button id="restgame">重新游戏</button>
    <div id="container"></div>
    <script src="../js/jquery-2.1.1.min.js"></script>
    <script src="../js/zrender.min.js"></script>
    <script src="../js/inner-map.js"></script>
    </body>
    </html>

    2.前端inner-map.js

    function setPanel() {
        var width = $(document.body).width();
        var height = $(document.body).height();
        $('#container').height(height - 100);
        $('#container').width(width - 30);
        $('canvas').attr("height",height - 100);
        $('canvas').attr("width", width - 30);
    }
    function savePoint(zr, pos, cycle) {
        $.post("./point/save", pos, function(data){
            cycle.pointId = data.info;
            pos.id = data.info;
            createText(zr, pos);
            savePointToLocal(pos.id, {"x": pos.pointx, "y":pos.pointy});
        }, "json");
    }
    function getAllPoints(zr) {
        $.get("./point/getAll", function(data){
            var jp = data;
            for(var i=0; i<jp.length; i++){
                console.log("id: " + jp[i].id + ", x: " + jp[i].pointx + ", y: " + jp[i].pointy);
                createPoint(zr, jp[i]);
                createText(zr, jp[i]);
                savePointToLocal(jp[i].id, {"x": jp[i].pointx, "y":jp[i].pointy})
            }
        }, "json");
    }
    function getPaths(zr, src, dst) {
        $.get("./go", {"srcId": src, "dstId": dst}, function(data){
            var jp = data;
            for(var i=0; i<jp.length; i++){
                console.log("id: " + jp[i].id + ", x: " + jp[i].pointx + ", y: " + jp[i].pointy);
            }
            showPath(zr, jp);
        }, "json");
    }
    function showPath(zr, jp) {
        //jp的长度一定是大于等于2的,否则不可能行程一条路径
        if(jp.length <= 1){
            console.log("不是一个合法的路径");
            return;
        }
        for(var i = 0; i<jp.length-1; i++){
            var fp = jp[i];
            var tp = jp[i+1];
            var path = new zrender.Line({
                shape: {
                    x1:fp.pointx,
                    y1:fp.pointy,
                    x2:tp.pointx,
                    y2:tp.pointy
                },
                style: {
                    stroke:'green',
                    lineWidth: 3
                }
            });
            zr.add(path);
            showedPath.push(path);
        }
    }
    function savePointToLocal(idx, pos) {
        var spos = JSON.stringify(pos);
        sessionStorage.setItem(idx, spos);
    }
    function getPointFromLocal(idx) {
        var res = sessionStorage.getItem(idx);
        var pos = JSON.parse(res);
        return pos;
    }
    function saveEdge(line) {
        if(line.len <= 10){
            console.log("距离太近,不予考虑...");
            return;
        }
        $.post("./edge/save", {"from":line.from, "to": line.to, "len": line.len}, function(data){
            line.lineId = data.info;
        }, "json");
    }
    function getAllEdges(zr) {
        $.get("./edge/getAll", function(data){
            var je = data;
            for(var i=0; i<je.length; i++){
                console.log("id: " + je[i].id + ", point: " + je[i].point + ", neighbor: " + je[i].neighbor + ", weight: " + je[i].weight);
                createEdge(zr, je[i]);
            }
        }, "json");
    }
    function delPoint(zr, circle) {
        $.post("./point/del", {"id":circle.pointId}, function(data){
            zr.remove(circle);
            zr.remove(textMap[circle.pointId]);
            for(var i = 0; i<data.length; i++){
                var edgeId = data[i];
                var dline = edgeMap[edgeId];
                zr.remove(dline);
                delete(edgeMap[edgeId])
            }
        }, "json");
    }
    function delEdge(zr, line) {
        $.post("./edge/del", {"id":line.lineId}, function(data){
            zr.remove(line);
            delete(edgeMap[line.lineId]);
        }, "json");
    }
    function createEdge(zr, je) {
        var fp = je.point;
        var tp = je.neighbor;
        fpoint = getPointFromLocal(fp);
        tpoint = getPointFromLocal(tp);
        var line = new zrender.Line({
            shape: {
                x1:fpoint.x,
                y1:fpoint.y,
                x2:tpoint.x,
                y2:tpoint.y
            },
            style: {
                stroke:'black'
            }
        }).on("mousedown", function(ev){
            if(ev.which == 3) { //右键
                delEdge(zr, line);
            }
        });
        line.from = fp;
        line.to = tp;
        line.len = je.weight;
        line.lineId = je.id;
        zr.add(line);
        edgeMap[je.id] = line;
    }
    function calcLen(fpoint, tpoint) {
        var xx = (fpoint.x - tpoint.x) * (fpoint.x - tpoint.x);
        var yy = (fpoint.y - tpoint.y) * (fpoint.y - tpoint.y);
        var edge = Math.sqrt(xx + yy);
        return Math.round(edge);
    }
    var fpoint = {"x":0, "y":0};
    var tpoint = {"x":0, "y":0};
    var fcycle = null;
    var srcId = null;
    var dstId = null;
    var step = null;
    var edgeMap = {};
    var textMap = {};
    var showedPath = [];
    function createPoint(zr, pos) {
        var circle = new zrender.Circle({
            shape: {
                cx: 0,
                cy: 0,
                r: 10
            },
            position: [
                pos.pointx,
                pos.pointy
            ],
            style: {
                stroke: 'green',
                fill: 'red'
            }
        }).on('mouseover', function(){
            this.animateTo({
               shape: {
                   r: 20
               },
               style: {
                   stroke: 'green',
                   fill: 'blue'
               }
            }, 300)
        }).on('mouseout', function() {
            this.animateTo({
                shape: {
                    r: 10
                },
                style: {
                    stroke: 'green',
                    fill: 'red'
                }
            }, 300)
        }).on("mousedown", function(ev){
            if(ev.which == 1){ //左键
                fpoint = {"id": circle.pointId, "x": pos.pointx, "y": pos.pointy};
            }else if(ev.which == 3){//右轮
                delPoint(zr, circle);
            }else if(ev.which == 2){//中键
                 //var step = $('input:radio:checked').val();
                 if(step === 'st'){
                    srcId = circle.pointId;
                 }
                 if(step === 'ed'){
                    dstId = circle.pointId;
                 }
                 console.log("step: " + step + ", src: " + srcId + ", dst: " + dstId);
             }
        }).on("mouseup", function(ev){
            if(ev.which == 1){ //左键
                tpoint = {"id": circle.pointId, "x": pos.pointx, "y": pos.pointy};
                var line = new zrender.Line({
                    shape: {
                        x1:fpoint.x,
                        y1:fpoint.y,
                        x2:tpoint.x,
                        y2:tpoint.y
                    },
                    style: {
                        stroke:'black'
                    }
                }).on("mousedown", function(ev){
                    if(ev.which == 3){ //左键
                        delEdge(zr, line);
                    }
                });
                var len = calcLen(fpoint, tpoint);
                line.from = fpoint.id;
                line.to = tpoint.id;
                line.len = len;
                saveEdge(line);
                zr.add(line);
                edgeMap[line.lineId] = line;
            }else if(ev.which == 3){//右轮
    
            }
        })
        if(pos.id != null && pos.id != undefined){
            circle.pointId = pos.id;
        }
        zr.add(circle);
        return circle;
    }
    function createText(zr, pos) {
        var posText = new zrender.Text({
            style: {
                stroke: 'blue',
                text: "[" + pos.id + "] (" + pos.pointx + "," + pos.pointy + ")",
                fontSize: '11',
                textAlign:'center'
            },
            position: [pos.pointx, pos.pointy + 13]
        });
        zr.add(posText);
        textMap[pos.id] = posText;
    }
    
    $(document).ready(function() {            
        document.oncontextmenu = function(){
          return false;
        }
    
        var container = document.getElementById('container');
        var zr = zrender.init(container);
        
        setPanel();
        //注意,一定是先加载点,然后再加载边
        getAllPoints(zr);
        getAllEdges(zr);
        zr.on('click', function(e) {
            var pos = {"id": 0, "pointx": e.offsetX, "pointy": e.offsetY};
            var point = createPoint(zr, pos)
            savePoint(zr, pos, point);
        })
        //删除所有的节点
        $('#clearBtn').on('click', function(e) {
            zr.clear()
        })
        //选择起点和终点
        $("input[type=radio]").on("click", function(){
            step = $('input:radio:checked').val();
        });
        //开始绘制最短路径
        $('#nearest').on('click', function(e) {
            getPaths(zr, srcId, dstId);
        });
        //删除生成的最短路径,将上次的起始和结束点复位
        $('#restgame').on('click', function(e) {
            var len = showedPath.length;
            for(var i=0; i<len; i++){
                zr.remove(showedPath[i]);
            }
            showedPath.splice(0, len);
            srcId = null;
            dstId = null;
        })
    });

    3. Dijkstra最短路径

    package com.shihuc.up.nav.path.util;
    
    import org.springframework.data.mongodb.core.aggregation.ArrayOperators;
    
    import java.util.List;
    import java.util.Queue;
    import java.util.Stack;
    
    /**
     * @Author: chengsh05
     * @Date: 2019/12/9 10:25
     */
    public class DJMatrix {
    
        private static int INF = Integer.MAX_VALUE;
    
        public static void dijkstra(int vs, int mMatrix[][], int[] prev, int[] dist) {
            // flag[i]=true表示"顶点vs"到"顶点i"的最短路径已成功获取
            boolean[] flag = new boolean[mMatrix.length];
    
            // 初始化
            for (int i = 0; i < mMatrix.length; i++) {
                // 顶点i的最短路径还没获取到。
                flag[i] = false;
                // 顶点i的前驱顶点为0,此数组的价值在于计算出最终具体路径信息。
                prev[i] = 0;
                // 顶点i的最短路径为"顶点vs"到"顶点i"的权。
                dist[i] = mMatrix[vs][i];
            }
    
            // 对"顶点vs"自身进行初始化
            flag[vs] = true;
            dist[vs] = 0;
    
            // 遍历所有顶点;每次找出一个顶点的最短路径。
            int k=0;
            for (int i = 1; i < mMatrix.length; i++) {
                // 寻找当前最小的路径, 即,在未获取最短路径的顶点中,找到离vs最近的顶点(k)。
                int min = INF;
                for (int j = 0; j < mMatrix.length; j++) {
                    if (flag[j]==false && dist[j]<min) {
                        min = dist[j];
                        k = j;
                    }
                }
                // 标记"顶点k"为已经获取到最短路径
                flag[k] = true;
    
                // 修正当前最短路径和前驱顶点
                // 即,当已经求出"顶点k的最短路径"之后,更新"未获取最短路径的顶点的最短路径和前驱顶点"。
                for (int j = 0; j < mMatrix.length; j++) {
                    int tmp = (mMatrix[k][j]==INF ? INF : (min + mMatrix[k][j]));
                    if (flag[j]==false && (tmp<dist[j]) ) {
                        dist[j] = tmp;
                        prev[j] = k;
                    }
                }
            }
        }
    
        public static String calcPath(int vs, int ve, int prev[], Stack<Integer> pathOut) {
            String path = "" + ve;
            pathOut.push(ve);
            int vep = prev[ve];
            while (vep != 0 && vs != vep) {
                path = vep + "->" + path;
                pathOut.push(vep);
                vep = prev[vep];
            }
            pathOut.push(vs);
            return vs + "->" + path;
        }
    
        public static void main(String []args) {
            int stops[][] = new int [][] {
                    {0,   12,INF,INF,INF,16,14},
                    {12,   0,10,INF,INF, 7,INF},
                    {INF, 10, 0, 3, 5, 6,INF},
                    {INF,INF, 3, 0, 4,INF,INF},
                    {INF,INF, 5, 4, 0, 2, 8},
                    {16,   7, 6, INF, 2, 0, 9},
                    {14, INF,INF,INF, 8, 9, 0}
            };
    
            int vs = 0;
            int prev[] = new int[stops.length];
            int dist[] = new int[stops.length];
            dijkstra(vs, stops, prev, dist);
        }
    }

    4. 数据库表结构

    A.点表(记录的是关键分叉路口的位置,是像素点坐标)

    CREATE TABLE `dij_point` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `pointx` int(11) NOT NULL COMMENT '点的X坐标',
      `pointy` int(11) NOT NULL COMMENT '点的Y坐标',
      PRIMARY KEY (`id`),
      UNIQUE KEY `POINT_XY_IDX` (`pointx`,`pointy`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4;

    B.边表(记录可以通行的两点之间的边,边代表路径,是无方向的,边的长度用两个点之间的像素距离表示)

    CREATE TABLE `dij_edge` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `point` int(11) NOT NULL COMMENT 'point表的主键ID',
      `neighbor` int(11) NOT NULL COMMENT '指定点的邻居节点在point表的主键ID',
      `weight` int(11) NOT NULL COMMENT '边的权重,这里主要是像素距离',
      PRIMARY KEY (`id`),
      UNIQUE KEY `POINT_NEIG_IDX` (`point`,`neighbor`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;

    5. 效果展示

    其他的代码,这里就不做过多的贴出来,有兴趣的,可以去我的github看吧,navhttps://github.com/shihuc/nav)项目。可以fork,可以star,欢迎欢迎,关注我的博客,随时评论。

    接下来,看看效果图:

    A。 空的界面

    B。打点,选择出关键分叉路口点(这个思路有很大的好处,就是室内规划有变的时候,只需要在关键分叉口添加或者节点,局部调整一下路径连接)

    C。绘制任意两点之间可以通行的路径

    D。选择导航的起点(因为这里没有任何传感器设备,起点只能人为选择)

    E。选择终点(就是要到达的目的地)

    F。开始游戏(基于选择的起点和终点,选出最短的路径。说明下:绘制路径的时候,其实已经将两点之间的距离,即基于像素算出来的欧氏距离已经入库了)

    到此,一个简单的室内导航的应用方案,就完成了,有什么更好的创意,可以随时与我交流,关注博客,欢迎留言。

    注意:转载请写明出处。

  • 相关阅读:
    解决ActiveX插件ZIndex属性无效问题
    XNA游戏开发之字符篇
    XNA游戏开发之滚动背景
    ExtJs之FormPanel篇
    独立Discuz头像编辑模块
    XNA游戏开发之2D游戏
    WPF系列之应用程序生命周期
    DiscuzX2.0在windows下的配置
    Silverlight之out of Browser模式
    使用VisualStudio2010连接CodePlex进行代码管理
  • 原文地址:https://www.cnblogs.com/shihuc/p/12123400.html
Copyright © 2020-2023  润新知