소수점 이하 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로 이동
합니다 I 사용 FLOAT(10, 8)하거나 정확한 그래서이 데이터를 저장하기 위해 고려해야 할 다른 방법이있다. 지도 계산에 사용됩니다. 감사!


4
1.1mm의 정확도로 지구 표면에 값을 저장해야 합니까? 그렇다면 왜 왜 latlng에 값을 저장합니까?
ovangle


2
구글 문서가 잘못되었습니다! float정밀도가 7 자리 인 유형을 사용하지 마십시오 . 적어도 9가 필요합니다. 10이 필요하지 않습니다. 이상한 이유로 문서는 빼기 부호를 숫자로 계산합니다. 중 하나를 수행 double(9,6)하거나 decimal(9,6).
Ariel

5
당신은 얼마나 정밀도를 않습니다 정말 필요한가요? 소수점 6 자리는 서로 키스하는 두 사람을 구별하기에 충분한 정밀도를 제공합니다. 8 손가락을 구분할 수 있습니다. FLOAT1.7m (5.6ft)의 두 항목을 구분합니다. "map"응용 프로그램에는이 모든 것이 엄청나게 과도합니다!
Rick James

답변:


594

DECIMAL은 정확한 산술을위한 MySQL 데이터 유형입니다. FLOAT와 달리 정밀도는 모든 크기의 크기에 대해 고정되므로 FLOAT 대신 FLOAT를 사용하면 일부 계산을 수행 할 때 정밀도 오류를 피할 수 있습니다. 계산하지 않고 숫자를 저장하고 검색하는 경우 실제로는 DECIMAL을 사용하는 데 아무런 해가 없지만 FLOAT는 안전합니다. 계산으로 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이 부동 소수점 데이터 유형과 작동하는 방식을 설명합니다.

UPDATE : 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는 이진 근사를 저장합니다.
간 달리 터

1
정확성에 대한 의문으로, 나는 두 배로 갈 것입니다.
Ratata Tata

1
소수점 이하 8 자리는 1.1mm (1/16 인치 미만) 정밀도입니다. 위도와 경도를 위해 왜 이것이 필요할까요?
vartec

1
Facebook은 위도는 12 자리, lng는 13 자리까지 사용합니다. vartec은 소수점 8 자리는 1.1mm와 같다고 썼다. 7과 6은 어떻습니까? (나는 수학을 잘 못합니다). 지금은 double을 사용하고 있지만 유형을 변경하여 거리 계산을 얻을 수 있는지 확인하고 싶습니다. 감사합니다.
Alain Zelink

4
이 질문에 대한 답변 ( gis.stackexchange.com/questions/8650/… )은 위도와 경도의 소수점 이하 자릿수가 다른 정밀도에 대한 정보를 제공합니다.
간 달리 터

16

또한 float값이 반올림 되었음을 알 수 있습니다.

// 예 : 주어진 값 41.0473112,29.0077011

플로트 (11,7) | 십진수 (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
double필요한 정밀도를 가진 데이터 유형을 사용할 수 있습니다 .
Ariel

1
이 두 점을 구별 할 수있는 유용한지도를 보여주세요. 나는 두 표현이 모두 불필요하게 정확하다고 주장한다.
Rick James


7

데이터 유형을 부호있는 정수로 설정할 수 있습니다. 좌표를 SQL에 저장하면 lat * 10000000 및 long * 10000000으로 설정할 수 있습니다. 그리고 거리 / 반경으로 선택할 때 저장 좌표를 10000000으로 나눕니다. 300K 행으로 테스트했는데 쿼리 응답 시간이 좋습니다. (2 x 2.67GHz CPU, 2GB RAM, MySQL 5.5.49)


어느 것이 더 빠릅니까? 이것을하거나 float 또는 decimal을 사용합니까?
Dinidiniz

1
@Dinidiniz-속도 차이가 매우 작습니다. 행을 가져 오는 것은 모든 데이터베이스 작업의 타이밍을 압도합니다.
Rick James

왜 10000000입니까? 소수점 이하 자릿수가 6 자리를 초과하면 어떻게 되나요? 또는 항상 소수점 6 자리를 반환합니까?
Mahbub Morshed

@MahbubMorshed- 7 자리 숫자 -7 자리 숫자가 표시됩니다. 그러나 예,이 기술은 항상 정확히 7 자리를 저장합니다. (4 바이트 정수를 사용하는 경우 경도 값이 최대 180 일 수 있으므로 곱하기를 7 자리를 초과하여 증가시킬 수 없으며 부호있는 정수 최대 값이 넘치지 않아야합니다.) 단 정밀도 부동 소수점에 저장하는 것보다 2 자리 더 정확합니다. 큰 경도 값에서 약 5 자리에서 오른쪽으로 10 진수 포인트 만 있습니다. (179.99998 및 179.99997은 동일한 부동 값으로 저장할 수 있습니다. 179.99996은 179.99998과는 안전합니다.)
ToolmakerSteve

이것은 내가 보았던 최고의 절충점입니다. 여기 에서는 긴 / 위도 값 (-180 .. +180 범위 내)에 대해 소수점 이하 7 자리, 4 바이트 부호있는 정수로 제공하는 코드를 보여 줍니다. 작은 크기 (4B)에서 뛰어난 정밀도 (~ 1cm).
ToolmakerSteve

6

float를 사용하지 마십시오. 좌표가 둥글게되어 이상한 결과가 발생합니다.

십진수 사용



4

Lat / Lng을 MySQL에 저장하는 가장 좋은 방법은 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로 제한하지 않습니까? 이것은 태평양의 많은 부분을 제외합니다!
Rick James

그것은 절대 진리로 간주되어서는 안되는 예입니다. 다른 DECIMAL 10 진수 정밀도 (20, 18) 등을 사용할 수 있습니다. 지리적 및 공간 데이터를 저장해야하는 경우 postgis 데이터베이스를이 용도로 사용할 수 있습니다. MySQL Spatial Extensions는 OpenGIS Geometry Model을 따르기 때문에 좋은 대안입니다. 데이터베이스를 이식 가능하게 유지해야했기 때문에 사용하지 않았습니다. postgis.net
gilcierweb 17

(20,18)+/- 99에서도 최고입니다.
Rick James

그것은 절대 진리로 간주되어서는 안되는 예입니다. 다른 DECIMAL 10 진수 정밀도 (20, 18) 등을 사용할 수 있습니다. 지리적 및 공간 데이터를 저장해야하는 경우 postgis 데이터베이스를이 용도로 사용할 수 있습니다. MySQL Spatial Extensions는 OpenGIS Geometry Model을 따르기 때문에 좋은 대안입니다. 데이터베이스를 이식 가능하게 유지해야했기 때문에 사용하지 않았습니다. postgis.net
gilcierweb

십진법이 지리적 및 공간적 데이터만을 위해 만들어진 데이터베이스 postgis를 사용하는 데 도움이되지 않는다면, 이것은 단지 예일 뿐이며, 원하는 정밀도를 사용할 수 있습니다
gilcierweb

-1

Oğuzhan KURNUÇ 's answer의 정밀도를 사용 / 증명하는 코드 .

요약 :
작은 크기 (4B)에서 높은 정밀도 (~ 1cm).

[-180, 180] 범위의 값에 대한 정밀도 는 소수점 이하 7 자리입니다.
그건 소수점의 오른쪽의 숫자 7 (~ 1cm) , 9 자리의 총 (또는 10 진수 "180"의 "1"은 초기 수를 계산하는 경우) + -180 가까이 대.
이것을 ~ 7 자리 만 가진 4 바이트 float와 대조하여, + = 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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.