วิธีที่เร็วที่สุดในการค้นหาระยะห่างระหว่างคะแนน Lat / Long สองจุด


227

ปัจจุบันฉันมีสถานที่ตั้งอยู่หลายล้านแห่งในฐานข้อมูล mysql ทั้งหมดที่มีลองจิจูดและข้อมูลละติจูด

ฉันพยายามค้นหาระยะห่างระหว่างจุดหนึ่งและจุดอื่น ๆ ผ่านทางแบบสอบถาม ไม่เร็วเท่าที่ฉันต้องการโดยเฉพาะอย่างยิ่งเมื่อมีผู้ชมมากกว่า 100 ครั้งต่อวินาที

มีแบบสอบถามที่เร็วขึ้นหรืออาจเป็นระบบที่เร็วกว่า mysql สำหรับสิ่งนี้หรือไม่? ฉันใช้คำค้นหานี้:

SELECT 
  name, 
   ( 3959 * acos( cos( radians(42.290763) ) * cos( radians( locations.lat ) ) 
   * cos( radians(locations.lng) - radians(-71.35368)) + sin(radians(42.290763)) 
   * sin( radians(locations.lat)))) AS distance 
FROM locations 
WHERE active = 1 
HAVING distance < 10 
ORDER BY distance;

หมายเหตุ: ระยะทางที่จัดไว้ให้อยู่ในไมล์ หากคุณต้องการกิโลเมตรใช้แทน63713959


31
สูตรที่คุณให้ดูเหมือนจะมีองค์ประกอบมากมายที่คงที่ เป็นไปได้หรือไม่ที่จะคำนวณข้อมูลล่วงหน้าและเก็บค่าเหล่านั้นไว้ในฐานข้อมูลของคุณ ยกตัวอย่างเช่น 3959 acos * (cos (เรเดียน (42.290763)) เป็นค่าคงที่ แต่มี 4 คำนวณที่สำคัญอยู่ในนั้น แต่คุณก็สามารถเก็บ 6696.7837.
ปีเตอร์เอ็ม

1
หรืออย่างน้อยคำนวณค่าคงที่นอกแบบสอบถาม? ที่จะลดงานที่ต้องทำ
ปีเตอร์ M

2
@ Peter M ดูเหมือนว่าฐานข้อมูล SQL ที่เหมาะสมใด ๆ จะปรับให้เหมาะสมดังนั้นจึงคำนวณเพียงครั้งเดียว
mhenry1384

25
สำหรับผู้ที่สงสัย 42.290763 คือละติจูดและ -71.35368 คือลองจิจูดของจุดที่จะคำนวณระยะทาง
user276648

14
ระยะทางที่คำนวณโดยสูตรนี้เป็นไมล์ไม่ใช่เป็นกิโลเมตรโปรดแทนที่ 3959 ถึง 6371 เพื่อรับผลลัพธ์เป็นกิโลเมตร
Sahil

คำตอบ:


115

หรือในMySQL 5.1และเหนือ:

    SELECT  *
    FROM    table
    WHERE   MBRContains
                    (
                    LineString
                            (
                            Point (
                                    @lon + 10 / ( 111.1 / COS(RADIANS(@lat))),
                                    @lat + 10 / 111.1
                                  ),
                            Point (
                                    @lon - 10 / ( 111.1 / COS(RADIANS(@lat))),
                                    @lat - 10 / 111.1
                                  ) 
                            ),
                    mypoint
                    )

(@lat +/- 10 km, @lon +/- 10km)นี้จะเลือกทุกจุดโดยประมาณภายในกล่อง

อันนี้ไม่ได้เป็นกล่อง แต่เป็นสี่เหลี่ยมทรงกลม: ละติจูดและลองจิจูดส่วนที่ถูกผูกไว้ของทรงกลม สิ่งนี้อาจแตกต่างจากสี่เหลี่ยมผืนผ้าธรรมดาบนFranz Joseph Landแต่ค่อนข้างใกล้กับสถานที่ซึ่งอาศัยอยู่มากที่สุด

  • ใช้การกรองเพิ่มเติมเพื่อเลือกทุกอย่างภายในวงกลม (ไม่ใช่รูปสี่เหลี่ยม)

  • อาจใช้การกรองแบบละเอียดเพิ่มเติมกับบัญชีสำหรับระยะทางวงกลมขนาดใหญ่ (สำหรับระยะทางขนาดใหญ่)


15
@Quassnoi: การแก้ไขสองสามอย่าง: คุณอาจต้องการเปลี่ยนลำดับของพิกัดเป็น lat, long นอกจากนี้ระยะทางยาวเป็นสัดส่วนโคไซน์ของละติจูดไม่ใช่ลองจิจูด และคุณต้องการเปลี่ยนจากการคูณเป็นการหารดังนั้นพิกัดแรกของคุณจะถูกแก้ไขเป็น@lon - 10 / ( 111.1 / cos(@lat))(และเป็นวินาทีในคู่เมื่อทุกอย่างถูกต้อง
เอ็มเดฟ Auayan

8
คำเตือน : เนื้อความของคำตอบยังไม่ได้รับการแก้ไขเพื่อให้สอดคล้องกับความคิดเห็นที่ถูกต้องมากโดย @M Dave Auayan หมายเหตุเพิ่มเติม: วิธีการนี้จะตัดรูปแพร์หากวงกลมที่สนใจ (a) มีเสาหรือ (b) ถูกตัดกันโดย +/- 180 องศาของเส้นแวงลองจิจูด การใช้ยังcos(lon)แม่นยำสำหรับระยะทางเล็ก ๆ เท่านั้น ดูjanmatuschek.de/LatitudeLongitudeBounding ผู้ใต้บังคับบัญชา
John Machin

3
มีวิธีใดบ้างที่เราจะเข้าใจว่าค่าคงที่ (10, 111.11, @lat, @lon, mypoint) เป็นตัวแทนหรือไม่? ฉันสมมติว่า 10 เป็นระยะทางกิโลเมตร @lat และ @lon เป็นตัวแทนของ lattitue และลองจิจูดที่ให้มา แต่ 111.11 และ mypoint เป็นตัวแทนของอะไรในตัวอย่าง
ashays

4
@ashays: มี111.(1)ละติจูดประมาณหนึ่งกิโลเมตรในระดับละติจูด mypointเป็นเขตข้อมูลในตารางที่เก็บพิกัด
Quassnoi

1
แก้ไขข้อผิดพลาดอื่น - คุณจะหายไปปิด) ที่สองเพื่อบรรทัดสุดท้าย
INA

100

ไม่ใช่คำตอบเฉพาะ MySql แต่จะปรับปรุงประสิทธิภาพของคำสั่ง sql ของคุณ

สิ่งที่คุณทำอย่างมีประสิทธิภาพคือการคำนวณระยะทางทุกจุดในตารางเพื่อดูว่าอยู่ในระยะ 10 หน่วยของจุดที่กำหนดหรือไม่

สิ่งที่คุณสามารถทำได้ก่อนที่คุณจะเรียกใช้ sql นี้คือการสร้างสี่จุดที่วาดกล่อง 20 หน่วยด้านข้างโดยมีจุดอยู่ตรงกลางเช่น (x1, y1) . . (x4, y4) โดยที่ (x1, y1) คือ (ที่ให้ยาว + 10 หน่วย, รับไปยัง Lat + 10 หน่วย) . . (ที่ได้รับยาว - 10 หน่วย, ที่ได้รับแลต -10 หน่วย) ที่จริงแล้วคุณต้องการเพียงสองจุดด้านบนซ้ายและล่างขวาเรียกพวกเขา (X1, Y1) และ (X2, Y2)

ตอนนี้คำสั่ง SQL ของคุณใช้จุดเหล่านี้เพื่อแยกแถวที่แน่นอนมากกว่า 10u จากจุดที่คุณกำหนดไว้มันสามารถใช้ดัชนีบนละติจูดและลองจิจูดดังนั้นจะเป็นคำสั่งของขนาดเร็วกว่าสิ่งที่คุณมีอยู่ในปัจจุบัน

เช่น

select . . . 
where locations.lat between X1 and X2 
and   locations.Long between y1 and y2;

กล่องวิธีสามารถส่งกลับค่าบวกเท็จ (คุณสามารถรับคะแนนในมุมของกล่องที่> 10u จากจุดที่กำหนด) ดังนั้นคุณยังต้องคำนวณระยะทางของแต่ละจุด อย่างไรก็ตามสิ่งนี้จะเร็วขึ้นอีกมากเพราะคุณได้ จำกัด จำนวนคะแนนที่จะทดสอบกับจุดภายในกล่องอย่างมาก

ฉันเรียกเทคนิคนี้ว่า "การคิดภายในกล่อง" :)

แก้ไข:สิ่งนี้สามารถใส่ลงในคำสั่ง SQL เดียว?

ฉันไม่รู้ว่า mySql หรือ Php สามารถขอโทษได้อย่างไร ฉันไม่รู้ว่าที่ที่ดีที่สุดคือการสร้างจุดสี่จุดหรือวิธีที่พวกเขาสามารถส่งผ่านไปยังแบบสอบถาม mySql ใน Php ได้ อย่างไรก็ตามเมื่อคุณมีสี่คะแนนไม่มีอะไรหยุดคุณรวมคำสั่ง SQL ของคุณเองกับของฉัน

select name, 
       ( 3959 * acos( cos( radians(42.290763) ) 
              * cos( radians( locations.lat ) ) 
              * cos( radians( locations.lng ) - radians(-71.35368) ) 
              + sin( radians(42.290763) ) 
              * sin( radians( locations.lat ) ) ) ) AS distance 
from locations 
where active = 1 
and locations.lat between X1 and X2 
and locations.Long between y1 and y2
having distance < 10 ORDER BY distance;

ฉันรู้ด้วย MS SQL ฉันสามารถสร้างคำสั่ง SQL ที่ประกาศสี่โฟลท (X1, Y1, X2, Y2) และคำนวณพวกมันก่อนคำสั่งเลือก "หลัก" อย่างที่ฉันพูดฉันไม่รู้ว่ามันสามารถทำได้ด้วย MySql อย่างไรก็ตามฉันยังคงต้องการสร้างจุดสี่จุดใน C # และส่งผ่านเป็นพารามิเตอร์ไปยังแบบสอบถาม SQL

ขออภัยฉันไม่สามารถช่วยเหลือเพิ่มเติมได้ถ้าใครสามารถตอบ MySQL & Php บางส่วนได้โปรดแก้ไขคำตอบนี้ได้


4
คุณสามารถค้นหาโพรซีเดอร์ mysql สำหรับแนวทางนี้ในงานนำเสนอนี้: scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Lucia

37
หากต้องการค้นหาเป็นกิโลเมตรแทนไมล์ให้แทนที่ 3959 ด้วย 6371
ErichBSchulz

4
+1 ตัวเลือกที่ยอดเยี่ยม การเพิ่มกล่องลดข้อความค้นหาของฉันจาก 4s เป็น avg 0.03
jvenema

1
ดูเหมือนว่าจะมีเหตุผลมากคุณจองรางวัลสำหรับโซลูชันนี้! บนฐานข้อมูลระเบียน 2 ล้านแบบสอบถามไปจาก 16 วินาทีถึง 0.06 วินาที หมายเหตุ:ยิ่งเร็วยิ่งขึ้น (สำหรับตารางขนาดใหญ่) หากคุณตัดการคำนวณระยะทางออกจากแบบสอบถามและทำการคำนวณระยะทางในรหัสโปรแกรมของคุณ!
NLAnaconda

