Que tipo de dados MySQL deve ser usado para Latitude / Longitude com 8 casas decimais?

257

Estou trabalhando com dados do mapa e o número Latitude/Longitudese estende para 8 casas decimais. Por exemplo:

Latitude 40.71727401
Longitude -74.00898606

Vi no documento do Google que usa:

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

no entanto, suas casas decimais só vão para 6.
Devo usar FLOAT(10, 8)ou existe outro método a considerar para armazenar esses dados para que sejam precisos. Será usado com os cálculos do mapa. Obrigado!

Edward
fonte
4
Você realmente precisa armazenar valores na superfície da Terra com precisão de 1,1 mm ? Se sim, por que você está armazenando valores em latlng em primeiro lugar?
ovangle 22/03
2
O documento do Google está errado! Não use o floattipo - que possui apenas 7 dígitos de precisão. Você precisa de pelo menos 9. Você não precisa de 10 - os documentos, por algum motivo estranho, contam o sinal de menos como um dígito. Faça: double(9,6)ou decimal(9,6).
Ariel
5
Quanta precisão você realmente precisa? 6 casas decimais oferecem precisão suficiente para distinguir duas pessoas se beijando. 8 pode distinguir seus dedos. FLOATdistingue dois itens separados por 1,7 m (5,6 pés). Tudo isso é absurdamente excessivo para aplicativos de "mapa"!
Rick James

Respostas:

594

DECIMAL é o tipo de dados MySQL para aritmética exata. Ao contrário de FLOAT, sua precisão é fixa para qualquer tamanho de número; portanto, ao usá-lo em vez de FLOAT, você pode evitar erros de precisão ao fazer alguns cálculos. Se você estivesse apenas armazenando e recuperando os números sem cálculo, na prática o FLOAT seria seguro, embora não exista nenhum dano ao usar DECIMAL. Com os cálculos, o FLOAT ainda está ok, mas para ter certeza absoluta de 8d.p. precisão, você deve usar DECIMAL.

As latitudes variam de -90 a +90 (graus), então DECIMAL (10, 8) é bom para isso, mas as longitudes variam de -180 a +180 (graus), então você precisa de DECIMAL (11, 8). O primeiro número é o número total de dígitos armazenados e o segundo é o número após o ponto decimal.

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

Isso explica como o MySQL trabalha com tipos de dados de ponto flutuante.

ATUALIZAÇÃO: O MySQL suporta tipos de dados espaciais e Pointé um tipo de valor único que pode ser usado. Exemplo:

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
fonte
11
Talvez minha resposta tenha usado incorretamente a palavra exata, pois DECIMAL ainda é tão preciso quanto a precisão que você fornece. Meu argumento era que isso é preciso. É claro que alguns cálculos expandem o erro. Se eu tiver um DECMIAL x, o pecado (x ^ 100) será muito distante. Mas se (usando DECIMAL (10, 8) ou FLOAT (10, 8)) eu calcular 0,3 / 3, DECIMAL fornecerá 0.100000000000 (correto) e float fornecerá 0.100000003974 (correto para 8dp, mas estaria errado se multiplicado). Entendo que a principal diferença está em como os números são armazenados. DECIMAL armazena os dígitos decimais, onde FLOAT armazena a aproximação binária.
gandaliter
1
Pela dúvida da precisão, eu vou DOBRAR.
Ratata Tata 31/03
1
8 casas decimais têm precisão de 1,1 mm (menos de 1/16 de polegada). Por que você precisaria disso para latitude e longitude?
vartec
1
O Facebook parece usar até 12 casas decimais para lat e 13 para lng. vartec escreveu que 8 casas decimais são iguais a 1,1 mm; que tal 7 e 6? (Eu não sou bom em matemática). Estou usando o dobro por enquanto, mas gostaria de verificar se poderia ganhar nos cálculos de distância alterando o tipo. Obrigado.
Alain Zelink
4
As respostas para esta pergunta ( gis.stackexchange.com/questions/8650/… ) fornecem informações sobre a precisão obtida com diferentes números de casas decimais de latitude e longitude.
gandaliter
16

Além disso, você verá que os floatvalores são arredondados.

// por exemplo: valores fornecidos 41.0473112,29.0077011

flutuador (11,7) | decimal (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011

K-Gun
fonte
1
Você pode usar o doubletipo de dados, que possui a precisão necessária.
Ariel
1
Mostre-me um mapa útil que possa distinguir esses dois pontos. Afirmo que ambas as representações são "desnecessariamente precisas".
Rick James
14

no laravel usado tipo de coluna decimal para migração

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

para obter mais informações, consulte o tipo de coluna disponível

Jignesh Joisar
fonte
7

Você pode definir seu tipo de dados como número inteiro assinado. Ao armazenar coordenadas no SQL, você pode definir como lat * 10000000 e long * 10000000. E ao selecionar com distância / raio, você dividirá as coordenadas de armazenamento em 10000000. Fiz o teste com 300K linhas, o tempo de resposta da consulta é bom. (CPU de 2 x 2,67 GHz, 2 GB de RAM, MySQL 5.5.49)

Oğuzhan KURNUÇ
fonte
O que é mais rápido? Fazendo isso ou usando float ou decimal?
Dinidiniz 16/03/19
1
@Dinidiniz - A diferença de velocidade é muito pequena. A busca de linhas sobrecarrega o tempo de qualquer ação do banco de dados.
Rick James
Por que 10000000? O que acontece se ele contiver mais de 6 dígitos após o valor decimal? Ou sempre retornará 6 pontos decimais.
Mahbub Morshed
@MahbubMorshed - você quer dizer 7 dígitos - são exibidos 7 dígitos zero. Mas sim, essa técnica está sempre armazenando exatamente 7 dígitos, não mais. (Se você estiver usando um número inteiro de 4 bytes, não poderá aumentar o multiplicador além de 7 dígitos, pois o valor da longitude pode ser tão grande quanto 180 e deve evitar transbordar o máximo inteiro assinado.) São 2 dígitos mais precisos do que o armazenamento no float de precisão única, que possui apenas cerca de 5 dígitos à direita do ponto decimal em grandes valores de longitude. (179,99998 e 179,99997 pode armazenar o mesmo valor float; 179,99996 é seguramente longe de 179,99998)).
ToolmakerSteve
Esta é a melhor troca que eu já vi em qualquer lugar. Aqui eu mostro o código para usar e confirmar que ele fornece 7 dígitos após o ponto decimal, em um int assinado de 4 bytes, para valores longos / lat (portanto, dentro do intervalo -180 .. + 180). Grande precisão (~ 1 cm) em tamanho pequeno (4B).
ToolmakerSteve 31/03
6

Não use float ... Ele arredondará suas coordenadas, resultando em algumas ocorrências estranhas.

Use decimal

Sam Sabey
fonte
4

Eu acredito que a melhor maneira de armazenar Lat / Lng no MySQL é ter uma coluna POINT (tipo de dados 2D) com um índice 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));
ΔO 'deltazero'
fonte
0

Usando migrar ruby ​​nos trilhos

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
fonte
Isso não limitará longitudes a -99..99? Isso exclui grande parte do Pacífico!
Rick James
Esse é um exemplo que não deve ser tomado como verdade absoluta. Você pode usar outra precisão decimal DECIMAL (20, 18) e assim por diante ... Se precisar salvar dados geográficos e espaciais, poderá usar o banco de dados postgis para esse fim. As extensões espaciais do MySQL são uma boa alternativa, porque seguem o modelo de geometria OpenGIS. Não os usei porque precisava manter meu banco de dados portátil. postgis.net
gilcierweb
(20,18)também chega a +/- 99.
Rick James
Esse é um exemplo que não deve ser tomado como verdade absoluta. Você pode usar outra precisão decimal DECIMAL (20, 18) e assim por diante ... Se precisar salvar dados geográficos e espaciais, poderá usar o banco de dados postgis para esse fim. As extensões espaciais do MySQL são uma boa alternativa, porque seguem o modelo de geometria OpenGIS. Não os usei porque precisava manter meu banco de dados portátil. postgis.net
gilcierweb
Cara isso é apenas um exemplo, você pode usar a precisão que você quer, se decimal não está ajudando você usar um banco de dados PostGIS feito apenas para dados geográficos e espaciais
gilcierweb
-1

Código para usar / provar a precisão da resposta de Oğuzhan KURNUÇ .

RESUMO:
Ótima precisão (~ 1cm) em tamanho pequeno (4B).

A precisão é (muito próxima a) 7 dígitos decimais para valores acima do intervalo [-180, 180].
São 7 dígitos à direita do decimal (~ 1 cm) , para um total de 9 dígitos (ou 10 dígitos, se contar o "1" inicial de "180") próximo a -180.
Compare isso com uma flutuação de 4 bytes , que possui apenas ~ 7 dígitos no total, portanto, ~ 5 dígitos à direita do decimal próximo a + = 180 (~ 1m) .

Métodos para usar esta abordagem:

const double Fixed7Mult = 10000000;

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

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

Testes de precisão:

/// <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étodos auxiliares utilizados pelos testes:

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;
    }
}
Fabricante de ferramentas
fonte