使用地理空间数据

通过地理空间分析,您可以分析 BigQuery 中的地理位置数据。地理位置数据也称为地理空间数据。

使用地理空间数据时常见的对象类型包括:

  • 几何图形 表示地球上的表面区域。它通常使用点、线、多边形或点、线和多边形的集合来描述。几何图形集合是一组表示集合中所有形状的空间联合的几何图形。
  • 空间特征表示逻辑空间对象。它将几何图形与特定于应用的附加属性相结合。
  • 空间特征集合是一组空间特征。

在 BigQuery 中,GEOGRAPHY 数据类型表示几何图形值或几何图形集合。如需表示空间特征,请创建一个包含 GEOGRAPHY 列(用于几何图形)以及用于属性的其他列。表的每一行都是一个空间特征,整个表表示空间特征集合。

GEOGRAPHY 数据类型用于描述地球表面的点集。点集是 WGS84 参考球体上的一组点、线和多边形,具有测地线边。如需使用 GEOGRAPHY 数据类型,调用任一 GoogleSQL 地理位置函数即可。

加载地理空间数据

只需使用一组经度和纬度对,就能描述地球上的一个点。例如,您可以加载包含经度和纬度值的 CSV 文件,然后使用 ST_GEOGPOINT 函数将其转换为 GEOGRAPHY 值。

对于更复杂的地理位置,您可以将以下地理空间数据格式加载到 GEOGRAPHY 列中:

  • 已知文本 (WKT)
  • 已知二进制文件 (WKB)
  • GeoJSON

加载 WKT 或 WKB 数据

WKT 是一种文本格式,它使用点、线、具有可选孔的多边形或点、线和多边形的集合来描述单个几何形状。WKB 是 WKT 格式的二进制版本。对于不支持二进制数据的格式(如 JSON),WKB 可以采用十六进制编码。

例如,下列代码定义了 WKT 中的一个点:

POINT(-121 41)

为描述空间特征,WKT 通常嵌入在容器文件格式中(例如 CSV 文件)或者数据库表中。文件行或表行通常对应于空间特征。整个文件或表对应于特征集合。如需将 WKT 数据加载到 BigQuery,请提供可为地理空间数据指定 GEOGRAPHY 列的架构,。

例如,您可能有一个包含以下数据的 CSV 文件:

"POLYGON((-124.49 47.35,-124.49 40.73,-116.49 40.73,-116.49 47.35,-124.49 47.35))",poly1
"POLYGON((-85.6 31.66,-85.6 24.29,-78.22 24.29,-78.22 31.66,-85.6 31.66))",poly2
"POINT(1 2)",point1

您可以通过运行 bq 命令行工具 load 命令加载此文件:

bq load --source_format=CSV \
  --schema="geography:GEOGRAPHY,name:STRING" \
  mydataset.mytable filename1.csv

如需详细了解如何在 BigQuery 中加载数据,请参阅加载数据简介

要将 WKT 数据流式插入具有 GEOGRAPHY 列的现有 BigQuery 表,请在 API 请求中将数据序列化为字符串。

bq

运行 bq 命令行工具 insert 命令:

echo '{"geo": "LINESTRING (-118.4085 33.9416, -73.7781 40.6413)"}' \
    | bq insert my_dataset.geo_table

Python

试用此示例之前,请按照 BigQuery 快速入门:使用客户端库中的 Python 设置说明进行操作。如需了解详情,请参阅 BigQuery Python API 参考文档

如需向 BigQuery 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为客户端库设置身份验证

from google.cloud import bigquery
import shapely.geometry
import shapely.wkt

bigquery_client = bigquery.Client()

# This example uses a table containing a column named "geo" with the
# GEOGRAPHY data type.
table_id = "my-project.my_dataset.my_table"

# Use the Shapely library to generate WKT of a line from LAX to
# JFK airports. Alternatively, you may define WKT data directly.
my_geography = shapely.geometry.LineString(
    [(-118.4085, 33.9416), (-73.7781, 40.6413)]
)
rows = [
    # Convert data into a WKT string.
    {"geo": shapely.wkt.dumps(my_geography)},
]

