Jakiego typu danych MySQL należy użyć dla szerokości i długości geograficznej z 8 miejscami po przecinku?


257

Pracuję z danymi map i Latitude/Longitudeobejmuje 8 miejsc po przecinku. Na przykład:

Latitude 40.71727401
Longitude -74.00898606

Widziałem w dokumencie Google, który używa:

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

Jednak ich miejsca dziesiętne wynoszą tylko 6.
Powinienem użyć FLOAT(10, 8)lub istnieje inna metoda do rozważenia, aby przechowywać te dane, więc są one dokładne. Będzie używany z obliczeniami mapy. Dzięki!


4
Czy naprawdę musisz przechowywać wartości na powierzchni ziemi z dokładnością do 1,1 mm ? Jeśli tak, to dlaczego przede wszystkim przechowujesz wartości w formacie latlng?
— ovangle


2
Dokument Google jest NIEPRAWIDŁOWY! Nie używaj tego floattypu - ma on tylko 7 cyfr dokładności. Potrzebujesz co najmniej 9. Nie potrzebujesz 10 - dokumenty z jakiegoś dziwnego powodu liczą znak minus jako cyfrę. Wykonaj albo: double(9,6)albo decimal(9,6).
— Ariel

5
Ile naprawdę potrzebujesz precyzji ? 6 miejsc po przecinku daje wystarczającą precyzję, aby rozróżnić dwie osoby, które się całują. 8 może rozdzielić palce. FLOATrozróżnia dwa elementy w odległości 1,7 m (5,6 stopy) Wszystkie są absurdalnie przesadne jak na aplikacje „mapujące”!
— Rick James

Odpowiedzi:


594

DECIMAL to typ danych MySQL dla dokładnej arytmetyki. W przeciwieństwie do FLOAT, jego precyzja jest ustalona dla dowolnego rozmiaru liczby, więc używając go zamiast FLOAT można uniknąć błędów precyzji podczas wykonywania niektórych obliczeń. Jeśli tylko przechowywałeś i pobierałeś liczby bez obliczeń, w praktyce FLOAT byłby bezpieczny, chociaż korzystanie z DECIMAL nie szkodzi. Z obliczeń FLOAT nadal jest w większości w porządku, ale dla absolutnej pewności co do 8d.p. precyzji należy użyć DECIMAL.

Szerokość geograficzna wynosi od -90 do +90 (stopni), więc DECIMAL (10, 8) jest do tego odpowiedni, ale długości geograficzne wynoszą od -180 do +180 (stopni), więc potrzebujesz DECIMAL (11, 8). Pierwsza liczba to całkowita liczba zapisanych cyfr, a druga to liczba po przecinku.

W skrócie: lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL

To wyjaśnia, jak MySQL działa z zmiennoprzecinkowymi typami danych.

AKTUALIZACJA: MySQL obsługuje typy danych przestrzennych i Pointjest typem pojedynczej wartości, którego można używać. Przykład:

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
Być może moja odpowiedź niewłaściwie wykorzystała słowo „dokładny”, ponieważ DECIMAL jest wciąż tak dokładny, jak precyzja, którą mu podajesz. Chodziło mi o to, że jest tak dokładne. Oczywiście niektóre obliczenia rozszerzają błąd. Jeśli mam DECMIAL x, sin (x ^ 100) będzie daleko. Ale jeśli (używając DECIMAL (10, 8) lub FLOAT (10, 8)) obliczę 0,3 / 3, to DECIMAL daje 0,100000000000 (poprawnie), a float daje 0,100000003974 (poprawne do 8dp, ale byłoby błędne, gdyby zostało pomnożone). Rozumiem, że główna różnica polega na sposobie przechowywania liczb. DECIMAL przechowuje cyfry dziesiętne, gdzie FLOAT przechowuje przybliżenie binarne.
— gandaliter

1
Wątpię w precyzję, podwoję się.
— Ratata Tata

1
8 miejsc po przecinku to precyzja 1,1 mm (mniej niż 1/16 cala). Dlaczego miałbyś tego potrzebować do szerokości i długości geograficznej?
— vartec

1
Facebook wydaje się używać do 12 miejsc po przecinku dla lat i 13 dla lng. vartec napisał, że 8 miejsc po przecinku jest równe 1,1 mm; co z 7 i 6? (Nie jestem dobry z matematyki). Na razie używam podwójnego, ale chciałbym sprawdzić, czy mogę zyskać na obliczeniach odległości, zmieniając typ. Dziękuję Ci.
— Alain Zelink

4
Odpowiedzi na to pytanie ( gis.stackexchange.com/questions/8650/... ) zawierają informacje o precyzji, jaką uzyskuje się przy różnej liczbie miejsc dziesiętnych szerokości i długości geograficznej.
— gandaliter

16

Dodatkowo zobaczysz, że floatwartości są zaokrąglone.

// np .: podane wartości 41.0473112,29.0077011

pływak (11,7) | dziesiętny (11,7)
---------------------------
41,0473099 | 41,0473112
29,0077019 | 29,0077011


1
Możesz użyć doubletypu danych, który ma wymaganą precyzję.
— Ariel

1
Pokaż mi przydatną mapę, która potrafi rozróżnić te dwa punkty. Twierdzę, że obie reprezentacje są „niepotrzebnie precyzyjne”.
— Rick James

14

w laravel wykorzystano dziesiętny typ kolumny do migracji

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

Aby uzyskać więcej informacji, zobacz dostępny typ kolumny


7

Możesz ustawić swój typ danych jako liczbę całkowitą ze znakiem. Podczas przechowywania współrzędnych do SQL można ustawić jako lat * 10000000 i długi * 10000000. A kiedy wybierasz z odległością / promieniem, podzielisz współrzędne miejsca przechowywania na 10000000. Przetestowałem to z 300 000 wierszy, czas odpowiedzi na zapytanie jest dobry. (2 x 2,67 GHz, 2 GB pamięci RAM, MySQL 5.5.49)


Który jest szybszy? Robisz to lub używasz liczb zmiennoprzecinkowych lub dziesiętnych?
— Dinidiniz

1
@Dinidiniz - Różnica prędkości jest bardzo mała. Pobieranie wierszy przytłacza czas każdej akcji bazy danych.
— Rick James

Dlaczego 10000000? Co się stanie, jeśli po wartości dziesiętnej zawiera więcej niż 6 cyfr? Czy zawsze zwróci 6 miejsc po przecinku.
— Mahbub Morshed

@MahbubMorshed - masz na myśli 7 cyfr - pokazanych jest 7 cyfr zerowych. Ale tak, ta technika zawsze przechowuje dokładnie 7 cyfr, nigdy więcej. (Jeśli używasz 4-bajtowej liczby całkowitej, nie możesz zwiększyć mnożnika poza 7 cyfr, ponieważ wartość długości geograficznej może być tak duża, jak 180, i musisz unikać przepełnienia maksymalnej liczby całkowitej ze znakiem.) Są to 2 cyfry bardziej precyzyjne niż przechowywanie w liczbach zmiennoprzecinkowych o pojedynczej precyzji, który ma tylko około 5 cyfr po prawej stronie przecinka dziesiętnego przy dużych długościach geograficznych. (179,99998 i 179,99997 mogą przechowywać tę samą wartość zmienną; 179,99996 jest bezpiecznie daleki od 179,99998).)
— ToolmakerSteve

To najlepszy kompromis, jaki kiedykolwiek widziałem. Tutaj pokazuję kod do użycia i potwierdzam , że podaje on 7 cyfr po przecinku, w 4-bajtowym znaku int, dla wartości długich / lat (więc w zakresie -180 .. + 180). Wielka precyzja (~ 1 cm) w małym rozmiarze (4B).
— ToolmakerSteve

6

Nie używaj pływaka ... Zaokrągli twoje współrzędne, powodując dziwne zdarzenia.

Użyj dziesiętnego



4

Uważam, że najlepszym sposobem przechowywania Lat / Lng w MySQL jest posiadanie kolumny POINT (typ danych 2D) z indeksem 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

Korzystanie z migracji ruby ​​na szynach

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

Czy to nie ograniczy długości geograficznej do -99..99? To wyklucza znaczną część Pacyfiku!
— Rick James

To przykład, którego nie należy traktować jako absolutnej prawdy. Możesz użyć innej dokładności dziesiętnej (20, 18) i tak dalej ... Jeśli chcesz zapisać dane geograficzne i przestrzenne, możesz w tym celu skorzystać z bazy danych Postgis. Rozszerzenia przestrzenne MySQL są dobrą alternatywą, ponieważ są zgodne z modelem geometrycznym OpenGIS. Nie korzystałem z nich, ponieważ musiałem mieć przenośną bazę danych. postgis.net
— gilcierweb

(20,18)również kończy się na +/- 99.
— Rick James

To przykład, którego nie należy traktować jako absolutnej prawdy. Możesz użyć innej dokładności dziesiętnej (20, 18) i tak dalej ... Jeśli chcesz zapisać dane geograficzne i przestrzenne, możesz w tym celu skorzystać z bazy danych Postgis. Rozszerzenia przestrzenne MySQL są dobrą alternatywą, ponieważ są zgodne z modelem geometrycznym OpenGIS. Nie korzystałem z nich, ponieważ musiałem mieć przenośną bazę danych. postgis.net
— gilcierweb

Koleś, to tylko przykład, możesz użyć precyzji, którą chcesz, jeśli liczba dziesiętna nie pomaga w korzystaniu z postgis bazy danych stworzonej tylko dla danych geograficznych i przestrzennych
— gilcierweb

-1

Kod używający / potwierdzający precyzję odpowiedzi Oğuzhan KURNUÇ .

PODSUMOWANIE:
Wielka precyzja (~ 1 cm) w małym rozmiarze (4B).

Dokładność wynosi (bardzo blisko) 7 cyfr dziesiętnych dla wartości z zakresu [-180, 180].
To 7 cyfr na prawo od miejsca po przecinku (~ 1 cm) , w sumie 9 cyfr (lub 10 cyfr, licząc początkowe „1” z „180”) w pobliżu + -180.
Porównaj to z 4-bajtową liczbą zmiennoprzecinkową , która ma tylko ~ 7 cyfr łącznie, więc ~ 5 cyfr po prawej stronie dziesiętnej w pobliżu + = 180 (~ 1m) .

Metody zastosowania tego podejścia:

const double Fixed7Mult = 10000000;

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

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

Testy precyzji:

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

Metody pomocnicze stosowane w testach:

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;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.