Apache Sedona 1.9.1 构建并运行示例空间分析应用指南
Apache Sedona 1.9.1 构建并运行示例空间分析应用指南:完整 Python 示例源码与 spark-shell Scala 版示例,实测 ST_Contains 范围连接命中 3 点、ST_Intersects 计数 3、ST_Distance KNN 返回最近 2 点,含两种运行方式。
Apache Sedona 1.9.1 构建并运行示例空间分析应用指南
目标:从零构建并运行一个基于 Apache Sedona 1.9.1 的最小地理空间分析应用:构造「行政区域多边形表 × 兴趣点表」两张 DataFrame,实测三类典型查询——
ST_Contains范围连接命中 3 点、ST_Intersects相交计数 3、ST_DistanceKNN 查询返回距参照点最近 2 点。给出 Python 与 Scala(spark-shell)两版示例及真实回显。所有命令与输出均在 Ubuntu 22.04.5 + JDK 17 + Spark 3.5.9 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS
- Java:OpenJDK 17.0.20.1
- Spark:3.5.9(
/opt/spark,local 模式) - Sedona 版本:1.9.1(
/opt/sedona-jars/sedona-spark-shaded-3.5_2.12-1.9.1.jar) - GeoTools wrapper:
/opt/sedona-jars/geotools-wrapper-1.9.1-33.5.jar - Python:3.10.12(pyspark 3.5.9、apache-sedona 1.9.1、shapely 2.1.2)
- 示例目录:
/opt/sedona-demo/ - 演示账号:root
二、前置条件
已按《Ubuntu 22.04 安装 Apache Sedona 1.9.1 指南》完成安装:Sedona 与 GeoTools wrapper jar 位于 /opt/sedona-jars/,Spark 环境变量已生效,Python 路线已补装 pandas/pyarrow。环境自检:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PYSPARK_PYTHON=python3
ls -lh /opt/sedona-jars
实测回显:
total 110M
-rw-r--r-- 1 root root 31M Aug 1 06:25 geotools-wrapper-1.9.1-33.5.jar
-rw-r--r-- 1 root root 79M Aug 2 06:37 sedona-spark-shaded-3.5_2.12-1.9.1.jar
创建示例目录:
mkdir -p /opt/sedona-demo
三、示例场景与数据设计
示例模拟「POI 落入行政区域」的经典空间分析:
| 数据表 | 字段 | 记录 |
|---|---|---|
| polygons(区域表) | id、geom(WKT 多边形)、name | zone-a:左下角矩形 (0,0)-(4,4);zone-b:右上角矩形 (5,5)-(9,9) |
| points(兴趣点表) | pid、pt(WKT 点) | 4 个点:(1,1)、(2,3)、(6,6)、(20,20) |
三类查询:
- 范围连接(Range Join):
ST_Contains判断每个点落在哪个多边形内,预期 zone-a 命中点 10、11,zone-b 命中点 12,点 13 无所属; - 相交计数:
ST_Intersects统计所有相交(点在边界上也算)对数,预期 3; - KNN 查询:以
POINT (0 0)为参照,按ST_Distance升序取最近 2 点,预期 pid=10(距离 √2≈1.414)与 pid=11(距离 √13≈3.606)。
四、Python 示例源码
创建 /opt/sedona-demo/sedona_demo.py:
# Sedona 最小空间分析示例:注册 Sedona SQL + 范围连接 + KNN
from sedona.spark import SedonaContext
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.master("local[*]") \
.appName("SedonaPythonDemo") \
.config("spark.jars", "/opt/sedona-jars/sedona-spark-shaded-3.5_2.12-1.9.1.jar,/opt/sedona-jars/geotools-wrapper-1.9.1-33.5.jar") \
.config("spark.driver.memory", "512m") \
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
.getOrCreate()
sedona = SedonaContext.create(spark)
sedona.sql("SELECT ST_AsText(ST_Point(1.0, 2.0)) AS point").show(truncate=False)
# 区域表:两个多边形
polygons = sedona.createDataFrame(
[(1, "POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))", "zone-a"),
(2, "POLYGON ((5 5, 9 5, 9 9, 5 9, 5 5))", "zone-b")],
["id", "geom", "name"])
polygons.createOrReplaceTempView("polygons")
# 兴趣点表:四个点
points = sedona.createDataFrame(
[(10, "POINT (1 1)"), (11, "POINT (2 3)"),
(12, "POINT (6 6)"), (13, "POINT (20 20)")],
["pid", "pt"])
points.createOrReplaceTempView("points")
# 1) ST_Contains 范围连接:点落入哪个区域
sedona.sql("""
SELECT p.name, pt.pid
FROM polygons p JOIN points pt
ON ST_Contains(ST_GeomFromWKT(p.geom), ST_GeomFromWKT(pt.pt))
ORDER BY p.name, pt.pid""").show(truncate=False)
# 2) ST_Intersects 相交计数
sedona.sql("""
SELECT count(*) AS cnt
FROM polygons p JOIN points pt
ON ST_Intersects(ST_GeomFromWKT(p.geom), ST_GeomFromWKT(pt.pt))""").show(truncate=False)
# 3) KNN:按 ST_Distance 升序取最近 2 个点(参照点 POINT (0 0))
sedona.sql("""
SELECT pt.pid, ST_Distance(ST_GeomFromWKT('POINT (0 0)'), ST_GeomFromWKT(pt.pt)) AS dist
FROM points pt
ORDER BY dist ASC LIMIT 2""").show(truncate=False)
spark.stop()
💡 说明:WKT 字符串经
ST_GeomFromWKT转为几何类型后参与空间谓词计算;SedonaContext.create会自动注册全部ST_*函数与几何类型。KNN 采用「按 ST_Distance 升序排序再取前 n 行」的排序式写法,小数据量下最直观;大数据量建议配合空间分区与索引(见《Apache Sedona 1.9.1 配置参数详解》篇)。
五、编译与运行(Python 版)
Python 无需编译,直接以 python3 运行:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PYSPARK_PYTHON=python3
cd /opt/sedona-demo
python3 sedona_demo.py
或以 Spark 作业方式运行(spark-submit 默认打印 INFO 日志,可加 --conf spark.log.level=WARN 静默):
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PYSPARK_PYTHON=python3
cd /opt/sedona-demo
/opt/spark/bin/spark-submit --master "local[*]" --conf spark.log.level=WARN sedona_demo.py
两种方式的查询输出完全一致(见第六节)。
六、运行结果与解读
真实输出(python3 与 spark-submit 实测一致):
+-----------+
|point |
+-----------+
|POINT (1 2)|
+-----------+
+------+---+
|name |pid|
+------+---+
|zone-a|10 |
|zone-a|11 |
|zone-b|12 |
+------+---+
+---+
|cnt|
+---+
|3 |
+---+
+---+------------------+
|pid|dist |
+---+------------------+
|10 |1.4142135623730951|
|11 |3.605551275463989 |
+---+------------------+
结果解读:
- ST_Contains 范围连接:点 (1,1)、(2,3) 落在 zone-a 矩形 (0,0)-(4,4) 内;点 (6,6) 落在 zone-b 矩形 (5,5)-(9,9) 内;点 (20,20) 落在所有区域外,未出现在结果中。命中合计 3 行。
- ST_Intersects 计数:
count(*)返回 3,与范围连接命中数一致(本例点均在多边形内部,Contains 与 Intersects 判定结果相同;点若恰在边界上,仅 Intersects 判为真)。 - ST_Distance KNN:参照点
POINT (0 0)到点 (1,1) 的欧氏距离为 √2 ≈ 1.4142135623730951,到点 (2,3) 为 √13 ≈ 3.605551275463989,按升序取前 2。
spark-submit 运行结束时进程正常退出,日志尾部(INFO 级别)实测:
26/10/12 13:25:26 INFO SparkContext: Successfully stopped SparkContext
26/10/12 13:25:27 INFO ShutdownHookManager: Shutdown hook called
七、Scala 版示例(spark-shell)
同一场景的 spark-shell 版本。将以下内容保存为 /opt/sedona-demo/sedona_demo.scala:
import org.apache.sedona.spark.SedonaContext
val sedona = SedonaContext.create(spark)
val polygons = Seq(
(1, "POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))", "zone-a"),
(2, "POLYGON ((5 5, 9 5, 9 9, 5 9, 5 5))", "zone-b")
).toDF("id", "geom", "name")
polygons.createOrReplaceTempView("polygons")
val points = Seq(
(10, "POINT (1 1)"),
(11, "POINT (2 3)"),
(12, "POINT (6 6)"),
(13, "POINT (20 20)")
).toDF("pid", "pt")
points.createOrReplaceTempView("points")
sedona.sql("SELECT p.name, pt.pid FROM polygons p JOIN points pt ON ST_Contains(ST_GeomFromWKT(p.geom), ST_GeomFromWKT(pt.pt)) ORDER BY p.name, pt.pid").show(false)
sedona.sql("SELECT count(*) AS cnt FROM polygons p JOIN points pt ON ST_Intersects(ST_GeomFromWKT(p.geom), ST_GeomFromWKT(pt.pt))").show(false)
sedona.sql("SELECT pt.pid, ST_Distance(ST_GeomFromWKT('POINT (0 0)'), ST_GeomFromWKT(pt.pt)) AS dist FROM points pt ORDER BY dist ASC LIMIT 2").show(false)
System.exit(0)
运行(-i 直接加载脚本;方案 A 的 spark-defaults.conf 已全局引入 Sedona jar,无需再传参):
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
/opt/spark/bin/spark-shell --master "local[*]" -i /opt/sedona-demo/sedona_demo.scala
实测回显(横幅关键行 + 三段查询结果):
Spark context available as 'sc' (master = local[*], app id = local-1791810790684).
Spark session available as 'spark'.
+------+---+
|name |pid|
+------+---+
|zone-a|10 |
|zone-a|11 |
|zone-b|12 |
+------+---+
+---+
|cnt|
+---+
|3 |
+---+
+---+------------------+
|pid|dist |
+---+------------------+
|10 |1.4142135623730951|
|11 |3.605551275463989 |
+---+------------------+
⚠️ 注意:
spark-shell -i脚本注释务必只用 ASCII 字符;POSIX locale 下含中文注释会报java.nio.charset.MalformedInputException: Input length = 1(解决方式见《Ubuntu 22.04 安装 Apache Sedona 1.9.1 指南》常见问题 Q2)。
八、JVM 堆内存验证
示例驱动进程堆限制 512m(代码内 spark.driver.memory 或 spark-defaults.conf)。作业运行期间另开终端验证实际生效值:
/usr/lib/jvm/java-17-openjdk-amd64/bin/jps -v | grep -oE "\-Xmx[0-9]+m"
实测回显:
-Xmx512m
九、注意事项
⚠️ 注意:
- 范围连接谓词必须写成
ST_Contains(左表几何, 右表几何)的函数形态;把几何转成字符串再比较不会走空间连接。- Python 路线的 Sedona jar 通过
spark.jars注入,路径必须绝对路径且文件存在;Scala 路线推荐spark-defaults.conf全局配置。ORDER BY dist LIMIT n的 KNN 是全量排序,点表极大时先按空间范围过滤(ST_DWithin/包围盒)再排序,或建空间索引后用范围连接近似。- local 模式驱动即执行器,512m 堆适合演示;生产集群的内存配置见《Apache Sedona 1.9.1 配置参数详解》篇。
十、常见问题
Q1:报 UNRESOLVED_ROUTINE: Cannot resolve function ST_Contains?
Sedona jar 未加载或未注册。检查 spark.jars 路径是否正确,并确认执行了 SedonaContext.create(spark)(Python 导入自 sedona.spark)。
Q2:python3 直跑正常,spark-submit 报找不到 jar?
spark-submit 与 python3 直跑使用同一份代码时,spark.jars 在代码内配置,两者均应生效;若改成了在 spark-defaults.conf 配置而 submit 用户不同,注意配置文件读取路径为 $SPARK_HOME/conf/。
Q3:ST_Contains 查询比预期多/少行?
先检查 WKT 是否合法(ST_IsValid),再确认边界语义:点恰在多边形边上时 ST_Contains 为假、ST_Intersects 为真;本示例点均在内部,两者计数一致(均为 3)。
Q4:作业卡在连接远程服务器?
代码中 master("local[*]") 已固定本地模式;若被 spark-defaults.conf 的 spark.master 覆盖,显式加 --master "local[*]" 参数优先级更高。
十一、参考链接
- Sedona SQL 函数总览:
https://sedona.apache.org/latest-snapshot/api/sql/Overview/ - Sedona Spatial Join 文档:
https://sedona.apache.org/latest-snapshot/tutorial/sql/pure-sql/ - Spark spark-submit 指南:
https://spark.apache.org/docs/latest/submitting-applications.html - Sedona 官网:
https://sedona.apache.org/