#  table already exists and has a column
# named "geo" with data type GEOGRAPHY.
errors = bigquery_client.insert_rows_json(table_id, rows)
if errors:
    raise RuntimeError(f"row insert failed: {errors}")
else:
    print(f"wrote 1 row to {table_id}")

如需详细了解如何在 BigQuery 中流式传输数据,请参阅将数据流式插入 BigQuery

您还可以使用 ST_GeogFromText 函数将 WKT 文本字符串转换为 GEOGRAPHY 值。

加载 GeoJSON 数据

GeoJSON 是一种基于 JSON 的几何图形和空间特征格式。例如,下列代码定义了 GeoJSON 中的一个点:

{ "type": "Point", "coordinates": [-121,41] }

GeoJSON 数据可以包含以下任何对象类型:

  • 几何图形对象。几何图形对象是空间形状,描述为点、线和具有可选孔的多边形的集合。
  • 特征对象。特征对象包含几何图形以及其他名称/值对,其含义因应用而异。
  • 特征集合。特征集合是一组特征对象。

您可以通过以下两种方式将 GeoJSON 数据加载到 BigQuery 中:

加载以换行符分隔的 GeoJSON 文件

以换行符分隔的 GeoJSON 文件列表包含一个 GeoJSON 特征对象列表,在文件中每行一个对象。GeoJSON 特征对象是一个包含以下成员的 JSON 对象:

  • type。对于特征对象,该值必须为 Feature。BigQuery 会验证该值,但不会将其包含在表架构中。

  • geometry。值为 GeoJSON Geometry 对象或 null。BigQuery 会将此成员转换为 GEOGRAPHY 值。

  • properties。值为任意 JSON 对象或 null。如果值不是 null,则 BigQuery 会将 JSON 对象的每个成员加载为一个单独的表列。如需详细了解 BigQuery 如何解析 JSON 数据类型,请参阅加载 JSON 数据的详细信息

  • id。可选。如果存在,则值可以是字符串或数字。BigQuery 会将此值加载到名为 id 的列中。

如果特征对象包含此处未列出的其他成员,BigQuery 会将这些成员直接转换为表列。

您可以使用 bq 命令行工具的 bq load 命令加载以换行符分隔的 GeoJSON 文件,如下所示:

bq load \
 --source_format=NEWLINE_DELIMITED_JSON \
 --json_extension=GEOJSON \
 --autodetect \
 DATASET.TABLE \
 FILE_PATH_OR_URI

请替换以下内容:

  • DATASET 是数据集的名称。
  • TABLE 是目标表的名称。
  • FILE_PATH_OR_URI 是本地文件的路径或 Cloud Storage URI

上面的示例启用了架构自动检测功能。如需更好地控制 BigQuery 转换 properties 对象中的值的方式,您可以改为提供显式架构。如需了解详情,请参阅指定架构。如果您提供显式架构,请勿在架构定义中包含顶级 type 列。请为 properties 成员的每个成员定义一个单独的列,而不是定义单个嵌套列。

根据 RFC 7946 的定义,完整的 GeoJSON 数据结构是单个 JSON 对象。许多系统将 GeoJSON 数据导出为包含所有几何图形对象的单个 FeatureCollection 对象。如需将此格式加载到 BigQuery 中,您必须通过移除根级 FeatureCollection 对象并将各个特征对象拆分为单独的行来转换文件。例如,以下命令使用 jq 命令行工具将 GeoJSON 文件拆分为以换行符分隔的格式:

cat ~/file1.json | jq -c '.features[]' > converted.json

从以换行符分隔的的 GeoJSON 文件创建外部表

您可以通过创建外部表来查询存储在 Cloud Storage 中的以换行符分隔的 GeoJSON 文件。如需创建外部表,请使用 CREATE EXTERNAL TABLE DDL 语句。在 OPTIONS 子句中,将 format 选项设置为 NEWLINE_DELIMITED_JSON,并将 json_extension 选项设置为 GEOJSON

