2010-04-14 3 views
41

Чтобы нарисовать круг на карте, у меня есть центр GLatLng (A) и радиус (r) в метрах.Как вычислить latlng точки на некотором расстоянии от другого?

Вот диаграмма:

  ----------- 
     --/   \-- 
     -/     \- 
    /     \ 
    /      \ 
/     r  \ 
    |   *-------------* 
    \    A   /B 
    \      /
    \     /
     -\     /- 
     --\   /-- 
      ----------- 

Как рассчитать GLatLng в положении B? Предположим, что r параллельна экватору.

Получение радиуса, когда даны A и B, тривиально, используя метод GLatLng.distanceFrom(), но делать это не так. Кажется, мне нужно сделать более тяжелую математику.

+0

@Rene : Адаптация моего ответа на API GMaps v2 должна быть простой. Я считаю, что это просто вопрос замены 'google.maps.LatLng' на' GLatLng'. Дайте мне знать, если вы найдете какие-либо трудности. –

+0

Спасибо, никаких трудностей здесь :) –

ответ

85

Нам понадобится метод, который возвращает точку назначения, когда данный подшипник и пройденное расстояние от исходной точки. К счастью, есть очень хорошая реализация JavaScript Крисом Винессом по адресу Calculate distance, bearing and more between Latitude/Longitude points.

Следующая была адаптирована для работы с google.maps.LatLng класса:

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

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

google.maps.LatLng.prototype.destinationPoint = function(brng, dist) { 
    dist = dist/6371; 
    brng = brng.toRad(); 

    var lat1 = this.lat().toRad(), lon1 = this.lng().toRad(); 

    var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) + 
         Math.cos(lat1) * Math.sin(dist) * Math.cos(brng)); 

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

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

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

Вы бы просто использовать его следующим образом:

var pointA = new google.maps.LatLng(25.48, -71.26); 
var radiusInKm = 10; 

var pointB = pointA.destinationPoint(90, radiusInKm); 

Вот полный пример использования Google Maps API v3:

<!DOCTYPE html> 
<html> 
<head> 
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/> 
    <title>Google Maps Geometry</title> 
    <script src="http://maps.google.com/maps/api/js?sensor=false" 
      type="text/javascript"></script> 
</head> 
<body> 
    <div id="map" style="width: 400px; 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; 
     } 

     google.maps.LatLng.prototype.destinationPoint = function(brng, dist) { 
     dist = dist/6371; 
     brng = brng.toRad(); 

     var lat1 = this.lat().toRad(), lon1 = this.lng().toRad(); 

     var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) + 
           Math.cos(lat1) * Math.sin(dist) * Math.cos(brng)); 

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

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

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

     var pointA = new google.maps.LatLng(40.70, -74.00); // Circle center 
     var radius = 10;          // 10km 

     var mapOpt = { 
     mapTypeId: google.maps.MapTypeId.TERRAIN, 
     center: pointA, 
     zoom: 10 
     }; 

     var map = new google.maps.Map(document.getElementById("map"), mapOpt); 

     // Draw the circle 
     new google.maps.Circle({ 
     center: pointA, 
     radius: radius * 1000,  // Convert to meters 
     fillColor: '#FF0000', 
     fillOpacity: 0.2, 
     map: map 
     }); 

     // Show marker at circle center 
     new google.maps.Marker({ 
     position: pointA, 
     map: map 
     }); 

     // Show marker at destination point 
     new google.maps.Marker({ 
     position: pointA.destinationPoint(90, radius), 
     map: map 
     }); 
    </script> 
</body> 
</html> 

Скриншот:

Google Maps Geometry

UPDATE:

В ответ на Paul's комментарий ниже, это то, что происходит, когда круг обтекает один из полюсов.

Plotting pointA вблизи северного полюса, с радиусом 1000 км:

var pointA = new google.maps.LatLng(85, 0); // Close to north pole 
    var radius = 1000;       // 1000km 

Скриншот для pointA.destinationPoint(90, radius):

Close to north pole

+1

Значит, это всегда дает пункт назначения, лежащий на востоке? – Nirmal

+4

@Nirmal: Нет, это зависит от первого параметра, который вы передаете 'destinationPoint()'. 90 - Восток, но вы можете использовать любой подшипник, начиная с 0 = Север, двигаясь по часовой стрелке. –

+0

Спасибо, это прекрасно. –

3

Ответ на этот вопрос и многое другое можно найти здесь: http://www.edwilliams.org/avform.htm

+0

Я вижу много acos, sin, tan и т. Д. На этой странице. Но я буду придерживаться ответа Даниила. Спасибо за помощь. –

+0

@Paul, спасибо за такую ​​приятную ссылку ... – aProgrammer

+0

Ссылка не работает. –

3

Если вы после расстояния между 2 LAT/LNG точек через поверхность земли, вы можете найти javascript здесь:

http://www.movable-type.co.uk/scripts/latlong-vincenty.html

