An astropy based package to calculate if objects are in Earth's shadow
The Earth casts a shadow roughly the shape of a cylinder into space, with a radius equal to the Earth's radius, around the point opposite the sun (the "anti-solar point"). Three things make it non-trivial to calculate if an object in space is inside the shadow:
- The shadow's angular size depends on the distance of the object from the center of the Earth (or the altitude of the object above the surface). E.g., for a Low Earth Orbit (LEO) satellite, most of the sky is in shadow, but for a geostationary (GEO) satellite, the angular radius is about 9 degrees.
- The exact center and extent of the shadow depend on the observer's position, because of parallax.
- The shadow does not have a sharp edge, because the Earth's atmosphere refracts some of the light into the geometric shadow region. This allows some light to penetrate about 1 or 2 degrees into the shadow.
The reason this is important is that satellites tend to reflect sunlight, either constant (diffuse reflection) so they tend to leave streaks in astronomical images (e.g., see Nir et al. 2018) or by specular reflection causing glints (e.g., see Nir et al. 2021). Knowing that a point in the sky is in the shadow of the Earth allows one to rule out a source of light as coming from a satellite in Earth's orbit (up to some altitude). Because the vast majority of satellites are in LEO or GEO, it is usually good enough to to test this for GEO altitude.
Just pip install earthshadow
.
There are no exceptional dependencies other than astropy.
The two easiest functions to use are get_shadow_center
and get_shadow_radius
.
These will tell you the position and radius of the shadow in the sky,
allowing, e.g., to plan observations in a certain direction to purposefully
take images in the shadow (where there are no satellite streaks/glints).
from astropy.coordinates import SkyCoord
from astropy.time import Time
from astropy import units as u
from earthshadow import get_shadow_center, get_shadow_radius
# Get the center of the shadow in the sky
center = get_shadow_center(Time.now(), location='APO', orbit='GEO')
# Get the radius of the shadow in the sky
radius = get_shadow_radius(orbit='GEO')
The center of the shadow is essentially the anti-sun position, shifted by parallax due to the observer's position. It is returned as a SkyCoord object, to be used, e.g., for targeting a telescope in that direction. The shadow radius is just the angular size (in degrees) given the orbit of the target. Note that this is includes the full geometric shadow, not accounting for atmospheric refraction. The full shadow is generally 1--2 degrees smaller than this.
Note that by default the get_shadow_radius
function will return
the size of the shadow as seen from the center of the Earth,
as this is most useful for internal calculations in the module.
To translate this angle to the size of the shadow as seen by an observer
at sea-level, use geocentric_angle = False
in the function call.
Another use-case is to check if a given object is in the shadow.
This is usually used after already taking observations,
to rule out that a streak/glint is due to a satellite.
Use dist_from_shadow_center
to get the angle (in degrees)
the point (or points) are from the shadow's center,
and compare that to the output of get_shadow_radius
.
Both are given, by default, as angles seen by an observer
at the center of the Earth.
If the distance is more than 2 degrees smaller than the shadow,
the source can not be a satellite reflecting sunlight.
Note that the source can still be an object in very high orbit
or a light emitting object (e.g., a laser shot down from a satellite).
For example, given two arrays of coordinates given by ra_values
and dec_values
and a single observation time given by time
:
dist = dist_from_shadow_center(ra_values, dec_values, time=time, location='Palomar', orbit='GEO')
# Check if the object is in the shadow
for i, d in enumerate(dist):
if d < radius - 2*u.deg:
print(f'Object ({ra_values[i]}, {dec_values[i]}) is in the shadow')
else:
print(f'Object ({ra_values[i]}, {dec_values[i]}) is not in the shadow')
To make plots of the shadow region we provide two methods:
The show_shadow_region
which will display a rectangular map
of the RA/Dec region around the anti-sun position,
that shows, for a given orbit, what the shadow looks like
after applying the parallax appropriate for that observer.
The show_skymap
will show the position of the shadow
on an aitoff projection, with some other useful mapping aids
(to be added...).
show_shadow_region(time='now', location='Cerro Paranal', orbit='GEO')
show_skymap('now', location='Green Bank Telescope', orbit='Medium')
There are a few ways to format the inputs to various functions in this package.
- Time: input a string in ISO format, e.g.,
'2021-01-01 00:00:00'
, or an astropy Time object. Also, can simply use the string "now". If given as a float, assumes it is a Julian Date. Can also input a standard datetime object. - Location (
obs
): use a string name of the observatory (e.g., 'palomar') or give the earth location as an astropy EarthLocation object, or as a tuple of three numbers: (longitude, latitude, altitude). The numbers can be given as astropy Quantity objects, or as numbers, in which case they are assumed to be in degrees and meters, respectively. - Orbit (
orbit
): use a string name of the orbit (one of: 'LEO', 'GEO', 'GPS', 'MEDIUM', or 'HIGH') or give the radius of the orbit as a number in kilometers. This assumes the orbit distance is measured from the center of the Earth. If instead you'd like to specify the altitude above sea-level, use the additional inputgeocentric_orbit = False
which means the Earth's radius is subtracted from the input toorbit
in km.
The default values are given at the top of the earthshadow.py
module,
and can always be accessed by passing a None
value to any of these inputs.
Unless edited by the user, these defaults are time='now', obs='Palomar',
and orbit='GEO'.
The medium Earth orbit is arbitrarily defined as 1000 (which is the upper end of LEO, really)
and the high Earth orbit is arbitrarily defined as twice the GEO orbital radius.
Without a better defition of "high orbit", this is a good estimate of where
high satellites will orbit. Above this altitude the Earth's shadow is smaller than
the atmospheric refraction, so there is no real place where a satellite is in complete shadow
(e.g., the Moon is never completely darkened by the Earth, even in a total lunar eclipse).
To be added...