人工衛星の「姿勢」と聞くと、宇宙空間での向きを思い浮かべるでしょう。しかし、「向き」は何に対しての向きでしょうか。地球に対して? 太陽に対して? 星に対して?
答えは「場面による」です。衛星の姿勢を議論するためには、まず 基準となる座標系 を明確に定義しなければなりません。地球観測衛星はカメラを常に地球に向ける必要があり、通信衛星はアンテナをユーザーの方向に向ける必要があり、太陽観測衛星は望遠鏡を太陽に向ける必要があります。これらの要求を数学的に記述し制御するために、複数の座標系とその間の変換が不可欠なのです。
衛星の座標系と姿勢の定義を理解すると、以下の応用に直結します。
- 姿勢制御系の設計: 目標姿勢の指定と誤差の定義が正確にできる
- 軌道・姿勢連成解析: 軌道位置に応じた最適姿勢の計算ができる
- 外乱トルクの解析: 重力傾斜、空気抵抗、太陽輻射圧などの外乱を適切な座標系で評価できる
- テレメトリの解釈: 地上局が受信する姿勢データの意味を正しく理解できる
本記事の内容
- ECI(地球中心慣性座標系)の定義
- ECEF(地球固定座標系)の定義とECIからの変換
- LVLH(軌道座標系)の定義と軌道要素からの計算
- 機体座標系の定義と姿勢角(ロール・ピッチ・ヨー)
- ナディア指向(地球指向)の概念
- 外乱トルクの概要
- Pythonでの各座標系の3D可視化
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
ECI(地球中心慣性座標系)
なぜ慣性座標系が必要か
ニュートンの運動法則 $\bm{F} = m\bm{a}$ は、慣性座標系(加速度のない座標系)でのみ正しく成り立ちます。衛星の軌道運動や姿勢のダイナミクスを記述するには、まず慣性座標系を定義する必要があります。
地球表面に立つと自転による遠心力やコリオリ力を感じますが、これらは見かけの力であり、慣性座標系では現れません。宇宙工学では、地球の中心を原点とする慣性座標系 ECI(Earth-Centered Inertial) を基準として使います。
ECIの定義
ECIは J2000.0 エポック(2000年1月1日12時 TT)における以下の軸で定義されます。
- $x$ 軸: 春分点方向(地球の赤道面と黄道面の交線のうち、太陽が南半球側から北半球側へ通過する方向)
- $z$ 軸: 地球の自転軸方向(北極方向)
- $y$ 軸: $z \times x$ の右手系を完成させる方向
$$ \hat{\bm{x}}_{\text{ECI}} = \hat{\bm{\gamma}} \quad (\text{春分点方向}), \quad \hat{\bm{z}}_{\text{ECI}} = \hat{\bm{N}} \quad (\text{北極方向}) $$
ECIの重要な特性は、地球の自転とともに回転しないことです。地球は自転していますが、ECIの軸は恒星に対して固定されています(厳密には歳差・章動で極めてゆっくり移動しますが、短期間の衛星運用では無視できます)。
衛星の軌道位置はECIで記述するのが基本です。ケプラー軌道の6要素(軌道要素)は、ECIにおける楕円軌道のパラメータとして定義されます。
ECIは「宇宙空間での絶対的な基準」を提供しますが、地球上の特定の場所との対応を見るには、地球の自転を考慮した座標系が必要です。それが次に説明するECEFです。
ECEF(地球固定座標系)
ECEFの定義
ECEF(Earth-Centered Earth-Fixed) は、地球と一緒に回転する座標系です。
- 原点: 地球の質量中心
- $x$ 軸: 赤道面と本初子午線(グリニッジ子午線)の交点方向
- $z$ 軸: 北極方向(ECIと同じ)
- $y$ 軸: 右手系を完成させる方向(赤道面内、東経90°方向)
ECEFの最大の特徴は、地球上の固定点のECEF座標は時間によらず一定であることです。GPS受信機が出力する位置データ(緯度・経度・高度)は、ECEFに基づいています。
ECIからECEFへの変換
ECIとECEFの違いは、$z$ 軸まわりの回転だけです。地球の自転角 $\theta_G(t)$(グリニッジ恒星時角: GMST)を使って、
$$ \bm{r}_{\text{ECEF}} = R_z(\theta_G) \, \bm{r}_{\text{ECI}} $$
$$ R_z(\theta_G) = \begin{pmatrix} \cos\theta_G & \sin\theta_G & 0 \\ -\sin\theta_G & \cos\theta_G & 0 \\ 0 & 0 & 1 \end{pmatrix} $$
ここで $\theta_G(t)$ は時刻 $t$ におけるグリニッジ恒星時角で、地球の自転速度 $\omega_E \approx 7.2921 \times 10^{-5}$ rad/s に基づいて変化します。
$$ \theta_G(t) = \theta_{G,0} + \omega_E (t – t_0) $$
注意点として、この $R_z$ は通常の回転行列とは符号が逆です。ECIからECEFへの変換は、「ECIの座標を地球の自転分だけ逆に回す」操作に対応するためです。
ECEFは地球表面との対応に便利ですが、衛星の姿勢制御にはさらに衛星の軌道位置に紐づいた座標系が有用です。それが軌道座標系(LVLH)です。
LVLH(軌道座標系)
LVLHとは
LVLH(Local Vertical Local Horizontal) は、衛星の現在位置に原点を持ち、衛星の軌道に沿って定義される座標系です。軌道座標系、RSW座標系、Hill座標系 とも呼ばれます。
LVLHは地球観測衛星や通信衛星にとって最も自然な座標系です。なぜなら、地球を向くべきカメラやアンテナの方向が、この座標系の1つの軸と一致するからです。
LVLHの軸定義
複数の定義が存在しますが、最も一般的なものは以下の通りです。
- $z_o$ 軸(ナディア方向): 衛星から地球中心へ向かう方向($-\hat{\bm{r}}$)
- $x_o$ 軸(速度方向): 軌道面内で $z_o$ に垂直、衛星の進行方向に近い方向
- $y_o$ 軸(軌道面法線方向): $z_o \times x_o$ で右手系を完成
数学的に表すと、衛星のECI位置ベクトル $\bm{r}$ と速度ベクトル $\bm{v}$ を使って、
$$ \hat{\bm{z}}_o = -\frac{\bm{r}}{|\bm{r}|} $$
$$ \hat{\bm{y}}_o = -\frac{\bm{r} \times \bm{v}}{|\bm{r} \times \bm{v}|} $$
$$ \hat{\bm{x}}_o = \hat{\bm{y}}_o \times \hat{\bm{z}}_o $$
ここで、$\bm{r} \times \bm{v}$ は軌道の角運動量ベクトルの方向です。$\hat{\bm{y}}_o$ を角運動量の逆方向に取るのは、右手系で $x_o$ が進行方向になるようにするためです。
円軌道の場合、$\hat{\bm{x}}_o$ は正確に速度方向(along-track)と一致します。楕円軌道では、$\hat{\bm{x}}_o$ と速度方向の間にわずかなズレが生じますが、離心率が小さい(ほぼ円軌道の)衛星ではほとんど無視できます。
ECIからLVLHへの変換行列
ECIからLVLHへの回転行列 $\bm{C}_{OI}$(Orbit from Inertial)は、LVLH基底ベクトルのECI成分を行に並べたものです。
$$ \bm{C}_{OI} = \begin{pmatrix} \hat{\bm{x}}_o^T \\ \hat{\bm{y}}_o^T \\ \hat{\bm{z}}_o^T \end{pmatrix} $$
この行列を使って、ECIベクトルをLVLH成分に変換できます。
$$ \bm{r}_{\text{LVLH}} = \bm{C}_{OI} \, \bm{r}_{\text{ECI}} $$
LVLHは衛星の軌道位置とともに刻々と変化する座標系です。円軌道の場合、LVLHは軌道角速度 $n = \sqrt{\mu/a^3}$ で回転します。
衛星が軌道上を周回する際の「基準の向き」を定義するLVLH座標系ができました。では、この基準に対して衛星本体が実際にどの向きを向いているかをどう記述するのでしょうか。それが機体座標系と姿勢角です。
機体座標系と姿勢角
機体座標系の定義
機体座標系(Body frame) は、衛星の構造体に固定された座標系です。衛星が回転すると一緒に回転します。
通常、衛星の設計時に以下のように定義されます。
- $x_b$ 軸: 衛星の進行方向(ペイロード配置に依存)
- $y_b$ 軸: 衛星の翼方向(太陽電池パネルの法線方向に関連)
- $z_b$ 軸: 右手系を完成(ナディア方向に設定されることが多い)
実際の軸の割り当ては衛星のミッションや設計者の慣習によって異なります。重要なのは、機体座標系が衛星の物理的な構造に紐づいていることです。
姿勢角の定義
衛星の「姿勢」は、LVLH座標系と機体座標系の間の回転関係 で定義されます。この回転を3-2-1オイラー角で表したものが、衛星工学における 姿勢角(attitude angles)です。
$$ \bm{C}_{BI} = \bm{C}_{BO} \, \bm{C}_{OI} $$
ここで、 – $\bm{C}_{BI}$: ECIから機体座標系への回転行列 – $\bm{C}_{OI}$: ECIからLVLHへの回転行列 – $\bm{C}_{BO}$: LVLHから機体座標系への回転行列
$\bm{C}_{BO}$ を3-2-1オイラー角で表すと、
$$ \bm{C}_{BO} = R_x(\phi) \, R_y(\theta) \, R_z(\psi) $$
各角度の衛星工学での意味は以下の通りです。
- ロール角 $\phi$: $x_o$ 軸(進行方向)まわりの回転。衛星を横に傾ける動き
- ピッチ角 $\theta$: $y_o$ 軸(軌道面法線方向)まわりの回転。機首を上げ下げする動き
- ヨー角 $\psi$: $z_o$ 軸(ナディア方向)まわりの回転。衛星を水平面内で回す動き
地球観測衛星が理想的にナディアを指向しているとき、$\phi = \theta = \psi = 0$ です。姿勢角は、この理想状態からのずれを表します。
微小姿勢角の近似
多くの地球指向衛星では、姿勢角は数度以内($\phi, \theta, \psi \ll 1$)に制御されています。この場合、三角関数を線形化できます。
$\cos\alpha \approx 1$, $\sin\alpha \approx \alpha$ として、
$$ \bm{C}_{BO} \approx \begin{pmatrix} 1 & -\psi & \theta \\ \psi & 1 & -\phi \\ -\theta & \phi & 1 \end{pmatrix} = \bm{I} + [\bm{\delta\theta} \times] $$
ここで $[\bm{\delta\theta} \times]$ は姿勢角ベクトル $\bm{\delta\theta} = (\phi, \theta, \psi)^T$ の交差積行列(歪対称行列)です。
$$ [\bm{\delta\theta} \times] = \begin{pmatrix} 0 & -\psi & \theta \\ \psi & 0 & -\phi \\ -\theta & \phi & 0 \end{pmatrix} $$
この線形化は姿勢制御系の設計で非常に重要です。3軸の姿勢制御を独立に扱えるようになり、PD制御やPID制御の適用が容易になります。
衛星の座標系と姿勢の定義が整いました。ここで、多くの衛星が採用する代表的な姿勢モードについて見ておきましょう。
ナディア指向(地球指向)
ナディア指向とは
ナディア とは、衛星直下点の方向(衛星から地球中心への方向)を意味します。ナディア指向(nadir pointing) は、衛星のある軸(通常は $z_b$ 軸)を常にナディア方向に向ける運用モードです。
地球観測衛星、通信衛星、気象衛星の多くがナディア指向(またはそのバリエーション)を採用しています。カメラを常に地球に向ける、アンテナを地表に向けるなど、ミッション要求から自然に導かれる指向方式です。
ナディア指向の実現
完全なナディア指向では、機体座標系がLVLHと完全に一致します($\bm{C}_{BO} = \bm{I}$)。つまり姿勢角はすべてゼロです。
しかし、現実の衛星では外乱トルクの影響で姿勢がLVLHからずれます。姿勢制御系(ADCS: Attitude Determination and Control System)の役割は、このずれを検出し、アクチュエータ(リアクションホイール、磁気トルカ、スラスタなど)を使って補正することです。
その他の指向モード
ナディア指向以外にも、ミッションに応じてさまざまな指向モードがあります。
| 指向モード | 説明 | 代表的な衛星 |
|---|---|---|
| ナディア指向 | $z_b$ 軸を地球中心方向に向ける | 地球観測、通信 |
| 太陽指向 | ある軸を太陽方向に向ける | セーフモード、電力回復 |
| 慣性指向 | ECI座標系に対して固定 | 天文観測(ハッブルなど) |
| ターゲット指向 | 地上の特定地点を追跡 | 高分解能観測、レーダー |
| 速度方向指向 | 進行方向に向ける | SAR衛星、ドラッグフリー衛星 |
指向モードの切り替え(例えばナディア指向から太陽指向への遷移)では、大角度のスルーマヌーバが必要になります。このような場合、オイラー角ではジンバルロックのリスクがあるため、クォータニオンが使われます。
衛星の姿勢を乱す要因について理解しておくことも重要です。次に、主要な外乱トルクの概要を見ましょう。
外乱トルク
宇宙空間は真空ですが、衛星の姿勢を乱すさまざまなトルクが存在します。これらを 外乱トルク と呼びます。姿勢制御系を設計するには、各外乱の特性を理解し、制御系が十分な能力を持つようにする必要があります。
重力傾斜トルク
地球の重力は距離の2乗に反比例するため、衛星の地球に近い側と遠い側で重力の強さが微妙に異なります。衛星の質量分布が均一でない場合(実際にはほぼ全ての衛星がそうです)、この重力差がトルクを生みます。
重力傾斜トルクの大きさのオーダーは、
$$ T_g \sim \frac{3\mu}{R^3} |I_z – I_x| \sin 2\alpha $$
ここで $\mu$ は地球の重力パラメータ、$R$ は軌道半径、$I_x, I_z$ は慣性モーメント、$\alpha$ は姿勢角です。
重力傾斜トルクの重要な特徴は、衛星を長軸が地球方向を向く安定姿勢に戻す復元力として作用する ことです。これを積極的に利用した「重力傾斜安定」は、最も単純な姿勢安定化手法です。
空気抵抗トルク
低軌道衛星(高度600km以下程度)では、極めて希薄ながら大気が残存しています。衛星の圧力中心と質量中心がずれていると、空気力がトルクを生みます。
$$ T_a \sim \frac{1}{2} \rho v^2 C_D A \, l_{cp} $$
$\rho$ は大気密度、$v$ は軌道速度、$C_D$ は抵抗係数、$A$ は断面積、$l_{cp}$ は質量中心と圧力中心のずれです。大気密度は太陽活動に大きく依存し、太陽活動極大期には10倍以上変動することがあります。
太陽輻射圧トルク
太陽光の光子は運動量を持ち、衛星表面に当たるとその運動量を伝えます。太陽光の輻射圧 $P \approx 4.56 \times 10^{-6}$ N/m² は微小ですが、大きな太陽電池パネルを持つ衛星では無視できないトルクを生じます。
$$ T_s \sim P A_s \, l_{sp} (1 + \rho_r) $$
$A_s$ は太陽に面した面積、$l_{sp}$ は質量中心と太陽輻射圧中心のずれ、$\rho_r$ は反射率です。
残留磁気トルク
衛星内部の電流ループや磁性材料が磁気双極子モーメント $\bm{m}$ を持ち、地球の磁場 $\bm{B}$ との相互作用でトルクが生じます。
$$ \bm{T}_m = \bm{m} \times \bm{B} $$
このトルクは小さいですが、磁気トルカ(電磁石コイル)で人為的に磁気双極子を制御して姿勢制御に利用することもできます。特に小型衛星(CubeSatなど)では、磁気トルカが主要な姿勢制御アクチュエータとして使われます。
外乱トルクの比較
LEO(高度500km)における典型的な外乱トルクの大きさを比較します。
| 外乱源 | 典型的な大きさ [Nm] | 特徴 |
|---|---|---|
| 重力傾斜 | $10^{-5}$ | 周期的(軌道周期の2倍波)、姿勢依存 |
| 空気抵抗 | $10^{-6}$ | 高度と太陽活動に強く依存 |
| 太陽輻射圧 | $10^{-6}$ | 一定方向、日蝕時はゼロ |
| 残留磁気 | $10^{-6}$ | 周期的、磁場モデルで予測可能 |
これらの外乱トルクについて理解したところで、Pythonで各座標系を可視化してみましょう。
Pythonでの実装と可視化
各座標系の定義と変換の実装
import numpy as np
# 定数
MU_EARTH = 3.986004418e14 # 地球の重力パラメータ [m^3/s^2]
R_EARTH = 6.371e6 # 地球の平均半径 [m]
OMEGA_EARTH = 7.2921159e-5 # 地球の自転角速度 [rad/s]
def eci_to_ecef(r_eci, gmst):
"""ECI座標からECEF座標への変換"""
Rz = np.array([
[ np.cos(gmst), np.sin(gmst), 0],
[-np.sin(gmst), np.cos(gmst), 0],
[0, 0, 1]
])
return Rz @ r_eci
def compute_lvlh_dcm(r_eci, v_eci):
"""ECI位置・速度ベクトルからLVLH座標系の回転行列を計算"""
r = r_eci / np.linalg.norm(r_eci)
h = np.cross(r_eci, v_eci)
h = h / np.linalg.norm(h)
z_o = -r # ナディア方向
y_o = -h # 軌道面法線の逆方向
x_o = np.cross(y_o, z_o)
x_o = x_o / np.linalg.norm(x_o)
# 回転行列(各行がLVLH軸のECI成分)
C_OI = np.vstack([x_o, y_o, z_o])
return C_OI
def euler_to_dcm_321(phi, theta, psi):
"""3-2-1オイラー角から回転行列"""
Rx = np.array([[1,0,0],[0,np.cos(phi),-np.sin(phi)],[0,np.sin(phi),np.cos(phi)]])
Ry = np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta),0,np.cos(theta)]])
Rz = np.array([[np.cos(psi),-np.sin(psi),0],[np.sin(psi),np.cos(psi),0],[0,0,1]])
return Rx @ Ry @ Rz
# 円軌道の例(高度500km、軌道傾斜角45度)
alt = 500e3 # 高度 [m]
a = R_EARTH + alt
inc = np.radians(45) # 軌道傾斜角
nu = np.radians(30) # 真近点角
# 軌道面内のECI位置・速度
v_orbital = np.sqrt(MU_EARTH / a)
r_orb = a * np.array([np.cos(nu), np.sin(nu), 0])
v_orb = v_orbital * np.array([-np.sin(nu), np.cos(nu), 0])
# 軌道傾斜角の適用(x軸回りに傾ける、簡略化)
Rx_inc = np.array([
[1, 0, 0],
[0, np.cos(inc), -np.sin(inc)],
[0, np.sin(inc), np.cos(inc)]
])
r_eci = Rx_inc @ r_orb
v_eci = Rx_inc @ v_orb
# LVLH座標系の計算
C_OI = compute_lvlh_dcm(r_eci, v_eci)
print("LVLH rotation matrix (C_OI):")
print(np.round(C_OI, 6))
print(f"\nDet(C_OI) = {np.linalg.det(C_OI):.10f}")
print(f"C_OI @ C_OI.T =\n{np.round(C_OI @ C_OI.T, 10)}")
出力から、LVLH回転行列の行列式が1、$C_{OI} C_{OI}^T$ が単位行列であることが確認でき、正しく直交回転行列が構成されていることがわかります。
衛星軌道と座標系の3D可視化
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
MU_EARTH = 3.986004418e14
R_EARTH = 6.371e6
def compute_lvlh_dcm(r_eci, v_eci):
r = r_eci / np.linalg.norm(r_eci)
h = np.cross(r_eci, v_eci)
h = h / np.linalg.norm(h)
z_o = -r
y_o = -h
x_o = np.cross(y_o, z_o)
x_o = x_o / np.linalg.norm(x_o)
return np.vstack([x_o, y_o, z_o])
# 軌道パラメータ
alt = 500e3
a = R_EARTH + alt
inc = np.radians(45)
v_orbital = np.sqrt(MU_EARTH / a)
Rx_inc = np.array([
[1, 0, 0],
[0, np.cos(inc), -np.sin(inc)],
[0, np.sin(inc), np.cos(inc)]
])
# 軌道を1周分計算
nu_arr = np.linspace(0, 2*np.pi, 200)
orbit_eci = np.zeros((len(nu_arr), 3))
for i, nu in enumerate(nu_arr):
r_orb = a * np.array([np.cos(nu), np.sin(nu), 0])
orbit_eci[i] = Rx_inc @ r_orb
# 地球表面(簡易球体)
u = np.linspace(0, 2*np.pi, 40)
v = np.linspace(0, np.pi, 20)
xe = R_EARTH * np.outer(np.cos(u), np.sin(v))
ye = R_EARTH * np.outer(np.sin(u), np.sin(v))
ze = R_EARTH * np.outer(np.ones(np.size(u)), np.cos(v))
fig = plt.figure(figsize=(16, 7))
# === 左: ECI座標系と軌道 ===
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(xe, ye, ze, alpha=0.15, color='skyblue')
ax1.plot(orbit_eci[:,0], orbit_eci[:,1], orbit_eci[:,2],
'b-', linewidth=1.5, label='Orbit')
# ECI軸
scale = R_EARTH * 1.8
for i, (c, lbl) in enumerate(zip(['r','g','b'],
['$x_{ECI}$ (Vernal eq.)','$y_{ECI}$','$z_{ECI}$ (North)'])):
d = np.zeros(3); d[i] = scale
ax1.quiver(0,0,0,*d, color=c, arrow_length_ratio=0.05, linewidth=2)
ax1.text(*(d*1.1), lbl, color=c, fontsize=9)
# 衛星位置(nu=60度)
nu_sat = np.radians(60)
r_sat_orb = a * np.array([np.cos(nu_sat), np.sin(nu_sat), 0])
v_sat_orb = v_orbital * np.array([-np.sin(nu_sat), np.cos(nu_sat), 0])
r_sat = Rx_inc @ r_sat_orb
v_sat = Rx_inc @ v_sat_orb
ax1.scatter(*r_sat, c='orange', s=80, zorder=5, label='Satellite')
ax1.set_title('ECI Frame & Satellite Orbit', fontsize=13)
ax1.set_xlabel('X [m]'); ax1.set_ylabel('Y [m]'); ax1.set_zlabel('Z [m]')
ax1.legend(fontsize=9, loc='upper left')
# === 右: 衛星位置でのLVLH座標系 ===
ax2 = fig.add_subplot(122, projection='3d')
# 軌道(スケーリング)
s = 1e-6 # mからMmへ
ax2.plot(orbit_eci[:,0]*s, orbit_eci[:,1]*s, orbit_eci[:,2]*s,
'b-', linewidth=1, alpha=0.5)
ax2.plot_surface(xe*s, ye*s, ze*s, alpha=0.1, color='skyblue')
# 衛星位置
ax2.scatter(*(r_sat*s), c='orange', s=100, zorder=5)
# LVLH軸
C_OI = compute_lvlh_dcm(r_sat, v_sat)
lvlh_scale = R_EARTH * 0.5 * s
labels_o = [r'$x_o$ (along-track)', r'$y_o$ (cross-track)', r'$z_o$ (nadir)']
colors_o = ['#FF5722', '#4CAF50', '#2196F3']
for i in range(3):
d = C_OI[i] * lvlh_scale # LVLH軸のECI方向
ax2.quiver(*(r_sat*s), *d, color=colors_o[i],
arrow_length_ratio=0.08, linewidth=2.5)
ax2.text(*(r_sat*s + d*1.2), labels_o[i], color=colors_o[i], fontsize=9)
# ナディアライン(衛星→地球中心)
ax2.plot([r_sat[0]*s, 0], [r_sat[1]*s, 0], [r_sat[2]*s, 0],
'k--', linewidth=0.8, alpha=0.4)
ax2.set_title('LVLH Frame at Satellite Position', fontsize=13)
ax2.set_xlabel('X [Mm]'); ax2.set_ylabel('Y [Mm]'); ax2.set_zlabel('Z [Mm]')
plt.tight_layout()
plt.savefig('satellite_coordinate_systems.png', dpi=150, bbox_inches='tight')
plt.show()
上の2つのグラフから、衛星の座標系の階層構造が視覚的に理解できます。
- 左図(ECI座標系と軌道): 地球(水色の球)を中心にECI座標系の3軸が固定されています。赤い$x_{ECI}$は春分点方向、青い$z_{ECI}$は北極方向です。衛星の軌道(青線)は傾斜角45°で傾いており、ECI座標系に対して固定された楕円面上を描いています。
- 右図(LVLH座標系): 衛星位置に原点を持つLVLH座標系が表示されています。$z_o$(青)は地球中心に向かうナディア方向、$x_o$(赤)は軌道に沿った進行方向、$y_o$(緑)は軌道面に垂直な方向です。破線は衛星と地球中心を結ぶナディアラインです。
衛星の姿勢角の効果の可視化
LVLH座標系に対する姿勢角(ロール・ピッチ・ヨー)の効果を可視化します。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def euler_to_dcm_321(phi, theta, psi):
Rx = np.array([[1,0,0],[0,np.cos(phi),-np.sin(phi)],[0,np.sin(phi),np.cos(phi)]])
Ry = np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta),0,np.cos(theta)]])
Rz = np.array([[np.cos(psi),-np.sin(psi),0],[np.sin(psi),np.cos(psi),0],[0,0,1]])
return Rx @ Ry @ Rz
def plot_frame(ax, R, origin, length, labels, colors, alpha=1.0, fontsize=9):
for i in range(3):
d = R[:, i] * length
ax.quiver(*origin, *d, color=colors[i],
arrow_length_ratio=0.08, linewidth=2.5, alpha=alpha)
ax.text(*(origin + d*1.15), labels[i], color=colors[i],
fontsize=fontsize, alpha=alpha)
fig = plt.figure(figsize=(16, 5))
cases = [
(15, 0, 0, 'Roll φ=15°'),
(0, 15, 0, 'Pitch θ=15°'),
(0, 0, 15, 'Yaw ψ=15°')
]
lvlh_labels = [r'$x_o$', r'$y_o$', r'$z_o$']
body_labels = [r'$x_b$', r'$y_b$', r'$z_b$']
lvlh_colors = ['#FFCDD2', '#C8E6C9', '#BBDEFB'] # 薄い色
body_colors = ['#D32F2F', '#388E3C', '#1976D2'] # 濃い色
for idx, (phi_d, theta_d, psi_d, title) in enumerate(cases):
ax = fig.add_subplot(1, 3, idx+1, projection='3d')
# LVLH座標系(基準、薄い色)
plot_frame(ax, np.eye(3), np.zeros(3), 1.0,
lvlh_labels, lvlh_colors, alpha=0.5)
# 機体座標系(姿勢回転後)
C_BO = euler_to_dcm_321(np.radians(phi_d), np.radians(theta_d), np.radians(psi_d))
plot_frame(ax, C_BO.T, np.zeros(3), 1.0,
body_labels, body_colors)
ax.set_title(title, fontsize=13)
ax.set_xlim([-1.3,1.3]); ax.set_ylim([-1.3,1.3]); ax.set_zlim([-1.3,1.3])
ax.set_xlabel('Along-track'); ax.set_ylabel('Cross-track')
ax.set_zlabel('Nadir')
plt.suptitle('Satellite Attitude Angles: Body Frame (dark) vs LVLH Frame (light)',
fontsize=14, y=1.02)
plt.tight_layout()
plt.savefig('attitude_angles.png', dpi=150, bbox_inches='tight')
plt.show()
上の3つのパネルは、各姿勢角が衛星の向きにどう影響するかを示しています。薄い色がLVLH座標系(基準)、濃い色が機体座標系(実際の向き)です。
- ロール15°(左図): $x_o$ 軸(進行方向)まわりの回転。$y_b$ と $z_b$ が傾きますが、$x_b$ は変わりません。衛星を「横に傾ける」動きで、地球観測衛星では片側に位置するターゲットを観測する際に使われます。
- ピッチ15°(中図): $y_o$ 軸(軌道面法線方向)まわりの回転。$x_b$ が上を向き、$z_b$ がナディアから逸れます。カメラの指向を進行方向に傾けるフォワードルッキング観測に使われます。
- ヨー15°(右図): $z_o$ 軸(ナディア方向)まわりの回転。$x_b$ と $y_b$ が水平面内で回転しますが、$z_b$(ナディア方向)は変わりません。地上のトラックを調整する操作です。
軌道上での座標系変化の追跡
衛星が軌道を1周する間のLVLH座標系の変化を追跡します。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
MU_EARTH = 3.986004418e14
R_EARTH = 6.371e6
def compute_lvlh_dcm(r_eci, v_eci):
r = r_eci / np.linalg.norm(r_eci)
h = np.cross(r_eci, v_eci)
h = h / np.linalg.norm(h)
z_o = -r
y_o = -h
x_o = np.cross(y_o, z_o)
x_o = x_o / np.linalg.norm(x_o)
return np.vstack([x_o, y_o, z_o])
alt = 500e3
a = R_EARTH + alt
inc = np.radians(45)
v_orbital = np.sqrt(MU_EARTH / a)
Rx_inc = np.array([
[1, 0, 0],
[0, np.cos(inc), -np.sin(inc)],
[0, np.sin(inc), np.cos(inc)]
])
fig = plt.figure(figsize=(14, 10))
ax = fig.add_subplot(111, projection='3d')
# 地球
u = np.linspace(0, 2*np.pi, 40)
v = np.linspace(0, np.pi, 20)
s = 1e-6
xe = R_EARTH * s * np.outer(np.cos(u), np.sin(v))
ye = R_EARTH * s * np.outer(np.sin(u), np.sin(v))
ze = R_EARTH * s * np.outer(np.ones(np.size(u)), np.cos(v))
ax.plot_surface(xe, ye, ze, alpha=0.12, color='skyblue')
# 軌道
nu_orbit = np.linspace(0, 2*np.pi, 300)
orbit = np.zeros((len(nu_orbit), 3))
for i, nu in enumerate(nu_orbit):
r_orb = a * np.array([np.cos(nu), np.sin(nu), 0])
orbit[i] = Rx_inc @ r_orb
ax.plot(orbit[:,0]*s, orbit[:,1]*s, orbit[:,2]*s,
'b-', linewidth=1, alpha=0.5)
# 8つの位置でLVLH座標系を表示
nu_samples = np.linspace(0, 2*np.pi, 9)[:-1] # 0から315度まで
colors_axes = ['#D32F2F', '#388E3C', '#1976D2']
for nu in nu_samples:
r_orb = a * np.array([np.cos(nu), np.sin(nu), 0])
v_orb = v_orbital * np.array([-np.sin(nu), np.cos(nu), 0])
r_sat = Rx_inc @ r_orb
v_sat = Rx_inc @ v_orb
C_OI = compute_lvlh_dcm(r_sat, v_sat)
frame_scale = R_EARTH * 0.3 * s
for i in range(3):
d = C_OI[i] * frame_scale
ax.quiver(*(r_sat*s), *d, color=colors_axes[i],
arrow_length_ratio=0.12, linewidth=1.5)
ax.scatter(*(r_sat*s), c='orange', s=30, zorder=5)
ax.set_xlabel('X [Mm]', fontsize=11)
ax.set_ylabel('Y [Mm]', fontsize=11)
ax.set_zlabel('Z [Mm]', fontsize=11)
ax.set_title('LVLH Frame Evolution Along Orbit\n'
'(red: along-track, green: cross-track, blue: nadir)',
fontsize=13)
plt.tight_layout()
plt.savefig('lvlh_orbit_evolution.png', dpi=150, bbox_inches='tight')
plt.show()
上のグラフから、LVLH座標系が軌道に沿ってどのように変化するかが視覚的に理解できます。
- ナディア方向(青矢印): 常に地球中心を向いています。衛星の位置が変わるにつれて方向が変わりますが、常に地球に向かっているという特性は保たれています。
- 進行方向(赤矢印): 軌道に沿った接線方向を向いています。軌道1周で360°回転します。
- 軌道面法線(緑矢印): すべての位置で同じ方向を指しています。円軌道では角運動量ベクトルが一定であるため、軌道面法線も一定です。
LVLH座標系は軌道に「固着」して回転するため、ナディア指向衛星ではこの座標系と機体座標系を一致させれば良いのです。軌道周期は約95分(高度500km)なので、LVLH座標系はECIに対して約0.066°/s で回転していることになります。
外乱トルクの大きさの比較
LEO衛星における主要な外乱トルクの大きさを高度の関数として計算します。
import numpy as np
import matplotlib.pyplot as plt
R_EARTH = 6.371e6
MU_EARTH = 3.986004418e14
# 高度範囲
alt_km = np.linspace(200, 2000, 500)
alt_m = alt_km * 1e3
R = R_EARTH + alt_m
# 衛星パラメータ(典型的な中型衛星)
mass = 500 # kg
A_cross = 2.0 # 断面積 [m^2]
A_solar = 5.0 # 太陽に面した面積 [m^2]
delta_I = 50 # |Iz - Ix| [kg m^2]
l_cp = 0.05 # 圧力中心オフセット [m]
l_sp = 0.1 # 太陽輻射圧中心オフセット [m]
m_residual = 0.5 # 残留磁気モーメント [Am^2]
sin_2alpha = 0.1 # 典型的な姿勢角による因子
# 重力傾斜トルク
T_gg = 3 * MU_EARTH / R**3 * delta_I * sin_2alpha
# 空気抵抗トルク(指数大気モデル近似)
rho_0 = 1.225 # 海面大気密度 [kg/m^3]
H = 8500 # スケールハイト [m](低軌道用簡易モデル)
# より現実的なモデル: 高度500kmで約1e-12 kg/m^3
rho = 1e-10 * np.exp(-(alt_m - 300e3) / 50e3)
rho = np.clip(rho, 1e-16, None)
v_orb = np.sqrt(MU_EARTH / R)
Cd = 2.2
T_aero = 0.5 * rho * v_orb**2 * Cd * A_cross * l_cp
# 太陽輻射圧トルク
P_solar = 4.56e-6 # 太陽輻射圧 [N/m^2]
rho_ref = 0.5 # 反射率
T_srp = P_solar * A_solar * l_sp * (1 + rho_ref) * np.ones_like(alt_km)
# 残留磁気トルク(地球磁場のダイポールモデル)
B_0 = 3.12e-5 # 赤道表面での磁場 [T]
B = B_0 * (R_EARTH / R)**3
T_mag = m_residual * B
fig, ax = plt.subplots(figsize=(10, 7))
ax.semilogy(alt_km, T_gg, 'b-', linewidth=2, label='Gravity gradient')
ax.semilogy(alt_km, T_aero, 'r-', linewidth=2, label='Aerodynamic')
ax.semilogy(alt_km, T_srp, 'orange', linewidth=2, label='Solar radiation pressure')
ax.semilogy(alt_km, T_mag, 'g-', linewidth=2, label='Residual magnetic')
ax.set_xlabel('Altitude [km]', fontsize=13)
ax.set_ylabel('Disturbance Torque [Nm]', fontsize=13)
ax.set_title('Disturbance Torques vs Altitude (Typical LEO Satellite)', fontsize=14)
ax.legend(fontsize=12)
ax.grid(True, alpha=0.3, which='both')
ax.set_xlim([200, 2000])
ax.set_ylim([1e-10, 1e-3])
plt.tight_layout()
plt.savefig('disturbance_torques.png', dpi=150, bbox_inches='tight')
plt.show()
上のグラフから、外乱トルクの高度依存性が明確に読み取れます。
- 重力傾斜トルク(青): 高度が上がるにつれて $R^{-3}$ で減少しますが、LEO全域で最も支配的な外乱です。高度200kmでは $10^{-4}$ Nm程度、高度2000kmでも $10^{-6}$ Nm程度の大きさがあります。
- 空気抵抗トルク(赤): 大気密度の指数的減少を反映して、高度とともに急激に減少します。高度300km以下では重力傾斜と同程度ですが、600km以上ではほぼ無視できます。
- 太陽輻射圧トルク(オレンジ): 高度にほぼ依存しない一定値です。大気の影響がなくなる高軌道では、相対的に太陽輻射圧が支配的な外乱になります。
- 残留磁気トルク(緑): 地球磁場の $R^{-3}$ 減衰に従って減少します。
姿勢制御系の設計では、これらの外乱トルクの合計を上回るトルクをアクチュエータが発生できる必要があります。
まとめ
本記事では、人工衛星の姿勢制御に必要な座標系と姿勢の定義について解説しました。
- ECI(地球中心慣性座標系): 宇宙に対して固定された基準座標系。軌道力学とニュートン力学の基盤
- ECEF(地球固定座標系): 地球と一緒に回転。地上局との通信やGPS位置の記述に使用
- LVLH(軌道座標系): 衛星位置に原点を持ち、ナディア・進行方向・軌道面法線で定義。姿勢制御の基準
- 機体座標系: 衛星本体に固定。LVLHとの回転関係(姿勢角)で衛星の「向き」を定義
- 姿勢角: LVLH→機体座標系のオイラー角(ロール・ピッチ・ヨー)。微小角近似で線形化可能
- 外乱トルク: 重力傾斜、空気抵抗、太陽輻射圧、残留磁気が主要。高度により相対的重要性が変わる
各座標系は異なる目的に最適化されており、適切な座標系を選ぶことが正確な姿勢解析の第一歩です。次の記事では、回転運動のダイナミクスを支配する 慣性モーメント について学びます。
次のステップとして、以下の記事も参考にしてください。