import os from django.http import HttpResponse, JsonResponse, Http404 from django.shortcuts import get_object_or_404, render from lofar.sas.tmss.tmss.tmssapp import models from lofar.common.json_utils import get_default_json_object_for_schema from lofar.common.datetimeutils import formatDatetime from lofar.sas.tmss.tmss.tmssapp.adapters.parset import convert_to_parset from drf_yasg.utils import swagger_auto_schema from drf_yasg.openapi import Parameter from rest_framework.permissions import AllowAny from rest_framework.decorators import authentication_classes, permission_classes from django.apps import apps from rest_framework.decorators import api_view from datetime import datetime import dateutil.parser from astropy.coordinates import Angle import astropy.units from lofar.sas.tmss.tmss.tmssapp.conversions import local_sidereal_time_for_utc_and_station, local_sidereal_time_for_utc_and_longitude, timestamps_and_stations_to_sun_rise_and_set, coordinates_and_timestamps_to_separation_from_bodies, coordinates_timestamps_and_stations_to_target_rise_and_set # Note: Decorate with @api_view to get this picked up by Swagger def subtask_template_default_specification(request, subtask_template_pk:int): subtask_template = get_object_or_404(models.SubtaskTemplate, pk=subtask_template_pk) spec = get_default_json_object_for_schema(subtask_template.schema) return JsonResponse(spec) def task_template_default_specification(request, task_template_pk:int): task_template = get_object_or_404(models.TaskTemplate, pk=task_template_pk) spec = get_default_json_object_for_schema(task_template.schema) return JsonResponse(spec) def subtask_parset(request, subtask_pk:int): subtask = get_object_or_404(models.Subtask, pk=subtask_pk) parset = convert_to_parset(subtask) return HttpResponse(str(parset), content_type='text/plain') def index(request): return render(request, os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), '../../frontend','tmss_webapp/build/index.html')) #return render(request, "../../../frontend/frontend_poc/build/index.html") def task_specify_observation(request, pk=None): task = get_object_or_404(models.TaskDraft, pk=pk) return HttpResponse("response", content_type='text/plain') # Allow everybody to GET our publicly available template-json-schema's @permission_classes([AllowAny]) @authentication_classes([AllowAny]) @swagger_auto_schema(#method='GET', responses={200: 'Get the JSON schema from the template with the requested <template>, <name> and <version>', 404: 'the schema with requested <template>, <name> and <version> is not available'}, operation_description="Get the JSON schema for the given <template> with the given <name> and <version> as application/json content response.") #@api_view(['GET']) # todo: !! decorating this as api_view somehow breaks json ref resolution !! fix this and double url issue in urls.py, then use decorator here to include in Swagger def get_template_json_schema(request, template:str, name:str, version:str): template_model = apps.get_model("tmssapp", template) template_instance = get_object_or_404(template_model, name=name, version=version) schema = template_instance.schema response = JsonResponse(schema, json_dumps_params={"indent":2}) # config Access-Control. Our schemas use $ref url's to other schemas, mainly pointing to our own common schemas with base definitions. # We instruct the client to allow fetching those. response["Access-Control-Allow-Origin"] = "*" response["Access-Control-Allow-Methods"] = "GET, OPTIONS" return response # Allow everybody to GET our publicly available station group lookups @permission_classes([AllowAny]) @authentication_classes([AllowAny]) @swagger_auto_schema(#method='GET', responses={200: 'A JSON object with two properties: group:<the_group_name>, stations:<the_list_of_stations>', 404: 'No such group or template available'}, operation_description="Get a JSON list of stations for the given <station_group> name the the group definitions in the common_schema_template given by <template_name> and <template_version>") #@api_view(['GET']) # todo: fix double url issue in urls.py, then use decorator here to include in Swagger def get_stations_in_group(request, template_name:str, template_version:str, station_group:str): station_schema_template = get_object_or_404(models.CommonSchemaTemplate, name=template_name, version=template_version) station_schema = station_schema_template.schema if 'station_group' not in station_schema.get('definitions', {}): raise Http404('The JSON schema in template %s version %s has no station_group definitions' % (template_name, template_version)) groups = station_schema['definitions']['station_group']['anyOf'] try: selected_group = next(g for g in groups if g['title'].lower() == station_group.lower()) except StopIteration: raise Http404('No station_group with name "%s" found in the JSON schema. template=%s version=%s' % (station_group, template_name, template_version)) stations = selected_group['properties']['stations']['enum'][0] return JsonResponse({'group': station_group, 'stations': stations}) @permission_classes([AllowAny]) @authentication_classes([AllowAny]) @swagger_auto_schema(method='GET', responses={200: 'An isoformat timestamp of the current UTC clock of the system'}, operation_description="Get the current system time in UTC") @api_view(['GET']) def utc(request): return HttpResponse(datetime.utcnow().isoformat(), content_type='text/plain') @permission_classes([AllowAny]) @authentication_classes([AllowAny]) @swagger_auto_schema(method='GET', responses={200: 'The LST time in hms format at the given UTC time and station or longitude'}, operation_description="Get LST time for UTC time and station or longitude", manual_parameters=[Parameter(name='station', required=False, type='string', in_='query', description="A station names (defaults to CS002)"), Parameter(name='timestamp', required=False, type='string', in_='query', description="A timestamp in isoformat (defaults to utcnow)"), Parameter(name='longitude', required=False, type='string', in_='query', description="A longitude as float") ]) @api_view(['GET']) def lst(request): # Handling optional parameters via django paths in urls.py is a pain, we access them on the request directly instead. timestamp = request.GET.get('timestamp', None) station = request.GET.get('station', None) longitude = request.GET.get('longitude', None) # conversions if timestamp: timestamp = dateutil.parser.parse(timestamp) # isot to datetime if longitude: longitude = float(longitude) if station: lst_lon = local_sidereal_time_for_utc_and_station(timestamp, station) elif longitude: lst_lon = local_sidereal_time_for_utc_and_longitude(timestamp, longitude) else: # fall back to defaults lst_lon = local_sidereal_time_for_utc_and_station(timestamp) # todo: do we want to return a dict, so users can make sure their parameters were parsed correctly instead? return HttpResponse(str(lst_lon), content_type='text/plain') @permission_classes([AllowAny]) @authentication_classes([AllowAny]) @swagger_auto_schema(method='GET', responses={200: 'A JSON object with sunrise, sunset, day and night of the given stations at the given timestamps'}, operation_description="Get sunrise, sunset, day and night for stations and timestamps.\n\n" "Example request: /api/util/sun_rise_and_set?stations=CS002,CS005×tamps=2020-05-01,2020-09-09T11-11-00", manual_parameters=[Parameter(name='stations', required=False, type='string', in_='query', description="comma-separated list of station names"), Parameter(name='timestamps', required=False, type='string', in_='query', description="comma-separated list of isoformat timestamps")]) @api_view(['GET']) def get_sun_rise_and_set(request): """ returns sunrise and sunset at the given stations and timestamps, or today at LOFAR core if none specified. example request: /api/util/sun_rise_and_set?stations=CS002,CS005×tamps=2020-05-01,2020-09-09T11-11-00 """ timestamps = request.GET.get('timestamps', None) stations = request.GET.get('stations', None) if timestamps is None: timestamps = (datetime.utcnow(),) else: timestamps = timestamps.split(',') timestamps = tuple([dateutil.parser.parse(timestamp, ignoretz=True) for timestamp in timestamps]) # isot to datetime if stations is None: stations = ("CS002",) else: stations = tuple(stations.split(',')) 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 (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×tamps=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', description="comma-separated list of solar system bodies")]) @api_view(['GET']) 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 ''' timestamps = request.GET.get('timestamps', 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 via 'angle1', 'angle2' (and optionally 'direction_type') properties.") if timestamps is None: timestamps = (datetime.utcnow(),) else: timestamps = timestamps.split(',') timestamps = tuple([dateutil.parser.parse(timestamp, ignoretz=True) for timestamp in timestamps]) # isot to datetime # 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 = {} # 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) @permission_classes([AllowAny]) @authentication_classes([AllowAny]) @swagger_auto_schema(method='GET', responses={200: 'A JSON object with rise and set times of the given coordinates above the provided horizon, for each given station and timestamp.'}, operation_description="Get rise and set times of the given coordinates above the provided horizon, for each given station and timestamp. \n\n" "Example request: /api/util/target_rise_and_set?angle1=0.5&angle2=0.5×tamps=2020-01-01T15&horizon=0.3", 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='stations', required=False, type='string', in_='query', description="comma-separated list of station names"), Parameter(name='horizon', required=False, type='string', in_='query', description="Elevation above horizon for which to return rise/set times as float")]) @api_view(['GET']) def get_target_rise_and_set(request): ''' returns rise and set times of the given coordinates above the provided horizon, for each given station and timestamp. ''' timestamps = request.GET.get('timestamps', None) angle1 = request.GET.get('angle1') angle2 = request.GET.get('angle2') direction_type = request.GET.get("direction_type", "J2000") stations = tuple(request.GET.get('stations', "CS002").split(',')) horizon = request.GET.get('horizon', None) if angle1 is None or angle2 is None: raise ValueError("Please provide celestial coordinates via 'angle1', 'angle2' (and optionally 'direction_type') properties.") if timestamps is None: timestamps = (datetime.utcnow(),) else: timestamps = timestamps.split(',') timestamps = tuple([dateutil.parser.parse(timestamp, ignoretz=True) for timestamp in timestamps]) # isot to datetime if horizon is None: horizon = Angle(0, unit=astropy.units.rad) else: horizon = Angle(horizon, unit=astropy.units.rad) # calculate rise_set_dict = coordinates_timestamps_and_stations_to_target_rise_and_set(angle1=angle1, angle2=angle2, direction_type=direction_type, angle_to_horizon=horizon, timestamps=timestamps, stations=stations) return JsonResponse(rise_set_dict)