Skip to content
 
 

Repository files navigation

Visible Ephemeris

High-Performance Satellite Tracking Appliance (C++17 & Python)

Visible Ephemeris is a modern, spiritual successor to Quiktrak (1986), re-engineered for the Raspberry Pi 5 and modern silicon. It is capable of propagating 13,000+ satellites in real-time with sub-second updates while maintaining <5% CPU utilization.

It features a Hybrid Decoupled Architecture where the UI, Orbital Mechanics, and Network Services run on independent threads, ensuring the interface never freezes—even during heavy calculation loads.

Both C++ and Python implementations are provided with identical functionality.


Core Features

Tracking Engine

  • SGP4/SDP4 Propagation: Uses libsgp4 (C++) or Skyfield (Python) for high-precision orbital math.
  • High-Precision Orbit Propagator (HPOP): Optional --hpop mode that numerically integrates a full force model — EGM96 gravity (degree/order 20), Sun/Moon third-body, atmospheric drag, and solar radiation pressure — with an adaptive Fehlberg RK7(8) integrator, seeded from the TLE state vector at epoch. Available in both C++ and Python (via the ve_hpop pybind11 module). See High-Precision Orbit Propagator.
  • Massive Scale: Tracks the entire NORAD Active Catalog (13,000+ objects) simultaneously.
  • Smart Caching: Automatic TLE downloading and caching from Celestrak with 24-hour auto-refresh cycle. Historical TLEs (tle_cache/historical/YYYY-MM-DD/) are cached permanently since archived elements never change.
  • Multi-Group Selection: Track specific combinations (e.g., amateur,weather,stations) using the group_selection config or --groupsel argument.
  • Stability: Implements "Pre-calculate All" logic at startup to ensure 24-hour pass predictions are instantly available, eliminating "Calculating..." flicker and UI jitter.
  • Decoupled Clock: Simulation time input is treated as "Face Value" (Local Wall-Clock Time) for display, while strictly adhering to UTC for orbital physics, eliminating timezone confusion.
  • Historical Playback: Run the tracker at any past UTC date. For dates >24 h in the past, TLEs valid on that date are pulled from Space-Track.org's gp_history archive, cached permanently, and propagated exactly as the live tracker does. Full Iridium-NEXT constellation coverage from its 2017–2019 launches. See Historical Tracking.

Display Systems

  • NCurses Terminal Dashboard (C++):
    • Flicker-free, color-coded real-time data table.
    • Flare Detection: Identifies specular reflections from LEO satellites (flashing 'F' indicator).
    • Horizon Flash: Visual indicator (Red/White flashing) when a satellite is rising or setting (within 1° of horizon).
  • Console Output (Python):
    • Real-time updating satellite table with visibility status.
  • Web Dashboard (Port 8080):
    • Mercator Map: Live ground tracks, observer location marker, solar terminator, and satellite footprint visualization.
    • Polar Skyplot: Radar view of visible satellites with pulsing selection aura.
    • Smart Trails: Displays ground track history and future path.
    • Sortable Table: Click column headers to sort by Name, Azimuth, Elevation, etc.
  • Web Terminal Mirror (Port 12345):
    • Ultra-lightweight HTML mirror of the terminal output.
    • Uses HTTP/1.0 "Fire-and-Forget" protocol to prevent browser hanging on slow connections.

Operating Modes

Radio Mode (visible_only: false)

Shows ALL satellites in the selected group(s), color-coded by elevation and visibility:

Color Condition Description
Yellow Above min_el AND Visible Naked-eye visible: above the horizon, observer in astronomical twilight or darker (sun ≤ −12°), and satellite sunlit (not in Earth's shadow)
Green Above min_el AND NOT Visible Above minimum elevation but not naked-eye visible (daylight/twilight, or satellite eclipsed) - good for radio
Grey Below min_el OR Below Horizon Satellite is low or not yet risen - displayed for situational awareness

This mode displays every satellite in the group on the map and in tables, limited only by max_sats.

Naked-eye visibility definition

A satellite is reported Visible (yellow) when all of the following hold:

  1. It is above the observer's horizon (elevation > 0°).
  2. The observer is in astronomical twilight or darker — the Sun is at or below −12° altitude.
  3. The satellite is sunlit — not inside Earth's shadow.

Otherwise a sunlit satellite is reported as daylight/not-visible, and a shadowed one as eclipsed. This definition is identical in the C++ and Python implementations.

Optical Mode (visible_only: true)

Shows only satellites that meet the naked-eye visibility definition above (and are above min_el). This optional mode is for visual observers who only want satellites they can actually spot. With the default visible_only: false, every satellite above min_el is displayed and color coding alone distinguishes visibility.

Hardware Control

  • Radio Control: Automated Hamlib control for Transceiver Frequency/Mode (Doppler correction). Requires single satellite selection.
  • Rotator Control: Automated Hamlib control for Azimuth/Elevation tracking. Requires single satellite selection.

Installation

C++ Version (Primary)

We provide an automated build script build.sh that handles dependencies (including building libsgp4 from source) and installation.

cd VisibleEphemeris
chmod +x build.sh
./build.sh

Note: The script utilizes sudo to install dependencies and the final binary.

Manual Build

Dependencies (Ubuntu/Debian): cmake (≥ 3.14), a C++17 compiler (clang or g++), libncurses-dev, libcurl4-openssl-dev, and pkg-config. The SGP4 propagation library libsgp4 must also be present — build.sh builds it from dnwrnr/sgp4. libhamlib-dev is optional and enables radio/rotator control.

sudo apt install cmake clang libncurses-dev libcurl4-openssl-dev pkg-config libhamlib-dev

mkdir build && cd build
cmake ..                 # auto-selects clang if present, else GCC; add -DENABLE_HAMLIB=OFF to skip Hamlib
make -j$(nproc)

Optional CMake switches:

Option Default Effect
-DENABLE_HAMLIB=OFF ON Skip Hamlib radio/rotator support
-DBUILD_PYTHON_BINDINGS=ON OFF Build the ve_hpop pybind11 module (see HPOP)
-DBUILD_TESTS=ON OFF Build the unit tests; run with ctest

If libsgp4 is installed to a non-standard prefix (e.g. ~/sgp4/build/install, where build.sh puts it), put it on the library path at runtime:

LD_LIBRARY_PATH=~/sgp4/build/install/lib ./VisibleEphemeris

Python Version

The Python tracker is located in python_tracker/ and provides identical functionality.

Prerequisites:

  • Python 3.10+
  • Linux Environment (required for termios/tty interactive input support)

Installation:

cd python_tracker

# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install dependencies
pip install -r requirements.txt

Running:

source .venv/bin/activate  # If not already activated
python3 main.py

Configuration

Settings are stored in config.yaml and automatically saved on exit. Both C++ and Python versions use the same configuration format.

Configuration File (config.yaml)

lat: 39.6478              # Observer Latitude (decimal degrees)
lon: -76.1347             # Observer Longitude (decimal degrees)
alt: 0.075                # Observer Altitude (km)
max_sats: 100             # Maximum satellites to display
min_el: 0                 # Minimum elevation filter (degrees)
max_apo: -1               # Maximum apogee filter, -1 = disabled
trail_length_mins: 5      # Ground track trail length (+/- minutes)
group_selection: iridium-NEXT   # Celestrak group(s), comma-separated
sat_selection: ""         # Specific satellite names (overrides group_selection). e.g. "ISS,NOAA 19"
visible_only: false       # false = Radio Mode (all sats), true = Optical Mode
delta_t: 1.0              # Update interval in seconds (0.001-60)
radio_control: false      # Enable Hamlib radio control (Doppler)
rotator_control: false    # Enable Hamlib rotator control
rotator_host: localhost   # Rotator daemon host
rotator_port: 4533        # Rotator daemon port
rotator_min_el: 0         # Minimum elevation for rotator tracking

Note: Space-Track credentials are not stored in config.yaml. They live outside the repo in environment variables or ~/.config/visible-ephemeris/spacetrack.ini — see Space-Track credentials.

Command Line Arguments

Both C++ and Python accept the same flag names unless noted.

Argument Description Default
--lat <deg> Observer Latitude (Decimal Degrees) from config
--lon <deg> Observer Longitude (Decimal Degrees) from config
--alt <km> Observer Altitude (km) from config
--groupsel <list> Comma-separated Celestrak groups (e.g., amateur,weather) active
--satsel <list> Comma-separated satellite names (substring match); overrides --groupsel from config
--visible Optical Mode: show only sunlit satellites from config
--no-visible Radio Mode: show ALL satellites (color-coded by visibility) -
--minel <deg> Minimum elevation filter 0.0
--maxsats <N> Maximum satellites to display 100
--maxapo <km> Filter satellites with apogee > N km -1 (disabled)
--trail_mins <N> Ground track trail length (+/- minutes) 5
--radio <bool> Enable Hamlib radio control (C++ only, requires single --satsel) false
--rotator <bool> Enable Hamlib rotator control (C++ only, requires single --satsel) false
--refresh Force fresh download of TLE data (C++ only) false
--time <str> Simulate time in UTC ("YYYY-MM-DD HH:MM:SS"). Past dates >24 h ago trigger historical TLE retrieval from Space-Track — see Historical Tracking Real-time
--deltaT <sec> Update interval in seconds (0.001-60) 1.0
--hpop Use the High-Precision Orbit Propagator instead of SGP4 (see below) off
--hpop-degree <N> HPOP geopotential degree/order (1-20) 20
--no-drag HPOP: disable atmospheric drag drag on
--no-srp HPOP: disable solar radiation pressure srp on
--no-thirdbody HPOP: disable Sun/Moon third-body perturbations on
--port <A,B,C> Override network ports (web,text,physics) — C++ only 8080,12345,12346
--groupbuild Enter Mission Planner builder mode (C++ only) -

High-Precision Orbit Propagator (HPOP)

By default the tracker propagates orbits analytically with SGP4. The --hpop option instead numerically integrates a full force model, which is more accurate than SGP4's truncated secular/periodic theory — especially over multi- hour to multi-day arcs and for non-spherical-gravity-sensitive orbits.

How the initial state is obtained

A TLE does not contain an osculating state vector — lines 1–2 hold SGP4 mean elements. HPOP therefore converts the TLE to a state vector the only correct way: it evaluates SGP4 at the element-set epoch to produce an osculating ECI (TEME) position/velocity (r₀, v₀), and uses that as the initial condition for numerical integration. The integration frame is the same TEME-as-pseudo-inertial frame SGP4 produces, so all downstream geometry (look angles, ground track, visibility) is unchanged.

Force model

Perturbation Model
Earth gravity EGM96 spherical harmonics to degree/order 20 (Cunningham/Pines recursion), evaluated Earth-fixed via GMST. Coefficients embedded in the binary.
Third body Sun and Moon point-mass (Montenbruck-Gill analytic ephemerides)
Atmospheric drag Piecewise-exponential density (Vallado); co-rotating atmosphere. Ballistic coefficient Cd·A/m = 2·B*/0.15696615 derived from the TLE B* term
Solar radiation pressure 4.56e-6 N/m² at 1 AU, cylindrical Earth-shadow; Cr·A/m shared from the drag area

Integration uses an adaptive Fehlberg RK7(8) scheme (8th-order solution, embedded 7th-order error control). Verification: HPOP at epoch reproduces the SGP4 seed exactly; two-body semi-major axis is conserved to < 1 mm over a day.

HPOP is heavier than SGP4. It is best for a focused set of satellites (--satsel) rather than the full 13,000-object catalog.

Running

# C++ — track the ISS with the full high-precision model
LD_LIBRARY_PATH=~/sgp4/build/install/lib ./VisibleEphemeris --satsel ISS --hpop

# Geopotential to degree 10, gravity + Sun/Moon only (no drag/SRP)
./VisibleEphemeris --satsel ISS --hpop --hpop-degree 10 --no-drag --no-srp

# Python tracker
python3 main.py --satsel ISS --hpop

Python module (pybind11)

The same C++ propagator is exposed to Python as the ve_hpop module. Build it with the BUILD_PYTHON_BINDINGS CMake option (pybind11 is located via find_package/pip, or fetched from GitHub if absent):

cd build
cmake .. -DBUILD_PYTHON_BINDINGS=ON -DPython_EXECUTABLE=$(which python3)
cmake --build . --target ve_hpop      # -> ve_hpop.<abi>.so

The Python tracker auto-discovers the module in ../build; you can also add the build directory to PYTHONPATH. Usage:

import ve_hpop
p = ve_hpop.Propagator(name, line1, line2, degree=20,
                       drag=True, srp=True, thirdbody=True)  # optional mass_kg/area/Cd/Cr
r, v = p.propagate_jd(jd)             # ECI/TEME position (km) & velocity (km/s)
r, v = p.propagate(datetime_utc)      # also accepts a Python datetime
lat, lon, alt = p.geodetic_jd(jd)     # WGS84 sub-satellite point
print(p.epoch_jd, p.cd_area_over_m, p.drag_enabled)
# module helpers: ve_hpop.sun_position_eci(jd), moon_position_eci(jd),
#                 atmosphere_density(alt_km), gmst_rad(jd)

Example: HPOP vs SGP4 over a day

examples/compare_iss.cpp propagates the ISS for a full day with both HPOP and SGP4 and prints the position difference decomposed into the orbital RSW frame (radial / along-track / cross-track):

cd build
cmake .. -DBUILD_EXAMPLES=ON && cmake --build . --target compare_iss
LD_LIBRARY_PATH=~/sgp4/build/install/lib ./compare_iss

The separation is essentially all along-track (the orbits stay on the same plane and shape but drift in phase), growing to ~10 km after 24 h - the expected signature of a full numerical force model diverging from SGP4's analytic theory. This is a model-difference demonstration, not an accuracy verdict: HPOP is seeded from SGP4's mean elements at epoch, so neither result is ground truth.


Historical Tracking (Past Dates)

When --time specifies a UTC date more than 24 hours in the past, the tracker fetches the TLEs that were current on that date from Space-Track.org (endpoint gp_history), caches them under tle_cache/historical/<YYYY-MM-DD>/, and propagates from there. This avoids the multi-kilometer SGP4 error that would result from propagating today's elements backward years.

Coverage is authoritative for the full USSPACECOM catalog back to 1957, including the entire Iridium-NEXT constellation (NORAD 41917–43478) from its 2017–2019 launches.

Space-Track credentials

Historical TLE retrieval uses Space-Track.org, the authoritative public archive operated by the U.S. 18th Space Defense Squadron. An account is free but required; it is only needed when --time selects a date more than 24 hours from wall-clock now. Real-time and near-real-time operation continue to use Celestrak and require no account.

Step 1 — Register for a Space-Track account

Open https://www.space-track.org/auth/createAccount in your browser and fill out the registration form. The following fields are required:

Field Notes
Email address Must be valid and active — a verification email is sent here, and it doubles as your login username.
Organization and interests Describe who you are and why you want the data (e.g., "Amateur radio operator; historical satellite visibility analysis with open-source Visible Ephemeris tracker"). A brief, honest description is enough.
Name (first / middle / last, optional prefix/suffix) "Special characters and numerals are not allowed in names (Dashes, periods, spaces and apostrophes are allowed)."
Phone number Used only for account-related contact.
Mailing address Street, city, state/region, postal code, country.

You will be asked to agree to the User Agreement. The key obligations are worth reading in full, but in summary:

  • You will not transfer data or technical information received from the site to any other entity without prior express approval. In practice, TLEs (which fall under "basic SSA data") are allowed to be used in your own tools and redistributed with citation; anything beyond basic data requires explicit authorization.
  • You will not share or transfer your username/password. Each individual using the data needs their own account.
  • Access is currently free ("The present U.S. Government policy is not to charge for website access") but must be renewed periodically.

For Visible Ephemeris, your use — pulling historical TLEs to your own cache for personal propagation — is within the scope of basic SSA data use. Do not check cached TLEs into a public repository or redistribute them in bulk.

Step 2 — Confirm your email

After submitting the form, Space-Track sends a confirmation email. Click the link to activate the account. If you don't receive it within a reasonable time or the form rejects your submission, contact admin@space-track.org.

Step 3 — Verify login on the website

Log in once at https://www.space-track.org/ with your email and chosen password before using the credentials here. This confirms the account is active and lets you read the current API documentation at https://www.space-track.org/documentation.

Step 4 — Provide credentials to Visible Ephemeris

Credentials are read at program start, in this order:

  1. Environment variables (recommended for shell-scripted use):

    export SPACETRACK_USER='your_email@example.com'
    export SPACETRACK_PASS='your_password'
  2. Config file ~/.config/visible-ephemeris/spacetrack.ini:

    [spacetrack]
    username = your_email@example.com
    password = your_password

    Create the directory and restrict permissions so only your account can read it:

    mkdir -p ~/.config/visible-ephemeris
    chmod 700 ~/.config/visible-ephemeris
    # ... create the file ...
    chmod 600 ~/.config/visible-ephemeris/spacetrack.ini

Both the C++ and Python implementations check these locations; whichever is found first wins. If neither is set when a historical query is attempted, Visible Ephemeris prints a clear error with these same instructions and exits non-zero — it never silently falls back to current Celestrak TLEs.

Step 5 — Mind the rate limits

Space-Track enforces API throttling. The Visible Ephemeris historical loader is designed to consume a tiny fraction of the budget — one gp_history range query per (date, group) combination, cached permanently so the second run is offline — but if you script many different dates back-to-back you may hit the limits:

  • General: "Limit API queries to less than 30 requests per 1 minute(s) and 300 requests per 1 hour(s)."
  • Violation: "Your space-track account may be suspended if you violate the usage policy."

One run of Visible Ephemeris over one date and one group = 2 requests (login + query). Each additional date or group name adds one more query.

What gp_history gives you

The gp_history class on the basicspacedata endpoint returns every General Perturbations (GP) element set — including classical TLEs — ever published by USSPACECOM for each object in the public catalog, keyed by EPOCH. This is the same data source CelesTrak and most other TLE services mirror from. Standard registered accounts have access to gp_history with no additional approval required.

Example: Iridium-NEXT on a past date

# C++
./VisibleEphemeris --time "2019-06-15 12:00:00" --groupsel iridium-NEXT

# Python
python3 main.py --time "2019-06-15 12:00:00" --groupsel iridium-NEXT

Expected behavior: the program prints [SPACETRACK] ..., writes tle_cache/historical/2019-06-15/iridium-NEXT.txt, loads the satellites, and runs normally. Re-running the same command uses the cache with no network call.

Historical TLE selection rule: for each NORAD ID, the tracker keeps the TLE with the latest EPOCH not exceeding the target date, within a ±10 day window. Satellites with no TLE in that window are skipped with a warning.


Usage Examples

1. Radio Mode - All Iridium Satellites

Display all Iridium NEXT satellites with elevation-based coloring:

# C++
./VisibleEphemeris --groupsel iridium-NEXT

# Python
python3 main.py --groupsel iridium-NEXT

With visible_only: false in config, all satellites appear on the map (yellow/green/grey).

2. Optical Mode - Visual Observing

Show only satellites visible to the naked eye (sunlit, observer in darkness):

# C++
./VisibleEphemeris --groupsel "stations,visual" --visible --minel 10

# Python
python3 main.py --groupsel "stations,visual" --visible --minel 10

3. Amateur Radio Satellites

Track ham radio satellites above the horizon:

./VisibleEphemeris --groupsel amateur --minel 0

4. Specific Location

./VisibleEphemeris --lat 39.54 --lon -76.09 --alt 0.1

5. Hardware Control (Single Target)

Track ISS with rotator control:

./VisibleEphemeris --satsel ISS --rotator

6. Custom Network Ports

Run on alternative ports (useful for multiple instances or firewall restrictions):

# Change all three ports
./VisibleEphemeris --port 9080,9345,9346

# Change only the web dashboard port
./VisibleEphemeris --port 9080

# Change only the physics stream port (keep others at defaults)
./VisibleEphemeris --port ,,9346

7. Historical Playback (Past Date)

Propagate positions as they would have been seen on an arbitrary UTC date. Requires a free Space-Track.org account for dates more than 24 hours in the past (see Space-Track credentials):

# All Iridium-NEXT satellites over a summer day in 2019
./VisibleEphemeris --time "2019-06-15 12:00:00" --groupsel iridium-NEXT

# Track the ISS at a specific moment in 2018
./VisibleEphemeris --time "2018-11-02 03:30:00" --satsel ISS

# Python tracker uses the same flags
python3 main.py --time "2019-06-15 12:00:00" --groupsel iridium-NEXT

TLEs are cached permanently under tle_cache/historical/YYYY-MM-DD/; re-running the same date is offline.

8. Near-Now Simulation (No Credentials Needed)

--time within 24 hours of wall-clock now stays on the live Celestrak path and does not require Space-Track credentials:

# 2 hours ago; uses today's cached Celestrak TLEs
./VisibleEphemeris --time "$(date -u -d '2 hours ago' +'%Y-%m-%d %H:%M:%S')" --groupsel stations

Keyboard Controls

Key Action
Q Quit (prompts to save configuration)
UP/DOWN Scroll satellite list
PAGE UP/DOWN Fast scroll

Network Services

Visible Ephemeris exposes three network interfaces. Ports can be overridden with the --port argument using comma-separated values (e.g., --port 9000,9001,9002). Use empty values to keep defaults (e.g., --port ,,12349 changes only the physics port).

Graphical Dashboard: http://<IP>:8080 (default)

  • Interactive Mercator map with satellite positions and ground tracks
  • Polar skyplot (toggle with MAP/SKY button)
  • Sortable satellite table
  • Click satellites to select and view details
  • Solar terminator visualization
  • Satellite footprint radius for selected satellite

Text Mirror: http://<IP>:12345 (default)

  • Lightweight HTML reflection of terminal output
  • Auto-refreshes every second
  • Works on low-bandwidth connections

Physics Stream: tcp://<IP>:12346 (default)

  • Raw TCP streaming of physics data (satellite positions, look angles, etc.)
  • Zero-buffer design: Data is only generated when clients are connected
  • Real-time streaming: Pushes updates at the rate set by --deltaT
  • Frame delimiter: Each frame ends with \n---END_FRAME---\n

Connecting to Physics Stream:

# Using netcat
nc localhost 12346

# Using telnet
telnet localhost 12346

# Using socat (with line buffering)
socat - TCP:localhost:12346

Example Output:

VISIBLE EPHEMERIS v12.65-CODE-ONLY
2026-02-23 12:34:56.7 LOC
OBS: 39.6478, -76.1347 | SHOWN: 5

NAME            AZ       EL      RANGE RR(km/s) VIS   NEXT EVENT
-------------------------------------------------------------------------
IRIDIUM 140       83.3     29.2     1271.8    0.092 DAY   LOS 6m 42s
IRIDIUM 110      243.1     14.2     1899.9    3.752 DAY   AOS 3m 54s
---END_FRAME---

Integration Notes:

  • Connect via TCP to receive continuous updates
  • Parse frames by splitting on ---END_FRAME---
  • Data format matches terminal display (fixed-width columns)
  • Disconnecting stops data transmission (no buffer accumulation)

Firewall Configuration (adjust if using --port):

sudo ufw allow 8080
sudo ufw allow 12345
sudo ufw allow 12346

Celestrak Groups

Common group names for --groupsel or group_selection:

Group Description
active All active satellites (~6000+)
stations Space stations (ISS, CSS, etc.)
visual Bright/easily visible satellites
amateur Amateur radio satellites
weather Weather satellites
noaa NOAA satellites
goes GOES weather satellites
starlink SpaceX Starlink constellation
oneweb OneWeb constellation
iridium Iridium (original)
iridium-NEXT Iridium NEXT constellation
gps-ops GPS satellites
galileo Galileo navigation
geo Geostationary satellites

Multiple groups can be combined: --groupsel amateur,weather,stations


Platform Notes

Ubuntu Linux (PC/Laptop)

  • Performance: High. Capable of tracking 20,000+ objects without UI lag.
  • Compiler: Builds with Clang or GCC (C++17). CMake auto-selects Clang when it is installed and falls back to the system default (GCC) otherwise; override with -DCMAKE_CXX_COMPILER=....
  • Radio Control: Typically uses USB interfaces. Ensure your user is in the dialout group: sudo usermod -aG dialout $USER

Raspberry Pi 5

  • Performance: Optimized. Maintains <5% CPU utilization for ~13,000 objects.
  • Radio Control: Can use USB or GPIO (HATs) for rig/rotor control.
  • Thermal: Passive cooling sufficient for typical loads; active cooling recommended for 24/7 operation.

Testing

Functional Equivalence Test (Python vs C++)

A comprehensive equivalence test verifies that the Python (Skyfield) and C++ (libsgp4) implementations produce matching orbital calculations. The test compiles a minimal C++ test harness, runs both engines against the same TLE data, observer location, and fixed UTC time, then compares results.

Running the test:

# From project root
python_tracker/.venv/bin/python tests/test_equivalence.py

What it tests:

Metric Tolerance Typical Difference
Azimuth / Elevation 0.15 deg < 0.001 deg
Slant Range 2.0 km < 0.03 km
Sub-satellite Lat/Lon 0.05 deg < 0.001 deg
Satellite Altitude 1.0 km < 0.002 km
Apogee 1.0 km < 0.001 km
Sun Position 0.5 deg < 0.005 deg
Visibility State string match see note below

Test satellites: ISS (LEO, ~420 km), NOAA 19 (polar, ~860 km), GPS BIIR-2 (MEO, ~20,200 km).

Visibility model note: Both implementations use the same naked-eye visibility definition (above the horizon + observer Sun ≤ −12° + satellite sunlit). They differ only in the Sun model used to compute the observer's solar altitude — Python uses the JPL DE421 ephemeris, C++ uses a Meeus analytical model. These agree to within a few thousandths of a degree, so the two can disagree only for a satellite whose observer-Sun altitude sits within ~0.005° of the −12° boundary. The satellite illumination (Earth-shadow) calculation agrees between both implementations.

Additional unit tests (Python, run by the equivalence test):

  • Apogee computation from TLE orbital elements (Kepler's third law)
  • Decay detection (80 km apogee threshold)

C++ Flare-Detection Unit Test

A standalone test of the specular-flare reflection geometry (unittests/test_flare.cpp):

# From project root
clang++ -std=c++17 -Iinclude unittests/test_flare.cpp src/visibility.cpp -o /tmp/test_flare && /tmp/test_flare

It covers a direct nadir flare (hit), a near-miss, an off-axis miss, high-orbit rejection (non-LEO), and daylight rejection. Expected output ends with ALL TESTS PASSED.


License & Credits

  • Author: Dr. Robert W. McGwier, PhD (N4HY)
  • AI Assistance: Claude (Anthropic) for implementation
  • Based on: Quiktrak (1981 VBasic, 1983 Commodore C, IBM C 1986, 1990, 1999)
  • License: MIT — see LICENSE

ENJOYMENT IS REQUIRED. REPORT BUGS!

About

A high performance physics based program using Kelso/Villado SGP4 for the C++ version, to track satellites (all in the Celestrak TLE), . A Python version is now included using Skyfield

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages