Satellite pass prediction and contact window computation
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

OCaml 87.9%
Dune 3.9%
Other 8.1%
21 1 0

Clone this repository

https://tangled.org/gazagnaire.org/ocaml-contact https://tangled.org/did:plc:jhift2vwcxhou52p3sewcrpx/ocaml-contact
git@git.recoil.org:gazagnaire.org/ocaml-contact git@git.recoil.org:did:plc:jhift2vwcxhou52p3sewcrpx/ocaml-contact

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

contact#

Satellite pass prediction and contact window computation.

contact computes when a satellite is visible from a ground station: acquisition of signal (AOS), loss of signal (LOS), maximum elevation, and pass duration. Orbit propagation is delegated to sgp4 (SGP4 from the NORAD General Perturbations models) and coordinate transforms to coordinate.

Installation#

Install with opam:

$ opam install contact

If opam cannot find the package, it may not yet be released in the public opam-repository. Add the overlay repository, then install it:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install contact

Usage#

Parse a Two-Line Element set, define a ground station, and predict all visible passes over a time window. The TLE below is an idealised ISS element set with epoch 2026-01-01; passes are computed from that epoch:

# let tle_string =
    "ISS (ZARYA)\n\
     1 25544U 98067A   26001.00000000  .00001764  00000+0  39538-4 0  9991\n\
     2 25544  51.6416  45.0000 0008003  90.0000   0.0000 15.49560000    15" in
  let tle = Result.get_ok (Sgp4.parse_tle_string tle_string) in
  let la = Contact.ground_station ~lat:34.05 ~lon:(-118.25) ~alt:0.071 in
  let passes = Contact.predict tle la ~duration_days:3.0 in
  List.length passes
- : int = 15

Each pass carries AOS/LOS Unix timestamps, ISO-8601 strings, the peak elevation in degrees, and duration in seconds:

# let tle_string =
    "ISS (ZARYA)\n\
     1 25544U 98067A   26001.00000000  .00001764  00000+0  39538-4 0  9991\n\
     2 25544  51.6416  45.0000 0008003  90.0000   0.0000 15.49560000    15" in
  let tle = Result.get_ok (Sgp4.parse_tle_string tle_string) in
  let la = Contact.ground_station ~lat:34.05 ~lon:(-118.25) ~alt:0.071 in
  match Contact.predict tle la ~duration_days:3.0 with
  | { max_elevation; duration; _ } :: _ ->
      Fmt.str "first pass: %.1f deg peak, %.0fs long"
        max_elevation duration
  | [] -> "no passes"
- : string = "first pass: 61.6 deg peak, 630s long"

Contact.elevation computes a point-in-time elevation angle instead of scanning for full pass windows:

# let tle_string =
    "ISS (ZARYA)\n\
     1 25544U 98067A   26001.00000000  .00001764  00000+0  39538-4 0  9991\n\
     2 25544  51.6416  45.0000 0008003  90.0000   0.0000 15.49560000    15" in
  let tle = Result.get_ok (Sgp4.parse_tle_string tle_string) in
  let la = Contact.ground_station ~lat:34.05 ~lon:(-118.25) ~alt:0.071 in
  let epoch_unix = Sgp4.epoch_unix tle in
  match Contact.elevation tle la epoch_unix with
  | Some el -> Fmt.str "%.2f" el
  | None -> "N/A"
- : string = "-43.84"

A negative elevation means the satellite is below the horizon; pass prediction filters these out using min_elevation (default 5°).

Licence#

ISC