Это та же формула используется в андроида API на android.location.Location::distanceTo

Вы можете легко преобразовать код из JavaScript в Java.

Если вы хотите, чтобы вычислить точку назначения, учитывая начальную точку, азимут и расстояние, то вам нужен этот метод:

http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html

Вот формулы в Java:

public class LatLngUtils { 

    /** 
    * @param lat1 
    *   Initial latitude 
    * @param lon1 
    *   Initial longitude 
    * @param lat2 
    *   destination latitude 
    * @param lon2 
    *   destination longitude 
    * @param results 
    *   To be populated with the distance, initial bearing and final 
    *   bearing 
    */ 

    public static void computeDistanceAndBearing(double lat1, double lon1, 
     double lat2, double lon2, double results[]) { 
    // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf 
    // using the "Inverse Formula" (section 4) 

    int MAXITERS = 20; 
    // Convert lat/long to radians 
    lat1 *= Math.PI/180.0; 
    lat2 *= Math.PI/180.0; 
    lon1 *= Math.PI/180.0; 
    lon2 *= Math.PI/180.0; 

    double a = 6378137.0; // WGS84 major axis 
    double b = 6356752.3142; // WGS84 semi-major axis 
    double f = (a - b)/a; 
    double aSqMinusBSqOverBSq = (a * a - b * b)/(b * b); 

    double L = lon2 - lon1; 
    double A = 0.0; 
    double U1 = Math.atan((1.0 - f) * Math.tan(lat1)); 
    double U2 = Math.atan((1.0 - f) * Math.tan(lat2)); 

    double cosU1 = Math.cos(U1); 
    double cosU2 = Math.cos(U2); 
    double sinU1 = Math.sin(U1); 
    double sinU2 = Math.sin(U2); 
    double cosU1cosU2 = cosU1 * cosU2; 
    double sinU1sinU2 = sinU1 * sinU2; 

    double sigma = 0.0; 
    double deltaSigma = 0.0; 
    double cosSqAlpha = 0.0; 
    double cos2SM = 0.0; 
    double cosSigma = 0.0; 
    double sinSigma = 0.0; 
    double cosLambda = 0.0; 
    double sinLambda = 0.0; 

    double lambda = L; // initial guess 
    for (int iter = 0; iter < MAXITERS; iter++) { 
     double lambdaOrig = lambda; 
     cosLambda = Math.cos(lambda); 
     sinLambda = Math.sin(lambda); 
     double t1 = cosU2 * sinLambda; 
     double t2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; 
     double sinSqSigma = t1 * t1 + t2 * t2; // (14) 
     sinSigma = Math.sqrt(sinSqSigma); 
     cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; // (15) 
     sigma = Math.atan2(sinSigma, cosSigma); // (16) 
     double sinAlpha = (sinSigma == 0) ? 0.0 : cosU1cosU2 * sinLambda 
     /sinSigma; // (17) 
     cosSqAlpha = 1.0 - sinAlpha * sinAlpha; 
     cos2SM = (cosSqAlpha == 0) ? 0.0 : cosSigma - 2.0 * sinU1sinU2 
     /cosSqAlpha; // (18) 

     double uSquared = cosSqAlpha * aSqMinusBSqOverBSq; // defn 
     A = 1 + (uSquared/16384.0) * // (3) 
      (4096.0 + uSquared * (-768 + uSquared * (320.0 - 175.0 * uSquared))); 
     double B = (uSquared/1024.0) * // (4) 
      (256.0 + uSquared * (-128.0 + uSquared * (74.0 - 47.0 * uSquared))); 
     double C = (f/16.0) * cosSqAlpha * (4.0 + f * (4.0 - 3.0 * cosSqAlpha)); // (10) 
     double cos2SMSq = cos2SM * cos2SM; 
     deltaSigma = B 
      * sinSigma 
      * // (6) 
      (cos2SM + (B/4.0) 
       * (cosSigma * (-1.0 + 2.0 * cos2SMSq) - (B/6.0) * cos2SM 
        * (-3.0 + 4.0 * sinSigma * sinSigma) 
        * (-3.0 + 4.0 * cos2SMSq))); 

     lambda = L 
      + (1.0 - C) 
      * f 
      * sinAlpha 
      * (sigma + C * sinSigma 
       * (cos2SM + C * cosSigma * (-1.0 + 2.0 * cos2SM * cos2SM))); // (11) 

     double delta = (lambda - lambdaOrig)/lambda; 
     if (Math.abs(delta) < 1.0e-12) { 
     break; 
     } 
    } 

    double distance = (b * A * (sigma - deltaSigma)); 
    results[0] = distance; 
    if (results.length > 1) { 
     double initialBearing = Math.atan2(cosU2 * sinLambda, cosU1 * sinU2 
      - sinU1 * cosU2 * cosLambda); 
     initialBearing *= 180.0/Math.PI; 
     results[1] = initialBearing; 
     if (results.length > 2) { 
     double finalBearing = Math.atan2(cosU1 * sinLambda, -sinU1 * cosU2 
      + cosU1 * sinU2 * cosLambda); 
     finalBearing *= 180.0/Math.PI; 
     results[2] = finalBearing; 
     } 
    } 
    } 

