1. 들어가는 글: 백문이 불여일코딩
지난 시간에 우리는 저궤도 군집 위성이 지구를 빈틈없이 덮기 위해 '워커 델타(Walker Delta)' 패턴을 쓴다는 것을 배웠습니다.
"사과 껍질 깎듯이 궤도면을 배치하고, 위성끼리 위상차를 둔다."
말로 들으면 그럴싸하지만, 실제로 어떻게 생겼는지 감이 잘 안 옵니다.
오늘은 파이썬의 matplotlib을 이용해 수천 개의 위성을 3차원으로 띄워보고, skyfield 라이브러리를 이용해 실제 스타링크 위성이 언제 내 머리 위를 지나가는지 계산해 보겠습니다.
2. 준비물: 라이브러리 설치
이번 실습에는 천문 계산용 라이브러리인 skyfield가 필요합니다.
pip install skyfield matplotlib numpy
3. 실습 1: 워커 델타(Walker Delta) 패턴 그리기
스타링크처럼 수천 개의 위성이 지구를 감싸는 '그물망'을 만들어 봅시다.
워커 델타 표기법 $i: t/p/f$를 사용합니다.
- $i$: 경사각 (Inclination)
- $t$: 총 위성 개수 (Total Satellites)
- $p$: 궤도면 개수 (Planes)
- $f$: 위상 인자 (Phasing Parameter - 인접 궤도면 간의 위성 위치 차이)
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def generate_walker_delta(total_sats, planes, phasing, inclination_deg, altitude_km):
"""
워커 델타 군집 위성의 3차원 좌표 생성 함수
"""
R_earth = 6371.0
r = R_earth + altitude_km
inclination = np.radians(inclination_deg)
sats_per_plane = total_sats // planes
positions = []
# 각 궤도면(Plane) 루프
for p in range(planes):
# 궤도면의 승교점 적경(RAAN): 360도를 궤도면 개수로 쪼갬
raan = 2 * np.pi * p / planes
# 각 위성(Satellite) 루프
for s in range(sats_per_plane):
# 위성 간 위상차(Mean Anomaly) 계산
# 1. 궤도 내에서 위성끼리의 간격
# 2. 인접 궤도면과의 위상차 (Phasing Parameter f 적용)
angle_in_plane = 2 * np.pi * s / sats_per_plane
phase_offset = 2 * np.pi * phasing * p / (total_sats)
theta = angle_in_plane + phase_offset # 최종 위상각
# 케플러 요소 -> 3차원 직교 좌표(x, y, z) 변환
x = r * (np.cos(raan) * np.cos(theta) - np.sin(raan) * np.sin(theta) * np.cos(inclination))
y = r * (np.sin(raan) * np.cos(theta) + np.cos(raan) * np.sin(theta) * np.cos(inclination))
z = r * (np.sin(theta) * np.sin(inclination))
positions.append([x, y, z])
return np.array(positions)
# --- 시뮬레이션 설정 (스타링크 1단계 유사 스펙) ---
# 53도 경사각, 위성 1584개, 72개 궤도면, 위상인자 1
positions = generate_walker_delta(total_sats=1584, planes=72, phasing=1, inclination_deg=53, altitude_km=550)
# --- 3D 시각화 ---
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')
# 지구 그리기 (파란 구)
u, v = np.mgrid[0:2*np.pi:20j, 0:np.pi:10j]
x_e = 6371 * np.cos(u) * np.sin(v)
y_e = 6371 * np.sin(u) * np.sin(v)
z_e = 6371 * np.cos(v)
ax.plot_wireframe(x_e, y_e, z_e, color="blue", alpha=0.1)
# 위성 점 찍기 (빨간 점)
# 데이터가 너무 많으므로 점 크기를 작게(s=1) 설정
ax.scatter(positions[:,0], positions[:,1], positions[:,2], s=1, c='red', alpha=0.8, label='Starlink Satellites')
ax.set_title("Walker Delta Constellation (Starlink-like Shell)")
ax.set_xlabel('X (km)')
ax.set_ylabel('Y (km)')
ax.set_zlabel('Z (km)')
plt.legend()
plt.show()
[실행 결과]

코드를 실행하면 파란색 지구 주변을 빨간 점들이 촘촘하게 그물망처럼 감싸고 있는 모습이 나타납니다.
특히 극지방(위아래)은 비어있고, 중위도 지역에 위성이 밀집된 53도 경사각의 특징을 눈으로 확인할 수 있습니다. 이것이 바로 전 세계 어디서나 인터넷을 터뜨리는 '메가 콘스텔레이션'의 실체입니다.
4. 실습 2: 오늘 밤 내 머리 위 스타링크는?
이제 진짜 현실 데이터를 가져옵니다. skyfield를 이용해 최신 TLE(궤도 정보)를 불러오고, 내가 사는 곳(예: 서울) 위를 지나가는 시간을 예측해 봅시다.
from skyfield.api import Topos, load
# 1. 최신 궤도 데이터(TLE) 불러오기
stations_url = 'http://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=tle'
satellites = load.tle_file(stations_url)
print(f"Loaded {len(satellites)} satellites.")
# 2. 관측자 위치 설정 (서울)
seoul = Topos('37.5665 N', '126.9780 E')
ts = load.timescale()
# 3. 특정 위성 하나 골라 추적하기 (수정된 부분)
# satellites는 리스트이므로, 바로 인덱스로 접근합니다.
target_index = 100
if target_index < len(satellites):
satellite = satellites[target_index]
print(f"Tracking Target: {satellite.name}")
# 4. 관측 가능 시간 계산 (오늘 ~ 내일)
t0 = ts.now()
t1 = ts.utc(t0.utc_datetime().year, t0.utc_datetime().month, t0.utc_datetime().day + 1)
# find_events: Rise, Culminate, Set 시각 계산
times, events = satellite.find_events(seoul, t0, t1, altitude_degrees=30.0)
print(f"\n=== Pass Schedule for {satellite.name} over Seoul ===")
if len(times) == 0:
print("아쉽게도 오늘/내일 중에는 30도 이상 높이 뜨는 관측 기회가 없네요.")
else:
for ti, event in zip(times, events):
name = ('rise above 30°', 'culminate', 'set below 30°')[event]
print(f"{ti.utc_strftime('%Y-%m-%d %H:%M:%S')} UTC : {name}")
else:
print("위성 데이터가 충분하지 않습니다.")
[코드 설명]
- load.tle_file: 인터넷에서 실시간으로 스타링크 위성들의 궤도 정보(TLE)를 긁어옵니다. (인터넷 연결 필수)
- Topos: 여러분이 계신 위도, 경도를 입력하면 됩니다.
- find_events: 복잡한 궤도 계산을 한 방에 해결해 주는 함수입니다. Rise(뜨고), Culminate(남중하고), Set(지는) 시간을 알려줍니다.
- altitude_degrees=30: 도심에서는 건물이 많아 30도 이상 높이 떴을 때만 보이기 때문에 필터를 걸었습니다.

5. 마무리: 우주를 코딩하다
오늘 우리는 파이썬으로 두 가지 시선을 경험했습니다.
- 신의 시선 (God's Eye): 지구 밖에서 수천 개의 위성이 춤추는 패턴(워커 델타)을 설계했습니다.
- 인간의 시선 (Observer's Eye): 지상에서 밤하늘을 올려다보며 언제 위성이 지나갈지 예측했습니다.
뉴스페이스 시대의 엔지니어는 이처럼 거시적인 설계 능력과 실시간 데이터 분석 능력을 동시에 갖춰야 합니다.
다음 글에서는 우주 산업의 가장 뜨거운 감자, '우주 쓰레기(Space Debris)' 문제를 다룹니다.
총알보다 10배 빠른 페인트 조각이 위성을 어떻게 파괴하는지, 그리고 이를 청소하기 위한 '청소부 위성(ADR)' 기술에 대해 알아보겠습니다.
'우주공학 > Python' 카테고리의 다른 글
| [Python 심] 셔터를 누르면 일어나는 일: 위성 데이터 생성 및 다운링크 시뮬레이터 (0) | 2026.02.06 |
|---|---|
| [Python] "30cm급 위성을 만들려면?" 광학 탑재체 설계 계산기 (0) | 2026.02.05 |
| [Python 위성 심화] 태양을 향해 쏴라 (2): 파이썬으로 구현하는 궤도 에너지 밸런스 시뮬레이션 (0) | 2026.01.28 |
| [Python 위성 심화] 화성에서 온 사진: 통신 링크 버짓(Link Budget) 계산기와 안테나 설계 (0) | 2026.01.27 |
| [Python 위성 심화] 내 위성, 안 부서질까? 진동 시험 데이터(PSD) 분석과 Grms 계산 (0) | 2026.01.27 |