我需要计算GPS坐标之间的距离来计算行进距离。我已经尝试了Haversine和Vincenty算法,这些算法在我的桌面PC上运行良好,但是当我将代码移植到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.3km,我使用毕达哥拉斯定理来计算距离。有没有简单的方法来改进我的算法?或者是否有其他算法不依赖于高度准确的sin / cos?
答案 0 :(得分:2)
在Marine AIS系统中使用的公认算法(在IEC61193-4中规定)是Rhumb Line算法。我已经使用Anthony Williams' fixed point maths library使用CORDIC算法在目标上成功实现了这一点,并且相信通常会比软件浮点数提供5倍的性能提升。
然而,库是C ++而不是C,这使得它易于使用,因为广泛的运算符重载,但可能不是你想要的。值得考虑为您的C代码使用C ++编译,只是为了这个库的好处。当然,问题在于Microchip的C31编译器奇怪地不支持C ++。
然而,有一点需要注意,log()函数的查找表太短了一个值,并且最后需要一个值为零的附加元素。我发现它之后安东尼证实了这一点,但我不相信他已经更新了下载。
无论哪种方式,答案可能是使用固定点和CORDIC。
要解决1m经度或赤道弧,你需要8位精度,所以单精度浮点数不足,使用双精度会大大减慢速度。检查MikroElectronica的C用户手册显示编译器仅支持单精度 - float
,double
和long double
都是32位,因此无法实现所需的精度在任何情况下都使用此编译器构建内置FP类型。
如果它有用,这是我的Rhumb Line代码使用Anthony的库:
部首:
#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
体:
#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 :(得分:1)
一些意见:
您需要指定计算的范围和精度要求。在确定用于计算余弦的方法时,范围和准确度非常重要。此外,如果纬度和经度的相对差异与到极点的角距离相比较小,那么您发布的毕达哥拉斯近似值效果很好。如果纬度不靠近,你的伪毕达哥拉斯算法在高纬度地区将无法正常工作。 (例如纬度为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度。)
在设计时,充分利用您的PC。您有一个非常强大的计算器,您可以评估大量测试点的高精度答案和您的近似,并看看它们如何不同,以评估准确性。如果您在此信息中推断出模式,则可以使用它来进行二阶近似以提高准确度。
更新:您声明您的范围/精度要求为+/- 60度(在一个半球的范围内没有优势)和1%的准确度。在此范围内,以度为单位的cos(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 ,但对参数误差和算术误差更敏感)或分成多个范围。
答案 2 :(得分:0)
您可能想尝试将预先计算的表用于sin和cos。 它使用更多内存,可以将通用处理器(不是你的情况)上的缓存丢弃,但在处理器上具有尽可能高的准确性并且速度非常快。
答案 3 :(得分:0)
你是一个固定点DSP(有效),所以你可能想看看定点功能;他们的表现可能会更好。
事实证明,Microchip有一个定点库:http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2680&dDocName=en552208我不知道它会有多大帮助 - 它可能缺乏你需要的精度。
这是一个如何自己做的例子:http://www.coranac.com/2009/07/sines
回到正轨 - Microchip页面建议他们的编译器和库兼容IEEE-754单精度和双精度。除非他们说的是半真半假,并且正在使用半精度(16位)格式。如果您仍然没有得到您需要的结果,我会考虑提交错误报告。