地理位置聚合
虽然按照地理位置对结果进行过滤或者打分很有用, 但是在地图上呈现信息给用户通常更加有用。一个查询可能会返回太多结果以至于不能单独地展现每一个地理坐标点,但是地理位置聚合可以用来将地理坐标聚集到更加容易管理的 buckets 中。
处理 geo_point
类型字段的三种聚合:
地理位置距离
将文档按照距离围绕一个中心点来分组。
将文档按照 geohash 范围来分组,用来显示在地图上。
- 返回一个包含所有地理位置坐标点的边界的经纬度坐标,这对显示地图时缩放比例的选择非常有用。
地理距离聚合
geo_distance
聚合 对一些搜索非常有用,例如找到所有距离我 1km 以内的披萨店。搜索结果应该也的确被限制在用户指定 1km 范围内,但是我们可以添加在 2km 范围内找到的其他结果:
GET /attractions/restaurant/_search
{
"query": {
"bool": {
"must": {
"match": { (1)
"name": "pizza"
}
},
"filter": {
"geo_bounding_box": {
"location": { (2)
"top_left": {
"lat": 40.8,
"lon": -74.1
},
"bottom_right": {
"lat": 40.4,
"lon": -73.7
}
}
}
}
}
},
"aggs": {
"per_ring": {
"geo_distance": { (3)
"field": "location",
"unit": "km",
"origin": {
"lat": 40.712,
"lon": -73.988
},
"ranges": [
{ "from": 0, "to": 1 },
{ "from": 1, "to": 2 }
]
}
}
},
"post_filter": { (4)
"geo_distance": {
"distance": "1km",
"location": {
"lat": 40.712,
"lon": -73.988
}
}
}
}
主查询查找名称中含有
pizza
的饭店。geo_bounding_box
筛选那些只在纽约区域的结果。geo_distance
聚合统计距离用户 1km 以内,1km 到 2km 的结果的数量。最后,
post_filter
将结果缩小至那些在用户 1km 范围内的饭店。
前面的请求 响应如下:
"hits": {
"total": 1,
"max_score": 0.15342641,
"hits": [ (1)
{
"_index": "attractions",
"_type": "restaurant",
"_id": "3",
"_score": 0.15342641,
"_source": {
"name": "Mini Munchies Pizza",
"location": [
-73.983,
40.719
]
}
}
]
},
"aggregations": {
"per_ring": { (2)
"buckets": [
{
"key": "*-1.0",
"from": 0,
"to": 1,
"doc_count": 1
},
{
"key": "1.0-2.0",
"from": 1,
"to": 2,
"doc_count": 1
}
]
}
}
post_filter
已经将搜索结果缩小至仅在用户 1km 范围以内的披萨店。聚合包括搜索结果加上其他在用户 2km 范围以内的披萨店。
在这个例子中,我们计算了落在每个同心环内的饭店数量。当然,我们可以在 per_rings
聚合下面嵌套子聚合来计算每个环的平均价格、最受欢迎程度,等等。
Geohash 网格聚合
通过一个查询返回的结果数量对在地图上单独的显示每一个位置点而言可能太多了。 geohash_grid
按照你定义的精度计算每一个点的 geohash 值而将附近的位置聚合在一起。
结果是一个网格—一个单元格表示一个可以显示在地图上的 geohash 。通过改变 geohash 的精度,你可以按国家或者城市街区来概括全世界。
聚合是稀疏的—它 仅返回那些含有文档的单元。 如果 geohashes 太精确,将产生太多的 buckets,它将默认返回那些包含了大量文档、最密集的10000个单元。 然而,为了计算哪些是最密集的 Top10000 ,它还是需要产生 所有 的 buckets 。可以通过以下方式来控制 buckets 的产生数量:
使用
geo_bounding_box
来限制结果。为你的边界大小选择一个适当的
precision
(精度)
GET /attractions/restaurant/_search
{
"size" : 0,
"query": {
"constant_score": {
"filter": {
"geo_bounding_box": {
"location": { (1)
"top_left": {
"lat": 40.8,
"lon": -74.1
},
"bottom_right": {
"lat": 40.4,
"lon": -73.7
}
}
}
}
}
},
"aggs": {
"new_york": {
"geohash_grid": { (2)
"field": "location",
"precision": 5
}
}
}
}
边界框将搜索限制在大纽约区的范围
Geohashes 精度为
5
大约是 5km x 5km。
Geohashes 精度为 5
,每个约25平方公里,所以10000个单元按这个精度将覆盖250000平方公里。我们指定的边界范围,约44km x 33km,或约1452平方公里,所以我们的边界在安全范围内;我们绝对不会在内存中创建了太多的 buckets。
前面的请求响应看起来是这样的:
...
"aggregations": {
"new_york": {
"buckets": [ (1)
{
"key": "dr5rs",
"doc_count": 2
},
{
"key": "dr5re",
"doc_count": 1
}
]
}
}
...
- 每个 bucket 包含作为
key
的 geohash 值
同样,我们也没有指定任何子聚合,所以我们得到是文档计数。如果需要,我们也可以了解这些 buckets 中受欢迎的餐厅类型、平均价格或其他细节。
Tip | 要在地图上绘制这些 buckets,你需要一个将 geohash 转换成同等边界框或中心点的库。JavaScript 和其他语言已有的库会为你执行这个转换,但你也可以从使用 geo-bounds-agg 的信息来进行类似的工作。 |
---|---|
地理边界聚合
在我们之前的例子中,我们通过一个覆盖大纽约区的边框来过滤结果。 然而,我们的结果全部都位于曼哈顿市中心。当为我们的用户显示一个地图的时候,放大包含数据的区域是有意义的;展示大量的空白空间是没有任何意义的。
geo_bounds
正好是这样的:它计算封装所有地理位置点需要的最小边界框:
GET /attractions/restaurant/_search
{
"size" : 0,
"query": {
"constant_score": {
"filter": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 40,8,
"lon": -74.1
},
"bottom_right": {
"lat": 40.4,
"lon": -73.9
}
}
}
}
}
},
"aggs": {
"new_york": {
"geohash_grid": {
"field": "location",
"precision": 5
}
},
"map_zoom": { (1)
"geo_bounds": {
"field": "location"
}
}
}
}
geo_bounds
聚合将计算封装所有匹配查询文档所需要的最小边界框。
响应现在包括了一个可以用来缩放地图的边界框。
...
"aggregations": {
"map_zoom": {
"bounds": {
"top_left": {
"lat": 40.722,
"lon": -74.011
},
"bottom_right": {
"lat": 40.715,
"lon": -73.983
}
}
},
...
事实上,我们甚至可以在每一个 geohash 单元内部使用 geo_bounds
聚合, 以免一个单元内的地理位置点仅集中在单元的一部分上:
GET /attractions/restaurant/_search
{
"size" : 0,
"query": {
"constant_score": {
"filter": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 40,8,
"lon": -74.1
},
"bottom_right": {
"lat": 40.4,
"lon": -73.9
}
}
}
}
}
},
"aggs": {
"new_york": {
"geohash_grid": {
"field": "location",
"precision": 5
},
"aggs": {
"cell": { (1)
"geo_bounds": {
"field": "location"
}
}
}
}
}
}
cell_bounds
子聚合会为每个 geohash 单元计算边界框。
现在在每个单元里的点有一个边界框。
...
"aggregations": {
"new_york": {
"buckets": [
{
"key": "dr5rs",
"doc_count": 2,
"cell": {
"bounds": {
"top_left": {
"lat": 40.722,
"lon": -73.989
},
"bottom_right": {
"lat": 40.719,
"lon": -73.983
}
}
}
},
...