Sedona 通过提供地理空间 k 近邻(kNN)连接方法来支持对地理空间数据进行最近邻搜索。该方法基于地理临近性识别给定空间点或区域的 k 个最近邻,通常使用空间坐标以及合适的距离度量(如欧氏距离或大圆距离)。
简介:用于在空间数据集中查找一个点或区域的 k 个最近邻的连接操作。
格式:ST_KNN(R: Table, S: Table, k: Integer, use_spheroid: Boolean)
其中 R 是查询侧表,S 是对象侧表,K 是邻居数量。use_spheroid 是一个布尔值,决定是否使用椭球距离。
查询侧表包含用于在对象侧表中查找 k 近邻的几何对象。
当查询数据或对象数据中存在非点几何(其他几何类型)时,会取每个几何对象的质心。
当距离上出现并列时,只有在下面的 sedona 配置被设置为 true 时,结果才会包含所有并列的几何对象:
关于内连接的说明:
ST_KNN 连接仅支持 left inner join。spark.sedona.join.knn.includeTieBreakers=true
在 ST_KNN 之后对结果 DataFrame 施加的过滤条件,部分可能会被下推到 kNN 连接的对象侧。这意味着这些过滤会在 kNN 连接执行之前应用到对象侧的读取阶段。如果你希望过滤在 kNN 连接之后再生效,请先将 kNN 连接的结果物化,然后再应用过滤条件。
例如,可以使用以下方式:
Scala 示例:
val knnResult = knnJoinDF.cache() val filteredResult = knnResult.filter(condition)
SQL 示例:
CREATE OR REPLACE TEMP VIEW knnResult AS SELECT * FROM ( -- Your KNN join SQL here ) AS knnView; CACHE TABLE knnResult; SELECT * FROM knnResult WHERE condition;
使用 barrier 函数可以阻止谓词下推,并在复杂的空间连接中控制谓词的求值顺序。该函数通过在运行时对布尔表达式求值来创建一个优化屏障。
barrier 函数接收一个作为字符串的布尔表达式,之后是若干对变量名及其取值,这些取值会在该表达式中被替换:
barrier(expression, var_name1, var_value1, var_name2, var_value2, ...)
过滤条件相对于 KNN 连接的放置位置会改变查询的语义:
查找距离每家豪华酒店最近的 3 家高评分餐厅,并确保 KNN 连接先完成,再进行过滤。
SELECT h.name AS hotel, r.name AS restaurant, r.rating FROM hotels AS h INNER JOIN restaurants AS r ON ST_KNN(h.geometry, r.geometry, 3, false) WHERE barrier('rating > 4.0 AND stars >= 4', 'rating', r.rating, 'stars', h.stars)
借助 barrier 函数,这条查询会先为每家酒店找到 3 家最近的餐厅(不考虑评分),随后再过滤,仅保留餐厅评分大于 4.0 且酒店星级不低于 4 的配对。如果不使用 barrier,优化器可能会将过滤下推,将查询改写为先过滤出高评分餐厅与豪华酒店,再在这些过滤后的子集中找最近的 3 个。
在 Sedona 中,如果使用硬编码的 SQL select 语句创建 DataFrame,并随后在 ST_KNN 连接中使用它们,Sedona 可能会以绕过 kNN 连接逻辑的方式来优化查询。具体地说,如果你像下面这样用硬编码 SQL 创建 DataFrame:
val df1 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom1") val df2 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom2") val df = df1.join(df2, expr("ST_KNN(geom1, geom2, 1)"))
Sedona 可能会把这次连接优化为类似下面的形式:
SELECT ST_KNN(ST_Point(0.0, 0.0), ST_Point(0.0, 0.0), 1)
结果是,ST_KNN 函数被当作用户自定义函数(UDF)处理,而非一个连接操作,从而阻止 Sedona 走 kNN 连接的执行路径。与典型的 UDF 不同,ST_KNN 函数会跨 DataFrame 作用在多行上,而不是仅作用在单行上。当出现这种情况时,查询会以 UnsupportedOperationException 失败,提示不支持该 KNN 谓词。
解决方法:
为防止 Spark 的优化绕过 kNN 连接逻辑,必须先将由硬编码 SQL select 创建的 DataFrame 物化,再执行连接。可以通过缓存 DataFrame 告诉 Spark 不要进行这种不希望的优化:
val df1 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom1").cache() val df2 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom2").cache() val df = df1.join(df2, expr("ST_KNN(geom1, geom2, 1)"))
通过 .cache() 物化 DataFrame 后,Spark 逻辑计划中会走正确的 kNN 连接路径,避免将 ST_KNN 当成普通 UDF 处理的优化。
假设我们有两张表 QUERIES 与 OBJECTS,数据如下:
QUERIES 表:
ID GEOMETRY NAME 1 POINT(1 1) station1 2 POINT(10 10) station2 3 POINT(-0.5 -0.5) station3
OBJECTS 表:
ID GEOMETRY NAME 1 POINT(11 5) bank1 2 POINT(12 1) bank2 3 POINT(-1 -1) bank3 4 POINT(-3 5) bank4 5 POINT(9 8) bank5 6 POINT(4 3) bank6 7 POINT(-4 -5) bank7 8 POINT(4 -2) bank8 9 POINT(-3 1) bank9 10 POINT(-7 3) bank10 11 POINT(11 5) bank11 12 POINT(12 1) bank12 13 POINT(-1 -1) bank13 14 POINT(-3 5) bank14 15 POINT(9 8) bank15 16 POINT(4 3) bank16 17 POINT(-4 -5) bank17 18 POINT(4 -2) bank18 19 POINT(-3 1) bank19 20 POINT(-7 3) bank20
SELECT QUERIES.ID AS QUERY_ID, QUERIES.GEOMETRY AS QUERIES_GEOM, OBJECTS.GEOMETRY AS OBJECTS_GEOM FROM QUERIES JOIN OBJECTS ON ST_KNN(QUERIES.GEOMETRY, OBJECTS.GEOMETRY, 4, FALSE)
输出:
+--------+-----------------+-------------+ |QUERY_ID|QUERIES_GEOM |OBJECTS_GEOM | +--------+-----------------+-------------+ |3 |POINT (-0.5 -0.5)|POINT (-1 -1)| |3 |POINT (-0.5 -0.5)|POINT (-1 -1)| |3 |POINT (-0.5 -0.5)|POINT (-3 1) | |3 |POINT (-0.5 -0.5)|POINT (-3 1) | |1 |POINT (1 1) |POINT (-1 -1)| |1 |POINT (1 1) |POINT (-1 -1)| |1 |POINT (1 1) |POINT (4 3) | |1 |POINT (1 1) |POINT (4 3) | |2 |POINT (10 10) |POINT (9 8) | |2 |POINT (10 10) |POINT (9 8) | |2 |POINT (10 10) |POINT (11 5) | |2 |POINT (10 10) |POINT (11 5) | +--------+-----------------+-------------+