Standalone, dependency-light satellite-constellation emulator: synthesizes per-target overpasses of an idealized sun-synchronous constellation and the full per-look observation geometry (WGS84 view geometry, solar geometry, Earth–Sun distance, two-leg line-of-sight Doppler), without a literal satellite inventory or repeat-cycle simulation.
Developed for the Google-RT satellite emulator campaign, but deliberately
grid-agnostic and I/O-free so it can serve any use case that needs
"when and how would an SSO instrument see this point on this day":
targets are plain TargetCells (lat, lon, land flag, opaque indices);
atmospheric-state sampling, land masks, and file formats live behind
strategy seams implemented by the host application.
Dependencies: Dates, LinearAlgebra, StaticArrays. Nothing else.
The emulator represents an effectively unlimited constellation of identical satellites (default: 600 km circular, 11:30 LTAN, ascending) as a virtual construction. For each requested local calendar day and each target it synthesizes the one ascending overpass whose ground track crosses the target, from the exact sun-synchronous identity
LMST(u) = LTAN + Δα(u)/15°·hour, Δα(u) = atan2(cos i · sin u, cos u)
(under the J₂ secular model the sub-satellite local mean solar time depends
only on latitude). The retrograde inclination (≈97.8°) is derived from the
J₂ nodal-precession condition, never hard-coded; the ground-track latitude
limit (≈82.2° geocentric) classifies poleward cells as orbit_unreachable
— there is no polar surrogate.
Off-nadir looks of the cross-track scan pattern (default −6°…+6°, five
angles, 12° FOV) all terminate at the same target cell center: each
look's platform is a different virtual satellite displaced cross-track,
solved on the real WGS84 ellipsoid (validated against the spherical
reference: 6° scan from 600 km ⇒ ≈6.57° surface VZA, ≈63 km displacement).
The signed displacement is stored per look as virtual_geometry_offset_m.
using OrbitSimulator, Dates
cells = [TargetCell((idx, idy, face), lat_deg, lon_deg, is_land), ...]
em = SatelliteEmulator(InMemoryCellSelector(cells)) # v1 defaults
plan = plan_day(em, Date(2019, 9, 3))
coverage_summary(plan) # observed / ocean / orbit_unreachable / not_sunlit
for obs in observations(plan)
obs.utc # shared bundle time (all five looks)
obs.solar.sza_deg # solar geometry at the overpass
[l.surface_vza_deg for l in obs.looks]
[l.relative_azimuth_deg for l in obs.looks]
[k.combined_solar_to_satellite_factor for k in obs.kinematics]
endEvery strategy is replaceable through its abstract type — orbit model,
pass direction, coverage/reachability/illumination policy, scan pattern,
Earth model, solar ephemeris, cell selector, state sampler, Doppler and
velocity model — by defining a new concrete type + methods for the
behavioral interface (overpass_state, is_reachable, is_observable,
look_states, solar_state, spectral_kinematics, select_cells,
sample_state). The planner never branches on strings.
- Azimuths: degrees clockwise from geodetic north at the target.
Relative azimuth is
mod(view_az − solar_az, 360)(the vSmartMOMΔφ = φ_view − φ_sunconvention). At exact nadir the along-track vertical plane is the deterministic reference (view azimuth = ground track heading). - Range rates: positive = increasing emitter–receiver separation;
Doppler factor
D = √((1−β)/(1+β))< 1 for receding (redshift). Wavenumber scales like frequency (ν_out = D·ν_in); wavelength is reciprocal. Two legs: Sun-rest → rotating column frame (D_solar), column frame → satellite (D_view, computed per look — cross-track Doppler is calculated, not forced to zero). - Earth–Sun distance: ratio in semi-major-axis units and metres via the exact IAU au (149 597 870 700 m); the range rate is the analytic derivative of the ephemeris distance model.
- "ECI" is the instantaneous inertial frame coincident with ECEF at the
observation epoch (
r_eci = r_ecef,v_eci = v_ecef + ω⃗_E×r); all Doppler projections are invariant under this choice. - Altitude: platforms sit exactly
altitude_mabove the WGS84 ellipsoid along the geodetic normal; orbital rates use the semi-major axisequatorial_radius + altitude_m. (A geocentric-circular orbit cannot also hold constant ellipsoidal height; this resolution is part of the virtual construction.)
InsolationEphemeris reimplements the analytic model of
Insolation.jl v1.1.0 (mean →
true anomaly by the O(e³) series, solar longitude, declination, ellipse
distance, equation of time), with the ClimaParams default parameter values
— reimplemented, with credit, to avoid the ClimaParams/artifact dependency
and to expose the exact analytic Earth–Sun range rate. The azimuth adapter
from Insolation's convention (0 = East, CCW) to compass is
compass_from_east_ccw_deg and is covered by tests.
Polar surrogates; explicit satellite counts/IDs/spacing/repeat cycles; finite-footprint/PSF integration; clouds and terrain elevation; real TLE/attitude propagation; atmospheric-wind Doppler; solar gravitational or convective line shifts; full barycentric corrections; Doppler resampling of spectra; a physical solar irradiance model.