Tellus搭載データを使ってパズルアプリ作ってみた
TellusでSARデータと光学データに馴染んでもらえるようなアプリを作ろう、ということで今回のアプリは誕生しました。
衛星データプラットフォームであるTellusは、今回の最新リリースで、Pythonを使ったデータ分析で便利なJupyter Notebookに対応しました。Tellusについて詳しくはこちらの記事をご覧ください。
Tellusに搭載されているAPIを引っ張ることができるようになったので、パズルアプリを作成しながらJupyter Notebookの機能をご紹介していきたいと思います!
※本記事の内容を実践するためにはTellusの利用登録が必要になります。利用登録はこちらから
Tellusには、大きく2種類のアクセス方法があります。
1つはブラウザ上に表示した地図上に様々なデータを重ね、操作できるTellus OS、もう1つはプログラミングでデータを解析する開発環境(以下、Jupyter Notebook)です。
Tellusの開発環境では、各種データとJupyter NotebookがAPIで連携しています。そのため、Jupyter Notebook上からプログラミングによりAPIで引っ張ることで、データを引っ張り、解析することができます。
この機能を使いながら衛星データパズルを作っていきます!
1. 要件定義
1.1 こんなアプリを作りたい!
Tellusは、他の衛星データプラットフォームに比べると、SARデータが多く搭載される、という特徴があります。光学衛星は太陽からの反射をカメラで捉えるのに対して、SAR衛星は自ら電波を発して観測します。そのため、光学衛星とは対照的に、雲を透過し、夜間にも観測できることから、SARデータは有用なデータであると言われています。
しかしながら、SARデータは一般的にはあまり知られていません。
そこで、SARデータと光学データに馴染んでもらえるようなアプリを作ろう、ということで今回のアプリは誕生しました。
完成したアプリはこちら
1.2 使用技術の選定
基本的な画像解析系のアプリでは、プログラミング言語としてPythonを利用することが多いです。Pythonには画像分割処理のライブラリがあり、今回のアプリ開発も効率よく行えそう、ということで、言語はPythonを選定しました。
フロントエンドのフレームワーク選定については、iOSやらAndroidのアプリではなく、多くの方に応用してもらいやすいだろう、ということを考えてPWAを導入しました。PWAの導入が容易そうなものという観点で、今回はNuxt.jsと@nuxjs/pwaを使うことにしました。
パズル画像処理プロセス | Python: Pllow(画像処理), Flask(API) |
フロントエンドのフレームワーク | Nuxt.js, @nuxtjs/pwa |
2. 事前準備
2.1 環境構築
開発環境の管理にはDockerを利用しました。
2.2 画面デザイン
実装方法を大まかに決めたので、次はパズルアプリのデザインを考えます。ゲームらしさを醸成しつつ、宇宙モチーフのディテールにこだわりました。衛星パズルアプリのスタート画面には、画像を引っ張っている陸域観測衛星だいち2号(ALOS-2)をモチーフにしたイラストを入れてみました。
3. 実装
3.1 ライブラリの選定
開発にあたって、以下のライブラリを利用しました。
簡易的なAPIとしてflaskを使用しました。画像を分割する処理のため、pillowというライブラリを入れました。
参考記事:
https://qiita.com/zaburo/items/5091041a5afb2a7dffc8
ライブラリ | 解説 |
flask flask-api | API作成を簡易的にするため |
pillow | 画像を分割する処理をするため |
requests | httpのリクエストを作成するため |
毎回画像変換して直接APIを叩くわけにもいかないので、SAR画像をAPIから取得してきて、pngに変換してストレージに保持することにしました。
# SAR画像(.tif)をAPIから取得してきて、それを画像クロップして、pngに変換してストレージに保持している
_img = Image.open(BytesIO(_res.content))
_img.point(lambda i:i*(1./256)).convert('L').crop(_box).resize((op_size, op_size)).save(save_sar_png_img_path, 'PNG', quality=True)
画面分割を取得しやすいAPIにしておくため、flaskを使ってAPIを作成しました。flaskでは、getクエリでパズルの分割情報を与えて、画像の分割をするようにしました。
cd image_processing
make devrun
SAR画像だけだととてもではないけどパズルが完成しないので、比較として光学画像も確認したい、というわけで、光学画像も取得しました。
# _req_url='https://gisapi.opf-dev.jp/true/9/449/202.png'
_req_url = '%s/%s/%d/%d/%d.png' % (img_url, map_kinds[map_kind], z, x, y)
_res = requests.get(_req_url, stream=True)
_img = Image.open(BytesIO(_res.content))
※2019年2月21日時点では、こちらのAPIは未公開となっています。正式なAPI発表をお待ちください。
見やすさ、パズルとして面白そう、という感覚で、琵琶湖・東京・佐渡島を引っ張ってみました。それぞれのパラメータは以下。
kind | 画像種類 |
z | ズーム値(大きければ大きいほど、ズームになる) |
x | 地図のx軸の値 |
y | 地図のy軸の値 |
split_n | 分割数(パズルの分割数、3の場合は3×3の9つに分割される) |
琵琶湖
curl http://localhost:5000\?z\=9\&x\=449\&y\=202\&kind\=true\&split_n\=3
curl http://localhost:5000\?z\=9\&x\=449\&y\=202\&kind\=true\&split_n\=4
curl http://localhost:5000\?z\=9\&x\=449\&y\=202\&kind\=true\&split_n\=5
東京
curl http://localhost:5000\?z\=10\&x\=909\&y\=403\&kind\=true\&split_n\=3
curl http://localhost:5000\?z\=10\&x\=909\&y\=403\&kind\=true\&split_n\=4
curl http://localhost:5000\?z\=10\&x\=909\&y\=403\&kind\=true\&split_n\=5
佐渡島
curl http://localhost:5000\?z\=8\&x\=226\&y\=98\&kind\=true\&split_n\=3
curl http://localhost:5000\?z\=8\&x\=226\&y\=98\&kind\=true\&split_n\=4
curl http://localhost:5000\?z\=8\&x\=226\&y\=98\&kind\=true\&split_n\=5
フロントとのつなぎ込みが必要だったため、当初batchでやろうと思っていましたが、nuxtからcallできるようにAPIにしました。またjsonで取得してくる画像の位置情報を管理し、そのパラメータをAPIに送るような、コンフィグラブルにできるようにしました。
3.2 画面のコーディング
画面全体の構成はこんな感じで書きました。
src
├ components(画面を構成するパーツ)
│ ├ puzzle
│ │ └ Puzzle.vue(パズル画面)
│ └ Result.vue(パズル完成画面に表示する結果モーダル)
│
├ layouts
│ ├ default.vue(共通のテンプレート?)
│ └ error.vue(エラーページ)
│
├ pages(画面の実装ファイルがあるフォルダ)
│ ├ difficulty
│ │ ├ _difficulty
│ │ │ └ map
│ │ │ ├ _map
│ │ │ │ ├ complete.vue(パズル完成時の画面)
│ │ │ │ └ index.vue(パズルの画面)
│ │ │ └ index.vue(マップ選択の画面)
│ │ └ index.vue(難易度選択の画面)
│ └ index.vue(トップ画面)
│
├ static
│ └ tile.json(パズルのタイル定義)
├ store
│ └ index.js(エントリーポイント)
└ puzzle.json(難易度ごとのパズルの説明)
PWAにするため、 @nuxtjs/pwa を使用しました。ライブラリをインストールして、nuxt.config.js でインポートするだけで簡単に使うことができます。@nuxtjs/pwaは単一ファイルコンポーネント(Vueファイル)でコンポーネント単位でのファイル分割が可能です。
今回フロントのデザインと密に連携するので、必要な部品については単一コンポーネント化して実装することで、デザイン確定後に呼出元の調整のみでいいようにしました。
またパズルで使う画像は、動的に簡単に変えられるようにしました。
方法としては、jsonで設定ファイルを書き、それをgetのrequest parameterとしてpythonのAPIをcallすることで、動的に画像を変更できるようにしました。
そうすることでjsonファイルを修正するだけで。tellusから取得する画像を変えることができ、パズルの画像を容易に変更できるようにしました。
{
"puzzles": [
{
"id": "lake-biwako",
"name": "琵琶湖",
"description": "日本最大の面積と貯水量を誇る湖",
"sar": [1000, 7800, 23000, 33000],
"parameters": [
{
"kind": "true",
"z": 9,
"x": 449,
"y": 202,
"split_n": 3
},
{
"kind": "true",
"z": 9,
"x": 449,
"y": 202,
"split_n": 4
},
{
"kind": "true",
"z": 9,
"x": 449,
"y": 202,
"split_n": 5
}
]
},
{
"id": "city-tokyo",
"name": "東京",
"description": "世界最大級のメトロポリス",
"parameters": [
{
"kind": "true",
"z": 10,
"x": 909,
"y": 403,
"split_n": 3
},
{
"kind": "true",
"z": 10,
"x": 909,
"y": 403,
"split_n": 4
},
{
"kind": "true",
"z": 10,
"x": 909,
"y": 403,
"split_n": 5
}
]
},
{
"id": "island-sado",
"name": "佐渡ヶ島",
"description": "金・銀で栄えた島",
"parameters": [
{
"kind": "true",
"z": 8,
"x": 226,
"y": 98,
"split_n": 3
},
{
"kind": "true",
"z": 8,
"x": 226,
"y": 98,
"split_n": 4
},
{
"kind": "true",
"z": 8,
"x": 226,
"y": 98,
"split_n": 5
}
]
}
]
}
SAR画像も似たような作りにしようと思いましたが、色々と仕様変更が有りハードコーディングされている状態になっています。
またSAR画像は光学画像と違い、tifという拡張子で画像が配信されるため、PNGに変換して、サイズを光学画像と同じ大きさにする必要があり、その処理も入っています。
_img.point(lambda i:i*(1./256)).convert('L').crop(_box).resize((op_size, op_size)).save(save_sar_png_img_path, 'PNG', quality=True)
3.3 パズルのロジック
あまりにも難しいのだけだと困るので、パズルのマス目は3×3、4×4、5×5の3パターン用意することにしました。あまりにも難しいと、永遠に時間がかかるので、たまにヒントで可視光を見せるように。完成画像を見るというボタンで、最終形態も見ることができるようにしました。
4. できたものの紹介・まとめ
で、最終的に完成したものがこちらです。結構難しいので、ぜひお試しください!
https://satellite-puzzle.app.tellusxdp.com
※Tellusの利用登録はこちらから