Merge branch 'dev' into 'master'
Merge Dev branch into Main. Completes the rewrite. See merge request chibill/satnogsmap!1dev
commit
f46b1e1d60
|
@ -0,0 +1,4 @@
|
||||||
|
requests==2.21.0
|
||||||
|
flask==1.0.2
|
||||||
|
apscheduler==3.5.3
|
||||||
|
satellitetle==0.5.1
|
116
satnogs.py
116
satnogs.py
|
@ -2,20 +2,20 @@ from datetime import datetime , timedelta
|
||||||
import requests
|
import requests
|
||||||
from flask import Flask , render_template,redirect,url_for
|
from flask import Flask , render_template,redirect,url_for
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
import ephem
|
|
||||||
from satnogs_api_client import fetch_satellites, DB_BASE_URL,fetch_tle_of_observation
|
from satnogs_api_client import fetch_satellites, DB_BASE_URL,fetch_tle_of_observation
|
||||||
from satellite_tle import fetch_tles
|
from satellite_tle import fetch_tles
|
||||||
|
|
||||||
scheduler = BackgroundScheduler()
|
scheduler = BackgroundScheduler()
|
||||||
app = Flask(__name__)
|
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 = []
|
Passes = []
|
||||||
|
Occuring_sats = {}
|
||||||
Stations = []
|
Stations = []
|
||||||
TLEs = {}
|
TLEs = {}
|
||||||
|
Transmitters = {}
|
||||||
|
|
||||||
class Pass:
|
class Pass:
|
||||||
id = 0
|
id = 0
|
||||||
|
@ -26,9 +26,6 @@ class Pass:
|
||||||
transmitter = None
|
transmitter = None
|
||||||
norad = 0
|
norad = 0
|
||||||
|
|
||||||
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():
|
def getActive():
|
||||||
|
@ -66,12 +63,12 @@ def getActive():
|
||||||
temp.start = datetime.strptime(x["start"],'%Y-%m-%dT%H:%M:%Sz')
|
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.end = datetime.strptime(x["end"],'%Y-%m-%dT%H:%M:%Sz')
|
||||||
temp.ground_station = x["ground_station"]
|
temp.ground_station = x["ground_station"]
|
||||||
|
temp.transmitter = x["transmitter"]
|
||||||
temp.norad = str(x["norad_cat_id"])
|
temp.norad = str(x["norad_cat_id"])
|
||||||
try:
|
try:
|
||||||
temp.satellite = requests.get("https://db.satnogs.org/api/satellites/"+str(x["norad_cat_id"])).json()
|
temp.satellite = requests.get("https://db.satnogs.org/api/satellites/"+str(x["norad_cat_id"])).json()
|
||||||
except:
|
except:
|
||||||
temp.satellite = {"name":""}
|
temp.satellite = {"name":""}
|
||||||
#temp.transmitter = requests.get("https://db.satnogs.org/api/transmitters/"+x["transmitter"]).json()
|
|
||||||
Passes.append(temp)
|
Passes.append(temp)
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,12 +101,31 @@ def GetGroundStations():
|
||||||
|
|
||||||
return stations
|
return stations
|
||||||
|
|
||||||
|
@scheduler.scheduled_job('interval',days=5)
|
||||||
|
def updateTransmitters():
|
||||||
|
global Transmitters
|
||||||
|
print "Updating Transmitters"
|
||||||
|
temp = requests.get("https://db.satnogs.org/api/transmitters/").json()
|
||||||
|
for x in temp:
|
||||||
|
if str(x["norad_cat_id"]) in Transmitters.keys():
|
||||||
|
Transmitters[str(x["norad_cat_id"])][x["uuid"]] = [x["description"],"#"+str("%06x" % random.randint(0, 0xFFFFFF))]
|
||||||
|
else:
|
||||||
|
Transmitters[str(x["norad_cat_id"])]={}
|
||||||
|
Transmitters[str(x["norad_cat_id"])][x["uuid"]] = [x["description"],"#"+str("%06x" % random.randint(0, 0xFFFFFF))]
|
||||||
|
#print Transmitters
|
||||||
|
|
||||||
@scheduler.scheduled_job('interval',minutes=3)
|
@scheduler.scheduled_job('interval',minutes=3)
|
||||||
def updatePasses():
|
def updatePasses():
|
||||||
global Passes
|
global Passes
|
||||||
|
global Occuring_sats
|
||||||
print "Updating Passes"
|
print "Updating Passes"
|
||||||
Passes = getActive()
|
Passes = getActive()
|
||||||
|
Occuring_sats = {}
|
||||||
|
for x in Passes:
|
||||||
|
if x.satellite['norad_cat_id'] not in TLEs.keys():
|
||||||
|
q = fetch_tle_of_observation(x.id)
|
||||||
|
TLEs[ x.norad ] = [str(x.satellite["name"]),str(q[0]),str(q[1])]
|
||||||
|
Occuring_sats[x.norad] = TLEs[x.norad]
|
||||||
|
|
||||||
@scheduler.scheduled_job('interval',hours=1)
|
@scheduler.scheduled_job('interval',hours=1)
|
||||||
def updateStations():
|
def updateStations():
|
||||||
|
@ -121,13 +137,14 @@ def updateStations():
|
||||||
def updateTLE():
|
def updateTLE():
|
||||||
print "Updating TLE"
|
print "Updating TLE"
|
||||||
global TlEs
|
global TlEs
|
||||||
sats = fetch_satellites(url=DB_BASE_URL, max_satellites=None)
|
sats = fetch_satellites(None,DB_BASE_URL)
|
||||||
satnogs_db_norad_ids = set(sat['norad_cat_id'] for sat in sats if sat['status'] != 're-entered')
|
satnogs_db_norad_ids = set(sat['norad_cat_id'] for sat in sats if sat['status'] != 're-entered')
|
||||||
# Remove satellites with temporary norad ids
|
# Remove satellites with temporary norad ids
|
||||||
temporary_norad_ids = set(filter(lambda norad_id: norad_id >= 99900, satnogs_db_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
|
satnogs_db_norad_ids = satnogs_db_norad_ids - temporary_norad_ids
|
||||||
|
|
||||||
# Fetch TLEs for the satellites of interest
|
# Fetch TLEs for the satellites of interest
|
||||||
|
|
||||||
tles = fetch_tles(satnogs_db_norad_ids)
|
tles = fetch_tles(satnogs_db_norad_ids)
|
||||||
TLEs = {}
|
TLEs = {}
|
||||||
for norad_id, (source, tle) in tles.items():
|
for norad_id, (source, tle) in tles.items():
|
||||||
|
@ -139,77 +156,42 @@ def updateTLE():
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def map_view():
|
def map_view():
|
||||||
stations = []
|
return render_template("map.html")
|
||||||
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')
|
@app.route('/active_stations')
|
||||||
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():
|
def api_active_stations():
|
||||||
stations = []
|
sations = []
|
||||||
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:
|
for x in Stations:
|
||||||
stations.append(x["id"])
|
sations.append({'id':x['id'],'name':x['name'],'lat_lng':[x["lat"],x['lng']]})
|
||||||
return json.dumps(stations)
|
return json.dumps(sations)
|
||||||
|
|
||||||
|
@app.route('/stations_from_sat/<string:norad>')
|
||||||
|
def api_occuring_observations(norad):
|
||||||
@app.route('/api/occuringobservations')
|
|
||||||
def api_occuring_observations():
|
|
||||||
obs = []
|
obs = []
|
||||||
|
trans = []
|
||||||
for x in Passes:
|
for x in Passes:
|
||||||
obs.append(x.id)
|
if x.norad == norad:
|
||||||
return json.dumps(obs)
|
obs.append([x.ground_station,Transmitters[norad][x.transmitter][1]])
|
||||||
|
trans.append(x.transmitter)
|
||||||
|
#print Transmitters[norad].values()
|
||||||
|
|
||||||
@app.route('/api/occuringsats')
|
transList = []
|
||||||
|
for x in set(trans):
|
||||||
|
transList.append(Transmitters[norad][x])
|
||||||
|
#print transList,norad
|
||||||
|
return json.dumps([obs,transList])
|
||||||
|
|
||||||
|
@app.route('/occuring_sats')
|
||||||
def api_occuring_sats():
|
def api_occuring_sats():
|
||||||
obs = {}
|
return json.dumps(Occuring_sats)
|
||||||
for x in Passes:
|
|
||||||
if x.satellite['norad_cat_id'] not in TLEs.keys():
|
|
||||||
q = fetch_tle_of_observation(x.id)
|
|
||||||
TLEs[ x.norad ] = [str(x.satellite["name"]),str(q[0]),str(q[1])]
|
|
||||||
satellite = ephem.readtle(TLEs[x.norad][0],TLEs[x.norad][1],TLEs[x.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
|
|
||||||
if x.satellite['norad_cat_id'] == 25544:
|
|
||||||
obs[x.norad] = {"name":satellite.name,"lat_lng":[lat,long],"eclipsed":satellite.eclipsed,"image":"/static/ISS"}
|
|
||||||
else:
|
|
||||||
obs[x.norad] = {"name":satellite.name,"lat_lng":[lat,long],"eclipsed":satellite.eclipsed,"image":None}
|
|
||||||
return json.dumps(obs)
|
|
||||||
|
|
||||||
@app.route('/api/satstationpairs')
|
|
||||||
def api_sat_station_pairs():
|
|
||||||
pairs = []
|
|
||||||
for x in Passes:
|
|
||||||
pairs.append([x.ground_station,x.norad])
|
|
||||||
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,satellite.eclipsed])
|
|
||||||
|
|
||||||
updatePasses()
|
updatePasses()
|
||||||
updateStations()
|
updateStations()
|
||||||
updateTLE()
|
updateTLE()
|
||||||
|
updateTransmitters()
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
app.run(use_reloader=False,host = "0.0.0.0")
|
app.run(use_reloader=False,host = "0.0.0.0",port=5001)
|
|
@ -1,6 +1,8 @@
|
||||||
import requests
|
from __future__ import print_function
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
NETWORK_DEV_BASE_URL = 'https://network-dev.satnogs.org'
|
NETWORK_DEV_BASE_URL = 'https://network-dev.satnogs.org'
|
||||||
NETWORK_BASE_URL = 'https://network.satnogs.org'
|
NETWORK_BASE_URL = 'https://network.satnogs.org'
|
||||||
|
@ -8,51 +10,42 @@ DB_BASE_URL = 'https://db.satnogs.org'
|
||||||
DB_DEV_BASE_URL = 'https://db-dev.satnogs.org'
|
DB_DEV_BASE_URL = 'https://db-dev.satnogs.org'
|
||||||
|
|
||||||
|
|
||||||
|
def get_paginated_endpoint(url, max_entries=None):
|
||||||
|
r = requests.get(url=url)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
|
||||||
|
while 'next' in r.links and (not max_entries or len(data) < max_entries):
|
||||||
|
next_page_url = r.links['next']['url']
|
||||||
|
|
||||||
|
r = requests.get(url=next_page_url)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
data.extend(r.json())
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def fetch_observation_data_from_id(norad_id, start, end, prod=True):
|
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
|
# 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
|
# 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={}'
|
query_str = '{}/api/observations/' \
|
||||||
|
'?satellite__norad_cat_id={}&start={}&end={}'
|
||||||
|
|
||||||
url = query_str.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
url = query_str.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
||||||
norad_id,
|
norad_id,
|
||||||
start.isoformat(),
|
start.isoformat(),
|
||||||
end.isoformat())
|
end.isoformat())
|
||||||
|
|
||||||
# print(url)
|
observations = get_paginated_endpoint(url)
|
||||||
r = requests.get(url=url)
|
|
||||||
|
|
||||||
if r.status_code != requests.codes.ok:
|
# Current prod is broken and can't filter on NORAD ID correctly,
|
||||||
print("No observations found for {}, start: {}, end: {}.".format(norad_id, start_time, end_time))
|
# use client-side filtering instead
|
||||||
raise
|
observations = list(observation for observation in observations
|
||||||
|
if observation['norad_cat_id'] == norad_id)
|
||||||
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
|
return observations
|
||||||
|
|
||||||
|
@ -62,38 +55,45 @@ def fetch_observation_data(observation_ids, prod=True):
|
||||||
|
|
||||||
observations = []
|
observations = []
|
||||||
for observation_id in observation_ids:
|
for observation_id in observation_ids:
|
||||||
r = requests.get(url='{}/api/observations/{}/'.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
base_url = (NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL)
|
||||||
|
r = requests.get(url='{}/api/observations/{}/'.format(base_url,
|
||||||
observation_id))
|
observation_id))
|
||||||
if r.status_code != requests.codes.ok:
|
r.raise_for_status()
|
||||||
print("Observation {} not found in network.".format(observation_id))
|
|
||||||
continue
|
|
||||||
observations.append(r.json())
|
observations.append(r.json())
|
||||||
|
|
||||||
return observations
|
return observations
|
||||||
|
|
||||||
|
|
||||||
def fetch_ground_station_data(ground_station_ids, prod=True):
|
def fetch_ground_station_data(ground_station_ids, prod=True):
|
||||||
# Fetch ground station metadata from network
|
# Fetch ground station metadata from network
|
||||||
ground_stations = []
|
ground_stations = []
|
||||||
for ground_station_id in ground_station_ids:
|
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,
|
# Skip frames from deleted groundstations, indidcated as ID 'None'
|
||||||
|
if str(ground_station_id) == 'None':
|
||||||
|
print("Skipping groundstation 'None'.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
base_url = (NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL)
|
||||||
|
r = requests.get(url='{}/api/stations/{}/'.format(base_url,
|
||||||
ground_station_id))
|
ground_station_id))
|
||||||
if r.status_code != requests.codes.ok:
|
r.raise_for_status()
|
||||||
print("Ground Station {} not found in db.".format(ground_station_id))
|
|
||||||
raise
|
|
||||||
data = r.json()
|
|
||||||
ground_stations.append(r.json())
|
ground_stations.append(r.json())
|
||||||
return ground_stations
|
return ground_stations
|
||||||
|
|
||||||
|
|
||||||
def fetch_satellite_data(norad_cat_id):
|
def fetch_satellite_data(norad_cat_id):
|
||||||
# Fetch satellite metadata from network
|
# Fetch satellite metadata from network
|
||||||
r = requests.get(url='{}/api/satellites/{}/'.format(DB_BASE_URL, norad_cat_id))
|
r = requests.get(url='{}/api/satellites/{}/'.format(DB_BASE_URL,
|
||||||
if r.status_code != requests.codes.ok:
|
norad_cat_id))
|
||||||
print("ERROR: Satellite {} not found in network.".format(norad_cat_id))
|
r.raise_for_status()
|
||||||
|
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
def fetch_tle_of_observation(observation_id, prod=True):
|
def fetch_tle_of_observation(observation_id, prod=True):
|
||||||
url = '{}/observations/{}/'.format(NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL,
|
base_url = (NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL)
|
||||||
|
url = '{}/observations/{}/'.format(base_url,
|
||||||
observation_id)
|
observation_id)
|
||||||
r = requests.get(url=url)
|
r = requests.get(url=url)
|
||||||
observation_page_html = r.text
|
observation_page_html = r.text
|
||||||
|
@ -107,68 +107,39 @@ def fetch_tle_of_observation(observation_id, prod=True):
|
||||||
return [obs_tle_2, obs_tle_3]
|
return [obs_tle_2, obs_tle_3]
|
||||||
|
|
||||||
|
|
||||||
def fetch_telemetry(norad_id, max_frames, url):
|
def fetch_telemetry(norad_id, url):
|
||||||
# http://db-dev.satnogs.org/api/telemetry/?satellite=43595
|
# http://db-dev.satnogs.org/api/telemetry/?satellite=43595
|
||||||
|
|
||||||
query_str = '{}/api/telemetry/?satellite={}'
|
query_str = '{}/api/telemetry/?satellite={}'
|
||||||
|
|
||||||
url = query_str.format(url, norad_id)
|
url = query_str.format(url, norad_id)
|
||||||
|
|
||||||
try:
|
telemetry = get_paginated_endpoint(url)
|
||||||
data = fetch_multi_page_api_endpoint(url, max_entries=max_frames)
|
|
||||||
except HTTPError:
|
|
||||||
print("No telemetry found for {}.".format(norad_id))
|
|
||||||
raise
|
|
||||||
|
|
||||||
return data
|
return telemetry
|
||||||
|
|
||||||
|
|
||||||
def fetch_satellites(max_satellites, url):
|
def fetch_transmitters(norad_id, url):
|
||||||
|
# http://db-dev.satnogs.org/api/transmitters/?satellite__norad_cat_id=25544
|
||||||
|
|
||||||
|
query_str = '{}/api/transmitters/?satellite__norad_cat_id={}'
|
||||||
|
|
||||||
|
url = query_str.format(url, norad_id)
|
||||||
|
|
||||||
|
transmitters = get_paginated_endpoint(url)
|
||||||
|
return transmitters
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_satellites(max_entries, url):
|
||||||
query_str = '{}/api/satellites/'
|
query_str = '{}/api/satellites/'
|
||||||
url = query_str.format(url)
|
url = query_str.format(url)
|
||||||
|
|
||||||
try:
|
satellites = get_paginated_endpoint(url, max_entries=max_entries)
|
||||||
data = fetch_multi_page_api_endpoint(url, max_entries=max_satellites)
|
return 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,
|
def post_telemetry(norad_id,
|
||||||
source,
|
source, # Receiver Callsign
|
||||||
lon,
|
lon,
|
||||||
lat,
|
lat,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.9 KiB |
|
@ -1,495 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// This file is part of Leaflet.Geodesic.
|
|
||||||
// Copyright (C) 2017 Henry Thasler
|
|
||||||
// based on code by Chris Veness Copyright (C) 2014 https://github.com/chrisveness/geodesy
|
|
||||||
//
|
|
||||||
// Leaflet.Geodesic is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Leaflet.Geodesic is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Leaflet.Geodesic. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
/** Extend Number object with method to convert numeric degrees to radians */
|
|
||||||
if (typeof Number.prototype.toRadians === "undefined") {
|
|
||||||
Number.prototype.toRadians = function() {
|
|
||||||
return this * Math.PI / 180;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extend Number object with method to convert radians to numeric (signed) degrees */
|
|
||||||
if (typeof Number.prototype.toDegrees === "undefined") {
|
|
||||||
Number.prototype.toDegrees = function() {
|
|
||||||
return this * 180 / Math.PI;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var INTERSECT_LNG = 179.999; // Lng used for intersection and wrap around on map edges
|
|
||||||
|
|
||||||
L.Geodesic = L.Polyline.extend({
|
|
||||||
options: {
|
|
||||||
color: "blue",
|
|
||||||
steps: 10,
|
|
||||||
dash: 1,
|
|
||||||
wrap: true
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(latlngs, options) {
|
|
||||||
this.options = this._merge_options(this.options, options);
|
|
||||||
this.options.dash = Math.max(1e-3, Math.min(1, parseFloat(this.options.dash) || 1));
|
|
||||||
this.datum = {};
|
|
||||||
this.datum.ellipsoid = {
|
|
||||||
a: 6378137,
|
|
||||||
b: 6356752.3142,
|
|
||||||
f: 1 / 298.257223563
|
|
||||||
}; // WGS-84
|
|
||||||
this._latlngs = this._generate_Geodesic(latlngs);
|
|
||||||
L.Polyline.prototype.initialize.call(this, this._latlngs, this.options);
|
|
||||||
},
|
|
||||||
|
|
||||||
setLatLngs: function(latlngs) {
|
|
||||||
this._latlngs = this._generate_Geodesic(latlngs);
|
|
||||||
L.Polyline.prototype.setLatLngs.call(this, this._latlngs);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates some statistic values of current geodesic multipolyline
|
|
||||||
* @returns (Object} Object with several properties (e.g. overall distance)
|
|
||||||
*/
|
|
||||||
getStats: function() {
|
|
||||||
let obj = {
|
|
||||||
distance: 0,
|
|
||||||
points: 0,
|
|
||||||
polygons: this._latlngs.length
|
|
||||||
}, poly, points;
|
|
||||||
|
|
||||||
for (poly = 0; poly < this._latlngs.length; poly++) {
|
|
||||||
obj.points += this._latlngs[poly].length;
|
|
||||||
for (points = 0; points < (this._latlngs[poly].length - 1); points++) {
|
|
||||||
obj.distance += this._vincenty_inverse(this._latlngs[poly][points],
|
|
||||||
this._latlngs[poly][points + 1]).distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates geodesic lines from geoJson. Replaces all current features of this instance.
|
|
||||||
* Supports LineString, MultiLineString and Polygon
|
|
||||||
* @param {Object} geojson - geosjon as object.
|
|
||||||
*/
|
|
||||||
geoJson: function(geojson) {
|
|
||||||
|
|
||||||
let normalized = L.GeoJSON.asFeature(geojson);
|
|
||||||
let features = normalized.type === "FeatureCollection" ? normalized.features : [
|
|
||||||
normalized
|
|
||||||
];
|
|
||||||
this._latlngs = [];
|
|
||||||
for (let feature of features) {
|
|
||||||
let geometry = feature.type === "Feature" ? feature.geometry :
|
|
||||||
feature,
|
|
||||||
coords = geometry.coordinates;
|
|
||||||
|
|
||||||
switch (geometry.type) {
|
|
||||||
case "LineString":
|
|
||||||
this._latlngs.push(this._generate_Geodesic([L.GeoJSON.coordsToLatLngs(
|
|
||||||
coords, 0)]));
|
|
||||||
break;
|
|
||||||
case "MultiLineString":
|
|
||||||
case "Polygon":
|
|
||||||
this._latlngs.push(this._generate_Geodesic(L.GeoJSON.coordsToLatLngs(
|
|
||||||
coords, 1)));
|
|
||||||
break;
|
|
||||||
case "Point":
|
|
||||||
case "MultiPoint":
|
|
||||||
console.log("Dude, points can't be drawn as geodesic lines...");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log("Drawing " + geometry.type +
|
|
||||||
" as a geodesic is not supported. Skipping...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
L.Polyline.prototype.setLatLngs.call(this, this._latlngs);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a great circle. Replaces all current lines.
|
|
||||||
* @param {Object} center - geographic position
|
|
||||||
* @param {number} radius - radius of the circle in metres
|
|
||||||
*/
|
|
||||||
createCircle: function(center, radius) {
|
|
||||||
let polylineIndex = 0;
|
|
||||||
let prev = {
|
|
||||||
lat: 0,
|
|
||||||
lng: 0,
|
|
||||||
brg: 0
|
|
||||||
};
|
|
||||||
let step;
|
|
||||||
|
|
||||||
this._latlngs = [];
|
|
||||||
this._latlngs[polylineIndex] = [];
|
|
||||||
|
|
||||||
let direct = this._vincenty_direct(L.latLng(center), 0, radius, this.options
|
|
||||||
.wrap);
|
|
||||||
prev = L.latLng(direct.lat, direct.lng);
|
|
||||||
this._latlngs[polylineIndex].push(prev);
|
|
||||||
for (step = 1; step <= this.options.steps;) {
|
|
||||||
direct = this._vincenty_direct(L.latLng(center), 360 / this.options
|
|
||||||
.steps * step, radius, this.options.wrap);
|
|
||||||
let gp = L.latLng(direct.lat, direct.lng);
|
|
||||||
if (Math.abs(gp.lng - prev.lng) > 180) {
|
|
||||||
let inverse = this._vincenty_inverse(prev, gp);
|
|
||||||
let sec = this._intersection(prev, inverse.initialBearing, {
|
|
||||||
lat: -89,
|
|
||||||
lng: ((gp.lng - prev.lng) > 0) ? -INTERSECT_LNG : INTERSECT_LNG
|
|
||||||
}, 0);
|
|
||||||
if (sec) {
|
|
||||||
this._latlngs[polylineIndex].push(L.latLng(sec.lat, sec.lng));
|
|
||||||
polylineIndex++;
|
|
||||||
this._latlngs[polylineIndex] = [];
|
|
||||||
prev = L.latLng(sec.lat, -sec.lng);
|
|
||||||
this._latlngs[polylineIndex].push(prev);
|
|
||||||
} else {
|
|
||||||
polylineIndex++;
|
|
||||||
this._latlngs[polylineIndex] = [];
|
|
||||||
this._latlngs[polylineIndex].push(gp);
|
|
||||||
prev = gp;
|
|
||||||
step++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._latlngs[polylineIndex].push(gp);
|
|
||||||
prev = gp;
|
|
||||||
step++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
L.Polyline.prototype.setLatLngs.call(this, this._latlngs);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a geodesic Polyline from given coordinates
|
|
||||||
* Note: dashed lines are under work
|
|
||||||
* @param {Object} latlngs - One or more polylines as an array. See Leaflet doc about Polyline
|
|
||||||
* @returns (Object} An array of arrays of geographical points.
|
|
||||||
*/
|
|
||||||
_generate_Geodesic: function(latlngs) {
|
|
||||||
let _geo = [], _geocnt = 0;
|
|
||||||
|
|
||||||
for (let poly = 0; poly < latlngs.length; poly++) {
|
|
||||||
_geo[_geocnt] = [];
|
|
||||||
let prev = L.latLng(latlngs[poly][0]);
|
|
||||||
for (let points = 0; points < (latlngs[poly].length - 1); points++) {
|
|
||||||
// use prev, so that wrapping behaves correctly
|
|
||||||
let pointA = prev;
|
|
||||||
let pointB = L.latLng(latlngs[poly][points + 1]);
|
|
||||||
if (pointA.equals(pointB)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let inverse = this._vincenty_inverse(pointA, pointB);
|
|
||||||
_geo[_geocnt].push(prev);
|
|
||||||
for (let s = 1; s <= this.options.steps;) {
|
|
||||||
let distance = inverse.distance / this.options.steps;
|
|
||||||
// dashed lines don't go the full distance between the points
|
|
||||||
let dist_mult = s - 1 + this.options.dash;
|
|
||||||
let direct = this._vincenty_direct(pointA, inverse.initialBearing, distance*dist_mult, this.options.wrap);
|
|
||||||
let gp = L.latLng(direct.lat, direct.lng);
|
|
||||||
if (Math.abs(gp.lng - prev.lng) > 180) {
|
|
||||||
let sec = this._intersection(pointA, inverse.initialBearing, {
|
|
||||||
lat: -89,
|
|
||||||
lng: ((gp.lng - prev.lng) > 0) ? -INTERSECT_LNG : INTERSECT_LNG
|
|
||||||
}, 0);
|
|
||||||
if (sec) {
|
|
||||||
_geo[_geocnt].push(L.latLng(sec.lat, sec.lng));
|
|
||||||
_geocnt++;
|
|
||||||
_geo[_geocnt] = [];
|
|
||||||
prev = L.latLng(sec.lat, -sec.lng);
|
|
||||||
_geo[_geocnt].push(prev);
|
|
||||||
} else {
|
|
||||||
_geocnt++;
|
|
||||||
_geo[_geocnt] = [];
|
|
||||||
_geo[_geocnt].push(gp);
|
|
||||||
prev = gp;
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_geo[_geocnt].push(gp);
|
|
||||||
// Dashed lines start a new line
|
|
||||||
if (this.options.dash < 1){
|
|
||||||
_geocnt++;
|
|
||||||
// go full distance this time, to get starting point for next line
|
|
||||||
let direct_full = this._vincenty_direct(pointA, inverse.initialBearing, distance*s, this.options.wrap);
|
|
||||||
_geo[_geocnt] = [];
|
|
||||||
prev = L.latLng(direct_full.lat, direct_full.lng);
|
|
||||||
_geo[_geocnt].push(prev);
|
|
||||||
}
|
|
||||||
else prev = gp;
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_geocnt++;
|
|
||||||
}
|
|
||||||
return _geo;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vincenty direct calculation.
|
|
||||||
* based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {number} initialBearing - Initial bearing in degrees from north.
|
|
||||||
* @param {number} distance - Distance along bearing in metres.
|
|
||||||
* @returns (Object} Object including point (destination point), finalBearing.
|
|
||||||
*/
|
|
||||||
|
|
||||||
_vincenty_direct: function(p1, initialBearing, distance, wrap) {
|
|
||||||
var φ1 = p1.lat.toRadians(),
|
|
||||||
λ1 = p1.lng.toRadians();
|
|
||||||
var α1 = initialBearing.toRadians();
|
|
||||||
var s = distance;
|
|
||||||
|
|
||||||
var a = this.datum.ellipsoid.a,
|
|
||||||
b = this.datum.ellipsoid.b,
|
|
||||||
f = this.datum.ellipsoid.f;
|
|
||||||
|
|
||||||
var sinα1 = Math.sin(α1);
|
|
||||||
var cosα1 = Math.cos(α1);
|
|
||||||
|
|
||||||
var tanU1 = (1 - f) * Math.tan(φ1),
|
|
||||||
cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)),
|
|
||||||
sinU1 = tanU1 * cosU1;
|
|
||||||
var σ1 = Math.atan2(tanU1, cosα1);
|
|
||||||
var sinα = cosU1 * sinα1;
|
|
||||||
var cosSqα = 1 - sinα * sinα;
|
|
||||||
var uSq = cosSqα * (a * a - b * b) / (b * b);
|
|
||||||
var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 *
|
|
||||||
uSq)));
|
|
||||||
var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
|
|
||||||
|
|
||||||
var σ = s / (b * A),
|
|
||||||
σʹ, iterations = 0;
|
|
||||||
var sinσ, cosσ;
|
|
||||||
var cos2σM;
|
|
||||||
do {
|
|
||||||
cos2σM = Math.cos(2 * σ1 + σ);
|
|
||||||
sinσ = Math.sin(σ);
|
|
||||||
cosσ = Math.cos(σ);
|
|
||||||
var Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM *
|
|
||||||
cos2σM) -
|
|
||||||
B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM *
|
|
||||||
cos2σM)));
|
|
||||||
σʹ = σ;
|
|
||||||
σ = s / (b * A) + Δσ;
|
|
||||||
} while (Math.abs(σ - σʹ) > 1e-12 && ++iterations);
|
|
||||||
|
|
||||||
var x = sinU1 * sinσ - cosU1 * cosσ * cosα1;
|
|
||||||
var φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - f) *
|
|
||||||
Math.sqrt(sinα * sinα + x * x));
|
|
||||||
var λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);
|
|
||||||
var C = f / 16 * cosSqα * (4 + f * (4 - 3 * cosSqα));
|
|
||||||
var L = λ - (1 - C) * f * sinα *
|
|
||||||
(σ + C * sinσ * (cos2σM + C * cosσ * (-1 + 2 * cos2σM * cos2σM)));
|
|
||||||
|
|
||||||
var λ2;
|
|
||||||
if (wrap) {
|
|
||||||
λ2 = (λ1 + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180...+180
|
|
||||||
} else {
|
|
||||||
λ2 = (λ1 + L); // do not normalize
|
|
||||||
}
|
|
||||||
|
|
||||||
var revAz = Math.atan2(sinα, -x);
|
|
||||||
|
|
||||||
return {
|
|
||||||
lat: φ2.toDegrees(),
|
|
||||||
lng: λ2.toDegrees(),
|
|
||||||
finalBearing: revAz.toDegrees()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vincenty inverse calculation.
|
|
||||||
* based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {LatLng} p1 - Latitude/longitude of start point.
|
|
||||||
* @param {LatLng} p2 - Latitude/longitude of destination point.
|
|
||||||
* @returns {Object} Object including distance, initialBearing, finalBearing.
|
|
||||||
* @throws {Error} If formula failed to converge.
|
|
||||||
*/
|
|
||||||
_vincenty_inverse: function(p1, p2) {
|
|
||||||
var φ1 = p1.lat.toRadians(),
|
|
||||||
λ1 = p1.lng.toRadians();
|
|
||||||
var φ2 = p2.lat.toRadians(),
|
|
||||||
λ2 = p2.lng.toRadians();
|
|
||||||
|
|
||||||
var a = this.datum.ellipsoid.a,
|
|
||||||
b = this.datum.ellipsoid.b,
|
|
||||||
f = this.datum.ellipsoid.f;
|
|
||||||
|
|
||||||
var L = λ2 - λ1;
|
|
||||||
var tanU1 = (1 - f) * Math.tan(φ1),
|
|
||||||
cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)),
|
|
||||||
sinU1 = tanU1 * cosU1;
|
|
||||||
var tanU2 = (1 - f) * Math.tan(φ2),
|
|
||||||
cosU2 = 1 / Math.sqrt((1 + tanU2 * tanU2)),
|
|
||||||
sinU2 = tanU2 * cosU2;
|
|
||||||
|
|
||||||
var λ = L,
|
|
||||||
λʹ, iterations = 0;
|
|
||||||
var cosSqα, sinσ, cos2σM, cosσ, σ, sinλ, cosλ;
|
|
||||||
do {
|
|
||||||
sinλ = Math.sin(λ);
|
|
||||||
cosλ = Math.cos(λ);
|
|
||||||
var sinSqσ = (cosU2 * sinλ) * (cosU2 * sinλ) + (cosU1 * sinU2 -
|
|
||||||
sinU1 * cosU2 * cosλ) * (cosU1 * sinU2 - sinU1 * cosU2 * cosλ);
|
|
||||||
sinσ = Math.sqrt(sinSqσ);
|
|
||||||
if (sinσ == 0) return 0; // co-incident points
|
|
||||||
cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;
|
|
||||||
σ = Math.atan2(sinσ, cosσ);
|
|
||||||
var sinα = cosU1 * cosU2 * sinλ / sinσ;
|
|
||||||
cosSqα = 1 - sinα * sinα;
|
|
||||||
cos2σM = cosσ - 2 * sinU1 * sinU2 / cosSqα;
|
|
||||||
if (isNaN(cos2σM)) cos2σM = 0; // equatorial line: cosSqα=0 (§6)
|
|
||||||
var C = f / 16 * cosSqα * (4 + f * (4 - 3 * cosSqα));
|
|
||||||
λʹ = λ;
|
|
||||||
λ = L + (1 - C) * f * sinα * (σ + C * sinσ * (cos2σM + C * cosσ * (-
|
|
||||||
1 + 2 * cos2σM * cos2σM)));
|
|
||||||
} while (Math.abs(λ - λʹ) > 1e-12 && ++iterations < 100);
|
|
||||||
if (iterations >= 100) {
|
|
||||||
console.log("Formula failed to converge. Altering target position.");
|
|
||||||
return this._vincenty_inverse(p1, {
|
|
||||||
lat: p2.lat,
|
|
||||||
lng: p2.lng - 0.01
|
|
||||||
});
|
|
||||||
// throw new Error('Formula failed to converge');
|
|
||||||
}
|
|
||||||
|
|
||||||
var uSq = cosSqα * (a * a - b * b) / (b * b);
|
|
||||||
var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 *
|
|
||||||
uSq)));
|
|
||||||
var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
|
|
||||||
var Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM *
|
|
||||||
cos2σM) -
|
|
||||||
B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM *
|
|
||||||
cos2σM)));
|
|
||||||
|
|
||||||
var s = b * A * (σ - Δσ);
|
|
||||||
|
|
||||||
var fwdAz = Math.atan2(cosU2 * sinλ, cosU1 * sinU2 - sinU1 * cosU2 *
|
|
||||||
cosλ);
|
|
||||||
var revAz = Math.atan2(cosU1 * sinλ, -sinU1 * cosU2 + cosU1 * sinU2 *
|
|
||||||
cosλ);
|
|
||||||
|
|
||||||
s = Number(s.toFixed(3)); // round to 1mm precision
|
|
||||||
return {
|
|
||||||
distance: s,
|
|
||||||
initialBearing: fwdAz.toDegrees(),
|
|
||||||
finalBearing: revAz.toDegrees()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the point of intersection of two paths defined by point and bearing.
|
|
||||||
* based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
|
|
||||||
*
|
|
||||||
* @param {LatLon} p1 - First point.
|
|
||||||
* @param {number} brng1 - Initial bearing from first point.
|
|
||||||
* @param {LatLon} p2 - Second point.
|
|
||||||
* @param {number} brng2 - Initial bearing from second point.
|
|
||||||
* @returns {Object} containing lat/lng information of intersection.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* var p1 = LatLon(51.8853, 0.2545), brng1 = 108.55;
|
|
||||||
* var p2 = LatLon(49.0034, 2.5735), brng2 = 32.44;
|
|
||||||
* var pInt = LatLon.intersection(p1, brng1, p2, brng2); // pInt.toString(): 50.9078°N, 4.5084°E
|
|
||||||
*/
|
|
||||||
_intersection: function(p1, brng1, p2, brng2) {
|
|
||||||
// see http://williams.best.vwh.net/avform.htm#Intersection
|
|
||||||
|
|
||||||
var φ1 = p1.lat.toRadians(),
|
|
||||||
λ1 = p1.lng.toRadians();
|
|
||||||
var φ2 = p2.lat.toRadians(),
|
|
||||||
λ2 = p2.lng.toRadians();
|
|
||||||
var θ13 = Number(brng1).toRadians(),
|
|
||||||
θ23 = Number(brng2).toRadians();
|
|
||||||
var Δφ = φ2 - φ1,
|
|
||||||
Δλ = λ2 - λ1;
|
|
||||||
|
|
||||||
var δ12 = 2 * Math.asin(Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
|
||||||
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ /
|
|
||||||
2)));
|
|
||||||
if (δ12 == 0) return null;
|
|
||||||
|
|
||||||
// initial/final bearings between points
|
|
||||||
var θ1 = Math.acos((Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) /
|
|
||||||
(Math.sin(δ12) * Math.cos(φ1)));
|
|
||||||
if (isNaN(θ1)) θ1 = 0; // protect against rounding
|
|
||||||
var θ2 = Math.acos((Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) /
|
|
||||||
(Math.sin(δ12) * Math.cos(φ2)));
|
|
||||||
var θ12, θ21;
|
|
||||||
if (Math.sin(λ2 - λ1) > 0) {
|
|
||||||
θ12 = θ1;
|
|
||||||
θ21 = 2 * Math.PI - θ2;
|
|
||||||
} else {
|
|
||||||
θ12 = 2 * Math.PI - θ1;
|
|
||||||
θ21 = θ2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var α1 = (θ13 - θ12 + Math.PI) % (2 * Math.PI) - Math.PI; // angle 2-1-3
|
|
||||||
var α2 = (θ21 - θ23 + Math.PI) % (2 * Math.PI) - Math.PI; // angle 1-2-3
|
|
||||||
|
|
||||||
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return null; // infinite intersections
|
|
||||||
if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection
|
|
||||||
|
|
||||||
//α1 = Math.abs(α1);
|
|
||||||
//α2 = Math.abs(α2);
|
|
||||||
// ... Ed Williams takes abs of α1/α2, but seems to break calculation?
|
|
||||||
|
|
||||||
var α3 = Math.acos(-Math.cos(α1) * Math.cos(α2) +
|
|
||||||
Math.sin(α1) * Math.sin(α2) * Math.cos(δ12));
|
|
||||||
var δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2),
|
|
||||||
Math.cos(α2) + Math.cos(α1) * Math.cos(α3));
|
|
||||||
var φ3 = Math.asin(Math.sin(φ1) * Math.cos(δ13) +
|
|
||||||
Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13));
|
|
||||||
var Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1),
|
|
||||||
Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
|
|
||||||
var λ3 = λ1 + Δλ13;
|
|
||||||
λ3 = (λ3 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
|
|
||||||
|
|
||||||
return {
|
|
||||||
lat: φ3.toDegrees(),
|
|
||||||
lng: λ3.toDegrees()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
|
|
||||||
* @param obj1
|
|
||||||
* @param obj2
|
|
||||||
* @returns obj3 a new object based on obj1 and obj2
|
|
||||||
*/
|
|
||||||
_merge_options: function(obj1, obj2) {
|
|
||||||
let obj3 = {};
|
|
||||||
for (let attrname in obj1) {
|
|
||||||
obj3[attrname] = obj1[attrname];
|
|
||||||
}
|
|
||||||
for (let attrname in obj2) {
|
|
||||||
obj3[attrname] = obj2[attrname];
|
|
||||||
}
|
|
||||||
return obj3;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
L.geodesic = function(latlngs, options) {
|
|
||||||
return new L.Geodesic(latlngs, options);
|
|
||||||
};
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
self.importScripts("satellite.js");
|
||||||
|
|
||||||
|
norad = ""
|
||||||
|
groundStations = []
|
||||||
|
TLE = []
|
||||||
|
|
||||||
|
onmessage = function(e) {
|
||||||
|
norad = e.data[0]
|
||||||
|
TLE = e.data[1]
|
||||||
|
getStations()
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(function(){
|
||||||
|
getStations()
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
setInterval(function(){
|
||||||
|
var satrec = self.satellite_js.twoline2satrec(TLE[1],TLE[2]);
|
||||||
|
var gmst = self.satellite_js.gstime(new Date());
|
||||||
|
var positionAndVelocity = self.satellite_js.propagate(satrec, new Date());
|
||||||
|
var positionEci = positionAndVelocity.position
|
||||||
|
var positionGd = self.satellite_js.eciToGeodetic(positionEci, gmst)
|
||||||
|
var longitude = positionGd.longitude
|
||||||
|
var latitude = positionGd.latitude
|
||||||
|
|
||||||
|
if (groundStations[1] == undefined){
|
||||||
|
groundStations.push([])
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage([norad,TLE[0],[degress(latitude),degress(longitude)],groundStations[0],groundStations[1]])
|
||||||
|
|
||||||
|
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
function getStations(){
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
groundStations = JSON.parse(this.responseText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/stations_from_sat/"+norad, true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function degress (radians) {
|
||||||
|
return radians * 180 / Math.PI;
|
||||||
|
};
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
File diff suppressed because one or more lines are too long
|
@ -7,13 +7,14 @@
|
||||||
crossorigin=""></script>
|
crossorigin=""></script>
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
<script src=" https://unpkg.com/@joergdietrich/leaflet.terminator@1.0.0/L.Terminator.js"></script>
|
<script src=" https://unpkg.com/@joergdietrich/leaflet.terminator@1.0.0/L.Terminator.js"></script>
|
||||||
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
|
|
||||||
<script src="/static/Leaflet.Geodesic.js"></script>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#mapid { height: 700px; }
|
#mapid { height: 700px; }
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
test = null
|
||||||
var light_sat = L.icon({
|
var light_sat = L.icon({
|
||||||
iconUrl: 'static/satellite-marker-light.png',
|
iconUrl: 'static/satellite-marker-light.png',
|
||||||
iconSize: [40, 40],
|
iconSize: [40, 40],
|
||||||
|
@ -72,138 +73,6 @@ var active_station = L.icon({
|
||||||
id: 'mapbox.streets',
|
id: 'mapbox.streets',
|
||||||
accessToken: 'pk.eyJ1IjoiY2hpYmlsbCIsImEiOiJjamxsNHBuZG4wdG1uM3FwYTN5c2ZubmxrIn0.ghkx6AngBzUiZQWBAWKziQ'
|
accessToken: 'pk.eyJ1IjoiY2hpYmlsbCIsImEiOiJjamxsNHBuZG4wdG1uM3FwYTN5c2ZubmxrIn0.ghkx6AngBzUiZQWBAWKziQ'
|
||||||
}).addTo(mymap);
|
}).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%}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var sats = {}
|
|
||||||
|
|
||||||
var links = {}
|
|
||||||
|
|
||||||
|
|
||||||
$.get("/api/occuringsats", function(data, status){
|
|
||||||
data = JSON.parse(data)
|
|
||||||
Object.keys(data).forEach(function(key){
|
|
||||||
sat = light_sat
|
|
||||||
if(data[key]["eclipsed"]){
|
|
||||||
sat = dark_sat
|
|
||||||
}
|
|
||||||
if(data[key]["image"] != null){
|
|
||||||
image = data[key]["image"]
|
|
||||||
if(data[key]["eclipsed"]){
|
|
||||||
image = image + "-dark.png"
|
|
||||||
}else{
|
|
||||||
image = image + "-light.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
sat = L.icon({
|
|
||||||
iconUrl: image,
|
|
||||||
iconSize: [40, 40],
|
|
||||||
iconAnchor: [20, 20],
|
|
||||||
popupAnchor: [0, 0],
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
marker = L.marker(data[key]["lat_lng"],{icon: sat,zIndexOffset:1000}).addTo(mymap);
|
|
||||||
marker.bindPopup("<b>Name: "+data[key]["name"]+"</b><br><b>Norad: "+key+"</b><br><b>Eclipsed?: "+data[key]["eclipsed"]);
|
|
||||||
sats[key] = marker;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$.get("/api/satstationpairs", function(data, status){
|
|
||||||
data = JSON.parse(data)
|
|
||||||
data.forEach(function(entry){
|
|
||||||
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: '#'+entry[1].toString(16).repeat(2).substr(0,6),weight: 3,opacity: 1,smoothFactor: 1});
|
|
||||||
|
|
||||||
firstpolyline.addTo(mymap)
|
|
||||||
|
|
||||||
links[entry[0]] = firstpolyline
|
|
||||||
stationList[entry[0]].setIcon(active_station)
|
|
||||||
stationList[entry[0]].setZIndexOffset(500)
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(function(){
|
|
||||||
|
|
||||||
Object.keys(links).forEach(function(key){
|
|
||||||
links[key].setStyle({opacity:0})
|
|
||||||
links[key].removeFrom(mymap)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
links = {}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$.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){
|
|
||||||
sat = light_sat
|
|
||||||
if(data[key]["eclipsed"]){
|
|
||||||
sat = dark_sat
|
|
||||||
}
|
|
||||||
if(data[key]["image"] != null){
|
|
||||||
image = data[key]["image"]
|
|
||||||
if(data[key]["eclipsed"]){
|
|
||||||
image = image + "-dark.png"
|
|
||||||
}else{
|
|
||||||
image = image + "-light.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
sat = L.icon({
|
|
||||||
iconUrl: image,
|
|
||||||
iconSize: [40, 40],
|
|
||||||
iconAnchor: [20, 20],
|
|
||||||
popupAnchor: [0, 0],
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
marker = L.marker(data[key]["lat_lng"],{icon: sat,zIndexOffset:1000}).addTo(mymap);
|
|
||||||
marker.bindPopup("<b>Name: "+data[key]["name"]+"</b><br><b>Norad: "+key+"</b><br><b>Eclipsed?: "+data[key]["eclipsed"]);
|
|
||||||
sats[key] = marker;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
$.get("/api/satstationpairs", function(data, status){
|
|
||||||
data = JSON.parse(data)
|
|
||||||
var usedStations = []
|
|
||||||
Object.keys(stationList).forEach(function(key){
|
|
||||||
stationList[key].setIcon(station)
|
|
||||||
stationList[key].setZIndexOffset(-1000)
|
|
||||||
|
|
||||||
})
|
|
||||||
data.forEach(function(entry){
|
|
||||||
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: '#'+entry[1].toString(16).repeat(2).substr(0,6),weight:3,opacity:1,smoothFactor: 1});
|
|
||||||
|
|
||||||
firstpolyline.addTo(mymap)
|
|
||||||
links[entry[0]] = firstpolyline
|
|
||||||
stationList[entry[0]].setIcon(active_station)
|
|
||||||
stationList[entry[0]].setZIndexOffset(500)
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
console.log(links)
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
var t = L.terminator();
|
var t = L.terminator();
|
||||||
t.addTo(mymap);
|
t.addTo(mymap);
|
||||||
setInterval(function(){updateTerminator(t)}, 500);
|
setInterval(function(){updateTerminator(t)}, 500);
|
||||||
|
@ -213,6 +82,113 @@ function updateTerminator(t) {
|
||||||
t.redraw();
|
t.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stations = {}
|
||||||
|
Workers = {}
|
||||||
|
Lines = {}
|
||||||
|
Sats = {}
|
||||||
|
|
||||||
|
dataS = null
|
||||||
|
function UpdateMap(e) {
|
||||||
|
var norad = e.data[0]
|
||||||
|
var name = e.data[1]
|
||||||
|
var satPos = e.data[2]
|
||||||
|
if (norad in Sats){
|
||||||
|
Sats[norad]._latlng = {"lat":satPos[0],"lng":satPos[1]}
|
||||||
|
Sats[norad].update()
|
||||||
|
}else{
|
||||||
|
Sats[norad] = L.marker(satPos,{icon: dark_sat,zIndexOffset:1000}).addTo(mymap);
|
||||||
|
Lines[norad] = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
popupText = "<b>Name: "+name+"</b><br><b>Norad: "+norad+"</b><br><b>Station Count: "+e.data[3].length+"</b><br>"
|
||||||
|
|
||||||
|
e.data[4].forEach(function(x){
|
||||||
|
//console.log(x + " "+norad)
|
||||||
|
popupText = popupText + '<div class="trans" style="background-color:'+x[1]+'";>'+x[0]+"</div>"
|
||||||
|
})
|
||||||
|
Sats[norad].bindPopup(popupText)
|
||||||
|
Sats[norad]._popup.setContent(popupText)
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
e.data[3].forEach(function(x){
|
||||||
|
temp.push(x[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
e.data[3].forEach(function(x){
|
||||||
|
if (Object.keys(Lines[norad]).includes(String(x[0]))){
|
||||||
|
Object.keys(Lines[norad]).forEach(function(y){
|
||||||
|
Lines[norad][y]._latlngs[1]= {"lat":satPos[0],"lng":satPos[1]}
|
||||||
|
|
||||||
|
})}else{Lines[norad][x[0]] = new L.Polyline([[Stations[x[0]]._latlng.lat,Stations[x[0]]._latlng.lng],[satPos[0],satPos[1]]], {color: x[1],weight: 3,opacity: 1,smoothFactor: 1});
|
||||||
|
Lines[norad][x[0]].addTo(mymap)};
|
||||||
|
})
|
||||||
|
Object.keys(Lines[norad]).forEach(function(x){
|
||||||
|
if (temp.includes(Number(x))){
|
||||||
|
|
||||||
|
}else{
|
||||||
|
Lines[norad][x].removeFrom(mymap)
|
||||||
|
delete Lines[norad][x]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$.get("/active_stations", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
dataS = data
|
||||||
|
data.forEach(function(x){
|
||||||
|
|
||||||
|
marker = L.marker(x["lat_lng"],{icon: station,zIndexOffset:-1000}).addTo(mymap);
|
||||||
|
marker.bindPopup("<b>Name: "+x['name']+"</b>")
|
||||||
|
Stations[x["id"]] = marker
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("/occuring_sats", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
Object.keys(data).forEach(function(x){
|
||||||
|
worker = new Worker('/static/Worker.js');
|
||||||
|
worker.onmessage = UpdateMap
|
||||||
|
worker.postMessage([x,data[x]]);
|
||||||
|
Workers[x] =worker
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setInterval(function(){
|
||||||
|
$.get("/occuring_sats", function(data, status){
|
||||||
|
data = JSON.parse(data)
|
||||||
|
Object.keys(data).forEach(function(x){
|
||||||
|
if (x in Workers){
|
||||||
|
}else{
|
||||||
|
worker = new Worker('/static/Worker.js');
|
||||||
|
worker.onmessage = UpdateMap
|
||||||
|
worker.postMessage([x,data[x]]);
|
||||||
|
Workers[x] =worker
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(Workers).forEach(function(x){
|
||||||
|
if (Object.keys(data).includes(x)){
|
||||||
|
}else{
|
||||||
|
Workers[x].terminate()
|
||||||
|
delete Workers[x]
|
||||||
|
UpdateMap({"data":[x,"",[0,0],[],[]]})
|
||||||
|
Sats[x].removeFrom(mymap)
|
||||||
|
delete Sats[x]
|
||||||
|
delete Lines[x]
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue