from typing import Union, List
import numpy as np
from warnings import warn
from astropy.coordinates import SkyCoord
from astropy.units import Quantity
from .base import BaseMission
from . import MISS_INDEX
from ..exceptions import NoObsAfterFilterError
[docs]
def multi_mission_filter_on_positions(positions: Union[list, np.ndarray, SkyCoord],
search_distance: Union[Quantity, float, int, list,
np.ndarray, dict] = None, missions: List[str] = None,
insts: dict = None) -> list[BaseMission]:
"""
Convenience function to search around a position for observations across multiple missions. By
default this function will search all available missions supported by DAXA. This will set up
Mission objects and filter their observations based on the input positions and search_distance
argument. If a mission does not have any observations matched after the filtering, they will not
be included in the list output.
:param list/np.ndarray/SkyCoord positions: The positions for which you wish to search for
observations. They can be passed either as a list or nested list (i.e. [r, d] OR [[r1, d1],
[r2, d2]]), a numpy array, or an already defined SkyCoord. If a list or array is passed then
the coordinates are assumed to be in degrees, and the default mission frame will be used.
:param Quantity/dict search_distance: The distance to search for observations within, the default
is None in which case standard search distances for different missions are used. The user
may pass a single Quantity to use for all missions or a dictionary with keys corresponding to
ALL or SOME of the missions specified by the 'mission' argument. In the case where only SOME
of the missions are specified in a distance dictionary, the default DAXA values will be used
for any that are missing. When specifying a search distance for a specific mission, this may
be either an Astropy Quantity that can be converted to degrees (a float/integer will be
assumed to be in units of degrees), as a dictionary of quantities/floats/ints where the keys
are names of different instruments (possibly with different field of views), or as a
non-scalar Quantity, list, or numpy array with one entry per set of coordinates (for when
you wish to use different search distances for each object). The default is None, in which
case a value of 1.2 times the approximate field of view defined for each instrument will be
used; where different instruments have different FoVs, observation searches will be
undertaken on an instrument-by-instrument basis using the different field of views.
:param list[str] missions: list of mission names that will have the filter performed on. If set
to None, this function will perform the search on all missions available within DAXA.
:param dict insts: Dictionary with mission names as keys, and values that are either Lists of
strings or a string giving the instrument(s) to be used for the search. The values
should be in the same format that you would parse to a Mission object when chosing
instruments when the object is first instantiated.
:return: A list of missions that have observations associated with them. The list contains
Mission objects that have had the filtering applied and have found matching observations.
:rtype: List[daxa.mission.BaseMission]
"""
# Checking the user inputs to the search_distance argument, we only check the mission level
# requirements to the input, for instrument specific checks, the indiviual filter_on_positions
# methods for each mission check this
if isinstance(search_distance, dict):
# check that all the keys are valid missions
if not all(miss in MISS_INDEX.keys() for miss in search_distance.keys()):
raise ValueError("Keys of the search_distance input ductionary not recognised, "
f"acceptable input missions are as follows: {MISS_INDEX.keys()}")
# In the case that some missions are specified but not all of them, we need to use the
# default search distances
if len(search_distance) != len(MISS_INDEX):
for miss in MISS_INDEX.keys():
if miss not in search_distance.keys():
# By setting this to None, DAXA will use the default values
search_distance[miss] = None
# For all input types into search_distance we translate into a dictionary, so that the argument
# can be used generically later in the code
elif isinstance(search_distance, Quantity):
search_distance = {miss: search_distance for miss in MISS_INDEX.keys()}
elif search_distance is None:
search_distance = {miss: None for miss in MISS_INDEX.keys()}
else:
raise ValueError("The search distance argument must be input as either a Quantity, to be "
"applied to all missions, a dictionary with keys of some or all missions, "
"or left to None such that the DAXA default search distances are used.")
# Checking inputs to missions argument
if missions is not None:
if not isinstance(missions, list):
raise ValueError("The missions argument must be input as a list of strings.")
if not all(isinstance(miss, str) for miss in missions):
raise ValueError("The missions argument must be input as a list of strings.")
if not all(miss in MISS_INDEX.keys() for miss in missions):
raise ValueError("Input missions not recognised, acceptable input missions are as follows:"
f"{MISS_INDEX.keys()}")
else:
mission_keys = missions
else:
mission_keys = MISS_INDEX.keys()
if insts is not None:
# This is the only check we will do, the format of the insts values argument can be checked when
# the mission class is instantiated later
if not all(miss in mission_keys for miss in insts):
raise ValueError("Input Missions given in the 'insts' argument are not recognised. "
"Either they have been misspelled or you have provided a mission to"
" the 'insts' dictionary that is not in the 'missions' input to this"
" function.")
# This will be appended to if observations are found for a mission
mission_list = []
for mission_key in mission_keys:
if insts is not None:
# For that case that only some of the missions select have a chosen instrument given
# in the 'insts' argument
try:
mission = MISS_INDEX[mission_key](insts=insts[mission_key])
except KeyError:
mission = MISS_INDEX[mission_key]()
else:
mission = MISS_INDEX[mission_key]()
try:
mission.filter_on_positions(positions, search_distance[mission_key])
mission_list.append(mission)
except NoObsAfterFilterError:
warn(f'No observations found after the filter for {mission_key}, will not be included '
'in the output dictionary.', stacklevel=2)
continue
# All other errors are to do with the user input arguments to positions and
# search_distance so we still want to raise those
return mission_list