纬度/经度(小数点后8位)应使用哪种MySQL数据类型?


257

我正在处理地图数据,并且Latitude/Longitude扩展到了8个小数位。例如:

Latitude 40.71727401
Longitude -74.00898606

我在Google文档中看到了 使用:

lat FLOAT( 10, 6 ) NOT NULL,  
lng FLOAT( 10, 6 ) NOT NULL

但是,它们的小数位仅移至6。
我应该使用FLOAT(10, 8)还是应该考虑使用另一种方法来存储此数据,以便精确。它将与地图计算一起使用。谢谢!


4
您是否真的需要将精确到1.1mm的值存储在地球表面上?如果是这样,那为什么首先要在latlng中存储值?
ovangle


2
Google文档是错误的!请勿使用float仅7位精度的类型。您至少需要9。您不需要10-出于某种奇怪的原因,文档将减号记为数字。请执行以下任一操作:double(9,6)decimal(9,6)
阿里埃勒(Ariel)2016年

5
真正需要多少精度?小数点后6位使您有足够的精度来区分两个彼此亲吻的人。8可以将手指分开。 FLOAT区分两个相距1.7m(5.6ft)的项目。对于“地图”应用程序而言,所有这些都太可笑了!
瑞克·詹姆斯

Answers:


594

DECIMAL是用于精确算术的MySQL数据类型。与FLOAT不同,它的精度对于任何大小的数字都是固定的,因此通过使用它代替FLOAT,可以在进行某些计算时避免精度误差。如果您只是在不进行计算的情况下存储和检索数字,那么在实践中FLOAT是安全的,尽管使用DECIMAL不会造成任何危害。经过计算,FLOAT基本上还是可以的,但是绝对要保证8d.p。精度,您应该使用DECIMAL。

纬度的范围是-90至+90(度),因此可以使用DECIMAL(10,8),但是经度的范围是-180至+180(度),因此您需要DECIMAL(11,8)。第一个数字是存储的总位数,第二个数字是小数点后的数字。

简而言之: lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL

解释了MySQL如何与浮点数据类型一起工作。

更新: MySQL支持空间数据类型,并且Point是可以使用的单值类型。例:

CREATE TABLE `buildings` (
  `coordinate` POINT NOT NULL,
  /* Even from v5.7.5 you can define an index for it */
  SPATIAL INDEX `SPATIAL` (`coordinate`)
) ENGINE=InnoDB;

/* then for insertion you can */
INSERT INTO `buildings` 
(`coordinate`) 
VALUES
(POINT(40.71727401 -74.00898606));

11
也许我的回答误用了精确一词,因为DECIMAL仍然仅与您给出的精度一样准确。我的意思是,它如此准确。当然,一些计算会扩展误差。如果我有一个DECMIAL x,那么sin(x ^ 100)将会消失。但是,如果(使用DECIMAL(10,8)或FLOAT(10,8))我计算0.3 / 3,则DECIMAL给出0.100000000000(正确),而float给出0.100000003974(正确到8dp,但是如果相乘则是错误的)。我了解主要区别在于数字的存储方式。DECIMAL存储十进制数字,FLOAT存储二进制近似值。
gandaliter 2012年

1
出于对精度的怀疑,我将进行双精度。
拉塔塔塔塔2014年

1
小数点后8位为1.1毫米(小于1/16英寸)精度。为什么您需要纬度和经度呢?
vartec

1
Facebook似乎最多使用12位小数点表示经纬度,使用13位小数表示经纬度。vartec写道,小数点后8位等于1.1毫米;那7和6呢?(我不擅长数学)。我现在正在使用double,但想检查是否可以通过更改类型来获得距离计算。谢谢。
Alain Zelink '16

4
该问题的答案(gis.stackexchange.com/questions/8650/…)提供了有关使用不同纬度和经度小数位数获得的精度的信息。
gandaliter'3

16

此外,您将看到float值被四舍五入。

//例如:给定值41.0473112,29.0077011

