Как рассчитать расстояние между координатами GPS на процессоре с плохой поддержкой плавающей запятой? - PullRequest
3 голосов
/ 15 августа 2011

Мне нужно рассчитать расстояние между координатами GPS для расчета пройденного расстояния. Я испробовал оба алгоритма Haversine и Vincenty, которые отлично работают на моем настольном ПК, но когда я портирую код на dsPIC, они возвращают 0 для точек, которые находятся близко (в пределах нескольких метров) из-за отсутствия точности с плавающей запятой и плохие реализации греха и cos.

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

double dist(double latA, double lonA, double latB, double lonB)
{
    double latD = fabs(latA - latB) * 111.3;
    double lonD = fabs(lonA - lonB) * 111.3 * cos(latA * 3.14159265 / 180);

    return sqrt(latD*latD + lonD*lonD) * 1000;
}

Предполагая, что расстояние для каждого 1 ° составляет 111,3 км, я использую теорему Пифагора для вычисления расстояния. Есть ли простой способ улучшить мой алгоритм? Или есть какие-то другие алгоритмы, которые не зависят от высокой точности sin / cos?

Ответы [ 4 ]

2 голосов
/ 19 августа 2011

Принятым алгоритмом для использования в морских системах AIS (определенным в МЭК 61193-4) является алгоритм Rhumb Line .Я успешно реализовал это на цели, используя библиотеку математики Энтони Уильямса с фиксированной точкой , которая использует алгоритм CORDIC , и я верю, что, как правило, дает увеличение производительности примерно в 5 раз по сравнению с программной плавающей точкой,

Тем не менее, библиотека в C ++, а не C, что делает ее простой в использовании из-за большой перегрузки операторов, но, возможно, это не то, что вы ищете.Однако стоит подумать об использовании компиляции C ++ для вашего кода на C, просто для пользы этой библиотеки.Проблема с этим, конечно, в том, что компилятор C31 Microchip странным образом не поддерживает C ++.

Однако одно предостережение заключается в том, что таблица поиска для функции log () слишком короткая на одно значение и нуждается в дополнительном элементев конце со значением ноль.Это было подтверждено Энтони после того, как я его нашел, но я не верю, что он обновил загрузку.

В любом случае, ответ, вероятно, заключается в использовании фиксированной точки и CORDIC.

Дляпри разрешении до 1 м по долготе или экваториальной дуге вам понадобится 8 цифр точности, поэтому поплавка с одинарной точностью будет недостаточно, а использование двойной точности значительно замедлит процесс.Проверка MikroElectronica's C User Manual показывает, что компилятор поддерживает только одинарную точность - float, double и long double все 32-битные, поэтому нет никакого способа достичь необходимой точности, используя встроенные типы FPв любом случае с этим компилятором.

Если он нужен, вот мой код Rhumb Line с использованием библиотеки Энтони:

Заголовок:

#if !defined cRhumbLine_INCLUDE
#define cRhumbLine_INCLUDE

#include "fixed.hpp"

//! Rhumb Line method for distance and bearing between two geodesic points
class cRhumbLine
{
public:

    //! @brief Default constructor
    //!
    //! Defines a zero length line, bearing zero
    cRhumbLine() : m_update_bearing(false), m_update_distance(false), m_distance(0), m_bearing(0) {} 

    //! @brief Constructor defining a line
    //!
    //! @param startLatDeg  Start latitude in degrees, negative values are south of equator
    //! @param startLonDeg  Start longitude in degrees, negative values are west of meridian.
    //! @param endLatDeg    End latitude in degrees, negative values are south of equator
    //! @param endLonDeg    End longitude in degrees, negative values are west of meridian.
    cRhumbLine( fixed startLatDeg, fixed startLonDeg, fixed endLatDeg, fixed endLonDeg ) 
    {
        define( startLatDeg, startLonDeg, endLatDeg, endLonDeg ) ;
    }

    //! @brief Define a start/ent point
    //!
    //! @param startLatDeg  Start latitude in degrees, negarive values are south of equator
    //! @param startLonDeg  Start longitude in degrees, negative values are west of meridian.
    //! @param endLatDeg    End latitude in degrees, negarive values are south of equator
    //! @param endLonDeg    End longitude in degrees, negative values are west of meridian.
    void define( fixed startLatDeg, fixed startLonDeg, fixed endLatDeg, fixed endLonDeg ) ;

    //! @brief Get start-end distance in nautical miles
    //! @return Start-end distance in nautical miles.
    fixed distanceNm() { return distanceMetres() / ONE_NM_IN_METRE ; }

    //! @brief Get start-end distance in metres.
    //! @return Start-end distance in metres.
    fixed distanceMetres()  ;

    //! @brief Get start-end bearing in degreed.
    //! @return Start-end bearing in degreed (0 <= x < 360).
    fixed bearingDeg()  ;

private:
    static const int ONE_NM_IN_METRE = 1852 ;

    bool m_update_bearing ;
    bool m_update_distance ;
    fixed m_distance ;
    fixed m_bearing ;

    fixed m_delta_phi ;
    fixed m_delta_lon ;
    fixed m_delta_lat ;
    fixed m_lat1_radians ;
} ;

#endif

Body:

#include "cRhumbLine.h"

void cRhumbLine::define( fixed startLatDeg, fixed startLonDeg, fixed endLatDeg, fixed endLonDeg )
{
    fixed lat1 = startLatDeg / 180 * fixed_pi ;
    fixed lon1 = startLonDeg / 180 * fixed_pi ;
    fixed lat2 = endLatDeg / 180 * fixed_pi ;
    fixed lon2 = endLonDeg / 180 * fixed_pi ;

    m_update_bearing = true ;
    m_update_distance = true ;

    m_delta_phi = log( tan( lat2 / 2 + fixed_quarter_pi ) / tan( lat1 / 2 + fixed_quarter_pi ) ) ;
    m_delta_lat = lat1 - lat2 ;
    m_delta_lon = lon1 - lon2 ;
    m_lat1_radians = lat1 ;

    // Deal with delta_lon > 180deg, take shorter route across meridian
    if( m_delta_lon.abs() > fixed_pi )
    {
        m_delta_lon = m_delta_lon > 0 ? -(fixed_two_pi - m_delta_lon) : (fixed_two_pi + m_delta_lon) ;
    }
} 


fixed cRhumbLine::distanceMetres()
{
    if( m_update_distance )
    {
        static const fixed mean_radius = 6371009 ; // in metres. Source: International Union of Geodesy and Geophysics (IUGG)

        fixed q = m_delta_phi != 0 ? m_delta_lat / m_delta_phi : cos( m_lat1_radians ) ; // Deal with lines with constant latitude

        m_distance = sqrt( ( sqr(m_delta_lat) + sqr(q) * sqr(m_delta_lon) ) ) * mean_radius ;
        m_update_distance = false ;
    }

    return m_distance ;
}


fixed cRhumbLine::bearingDeg()
{
    if( m_update_bearing )
    {
        static const fixed mean_radius = 6371009 ; // in metres. Source: International Union of Geodesy and Geophysics (IUGG)

        m_bearing = atan2( m_delta_lon, m_delta_phi ) / fixed_pi * 180 ;
        if( m_bearing == -180 )
        {
            m_bearing == 0 ;
        }
        else if( m_bearing < 0 )
        {
            m_bearing += 360 ;
        }
        m_update_bearing = false ;
    }

    return m_bearing ;
}
1 голос
/ 19 августа 2011

Некоторые комментарии:

  • Вам необходимо указать диапазон и требования к точности ваших вычислений. Диапазон и точность чрезвычайно важны при определении того, какой подход вы используете для вычисления косинусов. Кроме того, опубликованное вами приближение Пифагора хорошо работает, если относительные различия по широте и долготе невелики по сравнению с угловым расстоянием до полюса. Ваш псевдопифагорейский алгоритм не будет хорошо работать в высоких широтах, если широты не близки друг к другу. (например, с широтой 43.001 и 43.002 это будет работать хорошо, но не на 89.961 и 89.962)

  • Долготы должны быть рассчитаны с учетом их округлости - Ваш алгоритм потерпит неудачу вокруг международной линии даты, но это можно легко исправить, взяв продольную разность симметрично-по модулю 360, где smod(x,m) = mod(x+m/2,m)-m/2. (например, -179,5 - +179,5 = -359 градусов, но если вы вычислите smod(-359,360), вы получите +1 градус.)

  • Во время разработки хорошо используйте свой компьютер. У вас есть очень мощный калькулятор, и вы можете оценить для большого количества контрольных точек высокоточный ответ и ваше приближение и посмотреть, как они отличаются, чтобы оценить точность. Если вы выведите шаблон из этой информации, вы можете использовать его для приближения 2-го порядка для повышения точности.


обновление : вы заявляете, что ваши требования к диапазону / точности составляют +/- 60 градусов (нет преимущества в уменьшении диапазона в одном полушарии) и точность 1%. Хорошее приближение к cos (x) с x в градусах в этом диапазоне составляет c 2 (x) = 0,9942 - 1,39 * 10 -4 * x 2 = 0,9942 - (0,01179x) 2 ; его ошибка в этом диапазоне имеет максимальное значение 0,006.

Если вы хотите повысить точность, используйте полином 4-й степени (c 4 (x) = 0.999945- (0.01233015x) 2 + (0.007778x) 4 имеет максимальную ошибку в этом диапазоне менее 6x10 -5 , но гораздо более чувствительна к ошибкам параметров и арифметическим ошибкам) ​​или разбивается на несколько диапазонов.

0 голосов
/ 15 августа 2011

Вы находитесь на DSP с фиксированной точкой (эффективно), поэтому вы можете захотеть изучить функции с фиксированной точкой; они, вероятно, будут работать лучше.

Оказывается, что у Microchip есть библиотека с фиксированной запятой: http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2680&dDocName=en552208 Я не знаю, насколько это будет полезно - может не хватить необходимой точности.

А вот пример того, как сделать это самостоятельно: http://www.coranac.com/2009/07/sines

Вернемся на правильный путь - на странице микрочипа указывается, что их компилятор и библиотека совместимы с IEEE-754 как с одинарной, так и с двойной точностью. Если они не говорят полуправду и используют формат с половинной точностью (16 бит). Если вы по-прежнему не получаете нужных вам результатов, я рассмотрю вопрос об ошибке.

0 голосов
/ 15 августа 2011

Вы можете попытаться использовать предварительно вычисленную таблицу для sin и cos. Он использует больше памяти, может очистить кэш на процессоре общего назначения (не в вашем случае), но будет иметь как можно большую точность на вашем процессоре и будет довольно быстрым.

...