2015-08-28 02:38:49 -06:00
|
|
|
import urllib2
|
2014-10-25 15:47:24 -06:00
|
|
|
import ephem
|
2015-11-16 06:18:46 -07:00
|
|
|
import math
|
|
|
|
from operator import itemgetter
|
2014-10-25 15:47:24 -06:00
|
|
|
from datetime import datetime, timedelta
|
2015-07-22 05:16:15 -06:00
|
|
|
from StringIO import StringIO
|
2014-10-25 15:47:24 -06:00
|
|
|
|
2017-03-20 09:36:54 -06:00
|
|
|
from django.db.models import Count, Case, When, F
|
2015-04-11 11:32:44 -06:00
|
|
|
from django.conf import settings
|
2014-12-13 11:45:52 -07:00
|
|
|
from django.contrib import messages
|
2014-10-26 18:01:59 -06:00
|
|
|
from django.contrib.auth.decorators import login_required
|
2015-07-22 05:16:15 -06:00
|
|
|
from django.core.management import call_command
|
2017-03-20 09:36:54 -06:00
|
|
|
from django.core.urlresolvers import reverse
|
2017-02-21 15:21:09 -07:00
|
|
|
from django.http import JsonResponse, HttpResponseNotFound, HttpResponseServerError, HttpResponse
|
|
|
|
from django.shortcuts import get_object_or_404, render, redirect
|
|
|
|
from django.utils.timezone import now, make_aware, utc
|
|
|
|
from django.utils.text import slugify
|
|
|
|
from django.views.decorators.http import require_POST
|
2016-12-20 19:51:43 -07:00
|
|
|
from django.views.generic import ListView
|
|
|
|
|
2015-08-11 02:31:03 -06:00
|
|
|
from rest_framework import serializers, viewsets
|
|
|
|
|
2015-07-23 09:18:01 -06:00
|
|
|
from network.base.models import (Station, Transmitter, Observation,
|
2015-09-09 02:12:36 -06:00
|
|
|
Data, Satellite, Antenna, Tle, Rig)
|
2016-04-07 16:05:50 -06:00
|
|
|
from network.base.forms import StationForm, SatelliteFilterForm
|
2015-07-22 05:16:15 -06:00
|
|
|
from network.base.decorators import admin_required
|
2017-03-20 09:36:54 -06:00
|
|
|
from network.base.helpers import calculate_polar_data, resolve_overlaps
|
2014-09-08 11:36:12 -06:00
|
|
|
|
2014-09-08 13:07:15 -06:00
|
|
|
|
2015-08-11 02:31:03 -06:00
|
|
|
class StationSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = Station
|
|
|
|
fields = ('name', 'lat', 'lng')
|
|
|
|
|
|
|
|
|
|
|
|
class StationAllView(viewsets.ReadOnlyModelViewSet):
|
2016-05-06 15:04:08 -06:00
|
|
|
queryset = Station.objects.filter(active=True)
|
2015-08-11 02:31:03 -06:00
|
|
|
serializer_class = StationSerializer
|
|
|
|
|
|
|
|
|
2016-12-21 04:50:02 -07:00
|
|
|
def satellite_position(request, sat_id):
|
|
|
|
sat = get_object_or_404(Satellite, norad_cat_id=sat_id)
|
2017-03-11 13:29:31 -07:00
|
|
|
try:
|
|
|
|
satellite = ephem.readtle(
|
|
|
|
str(sat.latest_tle.tle0),
|
|
|
|
str(sat.latest_tle.tle1),
|
|
|
|
str(sat.latest_tle.tle2)
|
|
|
|
)
|
|
|
|
except:
|
|
|
|
data = {}
|
|
|
|
else:
|
|
|
|
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
satellite.compute(now)
|
|
|
|
data = {
|
|
|
|
'lon': '{0}'.format(satellite.sublong),
|
|
|
|
'lat': '{0}'.format(satellite.sublat)
|
|
|
|
}
|
2016-12-21 04:50:02 -07:00
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
|
|
|
2014-09-08 11:36:12 -06:00
|
|
|
def index(request):
|
|
|
|
"""View to render index page."""
|
2017-05-19 14:44:30 -06:00
|
|
|
observations = Observation.objects.all()
|
2015-05-11 11:05:31 -06:00
|
|
|
try:
|
|
|
|
featured_station = Station.objects.filter(active=True).latest('featured_date')
|
|
|
|
except Station.DoesNotExist:
|
|
|
|
featured_station = None
|
2014-09-08 11:36:12 -06:00
|
|
|
|
|
|
|
ctx = {
|
2016-04-09 10:19:47 -06:00
|
|
|
'latest_observations': observations.filter(end__lt=now()).order_by('-id')[:10],
|
2014-09-08 11:36:12 -06:00
|
|
|
'scheduled_observations': observations.filter(end__gte=now()),
|
2015-04-11 11:32:44 -06:00
|
|
|
'featured_station': featured_station,
|
|
|
|
'mapbox_id': settings.MAPBOX_MAP_ID,
|
|
|
|
'mapbox_token': settings.MAPBOX_TOKEN
|
2014-09-08 11:36:12 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return render(request, 'base/home.html', ctx)
|
2014-09-08 13:07:15 -06:00
|
|
|
|
|
|
|
|
2015-07-21 02:42:23 -06:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2015-07-22 05:16:15 -06:00
|
|
|
@admin_required
|
|
|
|
def settings_site(request):
|
|
|
|
"""View to render settings page."""
|
|
|
|
if request.method == 'POST':
|
|
|
|
if request.POST['fetch']:
|
|
|
|
try:
|
2016-01-26 08:05:17 -07:00
|
|
|
data_out = StringIO()
|
|
|
|
tle_out = StringIO()
|
|
|
|
call_command('fetch_data', stdout=data_out)
|
|
|
|
call_command('update_all_tle', stdout=tle_out)
|
|
|
|
request.session['settings_out'] = data_out.getvalue() + tle_out.getvalue()
|
2015-07-22 05:16:15 -06:00
|
|
|
except:
|
|
|
|
messages.error(request, 'fetch command failed.')
|
|
|
|
return redirect(reverse('base:settings_site'))
|
|
|
|
|
2016-01-26 08:05:17 -07:00
|
|
|
fetch_out = request.session.get('settings_out', False)
|
2015-07-22 05:16:15 -06:00
|
|
|
if fetch_out:
|
2016-01-26 08:05:17 -07:00
|
|
|
del request.session['settings_out']
|
2015-07-22 05:16:15 -06:00
|
|
|
return render(request, 'base/settings_site.html', {'fetch_data': fetch_out})
|
|
|
|
return render(request, 'base/settings_site.html')
|
|
|
|
|
|
|
|
|
2016-12-20 19:51:43 -07:00
|
|
|
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
|
2017-02-03 20:20:32 -07:00
|
|
|
Optionally filter based on good/bad/unvetted
|
2016-12-20 19:51:43 -07:00
|
|
|
"""
|
2017-02-03 20:20:32 -07:00
|
|
|
norad_cat_id = self.request.GET.get('norad', '')
|
|
|
|
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
|
|
|
|
|
|
|
|
if norad_cat_id == '':
|
|
|
|
observations = Observation.objects.all().order_by('-id')
|
2016-12-20 19:51:43 -07:00
|
|
|
else:
|
2017-02-03 20:20:32 -07:00
|
|
|
observations = Observation.objects.filter(
|
|
|
|
satellite__norad_cat_id=norad_cat_id).order_by('-id')
|
|
|
|
|
2017-02-24 03:57:19 -07:00
|
|
|
# Add the data subqueries as annotations
|
|
|
|
observations = observations.annotate(
|
|
|
|
data_count=Count('data', distinct=True),
|
|
|
|
nodata_count=Count(
|
|
|
|
Case(
|
|
|
|
When(data__vetted_status='no_data', then=1)
|
|
|
|
), distinct=True
|
|
|
|
),
|
|
|
|
unknown_count=Count(
|
|
|
|
Case(
|
|
|
|
When(data__vetted_status='unknown', then=1)
|
|
|
|
), distinct=True
|
|
|
|
),
|
|
|
|
vetted_count=Count(
|
|
|
|
Case(
|
|
|
|
When(data__vetted_status='verified', then=1)
|
|
|
|
), distinct=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
# Start with an empty queryset and add each filter as an or/union
|
|
|
|
resultset = Observation.objects.none()
|
|
|
|
if bad:
|
|
|
|
resultset |= observations.filter(nodata_count=F('data_count'))
|
|
|
|
if good:
|
|
|
|
resultset |= observations.filter(vetted_count__gt=0)
|
|
|
|
if unvetted:
|
|
|
|
resultset |= observations.filter(unknown_count__gt=0)
|
2017-02-03 20:20:32 -07:00
|
|
|
return resultset
|
2016-12-20 19:51:43 -07:00
|
|
|
|
|
|
|
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()
|
|
|
|
norad_cat_id = self.request.GET.get('norad', None)
|
2017-02-03 20:20:32 -07:00
|
|
|
context['bad'] = self.request.GET.get('bad', '1')
|
|
|
|
context['good'] = self.request.GET.get('good', '1')
|
|
|
|
context['unvetted'] = self.request.GET.get('unvetted', '1')
|
2016-12-20 19:51:43 -07:00
|
|
|
if norad_cat_id is not None and norad_cat_id != '':
|
|
|
|
context['norad'] = int(norad_cat_id)
|
|
|
|
return context
|
2014-09-18 07:34:39 -06:00
|
|
|
|
|
|
|
|
2014-10-26 18:01:59 -06:00
|
|
|
@login_required
|
2014-10-26 14:07:42 -06:00
|
|
|
def observation_new(request):
|
|
|
|
"""View for new observation"""
|
2014-10-26 18:01:59 -06:00
|
|
|
me = request.user
|
|
|
|
if request.method == 'POST':
|
2014-10-26 19:14:26 -06:00
|
|
|
sat_id = request.POST.get('satellite')
|
2015-07-23 09:18:01 -06:00
|
|
|
trans_id = request.POST.get('transmitter')
|
2016-04-23 11:14:50 -06:00
|
|
|
try:
|
|
|
|
start_time = datetime.strptime(request.POST.get('start-time'), '%Y-%m-%d %H:%M')
|
|
|
|
end_time = datetime.strptime(request.POST.get('end-time'), '%Y-%m-%d %H:%M')
|
|
|
|
except ValueError:
|
|
|
|
messages.error(request, 'Please use the datetime dialogs to submit valid values.')
|
|
|
|
return redirect(reverse('base:observation_new'))
|
|
|
|
|
2016-05-05 08:50:11 -06:00
|
|
|
if (end_time - start_time) > timedelta(minutes=int(settings.DATE_MAX_RANGE)):
|
2016-04-23 11:14:50 -06:00
|
|
|
messages.error(request, 'Please use the datetime dialogs to submit valid timeframe.')
|
|
|
|
return redirect(reverse('base:observation_new'))
|
|
|
|
|
2015-04-16 15:42:46 -06:00
|
|
|
start = make_aware(start_time, utc)
|
|
|
|
end = make_aware(end_time, utc)
|
2014-10-26 19:14:26 -06:00
|
|
|
sat = Satellite.objects.get(norad_cat_id=sat_id)
|
2015-07-23 09:18:01 -06:00
|
|
|
trans = Transmitter.objects.get(id=trans_id)
|
2016-01-23 02:40:56 -07:00
|
|
|
tle = Tle.objects.get(id=sat.latest_tle.id)
|
|
|
|
obs = Observation(satellite=sat, transmitter=trans, tle=tle,
|
2014-10-26 18:01:59 -06:00
|
|
|
author=me, start=start, end=end)
|
|
|
|
obs.save()
|
2015-04-16 15:42:46 -06:00
|
|
|
|
2017-03-04 13:50:02 -07:00
|
|
|
sat_ephem = ephem.readtle(str(sat.latest_tle.tle0),
|
|
|
|
str(sat.latest_tle.tle1),
|
|
|
|
str(sat.latest_tle.tle2))
|
|
|
|
observer = ephem.Observer()
|
|
|
|
observer.date = str(start)
|
|
|
|
|
2014-10-26 18:01:59 -06:00
|
|
|
total = int(request.POST.get('total'))
|
2015-04-16 15:42:46 -06:00
|
|
|
|
2014-10-26 19:14:26 -06:00
|
|
|
for item in range(total):
|
2015-04-16 15:42:46 -06:00
|
|
|
start = datetime.strptime(
|
|
|
|
request.POST.get('{0}-starting_time'.format(item)), '%Y-%m-%d %H:%M:%S.%f'
|
|
|
|
)
|
|
|
|
end = datetime.strptime(
|
|
|
|
request.POST.get('{}-ending_time'.format(item)), '%Y-%m-%d %H:%M:%S.%f'
|
|
|
|
)
|
2014-10-26 19:14:26 -06:00
|
|
|
station_id = request.POST.get('{}-station'.format(item))
|
2014-10-26 18:01:59 -06:00
|
|
|
ground_station = Station.objects.get(id=station_id)
|
2017-03-04 13:50:02 -07:00
|
|
|
observer.lon = str(ground_station.lng)
|
|
|
|
observer.lat = str(ground_station.lat)
|
|
|
|
observer.elevation = ground_station.alt
|
|
|
|
tr, azr, tt, altt, ts, azs = observer.next_pass(sat_ephem)
|
|
|
|
|
2015-04-16 15:42:46 -06:00
|
|
|
Data.objects.create(start=make_aware(start, utc), end=make_aware(end, utc),
|
2017-03-04 13:50:02 -07:00
|
|
|
ground_station=ground_station, observation=obs,
|
|
|
|
rise_azimuth=format(math.degrees(azr), '.0f'),
|
|
|
|
max_altitude=format(math.degrees(altt), '.0f'),
|
|
|
|
set_azimuth=format(math.degrees(azs), '.0f'))
|
2017-07-30 11:53:02 -06:00
|
|
|
time_start_new = ephem.Date(ts).datetime() + timedelta(minutes=1)
|
|
|
|
observer.date = time_start_new.strftime("%Y-%m-%d %H:%M:%S.%f")
|
2014-10-26 19:14:26 -06:00
|
|
|
|
2014-12-19 06:06:58 -07:00
|
|
|
return redirect(reverse('base:observation_view', kwargs={'id': obs.id}))
|
2014-10-26 18:01:59 -06:00
|
|
|
|
2017-08-12 12:56:34 -06:00
|
|
|
satellites = Satellite.objects.filter(transmitters__alive=True) \
|
|
|
|
.filter(status='alive').distinct()
|
2015-07-23 09:18:01 -06:00
|
|
|
transmitters = Transmitter.objects.filter(alive=True)
|
2014-10-26 14:07:42 -06:00
|
|
|
|
2016-05-09 06:29:23 -06:00
|
|
|
obs_filter = {}
|
2016-04-08 04:14:19 -06:00
|
|
|
if request.method == 'GET':
|
2016-05-09 06:29:23 -06:00
|
|
|
filter_form = SatelliteFilterForm(request.GET)
|
|
|
|
if filter_form.is_valid():
|
2017-01-28 09:53:19 -07:00
|
|
|
start_date = filter_form.cleaned_data['start_date']
|
|
|
|
end_date = filter_form.cleaned_data['end_date']
|
|
|
|
ground_station = filter_form.cleaned_data['ground_station']
|
|
|
|
norad = filter_form.cleaned_data['norad']
|
|
|
|
|
|
|
|
if start_date:
|
|
|
|
start_date = datetime.strptime(start_date,
|
|
|
|
'%Y/%m/%d %H:%M').strftime('%Y-%m-%d %H:%M')
|
|
|
|
if end_date:
|
|
|
|
end_date = (datetime.strptime(end_date, '%Y/%m/%d %H:%M') +
|
|
|
|
timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M')
|
2016-05-09 06:29:23 -06:00
|
|
|
obs_filter['exists'] = True
|
2017-01-28 09:53:19 -07:00
|
|
|
obs_filter['norad'] = norad
|
2016-05-09 06:29:23 -06:00
|
|
|
obs_filter['start_date'] = start_date
|
|
|
|
obs_filter['end_date'] = end_date
|
2017-03-04 12:10:26 -07:00
|
|
|
if ground_station:
|
|
|
|
obs_filter['ground_station'] = ground_station
|
2016-05-09 06:29:23 -06:00
|
|
|
else:
|
|
|
|
obs_filter['exists'] = False
|
2016-04-08 04:14:19 -06:00
|
|
|
|
2015-04-22 16:19:56 -06:00
|
|
|
return render(request, 'base/observation_new.html',
|
|
|
|
{'satellites': satellites,
|
2016-05-09 06:29:23 -06:00
|
|
|
'transmitters': transmitters, 'obs_filter': obs_filter,
|
2015-04-22 16:19:56 -06:00
|
|
|
'date_min_start': settings.DATE_MIN_START,
|
2016-10-28 10:40:08 -06:00
|
|
|
'date_min_end': settings.DATE_MIN_END,
|
2015-04-22 16:19:56 -06:00
|
|
|
'date_max_range': settings.DATE_MAX_RANGE})
|
2014-10-26 14:07:42 -06:00
|
|
|
|
|
|
|
|
2017-03-19 12:03:58 -06:00
|
|
|
def prediction_windows(request, sat_id, transmitter, start_date, end_date,
|
|
|
|
station_id=None):
|
2015-04-16 15:44:10 -06:00
|
|
|
try:
|
2017-08-12 12:56:34 -06:00
|
|
|
sat = Satellite.objects.filter(transmitters__alive=True) \
|
|
|
|
.filter(status='alive').distinct().get(norad_cat_id=sat_id)
|
2015-04-16 15:44:10 -06:00
|
|
|
except:
|
|
|
|
data = {
|
|
|
|
'error': 'You should select a Satellite first.'
|
|
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
2016-01-22 10:48:07 -07:00
|
|
|
|
2016-01-26 06:52:06 -07:00
|
|
|
try:
|
2016-02-20 10:31:42 -07:00
|
|
|
satellite = ephem.readtle(
|
|
|
|
str(sat.latest_tle.tle0),
|
|
|
|
str(sat.latest_tle.tle1),
|
|
|
|
str(sat.latest_tle.tle2)
|
|
|
|
)
|
2016-01-26 06:52:06 -07:00
|
|
|
except:
|
|
|
|
data = {
|
|
|
|
'error': 'No TLEs for this satellite yet.'
|
|
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
2014-10-25 15:47:24 -06:00
|
|
|
|
2017-03-19 12:03:58 -06:00
|
|
|
try:
|
|
|
|
downlink = Transmitter.objects.get(id=int(transmitter)).downlink_low
|
|
|
|
except:
|
|
|
|
data = {
|
|
|
|
'error': 'You should select a Transmitter first.'
|
|
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
2014-10-26 08:46:03 -06:00
|
|
|
end_date = datetime.strptime(end_date, '%Y-%m-%d %H:%M')
|
2014-10-25 15:47:24 -06:00
|
|
|
|
2014-10-26 14:07:42 -06:00
|
|
|
data = []
|
2014-10-25 15:47:24 -06:00
|
|
|
|
|
|
|
stations = Station.objects.all()
|
2016-05-09 06:29:23 -06:00
|
|
|
if station_id:
|
|
|
|
stations = stations.filter(id=station_id)
|
2014-10-25 15:47:24 -06:00
|
|
|
for station in stations:
|
2015-05-06 02:53:48 -06:00
|
|
|
if not station.online:
|
|
|
|
continue
|
2017-03-19 12:03:58 -06:00
|
|
|
|
2017-03-20 09:36:54 -06:00
|
|
|
# Skip if this station is not capable of receiving the frequency
|
|
|
|
if not downlink:
|
|
|
|
continue
|
2017-03-19 12:03:58 -06:00
|
|
|
frequency_supported = False
|
|
|
|
for gs_antenna in station.antenna.all():
|
2017-03-20 09:36:54 -06:00
|
|
|
if (gs_antenna.frequency <= downlink <= gs_antenna.frequency_max):
|
|
|
|
frequency_supported = True
|
2017-03-19 12:03:58 -06:00
|
|
|
if not frequency_supported:
|
|
|
|
continue
|
|
|
|
|
2014-10-25 15:47:24 -06:00
|
|
|
observer = ephem.Observer()
|
|
|
|
observer.lon = str(station.lng)
|
|
|
|
observer.lat = str(station.lat)
|
|
|
|
observer.elevation = station.alt
|
2014-10-26 08:46:03 -06:00
|
|
|
observer.date = str(start_date)
|
2014-10-25 15:47:24 -06:00
|
|
|
station_match = False
|
2014-10-26 14:07:42 -06:00
|
|
|
keep_digging = True
|
|
|
|
while keep_digging:
|
2015-04-16 15:44:10 -06:00
|
|
|
try:
|
|
|
|
tr, azr, tt, altt, ts, azs = observer.next_pass(satellite)
|
|
|
|
except ValueError:
|
|
|
|
data = {
|
|
|
|
'error': 'That satellite seems to stay always below your horizon.'
|
|
|
|
}
|
|
|
|
break
|
2014-10-26 14:07:42 -06:00
|
|
|
|
2016-03-18 19:59:59 -06:00
|
|
|
# no match if the sat will not rise above the configured min horizon
|
|
|
|
elevation = format(math.degrees(altt), '.0f')
|
|
|
|
if float(elevation) >= station.horizon:
|
|
|
|
if ephem.Date(tr).datetime() < end_date:
|
|
|
|
if ephem.Date(ts).datetime() > end_date:
|
|
|
|
ts = end_date
|
|
|
|
keep_digging = False
|
|
|
|
else:
|
|
|
|
time_start_new = ephem.Date(ts).datetime() + timedelta(minutes=1)
|
|
|
|
observer.date = time_start_new.strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
|
|
|
|
|
|
# Adjust or discard window if overlaps exist
|
|
|
|
window_start = make_aware(ephem.Date(tr).datetime(), utc)
|
|
|
|
window_end = make_aware(ephem.Date(ts).datetime(), utc)
|
2017-03-20 09:36:54 -06:00
|
|
|
|
|
|
|
# Check if overlaps with existing scheduled observations
|
|
|
|
gs_data = Data.objects.filter(ground_station=station)
|
|
|
|
window = resolve_overlaps(station, gs_data, window_start, window_end)
|
|
|
|
|
2016-03-18 19:59:59 -06:00
|
|
|
if window:
|
|
|
|
if not station_match:
|
|
|
|
station_windows = {
|
|
|
|
'id': station.id,
|
|
|
|
'name': station.name,
|
|
|
|
'window': []
|
|
|
|
}
|
|
|
|
station_match = True
|
|
|
|
window_start = window[0]
|
|
|
|
window_end = window[1]
|
2015-08-16 07:25:25 -06:00
|
|
|
station_windows['window'].append(
|
|
|
|
{
|
|
|
|
'start': window_start.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
|
|
|
'end': window_end.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
|
|
|
'az_start': azr
|
|
|
|
})
|
2016-03-18 19:59:59 -06:00
|
|
|
# In case our window was split in two
|
|
|
|
try:
|
|
|
|
window_start = window[2]
|
|
|
|
window_end = window[3]
|
|
|
|
station_windows['window'].append(
|
|
|
|
{
|
|
|
|
'start': window_start.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
|
|
|
'end': window_end.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
|
|
|
'az_start': azr
|
|
|
|
})
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
# window start outside of window bounds
|
|
|
|
break
|
2014-10-25 15:47:24 -06:00
|
|
|
else:
|
2016-03-18 19:59:59 -06:00
|
|
|
# did not rise above user configured horizon
|
2014-10-25 15:47:24 -06:00
|
|
|
break
|
|
|
|
|
|
|
|
if station_match:
|
2014-10-26 14:07:42 -06:00
|
|
|
data.append(station_windows)
|
2014-10-25 15:47:24 -06:00
|
|
|
|
2014-10-26 14:07:42 -06:00
|
|
|
return JsonResponse(data, safe=False)
|
2014-10-25 15:47:24 -06:00
|
|
|
|
|
|
|
|
2014-12-19 06:06:58 -07:00
|
|
|
def observation_view(request, id):
|
2014-09-21 14:18:16 -06:00
|
|
|
"""View for single observation page."""
|
|
|
|
observation = get_object_or_404(Observation, id=id)
|
2017-02-09 05:33:44 -07:00
|
|
|
dataset = Data.objects.filter(observation=observation)
|
2015-08-28 02:38:49 -06:00
|
|
|
|
Initial data vetting/verification system
Model change (with migration 0006) adds 3 fields to Data:
vetted_status (charfield with options for data status, default "unknown")
vetted_user (who vetted the data)
vetted_datetime (when it was vetted)
In addition, various boolean functions are added for the Data model
to check statuses. More functions are added to the Observation model
to check status of verification within an observation as well, assuming
multiple data entries in an Observation. With these, I also changed
"has_data" to "has_submitted_data" to be more specific alongside the
others.
For UX, we add a green check sign or red removal sign to the data
header in Observation view (along with green/red datetime in the footer)
if a data is verified good or bad, respectively. If there is an unknown
status, the data header is given a thumbs-up and thumbs-down button to
verify the data good or bad. These icons are only offered to is_staff,
the observation requestor, and any station owner in the observation.
These buttons trigger new URLs/functions in view:
data_verify(id)
data_mark_bad(id)
Returning the user back to the originating Observation page.
In the observation lists I changed the coloring of the ID button to be:
Future: light blue (same)
No uploaded data and/or all vetted bad data: red
Some or all unvetted data with no verified good data: orange
Some or all verified good data: green
These changes are reflected in the observations.html, home.html, and
user_detail.html templates.
solves satnogs/satnogs-network#171
2016-03-25 13:52:45 -06:00
|
|
|
# not all users will be able to vet data within an observation, allow
|
|
|
|
# staff, observation requestors, and station owners
|
|
|
|
is_vetting_user = False
|
2016-03-26 08:46:50 -06:00
|
|
|
if request.user.is_authenticated():
|
|
|
|
if request.user == observation.author or \
|
2017-02-09 05:33:44 -07:00
|
|
|
dataset.filter(
|
|
|
|
ground_station__in=Station.objects.filter(owner=request.user)).count or \
|
2016-03-26 08:46:50 -06:00
|
|
|
request.user.is_staff:
|
|
|
|
is_vetting_user = True
|
Initial data vetting/verification system
Model change (with migration 0006) adds 3 fields to Data:
vetted_status (charfield with options for data status, default "unknown")
vetted_user (who vetted the data)
vetted_datetime (when it was vetted)
In addition, various boolean functions are added for the Data model
to check statuses. More functions are added to the Observation model
to check status of verification within an observation as well, assuming
multiple data entries in an Observation. With these, I also changed
"has_data" to "has_submitted_data" to be more specific alongside the
others.
For UX, we add a green check sign or red removal sign to the data
header in Observation view (along with green/red datetime in the footer)
if a data is verified good or bad, respectively. If there is an unknown
status, the data header is given a thumbs-up and thumbs-down button to
verify the data good or bad. These icons are only offered to is_staff,
the observation requestor, and any station owner in the observation.
These buttons trigger new URLs/functions in view:
data_verify(id)
data_mark_bad(id)
Returning the user back to the originating Observation page.
In the observation lists I changed the coloring of the ID button to be:
Future: light blue (same)
No uploaded data and/or all vetted bad data: red
Some or all unvetted data with no verified good data: orange
Some or all verified good data: green
These changes are reflected in the observations.html, home.html, and
user_detail.html templates.
solves satnogs/satnogs-network#171
2016-03-25 13:52:45 -06:00
|
|
|
|
2017-02-09 05:33:44 -07:00
|
|
|
# This context flag will determine if a delete button appears for the observation.
|
|
|
|
is_deletable = False
|
|
|
|
if observation.author == request.user and observation.is_deletable_before_start:
|
|
|
|
is_deletable = True
|
2017-07-06 10:01:19 -06:00
|
|
|
if request.user.has_perm('base.delete_observation') and \
|
2017-02-09 05:33:44 -07:00
|
|
|
observation.is_deletable_after_end:
|
|
|
|
is_deletable = True
|
|
|
|
|
2015-09-02 09:34:37 -06:00
|
|
|
if settings.ENVIRONMENT == 'production':
|
|
|
|
discuss_slug = 'https://community.satnogs.org/t/observation-{0}-{1}-{2}' \
|
|
|
|
.format(observation.id, slugify(observation.satellite.name),
|
|
|
|
observation.satellite.norad_cat_id)
|
2017-05-06 12:14:22 -06:00
|
|
|
discuss_url = ('https://community.libre.space/new-topic?title=Observation {0}: {1}'
|
2016-05-05 08:50:11 -06:00
|
|
|
' ({2})&body=Regarding [Observation {3}](http://{4}{5}) ...'
|
|
|
|
'&category=observations') \
|
2015-09-02 09:34:37 -06:00
|
|
|
.format(observation.id, observation.satellite.name,
|
|
|
|
observation.satellite.norad_cat_id, observation.id,
|
|
|
|
request.get_host(), request.path)
|
|
|
|
try:
|
|
|
|
apiurl = '{0}.json'.format(discuss_slug)
|
|
|
|
urllib2.urlopen(apiurl).read()
|
|
|
|
has_comments = True
|
|
|
|
except:
|
|
|
|
has_comments = False
|
|
|
|
|
|
|
|
return render(request, 'base/observation_view.html',
|
2017-02-09 05:33:44 -07:00
|
|
|
{'observation': observation, 'dataset': dataset,
|
|
|
|
'has_comments': has_comments, 'discuss_url': discuss_url,
|
|
|
|
'discuss_slug': discuss_slug, 'is_vetting_user': is_vetting_user,
|
|
|
|
'is_deletable': is_deletable})
|
2014-09-21 14:18:16 -06:00
|
|
|
|
2014-10-16 07:42:44 -06:00
|
|
|
return render(request, 'base/observation_view.html',
|
2017-02-09 05:33:44 -07:00
|
|
|
{'observation': observation, 'dataset': dataset,
|
|
|
|
'is_vetting_user': is_vetting_user, 'is_deletable': is_deletable})
|
2014-10-27 18:45:22 -06:00
|
|
|
|
|
|
|
|
2015-08-14 12:58:54 -06:00
|
|
|
@login_required
|
|
|
|
def observation_delete(request, id):
|
|
|
|
"""View for deleting observation."""
|
|
|
|
me = request.user
|
|
|
|
observation = get_object_or_404(Observation, id=id)
|
2017-02-09 05:33:44 -07:00
|
|
|
# Having non-existent data is also grounds for deletion if user is staff
|
|
|
|
if (observation.author == me and observation.is_deletable_before_start) or \
|
|
|
|
(request.user.has_perm('base.delete_observation') and
|
2017-07-06 10:01:19 -06:00
|
|
|
observation.is_deletable_after_end):
|
2015-08-14 12:58:54 -06:00
|
|
|
observation.delete()
|
|
|
|
messages.success(request, 'Observation deleted successfully.')
|
|
|
|
else:
|
|
|
|
messages.error(request, 'Permission denied.')
|
|
|
|
return redirect(reverse('base:observations_list'))
|
|
|
|
|
|
|
|
|
Initial data vetting/verification system
Model change (with migration 0006) adds 3 fields to Data:
vetted_status (charfield with options for data status, default "unknown")
vetted_user (who vetted the data)
vetted_datetime (when it was vetted)
In addition, various boolean functions are added for the Data model
to check statuses. More functions are added to the Observation model
to check status of verification within an observation as well, assuming
multiple data entries in an Observation. With these, I also changed
"has_data" to "has_submitted_data" to be more specific alongside the
others.
For UX, we add a green check sign or red removal sign to the data
header in Observation view (along with green/red datetime in the footer)
if a data is verified good or bad, respectively. If there is an unknown
status, the data header is given a thumbs-up and thumbs-down button to
verify the data good or bad. These icons are only offered to is_staff,
the observation requestor, and any station owner in the observation.
These buttons trigger new URLs/functions in view:
data_verify(id)
data_mark_bad(id)
Returning the user back to the originating Observation page.
In the observation lists I changed the coloring of the ID button to be:
Future: light blue (same)
No uploaded data and/or all vetted bad data: red
Some or all unvetted data with no verified good data: orange
Some or all verified good data: green
These changes are reflected in the observations.html, home.html, and
user_detail.html templates.
solves satnogs/satnogs-network#171
2016-03-25 13:52:45 -06:00
|
|
|
@login_required
|
|
|
|
def data_verify(request, id):
|
|
|
|
me = request.user
|
|
|
|
data = get_object_or_404(Data, id=id)
|
|
|
|
data.vetted_status = 'verified'
|
|
|
|
data.vetted_user = me
|
|
|
|
data.vetted_datetime = datetime.today()
|
|
|
|
data.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
|
|
|
return redirect(reverse('base:observation_view', kwargs={'id': data.observation}))
|
|
|
|
|
|
|
|
|
|
|
|
@login_required
|
|
|
|
def data_mark_bad(request, id):
|
|
|
|
me = request.user
|
|
|
|
data = get_object_or_404(Data, id=id)
|
|
|
|
data.vetted_status = 'no_data'
|
|
|
|
data.vetted_user = me
|
|
|
|
data.vetted_datetime = datetime.today()
|
|
|
|
data.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
|
|
|
return redirect(reverse('base:observation_view', kwargs={'id': data.observation}))
|
|
|
|
|
|
|
|
|
2014-10-27 18:45:22 -06:00
|
|
|
def stations_list(request):
|
|
|
|
"""View to render Stations page."""
|
|
|
|
stations = Station.objects.all()
|
2014-12-14 05:50:39 -07:00
|
|
|
form = StationForm()
|
|
|
|
antennas = Antenna.objects.all()
|
2014-10-27 18:45:22 -06:00
|
|
|
|
2014-12-14 05:50:39 -07:00
|
|
|
return render(request, 'base/stations.html',
|
|
|
|
{'stations': stations, 'form': form, 'antennas': antennas})
|
2014-12-01 13:20:38 -07:00
|
|
|
|
|
|
|
|
2014-12-19 06:06:58 -07:00
|
|
|
def station_view(request, id):
|
2014-12-01 13:20:38 -07:00
|
|
|
"""View for single station page."""
|
|
|
|
station = get_object_or_404(Station, id=id)
|
2014-12-13 11:45:52 -07:00
|
|
|
form = StationForm(instance=station)
|
2014-12-14 05:47:50 -07:00
|
|
|
antennas = Antenna.objects.all()
|
2015-09-09 02:12:36 -06:00
|
|
|
rigs = Rig.objects.all()
|
2017-03-19 12:03:58 -06:00
|
|
|
unsupported_frequencies = request.GET.get('unsupported_frequencies', '0')
|
2014-12-01 13:20:38 -07:00
|
|
|
|
2015-11-16 06:18:46 -07:00
|
|
|
try:
|
2017-08-12 12:56:34 -06:00
|
|
|
satellites = Satellite.objects.filter(transmitters__alive=True) \
|
|
|
|
.filter(status='alive').distinct()
|
2015-11-16 06:18:46 -07:00
|
|
|
except:
|
|
|
|
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
|
|
|
|
|
|
|
|
nextpasses = []
|
|
|
|
passid = 0
|
|
|
|
|
|
|
|
for satellite in satellites:
|
2017-03-19 12:03:58 -06:00
|
|
|
# look for a match between transmitters from the satellite and
|
|
|
|
# ground station antenna frequency capabilities
|
|
|
|
if int(unsupported_frequencies) == 0:
|
|
|
|
frequency_supported = False
|
2017-03-20 09:36:54 -06:00
|
|
|
transmitters = Transmitter.objects.filter(satellite=satellite)
|
2017-03-19 12:03:58 -06:00
|
|
|
for gs_antenna in station.antenna.all():
|
2017-03-20 09:36:54 -06:00
|
|
|
for transmitter in transmitters:
|
|
|
|
if transmitter.downlink_low:
|
|
|
|
if (gs_antenna.frequency <=
|
|
|
|
transmitter.downlink_low <=
|
|
|
|
gs_antenna.frequency_max):
|
2017-03-19 12:03:58 -06:00
|
|
|
frequency_supported = True
|
|
|
|
if not frequency_supported:
|
|
|
|
continue
|
|
|
|
|
2015-11-16 06:18:46 -07:00
|
|
|
observer.date = ephem.date(datetime.today())
|
|
|
|
|
|
|
|
try:
|
2016-01-23 02:40:56 -07:00
|
|
|
sat_ephem = ephem.readtle(str(satellite.latest_tle.tle0),
|
|
|
|
str(satellite.latest_tle.tle1),
|
|
|
|
str(satellite.latest_tle.tle2))
|
2016-01-25 08:04:49 -07:00
|
|
|
except (ValueError, AttributeError):
|
2017-03-20 09:36:54 -06:00
|
|
|
continue
|
|
|
|
|
|
|
|
# Here we are going to iterate over each satellite to
|
|
|
|
# find its appropriate passes within a given time constraint
|
|
|
|
keep_digging = True
|
|
|
|
while keep_digging:
|
|
|
|
try:
|
|
|
|
tr, azr, tt, altt, ts, azs = observer.next_pass(sat_ephem)
|
|
|
|
except ValueError:
|
|
|
|
break # there will be sats in our list that fall below horizon, skip
|
|
|
|
except TypeError:
|
|
|
|
break # if there happens to be a non-EarthSatellite object in the list
|
|
|
|
except Exception:
|
|
|
|
break
|
|
|
|
|
|
|
|
if tr is None:
|
|
|
|
break
|
|
|
|
|
|
|
|
# using the angles module convert the sexagesimal degree into
|
|
|
|
# something more easily read by a human
|
|
|
|
elevation = format(math.degrees(altt), '.0f')
|
|
|
|
azimuth_r = format(math.degrees(azr), '.0f')
|
|
|
|
azimuth_s = format(math.degrees(azs), '.0f')
|
|
|
|
passid += 1
|
|
|
|
|
|
|
|
# show only if >= configured horizon and in next 6 hours,
|
|
|
|
# and not directly overhead (tr < ts see issue 199)
|
|
|
|
if tr < ephem.date(datetime.today() +
|
|
|
|
timedelta(hours=settings.STATION_UPCOMING_END)):
|
|
|
|
if (float(elevation) >= station.horizon and tr < ts):
|
|
|
|
valid = True
|
|
|
|
if tr < ephem.Date(datetime.now() +
|
|
|
|
timedelta(minutes=int(settings.DATE_MIN_START))):
|
|
|
|
valid = False
|
|
|
|
polar_data = calculate_polar_data(observer,
|
|
|
|
sat_ephem,
|
|
|
|
tr.datetime(),
|
|
|
|
ts.datetime(), 10)
|
|
|
|
sat_pass = {'passid': passid,
|
|
|
|
'mytime': str(observer.date),
|
|
|
|
'debug': observer.next_pass(sat_ephem),
|
|
|
|
'name': str(satellite.name),
|
|
|
|
'id': str(satellite.id),
|
2017-06-06 11:25:54 -06:00
|
|
|
'success_rate': str(satellite.success_rate),
|
|
|
|
'unknown_rate': str(satellite.unknown_rate),
|
|
|
|
'empty_rate': str(satellite.empty_rate),
|
|
|
|
'data_count': str(satellite.data_count),
|
|
|
|
'verified_count': str(satellite.verified_count),
|
|
|
|
'empty_count': str(satellite.empty_count),
|
|
|
|
'unknown_count': str(satellite.unknown_count),
|
2017-03-20 09:36:54 -06:00
|
|
|
'norad_cat_id': str(satellite.norad_cat_id),
|
|
|
|
'tr': tr.datetime(), # Rise time
|
|
|
|
'azr': azimuth_r, # Rise Azimuth
|
|
|
|
'tt': tt, # Max altitude time
|
|
|
|
'altt': elevation, # Max altitude
|
|
|
|
'ts': ts.datetime(), # Set time
|
|
|
|
'azs': azimuth_s, # Set azimuth
|
|
|
|
'valid': valid,
|
|
|
|
'polar_data': polar_data}
|
|
|
|
nextpasses.append(sat_pass)
|
|
|
|
observer.date = ephem.Date(ts).datetime() + timedelta(minutes=1)
|
|
|
|
else:
|
|
|
|
keep_digging = False
|
2015-11-16 06:18:46 -07:00
|
|
|
|
2014-12-01 13:20:38 -07:00
|
|
|
return render(request, 'base/station_view.html',
|
2015-04-11 11:32:44 -06:00
|
|
|
{'station': station, 'form': form, 'antennas': antennas,
|
|
|
|
'mapbox_id': settings.MAPBOX_MAP_ID,
|
2015-11-16 06:18:46 -07:00
|
|
|
'mapbox_token': settings.MAPBOX_TOKEN,
|
2015-09-09 02:12:36 -06:00
|
|
|
'nextpasses': sorted(nextpasses, key=itemgetter('tr')),
|
2017-03-19 12:03:58 -06:00
|
|
|
'rigs': rigs,
|
|
|
|
'unsupported_frequencies': unsupported_frequencies})
|
2014-12-13 11:45:52 -07:00
|
|
|
|
|
|
|
|
|
|
|
@require_POST
|
2014-12-19 07:46:31 -07:00
|
|
|
def station_edit(request):
|
2014-12-13 11:45:52 -07:00
|
|
|
"""Edit or add a single station."""
|
2014-12-14 05:47:50 -07:00
|
|
|
if request.POST['id']:
|
|
|
|
pk = request.POST.get('id')
|
|
|
|
station = get_object_or_404(Station, id=pk, owner=request.user)
|
|
|
|
form = StationForm(request.POST, request.FILES, instance=station)
|
|
|
|
else:
|
|
|
|
form = StationForm(request.POST, request.FILES)
|
2014-12-13 11:45:52 -07:00
|
|
|
if form.is_valid():
|
|
|
|
f = form.save(commit=False)
|
|
|
|
f.owner = request.user
|
|
|
|
f.save()
|
|
|
|
form.save_m2m()
|
2015-05-06 02:53:48 -06:00
|
|
|
if f.online:
|
|
|
|
messages.success(request, 'Successfully saved Ground Station.')
|
|
|
|
else:
|
|
|
|
messages.success(request, ('Successfully saved Ground Station. It will appear online '
|
|
|
|
'as soon as it connects with our API.'))
|
|
|
|
|
2014-12-19 07:46:31 -07:00
|
|
|
return redirect(reverse('base:station_view', kwargs={'id': f.id}))
|
2014-12-14 05:47:50 -07:00
|
|
|
else:
|
2016-04-23 09:05:57 -06:00
|
|
|
messages.error(request, 'Your Station submission had some errors.{0}'.format(form.errors))
|
2014-12-14 05:47:50 -07:00
|
|
|
return redirect(reverse('users:view_user', kwargs={'username': request.user.username}))
|
2015-08-24 06:13:59 -06:00
|
|
|
|
|
|
|
|
|
|
|
@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}))
|
2016-04-09 10:19:47 -06:00
|
|
|
|
|
|
|
|
|
|
|
def satellite_view(request, id):
|
|
|
|
try:
|
|
|
|
sat = get_object_or_404(Satellite, norad_cat_id=id)
|
|
|
|
except:
|
|
|
|
data = {
|
|
|
|
'error': 'Unable to find that satellite.'
|
|
|
|
}
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
|
|
|
data = {
|
|
|
|
'id': id,
|
|
|
|
'name': sat.name,
|
|
|
|
'names': sat.names,
|
|
|
|
'image': sat.image,
|
2017-06-06 11:25:54 -06:00
|
|
|
'success_rate': sat.success_rate,
|
|
|
|
'verified_count': sat.verified_count,
|
|
|
|
'empty_count': sat.empty_count,
|
|
|
|
'unknown_count': sat.unknown_count,
|
|
|
|
'data_count': sat.data_count,
|
2016-04-09 10:19:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return JsonResponse(data, safe=False)
|
|
|
|
|
2016-04-25 08:53:18 -06:00
|
|
|
|
|
|
|
def observation_data_view(request, id):
|
|
|
|
observation = get_object_or_404(Observation, data__id=id)
|
2016-05-05 08:50:11 -06:00
|
|
|
return redirect(reverse('base:observation_view',
|
|
|
|
kwargs={'id': observation.id}) + '#{0}'.format(id))
|