Canada Coverage Analysis

End-to-end demonstration of missiontools coverage and plotting features.

Scenario

  • Sun-synchronous orbit, 550 km, 10:30 LTAN (ascending)

  • Nadir-pointed pushbroom sensor, 20° half-angle FOV

  • Min ground elevation: 20°

  • Illumination constraint: solar zenith angle < 70° (daytime)

  • Area of interest: Canada (20 000 km² point density)

  • Simulation window: 90 days

[1]:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

from missiontools import Spacecraft, Sensor, AoI, Coverage
from missiontools.plotting import plot_ground_track, plot_coverage_map

1. Build the spacecraft

[2]:
EPOCH = np.datetime64('2025-05-01T00:00:00', 'us')

sc = Spacecraft.sunsync(
    altitude_km=550.0,
    node_solar_time='10:30',
    epoch=EPOCH,
)

print(f"Semi-major axis : {sc.a/1e3:.1f} km")
print(f"Inclination     : {np.degrees(sc.i):.2f} deg")
print(f"RAAN            : {np.degrees(sc.raan):.2f} deg")
print(f"Propagator      : {sc.propagator_type}")
Semi-major axis : 6928.1 km
Inclination     : 97.59 deg
RAAN            : 15.96 deg
Propagator      : j2

2. Attach a nadir-pointed sensor

[3]:
sensor = Sensor(half_angle_deg=20.0, body_vector=[0, 0, 1])
sc.add_sensor(sensor)

print(f"FOV half-angle  : {np.degrees(sensor.half_angle_rad):.1f}°")
FOV half-angle  : 20.0°

3. Define the Area of Interest

[4]:
# 20 000 km² per sample point  (~141 km spacing)
aoi = AoI.from_geography('Canada', point_density=20_000)
print(f"AoI sample points: {len(aoi)}")
AoI sample points: 644

4. Set up Coverage analysis

[5]:
cov = Coverage(
    aoi,
    [sensor],
    el_min_deg=20.0,
    sza_max_deg=70.0,
)

5. Run the 90-day simulation

[6]:
T_START = EPOCH
T_END   = EPOCH + np.timedelta64(90 * 24 * 3600, 's')

print("Computing coverage fraction ...")
frac = cov.coverage_fraction(T_START, T_END, max_step=np.timedelta64(10, 's'))
print(f"  Final cumulative coverage : {frac['final_cumulative']:.1%}")
print(f"  Mean instantaneous        : {frac['mean_fraction']:.1%}")

print("\nComputing revisit times ...")
rev = cov.revisit_time(T_START, T_END, max_step=np.timedelta64(10, 's'))
Computing coverage fraction ...
  Final cumulative coverage : 100.0%
  Mean instantaneous        : 0.0%

Computing revisit times ...

6. Ground track — first 24 hours

[7]:
fig = plt.figure(figsize=(14, 6))
ax  = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())

plot_ground_track(
    sc,
    T_START,
    T_START + np.timedelta64(24 * 3600, 's'),
    ax=ax,
    color='tab:orange',
    label='550 km SSO 10:30',
    linewidth=0.8,
)
ax.set_title('Ground track — first 24 hours')
ax.legend(loc='lower left')
plt.tight_layout()
plt.show()
../_images/examples_canada_coverage_13_0.png

7. Coverage maps over Canada

We use a Lambert Conformal Conic projection with the same geometric parameters as EPSG:3347 (Statistics Canada Lambert). We define it manually because Cartopy’s ccrs.epsg(3347) carries restrictive area-of-use bounds from the EPSG registry that clip southern Canada out of the renderable area.

[8]:
# Statistics Canada Lambert — same geometry as EPSG:3347 but without
# the restrictive area-of-use bounds that clip southern Canada.
canada_proj = ccrs.LambertConformal(
    central_longitude=-91.867,
    central_latitude=63.391,
    standard_parallels=(49, 77),
)

# --- Mean revisit time (hours) -------------------------------------------
mean_rev_hrs = rev['mean_revisit'] / 3600.0  # seconds → hours

fig, ax = plt.subplots(
    figsize=(12, 7),
    subplot_kw={'projection': canada_proj},
)
plot_coverage_map(
    aoi,
    mean_rev_hrs,
    ax=ax,
    auto_window=True,
    cmap='plasma_r',
    colorbar_label='Mean revisit time (hours)',
    title='90-day mean revisit time — Canada\n550 km SSO 10:30 LTAN, 20° half-angle, el ≥ 20°, SZA < 70°',
)
plt.tight_layout()
plt.show()
../_images/examples_canada_coverage_15_0.png
[9]:
# --- Maximum revisit time (hours) -----------------------------------------
max_rev_hrs = rev['max_revisit'] / 3600.0  # seconds → hours

fig, ax = plt.subplots(
    figsize=(12, 7),
    subplot_kw={'projection': canada_proj},
)
plot_coverage_map(
    aoi,
    max_rev_hrs,
    ax=ax,
    auto_window=True,
    cmap='plasma_r',
    colorbar_label='Max revisit time (hours)',
    title='90-day maximum revisit time — Canada\n550 km SSO 10:30 LTAN, 20° half-angle, el ≥ 20°, SZA < 70°',
)
plt.tight_layout()
plt.show()
../_images/examples_canada_coverage_16_0.png

8. Summary statistics

[ ]:

[10]:
print("=" * 50)
print("90-day coverage summary — Canada")
print("=" * 50)
print(f"Cumulative coverage fraction : {frac['final_cumulative']:.1%}")
print(f"Mean instantaneous coverage  : {frac['mean_fraction']:.1%}")
print()
global_mean_hrs = rev['global_mean'] / 3600.0
global_max_hrs  = rev['global_max']  / 3600.0
print(f"Global mean revisit time     : {global_mean_hrs:.1f} h")
print(f"Global max revisit time      : {global_max_hrs:.1f} h")
==================================================
90-day coverage summary — Canada
==================================================
Cumulative coverage fraction : 100.0%
Mean instantaneous coverage  : 0.0%

Global mean revisit time     : 60.1 h
Global max revisit time      : 893.9 h
[ ]: