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="morpeus-ofice-target",
    latitude=33.920466,
    longitude=-118.390008,
    minimum_elevation=45.0,
    altitude=1000.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-WD-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-WD-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="Walker Delta",
    number_of_planes=5,
    inter_plane_spacing=1.0,
    satellite_count=15.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=8000.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 22:04:54.843277 -- STARTED
2024-07-16 22:05:25.957908 -- STARTED
2024-07-16 22:05:56.971390 -- STARTED
2024-07-16 22:06:28.006017 -- STARTED
2024-07-16 22:06:59.037996 -- STARTED
2024-07-16 22:07:31.415950 -- COMPLETED
CD Completed at 2024-07-16 22:07:31.416685

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 22:07:32.629678 -- 0/12
2024-07-16 22:08:03.653984 -- 0/15
2024-07-16 22:08:34.722374 -- 0/15
2024-07-16 22:09:07.025833 -- 9/15
2024-07-16 22:09:38.135924 -- 15/15
MD Runs Completed at 2024-07-16 22:09:38.136333

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:09:38.148310 -- Retrieving 2f4bfccb-01b7-4092-9e62-f93a4b65a4c1
2024-07-16 22:09:40.273927 -- Retrieving 038b4788-8e2b-47af-963c-bd4b7088fb51
2024-07-16 22:09:42.388060 -- Retrieving 7cab8c28-4a45-4684-a6fe-734b78ecc580
2024-07-16 22:09:44.468958 -- Retrieving 51cc85ab-cfa4-4473-89e4-b69f1244b53d
2024-07-16 22:09:46.783017 -- Retrieving 2ead524d-90e1-4785-bb1e-8dcd333e34f3
2024-07-16 22:09:48.798417 -- Retrieving ae46d5f4-ab30-440e-89c6-acfb080e21ef
2024-07-16 22:09:50.905317 -- Retrieving 14076502-a3b9-4763-8562-41275d39808c
2024-07-16 22:09:53.030853 -- Retrieving 7b88dfc0-b656-4e4d-869c-b107a8cf1413
2024-07-16 22:09:55.104722 -- Retrieving 230b4c0b-9831-43ed-9325-fa4e943825fe
2024-07-16 22:09:57.252665 -- Retrieving 2b89e2b5-e63d-4fa7-9d2d-5d542558fa64
2024-07-16 22:09:59.282198 -- Retrieving bc1c360f-58b0-48a1-ad38-3e6c2a3341d3
2024-07-16 22:10:01.637163 -- Retrieving edebbd0a-706a-47e1-b70e-57b59cd5afd7
2024-07-16 22:10:03.750377 -- Retrieving e6dc4d61-8a98-415c-8e5e-16d0508d2de7
2024-07-16 22:10:05.873848 -- Retrieving 1b3d4c3a-65c8-47e6-b801-a59e447e44f9
2024-07-16 22:10:08.014623 -- Retrieving 52117054-8771-418f-9dc3-5011d23056ad
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()
No description has been provided for this image
In [ ]: