Как найти lat/long, который находится на x км к северу от заданного лат/длинного

У меня есть код С#, который генерирует карты Google. Эти коды просматривают все Точки, которые мне нужны для построения на карте, а затем выстраивают границы прямоугольника, чтобы включить эти точки. Затем он передает эту оценку API Карт Google, чтобы установить уровень масштабирования соответствующим образом, чтобы показать все точки на карте.

Этот код работает нормально, но у меня есть новое требование.

Одна из точек может иметь связанную с ней точность. Если это так, тогда я рисую круг вокруг точки с радиусом, заданным для значения точности. Опять же, это прекрасно работает, однако моя проверка границ теперь не делает то, что я хочу, чтобы она делала. Я хочу, чтобы ограничивающая рамка включала полный круг.

Для этого требуется, чтобы алгоритм взял точку x и вычислил точку y, которая была бы z метров к северу от x, а также z метров к югу от x.

У кого-нибудь есть этот алгоритм, желательно на С#. Я нашел общий алгоритм здесь, но я, кажется, не реализовал это правильно, так как ответы, которые я получаю, являются 1000-м км по течению.

Это общий пример

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

И это мой перевод на С#.

  // 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;
    }

Я вызываю эту функцию с отношением 0, чтобы вычислить новое северное положение и значение 180 для вычисления нового южного положения.

Кто-нибудь может увидеть, что я сделал неправильно или, возможно, предоставил известный рабочий алгоритм.

Ответ 1

Если у вас заданная широта и долгота, вы можете рассчитать правильную широту и долготу изменения ширины x-километра так:

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.

То же самое можно применить и к долготе. Если у вас есть общее расстояние плюс изменение, вы можете рассчитать общие степени аналогичным образом.

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.

Опять же, эти вычисления должны работать, но здесь я убегаю от чистой интуиции, но логика, похоже, сохраняется.

Изменить: Как указано Skizz 40,075, необходимо отрегулировать по окружности земли на любой заданной широте, используя 2.pi.r.cos(lat) или 40074.cos(lat)

Ответ 2

У меня очень похожий код. Это принесло мне очень близкие результаты по сравнению с другой реализацией.

Я думаю, что проблема с вашей заключается в том, что вы используете "расстояние" как линейное расстояние в метрах вместо расстояния angular в радианах.

/// <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

и LatLonAlt находится в градусах/метрах (преобразование происходит внутри). При необходимости отрегулируйте.

Я предполагаю, что вы можете выяснить, что значение для UnitConstants.DegreesToRadians:)

Ответ 3

Для ленивых людей (как и я;)) решение для копирования-вставки, версия Erich Mirabal с очень незначительными изменениями:

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

Использование:

[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}");
    }
}

Ответ 4

Я не уверен, что здесь что-то не хватает, но я думаю, что этот вопрос можно перефразировать так: "У меня есть точка lat/lon, и я хочу найти точку x метров на север и x метров к югу от эта точка."

Если это вопрос, тогда вам не нужно искать новую долготу (что упрощает задачу), вам просто нужна новая широта. Степень широты составляет примерно 60 морских миль в длину на Земле, а морская миля - 1852 метра. Итак, для новых широт х метров север и юг:

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

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

Это не совсем точно, потому что Земля не является идеальной сферой с ровно 60 морских миль между каждой степенью широты. Однако другие ответы предполагают, что линии широты являются эквидистантными, поэтому я предполагаю, что вас это не волнует. Если вы заинтересованы в том, сколько ошибок может возникнуть, в Википедии есть хорошая таблица, показывающая "Расстояние по поверхности на 1 градус по широте" для разных широт по этой ссылке:

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

Ответ 5

Существуют проблемы с двумя уравнениями на Эд Уильям довольно удивительный сайт... но я не анализировал их, чтобы понять почему.

Третье уравнение, которое я нашел здесь, кажется, дает правильные результаты.

Вот тестовый пример в php... третье уравнение верно, первые два дают дико неверные значения для долготы.

<?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";
?>

Примечание. Я получил по электронной почте автора (Эд Уильямса) из первых двух уравнений:

Из моих "заметок об осуществлении":

Примечание о функции мод. Это, по-видимому, выполняется по-разному в на разных языках, с различными соглашениями о том, является ли знак результат следует за знаком дивизора или дивиденда. (Мы хотим знак следовать за делителем или быть евклидовым. C fmod и Java% не работают.) В этом документе Mod (y, x) является остатком при делении y на x и всегда лежит в диапазоне 0 <= mod < Икс. Например: mod (2,3,2.) = 0,3 и мод (-2.3,2.) = 1.7

Если у вас есть функция floor (int в Excel), которая возвращает пол (x) = "наибольшее целое число, меньшее или равное x", например. пол (-2,3) = - 3 и пол (2.3) = 2

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

Следующее должно работать при отсутствии функции пола - независимо от "int" усекает или округляет вниз:

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

php подобен fmod в C и делает это "неправильным" для моих целей.

Ответ 6

Более точно, если вы сначала перепрограммируете его на UTM, а затем проверьте расстояние.

Надеюсь, что это поможет

Ответ 7

Для чего это стоит, у меня есть пример в PHP, который может делать то, что запрашивает OP. В моем примере он рисует окно вокруг стартовой координаты lat/long, но код может быть легко использован для получения одной точки, X-х километров или миль.

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