はじめに
これは Fusic Advent Calendar 2025 の20日目の記事です。前日はDaiki Urataによる「Cursor + Figma Slidesで実現するAIアシストなPresentation as Code」でした。
本記事では、Sentinel-2のデータをAWS 公開データから取得し、NDVI を可視化してみた話をします!
きっかけ
「人工衛星データ解析」と聞くと、
- GIS の専門知識が必要そう
- 高価なソフトや環境が必要そう
- 仕事でしか触れなさそう
という印象があるかもしれません。
しかし実は、Sentinel-2 の衛星データは AWS 上で無料公開されており、
Python + ローカル環境だけでも取得して分析することが可能です。
この記事では、
クラウド(AWS)に公開されている人工衛星データをPythonで読み取り
植生指数(NDVI)を計算して
カラフルな画像として可視化する
ところまでを簡単にやってみます。
使用技術
-
Python 3.12
-
AWS Open Data(Sentinel-2)
-
STAC API(Earth Search)
-
rasterio / numpy / Pillow
構成はとてもシンプルです。
Sentinel-2(AWS公開データ)
↓
Python(ローカル)
↓
NDVI画像(PNG)
Sentinel-2とは?
ESA(欧州宇宙機関)の地球観測衛星
約5日に1回、地球全体を観測
複数の「バンド(波長)」を持つ
今回は
- B04(Red)
- B08(NIR: 近赤外)
のバンドを使います。
データ取得方法:STAC API
AWS には Sentinel-2 の COG(Cloud Optimized GeoTIFF) が公開されており、
HTTP 経由で必要な部分だけ読み込めるのが特徴です。
今回は Earth Search の STAC API を使って、
- 地点(bbox)
- 日付
- 雲量
を条件にシーンを検索します。
今回の解析では、以下の範囲を対象に Sentinel-2 の衛星データを取得しています。
bbox = [130.30, 33.50, 130.60, 33.70]
これは 福岡県・福岡市周辺 を表すバウンディングボックス(矩形範囲)です。
bbox は以下の順番で指定します。
[minLongitude, minLatitude, maxLongitude, maxLatitude]
プロジェクト構成
requirements.txt
stac.py
ndvi.py
local_run.py
使用するライブラリ
# requirements.txt
requests
rasterio
numpy
Pillow
STAC で Sentinel-2 を検索する(stac.py)
STAC(SpatioTemporal Asset Catalog)は、「いつ・どこで・どんな地理データがあるか」をJSON API で検索できる仕組みです。
以下のコードでSentinel-2 の公開データを STAC API で検索し、条件に合う 1シーンを取得。NDVI 計算に必要な Red / NIR バンドのURLを取り出しています。
import requests
STAC_URL = "https://earth-search.aws.element84.com/v1/search"
def search_sentinel2(bbox, date_start, date_end, max_cloud=20):
payload = {
"collections": ["sentinel-2-l2a"],
"bbox": bbox,
"datetime": f"{date_start}/{date_end}",
"query": {
"eo:cloud_cover": {"lte": max_cloud}
},
"limit": 1
}
res = requests.post(STAC_URL, json=payload)
res.raise_for_status()
item = res.json()["features"][0]
return item
def pick_bands(item):
red = item["assets"]["B04"]["href"]
nir = item["assets"]["B08"]["href"]
return red, nir
NDVI を計算してカラー化(ndvi.py)
以下のコードでは、衛星画像(COG)を HTTP越しに直接読み込み、NDVI を数値として計算、疑似カラー画像に変換しています。
import numpy as np
import rasterio
from PIL import Image
import io
def read_band(href):
with rasterio.open(href) as ds:
return ds.read(1).astype(np.float32)
def compute_ndvi(red_href, nir_href):
red = read_band(red_href)
nir = read_band(nir_href)
ndvi = (nir - red) / (nir + red)
return np.clip(ndvi, -1, 1)
def ndvi_to_color_png(ndvi):
ndvi = np.nan_to_num(ndvi, nan=-1.0)
t = (ndvi + 1) / 2
rgb = np.zeros((*t.shape, 3), dtype=np.uint8)
rgb[..., 0] = (255 * (1 - t)).astype(np.uint8) # 赤
rgb[..., 1] = (180 * t).astype(np.uint8) # 緑
img = Image.fromarray(rgb, "RGB")
buf = io.BytesIO()
img.save(buf, format="PNG")
return buf.getvalue()
実行ファイル(local_run.py)
from stac import search_sentinel2, pick_bands
from ndvi import compute_ndvi, ndvi_to_color_png
bbox = [130.30, 33.50, 130.60, 33.70] # 福岡市周辺
date_start = "2025-07-01"
date_end = "2025-07-31"
item = search_sentinel2(bbox, date_start, date_end)
red, nir = pick_bands(item)
ndvi = compute_ndvi(red, nir)
png = ndvi_to_color_png(ndvi)
with open("ndvi.png", "wb") as f:
f.write(png)
print("ndvi.png を出力しました")
出力される画像
今回2025年7月1日~7月31日の範囲で、福岡市の画像をみた結果が以下です。
福岡市都心部にはあまり植生はないですが、東の方にいくとたくさん緑があるのがわかります。
また、大濠公園や福岡空港、那珂川があることもよくわかりますね。
おわりに
人工衛星のデータ解析というと難しそうですが、
「公開データをローカルでちょっと触る」
だけでも十分楽しい世界です。
ぜひチャレンジしてみてください!
