From 672b4ee97d22b161492a9ae2f0644a089d3c72dc Mon Sep 17 00:00:00 2001 From: Jia Yu Date: Mon, 11 May 2026 19:36:11 -0700 Subject: [PATCH 1/2] [GH-2867] Fix empty blog index under i18n suffix mode The mkdocs-material blog plugin generates archive and pagination views in a tempfile.mkdtemp() directory, which mkdocs-static-i18n's reconfigure_files drops as "Unhandled file case" because they live outside docs_dir. The result on /latest/blog/ was an empty index with no individual posts and no per-post pages. This regression was introduced when i18n was wired up in #2920 and is a known incompatibility (ultrabug/mkdocs-static-i18n#283). Add a hook that disconnects blog files from the i18n plugin before its on_files runs and reconnects them after, so the blog renders once in the default language and is served identically under every language version. Remove the .zh.md blog files added in #2950 since the current i18n setup cannot serve translated blog content alongside the workaround; they were also confusing the blog plugin's pagination. --- docs-overrides/hooks/i18n_blog_passthrough.py | 72 +++ docs/blog/index.zh.md | 28 - docs/blog/posts/h3.zh.md | 437 -------------- docs/blog/posts/intro-sedonadb-0-2.zh.md | 246 -------- docs/blog/posts/intro-sedonadb-0-3.zh.md | 480 ---------------- docs/blog/posts/intro-sedonadb.zh.md | 409 -------------- docs/blog/posts/intro-spatialbench.zh.md | 185 ------ docs/blog/posts/intro-to-sedona-blog.zh.md | 51 -- .../posts/sedona-2025-year-in-review.zh.md | 150 ----- ...patial-query-benchmarking-databricks.zh.md | 155 ----- .../blog/posts/spatial-tables-lakehouse.zh.md | 532 ------------------ mkdocs.yml | 2 + 12 files changed, 74 insertions(+), 2673 deletions(-) create mode 100644 docs-overrides/hooks/i18n_blog_passthrough.py delete mode 100644 docs/blog/index.zh.md delete mode 100644 docs/blog/posts/h3.zh.md delete mode 100644 docs/blog/posts/intro-sedonadb-0-2.zh.md delete mode 100644 docs/blog/posts/intro-sedonadb-0-3.zh.md delete mode 100644 docs/blog/posts/intro-sedonadb.zh.md delete mode 100644 docs/blog/posts/intro-spatialbench.zh.md delete mode 100644 docs/blog/posts/intro-to-sedona-blog.zh.md delete mode 100644 docs/blog/posts/sedona-2025-year-in-review.zh.md delete mode 100644 docs/blog/posts/spatial-query-benchmarking-databricks.zh.md delete mode 100644 docs/blog/posts/spatial-tables-lakehouse.zh.md diff --git a/docs-overrides/hooks/i18n_blog_passthrough.py b/docs-overrides/hooks/i18n_blog_passthrough.py new file mode 100644 index 00000000000..ec2d62dceb3 --- /dev/null +++ b/docs-overrides/hooks/i18n_blog_passthrough.py @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Works around the known incompatibility between mkdocs-material's blog plugin +# and mkdocs-static-i18n: the blog plugin generates archive/pagination files +# under a tempfile.mkdtemp() directory, which mkdocs-static-i18n's +# reconfigure_files drops because they live outside docs_dir. Result: the +# rendered /blog/ index has no individual posts. +# +# This hook removes blog files from the Files collection before i18n's on_files +# (priority -100) processes them, then re-adds them after. Net effect: the blog +# is rendered once in the default language and served identically under every +# language version. +# +# Source: https://github.com/ultrabug/mkdocs-static-i18n/issues/283 + +from mkdocs import plugins +from mkdocs.structure.files import File, Files + +BLOG_FILES: list = [] + + +@plugins.event_priority(-95) +def _on_files_disconnect_blog_files(files: Files, config, *_, **__): + """Remove blog files after the blog plugin's on_files (-50), before i18n's (-100).""" + global BLOG_FILES + BLOG_FILES = [] + non_blog_files: list[File] = [] + blog_prefixes: list[str] = [] + + for name, instance in config.plugins.items(): + if name.startswith("material/blog"): + blog_prefixes.append(instance.config.blog_dir) + + blog_prefix_tuple = tuple(p.rstrip("/") + "/" for p in blog_prefixes) + config.extra.i18n_blog_prefixes = blog_prefix_tuple + + for file in files: + if file.src_uri.startswith(blog_prefix_tuple): + BLOG_FILES.append(file) + else: + non_blog_files.append(file) + + return Files(non_blog_files) + + +@plugins.event_priority(-105) +def _on_files_connect_blog_files(files: Files, *_, **__): + """Restore blog files after i18n's on_files has run.""" + for file in BLOG_FILES: + files.append(file) + return files + + +on_files = plugins.CombinedEvent( + _on_files_disconnect_blog_files, + _on_files_connect_blog_files, +) diff --git a/docs/blog/index.zh.md b/docs/blog/index.zh.md deleted file mode 100644 index c3829ae013a..00000000000 --- a/docs/blog/index.zh.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -hide: - - - navigation - -title: 博客 ---- - - - -Apache Sedona 官方资讯来源,涵盖技术洞见、版本更新以及大规模空间数据管理的最佳实践。 diff --git a/docs/blog/posts/h3.zh.md b/docs/blog/posts/h3.zh.md deleted file mode 100644 index 5e5227861f8..00000000000 --- a/docs/blog/posts/h3.zh.md +++ /dev/null @@ -1,437 +0,0 @@ ---- -date: - created: 2025-09-05 -links: - - Apache Sedona Discord 社区: https://discord.com/invite/9A3k5dEBsY - - SedonaSnow: https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow - - Apache Sedona on Apache Flink: https://sedona.apache.org/latest/tutorial/flink/sql/ -authors: - - matt_forrest -title: 你应该用 H3 做地理空间分析吗?——基于 Apache Spark 和 Sedona 的深度解析 ---- - - - -TL;DR [H3 空间索引](https://www.uber.com/blog/h3/) 提供了一系列空间函数和统一的网格系统,可用于高效的数据聚合与可视化。H3 本质上是一种近似方法,能让部分计算更快,但精度有所下降。Sedona 支持 H3 空间索引,但在很多情况下,尤其当精度重要时,使用精确计算更为合适。 - - - -## 什么是 H3? - -H3 索引是一种空间索引,它将真实世界的几何(点、线、面)转换为六边形分层索引,也就是离散全球网格。例如: - -```py -# 40.6892524,-74.044552 - Location of the Statue of Liberty - -import h3 - -resolution = 8 -h3.latlng_to_cell(40.6892524, -74.044552, resolution) -"882a1072b5fffff" -``` - -每个单元都被另外 6 个单元包围,大约 7 个单元在一个分辨率上大致对应到下一个更粗分辨率上的一个更大的"父"单元。这种嵌套并不完美,子单元可能会略微越过父单元的边界(见下图): - -![Image showing H3 nested grid cells over San Francisco](../../image/blog/h3/image5.png){ align=center width="80%" } - -_图片来源:[Indexing h3geo.org](https://h3geo.org/docs/highlights/indexing/)_ - -每个单元还有可度量的面积,取决于它在地球上的位置,从 0 级到 15 级。 - -| 分辨率 | 单元平均面积 | 面积(km²) | 面积(m²) | -| ----- | ----- | ----- | ----- | -| **0** | 最大 | 4,250,546 km² | 4.25 billion m² | -| **15** | 最小 | 0.0000009 km² | 0.9 m² | - -获得这个索引后,你可以使用一系列函数进行近似空间计算,例如(但不限于): - -- 检视:获取单元分辨率、字符串转换等。 -- 遍历:距离、单元之间的路径、环距离等。 -- 层次:单元的父子关系 -- 区域:将多边形或其他几何转换为 H3 单元 -- 关系:单元的网格关系 -- 转换:单元转质心、坐标或几何 - -### 为什么开发 H3? - -H3 由 Uber 开发,旨在为数据科学函数提供一个通用层,用于分析乘车数据,并在司机端 App 中可视化展示上车热点。它提供了一种高效的方法来分析和可视化大规模点数据,而无需计算地理关系:数据可以直接发送到应用(在这里是司机移动 App),无需经纬度数据,只需 H3 索引即可。由于 H3 单元已作为地图图层存在于移动 App 中,因此可以高效地聚合并可视化展示热点。 - -### 镶嵌与铺砌 - -镶嵌(Tessellation),又称铺砌(tiling),是指如何用拼块铺满一个平面而不留空隙。 - -二维平面铺砌很直观,想想看棋盘是如何用方格拼块铺满的。 - -而用拼块铺满球面就比铺二维平面要复杂得多。H3 通过将六边形投影到二十面体的 20 个面上,然后映射到地球球面,从而构建其全球网格。本文不聚焦于数学,因此我们现在专注于 H3 在地理空间分析中的实际应用。 - -### 当今 H3 的用途? - -如今 H3 越来越多地被用于加速常见的空间操作,例如空间连接(spatial join)。由于几何会被转换为字符串或整数,基于非空间数据类型优化连接的工具可以更快地执行空间操作。由于单元在地球上始终具有相同的位置和形状,可视化也更高效——不需要将几何数据传给可视化应用。 - -不过,使用 H3 索引处理空间数据在精度、数据保留、数据转换与处理等方面也存在显著权衡。 - -* 丢失原始几何数据:如果丢弃原始几何数据,一旦转换为 H3 单元就无法再转换回原始几何数据 -* 复杂、耗时的多边形转换:将多边形转换为 H3 单元可能在计算上复杂且耗时。在点与多边形之间进行空间连接通常需要这一步。 -* 多边形覆盖不精确:用单元填充多边形时不存在完美的填充级别,因此一部分多边形会未被结果单元集合覆盖,而另一些原始多边形之外的区域则会被单元覆盖(见下图)。 -* 重叠效应:H3 单元在父与子之间没有完全的覆盖一致性,从而在 H3 单元内部带来一定程度的误差(参见上图中的重叠区域) - -![H3 cells overlapping a bounding box in London](../../image/blog/h3/image2.png){ align=center width="80%" } - -_图片来源:[StackOverflow](https://stackoverflow.com/questions/75860703/h3-how-to-get-the-h3-index-of-all-cells-that-are-at-least-partially-inside-a-b)_ - -Apache Sedona 同时提供了无需使用 H3 单元的基于几何的空间函数,以及创建和读取 H3 索引的函数。无论选择哪种方式,你都可以借助 Apache Sedona 在大规模数据上执行空间查询。 - -### H3 在 Spark 与 Sedona 中的示例 - -让我们创建一个包含起点和终点列的 DataFrame,使用纽约的帝国大厦、纽约的自由塔以及芝加哥的威利斯大厦。 - -```py -empire_state_building = Point(-73.985428, 40.748817) -freedom_tower = Point(-74.013379, 40.712743) -willis_tower = Point(-87.635918, 41.878876) - -df = sedona.createDataFrame( - [ - (empire_state_building, freedom_tower), - (empire_state_building, willis_tower), - ], - ["start", "end"], -) -``` - -让我们看看如何计算起点和终点之间的球面距离以及 H3 单元距离。 - -```python -res = ( - df.withColumn("st_distance_sphere", ST_DistanceSphere(col("start"), col("end"))) - .withColumn("start_cellid", ST_H3CellIDs(col("start"), 6, True)) - .withColumn("end_cellid", ST_H3CellIDs(col("end"), 6, True)) - .withColumn( - "h3_cell_distance", - ST_H3CellDistance(col("start_cellid")[0], col("end_cellid")[0]), - ) -) -``` - -这段代码有几点值得注意: - - * 实例化 `ST_H3CellIDs` 时必须提供分辨率。例如,`ST_H3CellIDs(col("start"), 6, True)` 使用分辨率 `6`。 - * `ST_H3CellDistance` 返回一个整数,表示 H3 网格上两个 H3 单元之间相隔的步数(六边形数)。 - -让我们看一下结果 DataFrame: - -```python -res.select("st_distance_sphere", "h3_cell_distance").show() -``` - -```text -+------------------+----------------+ -|st_distance_sphere|h3_cell_distance| -+------------------+----------------+ -|4651.5708314048225| 1| -|1145748.4514602514| 180| -+------------------+----------------+ -``` - -`ST_DistanceSphere` 函数返回两点之间的距离(单位:米),而 `ST_H3CellDistance` 函数返回在给定分辨率下两个 H3 单元之间相隔的 H3 单元数。 - -接下来让我们看一个更现实的 H3 使用示例。 - -## 在 Apache Sedona 中对比 H3 与几何 - -为了理解将 H3 单元用于空间分析的细微差别,我们可以使用 Apache Sedona 对比 H3 单元与几何的性能。Sedona 提供了四个用于处理 H3 单元的函数: - -- `ST_H3CellDistance`:测量两个单元之间的单元距离(不是真实世界距离) -- `ST_H3CellIDs`:生成一组 H3 单元 ID,用于覆盖多边形或线串 -- `ST_H3KRing`:生成原点单元在网格距离 k 以内的"实心圆盘" -- `ST_H3ToGeom`:将一个 H3 单元转换为六边形几何 - -为演示一个真实世界的用例,我们将使用美国联邦应急管理署(FEMA)的国家洪水危险图层(NFHL)数据。它表示 FEMA 定义的洪水风险区域,具体使用德克萨斯州哈里斯县(休斯顿所在地)的数据。 - -由于该数据集中很多几何特别复杂且精度很高(从建筑许可到洪水保险等许多决策都基于这些数据),它是一个非常合适的探索用例。我们将测试限定在这一具体的洪水区多边形上: - -```py -# Pull the single area to show on the map - -area = sedona.sql(""" -select * -from fema -where FLD_AR_ID = '48201C_9129' -""") -``` - -![Single FEMA Flood Plain polygon near Houston Texas](../../image/blog/h3/image6.png){ align=center width="80%" } - -由于 Sedona 使用基于整数的单元标识符表示 H3,我们还将创建一个用户自定义函数,将其转换为基于字符串的 H3 单元。这是因为 SedonaKepler 接受字符串以代替几何,这正是使用 H3 的主要优势之一。 - -```py -# Create a UDF to convert the H3 integer into a string to skip geometry creation for mapping in SedonaKepler - -from pyspark.sql.functions import udf -from pyspark.sql.types import StringType -import h3 - - -# Define a Python function that wraps h3.int_to_str -def h3_int_to_str(x): - return h3.int_to_str(x) - - -# Register the function as a Spark UDF -h3_int_to_str_udf = udf(h3_int_to_str, StringType()) - -sedona.udf.register("h3_int_to_str", h3_int_to_str_udf) -``` - -## H3 单元的覆盖重叠 - -H3 单元的一个主要问题是,在将多边形转换为 H3 单元时,它们与多边形的重叠并不均匀。 -有两种方法可以做到: - -- Cover(覆盖):确保多边形的每一部分都至少被一个单元覆盖 -- Fill(填充):仅当单元的质心落在多边形内部时才加入该单元 - -下面我们在第 8 级使用 cover 方法测试: - -```py -h3_cells_8 = sedona.sql("""with a as ( -select -explode(ST_H3CellIDs(geometry, 8, true)) as hex_id -from fema -where FLD_AR_ID = '48201C_9129') - -select h3_int_to_str(hex_id) as hex_id from a -""") -``` - -![H3 cells at level 8 overlapping a FEMA Flood plain polygon near Houston Texas](../../image/blog/h3/image3.png){ align=center width="80%" } - -可以看到,在这一级别存在大量额外覆盖。让我们降到第 12 级,看看 fill 和 cover 两种方法的对比。 - -```py -# Test the same overlap but with H3 Size 12 - -h3_cells_12_cover = sedona.sql("""with a as ( -select -explode(ST_H3CellIDs(geometry, 12, true)) as hex_id -from fema -where FLD_AR_ID = '48201C_9129') -select h3_int_to_str(hex_id) as hex_id from a -""") - -h3_cells_12_fill = sedona.sql("""with a as ( -select -explode(ST_H3CellIDs(geometry, 12, false)) as hex_id -from fema -where FLD_AR_ID = '48201C_9129') -select h3_int_to_str(hex_id) as hex_id from a -""") -``` - -![Animated GIF showing the differences between the original FEMA flood polygon, H3 cells at level 8 and level 12, and buildings that fall in each of the different areas](../../image/blog/h3/image4.gif) -你可以看到这一级别已经接近精确覆盖了,但即便使用 fill 方法仍存在重叠和未覆盖的区域。 - -我们也可以计算覆盖重叠的具体数值。首先是第 8 级图层: - -```py -# Find the excess coverage area for H3 coverage level 8 - -h3_cells_missing_8 = sedona.sql("""with a as ( -select -explode(ST_H3ToGeom(ST_H3CellIDs(geometry, 8, true))) as h3_geom -from fema -where FLD_AR_ID = '48201C_9129') - -select -sum((st_area(st_intersection(fema.geometry, a.h3_geom))) / st_area(fema.geometry)) - 1 percent_missing -from a -join fema -on st_intersects(h3_geom, geometry) -where FLD_AR_ID = '48201C_9129' -""") - -h3_cells_missing_8.show() -``` - -```text -+--------------------+ -| percent_missing| -+--------------------+ -|2.220446049250313...| -+--------------------+ -``` - -这意味着覆盖面积比原始几何多了 2.2 倍。 - -我们可以对第 12 级 fill 图层做同样的计算,它表现稍好一些: - -```py -# Find the percent of missing area with H3 coverage level 12 - -h3_cells_missing = sedona.sql("""with a as ( -select -explode(ST_H3ToGeom(ST_H3CellIDs(geometry, 12, false))) as h3_geom -from fema -where FLD_AR_ID = '48201C_9129') - -select -1 - sum((st_area(st_intersection(fema.geometry, a.h3_geom))) / st_area(fema.geometry)) percent_missing -from a -join fema -on st_intersects(h3_geom, geometry) -where FLD_AR_ID = '48201C_9129' -""") - -h3_cells_missing.show() -``` - -```text -+--------------------+ -| percent_missing| -+--------------------+ -|0.021671094911437705| -+--------------------+ -``` - -这意味着覆盖缺失约 2.16%——也就是说,H3 单元未能覆盖原始多边形的 2.16%。数值不算太高,但在进行空间连接时,我们会看到它的影响相当显著。 - -## H3 在空间连接中的取舍 - -下面让我们对比原始几何、H3 第 8 级以及 H3 第 12 级两种方法在空间连接中的表现。我们将使用 Overture Maps Foundation 建筑物足迹数据集,统计与这些图层相交的建筑物数量。 - -首先看一个基线:将原始几何与建筑物连接: - -```py -# Compare a spatial join at H3 level 8 with Overture Map Buildings - -true_spatial_join = sedona.sql(""" -select count(overture.id) as buildings -from overture.buildings_building overture -join fema -on st_intersects(fema.geometry, overture.geometry) -where fema.FLD_AR_ID = '48201C_9129' -""") - -true_spatial_join.show() -``` - -```text -+---------+ -|buildings| -+---------+ -| 1412| -+---------+ -``` - -这意味着有 1,412 栋建筑物至少与洪水区多边形上的某一点相接触。 - -接着用第 8 级 H3 单元测试。注意,你必须先将单元转换为几何,然后使用 `ST_Union_Aggr` 聚合为单个多边形,以避免对建筑物的重复计数。 - -```py -# Compare a spatial join at H3 level 8 with Overture Map Buildings - -h3_cells_join_level_8 = sedona.sql("""with a as ( -select -explode( -ST_H3ToGeom( -ST_H3CellIDs(geometry, 8, true) -) -) as h3_geom -from fema -where FLD_AR_ID = '48201C_9129'), - -b as ( -select ST_Union_Aggr(h3_geom) as h3_geom from a -) -select count(overture.id) as buildings -from overture.buildings_building overture -join b -on st_intersects(b.h3_geom, overture.geometry) -""") - -h3_cells_join_level_8.show() -``` - -```text -+---------+ -|buildings| -+---------+ -| 5402| -+---------+ -``` - -这个结果远远多于真实分析中我们想要的。再用重叠最少的 fill 方法、在第 12 级 H3 单元下试试。 - -```py -h3_cells_join_level_12 = sedona.sql("""with a as ( -select -explode( -ST_H3ToGeom( -ST_H3CellIDs(geometry, 12, false) -) -) as h3_geom -from fema -where FLD_AR_ID = '48201C_9129'), -b as ( -select ST_Union_Aggr(h3_geom) as h3_geom from a -) -select count(overture.id) as buildings -from overture.buildings_building overture -join b -on st_intersects(b.h3_geom, overture.geometry) -""") - -h3_cells_join_level_12.show() -``` - -```text -# ... text output ... -+---------+ -|buildings| -+---------+ -| 1457| -+---------+ -``` - -更接近真实结果,但相比真实多边形仍多算了 45 栋。使用 Apache Sedona 处理大规模数据时,你既不需要额外步骤,也无需在精度上做妥协。 - -下面是三种图层对比的最终地图: - -![Final Map](../../image/blog/h3/image7.gif){ align=center width="80%" } - -## 分布式查询中的数据倾斜 - -处理大规模地理空间数据时,性能不仅取决于索引速度或几何近似,还在于工作如何在集群中均匀分布。 - -H3 将空间划分为均匀的六边形网格。这种方法虽然简单,但在真实数据集中并不奏效——真实数据几乎总是倾斜的:密集的城市区域可能包含数百万个要素,而乡村地区则极少。均匀分区会让某些分区过载,而另一些分区负载不足,导致负载不均衡和拖尾任务,从而拖慢整个作业。 - -需要注意的是,H3 从未被设计来解决这个问题。相比之下,Apache Sedona 使用 KDB-tree 等自适应空间分区器,根据数据密度划分空间。这确保了分区更均衡,减少热点,并在规模化时提升查询性能。 - -![Data skew using different spatial partitioning methods over the United States using bounding boxes](../../image/blog/h3/image1.png){ align=center width="80%" } - -更深入的内容可参考 Jia Yu 的 [GeoSpark(现 Sedona)论文](https://jiayuasu.github.io/files/paper/GeoSpark_Geoinformatica_2018.pdf),它说明了均匀网格在数据倾斜工作负载下相对自适应方法的不足。 - -## 结论 - -H3 通过近似计算可以加速一些空间操作,但它牺牲了精度并带来了额外的复杂性。 - -Apache Sedona 可扩展,并能精确执行空间计算,使用上更简单。 - -H3 对于某些类型的空间计算仍是不错的选择,但很多时候,直接使用 Apache Sedona 内建的精确计算更为简单。 diff --git a/docs/blog/posts/intro-sedonadb-0-2.zh.md b/docs/blog/posts/intro-sedonadb-0-2.zh.md deleted file mode 100644 index 52364f38adf..00000000000 --- a/docs/blog/posts/intro-sedonadb-0-2.zh.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -date: - created: 2025-12-01 -links: - - SedonaDB: https://sedona.apache.org/sedonadb/ -authors: - - dewey - - kristin - - feng - - peter - - jess - - jia - - matt_powers -title: "SedonaDB 0.2.0 版本发布" ---- - - - -Apache Sedona 社区激动地宣布 [SedonaDB](https://sedona.apache.org/sedonadb) 0.2.0 版本正式发布! - -SedonaDB 是首款将空间数据视为一等公民的开源单节点分析数据库引擎,作为 Apache Sedona 的子项目开发。本次版本包含 [136 个已解决的 issue](https://github.com/apache/sedona-db/milestone/1?closed=1),来自 17 位贡献者,并新增 40 个函数。 - -Apache Sedona 在 Spark(SedonaSpark)、Flink(SedonaFlink)和 Snowflake(SedonaSnow)等分布式引擎上提供大规模地理空间处理能力。SedonaDB 将 Sedona 生态系统扩展到单节点引擎,专为中小规模数据分析进行优化,提供分布式系统通常难以企及的简洁性与速度。 - - - -## 版本亮点 - -- 空间函数覆盖度提升 -- 支持 GDAL/OGR 空间文件格式读取 -- 支持 GeoParquet 1.1 写入 -- 支持 Python 用户自定义函数 -- 初步实现栅格数据类型 -- 发布到 `crates.io` -- 构建系统改进 - -完整的 0.2.0 相对 0.1.0 的变更列表请参见 [0.2.0 里程碑](https://github.com/apache/sedona-db/milestone/1?closed=1)。 - -## 空间函数覆盖度提升 - -自 0.1.0 发布以来,我们很荣幸与众多贡献者合作,为不断壮大的函数库新增了 40 个 `ST_` 和 `RS_` 函数。rs_height、rs_scalex、rs_scaley、rs_skewx、rs_skewy、rs_upperleftx、rs_upperlefty、rs_width、st_azimuth、st_boundary、st_crosses、st_dump、st_endpoint、st_geometryfromtext、st_geometryn、st_isclosed、st_iscollection、st_isring、st_issimple、st_isvalid、st_isvalidreason、st_makevalid、st_minimumclearance、st_minimumclearanceline、st_npoints、st_numgeometries、st_overlaps、st_pointn、st_points、st_polygonize、st_polygonize_agg、st_reverse、st_simplify、st_simplifypreservetopology、st_snap、st_startpoint、st_translate、st_unaryunion 和 st_zmflag 的用户将很高兴地看到这些函数现已可在 SedonaDB 工作流中使用。 - -感谢 [Abeeujah](https://github.com/Abeeujah)、[ayushjariyal](https://github.com/ayushjariyal)、[jesspav](https://github.com/jesspav)、[joonaspessi](https://github.com/joonaspessi)、[petern48](https://github.com/petern48) 和 [yutannihilation](https://github.com/yutannihilation) 的贡献!(特别感谢 [petern48](https://github.com/petern48),他几乎评审了所有这些函数!) - -## GDAL/OGR 空间文件格式读取支持 - -SedonaDB 0.1.0 发布时支持 GeoParquet 读取和与 GeoPandas 的互操作,但对 GeoPackage、Shapefile、FlatGeoBuf 等文件格式的支持继承自 GeoPandas 的局限(尤其是将整个图层在内存中物化为 Pandas DataFrame)。驱动 GeoPandas 读取支持的库 ([pyogrio](https://github.com/geopandas/pyogrio)) 同时也暴露了[底层提供方(GDAL/OGR)的原生 Arrow 接口](https://gdal.org/en/stable/development/rfc/rfc86_column_oriented_api.html),而这恰好正是 SedonaDB 在底层使用的格式!这让我们能够一次性接入大量矢量格式,并直接连入 DataFusion 灵活的 `FileFormat` API。现在用户可以像读取 Parquet 一样读取空间文件格式: - -```python -# pip install "apache-sedona[db]" -import sedona.db - -sd = sedona.db.connect() -url = "https://raw.githubusercontent.com/geoarrow/geoarrow-data/v0.2.0/example-crs/files/example-crs_vermont-utm.fgb" -sd.read_pyogrio(url).to_pandas().plot() -``` - -它支持本地文件、`https://` URL(通过 `/vsicurl/`)以及压缩文件(通过 `/vsizip/`)。匹配到 1 个或多个本地文件的 glob 模式(例如 `sd.read_pyogrio("path/to/*.gpkg")`)也同样受支持。 - -和 SedonaDB 的 GeoParquet 支持类似,`ST_Intersects()` 等空间过滤器会尽可能下推到 GDAL/OGR 扫描层,以利用 GeoPackage、FlatGeoBuf 和 Shapefile 等格式中嵌入的空间索引。例如,我们可以从[远端托管的大型 FlatGeoBuf 文件](https://flatgeobuf.org/examples/maplibre/large.html)中查询小区域,而无需扫描整个文件: - -```python -# 12 GB file -url = "https://flatgeobuf.septima.dk/population_areas.fgb" -sd.read_pyogrio(url).to_view("population_areas") - -wkt = "POLYGON ((-73.978329 40.767412, -73.950005 40.767412, -73.950005 40.795098, -73.978329 40.795098, -73.978329 40.767412))" -sd.sql(f""" -SELECT sum(population::INTEGER) FROM population_areas -WHERE ST_Intersects(wkb_geometry, ST_SetSRID(ST_GeomFromWKT('{wkt}'), 4326)) -""").show() -# > ┌──────────────────────────────────┐ -# > │ sum(population_areas.population) │ -# > │ int64 │ -# > ╞══════════════════════════════════╡ -# > │ 256251 │ -# > └──────────────────────────────────┘ -``` - -## GeoParquet 1.1 写入支持 - -SedonaDB 的首个版本提供了 GeoParquet 文件的基础写入能力,但当时尚不支持规范的最新版本——也就是允许读取器仅读取生成 Parquet 文件中一小部分内容的能力。在最新版本中,`DataFrame.to_parquet("path/to/parquet", geoparquet_version="1.1")` 会添加 `bbox` 列,使 `sd.read_parquet()` 配合 `WHERE ST_Intersects()` 查询时只读取输入文件的一部分。 - -```python -# pip install "apache-sedona[db]" -import sedona.db - -sd = sedona.db.connect() -url = "https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/ns-water_water-point.fgb" - -sd.read_pyogrio(url).to_parquet("water_point.parquet", geoparquet_version="1.1") -``` - -将默认行组大小缩小到 ~100,000 并进行空间排序可能会提升读取性能——这能确保行组内包含相关性较高的要素。 - -```python -sd.sql("SET datafusion.execution.parquet.max_row_group_size = 100000") - -sd.read_parquet(url).to_view("water_point") - -sd.sql(""" -SELECT * FROM water_point -ORDER BY sd_order(geometry) -""").to_parquet("water_point.parquet", geoparquet_version="1.1") -``` - -## Python 用户自定义函数支持 - -用户自定义函数(UDF)是 Spark、DataFusion、DuckDB 等现代 DataFrame 引擎中众多工作流的关键组件,用于承载难以通过组合现有函数实现的用户特定逻辑。和 [SedonaSpark 为几何类型提供向量化 UDF 框架](https://sedona.apache.org/latest/tutorial/sql/#spatial-vectorized-udfs-python-only)类似,SedonaDB 0.2.0 提供了一个框架,允许从 SQL 工作流中引用用户特定逻辑(包括但不限于涉及几何的!)。例如,可以这样实现 `ST_Buffer()` 的 UDF: - -```python -import pyarrow as pa -import sedona.db -from sedonadb import udf -import shapely -import geoarrow.pyarrow as ga - -sd = sedona.db.connect() - - -@udf.arrow_udf(ga.wkb(), [udf.GEOMETRY, udf.NUMERIC]) -def shapely_udf(geom, distance): - geom_wkb = pa.array(geom.storage.to_array()) - distance = pa.array(distance.to_array()) - geom = shapely.from_wkb(geom_wkb) - result_shapely = shapely.buffer(geom, distance) - return pa.array(shapely.to_wkb(result_shapely)) - - -sd.register_udf(shapely_udf) -sd.sql("SELECT shapely_udf(ST_Point(0, 0), 2.0) as col").show() -# > ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ -# > │ col │ -# > │ geometry │ -# > ╞══════════════════════════════════════════════════════════════════════════════════════════════════╡ -# > │ POLYGON((2 0,1.9615705608064609 -0.3901806440322565,1.8477590650225735 -0.7653668647301796,1.66… │ -# > └──────────────────────────────────────────────────────────────────────────────────────────────────┘ -``` - -更多示例和文档请参见 [`arrow_udf()` 的文档页](https://sedona.apache.org/sedonadb/latest/reference/python/#sedonadb.udf.arrow_udf)。 - -## 初步的栅格数据类型实现 - -[Sedona Spark 中的栅格数据类型支持](https://sedona.apache.org/latest/tutorial/raster) 是一项广受欢迎的特性,在 SedonaDB 0.2.0 中我们也很高兴地提供了栅格数据类型以及一些基础函数! - -```python -import sedona.db - -sd = sedona.db.connect() - -sd.sql("SELECT RS_Width(RS_Example()) as width").show() -# > ┌────────┐ -# > │ width │ -# > │ uint64 │ -# > ╞════════╡ -# > │ 64 │ -# > └────────┘ -``` - -更多信息或参与方式请参见[栅格支持的总览 issue](https://github.com/apache/sedona-db/issues/246)。感谢 [jesspav](https://github.com/jesspav) 推动这项工作! - -## 发布到 `crates.io` - -由于 SedonaDB 的早期开发与我们在 GeoRust 生态中所依赖的一些 crate 改进紧密相关,首个 SedonaDB 版本中含有 git 依赖和指向我们实验所用 fork 的引用。虽然首个版本*能够*通过 git 依赖在 Rust 项目中使用,但这阻碍了下游项目发布到 crates.io,也无法清晰地表明我们确实公开了可以与任何基于 DataFusion 的项目一起使用的 Rust 公共 API!Rust 项目可以使用我们提供的组件,也可以使用预先组装好的 `SedonaContext`。 - -SedonaDB 0.2.0 不仅[发布到了 crates.io](https://crates.io/crates/sedona),还附带[一个 Rust 示例](https://github.com/apache/sedona-db/tree/main/examples/sedonadb-rust),帮助有兴趣的 Rust 项目快速上手: - -```toml -[package] -# ... - -[dependencies] -datafusion = { version = "50.2.0"} -sedona = { version = "0.2.0" } -# ... -``` - -```rust -use datafusion::{common::Result, prelude::*}; -use sedona::context::{SedonaContext, SedonaDataFrame}; - -#[tokio::main] -async fn main() -> Result<()> { - let ctx = SedonaContext::new_local_interactive().await?; - let url = "https://raw.githubusercontent.com/geoarrow/geoarrow-data/v0.2.0/natural-earth/files/natural-earth_cities_geo.parquet"; - let df = ctx.read_parquet(url, Default::default()).await?; - let output = df - .sort_by(vec![col("name")])? - .show_sedona(&ctx, Some(5), Default::default()) - .await?; - println!("{output}"); - Ok(()) -} -``` - -我们仍在了解下游项目是否对使用 Rust 组件感兴趣,所以欢迎你[创建 issue](https://github.com/apache/sedona-db/issues/new) 提出想法或问题。 - -特别感谢 [kylebarron](https://github.com/kylebarron) 在我们向上游 [geo-index](https://github.com/kylebarron/geo-index) 和 [wkb](https://github.com/georust/wkb) crate 提交 PR 时进行的评审! - -## 构建系统改进 - -构建系统的改进是 SedonaDB 0.2.0 版本中最不显眼但又最重要的工作之一;不过,我们很高兴能够发布支持 PROJ、GEOS 和 S2Geography 的 SedonaDB 0.2.0 Python 二进制(覆盖 MacOS、Windows 和 Linux,Python 3.9 到 3.14,包括 Python 3.13 和 3.14 的 free-threaded 变体)。[SedonaDB 0.2.0 的 R 版本可通过 R-Universe 安装](https://apache.r-universe.dev/sedonadb),现在 MacOS 和 Windows 用户都有预编译二进制可用。Windows 上的 PROJ 支持以及所有 R 平台上的 S2Geography 支持仍在推进中,有望在下一个版本中加入。 - -特别感谢 [yutannihilation](https://github.com/yutannihilation) 为 R 构建系统贡献了修复! - -## 贡献者 - -本次版本来自 17 位贡献者的工作。感谢你们的贡献! - -``` -git shortlog -sn apache-sedona-db-0.2.0.dev..apache-sedona-db-0.2.0 - 25 Dewey Dunnington - 16 Abeeujah - 13 Hiroaki Yutani - 13 Peter Nguyen - 12 Kristin Cowalcijk - 8 jp - 6 Feng Zhang - 4 Joonas Pessi - 3 Matthew Powers - 2 Cancai Cai - 2 Jia Yu - 1 L_Sowmya - 1 Liang Geng - 1 Peter Von der Porten - 1 Yongting You - 1 ayushjariyal - 1 dentiny -``` diff --git a/docs/blog/posts/intro-sedonadb-0-3.zh.md b/docs/blog/posts/intro-sedonadb-0-3.zh.md deleted file mode 100644 index 338e51cf22e..00000000000 --- a/docs/blog/posts/intro-sedonadb-0-3.zh.md +++ /dev/null @@ -1,480 +0,0 @@ ---- -date: - created: 2026-03-01 -links: - - SedonaDB: https://sedona.apache.org/sedonadb/ -authors: - - dewey - - kristin - - feng - - peter - - jia - - pranav -title: "SedonaDB 0.3.0 版本发布" ---- - - - -Apache Sedona 社区激动地宣布 -[SedonaDB](https://sedona.apache.org/sedonadb) 0.3.0 版本正式发布! - -SedonaDB 是首款将空间数据视为一等公民的开源单节点分析数据库引擎,作为 -Apache Sedona 的子项目开发。本次版本包含 -[187 个已解决的 issue](https://github.com/apache/sedona-db/milestone/2?closed=1), -来自 18 位贡献者,并新增 36 个函数。 - -Apache Sedona 在 Spark(SedonaSpark)、Flink(SedonaFlink)和 Snowflake -(SedonaSnow)等分布式引擎上提供大规模地理空间处理能力。SedonaDB 将 Sedona -生态系统扩展到单节点引擎,专为中小规模数据分析进行优化,提供分布式系统 -通常难以企及的简洁性与速度。 - - - -## 版本亮点 - -本次版本中有许多值得一提的亮点! - -- 超出内存大小的空间连接 -- 行级/单要素级 CRS 支持 -- 参数化 SQL 查询 -- Parquet 读取器和写入器改进 -- 支持 GDAL/OGR 写入 -- 空间函数覆盖度与文档改进 -- R DataFrame API - -```python -# pip install "apache-sedona[db]" -import sedona.db - -sd = sedona.db.connect() -sd.options.interactive = True -``` - -## 超出内存大小的空间连接 - -四个月前发布的首个 SedonaDB 版本就已经包含了一个极为灵活且高性能的空间 -连接。简单回顾一下,空间连接根据某种空间关系(例如相交或距离范围内) -找出两张表之间的交互。例如,如果你打算搬家并且喜欢披萨,SedonaDB 几乎能 -在你的网络/硬盘加载 [Overture Maps -Divisions](https://docs.overturemaps.org/guides/divisions/) 和 -[Places](https://docs.overturemaps.org/guides/places/) 数据的速度下, -为你找出披萨店最多的街区。 - -```python -overture = "s3://overturemaps-us-west-2/release/2026-02-18.0" -options = {"aws.skip_signature": True, "aws.region": "us-west-2"} -sd.read_parquet(f"{overture}/theme=places/type=place/", options=options).to_view( - "places" -) -sd.read_parquet( - f"{overture}/theme=divisions/type=division_area/", options=options -).to_view("divisions") - -sd.sql(""" - SELECT - get_field(names, 'primary') AS name, - geometry - FROM places - WHERE get_field(categories, 'primary') = 'pizza_restaurant' -""").to_view("pizza_restaurants") - -sd.sql(""" - SELECT divisions.id, get_field(divisions.names, 'primary') AS name, COUNT(*) AS n - FROM divisions - INNER JOIN pizza_restaurants ON ST_Contains(divisions.geometry, pizza_restaurants.geometry) - WHERE divisions.subtype = 'neighborhood' - GROUP BY divisions.id, get_field(divisions.names, 'primary') - ORDER BY n DESC -""").limit(5) -# > ┌──────────────────────────────────────┬────────────────────────────┬───────┐ -# > │ id ┆ name ┆ n │ -# > │ utf8 ┆ utf8 ┆ int64 │ -# > ╞══════════════════════════════════════╪════════════════════════════╪═══════╡ -# > │ 1373e423-efdc-471a-ae51-3e9d6c4231b2 ┆ UPZs de Bogotá ┆ 860 │ -# > ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤ -# > │ 74e87dad-3b11-4fc5-bedd-cb51a617e141 ┆ Distrito-sede de Guarulhos ┆ 388 │ -# > ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤ -# > │ 76077e78-c0c0-48f4-9683-1a16a56dfaf9 ┆ Roma I ┆ 346 │ -# > ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤ -# > │ ee274c81-5b62-4e80-be67-e613b9219b17 ┆ Roma VII ┆ 289 │ -# > ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤ -# > │ bf762f01-cad0-46be-b47b-76435478035c ┆ Birmingham ┆ 263 │ -# > └──────────────────────────────────────┴────────────────────────────┴───────┘ -``` - -这个连接算法大致流程是:(1) 将空间连接其中一侧的所有批次读入内存, -(2) 在内存中构建空间索引,(3) 对连接另一侧的每个批次并行查询索引, -并以流式方式返回结果。大多数空间 DataFrame 库的连接都是这么做的; -所不同的是,SedonaDB 还能利用 DataFusion 的并行流式执行基础设施,以及 -Apache Arrow 对几何数组的紧凑表示,高效执行普通用户能想到的大多数连接。 - -虽然初版的连接已经足够快,但它有一个硬性上限:构建侧未压缩的所有批次 -以及空间索引必须能放入内存。对于一台中端、内存 16 GB 的笔记本而言, -这大约相当于 2 亿个点(还没算非几何列所需的空间)——对大多数分析够用, -但对于大数据集或资源受限的环境(如容器或云端部署)则不够。 - -此外,我们还有一项独特的资源可用:Apache Sedona 社区!Sedona Spark -已经能进行分布式空间连接多年,其作者(Jia Yu、Kristin Cowalcijk 和 -Feng Zhang)都是活跃的 SedonaDB 贡献者。在 0.2 发布之后我们提出了一个 -问题:能否将 Sedona Spark 空间连接背后的思想搬过来,让 SedonaDB 可以 -连接……*任何东西*? - -最终的成果是几乎重写过的空间连接实现(包括 k 近邻或 KNN 连接), -让 SedonaDB 用户真正可以无所畏惧:只要你能写出查询,SedonaDB 就能完成任务。 -这是一项足够复杂的特性,后续会有专门的博客文章介绍。这里先给一个简单 -示例:SedonaDB 可以在仅 3 GB 内存的情况下,大约 30 秒内识别出一个 -包含 1.3 亿个点的数据集中重复(或近似重复)的点! - -```python -import sedona.db - -sd = sedona.db.connect() -sd.options.memory_limit = "3g" -sd.options.memory_pool_type = "fair" - -url = "https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/microsoft-buildings_point.parquet" -sd.read_parquet(url).to_view("buildings") -sd.sql(""" - SELECT ROW_NUMBER() OVER () AS building_id, geometry - FROM buildings - """).to_parquet("buildings_idx.parquet") - -sd.sql(""" - SELECT - l.building_id, - r.building_id AS nearest_building_id, - l.geometry AS geometry, - ST_Distance(l.geometry, r.geometry) AS dist - FROM "buildings_idx.parquet" AS l - JOIN "buildings_idx.parquet" AS r - ON l.building_id <> r.building_id - AND ST_DWithin(l.geometry, r.geometry, 1e-10) - """).to_memtable().to_view("duplicates", overwrite=True) - -sd.view("duplicates").count() -# > 1017476 -``` - -作为参考,DuckDB 1.5.0(beta)需要约 12 GB 内存才能完成同样的工作, -GeoPandas 至少需要 28 GB。 - -对于这件事所付出的工作量,光说"感谢"完全不够,我们只能告诉你 -[@Kontinuation](https://github.com/Kontinuation) 几乎独自设计并实现了 -这一切,希望你能给予他应有的钦佩。 - -## 行级/单要素级 CRS 支持 - -自首个版本起,SedonaDB 就遵循 GeoParquet、GeoPandas 等空间 DataFrame -库的惯例支持坐标参考系统(CRS):每一列可以可选地声明一个 CRS, -说明每个值的坐标如何与地球表面对应[^1]。这样做的好处在于将 CRS 的考量 -摊销到每个查询或每个批次一次;然而,这种模式让 SedonaDB 在与 PostGIS、 -Sedona Spark 等允许每个要素声明自己 CRS 的系统交互时显得别扭。 -单要素级 CRS 也有其他用处,例如在基于米的本地 CRS 中执行计算,或从 -多个来源读入数据后再让 SedonaDB 将它们转换到统一的 CRS。 - -单要素级 CRS 支持是在 SedonaDB 0.2 中加入的,但在 0.3 版本中我们将其 -贯穿整个技术栈,使得这种类型的列在所有函数中都能正常工作。例如, -如果你想把某些输入转换到本地 CRS,SedonaDB 可以做到: - -```python -import pandas as pd - -cities = pd.DataFrame( - { - "name": ["Ottawa", "Vancouver", "Toronto"], - "utm_code": ["EPSG:32618", "EPSG:32610", "EPSG:32617"], - "srid": [32618, 32610, 32617], - "longitude": [-75.7019612, -123.1235901, -79.38945855491194], - "latitude": [45.4186427, 49.2753624, 43.66464454743429], - } -) - -sd.create_data_frame(cities).to_view("cities", overwrite=True) -sd.sql(""" - SELECT - name, - ST_Transform( - ST_Point(longitude, latitude, 4326), - utm_code - ) AS geom - FROM cities - """).to_view("cities_item_crs") - -sd.view("cities_item_crs") -``` - - ┌───────────┬───────────────────────────────────────────────────────────────────────┐ - │ name ┆ geom │ - │ utf8 ┆ struct │ - ╞═══════════╪═══════════════════════════════════════════════════════════════════════╡ - │ Ottawa ┆ {item: POINT(445079.1082827766 5029697.641232558), crs: EPSG:32618} │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Vancouver ┆ {item: POINT(491010.24691805616 5458074.5942144925), crs: EPSG:32610} │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Toronto ┆ {item: POINT(629849.6255738225 4835886.955149793), crs: EPSG:32617} │ - └───────────┴───────────────────────────────────────────────────────────────────────┘ - -最关键的是,它还能转换回来! - -```python -sd.sql("SELECT name, ST_Transform(geom, 4326) FROM cities_item_crs") -``` - - ┌───────────┬────────────────────────────────────────────────┐ - │ name ┆ st_transform(cities_item_crs.geom,Int64(4326)) │ - │ utf8 ┆ geometry │ - ╞═══════════╪════════════════════════════════════════════════╡ - │ Ottawa ┆ POINT(-75.7019612 45.4186427) │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Vancouver ┆ POINT(-123.1235901 49.275362399999985) │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Toronto ┆ POINT(-79.38945855491194 43.664644547434285) │ - └───────────┴────────────────────────────────────────────────┘ - -创建单要素级 CRS 的主要途径是 `ST_Transform()` 和 -`ST_SetSRID()`,以及 `ST_Point()`、`ST_GeomFromWKT()`、`ST_GeomFromWKB()` -的最后一个参数。新增的 `ST_GeomFromEWKT()` 和 `ST_GeomFromEWKB()` -始终返回单要素级 CRS(并且在通过 `ST_AsEWKT()` 和 `ST_AsEWKB()` 导出时 -SRID 会被保留)。单要素级 CRS 应当在所有其他函数调用中得到保留 -(就像之前版本中类型级 CRS 那样)。我们很期待听到更多用例,以确保 -我们的支持覆盖到真实世界中各种使用场景! - -## 参数化查询 - -虽然 SedonaDB 的 Python 绑定提供了有限的 DataFrame API,但到目前为止与 -SedonaDB 进行实质性交互的主要方式仍是 SQL。SQL 受到 LLM 良好支持, -也让我们能利用 DataFusion 出色的 SQL 解析器;但在编程环境中与 SedonaDB -交互就会变得别扭,完全依赖将查询参数序列化到 SQL 中。 - -在 SedonaDB 0.3.0 中,我们在 R 和 Python 中添加了参数化查询支持,并为 -大多数类几何对象和/或类 CRS 对象提供了转换器。例如,如果你的上下文 -信息是一个 GeoPandas DataFrame,你现在可以将它插入到任何 SQL 查询中, -而无需担心设置 CRS 或将其序列化为 SQL。 - -```python -import geopandas - -url_countries = "https://raw.githubusercontent.com/geoarrow/geoarrow-data/v0.2.0/natural-earth/files/natural-earth_countries.fgb" -countries = geopandas.read_file(url_countries) -canada = countries.geometry[countries.name == "Canada"] - -url_cities = "https://raw.githubusercontent.com/geoarrow/geoarrow-data/v0.2.0/natural-earth/files/natural-earth_cities.fgb" -sd.read_pyogrio(url_cities).to_view("cities", overwrite=True) -sd.sql("SELECT * FROM cities WHERE ST_Contains($1, wkb_geometry)", params=(canada,)) -``` - - ┌───────────┬─────────────────────────────────────────────┐ - │ name ┆ wkb_geometry │ - │ utf8 ┆ geometry │ - ╞═══════════╪═════════════════════════════════════════════╡ - │ Ottawa ┆ POINT(-75.7019612 45.4186427) │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Vancouver ┆ POINT(-123.1235901 49.2753624) │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Toronto ┆ POINT(-79.38945855491194 43.66464454743429) │ - └───────────┴─────────────────────────────────────────────┘ - -## Parquet 改进 - -SedonaDB 首次发布时,Parquet 的 Geometry 和 Geography 类型才[刚被加入 -Parquet 规范](https://cloudnativegeo.org/blog/2025/02/geoparquet-2.0-going-native/),生态中的支持也很有限。 -从 SedonaDB 0.3.0 起,定义了几何和地理列的 Parquet 文件已可开箱即用! -这意味着 SedonaDB 不再需要额外的(bbox)列和 GeoParquet 元数据就能 -高效查询一个 Parquet 文件:内置的几何和地理类型自带嵌入式统计信息, -许多应用都能因此减小文件体积。 - -```python -# A Parquet file with no GeoParquet metadata -url = "https://raw.githubusercontent.com/geoarrow/geoarrow-data/v0.2.0/natural-earth/files/natural-earth_countries.parquet" -sd.read_parquet(url).schema -``` - - SedonaSchema with 3 fields: - name: utf8 - continent: utf8 - geometry: geometry - -虽然 SedonaDB 现在可以读取此类文件,但还不能写出它们(请期待 0.4.0!)。 -有关 Parquet 中空间类型的更多内容,请参见 [Parquet 博客上最近的深入文章](https://parquet.apache.org/blog/2026/02/13/native-geospatial-types-in-apache-parquet/)。 - -除了读取改进,我们还改进了 Parquet 的写入接口。具体来说是写出对 SedonaDB、 -GeoPandas 以及其他引擎的空间过滤下推都更友好的 GeoParquet 1.1 文件。 -搭配 SedonaDB 的 GDAL/OGR 读取支持,可以从几乎任何来源生成优化的 -GeoParquet 文件: - -```python -url = "https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/ns-water_water-point.fgb" -sd.read_pyogrio(url).to_parquet( - "optimized.parquet", - geoparquet_version="1.1", - max_row_group_size=100_000, - sort_by="wkb_geometry", -) -``` - -## GDAL/OGR 写入支持 - -在 SedonaDB 0.2.0 中,我们借助出色的 GDAL/OGR 以及 [pyogrio -包](https://github.com/geopandas/pyogrio) 的 Arrow 接口,增加了对 -Shapefile、FlatGeoBuf、GeoPackage 等多种空间文件格式的读取支持。 -在 0.3.0 中,我们又加入了写入支持!由于读取和写入都是流式的, -这让 SedonaDB 成为任意规模输入下读取/写入/转换工作流的优秀选择。 - -```python -sd.read_parquet("optimized.parquet").to_pyogrio("water-point.fgb") -``` - -## 空间函数覆盖度提升 - -自 0.2.0 发布以来,我们很荣幸与众多贡献者合作,为不断壮大的函数库 -新增了 36 个 `ST_` 和 `RS_` 函数。rs_bandpath、rs_convexhull、rs_crs、 -rs_envelope、rs_georeference、rs_numbands、rs_rastertoworldcoord、 -rs_rastertoworldcoordx、rs_rastertoworldcoordy、rs_rotation、rs_setcrs、 -rs_setsrid、rs_srid、rs_worldtorastercoord、rs_worldtorastercoordx、 -rs_worldtorastercoordy、st_affine、st_asewkb、st_asgeojson、st_concavehull、 -st_force2d、st_force3d、st_force3dm、st_force4d、st_geomfromewkb、 -st_geomfromewkt、st_geomfromwkbunchecked、st_interiorringn、st_linemerge、 -st_nrings、st_numinteriorrings、st_numpoints、st_rotate、st_rotatex、 -st_rotatey 和 st_scale 的用户将很高兴地看到这些函数现已可在 SedonaDB -工作流中使用。这使 `ST_` 和 `RS_` 函数总数达到 143 个,并尽可能在 -完整的几何类型矩阵以及 XY、XYZ、XYM、XYZM 维度下与 PostGIS 进行了 -兼容性测试。 - -在新增这些函数的过程中,我们发现返回大几何的 GEOS 函数(例如带参数的 -`ST_Buffer()` 或 `ST_Union()`)可以快得多。在 0.3.0 中,我们改进了 -将大型 GEOS 几何追加到输出数组的执行流程,使部分基准测试的性能 -提升了多达 70%! - -感谢 [2010YOUY01](https://github.com/2010YOUY01)、 -[Abeeujah](https://github.com/Abeeujah)、 -[jesspav](https://github.com/jesspav)、 -[Kontinuation](https://github.com/Kontinuation)、 -[petern48](https://github.com/petern48)、 -[prantogg](https://github.com/prantogg) 和 -[yutannihilation](https://github.com/yutannihilation) 的贡献! - -## R 端 GDAL/OGR 读取支持 - -SedonaDB R 版的早期测试者对其潜力表示兴奋,但指出要让它真正可用, -就需要在不经过 sf DataFrame 的情况下读取 Shapefile、FlatGeoBuf、 -GeoPackage 等空间格式。在 SedonaDB 0.3.0 中,我们新增了 `sd_read_sf()`, -它利用 sf 包中实验性提供的 GDAL ArrowArrayStream 特性。 - -```r -library(sedonadb) - -# Works with files -fgb <- system.file("files/natural-earth_cities.fgb", package = "sedonadb") -sd_read_sf(fgb) -``` - - - ┌────────────┬─────────────────────────────────────────────┐ - │ name ┆ wkb_geometry │ - │ utf8 ┆ geometry │ - ╞════════════╪═════════════════════════════════════════════╡ - │ Wellington ┆ POINT(174.77720094690068 -41.2920679923151) │ - ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Auckland ┆ POINT(174.763027 -36.8480549) │ - ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Melbourne ┆ POINT(144.9730704 -37.8180855) │ - ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Canberra ┆ POINT(149.1290262 -35.2830285) │ - ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Sydney ┆ POINT(151.2125477744749 -33.87137339218338) │ - ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ Suva ┆ POINT(178.4417073 -18.1330159) │ - └────────────┴─────────────────────────────────────────────┘ - Preview of up to 6 row(s) - -R 端的 GDAL/OGR 支持比 Python 端更有限(例如,SQL 过滤不会自动下推到 -数据源,必须在读取调用中显式指定);不过,它有望减少一些摩擦,并为 -更多用例解锁 R 支持! - -## R DataFrame API - -最后但同样重要的是,我们为 SedonaDB 加入了 [dplyr](https://dplyr.tidyverse.org) -后端的基础构件,包括基础的表达式翻译以及对 r-spatial 生态中 -大多数类几何对象的支持。这个 API 处于实验阶段,欢迎反馈! - -```r -library(sedonadb) - -url <- "https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/ns-water_water-point.parquet" -df <- sd_read_parquet(url) - -df |> - sd_group_by(FEAT_CODE) |> - sd_summarise( - n = n(), - geometry = st_collect_agg(geometry) - ) |> - sd_arrange(desc(n)) -``` - - - ┌───────────┬───────┬──────────────────────────────────────────────────────────┐ - │ FEAT_CODE ┆ n ┆ geometry │ - │ utf8 ┆ int64 ┆ geometry │ - ╞═══════════╪═══════╪══════════════════════════════════════════════════════════╡ - │ WARK60 ┆ 44406 ┆ MULTIPOINT Z((534300.0192 4986233.3817 82.3999999999941… │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ WARA60 ┆ 193 ┆ MULTIPOINT Z((291828.02660000045 4851581.779999999 18.3… │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ WAFA60 ┆ 90 ┆ MULTIPOINT Z((552953.3191 4974220.8818 0.95799999999871… │ - ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ - │ WADM60 ┆ 1 ┆ MULTIPOINT Z((421205.36319999956 4983412.968 22.8999999… │ - └───────────┴───────┴──────────────────────────────────────────────────────────┘ - Preview of up to 6 row(s) - -感谢 [e-kotov](https://github.com/e-kotov) 提出的详细需求和驱动用例! - -## 下一步计划 - -在 0.3.0 开发周期中,我们合并了两项激动人心的贡献的初步成果: -基于 GPU 加速的空间连接,以及对 LAS 和 LAZ 点云格式的读取支持。 -在 0.4.0 中,我们希望将这些组件打磨完善并正式上线!此外,我们还希望 -扩展引擎对 Geography 数据类型的全面支持,并加速其当前实现。 - -## 贡献者 - -```shell -git shortlog -sn apache-sedona-db-0.3.0.dev..apache-sedona-db-0.3.0 - 50 Dewey Dunnington - 45 Kristin Cowalcijk - 19 Hiroaki Yutani - 6 Balthasar Teuscher - 5 jp - 4 Peter Nguyen - 4 Yongting You - 3 Feng Zhang - 3 Liang Geng - 2 Abeeujah - 2 Matthew Powers - 1 Isaac Corley - 1 Jia Yu - 1 John Bampton - 1 Mayank Aggarwal - 1 Mehak3010 - 1 Pranav Toggi - 1 eitsupi -``` - -[^1]: 不太常见,但 SedonaDB 也支持非地球 CRS。 diff --git a/docs/blog/posts/intro-sedonadb.zh.md b/docs/blog/posts/intro-sedonadb.zh.md deleted file mode 100644 index 0a9e57990e6..00000000000 --- a/docs/blog/posts/intro-sedonadb.zh.md +++ /dev/null @@ -1,409 +0,0 @@ ---- -date: - created: 2025-09-24 -links: - - SedonaDB: https://sedona.apache.org/sedonadb/ - - SpatialBench: https://sedona.apache.org/spatialbench/ -authors: - - dewey - - kristin - - feng - - peter - - jess - - pranav - - james - - jia - - matt_powers - - kelly -title: "SedonaDB 正式发布:以地理空间为一等公民的单节点分析数据库引擎" ---- - - - -Apache Sedona 社区激动地宣布 [SedonaDB](https://sedona.apache.org/sedonadb) 首个版本正式发布! 🎉 - -SedonaDB 是首款将空间数据视为一等公民的开源单节点分析数据库引擎,作为 Apache Sedona 的子项目开发。 - -Apache Sedona 在 Spark(SedonaSpark)、Flink(SedonaFlink)和 Snowflake(SedonaSnow)等分布式引擎上提供大规模地理空间处理能力。SedonaDB 将 Sedona 生态系统扩展到单节点引擎,专为中小规模数据分析进行优化,提供分布式系统通常难以企及的简洁性与速度。 - - - -## 🤔 什么是 SedonaDB - -SedonaDB 使用 Rust 编写,轻量、极速,并且原生支持空间数据。开箱即用,它提供: - -* 🗺️ 在业界标准查询操作之上,全面支持空间类型、连接、CRS(坐标参考系统)和函数。 -* ⚡ 查询优化、索引以及数据裁剪等底层特性,让空间操作天然高性能。 -* 🐍 开发者熟悉的 Python 和 SQL 接口,并提供 R 与 Rust 的 API。 -* ☁️ 可在单机环境下灵活运行于本地文件或数据湖之上。 - -SedonaDB 基于 Apache Arrow 和 Apache DataFusion 构建,具备现代向量化查询引擎所需的一切。它的独特之处在于能够原生处理空间负载,无需扩展或插件。安装非常简单,SedonaDB 可轻松集成到本地开发与云端管道中,提供跨环境一致的使用体验。 - -SedonaDB 的首个版本提供了一整套几何向量操作能力,并能与 GeoArrow、GeoParquet 和 GeoPandas 无缝集成。未来版本将支持所有流行的空间函数,包括针对栅格数据的函数。 - -## 🚀 SedonaDB 快速上手示例 - -首先安装 SedonaDB: - -``` -pip install "apache-sedona[db]" -``` - -然后实例化连接: - -```python -import sedona.db - -sd = sedona.db.connect() -``` - -让我们用 SedonaDB 执行一次空间连接。 - -假设你有一张 `cities` 表,其中包含表示每个城市中心的经纬度点,还有一张 `countries` 表,其中一列包含该国家地理边界的多边形。 - -下面是 `cities` 表中的几行数据: - -``` -┌──────────────┬───────────────────────────────┐ -│ name ┆ geometry │ -│ utf8view ┆ geometry │ -╞══════════════╪═══════════════════════════════╡ -│ Vatican City ┆ POINT(12.4533865 41.9032822) │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ San Marino ┆ POINT(12.4417702 43.9360958) │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Vaduz ┆ POINT(9.5166695 47.1337238) │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -``` - -下面是 `countries` 表中的几行数据: - -``` -┌─────────────────────────────┬───────────────┬────────────────────────────────────────────────────┐ -│ name ┆ continent ┆ geometry │ -│ utf8view ┆ utf8view ┆ geometry │ -╞═════════════════════════════╪═══════════════╪════════════════════════════════════════════════════╡ -│ Fiji ┆ Oceania ┆ MULTIPOLYGON(((180 -16.067132663642447,180 -16.55… │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ United Republic of Tanzania ┆ Africa ┆ POLYGON((33.90371119710453 -0.9500000000000001,34… │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Western Sahara ┆ Africa ┆ POLYGON((-8.665589565454809 27.656425889592356,-8… │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -``` - -下面演示如何用一次空间连接计算每个城市所属的国家: - -```python -sd.sql(""" -select - cities.name as city_name, - countries.name as country_name, - continent -from cities -join countries -where ST_Intersects(cities.geometry, countries.geometry) -""").show(3) -``` - -代码使用 `ST_Intersects` 来判断一个城市是否位于某个国家境内。 - -下面是查询的结果: - -``` -┌───────────────┬─────────────────────────────┬───────────┐ -│ city_name ┆ country_name ┆ continent │ -│ utf8view ┆ utf8view ┆ utf8view │ -╞═══════════════╪═════════════════════════════╪═══════════╡ -│ Suva ┆ Fiji ┆ Oceania │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ -│ Dodoma ┆ United Republic of Tanzania ┆ Africa │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ -│ Dar es Salaam ┆ United Republic of Tanzania ┆ Africa │ -└───────────────┴─────────────────────────────┴───────────┘ -``` - -上述示例进行了一次点在多边形内(point-in-polygon)的连接,将城市位置(点)映射到它们所属的国家(多边形)。SedonaDB 利用空间索引(在合适的场景下)并基于输入数据样本在运行时动态调整连接策略,从而高效执行这些连接。许多通用引擎在这类操作的性能上表现挣扎,而 SedonaDB 是为空间负载量身打造的,始终能提供稳定的高性能结果。 - -## 📊 Apache Sedona SpatialBench - -为了检验我们在 SedonaDB 上的工作,我们还需要一种机制来评估其性能与速度。为此我们开发了 Apache Sedona SpatialBench——一套用于评估数据库系统地理空间 SQL 分析查询性能的基准测试。 - -让我们对比 SedonaDB 与 GeoPandas、DuckDB Spatial 在几条典型空间查询上的表现,这些查询定义于 [SpatialBench](https://sedona.apache.org/spatialbench/)。 - -下面是 SpatialBench v0.1 在 Queries 1–12、规模因子 1(SF1)和规模因子 10(SF10)下的结果。 - -![Scale Factor 1 benchmark results](../../image/blog/sedonadb1/sf1-09242025.png){ width="400" } -![Scale Factor 10 benchmark results](../../image/blog/sedonadb1/sf10-09242025.png){ width="400" } -{: .grid } - -SedonaDB 在各种查询类型上都有均衡的表现,并能有效扩展到 SF 10。DuckDB 在空间过滤和某些几何操作上表现出色,但在复杂连接和 KNN 查询上面临挑战。GeoPandas 虽然在 Python 生态中流行,但要有效处理更大数据集需要手工优化和并行化。完整的性能分析请参见 [SpatialBench 网站](https://sedona.apache.org/spatialbench/single-node-benchmarks/)。 - -下面是 SpatialBench Query #8 的示例,它在 SedonaDB 和 DuckDB 上都可运行: - -```sql -SELECT b.b_buildingkey, b.b_name, COUNT(*) AS nearby_pickup_count -FROM trip t JOIN building b ON ST_DWithin(ST_GeomFromWKB(t.t_pickuploc), ST_GeomFromWKB(b.b_boundary), 0.0045) -- ~500m -GROUP BY b.b_buildingkey, b.b_name -ORDER BY nearby_pickup_count DESC -``` - -这个查询特意在点和多边形之间执行了一次基于距离的空间连接,然后对结果进行聚合。 - -下面是查询的输出: - -``` -┌───────────────┬──────────┬─────────────────────┐ -│ b_buildingkey ┆ b_name ┆ nearby_pickup_count │ -│ int64 ┆ utf8view ┆ int64 │ -╞═══════════════╪══════════╪═════════════════════╡ -│ 3779 ┆ linen ┆ 42 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 19135 ┆ misty ┆ 36 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 4416 ┆ sienna ┆ 26 │ -└───────────────┴──────────┴─────────────────────┘ -``` - -下面是等效的 GeoPandas 代码: - -```python -trips_df = pd.read_parquet(data_paths["trip"]) -trips_df["pickup_geom"] = gpd.GeoSeries.from_wkb( - trips_df["t_pickuploc"], crs="EPSG:4326" -) -pickups_gdf = gpd.GeoDataFrame(trips_df, geometry="pickup_geom", crs="EPSG:4326") - -buildings_df = pd.read_parquet(data_paths["building"]) -buildings_df["boundary_geom"] = gpd.GeoSeries.from_wkb( - buildings_df["b_boundary"], crs="EPSG:4326" -) -buildings_gdf = gpd.GeoDataFrame( - buildings_df, geometry="boundary_geom", crs="EPSG:4326" -) - -threshold = 0.0045 # degrees (~500m) -result = ( - buildings_gdf.sjoin(pickups_gdf, predicate="dwithin", distance=threshold) - .groupby(["b_buildingkey", "b_name"], as_index=False) - .size() - .rename(columns={"size": "nearby_pickup_count"}) - .sort_values(["nearby_pickup_count", "b_buildingkey"], ascending=[False, True]) - .reset_index(drop=True) -) -``` - -## 🗺️ SedonaDB 的 CRS 管理 - -SedonaDB 在读写文件以及处理 DataFrame 时都会管理 CRS,让你的管道更安全,减少手动工作。 - -让我们计算佛蒙特州的建筑数量,以展示 SedonaDB 内置的 CRS 管理特性。 - -先用 GeoPandas 读取一个使用 EPSG 32618 CRS 的 FlatGeobuf 文件,然后将其转换为 SedonaDB DataFrame: - -```python -import geopandas as gpd - -path = "https://raw.githubusercontent.com/geoarrow/geoarrow-data/v0.2.0/example-crs/files/example-crs_vermont-utm.fgb" -gdf = gpd.read_file(path) -vermont = sd.create_data_frame(gdf) -``` - -让我们检查 `vermont` DataFrame 的 schema: - -``` -vermont.schema - -SedonaSchema with 1 field: - geometry: wkb -``` - -可以看到 `vermont` DataFrame 保留了 FlatGeobuf 文件中指定的 CRS。SedonaDB 还没有原生的 FlatGeobuf 读取器,但用 GeoPandas 的 FlatGeobuf 读取器再用一行代码转换为 SedonaDB DataFrame 也非常方便。 - -现在将一个 GeoParquet 文件读入 SedonaDB DataFrame。 - -```python -buildings = sd.read_parquet( - "https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/microsoft-buildings_point_geo.parquet" -) -``` - -检查 DataFrame 的 schema: - -``` -buildings.schema - -SedonaSchema with 1 field: - geometry: geometry -``` - -让我们把这两张表注册为视图,并运行一次空间连接,看看有多少建筑物位于佛蒙特州: - -```python -buildings.to_view("buildings", overwrite=True) -vermont.to_view("vermont", overwrite=True) - -sd.sql(""" -select count(*) from buildings -join vermont -where ST_Intersects(buildings.geometry, vermont.geometry) -""").show() -``` - -这条命令会正确报错,因为两张表的 CRS 不同。出于安全考虑,SedonaDB 宁可报错也不会给出错误的答案!错误信息也很容易调试: - -``` -SedonaError: type_coercion -caused by -Error during planning: Mismatched CRS arguments: ogc:crs84 vs epsg:32618 -Use ST_Transform() or ST_SetSRID() to ensure arguments are compatible. -``` - -让我们改写这次空间连接,将 `vermont` 的 CRS 转换为 EPSG:4326,使其与 `buildings` 的 CRS 兼容。 - -```python -sd.sql(""" -select count(*) from buildings -join vermont -where ST_Intersects(buildings.geometry, ST_Transform(vermont.geometry, 'EPSG:4326')) -""").show() -``` - -现在就能得到正确结果! - -``` -┌──────────┐ -│ count(*) │ -│ int64 │ -╞══════════╡ -│ 361856 │ -└──────────┘ -``` - -SedonaDB 在读写文件、与 GeoPandas DataFrame 互转或进行 DataFrame 操作时都会跟踪 CRS,让你的空间计算安全且正确! - -## 🎯 SedonaDB 的真实示例 - -下面来看一个更复杂的空间操作:KNN 连接。 - -假设你正在分析共享出行数据,并希望确定哪些建筑物最常出现在上车点附近——这有助于理解出行起点与附近的地标、商家或居住建筑之间的关系,这些可能会影响乘车需求的模式。 - -这个查询用空间最近邻分析,为每个出行上车点找出最近的 5 栋建筑物。对于每次出行,它都会识别出地理上最接近上车点的 5 栋建筑物,并计算到每栋建筑物的精确距离。 - -查询如下: - -```sql -WITH trip_with_geom AS ( - SELECT t_tripkey, t_pickuploc, ST_GeomFromWKB(t_pickuploc) as pickup_geom - FROM trip -), -building_with_geom AS ( - SELECT b_buildingkey, b_name, b_boundary, ST_GeomFromWKB(b_boundary) as boundary_geom - FROM building -) -SELECT - t.t_tripkey, - t.t_pickuploc, - b.b_buildingkey, - b.b_name AS building_name, - ST_Distance(t.pickup_geom, b.boundary_geom) AS distance_to_building -FROM trip_with_geom t JOIN building_with_geom b -ON ST_KNN(t.pickup_geom, b.boundary_geom, 5, FALSE) -ORDER BY distance_to_building ASC, b.b_buildingkey ASC -``` - -查询结果如下: - -``` -┌───────────┬───────────────────────────────┬───────────────┬───────────────┬──────────────────────┐ -│ t_tripkey ┆ t_pickuploc ┆ b_buildingkey ┆ building_name ┆ distance_to_building │ -│ int64 ┆ binary ┆ int64 ┆ utf8 ┆ float64 │ -╞═══════════╪═══════════════════════════════╪═══════════════╪═══════════════╪══════════════════════╡ -│ 5854027 ┆ 01010000001afa27b85825504001… ┆ 79 ┆ gainsboro ┆ 0.0 │ -├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 3326828 ┆ 01010000001bfcc5b8b7a95d4083… ┆ 466 ┆ deep ┆ 0.0 │ -├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 1239844 ┆ 0101000000ce471770d6ce2a40f9… ┆ 618 ┆ ivory ┆ 0.0 │ -└───────────┴───────────────────────────────┴───────────────┴───────────────┴──────────────────────┘ -``` - -这是 [SpatialBench](https://github.com/apache/sedona-spatialbench/) 中的一条查询。 - -## 🦀 为什么 SedonaDB 用 Rust 构建 - -SedonaDB 用 Rust 构建——这是一门高性能、内存安全的语言,提供细粒度的内存管理和成熟的数据库库生态。它充分利用了这一生态,集成了 [Apache DataFusion](https://github.com/apache/datafusion)、[GeoArrow](https://github.com/geoarrow/geoarrow) 和 [georust/geo](https://github.com/georust/geo) 等项目。 - -虽然 Spark 提供了扩展点,使 SedonaSpark 能在分布式场景下优化空间查询,但 DataFusion 在单节点上提供了用于裁剪、空间算子和优化器规则的稳定 API。这让我们能在引擎内部嵌入深度的空间感知能力,同时保留完整的非空间功能。感谢 DataFusion 项目及其社区,这一切才得以实现且过程愉快。 - -## ⚖️ 为什么同时需要 SedonaDB 和 SedonaSpark - -SedonaSpark 适用于大规模地理空间负载,或者你的生产环境已经在使用 Spark。例如,在 100 GB 矢量数据集与大型栅格数据集之间进行连接。但对于较小的数据集,Spark 的分布式架构会带来不必要的开销,让本地运行更慢、安装更困难、调优更棘手。 - -SedonaDB 更适合较小的数据集和本地计算场景。SedonaDB 的空间函数与 SedonaSpark 的函数兼容,因此适用于其中一个引擎的 SQL 片段通常也能在另一个引擎上运行。我们会持续推动两个项目 API 的全面互操作。下面这段用于分析 Overture 建筑表的代码片段可以同时在两个引擎上运行。 - -``` -nyc_bbox_wkt = ( - "POLYGON((-74.2591 40.4774, -74.2591 40.9176, -73.7004 40.9176, -73.7004 40.4774, -74.2591 40.4774))" -) - -sd.sql(f""" -SELECT - id, - height, - num_floors, - roof_shape, - ST_Centroid(geometry) as centroid -FROM - buildings -WHERE - is_underground = FALSE - AND height IS NOT NULL - AND height > 20 - AND ST_Intersects(geometry, ST_SetSRID(ST_GeomFromText('{nyc_bbox_wkt}'), 4326)) -LIMIT 5; -``` - -## 🚀 下一步 - -虽然 SedonaDB 已经经过充分测试,并提供了一组能完成多种空间分析的核心特性,但它仍是一个早期项目,有许多新功能的拓展空间。 - -许多 ST 函数有待补齐。其中一些相对直接,另一些则相当复杂。 - -社区还会为 SedonaDB 内建更多空间文件格式(如 GeoPackage 和 GeoJSON)的支持。在此之前,你可以先将这些格式的数据读入 GeoPandas DataFrame,再转换为 SedonaDB DataFrame。 - -栅格支持也在路线图中,这是一个复杂的工程,如果你对用 Rust 解决有挑战的问题感兴趣,这正是绝佳的贡献机会。 - -更多关于下一个版本任务的细节请参考 [SedonaDB v0.2 里程碑](https://github.com/apache/sedona-db/milestone/1)。也欢迎你创建 issue、在 Discord 留言或在 GitHub discussions 上头脑风暴新功能。 - -## 🤝 加入社区 - -Apache Sedona 社区有活跃的 Discord 社区、每月用户会议和定期的贡献者会议。 - -SedonaDB 欢迎来自社区的贡献。如有需要,你可以申请认领某个 issue,我们会很乐意分配给你。也欢迎你加入贡献者会议,其他活跃的贡献者会乐意帮助你完成 pull request! - -!!! info - 我们将以一场特别的 Apache Sedona 社区办公时间庆祝 SedonaDB 与 SpatialBench 的发布! - - 📅 2025 年 10 月 7 日 - - ⏰ 太平洋时间上午 8–9 点 - - 📍 线上 - - 🔗 [在此报名](https://bit.ly/3UBmxFY) diff --git a/docs/blog/posts/intro-spatialbench.zh.md b/docs/blog/posts/intro-spatialbench.zh.md deleted file mode 100644 index c54a2adc2cb..00000000000 --- a/docs/blog/posts/intro-spatialbench.zh.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -date: - created: 2025-12-11 -links: - - SpatialBench: https://sedona.apache.org/spatialbench/ -authors: - - pranav - - james - - jia - - matt_powers -title: "SpatialBench 正式发布:面向空间数据库查询的性能基准测试" ---- - - - -SpatialBench 是一个针对空间连接、距离查询和点在多边形内分析的基准测试框架。 - -传统的基准测试框架不包含空间工作流。单独对空间工作流进行基准测试很重要,因为擅长表格数据分析的引擎在空间查询上未必表现出色。 - -例如,下面是在同一台 ec2 实例上,SedonaDB、DuckDB 和 GeoPandas 在规模因子 1(SF-1)和 SF-10 下的 [SpatialBench 结果](https://sedona.apache.org/spatialbench/single-node-benchmarks/): - - - -![Scale Factor 1 benchmark results](../../image/blog/sedonadb1/sf1-09242025.png){ width="400" } -![Scale Factor 10 benchmark results](../../image/blog/sedonadb1/sf10-09242025.png){ width="400" } -{: .grid } - -你可以在任何支持空间分析的数据处理引擎上运行 SpatialBench 查询。 - -SpatialBench 目前包含 12 条查询,未来还会持续增加。 - -让我们看一个查询,以更好地理解空间数据分析。 - -## 空间查询示例 - -下面这条示例查询统计了起点在美国亚利桑那州 Sedona 城方圆 15 公里以内的共享出行行程的月度统计数据。 - -```sql -SELECT - DATE_TRUNC('month', t.t_pickuptime) AS pickup_month, COUNT(t.t_tripkey) AS total_trips, - AVG(t.t_distance) AS avg_distance, AVG(t.t_dropofftime - t.t_pickuptime) AS avg_duration, - AVG(t.t_fare) AS avg_fare -FROM trip t -WHERE ST_DWithin( - ST_GeomFromWKB(t.t_pickuploc), - ST_GeomFromText('POLYGON((-111.9060 34.7347, -111.6160 34.7347, -111.6160 35.0047, -111.9060 35.0047, -111.9060 34.7347))'), -- 10km bounding box around Sedona - 0.045 -- Additional 5km buffer -) -GROUP BY pickup_month -ORDER BY pickup_month -``` - -下面是查询返回的结果: - -``` -┌──────────────────────┬─────────────┬───────────────────┬─────────────────────┬───────────────────┐ -│ pickup_month ┆ total_trips ┆ avg_distance ┆ avg_duration ┆ avg_fare │ -│ timestamp(milliseco… ┆ int64 ┆ decimal128(19, 9) ┆ duration(milliseco… ┆ decimal128(19, 9) │ -╞══════════════════════╪═════════════╪═══════════════════╪═════════════════════╪═══════════════════╡ -│ 1992-04-01T00:00:00 ┆ 2 ┆ 0.000020000 ┆ 0 days 1 hours 23 … ┆ 0.000075000 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 1992-07-01T00:00:00 ┆ 1 ┆ 0.000010000 ┆ 0 days 0 hours 58 … ┆ 0.000040000 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ 1994-02-01T00:00:00 ┆ 2 ┆ 0.000020000 ┆ 0 days 1 hours 23 … ┆ 0.000050000 │ -└──────────────────────┴─────────────┴───────────────────┴─────────────────────┴───────────────────┘ -``` - -这条查询使用了几个空间函数: - -* `ST_DWithin`:如果上车点位于指定多边形的某一距离范围内,则返回 true -* `ST_GeomFromWKB`:将 well-known binary 转换为几何 -* `ST_GeomFromText`:将文本转换为几何 - -未针对空间数据设计的引擎可能不支持 `ST_DWithin`,或运行速度非常慢。 - -在某一点指定距离范围内的数据上运行聚合与报表是典型的空间工作流,因此这条查询能很好地代表一个引擎的空间能力。 - -完整的 SpatialBench 查询列表可在 [SpatialBench 项目](https://sedona.apache.org/spatialbench/queries/) 中找到。 - -## 具代表性查询的重要性 - -Apache Sedona 项目中 SpatialBench 的维护者在设计这些查询时,力求公平且准确地衡量引擎的空间能力。 - -查询不应偏向任何特定引擎,而应反映典型工作流。 - -如果你对某条 SpatialBench 查询有任何疑问或意见,请[创建一个 issue](https://github.com/apache/sedona-spatialbench/issues),让 Apache Sedona 社区可以讨论。 - -你还必须在高质量、有代表性的数据上运行这些查询,以确保基准测试的准确性。 - -## 可靠数据的重要性 - -SpatialBench 查询使用任何人都可以通过 spatialbench-cli 工具生成的合成数据集。合成数据集易于复现,但同样存在风险——它们可能无法代表真实数据。可在[项目页面](https://sedona.apache.org/spatialbench/datasets-generators/)了解当前开放的数据集和生成器。 - -例如,上面我们看到的查询识别了所有起点在 Sedona 城方圆 15 公里以内的行程。下面列出了数据可能给这条查询带来误导性结果的几种情况: - -* 选定区域内没有数据。 -* 数据集中在 15km 区域的某一小部分,而不是合理地分布在整座城市。 -* 数据被集中放在单个文件或行组中,使引擎仅依靠 Parquet 级别的边界框过滤就能完成查询,而未真正考验其空间能力。 - -SpatialBench 数据集不存在这些局限,因为它们包含真实的空间点过程。默认情况下,SpatialBench 在 Trip 和 Building 表上使用 **层次化 Thomas 分布**。这种过程能刻画真实城市的多尺度特征: - -* **城市级别的中心:** 大型都市自然涌现,规模符合 Pareto 分布。 -* **街区聚类:** 每个城市内部,行程或建筑围绕街区中心形成子聚类,产生密集和稀疏区域。 -* **高斯分布扩散:** 每个街区生成的点带有自然的空间变化,确保数据不会人为对齐到网格或完全均匀。 - -这种分层结构模拟了人类活动和城市发展在真实地理中的样子:少数大城市占主导,小城镇与之并存,每个城市内部某些街区繁华、另一些则较为冷清。通过编码这些模式,SpatialBench 数据不会陷入降低基准价值的人工模式——例如所有行程都落在完美网格上,或每栋建筑都等距分布。相反,数据呈现真实地理的异质性与不规则性,确保查询真正考验引擎的空间能力。 - -值得一提的是,这些分布也可配置。用户可根据测试目标选择多种分布。受支持的分布包括: - -* 均匀(Uniform) -* 正态(Normal) -* 对角(Diagonal) -* 位(Bit) -* Sierpinski -* Thomas -* 层次化 Thomas(Hierarchical Thomas) - -你还可以调整每种分布的参数(例如聚类数量、扩散程度、偏度),以匹配具体的基准测试场景。 - -更多关于如何配置 SpatialBench 数据生成的细节,请参阅 [SpatialBench 配置指南](https://github.com/apache/sedona-spatialbench/blob/main/spatialbench-cli/CONFIGURATION.md)。 - -真实数据让"Sedona 方圆 15 公里以内的所有行程"这类查询能够返回准确结果。覆盖时而丰富、时而稀疏,但始终以与真实地理一致的方式分布。真实的数据加上具代表性的空间查询,才让基于 SpatialBench 的基准测试具有意义。 - -## 数据规模因子 - -你可以使用不同的规模因子生成 SpatialBench 数据集。 - -规模因子 1 适合在单机或本地运行的查询。规模因子 1000 更适合在大型分布式集群上运行的查询。 - -与其他一些基准测试不同,SpatialBench 支持不同的规模因子,足够灵活,可以可靠地为小型和大型数据引擎基准测试。 - -## 可靠基准测试的要素 - -基准测试应当公正、无偏,并具备以下特征: - -1. 开源的查询/数据生成代码,结果可复现 -2. 公开所有软硬件版本 -3. 数据集可复现或可下载 -4. 完整披露任何针对引擎/厂商的调优或优化 -5. 详尽描述方法,并完整披露查询运行时间的所有组成部分 - -我们非常用心地确保 SpatialBench 的基准测试可靠且对数据社区有价值。如果你对我们的方法论有不同看法,我们鼓励你提出质疑。 - -## 未来工作 - -SpatialBench 的维护者将在下一个版本中加入栅格数据和查询。 - -这些查询将连接矢量与栅格数据,并提供一些具代表性的栅格工作流。这是关键的补充,因为栅格查询对于评估空间引擎的性能至关重要。 - -我们还会考虑社区对基准测试改进的建议。SpatialBench 的维护者有空间研究方面的经验,他们将以学术严谨的态度欢迎你的建议。 - -## 邀请厂商提交基准测试 - -我们鼓励厂商和实践者使用 SpatialBench 生成自己的基准测试,并将结果分享给社区。 - -请注明 SpatialBench 的来源,并遵循所有基准测试最佳实践。我们建议关注添加空间索引的影响——索引可以加速查询,但并非所有数据处理引擎都支持,而且一次性建立索引的开销较大。当你公布 SpatialBench 基准测试时,请披露所有细节。务必将查询运行时间的关键组成部分(例如数据加载到内存所需的时间)纳入考量。 - -## 如何贡献 - -为 SpatialBench 做贡献的方式有很多。 - -你可以在自己的电脑上、不同的运行时和硬件上运行基准测试,并将结果分享给社区。 - -你也可以提交 issue 或代码贡献。代码库是开源的,接受贡献。 - -我们也希望在不同引擎上运行基准测试。可靠地为基准测试搭建集群的代码相当复杂,尤其是对分布式引擎而言。如果你有兴趣开发能够自动在云端建立集群并基于开源分布式方案运行基准测试的脚本,请告诉我们。 - -SpatialBench 力求在基准测试领域处于前沿,你也可以将本项目用于学术研究。我们鼓励发表关于空间基准测试的论文,并乐于与学者合作。 diff --git a/docs/blog/posts/intro-to-sedona-blog.zh.md b/docs/blog/posts/intro-to-sedona-blog.zh.md deleted file mode 100644 index e7a4bfd2220..00000000000 --- a/docs/blog/posts/intro-to-sedona-blog.zh.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -date: - created: 2025-07-09 -links: - - Apache Sedona Discord 社区: https://discord.com/invite/9A3k5dEBsY - - SedonaSnow: https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow - - Apache Sedona on Apache Flink: https://sedona.apache.org/latest/tutorial/flink/sql/ -authors: - - kelly -title: 欢迎来到 Apache Sedona 博客! ---- - - - -欢迎来到全新的 Apache Sedona 博客! - -多年以来,Apache Sedona 一直是处理大规模地理空间数据集的首选开源引擎,它扩展了 Apache Spark, -以无与伦比的速度和效率处理复杂的空间操作。Sedona 的能力还远不止于 Spark,通过 -[SedonaSnow](https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow) -将空间分析带入 Snowflake 数据仓库,并通过[空间 SQL 集成](https://sedona.apache.org/latest/tutorial/flink/sql/) -将能力延伸到实时流处理引擎 Apache Flink。 - - - -这个全新的博客是我们分享整个 Sedona 生态系统资讯与最佳实践的空间。 - -无论你是经验丰富的数据科学家、构建可靠数据管道的工程师,还是 GIS 爱好者,这里都将成为你获取实用、专家级内容的新家园。 -我们会深入讲解进阶教程、探索架构模式,并讨论各种空间数据策略的取舍。从真实案例研究到我们对项目的前瞻性愿景, -我们将分享一系列洞察,帮助你掌握并创新使用 Apache Sedona。 - -更重要的是,这是一个属于整个社区的平台——也包括你。 -Apache Sedona 由其贡献者驱动,本博客也不例外。如果你构建过有意思的应用场景、攻克过棘手的优化问题, -或想分享某个教程,我们非常欢迎你成为客座作者。你的见解和真实故事弥足珍贵。 -如需分享投稿想法或提交草稿,请在 [Apache Sedona Discord 社区](https://discord.com/invite/9A3k5dEBsY) 上与我们联系。 diff --git a/docs/blog/posts/sedona-2025-year-in-review.zh.md b/docs/blog/posts/sedona-2025-year-in-review.zh.md deleted file mode 100644 index 2b8eafd8973..00000000000 --- a/docs/blog/posts/sedona-2025-year-in-review.zh.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -date: - created: 2026-01-11 -links: - - 发布说明: https://sedona.apache.org/latest/setup/release-notes/ - - SedonaDB: https://sedona.apache.org/sedonadb/ - - SpatialBench: https://sedona.apache.org/spatialbench/ - - Apache Parquet 和 Iceberg 原生 geo 类型: https://wherobots.com/blog/apache-iceberg-and-parquet-now-support-geo/ -authors: - - jia -title: "Apache Sedona 2025 年度回顾" ---- - - - -2025 年对 **Apache Sedona** 而言是里程碑式的一年。我们在 Spark、Flink 和 Snowflake 上的分布式空间分析取得重大进展,推出了名为 SedonaDB 的全新单节点引擎,并推动了基准测试与开放地理空间数据标准的发展。 - -本文总结了 Apache Sedona 生态在 2025 年最重要的亮点。 - - - -## 2025 年 Apache Sedona 生态版本 - -Apache Sedona 在 2025 年 1 月到 2026 年 1 月间发布了四个版本:1.7.1、1.7.2、1.8.0 和 1.8.1。同年,Sedona 生态从两个重要方向得到扩展:我们推出了用于快速单机分析的 SedonaDB,以及让空间性能比较可复现的 SpatialBench。 - -- Apache Sedona 版本:在分布式引擎与集成(Spark、Flink、Snowflake)上的持续改进。详见发布说明。 -- SedonaDB:为交互式分析和开发者工作流而生的全新单节点空间引擎。 -- SpatialBench:一套用于标准化跨引擎空间 SQL 性能评估的基准测试套件。 - -发布说明:[https://sedona.apache.org/latest/setup/release-notes/](https://sedona.apache.org/latest/setup/release-notes/) - -## 分布式引擎亮点 - -2025 年,SedonaSpark、SedonaFlink 和 SedonaSnow 都迎来了易用性的重大改进、更广泛的 SQL 覆盖,以及对现代开放地理空间数据格式的更好支持: - -* SedonaSpark 上的 GeoPandas API:用 GeoPandas 风格的代码编写,通过 Sedona 在 Spark 上运行,让空间连接(`sjoin`)、缓冲、距离、坐标系变换等熟悉的工作流能够超越单机规模。了解更多:[Apache Sedona 的 GeoPandas API](../../tutorial/geopandas-api.md)。 -* 用于聚类、异常检测和热点分析的 GeoStats:为 DataFrame 上常见的空间统计工作流提供内置工具,包括 DBSCAN 聚类、Local Outlier Factor(LOF)以及 Getis-Ord Gi/Gi* 热点分析。了解更多:[Stats 模块](../../api/stats/sql.md)。 -* 借助 GeoArrow 加速 SedonaSpark 到 GeoPandas 的转换:通过 Arrow/GeoArrow 更高效地将查询结果转换为 GeoPandas,例如 `geopandas.GeoDataFrame.from_arrow(dataframe_to_arrow(df))`。了解更多:[GeoPandas + Shapely 互操作性](../../tutorial/geopandas-shapely.md)。 -* STAC 目录读取器:使用 `sedona.read.format("stac")` 从本地文件、S3 或 HTTPS 端点加载 STAC 集合,并尽早应用时间/区域过滤以减少读取的数据量。同时支持需要认证的 STAC API。了解更多:[使用 Apache Sedona 和 Spark 处理 STAC 目录](../../tutorial/files/stac-sedona-spark.md)。 -* 更多内置数据源:更轻松地接入实际常用的格式,包括 GeoPackage 和 OSM PBF(OpenStreetMap)。了解更多:[SedonaSQL / DataFrame I/O 教程](../../tutorial/sql.md)。 -* 向量化 UDF(Python):通过 Apache Arrow 批量处理数据,以更快的方式运行 Python UDF,包括使用 Shapely 或 GeoPandas GeoSeries 的几何感知 UDF。了解更多:[Spatial vectorized UDFs (Python only)](../../tutorial/sql.md)。 -* 跨引擎函数扩展:Spark、Flink 和 Snowflake 上的函数覆盖度持续扩展。例如:ST_ApproximateMedialAxis、ST_StraightSkeleton、ST_Collect_Agg 以及 ST_OrientedEnvelope。详见各引擎的函数目录:[SedonaSpark SQL](../../api/sql/Overview.md)、[SedonaFlink SQL](../../api/flink/Overview.md) 和 [SedonaSnow SQL](../../api/snowflake/vector-data/Overview.md)。 - -## SedonaDB:全新的单节点空间引擎 - -2025 年最重要的进展之一是推出了 SedonaDB——一款面向单机地理空间数据的全新分析引擎。 - -SedonaDB 于 2025 年 9 月公布,代表了 Sedona 项目家族的新方向。它使用 Rust 编写,基于 Apache Arrow 和 DataFusion 构建,提供快速的列式执行能力以及轻量化的部署模式。 - -SedonaDB 在 2025 年发布了两个版本:0.1.0(首版)和 0.2.0(重大扩展)。 - -首版 0.1.0 引入了核心引擎,内建原生几何和地理类型、空间索引以及优化过的空间连接与最近邻查询,同时提供 Python 和 SQL 接口,带来零配置、嵌入式的使用体验。 - -2025 年 12 月发布的 SedonaDB 0.2.0 迅速扩展了引擎能力,新增包括栅格在内的更广泛空间 SQL 覆盖、对 GDAL 和 OGR 兼容格式的原生读取支持、带边界框元数据的 GeoParquet 1.1 写入支持、Python UDF 支持以及初步的栅格数据类型支持。 - -博客文章: - -* [SedonaDB 正式发布](https://sedona.apache.org/latest/blog/2025/09/24/introducing-sedonadb-a-single-node-analytical-database-engine-with-geospatial-as-a-first-class-citizen/) -* [SedonaDB 0.2.0 版本发布](https://sedona.apache.org/latest/blog/2025/12/01/sedonadb-020-release/) - -## SpatialBench:标准化空间性能评估 - -2025 年另一个重要里程碑是推出 SpatialBench——一套专为空间 SQL 工作负载设计的基准测试套件。 - -传统的数据库基准测试常常忽略地理空间分析中最关键的模式,例如空间连接、距离过滤和空间聚合。SpatialBench 正是为填补这一空白而生。 - -SpatialBench 提供: - -* 真实的空间数据集 -* 可配置的规模因子 -* 可复现的查询负载 -* 跨引擎可比较的结果 - -SpatialBench 的首个版本评估了 SedonaDB、带空间扩展的 DuckDB 和 GeoPandas,提供了透明且可复现的性能对比。 - -博客文章:[SpatialBench 正式发布](https://sedona.apache.org/latest/blog/2025/12/11/introducing-spatialbench-performance-benchmarks-for-spatial-database-queries/) - -## 推进开放地理空间数据格式 - -2025 年也是地理空间互操作性的转折点。Apache Iceberg 和 Apache Parquet 获得了原生的几何和地理类型支持,让在开放湖仓表中直接存储空间数据变得更轻松。 - -这一进步使得: - -* 开放且厂商中立的空间存储 -* 地理空间表的可靠事务 -* 提前过滤数据,让引擎可以扫描更少 -* 跨引擎的无缝互操作 - -Apache Sedona 与更广泛的地理空间社区都积极参与并推动了这项工作。 - -博客文章:[Apache Iceberg 和 Parquet 现已支持 Geo](https://wherobots.com/blog/apache-iceberg-and-parquet-now-support-geo/) - -## 社区与生态增长 - -除技术里程碑之外,2025 年 Apache Sedona 社区也持续增长: - -* 新的 committer 与贡献者加入项目 - - 新晋 committer:Pranav Toggi、Peter Nguyen、Dewey Dunnington - - 新晋 PMC 成员:Feng Zhang -* 全年参与的贡献者更多 - - 2025 年,27 位新贡献者首次为 Apache Sedona 仓库(SedonaSpark、SedonaFlink、SedonaSnow)做出贡献,使项目总贡献者达到 155 人。整个 2025 年共有 46 人参与 Apache Sedona 的贡献。 - - 生态内的新贡献者: - - SedonaDB:2025 年新增 26 位贡献者 - - SpatialBench:2025 年新增 8 位贡献者 -* 项目采用持续增长 - - Apache Sedona 累计下载量已超过 6500 万次。 - - 月下载量已超过 200 万次。 - - 提交活动从 2024 年的 1,509 次提交增长到 2025 年的 2,137 次提交。 - -Sedona 已演进为一个多引擎、多部署形态的生态,既反映了社区的需求,也体现了贡献者们持续不断的努力。 - -## 展望 2026 - -凭借在分布式分析、单节点引擎、基准测试和开放格式上的强劲势头,Apache Sedona 进入 2026 年的状态非常良好,具备进一步增长的能力。 - -我们将继续关注以下方向: - -* 更深入的栅格分析支持 -* 更广的 SpatialBench 覆盖 -* 与 Iceberg 原生空间特性的更紧密集成 -* 在 Python、SQL 和 Rust 上更佳的开发者体验 - -空间分析正在成为现代数据平台的核心能力,Apache Sedona 在其中的基础项目地位也日益凸显。 - -感谢社区中所有为这丰收的 2025 年做出贡献的每一位。 - -## 参考资料 - -* Sedona 1.7.1、1.7.2、1.8.0 和 1.8.1 发布说明:[https://sedona.apache.org/latest/setup/release-notes/](https://sedona.apache.org/latest/setup/release-notes/) -* SedonaDB 0.1.0 发布说明:[https://sedona.apache.org/latest/blog/2025/09/24/introducing-sedonadb-a-single-node-analytical-database-engine-with-geospatial-as-a-first-class-citizen/](https://sedona.apache.org/latest/blog/2025/09/24/introducing-sedonadb-a-single-node-analytical-database-engine-with-geospatial-as-a-first-class-citizen/) -* SedonaDB 0.2.0 发布说明:[https://sedona.apache.org/latest/blog/2025/12/01/sedonadb-020-release/](https://sedona.apache.org/latest/blog/2025/12/01/sedonadb-020-release/) -* SpatialBench 发布说明:[https://sedona.apache.org/latest/blog/2025/12/11/introducing-spatialbench-performance-benchmarks-for-spatial-database-queries/](https://sedona.apache.org/latest/blog/2025/12/11/introducing-spatialbench-performance-benchmarks-for-spatial-database-queries/) -* Apache Parquet 和 Iceberg 原生 geo 类型:[https://wherobots.com/blog/apache-iceberg-and-parquet-now-support-geo/](https://wherobots.com/blog/apache-iceberg-and-parquet-now-support-geo/) diff --git a/docs/blog/posts/spatial-query-benchmarking-databricks.zh.md b/docs/blog/posts/spatial-query-benchmarking-databricks.zh.md deleted file mode 100644 index 07c2c21557d..00000000000 --- a/docs/blog/posts/spatial-query-benchmarking-databricks.zh.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -date: - created: 2026-01-08 -links: - - SpatialBench: https://sedona.apache.org/spatialbench/ -authors: - - jia - - james - - pranav -title: "使用 SpatialBench 在 Databricks 上进行空间查询基准测试" -slug: spatial-query-benchmarking-databricks ---- - - - -[Databricks 最近宣布](https://www.databricks.com/blog/databricks-spatial-joins-now-17x-faster-out-box) Serverless SQL 用户"相比安装了 Apache Sedona 的经典集群,可获得高达 17 倍的性能提升"。遗憾的是,Databricks 并未提及结果的成本。这同时也是一次"苹果对橘子"的比较——Databricks 用来制造这 17 倍性能差距的 Serverless 计算形态与数量从未对外披露。其结果也仅限于特定的查询配置。 - -我们看到可以借助 [SpatialBench](https://sedona.apache.org/spatialbench/)——一个面向空间查询的全新基准测试框架——来解决这些问题。由于我们要对比不同类型的基础设施,我们的基准测试以性价比(price-performance)而非单纯性能为归一化标准,同时提供我们认为更全面的基准测试结果。 - -我们发现,在 Databricks SQL Serverless 上测试时,只有一条较简单的 SpatialBench 查询(\#2)的性价比与 Databricks 的说法一致。而在大多数其他查询上,Sedona 表现优秀,在覆盖更多查询的同时,性价比最高可达 6 倍。 - - - -下图展示了这些结果。图中将性价比归一化到 Databricks Jobs 集群上的 Sedona 配置(1x),并与 Serverless SQL 在 SpatialBench 规模因子 1000 下的结果进行比较。我们将深入说明这些结果是如何得到的,并分享其他结果。某些查询缺少数据点,是因为在我们使用的配置和限制下,这些查询未能完成或报错。 - -![SpatialBench @ SF1000](../../image/blog/query-benchmarking-dbx/SpatialBench@SF1000.png) - -## 什么是 SpatialBench? - -SpatialBench 之所以存在,是因为在它之前,空间查询并没有统一标准。SpatialBench 为用户提供了一种跨分析引擎一致地比较空间查询能力、成本与性能的方法。它提供面向多种规模因子(SF 100、1000 等)的空间数据生成器以及真实世界的空间查询,在广泛的查询复杂度范围内全面衡量查询成本。它让基准测试简单、公平、开放、便捷,我们鼓励大家参与开源贡献。 - -SpatialBench 框架包含 12 条按计算复杂度排序的查询:查询 1 最简单,查询 12 最复杂。下面是这些查询的概要描述。更多细节请访问 [SpatialBench 页面](https://sedona.apache.org/spatialbench/)。 - -| 查询 \# | SpatialBench 查询描述 | -|:--------:|:------------------------------------:| -| 1 | 空间过滤、聚合、排序 | -| 2 | 空间过滤、聚合、排序 | -| 3 | 空间过滤、聚合、排序 | -| 4 | 空间连接与聚合 | -| 5 | 空间聚合 | -| 6 | 空间连接 | -| 7 | 几何构造与访问 | -| 8 | 距离连接 | -| 9 | 多边形自连接 | -| 10 | 空间左连接 | -| 11 | 多路空间连接 | -| 12 | KNN 连接 | - -## 如何在 Serverless 与基于集群的引擎上使用 SpatialBench - -如果你用 SpatialBench 比较基于集群的引擎(例如 Amazon EMR 与 Databricks Classic Jobs Compute)之间的空间查询性能,可以将两边的底层计算配置设置为相同,然后衡量性能。但如果是在 Serverless 引擎上进行基准测试,事情就变得复杂,因为你并不知道底层计算的形态和数量。底层 CPU 核心可能多出 20 倍,你也无从得知! - -下面介绍如何处理这种以及其他情形: - -1. **以性价比归一化**:由于 Serverless 架构隐藏了底层硬件,直接的性能比较往往具有误导性。为了进行公平比较,基准测试应关注单次查询成本。在我们的图表中,我们更进一步——将每条查询的 Sedona 成本归一化为 1,然后将 Serverless SQL 单次查询的成本表示为 Sedona 的倍数。当然,你也可以反向操作。 -2. **用你自己的环境亲自测试**:你在生产中已有或计划上线的查询不太可能与基准测试所用的查询或数据完全一致,这些结果也不例外。因此,对于"你将看到这样的结果"这类保证应当谨慎对待。你也可能看到性价比下降,或者明显更好的结果\! -3. **评估可扩展性与创新成本**:在你有已知查询需要优化、或刚开始评估一个平台并希望获得方向性参考时,基于成本的基准测试很有用。你应当对比平台支持你尚未知如何衡量的可扩展性或创新能力。这需要使用未来可能用到的、在大规模下运行的查询来评估引擎。如果只根据当下能力来选型,未来在创新上的受限成本可能远高于成本基准中所见到的数字。 - -包括本文在内的基准测试结果,旨在为你提供方向并支持你自己的分析。其中也存在你需要警觉的偏差,我们正尽力以透明的方式、并采用开放且全面的基准测试流程来减轻这种偏差。最终,你应始终使用自己的查询和数据,基于公平的"苹果对苹果"的比较,亲自评估不同查询引擎。 - -## 基准测试配置 - -### Classic Databricks Jobs Sedona 配置 - -* Databricks DBR 16.4、Classic Jobs Compute 上的 Apache Sedona v1.7 -* 18 个 m7i.2xlarge worker \+ 1 个 driver,32GB GENERAL\_PURPOSE\_SSD。我们选用通用型 VM,因为在 AWS 上它们是与 Sedona 一起使用最常见的 VM 类型之一。 -* 成本计量:(EC2 与 EBS 按需小时基础设施成本 \+ Databricks DBU 小时成本) \* (查询运行时长(小时)) -* 10 小时查询超时 - -### Serverless SQL - -* Databricks Serverless SQL 配置:Medium -* 成本计量:(查询运行时长(小时)) \* ([24 DBUs/hr 或 $16.80/hr](https://www.databricks.com/product/pricing/product-pricing/instance-types)) -* 10 小时查询超时 - -### 执行的查询 - -对每种配置,我们在规模因子(SF)100 和 1000 下执行了全部 12 条 SpatialBench 查询,其中 SF1000 的数据规模是 SF100 的 10 倍,在未压缩 Parquet 格式下约占用 500GB 存储。 - -## 基准测试结果:能力 - -两种方案都未能在 10 小时超时内完成所有查询,部分查询甚至在超时前就报错。"DBRX Sedona" \= 本次基准测试所用的"Classic Databricks Jobs Sedona 配置"。 - -![SpatialBench-SF100-SF1000](../../image/blog/query-benchmarking-dbx/SpatialBench-SF100-SF1000.png) - -## 基准测试结果:性价比 - -下图展示了 SF100 和 SF1000 下,各查询相对于 Sedona(1x 基线)归一化的性价比。缺失的数据点与上面的能力矩阵一致——只展示完成了的查询的性价比。数值越低越好。 - -![SpatialBench @ SF100](../../image/blog/query-benchmarking-dbx/SpatialBench@SF100.png) - -![SpatialBench @ SF1000](../../image/blog/query-benchmarking-dbx/SpatialBench@SF1000.png) - -### SpatialBench @ SF100:每条查询的实际成本 - -| SpatialBench @ SF100 | | | -|:--------:|:--------------:|:------------------------------:| -| 查询 \# | Serverless SQL | Classic DBRX Jobs w/Sedona 1.7 | -| 1 | $0.12 | $0.13 | -| 2 | $0.14 | $2.17 | -| 3 | $0.15 | $0.08 | -| 4 | $0.33 | $0.16 | -| 5 | $1.18 | $0.23 | -| 6 | $0.35 | $1.05 | -| 7 | $0.61 | $0.16 | -| 8 | DNF | $0.30 | -| 9 | DNF | $0.17 | -| 10 | DNF | DNF | -| 11 | $22.00 | DNF | -| 12 | DNF | DNF | - -\* 在 SpatialBench @ SF100 中,Query 11 在 Serverless SQL 上耗时约 78 分钟完成。 - -### SpatialBench @ SF1000:每条查询的实际成本 - -| SpatialBench @ SF1000 | | | -|:--------:|:--------------:|:------------------------------:| -| 查询 \# | Serverless SQL | Classic DBRX Jobs w/Sedona 1.7 | -| 1 | $1.27 | $0.44 | -| 2 | $0.41 | $17.20 | -| 3 | $1.80 | $0.28 | -| 4 | $0.84 | $0.40 | -| 5 | $10.39 | $3.40 | -| 6 | DNF | DNF | -| 7 | $4.07 | $1.28 | -| 8 | DNF | DNF | -| 9 | DNF | $0.15 | -| 10 | DNF | DNF | -| 11 | DNF | DNF | -| 12 | DNF | DNF | - -## 结论与下一步 - -SpatialBench 为衡量与传达跨引擎空间查询性能提供了可重复的标准。我们通过测量 Databricks 上两种不同处理引擎的空间查询性能展示了它的实用性。基于我们与 SpatialBench 一起使用的配置和基准测试方法,Databricks Serverless SQL 在 SF100 和 SF1000 下的空间查询 2,以及 SF100 下的查询 6 上,都提供了出色的性价比。然而,Sedona 配置的性价比可高达 6 倍,且支持的查询更多。 - -如果你打算直接套用这些基准测试结果,我们建议你将自己现有的查询与 SpatialBench 查询进行映射,以辅助决策。 - -Apache Sedona 1.8.1 即将发布,将支持 Spark 4.0 和最新的 DBR 运行时。一旦发布,我们将在支持 Spark 4.0 的最新稳定版 DBR 上对 Sedona 1.8.1 扩展并分享同样的基准测试。 diff --git a/docs/blog/posts/spatial-tables-lakehouse.zh.md b/docs/blog/posts/spatial-tables-lakehouse.zh.md deleted file mode 100644 index af83ae7d316..00000000000 --- a/docs/blog/posts/spatial-tables-lakehouse.zh.md +++ /dev/null @@ -1,532 +0,0 @@ ---- -date: - created: 2025-10-21 -links: - - SedonaDB: https://sedona.apache.org/sedonadb/ - - SpatialBench: https://sedona.apache.org/spatialbench/ -authors: - - matt_powers -title: "使用 Iceberg 在数据湖仓中管理空间表" ---- - - - -本文解释了湖仓架构(Lakehouse Architecture)对空间表的好处,以及湖仓与数据仓库、数据湖的不同之处。 - - - -湖仓(例如 Iceberg、Delta Lake、Hudi)为表格数据提供的许多好处同样适用于空间数据,例如: - -* 可靠的事务 -* 数据版本化 -* 时间旅行(Time travel) -* Schema 约束 -* 优化能力 -* 等等 - -从 Iceberg v3 起,空间数据社区可以使用湖仓,因为它新增了几何/地理类型。 - -空间数据需要不同种类的元数据和优化,但并非总是需要完全不同的文件格式。Iceberg 现在可以存储几何与地理列所需的元数据。能够同时用 Iceberg 来管理表格和空间数据,是一件非常棒的事情。 - -本文还会解释为什么湖仓架构通常比仓库和数据湖更好。我们先从详细描述湖仓架构开始。 - -## 数据湖仓架构概述 - -湖仓将数据存储在像 Iceberg、Delta Lake 或 Hudi 这样的湖仓存储系统中。Iceberg v3 是目前最适合空间数据的选择,因为它原生支持几何和地理列。 - -湖仓中的表由像 Unity Catalog 或 Apache Polaris 这样的目录(catalog)进行治理。目录支持基于角色的访问控制(RBAC),以及多表事务等特性。 - -你可以在湖仓架构上对表进行查询,用于 BI、报表、数据科学、机器学习和其他复杂分析。 - -下图展示了湖仓架构: - -![Lakehouse architecture](../../image/blog/spatial_tables/image1.png){ align=center width="80%" } - -湖仓架构具有以下几项优势: - -* 数据以开放格式存储,因此任何引擎都可以查询,不存在厂商锁定。 -* 湖仓支持数据仓库常见的所有特性,如可靠事务、DML 操作和基于角色的访问控制。 -* 湖仓通常足以支撑像 BI 仪表盘这样的低延迟应用。 -* 湖仓可与 BigQuery、Redshift 或 Esri 等专有工具互操作。 -* 你可以将湖仓存储在基于云的存储系统中,而无需额外费用。 -* 湖仓兼容任意引擎。你可以用一个引擎做摄取、另一个引擎做 ETL、再用第三个引擎做机器学习。这种架构鼓励为每项工作选择最合适的引擎。 - -让我们看看湖仓与数据湖有何不同。 - -## 湖仓中的空间表 vs. 数据湖 - -数据湖将数据存储在文件中,没有元数据层,因此无法保证事务的可靠性。 - -下面是空间数据湖的几个例子: - -* 存储在 AWS S3 中的 GeoParquet 文件 -* 存储在 Azure Blob 中的 GeoJSON 文件 -* 存储在 GCP 中的、带 WKT 几何的 CSV 文件 - -由于数据湖不支持可靠事务,它们无法支持删除和合并这类开发者友好的功能,数据集变更时还会要求停机,也无法提供高级的性能特性。数据湖不支持删除向量(deletion vectors)或小文件合并等特性。 - -湖仓的元数据层使得便捷函数和更优性能成为可能。 - -湖仓的元数据层相对较小,因此湖仓和数据湖的存储成本大致相当。湖仓允许更好的性能,因此计算开销通常低于数据湖。 - -## 湖仓中的空间表 vs. 数据仓库 - -数据仓库是由专有引擎和文件格式驱动的分析系统。然而,由于市场压力,这个定义已经发生变化,一些数据仓库开始在专有文件格式之外支持湖仓存储系统。许多现代客户不希望被专有文件格式锁定。 - -数据仓库存在以下局限: - -* 它们通常将存储与计算捆绑销售,因此你即便只想要更多存储,也必须额外付费购买更多计算。 -* 它们以专有文件格式存储数据,与其他引擎不兼容。 -* 当将数据存储在开放文件格式中时,查询可能更慢。 -* 与其他用户共享计算时,当某个用户运行大查询会导致性能下降。 - -如今,数据仓库的严格定义正在变化,因为以前只支持专有文件格式的引擎现在也支持开放文件格式。你现在可以将数据仓库理解为:含有专有引擎或专有文件格式的系统。 - -许多现代企业偏好湖仓架构,因为它开放、能与任何构建了连接器的引擎兼容、厂商中立、成本低。 - -让我们看看如何创建一些 Iceberg 表。 - -## 用 Iceberg 创建表格表 - -下面演示如何创建一个 `customers` 表,包含 `id` 和 `first_name` 两列: - -```sql -CREATE TABLE local.db.customers (id string, first_name string) -USING iceberg -TBLPROPERTIES('format-version'='3'); -``` - -让我们向表中追加一些数据: - -```python -df = sedona.createDataFrame( - [ - ("a", "Bob"), - ("b", "Mary"), - ("c", "Sue"), - ], - ["id", "first_name"], -) - -df.write.format("iceberg").mode("append").saveAsTable("local.db.customers") -``` - -让我们对表运行一次查询: - -``` -sedona.table("local.db.customers").show() - -+---+----------+ -| id|first_name| -+---+----------+ -| a| Bob| -| b| Mary| -| c| Sue| -+---+----------+ -``` - -创建表格数据的表很简单。现在,让我们看看如何在 Iceberg 中创建带空间数据的表。 - -## 用 Iceberg v3 创建空间表 - -下面创建一个 `customer_purchases` 表,带有一个 `purchase_location` 列。 - -下面是用 Iceberg 创建该空间表的方法: - -```sql -CREATE TABLE local.db.customer_purchases (id string, price double, geometry geometry) -USING iceberg -TBLPROPERTIES('format-version'='3'); -``` - -现在向表中追加一些带经纬度坐标的空间数据: - -```python -coords = [ - (-88.110352, 24.006326), - (-77.080078, 24.006326), - (-77.080078, 31.503629), - (-88.110352, 31.503629), - (-88.110352, 24.006326), -] -df = sedona.createDataFrame( - [ - ("a", 10.99, Polygon(coords)), - ("b", 3.5, Point(1, 2)), - ("c", 1.95, Point(3, 4)), - ], - ["id", "price", "geometry"], -) - -df.write.format("iceberg").mode("append").saveAsTable("local.db.customer_purchases") -``` - -这张空间表既包含点也包含多边形。有些购买有精确位置,另一些则发生在某个区域内。 - -让我们看看如何把表格表与空间表进行连接。 - -## 连接 Iceberg 中的表格表与空间表 - -下面演示如何连接 `customers` 和 `customer_purchases` 表。 - -``` -customers = sedona.table("local.db.customers") -purchases = sedona.table("local.db.customer_purchases") - -joined = customers.join(purchases, "id") -joined.show() - -+---+----------+-----+--------------------+ -| id|first_name|price| geometry| -+---+----------+-----+--------------------+ -| a| Bob|10.99|POLYGON ((-88.110...| -| b| Mary| 3.5| POINT (1 2)| -| c| Sue| 1.95| POINT (3 4)| -+---+----------+-----+--------------------+ -``` - -现在我们可以在同一张表中看到客户信息及其购买位置。 - -继续阅读,可以看到使用空间连接(基于两表的几何列的连接)的示例。 - -使用 Sedona 连接任意表都很容易,无论底层文件格式如何,因为 Sedona 内置了众多文件读取器(例如,你可以轻松连接一个存储为 Shapefile 的表和一个存储为 GeoParquet 文件的表)。但当 Iceberg 在同一个 catalog 中同时管理表格表和空间表时,会更加轻松。 - -## 在湖仓中优化空间表 - -本节展示查询如何能在 Iceberg 表上跑得更快。 - -下面查询存储为 GeoParquet 文件的 Overture Maps Foundation buildings 数据集。 - -```python -( - sedona.table("open_data.overture_2025_03_19_1.buildings_building") - .withColumn("geometry", ST_GeomFromWKB(col("geometry"))) - .select("id", "geometry", "num_floors", "roof_color") - .createOrReplaceTempView("my_fun_view") -) -``` - -基于这个 GeoParquet 数据集,运行一个过滤查询,统计佛罗里达州 Gainesville 附近一个小区域内的建筑物数量。 - -```python -spot = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" -sql = f""" -select * from my_fun_view -where ST_Contains(ST_GeomFromWKT('{spot}'), geometry) -""" -sedona.sql(sql).count() -``` - -这个查询用时 45 秒。 - -让我们把这个数据集转换为 Iceberg: - -```python -df = sedona.table("open_data.overture_2025_03_19_1.buildings_building") - -sql = """ -CREATE TABLE local.db.overture_2025_03_19_1_buildings_building (id string, geometry geometry, num_floors integer, roof_color string) -USING iceberg -TBLPROPERTIES('format-version'='3'); -""" -sedona.sql(sql) - -( - df.select("id", "geometry", "num_floors", "roof_color") - .write.format("iceberg") - .mode("overwrite") - .saveAsTable("local.db.overture_2025_03_19_1_buildings_building") -) -``` - -现在在 Iceberg 表上重新运行同一条查询: - -```python -spot = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" -sql = f""" -select * from local.db.overture_2025_03_19_1_buildings_building -where ST_Contains(ST_GeomFromWKT('{spot}'), geometry) -""" -sedona.sql(sql).count() -``` - -这个查询在 4 秒内完成。 - -要让这个 Iceberg 查询跑得更快,我们还可以做更多优化,把空间上相近的数据放到相同的文件中。 - -## 更多 Iceberg 中的地理空间示例 - -本节用一个示意性的例子演示 Iceberg 中有助于管理空间数据的几个特性。首先创建两张表:一张存放图中蓝色的几何对象,另一张存放橙色的多边形: - -![Table geometries](../../image/blog/spatial_tables/image2.png){ align=center width="80%" } - -先创建 Iceberg 表: - -```sql -CREATE TABLE some_catalog.matt.icegeometries (id string, geometry geometry) -USING iceberg -TBLPROPERTIES('format-version'='3'); -``` - -向表中追加对象 `a`、`b`、`c`、`d` 和 `e`: - -```python -df = sedona.createDataFrame( - [ - ("a", "LINESTRING(1.0 3.0,3.0 1.0)"), - ("b", "LINESTRING(2.0 5.0,6.0 1.0)"), - ("c", "POLYGON((7.0 4.0,9.0 4.0,9.0 3.0,7.0 3.0,7.0 4.0))"), - ("d", "LINESTRING(2.0 7.0,4.0 9.0,7.0 8.0)"), - ("e", "LINESTRING(10.0 9.0,10.0 6.0)"), - ], - ["id", "geometry"], -) -df = df.withColumn("geometry", ST_GeomFromText(col("geometry"))) - -df.write.format("iceberg").mode("append").saveAsTable("some_catalog.matt.icegeometries") -``` - -查看表中的内容: - -``` -sedona.sql("SELECT * FROM some_catalog.matt.icegeometries;").show(truncate=False) - -+---+-----------------------------------+ -|id |geometry | -+---+-----------------------------------+ -|a |LINESTRING (1 3, 3 1) | -|b |LINESTRING (2 5, 6 1) | -|c |POLYGON ((7 4, 9 4, 9 3, 7 3, 7 4))| -|d |LINESTRING (2 7, 4 9, 7 8) | -|e |LINESTRING (10 9, 10 6) | -+---+-----------------------------------+ -``` - -Iceberg 让基于谓词删除表中行变得简单。 - -现在创建一张包含多边形的表。先创建 Iceberg 表: - -```sql -CREATE TABLE some_catalog.matt.icepolygons (id string, geometry geometry) -USING iceberg -TBLPROPERTIES('format-version'='3'); -``` - -追加对象 `polygon_x` 和 `polygon_y`: - -```python -df = sedona.createDataFrame( - [ - ("polygon_x", "POLYGON((3.0 5.0,8.0 5.0,8.0 2.0,3.0 2.0,3.0 5.0))"), - ("polygon_y", "POLYGON((5.0 9.0,8.0 9.0,8.0 7.0,5.0 7.0,5.0 9.0))"), - ], - ["id", "geometry"], -) -df = df.withColumn("geometry", ST_GeomFromText(col("geometry"))) - -df.write.format("iceberg").mode("append").saveAsTable("some_catalog.matt.icepolygons") -``` - -下面演示如何删除所有与任意多边形相交的线串。 - -```python -sql = """ -DELETE FROM some_catalog.matt.icegeometries -WHERE EXISTS ( - SELECT 1 - FROM some_catalog.matt.icepolygons - WHERE ST_Intersects(icegeometries.geometry, icepolygons.geometry) -) -""" -sedona.sql(sql) -``` - -查看表,可以看到几何 `b`、`c` 和 `d` 已从表中删除。 - -``` -sedona.sql("SELECT * FROM some_catalog.matt.icegeometries;").show(truncate=False) - -+---+-----------------------+ -|id |geometry | -+---+-----------------------+ -|a |LINESTRING (1 3, 3 1) | -|e |LINESTRING (10 9, 10 6)| -+---+-----------------------+ -``` - -Iceberg 也允许在表的不同版本之间进行时间旅行。下面查看 Iceberg 表目前所有的版本: - -``` -sql = "SELECT snapshot_id, committed_at, operation FROM some_catalog.matt.icegeometries.snapshots;" -sedona.sql(sql).show(truncate=False) - -+-------------------+-----------------------+---------+ -|snapshot_id |committed_at |operation| -+-------------------+-----------------------+---------+ -|1643575804253593143|2025-10-10 19:35:19.539|append | -|5206691623836785752|2025-10-10 19:35:41.214|overwrite| -+-------------------+-----------------------+---------+ -``` - -让我们查看在删除操作执行之前的表内容: - -``` -sql = "SELECT * FROM some_catalog.matt.icegeometries FOR SYSTEM_VERSION AS OF 1643575804253593143;" -sedona.sql(sql).show(truncate=False) - -+---+-----------------------------------+ -|id |geometry | -+---+-----------------------------------+ -|a |LINESTRING (1 3, 3 1) | -|b |LINESTRING (2 5, 6 1) | -|c |POLYGON ((7 4, 9 4, 9 3, 7 3, 7 4))| -|d |LINESTRING (2 7, 4 9, 7 8) | -|e |LINESTRING (10 9, 10 6) | -+---+-----------------------------------+ -``` - -每完成一次 Iceberg 事务,都会创建一个新的表版本。 - -**Iceberg 上的地理空间 upsert 操作** - -Iceberg 还支持 upsert 操作,允许你在一次事务中对表进行更新或插入。upsert 在增量更新场景中尤其有用。 - -下面是 Iceberg 表当前的内容: - -``` -+---+-----------------------+ -|id |geometry | -+---+-----------------------+ -|a |LINESTRING (1 3, 3 1) | -|e |LINESTRING (10 9, 10 6)| -+---+-----------------------+ -``` - -让我们用以下数据执行一次 upsert: - -``` -+---+---------------------+ -|id |geometry | -+---+---------------------+ -|a |LINESTRING (1 3, 3 1)| # duplicate -|e |LINESTRING (5 9, 10 6)| # updated geometry -|z |LINESTRING (6 7, 6 9)| # new data -+---+---------------------+ -``` - -upsert 追加的预期行为是: - -* 新数据应被追加 -* 已存在的数据应被更新 -* 重复的数据应被忽略 - -下面是执行此操作的代码: - -```python -merge_sql = """ -MERGE INTO some_catalog.matt.icegeometries target -USING source -ON target.id = source.id -WHEN MATCHED THEN - UPDATE SET - target.geometry = source.geometry -WHEN NOT MATCHED THEN - INSERT (id, geometry) VALUES (source.id, source.geometry) -""" -sedona.sql(merge_sql) -``` - -操作完成后,表的内容如下: - -``` -+---+----------------------+ -|id |geometry | -+---+----------------------+ -|a |LINESTRING (1 3, 3 1) | -|e |LINESTRING (5 9, 10 6)| -|z |LINESTRING (6 7, 6 9) | -+---+----------------------+ -``` - -MERGE 命令在地理空间表上还有许多其他实用场景。 - -**Iceberg 上的地理空间 schema 约束** - -Iceberg 支持 schema 约束,禁止将 schema 不匹配的数据追加到表中。如果你尝试将一个 schema 不匹配的 DataFrame 追加到 Iceberg 表,它会报错。 - -让我们创建一个 schema 与已有 Iceberg 表不同的 DataFrame: - -```python -df = sedona.createDataFrame( - [ - ("x", 2, "LINESTRING(8.0 8.0,3.0 3.0)"), - ("y", 3, "LINESTRING(5.0 5.0,1.0 1.0)"), - ], - ["id", "num", "geometry"], -) -df = df.withColumn("geometry", ST_GeomFromText(col("geometry"))) -``` - -现在尝试将 DataFrame 追加到表中: - -```python -df.write.format("iceberg").mode("append").saveAsTable("some_catalog.matt.icegeometries") -``` - -会得到如下错误: - -``` -AnalysisException: [INSERT_COLUMN_ARITY_MISMATCH.TOO_MANY_DATA_COLUMNS] Cannot write to `some_catalog`.`matt`.`icegeometries`, the reason is too many data columns: -Table columns: `id`, `geometry`. -Data columns: `id`, `num`, `geometry`. -``` - -追加操作被禁止,因为 DataFrame 的 schema 与 Iceberg 表的 schema 不同。 - -数据湖没有内建的 schema 约束,所以可能会在追加 schema 不匹配的数据后破坏表,或迫使开发者在读取表时使用特定语法。 - -schema 约束是保护数据表完整性的一个不错特性。 - -## Iceberg v3 中几何与地理列的规范 - -关于带几何和地理列的 Iceberg 规范更新,详见 [这个 pull request](https://github.com/apache/iceberg/pull/10981)。 - -Iceberg v3 规范中存储了以下关键信息: - -* 每个几何/地理列的 CRS -* 每个几何/地理列的边界框(bbox) - -引用[规范](https://iceberg.apache.org/spec/)中的描述: - -> Geospatial features from OGC – Simple feature access. Edge-interpolation is always linear/planar. See Appendix G. Parameterized by CRS C. If not specified, C is OGC:CRS84. - -附录 G: - -> The Geometry and Geography class hierarchy and its Well-known text (WKT) and Well-known binary (WKB) serializations (ISO supporting XY, XYZ, XYM, XYZM) are defined by OpenGIS Implementation Specification for Geographic information – Simple feature access – Part 1: Common architecture, from OGC (Open Geospatial Consortium). -> Points are always defined by the coordinates X, Y, Z (optional), and M (optional), in this order. X is the longitude/easting, Y is the latitude/northing, and Z is usually the height, or elevation. M is a fourth optional dimension, for example a linear reference value (e.g., highway milepost value), a timestamp, or some other value as defined by the CRS. -> The version of the OGC standard first used here is 1.2.1, but future versions may also be used if the WKB representation remains wire-compatible. - -## 结论 - -湖仓为数据社区提供了出色的特性,空间社区现在也可以在矢量数据集上享受这些好处。 - -你可以将几何和地理数据存储在 Iceberg 空间表中,从而把表格表和空间表放到同一个 catalog 内。以这种方式组织数据让你的空间数据更易被发现,也能提升查询性能。 - -空间数据社区仍然需要就将栅格数据(例如卫星影像)存入湖仓的最佳方式达成共识。请持续关注关于栅格数据的更多有趣讨论! diff --git a/mkdocs.yml b/mkdocs.yml index cc0ce02971b..a1f0b13beba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -373,6 +373,8 @@ plugins: type: datetime - mike: canonical_version: 'latest' +hooks: + - docs-overrides/hooks/i18n_blog_passthrough.py extra_css: - assets/stylesheets/extra.min.css extra_javascript: From 14de337332b48e7aabcceaea7d3db63f6e538af3 Mon Sep 17 00:00:00 2001 From: Jia Yu Date: Mon, 11 May 2026 22:04:56 -0700 Subject: [PATCH 2/2] [GH-2867] Address review on blog i18n hook Drop the carried-over config.extra.i18n_blog_prefixes assignment: it was intended for templates that consume the blog-prefix list, but no Sedona template reads it. Tighten the BLOG_FILES annotation to list[File] now that File is already imported. --- docs-overrides/hooks/i18n_blog_passthrough.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs-overrides/hooks/i18n_blog_passthrough.py b/docs-overrides/hooks/i18n_blog_passthrough.py index ec2d62dceb3..c107edc0e43 100644 --- a/docs-overrides/hooks/i18n_blog_passthrough.py +++ b/docs-overrides/hooks/i18n_blog_passthrough.py @@ -31,7 +31,7 @@ from mkdocs import plugins from mkdocs.structure.files import File, Files -BLOG_FILES: list = [] +BLOG_FILES: list[File] = [] @plugins.event_priority(-95) @@ -47,7 +47,6 @@ def _on_files_disconnect_blog_files(files: Files, config, *_, **__): blog_prefixes.append(instance.config.blog_dir) blog_prefix_tuple = tuple(p.rstrip("/") + "/" for p in blog_prefixes) - config.extra.i18n_blog_prefixes = blog_prefix_tuple for file in files: if file.src_uri.startswith(blog_prefix_tuple):