float(11,7)| 十进制(11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
您可以使用double具有所需精度的数据类型。
阿里埃勒(Ariel)2016年

1
给我看一个有用的地图,可以区分这两点。我声称这两种表述都是“不必要的精确”。
瑞克·詹姆斯


7

您可以将数据类型设置为有符号整数。将坐标存储为SQL时,可以设置为lat * 10000000和long * 10000000。并且当您选择距离/半径时,会将存储坐标划分为10000000。我用300K行进行了测试,查询响应时间很好。(2 x 2.67GHz CPU,2 GB RAM,MySQL 5.5.49)


哪个更快?这样做还是使用float或小数?
Dinidiniz

1
@Dinidiniz-速度差异很小。提取行使任何数据库操作的时间都不堪重负。
瑞克·詹姆斯

为什么是一千万?如果小数点后的位数超过6位,该怎么办?否则它将始终返回6个小数点。
Mahbub Morshed

@MahbubMorshed-您的意思是7位数字-显示7个零数字。但是,是的,这种技术总是存储准确的7位数字,仅此而已。(如果使用4字节整数,则不能将乘数增加到7位以上,因为经度值可以高达180,并且必须避免溢出有符号整数最大值。)这比以单精度浮点数存储的精度高2位,在大经度值时,小数点右边的数字只有约5位数。);(179.99996是安全地远离179.99998 179.99998和179.99997可以存储为相同的浮点值)。
ToolmakerSteve

这是我在任何地方都看到过的最佳折衷方案。在这里,我显示了要使用的代码,并确认代码提供了小数点后7位数字(以4字节有符号int表示),以表示long / lat值(因此在-180 .. + 180范围内)。尺寸小(4B),精度高(〜1cm)。
ToolmakerSteve

6

不要使用float ...它将使您的坐标变圆,从而导致一些奇怪的情况。

使用小数



4

我相信在MySQL中存储Lat / Lng的最佳方法是使用带有SPATIAL索引的POINT列(2D数据类型)。

CREATE TABLE `cities` (
  `zip` varchar(8) NOT NULL,
  `country` varchar (2) GENERATED ALWAYS AS (SUBSTRING(`zip`, 1, 2)) STORED,
  `city` varchar(30) NOT NULL,
  `centre` point NOT NULL,
  PRIMARY KEY (`zip`),
  KEY `country` (`country`),
  KEY `city` (`city`),
  SPATIAL KEY `centre` (`centre`)
) ENGINE=InnoDB;


INSERT INTO `cities` (`zip`, `city`, `centre`) VALUES
('CZ-10000', 'Prague', POINT(50.0755381, 14.4378005));

0

在导轨上使用迁移红宝石

class CreateNeighborhoods < ActiveRecord::Migration[5.0]
  def change
    create_table :neighborhoods do |t|
      t.string :name
      t.decimal :latitude, precision: 15, scale: 13
      t.decimal :longitude, precision: 15, scale: 13
      t.references :country, foreign_key: true
      t.references :state, foreign_key: true
      t.references :city, foreign_key: true

      t.timestamps
    end
  end
end

这不会将经度限制为-99..99吗?这排除了太平洋的大部分地区!
瑞克·詹姆斯

这是一个不应被视为绝对真理的例子。您可以使用其他DECIMAL十进制精度(20,18),依此类推...如果需要保存地理和空间数据,则可以使用postgis数据库来实现此目的。MySQL Spatial Extensions是一个很好的选择,因为它们遵循OpenGIS Geometry Model。我没有使用它们,因为我需要保持数据库的可移植性。postgis.net
gilcierweb

(20,18)也最高可达+/- 99。
瑞克·詹姆斯

这是一个不应被视为绝对真理的例子。您可以使用其他DECIMAL十进制精度(20,18),依此类推...如果需要保存地理和空间数据,则可以使用postgis数据库来实现此目的。MySQL Spatial Extensions是一个很好的选择,因为它们遵循OpenGIS Geometry Model。我没有使用它们,因为我需要保持数据库的可移植性。postgis.net
gilcierweb

杜德(Dude)只是一个例子,您可以使用所需的精度,如果小数点不能帮助您使用postgis,则该数据库仅用于地理和空间数据
gilcierweb

