博客地址:https://thans.cnmysql
我已經浪費了太多的時間在尋找定位軟件上了,所以這值得我去寫下如何去作。固然,在地球表面計算距離意味着計算大圓距離,能夠經過半正矢公式計算,也稱之爲球面餘弦定律公式。問題是:git
給出一個具備經緯度的位置表,其中哪一個位置最靠近給出的定位?sql
<!--more-->數據庫
你是否想問在哪裏我能夠找到一張具備經緯度的位置表?你能夠在互聯網上搜索「郵政編碼免費下載」或者「免費郵編下載」。而後將其加載到MySQL表中。有不少不一樣類型的地理數據能夠下載,附帶經緯度位置。服務器
這是SQL Server數據的美國郵政編碼數據包,若是你正好須要的話。oracle
本文中的邏輯適用於MySQL,MariaDB, PostgreSQL,和微軟的SQL Server。Oracle的工做方式有一點不一樣;這裏有一篇文章講述瞭如何在Oracle中實現。sqlserver
請慎重的使用郵政編碼數據做爲肯定位置的方法。郵政編碼僅被設計用於幫助優化郵政投遞。他們的數據用途有限,而且可能帶來錯誤的結果。例如,這是一篇地理學家寫的關於美國密西根州弗林特市水危機的文章。在很長一段時間,弗林特市的孩子彷佛沒有鉛中毒,由於研究員只看他們家的郵政編碼去找出他們住在哪裏。可是他們鉛中毒了,別和密歇根州政府犯相同的錯誤。測試
緯度和經度用度數表示。緯度描述了一個點在赤道以北或以南的距離。赤道上的點的緯度是零。北極的正(北)緯度爲90度,而且南極是負(南)緯度-90度。相應的,北半球的位置有着正緯度,而且南半球的位置有着負的緯度。優化
經度描述了一個點從本初子午線向東的距離:地球表面從一個點到另外一個點的任意直線。位於美國紐約市的帝國大廈的經度爲負(西),具體來講爲-73.9857。印度阿格拉的泰姬陵經度爲正(東經),具體爲78.0422。英國倫敦附近的格林威治天文臺,根據定義,經度爲零。編碼
所以,緯度是範圍內的值[-90,90]。經度是範圍(-180,180)內的值。這些值有時以度、分和秒錶示,而不是以度和小數表示。若是你打算作計算,先把分和秒轉換成小數。
在拿破崙時代,米是最先被定義的,因此從赤道到兩極有一千萬米。原來緯度上的米數是10000000/90或111.111千米。可是地球有點凸起,所以111.045千米/度被認爲是一個更好的近似值。
在這裏咱們爲了方便計算,咱們假設地球是一個球體。雖然這不是真的。它在赤道上有點凸起,可是定位問題,咱們假設是球體就足夠了。
這個公式(111.045千米/度)在你向北或者向南移動的時候很好用。若是你在改變你的緯度而不是經度。若是你在向東或者向西移動、在改變你的經度、在赤道上,它也能起做用。可是在赤道的南北邊,經度線愈來愈接近,因此若是你向西或向東移動一個刻度,你移動的距離就會小於111.045公里。當你往東或往西走一度時,你實際移動的距離其實是千米數。
111.045 * cos(latitude)
咱們在一些英國殖民地裏使用英里。海里是指緯度的一分鐘(1/60度)。因此每度有69法定英里或每度60海里。若是你正在處理這樣的應用,如GPS控制耕牛隊,你可能會發現它有助於知道有552浪(長度單位,至關於220碼、201米或⅛英里)每度。一些以美國爲中心的應用程序擾亂了經度。對西半球的位置來講,它們是正的而不是負的。若是你在調試什麼東西,要注意這個
任意兩點沿(球面)地球表面的距離是多少?用度數表示,用他們的經緯度表示?這是由球餘弦定理,或者半正矢公式決定的。這是MySQL語法中的:
DEGREES(ACOS(COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * COS(RADIANS(long1) - RADIANS(long2)) + SIN(RADIANS(lat1)) * SIN(RADIANS(lat2))))
它是地球表面的距離。當這些地方是你的公寓和當地超市,或者是澳大利亞悉尼和冰島雷克雅未克的機場時,它也一樣適用。注意,這個結果是以度爲單位的。這意味着若是咱們想要以千米爲單位的距離,咱們必須將它乘以111.045,即每度千米的數值。
請注意MS SQL Server須要使用一個float或double來表示RADIANS。RADIANS(30) 返回的是有問題的值,可是RADIANS(30)能正常工做。通常來講,MS SQL Server不會可靠的強制整integer類型的值轉換爲float或者double類型,全部請當心,不要在你須要使用float的時候使用integer類型。此外,請記住美國郵政編碼雖然看起來像數字,可是實際上是字符串。我住的地方郵政編碼是'01950',這和1950是不同的。
爲了在數據庫中找到與給定點的最近的點,咱們能夠這樣寫查詢。讓咱們使用經度爲-70.8一、緯度爲42.81的點。這個MySQL查詢按照距離的順序查找離給定點最近的15個點。 能夠在這邊測試:http://sqlfiddle.com/#!9/21e06/1
SELECT zip, primary_city, latitude, longitude, 111.045* DEGREES(ACOS(COS(RADIANS(latpoint)) * COS(RADIANS(latitude)) * COS(RADIANS(longpoint) - RADIANS(longitude)) + SIN(RADIANS(latpoint)) * SIN(RADIANS(latitude)))) AS distance_in_km FROM zip JOIN ( SELECT 42.81 AS latpoint, -70.81 AS longpoint ) AS p ON 1=1 ORDER BY distance_in_km LIMIT 15
注意使用鏈接將latpoint和longpoint放入查詢中。這樣編寫查詢很方便,由於公式中屢次引用了latpoint和longpoint。(MySQL不須要使用
ON 1=1
,可是PostgreSQL須要) (在SQL Server中, 使用SELECT TOP(15) zip …
來替換LIMIT 15.
)
很是好,咱們作到了,對吧?彆着急!這個查詢雖然是正確的,可是他很慢。
查詢速度很慢是由於它必須爲每一個可能的點對計算半正矢公式。所以,它使你的MySQL服務器作了不少數學運算,並強制它掃描整個位置表。如何優化?若是咱們能在表中的緯度和經度列上使用索引,那就太好了。爲此,咱們引入一個約束。假設咱們只關心郵政編碼表中距離(latpoint,longpoint)50千米之內的點。讓咱們找出如何使用索引來消除更遠的點。
請記住,根據本文前面的背景信息,緯度是111.045千米。因此,若是緯度列上有一個索引,咱們可使用相似這樣的SQL子句來消除太北或太南的點,這些點可能不在50千米以內。
latitude BETWEEN latpoint - (50.0 / 111.045) AND latpoint + (50.0 / 111.045)
這個WHERE
語句容許MySQL在計算半正矢距離公式以前使用索引省略許多緯度點。它容許MySQL對緯度索引執行範圍掃描。
最後,咱們可使用一個相似但更復雜的SQL子句來消除太東或太西的點。這個條款更復雜,由於經度是離咱們移動的赤道越遠的距離越小。請看下面公式:
longitude BETWEEN longpoint - (50.0 / (111.045 * COS(RADIANS(latpoint)))) AND longpoint + (50.0 / (111.045 * COS(RADIANS(latpoint))))
所以,將全部這些放在一塊兒,這個查詢將查找(latpoint,longpoint)50千米範圍內的最東邊15個點。
儘管這個查詢有點複雜,但它利用了緯度和經度索引,而且工做效率很高。
請注意,做爲整個查詢的一部分,咱們加入了這個子查詢。
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS radius, 111.045 AS distance_unit
這樣作的目的是使應用軟件更容易提供查詢所需的參數。Latpoint和Longpoint是您須要附近位置的特定位置。radius指定搜索應該走多遠。最後,若是你想用千米表示距離,距離單位應該是111.045。若是你想用英里表示距離,應該是69.0。
可是,這個邊界查詢有可能返回距離(latpoint,longpoint)對角線超過50km的一些點:它只檢查一個邊界矩形,而不是對角線距離。讓咱們加強查詢以消除超過50千米的點。
最後,許多人須要用英里而不是千米來計算他們的距離。這很簡單。只需將距離單位的值更改成69.0。
這是一個基於經緯度的典型商店查找程序或位置查找程序的查詢。應該可以適應你的使用,沒有太多的麻煩。
固然,這個查詢是用一個特定的ZIP表定義(一個美國郵政編碼表)編寫的。該zip
表包含名爲zip
、primary_city
、latitude
和longitude
等字段。請注意,該表在查詢中由 FROM zip AS z
引用。因此它的別名是z
。
你的位置表極可能有不一樣的列。重寫此查詢來適應你的查詢應該很簡單。在查詢中查找稱爲z.something
的字段,並用表中的字段名替換這些字段。例如,若是你的表名爲shop
,而且有shopname
、shoplat
和shoplong
字段,那麼你把z.shopname
替換爲z.primary_city
,以此類推。你將經過在查詢中包含FROM SHOP as z
來引用表。
原文地址:http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc