存储经纬度的正确/最佳类型


68

在像C,C ++或D这样的系统级编程语言中,用于存储纬度和经度的最佳类型/编码是什么?

我看到的选项是:

  • IEEE-754 FP以度或弧度表示
  • 度或弧度以固定点值形式存储在32或64位int中
  • 整数范围到度数范围的映射:-> deg = (360/2^32)*val
  • 度,分钟,秒和小数秒存储为int中的位字段
  • 某种结构。

简易解决方案(FP)的主要缺点是分辨率极不均匀(在英国的某个地方它可以以微米为单位,而在日本则无法做到)。这也有FP比较之类的所有问题。其他选项在数据生命周期的不同部分需要额外的精力。(生成,演示,计算等)

一种有趣的选择是浮动精度类型,其中随着纬度的增加,它会获得更多的比特,而经度则变得更少(因为它们向极点靠拢)。

相关问题并未涵盖此方面:


BTW:32位在大约0.3英寸的赤道处提供E / W分辨率。这接近高级GPS设置可以工作的比例(IIRC在某些模式下可以降低到大约0.5)。

OTOH如果将32位均匀地分布在地球表面,则可以在一侧索引大约344m的正方形,5字节给出21m,6B-> 1.3m和8B-> 5mm。

我现在没有特定的用途,但是之前已经使用过这种东西,并且期望在某个时候再次使用。


1
您已经在一些答案注释中以及在这个问题中指出了解决问题。您需要什么分辨率?还需要说明您还需要执行哪些操作。如果要进行Great Circle计算,则无论如何都需要转换为double / float。
cletus

1
因为64位双精度浮点值具有更大的粒度,这不是比32位int更好的选择吗?这是您评论的内容。使用它也更容易。
cletus

Answers:


42

最简单的方法是将其存储为浮点数/双精度度数。N和E为正,S和W为负。只需记住分钟和秒不超过60(所以31 45'N为31.75)。通过查看它们很容易理解值是什么,并且在必要时转换为弧度很简单。

纬度和经度(例如两个坐标之间的大圆弧距离)的计算在很大程度上依赖于三角函数,该函数通常使用双精度。任何其他格式都将至少依赖于正弦,余弦,atan2和平方根的另一种实现。任意精度数字(例如Java中的BigDecimal)将不适用于此功能。像int这样均匀分布2 ^ 32的东西将有类似的问题。

统一性的观点已在几条评论中提到。关于这一点,我将简单地指出,就经度而言,地球不是统一的。北极圈的一弧秒经度比赤道短。双精度浮子使地球上任何地方的精度都达到亚毫米。这还不够吗?如果没有,为什么不呢?

还值得注意的是,您想对这些信息做什么,因为所需的计算类型将影响您使用的存储格式。


1
一个有效的观点,但没有解决我希望解决的问题。
BCS

这个答案也是我想要的-认为如果您的问题没有解决,您需要澄清一下。
frankodwyer

3
我同意这可以回答所问的问题。我使用的大多数商业系统内部都使用FP度或弧度。缺乏或统一是纬度和经度定义的函数,而不是它们存储方式的函数。
肯·保罗

2
不要忘记,C语言(和大多数语言)中的数学函数期望以弧度表示的参数。任何使用度数存储值的系统都需要在使用数学函数之前转换为弧度。这并不意味着按度数存储是错误的。确实,这可能是最好的。但是要注意。
乔纳森·莱夫勒

21

与32位浮点数相比,经度和纬度的精度通常更高。因此,如果您担心存储空间,可以使用浮点数。但总的来说,将数字加倍会更方便。

弧度对于理论数学更方便。(例如,仅当使用弧度时,正弦的导数才是余弦。)但是度数通常对于人们来说更熟悉并且更易于解释,因此您可能希望坚持使用度数。


一个有效的观点,但没有解决我希望解决的问题。(我已经编辑了问题以澄清问题)
BCS

您需要什么分辨率?
cletus

@cletus,统一分辨率比高分辨率更受关注,但是32位均匀分布在我所需要的任何数量级之内。
BCS

3
请记住,至少在经度方面,地球不是统一的。北极圈的1秒经度与赤道的距离不同。为什么要/需要统一性?
cletus

FP之所以起作用,是因为在大多数情况下,已知该数字的分辨率与其大小成正比。对于纬度/经度,零点是完全任意的,因此不存在比例性。使用n位似乎很糟糕,然后在某些地方将其中的一些位烧掉以达到无用的分辨率。
BCS

11

根据Wikipedia上有关十进制度数的文章,精度为8的十进制度表示应该绰绰有余。

0 decimal places, 1.0 = 111 km
...
7 decimal places, 0.0000001 = 1.11 cm
8 decimal places, 0.00000001 = 1.11 mm

1
log_2(365 * 10 ^ 8)〜= 35因此需要〜70位,四舍五入为字节:9个字节。9个字节均匀分布,可以解析为0.1 mm ^ 2的区域。
BCS 2012年

错什么?它需要35位,而对于7位则需要不到32位,因此如果在进行计算时将它们转换为双精度数,则int足以存储365度并保留7位小数。
马丁

6

http://www.esri.com/news/arcuser/0400/wdside.html
在赤道处,经度的弧度秒大约等于纬度的弧度秒,即海里的1/60(或101.27)英尺或30.87米)。

