Sqlalchemy:纬度和经度的浮点精度?

9

我正在使用Sqlalchemy来定义我的表格等内容,这是我想到的一些代码:

locations = Table('locations', Base.metadata,
Column("lat", Float(Precision=64), primary_key=True),
Column("lng", Float(Precision=64), primary_key=True),
)

我在某个地方读到,纬度和经度需要比浮点数更好的精度,通常是双精度。所以我手动将精度设置为64,这足够吗?过度吗?这对我的情况有帮助吗?


1
“read somewhere”. 你真的应该找到那个参考资料,并从中获取准确的引用。1纬度是60海里,约为364,567英尺。这意味着9位数字(3位在左边,6位在右边)可以让你精确到一英尺以内。这大约是单精度浮点数。请找到说需要更多的引用,并提供链接或参考资料。 - S.Lott
这个链接显示有12位小数?那就是1.852E-9米。约等于2纳米。这似乎很荒谬。 - S.Lott
单精度浮点数可以保存6-7位小数;如果你需要9位,就不能使用单精度,但双精度足够了。 - Jonathan Leffler
一个7.22位的单精度数字可以让你在小数点右边获得4个数字。0.0001度是11米,36英尺。问题仍然是:你的用例是什么? 你的用例需要多少位数字? - S.Lott
我只想从Google地图或用户智能手机的位置中存储商铺的纬度/经度信息,当他们签到时。 - john
@S.Lott 请记住,没有有限数量的二进制数字可以存储像0.1这样的简单十进制数。将其存储为浮点数,用作坐标,您可能会偏离一公里。 - jpaugh
4个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
7
没有其他人提供具有证明的具体数字,以说明浮点数经纬度的最坏情况精度。我需要了解这个问题,因为我正在处理一个相关工作,所以在这里给出我的分析,希望对他人有所帮助。 单精度浮点数在有效数字(数字的二进制指数表示法)中提供24位精度。随着整数部分变大,小数点后的位数会减少。因此,纬度或经度的最坏情况精度是当大小与0的距离最远时。假设您将纬度限制在[-90,90]之间,将经度限制在(-180, 180]范围内,则最坏情况将发生在赤道经度为180度处。 在二进制中,180需要24位中的8位,留下小数点后16位。因此,在该经度上相邻可表示值之间的距离将为2 ^ -16度(约为1.526E-5)。将该数字(以弧度为单位)乘以赤道上地球的WGS-84半径(6,378,137 m),可以得到最坏情况下的精度。
2^-16 deg * 6,378,137 m * PI rad / 180 deg = 1.6986 m (5.5728 ft).

对存储为弧度的经纬度进行相同的分析,得到以下结果:

2^-22 rad * 6,378,137 m = 1.5207 m (4.9891 ft)

最后,如果你把纬度归一化到[-1, 1]范围内,并且将经度归一化到(-1, 1]范围内,那么你就可以获得以下最坏情况下的精度:

2^-24 * PI rad * 6,378,137 m = 1.1943 m (3.9184 ft)

将经纬度以弧度形式存储可增加约7英寸的额外精度,以标准化形式存储可增加约1'8"的额外精度,这都是在最坏情况下的。

如果在双精度和单精度之间进行转换时四舍五入(而不是截断),则单精度值将在上述计算出的两个连续值之间的距离的一半范围内。


你需要使用周长而不是半径,对吗?(我删除了之前有些混淆的评论) - HRJ
1
@HRJ 我们正在探讨一个问题,使用单精度数字表示连续经度之间的最坏情况分辨率是多少(以度和弧度为单位)?一旦我们得到这些数字(分别为2^-16和2^-22),我们将它们转换为弧长,使用赤道(最坏情况)周长。完整赤道周长的公式是2 * pi * R,我们要寻找的圆周率的部分是θ /(2 * pi)(θ以弧度为单位)。当你将它们相乘时,你只需得到R * θ(θ以弧度为单位)。 - Jeff G
@HRJ 请参阅Radian Definition。方程式 s = Rθ 实际上就是弧度的定义。在该方程中,s 是弧长,R 是半径,θ 是弧度制下所对应的角度大小。 - Jeff G
感谢您澄清周长与半径的疑惑。然而,您似乎假设所有24位都可用于尾数。但其中一个是专用的符号位。您可能需要从答案中减去一位数字。 - HRJ
实际上,您确实获得了24位精度。请参见链接表中的单精度。尾数在小数点前隐含着1,因此您最终获得N+1个有效位数,其中N是尾数中的位数。对于单精度,N为23,因此这将为您提供24位精度。 - Jeff G

