web-dev-qa-db-fra.com

Comment trouver le lat/long qui est x km au nord d'un lat/long donné?

J'ai un code C # qui génère Google Maps. Ce code examine tous les points que j'ai besoin de tracer sur la carte, puis calcule les limites d'un rectangle pour inclure ces points. Il transmet ensuite ces limites à l'API Google Maps pour définir le niveau de zoom de manière à afficher tous les points de la carte.

Ce code fonctionne bien mais j'ai une nouvelle exigence.

L'un des points peut avoir une précision associée. Si tel est le cas, je trace un cercle autour du point avec le rayon défini sur la valeur de précision. Encore une fois, cela fonctionne bien, mais ma vérification des limites ne fait pas ce que je veux. Je veux avoir la boîte englobante inclure le cercle complet.

Cela nécessite un algorithme pour prendre un point x et calculer le point y qui serait z mètres au nord de x et aussi z mètres au sud de x.

Quelqu'un at-il cet algorithme, de préférence en C #? J'ai trouvé un algorithme générique ici mais il semble que je ne l'ai pas implémenté correctement car les réponses que j'obtiens sont à des milliers de kilomètres.

Ceci est l'exemple générique

Lat/lon given radial and distance

A point {lat,lon} is a distance d out on the tc radial from point 1 if:

     lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
     IF (cos(lat)=0)
        lon=lon1      // endpoint a pole
     ELSE
        lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi
     ENDIF

Et ceci est ma traduction en C #.

  // Extend a Point North/South by the specified distance
    public static Point ExtendPoint(Point _pt, int _distance, int _bearing )
    {
        Decimal lat = 0.0;
        Decimal lng = 0.0;

        lat = Math.Asin(Math.Sin(_pt.Lat) * Math.Cos(_distance) + Math.Cos(_pt.Lat) * 
            Math.Sin(_distance) * Math.Cos(_bearing));

         if (Math.Cos(lat) == 0)
         {
            lng = _pt.Lng;      // endpoint a pole
         }
         else 
         {
             lng = (
                 (_pt.Lng - Math.Asin(Math.Sin(_bearing) * Math.Sin(_distance) / Math.Cos(lat)) 
                 + Math.PI) % (2 * Math.PI)) - Math.PI;
         }

         ret = new Point(lat,lng);
         return ret;
    }

J'appelle cette fonction avec un relèvement de 0 pour calculer la nouvelle position nord et une valeur de 180 pour calculer la nouvelle position sud.

Quelqu'un peut-il voir ce que j'ai mal fait ou peut-être fournir un algorithme de travail connu?

26
Steve Weet

Si vous avez une latitude et une longitude données, vous pouvez calculer la latitude et la longitude correctes d’un changement de latitude de x km comme suit:

new-lat = ((old-km-north + x-km-change)/40,075) * 360)
           ^ is the ratio of the                  ^ times the ratio of the circle
           of the earth the change                by 360 to get the total ratio 
           covers.                                covered in degrees.

La même chose peut s'appliquer à la longitude. Si vous avez la distance totale plus le changement, vous pouvez calculer le nombre total de degrés de la même manière. 

new-long = ((old-km-east + x-km-change)/40,075) * 360)
           ^ is the ratio of the                  ^ times the ratio of the circle
           of the earth the change                by 360 to get the total ratio 
           covers.                                covered in degrees.

Encore une fois, ces calculs devraient fonctionner, mais je ne parle que d’intuition, mais la logique semble être vraie.

Comme le souligne Skizz, 40 075 doivent être adaptés à la circonférence de la Terre à une latitude donnée, à l’aide de 2.pi.r.cos (lat) ou 40074.cos (lat)

6
Sam152

J'ai un morceau de code très similaire. Cela m'a donné des résultats très proches par rapport à une autre implémentation. 

Je pense que le problème avec le vôtre est que vous utilisez "distance" comme distance linéaire en mètres au lieu de distance angulaire en radians.

/// <summary>
/// Calculates the end-point from a given source at a given range (meters) and bearing (degrees).
/// This methods uses simple geometry equations to calculate the end-point.
/// </summary>
/// <param name="source">Point of Origin</param>
/// <param name="range">Range in meters</param>
/// <param name="bearing">Bearing in degrees</param>
/// <returns>End-point from the source given the desired range and bearing.</returns>
public static LatLonAlt CalculateDerivedPosition(LatLonAlt source, double range, double bearing)
{
    double latA = source.Latitude * UnitConstants.DegreesToRadians;
    double lonA = source.Longitude * UnitConstants.DegreesToRadians;
    double angularDistance = range / GeospatialConstants.EarthRadius;
    double trueCourse = bearing * UnitConstants.DegreesToRadians;

    double lat = Math.Asin(
        Math.Sin(latA) * Math.Cos(angularDistance) + 
        Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse));

    double dlon = Math.Atan2(
        Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA), 
        Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat));

    double lon = ((lonA + dlon + Math.PI) % UnitConstants.TwoPi) - Math.PI;

    return new LatLonAlt(
        lat * UnitConstants.RadiansToDegrees, 
        lon * UnitConstants.RadiansToDegrees, 
        source.Altitude);
}

public const double EarthRadius = 6378137.0;   //  WGS-84 ellipsoid parameters

et LatLonAlt est en degrés/mètres (la conversion a lieu en interne). Ajustez au besoin.

Je suppose que vous pouvez comprendre quelle est la valeur de UnitConstants.DegreesToRadians :)

20
Erich Mirabal

Pour les paresseux, (comme moi;)) une solution de copier-coller, la version d'Erich Mirabal avec des modifications très mineures:

using System.Device.Location; // add reference to System.Device.dll
public static class GeoUtils
{
    /// <summary>
    /// Calculates the end-point from a given source at a given range (meters) and bearing (degrees).
    /// This methods uses simple geometry equations to calculate the end-point.
    /// </summary>
    /// <param name="source">Point of Origin</param>
    /// <param name="range">Range in meters</param>
    /// <param name="bearing">Bearing in degrees</param>
    /// <returns>End-point from the source given the desired range and bearing.</returns>
    public static GeoCoordinate CalculateDerivedPosition(this GeoCoordinate source, double range, double bearing)
    {
        var latA = source.Latitude * DegreesToRadians;
        var lonA = source.Longitude * DegreesToRadians;
        var angularDistance = range / EarthRadius;
        var trueCourse = bearing * DegreesToRadians;

        var lat = Math.Asin(
            Math.Sin(latA) * Math.Cos(angularDistance) +
            Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse));

        var dlon = Math.Atan2(
            Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA),
            Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat));

        var lon = ((lonA + dlon + Math.PI) % (Math.PI*2)) - Math.PI;

        return new GeoCoordinate(
            lat * RadiansToDegrees,
            lon * RadiansToDegrees,
            source.Altitude);
    }

    private const double DegreesToRadians = Math.PI/180.0;
    private const double RadiansToDegrees = 180.0/ Math.PI;
    private const double EarthRadius = 6378137.0;
}

Usage:

[TestClass]
public class CalculateDerivedPositionUnitTest
{
    [TestMethod]
    public void OneDegreeSquareAtEquator()
    {
        var center = new GeoCoordinate(0, 0);
        var radius = 111320;
        var southBound = center.CalculateDerivedPosition(radius, -180);
        var westBound = center.CalculateDerivedPosition(radius, -90);
        var eastBound = center.CalculateDerivedPosition(radius, 90);
        var northBound = center.CalculateDerivedPosition(radius, 0);

        Console.Write($"leftBottom: {southBound.Latitude} , {westBound.Longitude} rightTop: {northBound.Latitude} , {eastBound.Longitude}");
    }
}
9
Zar Shardan

Je ne sais pas si je manque quelque chose ici, mais je pense que la question pourrait être reformulée comme suit: "J'ai un point lat/lon, et je veux trouver le point x mètres nord et x mètres sud. "

Si c'est la question alors vous n'avez pas besoin de trouver une nouvelle longitude (ce qui simplifie les choses), vous avez juste besoin d'une nouvelle latitude. Un degré de latitude mesure environ 60 milles marins n'importe où sur Terre et un mille marin mesure 1 852 mètres. Donc, pour les nouvelles latitudes x mètres au nord et au sud:

north_lat = lat + x / (1852 * 60)
north_lat = min(north_lat, 90)

south_lat = lat - x / (1852 * 60)
south_lat = max(south_lat, -90)

Ce n’est pas tout à fait exact car la Terre n’est pas une sphère parfaite avec exactement 60 milles marins entre chaque degré de latitude. Cependant, les autres réponses supposent que les lignes de latitude sont équidistantes, alors je suppose que vous vous en fichez. Si vous êtes intéressé par le nombre d'erreurs que cela pourrait introduire, il existe un joli tableau sur Wikipedia qui indique "Distance de surface par 1 ° de changement de latitude" pour différentes latitudes à ce lien:

http://en.wikipedia.org/wiki/Latitude#Degree_length

7
ryan_s

Il y a des problèmes avec les deux équations sur le site plutôt impressionnant d'Ed William ... mais je ne les ai pas analysées pour voir pourquoi.

Une troisième équation que j'ai trouvée ici semble donner des résultats corrects.

Voici le cas de test en php ... la troisième équation est correcte, les deux premières donnent des valeurs très incorrectes pour la longitude.

<?php
            $lon1 = -108.553412; $lat1 = 35.467155; $linDistance = .5; $bearing = 170;
            $lon1 = deg2rad($lon1); $lat1 = deg2rad($lat1);
            $distance = $linDistance/6371;  // convert dist to angular distance in radians
            $bearing = deg2rad($bearing);

            echo "lon1: " . rad2deg($lon1) . " lat1: " . rad2deg($lat1) . "<br>\n";

// doesn't work
            $lat2 = asin(sin($lat1) * cos($distance) + cos($lat1) * sin($distance) * cos($bearing) );
            $dlon = atan2(sin($bearing) * sin($distance) * cos($lat1), cos($distance) - sin($lat1) * sin($lat2));
            $lon2 = (($lon1 - $dlon + M_PI) % (2 * M_PI)) - M_PI;  // normalise to -180...+180

            echo "lon2: " . rad2deg($lon2) . " lat2: " . rad2deg($lat2) . "<br>\n";

// same results as above
            $lat3 = asin( (sin($lat1) * cos($distance)) + (cos($lat1) * sin($distance) * cos($bearing)));
            $lon3 = (($lon1 - (asin(sin($bearing) * sin($distance) / cos($lat3))) + M_PI) % (2 * M_PI)) - M_PI;

            echo "lon3: " . rad2deg($lon3) . " lat3: " . rad2deg($lat3) . "<br>\n";

// gives correct answer... go figure
            $lat4 = asin(sin($lat1) * cos($linDistance/6371) + cos($lat1) * sin($linDistance/6371) * cos($bearing) );
            $lon4 = $lon1 + atan2( (sin($bearing) * sin($linDistance/6371) * cos($lat1) ), (cos($linDistance/6371) - sin($lat1) * sin($lat2)));

            echo "lon4: " . rad2deg($lon4) . " lat4: " . rad2deg($lat4) . "<br>\n";
?>

Note J'ai reçu par email de l'auteur (Ed Williams) des deux premières équations:

De mes "notes d'implémentation":

Note sur la fonction mod. Cela semble être mis en œuvre différemment dans Langues, avec des conventions différentes selon que le signe du résultat Suit le signe du diviseur ou du dividende. (Nous voulons que le signe Suive le diviseur ou soit euclidien. Le fmod de C et le% de Java ne fonctionnent pas.) Dans ce document, Mod (y, x) est le reste lorsque divisant y par x et toujours est compris dans la plage 0 <= mod <x. Par exemple: mod (2.3,2.) = 0,3 et Mod (-2,3,2.) = 1,7

Si vous avez une fonction floor (int dans Excel), cela retourne floor (x) = "Le plus grand entier inférieur ou égal à x" par ex. étage (-2.3) = - 3 et étage (2.3) = 2

mod(y,x) = y - x*floor(y/x)

Ce qui suit devrait fonctionner en l'absence d'une fonction d'étage, indépendamment de , Que "int" soit tronqué ou arrondi au bas:

mod=y - x * int(y/x)
if ( mod < 0) mod = mod + x

php est comme fmod en C et le fait "mal" pour mes besoins.

4
Sy Moen

Il est plus précis si vous le reprojetez d’abord sur UTM puis que vous vérifiez la distance.

J'espère que cela t'aides

0
Pablo Cabrera

Pour ce que ça vaut, j’ai un exemple dans PHP qui peut faire ce que demande l’opérateur. Dans mon exemple, il dessine un cadre autour d'une coordonnée de départ, mais le code peut facilement être utilisé pour obtenir un seul point, un nombre X de kilomètres ou de kilomètres.

http://www.richardpeacock.com/blog/2011/11/draw-box-around-coordinate-google-maps-based-miles-or-kilometers

0
Richard