ElasticSearch - 地理座標點 geo_point

古古

2019/12/07


在 ElasticSearch 中,提供了兩種用來表示地理位置的方式,分別是 geo_pointgeo_shape

  1. 用緯度、經度表示的座標點使用 geo_point 字段類型(較常用),geo_point 允許你找到距離另一個座標點一定範圍內的座標點、計算出兩點之間的距離來排序或進行相關性打分、或者聚合到顯示在地圖上的一個網格
  2. 另一種是使用 GeoJSON 格式定義的複雜地理形狀,使用geo_shape字段類型

geo_point 經緯度座標格式

在 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 種地理座標點相關的過濾器,可以用來選中或者排除文檔

  • geo_bounding_box (矩形過濾) : 找出落在指定矩形框中的點,效率最高
  • geo_distance (圓形過濾) : 找出與指定位置在給定距離內的點
  • geo_polygon (自定義多邊型過濾) : 找出落在指定多邊形中的點 (注意這個過濾器使用代價很大)

geo_bounding_box 矩形過濾

指定一個矩形的頂部、底部、左邊界、右邊界,然後過濾器只需判斷座標的經度是否在左右邊界之間,緯度是否在上下邊界之間就可以判斷這個座標點是否在矩形範圍內

可以選擇使用 top_left + bottom_right 或是 top_right + bottom_left 或是 top + bottom + left + right

具體實例 : 找出那些位在 lat 40.7~40.8,且lon -73 ~ -74的座標點
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
                        }
                    }
                }
            }
        }
    }
}

geo_distance 圓形過濾

從給定的位置爲圓心畫一個圓,找出那些地理座標落在其中的文檔,distance支持的單位 : km、m、cm、mm…

圓形過濾計算代價比矩形過濾貴,爲了優化性能,ES 會先畫一個矩形框來圍住整個圓形,這樣就可以用矩形過濾先排除掉一些完全不可能的文檔,然後再對落在矩形內的座標點用地理距離計算方式處理

在使用圓形過濾時,需要思考是否真的要這麼精確的距離過濾?通常使用矩形模型 bounding box是更更高效的方式,並且往往也能滿足應用需求,假設user想找1km內的餐廳,是否不行找了一個1.2km的餐廳給他?這200m真的差距有這麼大嗎?需要根據實際使用情況做選擇

為了提高圓形過濾的性能,在計算兩點間的距離時,有多種犧牲性能換取精度的算法

  • arc : 最慢但最精確的計算方式 (默認的計算方式),這種方式把世界當作球體來處理,不過這種方式的精度還是有極限,因爲這個世界並不是完全的球體
  • plane : plane的計算方式把地球當成是平坦的,這種方式快一些但是精度略遜,在赤道附近的位置精度最好,而靠近兩極則變差
具體實例 : 給定一個位置 (40.715, -73.988),找出距離他1km以內的所有點
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 可以自定義一個多邊形,然後判斷這個座標點是否在該多邊形內

在使用 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 簡介