2
@ ไบนารี Worrier: ดังนั้น X1, X2 และ Y1, Y2 จะเป็น Longitude Min และ Max และ Latitude Min และ Max ตามตัวอย่างที่ให้ไว้ที่นี่: blog.fedecarg.com/2009/02/08/ ......โปรดให้คำแนะนำ
Prabhat

14

ฟังก์ชั่น MySQL ต่อไปนี้ถูกโพสต์ในโพสต์บล็อกนี้ ฉันไม่ได้ทดสอบมากนัก แต่จากสิ่งที่ฉันรวบรวมจากโพสต์หากเขตข้อมูลละติจูดและลองจิจูดของคุณได้รับการจัดทำดัชนีสิ่งนี้อาจทำงานได้ดีสำหรับคุณ:

DELIMITER $$

DROP FUNCTION IF EXISTS `get_distance_in_miles_between_geo_locations` $$
CREATE FUNCTION get_distance_in_miles_between_geo_locations(
  geo1_latitude decimal(10,6), geo1_longitude decimal(10,6), 
  geo2_latitude decimal(10,6), geo2_longitude decimal(10,6)) 
returns decimal(10,3) DETERMINISTIC
BEGIN
  return ((ACOS(SIN(geo1_latitude * PI() / 180) * SIN(geo2_latitude * PI() / 180) 
    + COS(geo1_latitude * PI() / 180) * COS(geo2_latitude * PI() / 180) 
    * COS((geo1_longitude - geo2_longitude) * PI() / 180)) * 180 / PI()) 
    * 60 * 1.1515);
END $$

DELIMITER ;

ตัวอย่างการใช้งาน:

สมมติว่าตารางที่placesมีเขตข้อมูลlatitude& longitude:

SELECT get_distance_in_miles_between_geo_locations(-34.017330, 22.809500,
latitude, longitude) AS distance_from_input FROM places;

ฉันได้ลองแล้วมันใช้งานได้ดี แต่อย่างใดมันก็ไม่อนุญาตให้ฉันใส่คำสั่ง WHERE ตาม distance_from_input ความคิดใดทำไม?
Chris Visser

คุณสามารถทำได้โดยเลือกย่อย: select * from (... ) เป็น t โดยที่ distance_from_input> 5;
Brad Parks

2
หรือเพียงแค่ไปที่: เลือก * จากสถานที่ที่ get_distance_in_miles_between_geo_locations (-34.017330, 22.809500, ละติจูด, ลองจิจูด)> 5000;
Brad Parks

2
Return Meters:SELECT ROUND(((ACOS(SIN(lat1 * PI() / 180) * SIN(lat2 * PI() / 180) + COS(lat1 * PI() / 180) * COS(lat2 * PI() / 180) * COS((lnt1 - lnt2) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) * 1.609344 * 1000) AS distance
Mohammad

13

ฉันต้องการที่จะแก้ปัญหาที่คล้ายกัน (การกรองแถวตามระยะทางจากจุดเดียว) และด้วยการรวมคำถามดั้งเดิมกับคำตอบและความคิดเห็นฉันจึงได้วิธีการแก้ปัญหาที่เหมาะกับฉันทั้ง MySQL 5.6 และ 5.7

SELECT 
    *,
    (6371 * ACOS(COS(RADIANS(56.946285)) * COS(RADIANS(Y(coordinates))) 
    * COS(RADIANS(X(coordinates)) - RADIANS(24.105078)) + SIN(RADIANS(56.946285))
    * SIN(RADIANS(Y(coordinates))))) AS distance
FROM places
WHERE MBRContains
    (
    LineString
        (
        Point (
            24.105078 + 15 / (111.320 * COS(RADIANS(56.946285))),
            56.946285 + 15 / 111.133
        ),
        Point (
            24.105078 - 15 / (111.320 * COS(RADIANS(56.946285))),
            56.946285 - 15 / 111.133
        )
    ),
    coordinates
    )
HAVING distance < 15
ORDER By distance

coordinatesเป็นฟิลด์ที่มีประเภทPOINTและมีSPATIALดัชนี
6371สำหรับการคำนวณระยะทาง
56.946285เป็นกิโลเมตรคือละติจูดสำหรับจุดศูนย์กลาง
24.105078คือลองจิจูดสำหรับจุดกลาง
15คือระยะทางสูงสุดในกิโลเมตร

ในการทดสอบของฉัน MySQL ใช้ดัชนี SPATIAL บนcoordinatesสนามเพื่อเลือกแถวทั้งหมดที่อยู่ในสี่เหลี่ยมอย่างรวดเร็วจากนั้นคำนวณระยะทางตามจริงสำหรับสถานที่ที่ถูกกรองทั้งหมดเพื่อแยกสถานที่ออกจากมุมสี่เหลี่ยมและออกจากสถานที่ภายในวงกลม

นี่คือการสร้างภาพข้อมูลของฉัน:

แผนที่

ดาวสีเทามองเห็นทุกจุดบนแผนที่ดาวสีเหลืองจะถูกส่งคืนโดยแบบสอบถาม MySQL ดาวสีเทาที่อยู่ภายในมุมของรูปสี่เหลี่ยมผืนผ้า (แต่อยู่นอกวงกลม) ถูกเลือกโดยMBRContains()จากนั้นยกเลิกการเลือกตามHAVINGข้อ


ไม่สามารถลงคะแนนได้เพียงพอ ค้นหาตารางที่มีระเบียนประมาณ 5 ล้านระเบียนและดัชนีเชิงพื้นที่ด้วยวิธีนี้เวลาค้นหาคือ 0.005 วินาทีในโปรเซสเซอร์ A8 เก่า ฉันรู้ว่าสามารถแทนที่ 6371 ด้วย 3959 เพื่อให้ได้ผลลัพธ์เป็นไมล์ แต่ต้องมีการปรับค่าของ 111.133 และ 111.320 หรือเป็นค่าคงที่ในระดับสากลหรือไม่
Wranorn

ทางออกที่ดี
SeaBiscuit

วิธีสร้าง Point คือ POINT (lat, lng) หรือ POINT (lng, lat)
user606669

2
@ user606669 It's POINT (lng, lat)
MārisKiseļovs

ฟังก์ชัน X () และ Y () ควรเป็น ST_Y และ ST_X ทุกวันนี้
Andreas

11

ถ้าคุณกำลังใช้ MySQL 5.7. * แล้วคุณสามารถใช้st_distance_sphere (จุดจุด)

Select st_distance_sphere(POINT(-2.997065, 53.404146 ), POINT(58.615349, 23.56676 ))/1000  as distcance

1
นี่เป็นทางเลือกที่ดีและง่ายต่อการอ่าน โปรดจำไว้ว่าการเรียงลำดับพารามิเตอร์ไปที่ POINT () คือ (lng, lat) มิฉะนั้นคุณจะต้องลงท้ายด้วย "ปิด" แต่ยังคงผลลัพธ์ที่แตกต่างกันมากกับวิธีอื่น ๆ ที่นี่ ดู: stackoverflow.com/questions/35939853/…
Andy P

9
SELECT * FROM (SELECT *,(((acos(sin((43.6980168*pi()/180)) * 
sin((latitude*pi()/180))+cos((43.6980168*pi()/180)) * 
cos((latitude*pi()/180)) * cos(((7.266903899999988- longitude)* 
pi()/180))))*180/pi())*60*1.1515 ) as distance 
FROM wp_users WHERE 1 GROUP BY ID limit 0,10) as X 
ORDER BY ID DESC

นี่คือแบบสอบถามการคำนวณระยะทางระหว่างคะแนนใน MySQL ฉันใช้มันในฐานข้อมูลที่ยาวนานมันทำงานได้สมบูรณ์แบบ! หมายเหตุ: ทำการเปลี่ยนแปลง (ชื่อฐานข้อมูลชื่อตารางคอลัมน์ ฯลฯ ) ตามความต้องการของคุณ


ค่า 1.1515 แสดงถึงอะไร? ฉันเคยเห็นสูตรที่คล้ายกันมาก่อน แต่มันใช้ 1.75 แทน 1.1515
TryHarder

1
ในการตอบคำถามของฉันฉันคิดว่าคำตอบอาจอยู่ที่นี่stackoverflow.com/a/389251/691053
TryHarder

8
set @latitude=53.754842;
set @longitude=-2.708077;
set @radius=20;

set @lng_min = @longitude - @radius/abs(cos(radians(@latitude))*69);
set @lng_max = @longitude + @radius/abs(cos(radians(@latitude))*69);
set @lat_min = @latitude - (@radius/69);
set @lat_max = @latitude + (@radius/69);

SELECT * FROM postcode
WHERE (longitude BETWEEN @lng_min AND @lng_max)
AND (latitude BETWEEN @lat_min and @lat_max);

แหล่ง


11
กรุณาอ้างอิงแหล่งที่มาของคุณ นี่คือจาก: blog.fedecarg.com/2009/02/08/…
redburn

69 ในกรณีนี้คืออะไร? จะทำอย่างไรในกรณีที่เรามีรัศมีของโลก
CodeRunner

2
กิโลเมตรใน 1 Latittude คือ 111 KM Mile in 1 Latittude คือ 69 ไมล์ และ 69 ไมล์ = 111 กม. นั่นเป็นเหตุผลที่เราใช้พารามิเตอร์ในการแปลง
CodeRunner

ฉันกำลังมองหาสิ่งนี้ตลอดไป ไม่รู้ว่ามันง่ายขนาดนั้น ขอบคุณมาก.
Vikas

สิ่งนี้จะไม่ถูกต้องหรือไม่เนื่องจาก lng_min / lng_max จะต้องใช้ lat_min และ lat_max ในคณิตศาสตร์รัศมี
Ben

6
   select
   (((acos(sin(('$latitude'*pi()/180)) * sin((`lat`*pi()/180))+cos(('$latitude'*pi()/180)) 
    * cos((`lat`*pi()/180)) * cos((('$longitude'- `lng`)*pi()/180))))*180/pi())*60*1.1515) 
    AS distance
    from table having distance<22;

5

ฟังก์ชั่น MySQL ซึ่งคืนค่าจำนวนเมตรระหว่างสองพิกัด:

CREATE FUNCTION DISTANCE_BETWEEN (lat1 DOUBLE, lon1 DOUBLE, lat2 DOUBLE, lon2 DOUBLE)
RETURNS DOUBLE DETERMINISTIC
RETURN ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180-lon1*PI()/180) ) * 6371000

หากต้องการคืนค่าในรูปแบบอื่นให้แทนที่6371000ในฟังก์ชั่นด้วยรัศมีของโลกในหน่วยที่คุณเลือก ยกตัวอย่างเช่นกิโลเมตรจะเป็นและจะเป็นไมล์63713959

ในการใช้งานฟังก์ชั่นนี้ให้เรียกใช้ฟังก์ชั่นอื่น ๆ ใน MySQL ตัวอย่างเช่นหากคุณมีโต๊ะcityคุณสามารถหาระยะทางระหว่างเมืองทุกเมืองกับเมืองอื่น ๆ :

SELECT
    `city1`.`name`,
    `city2`.`name`,
    ROUND(DISTANCE_BETWEEN(`city1`.`latitude`, `city1`.`longitude`, `city2`.`latitude`, `city2`.`longitude`)) AS `distance`
FROM
    `city` AS `city1`
JOIN
    `city` AS `city2`

4

รหัสแบบเต็มพร้อมรายละเอียดเกี่ยวกับวิธีติดตั้งเป็นปลั๊กอิน MySQL อยู่ที่นี่: https://github.com/lucasepe/lib_mysqludf_haversine

ฉันโพสต์เมื่อปีที่แล้วเป็นความคิดเห็น เนื่องจาก @TylerCollier กรุณาแนะนำให้ฉันโพสต์เป็นคำตอบนี่คือ

อีกวิธีคือการเขียนฟังก์ชั่น UDF ที่กำหนดเองซึ่งจะคืนค่าระยะทาง haversine จากสองจุด ฟังก์ชั่นนี้สามารถรับอินพุต:

lat1 (real), lng1 (real), lat2 (real), lng2 (real), type (string - optinal - 'km', 'ft', 'mi')

ดังนั้นเราสามารถเขียนสิ่งนี้:

SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2) < 40;

เพื่อดึงข้อมูลทั้งหมดด้วยระยะทางน้อยกว่า 40 กิโลเมตร หรือ:

SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2, 'ft') < 25;

เพื่อดึงข้อมูลทั้งหมดด้วยระยะทางน้อยกว่า 25 ฟุต

ฟังก์ชั่นหลักคือ:

double
haversine_distance( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ) {
    double result = *(double*) initid->ptr;
    /*Earth Radius in Kilometers.*/ 
    double R = 6372.797560856;
    double DEG_TO_RAD = M_PI/180.0;
    double RAD_TO_DEG = 180.0/M_PI;
    double lat1 = *(double*) args->args[0];
    double lon1 = *(double*) args->args[1];
    double lat2 = *(double*) args->args[2];
    double lon2 = *(double*) args->args[3];
    double dlon = (lon2 - lon1) * DEG_TO_RAD;
    double dlat = (lat2 - lat1) * DEG_TO_RAD;
    double a = pow(sin(dlat * 0.5),2) + 
        cos(lat1*DEG_TO_RAD) * cos(lat2*DEG_TO_RAD) * pow(sin(dlon * 0.5),2);
    double c = 2.0 * atan2(sqrt(a), sqrt(1-a));
    result = ( R * c );
    /*
     * If we have a 5th distance type argument...
     */
    if (args->arg_count == 5) {
        str_to_lowercase(args->args[4]);
        if (strcmp(args->args[4], "ft") == 0) result *= 3280.8399;
        if (strcmp(args->args[4], "mi") == 0) result *= 0.621371192;
    }

    return result;
}

