【问题标题】:Latitude/Longitude storage and compression in CC中的纬度/经度存储和压缩
【发布时间】:2023-12-30 10:48:01
【问题描述】:

有人知道最有效的纬度/经度坐标表示吗?对于消费类 GPS 设备,准确度级别应该足够了。

大多数实现似乎对每个单元使用double,但我怀疑float 或定点格式应该足够了。我很想知道任何尝试压缩和/或存储这些值的大型数组的人的来信。

编辑:

换句话说,代表消费级设备的纬度/经度所需的最低准确度是多少?

【问题讨论】:

  • 你想做什么?你在存储路径吗?
  • 在消费设备上存储和传输 GPS 数据。

标签: c compression gps floating-accuracy


【解决方案1】:

我个人会使用 32 位十进制定点表示,根据 Evan 的回答和我的 cmets 除以 1,000,000。

但是,如果空间真的很宝贵,这里有一些额外的想法:

  • 您可以在线上使用 26 位定点表示。这将需要将纬度和经度编组和解组为一个大字节数组,但在 32 位值表示上为每个位置节省 12 位 - 几乎节省了 19%,因此这可能是值得的。

  • 您可以利用这样一个事实,即当您越靠近两极时,经度值所需的精度就越低 - 它们在赤道处只需要 26 位值。因此,您可以编写一个方案,其中用于编码经度的位数取决于纬度的值。

  • 1234563作为最后一点的增量)。

