博客

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

Elasticsearch 查询语言 (ES|QL) 中的地理空间搜索。Elasticsearch 具有强大的地理空间搜索功能,ES|QL 将大幅提高易用性和 OGC 熟悉度。

亲身体验 Elasticsearch:在 Elasticsearch Labs 仓库中查看示例笔记本,开始免费云试用,或者立即在本地计算机上体验 Elastic。

多年来,Elasticsearch 一直拥有强大的地理空间搜索和分析功能,但其应用程序接口与典型 GIS 用户习惯的应用程序接口大相径庭。去年,我们 添加了 ES|QL 查询语言 ,这是一种管道式 查询语言 ,与 SQL 一样简单,甚至更容易。它特别适合 Elastic 擅长的搜索、安全和可观察性用例。我们还在 ES|QL 中添加了对地理空间搜索和分析的支持,使其更易于使用,特别是对于来自 SQL 或GIS社区的用户。

Elasticsearch 8.12 和 8.13 为 ES|QL 带来了对地理空间类型的基本支持。8.14 版增加了地理空间搜索功能后,这一功能大大增强。更重要的是,这种支持的设计与其他空间数据库(如 PostGIS ) 使用的 开放地理空间联盟(OGC) 的 简单地物访问 标准非常一致,使熟悉这些标准的 GIS 专家更容易使用。

在本博客中,我们将向您介绍如何使用 ES|QL 执行地理空间搜索,以及它与 SQL 和查询 DSL 的等效功能的比较。我们还将向您展示如何使用 ES|QL 执行空间连接,以及如何在 Kibana 地图中将结果可视化。请注意,此处描述的所有功能都在"技术预览版" 中,我们非常乐意听取您对我们如何改进这些功能的反馈意见。

搜索地理空间数据

让我们从一个查询示例开始:

FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location

这将搜索与三亚凤凰国际机场 (SYX) 周围矩形搜索多边形相交的任何城市边界多边形。

在一个包含机场、城市和城市边界的示例数据集中,该搜索可找到相交的多边形,并从匹配文档中返回所需字段:

缩写

机场

地区

城市

城市位置

SYX

三亚凤凰国际机场

天涯区

三亚

point(109.5036 18.2533)

这很容易!现在将其与经典的 Elasticsearch 查询 DSL 进行比较:

GET /airport_city_boundaries/_search
{
  "_source": ["abbrev", "airport", "region", "city", "city_location"],
  "query": {
    "geo_shape": {
      "city_boundary": {
        "shape": {
          "type": "polygon",
          "coordinates" : [[
            [109.4, 18.1],
            [109.6, 18.1],
            [109.6, 18.3],
            [109.4, 18.3],
            [109.4, 18.1]
          ]]
        }
      }
    }
  }
}

这两个查询的意图都相当明确,但 ES|QL 查询与 SQL 非常相似。同样的查询在 PostGIS 中是这样的:

SELECT abbrev, airport, region, city, city_location
FROM airport_city_boundaries
WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
);

请看 ES|QL 的例子。如此相似,不是吗?

FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location

我们发现,Elasticsearch API 的现有用户发现 ES|QL 更易于使用。现在,我们希望现有的 SQL 用户,尤其是空间 SQL 用户,会发现 ES|QL 与他们习惯看到的感觉非常相似。

为什么不是 SQL?

Elasticsearch SQL 如何?它已经存在了一段时间,并具有一些地理空间功能。不过,Elasticsearch SQL 是作为原始查询 API 的封装器编写的,这意味着只支持可转译到原始 API 的查询。ES|QL 没有这种限制。作为一个全新的堆栈,它可以进行许多 SQL 无法实现的优化。我们的基准测试表明,ES|QQL 通常比查询 API 更快,尤其是在聚合方面!

与 SQL 的区别