4

这取决于您使用数据的目的。如果您只需要将其精确到大约米级别,使用浮点数就可以了。如果在图形应用程序中使用数据,并且用户放大到太远,则会导致抖动效果。要了解更多关于抖动的信息,请参见Precisions, Precisions。希望这能帮到您。


2
< p > 更新:Jeff的回答有更好的分析。不过...

为了改进Jeff的答案:

如果将实际弧度角除以π,从而将角度编码为一个范围从0到±1的尺度上,那么就可以使用所有有效数字(23位(24-1个符号位))。精度应该是:

2^-23 * 6,378,137 m = 0.7603 m (76 cm)

我的旧回答:

32位浮点数可以表示大约7.2个十进制数字的精度。这只是一个近似值,因为浮点数实际上是二进制的,转换成十进制后,有效数字的数量可能会有所变化。

如果我们将其视为6个十进制数字的精度(为了保险起见),并且如果我们将纬度和经度以度为单位存储,则最差情况下我们获得了约111米的精度。在最好的情况下,如果我们获得7个十进制数字的精度,则精度约为11.1米。

使用弧度作为单位可以获得更高的精度。在最坏的情况下,我们获得了约63米的10百万分之一弧度的精度。在最好的情况下,它将是1百万分之一弧度,约为6米。

不用说,64位浮点数的精度将非常高(在最坏的情况下约为6微米)。


虽然你的逻辑是正确的,但你的前提是错误的。单精度数字根据数字不同会给出6到9个有效小数位。分析应该在二进制下进行,以消除结果中的歧义。这个错误的前提就是你得出结论比正确值高一个数量级的原因。请参考我的答案以获得更严谨的分析。 - Jeff G
你的新分析是不正确的。要让 Rθ 成为弧长,θ 必须以弧度为单位。但是,如果您像您所建议的那样将经度存储在 (-1, 1) 范围内,并且可以保证您永远不会接近第180个子午线以实际存储值±1,则可以获得更好的精度。我会更新我的回答,包括该分析。 - Jeff G
经过思考,您不必满足永远不将±1作为规范化经度存储的要求,以便在分析中使用完整的24位精度。这是因为在这样的系统中,下一个可表示的经度仍然提供了24位精度。 - Jeff G
@JeffG "θ必须以弧度为单位", 是的,但是可以通过应用比例因子来计算θ以弧度为单位。然而,我不确定在乘法过程中精度是否会得到保持,特别是π是无理数。我们能否在此聊天室进一步讨论:http://chat.stackoverflow.com/rooms/85134/lat-long-precision - HRJ
不要忘记根据您自己的评论更新答案:“θ可以通过应用比例因子计算为弧度”。该因子是PI,因此您答案中呈现的最坏精度需要乘以PI。我还认为您需要使用2^-24而不是2^-23。 - Jeff G

0

简而言之:如果一米的分辨率可以接受,那么存储度数的单精度浮点数就可以接受。

这个答案有点晚了,但我自己需要一个确定的答案,所以编写了一些代码来快速获取它。当然,有更优雅的方法来实现这一点,但看起来它能够工作。正如Jeff所指出的那样,最坏的情况将出现在东经/西经180度(即日期线)。

根据下面的代码,在日期线上使用存储度数的单精度浮点数时,精度为0.85米。当靠近本初子午线时,精度显著提高(达到毫米级)。

#include <stdio.h>

// per wikipedia, earth's circumference is 40,075.017 KM
#define METERS_PER_DEG  (40075017 / 360.0) 

// worst case scenario is near +/-180.0 (ie, the date line)    
#define LONGITUDE    180.0

int main()                                                                      
{                                                                               
   // subtract very small but increasingly larger values from
   //    180.0 and recast as float until it no longer equals 180.0
   double step = 1.0e-10;   
   int ctr = 1;
   while ((float) LONGITUDE == (float) (LONGITUDE - (double) ctr * step)) {
      ctr++;
   }
   double delta = (double) ctr * step;           
   printf("Longitude %f\n", LONGITUDE);
   printf("delta %f (%d steps)\n", delta, ctr);  
   printf("meters: %f\n", delta * METERS_PER_DEG);
   return 0;                                                                    
} 

这段代码的输出结果是

Longitude 180.000000 
delta 0.000008 (76294 steps) 
meters: 0.849301

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,