Loại dữ liệu MySQL nào nên được sử dụng cho Vĩ độ / Kinh độ với 8 chữ số thập phân?


257

Tôi đang làm việc với dữ liệu bản đồ và Latitude/Longitudemở rộng đến 8 vị trí thập phân. Ví dụ:

Latitude 40.71727401
Longitude -74.00898606

Tôi thấy trong tài liệu Google có sử dụng:

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

tuy nhiên, vị trí thập phân của chúng chỉ đến 6.
Tôi nên sử dụng FLOAT(10, 8)hoặc có phương pháp nào khác để xem xét việc lưu trữ dữ liệu này sao cho chính xác. Nó sẽ được sử dụng với các tính toán bản đồ. Cảm ơn!


4
Bạn có thực sự cần lưu trữ các giá trị trên bề mặt trái đất chính xác đến 1,1mm không? Nếu vậy, tại sao bạn lưu trữ giá trị trong latlng ở nơi đầu tiên?
ovangle 22/03/2015


2
Tài liệu google là SAI! Không sử dụng floatloại - chỉ có 7 chữ số chính xác. Bạn cần ít nhất 9. Bạn không cần 10 - các tài liệu cho một số lý do kỳ lạ đếm dấu trừ là một chữ số. Làm một trong hai: double(9,6)hoặc decimal(9,6).
Ariel

5
Bạn thực sự cần bao nhiêu độ chính xác ? 6 chữ số thập phân cung cấp cho bạn đủ chính xác để phân biệt hai người hôn nhau. 8 có thể nói ngón tay của bạn ngoài. FLOATphân biệt hai mục cách nhau 1,7m (5,6ft). Tất cả những thứ đó là quá mức lố bịch cho các ứng dụng "bản đồ"!
Rick James

Câu trả lời:


594

DECIMAL là kiểu dữ liệu MySQL cho số học chính xác. Không giống như FLOAT, độ chính xác của nó được cố định cho bất kỳ kích thước số nào, do đó, bằng cách sử dụng nó thay vì FLOAT, bạn có thể tránh được các lỗi chính xác khi thực hiện một số tính toán. Nếu bạn chỉ lưu trữ và truy xuất các số mà không tính toán thì trong thực tế, FLOAT sẽ an toàn, mặc dù không có hại gì khi sử dụng DECIMAL. Với tính toán, FLOAT vẫn chủ yếu là ok, nhưng để chắc chắn về 8d.p. độ chính xác bạn nên sử dụng DECIMAL.

Vĩ độ nằm trong khoảng từ -90 đến +90 (độ), do đó, DECIMAL (10, 8) là ổn đối với điều đó, nhưng kinh độ nằm trong khoảng từ -180 đến +180 (độ) vì vậy bạn cần phải có QUYẾT ĐỊNH (11, 8). Số đầu tiên là tổng số chữ số được lưu trữ và số thứ hai là số sau dấu thập phân.

Nói ngắn gọn: lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL

Điều này giải thích cách MySQL hoạt động với các kiểu dữ liệu dấu phẩy động.

CẬP NHẬT: MySQL hỗ trợ các kiểu dữ liệu không gianPointlà loại giá trị đơn có thể được sử dụng. Thí dụ:

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
Có lẽ câu trả lời của tôi đã sử dụng sai từ chính xác, vì DECIMAL vẫn chỉ chính xác như độ chính xác mà bạn đưa ra. Quan điểm của tôi là nó rằng chính xác. Tất nhiên một số tính toán mở rộng lỗi. Nếu tôi có một DECMIAL x thì sin (x ^ 100) sẽ biến mất. Nhưng nếu (sử dụng DECIMAL (10, 8) hoặc FLOAT (10, 8)) tôi tính 0,3 / 3 thì DECIMAL cho 0.100000000000 (đúng) và float cho 0.100000003974 (đúng thành 8dp, nhưng sẽ sai nếu nhân). Tôi hiểu sự khác biệt chính là cách các con số được lưu trữ. DECIMAL lưu trữ các chữ số thập phân, trong đó FLOAT lưu trữ xấp xỉ nhị phân.
kẻ phá hoại

1
Do nghi ngờ về độ chính xác, tôi sẽ đến NHÂN ĐÔI.
Ratata Tata

1
8 chữ số thập phân có độ chính xác 1,1mm (dưới 1/16 inch). Tại sao bạn cần điều đó cho vĩ độ và kinh độ?
vartec

1
Facebook dường như sử dụng tới 12 số thập phân cho lat và 13 cho lng. vartec đã viết rằng 8 số thập phân bằng 1,1mm; 7 và 6 thì sao? (Tôi không giỏi môn toán). Hiện tại tôi đang sử dụng gấp đôi nhưng muốn kiểm tra xem tôi có thể đạt được tính toán khoảng cách hay không bằng cách thay đổi loại. Cảm ơn bạn.
Alain Zelink

4
Các câu trả lời cho câu hỏi này ( gis.stackexchange.com/questions/8650/õ ) cung cấp thông tin về độ chính xác mà bạn nhận được với các số thập phân khác nhau của vĩ độ và kinh độ.
kẻ phá hoại

16

Ngoài ra, bạn sẽ thấy floatcác giá trị được làm tròn.

// ví dụ: các giá trị đã cho 41.0473112,29.0077011

phao (11,7) | số thập phân (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
Bạn có thể sử dụng doublekiểu dữ liệu, có độ chính xác cần thiết.
Ariel

1
Chỉ cho tôi một bản đồ hữu ích có thể phân biệt hai điểm đó. Tôi khẳng định rằng cả hai cách trình bày đều "chính xác không cần thiết".
Rick James

14

trong laravel sử dụng loại cột thập phân để di chuyển

$table->decimal('latitude', 10, 8);
$table->decimal('longitude', 11, 8);

để biết thêm thông tin xem loại cột có sẵn


7

Bạn có thể đặt kiểu dữ liệu của mình dưới dạng số nguyên đã ký. Khi bạn lưu trữ tọa độ thành SQL, bạn có thể đặt lat * 10000000 và dài * 10000000. Và khi bạn chọn với khoảng cách / bán kính, bạn sẽ chia tọa độ lưu trữ thành 10000000. Tôi đã kiểm tra nó với 300K hàng, thời gian phản hồi truy vấn là tốt. (CPU 2 x 2,67GHz, RAM 2 GB, MySQL 5,5,49)


Cái nào nhanh hơn? Làm điều này hoặc sử dụng float hoặc thập phân?
Dinidiniz 16/03/18

1
@Dinidiniz - Chênh lệch tốc độ rất nhỏ. Tìm nạp các hàng áp đảo thời gian của bất kỳ hành động cơ sở dữ liệu.
Rick James

Tại sao 10000000? Điều gì xảy ra nếu nó chứa hơn 6 chữ số sau giá trị thập phân? Hoặc nó sẽ luôn trả lại 6 điểm thập phân.
Mahbub Morshed

@MahbubMorshed - ý bạn là 7 chữ số - có 7 chữ số 0 được hiển thị. Nhưng vâng, kỹ thuật này luôn lưu trữ chính xác 7 chữ số, không hơn. (Nếu sử dụng số nguyên 4 byte, không thể tăng số nhân vượt quá 7 chữ số vì giá trị kinh độ có thể lớn tới 180 và phải tránh tràn tối đa số nguyên đã ký.) Đây là 2 chữ số chính xác hơn so với lưu trữ trong một số float chính xác đơn, chỉ có khoảng 5 chữ số từ phải sang dấu thập phân ở các giá trị kinh độ lớn. (179.99998 và 179.99997 có thể lưu trữ cùng một giá trị float; 179.99996 an toàn cách xa 179.99998).)
ToolmakerSteve

Đây là sự đánh đổi tốt nhất mà tôi thấy ở bất cứ đâu. Ở đây tôi hiển thị mã để sử dụng và để xác nhận rằng nó cung cấp 7 chữ số sau dấu thập phân, trong int có ký hiệu 4 byte, cho các giá trị dài / lat (vì vậy trong phạm vi -180 .. + 180). Độ chính xác tuyệt vời (~ 1cm) trong một kích thước nhỏ (4B).
ToolmakerSteve

6

Không sử dụng float ... Nó sẽ làm tròn tọa độ của bạn, dẫn đến một số sự cố lạ.

Sử dụng thập phân


4

MySQL hiện có hỗ trợ cho các loại dữ liệu không gian kể từ khi câu hỏi này được hỏi. Vì vậy, câu trả lời được chấp nhận hiện tại không sai, nhưng nếu bạn đang tìm kiếm chức năng bổ sung như tìm tất cả các điểm trong một đa giác đã cho thì hãy sử dụng kiểu dữ liệu POINT.

Kiểm tra Tài liệu Mysql về các loại dữ liệu không gian địa lý và các chức năng phân tích không gian


4

Tôi tin rằng cách tốt nhất để lưu trữ Lat / Lng trong MySQL là có một cột POINT (kiểu dữ liệu 2D) với chỉ mục SPATIAL.

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

Sử dụng ruby ​​di chuyển trên đường ray

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

Điều này có giới hạn kinh độ đến -99,99 không? Điều này không bao gồm phần lớn Thái Bình Dương!
Rick James

Đó là một ví dụ không nên được coi là sự thật tuyệt đối. Bạn có thể sử dụng một độ chính xác thập phân DECIMAL khác (20, 18), v.v ... Nếu bạn cần lưu dữ liệu địa lý và không gian, bạn có thể sử dụng cơ sở dữ liệu postgis cho mục đích này. Phần mở rộng không gian MySQL là một lựa chọn tốt vì chúng tuân theo Mô hình hình học OpenGIS. Tôi không sử dụng chúng vì tôi cần giữ cơ sở dữ liệu của mình. postgis.net
gilcierweb

(20,18)cũng đứng đầu ở +/- 99.
Rick James

Đó là một ví dụ không nên được coi là sự thật tuyệt đối. Bạn có thể sử dụng một độ chính xác thập phân khác của DECIMAL (20, 18), v.v ... Nếu bạn cần lưu dữ liệu địa lý và không gian, bạn có thể sử dụng cơ sở dữ liệu postgis cho mục đích này. Phần mở rộng không gian MySQL là một lựa chọn tốt vì chúng tuân theo Mô hình hình học OpenGIS. Tôi không sử dụng chúng vì tôi cần giữ cơ sở dữ liệu của mình. postgis.net
gilcierweb

Anh bạn đây chỉ là một ví dụ, bạn có thể sử dụng độ chính xác mà bạn muốn, nếu số thập phân không giúp bạn sử dụng postgis một cơ sở dữ liệu được tạo ra chỉ cho dữ liệu địa lý và không gian
gilcierweb

-1

Mã để sử dụng / chứng minh độ chính xác của câu trả lời của Oğuzhan KURNUÇ .

TÓM TẮT:
Độ chính xác tuyệt vời (~ 1cm) trong một kích thước nhỏ (4B).

Độ chính xác là (rất gần với) 7 chữ số thập phân cho các giá trị trong phạm vi [-180, 180].
Đó là 7 chữ số bên phải của số thập phân (~ 1cm) , với tổng số 9 chữ số (hoặc 10 chữ số, nếu tính "1" ban đầu của "180") gần + -180.
Tương phản điều này với một float 4 byte , chỉ có tổng số ~ 7 chữ số, vì vậy ~ 5 chữ số ở bên phải của số thập phân gần + = 180 (~ 1m) .

Phương pháp sử dụng phương pháp này:

const double Fixed7Mult = 10000000;

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

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

Kiểm tra độ chính xác:

/// <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}");
}

Phương pháp trợ giúp được sử dụng bởi các bài kiểm tra:

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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.