导出到 BigQuery

概览

Earth Engine 的计算架构经过优化,可实现快速可伸缩的图片(基于像素)计算。BigQuery 同样针对可伸缩的表格数据(矢量)处理进行了优化,并且具有许多功能,可与 Earth Engine 相得益彰。

工作流示例包括:

  • 对 Earth Engine 中生成的数据执行大型 BigQuery 联接
  • 使用从影像中派生的统计信息为矢量数据添加注释,以便在 BigQuery 中进行进一步处理
  • 定期将数据从 Earth Engine 导出到可追加的 BigQuery 表

如果您有其他绝佳的用例,我们非常乐意了解

BigQuery 基本知识

Earth Engine 会写入 BigQuery 表,所有表都包含在数据集中。如果 BigQuery 中不存在指定的数据集,导出任务将失败。如需了解详情,请参阅 BigQuery 数据集简介

数据集创建

数据集具有许多创建时选项,包括名称、存储区域和到期行为(以及其他一些更高级的选项)。

创建数据集的方法有很多种,但最简单的入门方式是通过 Cloud 控制台:

  1. 前往 Cloud 控制台中的 BigQuery 页面
  2. 如果系统提示,请点击“启用”以启用该 API。
  3. 在“SQL Workspace”标签页中,点击项目旁边的三点状菜单 ()。
  4. 选择“创建数据集”选项。
  5. 按照配置指南操作。

如需了解创建和配置数据集的所有选项,请参阅 BigQuery 文档

权限

除了使用 Earth Engine 所需的标准角色和权限之外,调用方还需要对 Cloud 项目或数据集拥有正确的 BigQuery 权限

  • bigquery.tables.get
  • bigquery.tables.create
  • bigquery.tables.updateData
  • bigquery.tables.delete
  • bigquery.jobs.create

以下任意组合预定义的 Identity and Access Management (IAM) 角色均包含必要的权限:

  • bigquery.dataEditorbigquery.jobUser
  • bigquery.dataOwnerbigquery.jobUser
  • bigquery.user
  • bigquery.admin

价格

BigQuery 是一项付费 Google Cloud 服务,因此您需要为使用 BigQuery 支付费用,包括您导出到 BigQuery 的所有 Earth Engine 数据的存储和分析费用。

如需详细了解 Earth Engine 的 BigQuery 导出功能的价格,请参阅下文中的价格部分

导出配置

语法

  Export.table.toBigQuery({
    'collection': myFeatureCollection,
    'table': 'myproject.mydataset.mytable',
    'description': 'put_my_data_in_bigquery',
    'append': true,
    'overwrite': false
  });
  task = ee.batch.Export.table.toBigQuery(
      collection=myFeatureCollection,
      table='myproject.mydataset.mytable',
      description='put_my_data_in_bigquery',
      append=True,
      overwrite=False)
  task.start()

自动或手动架构规范

如果 BigQuery 中没有表,Earth Engine 会尝试使用集合中第一个 ee.Feature 的属性确定架构。这是最佳猜测,您可以构建一个集合,其中第一个地图项的架构与其他地图项的架构不同。

如果您需要为 BigQuery 表使用特定架构,请通过创建具有目标架构的空表进行配置。

属性名称

Earth Engine 地图项的属性对应于 BigQuery 中的列。Earth Engine 使用名称“geo”将 ee.Feature 几何图形(“.geo”选择器)写入 BigQuery。

为避免重命名,请确保 ee.Feature 对象的属性是有效的列名称,并且没有任何属性的名称为“geo”(因为此名称用于地图项的几何图形,而该几何图形在 Earth Engine 中没有名称)。

由于对 BigQuery 列名称的限制,属性名称中的无效字符会导致导出失败。

类型转换

系统会尽可能将 Earth Engine(ee.Feature 属性的值)数据转换为等效的 BigQuery 类型。请注意,是否可为 null 由表架构控制,而非类型。

Earth Engine 类型 BigQuery 类型 备注
ee.String STRING
ee.Number FLOATINTEGER
ee.Geometry GEOGRAPHY
ee.Date TIMESTAMP
ee.ByteString BYTES
ee.Array STRUCT<ARRAY<INT64>, ARRAY<INT64|FLOAT64>> 请参阅数组部分
其他 ee.* 类型 不支持 请参阅JSON 值部分

数组

Earth Engine 会将任何多维 ee.Array 导出为 STRUCT<ARRAY<INT64> dimensions, ARRAY<INT64|FLOAT64> values>,类似于 BigQuery 的 ML.DECODE_IMAGE 函数所使用的格式。

结构体中的第一个数组 dimensions 包含 Earth Engine 数组的维度,即 d1dn

结构体中的第二个数组 values 包含多维数组中的所有值,这些值会展平为单个 BigQuery 数组。扁平化数组中的值总数为 ni=1di,原始 Earth Engine 数组中索引 (ii,,in) 对应的值与扁平化数组中的以下索引对应的值相同:

nj=1(ijnk=j+1dk)

对于常见情况,values 数组的编号表达式如下所示:

