camfi.datamodel.weather module

Provides utilities for working with weather data, and for calculating the movement of the sun from the persepcitve of specific locations.

Constants defined in this module

These can all be overwritten by setting environment variables with the same name.

  • SKYFIELD_DATA_DIR. Path to directory to save skyfield ephemeris data. By default, ~/skyfield-data will be used.

  • CAMFI_EPHEMERIS. Name of ephemeris file to use for calculating sunset and twilight. By default, de440s.bsp will be used. See the choosing an ephemeris in the Skyfield documentation for possible other ephemeris files to use. Note that the ephemeris file will be loaded when this module is imported. The first time this happens, the ephemeris file will be downloaded (def4402.bsp is about 37 mb).

Note: For testing purposes, a tiny abbreviated ephemeris file is included in the Camfi git repository. Without this, CI testing wouldn’t work. The included ephemeris limits the date range to a few days in 2021, so it should not be used when running Camfi normally. To use the included ephemeris (for testing purposes only), set the following environment variables (assuming you are running the tests from the camfi repo root directory):

SKYFIELD_DATA_DIR="camfi/test/data"
CAMFI_EPHEMERIS="test_ephem.bsp"
class camfi.datamodel.weather.Location(*, name: str, lat: float, lon: float, elevation_m: pydantic.types.NonNegativeFloat, tz: camfi.util.Timezone)

Bases: pydantic.main.BaseModel

Provides methods for working with locations.

Parameters
  • name (str) – Name of location.

  • lat (float) – Decimal latitude.

  • lon (float) – Decimal longitude.

  • elevation_m (NonNegativeFloat) – Elevation in metres.

  • tz (timezone) – Timezone offset. Can be given as ISO8601 timezone offset str (e.g. ‘Z’ or ‘+10:00’ or simply ‘+10’).

Examples

>>> Location(
...     name="canberra",
...     lat=-35.293056,
...     lon=149.126944,
...     elevation_m=578,
...     tz="+10:00",
... )
Location(name='canberra', lat=-35.293056, lon=149.126944, elevation_m=578.0, tz=Timezone(datetime.timezone(datetime.timedelta(seconds=36000))))
>>> Location(
...     name="greenwich",
...     lat=51.48,
...     lon=0,
...     elevation_m=47,
...     tz="Z",
... )
Location(name='greenwich', lat=51.48, lon=0.0, elevation_m=47.0, tz=Timezone(datetime.timezone.utc))
>>> Location(
...     name="nyc",
...     lat=40.712778,
...     lon=-74.006111,
...     elevation_m=10,
...     tz="-05",
... )
Location(name='nyc', lat=40.712778, lon=-74.006111, elevation_m=10.0, tz=Timezone(datetime.timezone(datetime.timedelta(days=-1, seconds=68400))))
get_sun_time_dataframe(days: Sequence[datetime.date]) pandas.core.frame.DataFrame

Calls self.search_sun_times on each day in days, and builds a DataFrame of sun times.

Parameters

days (Sequence[date]) – Dates which will become index for dataframe.

Returns

sun_df – DataFrame indexed by location and date, with columns “astronomical_twilight_start”, “nautical_twilight_start”, “civil_twilight_start”, “sunrise”, “sunset”, “nautical_twilight_end”, “civil_twilight_end”, “astronomical_twilight_end”.

Return type

pd.DataFrame

Examples

>>> location = Location(
...     name="canberra",
...     lat=-35.293056,
...     lon=149.126944,
...     elevation_m=578,
...     tz=timezone(timedelta(hours=10)),
... )
>>> days = [date(2021, 7, 23), date(2021, 7, 24), date(2021, 7, 25)]
>>> sun_df = location.get_sun_time_dataframe(days)
>>> np.all(sun_df["sunset"] > sun_df["sunrise"])
True
>>> sun_df
                         astronomical_twilight_start  ...        astronomical_twilight_end
location date                                         ...
canberra 2021-07-23 2021-07-23 05:36:52.178788+10:00  ... 2021-07-23 18:43:25.223475+10:00
         2021-07-24 2021-07-24 05:36:20.903963+10:00  ... 2021-07-24 18:43:59.629041+10:00
         2021-07-25 2021-07-25 05:35:48.170485+10:00  ... 2021-07-25 18:44:34.315154+10:00

[3 rows x 8 columns]
search_sun_times(day: datetime.date) dict

Gets sunrise, sunset, and twilight times for a given date.

Parameters

day (date) – Day to get times from.

Returns

twilight_times – dictionary with keys “astronomical_twilight_start”, “nautical_twilight_start”, “civil_twilight_start”, “sunrise”, “sunset”, “nautical_twilight_end”, “civil_twilight_end”, “astronomical_twilight_end”.

Return type

dict[str, datetime]

Examples

>>> location = Location(
...     name="canberra",
...     lat=-35.293056,
...     lon=149.126944,
...     elevation_m=578,
...     tz=timezone(timedelta(hours=10)),
... )
>>> day = date(2021, 7, 28)
>>> tt = location.search_sun_times(day)

The ordering of the transitions is as expected.

