diff --git a/requirments.txt b/requirments.txt new file mode 100644 index 0000000..1a226d3 --- /dev/null +++ b/requirments.txt @@ -0,0 +1,4 @@ +requests==2.21.0 +flask==1.0.2 +apscheduler==3.5.3 +satellitetle==0.5.1 \ No newline at end of file diff --git a/satnogs.py b/satnogs.py index e60494d..a35d1b0 100644 --- a/satnogs.py +++ b/satnogs.py @@ -2,20 +2,20 @@ from datetime import datetime , timedelta import requests from flask import Flask , render_template,redirect,url_for import json +import random from apscheduler.schedulers.background import BackgroundScheduler -import ephem from satnogs_api_client import fetch_satellites, DB_BASE_URL,fetch_tle_of_observation 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 = [] +Occuring_sats = {} Stations = [] TLEs = {} +Transmitters = {} class Pass: id = 0 @@ -25,10 +25,7 @@ class Pass: satellite = None transmitter = None norad = 0 - - def __repr__(self): - return "\n: {}\n: {}\n: {}\n: {}\n {}\n: {}".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(): @@ -66,12 +63,12 @@ def getActive(): 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.transmitter = x["transmitter"] temp.norad = str(x["norad_cat_id"]) 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: temp.satellite = {"name":""} - #temp.transmitter = requests.get("https://db.satnogs.org/api/transmitters/"+x["transmitter"]).json() Passes.append(temp) @@ -104,13 +101,32 @@ def GetGroundStations(): 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) def updatePasses(): global Passes + global Occuring_sats print "Updating Passes" 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) def updateStations(): global Stations @@ -121,13 +137,14 @@ def updateStations(): def updateTLE(): print "Updating TLE" 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') # 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) TLEs = {} for norad_id, (source, tle) in tles.items(): @@ -139,77 +156,42 @@ def updateTLE(): @app.route("/") 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(""+str(x.id)+"") - return json.dumps(obs) + return render_template("map.html") -@app.route('/api/activestations') +@app.route('/active_stations') 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 = [] + sations = [] for x in Stations: - stations.append(x["id"]) - return json.dumps(stations) - - + sations.append({'id':x['id'],'name':x['name'],'lat_lng':[x["lat"],x['lng']]}) + return json.dumps(sations) -@app.route('/api/occuringobservations') -def api_occuring_observations(): +@app.route('/stations_from_sat/') +def api_occuring_observations(norad): obs = [] + trans = [] for x in Passes: - obs.append(x.id) - return json.dumps(obs) + if x.norad == norad: + 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(): - obs = {} - 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) + return json.dumps(Occuring_sats) -@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/') -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() updateStations() updateTLE() +updateTransmitters() scheduler.start() -app.run(use_reloader=False,host = "0.0.0.0") \ No newline at end of file +app.run(use_reloader=False,host = "0.0.0.0",port=5001) \ No newline at end of file diff --git a/satnogs_api_client.py b/satnogs_api_client.py index 82b5e9e..59518af 100644 --- a/satnogs_api_client.py +++ b/satnogs_api_client.py @@ -1,6 +1,8 @@ -import requests +from __future__ import print_function import re +import requests + NETWORK_DEV_BASE_URL = 'https://network-dev.satnogs.org' NETWORK_BASE_URL = 'https://network.satnogs.org' @@ -8,92 +10,90 @@ DB_BASE_URL = 'https://db.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): - # 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 - 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, norad_id, start.isoformat(), end.isoformat()) - # print(url) - r = requests.get(url=url) + observations = get_paginated_endpoint(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)) + # Current prod is broken and can't filter on NORAD ID correctly, + # use client-side filtering instead + observations = list(observation for observation in observations + if observation['norad_cat_id'] == norad_id) 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 + base_url = (NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL) + r = requests.get(url='{}/api/observations/{}/'.format(base_url, + observation_id)) + r.raise_for_status() 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() + # 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)) + r.raise_for_status() + 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)) + r = requests.get(url='{}/api/satellites/{}/'.format(DB_BASE_URL, + norad_cat_id)) + r.raise_for_status() 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, + base_url = (NETWORK_BASE_URL if prod else NETWORK_DEV_BASE_URL) + url = '{}/observations/{}/'.format(base_url, observation_id) r = requests.get(url=url) 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] -def fetch_telemetry(norad_id, max_frames, url): +def fetch_telemetry(norad_id, 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 + telemetry = get_paginated_endpoint(url) - 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/' 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 + satellites = get_paginated_endpoint(url, max_entries=max_entries) + return satellites def post_telemetry(norad_id, - source, + source, # Receiver Callsign lon, lat, timestamp, diff --git a/satnogs_api_client.pyc b/satnogs_api_client.pyc new file mode 100644 index 0000000..91a2e0b Binary files /dev/null and b/satnogs_api_client.pyc differ diff --git a/static/ISS-dark.png b/static/ISS-dark.png deleted file mode 100644 index 0780e25..0000000 Binary files a/static/ISS-dark.png and /dev/null differ diff --git a/static/ISS-light.png b/static/ISS-light.png deleted file mode 100644 index 7bba7f6..0000000 Binary files a/static/ISS-light.png and /dev/null differ diff --git a/static/Leaflet.Geodesic.js b/static/Leaflet.Geodesic.js deleted file mode 100644 index 278815d..0000000 --- a/static/Leaflet.Geodesic.js +++ /dev/null @@ -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 . - - -/** 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); -}; diff --git a/static/Worker.js b/static/Worker.js new file mode 100644 index 0000000..72c91e0 --- /dev/null +++ b/static/Worker.js @@ -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; +}; \ No newline at end of file diff --git a/static/light-satellite-marker.png b/static/light-satellite-marker.png deleted file mode 100644 index af17f1b..0000000 Binary files a/static/light-satellite-marker.png and /dev/null differ diff --git a/static/satellite-marker.png b/static/satellite-marker.png deleted file mode 100644 index 697846d..0000000 Binary files a/static/satellite-marker.png and /dev/null differ diff --git a/static/satellite.js b/static/satellite.js new file mode 100644 index 0000000..e294cab --- /dev/null +++ b/static/satellite.js @@ -0,0 +1 @@ +!function(o,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(o.satellite_js={})}(this,function(o){"use strict";var Ho=Math.PI,Yo=2*Ho,m=Ho/180,t=180/Ho,s=398600.5,Ao=6378.137,Bo=60/Math.sqrt(Ao*Ao*Ao/s),p=1/Bo,Co=.00108262998905,e=-253215306e-14,Uo=-161098761e-14,Do=e/Co,Jo=2/3,a=Object.freeze({pi:Ho,twoPi:Yo,deg2rad:m,rad2deg:t,minutesPerDay:1440,mu:s,earthRadius:Ao,xke:Bo,tumin:p,j2:Co,j3:e,j4:Uo,j3oj2:Do,x2o3:Jo});function l(o,t){for(var s=[31,o%4==0?29:28,31,30,31,30,31,31,30,31,30,31],e=Math.floor(t),a=1,n=0;n+s[a-1]Ho&&(so - - +
@@ -32,7 +33,7 @@