3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

人工衛星などの宇宙物体の軌道情報(TLE/OMM)をCesiumJSで可視化するぞ!

3
Posted at

はじめに

地球のまわりには、人工衛星や宇宙ステーション、スペースデブリなど、さまざまな物体が周回しています。
近年は小型衛星のコンステレーション打ち上げラッシュで、その数もうなぎ登りです。

そんな宇宙物体たちの軌道情報を表すフォーマットとして、TLEやOMMというものがあります。この記事では、それらのフォーマットを解説したうえで、CesiumJSを使ってブラウザ上に軌道を可視化するところまでやってみます。

完成物はこんな感じです。

image.png

特定の衛星のTLEまたはOMMを入力すると、緑のラインで軌道リング、黄色の点で衛星の現在位置が表示されます。時計を早回しすると地球が自転しながら衛星がぐるぐる回っているのが見えます。

TLEとは

TLE(Two-Line Element set)は、人工衛星などの宇宙物体の軌道情報を表すためのフォーマットです。1960年代に米国防総省が策定したもので、現在でもデファクトスタンダードとして広く使われています。

名前の通り、2行のデータで軌道情報を表現します(衛星名を含めると3行)。

1960年代に策定されただけあって、80列のIBMパンチカード2枚に収まるよう設計されています。パンチカードが廃れてテキストファイルに移行した後も、互換性のために当時のレイアウトがそのまま引き継がれ、60年以上経った今もその形式が使われています。

