提问人:Justicle 提问时间:8/3/2009 最后编辑:luvieereJusticle 更新时间:11/16/2023 访问量:22310
C语言中的纬度/经度存储和压缩
Latitude/Longitude storage and compression in C
问:
有谁知道纬度/经度坐标的最有效表示?对于消费类 GPS 设备来说,精度水平应该足够。
大多数实现似乎都用于每个单元,但我怀疑定点格式或定点格式就足够了。我很想听听任何试图压缩和/或存储这些值的大型数组的人的意见。double
float
编辑:
换言之,对于消费级设备,表示经度/经度所需的最低精度是多少?
答:
编辑:从注释中添加了一些要点,32位值应该能够提供足够的精度。
我将使用 32 位定点表示形式。如果值为:
42.915512
,我会将存储在int32_t
中(它们可以是负数)。-99.521654
values * 100000
int32_t lat = 42915512;
int32_t lon = -99521654;
这是简单和准确之间的一个很好的折衷方案(小数点后 5
位通常就足够了,如果需要,您可以随时将其提高到最低点)。1000000
6
若要向用户显示,请按照 caf 的建议执行操作:
...向用户显示 - 使用整数 除法和取模,例如
printf("Lat = %d.%06d\n", lat / 1000000, abs(lat) % 1000000)
这些也将以有效的方式进行比较/排序,因为将保留相对顺序。
编辑:另一个好处是它可以通过网络发送或以可移植的方式以二进制格式存储到磁盘。
评论
浮标对于存储 GPS 坐标来说绰绰有余,即使消费级 GPS 设备的精度接近它们声称的精度。如果您不相信这是真的,请尝试以下两个简单的实验:
- 将两个或多个 GPS 设备带到田野某处的一个点,并记下每个设备测量的坐标。回到里面,在地图上绘制每个设备的点(我认为谷歌有一些东西可以为你做到这一点)。您会惊讶于这些点之间的距离(即使它们都应该测量完全相同的点)。
- 拿起你(据称)最精确的设备,把它放在一个可以得到卫星修复但不会被雨淋的地方,并记录几天内进行的一系列测量。绘制所有读数(如#1所示)。同样,你会惊讶于这些点(应该都相同或几乎相同)在地图上四处游荡,有时甚至高达几百英尺。
多年来,我一直在为支持 GPS 的 PDA 编写应用程序,并且我一遍又一遍地为可疑的客户验证了这一点(我什至以这种方式赢得了赌注)。市面上有更高质量的GPS设备,可以达到比这更高的精度,但更好的精度是通过更昂贵的芯片组实现的,并且这些设备在一个地方放置数天甚至数周,读数随时间平均。
四字节浮点数比设备本身要准确得多。当然,使用双倍不会伤害你,只要 2 倍因素对你来说不是问题。
评论
地球的周长约为 40.000 公里或 24900 英里。
您需要一米的精度(3 英尺)才能将 GPS 精度提高一个数量级。
因此,您需要 precisiton 来存储 40.000.000 个不同的值。这至少是 26 位信息。32 位 float 或 int 会做得很好。
评论
就我个人而言,我会使用 32 位十进制定点表示,根据 Evan 的回答和我的评论除以 1,000,000。
但是,如果空间确实很宝贵,这里有一些额外的想法:
您可以在线路上使用 26 位定点表示。这将需要将纬度和经度编组和解组为一个大字节数组,但与 32 位值表示相比,每个位置将节省 12 位 - 几乎节省了 19%,因此这很可能是值得的。
您可以利用这样一个事实,即当您靠近两极时,经度值需要的精度会降低 - 它们在赤道上只需要 26 位。因此,您可以编写一个方案,其中用于编码经度的位数取决于纬度的值。
如果您的数据具有其他可压缩属性(例如,所有点通常都非常接近),则可以利用这些属性进行特定利用,例如使用增量编码方案(其中除第一个点以外的每个点都可以编码为最后一个点的增量)。
令我惊讶的是,没有人发布这样一个事实,即 long/lat 是一种在球体上存储数据的糟糕方式(有人确实提到经度在两极附近需要较低的精度)。
基本上,您可以将数据位置存储为以米为单位的 X 和 Y 坐标。想象一个完全适合地球的立方体(哈哈,好吧,几乎适合它)。你只需要存储 X 和 Y 位置,而不是所有 3 个坐标,因为第 3 个坐标可以来自地球的重度,r = 平方根[x^2 + y^2 + z^2]。
因此,将您的纬度/经度转换为以米为单位的 x/y。每个坐标总共只需要 12756200 米(即地球的直径)。因此,您的总值只需要跨越 0 到 25,512,400(其他人声称 40,000,000,因为他们使用的是 long/lat)即可精确到 +/- 0.5m。
这将导致每个位置只有 25 位。如果我是你,我会把精度提高到2米以内,每个位置使用24位,因为这是一个整洁的3个字节。
此外,如果要在路径上存储航点信息,则可以将每个航点存储为与最后一个航点的偏移量。就像从 24 位 x/y 坐标开始一样。然后有一个 16 位的“更新”,通过添加/减去 x/y 仪表来调整位置。16 位将允许航点更新超过 400 米。因此,如果您知道该设备不适用于飞机并且每隔一段时间就会更新一次,那么这也可能是可以接受的。
评论
经度 23 度的 179 位精度低于 10 米,这是普通 GPS 设备所能提供的最佳精度。在赤道:
% gps distance "0.0, 179.0" "0.0, $((179 * (1 + 2**-23)))"
From 0.0, 179.0 to 0.0, 179.00002133846283 is 7.79 feet E
From 0.0, 179.0 to 0.0, 179.00002133846283 is 2.38 meters E
因此,IEEE 754 单精度浮点数(C 编译器称为 )将足以表示。谨防使用浮点数进行扩展计算!四舍五入错误可能会吃掉你的午餐。请咨询数值分析师。float
如果使用递归切片系统,则可以将纬度和经度值打包到单个 32 位整数中,分辨率最差为 ~2.4 米/像素(在赤道处)。使用每个级别两个位,您可以以 32 位存储 16 个级别。您可以查看这篇关于 Virtual Earth 平铺系统的文章,了解其工作原理。这使用墨卡托,所以它会给你带来极点的问题。您可以改用不同的投影,但仍然会得到非常相似的结果。
这也可用于粗略过滤器,以查找给定父图块中的任何点,因为前 N 位将相同(因此搜索变为位掩码)。
评论
在Garmin的IMG地图格式中,他们使用浮点数将坐标存储在边界框内,以设置框的边缘。框中的坐标是使用可变位数的位数定义的,这些位数在最小值和最大值之间只是线性的,具体取决于所需的精度。
例如:minlat=49.0, maxlat=50.0, minlon=122.0, maxlon=123.0, 位数=16
因此,值:
32768,32768 将转换为 49.5,122.5 16384,0 将是 49.25,122.0
如果您需要较低的精度,则可以生成相同的输出,位数=4
8,8 将转换为
49.5,122.5
4,0
将是 49.25, 122.0
如果要存储这些值的大型数组,则执行增量压缩并存储增量时,可以使用一些简单的技巧来大大减小数据流的大小。 您可以从“关键点”进行增量
K D D D
k + d 让你到达任何 d 点
增量都引用前一个 K,因此要重建任何点,您需要一个 K 和一个 D
或者你可以做犯罪的三角洲
K I I I I I I I I I I K
这可能需要多次总和才能到达所需的位置。但总体数据较小。SO 重组
k+i+i+i 到达第 4 点
最后,您可以将两者结合起来
K D I I I D I I I D I I I K
这就像带有 IPB 帧的 mpeg-2,但这样一来,您到任何位置都不会超过 4 个总和,并且您可以获得 Delta 和 Incrimental Compression 的一些好处。
假设地球是一个完美的球体(它不是,但足够近),半径“R”为 3959 英里(或 ×5280 英尺/英里 = 20903520 英尺),周长为 131340690 英尺(使用 2×PI×R)。
360 度经度覆盖 131340690 英尺。180 度纬度覆盖 65670345 英尺。
如果要将纬度/液化格存储精度降低到 3 英尺,则需要能够存储43780230 (131340690/3) 经度值和21890115 (65670345/3) 纬度值。43780230需要 25.38 位 (log(43780230)/log(2)) 来存储,21890115需要 24.38 位 (log(21890115)/log(2)) 来存储 – 或略低于 50 位(或 6.25 字节)。
因此,显而易见的问题是,如果您想将纬度和经度存储在 6 个字节中,那么精度是多少?好吧,6 个字节是 48 位。这意味着纬度为 23.5 位,经度为 24.5 位(经度的值是其两倍,即只有 1 位,24.5-23.5=1 位)。因此,23.5 位允许您表示从 0 到 11863282(11863283 个值)的数字。65670345 英尺除以 11863283 值等于 5.53 英尺(经度的精度值相同)。
底线:因此,如果你能同时承受 5.5 英尺的纬度和经度精度,你可以将这两个值打包到六个字节中。
*旁注:关于纬度和经度对于存储球体周围的位置信息是可怕的评论(因为在两极存储的信息较少)——好吧,这些评论经不起数学!让我们弄清楚。比方说,我们想要设计一个新的完美系统,它可以记录并在每平方英尺地球中心的地面上放置一根木桩。地球的表面积(R 为 3959 英里;球体表面积的公式)是 5490965469267303 SQ FT – 许多木桩需要 52.29 位来表示。现在,现有的经纬度系统使用矩形系统。矩形的宽度是地球的周长,矩形的高度是周长的 1/2)——这是 131340690 * 65670345(见上文),或 8625188424838050 SQ FT——这需要 52.94 位来表示(这个系统在两极周围的地面上放置了“太多”的木桩)。因此,令人震惊的答案是,无论是新的完美系统,还是旧的纬度/液化天然气系统,都需要 53 个实际位来存储地球上的单个位置,精度低至 1 英尺!
因为我需要它,所以这里是 Jerry Jongerius 答案的 python 代码,它表示 6 字节的 Lat/Lon 值,使用 23.5 位和 24.5 位在赤道附近精度约为 1.7m:
import struct
NBYTES=6
LATVALS=int(2**(NBYTES*4-0.5))
LONVALS=int(2**(NBYTES*4+0.5))
def serialize_gps(latlon):
lat=(int(latlon[0]*LATVALS/180)+LATVALS//2)%LATVALS
lon=(int(latlon[1]*LONVALS/360)+LONVALS//2)%LONVALS
return struct.pack("!Q",lat*LONVALS+lon)[8-NBYTES:]
def deserialize_gps(b):
if len(b)!=NBYTES:
raise Exception("len(b)!=NBYTES")
c=struct.unpack("!Q",(b"\x00"*6+b)[-8:])[0]
lat=(c//LONVALS-LATVALS//2)*180/LATVALS
lon=(c%LONVALS-LONVALS//2)*360/LONVALS
return (lat,lon)
s=serialize_gps((47.55754133918577,10.74961245059967))
print(deserialize_gps(s))
#(47.557526866719776, 10.749611216389258)
将纬度和经度值乘以 10,000,000 (10^7) 是在该范围内实现更高精度的好方法。使用此比例因子,您可以用 7 位小数精度表示纬度和经度坐标。这是你如何做到的:int32_t
对于纬度:
- 通过将纬度值乘以 10,000,000 (10^7) 来缩放纬度值。
- 这将有效地将从 -90.0000000 到 +90.0000000 度的纬度范围转换为 -900,000,000 到 +900,000,000 作为范围内的整数。
int32_t
对于经度:
- 通过将经度值乘以 10,000,000 (10^7) 来缩放经度值。
- 然后,移动缩放值以确保它们适合该范围。
int32_t
- 映射该范围内从 -180.0000000 到 +180.0000000 度到 -1,800,000,000 到 +1,800,000,000 的经度范围。
int32_t
应该是正确的吧?
评论
11930464.71
2**31/180
我同意山姆·梅森(Sam Mason)的观点。只需使用所需的位数/字节即可获得所需的角度精度。纬度和经度是角度。取您的总位数并将它们解释为 2*pi(或 360 度)的有符号(或无符号)分数。它也会自动地绕地球一圈。
如果您使用 32 位(假设有符号),最低有效位代表 1,4629 纳弧度或 527 纳度,这将为您提供赤道 10 毫米范围内的最坏情况精度。180 度与 -180 度 (0x80000000) 相同,360 度与 0 度 (0x00000000) 相同,添加或细分时无需任何检查或转换。
上一个:C语言中的纬度/经度存储和压缩
下一个:使用缩放容差比较浮点数
评论