🛰️

世界初のL/SデュアルバンドSAR衛星「NISAR」を触ってみよう

に公開

はじめに

SARデータに馴染みのある方は、ワクワクしながら待ち望んでいたのではないでしょうか。

2025年7月、ついにNASAとISROが共同開発したSAR衛星「NISAR」が打ち上げられました。当初は2019〜2021年頃に打ち上げられる予定でしたが、開発の遅延で2024年1月に延期、さらにレーダー反射板の熱問題が発覚して2025年7月まで延期が続きました。本当に打ち上がるのか怪しくなっていましたが、無事に実現しましたね。

そして9月にはファーストライト(初画像)が公開されました。


出典:NASA JPL

さらに、1月24日頃にはASF Data Searchでいくつかのデータが公開されました。


出典:ASF Data Search

ということで、本記事では、NISARの概要からデータの取得方法、Pythonを用いた画像の可視化およびジオレファレンス処理までを、実際にデータを扱いながら紹介します。

SARデータに興味がある方々の参考になれば幸いです。

NISARの概要

NISAR(NASA-ISRO Synthetic Aperture Radar) は、NASAとISRO(インド宇宙研究機関)が共同開発した地球観測衛星です。

主なスペックは以下のとおりです。

項目
軌道 太陽同期準回帰軌道(傾斜角98.4°)
軌道高度 747 km
回帰周期 12日
観測幅 242 km
分解能(アジマス方向) 7 m
分解能(レンジ方向) 5〜100 m (観測モードに依存)
設計寿命 5年

デュアルバンドレーダー

NISARの最大の特徴は、2つの周波数帯を搭載している点です。

バンド 開発 波長 主な用途
Lバンド NASA 24 cm 地形・森林観測、全偏波対応
Sバンド ISRO 12 cm 土壌水分、軽い植生

2つのバンドを持つのは非常に興味深いですが、正直なところ、PバンドでもCバンドでもなく、なぜSバンドを搭載したのか、僕はまだ十分に理解できていません。もしご存知の方がいれば、ぜひ教えていただけると嬉しいです。

SweepSARの採用

SweepSARとはざっくり言うと、広い範囲に一度に電波を照射し、返ってきた信号をアンテナの複数の要素で順に受け取りながら処理する技術です。ALOS-4で採用されているDigital Beam Forming(DBF)と類似した技術です。

SweepSARを採用することで、広い観測幅と高い分解能を両立できます。

SweepSARの概念図
SweepSARの概念図
出典:NASA-ISRO SAR (NISAR) Mission Science Users’ Handbook

ディザリング運用

NISARのもう一つの面白い特徴として、パルスごとに異なる周波数帯を交互に切り替えて送信する点があります。これをディザリングと呼びます。

NISARのファイルには、frequencyAfrequencyBという2つのSLC画像が格納されています。この記事ではメインのfrequencyA(高分解能)を使用します。サブのfrequencyBは、電離層補正のために用意されたようです。

frequencyA frequencyB
中心周波数 1.229 GHz 1.294 GHz
帯域幅 20 MHz 5 MHz
スラントレンジ間隔 6.25 m 24.98 m
用途 高分解能解析 電離層補正・InSAR

データ処理レベル

NISARのデータは、複数の処理レベルで提供されています。

レベル 製品名 内容
L0B RRSD(Radar Raw Signal Data) 未フォーカス生SARデータ
L1 RSLC(Range-Doppler Single Look Complex) スラントレンジSLC
L1 RIFG/RUNW(Range-Doppler Wrapped Interferogram) 干渉画像
L1 ROFF(Range-Doppler Pixel Offsets) 干渉画像
L2 GSLC(Geocoded Single Look Complex) 地理座標系SLC
L2 GUNW/GCOV(Geocoded Interferogram) 地理座標系干渉・共分散
L3 SM(Soil Moisture EASE-Grid 2.0) グローバル土壌水分

今回使用するL1 RSLCは、スラントレンジ座標系(衛星から見た座標)のSLCデータです。複素数(振幅+位相)で格納されており、強度解析にも干渉解析にも利用できます。

データの入手

NISARのデータは、NASA ASF DAAC(Alaska Satellite Facility)から無料でダウンロードできます。

ASF Data Search: https://search.asf.alaska.edu/

ASF Data Search

ファイル構造

NISAR L1 RSLCはHDF5形式で提供されています。ファイルサイズは数GB〜数十GBと大きいので、まずは構造を確認してみましょう。

ファイル構造の全体像(一部のみ)は、以下のようになっています。

science/LSAR/
├── identification/                    # メタデータ
│   ├── absoluteOrbitNumber            # 絶対軌道番号
│   ├── trackNumber                    # トラック番号
│   ├── frameNumber                    # フレーム番号
│   ├── orbitPassDirection             # 軌道方向(Ascending/Descending)
│   ├── lookDirection                  # 視線方向(Left/Right)
│   ├── zeroDopplerStartTime           # 観測開始時刻
│   ├── zeroDopplerEndTime             # 観測終了時刻
│   └── ...

