GCOM-C/SGLI の 8日平均クロロフィルa濃度を読み込み、対数スケールで黒潮・伊豆諸島周辺の分布を可視化する。
クロロフィルa濃度は、海洋表層に存在する植物プランクトン量の指標としてよく使われる。衛星海色センサーは、海面から戻ってくる可視光の色を観測し、経験式・半解析式などを通じてクロロフィルa濃度を推定する。
黒潮周辺では、黒潮本流、沿岸水、前線、渦、島の周辺などでクロロフィル分布が大きく変わる。海洋学実習で観測する海域がどのような海色場の中にあるのかを、事前に衛星データで確認することが目的である。
この演習では、元のSGLIファイルを学生用に軽量化した NetCDF ファイルを使う。元データは 0.0025° 格子で非常に高解像度であり、変数数も多いため、そのまま学生PCで扱うには重い。そこで CHLA_AVE だけを残し、空間的に粗くした軽量版を使う。
| 項目 | 内容 |
|---|---|
| ファイル名 | kuroshio_sgli_chl_20260501_8day_005deg.nc |
| 変数名 | chl(time, latitude, longitude) |
| 単位 | mg m-3 |
| 期間 | 2026-05-01 から 2026-05-08 の8日平均 |
| 代表日 | 2026-05-04 |
| 主な表示範囲 | 137–142°E, 32–36°N |
下の図は、このページの完全スクリプトを Jupyter Lab で実行して保存した PNG である。HTML と同じディレクトリに画像ファイルを置けば、このようにページ上に掲載できる。
| 書き方 | 意味 |
|---|---|
| xr.open_dataset(...) | NetCDFファイルを開く。 |
| ds["chl"] | クロロフィル変数を取り出す。 |
| isel(time=0) | time次元の最初の時刻を取り出す。 |
| sel(longitude=slice(...), latitude=slice(...)) | 緯度経度の範囲で切り出す。 |
| where(chl_sub > 0) | 0以下の値をNaNにする。 |
| LogNorm(vmin=0.1, vmax=30) | 色を対数スケールで表示する。 |
| ax.text(...) | 地図上に島名などの文字を書く。 |
| plt.savefig(...) | Jupyter Lab の作業ディレクトリに図をPNGとして保存する。 |
import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from matplotlib.colors import LogNorm
filename = "kuroshio_sgli_chl_20260501_8day_005deg.nc"
ds = xr.open_dataset(filename)
print(ds)
chl = ds["chl"].isel(time=0)
date_str = str(ds["time"].values[0])[:10]
print("Representative date:", date_str)
time=1 のファイルなので、isel(time=0) で1枚だけ取り出す。
lon_min, lon_max = 137, 142
lat_min, lat_max = 32, 36
ここを変更すると、表示する範囲を自由に変えられる。
lat = ds["latitude"]
lon = ds["longitude"]
if lat[0] < lat[-1]:
lat_slice = slice(lat_min, lat_max)
else:
lat_slice = slice(lat_max, lat_min)
if lon[0] < lon[-1]:
lon_slice = slice(lon_min, lon_max)
else:
lon_slice = slice(lon_max, lon_min)
chl_sub = chl.sel(
longitude=lon_slice,
latitude=lat_slice
)
lon_sub = chl_sub["longitude"].values
lat_sub = chl_sub["latitude"].values
print("Subset shape:", chl_sub.shape)
緯度座標はデータによって昇順・降順が異なることがある。そのため、lat[0] < lat[-1] で向きを判定してから slice を作っている。
chl_plot = chl_sub.where(chl_sub > 0)
chl_vmin = 0.1
chl_vmax = 30.0
fig = plt.figure(figsize=(9, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent(
[lon_min, lon_max, lat_min, lat_max],
crs=ccrs.PlateCarree()
)
p = ax.pcolormesh(
lon_sub,
lat_sub,
chl_plot,
transform=ccrs.PlateCarree(),
shading="auto",
cmap="jet",
norm=LogNorm(vmin=chl_vmin, vmax=chl_vmax)
)
クロロフィルは 0.1, 1, 10 のように桁で変化するため、線形スケールではなく対数スケールで見る。
ax.add_feature(cfeature.LAND, facecolor="0.75", zorder=3)
ax.coastlines(resolution="10m", linewidth=0.8, zorder=4)
islands = {
"Oshima": (139.38, 34.75),
"Miyake": (139.53, 34.08),
"Mikura": (139.60, 33.88),
"Hachijo": (139.78, 33.10),
}
for name, (x, y) in islands.items():
ax.plot(
x, y,
marker="o",
markersize=4,
color="black",
transform=ccrs.PlateCarree(),
zorder=10
)
ax.text(
x + 0.05, y + 0.05,
name,
transform=ccrs.PlateCarree(),
fontsize=10,
color="black",
weight="bold",
zorder=11,
bbox=dict(
facecolor="white",
edgecolor="none",
alpha=0.75,
boxstyle="round,pad=0.2"
)
)
日本語の島名は、学生PCのMatplotlib環境によって文字化けすることがある。そのため、ここでは英語表記にしている。
Jupyter Lab で作成した図をWebページに掲載するには、plt.show() の前に plt.savefig() を入れる。保存先は、Notebookを実行している作業ディレクトリである。
# 図を保存するファイル名
output_png = f"fig13_sgli_chl_{date_str}_{lon_min}_{lon_max}E_{lat_min}_{lat_max}N.png"
# Web掲載用に高解像度で保存
plt.savefig(output_png, dpi=300, bbox_inches="tight")
print("Saved figure:", output_png)
plt.show()
以下をJupyter Labのセルにそのまま貼り付けて実行する。地図を表示すると同時に、Web掲載用のPNGも作業ディレクトリへ保存する。
# ============================================================
# 13. GCOM-C/SGLIクロロフィルの読み込みとログスケール表示
# ============================================================
#
# このスクリプトで行うこと
# 1. 軽量化した SGLI クロロフィル NetCDF を読む
# 2. 表示したい緯度経度範囲を指定する
# 3. chl 変数を切り出す
# 4. 対数スケールでクロロフィルを地図表示する
# 5. 伊豆諸島の代表的な島名を英語で書き込む
# 6. 作成した図をPNGとして保存する
#
# ファイル:
# kuroshio_sgli_chl_20260501_8day_005deg.nc
#
# 出力図:
# fig13_sgli_chl_2026-05-04_137_142E_32_36N.png
# ============================================================
# ============================================================
# 0. ライブラリ
# ============================================================
import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from matplotlib.colors import LogNorm
# ============================================================
# 1. ファイルを開く
# ============================================================
filename = "kuroshio_sgli_chl_20260501_8day_005deg.nc"
ds = xr.open_dataset(filename)
print(ds)
# ============================================================
# 2. 表示範囲の設定
# ============================================================
#
# ここを変えれば、表示する緯度経度範囲を変更できる。
#
# 例1:SGLIデータ全体に近い範囲
# lon_min, lon_max = 123, 150
# lat_min, lat_max = 24, 50
#
# 例2:黒潮・黒潮続流を中心に見る
# lon_min, lon_max = 130, 150
# lat_min, lat_max = 28, 40
#
# 例3:観測実習海域付近を見る
# lon_min, lon_max = 137, 142
# lat_min, lat_max = 32, 36
# ============================================================
lon_min, lon_max = 137, 142
lat_min, lat_max = 32, 36
# ============================================================
# 3. データを取り出す
# ============================================================
#
# 今回のファイルは time=1 なので isel(time=0) でよい。
# chl の単位は mg m^-3。
# ============================================================
chl = ds["chl"].isel(time=0)
date_str = str(ds["time"].values[0])[:10]
print("Representative date:", date_str)
# ============================================================
# 4. 指定範囲だけ切り出す
# ============================================================
#
# latitude / longitude が昇順か降順かに対応できるようにする。
# 今回の軽量版ファイルでは、latitude が降順の場合があるので、
# slice の向きを自動判定する。
# ============================================================
lat = ds["latitude"]
lon = ds["longitude"]
if lat[0] < lat[-1]:
lat_slice = slice(lat_min, lat_max)
else:
lat_slice = slice(lat_max, lat_min)
if lon[0] < lon[-1]:
lon_slice = slice(lon_min, lon_max)
else:
lon_slice = slice(lon_max, lon_min)
chl_sub = chl.sel(
longitude=lon_slice,
latitude=lat_slice
)
lon_sub = chl_sub["longitude"].values
lat_sub = chl_sub["latitude"].values
print("Subset shape:", chl_sub.shape)
print("Subset lon:", float(np.nanmin(lon_sub)), "to", float(np.nanmax(lon_sub)))
print("Subset lat:", float(np.nanmin(lat_sub)), "to", float(np.nanmax(lat_sub)))
# ============================================================
# 5. 0以下をマスク
# ============================================================
#
# LogNorm を使うときは 0 以下の値を扱えない。
# そのため、0 以下は NaN にする。
# ============================================================
chl_plot = chl_sub.where(chl_sub > 0)
# ============================================================
# 6. カラースケールの設定
# ============================================================
#
# クロロフィルは桁で変わるのでログスケールで描く。
# ここでは 0.1〜30 mg m^-3 とする。
#
# 低濃度域をもっと見たい場合は chl_vmin = 0.01 にしてもよい。
# ============================================================
chl_vmin = 0.1
chl_vmax = 30.0
# ============================================================
# 7. 地図を描く
# ============================================================
fig = plt.figure(figsize=(9, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
# 表示範囲
ax.set_extent(
[lon_min, lon_max, lat_min, lat_max],
crs=ccrs.PlateCarree()
)
# クロロフィルを描画
p = ax.pcolormesh(
lon_sub,
lat_sub,
chl_plot,
transform=ccrs.PlateCarree(),
shading="auto",
cmap="jet",
norm=LogNorm(vmin=chl_vmin, vmax=chl_vmax)
)
# 陸域・海岸線
ax.add_feature(cfeature.LAND, facecolor="0.75", zorder=3)
ax.coastlines(resolution="10m", linewidth=0.8, zorder=4)
# ============================================================
# 8. 島名を地図に書き込む
# ============================================================
#
# 日本語は学生PCのMatplotlib環境で文字化けしやすい。
# そのため、ここでは英語表記にする。
# ============================================================
islands = {
"Oshima": (139.38, 34.75),
"Miyake": (139.53, 34.08),
"Mikura": (139.60, 33.88),
"Hachijo": (139.78, 33.10),
}
# 必要なら以下も追加できるが、伊豆諸島北部ではラベルが混みやすい。
# islands["Toshima"] = (139.28, 34.52)
# islands["Niijima"] = (139.25, 34.37)
# islands["Kozushima"] = (139.15, 34.22)
for name, (x, y) in islands.items():
# 島の位置に点を打つ
ax.plot(
x, y,
marker="o",
markersize=4,
color="black",
transform=ccrs.PlateCarree(),
zorder=10
)
# 島名を書く
ax.text(
x + 0.05, y + 0.05,
name,
transform=ccrs.PlateCarree(),
fontsize=10,
color="black",
weight="bold",
zorder=11,
bbox=dict(
facecolor="white",
edgecolor="none",
alpha=0.75,
boxstyle="round,pad=0.2"
)
)
# ============================================================
# 9. 緯度経度線・カラーバー・タイトル
# ============================================================
gl = ax.gridlines(
draw_labels=True,
linewidth=0.5,
color="gray",
alpha=0.5,
linestyle="--"
)
gl.top_labels = False
gl.right_labels = False
cb = plt.colorbar(
p,
ax=ax,
shrink=0.85,
pad=0.04,
extend="both"
)
cb.set_label("Chlorophyll-a concentration (mg m$^{-3}$)")
plt.title(
f"GCOM-C/SGLI 8-day mean chlorophyll-a concentration\n"
f"{date_str} | {lon_min}–{lon_max}°E, {lat_min}–{lat_max}°N"
)
plt.tight_layout()
# ============================================================
# 10. 図をPNGとして保存する
# ============================================================
#
# Jupyter Lab の作業ディレクトリに保存される。
# Webページに掲載するときは、HTMLと同じディレクトリに置く。
#
# 注意:環境によっては plt.show() の後に保存すると
# 白紙になることがあるので、plt.show() の前に保存する。
# ============================================================
output_png = f"fig13_sgli_chl_{date_str}_{lon_min}_{lon_max}E_{lat_min}_{lat_max}N.png"
plt.savefig(output_png, dpi=300, bbox_inches="tight")
print("Saved figure:", output_png)
plt.show()
# ============================================================
# 11. 後片付け
# ============================================================
ds.close()
print("Finished.")