    /* 
    * Vincenty Direct Solution of Geodesics on the Ellipsoid (c) Chris Veness 
    * 2005-2012 
    * 
    * from: Vincenty direct formula - T Vincenty, "Direct and Inverse Solutions 
    * of Geodesics on the Ellipsoid with application of nested equations", Survey 
    * Review, vol XXII no 176, 1975 http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf 
    */ 

    /** 
    * Calculates destination point and final bearing given given start point, 
    * bearing & distance, using Vincenty inverse formula for ellipsoids 
    * 
    * @param lat1 
    *   start point latitude 
    * @param lon1 
    *   start point longitude 
    * @param brng 
    *   initial bearing in decimal degrees 
    * @param dist 
    *   distance along bearing in metres 
    * @returns an array of the desination point coordinates and the final bearing 
    */ 

    public static void computeDestinationAndBearing(double lat1, double lon1, 
     double brng, double dist, double results[]) { 
    double a = 6378137, b = 6356752.3142, f = 1/298.257223563; // WGS-84 
                   // ellipsiod 
    double s = dist; 
    double alpha1 = toRad(brng); 
    double sinAlpha1 = Math.sin(alpha1); 
    double cosAlpha1 = Math.cos(alpha1); 

    double tanU1 = (1 - f) * Math.tan(toRad(lat1)); 
    double cosU1 = 1/Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1; 
    double sigma1 = Math.atan2(tanU1, cosAlpha1); 
    double sinAlpha = cosU1 * sinAlpha1; 
    double cosSqAlpha = 1 - sinAlpha * sinAlpha; 
    double uSq = cosSqAlpha * (a * a - b * b)/(b * b); 
    double A = 1 + uSq/16384 
     * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 
    double B = uSq/1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 
    double sinSigma = 0, cosSigma = 0, deltaSigma = 0, cos2SigmaM = 0; 
    double sigma = s/(b * A), sigmaP = 2 * Math.PI; 

    while (Math.abs(sigma - sigmaP) > 1e-12) { 
     cos2SigmaM = Math.cos(2 * sigma1 + sigma); 
     sinSigma = Math.sin(sigma); 
     cosSigma = Math.cos(sigma); 
     deltaSigma = B 
      * sinSigma 
      * (cos2SigmaM + B 
      /4 
       * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B/6 
        * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) 
        * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 
     sigmaP = sigma; 
     sigma = s/(b * A) + deltaSigma; 
    } 

    double tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1; 
    double lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, 
     (1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp)); 
    double lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 
     * sinSigma * cosAlpha1); 
    double C = f/16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 
    double L = lambda 
     - (1 - C) 
     * f 
     * sinAlpha 
     * (sigma + C * sinSigma 
      * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM))); 
    double lon2 = (toRad(lon1) + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise 
                      // to 
                      // -180...+180 

    double revAz = Math.atan2(sinAlpha, -tmp); // final bearing, if required 

    results[0] = toDegrees(lat2); 
    results[1] = toDegrees(lon2); 
    results[2] = toDegrees(revAz); 

    } 

    private static double toRad(double angle) { 
    return angle * Math.PI/180; 
    } 

    private static double toDegrees(double radians) { 
    return radians * 180/Math.PI; 
    } 

} 
+0

Заметим, что формула Vincenty с точностью до 0,5 мм, или 0,000015 "(!), Используется на эллипсоиде. Расчеты, основанные на сферической модели, такие как (гораздо более простой) Хаверсин, точны примерно до 0,3%. Таким образом, предыдущее решение javascript - это то, что нужно большинству людей. –

2

Javascript для многих геодезических расчетов (прямые & обратные задачи, расчеты площадок и т. Д.). доступен по адресу

http://geographiclib.sourceforge.net/scripts/geographiclib.js

Пример использования показан в

http://geographiclib.sourceforge.net/scripts/geod-calc.html

интерфейс для Google Maps предоставляется на

http://geographiclib.sourceforge.net/scripts/geod-google.html

Это включает в себя построение геодезической (голубой), геодезический круг (зеленый) и геодезическая оболочка (красный).

sample geodesic starting in New Zealand and wrapping 1.5 times round the world

0

Для расчета лат, длинная точки в заданном азимуте и расстоянии от другого, вы можете использовать google's JavaScript реализации:

var pointA = new google.maps.LatLng(25.48, -71.26); 
var distance = 10; // 10 metres 
var bearing 90; // 90 degrees 
var pointB = google.maps.geometry.spherical.computeOffset(pointA, distance, bearing); 

См https://developers.google.com/maps/documentation/javascript/reference#spherical Для документации

Смежные вопросы