室內導航簡單應用方案【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。開始游戲(基於選擇的起點和終點,選出最短的路徑。說明下:繪制路徑的時候,其實已經將兩點之間的距離,即基於像素算出來的歐氏距離已經入庫了)

 

到此,一個簡單的室內導航的應用方案,就完成了,有什么更好的創意,可以隨時與我交流,關注博客,歡迎留言。

注意:轉載請寫明出處。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM