Elasticsearch:使用 ES|QL 进行地理空间距离搜索

作者:来自 Elastic Craig Taverner

在 Elasticsearch 查询语言(ES|QL)中探索地理空间距离搜索,这是 Elasticsearch 地理空间搜索中最受欢迎和最有用的功能之一,也是 ES|QL 中的重要特性。

想获得 Elastic 认证吗?了解下一次 Elasticsearch 工程师培训的时间!

Elasticsearch 提供了大量新功能,帮助你根据自己的使用场景构建最佳搜索解决方案。浏览我们的示例笔记本以了解更多信息,开始免费的云试用,或立即在本地机器上体验 Elastic。


去年,我们介绍了如何使用 ES|QL 执行地理空间搜索,并在后续博客中展示了如何将地理空间数据摄取到 ES|QL 中使用。虽然这些博客介绍了 ES|QL 中强大的地理空间搜索功能,但它们并未涵盖其中最受欢迎的功能之一 ------ 在 Elasticsearch 8.15 中引入的距离搜索。

与我们为 ES|QL 添加的所有地理空间功能一样,这个功能也是为了严格遵循 OGC( Open Geospatial Consortium - 开放地理空间联盟)的 Simple Feature Access 标准设计的,该标准也被 PostGIS 等其他空间数据库使用,对于熟悉这些标准的 GIS 专家来说,这项功能更易于使用。

在这篇博客中,我们将向你展示如何使用 ES|QL 执行地理空间距离搜索,并与 SQL 和 Query DSL 的等效方式进行对比。

搜索地理空间数据

让我们回顾一下上一篇博客中使用的主要搜索函数 ------ ST_INTERSECTS 函数。假设你有一个丹麦兴趣点(POI)数据集,比如通过 Geofabrik 下载的 OpenStreetMap 丹麦数据,并且已经将其导入到 Elasticsearch 中(例如,使用 Kibana 地图导入 ESRI ShapeFile 的功能),你可以使用 ES|QL 在特定区域内搜索兴趣点,例如哥本哈根市内的兴趣点:

sql 复制代码
`

1.  FROM denmark_pois
2.  | WHERE name IS NOT NULL
3.  | WHERE ST_INTERSECTS(
4.      geometry,
5.      TO_GEOSHAPE(
6.        "POLYGON ((12.444077 55.606669, 12.681656 55.608996, 12.639084 55.720149, 12.593765 55.762282, 12.459869 55.747985, 12.417984 55.654735, 12.444077 55.606669))"
7.      )
8.    )
9.  | LIMIT 10000

`AI写代码

这将搜索所有位于我们用于描绘哥本哈根市的简单多边形内的点状几何图形。

但该查询由于使用了较大的多边形表达式,看起来并不优雅。我们更可能想要查询的是某个中心点一定距离内的所有点,例如我们当前在哥本哈根中央火车站的位置:

sql 复制代码
`

1.  FROM denmark_pois
2.  | WHERE name IS NOT NULL
3.  | WHERE ST_DISTANCE(
4.      geometry,
5.      TO_GEOPOINT("POINT (12.564926 55.672938)")
6.    ) < 10000
7.  | LIMIT 10000

`AI写代码

这个更简单的查询请求获取位于纬度 55.672938 和经度 12.564926(中央车站内)这个点 10,000 米(10 公里)范围内的所有点。

现在,将这个查询与 Elasticsearch Query DSL 中的等效查询进行对比:

bash 复制代码
`

1.  POST denmark_pois/_search
2.  {
3.    "size": 10000,
4.    "query": {
5.      "geo_distance": {
6.        "distance": "10km",
7.        "geometry": {
8.          "lat": 55.672938,
9.          "lon": 12.564926
10.        }
11.      }
12.    }
13.  }

`AI写代码

两个查询在意图上都相当清晰。不过请注意,ES|QL 查询与 SQL 非常相似。相同的查询在 PostGIS 中看起来像这样:

markdown 复制代码
`

1.  SELECT *
2.  FROM denmark_pois
3.  WHERE ST_Distance(
4.      geometry::geography,
5.      ST_SetSRID(ST_MakePoint(12.564926, 55.672938), 4326)::geography
6.  ) < 10000
7.  LIMIT 10000;

`AI写代码

回到 ES|QL 的例子,是不是很相似?实际上,ES|QL 查询甚至比 PostGIS 查询更简单,因为它不需要使用 ST_SetSRID 函数为点几何设置坐标参考系(CRS),也不需要使用 ::geography 类型转换来确保在球面坐标系统上进行距离计算。这是因为 ES|QL 的 TO_GEOPOINT 函数使用的是 geo_point 类型,而该类型始终处于 WGS84 坐标参考系中,并确保所有距离计算都基于球面坐标系统。

距离计算方式

这就引出了一个重要问题:距离是如何计算的?如前所述,ES|QL 的 geo_point 类型始终使用 WGS84 坐标参考系,它是一个球面坐标系统。实际的距离计算使用的是 Haversine 公式 ,该公式用于根据两个点的纬度和经度计算它们在球面上的距离。这个计算方式适用于 ES|QL 的 ST_DISTANCE 函数以及 Query DSL 的 geo_distance 查询。

这又引出另一个重要点:由于 ES|QL 与 Query DSL 兼容,甚至可以复用底层的 Lucene 空间索引,因此其距离计算也受到 Lucene 空间索引所定义的精度限制。Lucene 使用量化函数将 64 位浮点数转换为 32 位整数,这意味着 Elasticsearch 中的所有空间函数(包括 ES|QL 中的)都受限于这种量级约为 1 厘米 的精度。你可以在这篇博客中了解更多:Elasticsearch 中基于 BKD 的 geo_shapes:精度 + 效率 + 速度

ST_DISTANCE 的其他用法

我们可以在许多其他场景中使用 ST_DISTANCE 函数,包括当结果不用于地图显示时:

sql 复制代码
`

1.  FROM denmark_pois
2.  | WHERE name IS NOT NULL
3.  | WHERE ST_DISTANCE(geometry, TO_GEOPOINT("POINT (12.564926 55.672938)")) < 10000
4.  | STATS count=COUNT() BY fclass
5.  | SORT count DESC
6.  | LIMIT 16

`AI写代码

按类别统计 "兴趣点(points of interest)" 数量的表格结果,按最常见类别排序:

markdown 复制代码
 `1.       count     |    fclass
2.  ---------------+---------------
3.  1528           |fast_food
4.  930            |cafe
5.  842            |restaurant
6.  492            |clothes
7.  490            |bar
8.  457            |hairdresser
9.  368            |artwork
10.  364            |supermarket
11.  326            |convenience
12.  258            |bakery
13.  255            |bicycle_shop
14.  184            |kiosk
15.  135            |beverages
16.  133            |jeweller
17.  120            |butcher
18.  113            |pub`AI写代码

接下来我们可能想关注咖啡馆,找出离中央车站最近的几个:

sql 复制代码
`

1.  FROM denmark_pois
2.  | WHERE name IS NOT NULL AND fclass == "cafe"
3.  | EVAL distance = ST_DISTANCE(geometry, TO_GEOPOINT("POINT (12.564926 55.672938)"))
4.  | WHERE distance < 2000
5.  | SORT distance ASC
6.  | LIMIT 100

`AI写代码

这个查询不仅过滤结果,只包含咖啡馆,还计算并返回距离,最近的咖啡馆排在最前面。我们甚至可以用报告的距离在 Kibana 地图上进行颜色标注:

为什么不用 SQL?

Elasticsearch SQL 已经存在一段时间,并且具备一些地理空间功能。但是,Elasticsearch SQL 是作为原始 Query API 之上的一个封装写的,这意味着它只支持可以转换成原始 API 的查询。ES|QL 没有这个限制。作为一个全新的架构,ES|QL 允许许多 SQL 无法实现的优化。它甚至支持 Query API 不支持的功能,比如 EVAL 命令,可以评估表达式并返回结果。我们的基准测试显示,ES|QL 在很多情况下比 Query API 更快,尤其是在聚合操作中!

显然,从之前的例子看,ES|QL 和 SQL 很相似,但有一些重要区别,我们在之前关于 ES|QL 地理空间搜索的博客中做了更详细的讨论。

ST_DISTANCE 性能

一个明显的问题是 ST_DISTANCE 函数的性能如何?乍一看,似乎会很慢,因为它需要对索引中的每个点计算距离。但事实并非如此。ST_DISTANCE 函数经过优化,使用了与 Query DSL 中 geo_distance 查询相同的空间索引。实际上,即使是 SORT distance ASC 命令也经过优化,使用相同的空间索引,因此执行非常快。

去年我们首次实现 ST_DISTANCE 函数时,在基准测试数据集上运行大约需要 30 秒。随后我们进行了名为 "Lucene Pushdown" 的优化,确保在可能的情况下,查询能最大限度地利用底层的 Lucene 索引。经过该优化后,同样的查询只需要 50 毫秒。

这些优化是如何实现的呢?一般来说,对于像 ES|QL 这样的声明式查询语言,查询引擎能够分析查询内容,并确定最优的执行方式。像 ST_DISTANCE 这样的函数是否能被优化,取决于查询结构和底层数据。

举个例子,考虑下面的查询:

markdown 复制代码
`

1.  FROM airports
2.  | EVAL distance = ST_DISTANCE(location, TO_GEOPOINT("POINT(12.565 55.673)"))
3.  | WHERE distance < 1000000 AND scalerank < 6 AND distance > 10000
4.  | SORT distance ASC
5.  | KEEP distance, abbrev, name, location, country, city

`AI写代码

这条查询从哥本哈根中央车站计算到所有机场的距离,筛选出重要机场(scalerank 小于 6),并且距离在 10 公里到 1000 公里之间(排除了哥本哈根机场本身),最后按距离排序。这是相当繁重的工作,如何加速呢?

查询引擎内置一系列优化规则,通过反复应用这些规则,将查询转换成语义等价但执行更快的形式。

在这个例子中,主要做了以下优化:

  • 自动添加了 LIMIT 1000(如果你没有自己加,ES|QL 会自动加)。

  • SORTLIMIT 合并成一个 Lucene 特别支持的 TOPN 操作。

  • WHERE 条件拆成两部分:

    1. 先按 scalerank 过滤,这是一个已建立索引的字段,方便用 "Lucene Pushdown" 做优化。

    2. 对剩余文档计算距离,然后根据距离上下限过滤,这一步也可能进一步优化。

  • 推送尽可能多的过滤条件到 Lucene 底层:

    • scalerank 过滤直接推到 Lucene。

    • 把距离过滤转换成两个空间过滤:

      • ST_INTERSECTS(location, TO_GEOSHAPE("CIRCLE(12.565 55.673, 1000000)"))(距离上限)

      • ST_DISJOINT(location, TO_GEOSHAPE("CIRCLE(12.565 55.673, 10000)"))(距离下限)

    • 这些空间过滤利用 Lucene 的空间索引快速筛掉不匹配的文档。

    • TOPN 操作推给 Lucene,Lucene 原生支持 GeoDistanceSort

这大幅减少了需要计算距离的文档数量,极大减轻了 ES|QL 计算引擎负担。剩下的处理步骤是:

  • 从过滤后文档提取位置字段。

  • 计算每个文档的距离(因为查询仍需返回距离值)。

  • 提取 KEEP 命令要求的其他字段。

  • 各数据节点将数据返回给协调节点。

  • 协调节点对合并结果做最终的距离排序。

正如前面提到的,这种优化使查询性能大幅提升。在 6000 万条数据的基准测试中,经过优化的查询只需约 50 毫秒,而未优化时需要 30 秒。

OGC 函数

