当前位置: 首页 > news >正文

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 在特定区域内搜索兴趣点,例如哥本哈根市内的兴趣点:

FROM denmark_pois
| WHERE name IS NOT NULL
| WHERE ST_INTERSECTS(geometry,TO_GEOSHAPE("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))"))
| LIMIT 10000

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

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

FROM denmark_pois
| WHERE name IS NOT NULL
| WHERE ST_DISTANCE(geometry,TO_GEOPOINT("POINT (12.564926 55.672938)")) < 10000
| LIMIT 10000

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

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

POST denmark_pois/_search
{"size": 10000,"query": {"geo_distance": {"distance": "10km","geometry": {"lat": 55.672938,"lon": 12.564926}}}
}

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

SELECT *
FROM denmark_pois
WHERE ST_Distance(geometry::geography,ST_SetSRID(ST_MakePoint(12.564926, 55.672938), 4326)::geography
) < 10000
LIMIT 10000;

回到 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 函数,包括当结果不用于地图显示时:

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

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

     count     |    fclass
---------------+---------------
1528           |fast_food
930            |cafe
842            |restaurant
492            |clothes
490            |bar
457            |hairdresser
368            |artwork
364            |supermarket
326            |convenience
258            |bakery
255            |bicycle_shop
184            |kiosk
135            |beverages
133            |jeweller
120            |butcher
113            |pub

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

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

这个查询不仅过滤结果,只包含咖啡馆,还计算并返回距离,最近的咖啡馆排在最前面。我们甚至可以用报告的距离在 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 这样的函数是否能被优化,取决于查询结构和底层数据。

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

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

这条查询从哥本哈根中央车站计算到所有机场的距离,筛选出重要机场(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 的第一个示例是:

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

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

ROW ST_DISTANCE("POINT(-72.1235 42.3521)"::geo_point,"LINESTRING(-72.1260 42.45, -72.123 42.1546)"::geo_shape
)

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

接下来

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

  • ST_CENTROID_AGG(8.15 版本新增)

  • ST_EXTENT_AGG(8.18 版本新增)

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

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

相关文章:

  • 动态规划3——背包类动态规划详解
  • elasticSearch是什么,如何使用,有什么用
  • 考研系列—408真题操作系统篇(2015-2019)
  • Windows环境变量原理(用户变量与系统变量)(用户环境变量、系统环境变量)
  • centos6.5 老旧系统编译glib-2.58.3.tar.bz2
  • vue-property-decorator实践(一)
  • 如何通过插件系统打造个性化效率工作流
  • AUTOSAR图解==>AUTOSAR_TR_InteroperabilityOfAutosarTools
  • PEP 8: E302 expected 2 blank lines, found 0
  • [vela os_5] 中断系统 | 任务调度 | 日志系统
  • node.js连接mysql写接口(一)
  • jupyter notebook的相关知识及可能遇到的问题
  • spring-ai MCP Server运行一段时间后断联2
  • Spring Boot3批式访问Dify聊天助手接口
  • 力扣-70.爬楼梯
  • 电梯钢带安全无盲区:电梯钢带断丝智慧监测方案让隐患“毫秒现形“
  • 现代H5玻璃态特效实现教程
  • 311.循环数组中相邻元素的最大差值
  • hot100滑动窗口无重复字符串
  • 第 87 场周赛:比较含退格的字符串、数组中的最长山脉、一手顺子、访问所有节点的最短路径
  • 武汉做网站seo优化/软文推广怎么做
  • 网站后台编辑器编辑内容无法显示/免费b站推广网站2023
  • 响应式网站建设资讯/百度推广有哪些形式
  • 做建材外贸哪个网站比较好/永久免费的建站系统有哪些
  • 百度上开个网站怎么做/电子技术培训机构
  • 寿县住房与城乡建设局网站/seoul national university