「附近的门店」「三公里内的外卖」「按距离排序」是搜索服务的常见需求。Elasticsearch 提供 geo_point 与 geo_shape 两套地理数据类型,配合距离查询、地理排序与地理聚合,能支撑从坐标检索到地图可视化的完整链路。本文覆盖地理数据建模、查询、聚合与可视化。
1. 地理数据类型
一句话总结: geo_point 表示点坐标,geo_shape 表示多边形区域,两者决定后续能做什么样的查询。
1.1 geo_point 映射
{
"mappings": {
"properties": {
"location": { "type": "geo_point" },
"delivery_zone": { "type": "geo_shape" }
}
}
}
geo_point 存经纬度点,支持距离查询、距离排序与边界框过滤;geo_shape 存点线面,支持多边形相交、包含等空间关系判断。门店坐标用 geo_point,配送范围用 geo_shape,两者常常同文档共存。
1.2 写入坐标的三种形式
{ "location": { "lat": 39.915, "lon": 116.404 } }
{ "location": "39.915,116.404" }
{ "location": [116.404, 39.915] }
geo_point 接受对象、字符串(纬度,经度)与数组(经度,纬度)三种形式。数组形式必须遵守「经度在前」的 GeoJSON 约定,与直觉相反,写入前务必确认。字符串形式的逗号分隔也是纬度在前,与数组相反,混用最容易写错。
1.3 geo_shape 与 GeoJSON
{
"delivery_zone": {
"type": "polygon",
"coordinates": [
[
[116.3, 39.8], [116.6, 39.8],
[116.6, 40.1], [116.3, 40.1], [116.3, 39.8]
]
]
}
}
geo_shape 遵循 GeoJSON 规范:polygon 坐标是经度在前、闭合的环。多边形必须封闭(首尾点相同),带洞的多边形用两个环表达。geo_shape 索引默认基于四叉树/前缀树做空间索引,精度参数 precision 影响索引大小与查询精度。
2. 距离查询
一句话总结: geo_distance 按指定中心与半径筛出范围内文档,是「附近」检索的基础。
2.1 geo_distance 查询
{
"query": {
"geo_distance": {
"distance": "3km",
"location": { "lat": 39.915, "lon": 116.404 }
}
}
}
geo_distance 以中心点为圆心、distance 为半径做圆形范围过滤。distance 支持 m/km/mi 等单位。该查询默认不参与打分(等价 filter),只做空间筛选,配合排序输出按距离排名的结果。
2.2 geo_distance_range 环形带
{
"query": {
"bool": {
"filter": [
{
"geo_distance": {
"distance": "10km",
"location": { "lat": 39.915, "lon": 116.404 }
}
},
{
"range": { "price": { "lte": 100 } }
}
]
}
}
}
需要「3~10 公里」的环形带时,用两个 geo_distance 组合(within 10km 排除 within 3km),或用 geo_distance_range。实际场景多是距离 + 业务条件(价格、评分)的组合过滤,放进 bool filter 走位图缓存。
2.3 地球曲率与精度
距离计算在球面上进行,默认使用 Haversine 公式。小范围(城市内)可设置 distance_type 为 plane 平面近似换取更快速度,误差在数百公里级才会明显。查询精度与地理索引精度相关,写入时 orientation 与精度设置不当时边界点可能漏判。
3. 距离排序与衰减
一句话总结: 距离排序输出由近到远的结果,地理衰减函数让距离参与打分。
3.1 按距离排序
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": { "lat": 39.915, "lon": 116.404 },
"order": "asc",
"unit": "km"
}
}
],
"size": 20
}
_geo_distance 排序按到中心点的距离升序,unit 控制返回的距离单位。排序结果会附带排序值(距离),可直接用于展示「距您 2.3km」。深翻页仍推荐 search_after,排序值就是距离,游标稳定。
3.2 gauss 地理衰减
{
"query": {
"function_score": {
"query": { "match": { "category": "restaurant" } },
"functions": [
{
"gauss": {
"location": {
"origin": { "lat": 39.915, "lon": 116.404 },
"scale": "5km",
"decay": 0.5
}
}
}
],
"boost_mode": "multiply"
}
}
}
gauss 函数作用于地理字段时,距离 origin 越近分数越高,5km 处衰减到一半。适合「相关性与距离综合排序」:餐厅相关词匹配决定基础分,距离做平滑加权,近的排在前面但远的不被完全排除。
3.3 排序与打分的选择
纯距离排名用 _geo_distance 排序;相关 + 距离混合排序用 function_score + gauss。注意 gauss 的 scale 要按业务半径设,scale 设得太小会只有极近点得高分,太大则衰减无意义。地理字段参与打分会读 Doc Values,排序量大时注意性能。
4. 边界框与多边形过滤
一句话总结: 边界框与多边形查询把检索范围限制到地图区域,用于区域筛选与权限圈定。
4.1 geo_bounding_box
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { "lat": 40.1, "lon": 116.3 },
"bottom_right": { "lat": 39.8, "lon": 116.6 }
}
}
}
}
geo_bounding_box 用左上/右下两点定义矩形区域,是最快的地理过滤方式,底层走倒排索引的单元格编码,适合与地图可视区域联动(地图平移时刷新当前视野内的数据)。
4.2 geo_polygon 与 geo_shape 查询
{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "polygon",
"coordinates": [[ [116.3,39.8], [116.7,39.8], [116.7,40.1], [116.3,39.8] ]]
},
"relation": "intersects"
}
}
}
}
geo_polygon 查询点是否在多边形内(注意 polygon 点按 geo_point 存储);geo_shape 查询支持点与区域的包含、相交、不相交等空间关系(intersects / within / contains / disjoint)。配送范围权限、禁飞区、商圈圈定都靠这类查询。
4.3 空间关系的选择
{
"query": {
"geo_shape": {
"delivery_zone": {
"relation": "within"
}
}
}
}
relation 语义要看清方向:intersects 表示图形相交,within 表示点在区域内,contains 表示区域包含查询图形。用错方向(把 within 当 contains)会得到相反结果。查询端 shape 每次传入图形,索引端提前建立好区域字段性能更好。
5. 地理聚合
一句话总结: 地理聚合把坐标折叠成距离带、范围框与中心点,用于地图热力与分布统计。
5.1 geo_distance 聚合
{
"size": 0,
"aggs": {
"distance_bands": {
"geo_distance": {
"field": "location",
"origin": { "lat": 39.915, "lon": 116.404 },
"unit": "km",
"ranges": [
{ "to": 1 }, { "from": 1, "to": 3 },
{ "from": 3, "to": 5 }, { "from": 5 }
]
}
}
}
}
geo_distance 聚合按距离带分桶,统计「1km 内、13km、35km」各有多少门店,常用于分析配送覆盖。聚合结果可叠加折线图看覆盖衰减曲线。
5.2 geo_bounds 与 geo_centroid
{
"size": 0,
"aggs": {
"bounds": { "geo_bounds": { "field": "location" } },
"center": { "geo_centroid": { "field": "location" } }
}
}
geo_bounds 输出一组坐标的包围盒(最小经度/纬度、最大经度/纬度),用于把地图视野自动缩放到数据范围;geo_centroid 输出重心,用于展示分组数据的中心点。两者结合能自动框选一组数据的展示区域。
5.3 网格热力
{
"size": 0,
"aggs": {
"grid": {
"geohash_grid": {
"field": "location",
"precision": 6
}
}
}
}
geohash_grid 按 geohash 网格分桶统计每个小格内的数据量,是地图热力图的数据来源。precision 越大网格越细、桶越多,聚合开销越大。热力数据量巨大时建议只对当前视野内的数据做聚合,并用 BKD 的过滤器裁剪。
6. 性能与索引优化
一句话总结: 地理性能取决于字段类型、查询裁剪与 Doc Values 使用,先粗筛再精算。
6.1 查询裁剪策略
第一步:geo_bounding_box 粗筛到视野区域(倒排索引,快)
第二步:geo_distance 精算距离(Doc Values 计算)
第三步:gauss 参与打分排序(聚合与打分)
先边界框粗筛再距离精算,能避免对全量坐标做球面距离计算。边界框过滤走倒排索引的网格编码,比 Doc Values 逐文档计算快一个量级,这是地理查询优化最重要的顺序。
6.2 索引结构与精度
geo_shape 的索引精度默认 50m,precision 越小索引越大、查询越精确;geo_point 的 Doc Values 按压缩的经纬度存储,支持高效排序与聚合。大批量写入时注意地理字段的坐标校验成本,坐标范围越界(纬度超 ±90)会被拒绝。
6.3 常见性能坑
| 场景 | 问题 | 缓解 |
|---|---|---|
| 全量距离计算 | 无粗筛,慢 | 先 bounding_box |
| gauss 无 scale | 衰减失效 | 按业务半径设 scale |
| 数组坐标顺序反 | 定位到错误半球 | 统一经度,纬度 |
| geo_shape 精度过细 | 索引爆炸 | 按需设 precision |
| 大范围 geohash_grid | 桶数量爆炸 | 限视野、降 precision |
7. 地图可视化对接
一句话总结: Kibana Maps 直接消费 geo_point 字段,聚合接口为热力与聚类提供数据。
7.1 Kibana Maps 数据源
Kibana Maps 选择数据视图后,把 geo_point 字段拖入图层即可打点;文档级图层按字段渲染坐标点,聚合级图层用 geohash_grid 输出热力。数据视图需先把字段映射为 geo_point,否则地图图层无法识别坐标。
7.2 GeoJSON 与外部对接
{
"type": "Feature",
"properties": { "name": "门店A" },
"geometry": {
"type": "Point",
"coordinates": [116.404, 39.915]
}
}
ES 响应可转成 GeoJSON Feature 供前端地图库(Leaflet、Mapbox)直接渲染:每个命中文档转成一个 Point Feature,properties 放业务字段。geo_shape 索引的区域也能以 GeoJSON 形式导出叠加在底图上做范围展示。
7.3 地图联动查询
地图平移缩放时,前端把视野的 bounds(左上/右下坐标)作为 geo_bounding_box 查询参数请求接口,只加载当前视野数据。视野过大时聚合层按网格抽稀,避免一次渲染上万点。距离计算在服务端完成,前端只展示「距您 X km」的排序结果。
8. 总结
一句话总结: 地理搜索以 geo_point 与 geo_shape 建模,距离查询排序、边界框粗筛、地理聚合与地图可视化构成从坐标到地图的完整链路。
| 环节 | 要点 |
|---|---|
| 数据类型 | geo_point 存点,geo_shape 存面 |
| 坐标约定 | 数组与 GeoJSON 经度在前,字符串纬度在前 |
| 距离查询 | geo_distance 圆形过滤,配合 bool filter |
| 排序 | _geo_distance 纯距离,gauss 混合打分 |
| 区域过滤 | bounding_box 粗筛,polygon/shape 精确圈定 |
| 地理聚合 | distance 带、bounds、centroid、geohash_grid |
| 性能 | 先粗筛后精算,精度与桶数收敛 |
| 可视化 | Kibana Maps 打点,GeoJSON 供前端渲染 |
地理搜索把坐标变成第一等公民:门店、外卖、配送范围都能被查询、排序与聚合。核心顺序是先用边界框把数据裁到视野,再用距离精算排序,最后用 gauss 或聚合做综合分析。查询与打分的上游语法可阅读《Query DSL 与相关性打分》,字段与索引设计参考《数据建模与 Mapping 设计》。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。