Obliczanie odległości między dwoma współrzędnymi geograficznymi szerokości i długości geograficznej

139

Obliczam odległość między dwoma GeoCoordinates. Testuję moją aplikację pod kątem 3-4 innych aplikacji. Kiedy obliczam odległość, zwykle otrzymuję średnio 3,3 mili do moich obliczeń, podczas gdy inne aplikacje otrzymują 3,5 mili. To duża różnica w obliczeniach, które próbuję wykonać. Czy są jakieś dobre biblioteki klas do obliczania odległości? Obliczam to tak w C #:

public static double Calculate(double sLatitude,double sLongitude, double eLatitude, 
                               double eLongitude)
{
    var radiansOverDegrees = (Math.PI / 180.0);

    var sLatitudeRadians = sLatitude * radiansOverDegrees;
    var sLongitudeRadians = sLongitude * radiansOverDegrees;
    var eLatitudeRadians = eLatitude * radiansOverDegrees;
    var eLongitudeRadians = eLongitude * radiansOverDegrees;

    var dLongitude = eLongitudeRadians - sLongitudeRadians;
    var dLatitude = eLatitudeRadians - sLatitudeRadians;

    var result1 = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) + 
                  Math.Cos(sLatitudeRadians) * Math.Cos(eLatitudeRadians) * 
                  Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

    // Using 3956 as the number of miles around the earth
    var result2 = 3956.0 * 2.0 * 
                  Math.Atan2(Math.Sqrt(result1), Math.Sqrt(1.0 - result1));

    return result2;
}

Co mogłem zrobić źle? Czy powinienem najpierw obliczyć to w km, a następnie przeliczyć na mile?

Jason N. Gaylord
źródło
1
Średni promień Ziemi = 6,371 km = 3958,76 mil
Mitch Wheat
czy to nie powinno być na gis.stackexchange.com
Daniel Powell
Mógłby, ale moje pytanie dotyczy bardziej obliczenia tego na Windows Phone, który jest nieco inny. Formuła jest taka sama, ale nowsze wywołania metod, takie jak metoda DistanceTo, niekoniecznie są dostępne.
Jason N. Gaylord
1
Zaproponuj przechowywanie pi / 180, abyś nie musiał powtarzać obliczeń.
Chris Caviness,

Odpowiedzi:

313

GeoCoordinate klasa (.NET Framework 4 i wyższe) ma już GetDistanceTometody.

var sCoord = new GeoCoordinate(sLatitude, sLongitude);
var eCoord = new GeoCoordinate(eLatitude, eLongitude);

return sCoord.GetDistanceTo(eCoord);

Odległość jest podana w metrach.

Musisz odwołać się do System.Device.

Nigel Sampson
źródło
Nigel, jesteś pewien, że metoda DistanceTo zadziała na telefonie? Myślałem, że używa wersji 2.0 GeoCoordinate dla WP7.
Jason N. Gaylord
1
Sprawdziłem to, a GeoCordinate dla urządzenia ma metodę GetDistanceTo, do której się odwoływałeś (ale nie to, co masz powyżej). Nie ma sprawy. Mam zamiar to przetestować, aby sprawdzić, czy wbudowane obliczenia są lepsze. Dzięki, Nigel!
Jason N. Gaylord
1
Mogę zadać złe pytanie, ale w jakiej jednostce jest wynik? Czy to mile, czy kilometry. Nie mogę go nigdzie znaleźć.
Saeed Neamati
3
@SaeedNeamati - też tego szukał, według msdn.microsoft.com/en-us/library/ ... - to jest w metrach.
Andy Butland
Tak, GeoCoordinate.GetDistanceTo () zwraca wartość w metrach. Dla mnie w USA, jeśli jest mniejszy niż 1610, przeliczam go na stopy (metry * 3,28084), w przeciwnym razie przeliczam na mile (metry * 0,000621371). Dokładność jest więcej niż wystarczająca do moich celów.
user3235770
110

