Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Only recent releases are listed. Older entries are in this file's git history (`

## Unreleased

### Changed

- OMM interface overhaul: `omm_from_url` returns every field the source provided (metadata, `COMMENT`, Space-Track extras such as `OBJECT_TYPE`/`RCS_SIZE`/`TLE_LINE1`, XML `USER_DEFINED` parameters) instead of 17 keys; new `omm_from_file` / `omm_from_text` loaders (JSON or XML, detected from content) replace the `xmltodict` workaround; `TLE.from_omm` / `TLE.to_omm` convert between the representations; `sgp4` dict inputs go through the same serde parser as Rust (quoted numbers, `null`/empty optionals, case-insensitive metadata, any level of an xmltodict tree, `EPOCH` as `time`/`datetime`); propagation rejects `EPHEMERIS_TYPE` 4 (SGP4-XP) instead of running classic SGP4 on it; stubs gain the `OMMDict` type. Rust: `OMM.epoch` is an `Instant` (was a string; `epoch_instant()` deprecated), `OMM` derives `Serialize`, gains `from_mean_elements`, `from_tle`/`to_tle`, `from_json_value`, `from_text`/`from_file`, `reset_cache`; `from_json_string` accepts a bare object; `omm::Error` is `#[non_exhaustive]` with a single `InvalidField` variant; `Default` is removed ([#173](https://github.com/ssmichael1/satkit/pull/173))

### Fixed

- `satkit.density.nrlmsise(altitude_m, latitude_rad, longitude_rad, time)` converts its radian latitude/longitude to the degrees the model takes; the values were passed through unchanged, so a caller following the stub at 60° N was evaluated at 1.05° N (the `itrfcoord` overload and `nrlmsise00(latitude_deg=...)` were already correct) ([#171](https://github.com/ssmichael1/satkit/pull/171))
Expand Down
8 changes: 7 additions & 1 deletion docs/api/tle.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ API reference for the SGP4 analytic propagator and its inputs. For background, s

- [TLEs, SGP4 & OMMs](../guide/tle.md) — theory and format descriptions
- Primary sources: [Hoots & Roehrich (1980)](../guide/references.md#hoots1980) and [Vallado et al. (2006)](../guide/references.md#vallado2006) for SGP4; [Vallado & Crawford (2008)](../guide/references.md#vallado2008) for TLE fitting; [CCSDS 502.0-B-3](../guide/references.md#ccsds502) for OMMs
- Tutorials: [Two-Line Element Set](../tutorials/Two-Line Element Set.ipynb), [TLE Fitting](../tutorials/TLE Fitting.ipynb), [SGP4 vs Numerical Propagation](../tutorials/SGP4 vs Numerical Propagation.ipynb)
- Tutorials: [Two-Line Element Set](../tutorials/Two-Line Element Set.ipynb), [Orbital Mean-Element Message](../tutorials/Orbital Mean-Element Message.ipynb), [TLE Fitting](../tutorials/TLE Fitting.ipynb), [SGP4 vs Numerical Propagation](../tutorials/SGP4 vs Numerical Propagation.ipynb)

::: satkit.TLE

::: satkit.sgp4

::: satkit.omm_from_url

::: satkit.omm_from_file

::: satkit.omm_from_text

::: satkit.OMMDict
23 changes: 21 additions & 2 deletions docs/guide/tle.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ OMMs are commonly published as:
- XML
- KVN (key-value notation)

OMMs are commonly published as JSON or XML. The `satkit.omm_from_url()` function fetches OMMs from a URL and auto-detects the format, returning a list of Python dictionaries that can be passed directly to `satkit.sgp4()`.
satkit reads the JSON and XML forms (KVN is not supported). In Python an OMM is a plain dictionary keyed by the CCSDS field names: `satkit.omm_from_url()`, `satkit.omm_from_file()` and `satkit.omm_from_text()` return a list of them, with every field the source provided (Space-Track's catalog extras such as `OBJECT_TYPE` and `RCS_SIZE` included) and numbers converted from Space-Track's quoted strings. `satkit.sgp4()` accepts these dictionaries directly, as well as the raw output of `json.load` on a CelesTrak or Space-Track response. `satkit.TLE.from_omm()` and `satkit.TLE.to_omm()` convert between the two representations.

You can also provide OMM dictionaries manually (e.g. parsed from a local JSON file).
A message whose `MEAN_ELEMENT_THEORY` is not SGP4, whose `TIME_SYSTEM` is not UTC, or whose `EPHEMERIS_TYPE` is 4 (SGP4-XP, which Space-Track distributes alongside classic SGP4 sets) is rejected rather than propagated with the wrong theory.

In Rust the same message is the `satkit::omm::OMM` struct, which implements `SGP4Source`, serializes back to JSON with `serde`, and converts with `OMM::from_tle` / `OMM::to_tle`.

## Loading from URLs

Expand Down Expand Up @@ -132,3 +134,20 @@ XML format works the same way -- just change the URL:
```python
omms = sk.omm_from_url("https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=xml")
```

### OMM from a local file, and conversion to a TLE

```python
import satkit as sk

# JSON or XML, detected from the content; one dict per message
omms = sk.omm_from_file("gp.json")

# Everything the source provided is in the dict
print(omms[0]["OBJECT_NAME"], omms[0].get("OBJECT_TYPE"), omms[0].get("RCS_SIZE"))

# The same element set as a TLE object, and back again
tle = sk.TLE.from_omm(omms[0])
print("\n".join(tle.to_2line()))
assert sk.TLE.from_omm(tle.to_omm()).to_2line() == tle.to_2line()
```
1 change: 0 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ scienceplots
cartopy
certifi
ipykernel
xmltodict
pandas
scipy
requests
66 changes: 2 additions & 64 deletions docs/tutorials/Orbital Mean-Element Message.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -98,69 +98,7 @@
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"import satkit as sk\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import scienceplots # noqa: F401\n",
"plt.style.use([\"science\", \"no-latex\", \"../satkit.mplstyle\"])\n",
"%config InlineBackend.figure_formats = ['svg']\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\", \"Downloading\")\n",
"import cartopy.crs as ccrs\n",
"import cartopy.feature as cfeature\n",
"\n",
"# Fetch the latest ISS ephemeris in XML format. sk.omm_from_url auto-detects\n",
"# the format and normalizes the result to the same list-of-dicts shape as the\n",
"# JSON example above — no xmltodict or hand-written tree walking needed.\n",
"url = \"https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=xml\"\n",
"try:\n",
" omm = sk.omm_from_url(url)[0]\n",
"except RuntimeError as e:\n",
" print(f\"Live fetch unavailable ({e}); using the pinned OMM\")\n",
" omm = ISS_OMM\n",
"\n",
"# Get a representative time from the OMM epoch\n",
"epoch = sk.time(omm[\"EPOCH\"])\n",
"# Time array spanning roughly one orbit at 1-minute cadence\n",
"time_array = [epoch + sk.duration(minutes=i) for i in range(97)]\n",
"\n",
"# TEME (inertial) output from SGP4\n",
"pTEME, _vTEME = sk.sgp4(omm, time_array)\n",
"\n",
"# Rotate to Earth-fixed\n",
"pITRF = [sk.frametransform.rotation(sk.frame.TEME, sk.frame.ITRF, t) * p for t, p in zip(time_array, pTEME)]\n",
"\n",
"coord = [sk.itrfcoord(x) for x in pITRF]\n",
"\n",
"lat, lon, alt = zip(*[(c.latitude_deg, c.longitude_deg, c.altitude) for c in coord])\n",
"\n",
"# Break ground track at longitude discontinuities (date line crossings)\n",
"lon_arr, lat_arr = np.array(lon), np.array(lat)\n",
"breaks = np.where(np.abs(np.diff(lon_arr)) > 180)[0] + 1\n",
"lon_segs = np.split(lon_arr, breaks)\n",
"lat_segs = np.split(lat_arr, breaks)\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 5), subplot_kw={\"projection\": ccrs.PlateCarree()})\n",
"ax.add_feature(cfeature.LAND, facecolor=\"lightgray\")\n",
"ax.add_feature(cfeature.BORDERS, linewidth=0.5)\n",
"ax.add_feature(cfeature.COASTLINE, linewidth=0.5)\n",
"for lo, la in zip(lon_segs, lat_segs):\n",
" ax.plot(lo, la, linewidth=1.5, color=\"C0\", transform=ccrs.PlateCarree())\n",
"ax.set_title(\"ISS Ground Track\")\n",
"ax.set_global()\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"fig, ax = plt.subplots(figsize=(10, 4))\n",
"ax.plot([t.datetime() for t in time_array], np.array(alt) / 1e3)\n",
"ax.set_xlabel(\"Time\")\n",
"ax.set_ylabel(\"Altitude (km)\")\n",
"ax.set_title(\"ISS Altitude vs Time\")\n",
"fig.autofmt_xdate()\n",
"plt.tight_layout()\n",
"plt.show()"
]
"source": "import json\nimport tempfile\nimport os\n\n# --- JSON file -----------------------------------------------------------\n# Write a small single-satellite OMM JSON file, standing in for a\n# Space-Track / CelesTrak bulk download saved to disk.\nomm_json = \"\"\"[{\n \"OBJECT_NAME\": \"ISS (ZARYA)\", \"OBJECT_ID\": \"1998-067A\",\n \"EPOCH\": \"2021-10-02T14:10:59.999\", \"TIME_SYSTEM\": \"UTC\",\n \"MEAN_ELEMENT_THEORY\": \"SGP4\",\n \"MEAN_MOTION\": \"15.48915330\", \"ECCENTRICITY\": \"0.0007417\",\n \"INCLINATION\": \"51.6432\", \"RA_OF_ASC_NODE\": \"351.4697\",\n \"ARG_OF_PERICENTER\": \"130.5364\", \"MEAN_ANOMALY\": \"329.6482\",\n \"BSTAR\": \"0.0001027\", \"MEAN_MOTION_DOT\": \"0.00016717\",\n \"MEAN_MOTION_DDOT\": \"0\"\n}]\"\"\"\nwith tempfile.NamedTemporaryFile(\"w\", suffix=\".json\", delete=False) as fh:\n fh.write(omm_json)\n json_path = fh.name\n\n# sk.omm_from_file detects JSON vs XML from the content and returns the\n# same list of dicts as sk.omm_from_url. (Plain json.load works too: sgp4\n# accepts the raw dict, quoted numbers and all.)\nomm_list = sk.omm_from_file(json_path)\nos.unlink(json_path)\n\nepoch = sk.time(omm_list[0][\"EPOCH\"])\np_json, v_json = sk.sgp4(omm_list[0], epoch + sk.duration(minutes=30))\nprint(f\"Position from JSON file (m): {p_json}\")\n\n# --- XML text ------------------------------------------------------------\n# The same OMM in CCSDS XML. sk.omm_from_text parses it directly; no XML\n# library or tree-walking needed.\nomm_xml = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ndm><omm id=\"CCSDS_OMM_VERS\" version=\"2.0\">\n <body><segment>\n <metadata>\n <OBJECT_NAME>ISS (ZARYA)</OBJECT_NAME><OBJECT_ID>1998-067A</OBJECT_ID>\n <CENTER_NAME>EARTH</CENTER_NAME><REF_FRAME>TEME</REF_FRAME>\n <TIME_SYSTEM>UTC</TIME_SYSTEM>\n <MEAN_ELEMENT_THEORY>SGP4</MEAN_ELEMENT_THEORY>\n </metadata>\n <data>\n <meanElements>\n <EPOCH>2021-10-02T14:10:59.999</EPOCH>\n <MEAN_MOTION>15.48915330</MEAN_MOTION>\n <ECCENTRICITY>0.0007417</ECCENTRICITY>\n <INCLINATION>51.6432</INCLINATION>\n <RA_OF_ASC_NODE>351.4697</RA_OF_ASC_NODE>\n <ARG_OF_PERICENTER>130.5364</ARG_OF_PERICENTER>\n <MEAN_ANOMALY>329.6482</MEAN_ANOMALY>\n </meanElements>\n <tleParameters>\n <EPHEMERIS_TYPE>0</EPHEMERIS_TYPE>\n <CLASSIFICATION_TYPE>U</CLASSIFICATION_TYPE>\n <NORAD_CAT_ID>25544</NORAD_CAT_ID>\n <ELEMENT_SET_NO>900</ELEMENT_SET_NO>\n <REV_AT_EPOCH>29935</REV_AT_EPOCH>\n <BSTAR>0.0001027</BSTAR>\n <MEAN_MOTION_DOT>0.00016717</MEAN_MOTION_DOT>\n <MEAN_MOTION_DDOT>0</MEAN_MOTION_DDOT>\n </tleParameters>\n </data>\n </segment></body>\n</omm></ndm>\"\"\"\n\nomm_from_xml = sk.omm_from_text(omm_xml)[0]\np_xml, v_xml = sk.sgp4(omm_from_xml, epoch + sk.duration(minutes=30))\nprint(f\"Position from XML text (m): {p_xml}\")\n\n# Same OMM, same epoch -> identical states from both formats\nassert (p_json == p_xml).all() and (v_json == v_xml).all()\nprint(\"JSON and XML routes agree.\")\n\n# --- TLE <-> OMM -----------------------------------------------------------\n# An OMM dict converts to a TLE object and back; the TLE lines are the\n# classic 69-character form of the same element set.\ntle = sk.TLE.from_omm(omm_from_xml)\nprint(\"\\n\".join(tle.to_2line()))\nprint(json.dumps(tle.to_omm(), indent=1)[:200], \"...\")\n"
},
{
"cell_type": "markdown",
Expand All @@ -171,7 +109,7 @@
{
"cell_type": "code",
"id": "7c9e6f6c",
"source": "import json\nimport tempfile\nimport os\nimport xmltodict\n\n# --- JSON file -----------------------------------------------------------\n# Write a small single-satellite OMM JSON file, standing in for a\n# Space-Track / CelesTrak bulk download saved to disk.\nomm_json = \"\"\"[{\n \"OBJECT_NAME\": \"ISS (ZARYA)\", \"OBJECT_ID\": \"1998-067A\",\n \"EPOCH\": \"2021-10-02T14:10:59.999\", \"TIME_SYSTEM\": \"UTC\",\n \"MEAN_ELEMENT_THEORY\": \"SGP4\",\n \"MEAN_MOTION\": \"15.48915330\", \"ECCENTRICITY\": \"0.0007417\",\n \"INCLINATION\": \"51.6432\", \"RA_OF_ASC_NODE\": \"351.4697\",\n \"ARG_OF_PERICENTER\": \"130.5364\", \"MEAN_ANOMALY\": \"329.6482\",\n \"BSTAR\": \"0.0001027\", \"MEAN_MOTION_DOT\": \"0.00016717\",\n \"MEAN_MOTION_DDOT\": \"0\"\n}]\"\"\"\nwith tempfile.NamedTemporaryFile(\"w\", suffix=\".json\", delete=False) as fh:\n fh.write(omm_json)\n json_path = fh.name\n\n# The entire \"loader\": stdlib json -> list of dicts -> sgp4\nwith open(json_path) as fh:\n omm_list = json.load(fh)\n\nepoch = sk.time(omm_list[0][\"EPOCH\"])\np_json, v_json = sk.sgp4(omm_list[0], epoch + sk.duration(minutes=30))\nprint(f\"Position from JSON file (m): {p_json}\")\nos.unlink(json_path)\n\n# --- XML file ------------------------------------------------------------\n# The same OMM in CCSDS XML. xmltodict parses it; navigate down to the\n# body/segment/data element and hand that dictionary to sgp4 — the nested\n# meanElements / tleParameters groups are understood directly.\nomm_xml = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ndm><omm id=\"CCSDS_OMM_VERS\" version=\"2.0\">\n <body><segment>\n <metadata>\n <OBJECT_NAME>ISS (ZARYA)</OBJECT_NAME><OBJECT_ID>1998-067A</OBJECT_ID>\n <CENTER_NAME>EARTH</CENTER_NAME><REF_FRAME>TEME</REF_FRAME>\n <TIME_SYSTEM>UTC</TIME_SYSTEM>\n <MEAN_ELEMENT_THEORY>SGP4</MEAN_ELEMENT_THEORY>\n </metadata>\n <data>\n <meanElements>\n <EPOCH>2021-10-02T14:10:59.999</EPOCH>\n <MEAN_MOTION>15.48915330</MEAN_MOTION>\n <ECCENTRICITY>0.0007417</ECCENTRICITY>\n <INCLINATION>51.6432</INCLINATION>\n <RA_OF_ASC_NODE>351.4697</RA_OF_ASC_NODE>\n <ARG_OF_PERICENTER>130.5364</ARG_OF_PERICENTER>\n <MEAN_ANOMALY>329.6482</MEAN_ANOMALY>\n </meanElements>\n <tleParameters>\n <BSTAR>0.0001027</BSTAR>\n <MEAN_MOTION_DOT>0.00016717</MEAN_MOTION_DOT>\n <MEAN_MOTION_DDOT>0</MEAN_MOTION_DDOT>\n </tleParameters>\n </data>\n </segment></body>\n</omm></ndm>\"\"\"\n\nparsed = xmltodict.parse(omm_xml)\n# Multi-satellite files have a list under [\"ndm\"][\"omm\"]; this one is a\n# single entry, so index straight down to the data element.\ndata = parsed[\"ndm\"][\"omm\"][\"body\"][\"segment\"][\"data\"]\np_xml, v_xml = sk.sgp4(data, epoch + sk.duration(minutes=30))\nprint(f\"Position from XML file (m): {p_xml}\")\n\n# Same OMM, same epoch -> identical states from both file formats\nassert (p_json == p_xml).all() and (v_json == v_xml).all()\nprint(\"JSON and XML file routes agree.\")",
"source": "import json\nimport tempfile\nimport os\n\n# --- JSON file -----------------------------------------------------------\n# Write a small single-satellite OMM JSON file, standing in for a\n# Space-Track / CelesTrak bulk download saved to disk.\nomm_json = \"\"\"[{\n \"OBJECT_NAME\": \"ISS (ZARYA)\", \"OBJECT_ID\": \"1998-067A\",\n \"EPOCH\": \"2021-10-02T14:10:59.999\", \"TIME_SYSTEM\": \"UTC\",\n \"MEAN_ELEMENT_THEORY\": \"SGP4\",\n \"MEAN_MOTION\": \"15.48915330\", \"ECCENTRICITY\": \"0.0007417\",\n \"INCLINATION\": \"51.6432\", \"RA_OF_ASC_NODE\": \"351.4697\",\n \"ARG_OF_PERICENTER\": \"130.5364\", \"MEAN_ANOMALY\": \"329.6482\",\n \"BSTAR\": \"0.0001027\", \"MEAN_MOTION_DOT\": \"0.00016717\",\n \"MEAN_MOTION_DDOT\": \"0\"\n}]\"\"\"\nwith tempfile.NamedTemporaryFile(\"w\", suffix=\".json\", delete=False) as fh:\n fh.write(omm_json)\n json_path = fh.name\n\n# sk.omm_from_file detects JSON vs XML from the content and returns the\n# same list of dicts as sk.omm_from_url. (Plain json.load works too: sgp4\n# accepts the raw dict, quoted numbers and all.)\nomm_list = sk.omm_from_file(json_path)\nos.unlink(json_path)\n\nepoch = sk.time(omm_list[0][\"EPOCH\"])\np_json, v_json = sk.sgp4(omm_list[0], epoch + sk.duration(minutes=30))\nprint(f\"Position from JSON file (m): {p_json}\")\n\n# --- XML text ------------------------------------------------------------\n# The same OMM in CCSDS XML. sk.omm_from_text parses it directly; no XML\n# library or tree-walking needed.\nomm_xml = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ndm><omm id=\"CCSDS_OMM_VERS\" version=\"2.0\">\n <body><segment>\n <metadata>\n <OBJECT_NAME>ISS (ZARYA)</OBJECT_NAME><OBJECT_ID>1998-067A</OBJECT_ID>\n <CENTER_NAME>EARTH</CENTER_NAME><REF_FRAME>TEME</REF_FRAME>\n <TIME_SYSTEM>UTC</TIME_SYSTEM>\n <MEAN_ELEMENT_THEORY>SGP4</MEAN_ELEMENT_THEORY>\n </metadata>\n <data>\n <meanElements>\n <EPOCH>2021-10-02T14:10:59.999</EPOCH>\n <MEAN_MOTION>15.48915330</MEAN_MOTION>\n <ECCENTRICITY>0.0007417</ECCENTRICITY>\n <INCLINATION>51.6432</INCLINATION>\n <RA_OF_ASC_NODE>351.4697</RA_OF_ASC_NODE>\n <ARG_OF_PERICENTER>130.5364</ARG_OF_PERICENTER>\n <MEAN_ANOMALY>329.6482</MEAN_ANOMALY>\n </meanElements>\n <tleParameters>\n <EPHEMERIS_TYPE>0</EPHEMERIS_TYPE>\n <CLASSIFICATION_TYPE>U</CLASSIFICATION_TYPE>\n <NORAD_CAT_ID>25544</NORAD_CAT_ID>\n <ELEMENT_SET_NO>900</ELEMENT_SET_NO>\n <REV_AT_EPOCH>29935</REV_AT_EPOCH>\n <BSTAR>0.0001027</BSTAR>\n <MEAN_MOTION_DOT>0.00016717</MEAN_MOTION_DOT>\n <MEAN_MOTION_DDOT>0</MEAN_MOTION_DDOT>\n </tleParameters>\n </data>\n </segment></body>\n</omm></ndm>\"\"\"\n\nomm_from_xml = sk.omm_from_text(omm_xml)[0]\np_xml, v_xml = sk.sgp4(omm_from_xml, epoch + sk.duration(minutes=30))\nprint(f\"Position from XML text (m): {p_xml}\")\n\n# Same OMM, same epoch -> identical states from both formats\nassert (p_json == p_xml).all() and (v_json == v_xml).all()\nprint(\"JSON and XML routes agree.\")\n\n# --- TLE <-> OMM -----------------------------------------------------------\n# An OMM dict converts to a TLE object and back; the TLE lines are the\n# classic 69-character form of the same element set.\ntle = sk.TLE.from_omm(omm_from_xml)\nprint(\"\\n\".join(tle.to_2line()))\nprint(json.dumps(tle.to_omm(), indent=1)[:200], \"...\")\n",
"metadata": {},
"execution_count": null,
"outputs": []
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ classifiers = [
"Programming Language :: Python :: 3.14",
]
[project.optional-dependencies]
test = ["pytest", "xmltodict", "mypy", "scipy"]
test = ["pytest", "mypy", "scipy"]
# Offline data bundle (JPL ephemeris, full-degree gravity files, IERS tables);
# not required — the core tables are compiled in and the ephemeris is
# downloaded on first use.
Expand Down
1 change: 1 addition & 0 deletions python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ numpy = "0.29"
numeris = { version = "0.5.18", features = ["serde", "ode"] }
anyhow = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-pickle = "1.2.0"
process_path = "0.1.4"

Expand Down
1 change: 1 addition & 0 deletions python/satkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__version__ = version("satkit")

from .satkit import * # type: ignore
from ._types import OMMDict

# The core data (IERS nutation tables, gravity models to degree 70) is
# compiled into the extension, so satkit works with no data directory at all.
Expand Down
4 changes: 4 additions & 0 deletions python/satkit/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .satkit import *
from ._types import OMMDict

from . import jplephem
from . import frametransform
Expand Down Expand Up @@ -50,6 +51,9 @@ __all__ = [
"propresult",
"propstats",
"omm_from_url",
"omm_from_file",
"omm_from_text",
"OMMDict",
"tlefitstatus",
"__version__",
]
Loading