2010-04-23 52 views
12

我正在嘗試創建一個Google地圖,用戶可以在其中繪製他走/跑/騎自行車的路線,並查看他跑了多久。 GPolyline類與它的getLength()方法在這方面是非常有用的(至少對於谷歌地圖API V2),但我想添加基於距離的標記,例如1公里,5公里,10公里等標記,但似乎沒有什麼明顯的方法可以根據線條有多遠在折線上找到一個點。有什麼建議麼?如何根據沿線距離在Google地圖多段線上添加標記?

回答

32

在幾個月前有關於如何在SQL Server 2008的服務器端解決這個問題的answered a similar problem,我使用Google Maps API v2將相同的算法移植到JavaScript。

爲了這個例子,我們使用一個簡單的4點折線,總長約8,800米。下面的代碼片段將定義此折線,並使其在地圖上:

var map = new GMap2(document.getElementById('map_canvas')); 

var points = [ 
    new GLatLng(47.656, -122.360), 
    new GLatLng(47.656, -122.343), 
    new GLatLng(47.690, -122.310), 
    new GLatLng(47.690, -122.270) 
]; 

var polyline = new GPolyline(points, '#f00', 6); 

map.setCenter(new GLatLng(47.676, -122.343), 12); 
map.addOverlay(polyline); 

現在我們接近實際的算法之前,我們需要給一個起始點時,終點,返回目標點的功能,以及沿着該線路行進的距離,幸運的是,Chris Veness在Calculate distance, bearing and more between Latitude/Longitude points有一些方便的JavaScript實現。

特別是我已經適應從上面的源代碼以下兩種方法與谷歌的GLatLng類工作:

這些被用來擴展谷歌的GLatLng類方法moveTowards(),當以米爲單位給出另一點和距離時,它將沿着那個返回另一個GLatLng當距離從原始點走向作爲參數傳遞的點時。通過路徑的每個點

  1. 迭代:

    GLatLng.prototype.moveTowards = function(point, distance) { 
        var lat1 = this.lat().toRad(); 
        var lon1 = this.lng().toRad(); 
        var lat2 = point.lat().toRad(); 
        var lon2 = point.lng().toRad();   
        var dLon = (point.lng() - this.lng()).toRad(); 
    
        // Find the bearing from this point to the next. 
        var brng = Math.atan2(Math.sin(dLon) * Math.cos(lat2), 
             Math.cos(lat1) * Math.sin(lat2) - 
             Math.sin(lat1) * Math.cos(lat2) * 
             Math.cos(dLon)); 
    
        var angDist = distance/6371000; // Earth's radius. 
    
        // Calculate the destination point, given the source and bearing. 
        lat2 = Math.asin(Math.sin(lat1) * Math.cos(angDist) + 
            Math.cos(lat1) * Math.sin(angDist) * 
            Math.cos(brng)); 
    
        lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(angDist) * 
              Math.cos(lat1), 
              Math.cos(angDist) - Math.sin(lat1) * 
              Math.sin(lat2)); 
    
        if (isNaN(lat2) || isNaN(lon2)) return null; 
    
        return new GLatLng(lat2.toDeg(), lon2.toDeg()); 
    } 
    

    有了這種方法,我們可以按照如下現在解決這個問題。

  2. 找到迭代中的當前點到下一個點之間的距離。
  3. 如果在第2點的距離越大,我們需要在道路上行駛的距離:

    ...那麼目標點是這點和未來之間。只需將moveTowards()方法應用到當前點,傳遞下一個點和距離即可。返回結果並中斷迭代。

    否則:

    ...目標點位於迭代中下一個點的路徑中。我們需要從總距離中減去此點與下一個點之間的距離,以沿着路徑行進。以修改的距離繼續進行迭代。

您可能已經注意到,我們可以輕鬆地以遞歸方式實現上述而不是迭代。所以,讓我們做吧:

function moveAlongPath(points, distance, index) { 
    index = index || 0; // Set index to 0 by default. 

    if (index < points.length) { 
     // There is still at least one point further from this point. 

     // Construct a GPolyline to use its getLength() method. 
     var polyline = new GPolyline([points[index], points[index + 1]]); 

     // Get the distance from this point to the next point in the polyline. 
     var distanceToNextPoint = polyline.getLength(); 

     if (distance <= distanceToNextPoint) { 
     // distanceToNextPoint is within this point and the next. 
     // Return the destination point with moveTowards(). 
     return points[index].moveTowards(points[index + 1], distance); 
     } 
     else { 
     // The destination is further from the next point. Subtract 
     // distanceToNextPoint from distance and continue recursively. 
     return moveAlongPath(points, 
           distance - distanceToNextPoint, 
           index + 1); 
     } 
    } 
    else { 
     // There are no further points. The distance exceeds the length 
     // of the full path. Return null. 
     return null; 
    } 
} 