数组大小 维度 编入索引的表达式
一维 d1 [i1]
二维 d1, d2 [(i1 * d2) + i2]
三维 d1, d2, d3 [(i1 * d2 * d3) + (i2 * d3) + i3]

例如,假设有一个 2x3x4 Earth Engine 数组:

    ee.Array([
      [
        [1, 2, 3, 4],
        [5, 6, 7, 8],
        [9, 10, 11, 12]
      ],
      [
        [13, 14, 15, 16],
        [17, 18, 19, 20],
        [21, 22, 23, 24]
      ]
    ]);

此数组会转换为一个 BigQuery STRUCT,其 dimensions 元素为数组 [2, 3, 4]values 元素为展平数组 [1, 2, 3, 4, 5, 6, 7, 8, ..., 21, 22, 23, 24]。扁平化数组中的索引可以按 [(i1 * 12) + (i2 * 4) + i3] 计算。

JSON 值

为了支持单元格中更丰富的结构化数据,您可以将 Earth Engine 值编码为 JSON 对象。BigQuery 支持对 JSON 编码数据执行 SQL 操作,允许查询“深入探究”您在 Earth Engine 中生成的编码 JSON 值。

var states = ee.FeatureCollection('TIGER/2018/States');
var mod11a1 = ee.ImageCollection('MODIS/061/MOD11A1');

// Find the max day and night temperatures per pixel for a given time.
var maxTemp = mod11a1
    .select(['LST_Day_1km', 'LST_Night_1km'])
    .filterDate('2023-05-15', '2023-05-25')
    .max();

// Annotate each state with its max day/night temperatures.
var annotatedStates = states.map(function (e) {
  var dict = maxTemp.reduceRegion({
    reducer: ee.Reducer.max(),
    geometry: e.geometry(),
    scale: 10 * 1000,  // 10 km
  });
  // Convert the dictionary to JSON and add it as a property.
  return e.set('maxTemp', ee.String.encodeJSON(dict));
});

Export.table.toBigQuery(annotatedStates);

如需了解 Python API 以及如何使用 geemap 进行交互式开发,请参阅 Python 环境页面。

import ee
import geemap.core as geemap
states = ee.FeatureCollection('TIGER/2018/States')
mod11a1 = ee.ImageCollection('MODIS/061/MOD11A1')

# Find the max day and night temperatures per pixel for a given time.
max_temp = (
    mod11a1.select(['LST_Day_1km', 'LST_Night_1km'])
    .filterDate('2023-05-15', '2023-05-25')
    .max()
)


def get_max_temp_for_state(e):
  max_temp_dict = max_temp.reduceRegion(
      reducer=ee.Reducer.max(),
      geometry=e.geometry(),
      scale=10 * 1000,  # 10 km
  )
  # Convert the dictionary to JSON and add it as a property.
  return e.set('maxTemp', ee.String.encodeJSON(max_temp_dict))


# Annotate each state with its max day/night temperatures.
annotated_states = states.map(get_max_temp_for_state)

task = ee.batch.Export.table.toBigQuery(
    collection=annotated_states, table='myproject.mydataset.mytable'
)
task.start()

几何图形转换

BigQuery 对不同投影的支持有限,因此所有 Earth Engine 几何图形都会使用 1 米的误差边界转换为地理直线 EPSG:4326

如需更好地控制此转换过程,您可以手动映射地图项并转换其几何图形,例如:

var transformedCollection = originalCollection.map(function transformGeo(e) {
  var myErrorMargin = 10 * 1000;  // meters
  return e.setGeometry(e.geometry(myErrorMargin, 'EPSG:4326', true));
});

如需了解 Python API 以及如何使用 geemap 进行交互式开发,请参阅 Python 环境页面。

import ee
import geemap.core as geemap
def transform_geo(e):
  my_error_margin = 10 * 1000  # meters
  return e.setGeometry(e.geometry(my_error_margin, 'EPSG:4326', True))


transformed_collection = original_collection.map(transform_geo)

性能

Earth Engine 性能

Earth Engine 计算通常是 Export 操作的瓶颈。为此,请务必整理处理流程,以实现最大并行性。任何在串行处理中烘焙的计算(例如 ee.FeatureCollection.iterate())都可能会导致导出运行缓慢或失败。

BigQuery 中的性能

正确地构建和聚类数据是确保在 BigQuery 中高效执行查询的最佳方式。如果 BigQuery 中尚无表,则从 Earth Engine 导出的表会按地图项的几何图形(如果有)进行分片。对于地理空间数据,按地理位置字段进行聚类非常常见。它可以提高使用空间过滤条件的查询的性能并降低费用,最常用于以下 BigQuery 操作:

WHERE ST_DWithin(<table_column>, <constant_geography>, <distance>)
WHERE ST_Intersects(<table_column>, <constant_geography>)

通常,向未分片表添加分片也不会造成任何损害,但可能会略微增加将数据加载到表中的时间。如需详细了解查询优化,请参阅 BigQuery 文档

请注意,分片设置仅会影响写入表中的数据。

演示:使用 reduceRegions

在某些情况下,您可以使用 reduceRegions 从 Earth Engine 处理基础架构中获取尽可能多的并行性。此示例演示了如何使用较少的 reduceRegions 调用(几百个),而不是数万个 reduceRegion 调用(对集合映射函数的典型方法)。

