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 ワークスペース] タブで、プロジェクトの横にあるその他メニュー()をクリックします。
  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 FLOAT または INTEGER
ee.Geometry GEOGRAPHY
ee.Date TIMESTAMP
ee.ByteString BYTES
ee.Array STRUCT<ARRAY<INT64>, ARRAY<INT64|FLOAT64>> 配列のセクションをご覧ください。
その他の ee.* タイプ サポート対象外 JSON 値のセクションをご覧ください。

配列

Earth Engine は、BigQuery の ML.DECODE_IMAGE 関数で使用される形式と同様に、マルチディメンションの ee.ArraySTRUCT<ARRAY<INT64> dimensions, ARRAY<INT64|FLOAT64> values> にエクスポートします。

構造体の最初の配列 dimensions には、Earth Engine 配列のディメンション(d1dn)が含まれています。

構造体の 2 番目の配列 values には、多次元配列のすべての値が含まれ、単一の BigQuery 配列にフラット化されます。フラット化された配列の値の合計数は ni=1di です。元の Earth Engine 配列のインデックス (ii,,in) の値は、フラット化された配列のインデックスに次の値に対応します。

nj=1(ijnk=j+1dk)

一般的なケースでは、values 配列のインデックス式は次のとおりです。

配列サイズ サイズ インデックス登録式
1 次元 d1 [i1]
2 次元 d1, d2 [(i1 * d2) + i2]
3 次元 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]
      ]
    ]);

この配列は、dimensions 要素が配列 [2, 3, 4] で、values 要素がフラット化された配列 [1, 2, 3, 4, 5, 6, 7, 8, ..., 21, 22, 23, 24] である BigQuery STRUCT に変換されます。フラット化された配列のインデックスは、[(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 処理インフラストラクチャから可能な限り多くの並列処理を取得できます。この例では、数十万の reduceRegion 呼び出し(コレクションにわたる関数のマッピングの一般的なアプローチ)ではなく、少数の reduceRegions 呼び出し(数百)を使用する方法を示します。

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 テーブルにデータを同時に書き込むことができます。これは、より高いスループットでデータを書き込むメカニズムですが、複雑さ(タスクキューの管理、再試行など)が増加します。

append パラメータと overwrite パラメータのパフォーマンスの違い

上書きは、古いデータを上書きする前に BigQuery が新しいデータを処理する必要があるため、追加よりも時間がかかります。既存の BigQuery テーブルにエクスポートするときに {overwrite: true} パラメータを設定すると、安全な上書きプロセスがトリガーされます。

  1. 一時テーブル: データは、宛先データセット内の新しい一時テーブルにエクスポートされます。
  2. アトミックな上書き: 一時テーブルの内容が最終的な宛先テーブルにコピーされ、単一のアトミック トランザクションで既存のデータが置き換えられます。
  3. クリーンアップ: 一時テーブルが削除されます。

これにより、エクスポート中に既存のデータが破損することはありません。小さなテーブルの場合、通常は数分遅れます。

高パフォーマンスの代替手段

非常に高いスループットを必要とするワークフローの場合は、GeoBeam を使用して Earth Engine から BigQuery にデータを移動することを検討してください。これには、より多くの構成とインフラストラクチャが必要になるため、組み込みの Earth Engine 機能から始めることをおすすめします。

料金

BigQuery へのエクスポートはバッチ処理であり、バッチ EECU 時間を消費します。Earth Engine を商用または運用目的で使用している場合、BigQuery にデータをエクスポートすると、タスクで使用される EECU 時間が課金されます。すべての使用量は、Earth Engine の他の部分で機能するまったく同じモニタリング ツールでモニタリングできます。

Cloud 請求先アカウント

BigQuery にデータを書き込むには、関連付けられた Cloud プロジェクトで課金アカウントが有効になっている必要があります。請求先アカウントの構成の詳細については、Cloud 請求先アカウントのドキュメントをご覧ください。

下り(外向き)

上り(内向き)と下り(外向き)のすべての費用は、標準のネットワーク トラフィックとして請求されます。

Earth Engine は米国でのみホストされますが、BigQuery データセットは他の多くのリージョンでホストできます。関連するリージョンとデータ量によっては、Earth Engine から BigQuery にデータを書き込むと、かなりのネットワーク トラフィックが生成される可能性があります。

既知の問題

大きなポリゴンの向き

BigQuery エクスポート関数は、半球よりも大きいポリゴンの向きを反転(ポリゴンをその幾何学的補集に変換)することで、ポリゴンを反転します。まれに、半球よりも大きいポリゴンが読み込まれないことがあります。

必要に応じて、反転されたポリゴンは、BigQuery 式 ST_Difference(ST_GeogFromText('fullglobe'), geo) を使用して再度反転することで、BigQuery 内で修正できます。

詳しくは、こちらをご覧ください。