Jenis data MySQL apa yang harus digunakan untuk Latitude / Longitude dengan 8 tempat desimal?

257

Saya bekerja dengan data peta, dan Latitude/Longitudemeluas ke 8 tempat desimal. Sebagai contoh:

Latitude 40.71727401
Longitude -74.00898606

Saya melihat di dokumen Google yang menggunakan:

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

namun, tempat desimalnya hanya menuju 6.
Haruskah saya menggunakan FLOAT(10, 8)atau apakah ada metode lain yang perlu dipertimbangkan untuk menyimpan data ini sehingga tepat. Ini akan digunakan dengan perhitungan peta. Terima kasih!

Edward
sumber
4
Apakah Anda benar-benar perlu menyimpan nilai di permukaan bumi dengan akurat hingga 1.1mm ? Jika demikian, lalu mengapa Anda menyimpan nilai di latlng di tempat pertama?
Ovangle
2
Google doc SALAH! Jangan gunakan floattipe - yang hanya memiliki 7 digit presisi. Anda membutuhkan setidaknya 9. Anda tidak perlu 10 - dokumen untuk beberapa alasan aneh menghitung tanda minus sebagai digit. Apakah: double(9,6)atau decimal(9,6).
Ariel
5
Berapa banyak presisi yang Anda benar-benar perlu? 6 tempat desimal memberi Anda cukup presisi untuk membedakan dua orang saling berciuman. 8 dapat membedakan jari Anda. FLOATmembedakan dua item dengan jarak 1,7m (5,6ft). Semua itu terlalu berlebihan untuk aplikasi "peta"!
Rick James

Jawaban:

594

DECIMAL adalah tipe data MySQL untuk aritmatika yang tepat. Tidak seperti FLOAT, ketepatannya ditetapkan untuk semua ukuran angka, jadi dengan menggunakannya sebagai ganti FLOAT Anda mungkin menghindari kesalahan presisi saat melakukan beberapa perhitungan. Jika Anda hanya menyimpan dan mengambil nomor tanpa perhitungan maka dalam praktiknya FLOAT akan aman, meskipun tidak ada salahnya menggunakan DECIMAL. Dengan perhitungan FLOAT sebagian besar masih ok, tetapi untuk benar-benar yakin 8d.p. Anda harus menggunakan DECIMAL.

Garis lintang berkisar dari -90 hingga +90 (derajat), jadi DECIMAL (10, 8) ok untuk itu, tetapi garis bujur berkisar dari -180 hingga +180 (derajat) sehingga Anda memerlukan DECIMAL (11, 8). Angka pertama adalah jumlah total digit yang disimpan, dan yang kedua adalah angka setelah titik desimal.

Pendeknya: lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL

Ini menjelaskan bagaimana MySQL bekerja dengan tipe data floating-point.

PEMBARUAN: MySQL mendukung tipe data spasial dan Pointmerupakan tipe nilai tunggal yang dapat digunakan. Contoh:

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));
pengacau
sumber
11
Mungkin jawaban saya menyalahgunakan kata persis, karena DECIMAL masih seakurat ketepatan yang Anda berikan. Maksud saya adalah bahwa itu adalah yang akurat. Tentu saja beberapa perhitungan memperluas kesalahan. Jika saya memiliki DECMIAL x maka dosa (x ^ 100) akan menjadi jauh. Tetapi jika (menggunakan DECIMAL (10, 8) atau FLOAT (10, 8)) saya menghitung 0,3 / 3 maka DECIMAL memberikan 0,100000000000 (benar), dan float memberi 0,100000003974 (benar ke 8dp, tetapi akan salah jika dikalikan). Saya mengerti perbedaan utama adalah bagaimana angka-angka disimpan. DECIMAL menyimpan angka desimal, di mana FLOAT menyimpan perkiraan biner.
gandaliter
1
Dengan keraguan presisi, saya akan GANDA.
Ratata Tata
1
8 tempat desimal adalah presisi 1.1mm (kurang dari 1/16 inci). Mengapa Anda membutuhkannya untuk lintang dan bujur?
vartec
1
Facebook sepertinya menggunakan hingga 12 desimal untuk lat dan 13 untuk lng. vartec menulis bahwa 8 desimal sama dengan 1.1mm; bagaimana dengan 7 dan 6? (Aku tidak pandai matematika). Saya menggunakan ganda untuk saat ini tetapi ingin memeriksa apakah saya bisa mendapatkan perhitungan jarak dengan mengubah jenis. Terima kasih.
Alain Zelink
4
Jawaban atas pertanyaan ini ( gis.stackexchange.com/questions/8650/… ) memberikan informasi tentang ketepatan yang Anda dapatkan dengan jumlah desimal lintang dan bujur yang berbeda.
gandaliter
16

Selain itu, Anda akan melihat bahwa floatnilai dibulatkan.

// mis: nilai yang diberikan 41.0473112,29.0077011

float (11,7) | desimal (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011

K-Gun
sumber
1
Anda dapat menggunakan doubletipe data, yang membutuhkan ketelitian.
Ariel
1
Tunjukkan pada saya peta yang berguna yang dapat membedakan kedua titik itu. Saya mengklaim bahwa kedua representasi "tepat tidak perlu".
Rick James
14

di laravel digunakan tipe kolom desimal untuk migrasi

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

untuk informasi lebih lanjut lihat jenis kolom yang tersedia

Jignesh Joisar
sumber
7

Anda dapat mengatur tipe data Anda sebagai bilangan bulat yang ditandatangani. Saat Anda menyimpan koordinat ke SQL, Anda dapat mengatur sebagai lat * 10000000 dan panjang * 10000000. Dan ketika Anda memilih dengan jarak / jari-jari Anda akan membagi koordinat penyimpanan ke 10000000. Saya mengujinya dengan baris 300K, waktu respons kueri baik. (2 x 2,67GHz CPU, 2 GB RAM, MySQL 5.5.49)

Oğuzhan KURNUÇ
sumber
Mana yang lebih cepat? Melakukan ini atau menggunakan float atau desimal?
Dinidiniz
1
@Dididiniz - Perbedaan kecepatannya sangat kecil. Mengambil baris melebihi waktu tindakan database apa pun.
Rick James
Mengapa 10000000 Apa yang terjadi jika mengandung lebih dari 6 digit setelah nilai desimal? Atau akan selalu mengembalikan 6 poin desimal.
Mahbub Morshed
@ MahbubMorshed - maksud Anda 7 digit - ada 7 digit nol yang ditunjukkan. Tapi ya, teknik ini selalu menyimpan tepat 7 digit, tidak lebih. (Jika menggunakan integer 4-byte, tidak dapat meningkatkan pengali di luar 7 digit karena nilai bujur bisa sebesar 180, dan harus menghindari kelebihan maksimum integer bertanda.) Ini adalah 2 digit lebih tepat daripada menyimpan dalam float presisi tunggal, yang hanya memiliki sekitar 5 digit-ke-kanan-titik-desimal pada nilai bujur yang besar. (179.99998 dan 179.99997 dapat menyimpan nilai float yang sama; 179.99996 aman jauh dari 179.99998).)
ToolmakerSteve
Ini adalah trade-off terbaik yang pernah saya lihat di mana saja. Di sini saya menunjukkan kode untuk digunakan dan untuk mengkonfirmasi bahwa ia menyediakan 7 digit setelah titik desimal, dalam int bertanda 4-byte, untuk nilai panjang / lat (jadi dalam kisaran -180 .. + 180). Sangat presisi (~ 1cm) dalam ukuran kecil (4B).
ToolmakerSteve
6

Jangan gunakan float ... Ini akan mengelilingi koordinat Anda, menghasilkan beberapa kejadian aneh.

Gunakan desimal

Sam Sabey
sumber
4

MySQL sekarang memiliki dukungan untuk tipe data spasial sejak pertanyaan ini diajukan. Jadi jawaban yang diterima saat ini tidak salah, tetapi jika Anda mencari fungsionalitas tambahan seperti menemukan semua poin dalam poligon yang diberikan maka gunakan tipe data TITIK.

Periksa Dokumen Mysql tentang tipe data Geospasial dan fungsi analisis spasial

Tagihan--
sumber
4

Saya percaya cara terbaik untuk menyimpan Lat / Lng di MySQL adalah memiliki kolom TITIK (datatype 2D) dengan indeks 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));
ΔO 'deltazero'
sumber
0

Menggunakan migrasi ruby ​​di rel

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
gilcierweb
sumber
Tidakkah ini akan membatasi garis bujur menjadi -99..99? Ini tidak termasuk sebagian besar Pasifik!
Rick James
Itu adalah contoh yang tidak boleh dianggap sebagai kebenaran absolut. Anda dapat menggunakan presisi desimal DECIMAL lainnya (20, 18) dan seterusnya ... Jika Anda perlu menyimpan data geografis dan spasial, Anda dapat menggunakan database postgis untuk tujuan ini. MySQL Spatial Extensions adalah alternatif yang baik karena mengikuti OpenGIS Geometry Model. Saya tidak menggunakannya karena saya perlu menjaga portable database saya. postgis.net
gilcierweb
(20,18)juga keluar di +/- 99.
Rick James
Itu adalah contoh yang tidak boleh dianggap sebagai kebenaran absolut. Anda dapat menggunakan presisi desimal DECIMAL lainnya (20, 18) dan seterusnya ... Jika Anda perlu menyimpan data geografis dan spasial, Anda dapat menggunakan database postgis untuk tujuan ini. MySQL Spatial Extensions adalah alternatif yang baik karena mengikuti OpenGIS Geometry Model. Saya tidak menggunakannya karena saya perlu menjaga portable database saya. postgis.net
gilcierweb
Bung ini hanya sebuah contoh, Anda dapat menggunakan presisi yang Anda inginkan, jika desimal tidak membantu Anda menggunakan postgis, sebuah database yang dibuat hanya untuk data geografis dan spasial
gilcierweb
-1

Kode untuk menggunakan / membuktikan ketepatan jawaban Oğuzhan KURNUÇ .

RINGKASAN:
Presisi luar biasa (~ 1cm) dalam ukuran kecil (4B).

Presisi adalah (sangat dekat dengan) 7 angka desimal untuk nilai pada rentang [-180, 180].
Itu 7 digit ke kanan desimal (~ 1cm) , dengan total 9 digit (atau 10 digit, jika menghitung awal "1" dari "180") dekat + -180.
Bandingkan ini dengan float 4-byte , yang hanya memiliki ~ 7 digit total, jadi ~ 5 digit di sebelah kanan desimal dekat + = 180 (~ 1m) .

Metode untuk menggunakan pendekatan ini:

const double Fixed7Mult = 10000000;

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

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

Tes presisi:

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

Metode pembantu yang digunakan oleh tes:

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;
    }
}
ToolmakerSteve
sumber