Який тип даних MySQL слід використовувати для Latitude / Longitude з 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 WRONG! Не використовуйте 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 (градусів), тому вам потрібно ДЕКІМАЛЬНИЙ (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 все ще є настільки ж точним, як і точність, яку ви йому надаєте. Моя точка зору в тому , що вона є , що точно. Звичайно, деякі обчислення розширюють помилку. Якщо у мене є ДЕКСІАЛЬНИЙ x, то гріх (x ^ 100) буде віддалений. Але якщо (використовуючи DECIMAL (10, 8) або FLOAT (10, 8)) я обчислюю 0,3 / 3, тоді DECIMAL дає 0,100000000000 (правильне), а float дає 0,100000003974 (правильно на 8dp, але було б неправильно, якщо помножити). Я розумію, головна відмінність полягає в тому, як зберігаються номери. DECIMAL зберігає десяткові цифри, де FLOAT зберігає двійкове наближення.
гандалітер

1
По сумніву в точності я збираюся ДВОЙСЯ.
Ratata Tata

1
8 знаків після коми - це точність 1,1 мм (менше 1/16 дюйма). Навіщо вам це колись потрібно для широти і довготи?
vartec

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

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)


Що швидше? Робити це чи за допомогою плавця чи десяткової?
Дінідініз

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

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

@MahbubMorshed - ви маєте на увазі 7 цифр - показано 7 нульових цифр. Але так, ця методика завжди зберігає рівно 7 цифр, не більше. (Якщо використовувати 4-байтове ціле число, не можна збільшити множник понад 7 цифр, оскільки значення довготи може бути до 180, і повинно уникати переповнення підписаного цілого числа максимуму.) Це дві цифри точніше, ніж зберігання в одноточній поплавці, який має близько 5 цифр праворуч від десяткової крапки з великими значеннями довготи. (179,99998 179,99997 і можуть зберігати в тому ж значенні з плаваючою точкою; 179,99996 безпечно далека від 179.99998)).
ToolmakerSteve

Це найкращий компроміс, який я бачив де-небудь. Тут я показую код, який потрібно використовувати та підтверджувати, що він містить 7 цифр після десяткових знаків у 4-байтовому підписаному int для значень long / lat (тобто в діапазоні -180 .. + 180). Велика точність (~ 1см) в невеликих розмірах (4В).
ToolmakerSteve

6

Не використовуйте float ... Це округлятиме ваші координати, внаслідок чого виникають дивні випадки.

Використовуйте десятковий


4

MySQL тепер підтримує типи просторових даних, оскільки це питання було задано. Отже, поточна прийнята відповідь не є помилковою, але якщо ви шукаєте додаткову функціональність, як пошук усіх точок у даному полігоні, тоді використовуйте тип даних POINT.

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


4

Я вважаю, що найкращим способом зберігання Lat / Lng у MySQL є створення стовпця POINT (2D тип даних) із СПЕЦІАЛЬНИМ індексом.

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? Це виключає значну частину Тихого океану!
Рік Джеймс

Тобто приклад не слід сприймати як абсолютну істину. Ви можете використовувати іншу ДЕКІМАЛЬНУ десяткову точність (20, 18) і так далі ... Якщо вам потрібно зберегти географічні та просторові дані, ви можете використовувати для цього базу даних postgis. Просторові розширення MySQL є хорошою альтернативою, оскільки вони відповідають моделі геометрії OpenGIS. Я не використовував їх, бо мені потрібно було зберігати свою базу даних. postgis.net
gilcierweb

(20,18)також перевищує +/- 99.
Рік Джеймс

Тобто приклад не слід сприймати як абсолютну істину. Ви можете використовувати іншу ДЕКІМАЛЬНУ десяткову точність (20, 18) і так далі ... Якщо вам потрібно зберегти географічні та просторові дані, ви можете використовувати для цього базу даних postgis. Просторові розширення MySQL є хорошою альтернативою, оскільки вони відповідають моделі геометрії OpenGIS. Я не використовував їх, бо мені потрібно було зберігати свою базу даних. postgis.net
gilcierweb

Чувак, це лише приклад, ви можете використовувати потрібну точність, якщо десятковий не допомагає вам використовувати postgis базу даних,
створену

-1

Код для використання / доведення точності відповіді Оюжана КУРНУÇ .

ПІДСУМОК:
Велика точність (~ 1 см) у невеликих розмірах (4В).

Точність - це (дуже близька до) 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.