>>> tt["astronomical_twilight_start"] < tt["nautical_twilight_start"]
True
>>> tt["nautical_twilight_start"] < tt["civil_twilight_start"]
True
>>> tt["civil_twilight_start"] < tt["sunrise"]
True
>>> tt["sunrise"] < tt["sunset"]
True
>>> tt["sunset"] < tt["civil_twilight_end"]
True
>>> tt["civil_twilight_end"] < tt["nautical_twilight_end"]
True
>>> tt["nautical_twilight_end"] < tt["astronomical_twilight_end"]
True

And all of the datetimes are on the correct day.

>>> all(d.date() == day for d in tt.values())
True
twilight_state(dt: datetime.datetime) int

Gets the twilight state for the location at the specified time(s).

The meanings of the returned integer values are

  1. Dark of night.

  2. Astronomical twilight.

  3. Nautical twilight.

  4. Civil twilight.

  5. Daytime.

Parameters

dt (datetime) – datetime to evaluate. If timezone-naive, timezone will be taken from self.tz.

Returns

ts – Twilight value.

Return type

int

Examples

>>> location = Location(
...     name="canberra",
...     lat=-35.293056,
...     lon=149.126944,
...     elevation_m=578,
...     tz="+10:00",
... )
>>> location.twilight_state(datetime.fromisoformat("2021-07-28T12:00:00+10:00"))
4
>>> location.twilight_state(datetime.fromisoformat("2021-07-28T23:00:00+11:00"))
0

Timezone will be taken from self.tz if dt is timezone-naive.

>>> location.twilight_state(datetime.fromisoformat("2021-07-28T12:00:00"))
4

Skyfield provides mapping from these numbers to strings.

>>> almanac.TWILIGHTS[0]
'Night'
>>> almanac.TWILIGHTS[1]
'Astronomical twilight'
>>> almanac.TWILIGHTS[2]
'Nautical twilight'
>>> almanac.TWILIGHTS[3]
'Civil twilight'
>>> almanac.TWILIGHTS[4]
'Day'
twilight_states(datetimes: Sequence[datetime.datetime]) numpy.ndarray

Like Location.twilight_state but operates on sequence of datetimes.

Parameters

datetimes (Sequence[datetime]) – datetimes to evaluate. If timezone-naive, timezone will be taken from self.tz.

Returns

ts – Twilight values.

Return type

np.ndarray

Examples

>>> location = Location(
...     name="canberra",
...     lat=-35.293056,
...     lon=149.126944,
...     elevation_m=578,
...     tz=timezone(timedelta(hours=10)),
... )
>>> datetimes = [
...     datetime.fromisoformat("2021-07-28T12:00:00+10:00"),
...     datetime.fromisoformat("2021-07-28T23:00:00+11:00"),
...     datetime.fromisoformat("2021-07-28T12:00:00"),
... ]
>>> location.twilight_states(datetimes)
array([4, 0, 4])
class camfi.datamodel.weather.LocationWeatherStationCollector(*, locations: list, weather_stations: list, location_weather_station_mapping: dict)

Bases: pydantic.main.BaseModel

Contains lists of Locations and Weather stations, and a mapping between them.

Parameters
  • locations (list[Location]) – list of locations where cameras have been placed.

  • weather_stations (list[WeatherStation]) – list of weather stations.

  • location_weather_station_mapping (dict[str, str]) – A mapping between location names and weather_station names.

get_sun_time_dataframe(days: dict) pandas.core.frame.DataFrame

Calls .get_sun_time_dataframe on each location in self.locations, and builds a DataFrame of sun times.

Parameters

days (dict[str, Sequence[date]]) – Mapping from location name to sequences of dates which will become index for the dataframe.

Returns

sun_df – DataFrame indexed by location and date, with columns “astronomical_twilight_start”, “nautical_twilight_start”, “civil_twilight_start”, “sunrise”, “sunset”, “nautical_twilight_end”, “civil_twilight_end”, “astronomical_twilight_end”.

Return type

pd.DataFrame

get_weather_dataframe() pandas.core.frame.DataFrame

Calls .load_dataframe() on each WeatherStation in self.weather_stations, and builds a DataFrame of weather data.

Returns

weather_df – DataFrame with daily weather data, indexed by “weather_station” and “date”.

Return type

pd.DataFrame

get_weather_sun_dataframe(days: Optional[dict] = None) pandas.core.frame.DataFrame

Calls self.get_weather_dataframe and self.get_sun_time_dataframe, and merges the results into a single DataFrame.

Parameters

days (Optional[dict[str, Sequence[date]]]) – Mapping from location name to sequences of dates which will become index for the dataframe. If None (default), this will be inferred from the weather dataframe.

Returns

weather_sun_df – Merged weather and sun time DataFrame.

Return type

pd.DataFrame

class camfi.datamodel.weather.WeatherStation(*, location: camfi.datamodel.weather.Location, data_file: pydantic.types.FilePath)

Bases: pydantic.main.BaseModel

Contains information on a weather station.

Parameters
  • location (Location) – Location of weather station.

  • data_file (FilePath) – Path to csv file containing weather data from weather station.

load_dataframe()

Loads weather data from self.data_file into a pd.DataFrame

Returns

weather_df – DataFrame with daily weather data, indexed by “weather_station” and “date”.

Return type

pd.DataFrame