Cálculo da distância entre duas coordenadas geográficas de latitude e longitude

139

Estou calculando a distância entre dois GeoCoordinados. Estou testando meu aplicativo contra 3-4 outros aplicativos. Ao calcular a distância, tenho uma média de 5,3 milhas em meu cálculo, enquanto outros aplicativos estão em 3,5 milhas. É uma grande diferença para o cálculo que estou tentando executar. Existem boas bibliotecas de classes disponíveis para calcular a distância? Estou calculando assim em 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;
}

O que eu poderia estar fazendo de errado? Devo calculá-lo primeiro em km e depois converter em milhas?

Jason N. Gaylord
fonte
1
Terra raio médio = 6,371km = 3958.76 milhas
Mitch Trigo
Não deveria ser sobre gis.stackexchange.com
Daniel Powell
Poderia ter, mas minha pergunta diz respeito mais a calcular isso em um Windows Phone, que é um pouco diferente. A fórmula é a mesma, mas chamadas de método mais recentes, como o método DistanceTo, não estão necessariamente disponíveis.
Jason N. Gaylord
1
Sugira que você armazene pi / 180 para não repetir o cálculo.
22417 Chris Caviness

Respostas:

313

A classe GeoCoordinate (.NET Framework 4 e superior) já possui GetDistanceTométodo.

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

return sCoord.GetDistanceTo(eCoord);

A distância está em metros.

Você precisa fazer referência ao System.Device.

Nigel Sampson
fonte
Nigel, você tem certeza de que o método DistanceTo funcionará no telefone? Eu pensei que ele usava a versão 2.0 do GeoCoordinate for WP7.
Jason N. Gaylord
1
Eu verifiquei isso e o GeoCordinate para o dispositivo possui um método GetDistanceTo, que é o que você referenciou (mas não o que você tem acima). Nada demais. Vou testar isso para ver se o cálculo embutido é melhor. Obrigado Nigel!
Jason N. Gaylord
1
Eu posso fazer uma pergunta errada, mas em qual unidade é o resultado? São milhas ou quilômetros. Não consigo encontrar em lugar nenhum.
Saeed Neamati
3
@SaeedNeamati - estava procurando isso também, de acordo com msdn.microsoft.com/en-us/library/… - está em metros.
Andy Butland 24/03
Sim, GeoCoordinate.GetDistanceTo () retorna o valor em metros. Para mim, nos EUA, se for menor que 1610, eu o converto para pés (metros * 3,28084), caso contrário, eu converto para milhas (metros * 0,000621371). A precisão é mais do que suficiente para meus propósitos.
user3235770
110

GetDistance é a melhor solução , mas em muitos casos não podemos usar esse método (por exemplo, Universal App)

  • Pseudocódigo do algoritmo para calcular a distância entre to coorindates:

    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;
    }
  • Implementação de C # do mundo real , que utiliza métodos de extensão

    Uso:

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

    Implementação:

    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
fonte
1
Você pode fornecer a fórmula usada para este cálculo ou talvez alguns comentários sobre qual linha faz? o que eu precisaria mudar para ter diretamente a distância resultante em km em vez de milhas sem ter que converter?
AlbertoFdzM 8/0118
Obrigado por uma boa solução, agora posso usá-lo no meu aplicativo Desktop.
Jamshaid Kamran /
Funcionou muito bem no meu aplicativo UWP, onde não posso usar o GeoCoordinate.
Zach Green
1
o cálculo é 95% verdadeiro. a função abaixo é 100% precisa: stackoverflow.com/a/51839058/3736063
Malek Tubaisaht
31

E aqui, para aqueles que ainda não estão satisfeitos, o código original da GeoCoordinateclasse .NET-Frameworks , refatorado para um método independente:

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
fonte
8
Resposta bonita, gostaria de salientar que a distância resultante está em metros. como indicado na documentação oficial
LeviathanCode
Obrigado! Eu estava procurando o raio real da terra usado na classe GeoCoordinate.
KRoy 03/04
Otimização menor, ou para facilitar a leitura, poderia pré-calcular o pi / 180 double oneDegree = Math.PI / 180.0;?
brakeroo 26/04
1
Obrigado por sua resposta. Gostaria de deixar a resposta como está, porque esse é o código .NET original. Qualquer um pode se sentir livre para seguir sua sugestão, é claro.
Marc
17

Aqui está a versão JavaScript rapazes e moças

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

Para aqueles que usam o Xamarin e não têm acesso à classe GeoCoordinate, você pode usar a classe Local do Android:

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

Você pode usar esta função:

Fonte: 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
fonte
Funciona perfeitamente! Obrigado!
Schnapz 31/01/19
3

Existe esta biblioteca GeoCoordinate para estas plataformas:

  • Mono
  • .NET 4.5
  • .NET Core
  • Windows Phone 8.x
  • Plataforma Universal do Windows
  • Xamarin iOS
  • Xamarin Android

A instalação é feita via NuGet:

PM> GeoCoordinate do pacote de instalação

Uso

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

double distanceBetween = pin1.GetDistanceTo(pin2);

A distância entre as duas coordenadas, em metros .

A. Morel
fonte
3

Baseado na função de Elliot Wood, e se alguém estiver interessado em uma função C, este 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);
}

Você pode alterá-lo para dobrar . Retorna o valor em km.

Santiago Villafuerte
fonte
2

Calcular distância entre pontos de latitude e longitude ...

        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
fonte
1

Esta é uma pergunta antiga, no entanto, as respostas não me satisfaziam em relação ao desempenho e otimização.

Aqui está minha variante C # otimizada (distância em km, sem variáveis ​​e cálculos redundantes, muito próxima da expressão matemática do Haversine Formular https://en.wikipedia.org/wiki/Haversine_formula ).

Inspirado em: 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 da Wikipedia

JanW
fonte
0

Tente o seguinte:

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

Você pode 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);

boa sorte

Danilo Garro
fonte
0

Quando o poder da computação da CPU / matemática é limitado:

Há momentos (como no meu trabalho) em que a capacidade de computação é escassa (por exemplo, nenhum processador de ponto flutuante, trabalhando com pequenos microcontroladores) em que algumas funções trigonométricas podem levar uma quantidade exorbitante de tempo de CPU (por exemplo, mais de 3000 ciclos de clock), então quando eu só precisa de uma aproximação, especialmente se se a CPU não deve ser amarrada por um longo tempo, eu uso isso para minimizar a sobrecarga da 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. */
}

O crédito vai para https://github.com/kristianmandrup/geo_vectors/blob/master/Distance%20calc%20notes.txt .

V. Wheeler
fonte