GetDistance to najlepsze rozwiązanie , ale w wielu przypadkach nie możemy skorzystać z tej metody (np. Universal App)

  • Pseudokod Algorytmu do obliczania odległości między współrzędnymi:

    public static double DistanceTo(double lat1, double lon1, double lat2, double lon2, char unit = 'K')
    {
        double rlat1 = Math.PI*lat1/180;
        double rlat2 = Math.PI*lat2/180;
        double theta = lon1 - lon2;
        double rtheta = Math.PI*theta/180;
        double dist =
            Math.Sin(rlat1)*Math.Sin(rlat2) + Math.Cos(rlat1)*
            Math.Cos(rlat2)*Math.Cos(rtheta);
        dist = Math.Acos(dist);
        dist = dist*180/Math.PI;
        dist = dist*60*1.1515;
    
        switch (unit)
        {
            case 'K': //Kilometers -> default
                return dist*1.609344;
            case 'N': //Nautical Miles 
                return dist*0.8684;
            case 'M': //Miles
                return dist;
        }
    
        return dist;
    }
  • Implementacja języka C # w świecie rzeczywistym , która korzysta z metod rozszerzających

    Stosowanie:

    var distance = new Coordinates(48.672309, 15.695585)
                    .DistanceTo(
                        new Coordinates(48.237867, 16.389477),
                        UnitOfLength.Kilometers
                    );

    Realizacja:

    public class Coordinates
    {
        public double Latitude { get; private set; }
        public double Longitude { get; private set; }
    
        public Coordinates(double latitude, double longitude)
        {
            Latitude = latitude;
            Longitude = longitude;
        }
    }
    public static class CoordinatesDistanceExtensions
    {
        public static double DistanceTo(this Coordinates baseCoordinates, Coordinates targetCoordinates)
        {
            return DistanceTo(baseCoordinates, targetCoordinates, UnitOfLength.Kilometers);
        }
    
        public static double DistanceTo(this Coordinates baseCoordinates, Coordinates targetCoordinates, UnitOfLength unitOfLength)
        {
            var baseRad = Math.PI * baseCoordinates.Latitude / 180;
            var targetRad = Math.PI * targetCoordinates.Latitude/ 180;
            var theta = baseCoordinates.Longitude - targetCoordinates.Longitude;
            var thetaRad = Math.PI * theta / 180;
    
            double dist =
                Math.Sin(baseRad) * Math.Sin(targetRad) + Math.Cos(baseRad) *
                Math.Cos(targetRad) * Math.Cos(thetaRad);
            dist = Math.Acos(dist);
    
            dist = dist * 180 / Math.PI;
            dist = dist * 60 * 1.1515;
    
            return unitOfLength.ConvertFromMiles(dist);
        }
    }
    
    public class UnitOfLength
    {
        public static UnitOfLength Kilometers = new UnitOfLength(1.609344);
        public static UnitOfLength NauticalMiles = new UnitOfLength(0.8684);
        public static UnitOfLength Miles = new UnitOfLength(1);
    
        private readonly double _fromMilesFactor;
    
        private UnitOfLength(double fromMilesFactor)
        {
            _fromMilesFactor = fromMilesFactor;
        }
    
        public double ConvertFromMiles(double input)
        {
            return input*_fromMilesFactor;
        }
    } 
David Leitner
źródło
1
Czy możesz podać wzór użyty do tego rachunku, a może jakieś uwagi na temat tego, co robi linia? co musiałbym zmienić, aby bezpośrednio otrzymać wynikową odległość w kilometrach zamiast w milach bez konieczności przeliczania?
AlbertoFdzM,
Dzięki za dobre rozwiązanie, mogę go teraz używać w mojej aplikacji Desktop.
Jamshaid Kamran
Działało świetnie w mojej aplikacji UWP, w której nie mogę używać GeoCoordinate.
Zach Green,
1
Obliczenie jest prawdziwe w 95%. poniższa funkcja jest w 100% dokładna: stackoverflow.com/a/51839058/3736063
Malek Tubaisaht
31

A tutaj, dla tych, którzy nadal nie są zadowoleni, oryginalny kod z GeoCoordinateklasy .NET-Frameworks , refaktoryzowany na samodzielną metodę:

public double GetDistance(double longitude, double latitude, double otherLongitude, double otherLatitude)
{
    var d1 = latitude * (Math.PI / 180.0);
    var num1 = longitude * (Math.PI / 180.0);
    var d2 = otherLatitude * (Math.PI / 180.0);
    var num2 = otherLongitude * (Math.PI / 180.0) - num1;
    var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0);

    return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3)));
}
Marc
źródło
8
Śliczna odpowiedź, chciałbym zaznaczyć, że wynikowa odległość jest w metrach. jak stwierdzono w oficjalnej dokumentacji
kod Leviathan,
Dzięki! Szukałem rzeczywistego promienia ziemi używanego w klasie GeoCoordinate.
KRoy
Niewielka optymalizacja lub dla łatwiejszego czytania, czy można wstępnie obliczyć pi / 180 double oneDegree = Math.PI / 180.0;?
brakeroo
1
@brakeroo Dzięki za odpowiedź. Chciałbym zostawić odpowiedź tak, jak jest, ponieważ jest to oryginalny kod .NET. Oczywiście każdy może skorzystać z Twojej sugestii.
Marc
17

Oto wersja JavaScript, chłopaki i dziewczęta

function distanceTo(lat1, lon1, lat2, lon2, unit) {
      var rlat1 = Math.PI * lat1/180
      var rlat2 = Math.PI * lat2/180
      var rlon1 = Math.PI * lon1/180
      var rlon2 = Math.PI * lon2/180
      var theta = lon1-lon2
      var rtheta = Math.PI * theta/180
      var dist = Math.sin(rlat1) * Math.sin(rlat2) + Math.cos(rlat1) * Math.cos(rlat2) * Math.cos(rtheta);
      dist = Math.acos(dist)
      dist = dist * 180/Math.PI
      dist = dist * 60 * 1.1515
      if (unit=="K") { dist = dist * 1.609344 }
      if (unit=="N") { dist = dist * 0.8684 }
      return dist
}
Elliot Wood
źródło
10

Dla tych, którzy używają platformy Xamarin i nie mają dostępu do klasy GeoCoordinate, można zamiast tego użyć klasy Android Location:

public static double GetDistanceBetweenCoordinates (double lat1, double lng1, double lat2, double lng2) {
            var coords1 = new Location ("");
            coords1.Latitude = lat1;
            coords1.Longitude = lng1;
            var coords2 = new Location ("");
            coords2.Latitude = lat2;
            coords2.Longitude = lng2;
            return coords1.DistanceTo (coords2);
        }
Justin
źródło
3

Możesz użyć tej funkcji:

Źródło: https://www.geodatasource.com/developers/c-sharp