var lucas = ee.FeatureCollection('JRC/LUCAS_HARMO/COPERNICUS_POLYGONS/V1/2018');
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');

// Fetch the unique date values from the dataset.
var dates = lucas.aggregate_array('survey_date')
    .distinct()
    .map(function (date) {
      return ee.Date.parse('dd/MM/yy', date);
    });

// For each date, annotate the LUCAS samples with the Sentinel-2 band values for
// a two-week window.
function getLucasSamplesForDate(date) {
  date = ee.Date(date);
  var imageForDate = s2
    .filterDate(
      date.advance(-1, 'week'),
      date.advance(1, 'week'))
    .select('B.*');
  var median = imageForDate.median();
  var lucasForDate = lucas.filter(
    ee.Filter.equals('survey_date', date.format('dd/MM/yy')));
  var sample = median.reduceRegions({
    collection: lucasForDate,
    reducer: ee.Reducer.mean(),
    scale: 10,
    tileScale: 8,
  });
  return sample;
}

// Flatten the collection.
var withSamples =
    ee.FeatureCollection(dates.map(getLucasSamplesForDate))
      .flatten();

Export.table.toBigQuery({
  collection: withSamples,
  description: 'lucas_s2_annotated'
});

如需了解 Python API 以及如何使用 geemap 进行交互式开发,请参阅 Python 环境页面。

import ee
import geemap.core as geemap
lucas = ee.FeatureCollection('JRC/LUCAS_HARMO/COPERNICUS_POLYGONS/V1/2018')
s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')

# Fetch the unique date values from the dataset.
dates = (
    lucas.aggregate_array('survey_date')
    .distinct()
    .map(lambda date: ee.Date.parse('dd/MM/yy', date))
)


# For each date, annotate the LUCAS samples with the Sentinel-2 band values for
# a two-week window.
def get_lucas_samples_for_date(date):
  date = ee.Date(date)
  image_for_date = s2.filterDate(
      date.advance(-1, 'week'), date.advance(1, 'week')
  ).select('B.*')
  median = image_for_date.median()
  lucas_for_date = lucas.filter(
      ee.Filter.equals('survey_date', date.format('dd/MM/yy'))
  )
  sample = median.reduceRegions(
      collection=lucas_for_date,
      reducer=ee.Reducer.mean(),
      scale=10,
      tileScale=8,
  )
  return sample


# Flatten the collection.
with_samples = ee.FeatureCollection(
    dates.map(get_lucas_samples_for_date)
).flatten()

task = ee.batch.Export.table.toBigQuery(
    collection=with_samples,
    table='myproject.mydataset.mytable',
    description='lucas_s2_annotated',
)
task.start()

任务并行处理

借助 {append: true} 选项,多个任务可以同时将数据写入 BigQuery 表。这是一种以更高的吞吐量写入数据的机制,但代价是复杂性增加(管理任务队列、重试等)。

appendoverwrite 参数之间的性能差异

请注意,覆盖写入的速度比追加写入慢,因为 BigQuery 必须先处理新数据,然后才能覆盖旧数据。在导出到现有 BigQuery 表时设置 {overwrite: true} 参数会触发安全的覆盖流程:

  1. 临时表:数据会导出到目标数据集中的新临时表。
  2. 原子覆盖:系统会将临时表的内容复制到最终目标表,并在单个原子事务中替换现有数据。
  3. 清理:系统会删除临时表。

这样可以确保导出过程中出现的错误不会损坏您的现有数据。对于小表,延迟时间通常为几分钟。

高性能替代方案

对于需要非常高吞吐量的工作流,不妨考虑使用 GeoBeam 将数据从 Earth Engine 移至 BigQuery。这需要更多配置和基础架构,因此我们建议您先从内置的 Earth Engine 功能入手。

价格

导出到 BigQuery 是一个批处理流程,会消耗批处理 EECU 时间。如果您出于商业或运营目的使用 Earth Engine,则在将数据导出到 BigQuery 时,系统会按任务使用的 EECU 时间向您收费。您可以使用适用于 Earth Engine 其余部分的完全相同的监控工具来监控所有使用情况。

Cloud Billing 账号

如需将数据写入 BigQuery,关联的 Cloud 项目需要启用结算账号。如需详细了解结算账号配置,请参阅 Cloud Billing 账号文档

出站

所有入站流量和出站流量费用均按标准网络流量收费。

Earth Engine 仅托管在美国,但 BigQuery 数据集可托管在多个其他区域。从 Earth Engine 将数据写入 BigQuery 可能会产生大量网络流量,具体取决于涉及的区域和数据量。

已知问题

大型多边形的方向

BigQuery 导出函数会通过翻转方向(将多边形更改为其几何补全图)来反转大于半球的多边形。在极少数情况下,大于半球体的多边形可能会无法加载。

如果需要,您可以在 BigQuery 中使用 BigQuery 表达式 ST_Difference(ST_GeogFromText('fullglobe'), geo) 再次对已反转的多边形进行反转,以便进行更正。

如需了解详情,请点击此处