apsis
ShippedSatellite pass prediction on real orbital data - open source.
apsis is a small open-source backend that answers one operational question: when will a given satellite be visible from a given ground station, and where will it pass overhead? It pulls real two-line elements (TLEs) from CelesTrak, propagates them with SGP4 through skyfield, and returns the upcoming visible passes together with the sub-satellite ground track as GeoJSON. It runs on FastAPI and PostgreSQL/PostGIS, with scheduled jobs and a transactional outbox built into Postgres itself - no message broker.
Interactive map (requires JavaScript). The passes are described in the text.
The problem: ground-segment visibility
A ground station can only talk to a satellite while it is above the local horizon. Planning that contact window means propagating the orbit forward, finding the rise / culminate / set events above a minimum elevation, and projecting the path onto the ground. This is the same family of problem I work on in the European ground segment - satellite ground-segment tooling at Telespazio around SIBA and Sentinel/Copernicus. apsis distills it into a clean public reference, with none of the confidential parts.
The thesis: no broker
apsis has two kinds of background work: recurring (refresh TLEs every couple of hours) and reactive (when an orbit changes, recompute its passes). The reflex answer is a message broker plus a worker framework. apsis uses PostgreSQL for both: a scheduled_jobs table with a lease for the recurring side, and a transactional outbox (LISTEN/NOTIFY plus SELECT ... FOR UPDATE SKIP LOCKED) so an event fires if and only if the database write committed. One fewer moving part to deploy, secure and monitor.
The geometry: PostGIS
Ground stations are points, ground tracks are linestrings, all in WGS84 (longitude/latitude, SRID 4326). PostGIS stores and indexes them; the API serialises each track straight to GeoJSON - which is exactly what the map above renders, unmodified. Asking which passes cross a bounding box is then a GiST index away.
When I would not do this
The honest part. A Postgres-native queue is the right call for a single service that already owns its database and needs reliable, transactional background work at human scale. It is the wrong call at very high throughput, for fan-out to many independent consumers, or for cross-language and cross-service integration - reach for a real broker there. The full trade-off is written up as a note.
Architecture
Layered per context: API to use cases to services and repositories to models. The compute core - orbit propagation, pass finding, geometry - is pure, synchronous and side-effect free, offloaded from the async event loop with a small run_blocking helper. The design decisions are recorded as ADRs in the repository.