Journey CD-AMD API Demonstration¶
This notebook demonstrates using the JOE-Postgrest API to create a Constellation Mission Design, trigger the Constellation Design, and follow it's completion with a Mission Propagation for each satellite defined in the Constellation Design.
The Semi-major Axis, Inclination, and Eccentricity are then graphed for all satellites.
In [1]:
import os
import json
import requests
from uuid import uuid4
from time import sleep
from datetime import datetime, timedelta
from io import StringIO
import hashlib
import matplotlib.pyplot as plt
import pandas as pd
import jwt
import joe.model as joe
In [2]:
JOE_URL = "http://localhost:3001"
USERNAME = os.getenv("USER_NAME", "user@user.user")
PASSWORD = os.getenv("USER_PASSWORD", "m0rpheus")
In [3]:
TOKEN = None
def refresh_token():
global TOKEN
try:
resp = requests.post(f"{JOE_URL}/api/auth/login/", json={"email": USERNAME, "password": PASSWORD})
TOKEN = resp.json()['access']
except:
print(f"Failed: {resp.status_code} -- {resp.text}")
refresh_token()
In [4]:
company_id = jwt.decode(TOKEN, options={"verify_signature": False})['company']
Define Mission Context¶
In [5]:
joes = []
# Physics
physics = joe.Physics(
start_time=(datetime.utcnow() + timedelta(days=365)).isoformat(),
gravity_model="EIGEN-6S",
atmospheric_model="NRLMSISE-00",
third_body_model="all",
solar_radiation_model=True,
harmonic_degree=4,
harmonic_order=4
)
morpheus_office_target = joe.Target(
name="morpheus-ofice-target",
latitude=33.920466,
longitude=-118.390008,
minimum_elevation=45.0,
altitude=0.0,
shapefile=None
)
# Mission
mission = joe.Mission(
id=str(uuid4()),
creation_date=datetime.utcnow().isoformat(),
update_date=datetime.utcnow().isoformat(),
owned_by=company_id,
name=f"CD-MOO-TEST",
launch_date=(datetime.utcnow() + timedelta(days=365)).isoformat(),
amd_enabled=True,
physics=physics,
targets=[morpheus_office_target],
groundstations=[],
maneuvers=[])
joes.append(mission)
# Bus
bus = joe.Bus(**joe.Bus.BUS_TEMPLATES['6U'])
payload = joe.Payload(
fov_cross=40.0,
fov_along=20.0
)
thruster = joe.Propulsion(
type="Custom",
prop_mass=10.0,
isp=[float(200)],
thrust=[float(0.3)]
)
battery = joe.Battery(
voltage=float(12.7),
capacity=float(30000),
maximum_discharge_rate=float(30),
recommended_discharge_rate=float(30)
)
solar_panels = [joe.SolarPanel(
mount_type="Body",
cell_efficiency=float(0.8),
surface_area=0.04,
voltage=12.0,
) for i in range(3)]
power_budget = joe.PowerBudget(
budget={
"propulsion_active_power_draw": float(1),
"adcs_active_power_draw": float(1)
}
)
# Satellite
satellite = joe.Satellite(
id=str(uuid4()),
creation_date=datetime.utcnow().isoformat(),
update_date=datetime.utcnow().isoformat(),
owned_by=company_id,
mission=mission.id,
name=f"CD-MOO-TEST",
bus=bus,
payload=payload,
battery=battery,
solar_panels=solar_panels,
power_budget=power_budget,
propulsion=thruster)
joes.append(satellite)
In [6]:
constellationDesign = joe.ConstellationDesign(
id=str(uuid4()),
creation_date=datetime.utcnow().isoformat(),
update_date=datetime.utcnow().isoformat(),
owned_by=company_id,
mission=mission.id,
type="Multi-Objective",
number_of_planes=5,
inter_plane_spacing=1.0,
satellite_count=3.0,
elevation_angle=5.0,
desired_coverage_fold=5.0,
end_date_hours=5.0,
end_date_days=5.0,
spatial_coverage=5.0,
spatial_uniformity=5.0,
temporal_uniformity=5.0,
temporal_average_gap=5.0,
maximum_gap=5.0,
initial_orbit_type="LEO",
deployment_orbit_template="MEO",
semi_major_axis_value=6878.0,
semi_major_axis_type="???",
eccentricity=0.0005,
inclination=55.0,
argument_of_perigee=0.05,
raan=0.01,
true_anomaly=30.0,
targets=[morpheus_office_target],
generate=False
)
joes.append(constellationDesign)
/Users/lutherblackwood/prj/qajourney/venv/lib/python3.11/site-packages/joe/model/base.py:104: UserWarning: Casting number_of_planes from <class 'int'> to <class 'float'>. warnings.warn(f"Casting {varname} from {intype} to {outtype}.")
In [7]:
refresh_token()
for obj in joes:
obj_type = type(obj).__name__.lower()
resp = requests.post(
f"{JOE_URL}/{obj_type}",
json=obj.to_json(to_obj=True),
headers={"Authorization": f"Bearer {TOKEN}"})
assert resp.status_code == 201, f"Failed to create {type(obj).__name__}: {resp.status_code}--{resp.text}"
print(f"Created {type(obj).__name__} in Journey")
Created Mission in Journey Created Satellite in Journey Created ConstellationDesign in Journey
In [8]:
resp = requests.patch(
f"{JOE_URL}/constellationdesign?id=eq.{constellationDesign.id}",
json={
"generate": True
},
headers={"Authorization": f"Bearer {TOKEN}"})
assert resp.status_code == 200, f"Failed to create {type(obj).__name__}: {resp.status_code}--{resp.text}"
Poll for CD Completion¶
In [9]:
completed = False
while not completed:
refresh_token()
resp = requests.get(
f"{JOE_URL}/constellationdesignticket?mission=eq.{mission.id}",
headers={"Authorization": f"Bearer {TOKEN}"})
if resp.status_code == 200:
cd_ticket = resp.json()[0]
print(f"{datetime.utcnow()} -- {cd_ticket['status']}")
completed = cd_ticket['status'] == "COMPLETED"
if not completed:
sleep(30)
print(f"CD Completed at {datetime.utcnow()}")
2024-07-16 21:55:34.621515 -- STARTED 2024-07-16 21:56:05.632074 -- STARTED 2024-07-16 21:56:36.750163 -- STARTED 2024-07-16 21:57:09.048706 -- STARTED 2024-07-16 21:57:40.112518 -- STARTED 2024-07-16 21:58:11.192428 -- STARTED 2024-07-16 21:58:42.258024 -- STARTED 2024-07-16 21:59:14.536625 -- COMPLETED CD Completed at 2024-07-16 21:59:14.537068
Poll for MD Completion¶
In [10]:
completed = False
while not completed:
refresh_token()
resp = requests.get(
f"{JOE_URL}/missiondesignticket?constellation=eq.{cd_ticket['id']}",
headers={"Authorization": f"Bearer {TOKEN}"})
if resp.status_code == 200:
md_tickets = resp.json()
stati = [md['status'] for md in md_tickets]
num_completed = sum([s == "COMPLETED" for s in stati])
print(f"{datetime.utcnow()} -- {num_completed}/{len(stati)}")
completed = num_completed == len(stati)
if not completed:
sleep(30)
print(f"MD Runs Completed at {datetime.utcnow()}")
2024-07-16 21:59:15.635645 -- 0/3 2024-07-16 21:59:46.715756 -- 0/3 2024-07-16 22:00:18.815474 -- 0/3 2024-07-16 22:00:49.837909 -- 3/3 MD Runs Completed at 2024-07-16 22:00:49.838241
Plot Mission-Design Orbital Elements Results¶
In [11]:
data = {}
for md in md_tickets:
print(f"{datetime.utcnow()} -- Retrieving {md['id']}")
refresh_token()
data[md['id']] = {}
for data_type in ["orbital_elements", "power", "propulsion"]:
resp = requests.get(
f"{JOE_URL}/missiondesignresult?mission_design_ticket=eq.{md['id']}&key=eq.{data_type}",
headers={"Authorization": f"Bearer {TOKEN}"})
assert resp.status_code == 200, f"Failed to retrieve {data_type} for {md['id']} -- {resp.status_code} - {resp.text}"
data[md['id']][data_type] = pd.read_csv(StringIO(resp.text))
2024-07-16 22:00:49.843391 -- Retrieving 7657715b-37e5-464f-bd4c-93df40e7bdcc 2024-07-16 22:00:51.945163 -- Retrieving b472e812-6f64-4db9-a278-2d9541901de6 2024-07-16 22:00:54.098217 -- Retrieving c5c57f7a-cefa-43ab-8856-be66fe67a2ff
In [12]:
fig, axes = plt.subplots(nrows=3, ncols=len(data), sharey='row', sharex='col')
for m_idx, (md, md_data) in enumerate(data.items()):
for e_idx, elem in enumerate(['Semi-major Axis [km]', 'Inclination [degrees]', 'Eccentricity']):
ax = axes[e_idx][m_idx]
df = md_data['orbital_elements']
df['Time [UTC]'] = pd.to_datetime(df['Time [UTC]'], format='mixed')
x = df['Time [UTC]']
y = df[elem]
ax.plot(x, y, label=md)
ax.set_title(f"Sat_{m_idx}-{elem}")
fig.set_size_inches(48, 10.5, forward=True)
fig.autofmt_xdate()
In [ ]: