使用Haversine公式計算或查詢經緯度點之間的大圓距離(PHP、Python、MySQL、MSSQL示例)

Haversine 公式 - 大圓距離 - PHP、Python、MySQL

本月,我已經在PHP和MySQL方面進行了相當多的GIS編程。 在網上窺探,實際上我很難找到一些 地理計算 查找兩個位置之間的距離,所以我想在這里分享。

飛行地圖歐洲,具有很大的圓周距離

計算兩點之間的距離的簡單方法是使用勾股定律公式來計算三角形的斜邊(A²+B²=C²)。 這被稱為 歐氏距離.

這是一個有趣的開始,但不適用於地理,因為緯度和經度之間的距離為 不等距離 分開。 當您靠近赤道時,緯度線會越來越遠。 如果使用某種簡單的三角剖分方程,由於地球的曲率,它可能在一個位置精確地測量距離,而在另一個位置則非常錯誤。

大圓距離

在地球上長途旅行的路線被稱為 大圓距離. 也就是說……球體上兩點之間的最短距離與平面地圖上的點不同。 再加上緯度和經度線不是等距的事實……你的計算很困難。

這是有關Great Circles工作原理的精彩視頻說明。

Haversine公式

使用地球曲率的距離包含在 Haversine公式,它使用三角函數允許地球彎曲。 當您發現地球上兩個地方之間的距離(烏鴉飛翔)時,一條直線實際上是一條弧線。

這適用於空中飛行–您是否曾經查看過實際的飛行地圖並註意到它們是拱形的? 那是因為在兩點之間的拱門中飛行比直接飛到該位置要短。

PHP:計算2個緯度和經度之間的距離

這是計算兩點之間距離的 PHP 公式(以及英里與公里的轉換),四捨五入到小數點後兩位。

function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'miles') {
  $theta = $longitude1 - $longitude2; 
  $distance = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta))); 
  $distance = acos($distance); 
  $distance = rad2deg($distance); 
  $distance = $distance * 60 * 1.1515; 
  switch($unit) { 
    case 'miles': 
      break; 
    case 'kilometers' : 
      $distance = $distance * 1.609344; 
  } 
  return (round($distance,2)); 
}

變量是:

  • $緯度1 – 您的第一個位置的緯度變量。
  • $經度1 – 您的第一個位置的經度變量
  • $緯度2 – 第二個位置緯度的變量。
  • $經度2 – 第二個位置的經度變量。
  • $單位 – 默認是 英里. 這可以更新或傳遞為 公里.

Python:計算2個緯度和經度點之間的距離

無論如何,這是計算兩點之間距離的 Python 公式(以及英里與公里的轉換),四捨五入到小數點後兩位。 感謝我的兒子比爾卡爾,他是一名數據科學家 開放洞察, 對於代碼。

from numpy import sin, cos, arccos, pi, round

def rad2deg(radians):
    degrees = radians * 180 / pi
    return degrees

def deg2rad(degrees):
    radians = degrees * pi / 180
    return radians

def getDistanceBetweenPointsNew(latitude1, longitude1, latitude2, longitude2, unit = 'miles'):
    
    theta = longitude1 - longitude2
    
    distance = 60 * 1.1515 * rad2deg(
        arccos(
            (sin(deg2rad(latitude1)) * sin(deg2rad(latitude2))) + 
            (cos(deg2rad(latitude1)) * cos(deg2rad(latitude2)) * cos(deg2rad(theta)))
        )
    )
    
    if unit == 'miles':
        return round(distance, 2)
    if unit == 'kilometers':
        return round(distance * 1.609344, 2)

變量是:

  • 緯度1 – 您的第一個位置的變量 緯度.
  • 經度1 – 您的第一個位置的變量 經度
  • 緯度2 – 您的第二個位置的變量 緯度.
  • 經度2 – 您的第二個位置的變量 經度.
  • 單位 – 默認是 英里. 這可以更新或傳遞為 公里.

MySQL:通過使用緯度和經度以英里為單位計算距離來檢索範圍內的所有記錄

也可以使用SQL進行計算以查找特定距離內的所有記錄。 在此示例中,我將在MySQL中查詢MyTable,以查找到我在$ latitude和$ longitude位置處小於或等於變量$ distance(以Miles為單位)的所有記錄:

用於檢索特定範圍內的所有記錄的查詢 距離 通過計算經緯度兩個點之間的距離(英里)為:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`)*pi()/180)))) * 180/pi()) * 60 * 1.1515) as distance FROM `table` WHERE distance <= ".$distance."

您需要對此進行自定義:

  • $經度 –這是一個PHP變量,用於傳遞點的經度。
  • $緯度 –這是一個PHP變量,用於傳遞點的經度。
  • $距離 –這是您希望查找所有等於或小於等於的記錄的距離。
  • –這是表格...您將要用表格名稱替換它。
  • 緯度 –這是您的緯度領域。
  • 經度 –這是您經度的領域。

MySQL:通過使用緯度和經度以公里為單位計算距離來檢索範圍內的所有記錄

這是在MySQL中使用千米的SQL查詢:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) as distance FROM `table` WHERE distance <= ".$distance."

您需要對此進行自定義:

  • $經度 –這是一個PHP變量,用於傳遞點的經度。
  • $緯度 –這是一個PHP變量,用於傳遞點的經度。
  • $距離 –這是您希望查找所有等於或小於等於的記錄的距離。
  • –這是表格...您將要用表格名稱替換它。
  • 緯度 –這是您的緯度領域。
  • 經度 –這是您經度的領域。

我在企業映射平台中使用了此代碼,並將其用於在北美擁有1,000多個地點的零售商店,並且效果很好。

Microsoft SQL Server 地理距離:STDistance

如果您使用 Microsoft SQL Server,它們會提供自己的功能, 標準距離 用於使用 Geography 數據類型計算兩點之間的距離。

DECLARE @g geography;  
DECLARE @h geography;  
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);  
SET @h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);  
SELECT @g.STDistance(@h);  

向 Manash Sahoo,副總裁兼架構師致敬 Highbridge.

77 個評論

  1. 1

    非常感謝您的分享。 這是一項簡單的複制和粘貼工作,效果很好。 您為我節省了很多時間。
    供任何移植到C的人參考:
    double deg2rad(double deg){return deg *(3.14159265358979323846 / 180.0); }

  2. 2

    非常好的發布–非常好–我只需要更改持有經緯度的表的名稱即可。 它的運行速度非常快。.我有相當數量的經緯度(<400),但我認為這樣可以很好地擴展。 站點也不錯–我剛剛將其添加到我的del.icio.us帳戶中,並將定期檢查。

  3. 4
  4. 5
  5. 8
  6. 10
  7. 11
  8. 12
  9. 14
  10. 15
  11. 16

    我還發現WHERE不適用於我。 將其更改為HAVING,一切正常。 起初,我沒有閱讀註釋,而是使用嵌套選擇將其重寫。 兩者都可以正常工作。

  12. 17
  13. 18

    非常有用,非常感謝! 我在使用新的“ HAVING”(而不是“ WHERE”)時遇到了一些問題,但是一旦我在這裡閱讀了評論(在沮喪地磨牙= P大約半小時後),我就可以正常工作。 謝謝^ _ ^

  14. 19
  15. 20

    請記住,這樣的選擇語句將在計算上非常繁瑣,因此速度很慢。 如果您有很多這樣的查詢,它可能很快就會使您陷入困境。

    一種不太複雜的方法是使用由計算出的距離定義的SQUARE區域來進行第一次(粗略)選擇,即“從表名中選擇*,其中lat1和lat2之間的緯度以及lon1和lon2之間的經度”。 lat1 = targetlatitude – latdiff,lat2 = targetlatitude + latdiff,與lon類似。 latdiff〜=距離/ 111(對於km),或者距離/ 69對於英里,因為1個緯度為〜111 km(由於地球略呈橢圓形,因此略有變化,但足以滿足此目的)。 londiff =距離/(abs(cos(cos(deg2rad(latitude))* 111))或69代表英里(實際上,您可以取一個稍大的正方形來考慮變化)。 然後將其結果輸入到徑向選擇中。 只是不要忘記考慮越界坐標-即可接受的經度範圍是-180至+180,可接受的緯度範圍是-90至+90-如果您的latdiff或londiff超出此範圍。 請注意,在大多數情況下,這可能不適用,因為它只影響到楚科奇和阿拉斯加州的一部分,但只影響橫跨極地的一條太平洋上的直線的計算。

    通過此操作,我們可以大大減少進行此計算所需的分數。 如果數據庫中有一百萬個全局點,並且您想在100公里範圍內進行大致均勻的分佈,則您的第一次(快速)搜索區域為10000平方公里,並且可能會產生約20個結果(基於在表面積約500M平方公里),這意味著您需要為此查詢運行20次復雜距離計算,而不是一百萬次。

    • 21
      • 22

        很棒的建議! 我實際上是和一個開發人員一起工作的,該開發人員編寫了一個函數,該函數先拉出內部正方形,然後編寫一個遞歸函數,該函數在周長周圍形成“正方形”以包括和排除其餘點。 結果是非常快的結果–他可以在幾微秒內評估數百萬個點。

        我上面的方法絕對是“粗魯的”但有能力的。 再次感謝!

        • 23

          道格

          我一直在嘗試使用mysql和php來評估經緯度是否在多邊形內。 您知道您的開發者朋友是否發布了有關如何完成此任務的任何示例。 還是您知道任何好的例子。 提前致謝。

  16. 24

    大家好,這是我的測試SQL語句:

    SELECT DISTINCT area_id, (
    (
    (
    acos( sin( ( 13.65 * pi( ) /180 ) ) * sin( (
    `lat_dec` * pi( ) /180 ) ) + cos( ( 13.65 * pi( ) /180 ) ) * cos( (
    `lat_dec` * pi( ) /180 )
    ) * cos( (
    ( 51.02 - `lon_dec` ) * pi( ) /180 )
    )
    )
    ) *180 / pi( )
    ) *60 * 1.1515 * 1.609344
    ) AS distance
    FROM `post_codes` WHERE distance <= 50

    並且Mysql告訴我距離不作為一列存在,我可以使用order by,我可以在沒有WHERE的情況下進行操作,並且它可以工作,但不能使用…

  17. 26

    這很棒,但是就像鳥兒飛翔一樣。 嘗試以某種方式(可能使用道路等)將google maps API納入此範圍將是一個很好的選擇,只是為了提出一種使用其他運輸方式的想法。 我還沒有在PHP中創建一個模擬的退火函數,該函數能夠為旅行商問題提供有效的解決方案。 但是我認為我也許可以重用您的某些代碼。

  18. 27
  19. 28

    好文章! 我找到了很多描述如何計算兩點之間距離的文章,但我確實在尋找SQL代碼段。

  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    經過2天的研究,終於找到了解決我的問題的頁面。 看來我最好把我的WolframAlpha弄清楚,然後重新計算數學。 從WHERE到HAVING的更改使我的腳本運行正常。 謝謝

  25. 37
    • 38

      謝謝喬治。 我一直在找不到列“距離”。 一旦我將WHERE更改為HAVERING,它就會像魅力一樣發揮作用!

  26. 39

    我希望這是我在此找到的第一頁。 在嘗試了許多不同的命令之後,這是唯一可以正常工作的命令,並且只需進行最小的更改即可適合我自己的數據庫。
    非常感謝!

  27. 40

    我希望這是我在此找到的第一頁。 在嘗試了許多不同的命令之後,這是唯一可以正常工作的命令,並且只需進行最小的更改即可適合我自己的數據庫。
    非常感謝!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47
  34. 49
  35. 50
  36. 52
  37. 53
  38. 55
  39. 56
  40. 58

    感謝您發布這篇有用的文章,  
    但出於某些原因我想問
    如何獲取mysql db內部坐標與用戶插入php的坐標之間的距離?
    更清楚地描述:
    1.用戶必須插入[id]從數據庫和用戶本身的坐標中選擇指定數據
    2. php文件使用[id]獲取目標數據(坐標),然後計算用戶與目標點之間的距離

    還是可以簡單地從下面的代碼獲取距離?

    $ qry =“ SELECT *,((((acos(sin((“。$ latitude。” * pi()/ 180))* sin((`Latitude` * pi()/ 180))+ cos((”。 $ latitude。” * pi()/ 180))* cos((`Latitude` * pi()/ 180))* cos((((“。$ longitude。”-`Longitude`)* pi()/ 180) )))* 180 / pi())* 60 * 1.1515 * 1.609344)作為距`MyTable`的距離,其中distance> =“。$ distance。” >>>>我可以“拿走”到這裡的距離嗎?
    再次感謝,
    蒂米S

    • 59

      沒關係,我已經弄清楚了“函數”在php中是如何工作的
      $ dis = getDistanceBetweenPointsNew($ userLati,$ userLongi,$ lati,$ longi,$ unit ='Km')
      非常感謝!! 

  41. 60

    好的,我嘗試過的所有方法均無效。 我的意思是,我所擁有的作品,但是相距遙遠。

    有人可以看到這段代碼有什麼問題嗎?

    if(isset($ _ POST ['submitted'])){$ z = $ _POST ['zipcode']; $ r = $ _POST ['radius']; 回顯“。$ z的結果; $ sql = mysql_query(“ SELECT DISTINCT m.zipcode,m.MktName,m.LocAddSt,m.LocAddCity,m.LocAddState,m.x1,m.y1,m.verified,z1.lat,z2.lon,z1。 city,z1.state FROM mrk m,zip z1,zip z2 WHERE m.zipcode = z1.zipcode AND z2.zipcode = $ z AND(3963 * acos(truncate(sin(zin。z / 2.lat / 57.2958)* sin(m。 y1 / 57.2958)+ cos(z2.lat / 57.2958)* cos(m.y1 / 57.2958)* cos(m.x1 / 57.2958 – z2.lon / 57.2958),8))))<= $ r“)或死亡(mysql_error()); while($ row = mysql_fetch_array($ sql)){$ store1 = $ row ['MktName']。“”; $ store = $ row ['LocAddSt']。””; $ store。= $ row ['LocAddCity']。”,“。$ row ['LocAddState']。”。 “。$ row ['zipcode']; $ latitude1 = $ row ['lat']; $ longitude1 = $ row ['lon']; $ latitude2 = $ row ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew($ latitude1,$ longitude1,$ latitude2,$ longitude2,$ unit ='Mi'); // $ dis = distance($ lat1,$ lon1,$ lat2,$ lon2); $ verified = $ row ['verified']; if($ verified =='1'){迴聲“”; 回顯“”。$ store。””; echo $ dis。 “ 幾英里以外”; 迴聲“”; } else {echo“”。$ store。””; echo $ dis。 “ 幾英里以外”; 迴聲“”; }}}

    我的functions.php代碼
    函數getnew($ latitude1,$ longitude1,$ latitude2,$ longitude2,$ unit ='Mi'){$ theta = $ longitude1 – $ longitude2; $ distance =(sin(deg2rad($ latitude1))* sin(deg2rad($ latitude2)))+(cos(deg2rad($ latitude1))* cos(deg2rad($ latitude2))* cos(deg2rad($ theta)) ); $ distance = acos($ distance); $ distance = rad2deg($ distance); $距離= $距離* 60 * 1.1515; switch($ unit){case'Mi':break; 情況'Km':$ distance = $ distance * 1.609344; } return(round($ distance,2)); }

    謝謝你在前進

  42. 61
  43. 62

    嘿道格拉斯,很棒的文章。 我發現您對地理概念和代碼的解釋非常有趣。 我唯一的建議是對代碼進行空格和縮進顯示(例如,像Stackoverflow)。 我知道您想節省空間,但是傳統的代碼間距/縮進將使我(作為程序員)更容易閱讀和剖析。 無論如何,這是一件小事。 繼續努力。

  44. 64
  45. 65
  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    似乎在select和其中使用兩次公式的速度更快(mysql 5.9):
    $ formula =“((((acos(sin((”。$ latitude。“ * pi()/ 180))* sin((`Latitude` * pi()/ 180))+ cos((”。$ latitude。 ” * pi()/ 180))* cos((`Latitude` * pi()/ 180))* cos(((“。$ longitude.-`Longitude`)* pi()/ 180))))) * 180 / pi())* 60 * 1.1515 * 1.609344)”;
    $ sql ='SELECT *,'。$ formula。' 作為距表格WHERE'.. $ formula。'的距離。 <='。$ distance;

  51. 71
  52. 72

    非常感謝本文的剪裁。它非常有幫助。
    PHP最初是作為一個簡單的腳本平台創建的,稱為“個人主頁”。 如今,PHP(超文本預處理器的縮寫)是Microsoft的Active Server Pages(ASP)技術的替代方法。

    PHP是一種開放源代碼服務器端語言,用於創建動態網頁。 可以將其嵌入HTML。 PHP通常與Linux / UNIX Web服務器上的MySQL數據庫結合使用。 它可能是最流行的腳本語言。

  53. 73

    我發現上述解決方案無法正常工作。
    我需要更改為:

    $ qqq =“選擇*,((((acos(sin((“。$ latitude。” * pi()/ 180))* sin((`latt * pi()/ 180))+ cos((”。 $ latitude。“ * pi()/ 180))* cos((`latt ** pi()/ 180))* cos((((。。$ longitude。“-`longt`)* pi()/ 180) )))* 180 / pi())* 60 * 1.1515)作為距`register'的距離;

  54. 75
  55. 76

    您好,請我真的會在這方面需要您的幫助。

    我向我的網絡服務器發出了獲取請求 http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitude
    -2.23389 = $經度
    和20 =我要檢索的距離

    但是使用公式,它將檢索我的數據庫中的所有行

    $ results = DB :: select(DB :: raw(“ 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 * 1.609344)作為距離標記的距離,距離> =“。$ distance));

    [{{“ id”:1,“ name”:“ Frankie Johnnie&Luigo Too”,“ address”:“ 939 W El Camino Real,CA,Mountain”,“ lat”:37.386337280273,“ lng”:-122.08582305908, ” distance”:16079.294719663},{“ id”:2,“ name”:“ Amici東海岸比薩店”,“地址”:“ CA Castro St,加利福尼亞州山景城”,“ lat”:790,“ lng”: -37.387138366699,“距離”:122.08323669434},{“ id”:16079.175940152,“名稱”:“ Kapp's Pizza Bar&Grill”,“地址”:“ 3 Castro St,Mountain View,CA”,“ lat”:191, “ lng”:-37.393886566162,“距離”:122.07891845703},{“ id”:16078.381373826,“ name”:“ Round Table Pizza:Mountain View”,“ address”:“ 4 N Shoreline Blvd,Mountain View,CA”, “ lat”:570,“ lng”:-37.402652740479,“ distance”:122.07935333252},{“ id”:16077.420540582,“ name”:“ Tony&Alba's Pizza&Pasta”,“ address”:“ 5 Escuela Ave,Mountain View,CA”,“ lat”:619,“ lng”:-37.394012451172,“ distance”:122.09552764893},{“ id”:16078.563225154,“ name”:“ Oregano's Wood-fired Pizza”,“ address”:“ 6” El Camino Real,加利福尼亞州洛斯阿爾托斯市”,“緯度”:4546,“ lng”:-37.401725769043,“距離”:122.11464691162},{“ id”:16077.937560795,“ name”:“ The bars and grills”,“ address”:“ Manchester Whiteley Street 7”,“ lat”:24,“ lng”:-53.485118865967,“ distance”:2.1828699111938}]]

    我只想檢索20英里的行,但它會帶走所有行。 請問我在做什麼錯

  56. 77

    我正在尋找一個類似的查詢,但加強了一點——簡而言之,這是對每個坐標 2 英里內的所有坐標進行分組,然後計算每組中有多少坐標並只輸出具有最多坐標的一組——即使在具有最多坐標數的組中,您有多個組 – 只需從具有相同最大數量的組中輸出隨機組 –

你覺得呢?

本網站使用Akismet來減少垃圾郵件。 了解您的評論如何處理.