例如:

CREATE EXTERNAL TABLE mydataset.table1 OPTIONS (
  format="NEWLINE_DELIMITED_JSON",
  json_extension = 'GEOJSON',
  uris = ['gs://mybucket/geofile.json']
);

加载 GeoJSON 几何图形数据

地理空间分析支持加载作为文本字符串嵌入其他文件类型的各个 GeoJSON 几何图形对象。例如,您可以加载包含一列 GeoJSON 几何图形对象的 CSV 文件。

如需将这种类型的 GeoJSON 数据加载到 BigQuery,请提供为 GeoJSON 数据指定 GEOGRAPHY 列的架构。您必须手动提供架构。否则,如果启用了自动检测功能,BigQuery 会将数据加载为 STRING 值。

地理空间分析不支持使用此方法加载 GeoJSON 特征对象或特征集合。如果需要加载特征对象,请考虑使用以换行符分隔的 GeoJSON 文件。

要将 GeoJSON 数据流式插入具有 GEOGRAPHY 列的现有 BigQuery 表,请在 API 请求中将数据序列化为字符串。

bq

运行 bq 命令行工具 insert 命令:

echo '{"geo": "{\"type\": \"LineString\", \"coordinates\": [[-118.4085, 33.9416], [-73.7781, 40.6413]]}"}' \
  | bq insert my_dataset.geo_table

Python

试用此示例之前,请按照 BigQuery 快速入门:使用客户端库中的 Python 设置说明进行操作。如需了解详情,请参阅 BigQuery Python API 参考文档

如需向 BigQuery 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为客户端库设置身份验证

import geojson
from google.cloud import bigquery

bigquery_client = bigquery.Client()

# This example uses a table containing a column named "geo" with the
# GEOGRAPHY data type.
table_id = "my-project.my_dataset.my_table"

# Use the python-geojson library to generate GeoJSON of a line from LAX to
# JFK airports. Alternatively, you may define GeoJSON data directly, but it
# must be converted to a string before loading it into BigQuery.
my_geography = geojson.LineString([(-118.4085, 33.9416), (-73.7781, 40.6413)])
rows = [
    # Convert GeoJSON data into a string.
    {"geo": geojson.dumps(my_geography)}
]

#  table already exists and has a column
# named "geo" with data type GEOGRAPHY.
errors = bigquery_client.insert_rows_json(table_id, rows)
if errors:
    raise RuntimeError(f"row insert failed: {errors}")
else:
    print(f"wrote 1 row to {table_id}")

您还可以使用 ST_GEOGFROMGEOJSON 函数将 GeoJSON 几何图形对象转换为 GEOGRAPHY 值。 例如,您可以将几何图形对象存储为 STRING 值,然后运行调用 ST_GEOGFROMGEOJSON 的查询。

坐标系和边

在地理空间分析中,点是 WGS84 球体表面上的位置,以经度和大地维度表示。边是两个端点之间的球面测地线。(也就是说,边是球面上的最短路径。)

WKT 格式不提供坐标系。加载 WKT 数据时,地理空间分析假定数据使用具有球边的 WGS84 坐标。请确保您的源数据与该坐标系匹配,除非地理区域足够小,可以忽略球面和平面边缘之间的差异。

GeoJSON 明确使用具有平面边的 WGS84 坐标。加载 GeoJSON 数据时,地理空间分析将平面边转换为球面边。地理空间分析会在必要时为线添加其他点,使转换的边序列保持在原始测地线的 10 米范围内。这一过程称为曲面细分或非均匀致密化。您无法直接控制曲面细分过程。

如需加载具有球形边的地理区域,请使用 WKT。如需加载具有平面边的地理区域(通常称为几何图形),最简单的方法是使用 GeoJSON。但是,如果您的几何图形数据已采用 WKT 格式,则可以使用 STRING 类型加载数据,然后使用 ST_GEOGFROMTEXT函数将其转换为 GEOGRAPHY 值。将 planar 参数设置为 TRUE 即可将数据解读为平面。

