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

ES的基本內容介紹的已經差很少了,最後咱們再來看看GEO位置搜索,如今大部分APP都有基於位置搜索的功能,好比:咱們點外賣,能夠按照離咱們的距離進行排序,這樣能夠節省咱們的配送費和送餐的時間;還有找工做時,也能夠按照離本身家的距離進行排序,誰都想找個離家近的工做,對吧。這些功能都是基於GEO搜索實現的,目前支持GEO搜索功能的中間件有不少,像MySQL、Redis、ES等。咱們看看在ES當中怎麼實現GEO位置搜索。前端

GEO字段的建立

GEO類型的字段是不能使用動態映射自動生成的,咱們須要在建立索引時指定字段的類型爲geo_pointgeo_point類型的字段存儲的經緯度,咱們看看經緯度是怎麼定義的,java


英文 簡寫 正數 負數
維度 latitude lat 北緯 南緯
經度 longitude lon或lng 東經 西經

經度的簡寫有2個,通常經常使用的是lon,lng則在第三方地圖的開放平臺中使用比較多。下面咱們先建立一個帶有geo_point類型字段的索引,以下:git

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_pointshell

索引建立完了,咱們添加兩條數據吧,假設,路人甲在北京站,路人乙在朝陽公園。那麼咱們怎麼「北京站」和「朝陽公園」的經緯度呢?咱們在作項目時,前端都會接地圖控件,經緯度的信息能夠調用地圖控件的API獲取。在我們的示例中,也不接地圖控件了,太麻煩了,直接在網上找到「北京站」和「朝陽公園」的座標吧。json

咱們查到「北京站」的座標以下:微信

而後添加一條數據:
app

POST /my_geo/_doc
{
   "name":"路人甲",
   "location":{
       "lat": 39.90279998006104,
       "lon": 116.42703999493406
  }
}

再查「朝陽公園」的座標elasticsearch

再添加「路人乙」的信息
ide

POST /my_geo/_doc
{
   "name":"路人乙",
   "location":{
       "lat": 39.93367367974064,
       "lon": 116.47845257733152
  }
}

咱們再用elasticsearch-head插件看一下索引中的數據:ui

GEO查詢

「路人甲」和「路人乙」的信息都有了,可是沒有location字段的信息,由於location是特性類型的字段,在這裏是展現不出來的。咱們搜索一下吧,看看怎麼用geo搜索,假設「我」的位置在「工體」,咱們先要查到「工體」的座標,

而後再查詢5km範圍內都有誰,發送請求以下:

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。查詢一下,看看結果:

{
   ……
   "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,看看結果吧,

{
   ……
   "hits":[
      {
           "_index":"my_geo",
           "_type":"_doc",
           "_id":"ZdguXnIBOZNtuLQtMVfA",
           "_score":0,
           "_source":{
               "name":"路人乙",
               "location":{
                   "lat": 39.93367367974064,
      "lon": 116.47845257733152
              }
          }
      }
  ]
}

只有在「朝陽公園」的路人乙被搜索了出來。徹底符合預期,咱們再看看程序中怎麼使用GEO搜索。

JAVA 代碼

在定義實體類時,對應的GEO字段要使用特殊的類型,以下:

@Setter@Getter
public class MyGeo {

   private String name;
   private GeoPoint location;

}

location的類型是GeoPoint,添加數據的方法沒有變化,轉化成Json就能夠了。再看看查詢怎麼用,

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,座標點工體

  • 其餘的地方沒有變化

運行一下,看看結果,

{"name":"路人乙","location":{"lat":39.93360786576342,"lon":116.47853840802}}

只有在「朝陽公園」的路人乙被查詢了出來,符合預期。

距離排序

有的小夥伴可能會有這樣的疑問,我不想按照距離去查詢,只想把查詢結果按照離「我」的距離排序,該怎麼作呢?再看一下,

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,和當前的座標」工體「,再設置排序是升序。運行一下,看看結果,

{"name":"路人乙","location":{"lat":39.93360786576342,"lon":116.47853840802}}
{"name":"路人甲","location":{"lat":39.902799980059335,"lon":116.42721165631102}}

離「工體」比較近的「路人乙」排在了第一個,也是符合預期的。有問題你們評論區留言吧~


本文分享自微信公衆號 - 牛初九(tech-java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索