Quel type de données MySQL doit être utilisé pour Latitude / Longitude avec 8 décimales?


257

Je travaille avec des données cartographiques, et le Latitude/Longitudeétend à 8 décimales. Par exemple:

Latitude 40.71727401
Longitude -74.00898606

J'ai vu dans le document Google qui utilise:

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

cependant, leur décimale ne passe qu'à 6.
Dois-je utiliser FLOAT(10, 8)ou existe-t-il une autre méthode à considérer pour stocker ces données afin qu'elles soient précises. Il sera utilisé avec des calculs de carte. Merci!


4
Avez-vous vraiment besoin de stocker des valeurs à la surface de la terre précises à 1,1 mm ? Si oui, alors pourquoi stockez-vous des valeurs en latlng en premier lieu?
ovangle


2
Le document Google est faux! N'utilisez pas le floattype - qui n'a que 7 chiffres de précision. Vous avez besoin d'au moins 9. Vous n'avez pas besoin de 10 - les documents pour une raison étrange comptent le signe moins comme un chiffre. Faites soit: double(9,6)ou decimal(9,6).
Ariel

5
De quelle précision avez-vous vraiment besoin? 6 décimales vous donne suffisamment de précision pour distinguer deux personnes qui s'embrassent. 8 peut distinguer vos doigts. FLOATdistingue deux éléments distants de 1,7 m (5,6 pieds). Tous ces éléments sont ridiculement excessifs pour les applications "cartographiques"!
Rick James

Réponses:


594

DECIMAL est le type de données MySQL pour l'arithmétique exacte. Contrairement à FLOAT, sa précision est fixée pour n'importe quelle taille de nombre, donc en l'utilisant à la place de FLOAT, vous pouvez éviter les erreurs de précision lors de certains calculs. Si vous stockiez et récupériez simplement les nombres sans calcul, alors dans la pratique, FLOAT serait sûr, bien qu'il n'y ait aucun mal à utiliser DECIMAL. Avec les calculs, FLOAT est toujours correct, mais pour être absolument sûr de 8d.p. précision, vous devez utiliser DECIMAL.

Les latitudes varient de -90 à +90 (degrés), donc DECIMAL (10, 8) est correct pour cela, mais les longitudes varient de -180 à +180 (degrés), vous avez donc besoin de DECIMAL (11, 8). Le premier nombre est le nombre total de chiffres stockés et le second est le nombre après la virgule décimale.

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

Cela explique comment MySQL fonctionne avec les types de données à virgule flottante.

MISE À JOUR: MySQL prend en charge les types de données spatiales et Pointest un type à valeur unique qui peut être utilisé. Exemple:

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
Peut-être que ma réponse a mal utilisé le mot exact, car DECIMAL est toujours aussi précis que la précision que vous lui donnez. Mon point est qu'il est - ce exact. Bien sûr, certains calculs augmentent l'erreur. Si j'ai un DECMIAL x alors le péché (x ^ 100) va être loin. Mais si (en utilisant DECIMAL (10, 8) ou FLOAT (10, 8)) je calcule 0,3 / 3 alors DECIMAL donne 0.100000000000 (correct), et float donne 0.100000003974 (correct à 8dp, mais serait faux s'il était multiplié). Je comprends que la principale différence réside dans la façon dont les numéros sont stockés. DECIMAL stocke les chiffres décimaux, où FLOAT stocke l'approximation binaire.
gandaliter

1
Par le doute de la précision, je vais DOUBLER.
Ratata Tata

1
8 décimales est une précision de 1,1 mm (moins de 1/16 de pouce). Pourquoi auriez-vous besoin de cela pour la latitude et la longitude?
vartec

1
Facebook semble utiliser jusqu'à 12 décimales pour lat et 13 pour lng. vartec a écrit que 8 décimales est égal à 1,1 mm; qu'en est-il de 7 et 6? (Je ne suis pas bon en maths). J'utilise le double pour l'instant, mais je voudrais vérifier si je pouvais gagner en calculs de distance en changeant de type. Je vous remercie.
Alain Zelink

4
Les réponses à cette question ( gis.stackexchange.com/questions/8650/… ) donnent des informations sur la précision que vous obtenez avec différents nombres de décimales de latitude et de longitude.
gandaliter

16

De plus, vous verrez que les floatvaleurs sont arrondies.

// ex: valeurs données 41.0473112,29.0077011

flotteur (11,7) | décimal (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
Vous pouvez utiliser le doubletype de données, qui a la précision requise.
Ariel

1
Montrez-moi une carte utile qui peut distinguer ces deux points. Je prétends que les deux représentations sont "inutilement précises".
Rick James

14

dans laravel utilisé le type de colonne décimale pour la migration

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

pour plus d'informations, voir le type de colonne disponible


7

Vous pouvez définir votre type de données comme entier signé. Lorsque vous stockez les coordonnées en SQL, vous pouvez définir comme lat * 10000000 et long * 10000000. Et lorsque vous sélectionnez avec distance / rayon, vous divisez les coordonnées de stockage à 10000000. Je l'ai testé avec 300K lignes, le temps de réponse aux requêtes est bon. (2 x 2,67 GHz, 2 Go de RAM, MySQL 5.5.49)


Lequel est plus vite? Faire cela ou utiliser un flottant ou décimal?
Dinidiniz

1
@Dinidiniz - La différence de vitesse est très faible. La récupération des lignes dépasse le délai de toute action de base de données.
Rick James

Pourquoi 10000000? Que se passe-t-il s'il contient plus de 6 chiffres après la valeur décimale? Ou retournera-t-il toujours 6 décimales.
Mahbub Morshed

@MahbubMorshed - vous voulez dire 7 chiffres - il y a 7 chiffres zéro affichés. Mais oui, cette technique stocke toujours exactement 7 chiffres, pas plus. (Si vous utilisez un entier sur 4 octets, vous ne pouvez pas augmenter le multiplicateur au-delà de 7 chiffres, car la valeur de la longitude peut atteindre 180, et vous devez éviter le dépassement du nombre entier signé maximum.) C'est 2 chiffres plus précis que le stockage dans un flottant simple précision, qui a seulement environ 5 chiffres à droite du point décimal à de grandes valeurs de longitude. (179.99998 et 179.99997 peuvent stocker la même valeur flottante; 179.99996 est loin de 179.99998).)
ToolmakerSteve

C'est le meilleur compromis que j'aie jamais vu. Ici, je montre le code à utiliser et pour confirmer qu'il fournit 7 chiffres après le point décimal, dans un entier signé de 4 octets, pour les valeurs long / lat (donc dans la plage -180 .. + 180). Grande précision (~ 1cm) dans une petite taille (4B).
ToolmakerSteve

6

N'utilisez pas de flotteur ... Il arrondira vos coordonnées, ce qui entraînera des événements étranges.

Utiliser décimal



4

Je crois que la meilleure façon de stocker Lat / Lng dans MySQL est d'avoir une colonne POINT (type de données 2D) avec un index 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

Utilisation de Migrate Ruby sur des rails

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

Cela ne limitera-t-il pas les longitudes à -99..99? Cela exclut une grande partie du Pacifique!
Rick James

C'est un exemple qui ne doit pas être pris comme une vérité absolue. Vous pouvez utiliser une autre précision décimale DECIMAL (20, 18) et ainsi de suite ... Si vous avez besoin de sauvegarder des données géographiques et spatiales, vous pouvez utiliser la base de données postgis à cet effet. Les extensions spatiales MySQL sont une bonne alternative car elles suivent le modèle de géométrie OpenGIS. Je ne les ai pas utilisés parce que je devais garder ma base de données portable. postgis.net
gilcierweb

(20,18)arrive également à +/- 99.
Rick James

C'est un exemple qui ne doit pas être pris comme une vérité absolue. Vous pouvez utiliser une autre précision décimale DECIMAL (20, 18) et ainsi de suite ... Si vous avez besoin de sauvegarder des données géographiques et spatiales, vous pouvez utiliser la base de données postgis à cet effet. Les extensions spatiales MySQL sont une bonne alternative car elles suivent le modèle de géométrie OpenGIS. Je ne les ai pas utilisés parce que je devais garder ma base de données portable. postgis.net
gilcierweb

Mec, ce n'est qu'un exemple, vous pouvez utiliser la précision que vous voulez, si la décimale ne vous aide pas à utiliser postgis une base de données conçue uniquement pour les données géographiques et spatiales
gilcierweb

-1

Code pour utiliser / prouver la précision de la réponse d' Oğuzhan KURNUÇ .

RÉSUMÉ:
Grande précision (~ 1 cm) dans une petite taille (4B).

La précision est (très proche de) 7 chiffres décimaux pour les valeurs supérieures à la plage [-180, 180].
Cela fait 7 chiffres à droite de la décimale (~ 1 cm) , pour un total de 9 chiffres (ou 10 chiffres, si l'on compte le "1" initial de "180") près de + -180.
Comparez cela avec un flottant de 4 octets , qui n'a que ~ 7 chiffres au total, donc ~ 5 chiffres à droite de la décimale près de + = 180 (~ 1 m) .

Méthodes pour utiliser cette approche:

const double Fixed7Mult = 10000000;

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

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

Tests de précision:

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

Méthodes auxiliaires utilisées par les 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;
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.