└── RSLC/
    ├── swaths/                        # SLC画像データ本体
    │   ├── frequencyA/                # 高分解能サブバンド(帯域幅20MHz)
    │   │   ├── HH                     # HH偏波
    │   │   ├── HV                     # HV偏波
    │   │   ├── slantRange             # スラントレンジ距離
    │   │   ├── processedCenterFrequency   # 中心周波数
    │   │   ├── processedRangeBandwidth    # レンジ帯域幅
    │   │   └── slantRangeSpacing      # レンジ間隔
    │   │
    │   ├── frequencyB/                # 低分解能サブバンド(帯域幅5MHz)
    │   │   ├── HH                     # HH偏波
    │   │   ├── HV                     # HV偏波
    │   │   ├── slantRange             # スラントレンジ距離
    │   │   ├── processedCenterFrequency   # 中心周波数
    │   │   └── slantRangeSpacing      # レンジ間隔
    │   │
    │   └── zeroDopplerTime            # 方位時刻

    └── metadata/
        ├── geolocationGrid/           # 座標変換グリッド
        │   ├── coordinateX            # 経度
        │   ├── coordinateY            # 緯度
        │   ├── heightAboveEllipsoid   # 高さ層
        │   ├── incidenceAngle         # 入射角
        │   ├── slantRange             # グリッドのレンジ軸
        │   └── zeroDopplerTime        # グリッドの方位軸

        ├── orbit/                     # 軌道データ
        │   ├── time                   # 時刻
        │   ├── position               # 位置
        │   └── velocity               # 速度

        ├── attitude/                  # 姿勢データ
        ├── calibrationInformation/    # 校正データ
        └── processingInformation/     # 処理パラメータ

SLC画像データ本体は swaths/frequencyA/ および swaths/frequencyB/ に格納されています。

with h5py.File(h5_path, "r") as f:
    slc_ds = f["science/LSAR/RSLC/swaths/frequencyA/HH"]

    print(f"SLCデータ: {slc_ds.shape}, {slc_ds.dtype}")
    # → SLCデータ: (54720, 26347), complex64
  • 行(54720): アジマス方向(衛星の進行方向)
  • 列(26347): レンジ方向(衛星からの距離方向)
  • データ型: complex64(複素数)

約14億ピクセルの複素数データです。かなり大きいですね。

また、後ほど行うジオレファレンスに必要な座標データは metadata/geolocationGrid に格納されています。そのほか、軌道・姿勢・校正・処理パラメータなどのメタ情報も metadata/ 配下に格納されています。

SLC振幅画像の可視化

それでは、SLCデータから振幅を抽出して可視化してみましょう。

# SLCデータを読み込み(ダウンサンプリング)
DOWNSAMPLE = 10

with h5py.File(h5_path, "r") as f:
    slc = f["science/LSAR/RSLC/swaths/frequencyA/HH"][::DOWNSAMPLE, ::DOWNSAMPLE]

# 振幅をdBスケールに変換
amplitude = np.abs(slc)
amplitude = np.where(amplitude > 0, amplitude, np.nan)
amplitude_db = 20.0 * np.log10(amplitude)

# プロット
vmin = max(np.nanmedian(amplitude_db) - 15, -30)
vmax = np.nanmedian(amplitude_db) + 15

fig, ax = plt.subplots(figsize=(12, 10))
im = ax.imshow(amplitude_db, cmap="gray", vmin=vmin, vmax=vmax, aspect="auto")
ax.set_title("NISAR L1 RSLC - frequencyA HH Amplitude (dB)")
ax.set_xlabel("Range (pixels)")
ax.set_ylabel("Azimuth (pixels)")
fig.colorbar(im, ax=ax, label="Amplitude (dB)")
plt.show()

SLC振幅画像
© NASA/JPL-Caltech

無事に可視化できました。ただし、この画像はレーダー座標系のままで、横軸がレンジ方向(衛星からの距離)、縦軸がアジマス方向(衛星進行方向)を表しており、地理座標(緯度・経度)は付与されていません。

そこで次に、この画像に地理座標を付与していきます。

ジオレファレンス

NISAR L1 RSLCには、ジオレファレンス用の座標変換グリッド geolocationGrid が含まれています。これを使って、各ピクセルに地理座標を付与していきましょう。

座標変換の流れは以下のとおりです。

SLCピクセル(i, j)
  → (zeroDopplerTime, slantRange) を計算
  → geolocationGridのcoordinateX/Yを補間
  → 緯度・経度

高さ層について

geolocationGrid は20層の高さ層を持っています。SARは斜め方向から観測するため、地表の標高が異なると、同じレーダー座標でも対応する緯度・経度が変わります。

今回は簡易的に高さ0mの層(index=1)を使用しますが、より正確なジオレファレンスにはDEMを使った高さ補正が必要です。

HEIGHT_INDEX = 1  # 高さ0mの層を使用
GCP_STEP = 50     # GCPサンプリング間隔

# geolocationGridから補間器を構築
with h5py.File(h5_path, "r") as f:
    geo = f["science/LSAR/RSLC/metadata/geolocationGrid"]
    grid_zt = geo["zeroDopplerTime"][:]
    grid_sr = geo["slantRange"][:]
    lon_grid = geo["coordinateX"][HEIGHT_INDEX]
    lat_grid = geo["coordinateY"][HEIGHT_INDEX]

interp_lon = RegularGridInterpolator((grid_zt, grid_sr), lon_grid, method="linear")
interp_lat = RegularGridInterpolator((grid_zt, grid_sr), lat_grid, method="linear")

# 各ピクセルの緯度・経度を計算
with h5py.File(h5_path, "r") as f:
    swath_zt = f["science/LSAR/RSLC/swaths/zeroDopplerTime"][::DOWNSAMPLE]
    swath_sr = f["science/LSAR/RSLC/swaths/frequencyA/slantRange"][::DOWNSAMPLE]

n_az, n_rg = slc.shape
swath_zt = swath_zt[:n_az]
swath_sr = swath_sr[:n_rg]

zt_mesh, sr_mesh = np.meshgrid(swath_zt, swath_sr, indexing="ij")
pts = np.column_stack([zt_mesh.ravel(), sr_mesh.ravel()])

lon_2d = interp_lon(pts).reshape(slc.shape)
lat_2d = interp_lat(pts).reshape(slc.shape)

# 地理座標軸付きの画像をプロット
fig, ax = plt.subplots(figsize=(12, 10))
im = ax.pcolormesh(lon_2d, lat_2d, amplitude_db, cmap="gray", vmin=vmin, vmax=vmax, shading="auto")
ax.set_title("NISAR L1 RSLC - frequencyA HH Amplitude (Geocoded)")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_aspect("equal")
fig.colorbar(im, ax=ax, label="Amplitude (dB)")
plt.show()

# GeoTIFF出力(GCPを使ってreproject)
NODATA = -9999.0
gcps = []
for i in range(0, n_az, GCP_STEP):
    for j in range(0, n_rg, GCP_STEP):
        gcps.append(
            GroundControlPoint(row=i, col=j, x=float(lon_2d[i, j]), y=float(lat_2d[i, j]))
        )

src_data = np.where(np.isfinite(amplitude_db), amplitude_db, NODATA).astype(np.float32)
dst_crs = "EPSG:4326"

# GCPs付きラスターを一時ファイルに書き出し
tmp_path = "tmp_with_gcps.tif"
with rasterio.open(
    tmp_path, "w", driver="GTiff",
    height=src_data.shape[0], width=src_data.shape[1],
    count=1, dtype="float32", crs=dst_crs, gcps=gcps, nodata=NODATA,
) as dst:
    dst.write(src_data, 1)

# GCPを使って正規グリッドにreproject
output_path = "nisar_rslc_amplitude.tif"
with rasterio.open(tmp_path) as src:
    transform, width, height = calculate_default_transform(
        src.gcps[1], dst_crs, src.width, src.height,
        gcps=src.gcps[0], resolution=0.001,
    )
    with rasterio.open(
        output_path, "w", driver="GTiff",
        height=height, width=width, count=1, dtype="float32",
        crs=dst_crs, transform=transform, nodata=NODATA,
    ) as dst:
        reproject(
            source=rasterio.band(src, 1),
            destination=rasterio.band(dst, 1),
            src_crs=src.gcps[1], src_transform=None, gcps=src.gcps[0],
            dst_transform=transform, dst_crs=dst_crs,
            resampling=Resampling.bilinear, dst_nodata=NODATA,
        )

ジオレファレンス後の画像
© NASA/JPL-Caltech

これで地図と重ね合わせ可能な形式になりました。GeoTIFF形式でも出力しているので、QGISで表示してみましょう。

QGIS上での可視化
© NASA/JPL-Caltech © Open Street Map

さすが大型SAR衛星ですね。細かく見ていただくとわかりますが、このような簡易的なジオレファレンスだけでも、位置精度が非常に高いことがわかります。小型SAR衛星を扱っている方であれば実感されると思いますが、小型SARではここまでの精度はなかなか出ません。

まとめ

この記事では、NISAR L1 RSLCデータの読み込みから可視化、ジオレファレンス処理までを行いました。

これまでLバンドSARといえば日本のALOSシリーズのみで、基本的には有償での提供でした。それに対してNISARは12日周期で全球を観測し、データは無料で公開される予定です。これにより、LバンドSARデータがより身近なものになると期待されます。

SAR衛星データの活用が広がりつつある今、ぜひ皆さんもSAR衛星データを触ってみてください。
この記事が参考になれば幸いです。

宣伝

本記事では、以下のような内容については詳しく触れませんでした。

  • SARの基本的な仕組み(SweepSARやDBFなど)
  • ASFからのデータダウンロード手順(Earthdata Loginの作成、検索方法など)
  • SARデータの処理レベル
  • SLCデータの意味や振幅画像の解釈、dBスケールの考え方

これらの内容は、2月27日発売予定の 「SAR衛星データ解析入門」(講談社)で体系的に解説しています。SARの原理から基礎解析まで幅広くカバーしていますので、興味のある方はぜひご覧ください。

https://www.amazon.co.jp/dp/4065401453

Discussion