-1

使用/证明OğuzhanKURNUÇ答案的精度的代码

摘要:
尺寸小(4B),精度高(〜1cm)。

对于[-180,180]范围内的值,精度为(非常接近)7个十进制数字。
7位到小数点右侧(〜1厘米)的,对于总共9个位(或10位数字,如果计数的“180”的初始“1”)附近+ -180。
将此与一个4字节的float进行比较,该float总共只有〜7位数字,因此在+ = 180 (〜1m)附近的小数点右边为〜5位数字。

使用此方法的方法:

const double Fixed7Mult = 10000000;

public static int DecimalDegreesToFixed7(double degrees)
{
    return RoundToInt(degrees * Fixed7Mult);
}

public static double Fixed7ToDecimalDegrees(int fixed7)
{
    return fixed7 / (double)Fixed7Mult;
}

精度测试:

/// <summary>
/// This test barely fails in 7th digit to right of decimal point (0.0000001 as delta).
/// Passes with 0.0000002 as delta.
/// </summary>
internal static void TEST2A_LatLongPrecision()
{
    //VERY_SLOW_TEST Test2A_ForRange(-180, 360, 0.0000001);
    //FAILS Test2A_ForRange(-180, 0.1, 0.0000001);

    Test2A_ForRange(-180, 0.1, 0.0000002);
    Test2A_ForRange(0, 0.1, 0.0000002);
    Test2A_ForRange(179.9, 0.1, 0.0000002);
}

/// <summary>
/// Test for the smallest difference.  A: 9.9999994E-08.
/// </summary>
internal static void TEST2B_LatLongPrecision()
{
    double minDelta = double.MaxValue;
    double vAtMinDelta = 0;
    //VERY_SLOW_TEST Test2B_ForRange(-180, 360, ref minDelta, ref vAtMinDelta);
    Test2B_ForRange(-180, 0.1, ref minDelta, ref vAtMinDelta);
    Test2B_ForRange(0, 0.1, ref minDelta, ref vAtMinDelta);
    Test2B_ForRange(179.9, 0.1, ref minDelta, ref vAtMinDelta);

    // Fails. Smallest delta is 9.9999994E-08; due to slight rounding error in 7th decimal digit.
    //if (minDelta < 0.0000001)
    //  throw new InvalidProgramException($"Fixed7 has less than 7 decimal digits near {vAtMinDelta}");

    // Passes.
    if (minDelta < 0.000000099)
        throw new InvalidProgramException($"Fixed7 has less than 7 decimal digits near {vAtMinDelta}");
}

测试使用的辅助方法:

private static void Test2A_ForRange(double minV, double range, double deltaV)
{
    double prevV = 0;
    int prevFixed7 = 0;
    bool firstTime = true;
    double maxV = minV + range;
    for (double v = minV; v <= maxV; v += deltaV) {
        int fixed7 = DecimalDegreesToFixed7(v);
        if (firstTime)
            firstTime = false;
        else {
            // Check for failure to distinguish two values that differ only in 7th decimal digit.
            // Fails.
            if (fixed7 == prevFixed7)
                throw new InvalidProgramException($"Fixed7 doesn't distinguish between {prevV} and {v}");
        }
        prevV = v;
        prevFixed7 = fixed7;
    }
}

private static void Test2B_ForRange(double minV, double range, ref double minDelta, ref double vAtMinDelta)
{
    int minFixed7 = DecimalDegreesToFixed7(minV);
    int maxFixed7 = DecimalDegreesToFixed7(minV + range);

    bool firstTime = true;
    double prevV = 0;   // Initial value is ignored.
    for (int fixed7 = minFixed7; fixed7 < maxFixed7; fixed7++) {
        double v = Fixed7ToDecimalDegrees(fixed7);
        if (firstTime)
            firstTime = false;
        else {
            double delta = Math.Abs(v - prevV);
            if (delta < minDelta) {
                minDelta = delta;
                vAtMinDelta = v;
            }
        }
        prevV = v;
    }
}
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.