Quale tipo di dati MySQL dovrebbe essere usato per Latitudine / Longitudine con 8 cifre decimali?


257

Sto lavorando con i dati della mappa e l' Latitude/Longitudeestensione si estende a 8 cifre decimali. Per esempio:

Latitude 40.71727401
Longitude -74.00898606

Ho visto nel documento di Google che utilizza:

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

tuttavia, i loro decimali vanno solo a 6.
Dovrei usare FLOAT(10, 8)o c'è un altro metodo da considerare per memorizzare questi dati, quindi è preciso. Verrà utilizzato con i calcoli della mappa. Grazie!


4
Hai davvero bisogno di memorizzare valori sulla superficie terrestre con precisione di 1,1 mm ? Se è così, allora perché stai memorizzando i valori in latlng in primo luogo?
ovangle


2
Il documento di Google è SBAGLIATO! Non utilizzare il floattipo, che ha solo 7 cifre di precisione. Hai bisogno di almeno 9. Non hai bisogno di 10: i documenti per qualche strana ragione contano il segno meno come una cifra. Fare: double(9,6)o decimal(9,6).
Ariel,

5
Di quanta precisione hai davvero bisogno? 6 decimali ti danno abbastanza precisione per distinguere due persone che si baciano. 8 può distinguere le dita. FLOATdistingue due elementi distanti 1,7 m (5,6 piedi). Tutti questi sono ridicolmente eccessivi per le applicazioni "map"!
Rick James,

Risposte:


594

DECIMAL è il tipo di dati MySQL per l'aritmetica esatta. A differenza di FLOAT, la sua precisione è fissa per qualsiasi dimensione del numero, quindi usandola al posto di FLOAT potresti evitare errori di precisione quando esegui alcuni calcoli. Se stavi solo memorizzando e recuperando i numeri senza calcolo, in pratica FLOAT sarebbe sicuro, anche se non ci sono danni nell'uso di DECIMAL. Con i calcoli FLOAT è ancora per lo più ok, ma per essere assolutamente sicuro di 8d.p. precisione dovresti usare DECIMAL.

Le latitudini vanno da -90 a +90 (gradi), quindi DECIMAL (10, 8) è ok per quello, ma le lunghezze vanno da -180 a +180 (gradi) quindi è necessario DECIMAL (11, 8). Il primo numero è il numero totale di cifre memorizzate e il secondo è il numero dopo il punto decimale.

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

Questo spiega come MySQL funziona con tipi di dati a virgola mobile.

AGGIORNAMENTO: MySQL supporta tipi di dati spaziali ed Pointè un tipo a valore singolo che può essere utilizzato. Esempio:

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
Forse la mia risposta ha abusato della parola esatta, dato che DECIMAL è ancora accurato quanto la precisione che gli dai. Il mio punto era che è così preciso. Naturalmente alcuni calcoli espandono l'errore. Se ho un DECMIAL x allora sin (x ^ 100) sarà molto lontano. Ma se (usando DECIMAL (10, 8) o FLOAT (10, 8)) calcolo 0,3 / 3, DECIMAL fornisce 0,100000000000 (corretto) e float dà 0,100000003974 (corretto a 8dp, ma sarebbe sbagliato se moltiplicato). Capisco la differenza principale nel modo in cui i numeri sono memorizzati. DECIMAL memorizza le cifre decimali, dove FLOAT memorizza l'approssimazione binaria.
gandaliter,

1
Dal dubbio di precisione, vado a DOPPIO.
Ratata Tata,

1
8 cifre decimali indicano una precisione di 1,1 mm (meno di 1/16 di pollice). Perché mai ne avresti bisogno per latitudine e longitudine?
vartec,

1
Facebook sembra usare fino a 12 decimali per lat e 13 per lng. vartec ha scritto che 8 decimali sono pari a 1,1 mm; che dire di 7 e 6? (Non sono bravo in matematica). Sto usando il doppio per ora, ma vorrei verificare se potevo guadagnare nei calcoli della distanza cambiando tipo. Grazie.
Alain Zelink,

4
Le risposte a questa domanda ( gis.stackexchange.com/questions/8650/… ) forniscono informazioni sulla precisione che si ottiene con diversi numeri di posizioni decimali di latitudine e longitudine.
gandaliter

16

Inoltre, vedrai che i floatvalori sono arrotondati.

// ad es .: dati forniti 41.0473112,29.0077011

galleggiante (11,7) | decimale (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
È possibile utilizzare il doubletipo di dati, che ha la precisione necessaria.
Ariel,

1
Mostrami una mappa utile in grado di distinguere quei due punti. Dichiaro che entrambe le rappresentazioni sono "inutilmente precise".
Rick James,

14

in laravel ha usato il tipo di colonna decimale per la migrazione

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

per ulteriori informazioni consultare il tipo di colonna disponibile


7

È possibile impostare il tipo di dati come intero con segno. Quando si archiviano le coordinate su SQL, è possibile impostare come lat * 10000000 e long * 10000000. E quando selezioni per distanza / raggio dividi le coordinate di archiviazione su 10000000. L'ho provato con 300K righe, il tempo di risposta alla query è buono. (2 CPU da 2,67 GHz, 2 GB di RAM, MySQL 5.5.49)


Qual è più veloce? Fare questo o usando float o decimale?
Dinidiniz,

1
@Dinidiniz - La differenza di velocità è molto piccola. Il recupero delle righe travolge i tempi di qualsiasi azione del database.
Rick James,

Perché 10000000? Cosa succede se contiene più di 6 cifre dopo il valore decimale? O restituirà sempre 6 punti decimali.
Mahbub Morshed,

@MahbubMorshed - intendi 7 cifre - sono mostrate 7 cifre zero. Ma sì, questa tecnica memorizza sempre esattamente 7 cifre, non di più. (Se si utilizza un numero intero a 4 byte, non è possibile aumentare il moltiplicatore oltre 7 cifre poiché il valore della longitudine può essere grande fino a 180 e deve evitare il massimo di numeri interi con segno di overflow.) Si tratta di 2 cifre più precise rispetto alla memorizzazione in float a precisione singola, che ha solo 5 cifre a destra del punto decimale con valori di longitudine elevata. (179.99998 e 179.99997 possono memorizzare lo stesso valore float; 179.99996 è al sicuro lontano da 179.99998).)
ToolmakerSteve

Questo è il miglior compromesso che abbia mai visto. Qui mostro il codice da usare e per confermare che fornisce 7 cifre dopo il punto decimale, in un int con segno a 4 byte, per valori long / lat (quindi nell'intervallo -180 .. + 180). Grande precisione (~ 1 cm) in dimensioni ridotte (4B).
ToolmakerSteve

6

Non usare il float ... Arrotonderà le coordinate, causando strani eventi.

Usa decimale



4

Credo che il modo migliore per archiviare Lat / Lng in MySQL sia avere una colonna POINT (tipo di dati 2D) con un indice 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

Utilizzando migrare rubino su rotaie

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

Questo limite non si allunga a -99..99? Questo esclude gran parte del Pacifico!
Rick James,

Questo è un esempio non dovrebbe essere preso come verità assoluta. Puoi usare un'altra precisione decimale DECIMAL (20, 18) e così via ... Se hai bisogno di salvare dati geografici e spaziali puoi usare il database postgis per questo scopo. Le estensioni spaziali MySQL sono una buona alternativa perché seguono il modello geometria OpenGIS. Non li ho usati perché dovevo mantenere il mio database portatile. postgis.net
gilcierweb

(20,18)supera anche a +/- 99.
Rick James,

Questo è un esempio non dovrebbe essere preso come verità assoluta. Puoi usare un'altra precisione decimale DECIMAL (20, 18) e così via ... Se hai bisogno di salvare dati geografici e spaziali puoi usare il database postgis per questo scopo. Le estensioni spaziali MySQL sono una buona alternativa perché seguono il modello geometria OpenGIS. Non li ho usati perché dovevo mantenere il mio database portatile. postgis.net
gilcierweb

Amico, questo è solo un esempio, puoi usare la precisione che vuoi, se il decimale non ti aiuta a usare postgis un database creato solo per dati geografici e spaziali
gilcierweb,

-1

Codice per usare / dimostrare la precisione della risposta di Oğuzhan KURNUÇ .

SINTESI:
grande precisione (~ 1 cm) in dimensioni ridotte (4B).

La precisione è (molto vicina a) 7 cifre decimali per valori nell'intervallo [-180, 180].
Sono 7 cifre a destra del decimale (~ 1 cm) , per un totale di 9 cifre (o 10 cifre, se si considera "1" iniziale di "180") vicino a + -180.
Contrastalo con un float di 4 byte , che ha solo ~ 7 cifre in totale, quindi ~ 5 cifre a destra del decimale vicino + = 180 (~ 1m) .

Metodi per utilizzare questo approccio:

const double Fixed7Mult = 10000000;

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

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

Test di precisione:

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

Metodi di supporto utilizzati dai test:

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.