在C,C ++或D等系统级编程语言中,存储纬度和经度的最佳类型/编码是什么?
我看到的选项是:
deg = (360/2^32)*val
简易解决方案(FP)具有主要的缺点,它具有高度不均匀的分辨率(在英格兰的某个地方,它可以以微米为单位,在日本可以测量,但不能)。这也有FP比较等所有问题。其他选项需要在数据生命周期的不同部分进行额外的努力。 (生成,演示,计算等)
一个有趣的选择是浮动精度类型,其中随着纬度的增加,它会获得更多位,而经度会变得更少(当它们朝向极点靠近时)。
相关问题并未完全涵盖这一点:
OTOH如果32位均匀分布在地球表面,则可以对一侧约344m的正方形进行索引,5字节为21m,6B-> 1.3m,8B-> 5mm。
我现在没有特定的用途,但之前已经处理过这种事情,并且在某些时候再次期待。
答案 0 :(得分:36)
最简单的方法是将其存储为浮点数/双精度。 N和E为正,S和W为负。只需记住分数和秒数超过60(因此31 45'N为31.75)。通过查看它们很容易理解值是什么,并且在必要时,转换为弧度是微不足道的。
纬度和经度的计算,例如两个坐标之间的Great Circle距离,很大程度上依赖于三角函数,三角函数通常使用双精度。任何其他格式都将依赖于正弦,余弦,atan2和平方根的另一种实现。任意精度数字(例如Java中的BigDecimal)都不适用于此。类似于int ^ 2 32统一传播的东西会有类似的问题。
一些评论中提出了统一性。在此,我将简单地指出,地球在经度方面并不统一。北极圈的一弧秒经度比赤道的距离短。双精度浮子可在地球上的任何位置提供亚毫米级精度。这还不够吗?如果没有,为什么不呢?
同样值得注意的是您要对该信息做什么,因为您需要的计算类型会对您使用的存储格式产生影响。
答案 1 :(得分:17)
通常不知道经度和纬度比32位浮点更精确。因此,如果您担心存储空间,可以使用浮点数。但总的来说,将数字作为双精度数更方便。
Radians对理论数学更方便。 (例如,只有当你使用弧度时,正弦的导数才是余弦。)但是学位通常更熟悉,更容易被人解释,所以你可能想要坚持学位。
答案 2 :(得分:10)
根据Decimal Degrees上的维基百科文章,精度为8的十进制表示应该足够了。
0 decimal places, 1.0 = 111 km
...
7 decimal places, 0.0000001 = 1.11 cm
8 decimal places, 0.00000001 = 1.11 mm
答案 3 :(得分:4)
您提到的浮点值问题可能成为问题吗?如果答案是否定的,我建议只使用双精度的弧度值 - 无论如何你都需要进行三角计算。
如果在使用双精度时可能存在精度损失问题,或者您不会进行三角测量,我建议您将映射到整数范围的解决方案 - 这将为您提供最佳分辨率,可以轻松转换为您所使用的任何显示格式都将使用 - 在选择合适的0子午线后 - 可用于转换为高精度的浮点值。
PS:我一直想知道为什么似乎没有人使用地心球坐标 - 它们应该合理地接近地理坐标,并且不需要所有这些花哨的数学用于计算的球体;为了好玩,我想将Gauss-Krüger-Koordinaten(由德国Katasteramt使用)转换为GPS坐标 - 让我告诉你,这很丑:一个使用贝塞尔椭圆体,另一个使用WGS84,以及Gauss-Krüger映射本身就非常疯狂......
答案 4 :(得分:3)
0.3英寸的分辨率正在降低到几年的地震会产生影响的程度。您可能想重新考虑为什么您认为自己需要在全球范围内获得如此优秀的解决方案。
太平洋的一些传播中心变化与15 cm/year一样多。
答案 5 :(得分:3)
“最佳”编码真正取决于您的目标/要求。
如果你正在执行算术,浮点纬度,经度往往很方便。其他时候笛卡尔坐标(即x,y,z)可以更方便。例如,如果您只关心地球表面上的点,则可以使用n-vector。
对于长期存储,IEEE浮点将浪费您不关心的范围(对于纬度/经度)或者对于笛卡尔坐标可能不关心的精度(除非您想要非常好的精度在任何原因的原点)。您当然可以将任何类型的坐标映射到您的首选大小的整数,以便所述整数范围涵盖您感兴趣的分辨率范围。
当然还有其他事情要考虑,而不仅仅是在编码中浪费比特。例如,(Geohashes)[https://en.wikipedia.org/wiki/Geohash]具有很好的属性,很容易在同一区域找到其他地理位置。 (大多数将具有相同的前缀,并且您可以计算其他人将具有的前缀。)不幸的是,它们在靠近极点的赤道附近保持相同的经度。我目前正在使用64位地理数据进行存储,它在赤道上的分辨率约为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描述了这样一种用于编码法向量的系统,同时保持最低精度水平,但它也可以用于地理坐标。
答案 6 :(得分:2)
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.694365712453288230574085935741 = 12.945382716049382716049382716053每秒零件。
因此浮点数据类型在赤道上的精度可以达到30.87 / 12.945382716049382716049382716053~ = 2.38米。
答案 7 :(得分:2)
用于计算最大舍入误差(以米为单位)的Java程序,从将lat / long值转换为Float / Double:
_callbackUrl
输出:
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);
}
}
答案 8 :(得分:1)
如果通过“存储”来表示“留在记忆中”,那么真正的问题是:你打算用它们做什么?
我怀疑在这些坐标做任何有趣的事情之前,它们将通过math.h中的函数作为弧度漏斗。除非您计划实施相当多的超越函数,这些函数在Deg / Min / Secs上运行,并且包含在一个位域中。
那么为什么不保持简单,只需按照您的要求精度将它们存储在IEEE-754度或弧度中?
答案 9 :(得分: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);
}
}
}
答案 10 :(得分:1)
好问题!
我知道这个问题已经9岁了,我只知道您要寻找的答案的一部分,但是我来这里时遇到的是一个类似的问题,自问这个问题以来,很多事情已经发生了变化,例如硬件和GPSes。我经常在处理与不同应用程序中的各种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的输入,您将获得多少位数的真实精度,并且需要保留几位数?
开发时间成本
在C语言中,IEEE-754的64位双精度值('double')和32位单精度值('float')非常方便处理,因为这两者的数学库实际上都带有每个C编译器,通常都很可靠。如果您的CPU带有硬件浮点处理器,这是一个简单的选择。
RAM和存储成本
如果您必须在RAM(或存储,例如MYSQL)中保留大量这些值,则可用的RAM(和存储空间)可能会影响解决方案的可操作性。
可用数据与必需数据
在撰写本文时,我正在处理的一个示例(来到这个问题的原因)是我正在处理u-blox M8 GPS,它能够为我提供二进制GPS信息(节省了CPU开销)。翻译ASCII NMEA句子)。在这种二进制格式(称为“ UBX协议”)中,纬度和经度表示为带符号的32位整数,该表示形式能够表示(在赤道处)低至约1.11厘米的精度。例如,经度-105.0269805表示为-1050269805(使用所有32位),一个LSb的变化表示任何地方的纬度变化约为1.11厘米,在赤道处的经度约为1.11厘米(在高纬度处则较小,与余弦成比例)纬度)。该GPS所用于的应用程序执行导航任务,该导航任务(已经存在且经过测试的代码)需要“双重”数据类型。不幸的是,仅通过将整数的基数2位移动到“ double”的内部表示位中,就无法轻松地将该整数转换为IEEE-754 64位“ double”,因为要执行的十进制移位是以10为基数的十进制移位。取而代之的是以2为基数的十进制移位,那么整数的以2为基数的位数可以移到'double'的位域中,而只需很少的转换。但是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厘米。根据应用程序的需求,这可能不容易解决。
无论如何,我希望这些想法和讨论能为那些正在为该系统寻求“最佳设计”的人提供帮助。
亲切的问候, 维克
答案 11 :(得分:0)
您可以使用decimal
数据类型:
CREATE TABLE IF NOT EXISTS `map` (
`latitude` decimal(18,15) DEFAULT NULL,
`longitude` decimal(18,15) DEFAULT NULL
);
答案 12 :(得分: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位小数位数,这比任何人所需要的精度都要高(请参见幽默的示例)。
答案 13 :(得分:0)
正如@Roland Pihlakas 已经指出的,这取决于您使用坐标的精度。
我只是建议另一种观点:
float32
包含 23 个有效位 => ~4.7 米精度float64
包含 52 个有效位 => < 1 毫米精度答案 14 :(得分:0)
最小尺寸的最佳精度是 int32。
存储 7 个小数位(1.11 厘米误差)经度双数给你一个 +/-1.800.000.000 数,完美地存储在 int32 中,你只需要将双数乘以 10M 就像
int32_t lng = (int32_t)(double_lng * 10000000);
说明 (wikipedia)
赤道分为 360 度经度,因此赤道上的每一度代表 111,319.5 m(111.32 公里)。然而,当人们从赤道向极点移动时,经度乘以纬度的余弦,距离减小,在极点处接近零。赤道上 1cm 精度所需的小数位数为 7。如果您需要将 180º 与 7 个小数位存储在一个整数中,结果将是 1.800.000.000 数字,在 32 位整数范围内。
正如您在 Google 地图中看到的,当您点击任何地方时,Golge 会为您提供一个小数点后 6 位的浮点数,适合 32 位整数。
对比: