Welcher MySQL-Datentyp sollte für Latitude / Longitude mit 8 Dezimalstellen verwendet werden?

257

Ich arbeite mit Kartendaten und die Latitude/Longitudeerstreckt sich auf 8 Dezimalstellen. Beispielsweise:

Latitude 40.71727401
Longitude -74.00898606

Ich habe in dem Google-Dokument gesehen, das verwendet:

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

Ihre Dezimalstellen gehen jedoch nur auf 6.
Sollte ich eine FLOAT(10, 8)andere Methode verwenden, um diese Daten so zu speichern, dass sie präzise sind? Es wird mit Kartenberechnungen verwendet. Vielen Dank!

Edward
quelle
4
Müssen Sie wirklich Werte auf der Erdoberfläche speichern, die auf 1,1 mm genau sind ? Wenn ja, warum speichern Sie dann überhaupt Werte in latlng?
ovangle
2
Das Google-Dokument ist falsch! Verwenden Sie nicht den floatTyp, der nur 7 Stellen genau ist. Sie benötigen mindestens 9. Sie benötigen keine 10 - die Dokumente zählen aus irgendeinem Grund das Minuszeichen als Ziffer. Tun Sie entweder: double(9,6)oder decimal(9,6).
Ariel
5
Wie viel Präzision brauchen Sie wirklich ? 6 Dezimalstellen geben Ihnen genug Präzision, um zwei Personen zu unterscheiden, die sich küssen. 8 kann deine Finger auseinanderhalten. FLOATunterscheidet zwei Elemente in einem Abstand von 1,7 m. All dies ist für "Karten" -Anwendungen lächerlich übertrieben!
Rick James

Antworten:

594

DECIMAL ist der MySQL-Datentyp für exakte Arithmetik. Im Gegensatz zu FLOAT ist die Genauigkeit für jede Zahlengröße festgelegt. Wenn Sie es also anstelle von FLOAT verwenden, können Sie bei einigen Berechnungen Präzisionsfehler vermeiden. Wenn Sie nur die Zahlen ohne Berechnung speichern und abrufen würden, wäre FLOAT in der Praxis sicher, obwohl die Verwendung von DECIMAL keinen Schaden anrichtet. Mit Berechnungen ist FLOAT meistens noch in Ordnung, aber um absolut sicher zu sein, 8d.p. Präzision sollten Sie DECIMAL verwenden.

Die Breiten reichen von -90 bis +90 (Grad), daher ist DECIMAL (10, 8) dafür in Ordnung, aber die Längen reichen von -180 bis +180 (Grad), sodass Sie DECIMAL (11, 8) benötigen. Die erste Zahl ist die Gesamtzahl der gespeicherten Ziffern und die zweite die Zahl nach dem Dezimalpunkt.

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

Dies erklärt, wie MySQL mit Gleitkomma-Datentypen funktioniert.

UPDATE: MySQL unterstützt räumliche Datentypen und Pointist ein einwertiger Typ, der verwendet werden kann. Beispiel:

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));
Gandaliter
quelle
11
Vielleicht hat meine Antwort das Wort genau missbraucht, da DECIMAL immer noch nur so genau ist wie die Präzision, die Sie ihm geben. Mein Punkt war, dass es so genau ist. Natürlich erweitern einige Berechnungen den Fehler. Wenn ich ein DECMIAL x habe, dann ist sin (x ^ 100) weit weg. Wenn ich aber (mit DECIMAL (10, 8) oder FLOAT (10, 8)) 0,3 / 3 berechne, dann gibt DECIMAL 0,100000000000 (richtig) und float 0,100000003974 (richtig auf 8 dp, wäre aber falsch, wenn multipliziert). Ich verstehe, dass der Hauptunterschied darin besteht, wie die Zahlen gespeichert werden. DECIMAL speichert die Dezimalstellen, wobei FLOAT die binäre Approximation speichert.
Gandaliter
1
Im Zweifel an der Präzision gehe ich zu DOUBLE.
Ratata Tata
1
8 Dezimalstellen entsprechen einer Genauigkeit von 1,1 mm (weniger als 1/16 Zoll). Warum brauchen Sie das jemals für Längen- und Breitengrade?
Vartec
1
Facebook scheint bis zu 12 Dezimalstellen für lat und 13 für lng zu verwenden. vartec schrieb, dass 8 Dezimalstellen 1,1 mm entsprechen; Was ist mit 7 und 6? (Ich bin nicht gut in Mathe). Ich verwende derzeit double, möchte aber prüfen, ob ich durch Ändern des Typs an Entfernungsberechnungen gewinnen kann. Danke dir.
Alain Zelink
4
Die Antworten auf diese Frage ( gis.stackexchange.com/questions/8650/… ) geben Auskunft über die Genauigkeit, die Sie mit unterschiedlichen Dezimalstellen für Breiten- und Längengrade erhalten.
Gandaliter
16

Außerdem sehen Sie, dass die floatWerte gerundet sind.

// zB: gegebene Werte 41.0473112,29.0077011

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

K-Gun
quelle
1
Sie können den doubleDatentyp verwenden, der die erforderliche Genauigkeit aufweist.
Ariel
1
Zeigen Sie mir eine nützliche Karte, die diese beiden Punkte unterscheiden kann. Ich behaupte, dass beide Darstellungen "unnötig präzise" sind.
Rick James
14

In Laravel wird der Dezimalsäulentyp für die Migration verwendet

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

Weitere Informationen finden Sie unter verfügbarer Spaltentyp

Jignesh Joisar
quelle
7

Sie können Ihren Datentyp als vorzeichenbehaftete Ganzzahl festlegen. Wenn Sie Koordinaten für SQL speichern, können Sie lat * 10000000 und long * 10000000 festlegen. Und wenn Sie mit Abstand / Radius auswählen, teilen Sie die Speicherkoordinaten auf 10000000. Ich habe es mit 300K Zeilen getestet, die Antwortzeit für Abfragen ist gut. (2 x 2,67 GHz CPU, 2 GB RAM, MySQL 5.5.49)

Oğuzhan KURNUÇ
quelle
Welche ist schneller? Tun Sie dies oder verwenden Sie float oder decimal?
Dinidiniz
1
@ Dinidiniz - Der Geschwindigkeitsunterschied ist sehr gering. Das Abrufen von Zeilen überfordert das Timing einer Datenbankaktion.
Rick James
Warum 10000000? Was passiert, wenn es mehr als 6 Nachkommastellen enthält? Oder es werden immer 6 Dezimalstellen zurückgegeben.
Mahbub Morshed
@ MahbubMorshed - Sie meinen 7 Ziffern - es werden 7 Nullstellen angezeigt. Aber ja, diese Technik speichert immer genau 7 Ziffern, nicht mehr. (Wenn Sie eine 4-Byte-Ganzzahl verwenden, kann der Multiplikator nicht über 7 Stellen hinaus erhöht werden, da der Längengrad bis zu 180 betragen kann und ein Überlaufen des vorzeichenbehafteten Ganzzahlmaximums vermieden werden muss.) Dies sind 2 Stellen genauer als das Speichern in Float mit einfacher Genauigkeit. Das hat nur ungefähr 5 Stellen rechts vom Dezimalpunkt bei großen Längenwerten. (179,99998 und 179,99997 können speichern , wie gleiche Gleitkommawert; 179,99996 ist sicher weit von 179,99998)).
ToolmakerSteve
Dies ist der beste Kompromiss, den ich je gesehen habe. Hier zeige ich Code zur Verwendung und zur Bestätigung, dass er 7 Nachkommastellen in einem 4-Byte-Int mit Vorzeichen für Long / Lat-Werte (also innerhalb des Bereichs -180 .. + 180) enthält. Große Präzision (~ 1 cm) bei kleiner Größe (4B).
ToolmakerSteve
6

Verwenden Sie kein Float ... Es rundet Ihre Koordinaten ab, was zu seltsamen Ereignissen führt.

Verwenden Sie eine Dezimalstelle

Sam Sabey
quelle
4

MySQL unterstützt jetzt räumliche Datentypen, seit diese Frage gestellt wurde. Die aktuell akzeptierte Antwort ist also nicht falsch. Wenn Sie jedoch nach zusätzlichen Funktionen suchen, z. B. nach allen Punkten innerhalb eines bestimmten Polygons, verwenden Sie den Datentyp POINT.

Überprüfen Sie die MySQL-Dokumente zu Geodatentypen und den räumlichen Analysefunktionen

Rechnung--
quelle
4

Ich glaube, der beste Weg, Lat / Lng in MySQL zu speichern, besteht darin, eine POINT-Spalte (2D-Datentyp) mit einem SPATIAL-Index zu haben.

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'
quelle
0

Verwenden Sie Ruby auf Schienen migrieren

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
quelle
Wird dies nicht die Längengrade auf -99..99 begrenzen? Dies schließt einen Großteil des Pazifiks aus!
Rick James
Das ist ein Beispiel, das nicht als absolute Wahrheit angesehen werden sollte. Sie können eine andere dezimale Dezimalgenauigkeit (20, 18) usw. verwenden. Wenn Sie geografische und räumliche Daten speichern müssen, können Sie zu diesem Zweck die Postgis-Datenbank verwenden. Die MySQL Spatial Extensions sind eine gute Alternative, da sie dem OpenGIS-Geometriemodell folgen. Ich habe sie nicht verwendet, weil ich meine Datenbank portabel halten musste. postgis.net
Gilcierweb
(20,18)Auch bei +/- 99.
Rick James
Das ist ein Beispiel, das nicht als absolute Wahrheit angesehen werden sollte. Sie können eine andere dezimale Dezimalgenauigkeit (20, 18) usw. verwenden. Wenn Sie geografische und räumliche Daten speichern müssen, können Sie zu diesem Zweck die Postgis-Datenbank verwenden. Die MySQL Spatial Extensions sind eine gute Alternative, da sie dem OpenGIS-Geometriemodell folgen. Ich habe sie nicht verwendet, weil ich meine Datenbank portabel halten musste. postgis.net
Gilcierweb
Alter, dies ist nur ein Beispiel, Sie können die gewünschte Genauigkeit verwenden, wenn die Dezimalzahl Ihnen nicht dabei hilft, Postgis zu verwenden, eine Datenbank, die nur für geografische und räumliche Daten erstellt wurde
Gilcierweb,
-1

Code zur Verwendung / zum Nachweis der Genauigkeit der Antwort von Oğuzhan KURNUÇ .

ZUSAMMENFASSUNG:
Große Präzision (~ 1 cm) bei kleiner Größe (4B).

Die Genauigkeit beträgt (sehr nahe an) 7 Dezimalstellen für Werte über dem Bereich [-180, 180].
Das sind 7 Stellen rechts von der Dezimalstelle (~ 1 cm) , also insgesamt 9 Stellen (oder 10 Stellen, wenn die anfängliche "1" von "180" gezählt wird) in der Nähe von + -180.
Vergleichen Sie dies mit einem 4-Byte-Float , der insgesamt nur ~ 7 Stellen hat, also ~ 5 Stellen rechts von der Dezimalstelle in der Nähe von + = 180 (~ 1 m) .

Methoden zur Verwendung dieses Ansatzes:

const double Fixed7Mult = 10000000;

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

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

Präzisionstests:

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

Hilfsmethoden für Tests:

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
quelle