Skip to content
Snippets Groups Projects
Commit ec812e4f authored by Jörn Künsemöller's avatar Jörn Künsemöller
Browse files

TMSS-259: Change format according to Jan Davids suggestions. Change list input...

TMSS-259: Change format according to Jan Davids suggestions. Change list input to tuples and add caching.
parent e3bf43cb
No related branches found
No related tags found
1 merge request!284Resolve TMSS-259
......@@ -6,7 +6,7 @@ from astropy.coordinates.earth import EarthLocation
from astropy.coordinates import Angle, get_body
from astroplan.observer import Observer
import astropy.time
from functools import lru_cache
def create_astroplan_observer_for_station(station: str) -> Observer:
'''
......@@ -25,11 +25,12 @@ def create_astroplan_observer_for_station(station: str) -> Observer:
SUN_SET_RISE_ANGLE_TO_HORIZON = Angle(10, unit=astropy.units.deg)
SUN_SET_RISE_PRECISION = 15 # n_grid_points; higher is more precise but very costly; astropy defaults to 150, 15 seems to cause errors of typically one minute
def timestamps_and_stations_to_sun_rise_and_set(timestamps: [datetime], stations: [str], angle_to_horizon: Angle=SUN_SET_RISE_ANGLE_TO_HORIZON) -> dict:
@lru_cache(maxsize=256, typed=False) # does not like lists, so use sets to allow caching
def timestamps_and_stations_to_sun_rise_and_set(timestamps: tuple, stations: tuple, angle_to_horizon: Angle=SUN_SET_RISE_ANGLE_TO_HORIZON) -> dict:
"""
compute sunrise, sunset, day and night of the given stations at the given timestamps
:param timestamps: list of datetimes, e.g. [datetime(2020, 1, 1), datetime(2020, 1, 2)]
:param stations: list of station names, e.g. ["CS002"]
:param timestamps: tuple of datetimes, e.g. (datetime(2020, 1, 1), datetime(2020, 1, 2))
:param stations: tuple of station names, e.g. ("CS002",)
:return A dict that maps station names to a nested dict that contains lists of start and end times for sunrise, sunset, etc, on each requested date.
E.g.
{"CS002":
......@@ -62,36 +63,35 @@ def timestamps_and_stations_to_sun_rise_and_set(timestamps: [datetime], stations
return return_dict
def coordinates_and_timestamps_to_separation_from_bodies(coords: [astropy.coordinates.SkyCoord], timestamps: [datetime], bodies: [str], stations: [str]) -> dict:
@lru_cache(maxsize=256, typed=False) # does not like lists, so use sets to allow caching
def coordinates_and_timestamps_to_separation_from_bodies(angle1: float, angle2: float, direction_type: str, timestamps: tuple, bodies: tuple) -> dict:
"""
compute angular distances of the given sky coordinates from the given solar system bodies at the given timestamps and stations
:param coords: list of SkyCoord objects to measure separation
:param timestamps: list of datetimes, e.g. [datetime(2020, 1, 1, 15, 0, 0), datetime(2020, 1, 1, 16, 0, 0)]
:param stations: list of station names, e.g. ["CS002"]
:param bodies: list of solar system bodies, e.g. ['sun', 'moon', 'jupiter']
:return A dict that maps station names to a list with a dict for each given coordinate, which maps each body body to a list of separation angles for each given timestamp.
compute angular distances of the given sky coordinates from the given solar system bodies at the given timestamps (seen from LOFAR core)
:param angle1: first angle of celectial coordinates, e.g. RA
:param angle2: second angle of celectial coordinates, e.g. Dec
:param direction_type: direction_type of celectial coordinates, e.g. 'J2000'
:param timestamps: tuple of datetimes, e.g. (datetime(2020, 1, 1, 15, 0, 0), datetime(2020, 1, 1, 16, 0, 0))
:param bodies: tuple of solar system bodies, e.g. ('sun', 'moon', 'jupiter')
:return A dict that maps each body to a dict that maps the given timestamp to a separation angle from the given coordinate.
E.g.
{"CS002":
[
{"sun": [Angle("0.7rad"), Angle("0.7rad")], "moon": [Angle("0.4rad"), Angle("0.4rad")], "jupiter": [Angle("2.7rad"), Angle("2.7rad")]},
{"sun": [Angle("0.8rad"), Angle("0.8rad")], "moon": [Angle("0.5rad"), Angle("0.5rad")], "jupiter": [Angle("2.6rad"), Angle("2.6rad")]}
]
{
"sun": {datetime(2020, 1, 1, 6, 0, 0): Angle("0.7rad"), datetime(2020, 1, 1, 7, 0, 0): Angle("0.7rad")},
"moon": {datetime(2020, 1, 1, 6, 0, 0): Angle("0.4rad"), datetime(2020, 1, 1, 7, 0, 0): Angle("0.4rad")},
"jupiter": {datetime(2020, 1, 1, 6, 0, 0): Angle("2.7rad"), datetime(2020, 1, 1, 7, 0, 0): Angle("2.7rad")}
}
# todo: not so sure about this data structure. SkyCoord is not hashable, so we cannot use it as a key.
# todo: We could provide coordinates in a string format and use that as keys, or limit to a single pointing to avoid confusion.
"""
if direction_type == "J2000":
coord = astropy.coordinates.SkyCoord(ra=angle1, dec=angle2, unit=astropy.units.deg)
else:
raise ValueError("Do not know how to convert direction_type=%s to SkyCoord" % direction_type)
return_dict = {}
for station in stations:
for coord in coords:
body_angles = {}
for body in bodies:
location = create_astroplan_observer_for_station(station).location
for timestamp in timestamps:
# get body coords at timestamp
body_coord = get_body(body=body, time=astropy.time.Time(timestamp), location=location)
angle = coord.separation(body_coord)
body_angles.setdefault(body, []).append(angle)
return_dict.setdefault(station, []).append(body_angles)
for body in bodies:
location = create_astroplan_observer_for_station("CS002").location
for timestamp in timestamps:
# get body coords at timestamp
body_coord = get_body(body=body, time=astropy.time.Time(timestamp), location=location)
angle = coord.separation(body_coord)
return_dict.setdefault(body, {})[timestamp] = angle
return return_dict
......
......@@ -160,31 +160,31 @@ def get_sun_rise_and_set(request):
timestamps = request.GET.get('timestamps', None)
stations = request.GET.get('stations', None)
if timestamps is None:
timestamps = [datetime.utcnow()]
timestamps = (datetime.utcnow(),)
else:
timestamps = timestamps.split(',')
timestamps = [dateutil.parser.parse(timestamp) for timestamp in timestamps] # isot to datetime
timestamps = tuple([dateutil.parser.parse(timestamp) for timestamp in timestamps]) # isot to datetime
if stations is None:
stations = ["CS002"]
stations = ("CS002",)
else:
stations = stations.split(',')
stations = tuple(stations.split(','))
# todo: to improve speed for the frontend, we should probably precompute/cache these and return those (where available), to revisit after constraint table / TMSS-190 is done
return JsonResponse(timestamps_and_stations_to_sun_rise_and_set(timestamps, stations))
@permission_classes([AllowAny])
@authentication_classes([AllowAny])
@swagger_auto_schema(method='GET',
responses={200: 'A JSON object with angular distances of the given sky coordinates from the given solar system bodies at the given timestamps and stations. \n'
'Outer list contains results per coordinate in given order, inner list per timestamp.'},
operation_description="Get angular distances of the given sky coordinates from the given solar system bodies at all given timestamps and stations. \n\n"
"Example request: /api/util/sun_rise_and_set?coordinates=,CS005&timestamps=2020-05-01,2020-09-09T11-11-00",
manual_parameters=[Parameter(name='ras', required=True, type='string', in_='query',
description="comma-separated list of right ascensions (celestial longitudes)"),
Parameter(name='decs', required=True, type='string', in_='query',
description="comma-separated list of declinations (celestial latitude)"),
Parameter(name='stations', required=False, type='string', in_='query',
description="comma-separated list of station names"),
responses={200: 'A JSON object with angular distances of the given sky coordinates from the given solar system bodies at the given timestamps (seen from LOFAR core)'},
operation_description="Get angular distances of the given sky coordinates from the given solar system bodies at all given timestamps. \n\n"
"Example request: /api/util/angular_separation_from_bodies?angle1=1&angle2=1&timestamps=2020-01-01T15,2020-01-01T16",
manual_parameters=[Parameter(name='angle1', required=True, type='string', in_='query',
description="first angle of celectial coordinates as float, e.g. RA"),
Parameter(name='angle2', required=True, type='string', in_='query',
description="second angle of celectial coordinates as float, e.g. RA"),
Parameter(name='direction_type', required=False, type='string', in_='query',
description="direction_type of celectial coordinates as string, e.g. J2000"),
Parameter(name='timestamps', required=False, type='string', in_='query',
description="comma-separated list of isoformat timestamps"),
Parameter(name='bodies', required=False, type='string', in_='query',
......@@ -194,40 +194,28 @@ def get_angular_separation_from_bodies(request):
'''
returns angular distances of the given sky coordinates from the given astronomical objects at the given timestamps and stations
'''
stations = request.GET.get('stations', None)
timestamps = request.GET.get('timestamps', None)
ras = request.GET.get('ras', None)
decs = request.GET.get('decs', None)
bodies = request.GET.get('bodies', None)
angle1 = request.GET.get('angle1')
angle2 = request.GET.get('angle2')
direction_type = request.GET.get("direction_type", "J2000")
bodies = tuple(request.GET.get('bodies', "sun,moon,jupiter").split(','))
if angle1 is None or angle2 is None:
raise ValueError("Please provide celestial coordinates in radians/J2000 via the 'angle1', 'angle2' and 'direction_type' properties.")
if timestamps is None:
timestamps = [datetime.utcnow()]
timestamps = (datetime.utcnow(),)
else:
timestamps = timestamps.split(',')
timestamps = [dateutil.parser.parse(timestamp) for timestamp in timestamps] # isot to datetime
timestamps = tuple([dateutil.parser.parse(timestamp) for timestamp in timestamps]) # isot to datetime
if bodies is None:
bodies = ['sun', 'moon', 'jupiter']
else:
bodies = bodies.split(',')
# calculate
sep_dict = coordinates_and_timestamps_to_separation_from_bodies(angle1=angle1, angle2=angle2, direction_type=direction_type, bodies=bodies, timestamps=timestamps)
new_sep_dict = {}
if stations is None:
stations = ["CS002"]
else:
stations = stations.split(',')
if ras is None or decs is None:
raise ValueError("Please provide celestial coordinates in radians/J2000 via the 'ras' and 'decs' the properties.")
coords = []
longitudes = ras.split(',')
latitudes = decs.split(',')
for ra, dec in zip(longitudes, latitudes):
coords.append(SkyCoord(ra=ra, dec=dec, unit=u.rad, equinox='J2000'))
sep_dict = coordinates_and_timestamps_to_separation_from_bodies(coords=coords, stations=stations, bodies=bodies, timestamps=timestamps)
for station, v in sep_dict.items():
for coord in v:
for object, angles in coord.items():
coord[object] = [angle.rad for angle in angles]
return JsonResponse(sep_dict)
# serialize angles and datetimes for json response
for body, timestamps in sep_dict.items():
for timestamp, angle in timestamps.items():
new_sep_dict.setdefault(body, {})[timestamp.isoformat()] = angle.rad
return JsonResponse(new_sep_dict)
......@@ -173,58 +173,62 @@ class UtilREST(unittest.TestCase):
self.assertIn("celestial coordinates", r.content.decode('utf-8'))
def test_util_angular_separation_from_bodies_returns_json_structure_with_defaults(self):
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?ras=1&decs=1', auth=AUTH)
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=1&angle2=1', auth=AUTH)
self.assertEqual(r.status_code, 200)
r_dict = json.loads(r.content.decode('utf-8'))
# assert defaults to core and today
self.assertIn('CS002', r_dict.keys())
# assert default bodies
for key in ['sun', 'jupiter', 'moon']:
self.assertIn(key, r_dict['CS002'][0])
self.assertEqual(type(r_dict['CS002'][0]['jupiter'][0]), float)
self.assertIn(key, r_dict.keys())
def test_util_angular_separation_from_bodies_considers_stations(self):
stations = ['CS005', 'RS305', 'DE609']
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?ras=1&decs=1&stations=%s' % ','.join(stations), auth=AUTH)
# assert timestamp is now and has a value
returned_datetime = dateutil.parser.parse(list(r_dict['jupiter'].keys())[0])
current_datetime = datetime.datetime.utcnow()
delta = abs((returned_datetime - current_datetime).total_seconds())
self.assertTrue(delta < 60.0)
self.assertEqual(type(list(r_dict['jupiter'].values())[0]), float)
def test_util_angular_separation_from_bodies_considers_bodies(self):
bodies = ['sun', 'neptune', 'mercury']
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=1&angle2=1&bodies=%s' % ','.join(bodies), auth=AUTH)
self.assertEqual(r.status_code, 200)
r_dict = json.loads(r.content.decode('utf-8'))
# assert station is included in response and angles differ
angle_last = None
for station in stations:
self.assertIn(station, r_dict.keys())
angle = r_dict[station][0]['jupiter'][0]
for body in bodies:
self.assertIn(body, r_dict.keys())
angle = list(r_dict[body].values())[0]
if angle_last:
self.assertNotEqual(angle, angle_last)
angle_last = angle
def test_util_angular_separation_from_bodies_considers_timestamps(self):
timestamps = ['2020-01-01', '2020-02-22T16-00-00', '2020-3-11', '2020-01-01']
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?ras=1&decs=1&timestamps=%s' % ','.join(timestamps), auth=AUTH)
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=1&angle2=1&timestamps=%s' % ','.join(timestamps), auth=AUTH)
self.assertEqual(r.status_code, 200)
r_dict = json.loads(r.content.decode('utf-8'))
# assert all requested timestamps yield a response and angles differ
self.assertEqual(len(timestamps), len(r_dict['CS002'][0]['jupiter']))
angle_last = None
for i in range(len(timestamps)):
angle = r_dict['CS002'][0]['jupiter'][i]
for timestamp in timestamps:
expected_timestamp = dateutil.parser.parse(timestamp).isoformat()
self.assertIn(expected_timestamp, r_dict['jupiter'])
angle = r_dict['jupiter'][expected_timestamp]
if angle_last:
self.assertNotEqual(angle, angle_last)
angle_last = angle
def test_util_angular_separation_from_bodies_considers_coordinates(self):
ras = ['1.0', '1.1', '1.2']
decs = ['1.0', '1.1', '1.2']
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?ras=%s&decs=%s' % (','.join(ras), ','.join(decs)), auth=AUTH)
self.assertEqual(r.status_code, 200)
r_dict = json.loads(r.content.decode('utf-8'))
test_coords = [(1, 1,"J2000"), (1.1, 1, "J2000"), (1.1, 1.1, "J2000")]
for coords in test_coords:
r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=%s&angle2=%s&direction_type=%s' % coords, auth=AUTH)
self.assertEqual(r.status_code, 200)
r_dict = json.loads(r.content.decode('utf-8'))
# assert all requested timestamps yield a response and angles differ
self.assertEqual(len(ras), len(r_dict['CS002']))
angle_last = None
for i in range(len(ras)):
angle = r_dict['CS002'][i]['jupiter'][0]
# assert all requested timestamps yield a response and angles differ
angle_last = None
angle = list(r_dict['jupiter'].values())[0]
if angle_last:
self.assertNotEqual(angle, angle_last)
angle_last = angle
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment