Какой тип данных MySQL следует использовать для широты / долготы с 8 знаками после запятой?


257

Я работаю с данными карты, и Latitude/Longitudeрасширяется до 8 десятичных знаков. Например:

Latitude 40.71727401
Longitude -74.00898606

Я видел в документе Google, который использует:

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

однако их десятичные разряды идут только к 6.
Должен ли я использовать FLOAT(10, 8)или есть другой метод для хранения этих данных, чтобы он был точным. Он будет использоваться с расчетами карты. Спасибо!


4
Вам действительно нужно хранить значения на поверхности Земли с точностью до 1,1 мм ? Если так, то почему вы храните значения в latlng в первую очередь?
Овангл


2
Документ Google НЕПРАВИЛЬНО! Не используйте floatтип - он имеет только 7 цифр точности. Вам нужно как минимум 9. Вам не нужно 10 - документы по какой-то странной причине считают знак минус цифрой. Делай или: double(9,6)или decimal(9,6).
Ариэль

5
Сколько точности вам действительно нужно? 6 знаков после запятой дают вам достаточно точности, чтобы различить двух людей, целующих друг друга. 8 можно развести пальцы. FLOATразличает два элемента на расстоянии 1,7 м (5,6 фута) друг от друга. Все это смехотворно излишне для приложений «карты»!
Рик Джеймс

Ответы:


594

DECIMAL - это тип данных MySQL для точной арифметики. В отличие от FLOAT его точность фиксирована для любого размера числа, поэтому, используя его вместо FLOAT, вы можете избежать ошибок точности при выполнении некоторых вычислений. Если вы просто сохраняете и извлекаете числа без расчета, то на практике FLOAT будет безопасным, хотя использование DECIMAL не повредит. С расчетами FLOAT все еще в основном нормально, но чтобы быть абсолютно уверенным в 8d.p. Точность вы должны использовать DECIMAL.

Широты варьируются от -90 до +90 (градусов), поэтому для этого вполне подойдет DECIMAL (10, 8), а для долгот - от -180 до +180 (градусов), поэтому вам нужно DECIMAL (11, 8). Первое число - это общее количество сохраненных цифр, а второе - число после десятичной точки.

Коротко: lat DECIMAL(10, 8) NOT NULL, lng DECIMAL(11, 8) NOT NULL

Это объясняет, как MySQL работает с типами данных с плавающей точкой.

ОБНОВЛЕНИЕ: MySQL поддерживает пространственные типы данных и Pointявляется типом с одним значением, который можно использовать. Пример:

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
Возможно, в моем ответе неправильно использовалось слово точный, поскольку DECIMAL все еще точен настолько, насколько точно вы его даете. Моя точка зрения в том , что она является , что точно. Конечно, некоторые вычисления увеличивают ошибку. Если у меня будет DECMIAL x, тогда sin (x ^ 100) будет далеко. Но если (используя DECIMAL (10, 8) или FLOAT (10, 8)) я вычисляю 0,3 / 3, тогда DECIMAL дает 0,100000000000 (правильно), а float дает 0,100000003974 (правильно до 8dp, но будет неправильно при умножении). Я понимаю, главное отличие состоит в том, как хранятся цифры. DECIMAL хранит десятичные цифры, где FLOAT хранит двоичное приближение.
гандалитер

1
Сомневаясь в точности, я собираюсь удвоить.
Ратата Тата

1
8 знаков после запятой - точность 1,1 мм (менее 1/16 дюйма). Зачем вам это нужно для широты и долготы?
vartec

1
Facebook, кажется, использует до 12 десятичных знаков для лат и 13 для lng. vartec пишет, что 8 десятичных знаков равны 1,1 мм; как насчет 7 и 6? (Я не очень хорош в математике). Я сейчас использую double, но хотел бы проверить, смогу ли я увеличить дистанцию, изменив тип. Спасибо.
Ален Зелинк

4
Ответы на этот вопрос ( gis.stackexchange.com/questions/8650/… ) дают информацию о точности, которую вы получаете при различном количестве десятичных разрядов широты и долготы.
гандалитер

16

Кроме того, вы увидите, что floatзначения округлены.

// напр .: заданные значения 41.0473112,29.0077011

