Azure Maps でオープンデータをマッシュアップ
この記事は、Microsoft Azure Tech Advent Calender 2024 の 12/21 の記事です。
単に衛星画像分析に興味があって、興味本位でやっているというだけなのですが 毎年この年始の時期に近づいているくると、家族で出かけることが増えたり、子どもはサンタクロースの追跡していたり (?)、地図を開く機会が増えるかと思います。
今回は、地理空間情報を扱う geopandas など Python の地理空間情報処理ライブライを使ってみることと、Azure Maps とオープンデータを用いて、データのマッシュアップをしてみたいと思います。
データ
地図で扱うデータは、地理空間情報と呼ばれ、経度、緯度といった位置を示す情報 (位置情報) と、
関連する情報を含むデータです。
データには大きく2種類あります。
特徴 | ラスター形式 | ベクター形式 |
---|---|---|
データの種類 | 画像データ | ポリゴン、ライン、ポイント |
データサイズ | 大きい | 小さい |
利用例 | 衛星画像、航空写真 | 地図データ (道路、境界線) |
解像度 | 固定 | 可変 |
ファイル形式 | GeoTIFF | GeoJSON, Shapefile |
今回は、TIFF 形式のラスターデータを欧州宇宙機関 (ESA) が提供する Sentinel Hub EO Browser から Landsat-8 の画像を取得します (無料のユーザー登録を行うことでデータのダウンロードが可能になります)。こちらのプラットフォームは、様々な衛星データを無料にて取得することができます。
また、ベクターデータとして、住んでいる土地の公園データを 鎌倉なびマップ より取得しています。
Azure Maps とは
Azure Maps は、Microsoft Azure のクラウドベースの地理空間サービスです。地図の表示、ルート検索、ジオコーディング、リバースジオコーディング、トラフィック情報、地図のスタイル設定、地図のデータの取得などの機能を提供しています。
主な機能は以下の通りです。
- 地図の表示
- ルート検索
- ジオコーディング / 逆ジオコーディング
- トラフィック情報
- 地図のスタイル設定
- 地図のデータの取得
- 天気情報の取得
環境構築
こちらの環境を用意します。
- Azure Machine Learning ワークスペース
-
Azure ストレージ アカウント
- コンテナー名は
data
としています。
- コンテナー名は
- Azure Maps アカウント
必要なライブラリのインストール
データの読み込み、整形を行うため、必要なライブラリをインストールします。
Azure Machine Learning のインスタンスには、Conda がインストールされているので、
以下の Conda 環境ファイルを使ってインストールを行い、次いでその環境をアクティブにし、Python カーネルを登録します。
早速、Azure Machine Learning のワークスペースに接続し、ターミナルを開きます。
Conda 環境ファイルはこちら。
name: geolab-env
channels:
- conda-forge
dependencies:
- geospatial
- pip
- azure-storage-blob
- pip:
- ipykernel
- matplotlib
上記環境ファイルを用いて、環境を作成します。
(azureml_py38) azureuser@geolabcomp:~/cloudfiles/code/Users/yuri.ohno$ conda env list
# conda environments:
#
base /anaconda
azureml_py310_sdkv2 /anaconda/envs/azureml_py310_sdkv2
azureml_py38 * /anaconda/envs/azureml_py38
azureml_py38_PT_TF /anaconda/envs/azureml_py38_PT_TF
jupyter_env /anaconda/envs/jupyter_env
mount_env /anaconda/envs/mount_env
(azureml_py38) azureuser@geolabcomp:~/cloudfiles/code/Users/yuri.ohno$ conda env create -f environment.yml
...(省略)...
done
#
# To activate this environment, use
#
# $ conda activate geolab-env
#
# To deactivate an active environment, use
#
# $ conda deactivate
(azureml_py38) azureuser@geolabcomp:~/cloudfiles/code/Users/yuri.ohno$ conda activate geolab-env
(geolab-env) azureuser@geolabcomp:~/cloudfiles/code/Users/yuri.ohno$ python -m ipykernel install --user --name geolab-env --display-name "GeoLab Env"
Installed kernelspec geolab-env in /home/azureuser/.local/share/jupyter/kernels/geolab-env
はい、これで準備が整いました。
データの取得
予め取得したデータをストレージ アカウント (Blob ストレージ) にアップロードします。
そのデータを以下のように取得します。
import requests
import aiohttp
import geopandas as gpd
from IPython.display import Image, display
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
import matplotlib.pyplot as plt
from rasterio.plot import show
# Blob データをダウンロード
# Azure Blob Storageの接続情報
connect_str = "<your_connection_string>"
container_name = "data"
blob1 = "park.geojson"
blob2 = "2024-12-12-00_00_2024-12-12-23_59_Landsat_8-9_L2_True_color.tiff"
def get_blob(conn, containername, blobname):
blob_service_client = BlobServiceClient.from_connection_string(conn)
container_client = blob_service_client.get_container_client(containername)
blob_client = container_client.get_blob_client(blobname)
blob_data = blob_client.download_blob().readall()
with open(blobname, "wb") as f:
f.write(blob_data)
get_blob(connect_str, container_name, blob1)
get_blob(connect_str, container_name, blob2)
衛星画像上に公園データを表示
取得したデータを地図上に表示します。
tif_data = rasterio.open("2024-12-12-00_00_2024-12-12-23_59_Landsat_8-9_L2_True_color.tiff")
geojson_data = gpd.read_file("park.geojson")
fig, ax = plt.subplots()
geojson_data.plot(ax=ax, color='orangered')
show(tif_data, ax=ax)
うーん、分解能が低いことと、公園データが散らばりが小さいため、
だいたい鎌倉周辺かなぁ.. という感じでした。
いよいよ Azure Maps を使ってみる
次に Azure Maps を使って、地図上にデータを表示してみます。
やっていることととしては、北鎌倉駅から最も近い公園を探し、そのルートを表示します。
maps_key = "<your_azure_maps_key>"
current_location = [35.336999, 139.544814]
geojson_data = gpd.read_file("park.geojson")
session = aiohttp.ClientSession()
async def get_route(session, origin, destination):
"""
Azure Maps よりルートの取得
"""
url = "https://atlas.microsoft.com/route/directions/json"
params = {
"subscription-key": maps_key,
"api-version": "1.0",
"query": f"{origin[0]},{origin[1]}:{destination[0]},{destination[1]}"
}
response = await session.get(url, params=params)
if response.status != 200:
raise Exception("API call with status code {response.status}")
data = await response.json()
return data
async def show_route_image(session,center,pin,path):
"""
Azure Maps よりルート、始点、終点を表示した画像データを取得
"""
url = "https://atlas.microsoft.com/map/static/png"
params = {
"subscription-key": maps_key,
"api-version": "1.0",
"center": f"{center[1]},{center[0]}",
"pins": pin,
"path": path
}
response = await session.get(url, params=params)
if response.status != 200:
raise Exception("API call with status code {response.status}")
data = await response.content.read()
display(Image(data))
def format_pin(origin,destination):
"""
Azure Maps にて静止画像に表示するピンデータを整形
https://learn.microsoft.com/ja-jp/rest/api/maps/render/get-map-static-image?view=rest-maps-2024-04-01&tabs=HTTP#uri-parameters
"""
return "default|cofa8072||{} {}|{} {}".format(origin[1],origin[0],destination[1],destination[0])
def format_path(route):
"""
Azure Maps にて静止画像に表示するパスデータを整形
https://learn.microsoft.com/ja-jp/rest/api/maps/render/get-map-static-image?view=rest-maps-2024-04-01&tabs=HTTP#uri-parameters
"""
path=[]
for i in range(len(route["routes"][0]["legs"][0]["points"])):
location = list(route["routes"][0]["legs"][0]["points"][i].values())
location[0], location[1] = location[1], location[0]
path.append(location)
formattedpath = ('|'.join(map(str,path))).replace('[','').replace(']','').replace(',','')
return "lc0f6dd9|lw6||{}".format(formattedpath)
nearest_point = None
nearest_route = None
min_distance = float("inf")
for i, feature in geojson_data.iterrows():
point = (feature["geometry"].y, feature["geometry"].x) # [lon,lat] -> (lat,lon)
route = await get_route(session, current_location, point)
distance = route["routes"][0]["summary"]["lengthInMeters"]
if distance < min_distance:
min_distance = distance
nearest_point = point
nearest_route = route
if i == 3:
break
await show_route_image(session,current_location,format_pin(current_location,nearest_point),format_path(nearest_route))
おおっ、北鎌倉駅から最寄りの公園までの経路が取れました。
なお、取得した GeoJSON データに含まれる公園データは、281 件と多いため、最初の 4 件のみを取得しています。
また、上記コードについて2点補足します。
1つは、Azure Maps Route - Get Route Directions の応答として、ルートの距離 (lengthInMeters
) が含まれています。これを利用して、最も近い公園を探しています。
2点目は、静止画像を取得する Azure Maps Render - Get Map Static Image は、パラメータとして、中心座標の center
、もしくは境界を示す bbox
のパラメータ指定が必須となります。
まとめ
今回は、geopandas や rasterio といった地理空間情報を扱う Python ライブラリを用いてみました。
さらに Azure Maps を使って、地図上にデータを表示する例を示しました。
もう一歩踏み込んだ解析としては、衛星画像データから地物の検出や分類を行うことが考えられます。
その一例でラスター画像からプールを検出させる例がこちらにありますので、参考にしてみてください。
Discussion