Расчет расстояния между двумя географическими координатами широты и долготы

144

Я рассчитываю расстояние между двумя географическими координатами. Я тестирую свое приложение против 3-4 других приложений. Когда я рассчитываю расстояние, я обычно получаю в среднем 3,3 мили для своих расчетов, тогда как другие приложения получают 3,5 мили. Это большая разница для вычислений, которые я пытаюсь выполнить. Есть ли какие-нибудь хорошие библиотеки классов для расчета расстояния? Я рассчитываю это так на 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;
}

Что я делаю не так? Следует ли мне сначала рассчитать его в км, а затем преобразовать в мили?

Джейсон Н. Гейлорд
источник
1
Средний радиус Земли = 6371 км = 3958,76 миль
Митч Уит
Разве это не должно быть на gis.stackexchange.com
Дэниел Пауэлл
Это могло бы быть, но мой вопрос больше касается расчета этого на Windows Phone, который немного отличается. Формула та же, но вызовы новых методов, таких как метод DistanceTo, не обязательно доступны.
Джейсон Н. Гейлорд
1
Предлагаем вам сохранить число pi / 180, чтобы не повторять вычисления.
Chris Caviness

Ответы:

321

У класса GeoCoordinate (.NET Framework 4 и выше) уже есть GetDistanceToметод.

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

return sCoord.GetDistanceTo(eCoord);

Расстояние в метрах.

Вам нужно сослаться на System.Device.

Найджел Сэмпсон
источник
Найджел, вы уверены, что метод DistanceTo будет работать на телефоне? Я думал, что он использует версию 2.0 GeoCoordinate для WP7.
Джейсон Н. Гейлорд
1
Я проверил это, и в GeoCordinate для устройства есть метод GetDistanceTo, на который вы ссылались (но не на то, что у вас есть выше). Ничего страшного. Я собираюсь проверить это, чтобы увидеть, лучше ли встроенный расчет. Спасибо, Найджел!
Джейсон Н. Гейлорд
1
Я могу задать неправильный вопрос, но в какой единице результат? Это мили или километры. Нигде не могу найти.
Саид Нямати,
3
@SaeedNeamati - это тоже искал, согласно msdn.microsoft.com/en-us/library/… - это в метрах.
Энди Батленд,
Да, GeoCoordinate.GetDistanceTo () возвращает значение в метрах. Для меня в США, если оно меньше 1610, я конвертирую его в футы (метры * 3,28084), иначе я конвертирую в мили (метры * 0,000621371). Точности более чем достаточно для моих целей.
buzzard51
115

GetDistance - лучшее решение , но во многих случаях мы не можем использовать этот метод (например, универсальное приложение)

  • Псевдокод алгоритма вычисления расстояния между координатами:

    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;
    }
    
  • Реальная реализация C # , в которой используются методы расширения

    Применение:

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

    Реализация:

    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;
        }
    } 
    
Дэвид Лейтнер
источник
1
Можете ли вы предоставить формулу, используемую для этого исчисления, или, возможно, некоторые комментарии о том, что делает линия? что мне нужно изменить, чтобы получить итоговое расстояние в км, а не в милях без преобразования?
AlbertoFdzM 08
Спасибо за хорошее решение, теперь я могу использовать его в своем приложении Desktop.
Джамшайд Камран 02
Отлично работал в моем приложении UWP, где я не могу использовать GeoCoordinate.
Зак Грин
1
расчет верен на 95%. приведенная ниже функция точна на 100%: stackoverflow.com/a/51839058/3736063
Malek Tubaisaht
44

И вот, для тех, кто все еще не удовлетворен, исходный код из GeoCoordinateкласса .NET-Frameworks , преобразованный в автономный метод:

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)));
}
Марк
источник
10
Прекрасный ответ, хочу отметить, что в результате получается расстояние в метрах. как указано в официальной документации
LeviathanCode
Благодарность! Я искал фактический радиус земли, используемый в классе GeoCoordinate.
KRoy
Небольшая оптимизация или для облегчения чтения могла бы предварительно вычислить pi / 180 double oneDegree = Math.PI / 180.0;?
brakeroo
1
@brakeroo Спасибо за ответ. Я бы хотел оставить ответ как есть, потому что это исходный код .NET. Конечно, любой может свободно последовать твоему предложению.
Марк
17

Вот версия JavaScript, ребята и девчонки

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
}
Эллиот Вуд
источник
10

Для тех, кто использует Xamarin и не имеет доступа к классу GeoCoordinate, вы можете вместо этого использовать класс 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);
        }
Джастин
источник
3

Вы можете использовать эту функцию:

Источник: 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"));
Янга
источник
Прекрасно работает! Благодарность!
Schnapz
3

Для этих платформ есть библиотека GeoCoordinate :

  • Мононуклеоз
  • .NET 4.5
  • .NET Core
  • Windows Phone 8.x
  • Универсальная платформа Windows
  • Xamarin iOS
  • Xamarin Android

Установка выполняется через NuGet:

PM> Установить пакет GeoCoordinate

Применение

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

double distanceBetween = pin1.GetDistanceTo(pin2);

Расстояние между двумя координатами в метрах .

А. Морель
источник
3

На основе функции Эллиота Вуда, и если кого-то интересует функция C, эта работает ...

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

Вы можете изменить его на двойной . Возвращает значение в км.

Сантьяго Вильяфуэрте
источник
2

Расчет расстояния между точками широты и долготы ...

        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;
Маниш шарма
источник
1

Это старый вопрос, тем не менее, ответы меня не удовлетворили с точки зрения производительности и оптимизации.

Вот мой оптимизированный вариант C # (расстояние в км, без переменных и избыточных вычислений, очень близко к математическому выражению Haversine Formular https://en.wikipedia.org/wiki/Haversine_formula ).

На основе: 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
    }
}

Формула Хаверсина из Википедии

Янв
источник
0

Попробуй это:

    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);
    }
Хамзе Собо
источник
0

Вы можете использовать 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);

удачи

Данило Гарро
источник
0

Когда вычислительная мощность ЦП / математики ограничена:

Бывают случаи (например, в моей работе), когда вычислительная мощность недостаточна (например, нет процессора с плавающей запятой, работа с небольшими микроконтроллерами), когда некоторые триггерные функции могут занимать непомерное количество процессорного времени (например, 3000+ тактовых циклов), поэтому, когда я требуется только приближение, особенно если ЦП не должен быть связан в течение длительного времени, я использую это, чтобы минимизировать накладные расходы ЦП:

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

Кредит идет на https://github.com/kristianmandrup/geo_vectors/blob/master/Distance%20calc%20notes.txt .

В. Уиллер
источник