1. 程式人生 > >ES7學習筆記(十三)GEO位置搜尋

ES7學習筆記(十三)GEO位置搜尋

ES的基本內容介紹的已經差不多了,最後我們再來看看**GEO位置搜尋**,現在大部分APP都有基於位置搜尋的功能,比如:我們點外賣,可以按照離我們的距離進行排序,這樣可以節省我們的配送費和送餐的時間;還有找工作時,也可以按照離自己家的距離進行排序,誰都想找個離家近的工作,對吧。這些功能都是基於GEO搜尋實現的,目前支援GEO搜尋功能的中介軟體有很多,像MySQL、Redis、ES等。我們看看在ES當中怎麼實現GEO位置搜尋。 ## GEO欄位的建立 GEO型別的欄位是不能使用動態對映自動生成的,我們需要在建立索引時指定欄位的型別為`geo_point`,`geo_point`型別的欄位儲存的經緯度,我們看看經緯度是怎麼定義的, | | 英文 | 簡寫 | 正數 | 負數 | | ---- | --------- | -------- | ---- | ---- | | 維度 | latitude | lat | 北緯 | 南緯 | | 經度 | longitude | lon或lng | 東經 | 西經 | 經度的簡寫有2個,一般常用的是lon,lng則在第三方地圖的開放平臺中使用比較多。下面我們先建立一個帶有`geo_point`型別欄位的索引,如下: ```shell PUT /my_geo { "settings":{ "analysis":{ "analyzer":{ "default":{ "type":"ik_max_word" } } } }, "mappings":{ "dynamic_date_formats":[ "MM/dd/yyyy", "yyyy/MM/dd HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss" ], "properties":{ "location":{ "type":"geo_point" } } } } ``` 建立了一個my_geo索引,在索引中有一些基礎的配置,預設IK分詞器,動態對映的時間格式。重點是最後我們添加了一個欄位location,它的型別是`geo_point`。 索引建立完了,我們新增兩條資料吧,假設,路人甲在北京站,路人乙在朝陽公園。那麼我們怎麼“北京站”和“朝陽公園”的經緯度呢?我們在做專案時,前端都會接地圖控制元件,經緯度的資訊可以呼叫地圖控制元件的API獲取。在咱們的示例中,也不接地圖控制元件了,太麻煩了,直接在網上找到“北京站”和“朝陽公園”的座標吧。 我們查到“北京站”的座標如下: ![](https://img2020.cnblogs.com/blog/1191201/202005/1191201-20200529142645216-1862423870.png) 然後新增一條資料: ```shell POST /my_geo/_doc { "name":"路人甲", "location":{ "lat": 39.90279998006104, "lon": 116.42703999493406 } } ``` 再查“朝陽公園”的座標 ![](https://img2020.cnblogs.com/blog/1191201/202005/1191201-20200529142701603-658979899.png) 再新增“路人乙”的資訊 ```shell POST /my_geo/_doc { "name":"路人乙", "location":{ "lat": 39.93367367974064, "lon": 116.47845257733152 } } ``` 我們再用`elasticsearch-head`外掛看一下索引中的資料: ![](https://img2020.cnblogs.com/blog/1191201/202005/1191201-20200529142719042-805811473.png) ## GEO查詢 “路人甲”和“路人乙”的資訊都有了,但是沒有`location`欄位的資訊,因為`location`是特性型別的欄位,在這裡是展示不出來的。我們搜尋一下吧,看看怎麼用geo搜尋,假設“我”的位置在“工體”,我們先要查到“工體”的座標, ![](https://img2020.cnblogs.com/blog/1191201/202005/1191201-20200529142729394-173210435.png) 然後再查詢5km範圍內都有誰,傳送請求如下: ```shell POST /my_geo/_search { "query":{ "bool":{ "filter":{ "geo_distance":{ "distance":"5km", "location":{ "lat":39.93031708627304, "lon":116.4470385453491 } } } } } } ``` 在查詢的時候用的是`filter`查詢,再filter查詢裡再使用`geo_distance`查詢,我們定義距離`distance`為5km,再指定geo型別的欄位`location`,當前的座標為:39.93031708627304N,116.4470385453491E。查詢一下,看看結果: ```json { …… "hits":[ { "_index":"my_geo", "_type":"_doc", "_id":"AtgtXnIBOZNtuLQtIVdD", "_score":0, "_source":{ "name":"路人甲", "location":{ "lat": 39.90279998006104, "lon": 116.42703999493406 } } }, { "_index":"my_geo", "_type":"_doc", "_id":"ZdguXnIBOZNtuLQtMVfA", "_score":0, "_source":{ "name":"路人乙", "location":{ "lat": 39.93367367974064, "lon": 116.47845257733152 } } } ] } ``` 看來,我們站在“工體”,“北京站”的路人甲和“朝陽公園”的路人乙都在5km的範圍內。把範圍縮短一點如何,改為3km看看,搜尋的請求不變,只是把`distance`改為3km,看看結果吧, ```json { …… "hits":[ { "_index":"my_geo", "_type":"_doc", "_id":"ZdguXnIBOZNtuLQtMVfA", "_score":0, "_source":{ "name":"路人乙", "location":{ "lat": 39.93367367974064, "lon": 116.47845257733152 } } } ] } ``` 只有在“朝陽公園”的路人乙被搜尋了出來。完全符合預期,我們再看看程式中怎麼使用GEO搜尋。 ## JAVA 程式碼 在定義實體類時,對應的GEO欄位要使用特殊的型別,如下: ```java @Setter@Getter public class MyGeo { private String name; private GeoPoint location; } ``` location的型別是`GeoPoint`,新增資料的方法沒有變化,轉化成Json就可以了。再看看查詢怎麼用, ```java public void searchGeo() throws IOException { SearchRequest searchRequest = new SearchRequest("my_geo"); SearchSourceBuilder ssb = new SearchSourceBuilder(); //工體的座標 GeoPoint geoPoint = new GeoPoint(39.93367367974064d,116.47845257733152d); //geo距離查詢 name=geo欄位 QueryBuilder qb = QueryBuilders.geoDistanceQuery("location") //距離 3KM .distance(3d, DistanceUnit.KILOMETERS) //座標工體 .point(geoPoint); ssb.query(qb); searchRequest.source(ssb); SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : response.getHits().getHits()) { System.out.println(hit.getSourceAsString()); } } ``` * SearchRequest指定索引`my_geo` * 建立工體的座標點`GeoPoint` * 建立geo距離查詢,指定geo欄位`location`,距離3km,座標點工體 * 其他的地方沒有變化 執行一下,看看結果, ```shell {"name":"路人乙","location":{"lat":39.93360786576342,"lon":116.47853840802}} ``` 只有在“朝陽公園”的路人乙被查詢了出來,符合預期。 ## 距離排序 有的小夥伴可能會有這樣的疑問,我不想按照距離去查詢,只想把查詢結果按照離“我”的距離排序,該怎麼做呢?再看一下, ```java public void searchGeoSort() throws IOException { SearchRequest searchRequest = new SearchRequest("my_geo"); SearchSourceBuilder ssb = new SearchSourceBuilder(); //工體的座標 GeoPoint geoPoint = new GeoPoint(39.93367367974064d,116.47845257733152d); GeoDistanceSortBuilder sortBuilder = SortBuilders .geoDistanceSort("location", geoPoint) .order(SortOrder.ASC); ssb.sort(sortBuilder); searchRequest.source(ssb); SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : response.getHits().getHits()) { System.out.println(hit.getSourceAsString()); } } ``` 這次查詢並沒有設定查詢條件,而是建立了一個geo距離排序,同樣,先指定geo欄位`location`,和當前的座標工體,再設定排序是升序。執行一下,看看結果, ```shell {"name":"路人乙","location":{"lat":39.93360786576342,"lon":116.47853840802}} {"name":"路人甲","location":{"lat":39.902799980059335,"lon":116.42721165631102}} ``` 離“工體”比較近的“路人乙”排在了第一個,也是符合預期的。有問題大家評論區