Pythonで衛星画像を触ってみる その１

## はじめに
[Tellus](!https://www.tellusxdp.com/ja/)という日本発の衛星データプラットフォームをご存知でしょうか。こちらのプラットフォームを通して国内外の機関・企業が運営している衛星データに触れることができます。今回はTellusを利用して衛星データを取得・操作してみたので、その備忘録を記しておきたいと思います。

## やりたいこと
1. 人工衛星が撮影したデータを取得する
1. 取得したデータに対して何かしら操作してみる

## 参考サイト
Tellusトップページ：https://www.tellusxdp.com/ja/
Tellus API　　　　：https://www.tellusxdp.com/docs/travelers/
やりたいこと内容  ：https://sorabatake.jp/25931/
　(→こちらのサイトで利用されているTellus APIは古いVersionです)

## 実施環境
* Tellus開発環境 (さくらVPS Ubuntu)
→Tellus APIはTellusから提供される開発環境のみで利用可能です。
* Jupyter Notebook (or Jupyter Lab)

## やってみる
本「その１」の記事では取得したいデータを検索し、ローカル環境にダウンロードする部分までを記したいと思います。

### 流れの確認
衛星データをTellusから取得する流れは基本的に以下のイメージです。
1. 取得したいデータセットIDを決める。
1. データセットから条件を指定して検索し、シーンIDを取得する。
1. シーンIDからファイルIDを取得する。
1. データセット、シーンID、ファイルIDを指定してダウンロードURLを生成する。
1. ダウンロードURLを利用してファイルを書き出す（ダウンロードする）。

ではやっていきましょう。

### 取得したいデータの検索
まずは必要なライブラリをインポートします。
```python
# 必要なライブラリをインポートします。
import os
import requests
import json
```
次に、特定の場所（例えば北海道の○○市周辺とか）のデータを検索する関数を作成します。
以下に設定されている`TOKEN`は[Tellusにて作成](!https://www.tellusxdp.com/account/setting/api-access/)します。
`DATA_SET`は[Tellus Traveler](!https://www.tellusxdp.com/traveler/viewer/)から使いたいデータセットのIDを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/3f739afc-cdb3-f6a7-9cab-2170c4821ffb.png)

```python
TOKEN = "所得したトークン"
DATA_SET = "10402ffd-fb70-4625-9b46-63cec87cd5a9" ##ASNARO-2のデータセットIDをセット

#所定の場所のデータ検索をする関数
# Specify dataset and search. Using /datasets/{dataset_id}/data-search/ API.
def scene_search(dataset_id, intersects=None, query={}, sortby=None, paginate=None):
    url = 'https://www.tellusxdp.com/api/traveler/v1/datasets/{}/data-search/'.format(dataset_id)
    headers = {
        "Authorization": "Bearer " + TOKEN,
        'Content-Type': 'application/json'
    }
    payloads = {}
    if intersects is not None:
        payloads['intersects'] = intersects
    if query is not None:
        payloads['query'] = query
    if isinstance(sortby, list):
        payloads['sortby'] = sortby
    if paginate is not None:
        payloads['paginate'] = paginate
    
    r = requests.post(url, headers=headers, data=json.dumps(payloads))
    print(r.status_code)
    if r.status_code != 200:
        print(r.content)
        raise ValueError('status error({}).'.format(r.status_code))
        
    return r.json()

```
上記の関数を呼び出し、今回は緯度経度を指定してデータを検索します。
```python
# 検索条件を指定してデータを検索する関数（AOIを指定）

def get_request():
    ret = scene_search(
        DATA_SET,
        intersects={
            'type':'Polygon','coordinates':[
                [
                    [141.84938200356842,42.75105653576509],
                    [141.83924706503996,42.71645449666897],
                    [141.89461385885272,42.69893933684679],
                    [141.96255548380265,42.72252158826697],
                    [141.95711264644478,42.761943222877846],
                    [141.89968132811697,42.76359672954325],
                    [141.84938200356842,42.75105653576509]
                ]
            ]
        },
    )
    return ret

print(get_request())
```
上記を実行すると、以下のようなレスポンスが返ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/ebe837ed-2254-3e32-9a51-89a6cdf162f9.png)

この中から、今回は以下のシーンIDを使っていきます。
```python
# idが「7d47301d-9533-4be2-a010-31a86912e865」の2020-09-05に撮影されたデータと、
# id「2c9ccde2-3a8c-4cd2-aac5-4f0c3b33a4a4」で2018-09-08に撮影されたデータを使用する。

SCENE_ID1 = '7d47301d-9533-4be2-a010-31a86912e865'
SCENE_ID2 = '2c9ccde2-3a8c-4cd2-aac5-4f0c3b33a4a4'
```
次に、シーンIDを用いてファイルIDを取得する関数を定義し、実行します。
```python
# ファイルID等を取得する関数
def get_files_list(dataset_id, data_id):
    url = 'https://www.tellusxdp.com/api/traveler/v1/datasets/{}/data/{}/files/'.format(dataset_id, data_id)
    headers = {
        "Authorization": "Bearer " + TOKEN,
        'content-type': 'application/json'
    }
    
    r = requests.get(url, headers=headers)
    assert r.status_code == 200
    return r.json()

print(get_files_list(DATA_SET, SCENE_ID1))
print(get_files_list(DATA_SET, SCENE_ID2))
```
上記を実行すると、レスポンスとして沢山のファイルIDが返却されます。この中から今回は以下のファイルIDを使いたいと思います。
```python
# 上記関数で取得したファイルID
FILE_ID1 = '4'
FILE_ID2 = '3'
```
以下部分の実行はオプションですが、ファイルIDを指定すれば使わない他のファイルの情報は含まれない、指定したファイルIDのみの情報が取得できるAPIがあるので、そちらのAPIを実行して見やすいレスポンスを取得します。
```python
# ファイルIDで指定したファイルのみの情報を抽出
def get_file_info(dataset_id, data_id, file_id):
    url = 'https://www.tellusxdp.com/api/traveler/v1/datasets/{}/data/{}/files/{}/'.format(dataset_id, data_id, file_id)
    headers = {
        "Authorization": "Bearer " + TOKEN,
        'content-type': 'application/json'
    }
    
    r = requests.get(url, headers=headers)
    return r.json()

FILE_INFO1 = get_file_info(DATA_SET, SCENE_ID1, FILE_ID1)
FILE_INFO2 = get_file_info(DATA_SET, SCENE_ID2, FILE_ID2)

print(FILE_INFO1)
print(FILE_INFO2)
```
こんな感じのレスポンスが返ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/e35a3ce9-d59b-fda2-ed30-ec08e9f4f536.png)

### 画像のダウンロードURLの作成とダウンロード
上記までで取得した情報（シーンID、ファイルID）を利用してダウンロードURLを生成します。
```python
# Create download URL
def get_download_url(dataset_id, data_id, file_id):
    url = 'https://www.tellusxdp.com/api/traveler/v1/datasets/{}/data/{}/files/{}/download-url/'.format(dataset_id, data_id, file_id)
    headers = {
        "Authorization": "Bearer " + TOKEN,
        'Content-Type': 'application/json',
    }
    
    r = requests.post(url, headers=headers)
    assert r.status_code == 200
    return json.loads(r.content)

DOWNLOAD_URL1 = get_download_url(DATA_SET, SCENE_ID1, FILE_ID1)
DOWNLOAD_URL2 = get_download_url(DATA_SET, SCENE_ID2, FILE_ID2)

print(DOWNLOAD_URL1)
print(DOWNLOAD_URL2)
```
あとは上記で作成されたダウンロードURLから必要な部分を切り出し、ファイルとしてローカルに書き出す処理を実行すればファイルがダウンロードされます。(以下は1つめの画像のみ記載してますが、2つめの画像も同様に実施します)
```python
#取得した情報から必要な情報を取り出す 1つ目の画像
URL1 = DOWNLOAD_URL1['download_url']
FILE_NAME1 = FILE_INFO1['name']
SIZE_BYTES1 = FILE_INFO1['size_bytes']

print('File Name: ' + FILE_NAME1)
print('File Size; ' + str(SIZE_BYTES1))

#ファイルを作成して取得したファイル内容を書き出す。1つ目の画像
response = requests.get(URL1)
with open(FILE_NAME1, "wb") as f:
    for chunk in response.iter_content(chunk_size=1024):
        if chunk:
            f.write(chunk)
            f.flush()
```
無事にファイルの書き出しができました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/56d61f38-5922-1193-b823-a52332a08651.png)

## その2へ続く
[その２](!https://qiita.com/kennyQiita/items/a3af1ad0c63b4556d957)ではダウンロードした画像を使って、衛星画像の操作を少しやってみたいと思います。