diff --git a/db/api/views.py b/db/api/views.py index d7f423e..57b500a 100644 --- a/db/api/views.py +++ b/db/api/views.py @@ -1,5 +1,3 @@ -from orbit import satellite - from rest_framework import viewsets, mixins, status from rest_framework.parsers import FormParser, FileUploadParser from rest_framework.permissions import AllowAny @@ -9,6 +7,7 @@ from django.core.files.base import ContentFile from db.api import serializers, filters, pagination from db.base.models import Mode, Satellite, Transmitter, DemodData +from db.base.tasks import update_satellite class ModeView(viewsets.ReadOnlyModelViewSet): @@ -40,24 +39,15 @@ class TelemetryView(viewsets.ModelViewSet, mixins.CreateModelMixin): def create(self, request, *args, **kwargs): data = {} - create_satellite = False norad_cat_id = request.data.get('noradID') - try: - data['satellite'] = Satellite.objects.get(norad_cat_id=norad_cat_id).id - except Satellite.DoesNotExist: - create_satellite = True - if create_satellite: + if not Satellite.objects.get(norad_cat_id=norad_cat_id).exists(): try: - sat = satellite(norad_cat_id) - except IndexError: + update_satellite(norad_cat_id, update_name=True, update_tle=True) + except LookupError: return Response(status=status.HTTP_400_BAD_REQUEST) - else: - tle = sat.tle() - obj = Satellite.objects.create(norad_cat_id=norad_cat_id, name=tle[0], - tle1=tle[1], tle2=tle[2]) - data['satellite'] = obj + data['satellite'] = Satellite.objects.get(norad_cat_id=norad_cat_id).id data['station'] = request.data.get('source') timestamp = request.data.get('timestamp') data['timestamp'] = timestamp diff --git a/db/base/management/commands/delete_satellite.py b/db/base/management/commands/delete_satellite.py new file mode 100644 index 0000000..c22543b --- /dev/null +++ b/db/base/management/commands/delete_satellite.py @@ -0,0 +1,22 @@ +from django.core.management.base import BaseCommand + +from db.base.models import Satellite + + +class Command(BaseCommand): + help = 'Delete selected Satellites' + + def add_arguments(self, parser): + # Positional arguments + parser.add_argument('norad_ids', + nargs='+', + metavar='') + + def handle(self, *args, **options): + for norad_id in options['norad_ids']: + try: + Satellite.objects.get(norad_cat_id=norad_id).delete() + self.stdout.write('Deleted satellite {}.'.format(norad_id)) + continue + except Exception: + self.stderr.write('Satellite with Identifier {} does not exist'.format(norad_id)) diff --git a/db/base/management/commands/fetch_satellites.py b/db/base/management/commands/fetch_satellites.py index d507979..e41a1f4 100644 --- a/db/base/management/commands/fetch_satellites.py +++ b/db/base/management/commands/fetch_satellites.py @@ -1,8 +1,6 @@ -from orbit import satellite +from django.core.management.base import BaseCommand -from django.core.management.base import BaseCommand, CommandError - -from db.base.models import Satellite +from db.base.tasks import update_satellite class Command(BaseCommand): @@ -10,40 +8,14 @@ class Command(BaseCommand): def add_arguments(self, parser): # Positional arguments - parser.add_argument('satellite_identifiers', + parser.add_argument('norad_ids', nargs='+', - metavar='') - - # Named (optional) arguments - parser.add_argument( - '--delete', - action='store_true', - dest='delete', - default=False, - help='Delete Satellite' - ) + metavar='') def handle(self, *args, **options): - for item in options['satellite_identifiers']: - if options['delete']: - try: - Satellite.objects.get(norad_cat_id=item).delete() - self.stdout.write('Satellite {}: deleted'.format(item)) - continue - except Exception: - raise CommandError('Satellite with Identifier {} does not exist'.format(item)) - + for norad_id in options['norad_ids']: try: - sat = satellite(item) - except Exception: - raise CommandError('Satellite with Identifier {} does not exist'.format(item)) - - try: - obj = Satellite.objects.get(norad_cat_id=item) - except Exception: - obj = Satellite(norad_cat_id=item) - - obj.name = sat.name() - obj.save() - - self.stdout.write('fetched data for {}: {}'.format(obj.norad_cat_id, obj.name)) + update_satellite(int(norad_id), update_name=True, update_tle=False) + except LookupError: + self.stderr.write('Satellite {} not found in Celestrak'.format(norad_id)) + continue diff --git a/db/base/management/commands/update_all_tle.py b/db/base/management/commands/update_all_tle.py index 939c566..cd8e219 100644 --- a/db/base/management/commands/update_all_tle.py +++ b/db/base/management/commands/update_all_tle.py @@ -1,28 +1,10 @@ -from orbit import satellite - from django.core.management.base import BaseCommand -from db.base.models import Satellite +from db.base.tasks import update_all_tle class Command(BaseCommand): help = 'Update TLEs for existing Satellites' def handle(self, *args, **options): - - satellites = Satellite.objects.all() - - for obj in satellites: - try: - sat = satellite(obj.norad_cat_id) - except Exception: - self.stdout.write(('Satellite {} with Identifier {} does ' - 'not exist').format(obj.name, obj.norad_cat_id)) - continue - - tle = sat.tle() - obj.tle1 = tle[1] - obj.tle2 = tle[2] - obj.save() - self.stdout.write(('Satellite {} with Identifier {} ' - 'found [updated]').format(obj.name, obj.norad_cat_id)) + update_all_tle() diff --git a/db/base/migrations/0003_satellite_tle_source.py b/db/base/migrations/0003_satellite_tle_source.py new file mode 100644 index 0000000..f20d3d9 --- /dev/null +++ b/db/base/migrations/0003_satellite_tle_source.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-09-19 00:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0002_demoddata_is_decoded'), + ] + + operations = [ + migrations.AddField( + model_name='satellite', + name='tle_source', + field=models.CharField(blank=True, max_length=300), + ), + ] diff --git a/db/base/models.py b/db/base/models.py index 6565cd5..8edfd7e 100644 --- a/db/base/models.py +++ b/db/base/models.py @@ -68,6 +68,7 @@ class Satellite(models.Model): help_text='Ideally: 250x250') tle1 = models.CharField(max_length=200, blank=True) tle2 = models.CharField(max_length=200, blank=True) + tle_source = models.CharField(max_length=300, blank=True) status = models.CharField(choices=zip(SATELLITE_STATUS, SATELLITE_STATUS), max_length=10, default='alive') diff --git a/db/base/tasks.py b/db/base/tasks.py index d5b0c4c..636a689 100644 --- a/db/base/tasks.py +++ b/db/base/tasks.py @@ -1,8 +1,6 @@ import csv from datetime import datetime, timedelta -from orbit import satellite - from django.db.models import Count, Max from django.conf import settings from django.core.cache import cache @@ -12,6 +10,11 @@ from django.template.loader import render_to_string from django.utils.timezone import make_aware from influxdb import InfluxDBClient +from sgp4.earth_gravity import wgs72 +from sgp4.io import twoline2rv + +from satellite_tle import fetch_tle_from_celestrak, fetch_tles + from db.base.models import Satellite, DemodData from db.base.utils import calculate_statistics from db.celery import app @@ -24,21 +27,76 @@ def check_celery(): pass +@app.task +def update_satellite(norad_id, update_name=True, update_tle=True): + """Task to update the name and/or the tle of a satellite, or create a + new satellite in the db if no satellite with given norad_id can be found""" + + tle = fetch_tle_from_celestrak(norad_id) + + satellite_created = False + try: + satellite = Satellite.objects.get(norad_cat_id=norad_id) + except Satellite.DoesNotExist: + satellite_created = True + satellite = Satellite(norad_cat_id=norad_id) + + if update_name: + satellite.name = tle[0] + + if update_tle: + satellite.tle_source = 'Celestrak (satcat)' + satellite.tle1 = tle[1] + satellite.tle2 = tle[2] + + satellite.save() + + if satellite_created: + print('Created satellite {}: {}'.format(satellite.norad_cat_id, satellite.name)) + else: + print('Updated satellite {}: {}'.format(satellite.norad_cat_id, satellite.name)) + + @app.task def update_all_tle(): """Task to update all satellite TLEs""" - satellites = Satellite.objects.all() - for obj in satellites: - try: - sat = satellite(obj.norad_cat_id) - except Exception: + satellites = Satellite.objects.all() + norad_ids = list(int(sat.norad_cat_id) for sat in satellites) + + tles = fetch_tles(norad_ids) + + missing_norad_ids = [] + for satellite in satellites: + norad_id = satellite.norad_cat_id + + if norad_id not in tles.keys(): + # No TLE available for this satellite + missing_norad_ids.append(norad_id) continue - tle = sat.tle() - obj.tle1 = tle[1] - obj.tle2 = tle[2] - obj.save() + source, tle = tles[norad_id] + + if satellite.tle1 and satellite.tle2: + current_sat = twoline2rv(satellite.tle1, satellite.tle2, wgs72) + new_sat = twoline2rv(tle[1], tle[2], wgs72) + + if new_sat.epoch < current_sat.epoch: + # Epoch of new TLE is larger then the TLE already in the db + continue + + satellite.tle_source = source + satellite.tle1 = tle[1] + satellite.tle2 = tle[2] + satellite.save() + + print('Updated TLE for {}: {} from {}'.format(norad_id, + satellite.name, + source)) + + for norad_id in missing_norad_ids: + satellite = satellites.get(norad_cat_id=norad_id) + print('NO TLE found for {}: {}'.format(norad_id, satellite.name)) @app.task diff --git a/requirements.txt b/requirements.txt index 3ca8bd2..46904df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ Markdown==2.6.11 mysqlclient==1.3.12 oauthlib==2.0.6 olefile==0.45.1 -orbit@git+https://gitlab.com/librespacefoundation/orbit.git@db463c2d261365485c157f2c86233aaf44e577a2 +satellitetle==0.4.0 Pillow==5.0.0 pyephem==3.7.6.0 python-dateutil==2.7.3 @@ -46,6 +46,7 @@ requests-cache==0.4.13 requests-oauthlib==0.8.0 rjsmin==1.0.12 satnogsdecoders==0.1 +sgp4==1.4 shortuuid==0.5.0 simplejson==3.16.0 six==1.11.0 diff --git a/setup.cfg b/setup.cfg index 2aac34d..0c3ce2e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,7 +55,7 @@ install_requires = Markdown~=2.6.0 django-filter~=1.1.0 # Astronomy - orbit@git+https://gitlab.com/librespacefoundation/orbit.git@db463c2d261365485c157f2c86233aaf44e577a2 + satellitetle~=0.4.0 # Unsorted influxdb~=5.2.0 pytool~=3.10.0