плавать (11,7) | десятичное (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
Вы можете использовать doubleтип данных, который имеет необходимую точность.
Ариэль

1
Покажите мне полезную карту, которая может различить эти две точки. Я утверждаю, что оба представления "излишне точны".
Рик Джеймс

14

в laravel для переноса используется тип десятичного столбца

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

для получения дополнительной информации см. доступный тип столбца


7

Вы можете установить свой тип данных как целое число со знаком. Когда вы сохраняете координаты в SQL, вы можете установить значения lat * 10000000 и long * 10000000. И когда вы выбираете с расстоянием / радиусом, вы разделите координаты хранилища на 10000000. Я тестировал его с 300К строк, время ответа на запрос хорошее (2 x 2,67 ГГц ЦП, 2 ГБ ОЗУ, MySQL 5.5.49)


Что быстрее? Делаете это или используете float или decimal?
Динидиниз

1
@Dinidiniz - разница в скорости очень мала. Выборка строк перегружает время любого действия базы данных.
Рик Джеймс

Почему 10000000? Что произойдет, если он содержит более 6 цифр после десятичного значения? Или он всегда будет возвращать 6 десятичных знаков.
Махбуб Моршед

@MahbubMorshed - вы имеете в виду 7 цифр - показаны 7 нулевых цифр. Но да, эта техника всегда хранит ровно 7 цифр, не более. (Если используется 4-байтовое целое число, нельзя увеличить множитель свыше 7 цифр, поскольку значение долготы может достигать 180 и не должно допускать переполнения максимального целого числа со знаком.) Это на 2 цифры точнее, чем сохранение в плавающей запятой одинарной точности, который имеет только около 5 цифр справа от десятичной точки при больших значениях долготы. (179.99998 и 179.99997 могут хранить как одно и то же значение с плавающей точкой; 179.99996 безопасно от 179.99998).)
ToolmakerSteve

Это лучший компромисс, который я когда-либо видел. Здесь я показываю код для использования и подтверждения того, что он предоставляет 7 цифр после десятичной точки в 4-байтовом знаке int для значений long / lat (так, в пределах диапазона -180 .. + 180). Большая точность (~ 1 см) в небольшом размере (4B).
ToolmakerSteve

6

Не используйте float ... Он будет округлять ваши координаты, что приведет к некоторым странным событиям.

Используйте десятичную


4

MySQL теперь поддерживает пространственные типы данных, так как этот вопрос был задан. Таким образом, текущий принятый ответ не является неправильным, но если вы ищете дополнительные функциональные возможности, такие как поиск всех точек в данном многоугольнике, используйте тип данных POINT.

Ознакомьтесь с документами Mysql о типах геопространственных данных и функциях пространственного анализа


4

Я считаю, что лучший способ хранить Lat / Lng в MySQL - это иметь столбец POINT (2D-тип данных) с индексом 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

Использование миграционного рубина на рельсах

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

Разве это не ограничивает долготы до -99..99? Это исключает большую часть Тихого океана!
Рик Джеймс

Это пример, который не следует воспринимать как абсолютную истину. Вы можете использовать другую десятичную точность DECIMAL (20, 18) и т. Д. Если вам нужно сохранить географические и пространственные данные, вы можете использовать базу данных postgis для этой цели. Пространственные расширения MySQL являются хорошей альтернативой, поскольку они следуют модели геометрии OpenGIS. Я не использовал их, потому что мне нужно было поддерживать переносимость базы данных. postgis.net
gilcierweb

(20,18)также достигает максимума в +/- 99.
Рик Джеймс

Это пример не следует воспринимать как абсолютную истину. Вы можете использовать другую десятичную точность DECIMAL (20, 18) и т. Д. Если вам нужно сохранить географические и пространственные данные, вы можете использовать базу данных postgis для этой цели. Пространственные расширения MySQL являются хорошей альтернативой, поскольку они следуют модели геометрии OpenGIS. Я не использовал их, потому что мне нужно было сохранять базу данных переносимой. postgis.net
gilcierweb

Чувак, это всего лишь пример, вы можете использовать нужную вам точность, если десятичная дробь не помогает вам использовать postgis базу данных, созданную только для географических и пространственных данных
gilcierweb

-1

Код для использования / подтверждения точности ответа Oğuzhan KURNUÇ .

РЕЗЮМЕ:
Большая точность (~ 1 см) в небольшом размере (4B).

Точность составляет (очень близко к) 7 десятичных цифр для значений в диапазоне [-180, 180].
Это 7 цифр справа от десятичного числа (~ 1 см) , в общей сложности 9 цифр (или 10 цифр, если считать начальные "1" или "180") около + -180.
Сравните это с 4-байтовым числом с плавающей точкой , которое имеет всего ~ 7 цифр, так что ~ 5 цифр справа от десятичной точки около + = 180 (~ 1 м) .

Методы использования этого подхода:

const double Fixed7Mult = 10000000;

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

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

Тесты точности:

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

Вспомогательные методы, используемые тестами:

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;
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.