You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2026/05/11 19:08:06 UTC

(sedona) 07/12: [GH-2911] Translate Tutorial / Programming Guide (part 3) to Chinese (#2934)

This is an automated email from the ASF dual-hosted git repository.

jiayuasu pushed a commit to branch branch-1.9.0
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 6ddd74ce063ee0fa9425b97dd14c84fa3068534c
Author: Jia Yu <ji...@apache.org>
AuthorDate: Fri May 8 09:27:52 2026 -0700

    [GH-2911] Translate Tutorial / Programming Guide (part 3) to Chinese (#2934)
    
    (cherry picked from commit fd4675b335e50afbe2051a2381cd3896b0648e7c)
---
 docs/tutorial/raster.zh.md        | 758 ++++++++++++++++++++++++++++++++++++++
 docs/tutorial/snowflake/sql.zh.md | 618 +++++++++++++++++++++++++++++++
 2 files changed, 1376 insertions(+)

diff --git a/docs/tutorial/raster.zh.md b/docs/tutorial/raster.zh.md
new file mode 100644
index 0000000000..4b4dac57a5
--- /dev/null
+++ b/docs/tutorial/raster.zh.md
@@ -0,0 +1,758 @@
+<!--
+ 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.
+ -->
+
+!!!note
+    Sedona 中所有栅格函数均使用从 1 开始的索引,唯一例外是 [map algebra 函数](../api/sql/Raster-map-algebra.md),它使用从 0 开始的索引。
+
+!!!note
+    Sedona 假定地理坐标按 lon/lat 顺序排列。如果您的数据是 lat/lon 顺序,请使用 `ST_FlipCoordinates` 交换 X 与 Y。
+
+自 `v1.1.0` 起,Sedona SQL 在 DataFrame 与 SQL 中支持栅格数据源与栅格算子。==Scala、Java、Python、R== 等所有 Sedona 语言绑定都已支持栅格能力。
+
+本页介绍如何使用 SedonaSQL 管理栅格数据。
+
+=== "Scala"
+
+	```scala
+	var myDataFrame = sedona.sql("YOUR_SQL")
+	myDataFrame.createOrReplaceTempView("rasterDf")
+	```
+
+=== "Java"
+
+	```java
+	Dataset<Row> myDataFrame = sedona.sql("YOUR_SQL")
+	myDataFrame.createOrReplaceTempView("rasterDf")
+	```
+
+=== "Python"
+
+	```python
+	myDataFrame = sedona.sql("YOUR_SQL")
+	myDataFrame.createOrReplaceTempView("rasterDf")
+	```
+
+SedonaSQL 详细 API 说明请参阅 [SedonaSQL API](../api/sql/Overview.md)。示例栅格数据可在 [Sedona GitHub 仓库](https://github.com/apache/sedona/blob/0eae42576c2588fe278f75cef3b17fee600eac90/spark/common/src/test/resources/raster/raster_with_no_data/test5.tiff) 中找到。
+
+## 配置依赖
+
+=== "Scala/Java"
+
+	1. 阅读 [Sedona Maven Central 坐标](../setup/maven-coordinates.md),并在 build.sbt 或 pom.xml 中添加 Sedona 依赖。
+	2. 在 build.sbt 或 pom.xml 中添加 [Apache Spark core](https://mvnrepository.com/artifact/org.apache.spark/spark-core) 与 [Apache SparkSQL](https://mvnrepository.com/artifact/org.apache.spark/spark-sql) 依赖。
+	3. 参考 [SQL 示例项目](demo.md)。
+
+=== "Python"
+
+	1. 请阅读 [快速开始](../setup/install-python.md) 安装 Sedona Python。
+	2. 本教程基于 [Sedona SQL Jupyter Notebook 示例](jupyter-notebook.md)。
+
+## 创建 Sedona 配置
+
+在程序起始处使用以下代码创建 Sedona 配置。如果您已经有了由 Wherobots / AWS EMR / Databricks 创建的 SparkSession(通常名为 `spark`),可跳过此步骤直接使用 `spark`。
+
+可以在 builder 中追加额外的 Spark 运行时配置,例如 `SedonaContext.builder().config("spark.sql.autoBroadcastJoinThreshold", "10485760")`。
+
+=== "Scala"
+
+	```scala
+	import org.apache.sedona.spark.SedonaContext
+
+	val config = SedonaContext.builder()
+	.master("local[*]") // 集群模式下请删除此行
+	.appName("readTestScala") // 改成合适的名字
+	.getOrCreate()
+	```
+	如果同时使用 SedonaViz 与 SedonaSQL,请在 `SedonaContext.builder()` 之后追加以下行启用 Sedona Kryo 序列化器:
+	```scala
+	.config("spark.kryo.registrator", classOf[SedonaVizKryoRegistrator].getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
+	```
+
+=== "Java"
+
+	```java
+	import org.apache.sedona.spark.SedonaContext;
+
+	SparkSession config = SedonaContext.builder()
+	.master("local[*]") // 集群模式下请删除此行
+	.appName("readTestScala") // 改成合适的名字
+	.getOrCreate()
+	```
+	如果同时使用 SedonaViz 与 SedonaSQL,请在 `SedonaContext.builder()` 之后追加以下行启用 Sedona Kryo 序列化器:
+	```scala
+	.config("spark.kryo.registrator", SedonaVizKryoRegistrator.class.getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
+	```
+
+=== "Python"
+
+	```python
+	from sedona.spark import *
+
+	config = SedonaContext.builder() .\
+	    config('spark.jars.packages',
+	           'org.apache.sedona:sedona-spark-shaded-3.3_2.12:{{ sedona.current_version }},'
+	           'org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}'). \
+	    getOrCreate()
+	```
+    请将 sedona-spark-shaded 包名中的 `3.3` 替换为对应的 Spark major.minor 版本,例如 `sedona-spark-shaded-3.4_2.12:{{ sedona.current_version }}`。
+
+## 初始化 SedonaContext
+
+在创建 Sedona 配置之后加上以下代码。如果您已经有了由 Wherobots / AWS EMR / Databricks 创建的 SparkSession(通常名为 `spark`),请改为调用 `SedonaContext.create(spark)`。
+
+=== "Scala"
+
+	```scala
+	import org.apache.sedona.spark.SedonaContext
+
+	val sedona = SedonaContext.create(config)
+	```
+
+=== "Java"
+
+	```java
+	import org.apache.sedona.spark.SedonaContext;
+
+	SparkSession sedona = SedonaContext.create(config)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.spark import *
+
+	sedona = SedonaContext.create(config)
+	```
+
+也可以通过 `spark-submit` / `spark-shell` 传入 `--conf spark.sql.extensions=org.apache.sedona.sql.SedonaSqlExtensions` 一并完成注册。
+
+## 加载 GeoTiff 数据
+
+加载 GeoTiff 栅格数据的推荐方式是 `raster` 数据源。它会加载 GeoTiff 文件并自动将其切分为较小的 tile,每个 tile 在结果 DataFrame 中作为一行,并以 `Raster` 类型存储。
+
+=== "Scala"
+    ```scala
+    var rasterDf = sedona.read.format("raster").load("/some/path/*.tif")
+    rasterDf.createOrReplaceTempView("rasterDf")
+    rasterDf.show()
+    ```
+
+=== "Java"
+    ```java
+    Dataset<Row> rasterDf = sedona.read().format("raster").load("/some/path/*.tif");
+    rasterDf.createOrReplaceTempView("rasterDf");
+    rasterDf.show();
+    ```
+
+=== "Python"
+    ```python
+    rasterDf = sedona.read.format("raster").load("/some/path/*.tif")
+    rasterDf.createOrReplaceTempView("rasterDf")
+    rasterDf.show()
+    ```
+
+输出大致如下:
+
+```
++--------------------+---+---+----+
+|                rast|  x|  y|name|
++--------------------+---+---+----+
+|GridCoverage2D["g...|  0|  0| ...|
+|GridCoverage2D["g...|  1|  0| ...|
+|GridCoverage2D["g...|  2|  0| ...|
+...
+```
+
+各列含义:
+
+- `rast`:以 `Raster` 类型表示的栅格数据。
+- `x`:tile 的 X 坐标(从 0 开始);只有未禁用 retile 时才会出现。
+- `y`:tile 的 Y 坐标(从 0 开始);同上。
+- `name`:栅格文件名。
+
+### Tiling 选项
+
+默认情况下启用 tiling(`retile = true`),且 tile 大小由 GeoTiff 的内部 tile 方案决定,无需手动指定 `tileWidth` 或 `tileHeight`。建议栅格数据使用 [Cloud Optimized GeoTIFF (COG)](https://www.cogeo.org/) 格式,因为它通常会把像素数据组织为正方形 tile。
+
+也可以可选地覆盖 tile 大小,或完全禁用 tiling:
+
+| 选项 | 默认值 | 说明 |
+| :--- | :--- | :--- |
+| `retile` | `true` | 是否启用 tiling。设为 `false` 则把整张栅格作为一行加载。 |
+| `tileWidth` | GeoTiff 内部 tile 宽度 | 可选。手动指定每个 tile 的宽度(像素)。 |
+| `tileHeight` | 若设置了 `tileWidth` 则与之相同,否则使用 GeoTiff 内部 tile 高度 | 可选。手动指定每个 tile 的高度(像素)。 |
+| `padWithNoData` | `false` | 当右、下边缘的 tile 小于指定大小时,使用 NODATA 值进行填充。 |
+
+覆盖 tile 大小:
+
+=== "Python"
+    ```python
+    rasterDf = (
+        sedona.read.format("raster")
+        .option("tileWidth", "256")
+        .option("tileHeight", "256")
+        .load("/some/path/*.tif")
+    )
+    ```
+
+!!!note
+    如果栅格数据的内部 tile 方案不利于切分,`raster` 数据源会抛出错误。可以通过 `option("retile", "false")` 关闭自动切分,或手动指定 tile 大小绕过该问题。更彻底的做法是使用 `gdal_translate` 等工具把数据转换为 COG 格式。
+
+### 从目录中加载栅格文件
+
+`raster` 数据源也支持 Spark 通用文件源选项,如 `option("pathGlobFilter", "*.tif*")` 与 `option("recursiveFileLookup", "true")`。例如,递归加载某目录下所有 `.tif` 文件:
+
+=== "Python"
+    ```python
+    rasterDf = (
+        sedona.read.format("raster")
+        .option("recursiveFileLookup", "true")
+        .option("pathGlobFilter", "*.tif*")
+        .load(path_to_raster_data_folder)
+    )
+    ```
+
+!!!tip
+    当传入路径以 `/` 结尾时,`raster` 数据源会递归地在该目录及其子目录中查找栅格文件。这等价于不带尾部 `/` 并设置 `option("recursiveFileLookup", "true")`。
+
+!!!tip
+    加载栅格之后,可以在 Jupyter Notebook 中通过 `SedonaUtils.display_image(df)` 快速预览:它会自动识别栅格列并以图像形式渲染。详情请参阅 [栅格可视化文档](../api/sql/Raster-Functions.md#raster-output)。
+
+## 加载非 GeoTiff 数据(NetCDF、Arc Grid)
+
+对于 NetCDF、Arc Info ASCII Grid 等非 GeoTiff 栅格格式,请配合 Spark 内置的 `binaryFile` 数据源与 Sedona 的栅格构造器一起使用。
+
+### 步骤 1:加载为 binary DataFrame
+
+=== "Scala"
+    ```scala
+    var rawDf = sedona.read.format("binaryFile").load(path_to_raster_data)
+    rawDf.createOrReplaceTempView("rawdf")
+    rawDf.show()
+    ```
+
+=== "Java"
+    ```java
+    Dataset<Row> rawDf = sedona.read().format("binaryFile").load(path_to_raster_data);
+    rawDf.createOrReplaceTempView("rawdf");
+    rawDf.show();
+    ```
+
+=== "Python"
+    ```python
+    rawDf = sedona.read.format("binaryFile").load(path_to_raster_data)
+    rawDf.createOrReplaceTempView("rawdf")
+    rawDf.show()
+    ```
+
+输出大致如下:
+
+```
+|                path|    modificationTime|length|             content|
++--------------------+--------------------+------+--------------------+
+|file:/Download/ra...|2023-09-06 16:24:...|174803|[49 49 2A 00 08 0...|
+```
+
+如需加载多个栅格文件,可递归加载:
+
+=== "Python"
+    ```python
+    rawDf = (
+        sedona.read.format("binaryFile")
+        .option("recursiveFileLookup", "true")
+        .option("pathGlobFilter", "*.asc*")
+        .load(path_to_raster_data_folder)
+    )
+    rawDf.createOrReplaceTempView("rawdf")
+    rawDf.show()
+    ```
+
+### 步骤 2:创建 Raster 类型列
+
+SedonaSQL 中所有栅格运算都要求 Raster 类型对象,可使用以下任一构造器:
+
+#### 从 GeoTiff 创建
+
+```sql
+SELECT RS_FromGeoTiff(content) AS rast, modificationTime, length, path FROM rawdf
+```
+
+#### 从 Arc Grid 创建
+
+```sql
+SELECT RS_FromArcInfoAsciiGrid(content) AS rast, modificationTime, length, path FROM rawdf
+```
+
+#### 从 NetCDF 创建
+
+加载 NetCDF 文件的方法详见 [RS_FromNetCDF](../api/sql/Raster-Constructors/RS_FromNetCDF.md)。
+
+校验栅格列是否创建成功:
+
+```python
+rasterDf.printSchema()
+```
+
+```
+root
+ |-- rast: raster (nullable = true)
+ |-- modificationTime: timestamp (nullable = true)
+ |-- length: long (nullable = true)
+ |-- path: string (nullable = true)
+```
+
+## 栅格的元数据
+
+Sedona 提供了获取栅格元数据的函数,以及获取栅格 world file 的函数。
+
+### 元数据
+
+该函数返回一个数组,包含栅格的所有必要信息,详见 [RS_MetaData](../api/sql/Raster-Operators/RS_MetaData.md)。
+
+```sql
+SELECT RS_MetaData(rast) FROM rasterDf
+```
+
+输出形如:
+
+```
+[-1.3095817809482181E7, 4021262.7487925636, 512.0, 517.0, 72.32861272132695, -72.32861272132695, 0.0, 0.0, 3857.0, 1.0]
+```
+
+数组中前两个元素是栅格图像左上像素的真实地理坐标(如经纬度),接下来的两个元素是栅格的像素维度。
+
+### World File
+
+[world file](https://en.wikipedia.org/wiki/World_file) 有 GDAL 与 ESRI 两种 georeference。详情参见 [RS_GeoReference](../api/sql/Raster-Accessors/RS_GeoReference.md)。
+
+```sql
+SELECT RS_GeoReference(rast, "ESRI") FROM rasterDf
+```
+
+输出如下:
+
+```
+72.328613
+0.000000
+0.000000
+-72.328613
+-13095781.645176
+4021226.584486
+```
+
+world file 用于通过建立图像到世界坐标的变换,把真实地理坐标对应到图像像素,从而实现 georeference 与 geolocate。
+
+## 栅格操作
+
+自 `v1.5.0` 起 Sedona 增加了大量栅格操作能力。下面给出几个示例查询。
+
+!!!note
+    更多栅格操作请参阅 [SedonaSQL 栅格算子](../api/sql/Raster-Functions.md#raster-operators)。
+
+### 坐标转换
+
+Sedona 支持按需在像素坐标与世界坐标之间互相转换。
+
+#### PixelAsPoint
+
+使用 [RS_PixelAsPoint](../api/sql/Pixel-Functions/RS_PixelAsPoint.md) 将像素坐标转换为世界坐标位置:
+
+```sql
+SELECT RS_PixelAsPoint(rast, 450, 400) FROM rasterDf
+```
+
+输出:
+
+```
+POINT (-13063342 3992403.75)
+```
+
+#### 世界坐标转栅格坐标
+
+使用 [RS_WorldToRasterCoord](../api/sql/Raster-Accessors/RS_WorldToRasterCoord.md) 把世界坐标位置转换为像素坐标。仅取 X 用 [RS_WorldToRasterCoordX](../api/sql/Raster-Accessors/RS_WorldToRasterCoordX.md),仅取 Y 用 [RS_WorldToRasterCoordY](../api/sql/Raster-Accessors/RS_WorldToRasterCoordY.md)。
+
+```sql
+SELECT RS_WorldToRasterCoord(rast, -1.3063342E7, 3992403.75)
+```
+
+输出:
+
+```
+POINT (450 400)
+```
+
+### 像素操作
+
+使用 [RS_Values](../api/sql/Raster-Operators/RS_Values.md) 获取由点几何数组指定位置上的栅格值。这些点几何的坐标表示真实世界位置。
+
+```sql
+SELECT RS_Values(rast, Array(ST_Point(-13063342, 3992403.75), ST_Point(-13074192, 3996020)))
+```
+
+输出:
+
+```
+[132.0, 148.0]
+```
+
+要在网格或某个几何区域内修改像素值,可使用 [RS_SetValues](../api/sql/Raster-Operators/RS_SetValues.md):
+
+```sql
+SELECT RS_SetValues(
+        rast, 1, 250, 260, 3, 3,
+        Array(10, 12, 17, 26, 28, 37, 43, 64, 66)
+    )
+```
+
+详细用法请点击上面的链接。
+
+### 波段操作
+
+Sedona 提供了从栅格图像中选择特定波段并构造新栅格的 API。例如,使用 [RS_Band](../api/sql/Raster-Band-Accessors/RS_Band.md) 从栅格中选取 2 个波段以构造多波段栅格。
+
+下面以一张 [多波段栅格](https://github.com/apache/sedona/blob/2a0b36989aa895c0781f9a10c907dd726506d0b7/spark/common/src/test/resources/raster_geotiff_color/FAA_UTM18N_NAD83.tif) 为例进行演示,加载与转换为 raster 类型的方式与前文相同。
+
+```sql
+SELECT RS_Band(colorRaster, Array(1, 2))
+```
+
+如果有多个单波段栅格,希望把额外的波段加入栅格以执行 [map algebra 操作](#execute-map-algebra-operations),可使用 [RS_AddBand](../api/sql/Raster-Operators/RS_AddBand.md):
+
+```sql
+SELECT RS_AddBand(raster1, raster2, 1, 2)
+```
+
+执行后 `raster1` 会附带上 `raster2` 中指定的波段。
+
+### 重采样栅格数据
+
+Sedona 支持使用最近邻、双线性、双三次等不同插值方法对栅格数据进行重采样,以改变 cell 大小或对齐栅格网格,详见 [RS_Resample](../api/sql/Raster-Operators/RS_Resample.md)。
+
+```sql
+SELECT RS_Resample(rast, 50, -50, -13063342, 3992403.75, true, "bicubic")
+```
+
+更多用法请参考链接中的文档。
+
+## 执行 map algebra 操作
+
+map algebra 是一种通过数学表达式对栅格进行计算的方法。表达式既可以是简单的算术运算,也可以是多个运算的复杂组合。
+
+归一化植被指数(NDVI)是一个简单的图形指标,常用于分析航天平台的遥感测量结果,评估观测目标是否包含活体绿色植被:
+
+```
+NDVI = (NIR - Red) / (NIR + Red)
+```
+
+其中 NIR 表示近红外波段,Red 表示红色波段。
+
+```sql
+SELECT RS_MapAlgebra(raster, 'D', 'out = (rast[3] - rast[0]) / (rast[3] + rast[0]);') as ndvi FROM raster_table
+```
+
+更多内容请参阅 [Map Algebra API](../api/sql/Raster-map-algebra.md)。
+
+## 栅格与矢量数据的互通
+
+### 几何转栅格
+
+可以使用 [RS_AsRaster](../api/sql/Raster-Operators/RS_AsRaster.md) 把几何对象栅格化:
+
+```sql
+SELECT RS_AsRaster(
+        ST_GeomFromWKT('POLYGON((150 150, 220 260, 190 300, 300 220, 150 150))'),
+        RS_MakeEmptyRaster(1, 'b', 4, 6, 1, -1, 1),
+        'b', 230
+    )
+```
+
+针对该矢量生成的图像如下:
+
+![Rasterized vector](../image/rasterized-image.png)
+
+!!!note
+    示例图像中的矢量坐标做了适度放大以便观察,实际使用时不一定与本例完全一致。
+
+### 空间范围查询
+
+Sedona 提供了基于几何窗口的栅格谓词来执行范围查询。例如使用 [RS_Intersects](../api/sql/Raster-Predicates/RS_Intersects.md):
+
+```sql
+SELECT rast FROM rasterDf WHERE RS_Intersect(rast, ST_GeomFromWKT('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'))
+```
+
+### 空间连接查询
+
+Sedona 的栅格谓词同样支持基于栅格列与几何列的空间连接:
+
+```sql
+SELECT r.rast, g.geom FROM rasterDf r, geomDf g WHERE RS_Interest(r.rast, g.geom)
+```
+
+!!!note
+    上述范围查询与连接查询都会基于提供的几何边界以及栅格的空间边界进行过滤。
+
+    Sedona 还提供了更多用于空间范围查询与空间连接的栅格谓词,详见 [栅格谓词文档](../api/sql/Raster-Functions.md#raster-predicates)。
+
+## 可视化栅格图像
+
+Sedona 提供了若干 API 把栅格数据以图像形式可视化。
+
+### Base64 字符串
+
+[RS_AsBase64](../api/sql/Raster-Output/RS_AsBase64.md) 将栅格编码为 Base64 字符串,可以使用 [在线解码器](https://base64-viewer.onrender.com/) 查看:
+
+```sql
+SELECT RS_AsBase64(rast) FROM rasterDf
+```
+
+### HTML Image
+
+[RS_AsImage](../api/sql/Raster-Output/RS_AsImage.md) 返回一个 HTML `<img>` 标签,可以在 HTML viewer 或 Jupyter Notebook 中查看:
+
+```sql
+SELECT RS_AsImage(rast, 500) FROM rasterDf
+```
+
+输出如下:
+
+![Output](../image/DisplayImage.png)
+
+!!!tip
+    在 Jupyter Notebook 中使用 `SedonaUtils.display_image` 即可直接渲染栅格,无需手动调用 RS_AsImage:
+
+    ```python
+    from sedona.spark import SedonaUtils
+
+    SedonaUtils.display_image(rasterDf)
+    ```
+
+    详情请参阅 [在 Jupyter 中显示栅格](../api/sql/Raster-Functions.md#raster-output)。
+
+### 二维矩阵
+
+如果上述 API 不能满足需求,Sedona 还提供了将栅格数据可视化为二维矩阵的 API:
+
+```sql
+SELECT RS_AsMatrix(rast) FROM rasterDf
+```
+
+输出形如:
+
+```sql
+| 1   3   4   0|
+| 2   9  10  11|
+| 3   4   5   6|
+```
+
+更多用法请参阅 [栅格可视化文档](../api/sql/Raster-Functions.md#raster-output)。
+
+## 保存到永久存储
+
+保存栅格数据分两步:(1) 使用某个 `RS_AsXXX` 函数将 Raster 列转换为二进制格式;(2) 通过 Sedona 的 `raster` 数据源 writer 把二进制 DataFrame 写入文件。
+
+### 步骤 1:转换为二进制格式
+
+可选的输出格式函数:
+
+| 函数 | 格式 | 说明 |
+| :--- | :--- | :--- |
+| [RS_AsGeoTiff](../api/sql/Raster-Output/RS_AsGeoTiff.md) | GeoTiff | 通用栅格格式,可选压缩 |
+| [RS_AsCOG](../api/sql/Raster-Output/RS_AsCOG.md) | Cloud Optimized GeoTiff | 适合云存储,支持高效的范围读取 |
+| [RS_AsArcGrid](../api/sql/Raster-Output/RS_AsArcGrid.md) | Arc Grid | 基于 ASCII 的格式,仅支持单波段 |
+| [RS_AsPNG](../api/sql/Raster-Output/RS_AsPNG.md) | PNG | 图像格式,仅支持无符号整型像素 |
+
+```sql
+SELECT RS_AsGeoTiff(rast) AS raster_binary FROM rasterDf
+```
+
+### 步骤 2:写入文件
+
+使用 Sedona 内置的 `raster` 数据源写出二进制 DataFrame:
+
+=== "Scala"
+    ```scala
+    rasterDf.withColumn("raster_binary", expr("RS_AsGeoTiff(rast)"))
+      .write.format("raster").mode("overwrite").save("my_raster_file")
+    ```
+
+=== "Python"
+    ```python
+    rasterDf.withColumn("raster_binary", expr("RS_AsGeoTiff(rast)")).write.format(
+        "raster"
+    ).mode("overwrite").save("my_raster_file")
+    ```
+
+writer 数据源的常用选项:
+
+| 选项 | 默认值 | 说明 |
+| :--- | :--- | :--- |
+| `rasterField` | schema 中最后一个 `binary` 列 | 要写出的二进制列名。当 DataFrame 含有多个 binary 列时,强烈建议显式指定。 |
+| `fileExtension` | `.tiff` | 输出文件的扩展名(如 `.png`、`.asc`)。 |
+| `pathField` | None | 包含输出文件名的列名。仅使用基础文件名(去掉目录),并把已有扩展名替换为 `fileExtension`。未设置时每个文件以随机 UUID 命名。 |
+| `useDirectCommitter` | `true` | 若为 `true`,文件直接写入目标位置;若为 `false`,先写入临时位置再移动。`false` 速度较慢,尤其在 S3 等对象存储上。 |
+
+带全部选项的示例:
+
+=== "Scala"
+    ```scala
+    rasterDf.withColumn("raster_binary", expr("RS_AsGeoTiff(rast)"))
+      .write.format("raster")
+      .option("rasterField", "raster_binary")
+      .option("pathField", "name")
+      .option("fileExtension", ".tiff")
+      .mode("overwrite")
+      .save("my_raster_file")
+    ```
+
+=== "Python"
+    ```python
+    rasterDf.withColumn("raster_binary", expr("RS_AsGeoTiff(rast)")).write.format(
+        "raster"
+    ).option("rasterField", "raster_binary").option("pathField", "name").option(
+        "fileExtension", ".tiff"
+    ).mode(
+        "overwrite"
+    ).save(
+        "my_raster_file"
+    )
+    ```
+
+写出后的文件结构形如:
+
+```
+my_raster_file
+- part-00000-6c7af016-c371-4564-886d-1690f3b27ca8-c000
+	- test1.tiff
+	- .test1.tiff.crc
+- part-00001-6c7af016-c371-4564-886d-1690f3b27ca8-c000
+	- test2.tiff
+	- .test2.tiff.crc
+- _SUCCESS
+```
+
+读回保存的栅格:
+
+```python
+rasterDf = sedona.read.format("raster").load("my_raster_file/*/*.tiff")
+```
+
+## 在 Python 中收集并本地处理带栅格列的 DataFrame
+
+自 `v1.6.0` 起,Sedona 支持把含有栅格列的 DataFrame 收集(collect)到 Python 端进行本地处理。栅格对象在 Python 端表示为 `SedonaRaster`,可对其执行各种栅格操作。
+
+!!!tip
+    如果只是希望在 Jupyter 中快速预览栅格,无需 collect DataFrame,使用 `SedonaUtils.display_image(df)` 即可:
+
+    ```python
+    from sedona.spark import SedonaUtils
+
+    SedonaUtils.display_image(df_raster)
+    ```
+
+```python
+df_raster = (
+    sedona.read.format("raster").option("retile", "false").load("/path/to/raster.tif")
+)
+rows = df_raster.collect()
+raster = rows[0].rast
+raster  # <sedona.raster.sedona_raster.InDbSedonaRaster at 0x1618fb1f0>
+```
+
+通过 `SedonaRaster` 对象的属性可以读取栅格元数据:
+
+```python
+raster.width  # 栅格宽度
+raster.height  # 栅格高度
+raster.affine_trans  # 仿射变换矩阵
+raster.crs_wkt  # 以 WKT 表示的坐标参考系
+```
+
+可以通过 `as_numpy` 或 `as_numpy_masked` 方法获取栅格波段数据的 numpy 数组,数组按 CHW 顺序组织。
+
+```python
+raster.as_numpy()  # 栅格的 numpy 数组
+raster.as_numpy_masked()  # nodata 值被掩为 nan 的 numpy 数组
+```
+
+如果希望使用 `rasterio` 处理栅格数据,可以通过 `as_rasterio` 方法获取一个 `rasterio.DatasetReader` 对象。
+
+!!!note
+    使用此方法需要安装 `rasterio` 包(>= 1.2.10):`pip install rasterio`。
+
+```python
+ds = raster.as_rasterio()  # rasterio.DatasetReader 对象
+# 使用 rasterio 处理栅格
+band1 = ds.read(1)  # 读取第 1 个波段
+```
+
+## 编写处理栅格数据的 Python UDF
+
+可以编写 Python UDF 来处理栅格数据。UDF 接收 `SedonaRaster` 对象作为输入,返回任意 Spark 数据类型。下面是一个计算栅格均值的 UDF 示例:
+
+```python
+from pyspark.sql.types import DoubleType
+
+
+def mean_udf(raster):
+    return float(raster.as_numpy().mean())
+
+
+sedona.udf.register("mean_udf", mean_udf, DoubleType())
+df_raster.withColumn("mean", expr("mean_udf(rast)")).show()
+```
+
+```
++--------------------+------------------+
+|                rast|              mean|
++--------------------+------------------+
+|GridCoverage2D["g...|1542.8092886117788|
++--------------------+------------------+
+```
+
+返回栅格对象的 UDF 较为棘手,因为 Sedona 目前还不支持序列化 Python 端的栅格对象。但可以让 UDF 返回波段数据数组,再通过 `RS_MakeRaster` 构造栅格对象。下面是一个基于输入栅格第一个波段创建掩码栅格的 Python UDF:
+
+```python
+from pyspark.sql.types import ArrayType, DoubleType
+import numpy as np
+
+
+def mask_udf(raster):
+    band1 = raster.as_numpy()[0, :, :]
+    mask = (band1 < 1400).astype(np.float64)
+    return mask.flatten().tolist()
+
+
+sedona.udf.register("mask_udf", band_udf, ArrayType(DoubleType()))
+df_raster.withColumn("mask", expr("mask_udf(rast)")).withColumn(
+    "mask_rast", expr("RS_MakeRaster(rast, 'I', mask)")
+).show()
+```
+
+```
++--------------------+--------------------+--------------------+
+|                rast|                mask|           mask_rast|
++--------------------+--------------------+--------------------+
+|GridCoverage2D["g...|[0.0, 0.0, 0.0, 0...|GridCoverage2D["g...|
++--------------------+--------------------+--------------------+
+```
+
+## 性能优化
+
+处理大规模栅格数据集时,请参考 [在 Parquet 中存储栅格几何对象](storing-blobs-in-parquet.md) 一文中的性能优化建议。
diff --git a/docs/tutorial/snowflake/sql.zh.md b/docs/tutorial/snowflake/sql.zh.md
new file mode 100644
index 0000000000..b044421b2b
--- /dev/null
+++ b/docs/tutorial/snowflake/sql.zh.md
@@ -0,0 +1,618 @@
+<!--
+ 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.
+ -->
+
+完成安装后,您就可以开始使用 Sedona 函数了。请使用具备访问数据库权限的用户重新登录 Snowflake。
+
+!!!note
+	使用 Sedona 函数时请始终保留 schema 名 `SEDONA`(如 `SEDONA.ST_GeomFromWKT`),以避免与 Snowflake 内置函数冲突。
+
+## 在 Snowflake 中访问 Sedona 函数
+
+请先确保引用了正确的函数。默认情况下,Sedona 函数位于 `SEDONASNOW.SEDONA` 这个数据库.schema 下:
+
+```sql
+USE DATABASE SEDONASNOW;
+```
+
+也可以使用其他数据库,并通过完全限定名调用,例如 `SEDONASNOW.SEDONA.ST_GeomFromText`。
+
+## 创建示例表
+
+下面创建一个 `city_tbl`,其中包含城市的位置与名称。每个位置以 WKT 字符串表示:
+
+```sql
+CREATE OR REPLACE TABLE city_tbl (wkt STRING, city_name STRING);
+INSERT INTO city_tbl(wkt, city_name) VALUES ('POINT (-122.33 47.61)', 'Seattle');
+INSERT INTO city_tbl(wkt, city_name) VALUES ('POINT (-122.42 37.76)', 'San Francisco');
+```
+
+可以查看该表的内容:
+
+```sql
+SELECT *
+FROM city_tbl;
+```
+
+输出:
+
+```
+WKT	CITY_NAME
+POINT (-122.33 47.61)	Seattle
+POINT (-122.42 37.76)	San Francisco
+```
+
+## 创建 Geometry/Geography 列
+
+SedonaSQL 中所有几何运算都作用在 Geometry/Geography 类型对象上。因此在执行任何查询之前,需要在表上构造一列 Geometry/Geography 类型列:
+
+```sql
+CREATE OR REPLACE TABLE city_tbl_geom AS
+SELECT SEDONA.ST_GeomFromWKT(wkt) AS geom, city_name
+FROM city_tbl
+```
+
+`city_tbl_geom` 表中的 `geom` 列现在是 `Binary` 类型,其中数据以 Sedona 能理解的格式存储。该查询的输出会以 WKB 二进制形式显示几何对象,类似如下:
+
+```
+GEOM CITY_NAME
+010100000085eb51b81e955ec0ae47e17a14ce4740  Seattle
+01010000007b14ae47e19a5ec0e17a14ae47e14240  San Francisco
+```
+
+如需以人类可读的方式查看该列内容,可以使用 `SEDONA.ST_AsText`,例如:
+
+```sql
+SELECT SEDONA.ST_AsText(geom), city_name
+FROM city_tbl_geom
+```
+
+也可以创建 Snowflake 原生 Geometry / Geography 类型列。例如,以下代码创建一个 Snowflake 原生 Geometry 类型列(注意函数名称没有 `SEDONA` 前缀):
+
+```sql
+CREATE OR REPLACE TABLE city_tbl_geom AS
+SELECT ST_GeometryFromWKT(wkt) AS geom, city_name
+FROM city_tbl
+```
+
+下面的代码创建一个 Snowflake 原生 Geography 类型列(同样没有 `SEDONA` 前缀):
+
+```sql
+CREATE OR REPLACE TABLE city_tbl_geom AS
+SELECT ST_GeographyFromWKT(wkt) AS geom, city_name
+FROM city_tbl
+```
+
+!!!note
+	SedonaSQL 提供了大量构造 Geometry 列的函数,详见 [SedonaSQL API](../../api/snowflake/vector-data/Geometry-Functions.md)。
+
+## 检查经纬度顺序(lon/lat)
+
+在 SedonaSnow `v1.4.1` 及之前的版本中,下列函数使用 lat/lon 顺序:
+
+* SEDONA.ST_Transform
+* SEDONA.ST_DistanceSphere
+* SEDONA.ST_DistanceSpheroid
+
+下列函数使用 `lon/lat` 顺序:
+
+* SEDONA.ST_GeomFromGeoHash
+* SEDONA.ST_GeoHash
+* SEDONA.ST_S2CellIDs
+
+自 Sedona `v1.5.0` 起,所有函数统一使用 lon/lat 顺序。
+
+如果原始数据的坐标顺序与所需顺序不一致,可使用 `SEDONA.ST_FlipCoordinates(geom: Geometry)` 进行翻转。
+
+上文示例数据采用 lon/lat 顺序,可按如下方式翻转坐标:
+
+```sql
+CREATE OR REPLACE TABLE city_tbl_geom AS
+SELECT SEDONA.ST_FlipCoordinates(geom) AS geom, city_name
+FROM city_tbl_geom
+```
+
+再次查看表内容,此时已变为 lat/lon 顺序:
+
+```sql
+SELECT SEDONA.ST_AsText(geom), city_name
+FROM city_tbl_geom
+```
+
+输出:
+
+```
+GEOM	CITY_NAME
+POINT (47.61 -122.33)	Seattle
+POINT (37.76 -122.42)	San Francisco
+```
+
+## 保存为普通列
+
+要将表持久化到永久存储中,只需把 Geometry 类型列里的每个几何对象转换回普通字符串再保存即可。
+
+可使用以下代码把表中的 Geometry 列还原为 WKT 字符串列:
+
+```sql
+SELECT SEDONA.ST_AsText(geom)
+FROM city_tbl_geom
+```
+
+!!!note
+	SedonaSQL 提供了多种保存 Geometry 列的函数,详见 [SedonaSQL API](../../api/snowflake/vector-data/Overview.md)。
+
+## 转换坐标参考系
+
+Sedona 不会自动管理一列 Geometry 中所有几何对象的坐标单位(基于度还是基于米)。SedonaSQL 中所有相关距离的单位与 Geometry 列中几何对象的单位保持一致。
+
+要转换 Geometry 列的坐标参考系,可使用 `SEDONA.ST_Transform(A:geometry, SourceCRS:string, TargetCRS:string)`。
+
+`SEDONA.ST_Transform` 第一个 EPSG 代码 EPSG:4326 是源 CRS——也就是最常见的基于度的 CRS(WGS84)。
+
+第二个 EPSG 代码 EPSG:3857 是目标 CRS——最常见的基于米的 CRS。
+
+`SEDONA.ST_Transform` 会把这些几何对象的 CRS 从 EPSG:4326 转换到 EPSG:3857。详细 CRS 信息可以在 [EPSG.io](https://epsg.io/) 找到。
+
+!!!note
+	该函数在 1.5.0+ 中使用 lon/lat 顺序,在 1.4.1 及之前使用 lat/lon 顺序。可以使用 `SEDONA.ST_FlipCoordinates` 交换 X 与 Y。
+
+按以下方式转换示例数据:
+
+```sql
+SELECT SEDONA.ST_AsText(SEDONA.ST_Transform(geom, 'epsg:4326', 'epsg:3857')), city_name
+FROM city_tbl_geom
+```
+
+输出:
+
+```
+POINT (6042216.250411431 -13617713.308741156)  Seattle
+POINT (4545577.120361927 -13627732.06291255)  San Francisco
+```
+
+`SEDONA.ST_Transform` 同样支持 OGC WKT 格式的 CRS 字符串。下面的查询输出与上面相同,但目标 CRS 以 OGC WKT 字符串给出:
+
+```sql
+SELECT SEDONA.ST_AsText(SEDONA.ST_Transform(geom, 'epsg:4326', 'PROJCS["WGS 84 / Pseudo-Mercator",
+     GEOGCS["WGS 84",
+         DATUM["WGS_1984",
+             SPHEROID["WGS 84",6378137,298.257223563,
+                 AUTHORITY["EPSG","7030"]],
+             AUTHORITY["EPSG","6326"]],
+         PRIMEM["Greenwich",0,
+             AUTHORITY["EPSG","8901"]],
+         UNIT["degree",0.0174532925199433,
+             AUTHORITY["EPSG","9122"]],
+         AUTHORITY["EPSG","4326"]],
+     PROJECTION["Mercator_1SP"],
+     PARAMETER["central_meridian",0],
+     PARAMETER["scale_factor",1],
+     PARAMETER["false_easting",0],
+     PARAMETER["false_northing",0],
+     UNIT["metre",1,
+         AUTHORITY["EPSG","9001"]],
+     AXIS["Easting",EAST],
+     AXIS["Northing",NORTH],
+     EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"],
+     AUTHORITY["EPSG","3857"]]')), city_name
+FROM city_tbl_geom
+```
+
+## 范围查询
+
+使用 ==SEDONA.ST_Contains==、==SEDONA.ST_Intersects==、==SEDONA.ST_Within== 在单列上执行范围查询。
+
+下例查找位于给定多边形内的所有几何对象:
+
+```sql
+SELECT *
+FROM city_tbl_geom
+WHERE SEDONA.ST_Contains(SEDONA.ST_PolygonFromEnvelope(1.0,100.0,1000.0,1100.0), geom)
+```
+
+!!!note
+	了解如何创建 Geometry 类型的查询窗口请参阅 [SedonaSQL API](../../api/snowflake/vector-data/Geometry-Functions.md)。
+
+## KNN 查询
+
+使用 ==SEDONA.ST_Distance==、==SEDONA.ST_DistanceSphere==、==ST_DistanceSpheroid== 计算距离并排序。
+
+下面的代码返回距离给定点最近的 5 个对象:
+
+```sql
+SELECT geom, SEDONA.ST_Distance(SEDONA.ST_Point(1.0, 1.0), geom) AS distance
+FROM city_tbl_geom
+ORDER BY distance DESC
+LIMIT 5
+```
+
+## 范围连接查询
+
+!!!warning
+	Sedona 在 Snowflake 中的范围连接不会触发 Sedona 的优化空间连接算法(Sedona Spark 会触发),它使用的是 Snowflake 默认的笛卡尔连接,速度非常慢。建议改用 Sedona 的 S2 等值连接,或者使用 Snowflake 原生 ST 函数 + 原生 `Geography` 类型来执行范围连接,这样可以触发 Snowflake 的 `GeoJoin` 算法。
+
+简介:从 A、B 两侧的几何对象中找到所有满足某种谓词的几何对组合。
+
+示例:
+
+构造演示用的表:
+
+```sql
+CREATE OR REPLACE TABLE polygondf AS
+SELECT SEDONA.ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))') polygonshape;
+```
+
+```sql
+CREATE OR REPLACE TABLE pointdf AS
+SELECT SEDONA.ST_GeomFromText('POINT(0.5 0.5)') pointshape;
+```
+
+使用不同的空间谓词执行查询:
+
+```sql
+SELECT *
+FROM polygondf, pointdf
+WHERE SEDONA.ST_Contains(polygondf.polygonshape,pointdf.pointshape)
+```
+
+```sql
+SELECT *
+FROM polygondf, pointdf
+WHERE SEDONA.ST_Intersects(polygondf.polygonshape,pointdf.pointshape)
+```
+
+```sql
+SELECT *
+FROM pointdf, polygondf
+WHERE SEDONA.ST_Within(pointdf.pointshape, polygondf.polygonshape)
+```
+
+对应的更快的空间连接(仅使用 Snowflake 原生函数)写法:
+
+```sql
+WITH polygondf AS (
+    SELECT ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))') polygonshape
+),
+pointdf AS (
+    SELECT ST_GeomFromText('POINT(0.5 0.5)') pointshape
+)
+SELECT *
+FROM polygondf, pointdf
+WHERE ST_Contains(polygondf.polygonshape,pointdf.pointshape)
+```
+
+`ST_Intersects`、`ST_Within` 等其他谓词也可以原生使用。
+
+## 距离连接
+
+!!!warning
+	Sedona 在 Snowflake 中的距离连接不会触发 Sedona 的优化空间连接算法(Sedona Spark 会触发),它使用的是 Snowflake 默认的笛卡尔连接,速度非常慢。建议改用 Sedona 的 S2 等值连接,或者使用 Snowflake 原生 ST 函数 + 原生 `Geography` 类型来执行范围连接,从而触发 Snowflake 的 `GeoJoin` 算法。
+
+简介:从 A、B 两侧的几何对象中找到所有距离小于等于某阈值的对组合。支持的平面欧氏距离函数包括 `SEDONA.ST_Distance`、`SEDONA.ST_HausdorffDistance`、`SEDONA.ST_FrechetDistance`,基于米的测地距离函数包括 `SEDONA.ST_DistanceSpheroid` 与 `SEDONA.ST_DistanceSphere`。Snowflake 仅原生支持 `ST_Distance`。
+
+先创建两个仅含一个几何对象的演示表:
+
+```sql
+CREATE OR REPLACE TABLE pointdf2 AS
+SELECT SEDONA.ST_GeomFromText('POINT(0 0)') pointshape;
+```
+
+```sql
+CREATE OR REPLACE TABLE polygondf2 AS
+SELECT SEDONA.ST_GeomFromText('POLYGON((0.5 0.5, 0.5 1, 1 1, 1 0.5, 0.5 0.5))') polygonshape;
+```
+
+平面欧氏距离示例:
+
+`POINT(0 0)` 与 `POINT(0.5 0.5)` 之间的常规 L2 欧氏距离是 0.5 的平方根,因此下面这条查询返回这唯一一对点:
+
+```sql
+SELECT *
+FROM pointdf, pointdf2
+WHERE SEDONA.ST_Distance(pointdf.pointshape,pointdf2.pointshape) <= sqrt(0.5)
+```
+
+两个不相离的多边形之间的 L2 距离为 0,因此下面的查询返回这唯一的多边形对:
+
+```sql
+SELECT *
+FROM polygondf, polygondf2
+WHERE SEDONA.ST_Distance(polygondf.polygonshape,polygondf2.polygonshape) <= 0
+```
+
+但 Hausdorff 与 Fréchet 距离未必如此。下面的两个查询都不会返回任何行:
+
+```sql
+SELECT *
+FROM polygondf, polygondf2
+WHERE SEDONA.ST_HausdorffDistance(polygondf.polygonshape, polygondf2.polygonshape, 0.3) <= 0
+```
+
+```sql
+SELECT *
+FROM polygondf, polygondf2
+WHERE SEDONA.ST_FrechetDistance(polygondf.polygonshape, polygondf2.polygonshape)  <= 0
+```
+
+注意:只有 Hausdorff 距离接受第三个参数 `densityFraction`,取值范围 $(0,1]$,值越小结果越精确。
+
+!!!warning
+	如果使用 `SEDONA.ST_Distance`、`SEDONA.ST_HausdorffDistance` 或 `SEDONA.ST_FrechetDistance` 等平面欧氏距离函数作为谓词,Sedona 不会管理 `distance` 的单位(度还是米),其单位与几何对象保持一致。如果坐标是经纬度,那么 `distance` 的单位应当是度而不是米或英里。要修改几何对象的单位,要么通过 [SEDONA.ST_Transform](../../api/snowflake/vector-data/Spatial-Reference-System/ST_Transform.md) 转换到基于米的坐标系;如果不希望转换数据,请改用 `SEDONA.ST_DistanceSpheroid` 或 `SEDONA.ST_DistanceSphere`。
+
+例如,下面的查询大约返回 78.45 千米——因为即使没有显式设置 CRS,几何对象也被默认视为经纬度:
+
+```sql
+SELECT SEDONA.ST_DistanceSpheroid(pointdf.pointshape,pointdf2.pointshape)
+FROM pointdf, pointdf2
+```
+
+下面这条查询展示了:在 `GEOGRAPHY` 对象上使用 Snowflake 原生的 `ST_DISTANCE` 与 `SEDONA.ST_DistanceSphere`、`SEDONA.ST_DistanceSpheroid` 给出的结果相近:
+
+```sql
+SELECT
+    SEDONA.ST_DistanceSphere(
+        pointdf.pointshape,
+        pointdf2.pointshape
+    ) SEDONA_ST_DistanceSphere,
+    --
+    SEDONA.ST_DistanceSpheroid(
+        pointdf.pointshape,
+        pointdf2.pointshape
+    ) SEDONA_ST_DistanceSpheroid,
+    --
+    ST_DISTANCE(
+        TO_GEOGRAPHY(SEDONA.st_astext(pointdf.pointshape)),
+        TO_GEOGRAPHY(SEDONA.st_astext(pointdf2.pointshape) )
+    ) SFKL_ST_DISTANCE
+FROM pointdf, pointdf2
+```
+
+输出:
+
+| SEDONA_ST_DISTANCESPHERE | SEDONA_ST_DISTANCESPHEROID | SFKL_ST_DISTANCE |
+| ------------------------ | -------------------------- | ---------------- |
+| 78626.28640698           | 78451.248031239            | 78626.311089506  |
+
+## 基于 Google S2 的近似等值连接
+
+可以使用 Sedona 内置的 Google S2 函数来执行近似等值连接。该方法借助 Snowflake 内置的等值连接算法,可以选择跳过 refinement 步骤来牺牲精度换取性能。Sedona 的 S2 函数与 Snowflake 原生的 H3、Geohash 函数互为补充。
+
+操作步骤如下:
+
+### 1. 为两张表生成 S2 ID
+
+使用 [SEDONA.ST_S2CellIds](../../api/snowflake/vector-data/Spatial-Indexing/ST_S2CellIDs.md) 为每个几何对象生成 cell ID,单个几何对象可能产生多个 ID。
+
+```sql
+SELECT * FROM lefts, TABLE(FLATTEN(SEDONA.ST_S2CellIDs(lefts.geom, 15))) s1
+```
+
+```sql
+SELECT * FROM rights, TABLE(FLATTEN(SEDONA.ST_S2CellIDs(rights.geom, 15))) s2
+```
+
+### 2. 执行等值连接
+
+按 S2 cellId 对两张表做等值连接:
+
+```sql
+SELECT lcs.id AS lcs_id, lcs.geom AS lcs_geom, lcs.name AS lcs_name, rcs.id AS rcs_id, rcs.geom AS rcs_geom, rcs.name AS rcs_name
+FROM lcs JOIN rcs ON lcs.cellId = rcs.cellId
+```
+
+### 3. 可选:refine 结果
+
+由于 S2 cellId 的特性,连接结果可能存在少量假阳性,具体程度取决于 S2 level:level 越小,cell 越大,展开行数越少,但假阳性越多。
+
+为保证正确性,可使用任一 [空间谓词](../../api/snowflake/vector-data/Geometry-Functions.md#predicates) 过滤掉假阳性。将下面的查询替换步骤 2 中的查询:
+
+```sql
+SELECT lcs.id AS lcs_id, lcs.geom AS lcs_geom, lcs.name AS lcs_name, rcs.id AS rcs_id, rcs.geom AS rcs_geom, rcs.name AS rcs_name
+FROM lcs, rcs
+WHERE lcs.cellId = rcs.cellId AND ST_Contains(lcs.geom, rcs.geom)
+```
+
+可以看到,相比步骤 2 多了一个 `ST_Contains` 过滤来剔除假阳性,也可以使用 `ST_Intersects` 等其他谓词。
+
+!!!tip
+	如果不要求 100% 准确度且希望更快的查询速度,可以跳过此步骤。
+
+### 4. 可选:去重
+
+由于生成 S2 cellId 时使用了 `Flatten`,结果 DataFrame 中可能出现 `<lcs_geom, rcs_geom>` 重复匹配。可通过 GroupBy 去重:
+
+```sql
+SELECT lcs_id, rcs_id, ANY_VALUE(lcs_geom), ANY_VALUE(lcs_name), ANY_VALUE(rcs_geom), ANY_VALUE(rcs_name)
+FROM joinresult
+GROUP BY (lcs_id, rcs_id)
+```
+
+`ANY_VALUE` 用于在多个重复值中取一个。
+
+如果没有为每个几何对象提供唯一 ID,也可以直接按几何分组:
+
+```sql
+SELECT lcs_geom, rcs_geom, ANY_VALUE(lcs_name), ANY_VALUE(rcs_name)
+FROM joinresult
+GROUP BY (lcs_geom, rcs_geom)
+```
+
+!!!note
+	做点-多边形 join 时不会出现该问题,可以放心忽略;只有 polygon-polygon、polygon-linestring、linestring-linestring 等连接才会遇到此问题。
+
+### 5. 完整示例
+
+下面的查询创建了 2 个感兴趣区域(AOI)和 5 种空间查询(用经纬度边界框表示),分别为两张表生成 S2 覆盖,再以 S2 cell ID 进行合并。可以叠加空间过滤来保证连接结果的精确性。
+
+下例中的几何对象使用 Snowflake 原生 `ST_GeogFromText`(被视作 geography),用 `ST_GeomFromText` 也是可行的。注意 `SEDONA.ST_S2CellIDs` 接受 Snowflake `GEOGRAPHY` 或 `GEOMETRY` 对象以及一个整数 S2 精度。
+
+```sql
+-- lng/lat 顺序:无需翻转坐标
+-- lefts 是感兴趣区域(AOI)
+with lefts AS (
+  SELECT index AS poly_id_left, value AS wkt FROM TABLE(
+      SPLIT_TO_TABLE (
+          'POLYGON ((-74.64966372842101805 44.92318068906040196, -73.05513946490677313 44.92318068906040196, -73.05513946490677313 45.9127817308399031, -74.64966372842101805 45.9127817308399031, -74.64966372842101805 44.92318068906040196))|POLYGON ((-71.72125014775386376 46.58534825561803672, -70.72763860520016976 46.58534825561803672, -70.72763860520016976 47.26007441306773416, -71.72125014775386376 47.26007441306773416, -71.72125014775386376 46.58534825561803672))', '|'
+          )
+      )
+
+),
+-- rights 是查询多边形
+-- 第 1 个多边形与两个 AOI 都相交
+-- 第 2 个多边形包含两个 AOI
+-- 第 3 个多边形被 AOI 1 包含
+-- 第 4 个多边形与 AOI 1 接触
+-- 第 5 个多边形与两个 AOI 都不相交
+rights AS (
+  SELECT index AS poly_id_right, value AS wkt FROM TABLE(
+      SPLIT_TO_TABLE (
+          'POLYGON ((-73.51160030163114811 45.54300066590783302, -71.31736631503980561 45.54300066590783302, -71.31736631503980561 46.82515071005796869, -73.51160030163114811 46.82515071005796869, -73.51160030163114811 45.54300066590783302))|POLYGON ((-75.26522832 44.23585380, -69.77500453509208 44.23585380, -69.77500453509208 47.59180373699041411, -75.26522832 47.59180373699041411, -75.26522832 44.23585380))|POLYGON ((-73.82001807503861812 45.0759163125215423, -73.34011383205873358 45.0 [...]
+          )
+      )
+),
+-- 为两张表的多边形分别计算 S2 cell 覆盖
+-- S2 会对多边形进行离散化,分辨率越高结果越精确,但计算开销也越大
+-- 两张表必须使用相同 level 进行离散化,本例为 10
+lefts_s2 AS (
+    SELECT * FROM lefts, TABLE(FLATTEN(SEDONA.ST_S2CellIDs(ST_GeogFromText(lefts.wkt), 10)))
+),
+rights_s2 AS (
+    SELECT * FROM rights, TABLE(FLATTEN(SEDONA.ST_S2CellIDs(ST_GeogFromText(rights.wkt), 10)))
+)
+-- 按 S2 索引(int)合并,并 group by 以恢复原多边形而非 S2 cell
+-- 加上空间谓词可保证精确性;如果更看重速度可以省略
+-- 预期除第 5 个查询外都会匹配
+-- 期望结果共 6 行:1(接触)+ 1(被包含)+ 2(与两者相交)+ 2(包含两者)
+-- AOI 1(poly_id_left 1)应出现 4 次,AOI 2(poly_id_left 2)应出现 2 次
+SELECT rights_s2.wkt ,  lefts_s2.wkt, LISTAGG(DISTINCT poly_id_right::TEXT) poly_id_right, LISTAGG(DISTINCT poly_id_left::TEXT) poly_id_left
+FROM lefts_s2,rights_s2
+WHERE rights_s2.value = lefts_s2.value AND NOT ST_DISJOINT(ST_GeogFromText(rights_s2.wkt) , ST_GeogFromText( lefts_s2.wkt ) )
+GROUP BY rights_s2.wkt ,  lefts_s2.wkt
+
+```
+
+本例中省略 `not ST_DISJOINT(ST_GeogFromText(rights_s2.wkt) , ST_GeogFromText( lefts_s2.wkt ) )` 也会得到相同的结果。
+
+### 距离连接中的 S2 用法
+
+S2 同样适用于距离连接。先使用 `SEDONA.ST_Buffer(geometry, distance)` 把其中一列原始几何对象包裹起来。如果原始几何是点,`SEDONA.ST_Buffer` 会把它们变成半径为 `distance` 的圆。注意:Snowflake 没有原生的 `ST_Buffer` 函数。
+
+由于坐标位于经纬度系统,`distance` 的单位应为度而不是米或英里。可以通过 `METER_DISTANCE/111000.0` 做近似换算,再过滤掉假阳性。靠近极地或反子午线的数据可能会因此产生不准确的结果。
+
+简而言之,先在左表上跑下面这条查询(替换 `METER_DISTANCE` 为米值),然后在步骤 1 中基于 `buffered_geom` 列生成 S2 ID,再在原始 `geom` 列上执行步骤 2、3、4:
+
+```sql
+SELECT id, geom, SEDONA.ST_Buffer(geom, METER_DISTANCE/111000.0) AS buffered_geom, name
+FROM lefts
+```
+
+## Sedona 独有的函数
+
+Sedona 实现了 200 多个地理空间矢量与栅格函数,远多于 Snowflake 原生函数。例如:
+
+* [SEDONA.ST_3DDistance](../../api/snowflake/vector-data/Measurement-Functions/ST_3DDistance.md)
+* [SEDONA.ST_Force2D](../../api/snowflake/vector-data/Geometry-Editors/ST_Force_2D.md)
+* [SEDONA.ST_GeometryN](../../api/snowflake/vector-data/Geometry-Accessors/ST_GeometryN.md)
+* [SEDONA.ST_MakeValid](../../api/snowflake/vector-data/Geometry-Validation/ST_MakeValid.md)
+* [SEDONA.ST_Multi](../../api/snowflake/vector-data/Geometry-Editors/ST_Multi.md)
+* [SEDONA.ST_NumGeometries](../../api/snowflake/vector-data/Geometry-Accessors/ST_NumGeometries.md)
+* [SEDONA.ST_ReducePrecision](../../api/snowflake/vector-data/Geometry-Processing/ST_ReducePrecision.md)
+* [SEDONA.ST_SubdivideExplode](../../api/snowflake/vector-data/Overlay-Functions/ST_SubDivideExplode.md)
+
+可点击上面的链接深入了解这些函数。更多函数请参考 [SedonaSQL API](../../api/snowflake/vector-data/Overview.md)。
+
+## 与 Snowflake 原生函数互通
+
+Sedona 可以与 Snowflake 原生函数无缝互通,主要有两种方式:
+
+* 使用 `Sedona 函数` 创建 Geometry 列,然后使用 Snowflake 原生函数与 Sedona 函数共同查询;
+* 使用 `Snowflake 原生函数` 创建 Geometry/Geography 列,然后使用 Snowflake 原生函数与 Sedona 函数共同查询。
+
+下面分别进行演示。
+
+### 由 Sedona 几何构造器创建的几何
+
+这种情况下 Sedona 使用 EWKB 作为几何对象的输入/输出类型。如果数据集中已是内置的 Snowflake GEOMETRY/GEOGRAPHY 类型,可以通过该函数轻松转换为 EWKB。
+
+#### 从 Snowflake 原生函数到 Sedona 函数
+
+下例中 `SEDONA.ST_X` 是 Sedona 函数,`ST_GeommetryFromWkt` 与 `ST_AsEWKB` 是 Snowflake 原生函数:
+
+```sql
+SELECT SEDONA.ST_X(ST_AsEWKB(ST_GeometryFromWkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'))) FROM {{geometry_table}};
+```
+
+#### 从 Sedona 函数到 Snowflake 原生函数
+
+下例中 `SEDONA.ST_GeomFromText` 是 Sedona 函数,`ST_AREA` 与 `to_geometry` 是 Snowflake 原生函数:
+
+```sql
+SELECT ST_AREA(to_geometry(SEDONA.ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')));
+```
+
+#### 优点:
+
+Sedona 的几何构造器比 Snowflake 原生函数更强大,主要优势如下:
+
+* Sedona 提供了更多构造器,尤其是用于 3D(XYZ)几何对象的构造器,Snowflake 原生函数不具备。
+* WKB 序列化更高效。如果需要使用多个 Sedona 函数,建议采用此方式,可能带来 2 倍左右的性能提升。
+* 几何对象的 SRID 信息会被保留。下面这种方式会丢失 SRID 信息。
+
+### 由 Snowflake 几何构造器创建的 Geometry / Geography
+
+这种情况下 Sedona 使用 Snowflake 原生 GEOMETRY/GEOGRAPHY 类型作为输入/输出类型,序列化格式为 GeoJSON 字符串。
+
+```sql
+SELECT ST_AREA(SEDONA.ST_Buffer(ST_GeometryFromWkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'), 1));
+```
+
+下例中 `SEDONA.ST_Buffer` 是 Sedona 函数,`ST_GeommetryFromWkt` 与 `ST_AREA` 是 Snowflake 原生函数。
+
+可以看到,Sedona 函数与 Snowflake 原生函数可以混用而无需显式转换。
+
+#### 优点:
+
+* 无需显式转换几何类型,使用更方便。
+
+注意:Snowflake 原生会把 Geometry 类型数据序列化为 GeoJSON 字符串再传给 UDF。GeoJSON 规范不包含 SRID。因此,如果直接混用 Snowflake 函数与 Sedona 函数而不使用 `WKB`,SRID 信息会丢失。
+
+下例中 SRID=4326 信息会丢失:
+
+```sql
+SELECT ST_AsEWKT(SEDONA.ST_SetSRID(ST_GeometryFromWKT('POINT(1 2)'), 4326))
+```
+
+输出:
+
+```
+SRID=0;POINT(1 2)
+```
+
+## 已知问题
+
+1. Sedona Snowflake 不支持 `M` 维度,原因是 WKB 序列化的限制。Sedona Spark 与 Sedona Flink 由于使用了内部专有序列化格式,因此支持 XYZM。Sedona Snowflake 中虽然存在与 `M` 相关的函数,但所有 `M` 值会被忽略。
+2. Sedona H3 函数尚不可用,因为 Snowflake 不允许在 UDF 中嵌入 C 代码。
+3. 由于 Snowflake 当前限制(`Data type GEOMETRY is not supported in non-SQL UDTF return type`),所有用户自定义表函数(UDTF)只能配合 Sedona 构造器创建的几何对象使用,包括:
+   	* SEDONA.ST_MinimumBoundingRadius
+   	* SEDONA.ST_Intersection_Aggr
+   	* SEDONA.ST_SubDivideExplode
+   	* SEDONA.ST_Envelope_Aggr
+   	* SEDONA.ST_Union_Aggr
+   	* SEDONA.ST_Collect
+   	* SEDONA.ST_Dump
+4. Snowflake 中只提供 Sedona 的 ST 函数,栅格函数(RS 函数)暂未支持。