882 lines
35 KiB
Python
882 lines
35 KiB
Python
import urllib2
|
|
from collections import defaultdict
|
|
from datetime import datetime, timedelta
|
|
from operator import itemgetter
|
|
|
|
import ephem
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.core.urlresolvers import reverse
|
|
from django.db.models import Count, Prefetch
|
|
from django.forms import ValidationError, formset_factory
|
|
from django.http import HttpResponse, HttpResponseNotFound, \
|
|
HttpResponseServerError, JsonResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.utils.text import slugify
|
|
from django.utils.timezone import make_aware, now, utc
|
|
from django.views.generic import ListView
|
|
from rest_framework import serializers, viewsets
|
|
|
|
from network.base.db_api import DBConnectionError, get_transmitter_by_uuid, \
|
|
get_transmitters_by_norad_id, get_transmitters_by_status
|
|
from network.base.decorators import admin_required, ajax_required
|
|
from network.base.forms import BaseObservationFormSet, ObservationForm, \
|
|
SatelliteFilterForm, StationForm
|
|
from network.base.models import Antenna, LatestTle, Observation, Satellite, \
|
|
Station, StationStatusLog
|
|
from network.base.perms import delete_perms, schedule_perms, \
|
|
schedule_station_perms, vet_perms
|
|
from network.base.scheduling import create_new_observation, \
|
|
get_available_stations, predict_available_observation_windows
|
|
from network.base.stats import satellite_stats_by_transmitter_list, \
|
|
transmitter_stats_by_uuid
|
|
from network.base.tasks import fetch_data, update_all_tle
|
|
from network.base.validators import NegativeElevationError, \
|
|
ObservationOverlapError, SinglePassError, \
|
|
is_transmitter_in_station_range
|
|
from network.users.models import User
|
|
|
|
|
|
class StationSerializer(serializers.ModelSerializer):
|
|
status_display = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Station
|
|
fields = ('name', 'lat', 'lng', 'id', 'status', 'status_display')
|
|
|
|
def get_status_display(self, obj):
|
|
try:
|
|
return obj.get_status_display()
|
|
except AttributeError:
|
|
return None
|
|
|
|
|
|
class StationAllView(viewsets.ReadOnlyModelViewSet):
|
|
queryset = Station.objects.exclude(status=0)
|
|
serializer_class = StationSerializer
|
|
|
|
|
|
def index(request):
|
|
"""View to render index page."""
|
|
return render(request, 'base/home.html', {'mapbox_id': settings.MAPBOX_MAP_ID,
|
|
'mapbox_token': settings.MAPBOX_TOKEN})
|
|
|
|
|
|
def custom_404(request):
|
|
"""Custom 404 error handler."""
|
|
return HttpResponseNotFound(render(request, '404.html'))
|
|
|
|
|
|
def custom_500(request):
|
|
"""Custom 500 error handler."""
|
|
return HttpResponseServerError(render(request, '500.html'))
|
|
|
|
|
|
def robots(request):
|
|
data = render(request, 'robots.txt', {'environment': settings.ENVIRONMENT})
|
|
response = HttpResponse(data,
|
|
content_type='text/plain; charset=utf-8')
|
|
return response
|
|
|
|
|
|
@admin_required
|
|
def settings_site(request):
|
|
"""View to render settings page."""
|
|
if request.method == 'POST':
|
|
fetch_data.delay()
|
|
update_all_tle.delay()
|
|
messages.success(request, 'Data fetching task was triggered successfully!')
|
|
return redirect(reverse('users:view_user', kwargs={"username": request.user.username}))
|
|
return render(request, 'base/settings_site.html')
|
|
|
|
|
|
class ObservationListView(ListView):
|
|
"""
|
|
Displays a list of observations with pagination
|
|
"""
|
|
model = Observation
|
|
context_object_name = "observations"
|
|
paginate_by = settings.ITEMS_PER_PAGE
|
|
template_name = 'base/observations.html'
|
|
|
|
def get_queryset(self):
|
|
"""
|
|
Optionally filter based on norad get argument
|
|
Optionally filter based on future/good/bad/unvetted/failed
|
|
"""
|
|
norad_cat_id = self.request.GET.get('norad', '')
|
|
observer = self.request.GET.get('observer', '')
|
|
station = self.request.GET.get('station', '')
|
|
start = self.request.GET.get('start', '')
|
|
end = self.request.GET.get('end', '')
|
|
self.filtered = False
|
|
|
|
bad = self.request.GET.get('bad', '1')
|
|
if bad == '0':
|
|
bad = False
|
|
else:
|
|
bad = True
|
|
good = self.request.GET.get('good', '1')
|
|
if good == '0':
|
|
good = False
|
|
else:
|
|
good = True
|
|
unvetted = self.request.GET.get('unvetted', '1')
|
|
if unvetted == '0':
|
|
unvetted = False
|
|
else:
|
|
unvetted = True
|
|
future = self.request.GET.get('future', '1')
|
|
if future == '0':
|
|
future = False
|
|
else:
|
|
future = True
|
|
failed = self.request.GET.get('failed', '1')
|
|
if failed == '0':
|
|
failed = False
|
|
else:
|
|
failed = True
|
|
results = self.request.GET.getlist('results')
|
|
|
|
if False in (bad, good, unvetted, future, failed):
|
|
self.filtered = True
|
|
if results:
|
|
self.filtered = True
|
|
|
|
observations = Observation.objects.all()
|
|
if not norad_cat_id == '':
|
|
observations = observations.filter(
|
|
satellite__norad_cat_id=norad_cat_id)
|
|
self.filtered = True
|
|
if not observer == '':
|
|
observations = observations.filter(
|
|
author=observer)
|
|
self.filtered = True
|
|
if not station == '':
|
|
observations = observations.filter(
|
|
ground_station_id=station)
|
|
self.filtered = True
|
|
if not start == '':
|
|
observations = observations.filter(
|
|
start__gt=start)
|
|
self.filtered = True
|
|
if not end == '':
|
|
observations = observations.filter(
|
|
end__lt=end)
|
|
self.filtered = True
|
|
|
|
if not bad:
|
|
observations = observations.exclude(vetted_status='bad')
|
|
if not good:
|
|
observations = observations.exclude(vetted_status='good')
|
|
if not failed:
|
|
observations = observations.exclude(vetted_status='failed')
|
|
if not unvetted:
|
|
observations = observations.exclude(vetted_status='unknown', end__lte=now())
|
|
if not future:
|
|
observations = observations.exclude(vetted_status='unknown', end__gt=now())
|
|
if results:
|
|
if 'w0' in results:
|
|
observations = observations.filter(waterfall='')
|
|
elif 'w1' in results:
|
|
observations = observations.exclude(waterfall='')
|
|
if 'a0' in results:
|
|
observations = observations.exclude(archived=True).filter(payload='')
|
|
elif 'a1' in results:
|
|
observations = observations.exclude(archived=False, payload='')
|
|
if 'd0' in results:
|
|
observations = observations.filter(demoddata__payload_demod__isnull=True)
|
|
elif 'd1' in results:
|
|
observations = observations.exclude(demoddata__payload_demod__isnull=True)
|
|
return observations
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""
|
|
Need to add a list of satellites to the context for the template
|
|
"""
|
|
context = super(ObservationListView, self).get_context_data(**kwargs)
|
|
context['satellites'] = Satellite.objects.all()
|
|
context['authors'] = User.objects.annotate(obs_count=Count('observations')) \
|
|
.filter(obs_count__gt=0) \
|
|
.order_by('first_name', 'last_name', 'username')
|
|
context['stations'] = Station.objects.all().order_by('id')
|
|
norad_cat_id = self.request.GET.get('norad', None)
|
|
observer = self.request.GET.get('observer', None)
|
|
station = self.request.GET.get('station', None)
|
|
start = self.request.GET.get('start', None)
|
|
end = self.request.GET.get('end', None)
|
|
context['future'] = self.request.GET.get('future', '1')
|
|
context['bad'] = self.request.GET.get('bad', '1')
|
|
context['good'] = self.request.GET.get('good', '1')
|
|
context['unvetted'] = self.request.GET.get('unvetted', '1')
|
|
context['failed'] = self.request.GET.get('failed', '1')
|
|
context['results'] = self.request.GET.getlist('results')
|
|
context['filtered'] = self.filtered
|
|
if norad_cat_id is not None and norad_cat_id != '':
|
|
context['norad'] = int(norad_cat_id)
|
|
if observer is not None and observer != '':
|
|
context['observer_id'] = int(observer)
|
|
if station is not None and station != '':
|
|
context['station_id'] = int(station)
|
|
if start is not None and start != '':
|
|
context['start'] = start
|
|
if end is not None and end != '':
|
|
context['end'] = end
|
|
if 'scheduled' in self.request.session:
|
|
context['scheduled'] = self.request.session['scheduled']
|
|
try:
|
|
del self.request.session['scheduled']
|
|
except KeyError:
|
|
pass
|
|
context['can_schedule'] = schedule_perms(self.request.user)
|
|
return context
|
|
|
|
|
|
def observation_new_post(request):
|
|
ObservationFormSet = formset_factory(ObservationForm, formset=BaseObservationFormSet,
|
|
min_num=1, validate_min=True)
|
|
formset = ObservationFormSet(request.user, request.POST, prefix='obs')
|
|
try:
|
|
if formset.is_valid():
|
|
new_observations = []
|
|
for observation_data in formset.cleaned_data:
|
|
station = observation_data['ground_station']
|
|
start = observation_data['start']
|
|
end = observation_data['end']
|
|
transmitter_uuid = observation_data['transmitter_uuid']
|
|
transmitter = formset.transmitters[transmitter_uuid]
|
|
author = request.user
|
|
observation = create_new_observation(station=station, transmitter=transmitter,
|
|
start=start, end=end, author=author)
|
|
new_observations.append(observation)
|
|
|
|
total = formset.total_form_count()
|
|
|
|
for observation in new_observations:
|
|
observation.save()
|
|
|
|
try:
|
|
del request.session['scheduled']
|
|
except KeyError:
|
|
pass
|
|
request.session['scheduled'] = list(obs.id for obs in new_observations)
|
|
|
|
# If it's a single observation redirect to that one
|
|
if total == 1:
|
|
messages.success(request, 'Observation was scheduled successfully.')
|
|
return redirect(reverse('base:observation_view',
|
|
kwargs={'id': new_observations[0].id}))
|
|
|
|
messages.success(request, str(total) + ' Observations were scheduled successfully.')
|
|
return redirect(reverse('base:observations_list'))
|
|
|
|
else:
|
|
errors_list = [error for error in formset.errors if error]
|
|
if errors_list:
|
|
for field in errors_list[0]:
|
|
messages.error(request, '{0}'.format(errors_list[0][field][0]))
|
|
else:
|
|
messages.error(request, '{0}'.format(formset.non_form_errors()[0]))
|
|
return redirect(reverse('base:observation_new'))
|
|
except ValidationError as e:
|
|
messages.error(request, '{0}'.format(e.message))
|
|
return redirect(reverse('base:observation_new'))
|
|
except LatestTle.DoesNotExist:
|
|
message = 'Scheduling failed: Satellite without TLE'
|
|
messages.error(request, '{0}'.format(message))
|
|
return redirect(reverse('base:observation_new'))
|
|
except ObservationOverlapError as e:
|
|
messages.error(request, '{0}'.format(e.message))
|
|
return redirect(reverse('base:observation_new'))
|
|
except NegativeElevationError as e:
|
|
messages.error(request, '{0}'.format(e.message))
|
|
return redirect(reverse('base:observation_new'))
|
|
except SinglePassError as e:
|
|
messages.error(request, '{0}'.format(e.message))
|
|
return redirect(reverse('base:observation_new'))
|
|
|
|
|
|
@login_required
|
|
def observation_new(request):
|
|
"""View for new observation"""
|
|
can_schedule = schedule_perms(request.user)
|
|
if not can_schedule:
|
|
messages.error(request, 'You don\'t have permissions to schedule observations')
|
|
return redirect(reverse('base:observations_list'))
|
|
|
|
if request.method == 'POST':
|
|
return observation_new_post(request)
|
|
|
|
satellites = Satellite.objects.filter(status='alive')
|
|
|
|
obs_filter = {}
|
|
if request.method == 'GET':
|
|
filter_form = SatelliteFilterForm(request.GET)
|
|
if filter_form.is_valid():
|
|
start = filter_form.cleaned_data['start']
|
|
end = filter_form.cleaned_data['end']
|
|
ground_station = filter_form.cleaned_data['ground_station']
|
|
transmitter = filter_form.cleaned_data['transmitter']
|
|
norad = filter_form.cleaned_data['norad']
|
|
|
|
obs_filter['dates'] = False
|
|
if start and end: # Either give both dates or ignore if only one is given
|
|
start = datetime.strptime(start, '%Y/%m/%d %H:%M').strftime('%Y-%m-%d %H:%M')
|
|
end = (datetime.strptime(end, '%Y/%m/%d %H:%M') +
|
|
timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M')
|
|
obs_filter['start'] = start
|
|
obs_filter['end'] = end
|
|
obs_filter['dates'] = True
|
|
|
|
obs_filter['exists'] = True
|
|
if norad:
|
|
obs_filter['norad'] = norad
|
|
obs_filter['transmitter'] = transmitter # Add transmitter only if norad exists
|
|
if ground_station:
|
|
obs_filter['ground_station'] = ground_station
|
|
else:
|
|
obs_filter['exists'] = False
|
|
|
|
return render(request, 'base/observation_new.html',
|
|
{'satellites': satellites, 'obs_filter': obs_filter,
|
|
'date_min_start': settings.OBSERVATION_DATE_MIN_START,
|
|
'date_min_end': settings.OBSERVATION_DATE_MIN_END,
|
|
'date_max_range': settings.OBSERVATION_DATE_MAX_RANGE,
|
|
'warn_min_obs': settings.OBSERVATION_WARN_MIN_OBS})
|
|
|
|
|
|
@ajax_required
|
|
def prediction_windows(request):
|
|
sat_norad_id = request.POST['satellite']
|
|
transmitter = request.POST['transmitter']
|
|
start = request.POST['start']
|
|
end = request.POST['end']
|
|
station_ids = request.POST.getlist('stations[]', [])
|
|
min_horizon = request.POST.get('min_horizon', None)
|
|
overlapped = int(request.POST.get('overlapped', 0))
|
|
try:
|
|
sat = Satellite.objects.filter(status='alive').get(norad_cat_id=sat_norad_id)
|
|
except Satellite.DoesNotExist:
|
|
data = [{
|
|
'error': 'You should select a Satellite first.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
|
|
try:
|
|
tle = LatestTle.objects.get(satellite_id=sat.id)
|
|
except LatestTle.DoesNotExist:
|
|
data = [{
|
|
'error': 'No TLEs for this satellite yet.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
|
|
try:
|
|
transmitter = get_transmitter_by_uuid(transmitter)
|
|
if len(transmitter) == 0:
|
|
data = [{
|
|
'error': 'You should select a valid Transmitter.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
else:
|
|
downlink = transmitter[0]['downlink_low']
|
|
except DBConnectionError as e:
|
|
data = [{
|
|
'error': e.message
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
|
|
start = make_aware(datetime.strptime(start, '%Y-%m-%d %H:%M'), utc)
|
|
end = make_aware(datetime.strptime(end, '%Y-%m-%d %H:%M'), utc)
|
|
|
|
data = []
|
|
|
|
scheduled_obs_queryset = Observation.objects.filter(end__gt=now())
|
|
stations = Station.objects.filter(status__gt=0).prefetch_related(
|
|
Prefetch('observations',
|
|
queryset=scheduled_obs_queryset,
|
|
to_attr='scheduled_obs'),
|
|
'antenna')
|
|
if len(station_ids) > 0 and station_ids != ['']:
|
|
stations = stations.filter(id__in=station_ids)
|
|
if len(stations) == 0:
|
|
if len(station_ids) == 1:
|
|
data = [{
|
|
'error': 'Station is offline or it doesn\'t exist.'
|
|
}]
|
|
else:
|
|
data = [{
|
|
'error': 'Stations are offline or they don\'t exist.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
|
|
passes_found = defaultdict(list)
|
|
available_stations = get_available_stations(stations, downlink, request.user)
|
|
for station in available_stations:
|
|
station_passes, station_windows = predict_available_observation_windows(station,
|
|
min_horizon,
|
|
overlapped,
|
|
tle,
|
|
start,
|
|
end,
|
|
sat)
|
|
passes_found[station.id] = station_passes
|
|
if station_windows:
|
|
data.append({'id': station.id,
|
|
'name': station.name,
|
|
'status': station.status,
|
|
'lng': station.lng,
|
|
'lat': station.lat,
|
|
'alt': station.alt,
|
|
'window': station_windows})
|
|
|
|
if not data:
|
|
error_message = 'Satellite is always below horizon or ' \
|
|
'no free observation time available on visible stations.'
|
|
error_details = {}
|
|
for station in available_stations:
|
|
if station.id not in passes_found.keys():
|
|
error_details[station.id] = 'Satellite is always above or below horizon.\n'
|
|
else:
|
|
error_details[station.id] = 'No free observation time during passes available.\n'
|
|
|
|
data = [{'error': error_message,
|
|
'error_details': error_details,
|
|
'passes_found': passes_found}]
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
def observation_view(request, id):
|
|
"""View for single observation page."""
|
|
observation = get_object_or_404(Observation, id=id)
|
|
|
|
can_vet = vet_perms(request.user, observation)
|
|
|
|
can_delete = delete_perms(request.user, observation)
|
|
|
|
if observation.has_audio and not observation.audio_url:
|
|
messages.error(request, 'Audio file is not currently available,'
|
|
' if the problem persists please contact an administrator.')
|
|
|
|
if settings.ENVIRONMENT == 'production':
|
|
discuss_slug = 'https://community.libre.space/t/observation-{0}-{1}-{2}' \
|
|
.format(observation.id, slugify(observation.satellite.name),
|
|
observation.satellite.norad_cat_id)
|
|
discuss_url = ('https://community.libre.space/new-topic?title=Observation {0}: {1}'
|
|
' ({2})&body=Regarding [Observation {3}](http://{4}{5}) ...'
|
|
'&category=observations') \
|
|
.format(observation.id, observation.satellite.name,
|
|
observation.satellite.norad_cat_id, observation.id,
|
|
request.get_host(), request.path)
|
|
has_comments = True
|
|
apiurl = '{0}.json'.format(discuss_slug)
|
|
try:
|
|
urllib2.urlopen(apiurl).read()
|
|
except urllib2.URLError:
|
|
has_comments = False
|
|
|
|
return render(request, 'base/observation_view.html',
|
|
{'observation': observation, 'has_comments': has_comments,
|
|
'discuss_url': discuss_url, 'discuss_slug': discuss_slug,
|
|
'can_vet': can_vet, 'can_delete': can_delete})
|
|
|
|
return render(request, 'base/observation_view.html',
|
|
{'observation': observation, 'can_vet': can_vet,
|
|
'can_delete': can_delete})
|
|
|
|
|
|
@login_required
|
|
def observation_delete(request, id):
|
|
"""View for deleting observation."""
|
|
observation = get_object_or_404(Observation, id=id)
|
|
can_delete = delete_perms(request.user, observation)
|
|
if can_delete:
|
|
observation.delete()
|
|
messages.success(request, 'Observation deleted successfully.')
|
|
else:
|
|
messages.error(request, 'Permission denied.')
|
|
return redirect(reverse('base:observations_list'))
|
|
|
|
|
|
@login_required
|
|
@ajax_required
|
|
def observation_vet(request, id):
|
|
try:
|
|
observation = Observation.objects.get(id=id)
|
|
except Observation.DoesNotExist:
|
|
data = {
|
|
'error': 'Observation does not exist.'
|
|
}
|
|
return JsonResponse(data, safe=False)
|
|
|
|
status = request.POST.get('status', None)
|
|
can_vet = vet_perms(request.user, observation)
|
|
|
|
if status not in ['good', 'bad', 'failed', 'unknown']:
|
|
data = {
|
|
'error': 'Invalid status, select one of \'good\', \'bad\', \'failed\' and \'unknown\'.'
|
|
}
|
|
return JsonResponse(data, safe=False)
|
|
if not can_vet:
|
|
data = {
|
|
'error': 'Permission denied.'
|
|
}
|
|
return JsonResponse(data, safe=False)
|
|
|
|
observation.vetted_status = status
|
|
observation.vetted_user = request.user
|
|
observation.vetted_datetime = now()
|
|
observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
|
data = {
|
|
'vetted_status': observation.vetted_status,
|
|
'vetted_status_display': observation.get_vetted_status_display(),
|
|
'vetted_user': observation.vetted_user.displayname,
|
|
'vetted_datetime': observation.vetted_datetime.strftime('%Y-%m-%d %H:%M:%S')
|
|
}
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
def stations_list(request):
|
|
"""View to render Stations page."""
|
|
stations = Station.objects.annotate(total_obs=Count('observations'))
|
|
form = StationForm()
|
|
antennas = Antenna.objects.all()
|
|
online = stations.filter(status=2).count()
|
|
testing = stations.filter(status=1).count()
|
|
|
|
return render(request, 'base/stations.html',
|
|
{'stations': stations, 'form': form, 'antennas': antennas,
|
|
'online': online, 'testing': testing,
|
|
'mapbox_id': settings.MAPBOX_MAP_ID, 'mapbox_token': settings.MAPBOX_TOKEN})
|
|
|
|
|
|
def station_view(request, id):
|
|
"""View for single station page."""
|
|
station = get_object_or_404(Station, id=id)
|
|
form = StationForm(instance=station)
|
|
antennas = Antenna.objects.all()
|
|
unsupported_frequencies = request.GET.get('unsupported_frequencies', '0')
|
|
|
|
can_schedule = schedule_station_perms(request.user, station)
|
|
|
|
# Calculate uptime
|
|
uptime = '-'
|
|
try:
|
|
latest = StationStatusLog.objects.filter(station=station)[0]
|
|
except IndexError:
|
|
latest = None
|
|
if latest:
|
|
if latest.status:
|
|
try:
|
|
offline = StationStatusLog.objects.filter(station=station, status=0)[0]
|
|
uptime = latest.changed - offline.changed
|
|
except IndexError:
|
|
uptime = now() - latest.changed
|
|
uptime = str(uptime).split('.')[0]
|
|
|
|
if request.user.is_authenticated():
|
|
if request.user == station.owner:
|
|
wiki_help = ('<a href="{0}" target="_blank" class="wiki-help"><span class="glyphicon '
|
|
'glyphicon-question-sign" aria-hidden="true"></span>'
|
|
'</a>'.format(settings.WIKI_STATION_URL))
|
|
if station.is_offline:
|
|
messages.error(request, ('Your Station is offline. You should make '
|
|
'sure it can successfully connect to the Network API. '
|
|
'{0}'.format(wiki_help)))
|
|
if station.is_testing:
|
|
messages.warning(request, ('Your Station is in Testing mode. Once you are sure '
|
|
'it returns good observations you can put it online. '
|
|
'{0}'.format(wiki_help)))
|
|
|
|
return render(request, 'base/station_view.html',
|
|
{'station': station, 'form': form, 'antennas': antennas,
|
|
'mapbox_id': settings.MAPBOX_MAP_ID,
|
|
'mapbox_token': settings.MAPBOX_TOKEN,
|
|
'can_schedule': can_schedule,
|
|
'unsupported_frequencies': unsupported_frequencies,
|
|
'uptime': uptime})
|
|
|
|
|
|
def station_log(request, id):
|
|
"""View for single station status log."""
|
|
station = get_object_or_404(Station, id=id)
|
|
station_log = StationStatusLog.objects.filter(station=station)
|
|
|
|
return render(request, 'base/station_log.html',
|
|
{'station': station, 'station_log': station_log})
|
|
|
|
|
|
@ajax_required
|
|
def scheduling_stations(request):
|
|
"""Returns json with stations on which user has permissions to schedule"""
|
|
uuid = request.POST.get('transmitter', None)
|
|
if uuid is None:
|
|
data = [{
|
|
'error': 'You should select a Transmitter.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
else:
|
|
try:
|
|
transmitter = get_transmitter_by_uuid(uuid)
|
|
if len(transmitter) == 0:
|
|
data = [{
|
|
'error': 'You should select a Transmitter.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
else:
|
|
downlink = transmitter[0]['downlink_low']
|
|
if downlink is None:
|
|
data = [{
|
|
'error': 'You should select a valid Transmitter.'
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
except DBConnectionError as e:
|
|
data = [{
|
|
'error': e.message
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
|
|
stations = Station.objects.filter(status__gt=0)
|
|
available_stations = get_available_stations(stations, downlink, request.user)
|
|
import sys
|
|
sys.stdout.flush()
|
|
data = {
|
|
'stations': StationSerializer(available_stations, many=True).data,
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
@ajax_required
|
|
def pass_predictions(request, id):
|
|
"""Endpoint for pass predictions"""
|
|
scheduled_obs_queryset = Observation.objects.filter(end__gt=now())
|
|
station = get_object_or_404(
|
|
Station.objects.prefetch_related(
|
|
Prefetch('observations',
|
|
queryset=scheduled_obs_queryset,
|
|
to_attr='scheduled_obs'),
|
|
'antenna'),
|
|
id=id)
|
|
unsupported_frequencies = request.GET.get('unsupported_frequencies', '0')
|
|
|
|
try:
|
|
latest_tle_queryset = LatestTle.objects.all()
|
|
satellites = Satellite.objects.filter(
|
|
status='alive'
|
|
).prefetch_related(
|
|
Prefetch('tles', queryset=latest_tle_queryset, to_attr='tle')
|
|
)
|
|
except Satellite.DoesNotExist:
|
|
pass # we won't have any next passes to display
|
|
|
|
# Load the station information and invoke ephem so we can
|
|
# calculate upcoming passes for the station
|
|
observer = ephem.Observer()
|
|
observer.lon = str(station.lng)
|
|
observer.lat = str(station.lat)
|
|
observer.elevation = station.alt
|
|
observer.horizon = str(station.horizon)
|
|
|
|
nextpasses = []
|
|
start = make_aware(datetime.utcnow(), utc)
|
|
end = make_aware(datetime.utcnow() + timedelta(hours=settings.STATION_UPCOMING_END), utc)
|
|
observation_min_start = (
|
|
datetime.utcnow() + timedelta(minutes=settings.OBSERVATION_DATE_MIN_START)
|
|
).strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
|
|
try:
|
|
all_transmitters = get_transmitters_by_status('active')
|
|
except DBConnectionError:
|
|
all_transmitters = []
|
|
|
|
if all_transmitters:
|
|
for satellite in satellites:
|
|
# look for a match between transmitters from the satellite and
|
|
# ground station antenna frequency capabilities
|
|
if int(unsupported_frequencies) == 0:
|
|
norad_id = satellite.norad_cat_id
|
|
transmitters = [t for t in all_transmitters if t['norad_cat_id'] == norad_id and
|
|
is_transmitter_in_station_range(t, station)]
|
|
if not transmitters:
|
|
continue
|
|
|
|
if satellite.tle:
|
|
tle = satellite.tle[0]
|
|
else:
|
|
continue
|
|
|
|
station_passes, station_windows = predict_available_observation_windows(station,
|
|
None,
|
|
2,
|
|
tle,
|
|
start,
|
|
end,
|
|
satellite)
|
|
|
|
if station_windows:
|
|
satellite_stats = satellite_stats_by_transmitter_list(transmitters)
|
|
for window in station_windows:
|
|
valid = window['start'] > observation_min_start and window['valid_duration']
|
|
window_start = datetime.strptime(window['start'], '%Y-%m-%d %H:%M:%S.%f')
|
|
window_end = datetime.strptime(window['end'], '%Y-%m-%d %H:%M:%S.%f')
|
|
sat_pass = {'name': str(satellite.name),
|
|
'id': str(satellite.id),
|
|
'success_rate': str(satellite_stats['success_rate']),
|
|
'bad_rate': str(satellite_stats['bad_rate']),
|
|
'unvetted_rate': str(satellite_stats['unvetted_rate']),
|
|
'future_rate': str(satellite_stats['future_rate']),
|
|
'total_count': str(satellite_stats['total_count']),
|
|
'good_count': str(satellite_stats['good_count']),
|
|
'bad_count': str(satellite_stats['bad_count']),
|
|
'unvetted_count': str(satellite_stats['unvetted_count']),
|
|
'future_count': str(satellite_stats['future_count']),
|
|
'norad_cat_id': str(satellite.norad_cat_id),
|
|
'tle1': window['tle1'],
|
|
'tle2': window['tle2'],
|
|
'tr': window_start, # Rise time
|
|
'azr': window['az_start'], # Rise Azimuth
|
|
'altt': window['elev_max'], # Max altitude
|
|
'ts': window_end, # Set time
|
|
'azs': window['az_end'], # Set azimuth
|
|
'valid': valid,
|
|
'overlapped': window['overlapped'],
|
|
'overlap_ratio': window['overlap_ratio']}
|
|
nextpasses.append(sat_pass)
|
|
|
|
data = {
|
|
'id': id,
|
|
'nextpasses': sorted(nextpasses, key=itemgetter('tr')),
|
|
'ground_station': {'lng': str(station.lng),
|
|
'lat': str(station.lat),
|
|
'alt': station.alt}
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
def station_edit(request, id=None):
|
|
"""Edit or add a single station."""
|
|
station = None
|
|
antennas = Antenna.objects.all()
|
|
if id:
|
|
station = get_object_or_404(Station, id=id, owner=request.user)
|
|
|
|
if request.method == 'POST':
|
|
if station:
|
|
form = StationForm(request.POST, request.FILES, instance=station)
|
|
else:
|
|
form = StationForm(request.POST, request.FILES)
|
|
if form.is_valid():
|
|
f = form.save(commit=False)
|
|
if not station:
|
|
f.testing = True
|
|
f.owner = request.user
|
|
f.save()
|
|
form.save_m2m()
|
|
messages.success(request, 'Ground Station saved successfully.')
|
|
return redirect(reverse('base:station_view', kwargs={'id': f.id}))
|
|
else:
|
|
messages.error(request, ('Your Ground Station submission has some '
|
|
'errors. {0}').format(form.errors))
|
|
return render(request, 'base/station_edit.html',
|
|
{'form': form, 'station': station, 'antennas': antennas})
|
|
else:
|
|
if station:
|
|
form = StationForm(instance=station)
|
|
else:
|
|
form = StationForm()
|
|
return render(request, 'base/station_edit.html',
|
|
{'form': form, 'station': station, 'antennas': antennas})
|
|
|
|
|
|
@login_required
|
|
def station_delete(request, id):
|
|
"""View for deleting a station."""
|
|
me = request.user
|
|
station = get_object_or_404(Station, id=id, owner=request.user)
|
|
station.delete()
|
|
messages.success(request, 'Ground Station deleted successfully.')
|
|
return redirect(reverse('users:view_user', kwargs={'username': me}))
|
|
|
|
|
|
def transmitters_with_stats(transmitters_list):
|
|
transmitters_with_stats_list = []
|
|
for transmitter in transmitters_list:
|
|
transmitter_stats = transmitter_stats_by_uuid(transmitter['uuid'])
|
|
transmitter_with_stats = dict(transmitter, **transmitter_stats)
|
|
transmitters_with_stats_list.append(transmitter_with_stats)
|
|
return transmitters_with_stats_list
|
|
|
|
|
|
def satellite_view(request, id):
|
|
try:
|
|
sat = Satellite.objects.get(norad_cat_id=id)
|
|
except Satellite.DoesNotExist:
|
|
data = {
|
|
'error': 'Unable to find that satellite.'
|
|
}
|
|
return JsonResponse(data, safe=False)
|
|
|
|
try:
|
|
transmitters = get_transmitters_by_norad_id(norad_id=id)
|
|
except DBConnectionError as e:
|
|
data = [{
|
|
'error': e.message
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
satellite_stats = satellite_stats_by_transmitter_list(transmitters)
|
|
data = {
|
|
'id': id,
|
|
'name': sat.name,
|
|
'names': sat.names,
|
|
'image': sat.image,
|
|
'success_rate': satellite_stats['success_rate'],
|
|
'good_count': satellite_stats['good_count'],
|
|
'bad_count': satellite_stats['bad_count'],
|
|
'unvetted_count': satellite_stats['unvetted_count'],
|
|
'future_count': satellite_stats['future_count'],
|
|
'total_count': satellite_stats['total_count'],
|
|
'transmitters': transmitters_with_stats(transmitters)
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
def transmitters_view(request):
|
|
norad_id = request.POST['satellite']
|
|
station_id = request.POST.get('station_id', None)
|
|
try:
|
|
Satellite.objects.get(norad_cat_id=norad_id)
|
|
except Satellite.DoesNotExist:
|
|
data = {
|
|
'error': 'Unable to find that satellite.'
|
|
}
|
|
return JsonResponse(data, safe=False)
|
|
|
|
try:
|
|
transmitters = get_transmitters_by_norad_id(norad_id)
|
|
except DBConnectionError as e:
|
|
data = [{
|
|
'error': e.message
|
|
}]
|
|
return JsonResponse(data, safe=False)
|
|
|
|
transmitters = [t for t in transmitters
|
|
if t['status'] == 'active' and t['downlink_low'] is not None]
|
|
if station_id:
|
|
supported_transmitters = []
|
|
station = Station.objects.get(id=station_id)
|
|
for transmitter in transmitters:
|
|
transmitter_supported = is_transmitter_in_station_range(transmitter, station)
|
|
if transmitter_supported:
|
|
supported_transmitters.append(transmitter)
|
|
transmitters = supported_transmitters
|
|
|
|
data = {
|
|
'transmitters': transmitters_with_stats(transmitters)
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|