2010-04-23 36 views
12

Tôi đang cố gắng tạo Bản đồ Google, nơi người dùng có thể vẽ sơ đồ tuyến đường mà anh ta đi/chạy/đạp xe và xem anh ta chạy trong bao lâu. Lớp GPolyline với phương pháp getLength() rất hữu ích trong vấn đề này (ít nhất là đối với API Google Maps V2), nhưng tôi muốn thêm điểm đánh dấu dựa trên khoảng cách, ví dụ: điểm đánh dấu là 1 km, 5 km, 10 km, v.v. nhưng có vẻ như không có cách nào rõ ràng để tìm một điểm trên một polyline dựa trên khoảng cách dọc theo đường thẳng. Bất kỳ đề xuất?Làm cách nào để thêm điểm đánh dấu trên các bản đồ của Google Maps dựa trên khoảng cách dọc theo đường?

Trả lời

32

answered a similar problem một vài tháng trước về cách giải quyết vấn đề này ở phía máy chủ trong SQL Server 2008, tôi đang chuyển cùng một thuật toán cho JavaScript bằng cách sử dụng Google Maps API v2.

Vì lợi ích của ví dụ này, hãy sử dụng một polyline 4 điểm đơn giản, với tổng chiều dài khoảng 8.800 mét. Đoạn dưới đây sẽ xác định polyline này và sẽ làm cho nó trên bản đồ:

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); 

Bây giờ trước khi chúng ta tiếp cận các thuật toán thực tế, chúng tôi sẽ cần một hàm trả về các điểm đến khi đưa ra một điểm bắt đầu, điểm cuối, và khoảng cách di chuyển dọc theo tuyến đó, May mắn thay, có một vài cách triển khai JavaScript tiện dụng của Chris Veness tại Calculate distance, bearing and more between Latitude/Longitude points.

Đặc biệt tôi đã thích nghi trong hai phương pháp sau đây từ các nguồn trên để làm việc với lớp GLatLng của Google:

Chúng được sử dụng để mở rộng lớp GLatLng của Google với một phương thức moveTowards(), khi được cho một điểm khác và khoảng cách tính bằng mét, nó sẽ trả về một số khác là GLatLng ine khi khoảng cách được di chuyển từ điểm gốc tới điểm được truyền dưới dạng tham số.

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()); 
} 

Có phương pháp này, bây giờ chúng ta có thể giải quyết vấn đề như sau:

  1. Duyệt qua mỗi điểm của con đường.
  2. Tìm khoảng cách giữa điểm hiện tại trong lần lặp tới điểm tiếp theo.
  3. Nếu khoảng cách ở điểm 2 lớn hơn khoảng cách chúng ta cần di chuyển trên đường dẫn:

    ... thì điểm đến nằm giữa điểm này và điểm tiếp theo. Chỉ cần áp dụng phương thức moveTowards() cho điểm hiện tại, đi qua điểm tiếp theo và khoảng cách để di chuyển. Trả về kết quả và ngắt lặp lại.

    Khác:

    ...điểm đích là tiếp tục trong đường dẫn từ điểm tiếp theo trong lần lặp lại. Chúng ta cần trừ khoảng cách giữa điểm này và điểm tiếp theo từ tổng khoảng cách để di chuyển dọc theo đường dẫn. Tiếp tục thông qua việc lặp lại với khoảng cách được sửa đổi.

Bạn có thể nhận thấy rằng chúng tôi có thể dễ dàng triển khai thực hiện theo cách đệ quy, thay vì lặp lại. Vì vậy, hãy làm điều đó:

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; 
    } 
} 

Với phương pháp trên, nếu chúng ta định nghĩa một mảng của GLatLng điểm, và chúng tôi gọi chức năng moveAlongPath() của chúng tôi với mảng này của điểm và với khoảng cách 2.500 mét, nó sẽ trả về một GLatLng trên con đường đó cách điểm đầu tiên 2,5km.

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. 

Do đó tất cả những gì chúng tôi cần làm là gọi moveAlongPath() cho mỗi điểm kiểm tra chúng tôi cần trên đường dẫn. Nếu bạn cần ba dấu tại 1km, 5km và 10km, bạn chỉ có thể làm:

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

Lưu ý tuy nhiên đó moveAlongPath() có thể trở lại null nếu chúng tôi yêu cầu một điểm kiểm tra thêm từ tổng chiều dài của con đường, vì vậy nó sẽ được khôn ngoan hơn để kiểm tra giá trị trả về trước khi chuyển nó đến new GMarker().

Chúng tôi có thể cùng nhau thực hiện việc này. Trong ví dụ này, chúng tôi đang thả một điểm đánh dấu mỗi 1.000 mét dọc theo con đường 8.8km được xác định trước đó:

<!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> 

Ảnh chụp màn hình của ví dụ trên, cho thấy một dấu hiệu mỗi 1.000 mét:

Google Maps - Move Point Along a Path

+0

Tôi đang sử dụng Google Map Api V3, công thức của bạn có vẻ là tốt, nhưng khi tôi phóng to đến mức đường, tôi có thể thấy khoảng cách giữa đường kẻ được vẽ bởi google và điểm đánh dấu của tôi. Có lý do gì không? – Nordes

+0

@Nordes: Điều này có xảy ra với ví dụ trên không? Tôi đã cố gắng phóng to đến mức thu phóng tối đa và các điểm đánh dấu xuất hiện trên đường. Ảnh chụp màn hình: http://img408.imageshack.us/img408/8687/gmapnospace.png –

+0

Tôi sẽ thử với tất cả các mã của bạn. Trên thực tế, tôi chỉ sử dụng công thức "haversine" mà bạn đã tạo trong JS. Có lẽ tôi đã thực hiện một tính toán sai lầm ở đâu đó. Tôi sẽ lấy lại cho bạn khi tôi thử với mã của bạn. – Nordes

3

Có thể cách tiếp cận tốt nhất là tính toán các điểm này ở đâu.

Là thuật toán cơ bản bạn có thể lặp qua tất cả các điểm trong Polyline và tính toán khoảng cách tích lũy - nếu phân đoạn tiếp theo đặt bạn vượt quá khoảng cách, bạn có thể nội suy điểm mà khoảng cách đã đạt được - sau đó chỉ cần thêm một điểm quan tâm đến bản đồ của bạn cho điều đó.

+0

Yeah, mà nên là hoàn toàn khả thi - Tôi chỉ hy vọng có được một số loại cách lén lút để làm cho các API để làm điều đó :) – mikl

+0

@mikl Tôi có thể là một masochist nói điều này, nhưng tôi nghĩ nó thú vị hơn để giải quyết các giải pháp như thế này, nơi không có phương pháp API rõ ràng –

3

Tôi đã tìm ra lý do tại sao tôi có sự thiếu chính xác . Trên thực tế trong V3 của GMap, chúng tôi không có chức năng "getLength" nữa để trả về độ dài trong Km hoặc Mét của polyLine.

đây là nguyên mẫu cho chức năng yêu cầu - hy vọng điều này sẽ giúp thêm nữa:

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

tôi muốn cổng Daniel Vassalo's answer lên iOS, nhưng nó đã không làm việc đúng cách và một số dấu bị thất lạc cho đến khi tôi thay đổi

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

đến

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

Vì vậy, nếu bất kỳ ai gặp khó khăn để tìm ra lý do tại sao các điểm đánh dấu bị thất lạc, hãy thử điều này và có thể nó sẽ giúp ích.

1

Tôi đã sử dụng phương pháp Martin Zeitler để làm việc với Google Map V3 và hoạt động tốt.

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; 
    } 
Các vấn đề liên quan