8 ondalık basamaklı Enlem / Boylam için hangi MySQL veri türü kullanılmalıdır?


257

Harita verileri ile çalışıyorum ve Latitude/Longitude8 ondalık basamağa kadar uzanıyor. Örneğin:

Latitude 40.71727401
Longitude -74.00898606

Şunu kullanan Google dokümanında gördüm :

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

ancak, ondalık basamakları yalnızca 6'ya gider. Bu verileri saklamam için başka bir yöntem
kullanmalı mıyım FLOAT(10, 8)yoksa bu yüzden de kesin. Harita hesaplamaları ile kullanılacaktır. Teşekkürler!


4
Gerçekten değerleri 1.1mm'ye kadar dünya yüzeyinde depolamanız gerekiyor mu? Öyleyse, neden ilk etapta değerleri latlng'de saklıyorsunuz?
15'te ovangle


2
Google doc YANLIŞ! floatSadece 7 basamak hassasiyeti olan türü kullanmayın . En az 9'a ihtiyacınız var. 10'a ihtiyacınız yok - garip bir nedenle dokümanlar eksi işaretini bir rakam olarak sayıyor. Aşağıdakilerden birini yapın: double(9,6)veya decimal(9,6).
Ariel

5
Gerçekten ne kadar hassasiyete ihtiyacınız var? 6 ondalık basamak, birbirini öpen iki kişiyi ayırt etmek için yeterli hassasiyet sağlar. 8 parmaklarınızı birbirinden ayırabilir. FLOATbirbirinden 1,7m (5,6ft) uzaklıktaki iki öğeyi ayırır. Bunların hepsi "harita" uygulamaları için gülünç derecede aşırı!
Rick James

Yanıtlar:


594

DECIMAL, kesin aritmetik için MySQL veri tipidir. FLOAT'ın aksine hassasiyeti herhangi bir sayı boyutu için sabittir, bu nedenle FLOAT yerine bunu kullanarak bazı hesaplamalar yaparken hassas hatalardan kaçınabilirsiniz. Sadece sayıları hesaplama yapmadan saklayıp alıyorsanız, pratikte DECIMAL kullanımında herhangi bir zarar olmamasına rağmen FLOAT güvenlidir. Hesaplamalar ile FLOAT hala çoğunlukla iyi, ama 8d.p'den kesinlikle emin olmak için. Hassasiyeti DECIMAL kullanmalısınız.

Enlemler -90 ila +90 (derece) arasında değişir, bu nedenle DECIMAL (10, 8) bunun için uygundur, ancak boylamlar -180 ila +180 (derece) arasında değişir, bu nedenle DECIMAL'a (11, 8) ihtiyacınız vardır. İlk sayı, saklanan toplam basamak sayısı ve ikincisi ondalık noktadan sonraki sayıdır.

Kısacası: lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL

Bu , MySQL'in kayan noktalı veri türleriyle nasıl çalıştığını açıklar.

GÜNCELLEME: MySQL, Mekansal veri türlerini destekler ve Pointkullanılabilen tek değerli bir türdür. Misal:

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
Belki de cevabım DECIMAL hala verdiğiniz hassasiyet kadar doğru olduğu için kelimeyi tam olarak yanlış kullanmıştır. Benim demek ki o yüzden olduğunu bu kadar kesin. Tabii ki bazı hesaplamalar hatayı genişletir. Eğer bir DECMIAL x varsa o zaman sin (x ^ 100) kapalı olacak. Ancak (DECIMAL (10, 8) veya FLOAT (10, 8) kullanarak) 0.3 / 3 hesaplarsam, DECIMAL 0.100000000000 (doğru) verir ve şamandıra 0.100000003974 (8dp'ye doğrudur, ancak çarpılırsa yanlış olur) verir. Ana farkın sayıların nasıl saklandığını anlıyorum. DECIMAL ondalık basamakları saklar, burada FLOAT ikili yaklaşımı depolar.
gandaliter

1
Hassasiyet şüphesiyle, ÇİFTE gideceğim.
Ratata Tata

1
8 ondalık basamak 1,1 mm (1/16 inçten daha az) hassasiyettir. Neden enlem ve boylam için buna ihtiyacınız olsun ki?
vartec

1
Facebook lat için 12 ondalık, lng için 13 ondalık basamak kullanıyor gibi görünüyor. vartec, 8 ondalık sayının 1.1 mm'ye eşit olduğunu yazdı; 7 ve 6'ya ne dersin? (Matematikte iyi değilim). Şimdilik çift kullanıyorum ama tip değiştirerek mesafe hesaplamalarında kazanıp kazanamayacağımı kontrol etmek istiyorum. Teşekkür ederim.
Alain Zelink

4
Bu sorunun yanıtları ( gis.stackexchange.com/questions/8650/… ), farklı sayıda ondalık enlem ve boylam yerlerinde elde ettiğiniz hassasiyet hakkında bilgi verir.
gandaliter

16

Ayrıca, floatdeğerlerin yuvarlandığını göreceksiniz .

// örneğin: verilen değerler 41.0473112,29.0077011

şamandıra (11,7) | ondalık (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
doubleGereken hassasiyete sahip veri türünü kullanabilirsiniz .
Ariel

1
Bana bu iki noktayı ayırt edebilecek yararlı bir harita gösterin. Her iki temsilin de "gereksiz yere kesin" olduğunu iddia ediyorum.
Rick James

14

laravel'de geçiş için ondalık sütun türü kullanıldı

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

daha fazla bilgi için kullanılabilir sütun türüne bakın


7

Veri türünüzü işaretli tam sayı olarak ayarlayabilirsiniz. SQL için koordinatları depoladığınızda lat * 10000000 ve long * 10000000 olarak ayarlayabilirsiniz. Ve mesafe / yarıçap ile seçtiğinizde, depolama koordinatlarını 10000000'e bölebilirsiniz. 300K satırlarla test ettim, sorgu yanıt süresi iyidir. (2 x 2.67GHz CPU, 2 GB RAM, MySQL 5.5.49)


Hangisi daha hızlı? Bunu yapmak veya kayan nokta veya ondalık kullanarak?
Dinidiniz

1
@Dinidiniz - Hız farkı çok küçük. Satırlar getirilirse, herhangi bir veritabanı işleminin zamanlaması bunalır.
Rick James

Neden 10000000? Ondalık değerden sonra 6'dan fazla basamak içeriyorsa ne olur? Yoksa her zaman 6 ondalık nokta döndürür.
Mahbub Morshed

@MahbubMorshed - 7 rakam demek - gösterilen 7 sıfır rakam var. Ama evet, bu teknik her zaman tam olarak 7 basamak saklıyor, artık yok. (4 baytlık tamsayı kullanılıyorsa, çarpanı 7 basamağın üzerine çıkaramazsınız, çünkü boylam değeri 180 kadar büyük olabilir ve işaretli tamsayı taşmasını önlemek gerekir.) Bu, tek duyarlıklı şamandırada saklamaktan 2 basamak daha hassastır, büyük boylam değerlerinde yalnızca ondalık basamağa kadar ondalık basamağa sahiptir. (179,99998 ve 179,99997 aynı yüzer değer olarak depolayabilir; 179,99996 179.99998 uzak güvenli bir şekilde olan)).
ToolmakerSteve

Bu, her yerde gördüğüm en iyi değiş tokuş. Burada , uzun / lat değerleri (yani -180 .. + 180 aralığında) için 4 bayt imzalı bir int, ondalık noktadan sonra 7 basamak sağladığını ve onayladığını göstermek için kod gösteririm . Küçük boyutta (4B) büyük hassasiyet (~ 1 cm).
ToolmakerSteve

6

Şamandıra kullanmayın ... Koordinatlarınızı yuvarlar ve bazı garip olaylara neden olur.

Ondalık kullan



4

MySQL Lat / Lng depolamak için en iyi yolu bir SPATIAL indeksi ile bir POINT sütun (2D veri türü) sahip olduğuna inanıyorum.

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

Raylar üzerinde migrate ruby ​​kullanma

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

Bu sınır -99..99 ile boylam yapmayacak mı? Bu Pasifik'in çoğunu hariç tutar!
Rick James

Bu bir örnek olarak mutlak gerçek olarak alınmamalıdır. Başka bir DECIMAL ondalık duyarlık (20, 18) vb. Kullanabilirsiniz ... Coğrafi ve uzamsal verileri kaydetmeniz gerekiyorsa, bu amaçla postgis veritabanını kullanabilirsiniz. MySQL Uzamsal Uzantıları, OpenGIS Geometri Modelini izledikleri için iyi bir alternatiftir. Veritabanımı taşınabilir tutmam gerektiğinden bunları kullanmadım. postgis.net
gilcierweb

(20,18)Ayrıca +/- 99 ile zirveye çıkıyor.
Rick James

Bu bir örnek olarak mutlak gerçek olarak alınmamalıdır. Başka bir DECIMAL ondalık duyarlık (20, 18) vb. Kullanabilirsiniz ... Coğrafi ve uzamsal verileri kaydetmeniz gerekiyorsa, bu amaçla postgis veritabanını kullanabilirsiniz. MySQL Uzamsal Uzantıları, OpenGIS Geometri Modelini izledikleri için iyi bir alternatiftir. Veritabanımı taşınabilir tutmam gerektiğinden bunları kullanmadım. postgis.net
gilcierweb

Ahbap bu sadece bir örnektir, ondalık sayı sadece postgis'i sadece coğrafi ve uzamsal veriler için yapılmış bir veritabanı kullanmanıza yardımcı olmazsa, istediğiniz hassasiyeti kullanabilirsiniz
gilcierweb

-1

Oğuzhan KURNUÇ'un cevabının doğruluğunu kullanma / kanıtlama kodu .

ÖZET:
Küçük boyutta (4B) büyük hassasiyet (~ 1 cm).

[-180, 180] aralıktaki değerler için kesinlik (çok yakın) 7 ondalık basamaktır.
De bu ondalık sağındaki 7 basamak (~ 1 cm) , 9 basamak toplam (ya da 10 haneli "180" bir "1" ilk sayımı kullanılırsa) + -180 yakın için.
Bunu, toplamda sadece ~ 7 basamaklı 4 baytlık bir kayan nokta ile kontrastlayın , bu nedenle ondalıktan sağda ~ 5 basamak + = 180 (~ 1m) yakınında .

Bu yaklaşımı kullanma yöntemleri:

const double Fixed7Mult = 10000000;

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

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

Hassasiyet testleri:

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

Testlerde kullanılan yardımcı yöntemler:

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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.