选择互换格式时,请务必了解源数据所用的坐标系。大多数系统要么明确支持从 WKT 解析地理位置(而不是几何图形),要么采用平面边。

您的坐标应采用先经度后纬度的格式。如果地理位置有任何较长的段或边,则必须对它们进行曲面细分,因为地理空间分析会将其解析为球形测地线,这可能与您的数据原始坐标系不对应。

多边形方向

在球体上,每个多边形都有一个互补的多边形。例如,描述地球大陆的多边形具有一个描述地球海洋的互补多边形。因为两个多边形由相同的边界环描述,因此需要使用规则来解决有关给定 WKT 字符串描述两个多边形中的哪个多边形的模糊性。

当您从文件或使用流式提取加载 WKT 和 WKB 字符串时,地理空间分析会假设输入中的多边形按如下方式定向:如果您按输入顶点顺序穿过多边形的边界,则多边形的内部在左侧。将地理位置对象导出到 WKT 和 WKB 字符串时,地理空间分析使用同一规则。

如果您使用 ST_GeogFromText 函数将 WKT 字符串转换为 GEOGRAPHY 值,则 oriented 参数指定函数确定多边形的方式:

  • FALSE:将输入解释为具有较小面积的多边形。这是默认行为。

  • TRUE:使用前面介绍的左手方向规则。通过此选项,您可以加载面积大于半球的多边形。

由于 GeoJSON 字符串是在平面地图上定义的,因此即使输入不遵循以下 GeoJSON 格式规范中定义的方向规则,也可以确定方向,RFC 7946

处理格式不正确的空间数据

从其他工具将空间数据加载到 BigQuery 时,您可能会遇到由于无效的 WKT 或 GeoJSON 数据而导致的转换错误。例如,Edge K has duplicate vertex with edge N 错误表示该多边形具有重复的顶点(除第一个和最后一个顶点之外)。

为避免格式问题,您可以使用生成符合标准的输出的函数。例如,当您从 PostGIS 导出数据时,可以使用 PostGIS ST_MakeValid 函数来标准化输出。或者,以文本形式导入数据,然后通过 make_valid 参数来调用 ST_GEOGFROMTEXTST_GEOGFROMGEOJSON 对其进行转换。当 make_validTRUE 时,这些函数会尝试修复无效多边形。

要查找或忽略格式不正确的数据,请使用 SAFE 函数前缀输出有问题的数据。例如,以下查询使用 SAFE 前缀来检索格式不正确的空间数据。

SELECT
  geojson AS bad_geojson
FROM
  mytable
WHERE
  geojson IS NOT NULL
  AND SAFE.ST_GeogFromGeoJson(geojson) IS NULL

限制条件

地理空间分析不支持地理空间格式中的以下特性:

  • 三维几何图形。这包括 WKT 格式中的“Z”后缀,以及 GeoJSON 格式中的海拔坐标。
  • 线性参考系统。这包括 WKT 格式中的“M”后缀。
  • 除几何图形基元或多部分几何图形以外的 WKT 几何图形对象。具体而言,地理空间分析仅支持 Point、MultiPoint、LineString、MultiLineString、Polygon、MultiPolygon、GeometryCollection。

如需了解特定于 GeoJson 和 WKT 输入格式的限制条件,请参阅 ST_GeogFromGeoJsonST_GeogFromText

加载 Google Earth Engine 地理空间数据

Google Earth Engine 是一个地理空间数据平台,它使用光栅数据编译和分析来自卫星和地球观测图像的信息,数据按代表数字图像信息的单元网格组织。BigQuery 主要处理表格向量数据,但用户可以将其 BigQuery 数据与 Earth Engine 中的光栅数据结合使用,将向量数据和光栅数据集都纳入其工作流中。

如需了解如何将 Earth Engine 数据导出到 BigQuery,请参阅导出到 BigQuery

转换地理空间数据

