diff --git a/docs/api/sql/CRS-Transformation.zh.md b/docs/api/sql/CRS-Transformation.zh.md new file mode 100644 index 00000000000..b8d4d28f3c5 --- /dev/null +++ b/docs/api/sql/CRS-Transformation.zh.md @@ -0,0 +1,451 @@ + + +# CRS 转换 + +Sedona 通过 `ST_Transform` 函数提供坐标参考系(CRS)转换功能。自 v1.9.0 起,Sedona 使用 proj4sedona 库 —— 一个纯 Java 实现,支持多种 CRS 输入格式以及基于网格的转换。 + +## 支持的 CRS 格式 + +Sedona 支持以下用于指定源 CRS 与目标 CRS 的格式: + +### 权威机构代码(Authority Code) + +最常见的指定 CRS 的方式是使用形如 `AUTHORITY:CODE` 的权威机构代码。Sedona 使用 [spatialreference.org](https://spatialreference.org/projjson_index.json) 作为开源的 CRS 数据库,支持多个权威机构: + +| Authority | 说明 | 示例 | +|-----------|-------------|---------| +| EPSG | European Petroleum Survey Group | `EPSG:4326`, `EPSG:3857` | +| ESRI | Esri 坐标系 | `ESRI:102008`, `ESRI:54012` | +| IAU | International Astronomical Union(行星 CRS) | `IAU:30100` | +| SR-ORG | 用户贡献的定义 | `SR-ORG:6864` | + +```sql +-- Transform from WGS84 (EPSG:4326) to Web Mercator (EPSG:3857) +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'EPSG:3857' +) AS transformed_point +``` + +输出: + +``` +POINT (-13627665.271218014 4548257.702387721) +``` + +```sql +-- Transform using ESRI authority code (North America Albers Equal Area Conic) +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'ESRI:102008' +) AS transformed_point +``` + +```sql +-- Transform from WGS84 to UTM Zone 10N (EPSG:32610) +SELECT ST_Transform( + ST_GeomFromText('POLYGON((-122.5 37.5, -122.5 38.0, -122.0 38.0, -122.0 37.5, -122.5 37.5))'), + 'EPSG:4326', + 'EPSG:32610' +) AS transformed_polygon +``` + +你可以在 [spatialreference.org](https://spatialreference.org/projjson_index.json) 或 [EPSG.io](https://epsg.io/) 上浏览可用的 CRS 代码。 + +### WKT1(OGC Well-Known Text) + +WKT1 是 OGC 提供的 CRS 定义的 Well-Known Text 格式。投影 CRS 以 `PROJCS[...]` 开头,地理 CRS 以 `GEOGCS[...]` 开头。 + +```sql +-- Transform using WKT1 format for target CRS +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'PROJCS["WGS 84 / Pseudo-Mercator", + GEOGCS["WGS 84", + DATUM["WGS_1984", + SPHEROID["WGS 84",6378137,298.257223563]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433]], + PROJECTION["Mercator_1SP"], + PARAMETER["central_meridian",0], + PARAMETER["scale_factor",1], + PARAMETER["false_easting",0], + PARAMETER["false_northing",0], + UNIT["metre",1], + AUTHORITY["EPSG","3857"]]' +) AS transformed_point +``` + +### WKT2(ISO 19162:2019) + +WKT2 是现代的 ISO 19162:2019 标准格式。投影 CRS 以 `PROJCRS[...]` 开头,地理 CRS 以 `GEOGCRS[...]` 开头。 + +```sql +-- Transform using WKT2 format for target CRS +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'PROJCRS["WGS 84 / UTM zone 10N", + BASEGEOGCRS["WGS 84", + DATUM["World Geodetic System 1984", + ELLIPSOID["WGS 84",6378137,298.257223563]]], + CONVERSION["UTM zone 10N", + METHOD["Transverse Mercator"], + PARAMETER["Latitude of natural origin",0], + PARAMETER["Longitude of natural origin",-123], + PARAMETER["Scale factor at natural origin",0.9996], + PARAMETER["False easting",500000], + PARAMETER["False northing",0]], + CS[Cartesian,2], + AXIS["easting",east], + AXIS["northing",north], + UNIT["metre",1], + ID["EPSG",32610]]' +) AS transformed_point +``` + +### PROJ 字符串 + +PROJ 字符串提供一种使用投影参数紧凑地定义 CRS 的方式。它以 `+proj=` 开头。 + +```sql +-- Transform using PROJ string for UTM Zone 10N +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + '+proj=longlat +datum=WGS84 +no_defs', + '+proj=utm +zone=10 +datum=WGS84 +units=m +no_defs' +) AS transformed_point +``` + +```sql +-- Transform using PROJ string for Lambert Conformal Conic +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + '+proj=lcc +lat_1=33 +lat_2=45 +lat_0=39 +lon_0=-96 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs' +) AS transformed_point +``` + +### PROJJSON + +PROJJSON 是 CRS 的 JSON 表示形式,便于在基于 JSON 的工作流中使用。 + +```sql +-- Transform using PROJJSON for target CRS +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + '{ + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 10N", + "base_crs": { + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + {"name": "Longitude", "abbreviation": "lon", "direction": "east", "unit": "degree"}, + {"name": "Latitude", "abbreviation": "lat", "direction": "north", "unit": "degree"} + ] + } + }, + "conversion": { + "name": "UTM zone 10N", + "method": {"name": "Transverse Mercator"}, + "parameters": [ + {"name": "Latitude of natural origin", "value": 0, "unit": "degree"}, + {"name": "Longitude of natural origin", "value": -123, "unit": "degree"}, + {"name": "Scale factor at natural origin", "value": 0.9996}, + {"name": "False easting", "value": 500000, "unit": "metre"}, + {"name": "False northing", "value": 0, "unit": "metre"} + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + {"name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre"}, + {"name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre"} + ] + }, + "id": {"authority": "EPSG", "code": 32610} + }' +) AS transformed_point +``` + +## URL CRS Provider + +自 v1.9.0 起,Sedona 支持从远程 HTTP 服务器解析 CRS 定义。当你需要使用内置数据库中不包含的自定义或内部 CRS 定义,或希望使用你自己的 CRS 定义服务时,这一功能非常有用。 + +配置后,URL 提供者会**先于**内置 CRS 数据库被查询。如果 URL 提供者返回了有效的 CRS 定义,则直接使用该定义。如果 URL 返回 404 或错误,Sedona 会回退到内置定义。 + +### 托管 CRS 定义 + +你可以将自定义 CRS 定义托管在任何 HTTP 可访问的位置。两种常见做法: + +- **GitHub 仓库**:把 CRS 定义文件放在一个公开的 GitHub 仓库中,并使用 raw 内容 URL。这种方式最容易上手 —— 无需自建服务器。 +- **公开的 S3 桶**:把 CRS 定义文件上传到具有公共读权限的 Amazon S3 桶,并使用 S3 静态网站 URL 或 CloudFront 分发地址。 + +每个文件应包含一个 CRS 定义,其格式通过 `spark.sedona.crs.url.format` 指定(PROJJSON、PROJ 字符串、WKT1 或 WKT2)。 + +### 配置 + +在创建 Sedona 会话时设置以下 Spark 配置属性: + +```python +config = ( + SedonaContext.builder() + .config("spark.sedona.crs.url.base", "https://crs.example.com") + .config("spark.sedona.crs.url.pathTemplate", "/{authority}/{code}.json") + .config("spark.sedona.crs.url.format", "projjson") + .getOrCreate() +) +sedona = SedonaContext.create(config) +``` + +在默认的路径模板下,解析 `EPSG:4326` 时会请求: + +``` +https://crs.example.com/epsg/4326.json +``` + +只有 `spark.sedona.crs.url.base` 是必需的。另外两个属性都有合理的默认值(`/{authority}/{code}.json` 与 `projjson`)。 + +### 支持的响应格式 + +| Format 取值 | 说明 | 内容示例 | +|-------------|-------------|----------------| +| `projjson` | PROJJSON(默认) | `{"type": "GeographicCRS", ...}` | +| `proj` | PROJ 字符串 | `+proj=longlat +datum=WGS84 +no_defs` | +| `wkt1` | OGC WKT1 | `GEOGCS["WGS 84", ...]` | +| `wkt2` | ISO 19162 WKT2 | `GEOGCRS["WGS 84", ...]` | + +### 示例:GitHub 仓库 + +假设你有一个 GitHub 仓库 `myorg/crs-definitions`,结构如下: + +``` +crs-definitions/ + epsg/ + 990001.proj + 990002.proj +``` + +其中 `epsg/990001.proj` 包含一条 PROJ 字符串,例如: + +``` ++proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +no_defs +``` + +将 Sedona 指向 GitHub raw 内容 URL: + +```python +config = ( + SedonaContext.builder() + .config( + "spark.sedona.crs.url.base", + "https://raw.githubusercontent.com/myorg/crs-definitions/main", + ) + .config("spark.sedona.crs.url.pathTemplate", "/epsg/{code}.proj") + .config("spark.sedona.crs.url.format", "proj") + .getOrCreate() +) +sedona = SedonaContext.create(config) + +# Resolves EPSG:990001 from: +# https://raw.githubusercontent.com/myorg/crs-definitions/main/epsg/990001.proj +sedona.sql(""" + SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'EPSG:990001' + ) AS transformed_point +""").show() +``` + +### 示例:自建 CRS 服务器 + +```python +config = ( + SedonaContext.builder() + .config("spark.sedona.crs.url.base", "https://crs.mycompany.com") + .config("spark.sedona.crs.url.pathTemplate", "/epsg/{code}.proj") + .config("spark.sedona.crs.url.format", "proj") + .getOrCreate() +) +sedona = SedonaContext.create(config) + +# Now ST_Transform will try https://crs.mycompany.com/epsg/3857.proj +# before falling back to built-in definitions +sedona.sql(""" + SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'EPSG:3857' + ) AS transformed_point +""").show() +``` + +### 示例:自定义权威机构代码 + +URL 提供者对于不存在于任何公开数据库中的自定义或内部权威机构代码尤其有用。使用默认的路径模板 `/{authority}/{code}.json` 时,`{authority}` 占位符会被 CRS 字符串中的权威机构名称(小写)替换: + +```python +config = ( + SedonaContext.builder() + .config("spark.sedona.crs.url.base", "https://crs.mycompany.com") + .config("spark.sedona.crs.url.format", "proj") + .getOrCreate() +) +sedona = SedonaContext.create(config) + +# Resolves MYORG:1001 from: +# https://crs.mycompany.com/myorg/1001.json +sedona.sql(""" + SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + 'EPSG:4326', + 'MYORG:1001' + ) AS transformed_point +""").show() +``` + +### 示例:将几何对象的 SRID 与 URL 提供者一起使用 + +如果几何对象已设置了 SRID(例如通过 `ST_SetSRID`),可以省略源 CRS 参数。此时源 CRS 会从几何对象的 SRID 推导为 EPSG 代码: + +```python +config = ( + SedonaContext.builder() + .config("spark.sedona.crs.url.base", "https://crs.mycompany.com") + .config("spark.sedona.crs.url.format", "proj") + .getOrCreate() +) +sedona = SedonaContext.create(config) + +# The source CRS is taken from the geometry's SRID (4326 → EPSG:4326). +# Only the target CRS string is needed. +sedona.sql(""" + SELECT ST_Transform( + ST_SetSRID(ST_GeomFromText('POINT(-122.4194 37.7749)'), 4326), + 'EPSG:3857' + ) AS transformed_point +""").show() +``` + +### 禁用 URL 提供者 + +要避免启用 URL 提供者,可以省略 `spark.sedona.crs.url.base` 或保持其为空字符串(默认值)。注意,一旦 URL 提供者已在某个 executor JVM 中注册,它在该 JVM 的生命周期内会一直保持激活。 + +参见:[Configuration parameters](Parameter.md#crs-transformation) 中 URL CRS 提供者的完整配置列表。 + +## 网格文件支持 + +网格文件支持高精度的基准面(datum)转换,例如 NAD27 到 NAD83 或 OSGB36 到 ETRS89。Sedona 支持从多种来源加载网格文件。 + +### 网格文件来源 + +可以在 PROJ 字符串中通过 `+nadgrids` 参数指定网格文件: + +| 来源 | 格式 | 示例 | +|--------|--------|---------| +| 本地文件 | 绝对路径 | `+nadgrids=/path/to/grid.gsb` | +| PROJ CDN | `@` 前缀 | `+nadgrids=@us_noaa_conus.tif` | +| HTTPS URL | 完整 URL | `+nadgrids=https://cdn.proj.org/us_noaa_conus.tif` | + +使用 `@` 前缀时,网格文件会自动从 [PROJ CDN](https://cdn.proj.org/) 获取。 + +### 可选网格与必需网格 + +- **`@` 前缀(可选)**:如果该网格不可用,转换会继续进行。当网格能提升精度但并非必需时使用。 +- **无前缀(必需)**:如果找不到该网格文件,会抛出错误。 + +### 使用网格文件的 SQL 示例 + +```sql +-- Transform NAD27 to NAD83 using PROJ CDN grid (optional) +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + '+proj=longlat +datum=NAD27 +no_defs +nadgrids=@us_noaa_conus.tif', + 'EPSG:4269' +) AS transformed_point +``` + +```sql +-- Transform using mandatory grid file (error if not found) +SELECT ST_Transform( + ST_GeomFromText('POINT(-122.4194 37.7749)'), + '+proj=longlat +datum=NAD27 +no_defs +nadgrids=us_noaa_conus.tif', + 'EPSG:4269' +) AS transformed_point +``` + +```sql +-- Transform OSGB36 to ETRS89 using UK grid +SELECT ST_Transform( + ST_GeomFromText('POINT(-0.1276 51.5074)'), + '+proj=longlat +datum=OSGB36 +nadgrids=@uk_os_OSTN15_NTv2_OSGBtoETRS.gsb +no_defs', + 'EPSG:4258' +) AS transformed_point +``` + +## 坐标顺序 + +Sedona 期望几何对象使用 **经度/纬度(lon/lat)** 顺序。如果你的数据是 lat/lon 顺序,请在转换前使用 `ST_FlipCoordinates` 交换坐标。 + +```sql +-- If your data is in lat/lon order, flip first +SELECT ST_Transform( + ST_FlipCoordinates(ST_GeomFromText('POINT(37.7749 -122.4194)')), + 'EPSG:4326', + 'EPSG:3857' +) AS transformed_point +``` + +Sedona 会自动处理 CRS 定义中的坐标顺序,确保源 CRS 与目标 CRS 在内部都使用 lon/lat 顺序。 + +## 使用几何对象的 SRID + +如果几何对象已经设置了 SRID,可以省略源 CRS 参数: + +```sql +-- Set SRID on geometry and transform using only target CRS +SELECT ST_Transform( + ST_SetSRID(ST_GeomFromText('POINT(-122.4194 37.7749)'), 4326), + 'EPSG:3857' +) AS transformed_point +``` + +## 参见 + +- [ST_Transform](Spatial-Reference-System/ST_Transform.md) —— 函数参考 +- [ST_SetSRID](Spatial-Reference-System/ST_SetSRID.md) —— 设置几何对象的 SRID +- [ST_SRID](Spatial-Reference-System/ST_SRID.md) —— 获取几何对象的 SRID +- [ST_FlipCoordinates](Geometry-Editors/ST_FlipCoordinates.md) —— 交换 X 与 Y 坐标 diff --git a/docs/api/sql/DataFrameAPI.zh.md b/docs/api/sql/DataFrameAPI.zh.md new file mode 100644 index 00000000000..e73f56ea1f6 --- /dev/null +++ b/docs/api/sql/DataFrameAPI.zh.md @@ -0,0 +1,98 @@ + + +Sedona SQL 函数可以通过类似于 Spark 函数的 DataFrame 风格 API 使用。 + +下列对象包含了对外暴露的函数:`org.apache.spark.sql.sedona_sql.expressions.st_functions`、`org.apache.spark.sql.sedona_sql.expressions.st_constructors`、`org.apache.spark.sql.sedona_sql.expressions.st_predicates` 以及 `org.apache.spark.sql.sedona_sql.expressions.st_aggregates`。 + +每个函数都可以接收全为 `Column` 类型的参数。此外,重载形式通常还可以接收 `String` 和其他 Scala 类型(例如 `Double`)的混合参数。 + +一般而言适用以下规则(不过具体函数请参考各自的文档以了解可能的例外): + +=== "Scala" + 1. 每个函数都返回一个 `Column`,因此可以与 Spark 函数以及 `DataFrame` 的方法(如 `DataFrame.select` 或 `DataFrame.join`)互换使用。 + 2. 每个函数都有一种全部以 `Column` 作为参数的形式。 + 这种形式是最通用的。 + 3. 大多数函数都有一种以 `String` 参数与其他 Scala 类型混合使用的形式。 + +=== "Python" + + 1. `Column` 类型的参数会被原样透传,并始终被接受。 + 2. `str` 类型的参数总是被视为列名,并会自动包装到 `Column` 中。 + 如果需要传递一个实际的字符串字面量,则需要使用 `pyspark.sql.functions.lit` 把它包装成 `Column`。 + 3. 其他类型的参数会按函数逐一检查。一般来说,能够合理对应 python 原生类型的参数会被接受并透传。 4. 目前所有函数都不接受 Shapely 的 `Geometry` 对象。 + +允许的参数类型组合视具体函数而定。 +不过在这些情况下,所有 `String` 参数都会被视为列名,并自动包装成 `Column`。 +非 `String` 参数会被视为字面量并直接传给对应的 sedona 函数。如果你需要传递 `String` 类型的字面量,应使用 sedona 函数的全 `Column` 形式,并通过 Spark 的 `lit` 函数将该 `String` 字面量包装成 `Column`。 + +下面是使用该 API 的一个简短示例(用到了 Spark 的 `array_min` 和 `array_max` 函数): + +=== "Scala" + + ```scala + val values_df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values") + val min_value = array_min("values") + val max_value = array_max("values") + val point_df = values_df.select(ST_Point(min_value, max_value).as("point")) + ``` + +=== "Python" + + ```python3 + from pyspark.sql import functions as f + + from sedona.spark import * + + df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values") + + min_value = f.array_min("values") + max_value = f.array_max("values") + + df = df.select(ST_Point(min_value, max_value).alias("point")) + ``` + +上面的代码将生成如下 dataframe: + +``` ++-----------+ +|point | ++-----------+ +|POINT (0 2)| ++-----------+ +``` + +某些函数会接受 python 的原生值并将其推断为字面量。 +例如: + +```python3 +from sedona.spark import * + +df = df.select(ST_Point(1.0, 3.0).alias("point")) +``` + +这会生成一个包含常量点的列: + +``` ++-----------+ +|point | ++-----------+ +|POINT (1 3)| ++-----------+ +``` diff --git a/docs/api/sql/NearestNeighbourSearching.zh.md b/docs/api/sql/NearestNeighbourSearching.zh.md new file mode 100644 index 00000000000..15909cb58ef --- /dev/null +++ b/docs/api/sql/NearestNeighbourSearching.zh.md @@ -0,0 +1,203 @@ + + + +Sedona 通过提供地理空间 k 近邻(kNN)连接方法来支持对地理空间数据进行最近邻搜索。该方法基于地理临近性识别给定空间点或区域的 k 个最近邻,通常使用空间坐标以及合适的距离度量(如欧氏距离或大圆距离)。 + +## ST_KNN + +简介:用于在空间数据集中查找一个点或区域的 k 个最近邻的连接操作。 + +格式:`ST_KNN(R: Table, S: Table, k: Integer, use_spheroid: Boolean)` + +其中 R 是查询侧表,S 是对象侧表,K 是邻居数量。use_spheroid 是一个布尔值,决定是否使用椭球距离。 + +查询侧表包含用于在对象侧表中查找 k 近邻的几何对象。 + +当查询数据或对象数据中存在非点几何(其他几何类型)时,会取每个几何对象的质心。 + +当距离上出现并列时,只有在下面的 sedona 配置被设置为 true 时,结果才会包含所有并列的几何对象: + +**关于内连接的说明:** + +- `ST_KNN` 连接仅支持 left inner join。 +- 它只返回那些至少存在一个在 k 近邻范围内的匹配邻居的对。 +- 如果某个查询点没有有效邻居(例如 k 设得太大),它会被从结果中排除。 + +``` +spark.sedona.join.knn.includeTieBreakers=true +``` + +### 谓词下推注意事项: + +在 ST_KNN 之后对结果 DataFrame 施加的过滤条件,部分可能会被下推到 kNN 连接的对象侧。这意味着这些过滤会在 kNN 连接执行之前应用到对象侧的读取阶段。如果你希望过滤在 kNN 连接之后再生效,请先将 kNN 连接的结果物化,然后再应用过滤条件。 + +例如,可以使用以下方式: + +Scala 示例: + +``` +val knnResult = knnJoinDF.cache() +val filteredResult = knnResult.filter(condition) +``` + +SQL 示例: + +``` +CREATE OR REPLACE TEMP VIEW knnResult AS +SELECT * FROM ( + -- Your KNN join SQL here +) AS knnView; +CACHE TABLE knnResult; +SELECT * FROM knnResult WHERE condition; +``` + +### 优化屏障 + +使用 `barrier` 函数可以阻止谓词下推,并在复杂的空间连接中控制谓词的求值顺序。该函数通过在运行时对布尔表达式求值来创建一个优化屏障。 + +`barrier` 函数接收一个作为字符串的布尔表达式,之后是若干对变量名及其取值,这些取值会在该表达式中被替换: + +```sql +barrier(expression, var_name1, var_value1, var_name2, var_value2, ...) +``` + +过滤条件相对于 KNN 连接的放置位置会改变查询的语义: + +- **在 KNN 之前过滤**:先对数据进行过滤,再在过滤后的子集上查找 K 个最近邻。回答的是“评分高的餐厅中,最近的 K 家是哪几家?” +- **在 KNN 之后过滤**:先在全部数据中查找 K 个最近邻,再对结果进行过滤。回答的是“最近的 K 家餐厅中,哪几家是高评分的?” + +### 示例 + +查找距离每家豪华酒店最近的 3 家高评分餐厅,并确保 KNN 连接先完成,再进行过滤。 + +```sql +SELECT + h.name AS hotel, + r.name AS restaurant, + r.rating +FROM hotels AS h +INNER JOIN restaurants AS r +ON ST_KNN(h.geometry, r.geometry, 3, false) +WHERE barrier('rating > 4.0 AND stars >= 4', + 'rating', r.rating, + 'stars', h.stars) +``` + +借助 barrier 函数,这条查询会先为每家酒店找到 3 家最近的餐厅(不考虑评分),随后再过滤,仅保留餐厅评分大于 4.0 且酒店星级不低于 4 的配对。如果不使用 barrier,优化器可能会将过滤下推,将查询改写为先过滤出高评分餐厅与豪华酒店,再在这些过滤后的子集中找最近的 3 个。 + +### 在 ST_KNN 连接中处理 SQL 定义的表: + +在 Sedona 中,如果使用硬编码的 SQL select 语句创建 DataFrame,并随后在 `ST_KNN` 连接中使用它们,Sedona 可能会以绕过 kNN 连接逻辑的方式来优化查询。具体地说,如果你像下面这样用硬编码 SQL 创建 DataFrame: + +```scala +val df1 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom1") +val df2 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom2") + +val df = df1.join(df2, expr("ST_KNN(geom1, geom2, 1)")) +``` + +Sedona 可能会把这次连接优化为类似下面的形式: + +```sql +SELECT ST_KNN(ST_Point(0.0, 0.0), ST_Point(0.0, 0.0), 1) +``` + +结果是,ST_KNN 函数被当作用户自定义函数(UDF)处理,而非一个连接操作,从而阻止 Sedona 走 kNN 连接的执行路径。与典型的 UDF 不同,ST_KNN 函数会跨 DataFrame 作用在多行上,而不是仅作用在单行上。当出现这种情况时,查询会以 UnsupportedOperationException 失败,提示不支持该 KNN 谓词。 + +解决方法: + +为防止 Spark 的优化绕过 kNN 连接逻辑,必须先将由硬编码 SQL select 创建的 DataFrame 物化,再执行连接。可以通过缓存 DataFrame 告诉 Spark 不要进行这种不希望的优化: + +```scala +val df1 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom1").cache() +val df2 = sedona.sql("SELECT ST_Point(0.0, 0.0) as geom2").cache() + +val df = df1.join(df2, expr("ST_KNN(geom1, geom2, 1)")) +``` + +通过 `.cache()` 物化 DataFrame 后,Spark 逻辑计划中会走正确的 kNN 连接路径,避免将 ST_KNN 当成普通 UDF 处理的优化。 + +### SQL 示例 + +假设我们有两张表 `QUERIES` 与 `OBJECTS`,数据如下: + +QUERIES 表: + +``` +ID GEOMETRY NAME +1 POINT(1 1) station1 +2 POINT(10 10) station2 +3 POINT(-0.5 -0.5) station3 +``` + +OBJECTS 表: + +``` +ID GEOMETRY NAME +1 POINT(11 5) bank1 +2 POINT(12 1) bank2 +3 POINT(-1 -1) bank3 +4 POINT(-3 5) bank4 +5 POINT(9 8) bank5 +6 POINT(4 3) bank6 +7 POINT(-4 -5) bank7 +8 POINT(4 -2) bank8 +9 POINT(-3 1) bank9 +10 POINT(-7 3) bank10 +11 POINT(11 5) bank11 +12 POINT(12 1) bank12 +13 POINT(-1 -1) bank13 +14 POINT(-3 5) bank14 +15 POINT(9 8) bank15 +16 POINT(4 3) bank16 +17 POINT(-4 -5) bank17 +18 POINT(4 -2) bank18 +19 POINT(-3 1) bank19 +20 POINT(-7 3) bank20 +``` + +```sql +SELECT + QUERIES.ID AS QUERY_ID, + QUERIES.GEOMETRY AS QUERIES_GEOM, + OBJECTS.GEOMETRY AS OBJECTS_GEOM +FROM QUERIES JOIN OBJECTS ON ST_KNN(QUERIES.GEOMETRY, OBJECTS.GEOMETRY, 4, FALSE) +``` + +输出: + +``` ++--------+-----------------+-------------+ +|QUERY_ID|QUERIES_GEOM |OBJECTS_GEOM | ++--------+-----------------+-------------+ +|3 |POINT (-0.5 -0.5)|POINT (-1 -1)| +|3 |POINT (-0.5 -0.5)|POINT (-1 -1)| +|3 |POINT (-0.5 -0.5)|POINT (-3 1) | +|3 |POINT (-0.5 -0.5)|POINT (-3 1) | +|1 |POINT (1 1) |POINT (-1 -1)| +|1 |POINT (1 1) |POINT (-1 -1)| +|1 |POINT (1 1) |POINT (4 3) | +|1 |POINT (1 1) |POINT (4 3) | +|2 |POINT (10 10) |POINT (9 8) | +|2 |POINT (10 10) |POINT (9 8) | +|2 |POINT (10 10) |POINT (11 5) | +|2 |POINT (10 10) |POINT (11 5) | ++--------+-----------------+-------------+ +``` diff --git a/docs/api/sql/Optimizer.zh.md b/docs/api/sql/Optimizer.zh.md new file mode 100644 index 00000000000..e9be8d04a4f --- /dev/null +++ b/docs/api/sql/Optimizer.zh.md @@ -0,0 +1,418 @@ + + +Sedona 的空间算子完全支持 Apache SparkSQL 的查询优化器。它具备以下查询优化特性: + +* 自动优化范围连接查询和距离连接查询。 +* 自动执行谓词下推。 + +!!! tip + Sedona 连接的性能受分区数影响很大。如果连接性能不理想,请在创建原始 DataFrame 之后执行 `df.repartition(XXX)` 以增加分区数。 + +## 范围连接(Range join) + +简介:从 A 与 B 中查找满足某个谓词的几何对象配对。SedonaSQL 支持的大多数谓词都可以触发范围连接。 + +SQL 示例 + +```sql +SELECT * +FROM polygondf, pointdf +WHERE ST_Contains(polygondf.polygonshape,pointdf.pointshape) +``` + +```sql +SELECT * +FROM polygondf, pointdf +WHERE ST_Intersects(polygondf.polygonshape,pointdf.pointshape) +``` + +```sql +SELECT * +FROM pointdf, polygondf +WHERE ST_Within(pointdf.pointshape, polygondf.polygonshape) +``` + +```sql +SELECT * +FROM pointdf, polygondf +WHERE ST_DWithin(pointdf.pointshape, polygondf.polygonshape, 10.0) +``` + +Spark SQL 物理计划: + +``` +== Physical Plan == +RangeJoin polygonshape#20: geometry, pointshape#43: geometry, false +:- Project [st_polygonfromenvelope(cast(_c0#0 as decimal(24,20)), cast(_c1#1 as decimal(24,20)), cast(_c2#2 as decimal(24,20)), cast(_c3#3 as decimal(24,20)), mypolygonid) AS polygonshape#20] +: +- *FileScan csv ++- Project [st_point(cast(_c0#31 as decimal(24,20)), cast(_c1#32 as decimal(24,20)), myPointId) AS pointshape#43] + +- *FileScan csv + +``` + +!!!note + SedonaSQL 中所有的连接查询都是内连接 + +## 距离连接(Distance join) + +简介:从 A 与 B 中查找彼此之间的距离小于或等于某个阈值的几何对象配对。支持平面欧氏距离计算器 `ST_Distance`、`ST_HausdorffDistance`、`ST_FrechetDistance`,以及基于米制的大地测量距离计算器 `ST_DistanceSpheroid` 与 `ST_DistanceSphere`。 + +平面欧氏距离的 Spark SQL 示例: + +*仅考虑==完全位于某个距离之内==* + +```sql +SELECT * +FROM pointdf1, pointdf2 +WHERE ST_Distance(pointdf1.pointshape1,pointdf2.pointshape2) < 2 +``` + +```sql +SELECT * +FROM pointDf, polygonDF +WHERE ST_HausdorffDistance(pointDf.pointshape, polygonDf.polygonshape, 0.3) < 2 +``` + +```sql +SELECT * +FROM pointDf, polygonDF +WHERE ST_FrechetDistance(pointDf.pointshape, polygonDf.polygonshape) < 2 +``` + +*考虑==与某个距离范围相交==* + +```sql +SELECT * +FROM pointdf1, pointdf2 +WHERE ST_Distance(pointdf1.pointshape1,pointdf2.pointshape2) <= 2 +``` + +```sql +SELECT * +FROM pointDf, polygonDF +WHERE ST_HausdorffDistance(pointDf.pointshape, polygonDf.polygonshape) <= 2 +``` + +```sql +SELECT * +FROM pointDf, polygonDF +WHERE ST_FrechetDistance(pointDf.pointshape, polygonDf.polygonshape) <= 2 +``` + +Spark SQL 物理计划: + +``` +== Physical Plan == +DistanceJoin pointshape1#12: geometry, pointshape2#33: geometry, 2.0, true +:- Project [st_point(cast(_c0#0 as decimal(24,20)), cast(_c1#1 as decimal(24,20)), myPointId) AS pointshape1#12] +: +- *FileScan csv ++- Project [st_point(cast(_c0#21 as decimal(24,20)), cast(_c1#22 as decimal(24,20)), myPointId) AS pointshape2#33] + +- *FileScan csv +``` + +!!!warning + 如果使用 `ST_Distance`、`ST_HausdorffDistance` 或 `ST_FrechetDistance` 等平面欧氏距离函数作为谓词,Sedona 不会管理距离的单位(度或米),它与几何对象保持一致。如果坐标位于经纬度系统下,`distance` 的单位应是度,而不是米或英里。若想更换几何对象的单位,请将坐标参考系转换到基于米的坐标系。参见 [ST_Transform](Spatial-Reference-System/ST_Transform.md)。如果不想转换数据,请考虑使用 `ST_DistanceSpheroid` 或 `ST_DistanceSphere`。 + +基于米制大地测量距离 `ST_DistanceSpheroid` 的 Spark SQL 示例(对 `ST_DistanceSphere` 同样适用): + +*==小于某个距离==* + +```sql +SELECT * +FROM pointdf1, pointdf2 +WHERE ST_DistanceSpheroid(pointdf1.pointshape1,pointdf2.pointshape2) < 2 +``` + +*==小于等于某个距离==* + +```sql +SELECT * +FROM pointdf1, pointdf2 +WHERE ST_DistanceSpheroid(pointdf1.pointshape1,pointdf2.pointshape2) <= 2 +``` + +!!!warning + 如果使用 `ST_DistanceSpheroid` 或 `ST_DistanceSphere` 作为谓词,距离单位为米。目前,使用大地测量距离计算器的距离连接对点数据效果最好。对于非点数据,仅会考虑它们的质心。 + +## 空间左连接(Spatial Left Join) + +简介:以范围连接或距离连接的空间性能来执行左连接。 +这样既可以找到 A 与 B 中满足连接条件的几何对象配对,同时还能保留 A 中那些在 B 中找不到任何匹配几何的记录。 + +范围连接与距离连接==不支持==如下所示的 LEFT JOIN: + +```sql +SELECT a.*, b.* FROM a +LEFT JOIN b ON ST_INTERSECTS(a.geometry, b.geometry) +``` + +这会导致使用 **BroadcastIndexJoin**,在两个大数据集上会非常低效。 +否则就会触发 **BroadcastNestedLoopJoin**,这是最慢的选项。 + +为了利用 Sedona 的空间连接性能,可以通过将一次 INNER JOIN 与一次 LEFT JOIN 组合,来生成左连接的结果。 + +1. 通过内连接,我们收集左侧的 ID 以及右侧所有需要的列(把结果视为 **A'**) +2. 第二步,将左侧 A 与内连接结果 **A'** 合并。 + A 中的所有记录被原样保留,而右侧 B 的记录通过 **A'** 传递出来。 + +```sql +WITH inner_join AS ( + SELECT + dfA.a_id + , dfB.b_id + FROM dfA, dfB + WHERE ST_INTERSECTS(dfA.geometry, dfB.geometry) +) + +SELECT + dfA.*, + inner_join.b_id +FROM dfA +LEFT JOIN inner_join + ON dfA.a_id = inner_join.a_id; +``` + +!!!note + 可以将这种策略定义为一个存储过程或 DBT 宏,以避免重复编写相同的代码。 + +## 广播索引连接(Broadcast index join) + +简介:执行范围连接或距离连接,但将其中一侧广播。这样可以保留非广播侧的分区,避免 shuffle。 + +Sedona 会在被广播的表上构建空间索引。 + +只有当正确的一侧带有 broadcast 提示时,Sedona 才会使用广播连接。 +支持的连接类型——广播侧组合如下: + +* Inner —— 两侧任一,如果两侧都带有提示则优先广播左侧 +* Left semi —— 广播右侧 +* Left anti —— 广播右侧 +* Left outer —— 广播右侧 +* Right outer —— 广播左侧 + +```scala +pointDf.alias("pointDf").join(broadcast(polygonDf).alias("polygonDf"), expr("ST_Contains(polygonDf.polygonshape, pointDf.pointshape)")) +``` + +在 SQL 中指定 broadcast 提示,使用以下语法: + +```sql +SELECT /*+ BROADCAST(polygonDf) */ + pointDf.*, + polygonDf.* +FROM pointDf +JOIN polygonDf + ON ST_Contains(polygonDf.polygonshape, pointDf.pointshape); +``` + +Spark SQL 物理计划: + +``` +== Physical Plan == +BroadcastIndexJoin pointshape#52: geometry, BuildRight, BuildRight, false ST_Contains(polygonshape#30, pointshape#52) +:- Project [st_point(cast(_c0#48 as decimal(24,20)), cast(_c1#49 as decimal(24,20))) AS pointshape#52] +: +- FileScan csv ++- SpatialIndex polygonshape#30: geometry, QUADTREE, [id=#62] + +- Project [st_polygonfromenvelope(cast(_c0#22 as decimal(24,20)), cast(_c1#23 as decimal(24,20)), cast(_c2#24 as decimal(24,20)), cast(_c3#25 as decimal(24,20))) AS polygonshape#30] + +- FileScan csv +``` + +这同样适用于使用 `ST_Distance`、`ST_DistanceSpheroid`、`ST_DistanceSphere`、`ST_HausdorffDistance` 或 `ST_FrechetDistance` 的距离连接: + +```scala +pointDf1.alias("pointDf1").join(broadcast(pointDf2).alias("pointDf2"), expr("ST_Distance(pointDf1.pointshape, pointDf2.pointshape) <= 2")) +``` + +Spark SQL 物理计划: + +``` +== Physical Plan == +BroadcastIndexJoin pointshape#52: geometry, BuildRight, BuildLeft, true, 2.0 ST_Distance(pointshape#52, pointshape#415) <= 2.0 +:- Project [st_point(cast(_c0#48 as decimal(24,20)), cast(_c1#49 as decimal(24,20))) AS pointshape#52] +: +- FileScan csv ++- SpatialIndex pointshape#415: geometry, QUADTREE, [id=#1068] + +- Project [st_point(cast(_c0#48 as decimal(24,20)), cast(_c1#49 as decimal(24,20))) AS pointshape#415] + +- FileScan csv +``` + +注意:如果 distance 是一个表达式,它只会在 ST_Distance 的第一个参数(上例中的 `pointDf1`)上求值。 + +## 自动广播索引连接 + +当参与空间连接的某张表小于阈值时,Sedona 会自动选择广播索引连接而不是 Sedona 优化连接。当前阈值由 [sedona.join.autoBroadcastJoinThreshold](Parameter.md) 控制,默认与 `spark.sql.autoBroadcastJoinThreshold` 保持一致。 + +## 栅格连接(Raster join) + +空间连接的优化同样适用于栅格谓词,例如 `RS_Intersects`、`RS_Contains` 与 `RS_Within`。 + +SQL 示例: + +```sql +-- Raster-geometry join +SELECT df1.id, df2.id, RS_Value(df1.rast, df2.geom) FROM df1 JOIN df2 ON RS_Intersects(df1.rast, df2.geom) + +-- Raster-raster join +SELECT df1.id, df2.id FROM df1 JOIN df2 ON RS_Intersects(df1.rast, df2.rast) +``` + +这些查询可被规划为 RangeJoin 或 BroadcastIndexJoin。下面是一个使用 RangeJoin 的物理计划示例: + +``` +== Physical Plan == +*(1) Project [id#14, id#25] ++- RangeJoin rast#13: raster, geom#24: geometry, INTERSECTS, **org.apache.spark.sql.sedona_sql.expressions.RS_Intersects** + :- LocalTableScan [rast#13, id#14] + +- LocalTableScan [geom#24, id#25] +``` + +## 基于 Google S2 的近似等值连接 + +如果 Sedona 优化连接性能不理想(可能由复杂且相互重叠的几何对象导致),可以借助 Sedona 内置的、基于 Google S2 的近似等值连接。该等值连接利用 Spark 内部的等值连接算法,并且如果你愿意牺牲一些查询精度跳过精化步骤,性能可能更佳。 + +请按以下步骤操作: + +### 1. 为两张表生成 S2 id + +使用 [ST_S2CellIDs](Spatial-Indexing/ST_S2CellIDs.md) 生成 cell ID。每个几何对象可能产生一个或多个 ID。 + +```sql +SELECT id, geom, name, explode(ST_S2CellIDs(geom, 15)) as cellId +FROM lefts +``` + +```sql +SELECT id, geom, name, explode(ST_S2CellIDs(geom, 15)) as cellId +FROM rights +``` + +### 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. 可选:精化结果 + +由于 S2 Cellid 的特性,依据所选 S2 层级的不同,等值连接结果中可能会有少量误报。层级越小,cell 越大,膨胀(exploded)后的行数越少,但误报越多。 + +为保证正确性,可以使用 [空间谓词](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 Cell Ids 时使用了 explode 函数,结果 DataFrame 中可能会有若干重复的 匹配。可以通过 GroupBy 查询移除它们。 + +```sql +SELECT lcs_id, rcs_id, first(lcs_geom), first(lcs_name), first(rcs_geom), first(rcs_name) +FROM joinresult +GROUP BY (lcs_id, rcs_id) +``` + +`first` 函数用于在一组重复值中取第一个。 + +如果你没有每个几何对象的唯一 id,也可以按几何对象本身分组。见下: + +```sql +SELECT lcs_geom, rcs_geom, first(lcs_name), first(rcs_name) +FROM joinresult +GROUP BY (lcs_geom, rcs_geom) +``` + +!!!note + 如果你做的是 point-in-polygon 连接,则不存在此问题,可以放心忽略。该问题仅在 polygon-polygon、polygon-linestring、linestring-linestring 连接中出现。 + +### S2 用于距离连接 + +这也适用于距离连接。你需要先用 `ST_Buffer(geometry, distance)` 包装其中一个原始几何列。如果原始几何列是点,这次 `ST_Buffer` 会将它们变成半径为 `distance` 的圆。 + +由于坐标位于经纬度系统下,`distance` 的单位应是度而非米或英里。可以通过 `METER_DISTANCE/111000.0` 得到近似值,然后过滤掉误报。注意,当数据接近极点或反子午线时,这种做法可能导致不准确的结果。 + +简而言之,在第 1 步之前先对左表运行下面的查询。请将 `METER_DISTANCE` 替换为以米为单位的距离。在第 1 步中,基于 `buffered_geom` 列生成 S2 ID。然后在原始的 `geom` 列上运行第 2、3、4 步。 + +```sql +SELECT id, geom, ST_Buffer(geom, METER_DISTANCE/111000.0) as buffered_geom, name +FROM lefts +``` + +## 常规空间谓词下推 + +简介:当同一个 WHERE 子句中同时包含一个连接查询和一个谓词时,先将谓词作为过滤条件执行,再执行连接查询。 + +SQL 示例 + +```sql +SELECT * +FROM polygondf, pointdf +WHERE ST_Contains(polygondf.polygonshape,pointdf.pointshape) +AND ST_Contains(ST_PolygonFromEnvelope(1.0,101.0,501.0,601.0), polygondf.polygonshape) +``` + +Spark SQL 物理计划: + +``` +== Physical Plan == +RangeJoin polygonshape#20: geometry, pointshape#43: geometry, false +:- Project [st_polygonfromenvelope(cast(_c0#0 as decimal(24,20)), cast(_c1#1 as decimal(24,20)), cast(_c2#2 as decimal(24,20)), cast(_c3#3 as decimal(24,20)), mypolygonid) AS polygonshape#20] +: +- Filter **org.apache.spark.sql.sedona_sql.expressions.ST_Contains$** +: +- *FileScan csv ++- Project [st_point(cast(_c0#31 as decimal(24,20)), cast(_c1#32 as decimal(24,20)), myPointId) AS pointshape#43] + +- *FileScan csv +``` + +## 将空间谓词下推到 GeoParquet + +Sedona 支持对 GeoParquet 文件进行空间谓词下推。当对由 GeoParquet 文件支撑的 dataframe 应用空间过滤时,Sedona 会利用 +[元数据中的 `bbox` 属性](https://github.com/opengeospatial/geoparquet/blob/v1.0.0-beta.1/format-specs/geoparquet.md#bbox) +来判断该文件中的全部数据是否会被该空间谓词过滤掉。当 GeoParquet 数据集按空间临近性进行分区时, +这种优化可以减少需要扫描的文件数量。 + +为了最大化 Sedona 对 GeoParquet 的过滤下推性能,建议按几何对象的 geohash 值(参见 [ST_GeoHash](Geometry-Output/ST_GeoHash.md))对数据排序后再保存为 GeoParquet 文件。示例如下: + +``` +SELECT col1, col2, geom, ST_GeoHash(geom, 5) as geohash +FROM spatialDf +ORDER BY geohash +``` + +下图展示了一个 GeoParquet 数据集的可视化。所有 GeoParquet 文件的 `bbox` 以蓝色矩形绘制,查询窗口以红色矩形绘制。Sedona 只会扫描 6 个文件中的 1 个就能 +回答类似 `SELECT * FROM geoparquet_dataset WHERE ST_Intersects(geom, )` 的查询,因此只需要扫描浅绿色矩形覆盖的部分数据。 + +![Visualization of a GeoParquet dataset](../../image/geoparquet-pred-pushdown.png "Visualization of a GeoParquet dataset") + +我们可以对比启用与不启用空间谓词时查询 GeoParquet 数据集的指标,可以观察到使用空间谓词时扫描的行数明显减少。 + +| 不使用空间谓词 | 使用空间谓词 | +|-------------------------------------------------------------------------------------------------| ----------- | +| ![Scan geoparquet without spatial predicate](../../image/scan-parquet-without-spatial-pred.png) | ![Scan geoparquet with spatial predicate](../../image/scan-parquet-with-spatial-pred.png) | + +将空间谓词下推到 GeoParquet 默认启用。用户可以通过将 Spark 配置 `spark.sedona.geoparquet.spatialFilterPushDown` 设置为 `false` 来手动禁用。 diff --git a/docs/api/sql/Overview.zh.md b/docs/api/sql/Overview.zh.md new file mode 100644 index 00000000000..a72e8e5a163 --- /dev/null +++ b/docs/api/sql/Overview.zh.md @@ -0,0 +1,97 @@ + + +# 简介 + +## 函数列表 + +SedonaSQL 支持 SQL/MM Part3 空间 SQL 标准,包含以下四种 SQL 算子。所有这些算子都可以通过下面的方式直接调用: + +```scala +var myDataFrame = sedona.sql("YOUR_SQL") +``` + +此外,也可以使用 `expr` 和 `selectExpr` 调用: + +```scala +myDataFrame.withColumn("geometry", expr("ST_*")).selectExpr("ST_*") +``` + +* 构造函数(Constructor):根据输入的字符串或坐标构造一个 Geometry + * 示例:ST_GeomFromWKT (string)。根据 WKT 字符串创建一个 Geometry。 + * 文档:[点击查看](Geometry-Functions.md) +* 函数(Function):在指定的一列或多列上执行某个函数 + * 示例:ST_Distance (A, B)。给定两个 Geometry A 和 B,返回 A 与 B 的欧氏距离。 + * 文档:函数按类别组织。参见 [Geometry Accessors](Geometry-Functions.md)、[Geometry Editors](Geometry-Functions.md)、[Measurement Functions](Geometry-Functions.md#measurement-functions)、[Geometry Processing](Geometry-Functions.md)、[Overlay Functions](Geometry-Functions.md#overlay-functions),以及侧边栏中的其他分类。 +* 聚合函数(Aggregate function):对给定列返回聚合后的值 + * 示例:ST_Envelope_Aggr (Geometry column)。给定一个 Geometry 列,计算该列所有几何对象的总外接包络。 + * 文档:[点击查看](Geometry-Functions.md#aggregate-functions) +* 谓词(Predicate):在给定的列上执行逻辑判断,返回 true 或 false + * 示例:ST_Contains (A, B)。检查 A 是否完全包含 B。如果是则返回 "True",否则返回 "False"。 + * 文档:[点击查看](Geometry-Functions.md#predicates) + +Sedona 还提供了一个 Adapter 用于在 SpatialRDD 与 DataFrame 之间相互转换。详见 [Adapter Scaladoc](../../scaladoc/spark/org/apache/sedona/sql/utils/index.html) + +SedonaSQL 支持 SparkSQL 查询优化器,相关文档见[此处](Optimizer.md) + +## 栅格函数列表 + +SedonaSQL 同样支持栅格数据处理。栅格函数以 `RS_` 为前缀。所有栅格算子的调用方式与矢量算子相同: + +```scala +var myDataFrame = sedona.sql("YOUR_SQL") +``` + +* 构造函数(Constructor):根据输入的文件或参数构造一个 Raster + * 示例:RS_FromGeoTiff (binary)。从一个 GeoTiff 二进制数据创建一个 Raster。 + * 文档:[点击查看](Raster-Functions.md#raster-constructors) +* 函数(Function):在指定的一列或多列 Raster 上执行某个函数 + * 示例:RS_Value (raster, point)。给定一个 Raster 和一个 Point 几何对象,返回该位置上的像元值。 + * 文档:函数按类别组织。参见 [Raster Accessors](Raster-Functions.md#raster-accessors)、[Raster Operators](Raster-Functions.md#raster-operators)、[Raster Band Accessors](Raster-Functions.md#raster-band-accessors)、[Raster Output](Raster-Functions.md#raster-output),以及侧边栏中的其他分类。 +* 聚合函数(Aggregate function):对给定的 Raster 列返回聚合后的值 + * 示例:RS_Union_Aggr (Raster column)。给定一个 Raster 列,将所有 Raster 合并为单个多波段栅格。 + * 文档:[点击查看](Raster-Functions.md#raster-aggregate-functions) +* 谓词(Predicate):在给定的列上执行逻辑判断,返回 true 或 false + * 示例:RS_Intersects (raster, geometry)。判断一个栅格是否与一个几何对象相交。如果是则返回 "True",否则返回 "False"。 + * 文档:[点击查看](Raster-Functions.md#raster-predicates) + +## 栅格快速入门 + +详细说明请参见[编写栅格 DataFrame/SQL 应用](../../tutorial/raster.md)。 + +## 快速入门 + +详细说明请参见[编写 SQL/DataFrame 应用](../../tutorial/sql.md)。 + +1. 在你的项目 pom.xml 或 build.sbt 中加入 Sedona-core 和 Sedona-SQL +2. 如果需要定制 SparkSession,创建你自己的 Sedona 配置。 + +```scala +import org.apache.sedona.spark.SedonaContext +val config = SedonaContext.builder(). + master("local[*]").appName("SedonaSQL") + .getOrCreate() +``` + +3. 在 Sedona context 声明之后加上以下代码: + +```scala +import org.apache.sedona.spark.SedonaContext +val sedona = SedonaContext.create(config) +``` diff --git a/docs/api/sql/Parameter.zh.md b/docs/api/sql/Parameter.zh.md new file mode 100644 index 00000000000..2d71f8f9b8a --- /dev/null +++ b/docs/api/sql/Parameter.zh.md @@ -0,0 +1,84 @@ + + +## 使用方式 + +SedonaSQL 支持多种参数。修改它们的取值有以下几种方式: + +1. 通过 SparkConf 设置: + +```scala +sparkSession = SparkSession.builder(). + config("spark.serializer","org.apache.spark.serializer.KryoSerializer"). + config("spark.kryo.registrator", "org.apache.sedona.core.serde.SedonaKryoRegistrator"). + config("sedona.global.index","true") + master("local[*]").appName("mySedonaSQLdemo").getOrCreate() +``` + +2. 查看当前的 SedonaSQL 配置: + +```scala +val sedonaConf = new SedonaConf(sparkSession.conf) +println(sedonaConf) +``` + +3. Sedona 参数可以在运行时动态修改: + +```scala +sparkSession.conf.set("sedona.global.index","false") +``` + +此外,你也可以在参数名前添加 `spark` 前缀,例如: + +```scala +sparkSession.conf.set("spark.sedona.global.index","false") +``` + +不过,所有通过 `spark` 前缀设置的参数都会被 Spark 所识别,这意味着你可以提前通过 `spark-defaults.conf` 或 Spark on Kubernetes 的配置来设定这些参数。 + +如果同一个参数同时通过 `sedona` 和 `spark.sedona` 两种前缀进行设置,则 `sedona` 前缀设置的值会覆盖 `spark.sedona` 前缀设置的值。 + +## 通用参数 + +| 参数 | 说明 | 默认值 | 可选值 | +| :--- | :--- | :--- | :--- | +| `sedona.global.index` | 是否启用空间索引(目前仅支持 SQL 范围连接与 SQL 距离连接) | `true` | `true`, `false` | +| `sedona.global.indextype` | 空间索引类型,仅在 `sedona.global.index` 为 true 时生效 | `rtree` | `rtree`, `quadtree` | +| `spark.sedona.enableParserExtension` | 启用解析器扩展,用于在 SQL DDL 语句中解析 GEOMETRY 数据类型 | `true` | `true`, `false` | + +## 连接(Join)相关参数 + +| 参数 | 说明 | 默认值 | 可选值 | +| :--- | :--- | :--- | :--- | +| `sedona.join.autoBroadcastJoinThreshold` | 执行连接时,将被广播到所有工作节点的表的最大字节大小。设为 -1 可禁用自动广播。 | 与 `spark.sql.autoBroadcastJoinThreshold` 相同 | 带有字节后缀的整数,例如 `10MB` 或 `512KB` | +| `sedona.join.gridtype` | 连接查询使用的空间分区网格类型 | `kdbtree` | `quadtree`, `kdbtree` | +| `spark.sedona.join.knn.includeTieBreakers` | KNN 连接结果是否包含所有并列项,可能返回多于 k 个结果 | `false` | `true`, `false` | +| `sedona.join.indexbuildside` | **(高级)** Sedona 在哪一侧构建空间索引 | `left` | `left`, `right` | +| `sedona.join.numpartition` | **(高级)** 连接查询双方的分区数 | `-1`(使用现有分区数) | 任意整数 | +| `sedona.join.spatitionside` | **(高级)** 空间分区阶段的主导侧 | `left` | `left`, `right` | +| `sedona.join.optimizationmode` | **(高级)** Sedona 何时优化空间连接 SQL 查询 | `nonequi` | `all`(始终优化,包括等值连接),`none`(禁用优化),`nonequi`(仅优化非等值连接) | + +## CRS 转换参数 + +| 参数 | 说明 | 默认值 | 可选值 | 起始版本 | +| :--- | :--- | :--- | :--- | :--- | +| `spark.sedona.crs.geotools` | 控制 ST_Transform 中 CRS 转换所使用的库 | `raster` | `none`(全部使用 proj4sedona),`raster`(矢量使用 proj4sedona,栅格使用 GeoTools),`all`(全部使用 GeoTools —— 旧版行为) | v1.9.0 | +| `spark.sedona.crs.url.base` | CRS 定义服务器的基础 URL,用于通过 HTTP 解析权威机构代码(如 EPSG)。设置后,ST_Transform 会先查询该 URL 提供者,再回退到内置定义。 | _(空 —— 禁用)_ | 例如 `https://crs.example.com` | v1.9.0 | +| `spark.sedona.crs.url.pathTemplate` | 附加在 `spark.sedona.crs.url.base` 之后的 URL 路径模板。占位符 `{authority}` 和 `{code}` 会在运行时被替换。 | `/{authority}/{code}.json` | 例如 `/epsg/{code}.json` | v1.9.0 | +| `spark.sedona.crs.url.format` | URL 提供者返回的 CRS 定义格式 | `projjson` | `projjson`、`proj`、`wkt1`(OGC WKT1)、`wkt2`(ISO 19162 WKT2) | v1.9.0 | diff --git a/docs/api/sql/Raster-affine-transformation.zh.md b/docs/api/sql/Raster-affine-transformation.zh.md new file mode 100644 index 00000000000..c29430e097a --- /dev/null +++ b/docs/api/sql/Raster-affine-transformation.zh.md @@ -0,0 +1,85 @@ + + +本页介绍 Sedona Raster 中仿射变换(Affine Transformation)的基本概念。 + +![Raster_Affine_Transformation](../../image/Raster_Affine_Transformation/Raster_Affine_Transformation.svg "Raster Affine Transformation") + +## 仿射变换 + +仿射变换是计算机图形学、几何与图像处理中的一个基础概念,指以保持直线性与平行性(但不一定保持距离与角度)的方式对对象进行变换。这类变换是一种线性变换叠加上一次平移,因此能够在不改变点、线、面相对排列的前提下,对对象进行平移、缩放、旋转与剪切。 + +### 仿射变换的组成 + +仿射变换可以表示为矩阵运算。在二维空间中,典型的仿射变换矩阵是一个 3x3 矩阵,如下所示: + +``` +| ScaleX SkewX TranslationX | +| SkewY ScaleY TranslationY | +| 0 0 1 | +``` + +这里,`ScaleX、ScaleY、SkewX、SkewY、TranslationX` 和 `TranslationY` 是定义该变换的参数: + +- `ScaleX` 和 `ScaleY` 分别是 x 轴和 y 轴方向的缩放因子。 +- `SkewX` 和 `SkewY` 引入剪切效果,使形状产生“倾斜”。 +- `TranslationX` 和 `TranslationY` 是平移参数,分别将形状沿 x 与 y 方向移动。 + +### 仿射变换的类型 + +1. **平移(Translation)**:将图形或空间中的每个点沿指定方向移动相同的距离。主要影响 `TranslationX` 与 `TranslationY` 分量。 + +2. **缩放(Scaling)**:将每个点的坐标乘以一个常数(x 轴方向使用 ScaleX,y 轴方向使用 ScaleY),从而放大或缩小其尺寸。缩放可以是均匀的(两个轴使用相同因子),也可以是非均匀的(两轴因子不同)。 + +3. **旋转(Rotation)**:围绕某一点(通常是原点或指定点)对对象进行旋转。可以通过 `ScaleX、ScaleY、SkewX` 与 `SkewY` 的组合来表示,这些参数由旋转角的余弦和正弦推导得到。 + +4. **剪切(Shearing)**:将平行线变换为仍然平行的直线,但移动它们的位置,使其不再垂直于原来的方向。影响 `SkewX` 与 `SkewY` 分量。 + +5. **反射(Reflection)**:沿指定轴翻转对象,可通过缩放与旋转的组合来实现。 + +### 数学性质 + +- **共线性与共点性**:仿射变换保持点的共线性(点位于同一直线上)以及直线的共点性(直线的交点)。 +- **线段比例**:仿射变换还保持同一直线上各点之间距离的比例。 + +## 仿射变换的组成 + +在仿射变换中(这是处理图形、图像与几何数据的基础),**ScaleX**、**ScaleY**、**SkewX** 和 **SkewY** 这几个术语分别指代会改变对象形状与位置的特定变换: + +### ScaleX 与 ScaleY + +- **ScaleX**:表示沿 x 轴的缩放因子。它改变图像或对象的宽度。大于 1 的取值会增大宽度,小于 1 的取值会减小宽度,负值则会在缩放的同时将对象沿 x 轴反射。 + +- **ScaleY**:表示沿 y 轴的缩放因子。它影响对象的高度。与 ScaleX 类似,大于 1 的取值会沿垂直方向放大对象,小于 1 的取值会缩小对象,负值则会沿 y 轴翻转对象。 + +### SkewX 与 SkewY + +- **SkewX**:用于沿 x 轴方向对对象进行剪切或扭曲。它按每个点的 y 坐标的比例平移其 x 坐标,从而产生倾斜效果。该变换可用于在 2D 表达中营造深度或透视的视觉效果。 + +- **SkewY**:与 SkewX 相对应,SkewY 沿 y 轴方向对对象进行剪切。它根据每个点的 x 坐标改变其 y 坐标,同样产生倾斜效果,但方向是垂直的。 + +这些变换通常组合在一个变换矩阵中使用,使它们能够以协同且一致的方式作用于对象。下面是这种矩阵的典型表达: + +``` +| ScaleX SkewX TranslationX | +| SkewY ScaleY TranslationY | +| 0 0 1 | +``` + +这些参数可以以不同方式组合,从而在 2D 与 3D 图形应用中对图像或形状执行旋转、平移、缩放与剪切等复杂变换。 diff --git a/docs/api/sql/Raster-map-algebra.zh.md b/docs/api/sql/Raster-map-algebra.zh.md new file mode 100644 index 00000000000..eb5b6e35c5d --- /dev/null +++ b/docs/api/sql/Raster-map-algebra.zh.md @@ -0,0 +1,145 @@ + + +## 地图代数(Map Algebra) + +地图代数是一种使用数学表达式来执行栅格计算的方法。表达式可以是简单的算术运算,也可以是多种运算的复杂组合。该表达式可以作用在单个栅格波段,也可以作用在多个栅格波段上。表达式的结果是一个新的栅格。 + +!!!tip + 要在 Jupyter notebook 中直观地查看地图代数运算的结果,可以使用 `SedonaUtils.display_image(df)`。它会自动识别栅格列并以图像形式渲染。详情请参阅 [Raster Output 文档](Raster-Functions.md#raster-output)。 + +Apache Sedona 提供了两种执行地图代数运算的方法: + +1. 使用 `RS_MapAlgebra` 函数。 +2. 使用 `RS_BandAsArray` 及基于数组的地图代数函数,例如 `RS_Add`、`RS_Multiply` 等。 + +一般来说,`RS_MapAlgebra` 函数更灵活,可用于执行更复杂的运算。该函数接收 3 到 4 个参数: + +```sql +RS_MapAlgebra(rast: Raster, pixelType: String, script: String, [noDataValue: Double]) +``` + +* `rast`:要应用地图代数表达式的栅格。 +* `pixelType`:输出栅格的数据类型。可选值为 `D`(double)、`F`(float)、`I`(integer)、`S`(short)、`US`(unsigned short)或 `B`(byte)。如果指定为 `NULL`,则输出栅格与输入栅格使用相同的数据类型。 +* `script`:地图代数脚本。[格式的更多说明请参见此处。](https://github.com/geosolutions-it/jai-ext/wiki/Jiffle) +* `noDataValue`:(可选)输出栅格的 nodata 值。 + +从 `v1.5.1` 版本起,`RS_MapAlgebra` 函数支持两个栅格列输入,并支持多波段栅格。该函数接受 5 个参数: + +```sql +RS_MapAlgebra(rast0: Raster, rast1: Raster, pixelType: String, script: String, noDataValue: Double) +``` + +* `rast0`:第一个要应用地图代数表达式的栅格。 +* `rast1`:第二个要应用地图代数表达式的栅格。 +* `pixelType`:输出栅格的数据类型。可选值为 `D`(double)、`F`(float)、`I`(integer)、`S`(short)、`US`(unsigned short)或 `B`(byte)。如果指定为 `NULL`,则输出栅格与输入栅格使用相同的数据类型。 +* `script`:地图代数脚本。[格式的更多说明请参见此处。](https://github.com/geosolutions-it/jai-ext/wiki/Jiffle) +* `noDataValue`:(非可选)输出栅格的 nodata 值,允许传 `null`。 + +双栅格输入的 `RS_MapAlgebra` 的 Spark SQL 示例: + +```sql +RS_MapAlgebra(rast0, rast1, 'D', 'out = rast0[0] * 0.5 + rast1[0] * 0.5;', null) +``` + +`RS_MapAlgebra` 的性能也很不错,因为它由 [Jiffle](https://github.com/geosolutions-it/jai-ext/wiki/Jiffle) 提供支持,可以被编译为 Java 字节码以 +执行。下面我们将演示如何用这两种方式实现常见的地图代数运算。 + +!!!Note + `RS_MapAlgebra` 函数可以将输出栅格转换为 `pixelType` 指定的另一种数据类型: + + - 如果 `pixelType` 比输入栅格的数据类型更小,会执行窄化转换,可能导致数据丢失。 + + - 如果 `pixelType` 更大,会执行扩宽转换,从而保留数据精度。 + + - 如果 `pixelType` 与输入栅格的数据类型一致,则不进行任何转换。 + + 这使得用户可以控制输出像素的数据类型。在强制转换为较小类型时,应注意可能带来的精度影响。 + +### NDVI + +归一化植被指数(NDVI, Normalized Difference Vegetation Index)是一种简单的图形化指标,可用于分析遥感测量数据(通常但并非必然来自太空平台),评估目标区域是否含有活的绿色植被。NDVI 已成为判断给定区域是否含有活绿色植被的事实标准。NDVI 的计算公式如下: + +``` +NDVI = (NIR - Red) / (NIR + Red) +``` + +其中 NIR 表示近红外波段,Red 表示红光波段。 + +假设我们有若干个 4 波段的栅格,分别对应红、绿、蓝、近红外四个波段。我们希望为每个栅格计算 NDVI。可以使用 `RS_MapAlgebra` 函数来实现: + +```sql +SELECT RS_MapAlgebra(rast, 'D', 'out = (rast[3] - rast[0]) / (rast[3] + rast[0]);') as ndvi FROM raster_table +``` + +这里的 Jiffle 脚本是 `out = (rast[3] - rast[0]) / (rast[3] + rast[0]);`。`rast` 变量始终绑定到输入栅格, +而 `out` 变量绑定到输出栅格。Jiffle 会迭代输入栅格的每个像素,并为每个像素执行该脚本。`rast[3]` 与 `rast[0]` +分别指代当前像素中近红外波段和红光波段的取值,`out` 变量则是当前的输出像素值。 + +`RS_MapAlgebra` 函数的结果是一个单波段栅格。由于我们将 `pixelType` 指定为 `D`,该波段的类型为 double。 + +也可以使用基于数组的地图代数函数实现相同的 NDVI 计算: + +```sql +SELECT RS_Divide( + RS_Subtract(RS_BandAsArray(rast, 1), RS_BandAsArray(rast, 4)), + RS_Add(RS_BandAsArray(rast, 1), RS_BandAsArray(rast, 4))) as ndvi FROM raster_table +``` + +`RS_BandAsArray` 函数会将输入栅格中指定的波段提取为一个 double 数组,而 `RS_Add`、`RS_Subtract` 与 `RS_Divide` 函数则在数组上执行算术运算。使用基于数组的地图代数函数的代码相对更冗长。不过,还有一个 `RS_NormalizedDifference` 函数可以更简洁地计算 NDVI: + +```sql +SELECT RS_NormalizedDifference(RS_BandAsArray(rast, 1), RS_BandAsArray(rast, 4)) as ndvi FROM raster_table +``` + +基于数组的地图代数函数的结果是一个 double 数组。用户可以使用 `RS_AddBandFromArray` 将该数组作为新的波段添加到栅格中。 + +### AWEI + +自动水体提取指数(AWEI, Automated Water Extraction Index)是一种光谱指数,可用于从遥感影像中提取水体。AWEI 的计算公式如下: + +``` +AWEI = 4 * (Green - SWIR2) - (0.25 * NIR + 2.75 * SWIR1) +``` + +使用 `RS_MapAlgebra` 可以很容易地实现 AWEI: + +```sql +-- Assume that the raster includes all 13 Sentinel-2 bands +SELECT RS_MapAlgebra(rast, 'D', 'out = 4 * (rast[2] - rast[11]) - (0.25 * rast[7] + 2.75 * rast[12]);') as awei FROM raster_table +``` + +也可以使用基于数组的地图代数函数实现同样的 AWEI 计算,代码看起来更冗长: + +```sql +SELECT RS_Subtract( + RS_Add(RS_MultiplyFactor(band_nir, 0.25), RS_MultiplyFactor(band_swir1, 2.75)), + RS_MultiplyFactor(RS_Subtract(band_swir2, band_green), 4)) as awei +FROM ( +SELECT RS_BandAsArray(rast, 3) AS band_green, + RS_BandAsArray(rast, 12) AS band_swir2, + RS_BandAsArray(rast, 13) AS band_swir1, + RS_BandAsArray(rast, 8) AS band_nir +FROM raster_table) t +``` + +### 延伸阅读 + +* [Jiffle language summary](https://github.com/geosolutions-it/jai-ext/wiki/Jiffle---language-summary) +* [Raster operators](Raster-Functions.md#raster-operators) diff --git a/docs/api/sql/Reading-legacy-parquet.zh.md b/docs/api/sql/Reading-legacy-parquet.zh.md new file mode 100644 index 00000000000..8c66199d544 --- /dev/null +++ b/docs/api/sql/Reading-legacy-parquet.zh.md @@ -0,0 +1,72 @@ + + +由于 Apache Sedona 1.4.0 对 `GeometryUDT` 的 SQL 类型 +([SEDONA-205](https://issues.apache.org/jira/browse/SEDONA-205))以及几何值的序列化格式 +([SEDONA-207](https://issues.apache.org/jira/browse/SEDONA-207))引入了破坏性变更,因此由 Apache Sedona 1.3.1 或更早版本写出的、包含几何列的 Parquet 文件无法被 Apache Sedona 1.4.0 或更高版本直接读取。 + +对于在 Apache Sedona 1.3.1-incubating 或更早版本下、使用 `"parquet"` 格式写出的 parquet 文件: + +```python +df.write.format("parquet").save("path/to/parquet/files") +``` + +如果用 Apache Sedona 1.4.0 或更高版本通过 `spark.read.format("parquet").load("path/to/parquet/files")` 来读取这些文件,将会抛出异常: + +``` +24/01/08 12:52:56 ERROR Executor: Exception in task 0.0 in stage 12.0 (TID 11) +org.apache.spark.sql.AnalysisException: Invalid Spark read type: expected required group geom (LIST) { + repeated group list { + required int32 element (INTEGER(8,true)); + } +} to be list type but found Some(BinaryType) + at org.apache.spark.sql.execution.datasources.parquet.ParquetSchemaConverter$.checkConversionRequirement(ParquetSchemaConverter.scala:745) + at org.apache.spark.sql.execution.datasources.parquet.ParquetToSparkSchemaConverter.$anonfun$convertGroupField$3(ParquetSchemaConverter.scala:343) + at scala.Option.fold(Option.scala:251) + at org.apache.spark.sql.execution.datasources.parquet.ParquetToSparkSchemaConverter.convertGroupField(ParquetSchemaConverter.scala:324) + at org.apache.spark.sql.execution.datasources.parquet.ParquetToSparkSchemaConverter.convertField(ParquetSchemaConverter.scala:188) + at org.apache.spark.sql.execution.datasources.parquet.ParquetToSparkSchemaConverter.$anonfun$convertInternal$3(ParquetSchemaConverter.scala:147) + at org.apache.spark.sql.execution.datasources.parquet.ParquetToSparkSchemaConverter.$anonfun$convertInternal$3$adapted(ParquetSchemaConverter.scala:117) + at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:286) + at scala.collection.immutable.Range.foreach(Range.scala:158) + at scala.collection.TraversableLike.map(TraversableLike.scala:286) + at scala.collection.TraversableLike.map$(TraversableLike.scala:279) + at scala.collection.AbstractTraversable.map(Traversable.scala:108) + ... +``` + +自 v1.5.1 起,GeoParquet 支持读取旧版 Parquet 文件。你可以使用 `"geoparquet"` 格式,并加上 `.option("legacyMode", "true")` 选项。示例如下: + +=== "Scala/Java" + + ```scala + val df = sedona.read.format("geoparquet").option("legacyMode", "true").load("path/to/legacy-parquet-files") + ``` + +=== "Java" + + ```java + Dataset df = sedona.read.format("geoparquet").option("legacyMode", "true").load("path/to/legacy-parquet-files") + ``` + +=== "Python" + + ```python + df = sedona.read.format("geoparquet").option("legacyMode", "true").load("path/to/legacy-parquet-files") + ``` diff --git a/docs/api/sql/Spider.zh.md b/docs/api/sql/Spider.zh.md new file mode 100644 index 00000000000..0e0726dca13 --- /dev/null +++ b/docs/api/sql/Spider.zh.md @@ -0,0 +1,322 @@ + + +Sedona 提供了一个名为 Spider 的空间数据生成器。它是一个数据源,可以根据用户指定的参数生成随机的空间数据。 + +## 快速入门 + +在[创建好 `SedonaContext` 对象](Overview.md#quick-start)之后,你可以使用 `spider` 数据源创建一个 DataFrame。 + +```python +df_random_points = sedona.read.format("spider").load(n=1000, distribution="uniform") +df_random_boxes = sedona.read.format("spider").load( + n=1000, distribution="gaussian", geometryType="box", maxWidth=0.05, maxHeight=0.05 +) +df_random_polygons = sedona.read.format("spider").load( + n=1000, + distribution="bit", + geometryType="polygon", + minSegment=3, + maxSegment=5, + maxSize=0.1, +) +``` + +现在我们得到了三个包含随机空间数据的 DataFrame。可以查看 `df_random_points` DataFrame 的前 3 行,确认数据已被正确生成。 + +```python +df_random_points.show(3, False) +``` + +输出: + +``` ++---+---------------------------------------------+ +|id |geometry | ++---+---------------------------------------------+ +|1 |POINT (0.8781393502074886 0.5925787985028703)| +|2 |POINT (0.3159498147172185 0.1907316577342276)| +|3 |POINT (0.2618294441170143 0.3623164670133922)| ++---+---------------------------------------------+ +only showing top 3 rows +``` + +生成的 DataFrame 有两列:`id` 和 `geometry`。`id` 列是每条记录的唯一标识,`geometry` 列是随机生成的空间数据。 + +我们可以使用下面的代码绘制出全部 3 个 DataFrame: + +```python +import matplotlib.pyplot as plt +import geopandas as gpd + +# Convert DataFrames to GeoDataFrames +gdf_random_points = gpd.GeoDataFrame(df_random_points.toPandas(), geometry="geometry") +gdf_random_boxes = gpd.GeoDataFrame(df_random_boxes.toPandas(), geometry="geometry") +gdf_random_polygons = gpd.GeoDataFrame( + df_random_polygons.toPandas(), geometry="geometry" +) + +# Create a figure and a set of subplots +fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + +# Plot each GeoDataFrame on a different subplot +gdf_random_points.plot(ax=axes[0], color="blue", markersize=5) +axes[0].set_title("Random Points") + +gdf_random_boxes.boundary.plot(ax=axes[1], color="red") +axes[1].set_title("Random Boxes") + +gdf_random_polygons.boundary.plot(ax=axes[2], color="green") +axes[2].set_title("Random Polygons") + +# Adjust the layout +plt.tight_layout() + +# Show the plot +plt.show() +``` + +输出: + +![Random Spatial Data](../../image/spider/spider-quickstart.png) + +你可以浏览 [SpiderWeb](https://spider.cs.ucr.edu/) 网站调试参数,观察它们对生成数据的影响。当你确定满意的参数后,便可以在自己的 Spider DataFrame 创建代码中使用它们。下面的章节会详细介绍各项参数。 + +## 通用参数 + +下列参数对所有分布都适用。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| n | 生成的记录数量 | 100 | +| distribution | 分布类型。详见 [Distributions](#distributions)。 | `uniform` | +| numPartitions | 生成的数据所使用的分区数 | 你的 Spark Context 的默认并行度 | +| seed | 随机种子 | 当前时间戳(毫秒) | + +!!! warning + 在不同的 Java 版本或 Sedona 版本下,相同的 `seed` 参数可能产生不同的结果。 + +## 分布(Distributions) + +Spider 支持在多种分布下生成随机的点、矩形与多边形。你可以通过访问 [SpiderWeb](https://spider.cs.ucr.edu/) 网站来探索 Spider 的能力。可以通过 `distribution` 参数来指定分布类型。下面列出了各分布的参数。 + +### 均匀分布(Uniform Distribution) + +均匀分布会在单位正方形 `[0, 1] x [0, 1]` 内生成随机几何对象。通过将 `distribution` 参数设为 `uniform` 即可选择该分布。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| geometryType | 几何类型,可选 `point`、`box` 或 `polygon` | `point` | +| maxWidth | 生成的矩形的最大宽度 | 0.01 | +| maxHeight | 生成的矩形的最大高度 | 0.01 | +| minSegment | 生成的多边形的最少边数 | 3 | +| maxSegment | 生成的多边形的最多边数 | 3 | +| maxSize | 生成的多边形的最大尺寸 | 0.01 | + +示例: + +```python +import geopandas as gpd + +df = sedona.read.format("spider").load( + n=300, distribution="uniform", geometryType="box", maxWidth=0.05, maxHeight=0.05 +) +gpd.GeoDataFrame(df.toPandas(), geometry="geometry").boundary.plot() +``` + +![Uniform Distribution](../../image/spider/spider-uniform.png) + +### 高斯分布(Gaussian Distribution) + +高斯分布在均值为 `[0.5, 0.5]`、标准差为 `[0.1, 0.1]` 的高斯分布下生成随机几何对象。通过将 `distribution` 参数设为 `gaussian` 即可选择该分布。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| geometryType | 几何类型,可选 `point`、`box` 或 `polygon` | `point` | +| maxWidth | 生成的矩形的最大宽度 | 0.01 | +| maxHeight | 生成的矩形的最大高度 | 0.01 | +| minSegment | 生成的多边形的最少边数 | 3 | +| maxSegment | 生成的多边形的最多边数 | 3 | +| maxSize | 生成的多边形的最大尺寸 | 0.01 | + +示例: + +```python +import geopandas as gpd + +df = sedona.read.format("spider").load( + n=300, distribution="gaussian", geometryType="polygon", maxSize=0.05 +) +gpd.GeoDataFrame(df.toPandas(), geometry="geometry").boundary.plot() +``` + +![Gaussian Distribution](../../image/spider/spider-gaussian.png) + +### 位分布(Bit Distribution) + +位分布按一种 bit 分布生成随机几何对象。通过将 `distribution` 参数设为 `bit` 即可选择该分布。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| geometryType | 几何类型,可选 `point`、`box` 或 `polygon` | `point` | +| probability | 设置某一位为 1 的概率 | 0.2 | +| digits | 生成数据中的位数 | 10 | +| maxWidth | 生成的矩形的最大宽度 | 0.01 | +| maxHeight | 生成的矩形的最大高度 | 0.01 | +| minSegment | 生成的多边形的最少边数 | 3 | +| maxSegment | 生成的多边形的最多边数 | 3 | +| maxSize | 生成的多边形的最大尺寸 | 0.01 | + +示例: + +```python +import geopandas as gpd + +df = sedona.read.format("spider").load( + n=300, distribution="bit", geometryType="point", probability=0.2, digits=10 +) +gpd.GeoDataFrame(df.toPandas(), geometry="geometry").plot(markersize=1) +``` + +![Bit Distribution](../../image/spider/spider-bit.png) + +### 对角分布(Diagonal Distribution) + +对角分布在对角线 `y = x` 上生成随机几何对象,对不完全落在对角线上的几何对象施加一定的离散。通过将 `distribution` 参数设为 `diagonal` 即可选择该分布。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| geometryType | 几何类型,可选 `point`、`box` 或 `polygon` | `point` | +| percentage | 恰好落在对角线上的记录所占比例 | 0.5 | +| buffer | 对未完全落在对角线上的点,其离散范围(缓冲)大小 | 0.5 | +| maxWidth | 生成的矩形的最大宽度 | 0.01 | +| maxHeight | 生成的矩形的最大高度 | 0.01 | +| minSegment | 生成的多边形的最少边数 | 3 | +| maxSegment | 生成的多边形的最多边数 | 3 | +| maxSize | 生成的多边形的最大尺寸 | 0.01 | + +示例: + +```python +import geopandas as gpd + +df = sedona.read.format("spider").load( + n=300, distribution="diagonal", geometryType="point", percentage=0.5, buffer=0.5 +) +gpd.GeoDataFrame(df.toPandas(), geometry="geometry").plot(markersize=1) +``` + +![Diagonal Distribution](../../image/spider/spider-diagonal.png) + +### 谢尔宾斯基分布(Sierpinski Distribution) + +谢尔宾斯基分布在一个谢尔宾斯基三角形上分布地生成随机几何对象。通过将 `distribution` 参数设为 `sierpinski` 即可选择该分布。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| geometryType | 几何类型,可选 `point`、`box` 或 `polygon` | `point` | +| maxWidth | 生成的矩形的最大宽度 | 0.01 | +| maxHeight | 生成的矩形的最大高度 | 0.01 | +| minSegment | 生成的多边形的最少边数 | 3 | +| maxSegment | 生成的多边形的最多边数 | 3 | +| maxSize | 生成的多边形的最大尺寸 | 0.01 | + +示例: + +```python +import geopandas as gpd + +df = sedona.read.format("spider").load( + n=2000, distribution="sierpinski", geometryType="point" +) +gpd.GeoDataFrame(df.toPandas(), geometry="geometry").plot(markersize=1) +``` + +![Sierpinski Distribution](../../image/spider/spider-sierpinski.png) + +### 地块分布(Parcel Distribution) + +该生成器生成类似地块的矩形。其工作方式是沿最长维度递归地切分输入域(单位正方形),然后随机抖动(dither)每个生成的矩形以增加随机性。该生成器仅能生成矩形。通过将 `distribution` 参数设为 `parcel` 即可选择该分布。 + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| dither | 抖动量,以边长的比例表示。允许范围 [0, 1] | 0.5 | +| splitRange | 切分矩形时允许的范围。允许范围 [0.0, 0.5]。0.0 表示允许任意切分值,0.5 表示总是从中间切分。 | 0.5 | + +示例: + +```python +import geopandas as gpd + +df = sedona.read.format("spider").load( + n=300, distribution="parcel", dither=0.5, splitRange=0.5 +) +gpd.GeoDataFrame(df.toPandas(), geometry="geometry").boundary.plot() +``` + +![Parcel Distribution](../../image/spider/spider-parcel.png) + +!!!note + `parcel` 分布生成的分区数始终是 4 的幂。这是为了保证所生成数据的质量。如果指定的 `numPartitions` 不是 4 的幂,会被自动调整为不大于指定值的、最接近的 4 的幂。 + +## 仿射变换 + +Spider 生成的随机空间数据多数位于单位正方形 `[0, 1] x [0, 1]` 中。如果你希望在不同的区域内生成随机空间数据,可以通过指定仿射变换参数对数据进行缩放和平移,将其转换到目标区域。 + +下面的代码展示了如何通过仿射变换在不同区域内生成随机空间数据。 + +仿射变换的参数如下: + +| 参数 | 说明 | 默认值 | +| --------- | ----------- | ------------- | +| translateX | 水平方向平移数据 | 0 | +| translateY | 垂直方向平移数据 | 0 | +| scaleX | 水平方向缩放数据 | 1 | +| scaleY | 垂直方向缩放数据 | 1 | +| skewX | 水平方向剪切数据 | 0 | +| skewY | 垂直方向剪切数据 | 0 | + +仿射变换按如下方式作用于生成的数据: + +``` +x' = translateX + scaleX * x + skewX * y +y' = translateY + skewY * x + scaleY * y +``` + +示例: + +```python +import geopandas as gpd + +df_random_points = sedona.read.format("spider").load( + n=1000, distribution="uniform", translateX=0.5, translateY=0.5, scaleX=2, scaleY=2 +) +gpd.GeoDataFrame(df_random_points.toPandas(), geometry="geometry").plot(markersize=1) +``` + +此时数据位于区域 `[0.5, 2.5] x [0.5, 2.5]` 内。 + +![Affine Transformation](../../image/spider/spider-uniform-affine.png) + +## 参考资料 + +- Puloma Katiyar, Tin Vu, Sara Migliorini, Alberto Belussi, Ahmed Eldawy. "SpiderWeb: A Spatial Data Generator on the Web", ACM SIGSPATIAL 2020, Seattle, WA +- Beast Spatial Data Generator: https://bitbucket.org/bdlabucr/beast/src/master/doc/spatial-data-generator.md +- SpiderWeb: A Spatial Data Generator on the Web: https://spider.cs.ucr.edu/ +- SpiderWeb YouTube Video: https://www.youtube.com/watch?v=h0xCG6Swdqw diff --git a/docs/api/sql/Visualization-SedonaKepler.zh.md b/docs/api/sql/Visualization-SedonaKepler.zh.md new file mode 100644 index 00000000000..06b2c7dce82 --- /dev/null +++ b/docs/api/sql/Visualization-SedonaKepler.zh.md @@ -0,0 +1,96 @@ + + +SedonaKepler 提供了一组 API,便于在 Jupyter notebook/lab 环境中对地理空间数据进行快速、交互式的可视化。 + +要开始使用 SedonaKepler,只需通过下面的方式导入 Sedona: + +```python +from sedona.spark import * +``` + +也可以使用以下方式导入: + +```python +from sedona.spark import SedonaKepler +``` + +下面是 SedonaKepler 暴露的所有 API 的详细说明: + +### **使用 SedonaKepler.create_map 创建地图对象** + +SedonaKepler 暴露的 create_map API 的签名如下: + +```python +def create_map( + df: SedonaDataFrame = None, name: str = "unnamed", config: dict = None +) -> map: ... +``` + +参数 'name' 用于在地图对象中关联传入的 SedonaDataFrame,应用到该地图上的任何配置都会与该名称绑定。建议为 dataframe 传入唯一的标识符。 + +如果没有传入 SedonaDataFrame 对象,会返回一个空地图(如果传入了 config 则会应用该 config)。之后可以通过 `add_df` 方法添加 SedonaDataFrame。 + +也可以可选地传入一个 map config,对地图进行预设的定制。 + +!!!Note + map config 是按显示的 SedonaDataFrame 的名称来引用每一项定制配置的,如果名称不匹配,则该 config 不会被应用到地图对象上。 + +!!! abstract "示例用法(参考自 Sedona Jupyter 示例)" + + === "Python" + ```python + map = SedonaKepler.create_map(df=groupedresult, name="AirportCount") + map + ``` + +### **使用 SedonaKepler.add_df 向地图对象添加 SedonaDataFrame** + +SedonaKepler 暴露的 add_df API 的签名如下: + +```python +def add_df(map, df: SedonaDataFrame, name: str = "unnamed"): ... +``` + +该 API 可用于向已经创建的地图对象添加一个 SedonaDataFrame。传入的 map 对象会被直接修改,不返回任何值。 + +参数 name 的约束条件与 'create_map' 相同。 + +!!!Tip + 可以通过该方法向一个地图对象添加多个 dataframe,从而在同一张地图上一起可视化。 + +!!! abstract "示例用法(参考自 Sedona Jupyter 示例)" + === "Python" + ```python + map = SedonaKepler.create_map() + SedonaKepler.add_df(map, groupedresult, name="AirportCount") + map + ``` + +### **通过地图设置配置** + +由 SedonaKepler 创建的地图对象在被访问渲染时,会附带一个 config 面板,可用于自定义地图 + +### **保存与设置 config** + +可以通过访问地图对象的 'config' 属性(如 `map.config`)来获取其当前 config。如果每次都需要渲染完全相同的地图,可以将该 config 保存下来以备后续使用,或在不同 notebook 之间复用。 + +!!!Note + map config 是按 dataframe 的名称来引用每一项已应用的定制配置的,因此它只在使用相同 dataframe 名称的地图上生效。 + 更多细节请参见 keplerGl 文档:[这里](https://docs.kepler.gl/docs/keplergl-jupyter#6.-match-config-with-data) diff --git a/docs/api/sql/Visualization-SedonaPyDeck.zh.md b/docs/api/sql/Visualization-SedonaPyDeck.zh.md new file mode 100644 index 00000000000..443f41e0448 --- /dev/null +++ b/docs/api/sql/Visualization-SedonaPyDeck.zh.md @@ -0,0 +1,161 @@ + + +SedonaPyDeck 提供了一组 API,便于在 Jupyter notebook/lab 环境中对地理空间数据进行快速、交互式的可视化。 + +要开始使用 SedonaPyDeck,只需通过下面的方式导入 Sedona: + +```python +from sedona.spark import * +``` + +也可以使用以下方式导入: + +```python +from sedona.spark import SedonaPyDeck +``` + +!!!Note + 关于可选参数的更多说明,请访问 [PyDeck 文档](https://deckgl.readthedocs.io/en/latest/deck.html)。 + + 当用户为 `map_style` 选择 'salellite' 选项时,SedonaPyDeck 默认假定地图提供方为 Mapbox。 + +下面是 SedonaPyDeck 暴露的所有 API 的详细说明: + +### **几何对象地图(Geometry Map)** + +```python +def create_geometry_map( + df, + fill_color="[85, 183, 177, 255]", + line_color="[85, 183, 177, 255]", + elevation_col=0, + initial_view_state=None, + map_style=None, + map_provider=None, + api_keys=None, + stroked=True, +): ... +``` + +参数 `fill_color` 可以传入一组 RGB/RGBA 值的列表,也可以传入一个根据某列值生成 RGB/RGBA 取值的字符串,用于为地图中的多边形或点几何对象着色。 + +参数 `line_color` 可以传入一组 RGB/RGBA 值的列表,也可以传入一个根据某列值生成 RGB/RGBA 取值的字符串,用于为地图中的线几何对象着色。 + +参数 `elevation_col` 可以传入一个静态高程值,也可以像 `fill_color` 一样基于列值给出高程;仅对地图中的多边形几何对象生效。 + +参数 `stroked` 决定是否在多边形和点周围绘制描边,接受布尔值。更多信息请参阅 [deck.gl 的相关文档](https://deck.gl/docs/api-reference/layers/geojson-layer#:~:text=%27circle%27.-,stroked,-(boolean%2C%20optional))。 + +可选地,可以传入 `initial_view_state`、`map_style`、`map_provider`、`api_keys` 等参数,按用户喜好配置地图。 +关于参数及其默认值的更多信息,可以在 PyDeck 网站,以及 deck.gl 的[相关页面](https://github.com/visgl/deck.gl/blob/8.9-release/docs/api-reference/layers/geojson-layer.md)中找到。 + +### **分级统计地图(Choropleth Map)** + +```python +def create_choropleth_map( + df, + fill_color=None, + plot_col=None, + initial_view_state=None, + map_style=None, + map_provider=None, + api_keys=None, + elevation_col=0, + stroked=True, +): ... +``` + +参数 `fill_color` 可以传入一组 RGB/RGBA 值的列表,也可以传入一个根据某列值生成 RGB/RGBA 取值的字符串。 + +参数 `stroked` 决定是否在多边形和点周围绘制描边,接受布尔值。更多信息请参阅 [deck.gl 的相关文档](https://deck.gl/docs/api-reference/layers/geojson-layer#:~:text=%27circle%27.-,stroked,-(boolean%2C%20optional))。 + +例如,下面这些都是 fill_color 的合法取值: + +```python +fill_color = [255, 12, 250] +fill_color = [0, 12, 250, 255] +fill_color = ( + "[0, 12, 240, AirportCount * 10]" ## AirportCount is a column in the passed df +) +``` + +除传入 `fill_color` 参数外,也可以传入 'plot_col',指定用于决定分级统计的列。 +随后 SedonaPyDeck 会根据该列的取值创建一个默认的配色方案。 + +参数 `elevation_col` 可以传入数值,或包含列名(及其上的运算)的字符串值,用于为所绘制的多边形(如有)设置 3D 高程。 + +可选地,可以传入 `initial_view_state`、`map_style`、`map_provider`、`api_keys` 等参数,按用户喜好配置地图。 +关于参数及其默认值的更多信息可以在 PyDeck 网站上找到。 + +### **散点图(Scatterplot)** + +```python +def create_scatterplot_map( + df, + fill_color="[255, 140, 0]", + radius_col=1, + radius_min_pixels=1, + radius_max_pixels=10, + radius_scale=1, + initial_view_state=None, + map_style=None, + map_provider=None, + api_keys=None, +): ... +``` + +参数 `fill_color` 可以传入一组 RGB/RGBA 值的列表,也可以传入一个根据某列值生成 RGB/RGBA 取值的字符串。 + +参数 `radius_col` 可以传入数值,或包含列名上各种运算的字符串值,用于指定绘制点的半径。 + +参数 `radius_min_pixels` 可以传入数值,用于设定以像素为单位的最小半径。可以防止在缩小时绘制的圆变得过小。 + +参数 `radius_max_pixels` 可以传入数值,用于设定以像素为单位的最大半径。可以防止在放大时圆变得过大。 + +参数 `radius_scale` 可以传入数值,作用于所有点的全局半径乘数。 + +可选地,可以传入 `initial_view_state`、`map_style`、`map_provider`、`api_keys` 等参数,按用户喜好配置地图。 +关于参数及其默认值的更多信息,可以在 PyDeck 网站,以及 deck.gl 的[相关页面](https://github.com/visgl/deck.gl/blob/8.9-release/docs/api-reference/layers/scatterplot-layer.md)中找到。 + +### **热力图(Heatmap)** + +```python +def create_heatmap( + df, + color_range=None, + weight=1, + aggregation="SUM", + initial_view_state=None, + map_style=None, + map_provider=None, + api_keys=None, +): ... +``` + +参数 `color_range` 可以可选地传入一组 RGB 值的列表,SedonaPyDeck 默认使用 `6-class YlOrRd` 作为 color_range。 +更多示例可参见 [colorbrewer](https://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6)。 + +参数 `weight` 可以传入数值,或包含列名及其上运算的字符串值,用于在绘制热力图时确定每个点的权重。 +默认情况下,SedonaPyDeck 将每个点的权重设为 1。 + +参数 `aggregation` 可用于定义热力图在缩小(聚合到更低分辨率)时所采用的聚合策略。 +可选 "MEAN" 或 "SUM"。SedonaPyDeck 默认使用 "MEAN" 作为聚合策略。 + +可选地,可以传入 `initial_view_state`、`map_style`、`map_provider`、`api_keys` 等参数,按用户喜好配置地图。 +关于参数及其默认值的更多信息,可以在 PyDeck 网站,以及 deck.gl 的[相关页面](https://github.com/visgl/deck.gl/blob/8.9-release/docs/api-reference/aggregation-layers/heatmap-layer.md)中找到。 diff --git a/docs/api/stats/sql.zh.md b/docs/api/stats/sql.zh.md new file mode 100644 index 00000000000..6f248775db3 --- /dev/null +++ b/docs/api/stats/sql.zh.md @@ -0,0 +1,244 @@ + + +## 概述 + +Sedona 的 stats 模块为带有空间列的 dataframe 提供了用于地理空间 +统计分析的 Scala 和 Python 函数。 +stats 模块构建在 core 模块之上,提供了一组可用于 +对这些 dataframe 进行空间分析的函数。stats 模块设计上配合 core 模块以及 +viz 模块共同使用,从而提供一套完整的地理空间分析工具。 + +## 使用 DBSCAN + +DBSCAN 函数在 scala/java 中位于 `org.apache.sedona.stats.clustering.DBSCAN.dbscan`,在 python 中位于 `sedona.stats.clustering.dbscan.dbscan`。 + +该函数使用 DBSCAN 算法为 dataframe 的每一条记录标注一个聚类标签。 +dataframe 应至少包含一列 `GeometryType` 类型的列。各行必须唯一。如果仅有一列 +几何列,会自动使用该列。如果有两列,会使用名为 +'geometry' 的那一列。如果有多于一列且没有任何一列名为 'geometry',则 +必须显式提供列名。新增列将命名为 'cluster'。 + +### 参数 + +括号中的名字是 python 中的变量名 + +- dataframe —— 待聚类的 dataframe。必须包含至少一列 GeometryType 列 +- epsilon —— DBSCAN 算法的最小距离参数 +- minPts (min_pts) —— DBSCAN 算法的最小点数参数 +- geometry —— 几何列的列名 +- includeOutliers (include_outliers) —— 是否在输出中包含离群点。默认为 false +- useSpheroid (use_spheroid) —— 是否使用椭球距离计算(而非笛卡尔距离)。默认为 false + +输出是输入 DataFrame,每一行新增了聚类标签。如果包含离群点,离群点的聚类值为 -1。 + +## 使用局部离群因子(LOF) + +LOF 函数在 scala/java 中位于 `org.apache.sedona.stats.outlierDetection.LocalOutlierFactor.localOutlierFactor`,在 python 中位于 `sedona.stats.outlier_detection.local_outlier_factor.local_outlier_factor`。 + +该函数为 dataframe 的每一条记录新增一列,记录其局部离群因子。 +dataframe 应至少包含一列 `GeometryType` 类型的列。各行必须唯一。如果仅有一列 +几何列,会自动使用该列。如果有两列,会使用名为 +'geometry' 的那一列。如果有多于一列且没有任何一列名为 'geometry',则 +必须显式提供列名。 + +### 参数 + +括号中的名字是 python 中的变量名 + +- dataframe —— 包含点几何对象的 dataframe +- k —— 计算 LOF 时考虑的最近邻居的数量 +- geometry —— 几何列的列名 +- handleTies (handle_ties) —— 是否在 k 距离计算中处理并列情形。默认为 false +- useSpheroid (use_spheroid) —— 是否使用椭球距离计算(而非笛卡尔距离)。默认为 false + +输出是输入 DataFrame,每一行新增了 lof 值。 + +## 使用 Getis-Ord Gi(*) + +G Local 函数在 scala/java 中位于 `org.apache.sedona.stats.hotspotDetection.GetisOrd.gLocal`,在 python 中位于 `sedona.stats.hotspot_detection.getis_ord.g_local`。 + +对 dataframe 的 x 列执行 Gi 或 Gi* 统计。 + +权重应为本行的邻居。权重元素应为包含 value 列与 neighbor 列的结构体。neighbor 列的内容应是 +与父行同类型的邻居(但不再包含 neighbors)。生成此列的方法请参见 _Using the Distance +Weighting Function_ 一节。要计算 Gi* +统计量,请确保焦点观测值位于邻居数组中(即本行本身出现在 weights 列中)且 `star=true`。显著性通过 z 分数计算。 + +### 参数 + +- dataframe —— 用于执行 G 统计的 dataframe +- x —— 要执行热点分析的列名 +- weights —— 包含邻居数组的列名。neighbor 列的内容应是与父行同类型的邻居(但不再包含 neighbors)。你可以使用 `Weighting` 类的函数来生成该列。 +- star —— 焦点观测值是否包含在邻居数组中。若为 true 则计算 Gi*,否则计算 Gi + +输出是输入 DataFrame,每一行新增以下列:G、E[G]、V[G]、Z、P。 + +## 使用距离加权函数 + +Weighting 相关函数在 scala/java 中位于 `org.apache.sedona.stats.Weighting`,在 python 中位于 `sedona.stats.weighting`。 + +该函数会新增一列,包含一组由 value 列和 neighbor 列构成的结构体数组。 + +通用的 `addDistanceBandColumn`(python 中为 `add_distance_band_column`)函数会为 dataframe 添加一个 weights 列,包含距离阈值内的其他记录及其权重。 + +dataframe 应至少包含一列 `GeometryType` 类型的列。各行必须唯一。如果仅有一列 +几何列,会自动使用该列。如果有两列,会使用名为 +'geometry' 的那一列。如果有多于一列且没有任何一列名为 'geometry',则 +必须显式提供列名。新增列将命名为 'cluster'。 + +### 参数 + +#### addDistanceBandColumn + +括号中的名字是 python 中的变量名 + +- dataframe —— 包含几何列的 DataFrame +- threshold —— 判定邻居的距离阈值 +- binary —— 邻居使用二进制权重还是反距离权重(dist^alpha) +- alpha —— 反距离权重使用的 alpha 值,当 binary 为 true 时忽略 +- includeZeroDistanceNeighbors (include_zero_distance_neighbors) —— 是否包含距离为 0 的邻居。若包含且 binary 为 false,按浮点规范(除以 0),其值会是无穷大 +- includeSelf (include_self) —— 是否将自身包含在邻居列表中 +- selfWeight (self_weight) —— 自身权重的取值 +- geometry —— 几何列的列名 +- useSpheroid (use_spheroid) —— 是否使用椭球距离计算(而非笛卡尔距离)。默认为 false + +#### addBinaryDistanceBandColumn + +括号中的名字是 python 中的变量名 + +- dataframe —— 包含几何列的 DataFrame +- threshold —— 判定邻居的距离阈值 +- includeZeroDistanceNeighbors (include_zero_distance_neighbors) —— 是否包含距离为 0 的邻居。若包含且 binary 为 false,按浮点规范(除以 0),其值会是无穷大 +- includeSelf (include_self) —— 是否将自身包含在邻居列表中 +- selfWeight (self_weight) —— 自身权重的取值 +- geometry —— 几何列的列名 +- useSpheroid (use_spheroid) —— 是否使用椭球距离计算(而非笛卡尔距离)。默认为 false + +以上两种函数的输出均为输入 DataFrame,每一行新增 weights 列。 + +## Moran I + +Moran I 是一种空间自相关算法,它同时使用空间 +位置和非空间属性。当取值接近 1 时 +表示存在空间相关性;当取值接近 0 时 +表示不存在相关性,数据随机分布;当 +MoranI 自相关值接近 -1 时表示存在负 +相关。负相关意味着邻近的取值彼此差异较大。 + +下图展示了不同空间相关性的取值 + +- 左侧为负相关(-1) +- 中间为正相关(1) +- 右侧相关性接近 0,数据是随机的。 + +![moranI.png](../../image/moranI.png) + +Moran 统计可以作为 Scala/Java 与 Python 函数使用。 +该输入函数需要一个 weight DataFrame。你可以用 +Apache Sedona 的加权函数来构造该 weight DataFrame。需要注意的是, +你的输入必须包含一个唯一标识要素的 id 列以及一个 value 字段。MoranI Apache Sedona 函数 +所需的最简 schema 如下: + +``` + |-- id: integer (nullable = true) + |-- value: double (nullable = true) + |-- weights: array (nullable = false) + | |-- element: struct (containsNull = false) + | | |-- neighbor: struct (nullable = false) + | | | |-- id: integer (nullable = true) + | | | |-- value: double (nullable = true) + | | |-- value: double (nullable = true) +``` + +你可以通过函数参数控制 value 列的列名和 id。 + +要使用 [Apache Sedona 权重函数](#adddistancebandcolumn),需要把 id 列和 value 列传给保留参数。 + +=== "Scala" + + ```scala + val weights = Weighting.addDistanceBandColumn( + positiveCorrelationFrame, + 1.0, + savedAttributes = Seq("id", "value") + ) + + val moranResult = Moran.getGlobal(weights, idColumn = "id") + + // result fields + moranResult.getPNorm + moranResult.getI + moranResult.getZNorm + ``` + +=== "Python" + + ```python + from sedona.spark.stats.autocorrelation.moran import Moran + from sedona.spark.stats.weighting import add_binary_distance_band_column + + result = add_binary_distance_band_column(df, 1.0, saved_attributes=["id", "value"]) + + moran_i_result = Moran.get_global(result) + + ## result fields + moran_i_result.p_norm + moran_i_result.i + moran_i_result.z_norm + ``` + +结果中会得到 Z norm、P norm 以及 Moran I 值。 + +下面是函数的完整签名 + +=== "Scala" + + ```scala + def getGlobal( + dataframe: DataFrame, + twoTailed: Boolean = true, + idColumn: String = ID_COLUMN, + valueColumnName: String = VALUE_COLUMN): MoranResult + + // java interface + public interface MoranResult { + public double getI(); + public double getPNorm(); + public double getZNorm(); + } + ``` + +=== "Python" + + ```python + def get_global( + df: DataFrame, + two_tailed: bool = True, + id_column: str = "id", + value_column: str = "value", + ) -> MoranResult: ... + + + @dataclass + class MoranResult: + i: float + p_norm: float + z_norm: float + ``` diff --git a/docs/api/viz/java-api.zh.md b/docs/api/viz/java-api.zh.md new file mode 100644 index 00000000000..e1b26ab735a --- /dev/null +++ b/docs/api/viz/java-api.zh.md @@ -0,0 +1,22 @@ + + +请阅读 [Javadoc](../../javadoc/spark/) + +注意:Scala 可以无缝调用 Java API。这意味着 Scala 用户与 Java 用户使用相同的 API。 diff --git a/docs/api/viz/sql.zh.md b/docs/api/viz/sql.zh.md new file mode 100644 index 00000000000..3d633aab36d --- /dev/null +++ b/docs/api/viz/sql.zh.md @@ -0,0 +1,161 @@ + + +## 快速入门 + +详细说明请参见:[可视化空间 DataFrame/RDD](../../tutorial/viz.md)。 + +1. 在你的项目 pom.xml 或 build.sbt 中加入 Sedona-core、Sedona-SQL、Sedona-Viz +2. 声明你的 Spark Session + +```scala +sparkSession = SparkSession.builder(). +config("spark.serializer","org.apache.spark.serializer.KryoSerializer"). +config("spark.kryo.registrator", "org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator"). +master("local[*]").appName("mySedonaVizDemo").getOrCreate() +``` + +3. 在 SparkSession 声明之后加上以下代码: + +```scala +SedonaSQLRegistrator.registerAll(sparkSession) +SedonaVizRegistrator.registerAll(sparkSession) +``` + +## 常规函数 + +### ST_Colorize + +简介:根据像素的权重返回对应的颜色。权重可以是空间对象的空间聚合值,也可以是诸如温度、湿度等空间观测值。 + +!!!note + 该颜色在 DataFrame 中被编码为 Integer 类型的数值。当你打印它时,看到的会是一些看似无意义的数字。可以直接把它们当作 GeoSparkViz 中的颜色处理。 + +格式: + +``` +ST_Colorize (weight: Double, maxWeight: Double, mandatory color: String (Optional)) +``` + +起始版本:`v1.0.0` + +#### 生成不同颜色 —— 热力图 + +该函数会根据所有像素中的最大权重对当前权重做归一化,从而让不同像素得到不同的颜色。 + +SQL 示例 + +```sql +SELECT pixels.px, ST_Colorize(pixels.weight, 999) AS color +FROM pixels +``` + +#### 生成统一颜色 —— 散点图 + +如果将一个强制颜色名作为第三个输入参数传入,该函数会直接输出该颜色,而忽略权重。这种情况下,所有像素都使用同一个颜色。 + +SQL 示例 + +```sql +SELECT pixels.px, ST_Colorize(pixels.weight, 999, 'red') AS color +FROM pixels +``` + +下面是一些可输入的颜色名示例: + +``` +"firebrick" +"#aa38e0" +"0x40A8CC" +"rgba(112,36,228,0.9)" +``` + +预定义颜色的完整列表请参见 [AWT Colors](https://static.javadoc.io/org.beryx/awt-color-factory/1.0.0/org/beryx/awt/color/ColorFactory.html)。 + +### ST_EncodeImage + +简介:返回一个 Java PNG BufferedImage 的 base64 字符串表示。该函数主要用于服务端—客户端环境,例如将 base64 字符串从 GeoSparkViz 传输到 Apache Zeppelin。 + +格式:`ST_EncodeImage (A: Image)` + +起始版本:`v1.0.0` + +SQL 示例 + +```sql +SELECT ST_EncodeImage(images.img) +FROM images +``` + +### ST_Pixelize + +简介:在给定的分辨率下,将一个几何对象转换成像素数组 + +你应当与 `Lateral View` 和 `Explode` 一起使用该函数。 + +格式: + +``` +ST_Pixelize (A: Geometry, ResolutionX: Integer, ResolutionY: Integer, Boundary: Geometry) +``` + +起始版本:`v1.0.0` + +SQL 示例 + +```sql +SELECT ST_Pixelize(shape, 256, 256, (ST_Envelope_Aggr(shape) FROM pointtable)) +FROM polygondf +``` + +### ST_TileName + +简介:返回给定缩放级别下的地图瓦片名称。请参阅 [OpenStreetMap ZoomLevel](http://wiki.openstreetmap.org/wiki/Zoom_levels) 和 [OpenStreetMap tile name](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames)。 + +!!!note + 瓦片名称的格式为 "Z-X-Y" 字符串。Z 为缩放级别,X 为 X 轴上的瓦片坐标,Y 为 Y 轴上的瓦片坐标。 + +格式:`ST_TileName (A: Pixel, ZoomLevel: Integer)` + +起始版本:`v1.0.0` + +SQL 示例 + +```sql +SELECT ST_TileName(pixels.px, 3) +FROM pixels +``` + +## 聚合函数 + +### ST_Render + +简介:给定一组像素及其颜色,返回一个 Java PNG BufferedImage。第 3 个参数为可选的缩放级别。当你需要渲染瓦片而不是单张图像时,应当使用该缩放级别参数。 + +格式:`ST_Render (A: Pixel, B: Color, C: Integer - optional zoom level)` + +起始版本:`v1.0.0` + +SQL 示例 + +```sql +SELECT tilename, ST_Render(pixels.px, pixels.color) AS tileimg +FROM pixels +GROUP BY tilename +```