利用上述方法,如果我們定義的GLatLng點的數組,我們調用我們moveAlongPath()功能與此陣點和2500米的距離,它會返回一個GLatLng在距離第一點2.5公里的路上。

var points = [ 
    new GLatLng(47.656, -122.360), 
    new GLatLng(47.656, -122.343), 
    new GLatLng(47.690, -122.310), 
    new GLatLng(47.690, -122.270) 
]; 

var destinationPointOnPath = moveAlongPath(points, 2500); 

// destinationPointOnPath will be a GLatLng on the path 
// at 2.5km from the start. 

因此,所有我們需要做的就是調用moveAlongPath()爲我們所需要的路徑上的每個檢查點。如果您需要距1km,5公里和10公里三個標記,你可以簡單地做:

map.addOverlay(new GMarker(moveAlongPath(points, 1000))); 
map.addOverlay(new GMarker(moveAlongPath(points, 5000))); 
map.addOverlay(new GMarker(moveAlongPath(points, 10000))); 

然而要注意moveAlongPath()可能返回null如果我們從路徑的總長度要求檢查點進一步,所以這將是在將其傳遞到new GMarker()之前,請檢查返回值。

我們可以把它放在一起,以便全面實施。在這個例子中,我們正在下降的標記沿8.8公里路徑每千米前面定義:

<!DOCTYPE html> 
<html> 
<head> 
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/> 
    <title>Google Maps - Moving point along a path</title> 
    <script src="http://maps.google.com/maps?file=api&v=2&sensor=false" 
      type="text/javascript"></script> 
</head> 
<body onunload="GUnload()"> 
    <div id="map_canvas" style="width: 500px; height: 300px;"></div> 

    <script type="text/javascript"> 

    Number.prototype.toRad = function() { 
     return this * Math.PI/180; 
    } 

    Number.prototype.toDeg = function() { 
     return this * 180/Math.PI; 
    } 

    GLatLng.prototype.moveTowards = function(point, distance) { 
     var lat1 = this.lat().toRad(); 
     var lon1 = this.lng().toRad(); 
     var lat2 = point.lat().toRad(); 
     var lon2 = point.lng().toRad();   
     var dLon = (point.lng() - this.lng()).toRad(); 

     // Find the bearing from this point to the next. 
     var brng = Math.atan2(Math.sin(dLon) * Math.cos(lat2), 
          Math.cos(lat1) * Math.sin(lat2) - 
          Math.sin(lat1) * Math.cos(lat2) * 
          Math.cos(dLon)); 

     var angDist = distance/6371000; // Earth's radius. 

     // Calculate the destination point, given the source and bearing. 
     lat2 = Math.asin(Math.sin(lat1) * Math.cos(angDist) + 
         Math.cos(lat1) * Math.sin(angDist) * 
         Math.cos(brng)); 

     lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(angDist) * 
           Math.cos(lat1), 
           Math.cos(angDist) - Math.sin(lat1) * 
           Math.sin(lat2)); 

     if (isNaN(lat2) || isNaN(lon2)) return null; 

     return new GLatLng(lat2.toDeg(), lon2.toDeg()); 
    } 

    function moveAlongPath(points, distance, index) {   
     index = index || 0; // Set index to 0 by default. 

     if (index < points.length) { 
     // There is still at least one point further from this point. 

     // Construct a GPolyline to use the getLength() method. 
     var polyline = new GPolyline([points[index], points[index + 1]]); 

     // Get the distance from this point to the next point in the polyline. 
     var distanceToNextPoint = polyline.getLength(); 

     if (distance <= distanceToNextPoint) { 
      // distanceToNextPoint is within this point and the next. 
      // Return the destination point with moveTowards(). 
      return points[index].moveTowards(points[index + 1], distance); 
     } 
     else { 
      // The destination is further from the next point. Subtract 
      // distanceToNextPoint from distance and continue recursively. 
      return moveAlongPath(points, 
           distance - distanceToNextPoint, 
           index + 1); 
     } 
     } 
     else { 
     // There are no further points. The distance exceeds the length 
     // of the full path. Return null. 
     return null; 
     } 
    } 

    var map = new GMap2(document.getElementById('map_canvas')); 

    var points = [ 
     new GLatLng(47.656, -122.360), 
     new GLatLng(47.656, -122.343), 
     new GLatLng(47.690, -122.310), 
     new GLatLng(47.690, -122.270) 
    ]; 

    var polyline = new GPolyline(points, '#f00', 6); 

    var nextMarkerAt = 0;  // Counter for the marker checkpoints. 
    var nextPoint = null;  // The point where to place the next marker. 

    map.setCenter(new GLatLng(47.676, -122.343), 12); 

    // Draw the path on the map. 
    map.addOverlay(polyline); 

    // Draw the checkpoint markers every 1000 meters. 
    while (true) { 
     // Call moveAlongPath which will return the GLatLng with the next 
     // marker on the path. 
     nextPoint = moveAlongPath(points, nextMarkerAt); 

     if (nextPoint) { 
     // Draw the marker on the map. 
     map.addOverlay(new GMarker(nextPoint)); 

     // Add +1000 meters for the next checkpoint. 
     nextMarkerAt += 1000;  
     } 
     else { 
     // moveAlongPath returned null, so there are no more check points. 
     break; 
     }    
    } 
    </script> 
</body> 
</html> 

上面的例子的屏幕截圖,示出了標記物每千米:

Google Maps - Move Point Along a Path

+0

我正在使用Google Map Api V3,您的公式似乎很好,但是當我縮放到道路層面時,我可以看到線條之間的距離由谷歌和我的標記。這有什麼理由呢? – Nordes 2010-08-06 13:45:52

+0

@Nordes:這是否發生在上面的例子中?我試圖放大到最大縮放級別,並且標記似乎在線上。屏幕截圖:http://img408.imageshack.us/img408/8687/gmapnospace.png – 2010-08-09 06:20:27

+0

我會嘗試使用您的所有代碼。實際上,我只使用你在JS中創建的「hasrsine」公式。也許我在某處做了一個錯誤的計算。一旦我嘗試使用您的代碼,我會盡快回復您。 – Nordes 2010-08-09 10:10:09

3

可能最好的辦法是計算這些點的位置。

作爲一種基本算法,您可以迭代Polyline中的所有點,並計算累積距離 - 如果下一個段將您放在距離上,則可以插入距離已到達的點 - 然後只需添加這是你的地圖的一個興趣點。

+0

是啊,這應該是可行的 - 我只是希望有某種偷偷摸摸的方式讓API來做到這一點:) – mikl 2010-04-23 11:51:47

+0

@mikl我可能是一個受虐狂說,但我認爲它更有趣找出這樣的解決方案,其中沒有明顯的API方法 – 2010-04-23 13:28:45

3

我發現爲什麼我有這種不確定性。實際上,在GMap的V3中,我們沒有函數「getLength」 ,它返回的是Km 或polyLine的Meters中的長度。

這裏的所需功能的原型 - 希望這有助於任何進一步的:

google.maps.Polygon.prototype.Distance = function() { 
    var dist = 0; 
    for (var i=1; i < this.getPath().getLength(); i++) { 
     dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i-1)); 
    } 
    return dist; 
} 

google.maps.LatLng.prototype.distanceFrom = function(newLatLng) { 
    //var R = 6371; // km (change this constant to get miles) 
    var R = 6378100; // meters 
    var lat1 = this.lat(); 
    var lon1 = this.lng(); 
    var lat2 = newLatLng.lat(); 
    var lon2 = newLatLng.lng(); 
    var dLat = (lat2-lat1) * Math.PI/180; 
    var dLon = (lon2-lon1) * Math.PI/180; 
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
     Math.cos(lat1 * Math.PI/180) * Math.cos(lat2 * Math.PI/180) * 
     Math.sin(dLon/2) * Math.sin(dLon/2); 
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
    var d = R * c; 
    return d; 
} 

source

0

我想端口Daniel Vassalo's answer到iOS,但它不能正常工作,一些標記被放錯地方,直到我改變了

var dLon = (point.lng() - this.lng()).toRad(); 

var dLon = point.lng().toRad() - this.lng().toRad(); 

因此,如果任何人有麻煩找出爲什麼標記是錯位的,試試這個,也許它會有所幫助。

1

我已經使用Martin Zeitler方法與谷歌地圖V3和它的工作正常。

function init() { 
     var mapOptions = { 
      zoom: 15, 
      center: new google.maps.LatLng(-6.208437004433984, 106.84543132781982), 
      suppressInfoWindows: true, 
        }; 

     // Get all html elements for map 
     var mapElement = document.getElementById('map1'); 

     // Create the Google Map using elements 
     map = new google.maps.Map(mapElement, mapOptions); 

     var nextMarkerAt = 0;  // Counter for the marker checkpoints. 
     var nextPoint = null;  // The point where to place the next marker. 


     while (true) { 

      var routePoints = [ new google.maps.LatLng(47.656, -122.360), 
           new google.maps.LatLng(47.656, -122.343), 
           new google.maps.LatLng(47.690, -122.310), 
           new google.maps.LatLng(47.690, -122.270)]; 

       nextPoint = moveAlongPath(routePoints, nextMarkerAt); 

      if (nextPoint) { 
       //Adding marker from localhost 
       MarkerIcon = "http://192.168.1.1/star.png"; 
       var marker = new google.maps.Marker 
        ({position: nextPoint, 
         map: map, 
         icon: MarkerIcon 
        }); 
       // Add +1000 meters for the next checkpoint. 
       nextMarkerAt +=1000; 

      } 
      else { 
       // moveAlongPath returned null, so there are no more check points. 
       break; 
      } 
     } 
} 


    Number.prototype.toRad = function() { 
     return this * Math.PI/180; 
    } 

    Number.prototype.toDeg = function() { 
     return this * 180/Math.PI; 
    } 

    function moveAlongPath(point, distance, index) { 
     index = index || 0; // Set index to 0 by default. 

     var routePoints = []; 

     for (var i = 0; i < point.length; i++) { 
      routePoints.push(point[i]); 
     } 

     if (index < routePoints.length) { 
      // There is still at least one point further from this point. 

      // Construct a GPolyline to use the getLength() method. 
      var polyline = new google.maps.Polyline({ 
       path: [routePoints[index], routePoints[index + 1]], 
       strokeColor: '#FF0000', 
       strokeOpacity: 0.8, 
       strokeWeight: 2, 
       fillColor: '#FF0000', 
       fillOpacity: 0.35 
      }); 

      // Get the distance from this point to the next point in the polyline. 
      var distanceToNextPoint = polyline.Distance(); 

      if (distance <= distanceToNextPoint) { 
       // distanceToNextPoint is within this point and the next. 
       // Return the destination point with moveTowards(). 
       return moveTowards(routePoints, distance,index); 
      } 
      else { 
       // The destination is further from the next point. Subtract 
       // distanceToNextPoint from distance and continue recursively. 
       return moveAlongPath(routePoints, 
        distance - distanceToNextPoint, 
        index + 1); 
      } 
     } 
     else { 
      // There are no further points. The distance exceeds the length 
      // of the full path. Return null. 
      return null; 
     } 
    } 

    function moveTowards(point, distance,index) { 

     var lat1 = point[index].lat.toRad(); 
     var lon1 = point[index].lng.toRad(); 
     var lat2 = point[index+1].lat.toRad(); 
     var lon2 = point[index+1].lng.toRad(); 
     var dLon = (point[index + 1].lng - point[index].lng).toRad(); 

     // Find the bearing from this point to the next. 
     var brng = Math.atan2(Math.sin(dLon) * Math.cos(lat2), 
      Math.cos(lat1) * Math.sin(lat2) - 
      Math.sin(lat1) * Math.cos(lat2) * 
      Math.cos(dLon)); 

     var angDist = distance/6371000; // Earth's radius. 

     // Calculate the destination point, given the source and bearing. 
     lat2 = Math.asin(Math.sin(lat1) * Math.cos(angDist) + 
      Math.cos(lat1) * Math.sin(angDist) * 
      Math.cos(brng)); 

     lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(angDist) * 
      Math.cos(lat1), 
      Math.cos(angDist) - Math.sin(lat1) * 
      Math.sin(lat2)); 

     if (isNaN(lat2) || isNaN(lon2)) return null; 



     return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg()); 
    } 

    google.maps.Polyline.prototype.Distance = function() { 
     var dist = 0; 
     for (var i = 1; i < this.getPath().getLength(); i++) { 
      dist += this.getPath().getAt(i).distanceFrom(this.getPath().getAt(i - 1)); 
     } 
     return dist; 
    } 

    google.maps.LatLng.prototype.distanceFrom = function (newLatLng) { 
     //var R = 6371; // km (change this constant to get miles) 
     var R = 6378100; // meters 
     var lat1 = this.lat(); 
     var lon1 = this.lng(); 
     var lat2 = newLatLng.lat(); 
     var lon2 = newLatLng.lng(); 
     var dLat = (lat2 - lat1) * Math.PI/180; 
     var dLon = (lon2 - lon1) * Math.PI/180; 
     var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
      Math.cos(lat1 * Math.PI/180) * Math.cos(lat2 * Math.PI/180) * 
      Math.sin(dLon/2) * Math.sin(dLon/2); 
     var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 
     var d = R * c; 
     return d; 
    } 
相關問題