直近の衛星海面高度データを用いて、黒潮海域のADT・SLAを可視化し、海面高度勾配から地衡流を計算する。前方差分と中央差分の違いを確認し、CMEMSに含まれる流速と比較する。
海洋学実習では、船で観測する前に、衛星データから現在の海況を把握しておくことが重要である。この回では、黒潮海域の海面高度データを使い、黒潮の位置、海面高度勾配、地衡流を自分で確認する。
使用するファイルは、作業ディレクトリ直下に置く。
| 変数 | 意味 | 今回の使い方 |
|---|---|---|
adt | Absolute Dynamic Topography。平均場を含む海面高度。 | 黒潮の流路と絶対地衡流を見る。 |
sla | Sea Level Anomaly。平均海面からの偏差。 | 渦・蛇行・地衡流偏差を見る。 |
ugos, vgos | CMEMSに含まれる絶対地衡流。 | ADTから自分で計算した流速と比較する。 |
ugosa, vgosa | CMEMSに含まれる地衡流偏差。 | SLAから自分で計算した流速と比較する。 |
海面高度が場所によって違うと、海中には水平圧力勾配が生じる。静水圧近似では、鉛直方向の圧力勾配は重力とつり合うため、海面の傾きは水平方向の圧力勾配として現れる。
時間変化項や非線形項がコリオリ項に比べて小さい場合、水平運動は主に圧力勾配力とコリオリ力のつり合いで近似できる。この近似が地衡流である。
ここで、ug は東西方向流速、vg は南北方向流速、g は重力加速度、f = 2Ωsinφ はコリオリパラメータ、η は海面高度である。
コンピュータ上の海面高度は、連続関数ではなく格子点上の値である。そのため、偏微分をそのまま計算するのではなく、格子点間の差で近似する。これを有限差分という。



