diff --git a/SAS/TMSS/src/tmss/tmssapp/conversions.py b/SAS/TMSS/src/tmss/tmssapp/conversions.py index a9eddb9d5d959f38df66ba196c939bdeddbf1fe1..037d4b31d57c3d921e04780963e2762997f6a339 100644 --- a/SAS/TMSS/src/tmss/tmssapp/conversions.py +++ b/SAS/TMSS/src/tmss/tmssapp/conversions.py @@ -1,7 +1,7 @@ from astropy.time import Time import astropy.units from lofar.lta.sip import station_coordinates -from datetime import datetime +from datetime import datetime, timedelta, time as dtime from astropy.coordinates.earth import EarthLocation from astropy.coordinates import Angle, get_body from astroplan.observer import Observer @@ -20,8 +20,10 @@ def create_astroplan_observer_for_station(station: str) -> Observer: observer = Observer(location, name="LOFAR", timezone="UTC") return observer + # default angle to the horizon at which the sunset/sunrise starts and ends, as per LOFAR definition. 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: """ @@ -46,13 +48,13 @@ def timestamps_and_stations_to_sun_rise_and_set(timestamps: [datetime], stations for station in stations: for timestamp in timestamps: observer = create_astroplan_observer_for_station(station) - sunrise_start = observer.sun_rise_time(time=Time(timestamp), which='previous') + sunrise_start = observer.sun_rise_time(time=Time(timestamp), which='previous', n_grid_points=SUN_SET_RISE_PRECISION) if sunrise_start.to_datetime().date() < timestamp.date(): - sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='next') - sunrise_end = observer.sun_rise_time(time=Time(timestamp), horizon=angle_to_horizon, which='next') - sunset_start = observer.sun_set_time(time=sunrise_end, horizon=angle_to_horizon, which='next') - sunset_end = observer.sun_set_time(time=sunrise_end, horizon=-angle_to_horizon, which='next') - sunrise_next_start = observer.sun_rise_time(time=sunset_end, horizon=-angle_to_horizon, which='next') + sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION) + sunrise_end = observer.sun_rise_time(time=Time(timestamp), horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION) + sunset_start = observer.sun_set_time(time=sunrise_end, horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION) + sunset_end = observer.sun_set_time(time=sunrise_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION) + sunrise_next_start = observer.sun_rise_time(time=sunset_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION) return_dict.setdefault(station, {}).setdefault("sunrise", []).append({"start": sunrise_start.to_datetime(), "end": sunrise_end.to_datetime()}) return_dict[station].setdefault("sunset", []).append({"start": sunset_start.to_datetime(), "end": sunset_end.to_datetime()}) return_dict[station].setdefault("day", []).append({"start": sunrise_end.to_datetime(), "end": sunset_start.to_datetime()}) diff --git a/SAS/TMSS/src/tmss/tmssapp/views.py b/SAS/TMSS/src/tmss/tmssapp/views.py index a427ee8c33283082302861eb0366683c16b9b33a..7bee3fd449aea5f42c170720c42cba3160812b82 100644 --- a/SAS/TMSS/src/tmss/tmssapp/views.py +++ b/SAS/TMSS/src/tmss/tmssapp/views.py @@ -169,6 +169,7 @@ def get_sun_rise_and_set(request): else: stations = 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]) diff --git a/SAS/TMSS/test/t_conversions.py b/SAS/TMSS/test/t_conversions.py index 14231c4f091c04b1f3c53b971bbf069555e6000f..8d987ce104c005598472b0781b1c2cb88cf6941d 100755 --- a/SAS/TMSS/test/t_conversions.py +++ b/SAS/TMSS/test/t_conversions.py @@ -165,6 +165,69 @@ class UtilREST(unittest.TestCase): response_date = dateutil.parser.parse(r_dict['CS002']['sunrise'][i]['start']).date() self.assertEqual(expected_date, response_date) + def test_util_angular_separation_from_bodies_yields_error_when_no_pointing_is_given(self): + r = requests.get(BASE_URL + '/util/angular_separation_from_bodies', auth=AUTH) + + # assert error + self.assertEqual(r.status_code, 500) + 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) + 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()) + for key in ['sun', 'jupiter', 'moon']: + self.assertIn(key, r_dict['CS002'][0]) + self.assertEqual(type(r_dict['CS002'][0]['jupiter'][0]), float) + + 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) + 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] + 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×tamps=%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] + 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')) + + # 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] + if angle_last: + self.assertNotEqual(angle, angle_last) + angle_last = angle if __name__ == "__main__": os.environ['TZ'] = 'UTC'