1. 程式人生 > >地圖上點到範圍的最短距離演算法

地圖上點到範圍的最短距離演算法

今天是七夕,應該留點什麼才對。恰好遇到地圖上點與多邊形的距離計算問題,如果是Oracle Spatial控制元件函式計算的話,有如下幾個缺點:耗時久、計算範圍有侷限。所以我想要在前端地圖上計算該點到範圍的最短距離。

海倫公式計算面積 :

S=P(Pa)(Pb)(Pc)
三角形面積公式:
S=2

根據面積法可推斷出高,即點到一條直線的最短距離。所以我們只要知道哪條邊是點到這個範圍最近的那條邊即可。多邊形是由N個點組合而成,所以只要計算點到這N個點的距離,再排序拿到最近的2個距離對應的點A 、B ,與範圍外的點C 組成最近三角形,利用面積相等公式得到高,也就是點到範圍最近的距離了。當然啦~只有銳角三角形才成立,鈍角三角形的需要另想辦法~但是先把銳角三角形的貼出來吧。

將以上想法用程式碼實現如下:

//求點與範圍最短的距離
 function  getDis(point,polygon){

   //得到點到範圍上每個點的距離。排序拿到最短的2個。以這2個點+外點 做三角形算最短距離 
   var points = polygon.getPath();
   var disArray = [] ;
   for(var i= 0;i<points.length;i++){
      var disI =  map.getDistance(point,points[i]);//計算地圖上點到點的距離
      disArray.push( disI );
   }

  //順序推導
var newArray = disArray.slice();//複製陣列 newArray.sort();//複製後的陣列排序 //找出新陣列中第一個元素和第二個元素在原陣列中的位置進而拿到下標 var ac = newArray[0]; var bc = newArray[1]; var pointA ,pointB ; for(var i =0;i<disArray.length;i++){ if(ac == disArray[i]){ pointA = points[i]; } if(bc ==disArray[i]){ pointB = points[i]; } } var
ab = map.getDistance(pointA,pointB); var p =( ab + ac+ bc)/2 ; //半周長 var S = Math.sqrt(p*(p-ab)*(p-bc)*(p-ac));//面積 var h = 2*S / ab ; return h ; }

這裡寫圖片描述

–by 2016-9-29 補充說明
這次貼出判斷是否銳角的程式碼,這樣就可以比較這2點的高是否是可採取的。如果不是銳角的話,需要再判斷其他的點。思前想後,這個演算法也不是很完美,儘量靠近最短距離吧。請細心的朋友幫我指正^-^。
1、相鄰的情況:
銳角:
這裡寫圖片描述
鈍角:
這裡寫圖片描述
2、不相鄰的情況:
需要與其他的點重做三角形判斷
這裡寫圖片描述

–我們只需要關注多邊形上的2點與範圍外的點組成的夾角是否是銳角即可。

  直角三角形的勾股定理:a*a +b*b = c*c
//是否銳角三角形:
        function  ifAcuteAngle(pointA,pointB,pointC){

            //求三邊長:ab  ac  bc 
            var  ab,ac,bc ;
            var result = false ;
            ab =  map.getDistance(pointA,pointB);
            ac =  map.getDistance(pointA,pointC);
            bc =  map.getDistance(pointB,pointC);
            return     result = ((ab*ab +ac*ac - bc*bc )> 0)&&((ab*ab +bc*bc - ac*ac)  >0 ) ;

        }

目前計算最短距離的演算法是:
1、多邊形上的2點是否是相鄰:則需要知道下標的絕對值是否=1
2、多邊形與範圍外的一點形成的夾角師傅是銳角 ==>三個座標點
只有既是相鄰點又是銳角的情況下才可以取高為最短距離。
否則就不可以取高為最短距離。

貼出整段程式碼:百度祕鑰需要自己去申請,我就不提供了~

<html>
<head>
<title></title>
<meta http-equiv="content-type" content="text/html; charset=gbk">
</head>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=百度祕鑰"></script>
<body>
  input:<input type="text"  id="input" />
  <input type="button" value ="show" id="button" />

  dis:<input type="text"  id="dis" />

 <input type="button" value ="clear" id="clear" />

  <div id="allmap" style="width:100%;height:90%"></div>
  <script type="text/javascript">

    var map = new BMap.Map("allmap");
    map.centerAndZoom(new BMap.Point(121.309918,31.207647), 14);
    map.enableScrollWheelZoom();


        //新增固定範圍
        var polygon = new BMap.Polygon([
        new BMap.Point(121.309056,31.22383),    
        new BMap.Point(121.32006,31.216747),
        new BMap.Point(121.322575,31.200193),
        new BMap.Point(121.30752,31.189041),
        new BMap.Point(121.322998,31.185036),
        new BMap.Point(121.331046,31.171689),
        new BMap.Point(121.331621,31.170082),
        new BMap.Point(121.353324,31.177868),
        new BMap.Point(121.363673,31.187755),
        new BMap.Point(121.35548,31.229018),
        new BMap.Point(121.335646,31.229141),
        new BMap.Point(121.317967,31.224201)
        ], {strokeColor:"red", strokeWeight:2, strokeOpacity:0.8});  //建立多邊形


        map.addOverlay(polygon);   //增加多邊形
        document.getElementById("clear").onclick = function(){    
           map.clearOverlays();
           map.addOverlay(polygon);   //增加多邊形          
        }
       document.getElementById("button").onclick = function(){     
                var point = document.getElementById("input").value ;
                var lng  = parseFloat(point.split(",")[0]);
                var lat = parseFloat(point.split(",")[1]);
                var pointM = new BMap.Point(lng,lat);
                var marker = new BMap.Marker(pointM) ;        
                marker.setLabel(new BMap.Label("C",{offset:new BMap.Size(20,-10)})); 
                map.addOverlay(marker);
               document.getElementById("dis").value =   getShortDis(pointM,polygon);
       }


        function  showMarker(point,name){
               var marker = new BMap.Marker(point) ;          
                marker.setLabel(new BMap.Label(name,{offset:new BMap.Size(20,-10)}));         
                map.addOverlay(marker);
        }
       function  showLine(pointA,pointB){  
             var points = new Array();
             points.push(pointA);
             points.push(pointB) ;
             map.addOverlay( new BMap.Polyline(points));
       }


       function  getShortDis(point,polygon){

           //得到點到範圍上每個點的距離。排序拿到最短的2個。以這2個點+外點 做三角形算最短距離
           var points = polygon.getPath();
           var disArray = [] ;
           for(var i= 0;i<points.length;i++){
              var disI =  map.getDistance(point,points[i]);//計算地圖上點到點的距離
              disArray.push( disI);
           }
          //順序推導
          var newArray = disArray.slice();//複製陣列,為的是不影響原陣列的資料
          newArray.sort(function(n1,n2){ return n1-n2  ; }); //數值型別的排序

          //以最短的兩條邊找到合適的三角形
            var ac = newArray[0]; //若以下判斷都不適用時,直接返回ac
            var bc = newArray[1];
            var isNeighber = false ;
            var isAcuteAngle = false ;
            var pointA,pointB,m,n ; 
          //以距離得到AB 的座標點      
           for(var i =0;i<disArray.length;i++){
               if(ac == disArray[i]){
               pointA = points[i];
               showMarker(pointA,"A:"+ac);
               showLine(point,pointA);
                m = i ;
              }
             if(bc ==disArray[i]){
              pointB = points[i];
              showMarker(pointB,"B:"+bc);
              showLine(point,pointB);
              n = i ;
           }
          }
         //以2點下標差值的絕對值判斷仍需要計算幾個多邊形
        isNeighber =  Math.abs(m-n)==1 ;
        isAcuteAngle =ifAcuteAngle(pointA,pointB,point) ;
        if(isNeighber && isAcuteAngle){

           return   getH(pointA,pointB,point,disArray);

        }else{

              //是相鄰的,但是是鈍角。則直接以第三點為判斷,若第三點也為鈍角,直接以最短邊為距離返回,若第3點不是鈍角,則求高後進行比較再返回
               if(isNeighber){

                  var dc = newArray[2];
                  var pointD =point[getIndex(dc,disArray)];
                  //判斷ACD 和BCD 是否是相鄰且是鈍角
                  if(iFNeighber(pointA,pointD,point,disArray)&&ifAcuteAngle(pointA,pointD,point)){
                        var h = getH(pointA,pointD,point,disArray);
                        return h < ac ?  h : ac ;
                  }else if(iFNeighber(pointB,pointD,point,disArray)&&ifAcuteAngle(pointB,pointD,point)){
                        var h = getH(pointB,pointD,point,disArray);
                        return h < ac ?  h : ac ;
                  }else{
                         return ac ;
                  }

               }else{
                  //不是相鄰的,需要計算中間所有點的
                  var num =  Math.abs(m-n) ;//中間點的個數
                  var hArray = [];
                  for(var k =0;k<num ;k++){

                       //取到中間的每個點,組合三角形。
                       var  nc = disArray[k+2];
                       var pointN = points[getIndex(nc,disArray)];
                       var ha =  getH(pointA,pointN,point,disArray) ;
                       var hb =  getH(pointB,pointN,point,disArray) ;
                       var min =  Math.min(ac,ha,hb) ;
                       if(min < ac){
                          hArray.push(min);
                       }
                  }

                  if(hArray.length == 0){

                    return ac ;

                  }else{

                      //取陣列中最小的那個值
                      hArray.sort(function(n1,n2){ return n1-n2  ; });
                      return  hArray[0];
                  }

               }
        }

       }

       //根據距離,得到下標
       function  getIndex(dis,disArray){

            for(var  i = 0 ;i<disArray.length;i++){

                 if(dis ==disArray[i] ){

                   return i ;
                 }
            }

       }  

       //是否相鄰:
       function iFNeighber(pointA,pointB,pointC,disArray){

          var ab =  map.getDistance(pointA,pointB);
          var ac =  map.getDistance(pointA,pointC);
          var bc =  map.getDistance(pointB,pointC); 

          var m ,n ;           
         //找出下標:
          m  = getIndex(ac,disArray);
          n =  getIndex(bc,disArray);


         return  Math.abs(m-n)==1 ;
       }

        //是否銳角三角形:
        function  ifAcuteAngle(pointA,pointB,pointC){

            //求三邊長:ab  ac  bc 
            var  ab,ac,bc ;
            var result = false ;
            ab =  map.getDistance(pointA,pointB);
            ac =  map.getDistance(pointA,pointC);
            bc =  map.getDistance(pointB,pointC);
            return     result = ((ab*ab +ac*ac - bc*bc )> 0)&&((ab*ab +bc*bc - ac*ac)  >0 ) ;

        }

      //根據面積法得到C到AB的高,即範圍外一點到多邊形的最短距離
       function   getH(pointA,pointB,pointC,disArray){
          var ab,ac,bc ,h;
          var ab =  map.getDistance(pointA,pointB);
          var ac =  map.getDistance(pointA,pointC);
          var bc =  map.getDistance(pointB,pointC);     
         if(iFNeighber(pointA,pointB,pointC,disArray)&&ifAcuteAngle(pointA,pointB,pointC)){
             var p =( ab + ac+ bc)/2 ; //半周長
             var S =  Math.sqrt(p*(p-ab)*(p-bc)*(p-ac));//面積
             h = 2*S / ab ;
             return h <ac ?h:ac
         }
         return ac ;
        }

  </script>

</body>
</html>