显然,从前面的例子来看,ES|QL 与 SQL 有些相似,但也有一些重要的区别。例如,ES|QL 是一种管道式查询语言,它以 FROM 等源命令开始,然后用管道 | 字符将所有后续命令串联起来。这样就很容易理解每条命令是如何接收数据表并对该表执行某些操作的,例如使用WHERE 进行过滤,使用EVAL 添加列,或使用STATS 执行聚合。与从SELECT 开始定义最终输出列不同,可以有一个或多个KEEP 命令,最后一个命令指定最终输出结果。这种结构简化了查询的推理过程。

仔细观察上述示例中的WHERE 命令,我们会发现它与 PostGIS 示例非常相似:

ES|QL

WHERE ST_INTERSECTS(
    city_boundary,
    "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)

PostGIS

WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
)

除了字符串引号字符的不同,最大的区别在于我们如何将字符串类型转换为空间类型。在 PostGIS 中,我们使用::geometry 后缀,而在 ES|QL 中,我们使用::geo_shape 后缀。这是因为 ES|QL 在 Elasticsearch 中运行,而类型转换操作符:: 可用于将字符串转换为ES|QL 支持的任何类型,在本例中就是geo_shape 。此外,Elasticsearch 中的geo_shape 和geo_point 类型意味着空间坐标系为 WGS84,通常使用 SRID 编号 4326。在 PostGIS 中,这一点需要明确,因此在 WKT 字符串中使用了SRID=4326; 前缀。如果删除该前缀,SRID 将被设置为 0,这更像 Elasticsearch 类型cartesian_point 和cartesian_shape ,它们与任何特定坐标系无关。

ES|QL 和 PostGIS 也提供了类型转换函数语法:

ES|QL

WHERE ST_INTERSECTS(
    city_boundary,
    TO_GEOSHAPE("POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))")
)

PostGIS

WHERE ST_INTERSECTS(
    city_boundary,
    ST_SetSRID(
      ST_GeomFromText('POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'),
      4326
    )
)

OGC 功能

Elasticsearch 8.14 引入了以下四种 OGC 空间搜索功能:

ES|QL

PostGIS

描述

ST_INTERSECTS

ST_Intersects

如果两个几何图形相交,则返回 true,否则返回 false。

ST_DISJOINT

ST_Disjoint

如果两个几何图形不相交,则返回 true,否则返回 false。ST_INTERSECTS 的倒数。

ST_CONTAINS

ST_Contains

如果一个几何体包含另一个几何体,则返回 true,否则返回 false。

ST_WITHIN

ST_Within

如果一个几何体位于另一个几何体内,则返回 true,否则返回 false。ST_CONTAINS 的倒数。

这些函数的行为与 PostGIS 的对应函数类似,使用方法也相同。例如,如果两个几何图形相交,ST_INTERSECTS 返回 true,否则返回 false。如果您跟踪上表中的文档链接,可能会发现所有 ES|QL 示例都在FROM 子句之后的WHERE 子句中,而所有 PostGIS 示例都使用了字面几何图形。事实上,这两个平台都支持在查询的任何部分使用这些函数。

ST_INTERSECTS 的 PostGIS 文档中的第一个示例是

SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);

ES|QL 相当于此:

ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)

请注意我们在 PostGIS 示例中没有指定 SRID。这是因为在 PostGIS 中使用geometry 类型时,所有计算都是在平面坐标系上进行的,因此如果两个几何图形具有相同的 SRID,SRID 是什么并不重要。在 Elasticsearch 中,大多数函数也是如此,但也有例外,geo_shape 和geo_point 使用球形计算,我们将在下一篇关于空间距离搜索的博客中看到这一点。

ES|QL 多功能性

因此,我们已经看到了在WHERE 子句和ROW 命令中使用空间函数的示例。它们还能在哪里发挥作用?EVAL 命令就是一个非常有用的地方。该命令允许您评估表达式并返回结果。例如,让我们确定按国名分组的所有机场的中心点是否都在勾勒出国家的边界内:

FROM airports
| EVAL in_uk = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL in_iceland = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| EVAL within_uk = ST_WITHIN(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL within_iceland = ST_WITHIN(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| STATS centroid = ST_CENTROID_AGG(location), count=COUNT() BY in_uk, in_iceland, within_uk, within_iceland
| SORT count ASC

结果在意料之中,英国机场的中心点在英国边界内,而不在冰岛边界内,反之亦然:

中心点

计数

英国

冰岛

within_uk

冰岛境内

点(-21.94663446396589364.13187285885215)

1

错误

true

错误

true

点 (-2.597342072712148 54.33551226578214)

17

true

错误

true

错误

点 (0.04453958108176276 23.74658354606057)

873

错误

错误

错误

错误

事实上,这些函数可用于查询的任何部分,只要其签名是合理的。它们都接收两个参数,参数可以是一个字面空间对象,也可以是一个空间类型的字段,并且都返回一个布尔值。一个重要的考虑因素是,几何图形的坐标参考系(CRS)必须匹配,否则将返回错误信息。这意味着不能在同一个函数调用中混合使用geo_shape 和cartesian_shape 类型。不过,您可以混合使用geo_point 和geo_shape 类型,因为geo_point 类型是geo_shape 类型的特例,两者共享相同的坐标参考系。上文定义的每个函数的文档都列出了支持的类型组合。

此外,任一参数都可以是空间字面量或字段,顺序不限。您甚至可以指定两个字段、两个字面量、一个字段和一个字面量或一个字面量和一个字段。唯一的要求是类型必须兼容。例如,此查询比较同一索引中的两个字段:

FROM airport_city_boundaries
| EVAL in_city = ST_INTERSECTS(city_location, city_boundary)
| STATS count=COUNT(*) BY in_city
| SORT count ASC
| EVAL cardinality = CASE(count < 10, "very few", count < 100, "few", "many")
| KEEP cardinality, count, in_city

该查询基本上是询问城市位置是否在城市边界内,一般来说应该是正确的,但总有例外:

基数

计数

城市

很少

29

错误

很多

740

true

一个更有趣的问题是,机场所在地是否在机场服务城市的边界之内。不过,机场位置所在的索引与包含城市边界的索引不同。这就需要一种方法来有效查询和关联这两个独立索引中的数据。

空间连接

ES|QL 不支持JOIN 命令,但可以使用ENRICH 命令实现连接的特殊情况,其行为类似于 SQL 中的 "左连接"。该命令的操作类似于 SQL 中的 "左连接",允许您根据两个数据集之间的空间关系,用另一个索引中的数据来丰富一个索引的结果。

例如,我们可以通过查找包含机场位置的城市边界,用机场服务城市的附加信息来丰富机场表的结果,然后对结果进行一些统计:

FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| MV_EXPAND city_boundary
| EVAL boundary_wkt_length = LENGTH(TO_STRING(city_boundary))
| STATS centroid = ST_CENTROID_AGG(location), count = COUNT(city_location), min_wkt = MIN(boundary_wkt_length), max_wkt = MAX(boundary_wkt_length) BY region
| SORT count DESC
| LIMIT 5

这将返回拥有最多机场的前 5 个区域,以及所有匹配区域机场的中心点和这些区域内城市边界的 WKT 表示长度范围:

中心点

计数

min_wkt

max_wkt

地区

点(-32.5609347096071932.598117914802714)

90

207

207

无效

点(-73.9451533276587740.70366442203522)

9

438

438

纽约市

点(-83.1039831787347842.300230911932886)

9

473

473

底特律

点(-156.302024586126220.176383580081165)

5

307

803

夏威夷

点(-73.8890273217111845.57078813901171)

4

837

837

蒙特利尔

那么,这里到底发生了什么?所谓的JOIN 发生在哪里?问题的关键在于ENRICH 命令:

FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary

该命令指示 Elasticsearch 丰富从airports 索引中获取的结果,并在原始索引的city_location 字段和airport_city_boundaries 索引的city_boundary 字段之间执行intersects 连接,我们在前面的几个示例中使用了该连接。但其中一些信息在该查询中并不清晰。我们看到的是丰富策略的名称city_boundaries ,缺失的信息被封装在该策略定义中。

{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}

在这里我们可以看到,它将执行geo_match 查询(intersects 是默认值),要匹配的字段是city_boundary ,而enrich_fields 是我们要添加到原始文档中的字段。其中一个字段region 实际上被用作STATS 命令的分组键,如果没有这种 "左连接 "功能,我们是做不到这一点的。有关 enrich 策略的更多信息,请参阅enrich 文档。在阅读这些文档时,你会注意到它们描述了如何通过配置摄取管道,在索引时使用丰富索引来丰富数据。ES|QL 不需要这样做,因为ENRICH 命令在查询时起作用。只需用必要的数据和丰富策略准备丰富索引,然后在 ES|QL 查询中使用ENRICH 命令即可。

您可能还会注意到,最常见的地区是null 。这意味着什么?回想一下,我曾将此命令比作 SQL 中的 "左连接",也就是说,如果没有为某个机场找到匹配的城市边界,则仍会返回该机场,但airport_city_boundaries 索引中的字段值为null 。结果发现,有 89 个机场没有找到匹配的city_boundary ,有一个机场找到了匹配的region 字段,但该字段是null 。这样就统计出了 90 个机场,结果中没有region 。另一个有趣的细节是MV_EXPAND 命令的必要性。这样做是必要的,因为ENRICH 命令可能会为每个输入行返回多个结果,而MV_EXPAND 则有助于将这些结果分成多行,每个结果一行。这也解释了为什么"Hawaii" 会显示不同的min_wkt 和max_wkt 结果:存在多个名称相同但边界不同的区域。

Kibana 地图

Kibana 在地图应用程序中添加了对空间 ES|QL 的支持。这意味着您现在可以使用 ES|QL 在 Elasticsearch 中搜索地理空间数据,并在地图上将结果可视化。

在添加图层菜单中有一个新的图层选项,名为"ES|QL" 。与迄今为止介绍的所有地理空间功能一样,该功能在"技术预览版" 中。选择该选项可根据 ES|QL 查询结果在地图上添加图层。例如,您可以在地图上添加一个图层,显示世界上所有的机场。

或者您也可以添加一个图层,显示airport_city_boundaries 索引中的多边形,或者更好的办法是使用上面那个复杂的ENRICH 查询,生成每个地区有多少个机场的统计数据?

后续工作计划

你可能已经注意到,在上面的两个例子中,我们又加入了一个空间函数ST_CENTROID_AGG 。这是STATS 命令中使用的聚合函数,也是我们计划添加到 ES|QL 中的众多空间分析功能中的第一个。当我们有更多展示时,我们会在博客上介绍!

在此之前,我们想向您详细介绍我们正在开发的一项特别令人兴奋的功能:执行空间距离搜索的能力,这是 Elasticsearch 最常用的空间搜索功能之一。你能想象距离搜索的语法会是什么样子吗?或许类似于 OGC 功能?敬请关注本系列的下一篇博客,一探究竟!

剧透警告:Elasticsearch 8.15 刚刚发布,其中包含使用 ES|QL 的空间距离搜索!

这些内容对您有多大帮助?

相关内容

利用 Elasticsearch 和 SigLIP-2 对山峰进行多模式搜索

利用 Elasticsearch 和 SigLIP-2 对山峰进行多模式搜索

Navneet Kumar
使用基于用户行为数据的判断列表在 Elasticsearch 中训练 LTR 模型

使用基于用户行为数据的判断列表在 Elasticsearch 中训练 LTR 模型

Alexander Dávila
评估搜索相关性第一部分 —— BEIR 基准测试

评估搜索相关性第一部分 —— BEIR 基准测试

Thanos Papaoikonomou
人工智能剽窃利用 Elasticsearch 检测剽窃行为

人工智能剽窃利用 Elasticsearch 检测剽窃行为

Priscilla Parodi
使用 Elasticsearch 进行词法和语义搜索

使用 Elasticsearch 进行词法和语义搜索

Priscilla Parodi

准备好打造最先进的搜索体验了吗?

足够先进的搜索不是一个人的努力就能实现的。Elasticsearch 由数据科学家、ML 操作员、工程师以及更多和您一样对搜索充满热情的人提供支持。让我们联系起来,共同打造神奇的搜索体验,让您获得想要的结果。