3

ได้อย่างรวดเร็วง่ายและถูกต้อง (สำหรับระยะทางขนาดเล็ก) ประมาณสามารถทำได้ด้วยการฉายทรงกลม อย่างน้อยในอัลกอริทึมการกำหนดเส้นทางของฉันฉันได้รับการเพิ่ม 20% เมื่อเทียบกับการคำนวณที่ถูกต้อง ในโค้ด Java ดูเหมือนว่า:

public double approxDistKm(double fromLat, double fromLon, double toLat, double toLon) {
    double dLat = Math.toRadians(toLat - fromLat);
    double dLon = Math.toRadians(toLon - fromLon);
    double tmp = Math.cos(Math.toRadians((fromLat + toLat) / 2)) * dLon;
    double d = dLat * dLat + tmp * tmp;
    return R * Math.sqrt(d);
}

ไม่แน่ใจเกี่ยวกับ MySQL (ขอโทษด้วย!)

ให้แน่ใจว่าคุณรู้เกี่ยวกับข้อ จำกัด (พารามิเตอร์ที่สามของ assertEquals หมายถึงความแม่นยำในกิโลเมตร):

    float lat = 24.235f;
    float lon = 47.234f;
    CalcDistance dist = new CalcDistance();
    double res = 15.051;
    assertEquals(res, dist.calcDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
    assertEquals(res, dist.approxDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);

    res = 150.748;
    assertEquals(res, dist.calcDistKm(lat, lon, lat - 1, lon + 1), 1e-3);
    assertEquals(res, dist.approxDistKm(lat, lon, lat - 1, lon + 1), 1e-2);

    res = 1527.919;
    assertEquals(res, dist.calcDistKm(lat, lon, lat - 10, lon + 10), 1e-3);
    assertEquals(res, dist.approxDistKm(lat, lon, lat - 10, lon + 10), 10);

3

นี่คือคำอธิบายโดยละเอียดของการค้นหาระยะทางทางภูมิศาสตร์ด้วย MySQL โซลูชันที่ยึดตามการใช้งานของ Haversine Formula ถึง mysql คำอธิบายโซลูชันที่สมบูรณ์พร้อมทฤษฎีการนำไปปฏิบัติและการเพิ่มประสิทธิภาพการทำงานต่อไป แม้ว่าส่วนการเพิ่มประสิทธิภาพเชิงพื้นที่ไม่ทำงานถูกต้องในกรณีของฉัน http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL


3

อ่านGeo Distance Search ด้วย MySQLซึ่งเป็นโซลูชั่นที่ใช้การใช้ Haversine Formula เป็น MySQL นี่คือคำอธิบายโซลูชันที่สมบูรณ์พร้อมทั้งทฤษฎีการนำไปใช้และการเพิ่มประสิทธิภาพการทำงานต่อไป แม้ว่าส่วนการเพิ่มประสิทธิภาพเชิงพื้นที่จะไม่ทำงานอย่างถูกต้องในกรณีของฉัน

ฉันสังเกตเห็นข้อผิดพลาดสองข้อในเรื่องนี้:

  1. การใช้absคำสั่ง select ใน p8 ฉันไม่absได้ใช้งานเลย

  2. ฟังก์ชั่นในระยะที่ค้นหาอวกาศบน p27 ไม่แปลงเรเดียนหรือเส้นแวงคูณด้วยcos(latitude)เว้นแต่ข้อมูลเชิงพื้นที่ของเขาจะเต็มไปด้วยนี้ในการพิจารณา (ไม่สามารถบอกได้จากบริบทของบทความ) แต่ตัวอย่างของเขาบน P26 บ่งชี้ว่าข้อมูลเชิงพื้นที่ของเขาPOINTไม่ได้เต็มไปด้วย เรเดียนหรือองศา


0
$objectQuery = "SELECT table_master.*, ((acos(sin((" . $latitude . "*pi()/180)) * sin((`latitude`*pi()/180))+cos((" . $latitude . "*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((" . $longitude . "- `longtude`)* pi()/180))))*180/pi())*60*1.1515  as distance FROM `table_post_broadcasts` JOIN table_master ON table_post_broadcasts.master_id = table_master.id WHERE table_master.type_of_post ='type' HAVING distance <='" . $Radius . "' ORDER BY distance asc";

0

ใช้ mysql

SET @orig_lon = 1.027125;
SET @dest_lon = 1.027125;

SET @orig_lat = 2.398441;
SET @dest_lat = 2.398441;

SET @kmormiles = 6371;-- for distance in miles set to : 3956

SELECT @kmormiles * ACOS(LEAST(COS(RADIANS(@orig_lat)) * 
 COS(RADIANS(@dest_lat)) * COS(RADIANS(@orig_lon - @dest_lon)) + 
 SIN(RADIANS(@orig_lat)) * SIN(RADIANS(@dest_lat)),1.0)) as distance;

ดู: https://andrew.hedges.name/experiments/haversine/

ดู: https://stackoverflow.com/a/24372831/5155484

ดู: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

หมายเหตุ: LEASTใช้เพื่อหลีกเลี่ยงค่า Null ตามความคิดเห็นที่แนะนำในhttps://stackoverflow.com/a/24372831/5155484

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