private double distance(double lat1, double lon1, double lat2, double lon2, char unit) {
  if ((lat1 == lat2) && (lon1 == lon2)) {
    return 0;
  }
  else {
    double theta = lon1 - lon2;
    double dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta));
    dist = Math.Acos(dist);
    dist = rad2deg(dist);
    dist = dist * 60 * 1.1515;
    if (unit == 'K') {
      dist = dist * 1.609344;
    } else if (unit == 'N') {
      dist = dist * 0.8684;
    }
    return (dist);
  }
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::  This function converts decimal degrees to radians             :::
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
private double deg2rad(double deg) {
  return (deg * Math.PI / 180.0);
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::  This function converts radians to decimal degrees             :::
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
private double rad2deg(double rad) {
  return (rad / Math.PI * 180.0);
}

Console.WriteLine(distance(32.9697, -96.80322, 29.46786, -98.53506, "M"));
Console.WriteLine(distance(32.9697, -96.80322, 29.46786, -98.53506, "K"));
Console.WriteLine(distance(32.9697, -96.80322, 29.46786, -98.53506, "N"));
Yanga
źródło
Działa świetnie! Dzięki!
Schnapz
3

Istnieje biblioteka GeoCoordinate dla tych platform:

  • Mononukleoza
  • .NET 4.5
  • .NET Core
  • Windows Phone 8.x
  • Uniwersalna platforma Windows
  • Xamarin iOS
  • Xamarin Android

Instalacja odbywa się za pośrednictwem NuGet:

PM> Zainstaluj pakiet GeoCoordinate

Stosowanie

GeoCoordinate pin1 = new GeoCoordinate(lat, lng);
GeoCoordinate pin2 = new GeoCoordinate(lat, lng);

double distanceBetween = pin1.GetDistanceTo(pin2);

Odległość między dwoma współrzędnymi w metrach .

A. Morel
źródło
3

Oparty na funkcji Elliota Wooda i jeśli ktoś jest zainteresowany funkcją C, ta działa ...

#define SIM_Degree_to_Radian(x) ((float)x * 0.017453292F)
#define SIM_PI_VALUE                         (3.14159265359)

float GPS_Distance(float lat1, float lon1, float lat2, float lon2)
{
   float theta;
   float dist;

   theta = lon1 - lon2;

   lat1 = SIM_Degree_to_Radian(lat1);
   lat2 = SIM_Degree_to_Radian(lat2);
   theta = SIM_Degree_to_Radian(theta);

   dist = (sin(lat1) * sin(lat2)) + (cos(lat1) * cos(lat2) * cos(theta));
   dist = acos(dist);

//   dist = dist * 180.0 / SIM_PI_VALUE;
//   dist = dist * 60.0 * 1.1515;
//   /* Convert to km */
//   dist = dist * 1.609344;

   dist *= 6370.693486F;

   return (dist);
}

Możesz to zmienić na podwojenie . Zwraca wartość w km.

Santiago Villafuerte
źródło
2

Obliczanie odległości między punktami szerokości i długości geograficznej ...

        double Lat1 = Convert.ToDouble(latitude);
        double Long1 = Convert.ToDouble(longitude);

        double Lat2 = 30.678;
        double Long2 = 45.786;
        double circumference = 40000.0; // Earth's circumference at the equator in km
        double distance = 0.0;
        double latitude1Rad = DegreesToRadians(Lat1);
        double latititude2Rad = DegreesToRadians(Lat2);
        double longitude1Rad = DegreesToRadians(Long1);
        double longitude2Rad = DegreesToRadians(Long2);
        double logitudeDiff = Math.Abs(longitude1Rad - longitude2Rad);
        if (logitudeDiff > Math.PI)
        {
            logitudeDiff = 2.0 * Math.PI - logitudeDiff;
        }
        double angleCalculation =
            Math.Acos(
              Math.Sin(latititude2Rad) * Math.Sin(latitude1Rad) +
              Math.Cos(latititude2Rad) * Math.Cos(latitude1Rad) * Math.Cos(logitudeDiff));
        distance = circumference * angleCalculation / (2.0 * Math.PI);
        return distance;
Manish Sharma
źródło
1

To stare pytanie, niemniej odpowiedzi nie zadowalały mnie pod względem wydajności i optymalizacji.

Tutaj mój zoptymalizowany wariant C # (odległość w km, bez zmiennych i zbędnych obliczeń, bardzo zbliżony do matematycznego wyrażenia Haversine Formular https://en.wikipedia.org/wiki/Haversine_formula ).

Zainspirowany: https://rosettacode.org/wiki/Haversine_formula#C.23

public static class Haversine
{
    public static double Calculate(double lat1, double lon1, double lat2, double lon2)
    {
        double rad(double angle) => angle * 0.017453292519943295769236907684886127d; // = angle * Math.Pi / 180.0d
        double havf(double diff) => Math.Pow(Math.Sin(rad(diff) / 2d), 2); // = sin²(diff / 2)
        return 12745.6 * Math.Asin(Math.Sqrt(havf(lat2 - lat1) + Math.Cos(rad(lat1)) * Math.Cos(rad(lat2)) * havf(lon2 - lon1))); // earth radius 6.372,8‬km x 2 = 12745.6
    }
}

Haversine Formular z Wikipedii

JanW
źródło
0

Spróbuj tego:

    public double getDistance(GeoCoordinate p1, GeoCoordinate p2)
    {
        double d = p1.Latitude * 0.017453292519943295;
        double num3 = p1.Longitude * 0.017453292519943295;
        double num4 = p2.Latitude * 0.017453292519943295;
        double num5 = p2.Longitude * 0.017453292519943295;
        double num6 = num5 - num3;
        double num7 = num4 - d;
        double num8 = Math.Pow(Math.Sin(num7 / 2.0), 2.0) + ((Math.Cos(d) * Math.Cos(num4)) * Math.Pow(Math.Sin(num6 / 2.0), 2.0));
        double num9 = 2.0 * Math.Atan2(Math.Sqrt(num8), Math.Sqrt(1.0 - num8));
        return (6376500.0 * num9);
    }
Hamzeh Soboh
źródło
0

Możesz użyć System.device.Location:

System.device.Location.GeoCoordinate gc = new System.device.Location.GeoCoordinate(){
Latitude = yourLatitudePt1,
Longitude = yourLongitudePt1
};

System.device.Location.GeoCoordinate gc2 = new System.device.Location.GeoCoordinate(){
Latitude = yourLatitudePt2,
Longitude = yourLongitudePt2
};

Double distance = gc2.getDistanceTo(gc);

powodzenia

Danilo Garro
źródło
0

Gdy moc obliczeniowa procesora / matematyki jest ograniczona:

Są chwile (takie jak w mojej pracy), kiedy moc obliczeniowa jest ograniczona (np. Brak procesora zmiennoprzecinkowego, praca z małymi mikrokontrolerami), w których niektóre funkcje trygonometryczne mogą zająć wygórowaną ilość czasu procesora (np. 3000+ cykli zegara), więc kiedy ja potrzebuję tylko przybliżenia, zwłaszcza jeśli procesor nie może być związany przez długi czas, używam tego, aby zminimalizować obciążenie procesora:

/**------------------------------------------------------------------------
 * \brief  Great Circle distance approximation in km over short distances.
 *
 * Can be off by as much as 10%.
 *
 * approx_distance_in_mi = sqrt(x * x + y * y)
 *
 * where x = 69.1 * (lat2 - lat1)
 * and y = 69.1 * (lon2 - lon1) * cos(lat1/57.3)
 *//*----------------------------------------------------------------------*/
double    ApproximateDisatanceBetweenTwoLatLonsInKm(
                  double lat1, double lon1,
                  double lat2, double lon2
                  ) {
    double  ldRadians, ldCosR, x, y;

    ldRadians = (lat1 / 57.3) * 0.017453292519943295769236907684886;
    ldCosR = cos(ldRadians);
    x = 69.1 * (lat2 - lat1);
    y = 69.1 * (lon2 - lon1) * ldCosR;

    return sqrt(x * x + y * y) * 1.609344;  /* Converts mi to km. */
}

Kredyt trafia do https://github.com/kristianmandrup/geo_vectors/blob/master/Distance%20calc%20notes.txt .

V. Wheeler
źródło