如果表包含单独的经度列和纬度列,则可以使用 GoogleSQL 地理位置函数(如 ST_GeogPoint)将这些值转换为地理位置。例如,如果有用于经度和纬度的两个 DOUBLE 列,则可以使用以下查询创建地理位置列:

SELECT
  *,
  ST_GeogPoint(longitude, latitude) AS g
FROM
  mytable

BigQuery 可以将 WKT 和 GeoJSON 字符串转换为地理位置类型。如果您的数据采用 Shapefile 等其他格式,请使用外部工具将数据转换为受支持的输入文件格式(例如 CSV 文件),其中 GEOGRAPHY 列编码为 WKT 或 GeoJSON 字符串。

对地理空间数据进行分区和聚簇

您可以对包含 GEOGRAPHY 列的表进行分区聚簇。您可以将 GEOGRAPHY 列用作聚簇列,但不能将 GEOGRAPHY 列用作分区列。

如果将 GEOGRAPHY 数据存储在表中,且查询使用空间谓词过滤数据,请确保该表按 GEOGRAPHY 列进行聚簇。这通常可提升查询性能并可能降低费用。空间谓词调用布尔值地理函数,并将 GEOGRAPHY 列作为参数之一。以下示例展示了使用 ST_DWithin 函数的空间谓词:

WHERE ST_DWithin(geo, ST_GeogPoint(longitude, latitude), 100)

将 JOIN 用于空间数据

空间 JOIN 是两个表的联接,在 (WHERE) 子句中具有谓词地理位置函数。例如:

-- how many stations within 1 mile range of each zip code?
SELECT
    zip_code AS zip,
    ANY_VALUE(zip_code_geom) AS polygon,
    COUNT(*) AS bike_stations
FROM
    `bigquery-public-data.new_york.citibike_stations` AS bike_stations,
    `bigquery-public-data.geo_us_boundaries.zip_codes` AS zip_codes
WHERE ST_DWithin(
         zip_codes.zip_code_geom,
         ST_GeogPoint(bike_stations.longitude, bike_stations.latitude),
         1609.34)
GROUP BY zip
ORDER BY bike_stations DESC

当您的地理位置数据持久保留时,空间联接的效果会更好。上述示例在查询中创建了地理位置值。将地理位置值存储在 BigQuery 表格中,效果更好。

例如,以下查询检索经度、纬度对,并将其转换为地理位置点。运行此查询时,您需要指定新目标表格来存储查询结果:

SELECT
  *,
  ST_GeogPoint(pLongitude, pLatitude) AS p
FROM
  mytable

BigQuery 使用以下 GoogleSQL 谓词函数为 INNER JOIN 和 CROSS JOIN 运算符实现经过优化的空间联接:

未针对以下情况优化空间联接:

  • LEFT、RIGHT 或 FULL OUTER 联接
  • 在涉及 ANTI 联接的情况中
  • 空间谓词被否定时

仅当距离参数为常量表达式时,使用 ST_DWithin 谓词的 JOIN 才会优化。

导出空间数据

从 BigQuery 导出空间数据时,GEOGRAPHY 列值的格式始终为 WKT 字符串。如需以 GeoJSON 格式导出数据,请使用 ST_AsGeoJSON 函数。

如果用于分析导出数据的工具不能识别 GEOGRAPHY 数据类型,您可以使用地理位置函数(如 ST_AsTextST_AsGeoJSON)将列值转换为字符串。地理空间分析会在必要时为线添加其他点,使转换的边序列保持在原始测地线的 10 米范围内。

例如,以下查询使用 ST_AsGeoJSON 将 GeoJSON 值转换为字符串。

SELECT
  ST_AsGeoJSON(ST_MakeLine(ST_GeogPoint(1,1), ST_GeogPoint(3,2)))

生成的数据如下所示:

{ "type": "LineString", "coordinates": [ [1, 1], [1.99977145571783, 1.50022838764041], [2.49981908082299, 1.75018082434274], [3, 2] ] }

GeoJSON 线具有两个附加点。地理空间分析添加了这些点,使 GeoJSON 线严格遵循原始线的地面路径。

后续步骤