onesided は、厳密な意味での移流方程式の風上差分ではなく、海面高度勾配を片側の格子差で近似する片側差分である。授業では、中央差分との違いを見るための比較用として使う。地衡流の式には ∂η/∂x や ∂η/∂y が出てくる。これは、海面高度 η が東西方向・南北方向にどれくらい傾いているかを表す。
しかし、衛星データは連続した曲線ではなく、緯度経度の格子点上に並んだ値である。そのため、微分を直接計算するのではなく、隣り合う格子点の差で近似する。
η0 η1 η2 η3 η4
●----●----●----●----●
x0 x1 x2 x3 x4
たとえば、点 x2 付近の傾きを知りたい場合、中央差分では、点 x2 をはさんだ左右の値を使う。
中央差分のコードでわかりにくいのは、次のような配列の切り出しである。
dlon2 = lon_rad[2:] - lon_rad[:-2]
d_eta_dx[:, 1:-1] = (eta[:, 2:] - eta[:, :-2]) / dx2d
まず、1次元の配列で考える。
eta = [10, 12, 15, 21, 22, 25]
| 書き方 | 取り出される値 | 意味 |
|---|---|---|
| eta[2:] | [15, 21, 22, 25] | 2番目以降。中央差分では「右隣側」の値に対応する。 |
| eta[:-2] | [10, 12, 15, 21] | 最後の2個を除く。中央差分では「左隣側」の値に対応する。 |
| eta[1:-1] | [12, 15, 21, 22] | 最初と最後を除いた内側。計算結果を入れる中央の点に対応する。 |
したがって、
eta[2:] - eta[:-2]
は、次の差をまとめて計算している。
[15-10, 21-12, 22-15, 25-21]
これは、各点について η[i+1] − η[i−1] を計算していることになる。
| 中央の点 | 使う差 | 意味 |
|---|---|---|
| i = 1 | η[2] − η[0] | 点1をはさんだ右と左の差 |
| i = 2 | η[3] − η[1] | 点2をはさんだ右と左の差 |
| i = 3 | η[4] − η[2] | 点3をはさんだ右と左の差 |
| i = 4 | η[5] − η[3] | 点4をはさんだ右と左の差 |
中央差分では、注目点の左隣と右隣が必要である。したがって、最初の点には左隣がなく、最後の点には右隣がない。
0 1 2 3 4 5
●---●---●---●---●---●
↑ ↑ ↑ ↑
計算できる点
そのため、中央差分の結果は、元の配列と同じ大きさの配列を NaN で用意しておき、内側だけに値を入れている。
d_eta_dx = np.full_like(eta, np.nan, dtype=float)
d_eta_dx[:, 1:-1] = (eta[:, 2:] - eta[:, :-2]) / dx2d
1:-1 は「最初と最後を除いた内側」を意味する。外側1格子は中央差分では計算できないので、NaN のまま残る。
海面高度 eta は、緯度 × 経度の2次元配列である。一般に、
eta[緯度方向, 経度方向]
のように考える。
| 計算したい勾配 | Pythonの書き方 | 何をしているか |
|---|---|---|
| 東西方向の勾配 dη/dx | eta[:, 2:] - eta[:, :-2] | すべての緯度について、経度方向に右隣 − 左隣を計算する。 |
| 南北方向の勾配 dη/dy | eta[2:, :] - eta[:-2, :] | すべての経度について、緯度方向に上側 − 下側を計算する。 |
つまり、コロン : が入っている方向は「全部使う」という意味である。
# x方向、つまり経度方向の差
eta[:, 2:] - eta[:, :-2]
# y方向、つまり緯度方向の差
eta[2:, :] - eta[:-2, :]
NetCDFの座標は、たとえば longitude = 139.0, 139.25, 139.5 のように「度」で表されている。しかし地衡流の式に出てくる x, y は、メートル単位の距離である。
そのため、緯度経度の差をラジアンに直し、地球半径をかけて距離に変換する。
lon_rad = np.deg2rad(lon)
lat_rad = np.deg2rad(lat)
南北方向の距離は、地球半径を R とすると、
で計算できる。ここで dφ は緯度差をラジアンで表したものである。
dlat2 = lat_rad[2:] - lat_rad[:-2]
dy2d = R * dlat2[:, np.newaxis]
一方、東西方向の距離は緯度によって変わる。赤道付近では経度1度の距離は長く、高緯度では短くなる。そのため、緯度の余弦 cos(latitude) をかける。
dlon2 = lon_rad[2:] - lon_rad[:-2]
dx2d = R * np.cos(lat2d_rad[:, 1:-1]) * dlon2[np.newaxis, :]
| 方向 | 距離計算 | 理由 |
|---|---|---|
| 南北方向 | dy = R dlat | 緯度1度あたりの距離はほぼ一定。 |
| 東西方向 | dx = R cos(lat) dlon | 経度1度あたりの距離は緯度によって変わる。 |
np.newaxis は、配列の次元を1つ増やすための書き方である。ここでは、1次元の距離配列を、2次元の緯度経度格子と掛け算できる形にするために使っている。
dlon2.shape
# 例: (198,)
dlon2[np.newaxis, :].shape
# 例: (1, 198)
dlon2 は経度方向だけの1次元配列である。一方、np.cos(lat2d_rad[:, 1:-1]) は緯度 × 経度の2次元配列である。両者を掛け算するために、dlon2[np.newaxis, :] として横方向の2次元配列にしている。
最終的に、中央差分のコードは次のように読める。
| コード | 日本語での意味 |
|---|---|
| dlon2 = lon_rad[2:] - lon_rad[:-2] | 中央差分で使う、右隣と左隣の経度差を作る。 |
| dx2d = R * cos(lat) * dlon2 | 経度差を、各緯度での東西距離 m に直す。 |
| eta[:, 2:] - eta[:, :-2] | 海面高度について、右隣 − 左隣を計算する。 |
| d_eta_dx[:, 1:-1] = ... | 計算した東西勾配を、内側の格子点に入れる。 |
| dlat2 = lat_rad[2:] - lat_rad[:-2] | 中央差分で使う、上側と下側の緯度差を作る。 |
| dy2d = R * dlat2[:, np.newaxis] | 緯度差を南北距離 m に直し、2次元配列と計算できる形にする。 |
| eta[2:, :] - eta[:-2, :] | 海面高度について、上側 − 下側を計算する。 |
| d_eta_dy[1:-1, :] = ... | 計算した南北勾配を、内側の格子点に入れる。 |
片側差分では、注目点とその隣の点だけを使う。
コードでは、次のようになる。
dlon1 = lon_rad[1:] - lon_rad[:-1]
d_eta_dx[:, :-1] = (eta[:, 1:] - eta[:, :-1]) / dx2d
中央差分と比較すると、使っている点が違う。
| 方法 | 使う値 | 特徴 |
|---|---|---|
| 片側差分 | η[j+1] − η[j] | 端の近くまで計算しやすいが、勾配の位置が格子点の中間にずれやすい。 |
| 中央差分 | η[j+1] − η[j−1] | 注目点を中心に左右対称に見るので、一般に滑らかで精度がよい。ただし端は計算できない。 |
ページ内では、まずADTとSLAの基本図を見せ、そのあとCMEMS流速と自分で計算した地衡流の重ね描き、さらに差分図を示す。本文の中心に使うのは図4〜図8で、図9〜図10は差分法の違いを考える発展例として扱う。







下のコードをJupyter Labのセルにコピーして実行する。図は画面に表示されるだけでなく、figures_11_kuroshio/ フォルダにもPNGとして保存される。
# ============================================================
# 11. 黒潮海域の海面高度・地衡流解析(完全スクリプト)
# ============================================================
#
# このスクリプトで行うこと
# ------------------------------------------------------------
# 1. 黒潮海域の海面高度データ(NetCDF)を読み込む
# 2. ADT(Absolute Dynamic Topography)を描く
# 3. SLA(Sea Level Anomaly)を描く
# 4. CMEMSの流速(ugos/vgos, ugosa/vgosa)を表示する
# 5. ADT / SLA から自分で地衡流を計算する
# 6. 差分法を中央差分 / 片側差分で切り替える
# 7. CMEMS流速と自分で計算した流速を比較する
# 8. 差の統計量や差分マップを描く
# 9. Webサイト掲載用に各図を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 LinearSegmentedColormap, TwoSlopeNorm
from matplotlib.lines import Line2D
from pathlib import Path
# ============================================================
# 1. 基本設定
# ============================================================
# ファイル名(作業ディレクトリ直下に置く)
filename = "kuroshio_altimetry_daily_20260501_20260512.nc"
# 使う日
# it = 0 で最初の日、it = -1 で最後の日
it = -1
# 地図範囲
lon_min, lon_max = 100, 150
lat_min, lat_max = 20, 50
# ベクトルの間引き
skip = 16
# ベクトルの長さ調整
# scale を大きくすると矢印は短くなる
vector_scale = 18
# 比較に使う差分法
# "central" : 中央差分
# "onesided" : 片側差分(比較用)
method_main = "central"
# 片側差分と中央差分の違いも見たい場合
compare_two_methods = True
# 図の保存先フォルダ
figdir = Path("figures_11_kuroshio")
figdir.mkdir(exist_ok=True)
# 図保存用の通し番号
fig_count = 0
def save_current_figure(label):
"""
現在の図をPNGとして保存する。
plt.show() の直前で呼び出す。
"""
global fig_count
fig_count += 1
out_png = figdir / f"fig11_kuroshio_{fig_count:02d}_{label}_{date_str}.png"
plt.gcf().savefig(
out_png,
dpi=300,
bbox_inches="tight",
facecolor="white"
)
print("Saved figure:", out_png)
# 定数
g = 9.81 # 重力加速度 [m/s^2]
Omega = 7.2921e-5 # 地球自転角速度 [rad/s]
R = 6371000.0 # 地球半径 [m]
# ============================================================
# 2. 海面高度らしいカラーマップ
# ============================================================
#
# ADTは「海面高度らしい」色:
# 低い = 青
# 高い = 黄~赤
#
# SLAは偏差なので、0を中心に
# 負 = 青
# 正 = 赤
# ============================================================
ssh_colors = [
"#08306b", # deep blue
"#2171b5", # blue
"#6baed6", # light blue
"#c7e9f1", # very light cyan
"#ffffbf", # pale yellow
"#fdae61", # orange
"#d7191c" # red
]
ssh_cmap = LinearSegmentedColormap.from_list("ssh_cmap", ssh_colors, N=256)
sla_cmap = "RdBu_r"
# ============================================================
# 3. データ読み込み
# ============================================================
ds = xr.open_dataset(filename)
print(ds)
lon = ds["longitude"].values
lat = ds["latitude"].values
date_str = str(ds["time"].values[it])[:10]
print("Selected date:", date_str)
adt = ds["adt"].isel(time=it)
sla = ds["sla"].isel(time=it)
ugos = ds["ugos"].isel(time=it)
vgos = ds["vgos"].isel(time=it)
ugosa = ds["ugosa"].isel(time=it)
vgosa = ds["vgosa"].isel(time=it)
# ============================================================
# 4. 地図の共通設定関数
# ============================================================
def setup_map(ax):
"""
地図の共通設定
"""
ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=ccrs.PlateCarree())
# 陸を灰色で塗る
ax.add_feature(cfeature.LAND, facecolor="0.82", zorder=5)
# 海岸線
ax.coastlines(resolution="50m", linewidth=0.9, zorder=6)
# 緯度経度線
gl = ax.gridlines(
draw_labels=True,
linewidth=0.5,
color="gray",
alpha=0.5,
linestyle="--"
)
gl.top_labels = False
gl.right_labels = False
return ax
# ============================================================
# 5. 差分法を切り替えられる地衡流計算関数
# ============================================================
def calc_geostrophic_current_switch(eta, lon, lat, method="central"):
eta = np.asarray(eta, dtype=float)
# 勾配配列を NaN で初期化
d_eta_dx = np.full_like(eta, np.nan, dtype=float)
d_eta_dy = np.full_like(eta, np.nan, dtype=float)
# 緯度経度をラジアンに変換
lon_rad = np.deg2rad(lon)
lat_rad = np.deg2rad(lat)
lon2d, lat2d = np.meshgrid(lon, lat)
lat2d_rad = np.deg2rad(lat2d)
# コリオリパラメータ
f = 2.0 * Omega * np.sin(lat2d_rad)
# 有効点判定
valid = np.isfinite(eta)
# --------------------------------------------------------
# 中央差分
# --------------------------------------------------------
if method == "central":
# x方向勾配 dη/dx
# 中央差分では、注目点 j の右隣 j+1 と左隣 j-1 を使う。
# eta[:, 2:] : 経度方向の右隣側の値
# eta[:, :-2] : 経度方向の左隣側の値
# [:, 1:-1] : 計算結果を入れる中央の格子点
# [η(j+1) - η(j-1)] / [x(j+1)-x(j-1)]
dlon2 = lon_rad[2:] - lon_rad[:-2]
dx2d = R * np.cos(lat2d_rad[:, 1:-1]) * dlon2[np.newaxis, :]
d_eta_dx[:, 1:-1] = (eta[:, 2:] - eta[:, :-2]) / dx2d
# y方向勾配 dη/dy
# eta[2:, :] : 緯度方向の上側の値
# eta[:-2, :] : 緯度方向の下側の値
# [1:-1, :] : 計算結果を入れる中央の格子点
# [η(i+1) - η(i-1)] / [y(i+1)-y(i-1)]
dlat2 = lat_rad[2:] - lat_rad[:-2]
dy2d = R * dlat2[:, np.newaxis]
d_eta_dy[1:-1, :] = (eta[2:, :] - eta[:-2, :]) / dy2d
# 周囲4方向の値が必要
valid_grad = np.zeros_like(valid, dtype=bool)
valid_grad[1:-1, 1:-1] = (
valid[1:-1, 1:-1] &
valid[1:-1, :-2] &
valid[1:-1, 2:] &
valid[:-2, 1:-1] &
valid[2:, 1:-1]
)
# --------------------------------------------------------
# 片側差分(比較用)
# --------------------------------------------------------
elif method == "onesided":
# x方向勾配 dη/dx
# [η(j+1) - η(j)] / [x(j+1)-x(j)]
dlon1 = lon_rad[1:] - lon_rad[:-1]
dx2d = R * np.cos(lat2d_rad[:, :-1]) * dlon1[np.newaxis, :]
d_eta_dx[:, :-1] = (eta[:, 1:] - eta[:, :-1]) / dx2d
# y方向勾配 dη/dy
# [η(i+1) - η(i)] / [y(i+1)-y(i)]
dlat1 = lat_rad[1:] - lat_rad[:-1]
dy2d = R * dlat1[:, np.newaxis]
d_eta_dy[:-1, :] = (eta[1:, :] - eta[:-1, :]) / dy2d
# 右隣・上隣の値が必要
valid_grad = np.zeros_like(valid, dtype=bool)
valid_grad[:-1, :-1] = (
valid[:-1, :-1] &
valid[:-1, 1:] &
valid[1:, :-1]
)
else:
raise ValueError('method must be "central" or "onesided"')
# 地衡流
ug = -g / f * d_eta_dy
vg = g / f * d_eta_dx
# 無効点はNaN
ug[~valid_grad] = np.nan
vg[~valid_grad] = np.nan
speed = np.sqrt(ug**2 + vg**2)
return ug, vg, speed
# ============================================================
# 6. 比較統計を出す関数
# ============================================================
def compare_velocity(u_calc, v_calc, u_ref, v_ref, name):
"""
計算した流速と参照流速(CMEMS)を比較する。
"""
valid = (
np.isfinite(u_calc) &
np.isfinite(v_calc) &
np.isfinite(u_ref) &
np.isfinite(v_ref)
)
du = np.full_like(u_calc, np.nan)
dv = np.full_like(v_calc, np.nan)
du[valid] = u_calc[valid] - u_ref[valid]
dv[valid] = v_calc[valid] - v_ref[valid]
speed_calc = np.sqrt(u_calc**2 + v_calc**2)
speed_ref = np.sqrt(u_ref**2 + v_ref**2)
dspeed = np.full_like(speed_calc, np.nan)
dspeed[valid] = speed_calc[valid] - speed_ref[valid]
print("============================================================")
print(name)
print("============================================================")
print("Number of valid points:", np.sum(valid))
print("du mean :", np.nanmean(du[valid]), "m/s")
print("dv mean :", np.nanmean(dv[valid]), "m/s")
print("du RMSE :", np.sqrt(np.nanmean(du[valid]**2)), "m/s")
print("dv RMSE :", np.sqrt(np.nanmean(dv[valid]**2)), "m/s")
print("speed RMSE:", np.sqrt(np.nanmean(dspeed[valid]**2)), "m/s")
print("du max abs:", np.nanmax(np.abs(du[valid])), "m/s")
print("dv max abs:", np.nanmax(np.abs(dv[valid])), "m/s")
corr_u = np.corrcoef(u_calc[valid], u_ref[valid])[0, 1]
corr_v = np.corrcoef(v_calc[valid], v_ref[valid])[0, 1]
print("u corr :", corr_u)
print("v corr :", corr_v)
return du, dv, dspeed
# ============================================================
# 7. 差分マップ描画関数
# ============================================================
def plot_difference_map(diff, title, label, vmax=None):
"""
差分マップを描く。
"""
fig = plt.figure(figsize=(10, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
setup_map(ax)
if vmax is None:
vmax = float(np.nanpercentile(np.abs(diff), 98))
p = ax.pcolormesh(
lon, lat, diff,
transform=ccrs.PlateCarree(),
shading="auto",
cmap="RdBu_r",
norm=TwoSlopeNorm(vmin=-vmax, vcenter=0.0, vmax=vmax)
)
cb = plt.colorbar(p, ax=ax, shrink=0.85, pad=0.03)
cb.set_label(label)
plt.title(title)
save_current_figure("difference")
plt.show()
# ============================================================
# 8. ベクトル重ね描き関数
# ============================================================
def overlay_two_vector_fields(
background,
u1, v1,
u2, v2,
title,
cbar_label,
cmap,
contour_levels=None,
vmin=None,
vmax=None,
norm=None
):
fig = plt.figure(figsize=(10, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
setup_map(ax)
# 背景図
if norm is None:
p = ax.pcolormesh(
lon, lat, background,
transform=ccrs.PlateCarree(),
shading="auto",
cmap=cmap,
vmin=vmin,
vmax=vmax
)
else:
p = ax.pcolormesh(
lon, lat, background,
transform=ccrs.PlateCarree(),
shading="auto",
cmap=cmap,
norm=norm
)
# 等値線
if contour_levels is not None:
cs = ax.contour(
lon, lat, background,
levels=contour_levels,
colors="0.35",
linewidths=0.6,
transform=ccrs.PlateCarree(),
zorder=4
)
ax.clabel(cs, fontsize=8, fmt="%.2f")
# ベクトルを間引く
lon_q = lon[::skip]
lat_q = lat[::skip]
u1_q = u1[::skip, ::skip]
v1_q = v1[::skip, ::skip]
u2_q = u2[::skip, ::skip]
v2_q = v2[::skip, ::skip]
# 黒:CMEMS
q1 = ax.quiver(
lon_q, lat_q,
u1_q, v1_q,
transform=ccrs.PlateCarree(),
color="black",
scale=vector_scale,
width=0.0022,
headwidth=3.8,
headlength=4.8,
zorder=7
)
# 赤:自分で計算した流速
q2 = ax.quiver(
lon_q, lat_q,
u2_q, v2_q,
transform=ccrs.PlateCarree(),
color="red",
scale=vector_scale,
width=0.0020,
headwidth=3.8,
headlength=4.8,
alpha=0.9,
zorder=8
)
# 凡例(左上:中国大陸側)
legend_elements = [
Line2D([0], [0], color="black", lw=2, label="CMEMS velocity"),
Line2D([0], [0], color="red", lw=2, label="Calculated from height")
]
ax.legend(
handles=legend_elements,
loc="upper left",
bbox_to_anchor=(0.02, 0.98),
frameon=True,
framealpha=0.95,
fontsize=11
)
# 矢印スケール
ax.quiverkey(
q1,
X=0.88, Y=-0.08,
U=1.0,
label="1.0 m/s",
labelpos="E",
coordinates="axes"
)
cb = plt.colorbar(p, ax=ax, shrink=0.85, pad=0.03)
cb.set_label(cbar_label)
plt.title(title)
save_current_figure("overlay")
plt.show()
# ============================================================
# 9. ADT のマッピング
# ============================================================
adt_vmin = float(np.nanpercentile(adt, 2))
adt_vmax = float(np.nanpercentile(adt, 98))
fig = plt.figure(figsize=(10, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
setup_map(ax)
p = ax.pcolormesh(
lon, lat, adt,
transform=ccrs.PlateCarree(),
shading="auto",
cmap=ssh_cmap,
vmin=adt_vmin,
vmax=adt_vmax
)
cs = ax.contour(
lon, lat, adt,
levels=np.arange(0.2, 2.1, 0.1),
colors="black",
linewidths=0.5,
transform=ccrs.PlateCarree()
)
ax.clabel(cs, fontsize=8, fmt="%.1f")
cb = plt.colorbar(p, ax=ax, shrink=0.85, pad=0.03)
cb.set_label("ADT (m)")
plt.title(f"Absolute Dynamic Topography (ADT)\n{date_str}")
save_current_figure("adt")
plt.show()
# ============================================================
# 10. SLA のマッピング
# ============================================================
sla_abs = float(np.nanpercentile(np.abs(sla), 98))
fig = plt.figure(figsize=(10, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
setup_map(ax)
p = ax.pcolormesh(
lon, lat, sla,
transform=ccrs.PlateCarree(),
shading="auto",
cmap=sla_cmap,
norm=TwoSlopeNorm(vmin=-sla_abs, vcenter=0.0, vmax=sla_abs)
)
cs = ax.contour(
lon, lat, sla,
levels=np.arange(-0.5, 0.55, 0.05),
colors="black",
linewidths=0.4,
transform=ccrs.PlateCarree()
)
ax.clabel(cs, fontsize=8, fmt="%.2f")
cb = plt.colorbar(p, ax=ax, shrink=0.85, pad=0.03)
cb.set_label("SLA (m)")
plt.title(f"Sea Level Anomaly (SLA)\n{date_str}")
save_current_figure("sla")
plt.show()
# ============================================================
# 11. ADT から地衡流を計算
# ============================================================
ug_adt_main, vg_adt_main, speed_adt_main = calc_geostrophic_current_switch(
adt.values, lon, lat, method=method_main
)
speed_cmems_abs = np.sqrt(ugos.values**2 + vgos.values**2)
# ============================================================
# 12. SLA から地衡流偏差を計算
# ============================================================
ug_sla_main, vg_sla_main, speed_sla_main = calc_geostrophic_current_switch(
sla.values, lon, lat, method=method_main
)
speed_cmems_ano = np.sqrt(ugosa.values**2 + vgosa.values**2)
# ============================================================
# 13. ADT + 絶対地衡流 を重ね描き
# ============================================================
overlay_two_vector_fields(
background=adt.values,
u1=ugos.values,
v1=vgos.values,
u2=ug_adt_main,
v2=vg_adt_main,
title=f"ADT and Absolute Geostrophic Velocity\n{date_str} ({method_main} difference)",
cbar_label="ADT (m)",
cmap=ssh_cmap,
contour_levels=np.arange(0.2, 2.1, 0.1),
vmin=adt_vmin,
vmax=adt_vmax
)
# ============================================================
# 14. SLA + 地衡流偏差 を重ね描き
# ============================================================
overlay_two_vector_fields(
background=sla.values,
u1=ugosa.values,
v1=vgosa.values,
u2=ug_sla_main,
v2=vg_sla_main,
title=f"SLA and Geostrophic Velocity Anomaly\n{date_str} ({method_main} difference)",
cbar_label="SLA (m)",
cmap=sla_cmap,
contour_levels=np.arange(-0.5, 0.55, 0.05),
norm=TwoSlopeNorm(vmin=-sla_abs, vcenter=0.0, vmax=sla_abs)
)
# ============================================================
# 15. CMEMSとの比較統計
# ============================================================
du_abs, dv_abs, dspeed_abs = compare_velocity(
ug_adt_main, vg_adt_main,
ugos.values, vgos.values,
f"ADT-derived velocity ({method_main}) vs CMEMS ugos/vgos"
)
du_ano, dv_ano, dspeed_ano = compare_velocity(
ug_sla_main, vg_sla_main,
ugosa.values, vgosa.values,
f"SLA-derived velocity anomaly ({method_main}) vs CMEMS ugosa/vgosa"
)
# ============================================================
# 16. 差分マップ
# ============================================================
# ADT由来の絶対地衡流との差
plot_difference_map(
du_abs,
title=f"Difference in zonal velocity\nCalculated from ADT - CMEMS ugos\n{date_str}",
label="du (m/s)",
vmax=0.10
)
plot_difference_map(
dv_abs,
title=f"Difference in meridional velocity\nCalculated from ADT - CMEMS vgos\n{date_str}",
label="dv (m/s)",
vmax=0.10
)
plot_difference_map(
dspeed_abs,
title=f"Difference in speed\nCalculated from ADT - CMEMS speed\n{date_str}",
label="speed difference (m/s)",
vmax=0.10
)
# SLA由来の地衡流偏差との差
plot_difference_map(
du_ano,
title=f"Difference in zonal velocity anomaly\nCalculated from SLA - CMEMS ugosa\n{date_str}",
label="du anomaly (m/s)",
vmax=0.02
)
plot_difference_map(
dv_ano,
title=f"Difference in meridional velocity anomaly\nCalculated from SLA - CMEMS vgosa\n{date_str}",
label="dv anomaly (m/s)",
vmax=0.02
)
plot_difference_map(
dspeed_ano,
title=f"Difference in anomaly speed\nCalculated from SLA - CMEMS anomaly speed\n{date_str}",
label="speed difference (m/s)",
vmax=0.02
)
# ============================================================
# 17. 中央差分と片側差分の比較(任意)
# ============================================================
if compare_two_methods:
# ADT
ug_adt_c, vg_adt_c, speed_adt_c = calc_geostrophic_current_switch(
adt.values, lon, lat, method="central"
)
ug_adt_o, vg_adt_o, speed_adt_o = calc_geostrophic_current_switch(
adt.values, lon, lat, method="onesided"
)
# SLA
ug_sla_c, vg_sla_c, speed_sla_c = calc_geostrophic_current_switch(
sla.values, lon, lat, method="central"
)
ug_sla_o, vg_sla_o, speed_sla_o = calc_geostrophic_current_switch(
sla.values, lon, lat, method="onesided"
)
# 統計比較
compare_velocity(
ug_adt_c, vg_adt_c,
ugos.values, vgos.values,
"ADT-derived velocity: central difference vs CMEMS ugos/vgos"
)
compare_velocity(
ug_adt_o, vg_adt_o,
ugos.values, vgos.values,
"ADT-derived velocity: one-sided difference vs CMEMS ugos/vgos"
)
compare_velocity(
ug_sla_c, vg_sla_c,
ugosa.values, vgosa.values,
"SLA-derived velocity anomaly: central difference vs CMEMS ugosa/vgosa"
)
compare_velocity(
ug_sla_o, vg_sla_o,
ugosa.values, vgosa.values,
"SLA-derived velocity anomaly: one-sided difference vs CMEMS ugosa/vgosa"
)
# 差分法どうしの差
du_method_adt = ug_adt_o - ug_adt_c
dv_method_adt = vg_adt_o - vg_adt_c
dspeed_method_adt = speed_adt_o - speed_adt_c
du_method_sla = ug_sla_o - ug_sla_c
dv_method_sla = vg_sla_o - vg_sla_c
dspeed_method_sla = speed_sla_o - speed_sla_c
# ADTの差分法比較
plot_difference_map(
du_method_adt,
title=f"Difference in zonal velocity\none-sided - central (ADT)\n{date_str}",
label="du difference (m/s)",
vmax=0.15
)
plot_difference_map(
dv_method_adt,
title=f"Difference in meridional velocity\none-sided - central (ADT)\n{date_str}",
label="dv difference (m/s)",
vmax=0.15
)
plot_difference_map(
dspeed_method_adt,
title=f"Difference in speed\none-sided - central (ADT)\n{date_str}",
label="speed difference (m/s)",
vmax=0.15
)
# SLAの差分法比較
plot_difference_map(
du_method_sla,
title=f"Difference in zonal velocity anomaly\none-sided - central (SLA)\n{date_str}",
label="du anomaly difference (m/s)",
vmax=0.02
)
plot_difference_map(
dv_method_sla,
title=f"Difference in meridional velocity anomaly\none-sided - central (SLA)\n{date_str}",
label="dv anomaly difference (m/s)",
vmax=0.02
)
plot_difference_map(
dspeed_method_sla,
title=f"Difference in speed anomaly\none-sided - central (SLA)\n{date_str}",
label="speed anomaly difference (m/s)",
vmax=0.02
)
# ============================================================
# 18. 終了処理
# ============================================================
ds.close()
print("Finished.")
中央差分を使うと、SLAから計算した地衡流偏差は、CMEMSの ugosa, vgosa と非常によく一致しやすい。一方、ADTから計算した絶対地衡流は、黒潮流軸や沿岸付近でCMEMSの ugos, vgos と差が残りやすい。
| 比較 | 期待される特徴 | 考えること |
|---|---|---|
| SLA → ugosa/vgosa | 中央差分ではほぼ一致しやすい | 地衡流偏差はSLAの勾配から計算されることを確認できる |
| ADT → ugos/vgos | 大局的には一致するが、黒潮流軸・沿岸で差が残りやすい | ADTにはMDTが含まれ、プロダクト処理、平滑化、マスク処理の影響を受ける |
| 片側差分 vs 中央差分 | 片側差分の方が差が大きくなりやすい | 数値微分の方法が流速推定に影響する |