LEOコンステレーションの設計と最適化問題を解説

近年、低軌道(LEO, Low Earth Orbit)に多数の衛星を打ち上げ、情報通信サービスを提供するLEOコンステレーションが商業化されつつあります。SpaceXのStarlink(スターリンク)が最も有名です。

LEOコンステレーションの設計では、限られた衛星数で地球全体をカバーするための軌道配置の最適化が重要な課題となります。

本記事の内容

  • LEOコンステレーションの基本概念
  • Walkerデルタパターンの数学的定式化
  • 被覆率の計算
  • Pythonでのコンステレーションシミュレーション

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

LEOコンステレーションの基本

軌道高度と特性

パラメータ LEO MEO GEO
高度 [km] 300-2000 2000-35786 35786
軌道周期 [分] 90-127 2-24時間 24時間
遅延 [ms] 1-7 30-120 ~240
必要衛星数 数百-数千 数十 3

LEOの利点は低遅延ですが、1機の衛星のカバー範囲が狭いため、多数の衛星が必要になります。

Walkerデルタパターン

コンステレーションの標準的な配置方法がWalkerデルタパターンです。$i:T/P/F$ の3つのパラメータで記述されます。

  • $i$: 軌道傾斜角 [deg]
  • $T$: 総衛星数
  • $P$: 軌道面数
  • $F$: 位相パラメータ ($0 \leq F < P$)

各軌道面には $S = T/P$ 機の衛星が均等に配置されます。

軌道面の配置

$P$ 個の軌道面の昇交点赤経は均等に分布します。

$$ \Omega_p = \frac{360° \cdot p}{P}, \quad p = 0, 1, \ldots, P-1 $$

衛星の配置

軌道面 $p$ 内の $s$ 番目の衛星の平均近点角は、

$$ M_{p,s} = \frac{360° \cdot s}{S} + \frac{360° \cdot F \cdot p}{T} $$

ここで $s = 0, 1, \ldots, S-1$ です。位相パラメータ $F$ は隣接軌道面間の衛星の位相差を制御します。

被覆率の計算

単一衛星のカバー範囲

高度 $h$ の衛星が最低仰角 $\varepsilon_{\min}$ 以上でカバーできる地表面積は、半頂角 $\theta_{\max}$ の円錐で近似できます。

$$ \theta_{\max} = \arccos\left(\frac{R_E}{R_E + h}\cos\varepsilon_{\min}\right) – \varepsilon_{\min} $$

カバー面積は、

$$ A_{\text{cover}} = 2\pi R_E^2 (1 – \cos\theta_{\max}) $$

全球被覆の必要衛星数

全球(面積 $4\pi R_E^2$)をカバーするために必要な最小衛星数の下限は、

$$ N_{\min} \geq \frac{4\pi R_E^2}{A_{\text{cover}}} = \frac{2}{1 – \cos\theta_{\max}} $$

実際には衛星間のカバー領域の重なりがあるため、この値の1.5から2倍程度の衛星が必要です。

Pythonでのコンステレーションシミュレーション

import numpy as np
import matplotlib.pyplot as plt

# 定数
Re = 6371.0  # 地球半径 [km]

def walker_constellation(T, P, F, h, inc):
    """Walkerデルタパターンのコンステレーションを生成

    Parameters
    ----------
    T : int - 総衛星数
    P : int - 軌道面数
    F : int - 位相パラメータ
    h : float - 軌道高度 [km]
    inc : float - 軌道傾斜角 [deg]

    Returns
    -------
    sats : list of dict - 各衛星の軌道要素
    """
    S = T // P  # 1面あたりの衛星数
    inc_rad = np.radians(inc)
    sats = []

    for p in range(P):
        raan = 360.0 * p / P  # 昇交点赤経 [deg]
        for s in range(S):
            M = 360.0 * s / S + 360.0 * F * p / T  # 平均近点角 [deg]
            M = M % 360.0
            sats.append({
                'plane': p,
                'index': s,
                'raan': raan,
                'M': M,
                'inc': inc,
                'h': h,
            })
    return sats

