Inital Commit
parent
935df0f59a
commit
0a21df4643
|
@ -0,0 +1,2 @@
|
||||||
|
from .fetch_tle import * # noqa
|
||||||
|
from .fetch_tles import * # noqa
|
|
@ -0,0 +1,64 @@
|
||||||
|
import csv
|
||||||
|
from lxml import html
|
||||||
|
import requests
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
SOURCES_LIST = pkg_resources.resource_filename('satellite_tle', 'sources.csv')
|
||||||
|
|
||||||
|
|
||||||
|
def get_tle_sources():
|
||||||
|
'''
|
||||||
|
Returns a list of (source, url)-tuples for well-known TLE sources.
|
||||||
|
'''
|
||||||
|
|
||||||
|
sources = []
|
||||||
|
|
||||||
|
with open(SOURCES_LIST) as csvfile:
|
||||||
|
csv_reader = csv.reader(csvfile,
|
||||||
|
delimiter=',',
|
||||||
|
quotechar='\'',
|
||||||
|
quoting=csv.QUOTE_NONNUMERIC)
|
||||||
|
for row in csv_reader:
|
||||||
|
source, url = row
|
||||||
|
sources.append((source, url))
|
||||||
|
|
||||||
|
return sources
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_tle_from_celestrak(norad_cat_id):
|
||||||
|
'''
|
||||||
|
Returns the TLE for a given norad_cat_id as currently available from CelesTrak.
|
||||||
|
Raises IndexError if no data is available for the given norad_cat_id.
|
||||||
|
'''
|
||||||
|
|
||||||
|
r = requests.get('https://www.celestrak.com/satcat/tle.php?CATNR={}'.format(norad_cat_id))
|
||||||
|
page = html.fromstring(r.text)
|
||||||
|
|
||||||
|
tle = page.xpath('//pre/text()')[0].split('\n')
|
||||||
|
if tle[1].strip() == 'No TLE found':
|
||||||
|
raise LookupError
|
||||||
|
|
||||||
|
return tle[1].strip(), tle[2].strip(), tle[3].strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_tles_from_url(url):
|
||||||
|
'''
|
||||||
|
Downloads the TLE set from the given url.
|
||||||
|
Returns a dictionary of the form {norad_id1: tle1, norad_id2: tle2} for all TLEs found.
|
||||||
|
tleN is returned as list of three strings: [satellite_name, line1, line2].
|
||||||
|
'''
|
||||||
|
|
||||||
|
r = requests.get(url)
|
||||||
|
|
||||||
|
tles = dict()
|
||||||
|
l0 = ''
|
||||||
|
l1 = ''
|
||||||
|
lines = r.text.splitlines()
|
||||||
|
for l in lines[:-1]:
|
||||||
|
if l[0] == '2':
|
||||||
|
norad_cat_id = int(l1[2:7].encode('ascii'))
|
||||||
|
tles[norad_cat_id] = (l0.strip(), l1, l)
|
||||||
|
l0 = l1
|
||||||
|
l1 = l
|
||||||
|
|
||||||
|
return tles
|
|
@ -0,0 +1,66 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from sgp4.earth_gravity import wgs72
|
||||||
|
from sgp4.io import twoline2rv
|
||||||
|
|
||||||
|
from . import get_tle_sources, fetch_tles_from_url, fetch_tle_from_celestrak
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_tles(requested_norad_ids):
|
||||||
|
'''
|
||||||
|
Returns the most recent TLEs found for the requested satellites
|
||||||
|
available via Celestrak, CalPoly and AMSAT.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# List of 2-tuples of the form (source, tle)
|
||||||
|
# source is a human-readable string
|
||||||
|
# tle is a 3-tuple of strings
|
||||||
|
tles = dict()
|
||||||
|
|
||||||
|
def update_tles(source, tle):
|
||||||
|
if norad_id not in requested_norad_ids:
|
||||||
|
# Satellite not requested,
|
||||||
|
# skip.
|
||||||
|
return
|
||||||
|
|
||||||
|
if norad_id not in tles.keys():
|
||||||
|
# Satellite requested and first occurence in the downloaded data,
|
||||||
|
# store new TLE.
|
||||||
|
#print('Found {}'.format(norad_id))
|
||||||
|
tles[norad_id] = source, tle
|
||||||
|
return
|
||||||
|
|
||||||
|
# There are multiple TLEs for this satellite available.
|
||||||
|
# Parse and compare epoch of both TLEs and choose the most recent one.
|
||||||
|
current_sat = twoline2rv(tles[norad_id][1][1], tles[norad_id][1][2], wgs72)
|
||||||
|
new_sat = twoline2rv(tle[1], tle[2], wgs72)
|
||||||
|
if new_sat.epoch > current_sat.epoch:
|
||||||
|
# Found a more recent TLE than the current one,
|
||||||
|
# store the new TLE.
|
||||||
|
|
||||||
|
tles[norad_id] = source, tle
|
||||||
|
|
||||||
|
# Fetch TLE sets from well-known TLE sources
|
||||||
|
sources = get_tle_sources()
|
||||||
|
|
||||||
|
for source, url in sources:
|
||||||
|
#print('Fetch from {}'.format(url))
|
||||||
|
new_tles = fetch_tles_from_url(url=url)
|
||||||
|
|
||||||
|
for norad_id, tle in new_tles.items():
|
||||||
|
update_tles(source, tle)
|
||||||
|
|
||||||
|
# Try fetching missing sats from another Celestrak endoint
|
||||||
|
missing_norad_ids = set(requested_norad_ids) - set(tles.keys())
|
||||||
|
|
||||||
|
for norad_id in missing_norad_ids:
|
||||||
|
try:
|
||||||
|
#print('Fetching {} from Celestrak (satcat):'.format(norad_id), end='')
|
||||||
|
tle = fetch_tle_from_celestrak(norad_id)
|
||||||
|
#print(' ok, ', end='')
|
||||||
|
update_tles('Celestrak (satcat)', tle)
|
||||||
|
except LookupError:
|
||||||
|
#print(' failed.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
return tles
|
|
@ -0,0 +1,46 @@
|
||||||
|
'CalPoly','http://mstl.atl.calpoly.edu/~ops/keps/kepler.txt'
|
||||||
|
'AMSAT','https://www.amsat.org/amsat/ftp/keps/current/nasabare.txt'
|
||||||
|
'Celestrak (tle-new)','https://www.celestrak.com/NORAD/elements/tle-new.txt'
|
||||||
|
'Celestrak (stations)','https://www.celestrak.com/NORAD/elements/stations.txt'
|
||||||
|
'Celestrak (visual)','https://www.celestrak.com/NORAD/elements/visual.txt'
|
||||||
|
'Celestrak (1999-025)','https://www.celestrak.com/NORAD/elements/1999-025.txt'
|
||||||
|
'Celestrak (iridium-33-debris)','https://www.celestrak.com/NORAD/elements/iridium-33-debris.txt'
|
||||||
|
'Celestrak (cosmos-2251-debris)','https://www.celestrak.com/NORAD/elements/cosmos-2251-debris.txt'
|
||||||
|
'Celestrak (2012-044)','https://www.celestrak.com/NORAD/elements/2012-044.txt'
|
||||||
|
'Celestrak (weather)','https://www.celestrak.com/NORAD/elements/weather.txt'
|
||||||
|
'Celestrak (noaa)','https://www.celestrak.com/NORAD/elements/noaa.txt'
|
||||||
|
'Celestrak (goes)','https://www.celestrak.com/NORAD/elements/goes.txt'
|
||||||
|
'Celestrak (resource)','https://www.celestrak.com/NORAD/elements/resource.txt'
|
||||||
|
'Celestrak (sarsat)','https://www.celestrak.com/NORAD/elements/sarsat.txt'
|
||||||
|
'Celestrak (dmc)','https://www.celestrak.com/NORAD/elements/dmc.txt'
|
||||||
|
'Celestrak (tdrss)','https://www.celestrak.com/NORAD/elements/tdrss.txt'
|
||||||
|
'Celestrak (argos)','https://www.celestrak.com/NORAD/elements/argos.txt'
|
||||||
|
'Celestrak (planet)','https://www.celestrak.com/NORAD/elements/planet.txt'
|
||||||
|
'Celestrak (spire)','https://www.celestrak.com/NORAD/elements/spire.txt'
|
||||||
|
'Celestrak (geo)','https://www.celestrak.com/NORAD/elements/geo.txt'
|
||||||
|
'Celestrak (intelsat)','https://www.celestrak.com/NORAD/elements/intelsat.txt'
|
||||||
|
'Celestrak (ses)','https://www.celestrak.com/NORAD/elements/ses.txt'
|
||||||
|
'Celestrak (iridium)','https://www.celestrak.com/NORAD/elements/iridium.txt'
|
||||||
|
'Celestrak (iridium-NEXT)','https://www.celestrak.com/NORAD/elements/iridium-NEXT.txt'
|
||||||
|
'Celestrak (orbcomm)','https://www.celestrak.com/NORAD/elements/orbcomm.txt'
|
||||||
|
'Celestrak (globalstar)','https://www.celestrak.com/NORAD/elements/globalstar.txt'
|
||||||
|
'Celestrak (amateur)','https://www.celestrak.com/NORAD/elements/amateur.txt'
|
||||||
|
'Celestrak (other-comm)','https://www.celestrak.com/NORAD/elements/other-comm.txt'
|
||||||
|
'Celestrak (gorizont)','https://www.celestrak.com/NORAD/elements/gorizont.txt'
|
||||||
|
'Celestrak (raduga)','https://www.celestrak.com/NORAD/elements/raduga.txt'
|
||||||
|
'Celestrak (molniya)','https://www.celestrak.com/NORAD/elements/molniya.txt'
|
||||||
|
'Celestrak (gps-ops)','https://www.celestrak.com/NORAD/elements/gps-ops.txt'
|
||||||
|
'Celestrak (glo-ops)','https://www.celestrak.com/NORAD/elements/glo-ops.txt'
|
||||||
|
'Celestrak (galileo)','https://www.celestrak.com/NORAD/elements/galileo.txt'
|
||||||
|
'Celestrak (beidou)','https://www.celestrak.com/NORAD/elements/beidou.txt'
|
||||||
|
'Celestrak (sbas)','https://www.celestrak.com/NORAD/elements/sbas.txt'
|
||||||
|
'Celestrak (nnss)','https://www.celestrak.com/NORAD/elements/nnss.txt'
|
||||||
|
'Celestrak (musson)','https://www.celestrak.com/NORAD/elements/musson.txt'
|
||||||
|
'Celestrak (science)','https://www.celestrak.com/NORAD/elements/science.txt'
|
||||||
|
'Celestrak (geodetic)','https://www.celestrak.com/NORAD/elements/geodetic.txt'
|
||||||
|
'Celestrak (engineering)','https://www.celestrak.com/NORAD/elements/engineering.txt'
|
||||||
|
'Celestrak (education)','https://www.celestrak.com/NORAD/elements/education.txt'
|
||||||
|
'Celestrak (military)','https://www.celestrak.com/NORAD/elements/military.txt'
|
||||||
|
'Celestrak (radar)','https://www.celestrak.com/NORAD/elements/radar.txt'
|
||||||
|
'Celestrak (cubesat)','https://www.celestrak.com/NORAD/elements/cubesat.txt'
|
||||||
|
'Celestrak (other)','https://www.celestrak.com/NORAD/elements/other.txt'
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
from datetime import datetime , timedelta
|
||||||
|
import requests
|
||||||
|
from flask import Flask , render_template,redirect,url_for
|
||||||
|
import json
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
import ephem
|
||||||
|
from satnogs_api_client import fetch_satellites, DB_BASE_URL
|
||||||
|
from satellite_tle import fetch_tles
|
||||||
|
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
modes = {1: 'FM', 2: 'AFSK1k2', 5: 'SSTV', 6: 'CW', 7: 'FMN', 9: 'USB', 15: 'GFSK4k8', 17: 'AHRPT', 18: 'AFSK9k6', 19: 'AM', 20: 'LSB', 21: 'FSK1k2', 22: 'FSK9k6', 26: 'GFSK1k2', 27: 'GFSK2k4', 28: 'GFSK9k6', 29: 'GFSK19k2', 30: 'MSK1k2', 31: 'MSK2k4', 32: 'MSK4k8', 33: 'MSK9k6', 34: 'MSK19k2', 35: 'MSK38k4', 36: 'GMSK1k2', 37: 'GMSK2k4', 38: 'GMSK4k8', 39: 'GMSK9k6', 40: 'PSK31', 41: 'PSK63', 42: 'QPSK31', 43: 'QPSK63', 44: 'APT', 45: 'HRPT', 46: 'FSK4k8', 47: 'BPSK1k2', 48: 'GMSK19k2', 49: 'AFSK', 50: 'BPSK', 51: 'FSK19k2', 52: 'BPSK115k2', 53: 'LRPT', 54: 'BPSK9k6', 55: 'FFSK1k2', 56: 'FSK2k4', 57: 'DSTAR', 58: 'DUV', 59: 'CERTO', 60: 'BPSK400', 61: 'OFDM', 62: 'QPSK38k4'}
|
||||||
|
|
||||||
|
|
||||||
|
Passes = []
|
||||||
|
Stations = []
|
||||||
|
TLEs = {}
|
||||||
|
|
||||||
|
class Pass:
|
||||||
|
id = 0
|
||||||
|
start = None
|
||||||
|
end = None
|
||||||
|
ground_station = None
|
||||||
|
satellite = None
|
||||||
|
transmitter = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "\n<ID>: {}\n<Start>: {}\n<End>: {}\n<Ground Station>: {}\n<Satellite> {}\n<Transmitter>: {}".format(self.id,self.start.strftime('%Y-%m-%dT%H:%M:%S%z'),self.end.strftime('%Y-%m-%dT%H:%M:%S%z'),self.ground_station,json.dumps(self.satellite,indent = 1),json.dumps(self.transmitter,indent=1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def getActive():
|
||||||
|
start = (datetime.utcnow() - timedelta(0,0,0,0,20)).strftime('%Y-%m-%dT%H:%M:%S%z')
|
||||||
|
end = (datetime.utcnow() + timedelta(0,0,0,0,30)).strftime('%Y-%m-%dT%H:%M:%S%z')
|
||||||
|
|
||||||
|
passesR = requests.get("https://network.satnogs.org/api/observations/?end="+end+"&format=json&start="+start)
|
||||||
|
passes = passesR.json()
|
||||||
|
if passesR.links.has_key("next"):
|
||||||
|
while passesR.links.has_key("next"):
|
||||||
|
passesR = requests.get(passesR.links["next"]["url"])
|
||||||
|
passes += passesR.json()
|
||||||
|
ground_stations = {}
|
||||||
|
for x in passes:
|
||||||
|
if datetime.strptime(x["start"],'%Y-%m-%dT%H:%M:%Sz') > datetime.utcnow() or datetime.strptime(x["end"],'%Y-%m-%dT%H:%M:%Sz') < datetime.utcnow():
|
||||||
|
passes.remove(x)
|
||||||
|
else:
|
||||||
|
if ground_stations.has_key(x["ground_station"]):
|
||||||
|
ground_stations[x["ground_station"]].append(x)
|
||||||
|
else:
|
||||||
|
ground_stations[x["ground_station"]] = []
|
||||||
|
ground_stations[x["ground_station"]].append(x)
|
||||||
|
passes = []
|
||||||
|
for x in ground_stations:
|
||||||
|
start = datetime.utcnow()
|
||||||
|
current = {"start":datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S%z')+"z"}
|
||||||
|
for y in ground_stations[x]:
|
||||||
|
if datetime.strptime(y["start"],'%Y-%m-%dT%H:%M:%Sz') < datetime.strptime(current["start"],'%Y-%m-%dT%H:%M:%Sz'):
|
||||||
|
current = y
|
||||||
|
passes.append(current)
|
||||||
|
Passes =[]
|
||||||
|
for x in passes:
|
||||||
|
temp = Pass()
|
||||||
|
temp.id = x["id"]
|
||||||
|
temp.start = datetime.strptime(x["start"],'%Y-%m-%dT%H:%M:%Sz')
|
||||||
|
temp.end = datetime.strptime(x["end"],'%Y-%m-%dT%H:%M:%Sz')
|
||||||
|
temp.ground_station = x["ground_station"]
|
||||||
|
temp.satellite = requests.get("https://db.satnogs.org/api/satellites/"+str(x["norad_cat_id"])) .json()
|
||||||
|
temp.transmitter = requests.get("https://db.satnogs.org/api/transmitters/"+x["transmitter"]).json()
|
||||||
|
Passes.append(temp)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Passes
|
||||||
|
|
||||||
|
|
||||||
|
def GetGroundStations():
|
||||||
|
stationsR = requests.get("https://network.satnogs.org/api/stations/")
|
||||||
|
stations = stationsR.json()
|
||||||
|
while stationsR.links.has_key("next"):
|
||||||
|
stationsR = requests.get(stationsR.links["next"]["url"])
|
||||||
|
stations += stationsR.json()
|
||||||
|
|
||||||
|
for x in stations:
|
||||||
|
if x["last_seen"] == None:
|
||||||
|
stations.remove(x)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if datetime.strptime(x["last_seen"],'%Y-%m-%dT%H:%M:%Sz') < (datetime.utcnow()- timedelta(10,0,0,0)):
|
||||||
|
stations.remove(x)
|
||||||
|
for x in stations:
|
||||||
|
if x["last_seen"] == None:
|
||||||
|
stations.remove(x)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if datetime.strptime(x["last_seen"],'%Y-%m-%dT%H:%M:%Sz') < (datetime.utcnow()- timedelta(10,0,0,0)):
|
||||||
|
stations.remove(x)
|
||||||
|
|
||||||
|
|
||||||
|
return stations
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.scheduled_job('interval',minutes=3)
|
||||||
|
def updatePasses():
|
||||||
|
global Passes
|
||||||
|
print "Updating Passes"
|
||||||
|
Passes = getActive()
|
||||||
|
|
||||||
|
@scheduler.scheduled_job('interval',hours=10)
|
||||||
|
def updateStations():
|
||||||
|
global Stations
|
||||||
|
print "Updating Stations"
|
||||||
|
Stations = GetGroundStations()
|
||||||
|
|
||||||
|
@scheduler.scheduled_job('interval',days=1)
|
||||||
|
def updateTLE():
|
||||||
|
print "Updating TLE"
|
||||||
|
sats = fetch_satellites(url=DB_BASE_URL, max_satellites=None)
|
||||||
|
satnogs_db_norad_ids = set(sat['norad_cat_id'] for sat in sats if sat['status'] != 're-entered')
|
||||||
|
# Remove satellites with temporary norad ids
|
||||||
|
temporary_norad_ids = set(filter(lambda norad_id: norad_id >= 99900, satnogs_db_norad_ids))
|
||||||
|
satnogs_db_norad_ids = satnogs_db_norad_ids - temporary_norad_ids
|
||||||
|
|
||||||
|
# Fetch TLEs for the satellites of interest
|
||||||
|
tles = fetch_tles(satnogs_db_norad_ids)
|
||||||
|
|
||||||
|
for norad_id, (source, tle) in tles.items():
|
||||||
|
TLEs[norad_id] = [str(tle[0]),str(tle[1]),str(tle[2])]
|
||||||
|
print('\nTLEs for {} of {} requested satellites found ({} satellites with temporary norad ids skipped).'.format(len(tles), len(satnogs_db_norad_ids), len(temporary_norad_ids)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Satnogs Network Info<br>Updated every 5 min<br><a href="/occuringobservations">IDs of all occuring observations with links to the obs</a><br><a href="/api/activestations">IDs of all stations actively in an observation</a><br><a href="/api/onlinestations">All online stations</a><br><a href="/api/occuringobservations">IDs of all occuring observations</a>'
|
||||||
|
|
||||||
|
@app.route("/map_view")
|
||||||
|
def map_view():
|
||||||
|
stations = []
|
||||||
|
for x in Stations:
|
||||||
|
stations.append({'id':x['id'],'name':x['name'],'lat_lng':[x["lat"],x['lng']]})
|
||||||
|
return render_template("map.html",stations = stations)
|
||||||
|
|
||||||
|
@app.route('/occuringobservations')
|
||||||
|
def occuring_observations():
|
||||||
|
obs = []
|
||||||
|
for x in Passes:
|
||||||
|
obs.append("<a href='https://network.satnogs.org/observations/"+str(x.id)+"'>"+str(x.id)+"</a>")
|
||||||
|
return json.dumps(obs)
|
||||||
|
|
||||||
|
@app.route('/api/activestations')
|
||||||
|
def api_active_stations():
|
||||||
|
stations = []
|
||||||
|
for x in Passes:
|
||||||
|
stations.append(x.ground_station)
|
||||||
|
return json.dumps(stations)
|
||||||
|
|
||||||
|
@app.route('/api/onlinestations')
|
||||||
|
def api_online_stations():
|
||||||
|
stations = []
|
||||||
|
for x in Stations:
|
||||||
|
stations.append(x["id"])
|
||||||
|
return json.dumps(stations)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/occuringobservations')
|
||||||
|
def api_occuring_observations():
|
||||||
|
obs = []
|
||||||
|
for x in Passes:
|
||||||
|
obs.append(x.id)
|
||||||
|
return json.dumps(obs)
|
||||||
|
|
||||||
|
@app.route('/api/occuringsats')
|
||||||
|
def api_occuring_sats():
|
||||||
|
obs = {}
|
||||||
|
for x in Passes:
|
||||||
|
satellite = ephem.readtle(TLEs[x.satellite['norad_cat_id']][0],TLEs[x.satellite['norad_cat_id']][1],TLEs[x.satellite['norad_cat_id']][2])
|
||||||
|
now = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
satellite.compute(now)
|
||||||
|
lat = satellite.sublat*57.295779514
|
||||||
|
long = satellite.sublong*57.295779514
|
||||||
|
obs[x.satellite['norad_cat_id']] = {"name":x.satellite["name"],"transmitter_name":x.transmitter["description"],"transmitter_downlink":x.transmitter["downlink_low"],"transmitter_mode":modes[x.transmitter["mode_id"]],"lat_lng":[lat,long]}
|
||||||
|
return json.dumps(obs)
|
||||||
|
|
||||||
|
@app.route('/api/satstationpairs')
|
||||||
|
def api_sat_station_pairs():
|
||||||
|
pairs = []
|
||||||
|
for x in Passes:
|
||||||
|
pairs.append([x.ground_station,x.satellite['norad_cat_id']])
|
||||||
|
return json.dumps(pairs)
|
||||||
|
|
||||||
|
@app.route('/api/getsatloc/<int:norad>')
|
||||||
|
def get_sat_loc(norad):
|
||||||
|
satellite = ephem.readtle(TLEs[norad][0],TLEs[norad][1],TLEs[norad][2])
|
||||||
|
now = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
satellite.compute(now)
|
||||||
|
lat = satellite.sublat*57.295779514
|
||||||
|
long = satellite.sublong*57.295779514
|
||||||
|
return json.dumps([lat,long])
|
||||||
|
|
||||||
|
updatePasses()
|
||||||
|
updateStations()
|
||||||
|
updateTLE()
|
||||||
|
scheduler.start()
|
||||||
|
app.run(use_reloader=False,host = "0.0.0.0")
|
|
@ -0,0 +1,188 @@
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
NETWORK_DEV_BASE_URL = 'https://network-dev.satnogs.org'
|
||||||
|
NETWORK_BASE_URL = 'https://network.satnogs.org'
|
||||||
|
DB_BASE_URL = 'https://db.satnogs.org'
|
||||||
|
DB_DEV_BASE_URL = 'https://db-dev.satnogs.org'
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_observation_data_from_id(norad_id, start, end, prod=True):
|
||||||
|
# Get all observations of the satellite with the given `norad_id` in the given timeframe
|
||||||
|
# https://network.satnogs.org/api/observations/?satellite__norad_cat_id=25544&start=2018-06-10T00:00&end=2018-06-15T00:00
|
||||||
|
|
||||||
|
query_str = '{}/api/observations/?satellite__norad_cat_id={}&start={}&end={}'
|
||||||
|
|
||||||
|
url = query_str.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
||||||
|
norad_id,
|
||||||
|
start.isoformat(),
|
||||||
|
end.isoformat())
|
||||||
|
|
||||||
|
# print(url)
|
||||||
|
r = requests.get(url=url)
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
print("No observations found for {}, start: {}, end: {}.".format(norad_id, start_time, end_time))
|
||||||
|
raise
|
||||||
|
|
||||||
|
observations = r.json()
|
||||||
|
|
||||||
|
next_page_available = ('Link' in r.headers.keys())
|
||||||
|
|
||||||
|
if next_page_available:
|
||||||
|
parts = r.headers['Link'].split(',')
|
||||||
|
for part in parts:
|
||||||
|
if part[-5:-1] == 'next':
|
||||||
|
next_page_url = part[1:-13]
|
||||||
|
|
||||||
|
while next_page_available:
|
||||||
|
# print(next_page_url)
|
||||||
|
r = requests.get(url=next_page_url)
|
||||||
|
|
||||||
|
observations.extend(r.json())
|
||||||
|
|
||||||
|
next_page_available = False
|
||||||
|
|
||||||
|
if 'Link' in r.headers.keys():
|
||||||
|
parts = r.headers['Link'].split(',')
|
||||||
|
for part in parts:
|
||||||
|
if part[-5:-1] == 'next':
|
||||||
|
next_page_url = part[1:-13]
|
||||||
|
next_page_available = True
|
||||||
|
|
||||||
|
# Current prod is broken and can't filter on NORAD ID correctly, use client-side filtering instead
|
||||||
|
observations = list(filter(lambda o: o['norad_cat_id'] == norad_id, observations))
|
||||||
|
|
||||||
|
return observations
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_observation_data(observation_ids, prod=True):
|
||||||
|
# Get station location from the observation via the observation_id
|
||||||
|
|
||||||
|
observations = []
|
||||||
|
for observation_id in observation_ids:
|
||||||
|
r = requests.get(url='{}/api/observations/{}/'.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
||||||
|
observation_id))
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
print("Observation {} not found in network.".format(observation_id))
|
||||||
|
continue
|
||||||
|
observations.append(r.json())
|
||||||
|
|
||||||
|
return observations
|
||||||
|
|
||||||
|
def fetch_ground_station_data(ground_station_ids, prod=True):
|
||||||
|
# Fetch ground station metadata from network
|
||||||
|
ground_stations = []
|
||||||
|
for ground_station_id in ground_station_ids:
|
||||||
|
r = requests.get(url='{}/api/stations/{}/'.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
||||||
|
ground_station_id))
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
print("Ground Station {} not found in db.".format(ground_station_id))
|
||||||
|
raise
|
||||||
|
data = r.json()
|
||||||
|
ground_stations.append(r.json())
|
||||||
|
return ground_stations
|
||||||
|
|
||||||
|
def fetch_satellite_data(norad_cat_id):
|
||||||
|
# Fetch satellite metadata from network
|
||||||
|
r = requests.get(url='{}/api/satellites/{}/'.format(DB_BASE_URL, norad_cat_id))
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
print("ERROR: Satellite {} not found in network.".format(norad_cat_id))
|
||||||
|
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def fetch_tle_of_observation(observation_id, prod=True):
|
||||||
|
url = '{}/observations/{}/'.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
||||||
|
observation_id)
|
||||||
|
r = requests.get(url=url)
|
||||||
|
observation_page_html = r.text
|
||||||
|
|
||||||
|
regex = r"<pre>1 (.*)<br>2 (.*)</pre>"
|
||||||
|
matches = re.search(regex, observation_page_html)
|
||||||
|
|
||||||
|
obs_tle_2 = '1 ' + matches.group(1)
|
||||||
|
obs_tle_3 = '2 ' + matches.group(2)
|
||||||
|
|
||||||
|
return [obs_tle_2, obs_tle_3]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_telemetry(norad_id, max_frames, url):
|
||||||
|
# http://db-dev.satnogs.org/api/telemetry/?satellite=43595
|
||||||
|
|
||||||
|
query_str = '{}/api/telemetry/?satellite={}'
|
||||||
|
url = query_str.format(url, norad_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = fetch_multi_page_api_endpoint(url, max_entries=max_frames)
|
||||||
|
except HTTPError:
|
||||||
|
print("No telemetry found for {}.".format(norad_id))
|
||||||
|
raise
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_satellites(max_satellites, url):
|
||||||
|
query_str = '{}/api/satellites/'
|
||||||
|
url = query_str.format(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = fetch_multi_page_api_endpoint(url, max_entries=max_satellites)
|
||||||
|
except HTTPError:
|
||||||
|
print("An HTTPError occured.")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def fetch_multi_page_api_endpoint(url, max_entries):
|
||||||
|
# print(url)
|
||||||
|
r = requests.get(url=url)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
|
||||||
|
next_page_available = ('Link' in r.headers.keys())
|
||||||
|
|
||||||
|
if next_page_available and (not max_entries or len(data) < max_entries):
|
||||||
|
parts = r.headers['Link'].split(',')
|
||||||
|
for part in parts:
|
||||||
|
if part[-5:-1] == 'next':
|
||||||
|
next_page_url = part[1:-13]
|
||||||
|
|
||||||
|
while next_page_available and (not max_entries or len(data) < max_entries):
|
||||||
|
# print(next_page_url)
|
||||||
|
r = requests.get(url=next_page_url)
|
||||||
|
|
||||||
|
data.extend(r.json())
|
||||||
|
|
||||||
|
next_page_available = False
|
||||||
|
|
||||||
|
if 'Link' in r.headers.keys():
|
||||||
|
parts = r.headers['Link'].split(',')
|
||||||
|
for part in parts:
|
||||||
|
if part[-5:-1] == 'next':
|
||||||
|
next_page_url = part[1:-13]
|
||||||
|
next_page_available = True
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def post_telemetry(norad_id,
|
||||||
|
source,
|
||||||
|
lon,
|
||||||
|
lat,
|
||||||
|
timestamp,
|
||||||
|
frame,
|
||||||
|
base_url=DB_DEV_BASE_URL):
|
||||||
|
payload = {'noradID': norad_id,
|
||||||
|
'source': source,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'latitude': lat,
|
||||||
|
'longitude': lon,
|
||||||
|
'frame': frame}
|
||||||
|
|
||||||
|
url = '{}/api/telemetry/'.format(base_url)
|
||||||
|
r = requests.post(url, data=payload)
|
||||||
|
|
||||||
|
if r.status_code != 201:
|
||||||
|
print('ERROR {}: {}'.format(r.status_code, r.text))
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,147 @@
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css"
|
||||||
|
integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
|
||||||
|
crossorigin=""/>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"
|
||||||
|
integrity="sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA=="
|
||||||
|
crossorigin=""></script>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#mapid { height: 500px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row"> .
|
||||||
|
<div class="col-md-12">
|
||||||
|
<center>
|
||||||
|
<img src="/static/satnogs-net-logo.png">
|
||||||
|
<br>
|
||||||
|
<div id="mapid" ></div>
|
||||||
|
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var sat = L.icon({
|
||||||
|
iconUrl: 'static/satellite-marker.png',
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [20, 20],
|
||||||
|
popupAnchor: [0, 0],
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var station = L.icon({
|
||||||
|
iconUrl: 'static/station-marker.png',
|
||||||
|
iconSize: [20, 20],
|
||||||
|
iconAnchor: [10, 10],
|
||||||
|
popupAnchor: [0, 0],
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var active_station = L.icon({
|
||||||
|
iconUrl: 'static/active-station-marker.png',
|
||||||
|
iconSize: [20, 20],
|
||||||
|
iconAnchor: [10, 10],
|
||||||
|
popupAnchor: [0, 0],
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var mymap = L.map('mapid').setView([0,0], 2);
|
||||||
|
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
|
||||||
|
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.streets',
|
||||||
|
accessToken: 'pk.eyJ1IjoiY2hpYmlsbCIsImEiOiJjamxsNHBuZG4wdG1uM3FwYTN5c2ZubmxrIn0.ghkx6AngBzUiZQWBAWKziQ'
|
||||||
|
}).addTo(mymap);
|
||||||
|
|
||||||
|
stationList = {}
|
||||||
|
|
||||||
|
{% for x in stations %}
|
||||||
|
marker = L.marker({{x["lat_lng"]}},{icon: station,zIndexOffset:-1000}).addTo(mymap);
|
||||||
|
marker.bindPopup("<b>Name: {{x['name']}}</b>")
|
||||||
|
stationList[{{x['id']}}] = marker
|
||||||
|
|
||||||
|
{%endfor%}
|
||||||
|
|
||||||
|
|
||||||
|
sats = {}
|
||||||
|
|
||||||
|
links = {}
|
||||||
|
|
||||||
|
|
||||||
|
$.get("/api/occuringsats", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
Object.keys(data).forEach(function(key){
|
||||||
|
marker = L.marker(data[key]["lat_lng"],{icon: sat,zIndexOffset:1000}).addTo(mymap);
|
||||||
|
marker.bindPopup("<b>Name: "+data[key]["name"]+"</b>");
|
||||||
|
sats[key] = marker;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("/api/satstationpairs", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
data.forEach(function(entry){
|
||||||
|
var firstpolyline = new L.Polyline([[stationList[entry[0]]._latlng.lat,stationList[entry[0]]._latlng.lng],[sats[entry[1]]._latlng.lat,sats[entry[1]]._latlng.lng]], {color: 'red',weight: 3,opacity: 1,smoothFactor: 1});
|
||||||
|
firstpolyline.addTo(mymap)
|
||||||
|
firstpolyline.bringToFront()
|
||||||
|
links[entry[1]] = firstpolyline
|
||||||
|
stationList[entry[0]].setIcon(active_station)
|
||||||
|
stationList[entry[0]].setZIndexOffset(500)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function(){
|
||||||
|
|
||||||
|
Object.keys(links).forEach(function(key){
|
||||||
|
links[key].removeFrom(mymap)
|
||||||
|
|
||||||
|
delete links[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.keys(links).forEach(function(key){
|
||||||
|
|
||||||
|
links[key].removeFrom(mymap)
|
||||||
|
delete links[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.keys(stationList).forEach(function(key){
|
||||||
|
stationList[key].setIcon(station)
|
||||||
|
stationList[key].setZIndexOffset(-1000)
|
||||||
|
|
||||||
|
})
|
||||||
|
$.get("/api/occuringsats", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
Object.keys(sats).forEach(function(key){
|
||||||
|
sats[key].removeFrom(mymap)
|
||||||
|
delete sats[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.keys(data).forEach(function(key){
|
||||||
|
marker = L.marker(data[key]["lat_lng"],{icon: sat,zIndexOffset:1000}).addTo(mymap);
|
||||||
|
marker.bindPopup("<b>Name: "+data[key]["name"]+"</b>");
|
||||||
|
sats[key] = marker;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("/api/satstationpairs", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
data.forEach(function(entry){
|
||||||
|
var firstpolyline = new L.Polyline([[stationList[entry[0]]._latlng.lat,stationList[entry[0]]._latlng.lng],[sats[entry[1]]._latlng.lat,sats[entry[1]]._latlng.lng]], {color: 'red',weight:3,opacity:1,smoothFactor: 1});
|
||||||
|
firstpolyline.addTo(mymap)
|
||||||
|
links[entry[1]] = firstpolyline
|
||||||
|
stationList[entry[0]].setIcon(active_station)
|
||||||
|
stationList[entry[0]].setZIndexOffset(500)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(links)
|
||||||
|
|
||||||
|
}, 60000*2);
|
||||||
|
|
||||||
|
</script>
|
Loading…
Reference in New Issue