5. 元データのダウンロード

たまにはローカルに戻りたい

これまでクラウド上で完結する解析をやってきましたが、時には元の衛星データ自体をダウンロードしたいこともあります。クラウドを通してGoogleの計算機環境を使えることこそがGEEの最大の魅力と言っていたじゃないか、と怒られそうですが、ローカルにはローカルの良さがあるのです。

  1. データが手元にある安心感。オープンフリーの時代とはいえ、Googleという私企業が衛星データ提供サービスを将来にわたって継続する確証はありません(個人的には継続・拡大していくと信じていますが)。GEE自体が存続していても、あるタイミングで課金制になり、個人では利用しづらくなるという可能性も捨てきれません。また、衛星プロダクトというのは処理アルゴリズム改良に伴いバージョンが更新されることがあり、過去のバージョンのデータがGEEプラットフォームからなくなるということは普通にありえます。そのほかにも、衛星データ提供機関のポリシーが変更となり、GEEで使えたはずの衛星データが使えなくなることもあるかもしれません。科学において研究の再現性は大変重要ですので、解析に使用した「生データ」を保存しておくことには大きな価値があります。

  2. 解析の自由度。GEEでの解析はGEE用のコンテナ(「ee.ナントカ」というオブジェクト)に対しGoogleの用意したメソッドを適用して行うことが基本です(詳しくは後述)。GEEにはすでに時系列解析やスペクトル解析、機械学習などさまざまな分析ツールが用意されていますが、それでも自作のプログラムや自前のGISソフトで解析がしたい、という場面は多々あります(研究開発目的であればとくに)。そのような場合、ローカルにデータがあったほうが何かと自由がききます。

  3. 描画機能がイマイチ(2019.01現在)。範囲を指定して地図を描き、スケールバーと緯度経度座標と凡例を表示する、といった、GISソフトの基本的な描画機能が現状のGEEにはありません。したがって、研究発表等に使えるクオリティの図をつくりたい場合、けっきょくは画像データを出力して、手元のGISソフトで描画を行うことになります。

そういったわけで元データをローカルに落としたいという需要はあります。そして、たんに「衛星データダウンロード用のプラットフォーム」として見ても、GEEは非常に優秀です。クオリティコントロールやちょっとしたバンド間演算くらいは行ったうえで、多数のデータを整ったフォーマットで出力してくれる、というだけでかなり仕事ははかどります。ですので今回は衛星データのダウンロードを試してみましょう。

使うのは基本的に、Export.image.toDrive() です。これにダウンロードしたいImageを与えると、アカウントに紐付いたGoogleDriveに保存してくれます。ためしに、おなじみのSentinel2 ImageCollectionのなかの3番目のImageをダウンロードしてみましょう(1番目や2番目は雲だらけだったので・・・笑)。

ご自身のGoogleDriveのなかに、"Cambodia"というフォルダを作り、容量が数GBは残っていることを確認したうえで、以下を実行してください。

  • cloudMaskingはいつもの雲マスクですが、最後の返り値を10000で割っています。元データは10000倍にスケーリングされているので、これでTOA反射率になります。

  • Sentinel2 ImageCollectionの抽出ですが、「B2, B3, B4, B8, B12の5バンドのみ選択」というメソッドを追加しました。

  • imageListは初出のee.List型です(Javascriptのリストとはちがう)。これに対しては.getメソッドが使えて、リストの3番目の要素(要素インデックスは0からスタート)を指定してee.Image型として読み込んでいます。ちなみに引数の100は取得する最大の要素数です。

  • Export.image.toDrive()のディクショナリには、与えるImage, タスク名(および出力ファイル名), 空間分解能 (m), フォルダ名, 切り出す範囲を指定します。

スクリプトを実行すると、右側の「Tasks」タブに実行可能なタスクが出てくるので、RUNしてみましょう。GoogleDriveへの出力がはじまります。10分も待てば終わると思いますが、時間がかかっている場合「reflesh」を押してみてください。スクリプトにミスがあって処理が進んでいない場合、エラーを出力してくれます。

フォーマットはGeoTiff、データム/投影法はWGS84/UTM48、青・緑・赤・NIR・SWIRの5バンド出力です。'test.tif'というファイル名でGoogleDriveに出てくると思いますのでダウンロードしましょう。右はローカルのGISソフトでtest.tifのトゥルーカラー画像を表示したものです(カラースケールは適当に調整済み)。意外と雲が取れてないみたいでした。

マスクされたところと雲を同じ白で表示してしまったのでわかりづらいですが…すいません

いろいろカスタマイズ

UTMだと扱いづらい、という気持ちの日もあるかもしれません。投影法を変えたい場合は、imageの部分をこうします:

第一引数はEPSGコード、第三引数はスケール (m) です。第二引数はCRS変換につかうmatrixを指定できるらしいですが、第三引数が指定された場合には不要です。

出力ファイル名(description)に、自動的にImageIDの文字列を指定することはできるでしょうか。'test'という名前ではいつのデータなのかよくわかりません、かといって毎回手動で書き換えるのも面倒です。幸い、Image型のデータのプロパティには.getメソッドでアクセスできます:

20150820T031536_20160929T061954_T48PWV

とConsoleに表示されました。これをファイル名にしてやればいいですよね。

Task name must be a string, Got: object

あん?Consoleでエラーがでましたね。descriptionにはstring型を指定しないとダメよ、というエラーみたいです。でも変数nameってstring(文字列)じゃないの?さっきprintで中身も確認したじゃん、とキレそうになりますよね。私はなりました。

これは重要なところでして、直接的な回答としては、「nameはee.String型であってstring型ではない」、となります。もっと一般化して言えば、「nameはサーバ側オブジェクトであってクライエント側オブジェクトではない」、となります。いい機会なので説明します。

GEEではサーバ側(Googleの計算機)とクライエント側(Chromeを見てるパソコン)で処理が分かれています。print, Map, Export, Chartなど、「ee.なんとか」オブジェクトに関係しない処理はクライエント側、逆に「ee.なんとか」オブジェクトのメソッドはサーバ側で実行されます。サーバ側の処理結果は「ee.なんとか」オブジェクト(すなわちサーバ側のオブジェクト)に格納されます[1]。これはJavaScriptの普通のオブジェクト(すなわちクライエント側のオブジェクト)とは異なり、実体がありません。なのでたとえば先ほどのように、ee.StringオブジェクトをJavaScriptの文字列(string)かのように扱うと怒られるのです。同様に、たとえば、ee.Number型とJavascriptの数値型の間で四則演算をしようとすると怒られます。

サーバ側で1+1を計算させたいなら、

と書かねばなりません。ee.Number()コンストラクタでJavaScript整数の1をee.Number型に変更してサーバに渡し、同じくee.Number型の1とサーバ側でadd演算を行う、という手続きです。キレないでください。

では今回のように、サーバ側のオブジェクト(たとえばee.String)をクライエント側のオブジェクト(string)としてキャストしたいときはどうすればいいのでしょうか。

こうです:

.getInfo() を使うことで、サーバ側のオブジェクトの実体をクライエント側に持ってくることができます。これで、先ほどのスクリプトはちゃんと期待通りに動いてくれます。これなら例えば、クライエント側で用意した自作プログラムなどと連携させることもできそうです。

ただしGoogle的にはこの方法は非推奨です。理由はおもに2つで、

  • Googleが「ee.なんとか」の中身を確認して展開するまで、コードのほかの処理が止まる。ときにはChromeがフリーズする。

  • クライエント側で処理を行う場合、自分のマシンのCPUを使って実行される。つまりGoogleの計算資源の恩恵が受けられない。

というわけでできる限りサーバ側オブジェクト&メソッドで処理を記述せよ、とのこと。しかし今回のようなケースは仕方ありませんので使っていきましょう(文字列くらいなら展開に時間はかからないし)。

さて、特定の日を出力する方法はわかりましたが、どうせなら一括で出力したいですね、たとえば2015年の画像を全部まとめて出すとか。どうしましょうか。

「Export.image.toDriveを関数化し.mapメソッドで回す」が思い浮かびますが、残念ながらダメです。.mapはサーバ側の処理で、Exportはクライエント側の処理だからです。

仕方ないので、forループを回すことにしましょう。imageListの要素数はimageList.size()で取得できるので、getするリストのインデックスを[0]~[要素数-1]まで増加させながら出力すればよいでしょう。これまでのコードもまとめて、以下になります。

forループのところで、ee.Number型であるimageList.size()をクライエント側に持ってくるのを忘れないでください。そうしないと大小比較ができません。また、変数imageを作成する段階でreprojectしてしまうとプロパティの'system:index'が落ちてしまうので、出力時にreprojectしています。

実行すると、Tasksに大量のRUNが並びますので、頑張ってポチってください。ImageIDをファイル名とするtifファイルがひとつひとつGoogleDriveに出力されるはずです。

それにしても、今回は十数シーンなのでいいですが、シーン数が膨大になったら地獄ですね。そこで、次回はこのポチる手間を省くため、「PythonによるローカルからのGEE実行」について話したいと思います。

[1] 正確に言うと、「ee.なんとか」は、サーバ側のオブジェクトの実体にアクセスするための、クライエント側のプロキシです。プロキシなので実体はありません。より詳しいことはこのあたりを読んでください。https://developers.google.com/earth-engine/client_server