在 ElasticSearch 中,提供了兩種用來表示地理位置的方式,分別是 geo_point
和 geo_shape
geo_point
字段類型(較常用),geo_point
允許你找到距離另一個座標點一定範圍內的座標點、計算出兩點之間的距離來排序或進行相關性打分、或者聚合到顯示在地圖上的一個網格geo_shape
字段類型在 mapping 定義時將字段類型定義為 geo_point
PUT mytest
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"my_location": {
"type": "geo_point"
}
}
}
}
目前有6種經緯度座標格式可以插入一個座標點到 Elasticsearch中,此處只介紹幾種常用的,全部支持的格式請參考 官方文檔
使用對象
POST mytest/_doc
{
"name": "Pala Pizza as an object",
"my_location": {
"lat": 40.722,
"lon": -73.989
}
}
使用數組 location : [ lon, lat ]
POST mytest/_doc
{
"name": "Pala Pizza as an array",
"my_location": [-73.989, 40.722]
}
使用字符串 location : “lat, lon”
POST mytest/_doc
{
"name": "Pala Pizza as a string",
"my_location": "40.722, -73.989"
}
使用Geohash
POST mytest/_doc
{
"name": "Pala Pizza as a geohash",
"my_location": "drm3btev3e86"
}
目前針對 geo_point 的查詢,ElasticSearch 提供 3 種地理座標點相關的過濾器,可以用來選中或者排除文檔
指定一個矩形的頂部、底部、左邊界、右邊界,然後過濾器只需判斷座標的經度是否在左右邊界之間,緯度是否在上下邊界之間就可以判斷這個座標點是否在矩形範圍內
可以選擇使用 top_left + bottom_right
或是 top_right + bottom_left
或是 top + bottom + left + right
GET mytest/_search
{
"query": {
"bool": {
"filter": {
"geo_bounding_box": {
"my_location": {
"top_left": {
"lat": 40.8,
"lon": -74.0
},
"bottom_right": {
"lat": 40.7,
"lon": -73.0
}
}
}
}
}
}
}
從給定的位置爲圓心畫一個圓,找出那些地理座標落在其中的文檔,distance支持的單位 : km、m、cm、mm…
圓形過濾計算代價比矩形過濾貴,爲了優化性能,ES 會先畫一個矩形框來圍住整個圓形,這樣就可以用矩形過濾先排除掉一些完全不可能的文檔,然後再對落在矩形內的座標點用地理距離計算方式處理
在使用圓形過濾時,需要思考是否真的要這麼精確的距離過濾?通常使用矩形模型 bounding box是更更高效的方式,並且往往也能滿足應用需求,假設user想找1km內的餐廳,是否不行找了一個1.2km的餐廳給他?這200m真的差距有這麼大嗎?需要根據實際使用情況做選擇
為了提高圓形過濾的性能,在計算兩點間的距離時,有多種犧牲性能換取精度的算法
arc
: 最慢但最精確的計算方式 (默認的計算方式),這種方式把世界當作球體來處理,不過這種方式的精度還是有極限,因爲這個世界並不是完全的球體plane
: plane的計算方式把地球當成是平坦的,這種方式快一些但是精度略遜,在赤道附近的位置精度最好,而靠近兩極則變差GET mytest/_search
{
"query": {
"bool": {
"filter": {
"geo_distance": {
"distance": "1km",
"distance_type": "arc",
"my_location": {
"lat": 40.715,
"lon": -73.988
}
}
}
}
}
}
geo_polygon 可以自定義一個多邊形,然後判斷這個座標點是否在該多邊形內
在使用 geo_polygon 時,需要使用 points
數組自定義多邊形,需要把該多邊形的所有點列出來在此數組裡,順序不影響
GET mytest/_search
{
"query": {
"bool" : {
"filter" : {
"geo_polygon" : {
"my_location" : {
"points" : [
{"lat" : 40, "lon" : -70},
{"lat" : 40, "lon" : -80},
{"lat" : 50, "lon" : -70},
{"lat" : 50, "lon" : -80}
]
}
}
}
}
}
}
注意,會需要在請求時決定距離的單位的原因是因為,這些用於排序的距離的值,會設置在每個返回的結果的sort元素中,方便我們取得他們實際上距離那個點多少距離,而此時就必須知道距離的單位
GET mytest/_search
{
"query": {
"bool": {
"filter": {
"geo_bounding_box": {
"my_location": {
"top_left": {
"lat": 40.8,
"lon": -74.0
},
"bottom_right": {
"lat": 40.4,
"lon": -73.0
}
}
}
}
}
},
"sort": [
{
//計算每個文檔中 my_location 字段與指定的 lat/lon 點間的距離
//my_location要先在索引中建為geo_point類型的字段
"_geo_distance": {
"my_location": {
"lat": 40.715,
"lon": -73.998
},
//將距離以 km 爲單位寫入到每個返回結果的sort中
"unit": "km",
"distance_type": "arc",
"order": "asc"
}
}
]
}
在sort存放的數字,告訴我們此餐廳到指定的位置的距離是0.0842km
"hits": [
{
"_index": "attractions",
"_type": "restaurant",
"_id": "2",
"_score": null,
"_source": {
"name": "New Malaysia",
"location": {
"lat": 40.715,
"lon": -73.997
}
},
"sort": [
0.08425653647614346
]
},
...
]
有可能距離是決定返回結果排序的唯一重要因素,不過更常見的情況是距離會和其它因素,比如全文檢索匹配度、流行程度或者價格一起決定排序結果
如果需要綜合多個維度一起進行排序,就需要使用 function_score 中指定方式讓我們把這些因子處理後得到一個綜合分,詳情此參考 ElasticSearch - function_score 簡介