正如上一篇博客中所述,Elasticsearch 8.14 引入了四个 OGC 空间搜索函数。随着 8.15 版本中 ST_DISTANCE 的加入,我们现在拥有了一套完整的 OGC 函数,这些函数被视为 ES|QL 中核心的"空间搜索"函数:

  • ST_INTERSECTS :如果两个几何体相交,返回 true,否则返回 false。与 PostGIS 中的 ST_Intersects 相对应。

  • ST_DISJOINT :如果两个几何体不相交,返回 true,否则返回 false。是 ST_INTERSECTS 的反义。与 PostGIS 中的 ST_Disjoint 相对应。

  • ST_CONTAINS:如果一个几何体包含另一个几何体,返回 true,否则返回 false。与 PostGIS 中的 ST_Contains 相对应。

  • ST_WITHIN :如果一个几何体位于另一个几何体内部,返回 true,否则返回 false。是 ST_CONTAINS 的反义。与 PostGIS 中的 ST_Within 相对应。

  • ST_DISTANCE :返回两个几何体之间的距离。如果字段类型是 geo_point,距离计算使用球面计算方法,与现有的 Elasticsearch geo_distance 查询相同。与 PostGIS 中的 ST_Distance 相对应。

所有这些函数的行为与其 PostGIS 对应函数相似,使用方式也相同。如果你查看上文中的文档链接,会发现所有 ES|QL 的示例都写在 FROM 之后的 WHERE 子句中,而 PostGIS 示例则使用具体的几何字面量。实际上,这两个平台都支持在查询中任何语义合理的部分使用这些函数。

限制

PostGIS 文档中 ST_DISTANCE 的第一个示例是:

ini 复制代码
`

1.  SELECT ST_Distance(
2.      'SRID=4326;POINT(-72.1235 42.3521)'::geometry,
3.      'SRID=4326;LINESTRING(-72.1260 42.45, -72.123 42.1546)'::geometry );

`AI写代码

这个在 ES|QL 中的等价写法是:

php 复制代码
`

1.  ROW ST_DISTANCE(
2.      "POINT(-72.1235 42.3521)"::geo_point,
3.      "LINESTRING(-72.1260 42.45, -72.123 42.1546)"::geo_shape
4.  )

`AI写代码

不过,目前 ES|QL 还不支持 geo_shape。现在只能计算两个 geo_point 几何体之间,或者两个 cartesian_point 几何体之间的距离。

接下来

自从加入 ST_DISTANCE 之后,我们新增了两个聚合函数:

这两个函数用于 STATS 命令的聚合,是我们计划在 ES|QL 中添加的众多空间分析功能中的前两个。我们有更多内容时会发布博客进行介绍!

原文:Geospatial distance search with ES|QL - Elasticsearch Labs

相关推荐
亲爱的非洲野猪13 小时前
基于ElasticSearch的法律法规检索系统架构实践
大数据·elasticsearch·系统架构
从零开始学习人工智能18 小时前
Doris 与 Elasticsearch:谁更适合你的数据分析需求?
大数据·elasticsearch·数据分析
代码搬运媛21 小时前
ES Modules 与 CommonJS 的核心区别详解
大数据·elasticsearch·搜索引擎
数据智能老司机1 天前
Elastic 向量搜索实战指南——Elastic中的模型管理与向量相关考量
elasticsearch·搜索引擎·llm
不爱学英文的码字机器1 天前
[Git] 标签管理
大数据·git·elasticsearch
异常君1 天前
Elasticsearch 与机器学习结合:实现高效模型推理的方案(上)
java·elasticsearch·机器学习
异常君1 天前
Elasticsearch 与机器学习结合:实现高效模型推理的方案(下)
java·elasticsearch·机器学习
Elastic 中国社区官方博客1 天前
使用 OpenTelemetry 和 Elastic 简化公共部门的可观察性
大数据·elasticsearch·搜索引擎·全文检索·可用性测试·opentelemetry
异常君1 天前
Elasticsearch 集群滚动升级实战 - 保障业务零停机
java·elasticsearch
异常君1 天前
FST 在 Elasticsearch 中的核心应用与性能优化实践
java·elasticsearch·性能优化