ชนิดข้อมูล 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 ในสถานที่แรก?
ovangle


2
Google เอกสารผิดพลาด! อย่าใช้floatประเภท - ที่มีความแม่นยำ 7 หลักเท่านั้น คุณต้องการอย่างน้อย 9 คุณไม่จำเป็นต้องมี 10 - เอกสารด้วยเหตุผลแปลก ๆ บางอย่างให้นับเครื่องหมายลบเป็นหลัก ทำอย่างใดอย่างหนึ่งหรือdouble(9,6) decimal(9,6)
Ariel

5
คุณจะทำอย่างไรที่มีความแม่นยำมากจริงๆต้อง? ทศนิยม 6 ตำแหน่งให้ความแม่นยำเพียงพอที่จะแยกคนสองคนมาจูบกัน 8 สามารถแยกนิ้วออกจากกัน FLOATแยกความแตกต่างสองรายการ 1.7m (5.6ft) ออกจากกัน สิ่งเหล่านี้มากเกินไปอย่างน่าหัวเราะสำหรับแอปพลิเคชั่น "แผนที่"!
Rick James

คำตอบ:


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 ใช้งานกับชนิดข้อมูลทศนิยม

UPDATE: MySQL รองรับประเภทข้อมูล Spatialและ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 แล้วบาป (x ^ 100) จะเป็นไปได้ แต่ถ้า (ใช้ DECIMAL (10, 8) หรือ FLOAT (10, 8)) ฉันคำนวณ 0.3 / 3 แล้ว DECIMAL ให้ 0.100000000000 (ถูกต้อง) และลอยให้ 0.100000003974 (ถูกต้อง 8dp แต่จะผิดถ้าคูณ) ฉันเข้าใจความแตกต่างที่สำคัญคือวิธีการจัดเก็บหมายเลข DECIMAL เก็บตัวเลขทศนิยมที่ FLOAT เก็บการประมาณแบบไบนารี
gandaliter

1
ด้วยความสงสัยในความแม่นยำฉันจะเพิ่มเป็นสองเท่า
Ratata Tata

1
8 ตำแหน่งทศนิยมคือ 1.1 มม. (น้อยกว่า 1/16 นิ้ว) ทำไมคุณถึงต้องการละติจูดและลองจิจูด?
vartec

1
Facebook ดูเหมือนจะใช้ทศนิยมสูงสุดถึง 12 หน่วยสำหรับ lat และ 13 สำหรับ lng vartec เขียนว่า 8 ทศนิยมเท่ากับ 1.1 มม. แล้ว 7 กับ 6 ล่ะ (ฉันไม่เก่งคณิตศาสตร์) ตอนนี้ฉันกำลังใช้สองเท่า แต่ต้องการตรวจสอบว่าฉันสามารถคำนวณระยะทางโดยเปลี่ยนประเภทได้หรือไม่ ขอบคุณ.
Alain Zelink

4
คำตอบสำหรับคำถามนี้ ( gis.stackexchange.com/questions/8650/… ) ให้ข้อมูลเกี่ยวกับความแม่นยำที่คุณได้รับด้วยจำนวนตำแหน่งทศนิยมละติจูดและลองจิจูดที่แตกต่างกัน
gandaliter

16

นอกจากนี้คุณจะเห็นว่าfloatค่านั้นถูกปัดเศษ

// เช่น: ค่าที่กำหนด 41.0473112,29.0077011

ลอย (11,7) | ทศนิยม (11,7)
---------------------------
41.0473099 | 41.0473112
29.0077019 | 29.0077011


1
คุณสามารถใช้doubleชนิดข้อมูลที่ต้องการความแม่นยำ
Ariel

1
แสดงแผนที่ที่มีประโยชน์ที่สามารถแยกความแตกต่างของจุดทั้งสองนั้นได้ ฉันอ้างว่าการเป็นตัวแทนทั้งสองนั้น "แม่นยำโดยไม่จำเป็น"
Rick James

14

ใน laravel ใช้ชนิดคอลัมน์ทศนิยมสำหรับการย้ายข้อมูล

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

สำหรับข้อมูลเพิ่มเติม ดูประเภทคอลัมน์ที่มีอยู่


7

คุณสามารถตั้งค่าชนิดข้อมูลของคุณเป็นจำนวนเต็มลงนาม เมื่อคุณจัดเก็บพิกัดกับ SQL คุณสามารถตั้งค่าเป็น lat * 10000000 และ long * 10000000 และเมื่อคุณเลือกด้วยระยะทาง / รัศมีคุณจะแบ่งพิกัดหน่วยเก็บข้อมูลเป็น 10,000,000 ฉันทดสอบกับแถว 300K เวลาตอบแบบสอบถามเป็นสิ่งที่ดี (CPU 2 x 2.67GHz, RAM 2 GB, MySQL 5.5.49)


ไหนเร็วกว่ากัน ทำสิ่งนี้หรือใช้ทศนิยมหรือทศนิยม?
Dinidiniz

1
@Dinidiniz - ความแตกต่างความเร็วมีขนาดเล็กมาก การดึงแถวเข้ามาทำให้เวลาของการดำเนินการฐานข้อมูล
Rick James

ทำไม 10,000,000 จะเกิดอะไรขึ้นถ้ามันมีตัวเลขมากกว่า 6 หลักหลังค่าทศนิยม หรือจะคืนค่าทศนิยม 6 ตำแหน่งเสมอ
Mahbub Morshed