【讨论】:

    【解决方案2】:

    编辑:添加了一些来自 cmets 的点,32 位值应该能够提供足够的精度。

    我会使用 32 位定点表示。如果值为:

    42.915512,-99.521654 我会将values * 100000 存储在int32_t中(它们可以是负数)。

    int32_t lat = 42915512;
    int32_t lon = -99521654;
    

    这是简单和准确之间的良好折衷(5 小数点通常就足够了,如果需要,您可以随时将其提高到 1000000 以得到 6)。

    要向用户显示,请执行caf 的建议:

    ... 向用户显示 - 使用整数 除法和取模,例如printf("Lat = %d.%06d\n", lat / 1000000, abs(lat) % 1000000)

    这些也将以一种有效的方式进行比较/排序,因为将保留相对顺序。

    编辑:另一个好处是它可以通过网络发送或以便携式方式以二进制格式存储到磁盘。

    【讨论】:

    • 也许要小心一点,不要抹杀 -77.521654 和 77.521654 之间差异的含义
    • 我建议使用 2 的乘数而不是 10,000。如果您发现必须对数字进行硬编码,则使用 10,000 可能是人类可读的,但否则就毫无用处。此外,如果您确实使用此方法,请始终使用宏/内联函数将双精度转换为整数。
    • unsigned 并没有太大影响,因为它们可能是负数。此外,0.0001 度可以达到 22 米,而消费者 GPS 可以比这更准确。所以使用有符号整数,并乘以至少 1000000(最大值仍然很容易适合有符号的 32 位)。
    • 谢谢,好点,我忘了考虑负值,我已经调整了我的答案。
    • 哦,不要强制转换为双倍并除以显示给用户 - 使用整数除和模,例如 printf("Lat = %d.%06d\n", lat / 1000000,纬度 % 1000000)
    【解决方案3】:

    地球的周长约为。 40.000 公里或 24900 英里。

    您需要一米(3 英尺)的精度才能比 GPS 精度高出一个数量级。

    因此,您需要精确存储 40.000.000 个不同的值。这至少是 26 位信息。 32 位浮点数或整数会很好。

    【讨论】:

    • 不,您需要存储 40,075,020 个不同的值才能获得一米的分辨率,这需要 26 位。
    • 实际上,32 位 IEEE 浮点数具有 23 个显式小数位(假设为 1),用于 24 个有效位。这只能区分所需的 4000 万个唯一值中的 1600 万个。换个角度看,它可以表示赤道2.4米以内的位置,可能还是够近的。
    • 我倾向于使用定点表示,因为浮点数对这种应用程序没有优势,并且有符号的 32 位值有很多位可供选择方便的比例。跨度>
    • @RBerteig 不要忘记这个符号,它会给你另一个信息,因为纬度和经度的默认表示是±180°。因为如果您接近于零精度会更好,32 位浮点数会为您提供 1m 的精度,但日期线附近的地球大约 1/5 除外。
    • @Ken:民用 GPS 设备的平均精度不可能达到 5 米(无论如何,精度通常在统计意义上用方差和标准偏差等来衡量 - 一个数字不可能反映准确性)。他们的意思是:该设备有时会测量距离真实位置 5 米以内的位置 - 有点像坏掉的时钟一天两次正确。
    【解决方案4】:

    浮点数对于存储 GPS 坐标来说已经绰绰有余,即使消费级 GPS 设备的精度接近声称的精度。如果您不相信这是真的,请尝试以下两个简单的实验:

    1. 将两台或多台 GPS 设备带到某处场地的一个地点,并记下每台设备测量的坐标。回到里面并在地图上绘制每个设备的点(我认为谷歌有一些东西可以为你做这件事)。您会惊讶于这些点相距多远(即使它们都应该测量完全相同的点)。
    2. 将您(据称)最准确的设备放在可以进行卫星定位但不会下雨的地方,并记录几天内进行的一系列测量。绘制所有读数(如 #1 所示)。同样,您会惊讶于这些点(应该都相同或几乎相同)如何在地图上到处游荡,有时甚至达到几百英尺。

    多年来,我一直在为支持 GPS 的 PDA 编写应用程序,并且我已经一次又一次地为可疑的客户验证了这一点(我什至以这种方式赢得了赌注)。有更高质量的 GPS 设备确实可以实现比这更好的精度,但是使用更昂贵的芯片组可以实现更好的精度,并且这些设备会在一个地方放置几天甚至几周,随着时间的推移平均读数。

    四字节浮点数比设备本身准确得多。只要 2X 因素对您来说不成问题,使用双精度数当然不会对您造成任何伤害。

    【讨论】:

    • 好点子 - 我想这个问题可以改写为“消费 gps 设备所需的最低准确度是多少?”
    • 该死的对所有答案都投了反对票?!我个人认为你和我的答案都是正确的。
    • 我曾让现实世界中的人们对我打破 GPS 准确度神话感到特别愤怒(然后我拿走了他们的钱)。我在 * 上遇到过一些人,他们认为 32 位浮点数与真空管属于同一类别。所以这个问题在某种意义上是一场完美风暴。 :)
    【解决方案5】:

    假设地球是一个半径“R”为 3959 英里(或 ×5280 英尺/英里 = 20903520 英尺)的完美球体(它不是,但足够近),周长为 131340690 英尺(使用 2×PI ×R)。

    360 度经度覆盖 131340690 英尺。 180 度的纬度覆盖 65670345 英尺。

    如果您想将 lat/lng 存储到 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 位(经度的值是两倍,只有一位,24.5-23.5=1 位)。因此 23.5 位允许您表示从 0 到 11863282 的数字(11863283 个值)。 65670345 英尺除以 11863283 的值是 5.53 英尺(经度的精度值相同)。

    底线:因此,如果您可以接受 5.5 英尺的纬度和经度精度,那么您可以将这两个值打包成仅 6 个字节。

    *旁注:关于 cmets,纬度和经度对于存储球体周围的位置信息非常糟糕(因为在两极存储的信息较少)——好吧,这些 cmets 经不起数学计算!让我们弄清楚。假设我们想设计一个新的完美系统,可以记录并在每平方英尺地球中心的地面上放置一个木桩。地球的表面积(R 为 3959 英里;球体表面积的公式)为 5490965469267303 SQ FT——许多桩需要 52.29 位来表示。现在现有的经纬度系统使用的是矩形系统。矩形的宽度是地球的周长,矩形的高度是周长的 1/2)——即 131340690 * 65670345(见上文),或 8625188424838050 SQ FT——需要 52.94 位来表示(该系统放置在电线杆周围的地面上“太多”木桩)。因此,令人震惊的答案是,新的完美系统和旧的 lat/lng 系统都需要 53 个实际位来存储地球上的单个位置,精确到 1 英尺!

    【讨论】:

      【解决方案6】:

      在经度 179 度处的 23 位精度可提供 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)将足以表示。小心使用浮点数进行扩展计算!舍入误差可能会吃掉你的午餐。咨询数值分析师。

      【讨论】:

        【解决方案7】:

        在 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

        【讨论】:

          【解决方案8】:

          如果您要存储这些值的大型数组,则有一些简单的技巧,如果您进行增量压缩并存储增量,您可以大大减少数据流的大小。 你可以从“关键点”做增量

          K D D D D D D D D D D D K D D D D ...

          k + d 带你到任何 d 点

          所有增量都引用了前一个 K,因此要重建任何点,您需要一个 K 和一个 D

          或者你可以做增量增量

          K I I I I I I I I I I K

          这可能需要多个总和才能到达所需位置。但总体数据较小。所以要重建

          k+i+i+i 到达第 4 点

          最后你可以将两者结合起来

          K D I I I D I I I D I I I K

          这就像带有 IPB 帧的 mpeg-2,但是这样您对任何位置的总和都不会超过 4,并且您可以获得 Delta 和增量压缩的一些好处。

          【讨论】:

            【解决方案9】:

            您可以将纬度和经度值打包在一个单个 32 位整数中,如果满足以下条件,最差的分辨率约为 2.4 米/像素(在赤道)您使用递归平铺系统。每个级别使用两位,您可以在 32 位中存储 16 个级别。查看有关Virtual Earth's tiling system 的文章,您可以了解它是如何工作的。这使用墨卡托,所以它会给你的两极带来问题。您可以改为使用不同的投影,但仍会得到非常相似的结果。

            这也可用于粗略过滤器,以查找给定父图块中的任何点,因为前 N 位将相同(因此搜索变为位掩码)。

            【讨论】:

            • -1:苹果和橙子:看文章中的表格,在 16 级,这给了我们 2.4 米/像素的分辨率,地图是 16,777,216 像素宽 (2^24),所以在缩放级别 16,我们需要 24 位 来存储每个纬度/经度值,即 48 位来存储两者。
            【解决方案10】:

            因为我需要它,所以这里是 Jerry Jongerius 的答案的 python 代码,它用 6 字节表示纬度/经度值,使用 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)
            

            【讨论】:

              【解决方案11】:

              我很惊讶没有人发布这样一个事实,即 long/lat 是一种在球体上存储数据的糟糕方式(有人确实提到经度在两极附近需要较低的精度)。

              基本上,您可以将数据位置存储为以米为单位的 X 和 Y 坐标。想象一个围绕地球的立方体完全适合(哈哈好吧几乎适合它)。您只需要存储 X 和 Y 位置,而不是所有 3 个坐标,因为第 3 个坐标可以来自地球的半径,r = square root[x^2 + y^2 + z^2] .

              因此,将您的纬度/经度转换为以米为单位的 x/y。每个坐标总共只需要 12756200m(这就是地球的直径)。因此,您的总价值只需跨越 0 到 25,512,400(其他人声称 40,000,000,因为他们使用的是经度/纬度)即可精确到 +/- 0.5m。

              这将导致每个位置只有 25 位。如果我是你,我只会将精度精确到 2m 以内,每个位置使用 24 位,因为这是一个整洁的 3 个字节。

              此外,如果您要在路径上存储路点信息,您可以将每个路点存储为与上一个路点的偏移量。就像从 24 位 x/y 坐标开始一样。然后有一个 16 位的“更新”,通过添加/减去 x/y 米来调整位置。 16 位将允许航点更新超过 400m。因此,如果您知道该设备不适用于飞机并且经常更新,这也可能是可以接受的。

              【讨论】:

              • 存储球体的 X/Y 坐标是行不通的。完全没有。在球体与 XY 平面的交点附近,您会损失很多精度,并且无法重建 Z 坐标——您只能得到半个球体。如果您正在寻找均匀性,请使用三维笛卡尔坐标。否则,lat/long 是一种很好的存储方式。
              • 哇,你应该给 Garmin 打个电话,向他们解释位置信息的纬度和经度有多“可怕”。这些年来他们在想什么?
              • UTM 对其东向和北向坐标对使用类似的方法,因此 X/Y“坐标”对球体有效。这都是投射的问题。
              • myforwik:不过,您的方法仍然存在问题。正如 Dietrich 提到的,您的 X/Y 版本不是一个好的投影。您需要展平为 2D 平面,而不是 3D 立方体。
              • Programming Pearls (2nd Edition) (ACM Press) (Paperback) 是一本很好的书,它讨论了转换为 x,y,z 以减少在地图的一个特定应用程序中发生的昂贵的三角操作的数量数据。
              最近更新 更多