image.png
(https://gportal.jaxa.jp/gpr/assets/mng_upload/GCOM-W/TLE.pdf より)

衛星番号の枯渇問題

TLEの衛星番号は5桁の数字フィールドですが、使える上限は99,999ではなく69,999です。
70,000〜99,999の番号は、軍事・機密衛星やアナリストオブジェクト(追跡データの精度が低く公開カタログに掲載できない物体)向けに予約・使用されており、一般公開されません。Space-Trackの公式ドキュメントでは 80,000〜89,999 がアナリストオブジェクト用として明記されており、残りの範囲も同様に非公開目的で予約されているとみられます。

そのため公開カタログは事実上69,999で上限となっており、それを超えた物体は6桁(100,000〜)の番号が付与されます。しかし、6桁の番号はTLEの5文字フィールドには収まらないため、TLEフォーマットでは提供されません。

CelesTrakは次のように警告しています(原文ママ):

URGENT: We will run out of 5-digit catalog numbers at 69999 not 99999, which is estimated to occur around 2026‑07‑12 (we're currently at 69703). At that point, newly cataloged objects will have 6-digit catalog numbers of 100000+ and GP data will not be available for them using the TLE format. CelesTrak developed new formats that removed this limitation (and finally fixed the Y2K problem) in May 2020 and immediately began providing GP data in those formats for software developers.

— TS Kelso (CelesTrak)

2026年7月12日といえばもう目前ですね。 商業コンステレーション(Starlink、OneWebなど)が数千機単位で打ち上げられ、デブリも増え続けた結果、カタログ番号の消費が急加速しました。

衛星番号の上限を超えた宇宙物体はTLEフォーマットでは提供されなくなります。その後継として宇宙業界全体で移行が進んでいるのが、次のセクションで説明するOMMです。

OMMとは

OMM(Orbit Mean-Elements Message)は、CCSDS(宇宙データシステム諮問委員会)が標準化したより現代的な軌道データフォーマットです。

ODM

OMMは単独のフォーマットではなく、ODM(Orbit Data Messages)という上位概念の一部です。ODMには以下のメッセージタイプが含まれます。

メッセージ 略称 概要
Orbit Mean-Elements Message OMM 平均軌道要素(TLEの後継)
Orbit Parameter Message OPM 状態ベクトル + 共分散行列
Orbit Ephemeris Message OEM 時系列の状態ベクトル
Orbit Comprehensive Message OCM OPM/OEM/OMMを統合した包括的なメッセージ

この記事で扱うOMMは、TLEと同じ「平均軌道要素」ベースのデータを表すもので、TLEの直接の後継フォーマットと言えます。

OMMのフォーマット

OMMはXML・KVN(Key-Value Notation)・JSONなど複数の形式で表現できます。Space-TrackなどのAPIからはJSON形式で取得できます。

{
  "OBJECT_NAME": "ISS (ZARYA)",
  "OBJECT_ID": "1998-067A",
  "EPOCH": "2025-06-27T13:08:59.858592",
  "MEAN_MOTION": 15.49567865,
  "ECCENTRICITY": 0.0002441,
  "INCLINATION": 51.6443,
  "RA_OF_ASC_NODE": 247.4627,
  "ARG_OF_PERICENTER": 130.5360,
  "MEAN_ANOMALY": 325.0288,
  "CLASSIFICATION_TYPE": "U",
  "NORAD_CAT_ID": "25544",
  "ELEMENT_SET_NO": 999,
  "REV_AT_EPOCH": 47567,
  "BSTAR": 0.00034560,
  "MEAN_MOTION_DOT": 0.00016717,
  "MEAN_MOTION_DDOT": 0.0,
  "EPHEMERIS_TYPE": 0
}

TLEとの違い

比較項目 TLE OMM
形式 固定幅テキスト2行 JSON / XML / KVN
カタログ番号上限 69,999 制限なし
可読性 低い 高い
標準化 米軍の慣習 CCSDS標準
エポックの精度 8桁小数(〜マイクロ秒) ISO 8601(マイクロ秒以上も可)
拡張性 ほぼなし 高い

軌道要素の中身は基本的に同じですが、OMMはカタログ番号の桁数制限がなく、人間が読みやすく、標準規格に準拠している点で優れています。


TLE/OMMが取得できる場所

CelesTrak

CelesTrakは、Dr. T.S. Kelsoが運営する非商用の宇宙情報サイトです。TLEやGP(General Perturbations)データを無料で公開しており、軌道情報の取得先として非常によく使われます。

ISSや気象衛星、GPS衛星など、カテゴリ別にまとめられたTLEを直接ダウンロードできます。JSON形式のOMM相当データも配信されています。

Space-Track

Space-Track(宇宙物体追跡)は、米国宇宙軍(USSPACECOM)が運営する公式データポータルです。無料ですがアカウント登録が必要で、APIを通じてOMM形式でのデータ取得もできます。

情報の大元

Space-TrackもCelesTrakも、元をたどれば 米国宇宙軍(US Space Force / USSPACECOM)が地上レーダー網で追跡した観測データが情報源です。日本のJAXAが独自にデータを持っているわけではなく、基本的にはこれらの米国発データを参照することになります。

実際に可視化してみる

前置きがとても長くなりましたが、この起動情報を可視化してみましょう。

技術スタック

ライブラリ バージョン 役割
React 19 UIフレームワーク
TypeScript 6 型安全
Vite 8 ビルドツール
CesiumJS 1.142 3D地球儀レンダリング
satellite.js 7 SGP4軌道計算
vite-plugin-cesium 1.2 CesiumをViteで使うためのプラグイン

CesiumJSはブラウザ上で3D地球儀を描画できるライブラリです。もともとNASAの技術をベースに開発され、現在はCesium社がメンテナンスしています。WebGLベースで高品質な地球描画が得意です。

satellite.jsはSGP4/SDP4軌道伝搬モデルをJavaScriptで実装したライブラリです。TLEを与えると、任意の時刻における衛星の位置・速度を計算してくれます。

CesiumJSで地球を描画

まずViewerを初期化します。デフォルトのBingマップだとAPIキーが必要なので、今回はArcGISの衛星画像を使います。

import { Viewer, UrlTemplateImageryProvider } from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'

const imagery = new UrlTemplateImageryProvider({
  url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
  maximumLevel: 19,
})

const viewer = new Viewer(containerElement, {
  timeline: false,
  animation: false,
  baseLayerPicker: false,
  geocoder: false,
  homeButton: false,
  sceneModePicker: false,
  navigationHelpButton: false,
  fullscreenButton: false,
})

viewer.imageryLayers.removeAll()
viewer.imageryLayers.addImageryProvider(imagery)

timelineanimationなど、デフォルトのUIコントロールは全部falseにして非表示にしています。代わりに自作のTimeControlコンポーネントを使います。

軌道の計算と描画

ケプラー軌道要素とは

人工衛星の軌道は、6つのケプラー軌道要素で記述できます(摂動を無視した場合)。軌道6要素の詳しい説明は「Pythonを使って人工衛星の軌道を表現する~軌道6要素、TLE~」が非常にわかりやすいので、ぜひ参照してください。

ここでは、古典的な軌道6要素とTLE・OMMのパラメーターの対応だけ押さえておきます。

古典的な軌道6要素 TLE OMM (JSON) 意味
軌道長半径 (a) meanMotion (n) MEAN_MOTION 等価だが別表現。ケプラー第3法則 n=√(μ/a³) で相互変換できる。観測しやすいnを使う
離心率 (e) eccentricity ECCENTRICITY 軌道の楕円度(0=真円、1=放物線)
軌道傾斜角 (i) inclination INCLINATION 赤道面と軌道面のなす角度
昇交点赤経 (Ω) raan RA_OF_ASC_NODE 軌道が南から北に赤道を横切る点の方向
近地点引数 (ω) argPerigee ARG_OF_PERICENTER 軌道面内での楕円の向き
平均近点角 (M) meanAnomaly MEAN_ANOMALY エポック時点での軌道上の位置。真近点角(ν)・離心近点角(E)でも表せるが、時間に線形なMを使う

TLEのLine 2とOMMにはこれらがそのまま入っています。

SGP4

実際の衛星軌道はケプラー軌道のような理想的な楕円にはなりません。地球は完全な球ではなく(赤道方向に膨らんでいる)、大気の抗力があり、月・太陽の重力も影響します。

SGP4(Simplified General Perturbations 4)は、これらの摂動力を近似的に考慮した軌道伝搬モデルです。TLEに含まれるBSTAR(大気抗力パラメーター)や平均運動の微分係数(ndot, nddot)はSGP4の計算に使われます。

satellite.jsを使えば、TLEやOMMを渡すだけでSGP4の計算を一発でやってくれます。

ECI座標系とECEF座標系

SGP4が出力するのは ECI(Earth-Centered Inertial)座標系の位置・速度です。

  • ECI:地球の中心を原点に、宇宙空間に固定された座標系。X軸は春分点方向、Z軸は地球の自転軸(北極)方向。地球が自転してもECIの軸は動きません。軌道はECIでは「固定された楕円」です。
  • ECEF(Earth-Centered Earth-Fixed):地球の中心を原点に、地球と一緒に回転する座標系。GPSの緯度・経度はECEFベースです。

春分点とは、春分の日(3月20日頃)に太陽が天の赤道を南から北に横切る瞬間の方向のことです。地球の自転に関係なく宇宙空間に固定された基準方向として使われます。

CesiumJSは内部でECEFを使っています。なので、SGP4が出力したECI座標を、表示のためにECEFに変換する必要があります。

この変換には GMST(Greenwich Mean Sidereal Time:グリニッジ恒星時) を使います。GMSTは「宇宙の基準方向(春分点)からグリニッジ子午線まで地球が何ラジアン自転したか」を表す値です。

// ECI (km) → ECEF Cartesian3 (m)
function eciToEcef(eci: { x: number; y: number; z: number }, gmst: number): Cartesian3 {
  const c = Math.cos(gmst), s = Math.sin(gmst)
  return new Cartesian3(
    ( eci.x * c + eci.y * s) * 1000,  // ECIのX・Yを回転してECEFのX
    (-eci.x * s + eci.y * c) * 1000,  // ECIのX・Yを回転してECEFのY
    eci.z * 1000,                      // Z軸(南北)は変わらない
  )
}

Z軸は地球の自転軸と一致しているので変換不要で、X・Yだけ回転させれば良いです。

軌道リングの計算

satellite.jsで1周期分の軌道上の点列を計算します。

TLEの場合は twoline2satrec、OMMの場合は json2satrec でそれぞれ satrec を生成し、あとはどちらも同じ propagate で計算できます。

import * as satellite from 'satellite.js'

// TLEから satrec を生成
const satrec = satellite.twoline2satrec(tleLine1, tleLine2)

// OMMのJSONオブジェクトから直接 satrec を生成(TLE変換不要)
// satellite.js の注記:「OMMのエポックはTLEより精度が高い」
const satrec = satellite.json2satrec(ommJsonObject)

あとはどちらの satrec も同じ propagate で使えます。

function computeOrbitEci(satrec: satellite.SatRec, periodMinutes: number, startDate: Date) {
  const positions = []

  for (let step = 0; step <= 360; step++) {
    const elapsed = (step / 360) * periodMinutes * 60 * 1000
    const date = new Date(startDate.getTime() + elapsed)

    // SGP4で位置を計算(ECI座標系、単位はkm)
    const result = satellite.propagate(satrec, date)
    if (!result?.position) continue

    const pos = result.position as { x: number; y: number; z: number }
    positions.push({ x: pos.x, y: pos.y, z: pos.z })
  }

  // 末尾に先頭を追加して輪を閉じる
  if (positions.length > 0) positions.push(positions[0])
  return positions
}

ポイントは、startDate に現在のCesiumクロック時刻を使うことです。TLEやOMMのエポック時刻を起点にすると、後で説明するRAAN歳差の影響で軌道リングと衛星位置がズレてしまいます。

計算した点列は、毎フレームの描画時(preRenderイベント内)に現在のGMSTでECEFへ変換してCesiumに渡します。

// CesiumのpreRenderイベント内(毎フレーム呼ばれる)
viewer.scene.preRender.addEventListener((_scene, time) => {
  const date = JulianDate.toDate(time)
  const gmst = satellite.gstime(date)  // 現在時刻のGMSTを計算

  // 軌道リングの各点をECI→ECEFに変換
  orbitPolyline.positions = orbitEciPoints.map(p => eciToEcef(p, gmst))
})

毎フレームGMSTで変換し直すことで、地球の自転に合わせて軌道リングのECEF座標が変化します。「ECI空間では固定された楕円」が「ECEFでは動いて見える」というわけです。

軌道リングのCesiumへの描画

CesiumにはEntity APIPrimitive APIという2種類の描画方法があります。軌道リングのように毎フレーム座標を更新するケースでは、より低レベルで高パフォーマンスなPrimitive APIを使います。

import { PolylineCollection, Material, Color } from 'cesium'

// PolylineCollectionでラインを管理
const polylines = new PolylineCollection()
viewer.scene.primitives.add(polylines)

// 軌道リングのポリラインを追加
const orbitLine = polylines.add({
  positions: [], // preRenderで毎フレーム更新
  width: 2,
  material: Material.fromType('Color', { color: Color.LIME.withAlpha(0.8) }),
})

特定の時刻での衛星位置の計算

衛星の現在位置も同様にSGP4で計算します。こちらは1点だけ計算すれば良いので簡単です。

function getSatellitePosition(satrec: satellite.SatRec, date: Date) {
  const result = satellite.propagate(satrec, date)
  if (!result?.position) return null
  return result.position as { x: number; y: number; z: number }
}

// preRender内で毎フレーム更新
viewer.scene.preRender.addEventListener((_scene, time) => {
  const date = JulianDate.toDate(time)
  const gmst = satellite.gstime(date)
  const eci = getSatellitePosition(tleLines, date)

  if (eci) {
    satPoint.position = eciToEcef(eci, gmst)
    satPoint.show = true
  }
})

RAAN歳差

ここまでで、大まかな実装はできました。しかし、最初は正しく表示できていても、時間が経つにつれ(特に時計を3600倍速などで早回しすると)、衛星の実際の位置が軌道リングからずれて表示されます。3日もすれば約3°ずれ、地表で300〜400km程度の見た目のズレになってしまいます。その原因となるのが、RAAN歳差(ラーン歳差)というものです。

実は、衛星は正確に同じ軌道を周回しているのではありません。J2項(地球の赤道膨らみによる非対称性)の影響で、少しずつ軌道面の向き(RAAN)がズレていきます。太陽同期軌道(傾斜角97〜98°付近)では約0.99°/日の速度でRAAN漂流が起きます。これまでの実装では、軌道リングを一度計算して固定していたため、時間が進むにつれズレが出てしまいます。

注意しておきたいのが、これはあくまで軌道リングの可視化実装上の問題で、これはSGP4の計算精度の問題ではないということです。衛星の現在位置はSGP4がRAAN歳差を含む摂動を考慮したうえで正しく計算します。そのため、定期的に軌道リングを再計算することでこの問題は解決できます。Cesiumクロックが半周期(約48分)進むたびに、現在時刻を起点として軌道リングを計算し直すことで、常に正しい衛星の軌道を表示します。

// 半周期ごとに軌道リングを現在時刻基点で再計算
const orbitBaseTimeRef = { current: startDate }

setInterval(() => {
  const now = viewer.clock.currentTime  // Cesiumの現在時刻
  const nowDate = JulianDate.toDate(now)

  const driftMs = Math.abs(nowDate.getTime() - orbitBaseTimeRef.current.getTime())
  const halfPeriodMs = periodMinutes * 30 * 1000

  if (driftMs < halfPeriodMs) return  // まだ再計算不要

  orbitBaseTimeRef.current = nowDate
  // 現在時刻を起点に軌道リングを再計算(直接ptsRefを更新するだけ)
  ptsRef.current = computeOrbitEci(tleLines, periodMinutes, nowDate)
}, 500)  // 0.5秒ごとにチェック

3600倍速の場合、0.5秒のリアルタイムで30分のシミュレーション時間が経過します。半周期(48分)の再計算条件は約0.8秒ごとに達するので、RAAN歳差によるズレが蓄積する前にリセットされます。

ECI固定カメラ

CesiumJSのカメラはECEF座標系で動きます。そのまま何もしないと、カメラが宇宙空間に固定されて地球が止まって見え、軌道リングが地球の自転につられてぐるぐる回って見えます。
これだと分かりにくいので、軌道リングが静止して地球が回って見えるようにしたいです。

そこで、カメラのECI座標を保存しておき、毎フレームGMSTで変換してECEFに直してからCesiumに渡すようにしました。GMSTが変化するにつれてカメラのECEF座標が変わり、まるで地球が自転しているように見えます。

const eciCam = { x: 0, y: 0, z: 0 }  // ECI座標でのカメラ位置

// ECEF→ECI変換(ECIを保存するとき)
function ecefToEci(ecef: Cartesian3, gmst: number): Cartesian3 {
  const c = Math.cos(gmst), s = Math.sin(gmst)
  return new Cartesian3(
    ecef.x * c - ecef.y * s,
    ecef.x * s + ecef.y * c,
    ecef.z,
  )
}

// preRender内で毎フレーム実行
viewer.scene.preRender.addEventListener((_scene, time) => {
  const gmst = satellite.gstime(JulianDate.toDate(time))

  // 保存しておいたECI座標を現在のGMSTでECEFに変換
  const c = Math.cos(gmst), s = Math.sin(gmst)
  const ecefPos = new Cartesian3(
     eciCam.x * c + eciCam.y * s,
    -eciCam.x * s + eciCam.y * c,
     eciCam.z,
  )

  // 地球の中心を見る方向ベクトルを計算
  const dir = Cartesian3.normalize(Cartesian3.negate(ecefPos, new Cartesian3()), new Cartesian3())
  // ... up ベクトルを計算 ...

  // Cesiumカメラの位置を強制的に上書き
  viewer.camera.setView({ destination: ecefPos, orientation: { direction: dir, up } })
})

まとめ

  • TLE:昔ながらの固定幅2行フォーマット。もうすぐカタログ番号が枯渇する
  • OMM:CCSSDSが標準化したJSONベースのフォーマット。TLEの後継
  • データ取得先:CelesTrakやSpace-Trackで無料取得可能(大元は米国宇宙軍)
  • SGP4:摂動を考慮した軌道伝搬モデル。satellite.jsでTLE(twoline2satrec)もOMM(json2satrec)も扱える
  • ECI/ECEF変換:軌道計算はECIで、CesiumはECEFなので変換が必要
  • ECI固定カメラ:カメラのECI座標を毎フレームECEFに変換してCesiumに渡すと地球が回って見える
  • RAAN歳差:J2摂動で軌道面が少しずつずれる。定期的に軌道リングを再計算して対処

実際に衛星のTLE/OMMを入れて動かしてみると、軌道の傾斜角や高度が感覚的に理解できてとても面白いですね。

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?