Cálculo de la distancia entre dos coordenadas geográficas de latitud y longitud

139

Estoy calculando la distancia entre dos GeoCoordinates. Estoy probando mi aplicación contra otras 3-4 aplicaciones. Cuando calculo la distancia, tiendo a obtener un promedio de 3.3 millas para mi cálculo, mientras que otras aplicaciones obtienen 3.5 millas. Es una gran diferencia para el cálculo que estoy tratando de realizar. ¿Hay alguna buena biblioteca de clases para calcular la distancia? Lo estoy calculando así en 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;
}

¿Qué podría estar haciendo mal? ¿Debería calcularlo primero en km y luego convertirlo en millas?

Jason N. Gaylord
fuente
1
Radio medio de la Tierra = 6,371 km = 3958.76 millas
Mitch Wheat
¿No debería estar esto en gis.stackexchange.com
Daniel Powell
Podría haberlo hecho, pero mi pregunta se refiere más al cálculo de esto en un Windows Phone que es un poco diferente. La fórmula es la misma, pero las llamadas a métodos más nuevos como el método DistanceTo no están necesariamente disponibles.
Jason N. Gaylord
1
Sugiero que almacene pi / 180 para que no tenga que seguir repitiendo el cálculo.
Chris Caviness

Respuestas:

313

La clase GeoCoordinate (.NET Framework 4 y superior) ya tiene GetDistanceTométodo.

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

return sCoord.GetDistanceTo(eCoord);

La distancia es en metros.

Debe hacer referencia a System.Device.

Nigel Sampson
fuente
Nigel, ¿estás seguro de que el método DistanceTo funcionará en el teléfono? Pensé que usaba la versión 2.0 de GeoCoordinate para WP7.
Jason N. Gaylord
1
Verifiqué esto y el GeoCordinate para el dispositivo tiene un método GetDistanceTo que es lo que usted ha mencionado (pero no lo que tiene arriba). No es gran cosa. Voy a probar esto para ver si el cálculo integrado es mejor. Gracias Nigel!
Jason N. Gaylord
1
Podría hacer una pregunta incorrecta, pero ¿en qué unidad está el resultado? ¿Son millas o kilómetros? No puedo encontrarlo por ninguna parte.
Saeed Neamati
3
@SaeedNeamati - también estaba buscando esto, de acuerdo con msdn.microsoft.com/en-us/library/… - está en metros.
Andy Butland
Sí, GeoCoordinate.GetDistanceTo () devuelve el valor en metros. Para mí, en EE. UU., Si es inferior a 1610, lo convierto a pies (metros * 3.28084); de lo contrario, lo convierto a millas (metros * 0.000621371). La precisión es más que suficiente para mis propósitos.
user3235770
110

GetDistance es la mejor solución , pero en muchos casos no podemos usar este método (por ejemplo, aplicación universal)

  • Pseudocódigo del algoritmo para calcular la distancia entre los coorindatos:

    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;
    }
  • Real World C # Implementation , que utiliza métodos de extensión

    Uso:

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

    Implementación:

    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
fuente
1
¿Puede proporcionar la fórmula utilizada para este cálculo o tal vez algunos comentarios sobre qué línea hace? ¿Qué tendría que cambiar para tener directamente la distancia resultante en km en lugar de millas sin tener que convertir?
AlbertoFdzM
Gracias por una buena solución, ahora puedo usarla en mi aplicación de escritorio.
Jamshaid Kamran
Funcionó muy bien en mi aplicación para UWP donde no puedo usar GeoCoordinate.
Zach Green
1
el cálculo es 95% verdadero. la siguiente función es 100% precisa: stackoverflow.com/a/51839058/3736063
Malek Tubaisaht
31

Y aquí, para aquellos que aún no están satisfechos, el código original de la GeoCoordinateclase .NET-Frameworks , refactorizado en un método independiente:

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)));
}
Bagazo
fuente
8
Hermosa respuesta, me gustaría señalar que la distancia resultante es en metros. como se indica en la documentación oficial
LeviathanCode
¡Gracias! Estaba buscando el radio real de la tierra utilizado en la clase GeoCoordinate.
KRoy
Una optimización menor, o para una lectura más fácil, ¿podría precalcular pi / 180 double oneDegree = Math.PI / 180.0;?
brakeroo
1
@brakeroo Gracias por su respuesta. Me gustaría dejar la respuesta tal como está, porque este es el código original de .NET. Sin embargo, cualquiera puede seguir su sugerencia, por supuesto.
Marc
17

Aquí está la versión de JavaScript chicos y chicas

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
fuente
10

Para aquellos que usan Xamarin y no tienen acceso a la clase GeoCoordinate, puede usar la clase de Ubicación de Android en su lugar:

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
fuente
3

Puedes usar esta función:

Fuente: 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
fuente
¡Funciona perfectamente! ¡Gracias!
Schnapz
3

Existe esta biblioteca GeoCoordinate para estas plataformas:

  • Mononucleosis infecciosa
  • .NET 4.5
  • .NET Core
  • Windows Phone 8.x
  • Plataforma universal de Windows
  • Xamarin iOS
  • Xamarin Android

La instalación se realiza a través de NuGet:

PM> Install-Package GeoCoordinate

Uso

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

double distanceBetween = pin1.GetDistanceTo(pin2);

La distancia entre las dos coordenadas, en metros .

A. Morel
fuente
3

Basado en la función de Elliot Wood, y si alguien está interesado en una función C, esta está funcionando ...

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

Puedes cambiarlo al doble . Devuelve el valor en km.

Santiago Villafuerte
fuente
2

Cálculo de la distancia entre los puntos de latitud y longitud ...

        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;
Sharma Manish
fuente
1

Esta es una vieja pregunta, sin embargo, las respuestas no me satisfacen con respecto al rendimiento y la optimización.

Aquí mi variante C # optimizada (distancia en km, sin variables y cálculos redundantes, muy cerca de la expresión matemática de Haversine Formular https://en.wikipedia.org/wiki/Haversine_formula ).

Inspirado por: 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 de Wikipedia

JanW
fuente
0

Prueba esto:

    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
fuente
0

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

buena suerte

Danilo Garro
fuente
0

Cuando la potencia de cálculo de CPU / matemáticas es limitada:

Hay momentos (como en mi trabajo) en que la potencia de cálculo es escasa (por ejemplo, sin procesador de punto flotante, trabajando con microcontroladores pequeños) donde algunas funciones trigonométricas pueden tomar una cantidad exorbitante de tiempo de CPU (por ejemplo, más de 3000 ciclos de reloj), por lo que cuando solo necesito una aproximación, especialmente si la CPU no debe estar atada por mucho tiempo, la uso para minimizar la sobrecarga de la CPU:

/**------------------------------------------------------------------------
 * \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. */
}

El crédito va a https://github.com/kristianmandrup/geo_vectors/blob/master/Distance%20calc%20notes.txt .

V. Wheeler
fuente