32位浮点数包含23个显式数据位。
180 * 3600需要log2(648000)= 19.305634287546711769425914064259位数据。请注意,符号位是单独存储的,因此我们只需要总计180度。
从23中减去log2(648000)的位后,我们剩下了3.694365712453288230574574085935741位用于亚秒级数据。
即2 ^ 3.694365712453288230574085935741 = 12.945382716049382716049382716053每秒零件数。
因此,浮点数据类型在赤道处的精度为30.87 / 12.945382716049382716049382716053〜= 2.38米。


4

您提到的浮点值问题可能成为问题吗?如果答案是否定的,我建议仅以双精度使用弧度值-如果仍要进行三角计算,则将需要它。

如果使用双精度时可能会出现精度损失的问题,或者您不打算进行三角测量,建议您使用映射到整数范围的解决方案-这将为您提供最佳分辨率,可以轻松转换为任何显示格式您将使用的语言环境-选择适当的0子午线后-可用于转换为高精度的浮点值。

PS:我一直想知道为什么似乎没有人使用地心球坐标-它们应该与地理坐标相当接近,并且不需要球体上​​所有这些花哨的数学来进行计算;出于娱乐目的,我想将高斯-克鲁格-科森(德国Katasteramt所使用的)转换为GPS坐标-让我告诉你,这很丑陋:一种使用贝塞尔椭球,另一种使用WGS84,以及高斯-克鲁格映射本身就非常疯狂...


1
@Greg:Gauss-Krüger坐标是从圆柱投影中获得的,但是“增强了”,因此在地图上使用标尺时(对于某些“有意义”;),您可以获得有意义的结果。这一切都始于我以为:嘿,这应该不太复杂...
Christoph

4

哪种编码“最佳”实际上取决于您的目标/要求。

如果您正在执行算术运算,则浮点经度,纬度通常很方便。其他时候,笛卡尔坐标(即x,y,z)会更方便。例如,如果只关心地球表面上的点,则可以使用n矢量

至于长期存储,IEEE浮点将浪费您不关心的范围(经度/纬度)或对于笛卡尔坐标可能不关心的精度(除非您希望在原点获得非常好的精度)无论出于何种原因)。当然,您可以将任一类型的坐标映射到您首选大小的整数,以使所述整数的整个范围都涵盖您感兴趣的分辨率所关心的范围。

当然,除了不浪费编码中的比特之外,还有其他事情要考虑。例如,(Geohashes)[https://en.wikipedia.org/wiki/Geohash]具有很好的属性,即可以很容易在同一区域中找到其他地理哈希。(大多数前缀将具有相同的前缀,您可以计算其他前缀将具有的前缀。)不幸的是,它们在赤道附近和极点附近的经度精度保持相同。我目前使用的是64位地理哈希存储,在赤道处分辨率约为3 m。

梅登黑德定位系统有一些相似的特征,但人与人之间的沟通位置,而不是在计算机上存储似乎更优化。(存储MLS字符串会浪费很多位,以进行一些琐碎的错误检测。)

我发现一个处理两极问题的系统军事网格参考系统,尽管它看起来也更注重人际交流。(从经纬度转换为纬度/经度似乎很痛苦。)

根据您的确切需求,您可以在两极附近使用类似于通用极地坐标坐标系的方法,并且在世界其他地方使用比UTM更合理的计算方法,并最多使用一位来指示这两种系统中的哪一种您正在使用。我最多只能说一点,因为您关心的大多数问题不太可能接近两极。例如,您可以使用“一半”,即11表示使用极坐标系统,而00、01和10表示使用其他系统,并且是表示形式的一部分。

抱歉,这有点长,但是我想保存最近学到的东西。遗憾的是,我还没有找到任何标准,合理且有效的方法来以统一的精度表示地球上的一个点。

编辑:我发现了另一种方法,它看起来更像您想要的方法,因为它更直接地利用了接近极点的经度所需的较低精度。事实证明,有关存储法向量的研究很多。使用优化的球坐标编码法向矢量描述了这样一种系统,它在保持最低精度的同时对法向矢量进行编码,但是它也可以用于地理坐标。


3

0.3英寸的分辨率正逐渐降低,以至几年后的地震有所作为。您可能需要重新考虑为什么您认为需要在全球范围内获得如此高的分辨率。

太平洋中一些传播中心的变化每年高达15厘米


0.3in适用于均匀情况。与一个32位FP一起工作,他知道您所获得的价值(最多?)不再有效。
BCS

3

一个Java程序,用于将纬度/经度值转换为浮点/双精度,以米为单位计算最大舍入误差:

import java.util.*;
import java.lang.*;
import com.javadocmd.simplelatlng.*;
import com.javadocmd.simplelatlng.util.*;

public class MaxError {
  public static void main(String[] args) {
    Float flng = 180f;
    Float flat = 0f;
    LatLng fpos = new LatLng(flat, flng);
    double flatprime = Float.intBitsToFloat(Float.floatToIntBits(flat) ^ 1);
    double flngprime = Float.intBitsToFloat(Float.floatToIntBits(flng) ^ 1);
    LatLng fposprime = new LatLng(flatprime, flngprime);

    double fdistanceM = LatLngTool.distance(fpos, fposprime, LengthUnit.METER);
    System.out.println("Float max error (meters): " + fdistanceM);

    Double dlng = 180d;
    Double dlat = 0d;
    LatLng dpos = new LatLng(dlat, dlng);
    double dlatprime = Double.longBitsToDouble(Double.doubleToLongBits(dlat) ^ 1);
    double dlngprime = Double.longBitsToDouble(Double.doubleToLongBits(dlng) ^ 1);
    LatLng dposprime = new LatLng(dlatprime, dlngprime);

    double ddistanceM = LatLngTool.distance(dpos, dposprime, LengthUnit.METER);
    System.out.println("Double max error (meters): " + ddistanceM);
  }
}

输出:

Float max error (meters): 1.7791213425235692
Double max error (meters): 0.11119508289500799

2

好问题!

我知道这个问题已经9岁了,我只知道您要寻找的答案的一部分,但是我来这里时遇到的是一个类似的问题,自提出该问题以来,很多事情已经发生了变化,例如可用的硬件和GPS 。我经常在处理与不同应用程序中的不同类型GPS兼容的固件中工作,却浪费了数小时(和几天)的时间,而我却花了很多时间(和天数)为与我合作或使用过的不同应用程序设计出“最佳设计”发达。

与往常一样,不同的解决方案将提供收益和成本,最终,“最佳设计”将始终是收益和成本与系统需求的“最佳匹配”。当我问同样的问题时,有一些事情我必须考虑:

CPU时间成本

如果CPU没有内置的浮点协处理器(许多微控制器就是这种情况),那么处理“ float”,“ double”和“ long double”会非常昂贵。例如,对于一个我经常使用的16位微控制器,使用“双”值的乘法运算将花费326个CPU时钟周期,而除法运算则花费1193个时钟周期。非常贵!

精度权衡

在赤道处,一个“浮点数”(IEEE-754 32位浮点值)需要表示一个带符号的度值,假设可以表示7个“干净的”有效十进制数字,那么一个最低有效十进制数字的变化(例如,从179.9999到180.0000)将代表约11.12米的距离。这可能会或可能不会满足硬系统精度要求。而“双”(代表15个“干净的”有效十进制数字,因此从179.999999999999变为180.000000000000)表示约0.00011毫米。

输入精度限制

如果您正在处理来自GPS的输入,那么您将获得多少位数的真实准确度,并且需要保留多少位数?

开发时间成本

IEEE-754的64位双精度值('double')和32位单精度值('float')在C语言中非常方便处理,因为几乎所有C编译器都附带了两者的数学库,并且通常非常可靠。如果您的CPU带有硬件浮点处理器,这是一个简单的选择。

RAM和存储成本

如果必须将大量这些值保留在RAM(或存储,例如MYSQL)中,则可用RAM(和存储空间)可能会影响解决方案的可操作性。

可用数据与必需数据

在撰写本文时,我正在处理的一个示例(来到这个问题的原因)是我正在处理一个u-blox M8 GPS,它能够为我提供二进制GPS信息(节省了转换ASCII NMEA的CPU开销。句子)。在这种二进制格式(称为“ UBX协议”)中,纬度和经度表示为带符号的32位整数,该表示形式能够表示(在赤道处)低至约1.11厘米的精度。例如,经度-105.0269805表示为-1050269805(使用所有32位),一个LSb的变化表示任何地方的纬度变化约为1.11厘米,在赤道处的经度约为1.11厘米(在较高的纬度处则较小,与余弦成比例)纬度)。GPS所使用的应用程序可以完成导航任务,而导航任务(已经存在且经过测试的代码)需要“ double”数据类型。不幸的是,仅通过将整数的基数2位移到“ double”的内部表示位中,就无法轻松地将该整数转换为IEEE-754 64位“ double”,因为要执行的十进制移位是以10为基数的十进制移位。取而代之的是以2为基数的十进制移位,那么整数的以2为基数的位数可以移到'double'的位域中,而只需很少的转换。但是a,我拥有的有符号整数不是这种情况。因此,这将使我在没有硬件浮点处理器的CPU上花费一个乘法:326个CPU时钟周期。仅通过将整数的基数2的位移动到“ double”的内部表示位中就无法轻松完成,因为要执行的十进制移位是10的基数十进制移位。取而代之的是以2为基数的十进制移位,则整数的以2为基数的位数可以移到'double'的位域中,而只需很少的转换。但是a,我拥有的有符号整数不是这种情况。因此,这将使我在没有硬件浮点处理器的CPU上花费一个乘法:326个CPU时钟周期。仅通过将整数的基数2的位移动到“ double”的内部表示位中就无法轻松完成,因为要执行的十进制移位是10的基数十进制移位。取而代之的是以2为基数的十进制移位,则整数的以2为基数的位数可以移到'double'的位域中,而只需很少的转换。但是a,我拥有的有符号整数不是这种情况。因此,这将使我在没有硬件浮点处理器的CPU上花费一个乘法:326个CPU时钟周期。但是a,我拥有的有符号整数不是这种情况。因此,这将使我在没有硬件浮点处理器的CPU上花费一个乘法:326个CPU时钟周期。但是a,我拥有的有符号整数不是这种情况。因此,这将使我在没有硬件浮点处理器的CPU上花费一个乘法:326个CPU时钟周期。

double   ldLatitude;
int32_t  li32LatFromGps;
ldLatitude = (double)li32LatFromGps * 0.0000001;

请注意,选择了此乘法:

ldLatitude = (double)li32LatFromGps / 10000000.0;

因为“双倍”乘法比我正在处理的CPU上的“双倍”除法快3.6倍。这就是微控制器世界中的生活。:-)

如果不是可以直接使用32位带符号整数完成导航任务,那将是BRILLIANT(将来可能,如果我可以在周末节省时间的话)。那么就不需要转换了。。。。。。。。。。CPU成本,可能效率更高。开发时间成本?这是另一个问题,尤其是在已经使用系统进行了充分测试的系统中,它使用的是IEEE-754 64位“ double”值!另外,已经存在提供地图数据(使用“双”度值)的软件,该软件也必须转换为使用带符号的整数-并非一夜之间!

一种非常有趣的选择是使用原始纬度/经度整数直接(不进行平移)表示“矩形”(实际上是梯形,在极点处变为三角形)的近似值之间的交点。在赤道处,这些矩形的尺寸为东西方向大约1.11厘米,南北方向为1.11厘米,而在说英国伦敦的纬度处,尺寸为大约0.69厘米的东西方向而向南1.11厘米。根据应用程序的需求,这可能不容易解决。

