代码之家  ›  专栏  ›  技术社区  ›  BCS

存储经纬度的合适/最佳类型

  •  61
  • BCS  · 技术社区  · 16 年前

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

    我看到的选项是:

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

    简单解决方案(fp)的主要缺点是它具有高度不均匀的分辨率(在英国的某个地方,它可以测量微米,而在日本,它不能测量微米)。这也有所有的FP比较和其他问题。其他选项需要在数据生命周期的不同部分付出额外的努力。(生成、表示、计算等)

    一个有趣的选择是一种浮动精度类型,随着纬度的增加,它得到更多的位,经度得到更少的位(因为它们越来越接近极点)。

    与此相关的问题还没有完全涵盖:


    顺便说一下:32位在赤道的E/W分辨率约为0.3英寸。这接近于高等级gps设置可以工作的范围(在某些模式下,iirc可以降到0.5左右)。

    如果32位均匀分布在地球表面,你可以在一个边上索引大约344米的正方形,5个字节表示21m,6b->1.3米和8b->5毫米。

    我现在还没有什么特别的用途,但我以前也做过类似的事情,希望在某个时候能再做一次。

    12 回复  |  直到 8 年前
        1
  •  37
  •   Community CDub    8 年前

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

    计算纬度和经度,如 Great Circle 两个坐标之间的距离很大程度上依赖于三角函数,通常使用双精度函数。任何其他格式都将至少依赖于正弦、余弦、atan2和平方根的另一个实现。任意精度的数字(如Java中的字节码)对此不起作用。类似于2^32均匀分布的int会有类似的问题。

    一致性的观点在一些评论中提出。在这一点上,我要简单地指出,地球在经度上是不均匀的。北极圈的一弧秒经度比赤道的距离短。双精度浮子在地球上任何地方都能提供亚毫米的精度。这还不够吗?如果没有,为什么不呢?

    还值得注意的是,您希望如何处理这些信息,因为您需要的计算类型将影响您使用的存储格式。

        2
  •  17
  •   John D. Cook    16 年前

    经度和纬度通常不比32位浮点精度高。所以如果你关心存储空间,你可以使用浮动。但一般来说,用双倍的数字更方便。

    弧度对理论计算更为方便。(例如,正弦的导数只有在使用弧度时才是余弦。)但是度数通常更为熟悉,也更容易被人们理解,因此您可能希望坚持使用度数。

        3
  •  10
  •   Pykler    13 年前

    根据维基百科的这篇文章 Decimal Degrees .

    0 decimal places, 1.0 = 111 km
    ...
    7 decimal places, 0.0000001 = 1.11 cm
    8 decimal places, 0.00000001 = 1.11 mm
    
        4
  •  4
  •   Christoph    16 年前

    您提到的浮点值问题是否会成为一个问题?如果答案是否定的,我建议只使用双精度的弧度值-如果你要做三角计算,你需要它。

    如果在使用双精度时可能会出现精度损失的问题,或者你不需要进行三角测量,我建议你的解决方案是映射到一个整数范围-这将给你最好的分辨率,可以很容易地转换为您所在地区将使用的任何显示格式,并且在选择适当的0子午线之后,可以用于转换为高精度的浮点值。

    PS: 我一直在想,为什么似乎没有人使用地心球坐标——它们应该与地理坐标相当接近,而且不需要在球体上进行这些奇特的数学运算;为了好玩,我想把高斯-克瑞-克瑞-科奥尔登(德国卡塔斯特公司正在使用)转换成GPS坐标-让我告诉你,这很难看:一个使用贝塞尔椭球体,另一个使用WGS84,高斯-克瑞-克瑞映射本身就很疯狂……

        5
  •  4
  •   Roland Pihlakas    9 年前

    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.694365712453288230574085935741位用于亚秒数据。
    即每秒2^3.6943657124532882305740859357741=12.945382716049382716049382716053个部件。
    因此,在赤道处,浮点数据类型的精度为30.87/12.945382716049382716049382716053~=2.38米。

        6
  •  3
  •   Greg Hewgill    16 年前

    0.3英寸的分辨率正在下降到几年来地震的影响程度。你可能想重新考虑一下,为什么你认为你需要在全世界范围内做出这么好的决定。

    太平洋上的一些传播中心的变化 15 cm/year .

        7
  •  3
  •   aij    11 年前

    什么编码是“最好的”实际上取决于你的目标/要求。

    如果你在执行算术,浮点纬度,经度通常是相当方便的。其他时候笛卡尔坐标(即x,y,z)可能更方便。例如,如果你只关心地球表面的点,你可以使用 n-vector .

    对于较长的存储,IEEE浮点会浪费你不关心的范围(对于LAT或Lon)或者精度,在笛卡尔坐标的情况下,你可能不关心(除非你想在任何原因下在原点上有很好的精度)。当然,您可以将任一类型的坐标映射到您喜欢大小的整数,这样,所述整数的整个范围就涵盖了您对所关心的分辨率感兴趣的范围。

    当然还有其他事情要考虑,而不仅仅是在编码中不浪费比特。例如,(geohashes)[https://en.wikipedia.org/wiki/geohash]有一个很好的属性,在同一区域很容易找到其他geohash。(大多数都有相同的前缀,您可以计算其他前缀)不幸的是,它们在赤道附近和两极附近保持相同的经度精度。我目前正在使用64位geohash进行存储,它在赤道提供了大约3米的分辨率。

    这个 Maidenhead Locator System 有一些相似的特点,但似乎更适合人与人之间的通信位置,而不是存储在计算机上。(存储mls字符串将浪费大量的位来进行一些非常简单的错误检测。)

    我发现有一个系统处理两极的方式不同 Military Grid Reference System ,尽管它看起来也更注重人与人之间的交流。(从转换到LAT/Lon似乎是一种痛苦。)

    根据您的具体需求,您可以使用类似于 Universal polar sereographic coordinate system 在极点附近还有一些比 UTM 对于世界其他地区,最多使用一个位来指示您正在使用的两个系统中的哪一个。我最多说一点,因为你所关心的大多数点不太可能在两极附近。例如,可以使用“半位”,即11表示使用极坐标系,而00、01和10表示使用另一个系统,并且是表示的一部分。

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

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

        8
  •  3
  •   John W. Phillips    9 年前

    一个Java程序,用于将米中的最大舍入误差从铸造LAT/long值转换成浮点/双值:

    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
    
        9
  •  1
  •   natevw    15 年前

    如果你所说的“存储”是指“记忆中的保存”,那么真正的问题是:你要用它们做什么?

    我猜想在这些坐标做任何有趣的事情之前,它们将以弧度的形式通过math.h.中的函数,除非你计划实现相当多的超越函数,这些函数在deg/min/secs压缩到一个位域中。

    所以,为什么不把事情简单化,以ieee-754度或弧度的精度存储在您的需求中呢?

        10
  •  1
  •   Augustin    9 年前

    以下代码将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

        11
  •  1
  •   V. Wheeler    6 年前

    好问题!

    我知道这个问题现在已经9岁了,我只知道你想要的答案的一部分,但是我来这里的时候也有一个类似的问题,自从这个问题被问到之后,很多事情都发生了变化,比如硬件和gpse可用。我经常在固件中处理不同类型应用程序中的不同类型的gpse,并且已经忘记了我花了多少时间(和天数)为不同的应用程序制定“最佳设计”,我曾经使用或开发过。

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

    CPU时间成本

    如果CPU没有内置浮点协处理器(许多微控制器都是这样),那么处理“float”、“double”和“long double”可能会非常昂贵。例如,对于我经常使用的一个16位微控制器,使用“double”值的乘法运算需要326个CPU时钟周期,除法运算需要1193个时钟周期。很贵!

    准确度权衡

    在赤道,需要表示有符号度值的“float”(ieee-754 32位浮点值),假设可以表示7个“干净”的有效小数位数,则一个最低有效小数位数(例如从179.9999到180.0000)的变化将表示大约11.12米。这可能满足也可能不满足硬系统精度要求。而“double”(表示15个“干净”的有效小数位数,因此从179.999999999999更改为180.000000000000)表示约0.00011 mm。

    输入精度限制

    如果你处理的是来自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所处的应用程序执行导航任务,该任务(已存在且经过良好测试的代码)需要“双”数据类型。不幸的是,要将这个整数转换为ieee-754 64位的“double”很难,只需将整数的基2位移动到“double”的内部表示位即可,因为要执行的十进制移位是基10十进制移位。如果它是一个基数-2的十进制移位,那么整数的基数-2位可以移动到“double”的位字段中,只需要很少的转换。但是,唉,我的有符号整数不是这样的。所以在一个没有硬件浮点处理器的CPU上,我要花费一个乘法运算:326个CPU时钟周期。

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

    注意,这个乘法选择了这个:

    ldLatitude = (double)li32LatFromGps / 10000000.0;
    

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

    如果导航任务可以直接用32位有符号整数来完成的话(如果我可以在周末抽出时间的话,将来也可能会如此),那将会是多么的精彩!那么就不需要转换了……但是用这样一个整数来做导航任务会不会花费更多呢?CPU的成本,可能更高效。开发时间成本?这是另一个问题,特别是在一个测试良好的系统已经到位,使用ieee-754 64位'双'值!另外,已经有软件提供地图数据(使用“double”度值),该软件也必须转换为使用有符号整数——而不是一夜之间的任务!

    一个非常有趣的选择是直接(无平移)使用原始经纬度整数表示“矩形”(实际上是梯形,在极点变成三角形)的近似值之间的交点。在赤道处,这些矩形将具有约1.11厘米的东西向1.11厘米的南北方向,而在英国伦敦的纬度上,其尺寸将是0.69厘米左右东西向南北1.11厘米。这可能很容易处理,也可能不容易处理,这取决于应用程序需要什么。

    不管怎样,我希望这些想法和讨论能帮助那些正在关注这个主题的人为他们的系统进行“最佳设计”。

    谨致问候, 维克

        12
  •  0
  •   bluish dmajkic    10 年前

    你可以使用 decimal datatype:

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