@MahbubMorshed - คุณหมายถึง7หลัก - แสดงตัวเลข 7 ศูนย์ แต่ใช่เทคนิคนี้จะเก็บ 7 หลักเสมอไม่มาก (หากใช้จำนวนเต็ม 4 ไบต์ไม่สามารถเพิ่มตัวคูณได้เกิน 7 หลักเนื่องจากค่าลองจิจูดอาจมีขนาดใหญ่ถึง 180 และต้องหลีกเลี่ยงการล้นจำนวนสูงสุดที่ลงนามแล้ว) นี่คือตัวเลข 2 หลักที่แม่นยำกว่าการจัดเก็บในทศนิยมที่มีความแม่นยำเดียว ซึ่งมีเพียง 5 หลัก - จาก - ไป - ขวา - ของ - จุดทศนิยมที่ค่าลองจิจูดขนาดใหญ่ (179.99998 และ 179.99997 อาจเก็บเป็นค่าลอยตัวเดียวกัน 179.99996 ปลอดภัยจาก 179.99998))
ToolmakerSteve

นี่เป็นการแลกเปลี่ยนที่ดีที่สุดที่ฉันเคยเห็นทุกที่ ที่นี่ฉันแสดงรหัสเพื่อใช้และเพื่อยืนยันว่ามีตัวเลข 7 หลักหลังจุดทศนิยมใน int 4-byte ที่ลงนามแล้วสำหรับค่า long / lat (ในช่วง -180 .. + 180) ความแม่นยำสูง (~ 1 ซม.) ในขนาดเล็ก (4B)
ToolmakerSteve

6

อย่าใช้โฟลต ... มันจะปัดเศษพิกัดของคุณทำให้เกิดเหตุการณ์แปลก ๆ

ใช้ทศนิยม


4

ตอนนี้ MySQL รองรับชนิดข้อมูลเชิงพื้นที่ตั้งแต่คำถามนี้ถูกถาม ดังนั้นคำตอบที่ได้รับการยอมรับในปัจจุบันนั้นไม่ผิด แต่ถ้าคุณกำลังมองหาฟังก์ชั่นเพิ่มเติมเช่นการหาจุดทั้งหมดภายในรูปหลายเหลี่ยมที่กำหนดให้ใช้ชนิดข้อมูล POINT

เช็คเอาท์ Mysql เอกสารใน Geospatial ชนิดข้อมูลและฟังก์ชั่นการวิเคราะห์เชิงพื้นที่


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 หรือไม่ สิ่งนี้ไม่รวมในมหาสมุทรแปซิฟิก!
Rick James

นี่คือตัวอย่างที่ไม่ควรถือเป็นความจริงเด็ดขาด คุณสามารถใช้ทศนิยมความแม่นยำทศนิยมอีก (20, 18) และอื่น ๆ ... หากคุณต้องการบันทึกข้อมูลทางภูมิศาสตร์และข้อมูลเชิงพื้นที่คุณสามารถใช้ฐานข้อมูล postgis เพื่อจุดประสงค์นี้ MySQL Spatial Extensions เป็นทางเลือกที่ดีเพราะทำตามแบบจำลอง OpenGIS Geometry ฉันไม่ได้ใช้เพราะฉันต้องการให้ฐานข้อมูลของฉันพกพาได้ postgis.net
gilcierweb

(20,18)ยังยอดที่ +/- 99
Rick James

นี่คือตัวอย่างที่ไม่ควรถือเป็นความจริงเด็ดขาด คุณสามารถใช้ทศนิยมความแม่นยำทศนิยมอีก (20, 18) และอื่น ๆ ... หากคุณต้องการบันทึกข้อมูลทางภูมิศาสตร์และข้อมูลเชิงพื้นที่คุณสามารถใช้ฐานข้อมูล postgis เพื่อจุดประสงค์นี้ MySQL Spatial Extensions เป็นทางเลือกที่ดีเพราะทำตามแบบจำลอง OpenGIS Geometry ฉันไม่ได้ใช้เพราะฉันต้องการให้ฐานข้อมูลของฉันพกพาได้ postgis.net
gilcierweb

เพื่อนนี่เป็นเพียงตัวอย่างคุณสามารถใช้ความแม่นยำที่คุณต้องการหากทศนิยมไม่ช่วยให้คุณใช้ postgis ฐานข้อมูลที่สร้างขึ้นสำหรับข้อมูลทางภูมิศาสตร์และอวกาศ
gilcierweb

-1

รหัสการใช้ / การพิสูจน์ความแม่นยำของคำตอบOğuzhanKURNUÇของ

สรุป:
ความแม่นยำสูง (~ 1 ซม.) ในขนาดที่เล็ก (4B)

ความแม่นยำคือ (ใกล้มาก) 7 หลักทศนิยมสำหรับค่าในช่วง [-180, 180]
นั่นคือ7 หลักทางด้านขวาของทศนิยม (~ 1 ซม.)รวม 9 หลัก (หรือ 10 หลักถ้านับ "1" จาก "180") เริ่มต้นใกล้ + -180
คมชัดนี้กับลอย 4 ไบต์ซึ่งมีเพียง ~ 7 หลักรวมดังนั้น ~ 5 ตัวเลขไปทางขวาของจุดทศนิยมใกล้ + = 180 (~ 1m)

วิธีการที่ใช้วิธีนี้:

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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.