无论如何,我希望这些想法和讨论能对正在为该系统寻求“最佳设计”的其他人提供帮助。

亲切的问候,维克


4
换句话说,是两倍。
杰里尔·库克

1

如果说“存储”是指“保留在内存中”,那么真正的问题是:您将如何处理它们?

我怀疑在这些坐标做任何有趣的事情之前,它们已经通过math.h中的函数以弧度的形式被漏斗了。除非您计划实现相当多的先验功能,这些先验功能可以对位字段中的Deg / Min / Secs进行操作。

那么,为什么不使事情保持简单,而仅以您要求的精度将它们存储在IEEE-754度或弧度内呢?


1
是的,在内存方面,胜过IEEE的并不多。另一方面,如果您要在磁盘上存储很多点(例如高分辨率矢量地图)或通过电线运输它们,则需要...
BCS

1

以下代码将WGS84坐标无损打包为无符号长整数(即8个字节):

using System;
using System.Collections.Generic;
using System.Text;

namespace Utils
{
    /// <summary>
    /// Lossless conversion of OSM coordinates to a simple long.
    /// </summary>
    unsafe class CoordinateStore
    {
        private readonly double _lat, _lon;
        private readonly long _encoded;

        public CoordinateStore(double lon,double lat)
        {
            // Ensure valid lat/lon
            if (lon < -180.0) lon = 180.0+(lon+180.0); else if (lon > 180.0) lon = -180.0 + (lon-180.0);
            if (lat < -90.0) lat = 90.0 + (lat + 90.0); else if (lat > 90.0) lat = -90.0 + (lat - 90.0);

            _lon = lon; _lat = lat;

            // Move to 0..(180/90)
            var dlon = (decimal)lon + 180m;
            var dlat = (decimal)lat + 90m;

            // Calculate grid
            var grid = (((int)dlat) * 360) + ((int)dlon);

            // Get local offset
            var ilon = (uint)((dlon - (int)(dlon))*10000000m);
            var ilat = (uint)((dlat - (int)(dlat))*10000000m);

            var encoded = new byte[8];
            fixed (byte* pEncoded = &encoded[0])
            {
                ((ushort*)pEncoded)[0] = (ushort) grid;
                ((ushort*)pEncoded)[1] = (ushort)(ilon&0xFFFF);
                ((ushort*)pEncoded)[2] = (ushort)(ilat&0xFFFF);
                pEncoded[6] = (byte)((ilon >> 16)&0xFF);
                pEncoded[7] = (byte)((ilat >> 16)&0xFF);

                _encoded = ((long*) pEncoded)[0];
            }
        }

