import calendar
import os
import re
import tempfile
from datetime import datetime
from benchmarks.comparative.implementations.gmat.base import (
build_task_result,
gmat_clear,
time_iterations,
)
_JD_TO_UTCMJD = 2430000.0
def _parse_tle_epoch_jd(line1: str) -> float:
epoch_str = line1[18:32].strip()
yy = int(epoch_str[:2])
day_frac = float(epoch_str[2:])
year = (1900 + yy) if yy >= 57 else (2000 + yy)
from datetime import date
jan1_ts = calendar.timegm(date(year, 1, 1).timetuple())
jd_utc = jan1_ts / 86400.0 + 2440587.5 + (day_frac - 1.0)
return jd_utc
def _jd_to_utcmjd(jd: float) -> float:
return jd - _JD_TO_UTCMJD
def _gmat_utcgreg_to_jd(s: str) -> float:
s = s.strip()
dot_pos = s.rfind('.')
frac = 0.0
if dot_pos > 0:
frac = float('0' + s[dot_pos:])
s_whole = s[:dot_pos]
else:
s_whole = s
dt = datetime.strptime(s_whole, '%d %b %Y %H:%M:%S')
ts = calendar.timegm(dt.timetuple())
return ts / 86400.0 + 2440587.5 + frac / 86400.0
def _build_contact_script(
tle_file: str,
report_file: str,
locations: list[dict],
min_elevation_deg: float,
utcmjd_start: float,
utcmjd_end: float,
) -> str:
lines = []
lines.append("Create Spacecraft Sat")
lines.append(f"Sat.EphemerisName = '{tle_file}'")
lines.append("Sat.Id = 'ISS'")
lines.append("")
lines.append("Create Propagator TLEProp")
lines.append("TLEProp.Type = SPICESGP4")
lines.append("TLEProp.InitialStepSize = 60")
lines.append("")
gs_names = []
for i, loc in enumerate(locations):
name = f"GS{i}"
gs_names.append(name)
lat = float(loc["lat"])
lon_east = float(loc["lon"]) % 360.0
alt_km = float(loc["alt"]) / 1000.0 lines.append(f"Create GroundStation {name}")
lines.append(f"{name}.CentralBody = Earth")
lines.append(f"{name}.StateType = Spherical")
lines.append(f"{name}.Location1 = {lat:.10f}")
lines.append(f"{name}.Location2 = {lon_east:.10f}")
lines.append(f"{name}.Location3 = {alt_km:.10f}")
lines.append(f"{name}.MinimumElevationAngle = {min_elevation_deg:.4f}")
lines.append("")
observers_str = ", ".join(gs_names)
lines.append("Create ContactLocator CL")
lines.append("CL.Target = Sat")
lines.append(f"CL.Observers = {{{observers_str}}}")
lines.append(f"CL.Filename = '{report_file}'")
lines.append("CL.StepSize = 30")
lines.append("CL.UseLightTimeDelay = false")
lines.append("CL.UseStellarAberration = false")
lines.append("CL.UseEntireInterval = false")
lines.append("CL.InputEpochFormat = UTCModJulian")
lines.append(f"CL.InitialEpoch = '{utcmjd_start:.10f}'")
lines.append(f"CL.FinalEpoch = '{utcmjd_end:.10f}'")
lines.append("")
elapsed_days = (utcmjd_end - utcmjd_start)
lines.append("BeginMissionSequence")
lines.append(f"Propagate TLEProp(Sat) {{Sat.ElapsedDays = {elapsed_days:.15f}}}")
lines.append("")
return "\n".join(lines)
def _parse_contact_report(report_file: str, n_locations: int) -> list[list[dict]]:
results: list[list[dict]] = [[] for _ in range(n_locations)]
gs_re = re.compile(r'^Observer:\s+(GS(\d+))')
row_re = re.compile(
r'^(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+)'
r'\s+'
r'(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+)'
)
current_idx: int | None = None
try:
with open(report_file) as f:
for line in f:
line = line.rstrip('\n')
m_gs = gs_re.match(line.strip())
if m_gs:
current_idx = int(m_gs.group(2))
continue
if current_idx is not None:
m_row = row_re.match(line.strip())
if m_row:
start_jd = _gmat_utcgreg_to_jd(m_row.group(1))
end_jd = _gmat_utcgreg_to_jd(m_row.group(2))
if current_idx < n_locations:
results[current_idx].append(
{"start_jd": start_jd, "end_jd": end_jd}
)
except FileNotFoundError:
pass
return results
def sgp4_access(params: dict, iterations: int):
line1 = params["line1"]
line2 = params["line2"]
locations = params["locations"]
min_el = float(params["min_elevation_deg"])
duration = float(params["search_duration_seconds"])
jd_start = _parse_tle_epoch_jd(line1)
jd_end = jd_start + duration / 86400.0
utcmjd_start = _jd_to_utcmjd(jd_start)
utcmjd_end = _jd_to_utcmjd(jd_end)
tmpdir = os.environ.get("TMPDIR", tempfile.gettempdir())
tle_file = os.path.join(tmpdir, "gmat_access.tle")
script_file = os.path.join(tmpdir, "gmat_access.script")
report_file = os.path.join(tmpdir, "gmat_access_contacts.txt")
with open(tle_file, "w") as f:
f.write("ISS\n")
f.write(line1 + "\n")
f.write(line2 + "\n")
script_content = _build_contact_script(
tle_file, report_file, locations, min_el, utcmjd_start, utcmjd_end
)
with open(script_file, "w") as f:
f.write(script_content)
n_locations = len(locations)
def run():
import gmatpy as gmat
if os.path.exists(report_file):
os.remove(report_file)
gmat_clear()
gmat.LoadScript(script_file)
gmat.RunScript()
return _parse_contact_report(report_file, n_locations)
times, results = time_iterations(run, iterations)
return build_task_result(
"access.sgp4_access",
iterations,
times,
results,
extra_metadata={
"propagator": "SPICESGP4",
"contact_locator": "GMAT ContactLocator",
"step_size_s": 30,
"light_time": False,
"stellar_aberration": False,
"n_locations": n_locations,
},
)