在 MySQL 数据库中存储纬度/经度时使用的理想数据类型是啥?
Posted
技术标签:
【中文标题】在 MySQL 数据库中存储纬度/经度时使用的理想数据类型是啥?【英文标题】:What is the ideal data type to use when storing latitude / longitude in a MySQL database?在 MySQL 数据库中存储纬度/经度时使用的理想数据类型是什么? 【发布时间】:2010-09-14 15:03:28 【问题描述】:考虑到我将在纬度/经度对上执行计算,哪种数据类型最适合与 mysql 数据库一起使用?
【问题讨论】:
我发现这个链接非常有用:howto-use-mysql-spatial-ext.blogspot.com/2007/11/… 它可能有点旧,但它包含一个完整的解释,包括示例。 恕我直言,这里的大多数人都不明白发生了什么。只要应用程序代码接触一个数字,只要使用双精度数(大多数情况下都是这样),该数字最多变成双精度数。然后用一百万个小数存储它不会有任何好处。使用有限个小数(例如6)破坏该精度的一部分并添加累积错误每次将其重新写入数据库时。 double 携带 ca 16 个有效数字,可能是所有小数。随着时间的推移,废弃其中的 10 个会产生累积的错误。出于某种原因,它是“浮点”。续。 续:当存储从外部来源获取的、未更改的且第一次作为源材料的数字时,6 位小数可能是可以的。但是,如果对它执行一次计算并再次存储它,那么通过强制执行特定的十进制格式来删除部分精度是愚蠢。仅在服务器内部执行计算可能会有所不同(服务器可能会或可能不会在内部使用除 double 之外的其他东西),并且在 c 的应用程序计算中使用比 double 更差的数字表示同样减少了对存储精度的需求。 续:如果服务器以 更高 精度存储数字,尽管声称“9.6”(我不知道它是否确实如此) ),那么所有这些都不重要,格式纯粹是为了方便 - 与精度问题无关。但是,如果服务器实际上使用该格式将任何数字四舍五入为 6 位小数精度,我不会感到惊讶。 续:最后:对于 lat,lon's,第 6 位小数是 捕捉 到 ca 的问题。 11 厘米网格。每次读取(触摸)、计算和存储时,使用 6 位小数,将有一个新的捕捉(= 累积误差)。如果所有的错误都发生在同一个方向,就会出现一个big错误。如果对其执行临时乘法(例如,放大,然后减去和缩小),它可能会变得更大。没有好的理由,不要放弃精度! 【参考方案1】:在 GIS 中使用 MySQL 的 spatial extensions。
【讨论】:
MYSQL Spatial 是一个不错的选择,但仍然有很大的限制和警告(截至 6)。请在下面查看我的答案... @James Schek 是对的。此外,MySQL 使用欧几里得几何进行所有计算,因此它并不代表 lat/lng 的实际用例。 仅供参考; Mysql 仅支持 *.myisam 表的空间索引,即 ISAM 引擎。链接:dev.mysql.com/doc/refman/5.0/en/creating-spatial-indexes.html 看这篇文章最后更新部分:mysqlserverteam.com/mysql-5-7-and-gis-an-example 这个答案怎么会得到这么多票,单行答案没有例子!【参考方案2】:基本上,这取决于您的位置所需的精度。使用 DOUBLE,您将获得 3.5nm 的精度。 DECIMAL(8,6)/(9,6) 下降到 16 厘米。 FLOAT 是 1.7m...
这个非常有趣的表有一个更完整的列表:http://mysql.rjweb.org/doc.php/latlng:
Datatype Bytes Resolution
Deg*100 (SMALLINT) 4 1570 m 1.0 mi Cities
DECIMAL(4,2)/(5,2) 5 1570 m 1.0 mi Cities
SMALLINT scaled 4 682 m 0.4 mi Cities
Deg*10000 (MEDIUMINT) 6 16 m 52 ft Houses/Businesses
DECIMAL(6,4)/(7,4) 7 16 m 52 ft Houses/Businesses
MEDIUMINT scaled 6 2.7 m 8.8 ft
FLOAT 8 1.7 m 5.6 ft
DECIMAL(8,6)/(9,6) 9 16cm 1/2 ft Friends in a mall
Deg*10000000 (INT) 8 16mm 5/8 in Marbles
DOUBLE 16 3.5nm ... Fleas on a dog
希望这会有所帮助。
【讨论】:
我需要针对帖子的内容写一篇建设性的、详细的评论,所以我会说,在观察 Rick James 网站提供的准确度表的同时,我对这个决议感到有点好笑描述“狗身上的跳蚤”,觉得值得称赞。从技术上讲,这是一个有用的描述,它帮助我决定在存储坐标以测量两个地址之间的距离时使用什么数据类型,@Simon,我要感谢你的分享。 FWIW,该链接对“SMALLINT scaled”的使用效率极低。 Oguzhan's answer 是在 4 字节有符号整数中存储小数点后 7 位的 long/lat 的好方法。小尺寸 (4B) 中的高精度 (~1cm)。 字节列准确吗? MySQL reference 说DOUBLE
是 8 个字节。
要在这里回答我自己的问题,我猜他们已经将每种类型的字节数加倍以考虑纬度 + 经度(即2 doubles == 16 bytes
)。【参考方案3】:
Google 为带有 Google 地图的示例“商店定位器”应用程序提供了一个从头到尾的 PHP/MySQL 解决方案。在此示例中,它们将 lat/lng 值存储为“Float”,长度为“10,6”
http://code.google.com/apis/maps/articles/phpsqlsearch.html
【讨论】:
Google 显然不了解 FLOAT 规范的工作原理:FLOAT(10,6)
为坐标的整数部分留下 4 位数字。不,符号不算数 - 它来自 (un)signed 属性。
但是,如果您需要将 [0, 180] 中的值存储为整数部分,应该就足够了,对吧?
@AlixAxel 我认为谷歌知道它在做什么。因为它声明:“使用 Google 地图当前的缩放功能,您应该只需要小数点后 6 位精度。这将使字段存储小数点后 6 位,再加上小数点前最多 4 位,例如 -123.456789 度。"。如果选中 unsigned,则模式将为 1234,567890。所以没问题。
@AlixAxel 他正在数序列中的数字;不使用实际坐标...
对 Laravel 使用数据类型 Double
【参考方案4】:
MySQL 的空间扩展是最佳选择,因为您可以使用空间运算符和索引的完整列表。空间索引将允许您非常快速地执行基于距离的计算。请记住,从 6.0 开始,空间扩展仍然不完整。我并不是要贬低 MySQL Spatial,只是让你在你走得太远之前知道其中的陷阱。
如果您严格处理点并且只处理 DISTANCE 函数,这很好。如果您需要使用多边形、线或缓冲点进行任何计算,除非您使用“相关”运算符,否则空间运算符不会提供准确的结果。请参阅21.5.6 顶部的警告。包含、内部或相交等关系使用的是 MBR,而不是确切的几何形状(即椭圆被视为矩形)。
此外,MySQL Spatial 中的距离与您的第一个几何图形的单位相同。这意味着如果您使用十进制度,那么您的距离测量值是十进制度。当您远离赤道时,这将很难获得准确的结果。
【讨论】:
重申:MySQL 空间扩展不适用于计算地球表面上由纬度/经度表示的点之间的大圆距离。它们的距离函数等仅对笛卡尔、平面、坐标有用。 上面评价很高的笔记似乎已经过时几年了。从 mysql 5.7 开始,ST_Distance_Sphere
可以做到这一点。【参考方案5】:
当我为从 ARINC424 构建的导航数据库执行此操作时,我进行了大量测试并回顾代码,我使用了 DECIMAL(18,12)(实际上是 NUMERIC(18,12),因为它是 firebird )。
浮点数和双精度数不那么精确,可能会导致舍入错误,这可能是一件非常糟糕的事情。我不记得我是否发现任何有问题的真实数据 - 但我相当确定无法准确存储在浮点数或双精度数中可能会导致问题
关键是,当使用度数或弧度时,我们知道值的范围 - 小数部分需要最多的数字。
MySQL Spatial Extensions 是一个不错的选择,因为它们关注The OpenGIS Geometry Model。我没有使用它们,因为我需要保持我的数据库可移植。
【讨论】:
谢谢,这很有帮助。从 2008 年开始阅读所有这些问题和答案,感觉很奇怪,因为这已经是 8 年前了。 @TheSexiestManinJamaica - 在 IEEE 754-1985 之前,计算机浮点硬件是混乱的。甚至在机器上a*b
不等于b*a
(对于某些值)。有很多例子有点像:2+2 = 3.9999
。该标准清理了很多混乱,并被几乎所有硬件和软件“迅速”采用。因此,这种讨论是有效的,不仅是从 2008 年开始,而且已经持续了三分之一个世纪。【参考方案6】:
取决于您需要的精度。
Datatype Bytes resolution
------------------ ----- --------------------------------
Deg*100 (SMALLINT) 4 1570 m 1.0 mi Cities
DECIMAL(4,2)/(5,2) 5 1570 m 1.0 mi Cities
SMALLINT scaled 4 682 m 0.4 mi Cities
Deg*10000 (MEDIUMINT) 6 16 m 52 ft Houses/Businesses
DECIMAL(6,4)/(7,4) 7 16 m 52 ft Houses/Businesses
MEDIUMINT scaled 6 2.7 m 8.8 ft
FLOAT 8 1.7 m 5.6 ft
DECIMAL(8,6)/(9,6) 9 16cm 1/2 ft Friends in a mall
Deg*10000000 (INT) 8 16mm 5/8 in Marbles
DOUBLE 16 3.5nm ... Fleas on a dog
发件人:http://mysql.rjweb.org/doc.php/latlng
总结一下:
最精确的可用选项是DOUBLE
。
最常见的类型是DECIMAL(8,6)/(9,6)
。
从MySQL 5.7 开始,考虑使用Spatial Data Types (SDT),特别是POINT
来存储单个坐标。在 5.7 之前,SDT 不支持索引(表类型为 MyISAM 时,5.6 除外)。
注意:
使用POINT
类时,存储坐标的参数顺序必须为POINT(latitude, longitude)
。
creating a spatial index 有一个特殊的语法。
使用 SDT 的最大好处是您可以访问Spatial Analyses Functions,例如计算两点之间的距离 (ST_Distance
) 并确定一个点是否包含在另一个区域内 (ST_Contains
)。
【讨论】:
您复制粘贴了先前答案的一部分并“总结”了创建该表的人不推荐的内容:«如何分区?嗯,MySQL 很挑剔。所以 FLOAT/DOUBLE 出来了。十进制已出。所以,我们陷入了一些混乱。本质上,我们需要将 Lat/Lng 转换为某种大小的 INT 并使用 PARTITION BY RANGE。» AND «FLOAT 有 24 个有效位; DOUBLE 有 53 个。(它们不与 PARTITIONing 一起使用,但为了完整性而包含在内。通常人们在使用 DOUBLE 时没有意识到它有多大的杀伤力,以及它占用了多少空间。)» 离开你写的 SDT 部分。 @Armfoot 如果您查看编辑时间,这是从我那里复制的另一个答案。没关系:我看到 Stack Overflow 更像是“为未来的我做的笔记”。 不,他没有从您那里复制,他只是像您从他在 2014 年引用的链接中一样粘贴表格(您的帖子来自 2015 年)。顺便说一句,我认为您在链接 Spatial 数据类型时拼错了“Special”。您编写的这部分实际上对想要开始使用它们的人很有用,如果您添加更多示例,例如CREATE TABLE geom (g GEOMETRY NOT NULL, SPATIAL INDEX(g)) ENGINE=MyISAM;
和有关 SDT 限制的警告,例如 James mentioned,也许您的答案会更简洁准确地帮助其他人人也是……
@Gajus - 很荣幸你们两个找到了我的文件! (不,我不知道跳蚤有多大,但我觉得它会引起别人的注意。)
使用POINT类时,存储坐标的参数顺序必须是POINT(longitude/X, latitude/Y)。【参考方案7】:
基于此 wiki 文章 http://en.wikipedia.org/wiki/Decimal_degrees#Accuracy MySQL 中合适的数据类型是 Decimal(9,6),用于存储经度和纬度 单独的字段。
【讨论】:
【参考方案8】:使用DECIMAL(8,6)
表示纬度(90 到 -90 度),使用 DECIMAL(9,6)
表示经度(180 到 -180 度)。 6 位小数适用于大多数应用程序。两者都应该“签名”以允许负值。
【讨论】:
DECIMAL
类型用于不接受 floor/ceil
的财务计算。普通的FLOAT
明显优于DECIMAL
。
@Kondybas - 由于数据库中的主要成本是获取行,因此浮点数和十进制数之间的性能差异应该不是问题。【参考方案9】:
不用走太远,根据谷歌地图,lat 和 lng 最好是 FLOAT(10,6)。
【讨论】:
你从哪里得到这些我找不到的信息?以防万一发生变化。 @webfacer,这里是“在 MySQL 中创建表”部分:developers.google.com/maps/documentation/javascript/… 例如lat FLOAT( 10, 6 ) NOT NULL,
lng FLOAT( 10, 6 ) NOT NULL
@webfacer,从mysql 8.0.17
开始,FLOAT
语法似乎已被弃用。 Mysql 现在建议只使用FLOAT
不带任何精度参数dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html 和dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
而且,MySQL 总是忽略括号中的数字,这些数字可以选择性地装饰 FLOAT 和 DOUBLE 声明。【参考方案10】:
我们在 oracle 数据库中将纬度/经度 X 1,000,000 存储为 NUMBERS 以避免双精度数的舍入错误。
考虑到小数点后 6 位的纬度/经度精度为 10 厘米,这正是我们所需要的。许多其他数据库也将 lat/long 存储到小数点后 6 位。
【讨论】:
如果您有大量数据,则乘以某个大数(例如一百万)非常好,因为整数运算(例如索引检索)比浮点数快得多。 @KaitlinDuckSherwood - 位就是位 - 我不知道 32 位浮点数的检索(索引或其他方式)比 32 位整数慢的任何原因。如今,即使是浮动数学也足够快,不会成为问题。尽管如此,我同意将隐含乘数与整数一起使用的评论:它最大限度地提高了 32 位的精度。随着技术的进步,有点面向未来。【参考方案11】:从一个完全不同的更简单的角度来看:
如果您依靠 Google 来显示您的地图、标记、多边形等,那么请让 Google 来完成计算! 您将资源保存在您的服务器上,您只需将纬度和经度一起存储为单个字符串 (VARCHAR
),例如:“-0000.0000001,-0000.000000000000001”(长度为 35,如果一个数字有超过 7 个十进制数字,那么它被四舍五入);
如果 Google 为每个数字返回超过 7 个十进制数字,则无论如何您都可以将该数据存储在您的字符串中,以防万一您想检测一些 flees or microbes in the future;
您可以使用他们的distance matrix 或geometry library 来计算距离,或者使用detecting points in certain areas 调用,就像这样简单:google.maps.geometry.poly.containsLocation(latLng, bermudaTrianglePolygon))
有很多“服务器端”API 可供您使用(Python、Ruby on Rails、PHP、CodeIgniter、Laravel、Yii、Zend Framework 等)使用 Google Maps API。
这样您就不必担心索引数字以及与数据类型相关的所有其他问题,这些问题可能会破坏您的坐标。
【讨论】:
不好。 OP 说他将对 lat/lng 对进行计算 - 你的答案排除了【参考方案12】:TL;DR
如果您不是在 NASA/军方工作,也不是制造飞机导航系统,请使用 FLOAT(8,5)。
要全面回答您的问题,您需要考虑几件事:
格式
度分秒:40° 26′ 46″ N 79° 58′ 56″ W 度十进制分:40° 26.767′ N 79° 58.933′ W 十进制度 1:40.446° N 79.982° W 十进制度 2:-32.60875、21.27812 其他一些自制格式?没有人禁止您制作自己的以家为中心的坐标系并将其存储为与您家的航向和距离。对于您正在处理的某些特定问题,这可能是有意义的。所以答案的第一部分是 - 您可以将坐标存储在应用程序使用的格式中,以避免不断的来回转换并进行更简单的 SQL 查询。
您很可能使用 Google Maps 或 OSM 来显示您的数据,而 GMaps 使用“十进制度 2”格式。因此以相同格式存储坐标会更容易。
精度
然后,您想定义所需的精度。当然,您可以存储诸如“-32.608697550570334,21.278081997935146”之类的坐标,但是您在导航到该点时是否关心过毫米?如果您不是在 NASA 工作,也不是在研究卫星、火箭或飞机的轨迹,那么几米的精度应该没问题。
常用的格式是点后 5 位数字,精度为 50 厘米。
示例:X,21.2780818和X,21.2780819之间有1cm的距离。因此,点后的 7 位数字为您提供 1/2 厘米的精度,点后的 5 位数字为您提供 1/2 米的精度(因为不同点之间的最小距离为 1m,因此舍入误差不能超过一半)。对于大多数民用目的来说,这应该足够了。
度十进制分钟格式(40° 26.767′ N 79° 58.933′ W)为您提供与点后 5 位数字完全相同的精度
节省空间的存储
如果您选择了十进制格式,那么您的坐标是一对(-32.60875、21.27812)。显然,2 x(符号 1 位,度数 2 位,指数 5 位)就足够了。
所以在这里我想支持来自 cmets 的 Alix Axel 说 Google 建议将其存储在 FLOAT(10,6) 中确实是额外的,因为您不需要 4 位数字作为 main部分(因为符号是分开的,纬度限制为 90,经度限制为 180)。您可以轻松地将 FLOAT(8,5) 用于 1/2m 精度或 FLOAT(9,6) 用于 50/2cm 精度。或者您甚至可以将 lat 和 long 存储在单独的类型中,因为 FLOAT(7,5) 足以存储 lat。请参阅 MySQL 浮点类型 reference。它们中的任何一个都会像普通的 FLOAT 一样,无论如何都等于 4 个字节。
现在空间通常不是问题,但如果您出于某种原因想要真正优化存储(免责声明:不要进行预优化),您可以压缩 lat(不超过 91 000 个值 + 符号)+ long(不超过 181 000 个值 + 符号)到 21 位,明显小于 2xFLOAT(8 字节 == 64 位)
【讨论】:
不只是 NASA 需要高精度。土木工程师和建筑商也需要它,否则你会在停车场和建筑物中遇到大水坑,所有的大理石都滚到角落里。但测量员并不依赖标准的手机级 GPS。对于标准 GPS,FLOAT(IEEE488 32 位浮点格式)具有足够的精度。【参考方案13】:根据您的应用程序,我建议使用 FLOAT(9,6)
空间键将为您提供更多功能,但在生产基准测试中,浮点数比空间键快得多。 (0,01 VS 0,001 在 AVG)
【讨论】:
你能在这里提供你的测试结果吗?【参考方案14】:MySQL 对所有浮点数使用 double ... 所以使用双精度型。在大多数情况下,使用浮点数会导致不可预测的舍入值
【讨论】:
MySQL 在DOUBLE
中执行操作。 MySQL 允许您将数据存储为 4 字节 FLOAT
或 8 字节 DOUBLE
。因此,将表达式存储到 FLOAT
列时可能会丢失精度。【参考方案15】:
虽然它并非对所有操作都是最佳的,但如果您正在制作地图图块或使用只有一个投影的大量标记(点)(例如墨卡托,如谷歌地图和许多其他滑动地图框架所期望的),我发现我所说的“大坐标系”非常非常方便。基本上,您以某种方式放大存储 x 和 y 像素坐标——我使用缩放级别 23。这有几个好处:
您只需执行一次昂贵的 lat/lng 到墨卡托像素转换,而不是每次处理该点时 从给定缩放级别的记录中获取图块坐标需要右移一次。 从记录中获取像素坐标需要一次右移和一次按位与。 移位非常轻量级,可以在 SQL 中执行,这意味着您可以执行 DISTINCT 以每个像素位置仅返回一条记录,这将减少后端返回的记录数,这意味着更少在前端处理。我在最近的一篇博文中谈到了这一切: http://blog.webfoot.com/2013/03/12/optimizing-map-tile-generation/
【讨论】:
【参考方案16】:纬度范围从 -90 到 +90(度),因此 DECIMAL(10, 8) 是可以的
经度范围从 -180 到 +180(度),因此您需要 DECIMAL(11, 8)。
注意:第一个数字是存储的总位数,第二个是小数点后的数字。
简而言之:lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL
【讨论】:
【参考方案17】:PostGIS 中的空间函数比 MySQL 空间函数中的函数更实用(即不受 BBOX 操作的限制)。看看吧:link text
【讨论】:
【参考方案18】:我建议您对 SQL Server 使用 Float 数据类型。
【讨论】:
【参考方案19】:存储 Lat Long 值的理想数据类型是小数(9,6)
这大约是 10 厘米的精度,同时仅使用 5 个字节的存储空间。
例如CAST(123.456789 作为十进制(9,6))
【讨论】:
【参考方案20】:Lat Long 计算需要精度,因此请使用某种类型的小数类型并使精度至少比您要存储的数字高 2 以执行数学计算。我不知道我的 sql 数据类型,但在 SQL Server 中,人们经常使用浮点数或实数而不是十进制数并遇到麻烦,因为这些是估计数字而不是真实数字。所以只要确保你使用的数据类型是真正的十进制类型而不是浮点十进制类型就可以了。
【讨论】:
浮点型和小数型都有它们的位置。根据经验,浮点数表示物理变量,小数表示可数实体(主要是金钱)。我不明白为什么你更喜欢小数作为纬度/经度 我也认为浮点数适用于纬度/经度。至少在 SQL Server 上(4 字节,7 位)。 float估计不准确,lat long中的准确湖是致命的!它可以将您指向地球上一个完全不同的地方。 浮点数据类型的最大错误足够低,这应该不是问题。我的意思是,无论如何,您都必须注意两种实现的错误乘法/累积。 @HLGEM - 四舍五入到一些 小数 位也会使您在地球上的不同位置。问题是那个不同的地点是否如此接近以至于无关紧要。【参考方案21】:@987654321@
应该为您提供所需的所有精度,并且比将每个坐标存储为字符串等更适合比较函数。
如果您的 MySQL 版本早于 5.0.3,您可能需要注意某些 floating point comparison errors。
在 MySQL 5.0.3 之前,DECIMAL 列以精确的精度存储值,因为它们表示为字符串,但 DECIMAL 值的计算是使用浮点运算完成的。从 5.0.3 开始,MySQL 以 64 位十进制数字的精度执行 DECIMAL 运算,这应该可以解决涉及 DECIMAL 列时最常见的不准确问题
【讨论】:
您需要一个真正的纬度/经度坐标数据类型以便于计算。想象一下类似于“select * from stores where distance(stores.location, mylocation) 之前没听说过空间扩展,听上去挺方便的好吧,之前做过一个继承的应用,做了很多地理相关的计算,一定要看看。跨度> @ConroyP - 不。那句话指出DECIMAL
(在 5.0.3 之前)由于使用浮动实现而存在某些错误。以上是关于在 MySQL 数据库中存储纬度/经度时使用的理想数据类型是啥?的主要内容,如果未能解决你的问题,请参考以下文章
需要使用 PHP 将 android app 生成的纬度和经度值存储到 mySQL 数据库中