Week 16 — 整合展示
week16_constellation/基礎知識導論
Week 16 基礎知識導論 — 覆蓋、重訪與星座設計
前十五週都在問「這一顆衛星做得到嗎」。
最後一週要問一個不同的問題:「一顆到底夠不夠?不夠的話,要幾顆?」
1. 這一週在解什麼問題
FIREYE 的需求是「單一火山重訪 < 24 hr」。
單顆衛星做得到嗎?做不到的話,補到幾顆才夠?
用 STK 的 Coverage Definition + Figure of Merit (RevisitTime) 量化,
再用 Python 的解析模型做便宜的規模掃描。
2. 核心觀念
2.1 覆蓋的三個常見度量
| 度量 | 定義 | 適用 |
|---|---|---|
| 重訪時間 (Revisit Time) | 同一格點連續兩次被覆蓋的間隔 | 監測型任務(本課) |
| 回應時間 (Response Time) | 從「現在下令」到「能觀測」的等待時間 | 應變型任務 |
| 覆蓋百分比 (% Coverage) | 任一瞬間被覆蓋的面積比例 | 通訊星座 |
選錯度量會得到錯的架構。
火山監測要的是重訪(定期回來看),不是瞬時覆蓋。
但如果需求改成「火山爆發後 30 分鐘內取像」,那就變成回應時間問題,答案會完全不同。
2.2 平均值不是重點,最差格點才是
本教材的實測結果:
| 構型 | 平均重訪 | 最差格點重訪 |
|---|---|---|
| 單顆 FIREYE | 19.10 hr | 48 hr |
| Walker 3 x 4(12 顆) | 1.75 hr | 2.05 hr |
單顆的平均值 19.10 hr 看起來滿足 24 hr 需求,
但最差的格點要等 48 小時 —— 需求其實沒達成。
而且注意兩個構型的平均與最差的差距:
單顆是 2.5 倍,星座只有 1.17 倍。
星座真正的價值,不是把平均值變好,而是把服務品質變均勻。
這是本週最重要的一句話。一個服務品質不均勻的系統,
使用者體驗由最差的那一格決定,不由平均值決定。
2.3 報酬遞減
12 顆衛星只換到 10.9 倍的平均重訪改善(19.10 / 1.75)。
衛星數量與覆蓋效能不是線性關係,更不是超線性。
粗略的直覺:對均勻分布的星座,重訪時間大致反比於衛星數。
所以「重訪減半」要「衛星加倍」—— 成本也加倍。
這條曲線決定了架構決策的甜蜜點在哪裡。
2.4 Walker Delta 星座
標準記法:
i : T / P / F
i傾角T衛星總數P軌道面數F相位因子(0 到 P−1),決定相鄰面之間的相位偏移
相鄰面的相位差:
Δu = F * 360° / T
各面的 RAAN 均勻分布:ΔΩ = 360° / P(Delta 型;Star 型是 180°/P)。
本週用的是 3 面 x 4 顆 = 12 顆。
設計旋鈕:
- P 多 → 覆蓋更均勻,但發射成本高(每個面要單獨發射或花 delta-V 改 RAAN)
- P 少(多顆同面)→ 一次發射多顆,便宜,但同面衛星覆蓋高度重疊
- F 影響格點的重訪分布均勻度,是最便宜的優化旋鈕(不花任何硬體成本)
2.5 改變 RAAN 很貴,但 J2 是免費的
在軌道上直接改 RAAN 的代價跟改傾角一樣高(Week 15 的 2.4)。
但 J2 攝動會讓不同高度的軌道以不同速率進動(Week 4 的 2.3):
dΩ/dt = -(3/2) * n * J2 * (Re/p)² * cos(i)
所以可以用「暫時改變高度 → 等 RAAN 漂到位 → 改回來」的方式建立軌道面,
代價只有兩次小的高度變更。這叫 J2-assisted plane change,是星座部署的標準技巧,
代價是時間(數週到數月)。
2.6 STK Coverage 的運作方式
CoverageDefinition 定義感興趣的區域與格點解析度
+ Asset(哪些衛星/感測器)
+ AdvancedGridPoint 設定
↓
Figure of Merit (FOM) 在格點上計算某個度量
例:RevisitTime / Access Duration / Response Time
↓
報表 / 等高線圖
格點解析度是精度與速度的權衡:格點太粗會漏掉最差區域,
太細則計算時間爆炸。本教材用美西 90 個格點、2 天分析窗。
2.7 解析模型的用途:便宜的掃描
Python 解析模型與 STK 的對照:
| 案例 | 解析模型 | STK | 比值 |
|---|---|---|---|
| 單顆 | — | 19.10 hr | 系統性低估約 6.5 倍 |
| Walker 3x4 | — | 1.75 hr | 同樣約 6.5 倍 |
解析模型系統性低估約 6.5 倍,但兩個案例的比值一致。
這代表:解析模型抓對了趨勢,只是常數不對。
所以可以用 STK 算兩三個點來校準它,再用校準後的解析模型便宜地掃描整個設計空間。
校準後的結論:要達到 3 hr 重訪目標,約需 7 顆衛星。
這是本課方法論的收尾:
高精度工具(STK)負責校準與驗證,
便宜的解析模型負責掃描設計空間。
兩者分工,而不是二選一。
2.8 星座的其他代價
決定「加衛星」之前,要把這些一起算進去:
- 發射成本:多個軌道面要多次發射
- 地面段:多顆衛星的排程與地面站容量(Week 14 的排程問題變 NP-hard)
- 碰撞風險與碎片責任:星座是太空碎片環境的主要新增來源
- 除役義務:每一顆都要能去軌(Week 10 的 2.8)
- 頻率協調:多顆同時發射會有干擾管理問題(Week 11 的 2.8)
3. 名詞與符號
| 名詞 | 意義 | 本課值 |
|---|---|---|
| Revisit Time | 重訪時間 | 單顆 19.10 hr / Walker 1.75 hr |
| Response Time | 回應時間 | — |
| FOM | Figure of Merit | 本課用 RevisitTime |
Walker Delta i:T/P/F |
星座記法 | 3 面 x 4 顆 |
P |
軌道面數 | 3 |
F |
相位因子 | 0 至 P−1 |
| 格點 (grid point) | 覆蓋計算的取樣點 | 美西 90 點 |
| 分析窗 | — | 2 天 |
4. 觀念 → 本週實驗對照
| 觀念 | 出現在本週的哪裡 |
|---|---|
| Coverage + RevisitTime FOM | week16_walker_coverage_Connect.txt(38 條指令) |
| Walker 3 x 4 建構 | 同上 |
| 解析模型與規模掃描 | week16_constellation_tradeoff.py |
| 校準後推 7 顆 | 同上 |
| 圖表 | week16_constellation.png(重訪時間 vs 星座規模) |
| 驗證段 | week16_verify.py |
| 實驗單 | Week16_Constellation_Lab_Handout.md |
5. 常見誤解
- 「平均重訪 19 hr 滿足 24 hr 需求。」 — 最差格點是 48 hr。
需求是對每一個目標成立,不是對平均值成立。 - 「12 顆就快 12 倍。」 — 實測只有 10.9 倍,而且真正的價值在均勻度,不在平均值。
- 「解析模型差 6.5 倍,所以沒用。」 — 反過來:比值一致代表趨勢正確。
校準後它是最便宜的掃描工具。 - 「加軌道面比加同面衛星好。」 — 覆蓋均勻度是好,但發射成本高很多。
要看你的成本模型。 - 「星座設計就是選 T/P/F。」 — 還有部署策略、地面段、去軌、頻率協調。
後面這些常常才是真正的成本。
6. 延伸閱讀
星座設計
- Wikipedia: Satellite constellation
— Walker、Street-of-coverage、Flower 等構型的概觀。 - MIT OCW 16.851 Satellite Engineering
— 星座設計與成本模型的講義。 - 教科書:Wertz, Mission Geometry: Orbit and Constellation Design and Management
(星座設計的標準參考書);SMAD 第 7 章。 - NASA Technical Reports Server (NTRS)
— 搜尋 "constellation design revisit time"。
巨型星座與其代價
- ESA Space Debris — 年度太空環境報告。
- NASA 軌道碎片計畫辦公室 — 碎片環境模型與季刊。
- IADC — 去軌與碎片緩解的國際準則。
- ITU-R 太空服務 — 頻率與軌道位置的協調機制。
工具
- Ansys STK 線上說明 — Coverage Definition 與 Figure of Merit。
本機:C:\Program Files\AGI\STK 12\Help\Content\cov\。 - eoPortal 衛星任務資料庫
— 查 Planet、Spire、Iridium NEXT 等真實星座的參數。
7. 課前自我檢核
- 平均重訪 19.10 hr、最差格點 48 hr。你的任務需求「重訪 < 24 hr」達成了嗎?為什麼?
- 用一句話說明星座的核心價值。(提示:不是「平均值變好」)
- Walker
i:T/P/F的F改變會影響什麼?它需要花錢嗎? - 為什麼「暫時改高度」可以用來改變 RAAN?代價是什麼?
- 解析模型系統性低估 6.5 倍。這代表它沒用,還是可以用?你會怎麼處理?
Week16_Constellation_Lab_Handout.md
Week 16 實驗單 — STK SmallSat 整合展示:從一顆到一個星座
主題:用 Coverage / Figure of Merit 量化「一顆 FIREYE 夠不夠」,並回答「不夠要幾顆」
工具:STK Coverage(主)+ Python 解析模型(設計掃描)
檔案:week16_walker_coverage_Connect.txt、week16_constellation_tradeoff.py
為什麼這週擺在最後
前面十五週都在把一顆 FIREYE 做到能用:選軌道、配電力、定姿態、開鏈路、排通聯。
這週問一個系統層級的問題 —— 火山噴發預警需要多快的重訪?一顆做得到嗎?
答案會很直接:做不到。而「要幾顆」正是把全學期的分析轉成任務架構決策的地方,
也是期末報告「未來趨勢/巨型星座」章節該有的量化依據。
學習目標
- 建立 CoverageDefinition:定義區域、格點解析度、指派 asset。
- 理解 asset 要指到感測器而非衛星 —— 前者是 FOV 限制的覆蓋,後者只是「有沒有飛過」。
- 用 Walker Delta 從單顆種子長出星座,理解
NumPlanes / NumSatsPerPlane / InterPlanePhaseIncrement的意義。 - 用 Figure of Merit(Revisit Time) 量化改善,並解讀「格點之間的差異」而不只看平均。
- 判讀次線性回報:12 倍的衛星換到幾倍的重訪改善?為什麼不是 12 倍?
Part A — STK:單顆 vs 星座(60 min)
- 報告目錄要先存在(路徑見
tools/stk_paths.py,預設C:\STK_Reports\)—— Connect 寫檔失敗是靜默失敗。
建起來:python -c "import sys;sys.path.insert(0,'tools');import stk_paths;print(stk_paths.ensure_reports_dir())" - 開啟 STK 12 桌面版,執行
week16_walker_coverage_Connect.txt。
不要用無介面的 STK Engine ——Graphics ... SetColor、OrbitWizard ... Color
等指令在 Engine 上會被拒絕。 - 腳本會產生兩份報告:
-C:\STK_Reports\week16_revisit_1sat.txt(單顆)
-C:\STK_Reports\week16_revisit_12sat.txt(Walker 3 面 × 4 顆) - 在 3D 視窗看 Walker 星座的軌道面分布,以及覆蓋格點的顏色變化。
基準結果(本機 STK 12.10.0 實測,分析窗 2 天,美西 30–55°N / 130–110°W,90 個格點):
| 星座 | 平均重訪 | 中位數 | 最差格點 |
|---|---|---|---|
| 1 顆 | 19.10 hr | 15.98 hr | 48.00 hr |
| 12 顆(Walker 3×4) | 1.75 hr | 1.81 hr | 2.05 hr |
紀錄:兩組的平均/中位/最差,以及改善倍率。
判讀重點:
- 單顆的「最差格點 48 小時」意味著什麼?對火山預警可接受嗎?
- 12 顆的平均與最差只差 0.3 hr,單顆卻差了 32 hr —— 星座除了縮短重訪,
更重要的是把服務品質變均勻。這一點在平均值裡看不出來,要看分布。
Part B — Python:回答「要幾顆」(45 min)
在 STK 裡把 N 從 1 掃到 48 太貴。改用解析模型掃描,再用 Part A 的實測校準。
執行 python week16_constellation_tradeoff.py,它會:
1. 讀 Part A 的兩份報告(找不到會明講,不會靜默假裝)。
2. 用 streets-of-coverage 一階模型算重訪 vs 星座規模。
3. 用實測校準模型後,回答目標重訪所需的衛星數。
基準結果:
| 星座 | 解析模型 | STK 實測 | 比值 |
|---|---|---|---|
| 1 顆 | 3.07 hr | 19.10 hr | 6.2 |
| 12 顆 | 0.26 hr | 1.75 hr | 6.8 |
解析模型系統性低估約 6.5 倍,但關鍵是 —— 兩個案例的比值幾乎一樣。
這代表模型抓對了「隨 N 縮放」的關係,只是絕對值有偏差。所以只要用一次 STK 實測
定出比例因子 k,就能拿模型去做便宜的規模掃描。
校準後(k = 6.52),目標重訪 3 小時 → 約需 7 顆。
紀錄:k 值、達成 3 hr 所需衛星數,以及你認為 k 為什麼是 6.5 而不是 1。
討論題(報告必答)
-
解析模型為什麼低估? 至少列出三個原因。提示:升軌與降軌的通過會擠在一起
算成兩次機會嗎?太陽同步軌道的地面軌跡有回歸週期,和模型假設的「均勻分布」一樣嗎?
「感測器看得到」等於「拍得到有用的影像」嗎(太陽高度角、雲遮)? -
次線性回報:12 倍衛星只換到 10.9 倍改善。如果繼續加到 24 顆,你預期改善多少?
什麼因素會讓回報進一步遞減? -
平面數 vs 每面顆數:總數固定 12 顆,3 面×4 顆 和 4 面×3 顆、2 面×6 顆
會一樣嗎?改 Connect 腳本的NumPlanes/NumSatsPerPlane實測,並解釋差異。 -
架構決策:7 顆 CubeSat 的發射與運營成本 vs 一顆大衛星。用 Week 3 的預算表
和 Week 15 的發射分析支撐你的建議。 -
接回主線:把星座的重訪改善接回 Week 12 的資料預算 —— 7 顆衛星產生的資料量
是一顆的 7 倍,現有的地面站與下傳速率吃得下嗎?
評分
| 項目 | 比重 |
|---|---|
| Coverage 建構與 FOM 判讀 | 30% |
| Walker 星座參數研究(討論題 3) | 25% |
| 解析模型與 STK 對照、k 值解釋 | 25% |
| 架構決策與跨週串接(討論題 4、5) | 20% |
延伸
- 換成 CoverageTime 或 ResponseTime FOM,看結論會不會改變。
- 把區域從美西換成全球(
LatBounds -90 90),看極軌星座在高緯的優勢。 - 用 Week 11 的 Comm 模組給每顆衛星加鏈路,看星座的下傳瓶頸在哪。
圖

week16_constellation/week16_constellation.png
程式碼
# -*- coding: utf-8 -*-
# ================================================================
# Week 16 Lab — 星座規模 vs 重訪時間:STK Coverage 對照解析模型
#
# 全學期的收束問題:「一顆 FIREYE 看火山夠嗎?不夠的話要幾顆?」
#
# 雙軌(同 Week 10 的哲學):
# (A) STK Coverage/FOM = 正牌答案。讀 week16_walker_coverage_Connect.txt
# 產生的兩份重訪報告(單顆 vs Walker 3x4)。
# (B) Python 解析模型 = 先估量級,並回答「要幾顆」這種設計問題 ——
# 掃描星座規模比在 STK 裡重建 N 次便宜得多。
#
# 解析模型(streets-of-coverage 一階近似):
# 感測器半錐角 eta、高度 h -> 地心半角 lambda = asin(sin(eta)*(RE+h)/RE) - eta
# 地面刈幅半寬 w = RE * lambda
# 緯度 phi 上相鄰地面軌跡的經度間距 dL = 2*pi*RE*cos(phi) * (T_orbit/T_earth)
# 每日通過次數 ~ N * (T_earth/T_orbit) * min(1, 2w/dL)
# 平均重訪 ~ T_earth / 每日通過次數
#
# 這個模型會系統性**低估**重訪時間(見實驗單的討論題),原因包括:
# 忽略升/降軌通過會擠在一起、忽略太陽同步軌道的回歸特性、
# 把「感測器看得到」等同「有效觀測」(未計太陽高度角、雲遮)。
# Lab 的重點就是解釋這個落差,而不是讓兩邊數字對上。
#
# 輸出:week16_constellation.png + 主控台摘要
# ================================================================
import os
import re
import sys
import numpy as np
import matplotlib.pyplot as plt
HERE = (os.path.dirname(os.path.abspath(__file__))
if "__file__" in globals() else os.path.abspath(os.getcwd()))
# ---------------- 任務參數(與 Connect 腳本一致)----------------
RE = 6378.137 # km
MU = 398600.4418 # km^3/s^2
ALT = 600.0 # km,SSO
CONE_DEG = 40.0 # 感測器半錐角
LAT_MID = 42.5 # 覆蓋區中緯(30~55 的中點)
T_EARTH = 86164.0 # s,恆星日
TARGET_REVISIT_HR = 3.0 # 火山早期預警的目標重訪
sys.path.insert(0, os.path.join(HERE, os.pardir, "tools"))
import stk_paths # noqa: E402 報告目錄的單一來源
REPORTS = {1: stk_paths.report("week16_revisit_1sat.txt"),
12: stk_paths.report("week16_revisit_12sat.txt")}
# ---------------- 1. 讀 STK 重訪報告 ----------------
_ROW = re.compile(r"^\s*(-?\d+\.\d+)\s+(-?\d+\.\d+)\s+(\d+\.\d+)\s*$")
def parse_revisit(path):
"""STK 'Value By Grid Point' 報告 -> (lat, lon, revisit_sec) 三個陣列。"""
lat, lon, val = [], [], []
with open(path, encoding="utf-8", errors="ignore") as f:
for line in f:
m = _ROW.match(line)
if m:
lat.append(float(m.group(1)))
lon.append(float(m.group(2)))
val.append(float(m.group(3)))
return np.array(lat), np.array(lon), np.array(val)
def load_stk():
"""回傳 {n_sat: revisit_array};缺檔就跳過並明講(不靜默)。"""
out = {}
for n, p in REPORTS.items():
if os.path.exists(p):
_, _, v = parse_revisit(p)
if len(v):
out[n] = v
print(f"[STK] {os.path.basename(p)}: {len(v)} 個格點")
continue
print(f"[STK] 找不到或無法解析 {p}")
if not out:
print(" 先在 STK 執行 week16_walker_coverage_Connect.txt 產生報告;"
"以下只顯示解析模型。")
return out
# ---------------- 2. 解析模型 ----------------
def orbit_period(alt_km):
a = RE + alt_km
return 2.0 * np.pi * np.sqrt(a ** 3 / MU)
def swath_half_width(alt_km, cone_deg):
"""地心半角 -> 地面刈幅半寬(km)。"""
eta = np.radians(cone_deg)
s = np.sin(eta) * (RE + alt_km) / RE
if s >= 1.0: # 錐角大到看見地平線
lam = np.pi / 2 - np.arcsin(RE / (RE + alt_km))
else:
lam = np.arcsin(s) - eta
return RE * lam
def mean_revisit_analytic(n_sat, alt_km=ALT, cone_deg=CONE_DEG, lat_deg=LAT_MID):
T = orbit_period(alt_km)
w = swath_half_width(alt_km, cone_deg)
dL = 2.0 * np.pi * RE * np.cos(np.radians(lat_deg)) * (T / T_EARTH)
frac = min(1.0, 2.0 * w / dL) # 落在刈幅內的軌跡比例
passes_per_day = n_sat * (T_EARTH / T) * frac
return T_EARTH / passes_per_day if passes_per_day else np.inf
# ---------------- 執行 ----------------
def main():
stk = load_stk()
T = orbit_period(ALT)
w = swath_half_width(ALT, CONE_DEG)
print(f"\n軌道週期 {T:.0f} s ({T/60:.1f} min) | 每日 {T_EARTH/T:.2f} 圈")
print(f"感測器 {CONE_DEG:.0f} 度半錐角 @ {ALT:.0f} km -> 刈幅半寬 {w:.1f} km"
f"(全寬 {2*w:.0f} km)")
print("\n=== STK Coverage 實測(緯度 30-55,經度 -130~-110)===")
for n in sorted(stk):
v = stk[n]
print(f" {n:>2} 顆 | 平均 {v.mean()/3600:6.2f} hr | 中位 {np.median(v)/3600:6.2f} hr"
f" | 最差 {v.max()/3600:6.2f} hr")
if len(stk) >= 2:
a, b = sorted(stk)
gain = stk[a].mean() / stk[b].mean()
print(f" {a} -> {b} 顆:衛星數 {b/a:.0f} 倍,重訪改善 {gain:.1f} 倍"
f" —— 次線性回報,這是星座設計的核心權衡")
print("\n=== 解析模型 ===")
for n in sorted(stk) or (1, 12):
print(f" {n:>2} 顆 | 解析 {mean_revisit_analytic(n)/3600:6.2f} hr", end="")
if n in stk:
r = stk[n].mean() / mean_revisit_analytic(n)
print(f" vs STK {stk[n].mean()/3600:6.2f} hr (STK 是解析的 {r:.1f} 倍)")
else:
print()
ns = np.arange(1, 49)
analytic = np.array([mean_revisit_analytic(int(n)) for n in ns]) / 3600.0
# 用 STK 的實測點校準解析模型(單一比例因子),再回答「要幾顆」
if stk:
k = np.mean([stk[n].mean() / mean_revisit_analytic(n) for n in stk])
calibrated = analytic * k
need = ns[calibrated <= TARGET_REVISIT_HR]
print(f"\n=== 設計問題:重訪要壓到 {TARGET_REVISIT_HR:.0f} hr 以內,需要幾顆? ===")
print(f" 用 STK 實測校準解析模型(比例因子 k = {k:.2f})")
print(f" -> 約需 {need.min() if len(need) else '>48'} 顆")
else:
calibrated, k = None, None
# ---------------- 出圖 ----------------
fig, ax = plt.subplots(1, 2, figsize=(13, 5))
ax[0].plot(ns, analytic, lw=1.6, ls="--", color="tab:gray",
label="analytic (uncalibrated)")
if calibrated is not None:
ax[0].plot(ns, calibrated, lw=2.0, color="tab:blue",
label=f"analytic x k={k:.2f} (calibrated to STK)")
for n in sorted(stk):
ax[0].plot(n, stk[n].mean()/3600, "o", ms=9, color="tab:red",
label=f"STK: {n} sat" if n == min(stk) else f"STK: {n} sat")
ax[0].axhline(TARGET_REVISIT_HR, color="k", ls=":", lw=1.2,
label=f"target {TARGET_REVISIT_HR:.0f} hr")
ax[0].set_xlabel("number of satellites")
ax[0].set_ylabel("mean revisit time (hr)")
ax[0].set_title(f"Revisit vs constellation size | {CONE_DEG:.0f}$^\\circ$ cone @ {ALT:.0f} km")
ax[0].set_yscale("log"); ax[0].grid(alpha=0.3, which="both"); ax[0].legend()
if stk:
data = [stk[n]/3600 for n in sorted(stk)]
ax[1].boxplot(data, tick_labels=[f"{n} sat" for n in sorted(stk)])
ax[1].set_ylabel("revisit time per grid point (hr)")
ax[1].set_title("STK coverage: spread across the volcano region")
ax[1].grid(alpha=0.3, axis="y")
else:
ax[1].text(0.5, 0.5, "no STK reports\nrun the Connect script first",
ha="center", va="center"); ax[1].axis("off")
fig.tight_layout()
out = os.path.join(HERE, "week16_constellation.png")
fig.savefig(out, dpi=130)
print(f"\nWrote {os.path.basename(out)}")
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
# ================================================================
# Week 16 驗證段 — 星座覆蓋改善與解析模型的一致性
#
# 本週的教學重點是「解析模型低估但比例一致」。那個一致性一旦不成立,
# 用一次 STK 實測校準模型的做法就不能用了 —— 所以它必須被檢查。
#
# 執行:python week16_verify.py
# 離開碼 0 = 全部通過,1 = 有項目對不上
# ================================================================
import os
import sys
# notebook 的 kernel 沒有 __file__。而且啟動 STK Engine 會改變 process 的
# 工作目錄,所以不能事後才靠 os.getcwd() —— notebook 開頭的 bootstrap cell
# 會先把週次目錄釘在 WEEK_DIR。
HERE = (os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals()
else globals().get("WEEK_DIR") or os.path.abspath(os.getcwd()))
REPO = os.path.abspath(os.path.join(HERE, os.pardir))
sys.path.insert(0, os.path.join(REPO, "tools"))
from stk_verify import Verifier, load_lab # noqa: E402
def main():
import numpy as np
m = load_lab(os.path.join(HERE, "week16_constellation_tradeoff.py"))
v = Verifier(None)
stk = m.load_stk()
if len(stk) < 2:
v.expect_true("需要兩份 STK 重訪報告", False,
"先執行 week16_walker_coverage_Connect.txt")
return 0 if v.report("Week 16 驗證段") else 1
v.expect("單顆平均重訪", stk[1].mean() / 3600, 19.10, tol=1.0, unit=" hr")
v.expect("12 顆平均重訪", stk[12].mean() / 3600, 1.75, tol=0.2, unit=" hr")
gain = stk[1].mean() / stk[12].mean()
v.expect("重訪改善倍率", gain, 10.9, tol=1.0, unit=" 倍")
v.expect_true("次線性回報:改善倍率 < 衛星數倍率", gain < 12.0,
f"12 倍衛星換到 {gain:.1f} 倍改善")
v.expect_true("星座讓服務品質變均勻",
(stk[12].max() - stk[12].mean()) < (stk[1].max() - stk[1].mean()) / 10,
f"最差-平均:單顆 {(stk[1].max()-stk[1].mean())/3600:.1f} hr,"
f"12 顆 {(stk[12].max()-stk[12].mean())/3600:.2f} hr")
ks = [stk[n].mean() / m.mean_revisit_analytic(n) for n in sorted(stk)]
v.expect_between("解析模型的低估比例 k", float(np.mean(ks)), 5.5, 7.5)
v.expect_true("兩個案例的 k 一致(差異 < 15%)",
abs(ks[0] - ks[1]) / np.mean(ks) < 0.15,
f"k = {ks[0]:.2f} 與 {ks[1]:.2f}")
ok = v.report("Week 16 驗證段")
return 0 if ok else 1
if __name__ == "__main__":
_rc = main()
if "ipykernel" not in sys.modules:
raise SystemExit(_rc)
# ============================================================
# Week 16 — STK SmallSat 整合展示:從單顆 FIREYE 到 Walker 星座
#
# 這是全學期的收束:把前面各週建立的 FIREYE 火山觀測任務,
# 用 STK 的 Coverage / Figure of Merit 量化「一顆不夠,那要幾顆?」
#
# 兩段對照(同一片區域、同一個感測器、同一段分析窗):
# Part A 單顆 FIREYE -> 重訪時間 FOM
# Part B Walker Delta 3 面 x 4 -> 重訪時間 FOM
# 差異就是星座的價值,也是期末報告「未來趨勢」章節的量化依據。
#
# 前置:報告輸出資料夾 C:\STK_Reports\ 需先手動建立(寫檔失敗為靜默失敗)。
# 需求:STK Enterprise(Coverage 模組)。無介面的 STK Engine 跑不動
# Graphics/VO 相關指令,請用桌面版。
#
# 驗證過的語法重點(這門課反覆踩過的坑):
# - Walker 的新語法是 Type/NumPlanes/NumSatsPerPlane/InterPlanePhaseIncrement
# - Coverage 網格用 Grid AreaOfInterest LatLonRegion <minLat> <minLon> <maxLat> <maxLon>
# - asset 要指到**感測器**才是 FOV 限制的覆蓋;指到衛星是「有沒有飛過」
# - Cov ... Asset ... Group 不被接受,要用 Assign
# ============================================================
New / Scenario Week16_Constellation
SetAnalysisTimePeriod * "1 Jan 2024 00:00:00.000" "3 Jan 2024 00:00:00.000"
SetTimePeriod * "1 Jan 2024 00:00:00.000" "3 Jan 2024 00:00:00.000"
SetUnits / km
# ---- 種子衛星:沿用 FIREYE 的 SSO 設定 ----
New / */Satellite FIREYE
OrbitWizard */Satellite/FIREYE SunSynchronous Altitude 600 Color yellow
SetAttitude */Satellite/FIREYE Profile NadirECFVel Offset 0.0
# 成像酬載:40 度半錐角(與 Week 4 的 FullScenario 一致)
New / */Satellite/FIREYE/Sensor Imager
Define */Satellite/FIREYE/Sensor/Imager SimpleCone 40
Graphics */Satellite/FIREYE/Sensor/Imager SetColor red
# ============================================================
# Part A — 單顆衛星的區域覆蓋
# 區域:美西火山帶(北緯 30-55,西經 130-110),涵蓋喀斯開山脈到阿留申
# ============================================================
New / */CoverageDefinition USWest_Cov
Cov */CoverageDefinition/USWest_Cov Grid AreaOfInterest LatLonRegion 30 -130 55 -110
Cov */CoverageDefinition/USWest_Cov Grid PointGranularity LatLon 2
Cov */CoverageDefinition/USWest_Cov Asset */Satellite/FIREYE/Sensor/Imager Assign
Cov */CoverageDefinition/USWest_Cov Access Compute
New / */CoverageDefinition/USWest_Cov/FigureOfMerit Revisit
Cov */CoverageDefinition/USWest_Cov/FigureOfMerit/Revisit FOMDefine Definition RevisitTime Compute Average
ReportCreate */CoverageDefinition/USWest_Cov/FigureOfMerit/Revisit Type Save Style "Value By Grid Point" File "C:\STK_Reports\week16_revisit_1sat.txt"
# ============================================================
# Part B — Walker Delta 星座(3 面 x 4 顆,相位增量 1)
# Walker 會以 FIREYE 為種子複製 12 顆,連 Imager 一起複製,
# 命名為 FIREYE11..FIREYE34(平面編號 + 平面內編號)。
# ============================================================
Walker */Satellite/FIREYE Type Delta NumPlanes 3 NumSatsPerPlane 4 InterPlanePhaseIncrement 1 ColorByPlane Yes
New / */CoverageDefinition Walker_Cov
Cov */CoverageDefinition/Walker_Cov Grid AreaOfInterest LatLonRegion 30 -130 55 -110
Cov */CoverageDefinition/Walker_Cov Grid PointGranularity LatLon 2
# 12 顆全部指派為 asset(指到感測器,不是衛星)
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE11/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE12/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE13/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE14/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE21/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE22/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE23/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE24/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE31/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE32/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE33/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Asset */Satellite/FIREYE34/Sensor/Imager Assign
Cov */CoverageDefinition/Walker_Cov Access Compute
New / */CoverageDefinition/Walker_Cov/FigureOfMerit Revisit
Cov */CoverageDefinition/Walker_Cov/FigureOfMerit/Revisit FOMDefine Definition RevisitTime Compute Average
ReportCreate */CoverageDefinition/Walker_Cov/FigureOfMerit/Revisit Type Save Style "Value By Grid Point" File "C:\STK_Reports\week16_revisit_12sat.txt"
# ============================================================
# 讀數:兩份報告的平均重訪時間差多少?
# 用 week16_constellation_tradeoff.py 讀這兩份檔並與解析模型對照。
# ============================================================