        public CoordinateStore(long source)
        {
            // Extract grid and local offset
            int grid;
            decimal ilon, ilat;
            var encoded = new byte[8];
            fixed(byte *pEncoded = &encoded[0])
            {
                ((long*) pEncoded)[0] = source;
                grid = ((ushort*) pEncoded)[0];
                ilon = ((ushort*)pEncoded)[1] + (((uint)pEncoded[6]) << 16);
                ilat = ((ushort*)pEncoded)[2] + (((uint)pEncoded[7]) << 16);
            }

            // Recalculate 0..(180/90) coordinates
            var dlon = (uint)(grid % 360) + (ilon / 10000000m);
            var dlat = (uint)(grid / 360) + (ilat / 10000000m);

            // Returns to WGS84
            _lon = (double)(dlon - 180m);
            _lat = (double)(dlat - 90m);
        }

        public double Lon { get { return _lon; } }
        public double Lat { get { return _lat; } }
        public long   Encoded { get { return _encoded; } }


        public static long PackCoord(double lon,double lat)
        {
            return (new CoordinateStore(lon, lat)).Encoded;
        }
        public static KeyValuePair<double, double> UnPackCoord(long coord)
        {
            var tmp = new CoordinateStore(coord);
            return new KeyValuePair<double, double>(tmp.Lat,tmp.Lon);
        }
    }
}

资料来源:http : //www.dupuis.me/node/35


您甚至不能将两个双打(104位)的尾数“无损”打包为64位。鸽眼原理。
BCS

@BCS:但是GPS没有利用双打的全部可能范围。
奥古斯丁

您没有说任何有关GPS的信息。确实,上述保存器比典型的GPS提供的准确性更高,但这不是您所说的。-还请注意,均匀分布的8个字节在地球表面的最大精度为〜5mm,接近于实际上从高端GPS可获得的〜10mm(并且比通过其他工具可获得的更长)。-而且,我知道GPS不会使用整个范围,所以我只计算了尾数位。
BCS

0

您可以使用decimal数据类型:

CREATE TABLE IF NOT EXISTS `map` (
  `latitude` decimal(18,15) DEFAULT NULL,
  `longitude` decimal(18,15) DEFAULT NULL 
);

0

在自己寻找答案后遇到了这个问题之后,这是基于一些先例的另一种可能的方案。

网络工作组RFC 3825为DHCP(即在网络上分发IP地址的系统)提出了基于坐标的地理位置选项。看到https://tools.ietf.org/rfc/rfc3825.txt

在此方案中,纬度和经度以固定点值的度数进行编码,其中前9位是有符号度,25位是分数度,而6位用于精度。精度位的值指示被认为是精确的25个小数位的数量(例如,通过消费者GPS与高精度测量员的GPS收集的坐标)。使用WGS84,精度是8位十进制数字,无论您在地球上的任何位置,其精度都可以达到1毫米左右。

正如其他一些人所发表的那样,浮点编码确实不适用于这种类型的事情。是的,它可以表示很多小数位,但是精度要么被忽略,要么必须在其他地方处理。例如,以全浮点精度打印浮点数或双精度数会导致带有十进制数字的数字非常不可能具有远程精度。同样,基于浮点数的计算方式,仅输出精度为8或10个十进制数字的浮点数或双精度数也不是源值的真实表示(例如,为什么使用浮点算术计算1.2-1.0不等于0.2) 。

有关为什么您应该关心坐标系精度的幽默示例,请参见https://xkcd.com/2170/

诚然,RFC 3825中使用的40位编码在32或64位环境中几乎不方便,但是这种样式可以轻松扩展为64位数字,其中9位用于带符号度,6位用于精度,保留49位小数部分。这将导致15位小数位数的精度,这基本上是任何人都不需要的精度(请参见幽默的示例)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.