Sign in

    moonsatkit

    Pure MoonBit toolkit for satellite orbit data, pass prediction, and visualization

    satellite
    orbit
    tle
    sgp4
    geodesy
    visualization
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    20

    No dependencies

    #MoonSatKit

    MoonSatKit is a pure MoonBit toolkit for satellite orbit data, TLE parsing, time handling, near-earth SGP4 propagation, ground-station observation, and pass prediction.

    Project status: near-earth propagation phase. The package now includes a pure MoonBit near-earth SGP4 implementation validated against the Vallado 00005 reference state, plus SGP4-based ground-station pass prediction. Deep-space SDP4, full official vector coverage, and export formats remain planned work.

    #Current scope

    • TLE parsing with raw field preservation
    • TLE checksum validation
    • SGP4 input metadata decoding: mean-motion derivatives and BSTAR
    • Pure MoonBit near-earth SGP4 propagation with WGS-72 constants and drag terms
    • Explicit Sgp4Elements initialization boundary with deep-space rejection
    • UTC civil timestamp parsing, formatting, Julian day conversion, and elapsed seconds
    • Leap-year and month-length helpers
    • TLE orbital-element decoding with structured numeric errors
    • Two-body Kepler preview propagation with ECI-style position and velocity output
    • GMST, ECI/ECEF position and state transforms with Earth rotation correction
    • WGS-84 geodetic and ground-station coordinate conversion
    • Ground-station ENU observation with azimuth, elevation, range, range rate, and horizon status
    • Two-body preview pass prediction with AOS, TCA, LOS, and maximum elevation
    • SGP4 pass prediction with one-second event refinement and ordered visibility windows
    • JSON serialization for orbit states and pass windows
    • Low-precision geocentric Sun position and right-ascension/declination output
    • Geometric sunlit, penumbra, and umbra classification for propagated states
    • CSV pass-window export and GeoJSON SGP4 ground-track export
    • Named two-line/three-line TLE catalog parsing and multi-satellite pass prediction
    • Pass duration reporting and browser catalog-pass JSON API
    • Vector, station, observation, and pass data types
    • CLI entry points for TLE validation and timestamp parsing

    #Quick start

    Parse a timestamp:

    moon run cmd/main -- time parse 2026-08-11T12:00:00

    Validate a TLE:

    moon run cmd/main -- tle validate "1 25544U 98067A 26224.50000000 .00001234 00000-0 12345-4 0 9991" "2 25544 51.6416 21.0000 0001234 123.4567 234.5678 15.50000000"

    Propagate a TLE at a UTC instant:

    moon run cmd/main -- propagate "<line1>" "<line2>" --at 2026-08-23T12:00:00

    Predict SGP4 passes for a ground station:

    moon run cmd/main -- passes "<line1>" "<line2>" --station 31.2304,121.4737,10 --from 2026-08-23T12:00:00 --hours 24 --min-elevation 10 --format json

    Export a GeoJSON ground track:

    moon run cmd/main -- track "<line1>" "<line2>" --from 2026-08-23T12:00:00 --seconds 86400 --step 60

    Classify satellite illumination and print a machine-readable report:

    moon run cmd/main -- eclipse "<line1>" "<line2>" --at 2026-08-23T12:00:00

    Predict pass windows from a validated TLE:

    let tle = @moonsatkit.validate_tle(line1, line2).unwrap() let station : @moonsatkit.GroundStation = { latitude_deg: 31.2304, longitude_deg: 121.4737, altitude_m: 10.0, } let passes = @moonsatkit.predict_passes_sgp4( tle, station, from=start, duration_hours=24, minimum_elevation_deg=10.0, ).unwrap()

    predict_passes remains the transparent two-body compatibility API.

    Parse a named TLE catalog and predict all satellite passes:

    let catalog = @moonsatkit.parse_tle_catalog(catalog_text).unwrap() let catalog_passes = @moonsatkit.predict_catalog_passes_sgp4( catalog, station, from=start, duration_hours=24, minimum_elevation_deg=10.0, ).unwrap() let json = @moonsatkit.catalog_pass_windows_to_json(catalog_passes)
    predict_passes_sgp4 uses the near-earth SGP4 propagator and returns integer-second AOS, TCA, and LOS timestamps plus the maximum elevation. Deep-space TLEs currently return UnsupportedOrbit until SDP4 is added.

    Export results:

    let json = @moonsatkit.pass_windows_to_json(passes) let csv = @moonsatkit.pass_windows_to_csv(passes) let track = @moonsatkit.track_geojson_sgp4(tle, start, 86400, 60).unwrap()

    #Acceptance scope

    The current acceptance baseline covers TLE checksum validation, near-earth SGP4 propagation, WGS-84 coordinate conversion, ground-station observation, one-second-refined pass windows, JSON/CSV/GeoJSON export, and geometric illumination classification. The reproducible checklist is in docs/ACCEPTANCE.md.

    #Roadmap

    • Deep-space SDP4 propagation and broader official SGP4 vector coverage
    • Higher-fidelity TEME, Earth orientation, and time-scale refinements
    • Multi-satellite catalogs, Doppler estimates, and constellation coverage

    #Development

    moon info moon fmt moon test -p oilleelssq-wq/moonsatkit --target all --deny-warn

    #License

    MoonSatKit is licensed under the Apache License 2.0.

    #Browser preview

    The repository includes a lightweight browser console at web/index.html. It renders a real MoonSatKit-generated GeoJSON track and SGP4 pass-window sample for Vallado catalog object 00005, with a timeline scrubber, TLE checksum validation, station marker, pass-elevation filter, GeoJSON import, and track download.

    Start a local preview from the repository root:

    cd web python -m http.server 4173

    Then open http://127.0.0.1:4173/. The page loads the self-contained MoonBit JS adapter at web/moonsatkit.js, which exposes TLE validation, SGP4 GeoJSON track generation, single-satellite pass-window prediction, and named catalog pass prediction through moon_sat_catalog_passes_json. Edit the TLE, station, start time, duration, or sample step and press 计算轨迹 to run the real near-earth SGP4 implementation in the browser. The bundled Vallado 00005 data remains available as the initial view and fallback demonstration when the adapter cannot be loaded. Rebuild the adapter after MoonBit changes with ./web/build.ps1 (or run moon build web/wasm --target js --release and copy _build/js/release/build/web/wasm/wasm.js to web/moonsatkit.js).

    CatalogPassWindow

    pub(all) struct CatalogPassWindow {
    name : String
    catalog_number : Int
    window : PassWindow
    } derive(Eq,
    Debug
    )

    A pass window associated with one entry in a TLE catalog.

    EcefState

    pub(all) struct EcefState {
    epoch : UtcDateTime
    position_km : Vector3
    velocity_km_s : Vector3
    }

    A state expressed in the Earth-fixed frame, in kilometres and kilometres per second.

    EclipseState

    pub(all) enum EclipseState {
    Sunlit
    Penumbra
    Umbra
    } derive(Eq,
    Debug
    )

    A geometric illumination classification for a satellite state.

    Penumbra means the satellite is inside the Earth's partial shadow. The model is intentionally geometric and is suitable for visibility analysis, not for spacecraft power or mission-safety decisions.

    EclipseState::to_string

    fn EclipseState::to_string(self : EclipseState) -> String

    GeodeticPosition

    pub(all) struct GeodeticPosition {
    latitude_deg : Double
    longitude_deg : Double
    altitude_km : Double
    }

    A WGS-84 geodetic position. Longitude is positive east and altitude is in kilometres.

    GroundStation

    pub(all) struct GroundStation {
    latitude_deg : Double
    longitude_deg : Double
    altitude_m : Double
    } derive(Eq,
    Debug
    )

    Observation

    pub(all) struct Observation {
    azimuth_deg : Double
    elevation_deg : Double
    range_km : Double
    range_rate_km_s : Double
    above_horizon : Bool
    } derive(Eq,
    Debug
    )

    OrbitElements

    pub(all) struct OrbitElements {
    catalog_number : Int
    epoch : UtcDateTime
    epoch_julian_day : Double
    inclination_deg : Double
    right_ascension_deg : Double
    eccentricity : Double
    argument_of_perigee_deg : Double
    mean_anomaly_deg : Double
    mean_motion_rev_per_day : Double
    mean_motion_rad_per_min : Double
    }

    Numerical orbital elements decoded from a TLE.

    The elements retain the TLE epoch and angular fields in conventional units. They are suitable for the two-body preview propagator and form the input boundary for the future SGP4 implementation.

    OrbitState

    pub(all) struct OrbitState {
    epoch : UtcDateTime
    position_km : Vector3
    velocity_km_s : Vector3
    }

    An inertial state expressed in kilometres and kilometres per second.

    PassWindow

    pub(all) struct PassWindow {
    aos : UtcDateTime
    tca : UtcDateTime
    los : UtcDateTime
    maximum_elevation_deg : Double
    } derive(Eq,
    Debug
    )

    PassWindow::duration_seconds

    fn PassWindow::duration_seconds(self : PassWindow) -> Int

    Return the visible duration of a pass in integral seconds.

    SatError

    pub(all) struct SatError {
    kind : SatErrorKind
    line : Int
    fragment : String
    message : String
    } derive(Eq,
    Debug
    )

    SatError::new

    fn SatError::new(kind : SatErrorKind, message : String, fragment? : String, line? : Int) -> SatError

    SatError::to_string

    fn SatError::to_string(self : SatError) -> String

    SatErrorKind

    pub(all) enum SatErrorKind {
    EmptyInput
    InvalidFormat
    InvalidChecksum
    InvalidCatalogNumber
    MismatchedCatalogNumber
    InvalidTleNumber
    UnsupportedOrbit
    PropagationError
    InvalidDateTime
    OutOfRange
    } derive(Eq,
    Debug
    )

    Sgp4Elements

    pub(all) struct Sgp4Elements {
    catalog_number : Int
    epoch : UtcDateTime
    epoch_julian_day : Double
    inclination_rad : Double
    right_ascension_rad : Double
    eccentricity : Double
    argument_of_perigee_rad : Double
    mean_anomaly_rad : Double
    mean_motion_rad_per_min : Double
    mean_motion_first_derivative_rev_per_day2 : Double
    mean_motion_second_derivative_div2_rev_per_day3 : Double
    bstar : Double
    }

    Numerical TLE fields required to initialize an SGP4 propagator.

    Angles are stored in radians. The derivative fields retain the TLE conventions: revolutions per day squared and the second derivative divided by two in revolutions per day cubed. BSTAR is dimensionless.

    SunPosition

    pub(all) struct SunPosition {
    position_km : Vector3
    distance_km : Double
    distance_au : Double
    right_ascension_deg : Double
    declination_deg : Double
    } derive(Eq,
    Debug
    )

    Low-precision geocentric Sun position for visibility and eclipse analysis.

    The position is expressed in the same J2000-style inertial frame used by the library's ECI helpers. Distance is in kilometres, right ascension and declination are in degrees, and distance_au is retained for diagnostics.

    Tle

    pub(all) struct Tle {
    line1 : String
    line2 : String
    catalog_number : Int
    epoch : String
    inclination : String
    right_ascension : String
    eccentricity : String
    argument_of_perigee : String
    mean_anomaly : String
    mean_motion : String
    mean_motion_first_derivative : String
    mean_motion_second_derivative : String
    bstar : String
    checksum_line1_ok : Bool
    checksum_line2_ok : Bool
    } derive(Eq,
    Debug
    )

    TleCatalogEntry

    pub(all) struct TleCatalogEntry {
    name : String
    tle : Tle
    } derive(Eq,
    Debug
    )

    A named TLE entry loaded from a two-line or three-line catalog.

    UtcDateTime

    pub(all) struct UtcDateTime {
    year : Int
    month : Int
    day : Int
    hour : Int
    minute : Int
    second : Int
    } derive(Compare, Eq,
    Debug
    )

    UtcDateTime::add_seconds

    fn UtcDateTime::add_seconds(self : UtcDateTime, seconds : Int) -> UtcDateTime

    Add an integral number of seconds to a UTC civil timestamp.

    This helper is intentionally calendar-based, so pass-search event times remain integer-second UTC values without relying on a platform date API.

    UtcDateTime::next_second

    fn UtcDateTime::next_second(self : UtcDateTime) -> UtcDateTime

    UtcDateTime::to_string

    fn UtcDateTime::to_string(self : UtcDateTime) -> String

    Vector3

    pub(all) struct Vector3 {
    x : Double
    y : Double
    z : Double
    } derive(Eq,
    Debug
    )

    Vector3::add

    fn Vector3::add(self : Vector3, other : Vector3) -> Vector3

    Vector3::dot

    fn Vector3::dot(self : Vector3, other : Vector3) -> Double

    Vector3::scale

    fn Vector3::scale(self : Vector3, factor : Double) -> Vector3

    Vector3::sub

    fn Vector3::sub(self : Vector3, other : Vector3) -> Vector3

    Vector3::to_string

    fn Vector3::to_string(self : Vector3) -> String

    Vector3::zero

    fn Vector3::zero() -> Vector3

    catalog_pass_windows_to_csv

    fn catalog_pass_windows_to_csv(passes : Array[CatalogPassWindow]) -> String

    Serialize catalog pass windows as CSV.

    catalog_pass_windows_to_json

    fn catalog_pass_windows_to_json(passes : Array[CatalogPassWindow]) -> String

    Serialize catalog pass windows with the satellite name and NORAD number.

    days_in_month

    fn days_in_month(year : Int, month : Int) -> Int

    ecef_to_eci

    fn ecef_to_eci(position : Vector3, instant : UtcDateTime) -> Vector3

    Rotate an Earth-fixed position into the inertial frame at the given instant.

    ecef_to_eci_state

    fn ecef_to_eci_state(state : EcefState) -> OrbitState

    Convert an Earth-fixed state to an inertial state, including Earth rotation.

    ecef_to_geodetic

    fn ecef_to_geodetic(position : Vector3) -> GeodeticPosition

    Convert Earth-fixed Cartesian coordinates to WGS-84 geodetic coordinates.

    eci_to_ecef

    fn eci_to_ecef(position : Vector3, instant : UtcDateTime) -> Vector3

    Rotate an ECI position into the Earth-fixed frame at the given instant.

    eci_to_ecef_state

    fn eci_to_ecef_state(state : OrbitState) -> EcefState

    Convert an inertial state to an Earth-fixed state, including Earth rotation.

    eclipse_report_to_json

    fn eclipse_report_to_json(tle : Tle, instant : UtcDateTime) -> Result[String, SatError]

    Serialize a propagated state together with its illumination status.

    eclipse_state

    fn eclipse_state(state : OrbitState) -> EclipseState

    Classify whether a satellite is sunlit, in the penumbra, or in the umbra.

    The Earth is modelled as a sphere with WGS-84's equatorial radius. The solar umbra and penumbra are treated as cones whose apexes are determined by the apparent Sun and Earth radii.

    eclipse_state_sgp4

    fn eclipse_state_sgp4(tle : Tle, instant : UtcDateTime) -> Result[EclipseState, SatError]

    Propagate a TLE and classify its illumination at the requested instant.

    geodetic_to_ecef

    fn geodetic_to_ecef(value : GeodeticPosition) -> Vector3

    Convert a WGS-84 geodetic position to Earth-fixed Cartesian coordinates.

    gmst_angle

    fn gmst_angle(julian_day_value : Double) -> Double

    Compute Greenwich mean sidereal time from an astronomical Julian Day.

    The result is a normalized angle in radians in the range [0, 2*pi).

    greenwich_mean_sidereal_time

    fn greenwich_mean_sidereal_time(value : UtcDateTime) -> Double

    Compute Greenwich mean sidereal time for a UTC timestamp.

    ground_station_to_ecef

    fn ground_station_to_ecef(station : GroundStation) -> Vector3

    Convert a ground station height in metres to Earth-fixed Cartesian coordinates.

    is_leap_year

    fn is_leap_year(year : Int) -> Bool

    julian_day

    fn julian_day(value : UtcDateTime) -> Double

    Convert a UTC civil timestamp to the astronomical Julian Day.

    The result follows the standard convention where 2000-01-01 12:00:00 UTC is 2451545.0. This helper uses the proleptic Gregorian calendar.

    observe

    fn observe(state : OrbitState, station : GroundStation) -> Observation

    Compute an observation from an inertial state by applying the ECI/ECEF transformation at the state's epoch first.

    observe_ecef

    fn observe_ecef(state : EcefState, station : GroundStation) -> Observation

    Compute the local ENU observation of an Earth-fixed satellite state.

    Azimuth is measured clockwise from north in degrees. Elevation is measured above the local horizon. Range and range rate use kilometres and kilometres per second, respectively.

    orbit_elements

    fn orbit_elements(tle : Tle) -> Result[OrbitElements, SatError]

    Decode the numerical orbital elements represented by a parsed TLE.

    orbit_state_geodetic

    fn orbit_state_geodetic(state : OrbitState) -> GeodeticPosition

    Convert an inertial state position to WGS-84 geodetic coordinates.

    orbit_state_to_json

    fn orbit_state_to_json(state : OrbitState) -> String

    Serialize an inertial orbit state as a small JSON object.

    parse_tle

    fn parse_tle(line1 : String, line2 : String) -> Result[Tle, SatError]

    Parse a two-line element set and preserve the raw orbital fields.

    parse_tle_catalog

    fn parse_tle_catalog(text : String) -> Result[Array[TleCatalogEntry], SatError]

    Parse a catalog containing either repeated two-line TLE records or the common three-line format with a satellite name before each pair.

    Blank lines are ignored. Named records keep their display name; unnamed records receive a NORAD name.

    parse_utc_datetime

    fn parse_utc_datetime(text : String) -> Result[UtcDateTime, SatError]

    pass_windows_to_csv

    fn pass_windows_to_csv(passes : Array[PassWindow]) -> String

    Serialize pass windows as a UTF-8 CSV document with a header row.

    pass_windows_to_json

    fn pass_windows_to_json(passes : Array[PassWindow]) -> String

    Serialize pass windows as a JSON array.

    predict_catalog_passes_sgp4

    fn predict_catalog_passes_sgp4(entries : Array[TleCatalogEntry], station : GroundStation, from : UtcDateTime, duration_hours : Int, minimum_elevation_deg : Double) -> Result[Array[CatalogPassWindow], SatError]

    Predict SGP4 pass windows for every entry in a named TLE catalog.

    predict_passes

    fn predict_passes(tle : Tle, station : GroundStation, from : UtcDateTime, duration_hours : Int, minimum_elevation_deg : Double) -> Result[Array[PassWindow], SatError]

    Predict visibility windows using the current two-body preview propagator.

    The returned event times have one-second resolution. AOS and LOS are refined by elevation-threshold bisection, while TCA is selected by a one-second search around the sampled elevation maximum.

    predict_passes_from_elements

    fn predict_passes_from_elements(elements : OrbitElements, station : GroundStation, from : UtcDateTime, duration_hours : Int, minimum_elevation_deg : Double) -> Result[Array[PassWindow], SatError]

    Predict visibility windows from already-decoded orbital elements.

    predict_passes_sgp4

    fn predict_passes_sgp4(tle : Tle, station : GroundStation, from : UtcDateTime, duration_hours : Int, minimum_elevation_deg : Double) -> Result[Array[PassWindow], SatError]

    Predict visibility windows with the near-earth SGP4 propagator.

    This API is the high-fidelity counterpart to predict_passes, which keeps its transparent two-body preview behavior for compatibility. A deep-space TLE returns UnsupportedOrbit because SDP4 is not implemented yet.

    propagate_sgp4

    fn propagate_sgp4(tle : Tle, instant : UtcDateTime) -> Result[OrbitState, SatError]

    Propagate a near-earth TLE with the Vallado SGP4 model.

    The returned state uses kilometres and kilometres per second. Deep-space orbits return UnsupportedOrbit until SDP4 is added.

    propagate_two_body

    fn propagate_two_body(elements : OrbitElements, instant : UtcDateTime) -> Result[OrbitState, SatError]

    Propagate an orbit with a two-body Kepler approximation.

    This is an intentionally transparent preview model. It does not include the perturbations and Earth orientation terms required for SGP4 accuracy.

    seconds_between

    fn seconds_between(start : UtcDateTime, end : UtcDateTime) -> Double

    Return the elapsed seconds from start to end.

    sgp4_elements

    fn sgp4_elements(tle : Tle) -> Result[Sgp4Elements, SatError]

    Decode the complete numerical TLE input boundary for a future SGP4 propagator. This function does not propagate a state.

    sun_position_eci

    fn sun_position_eci(instant : UtcDateTime) -> SunPosition

    Compute a low-precision geocentric Sun position.

    This follows the compact NOAA-style solar position equations. It is accurate enough for eclipse classification and educational visualisation; high-precision ephemerides remain outside the current scope.

    track_geojson_sgp4

    fn track_geojson_sgp4(tle : Tle, from : UtcDateTime, duration_seconds : Int, step_seconds : Int) -> Result[String, SatError]

    Export a near-earth SGP4 ground track as a GeoJSON LineString.

    Coordinates use the GeoJSON order [longitude, latitude, altitude_km]. The interval is sampled inclusively, so a zero-duration track contains one coordinate. Deep-space TLEs return UnsupportedOrbit.

    validate_tle

    fn validate_tle(line1 : String, line2 : String) -> Result[Tle, SatError]

    Parse and require both TLE checksum digits to be valid.

    vallado_00005_line1

    let vallado_00005_line1 : String

    Public SGP4 verification sample distributed with Vallado's CelesTrak test material. MoonSatKit currently uses it for TLE parsing and checksum tests; propagation checks can reuse the same fixture once SGP4 state vectors land.

    vallado_00005_line2

    let vallado_00005_line2 : String