def sat_subpoint(sat, t=0):
    """衛星の直下点(緯度・経度)を計算(簡易モデル)"""
    h = sat['h']
    a = Re + h
    mu = 398600.4418
    T_orb = 2 * np.pi * np.sqrt(a**3 / mu)
    n = 2 * np.pi / T_orb

    inc_rad = np.radians(sat['inc'])
    raan_rad = np.radians(sat['raan'])
    M_rad = np.radians(sat['M']) + n * t

    # 衛星の位置(軌道面内)
    lat = np.degrees(np.arcsin(np.sin(inc_rad) * np.sin(M_rad)))
    lon_inertial = np.degrees(np.arctan2(
        np.sin(M_rad) * np.cos(inc_rad), np.cos(M_rad)))
    # 地球自転を考慮
    omega_e = 2 * np.pi / 86164.1
    lon = (lon_inertial + np.degrees(raan_rad) - np.degrees(omega_e * t)) % 360
    if lon > 180:
        lon -= 360

    return lat, lon

def coverage_angle(h, eps_min_deg):
    """カバー範囲の半頂角 [rad]"""
    eps_min = np.radians(eps_min_deg)
    theta = np.arccos(Re / (Re + h) * np.cos(eps_min)) - eps_min
    return theta

# Starlinkライクなコンステレーション
T = 72   # 総衛星数(簡易版)
P = 6    # 軌道面数
F = 1    # 位相パラメータ
h = 550  # 軌道高度 [km]
inc = 53 # 軌道傾斜角 [deg]

sats = walker_constellation(T, P, F, h, inc)

# 被覆率の計算
eps_min = 25  # 最低仰角 [deg]
theta_cover = coverage_angle(h, eps_min)
A_cover = 2 * np.pi * Re**2 * (1 - np.cos(theta_cover))
A_earth = 4 * np.pi * Re**2
N_min = 2 / (1 - np.cos(theta_cover))

print(f"=== コンステレーション設計 ===")
print(f"Walker {inc}:{T}/{P}/{F}")
print(f"軌道高度: {h} km")
print(f"最低仰角: {eps_min} deg")
print(f"カバー半頂角: {np.degrees(theta_cover):.1f} deg")
print(f"1衛星のカバー面積: {A_cover:.0f} km^2")
print(f"地球全表面積: {A_earth:.0f} km^2")
print(f"必要最小衛星数(下限): {N_min:.0f}")

# 可視化
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# 衛星の配置(t=0)
lats = []
lons = []
colors = []
for sat in sats:
    lat, lon = sat_subpoint(sat, t=0)
    lats.append(lat)
    lons.append(lon)
    colors.append(sat['plane'])

scatter = axes[0].scatter(lons, lats, c=colors, cmap='tab10', s=30, zorder=5)
axes[0].set_xlim(-180, 180)
axes[0].set_ylim(-90, 90)
axes[0].set_xlabel('Longitude [deg]')
axes[0].set_ylabel('Latitude [deg]')
axes[0].set_title(f'Walker {inc}:{T}/{P}/{F} at h={h}km')
axes[0].grid(True, alpha=0.3)

# 各衛星のカバー範囲を円で描画
for lat, lon in zip(lats, lons):
    theta_deg = np.degrees(theta_cover)
    circle_lats = lat + theta_deg * np.cos(np.linspace(0, 2*np.pi, 50))
    circle_lons = lon + theta_deg / np.cos(np.radians(lat)) * np.sin(np.linspace(0, 2*np.pi, 50))
    circle_lats = np.clip(circle_lats, -90, 90)
    axes[0].plot(circle_lons, circle_lats, 'b-', alpha=0.1, linewidth=0.5)

plt.colorbar(scatter, ax=axes[0], label='Orbital Plane')

# 高度と必要衛星数の関係
altitudes = np.linspace(300, 2000, 100)
for eps in [10, 25, 40]:
    N_mins = []
    for alt in altitudes:
        theta = coverage_angle(alt, eps)
        N_min_val = 2 / (1 - np.cos(theta))
        N_mins.append(N_min_val)
    axes[1].semilogy(altitudes, N_mins, label=f'eps_min = {eps} deg')

axes[1].set_xlabel('Altitude [km]')
axes[1].set_ylabel('Minimum Number of Satellites')
axes[1].set_title('Minimum Satellites for Global Coverage')
axes[1].legend()
axes[1].grid(True)

plt.tight_layout()
plt.show()

まとめ

本記事では、LEOコンステレーションの設計と最適化問題について解説しました。

  • LEOコンステレーションは低遅延だが多数の衛星が必要である
  • Walkerデルタパターン $i:T/P/F$ はコンステレーション設計の標準的な手法である
  • 被覆率は軌道高度と最低仰角から幾何学的に計算できる
  • 全球被覆に必要な最小衛星数は $2/(1-\cos\theta_{\max})$ が下限である
  • 実用的な設計では衛星間の重複を考慮して1.5-2倍の衛星数が必要となる

次のステップとして、以下の記事も参考にしてください。