From 02994173672206b0cbade1dc599c981003f639c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Gru=CC=88ndger?= Date: Sat, 31 Aug 2019 10:14:41 +0200 Subject: [PATCH] Refactoring WIP --- Vagrantfile | 16 +- {ogn_python => app}/__init__.py | 8 +- {ogn_python => app}/backend/__init__.py | 0 app/backend/liveglidernet.py | 105 ++++ app/backend/ognrange.py | 54 ++ {ogn_python => app}/collect/__init__.py | 0 {ogn_python => app}/collect/celery.py | 47 +- {ogn_python => app}/collect/database.py | 22 +- app/collect/logbook.py | 200 ++++++++ app/collect/ognrange.py | 89 ++++ app/collect/stats.py | 451 +++++++++++++++++ app/collect/takeoff_landings.py | 144 ++++++ {ogn_python => app}/commands/__init__.py | 2 +- {ogn_python => app}/commands/database.py | 72 ++- {ogn_python => app}/commands/export.py | 84 ++- {ogn_python => app}/commands/flights.py | 32 +- {ogn_python => app}/commands/gateway.py | 16 +- app/commands/logbook.py | 119 +++++ {ogn_python => app}/commands/stats.py | 77 +-- {ogn_python => app}/config/__init__.py | 0 app/config/default.py | 27 + app/config/test.py | 6 + {ogn_python => app}/flask_celery.py | 9 +- {ogn_python => app}/gateway/__init__.py | 0 app/gateway/bulkimport.py | 407 +++++++++++++++ {ogn_python => app}/gateway/process_tools.py | 107 +++- app/live_routes.py | 90 ++++ {ogn_python => app}/model/__init__.py | 0 {ogn_python => app}/model/aircraft_beacon.py | 105 ++-- {ogn_python => app}/model/aircraft_type.py | 0 {ogn_python => app}/model/airport.py | 9 +- {ogn_python => app}/model/beacon.py | 4 +- {ogn_python => app}/model/country.py | 17 +- {ogn_python => app}/model/country_stats.py | 7 +- {ogn_python => app}/model/device.py | 24 +- {ogn_python => app}/model/device_info.py | 9 +- .../model/device_info_origin.py | 12 +- {ogn_python => app}/model/device_stats.py | 14 +- app/model/flights2d.py | 24 + {ogn_python => app}/model/geo.py | 6 +- {ogn_python => app}/model/logbook.py | 16 +- {ogn_python => app}/model/receiver.py | 8 +- {ogn_python => app}/model/receiver_beacon.py | 74 +-- .../model/receiver_coverage.py | 10 +- {ogn_python => app}/model/receiver_stats.py | 10 +- app/model/relation_stats.py | 26 + app/model/takeoff_landing.py | 16 + app/routes.py | 197 ++++++++ {ogn_python => app}/static/css/flags/LICENSE | 0 .../static/css/flags/flags.css | 0 .../static/css/flags/flags.png | Bin .../static/files/WineButton.png | Bin .../static/files/bootstrap/LICENSE.md | 0 .../bootstrap.colorpickersliders.css | 0 .../bootstrap/bootstrap.colorpickersliders.js | 0 .../bootstrap.colorpickersliders.nocielch.js | 0 .../files/bootstrap/typeahead.bundle.js | 0 {ogn_python => app}/static/files/heatmap2.js | 0 {ogn_python => app}/static/files/maptiles2.js | 0 {ogn_python => app}/static/files/mgrs.min.js | 0 {ogn_python => app}/static/files/ol/ol.css | 0 {ogn_python => app}/static/files/ol/ol.js | 0 {ogn_python => app}/static/files/style.css | 0 {ogn_python => app}/static/files/tinycolor.js | 0 .../static/files/url.js.sample | 0 {ogn_python => app}/static/img/Blank.gif | Bin .../static/img/Transparent.gif | Bin .../static/ognlive/barogram.js | 0 .../static/ognlive/horizZoomControl.js | 0 {ogn_python => app}/static/ognlive/ogn.js | 0 {ogn_python => app}/static/ognlive/ol.css | 0 {ogn_python => app}/static/ognlive/ol.js | 0 {ogn_python => app}/static/ognlive/osm.css | 0 .../static/ognlive/pict/OGN.png | Bin .../static/ognlive/pict/OGN_b.png | Bin .../static/ognlive/pict/OGN_g.png | Bin .../static/ognlive/pict/OGN_o.png | Bin .../static/ognlive/pict/OGN_p.png | Bin .../static/ognlive/pict/OGN_r.png | Bin .../ognlive/pict/OpenPortGuideLogo_32.png | Bin {ogn_python => app}/static/ognlive/pict/a.gif | Bin .../static/ognlive/pict/bin.gif | Bin .../static/ognlive/pict/c1.gif | Bin .../static/ognlive/pict/c2.gif | Bin .../static/ognlive/pict/c3.gif | Bin .../static/ognlive/pict/c4.gif | Bin .../static/ognlive/pict/c5.gif | Bin .../static/ognlive/pict/cancel-5.png | Bin .../static/ognlive/pict/close.png | Bin .../static/ognlive/pict/cordon.gif | Bin .../static/ognlive/pict/dbarrow.gif | Bin .../static/ognlive/pict/drapd.gif | Bin .../static/ognlive/pict/drape.gif | Bin .../static/ognlive/pict/drapf.gif | Bin .../static/ognlive/pict/drapi.gif | Bin .../static/ognlive/pict/drapn.gif | Bin .../static/ognlive/pict/draps.gif | Bin .../static/ognlive/pict/eye.gif | Bin .../static/ognlive/pict/favicon.gif | Bin .../static/ognlive/pict/h1.gif | Bin .../static/ognlive/pict/h2.gif | Bin .../static/ognlive/pict/h3.gif | Bin .../static/ognlive/pict/hel.png | Bin .../static/ognlive/pict/ico.gif | Bin .../static/ognlive/pict/ico.png | Bin .../static/ognlive/pict/l1.gif | Bin .../static/ognlive/pict/left-3.png | Bin {ogn_python => app}/static/ognlive/pict/m.gif | Bin .../static/ognlive/pict/min.png | Bin .../static/ognlive/pict/mm.gif | Bin .../static/ognlive/pict/mmm.gif | Bin .../static/ognlive/pict/mod.gif | Bin {ogn_python => app}/static/ognlive/pict/n.gif | Bin .../static/ognlive/pict/ogn-logo-ani.gif | Bin {ogn_python => app}/static/ognlive/pict/p.gif | Bin .../static/ognlive/pict/plu.png | Bin .../static/ognlive/pict/pp.gif | Bin .../static/ognlive/pict/ppp.gif | Bin .../static/ognlive/pict/rec.png | Bin .../static/ognlive/pict/rec0.png | Bin .../static/ognlive/pict/rec1.png | Bin .../static/ognlive/pict/recy.png | Bin .../static/ognlive/pict/redo-6.png | Bin .../static/ognlive/pict/right-3.png | Bin .../static/ognlive/pict/tra.gif | Bin .../static/ognlive/pict/yn0.gif | Bin .../static/ognlive/pict/yn1.gif | Bin {ogn_python => app}/static/ognlive/pict/z.gif | Bin {ogn_python => app}/static/ognlive/util.js | 0 .../templates/airport_detail.html | 0 {ogn_python => app}/templates/airports.html | 0 {ogn_python => app}/templates/base.html | 14 +- .../templates/device_detail.html | 0 {ogn_python => app}/templates/devices.html | 0 {ogn_python => app}/templates/logbook.html | 0 {ogn_python => app}/templates/ogn_live.html | 0 {ogn_python => app}/templates/ognrange.html | 0 .../templates/receiver_detail.html | 0 {ogn_python => app}/templates/receivers.html | 0 {ogn_python => app}/templates/statistics.html | 0 {ogn_python => app}/utils.py | 58 +-- ogn_python.py | 5 + ogn_python/app.py | 6 - ogn_python/backend/liveglidernet.py | 104 ---- ogn_python/backend/ognrange.py | 54 -- ogn_python/collect/logbook.py | 172 ------- ogn_python/collect/ognrange.py | 86 ---- ogn_python/collect/stats.py | 470 ----------------- ogn_python/collect/takeoff_landings.py | 145 ------ ogn_python/commands/logbook.py | 117 ----- ogn_python/config/default.py | 57 --- ogn_python/config/test.py | 6 - ogn_python/gateway/bulkimport.py | 322 ------------ ogn_python/live_routes.py | 90 ---- ogn_python/model/flights2d.py | 27 - ogn_python/model/relation_stats.py | 29 -- ogn_python/model/takeoff_landing.py | 16 - ogn_python/navigation.py | 14 - ogn_python/routes.py | 243 --------- tests/backend/test_backends.py | 126 +++-- tests/base.py | 14 +- tests/collect/test_database.py | 28 +- tests/collect/test_logbook.py | 22 +- tests/collect/test_ognrange.py | 24 +- tests/collect/test_stats.py | 50 +- tests/collect/test_takeoff_landing.py | 477 +++++++++++++----- tests/commands/test_database.py | 10 +- tests/model/all_classes.py | 8 +- tests/model/test_device.py | 12 +- tests/test_utils.py | 26 +- 170 files changed, 2959 insertions(+), 2645 deletions(-) rename {ogn_python => app}/__init__.py (69%) rename {ogn_python => app}/backend/__init__.py (100%) create mode 100644 app/backend/liveglidernet.py create mode 100644 app/backend/ognrange.py rename {ogn_python => app}/collect/__init__.py (100%) rename {ogn_python => app}/collect/celery.py (63%) rename {ogn_python => app}/collect/database.py (72%) create mode 100644 app/collect/logbook.py create mode 100644 app/collect/ognrange.py create mode 100644 app/collect/stats.py create mode 100644 app/collect/takeoff_landings.py rename {ogn_python => app}/commands/__init__.py (94%) rename {ogn_python => app}/commands/database.py (62%) rename {ogn_python => app}/commands/export.py (57%) rename {ogn_python => app}/commands/flights.py (89%) rename {ogn_python => app}/commands/gateway.py (59%) create mode 100644 app/commands/logbook.py rename {ogn_python => app}/commands/stats.py (59%) rename {ogn_python => app}/config/__init__.py (100%) create mode 100644 app/config/default.py create mode 100644 app/config/test.py rename {ogn_python => app}/flask_celery.py (62%) rename {ogn_python => app}/gateway/__init__.py (100%) create mode 100644 app/gateway/bulkimport.py rename {ogn_python => app}/gateway/process_tools.py (91%) create mode 100644 app/live_routes.py rename {ogn_python => app}/model/__init__.py (100%) rename {ogn_python => app}/model/aircraft_beacon.py (58%) rename {ogn_python => app}/model/aircraft_type.py (100%) rename {ogn_python => app}/model/airport.py (83%) rename {ogn_python => app}/model/beacon.py (93%) rename {ogn_python => app}/model/country.py (64%) rename {ogn_python => app}/model/country_stats.py (51%) rename {ogn_python => app}/model/device.py (69%) rename {ogn_python => app}/model/device_info.py (83%) rename {ogn_python => app}/model/device_info_origin.py (64%) rename {ogn_python => app}/model/device_stats.py (74%) create mode 100644 app/model/flights2d.py rename {ogn_python => app}/model/geo.py (59%) rename {ogn_python => app}/model/logbook.py (58%) rename {ogn_python => app}/model/receiver.py (68%) rename {ogn_python => app}/model/receiver_beacon.py (71%) rename {ogn_python => app}/model/receiver_coverage.py (56%) rename {ogn_python => app}/model/receiver_stats.py (71%) create mode 100644 app/model/relation_stats.py create mode 100644 app/model/takeoff_landing.py create mode 100644 app/routes.py rename {ogn_python => app}/static/css/flags/LICENSE (100%) rename {ogn_python => app}/static/css/flags/flags.css (100%) rename {ogn_python => app}/static/css/flags/flags.png (100%) rename {ogn_python => app}/static/files/WineButton.png (100%) rename {ogn_python => app}/static/files/bootstrap/LICENSE.md (100%) rename {ogn_python => app}/static/files/bootstrap/bootstrap.colorpickersliders.css (100%) rename {ogn_python => app}/static/files/bootstrap/bootstrap.colorpickersliders.js (100%) rename {ogn_python => app}/static/files/bootstrap/bootstrap.colorpickersliders.nocielch.js (100%) rename {ogn_python => app}/static/files/bootstrap/typeahead.bundle.js (100%) rename {ogn_python => app}/static/files/heatmap2.js (100%) rename {ogn_python => app}/static/files/maptiles2.js (100%) rename {ogn_python => app}/static/files/mgrs.min.js (100%) rename {ogn_python => app}/static/files/ol/ol.css (100%) rename {ogn_python => app}/static/files/ol/ol.js (100%) rename {ogn_python => app}/static/files/style.css (100%) rename {ogn_python => app}/static/files/tinycolor.js (100%) rename {ogn_python => app}/static/files/url.js.sample (100%) rename {ogn_python => app}/static/img/Blank.gif (100%) rename {ogn_python => app}/static/img/Transparent.gif (100%) rename {ogn_python => app}/static/ognlive/barogram.js (100%) rename {ogn_python => app}/static/ognlive/horizZoomControl.js (100%) rename {ogn_python => app}/static/ognlive/ogn.js (100%) rename {ogn_python => app}/static/ognlive/ol.css (100%) rename {ogn_python => app}/static/ognlive/ol.js (100%) rename {ogn_python => app}/static/ognlive/osm.css (100%) rename {ogn_python => app}/static/ognlive/pict/OGN.png (100%) rename {ogn_python => app}/static/ognlive/pict/OGN_b.png (100%) rename {ogn_python => app}/static/ognlive/pict/OGN_g.png (100%) rename {ogn_python => app}/static/ognlive/pict/OGN_o.png (100%) rename {ogn_python => app}/static/ognlive/pict/OGN_p.png (100%) rename {ogn_python => app}/static/ognlive/pict/OGN_r.png (100%) rename {ogn_python => app}/static/ognlive/pict/OpenPortGuideLogo_32.png (100%) rename {ogn_python => app}/static/ognlive/pict/a.gif (100%) rename {ogn_python => app}/static/ognlive/pict/bin.gif (100%) rename {ogn_python => app}/static/ognlive/pict/c1.gif (100%) rename {ogn_python => app}/static/ognlive/pict/c2.gif (100%) rename {ogn_python => app}/static/ognlive/pict/c3.gif (100%) rename {ogn_python => app}/static/ognlive/pict/c4.gif (100%) rename {ogn_python => app}/static/ognlive/pict/c5.gif (100%) rename {ogn_python => app}/static/ognlive/pict/cancel-5.png (100%) rename {ogn_python => app}/static/ognlive/pict/close.png (100%) rename {ogn_python => app}/static/ognlive/pict/cordon.gif (100%) rename {ogn_python => app}/static/ognlive/pict/dbarrow.gif (100%) rename {ogn_python => app}/static/ognlive/pict/drapd.gif (100%) rename {ogn_python => app}/static/ognlive/pict/drape.gif (100%) rename {ogn_python => app}/static/ognlive/pict/drapf.gif (100%) rename {ogn_python => app}/static/ognlive/pict/drapi.gif (100%) rename {ogn_python => app}/static/ognlive/pict/drapn.gif (100%) rename {ogn_python => app}/static/ognlive/pict/draps.gif (100%) rename {ogn_python => app}/static/ognlive/pict/eye.gif (100%) rename {ogn_python => app}/static/ognlive/pict/favicon.gif (100%) rename {ogn_python => app}/static/ognlive/pict/h1.gif (100%) rename {ogn_python => app}/static/ognlive/pict/h2.gif (100%) rename {ogn_python => app}/static/ognlive/pict/h3.gif (100%) rename {ogn_python => app}/static/ognlive/pict/hel.png (100%) rename {ogn_python => app}/static/ognlive/pict/ico.gif (100%) rename {ogn_python => app}/static/ognlive/pict/ico.png (100%) rename {ogn_python => app}/static/ognlive/pict/l1.gif (100%) rename {ogn_python => app}/static/ognlive/pict/left-3.png (100%) rename {ogn_python => app}/static/ognlive/pict/m.gif (100%) rename {ogn_python => app}/static/ognlive/pict/min.png (100%) rename {ogn_python => app}/static/ognlive/pict/mm.gif (100%) rename {ogn_python => app}/static/ognlive/pict/mmm.gif (100%) rename {ogn_python => app}/static/ognlive/pict/mod.gif (100%) rename {ogn_python => app}/static/ognlive/pict/n.gif (100%) rename {ogn_python => app}/static/ognlive/pict/ogn-logo-ani.gif (100%) rename {ogn_python => app}/static/ognlive/pict/p.gif (100%) rename {ogn_python => app}/static/ognlive/pict/plu.png (100%) rename {ogn_python => app}/static/ognlive/pict/pp.gif (100%) rename {ogn_python => app}/static/ognlive/pict/ppp.gif (100%) rename {ogn_python => app}/static/ognlive/pict/rec.png (100%) rename {ogn_python => app}/static/ognlive/pict/rec0.png (100%) rename {ogn_python => app}/static/ognlive/pict/rec1.png (100%) rename {ogn_python => app}/static/ognlive/pict/recy.png (100%) rename {ogn_python => app}/static/ognlive/pict/redo-6.png (100%) rename {ogn_python => app}/static/ognlive/pict/right-3.png (100%) rename {ogn_python => app}/static/ognlive/pict/tra.gif (100%) rename {ogn_python => app}/static/ognlive/pict/yn0.gif (100%) rename {ogn_python => app}/static/ognlive/pict/yn1.gif (100%) rename {ogn_python => app}/static/ognlive/pict/z.gif (100%) rename {ogn_python => app}/static/ognlive/util.js (100%) rename {ogn_python => app}/templates/airport_detail.html (100%) rename {ogn_python => app}/templates/airports.html (100%) rename {ogn_python => app}/templates/base.html (72%) rename {ogn_python => app}/templates/device_detail.html (100%) rename {ogn_python => app}/templates/devices.html (100%) rename {ogn_python => app}/templates/logbook.html (100%) rename {ogn_python => app}/templates/ogn_live.html (100%) rename {ogn_python => app}/templates/ognrange.html (100%) rename {ogn_python => app}/templates/receiver_detail.html (100%) rename {ogn_python => app}/templates/receivers.html (100%) rename {ogn_python => app}/templates/statistics.html (100%) rename {ogn_python => app}/utils.py (62%) create mode 100644 ogn_python.py delete mode 100644 ogn_python/app.py delete mode 100644 ogn_python/backend/liveglidernet.py delete mode 100644 ogn_python/backend/ognrange.py delete mode 100644 ogn_python/collect/logbook.py delete mode 100644 ogn_python/collect/ognrange.py delete mode 100644 ogn_python/collect/stats.py delete mode 100644 ogn_python/collect/takeoff_landings.py delete mode 100644 ogn_python/commands/logbook.py delete mode 100644 ogn_python/config/default.py delete mode 100644 ogn_python/config/test.py delete mode 100644 ogn_python/gateway/bulkimport.py delete mode 100644 ogn_python/live_routes.py delete mode 100644 ogn_python/model/flights2d.py delete mode 100644 ogn_python/model/relation_stats.py delete mode 100644 ogn_python/model/takeoff_landing.py delete mode 100644 ogn_python/navigation.py delete mode 100644 ogn_python/routes.py diff --git a/Vagrantfile b/Vagrantfile index 8ee8c2a..582d2c9 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,7 +4,7 @@ set -e # install PostgreSQL and PostGIS sudo apt-get update -sudo apt-get install -y --no-install-recommends postgresql-9.4-postgis-2.1 libpq-dev +sudo apt-get install -y postgresql-11-postgis-2.5 # create PostGIS database sudo sudo -u postgres createuser -s vagrant @@ -14,22 +14,18 @@ sudo sudo -u postgres psql -d ogn -c 'CREATE EXTENSION postgis;' # install python requirements cd /vagrant -sudo apt-get install -y --no-install-recommends redis-server build-essential python3 python3-pip python3-dev libpq-dev libgeos-dev -sudo -H pip3 install -r requirements.txt +sudo apt-get install -y python3-pip redis-server +sudo pip3 install -r requirements.txt # # initialize database -./manage.py db.init +#./manage.py db.init # # import registered devices from ddb -./manage.py db.import_ddb +#./manage.py db.import_ddb SCRIPT Vagrant.configure("2") do |config| - config.vm.box = 'debian/jessie64' - - # Current version is broken - config.vm.box_version = '8.5.2' - + config.vm.box = 'debian/buster64' config.vm.provision 'shell', inline: $script, privileged: false end diff --git a/ogn_python/__init__.py b/app/__init__.py similarity index 69% rename from ogn_python/__init__.py rename to app/__init__.py index ad25bd5..1ff0154 100644 --- a/ogn_python/__init__.py +++ b/app/__init__.py @@ -5,14 +5,14 @@ from flask_migrate import Migrate from flask_caching import Cache from celery import Celery -from ogn_python.flask_celery import make_celery +from app.flask_celery import make_celery # Initialize Flask app = Flask(__name__) # Load the configuration -#app.config.from_object('config.default') -app.config.from_envvar('OGN_CONFIG_MODULE') +app.config.from_object('app.config.default') +app.config.from_envvar("OGN_CONFIG_MODULE", silent=True) # Initialize other things bootstrap = Bootstrap(app) @@ -20,3 +20,5 @@ db = SQLAlchemy(app) migrate = Migrate(app, db) cache = Cache(app) celery = make_celery(app) + +from app import routes, commands diff --git a/ogn_python/backend/__init__.py b/app/backend/__init__.py similarity index 100% rename from ogn_python/backend/__init__.py rename to app/backend/__init__.py diff --git a/app/backend/liveglidernet.py b/app/backend/liveglidernet.py new file mode 100644 index 0000000..e3c43ba --- /dev/null +++ b/app/backend/liveglidernet.py @@ -0,0 +1,105 @@ +from datetime import datetime, timedelta, timezone, date + +from app.model import AircraftBeacon, Device, Receiver + +from app import db +from app.model import ReceiverBeacon + + +def utc_to_local(utc_dt): + return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None) + + +def encode(address): + return "xx" + address + + +def decode(code): + return code[2:9] + + +def rec(min_timestamp, min_online_timestamp): + last_seen_query = ( + db.session.query(ReceiverBeacon).filter(ReceiverBeacon.timestamp > min_timestamp).order_by(ReceiverBeacon.receiver_id, ReceiverBeacon.timestamp).distinct(ReceiverBeacon.receiver_id) + ) + + lines = [] + lines.append('') + lines.append("") + lines.append('') + for receiver_beacon in last_seen_query: + if receiver_beacon.location == None or receiver_beacon.name.startswith("FNB"): + continue + lines.append( + ''.format( + receiver_beacon.name, receiver_beacon.location.latitude, receiver_beacon.location.longitude, receiver_beacon.timestamp < min_online_timestamp + ) + ) + + lines.append("") + xml = "\n".join(lines) + + return xml + + +def lxml(show_offline=False, lat_max=90, lat_min=-90, lon_max=180, lon_min=-180): + + timestamp_range_filter = [db.between(AircraftBeacon.timestamp, datetime(2018, 7, 31, 11, 55, 0), datetime(2018, 7, 31, 12, 5, 0))] + + last_seen_query = db.session.query(AircraftBeacon).filter(*timestamp_range_filter).order_by(AircraftBeacon.device_id, AircraftBeacon.timestamp).distinct(AircraftBeacon.device_id) + lines = list() + lines.append('') + lines.append("") + + for aircraft_beacon in last_seen_query: + device = aircraft_beacon.device + + code = encode(device.address) + + if device.info: + if not device.info.tracked or not device.info.identified: + continue + + if not device.info.competition: + competition = device.info.registration[-2:] + else: + competition = device.info.competition + + if not device.info.registration: + registration = "???" + else: + registration = device.info.registration + + address = device.address + + else: + competition = ("_" + code[-2:]).lower() + registration = code + address = 0 + + elapsed_time = datetime.utcnow() - aircraft_beacon.timestamp + elapsed_seconds = int(elapsed_time.total_seconds()) + + lines.append( + ' '.format( + aircraft_beacon.location.latitude, + aircraft_beacon.location.longitude, + competition, + registration, + int(aircraft_beacon.altitude), + utc_to_local(aircraft_beacon.timestamp).strftime("%H:%M:%S"), + elapsed_seconds, + int(aircraft_beacon.track), + int(aircraft_beacon.ground_speed), + int(aircraft_beacon.climb_rate * 10) / 10, + aircraft_beacon.aircraft_type, + aircraft_beacon.receiver_name, + address, + code, + ) + ) + + lines.append("") + xml = "\n".join(lines) + + return xml diff --git a/app/backend/ognrange.py b/app/backend/ognrange.py new file mode 100644 index 0000000..cdef59b --- /dev/null +++ b/app/backend/ognrange.py @@ -0,0 +1,54 @@ +import json +from datetime import datetime, timedelta + +from sqlalchemy import func, case +from sqlalchemy.sql.expression import label +from app.model import Receiver, ReceiverCoverage + +from app import db + + +def alchemyencoder(obj): + """JSON encoder function for SQLAlchemy special classes.""" + + import decimal + from datetime import datetime + + if isinstance(obj, datetime): + return obj.strftime("%Y-%m-%d %H:%M") + elif isinstance(obj, decimal.Decimal): + return float(obj) + + +def stations2_filtered_pl(start, end): + last_10_minutes = datetime.utcnow() - timedelta(minutes=10) + + query = ( + db.session.query( + Receiver.name.label("s"), + label("lt", func.round(func.ST_Y(Receiver.location_wkt) * 10000) / 10000), + label("lg", func.round(func.ST_X(Receiver.location_wkt) * 10000) / 10000), + case([(Receiver.lastseen > last_10_minutes, "U")], else_="D").label("u"), + Receiver.lastseen.label("ut"), + label("v", Receiver.version + "." + Receiver.platform), + ) + .order_by(Receiver.lastseen) + .filter(db.or_(db.and_(start < Receiver.firstseen, end > Receiver.firstseen), db.and_(start < Receiver.lastseen, end > Receiver.lastseen))) + ) + + res = db.session.execute(query) + stations = json.dumps({"stations": [dict(r) for r in res]}, default=alchemyencoder) + + return stations + + +def max_tile_mgrs_pl(station, start, end, squares): + query = ( + db.session.query(func.right(ReceiverCoverage.location_mgrs_short, 4), func.count(ReceiverCoverage.location_mgrs_short)) + .filter(db.and_(Receiver.id == ReceiverCoverage.receiver_id, Receiver.name == station)) + .filter(ReceiverCoverage.location_mgrs_short.like(squares + "%")) + .group_by(func.right(ReceiverCoverage.location_mgrs_short, 4)) + ) + + res = {"t": squares, "p": ["{}/{}".format(r[0], r[1]) for r in query.all()]} + return json.dumps(res) diff --git a/ogn_python/collect/__init__.py b/app/collect/__init__.py similarity index 100% rename from ogn_python/collect/__init__.py rename to app/collect/__init__.py diff --git a/ogn_python/collect/celery.py b/app/collect/celery.py similarity index 63% rename from ogn_python/collect/celery.py rename to app/collect/celery.py index 5bea387..1bb4c2f 100644 --- a/ogn_python/collect/celery.py +++ b/app/collect/celery.py @@ -2,26 +2,26 @@ import datetime from celery.utils.log import get_task_logger -from ogn_python.collect.takeoff_landings import update_entries as takeoff_update_entries +from app.collect.takeoff_landings import update_entries as takeoff_update_entries -from ogn_python.collect.logbook import update_entries as logbook_update_entries -from ogn_python.collect.logbook import update_max_altitudes as logbook_update_max_altitudes +from app.collect.logbook import update_entries as logbook_update_entries +from app.collect.logbook import update_max_altitudes as logbook_update_max_altitudes -from ogn_python.collect.database import import_ddb as device_infos_import_ddb -from ogn_python.collect.database import update_country_code as receivers_update_country_code +from app.collect.database import import_ddb as device_infos_import_ddb +from app.collect.database import update_country_code as receivers_update_country_code -from ogn_python.collect.stats import create_device_stats, update_device_stats_jumps, create_receiver_stats, create_relation_stats, update_qualities, update_receivers, update_devices +from app.collect.stats import create_device_stats, update_device_stats_jumps, create_receiver_stats, create_relation_stats, update_qualities, update_receivers, update_devices -from ogn_python.collect.ognrange import update_entries as receiver_coverage_update_entries +from app.collect.ognrange import update_entries as receiver_coverage_update_entries -from ogn_python import db -from ogn_python import celery +from app import db +from app import celery logger = get_task_logger(__name__) -@celery.task(name='update_takeoff_landings') +@celery.task(name="update_takeoff_landings") def update_takeoff_landings(last_minutes): """Compute takeoffs and landings.""" @@ -31,7 +31,7 @@ def update_takeoff_landings(last_minutes): return result -@celery.task(name='update_logbook_entries') +@celery.task(name="update_logbook_entries") def update_logbook_entries(day_offset): """Add/update logbook entries.""" @@ -40,7 +40,7 @@ def update_logbook_entries(day_offset): return result -@celery.task(name='update_logbook_max_altitude') +@celery.task(name="update_logbook_max_altitude") def update_logbook_max_altitude(day_offset): """Add max altitudes in logbook when flight is complete (takeoff and landing).""" @@ -49,7 +49,7 @@ def update_logbook_max_altitude(day_offset): return result -@celery.task(name='import_ddb') +@celery.task(name="import_ddb") def import_ddb(): """Import registered devices from the DDB.""" @@ -57,7 +57,7 @@ def import_ddb(): return result -@celery.task(name='update_receivers_country_code') +@celery.task(name="update_receivers_country_code") def update_receivers_country_code(): """Update country code in receivers table if None.""" @@ -65,19 +65,16 @@ def update_receivers_country_code(): return result -@celery.task(name='purge_old_data') +@celery.task(name="purge_old_data") def purge_old_data(max_hours): """Delete AircraftBeacons and ReceiverBeacons older than given 'age'.""" - from ogn_python.model import AircraftBeacon, ReceiverBeacon - min_timestamp = datetime.datetime.utcnow() - datetime.timedelta(hours=max_hours) - aircraft_beacons_deleted = db.session.query(AircraftBeacon) \ - .filter(AircraftBeacon.timestamp < min_timestamp) \ - .delete() + from app.model import AircraftBeacon, ReceiverBeacon - receiver_beacons_deleted = db.session.query(ReceiverBeacon) \ - .filter(ReceiverBeacon.timestamp < min_timestamp) \ - .delete() + min_timestamp = datetime.datetime.utcnow() - datetime.timedelta(hours=max_hours) + aircraft_beacons_deleted = db.session.query(AircraftBeacon).filter(AircraftBeacon.timestamp < min_timestamp).delete() + + receiver_beacons_deleted = db.session.query(ReceiverBeacon).filter(ReceiverBeacon.timestamp < min_timestamp).delete() db.session.commit() @@ -85,7 +82,7 @@ def purge_old_data(max_hours): return result -@celery.task(name='update_stats') +@celery.task(name="update_stats") def update_stats(day_offset): """Create stats and update receivers/devices with stats.""" @@ -100,7 +97,7 @@ def update_stats(day_offset): update_devices(session=db.session) -@celery.task(name='update_ognrange') +@celery.task(name="update_ognrange") def update_ognrange(day_offset): """Create receiver coverage stats for Melissas ognrange.""" diff --git a/ogn_python/collect/database.py b/app/collect/database.py similarity index 72% rename from ogn_python/collect/database.py rename to app/collect/database.py index 79dcc2f..63ccb58 100644 --- a/ogn_python/collect/database.py +++ b/app/collect/database.py @@ -3,10 +3,10 @@ from sqlalchemy.sql import null, and_, func, not_, case from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql import insert -from ogn_python.model import Country, DeviceInfo, DeviceInfoOrigin, AircraftBeacon, ReceiverBeacon, Device, Receiver -from ogn_python.utils import get_ddb, get_flarmnet +from app.model import Country, DeviceInfo, DeviceInfoOrigin, AircraftBeacon, ReceiverBeacon, Device, Receiver +from app.utils import get_ddb, get_flarmnet -from ogn_python import app +from app import app def upsert(session, model, rows, update_cols): @@ -17,8 +17,7 @@ def upsert(session, model, rows, update_cols): stmt = insert(table).values(rows) on_conflict_stmt = stmt.on_conflict_do_update( - index_elements=table.primary_key.columns, - set_={k: case([(getattr(stmt.excluded, k) != null(), getattr(stmt.excluded, k))], else_=getattr(model, k)) for k in update_cols}, + index_elements=table.primary_key.columns, set_={k: case([(getattr(stmt.excluded, k) != null(), getattr(stmt.excluded, k))], else_=getattr(model, k)) for k in update_cols} ) # print(compile_query(on_conflict_stmt)) @@ -31,9 +30,7 @@ def update_device_infos(session, address_origin, path=None): else: device_infos = get_ddb(csv_file=path) - session.query(DeviceInfo) \ - .filter(DeviceInfo.address_origin == address_origin) \ - .delete(synchronize_session='fetch') + session.query(DeviceInfo).filter(DeviceInfo.address_origin == address_origin).delete(synchronize_session="fetch") session.commit() for device_info in device_infos: @@ -65,10 +62,11 @@ def update_country_code(session, logger=None): if logger is None: logger = app.logger - update_receivers = session.query(Receiver) \ - .filter(and_(Receiver.country_id == null(), Receiver.location_wkt != null(), func.st_within(Receiver.location_wkt, Country.geom))) \ - .update({Receiver.country_id: Country.gid}, - synchronize_session='fetch') + update_receivers = ( + session.query(Receiver) + .filter(and_(Receiver.country_id == null(), Receiver.location_wkt != null(), func.st_within(Receiver.location_wkt, Country.geom))) + .update({Receiver.country_id: Country.gid}, synchronize_session="fetch") + ) session.commit() diff --git a/app/collect/logbook.py b/app/collect/logbook.py new file mode 100644 index 0000000..ae59494 --- /dev/null +++ b/app/collect/logbook.py @@ -0,0 +1,200 @@ +from sqlalchemy import and_, or_, insert, update, exists, between +from sqlalchemy.sql import func, null +from sqlalchemy.sql.expression import true, false + +from app.model import TakeoffLanding, Logbook, AircraftBeacon +from app.utils import date_to_timestamps + +from app import app + + +def update_entries(session, date, logger=None): + """Add/update logbook entries.""" + + if logger is None: + logger = app.logger + + logger.info("Compute logbook.") + + # limit time range to given date and set window partition and window order + (start, end) = date_to_timestamps(date) + pa = TakeoffLanding.device_id + wo = and_(TakeoffLanding.device_id, TakeoffLanding.airport_id, TakeoffLanding.timestamp) + + # make a query with current, previous and next "takeoff_landing" event, so we can find complete flights + sq = ( + session.query( + TakeoffLanding.device_id, + func.lag(TakeoffLanding.device_id).over(partition_by=pa, order_by=wo).label("device_id_prev"), + func.lead(TakeoffLanding.device_id).over(partition_by=pa, order_by=wo).label("device_id_next"), + TakeoffLanding.timestamp, + func.lag(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_prev"), + func.lead(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_next"), + TakeoffLanding.track, + func.lag(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_prev"), + func.lead(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_next"), + TakeoffLanding.is_takeoff, + func.lag(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_prev"), + func.lead(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_next"), + TakeoffLanding.airport_id, + func.lag(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_prev"), + func.lead(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_next"), + ) + .filter(between(TakeoffLanding.timestamp, start, end)) + .subquery() + ) + + # find complete flights + complete_flight_query = session.query( + sq.c.timestamp.label("reftime"), + sq.c.device_id.label("device_id"), + sq.c.timestamp.label("takeoff_timestamp"), + sq.c.track.label("takeoff_track"), + sq.c.airport_id.label("takeoff_airport_id"), + sq.c.timestamp_next.label("landing_timestamp"), + sq.c.track_next.label("landing_track"), + sq.c.airport_id_next.label("landing_airport_id"), + ).filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) + + # find landings without start + only_landings_query = ( + session.query( + sq.c.timestamp.label("reftime"), + sq.c.device_id.label("device_id"), + null().label("takeoff_timestamp"), + null().label("takeoff_track"), + null().label("takeoff_airport_id"), + sq.c.timestamp.label("landing_timestamp"), + sq.c.track.label("landing_track"), + sq.c.airport_id.label("landing_airport_id"), + ) + .filter(sq.c.is_takeoff == false()) + .filter(or_(sq.c.is_takeoff_prev == false(), sq.c.is_takeoff_prev == null())) + ) + + # find starts without landing + only_starts_query = ( + session.query( + sq.c.timestamp.label("reftime"), + sq.c.device_id.label("device_id"), + sq.c.timestamp.label("takeoff_timestamp"), + sq.c.track.label("takeoff_track"), + sq.c.airport_id.label("takeoff_airport_id"), + null().label("landing_timestamp"), + null().label("landing_track"), + null().label("landing_airport_id"), + ) + .filter(sq.c.is_takeoff == true()) + .filter(or_(sq.c.is_takeoff_next == true(), sq.c.is_takeoff_next == null())) + ) + + # unite all computated flights + union_query = complete_flight_query.union(only_landings_query, only_starts_query).subquery() + + # if a logbook entry exist --> update it + upd = ( + update(Logbook) + .where( + and_( + Logbook.device_id == union_query.c.device_id, + union_query.c.takeoff_airport_id != null(), + union_query.c.landing_airport_id != null(), + or_( + and_(Logbook.takeoff_airport_id == union_query.c.takeoff_airport_id, Logbook.takeoff_timestamp == union_query.c.takeoff_timestamp, Logbook.landing_airport_id == null()), + and_(Logbook.takeoff_airport_id == null(), Logbook.landing_airport_id == union_query.c.landing_airport_id, Logbook.landing_timestamp == union_query.c.landing_timestamp), + ), + ) + ) + .values( + { + "reftime": union_query.c.reftime, + "takeoff_timestamp": union_query.c.takeoff_timestamp, + "takeoff_track": union_query.c.takeoff_track, + "takeoff_airport_id": union_query.c.takeoff_airport_id, + "landing_timestamp": union_query.c.landing_timestamp, + "landing_track": union_query.c.landing_track, + "landing_airport_id": union_query.c.landing_airport_id, + } + ) + ) + + result = session.execute(upd) + update_counter = result.rowcount + session.commit() + logger.debug("Updated logbook entries: {}".format(update_counter)) + + # if a logbook entry doesnt exist --> insert it + new_logbook_entries = session.query(union_query).filter( + ~exists().where( + and_( + Logbook.device_id == union_query.c.device_id, + or_( + and_(Logbook.takeoff_airport_id == union_query.c.takeoff_airport_id, Logbook.takeoff_timestamp == union_query.c.takeoff_timestamp), + and_(Logbook.takeoff_airport_id == null(), union_query.c.takeoff_airport_id == null()), + ), + or_( + and_(Logbook.landing_airport_id == union_query.c.landing_airport_id, Logbook.landing_timestamp == union_query.c.landing_timestamp), + and_(Logbook.landing_airport_id == null(), union_query.c.landing_airport_id == null()), + ), + ) + ) + ) + + ins = insert(Logbook).from_select( + ( + Logbook.reftime, + Logbook.device_id, + Logbook.takeoff_timestamp, + Logbook.takeoff_track, + Logbook.takeoff_airport_id, + Logbook.landing_timestamp, + Logbook.landing_track, + Logbook.landing_airport_id, + ), + new_logbook_entries, + ) + + result = session.execute(ins) + insert_counter = result.rowcount + session.commit() + + finish_message = "Logbook: {} inserted, {} updated".format(insert_counter, update_counter) + logger.debug(finish_message) + return finish_message + + +def update_max_altitudes(session, date, logger=None): + """Add max altitudes in logbook when flight is complete (takeoff and landing).""" + + if logger is None: + logger = app.logger + + logger.info("Update logbook max altitude.") + + if session is None: + session = app.session + + (start, end) = date_to_timestamps(date) + + logbook_entries = ( + session.query(Logbook.id) + .filter(and_(Logbook.takeoff_timestamp != null(), Logbook.landing_timestamp != null(), Logbook.max_altitude == null())) + .filter(between(Logbook.reftime, start, end)) + .subquery() + ) + + max_altitudes = ( + session.query(Logbook.id, func.max(AircraftBeacon.altitude).label("max_altitude")) + .filter(Logbook.id == logbook_entries.c.id) + .filter(and_(AircraftBeacon.device_id == Logbook.device_id, AircraftBeacon.timestamp >= Logbook.takeoff_timestamp, AircraftBeacon.timestamp <= Logbook.landing_timestamp)) + .group_by(Logbook.id) + .subquery() + ) + + update_logbook = session.query(Logbook).filter(Logbook.id == max_altitudes.c.id).update({Logbook.max_altitude: max_altitudes.c.max_altitude}, synchronize_session="fetch") + + session.commit() + + finish_message = "Logbook (altitude): {} entries updated.".format(update_logbook) + logger.info(finish_message) + return finish_message diff --git a/app/collect/ognrange.py b/app/collect/ognrange.py new file mode 100644 index 0000000..a6a20f6 --- /dev/null +++ b/app/collect/ognrange.py @@ -0,0 +1,89 @@ +from sqlalchemy import Date +from sqlalchemy import and_, insert, update, exists, between +from sqlalchemy.sql import func, null + +from app.model import AircraftBeacon, ReceiverCoverage +from app.utils import date_to_timestamps + +from app import app + + +def update_entries(session, date, logger=None): + """Create receiver coverage stats for Melissas ognrange.""" + + if logger is None: + logger = app.logger + + logger.info("Compute receiver coverages.") + + (start, end) = date_to_timestamps(date) + + # Filter aircraft beacons + sq = ( + session.query(AircraftBeacon.location_mgrs_short, AircraftBeacon.receiver_id, AircraftBeacon.signal_quality, AircraftBeacon.altitude, AircraftBeacon.device_id) + .filter(and_(between(AircraftBeacon.timestamp, start, end), AircraftBeacon.location_mgrs_short != null(), AircraftBeacon.receiver_id != null(), AircraftBeacon.device_id != null())) + .subquery() + ) + + # ... and group them by reduced MGRS, receiver and date + query = ( + session.query( + sq.c.location_mgrs_short, + sq.c.receiver_id, + func.cast(date, Date).label("date"), + func.max(sq.c.signal_quality).label("max_signal_quality"), + func.min(sq.c.altitude).label("min_altitude"), + func.max(sq.c.altitude).label("max_altitude"), + func.count(sq.c.altitude).label("aircraft_beacon_count"), + func.count(func.distinct(sq.c.device_id)).label("device_count"), + ) + .group_by(sq.c.location_mgrs_short, sq.c.receiver_id) + .subquery() + ) + + # if a receiver coverage entry exist --> update it + upd = ( + update(ReceiverCoverage) + .where(and_(ReceiverCoverage.location_mgrs_short == query.c.location_mgrs_short, ReceiverCoverage.receiver_id == query.c.receiver_id, ReceiverCoverage.date == date)) + .values( + { + "max_signal_quality": query.c.max_signal_quality, + "min_altitude": query.c.min_altitude, + "max_altitude": query.c.max_altitude, + "aircraft_beacon_count": query.c.aircraft_beacon_count, + "device_count": query.c.device_count, + } + ) + ) + + result = session.execute(upd) + update_counter = result.rowcount + session.commit() + logger.debug("Updated receiver coverage entries: {}".format(update_counter)) + + # if a receiver coverage entry doesnt exist --> insert it + new_coverage_entries = session.query(query).filter( + ~exists().where(and_(ReceiverCoverage.location_mgrs_short == query.c.location_mgrs_short, ReceiverCoverage.receiver_id == query.c.receiver_id, ReceiverCoverage.date == date)) + ) + + ins = insert(ReceiverCoverage).from_select( + ( + ReceiverCoverage.location_mgrs_short, + ReceiverCoverage.receiver_id, + ReceiverCoverage.date, + ReceiverCoverage.max_signal_quality, + ReceiverCoverage.min_altitude, + ReceiverCoverage.max_altitude, + ReceiverCoverage.aircraft_beacon_count, + ReceiverCoverage.device_count, + ), + new_coverage_entries, + ) + + result = session.execute(ins) + insert_counter = result.rowcount + session.commit() + + finish_message = "ReceiverCoverage: {} inserted, {} updated".format(insert_counter, update_counter) + logger.debug(finish_message) + return finish_message diff --git a/app/collect/stats.py b/app/collect/stats.py new file mode 100644 index 0000000..2312820 --- /dev/null +++ b/app/collect/stats.py @@ -0,0 +1,451 @@ +from sqlalchemy import insert, distinct, between, literal +from sqlalchemy.sql import null, and_, func, or_, update +from sqlalchemy.sql.expression import case + +from app.model import AircraftBeacon, DeviceStats, Country, CountryStats, ReceiverStats, ReceiverBeacon, RelationStats, Receiver, Device + +from app.utils import date_to_timestamps + +from app import app + +# 40dB@10km is enough for 640km +MAX_PLAUSIBLE_QUALITY = 40 + + +def create_device_stats(session, date, logger=None): + """Add/update device stats.""" + + if logger is None: + logger = app.logger + + (start, end) = date_to_timestamps(date) + + # First kill the stats for the selected date + deleted_counter = session.query(DeviceStats).filter(DeviceStats.date == date).delete() + + # Since "distinct count" does not work in window functions we need a work-around for receiver counting + sq = ( + session.query(AircraftBeacon, func.dense_rank().over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.receiver_id).label("dr")) + .filter(and_(between(AircraftBeacon.timestamp, start, end), AircraftBeacon.device_id != null())) + .filter(or_(AircraftBeacon.error_count == 0, AircraftBeacon.error_count == null())) + .subquery() + ) + + # Calculate stats, firstseen, lastseen and last values != NULL + device_stats = session.query( + distinct(sq.c.device_id).label("device_id"), + literal(date).label("date"), + func.max(sq.c.dr).over(partition_by=sq.c.device_id).label("receiver_count"), + func.max(sq.c.altitude).over(partition_by=sq.c.device_id).label("max_altitude"), + func.count(sq.c.device_id).over(partition_by=sq.c.device_id).label("aircraft_beacon_count"), + func.first_value(sq.c.name).over(partition_by=sq.c.device_id, order_by=case([(sq.c.name == null(), None)], else_=sq.c.timestamp).asc().nullslast()).label("name"), + func.first_value(sq.c.timestamp).over(partition_by=sq.c.device_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).asc().nullslast()).label("firstseen"), + func.first_value(sq.c.timestamp).over(partition_by=sq.c.device_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("lastseen"), + func.first_value(sq.c.aircraft_type).over(partition_by=sq.c.device_id, order_by=case([(sq.c.aircraft_type == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("aircraft_type"), + func.first_value(sq.c.stealth).over(partition_by=sq.c.device_id, order_by=case([(sq.c.stealth == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("stealth"), + func.first_value(sq.c.software_version) + .over(partition_by=sq.c.device_id, order_by=case([(sq.c.software_version == null(), None)], else_=sq.c.timestamp).desc().nullslast()) + .label("software_version"), + func.first_value(sq.c.hardware_version) + .over(partition_by=sq.c.device_id, order_by=case([(sq.c.hardware_version == null(), None)], else_=sq.c.timestamp).desc().nullslast()) + .label("hardware_version"), + func.first_value(sq.c.real_address).over(partition_by=sq.c.device_id, order_by=case([(sq.c.real_address == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("real_address"), + ).subquery() + + # And insert them + ins = insert(DeviceStats).from_select( + [ + DeviceStats.device_id, + DeviceStats.date, + DeviceStats.receiver_count, + DeviceStats.max_altitude, + DeviceStats.aircraft_beacon_count, + DeviceStats.name, + DeviceStats.firstseen, + DeviceStats.lastseen, + DeviceStats.aircraft_type, + DeviceStats.stealth, + DeviceStats.software_version, + DeviceStats.hardware_version, + DeviceStats.real_address, + ], + device_stats, + ) + res = session.execute(ins) + insert_counter = res.rowcount + session.commit() + logger.debug("DeviceStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter)) + + return "DeviceStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter) + + +def create_receiver_stats(session, date, logger=None): + """Add/update receiver stats.""" + + if logger is None: + logger = app.logger + + (start, end) = date_to_timestamps(date) + + # First kill the stats for the selected date + deleted_counter = session.query(ReceiverStats).filter(ReceiverStats.date == date).delete() + + # Select one day + sq = session.query(ReceiverBeacon).filter(between(ReceiverBeacon.timestamp, start, end)).subquery() + + # Calculate stats, firstseen, lastseen and last values != NULL + receiver_stats = session.query( + distinct(sq.c.receiver_id).label("receiver_id"), + literal(date).label("date"), + func.first_value(sq.c.timestamp).over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).asc().nullslast()).label("firstseen"), + func.first_value(sq.c.timestamp).over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("lastseen"), + func.first_value(sq.c.location).over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.location == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("location_wkt"), + func.first_value(sq.c.altitude).over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.altitude == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("altitude"), + func.first_value(sq.c.version).over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.version == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("version"), + func.first_value(sq.c.platform).over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.platform == null(), None)], else_=sq.c.timestamp).desc().nullslast()).label("platform"), + ).subquery() + + # And insert them + ins = insert(ReceiverStats).from_select( + [ + ReceiverStats.receiver_id, + ReceiverStats.date, + ReceiverStats.firstseen, + ReceiverStats.lastseen, + ReceiverStats.location_wkt, + ReceiverStats.altitude, + ReceiverStats.version, + ReceiverStats.platform, + ], + receiver_stats, + ) + res = session.execute(ins) + insert_counter = res.rowcount + session.commit() + logger.warn("ReceiverStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter)) + + # Update aircraft_beacon_count, aircraft_count and max_distance + aircraft_beacon_stats = ( + session.query( + AircraftBeacon.receiver_id, + func.count(AircraftBeacon.timestamp).label("aircraft_beacon_count"), + func.count(func.distinct(AircraftBeacon.device_id)).label("aircraft_count"), + func.max(AircraftBeacon.distance).label("max_distance"), + ) + .filter(and_(between(AircraftBeacon.timestamp, start, end), AircraftBeacon.error_count == 0, AircraftBeacon.quality <= MAX_PLAUSIBLE_QUALITY, AircraftBeacon.relay == null())) + .group_by(AircraftBeacon.receiver_id) + .subquery() + ) + + upd = ( + update(ReceiverStats) + .where(and_(ReceiverStats.date == date, ReceiverStats.receiver_id == aircraft_beacon_stats.c.receiver_id)) + .values( + {"aircraft_beacon_count": aircraft_beacon_stats.c.aircraft_beacon_count, "aircraft_count": aircraft_beacon_stats.c.aircraft_count, "max_distance": aircraft_beacon_stats.c.max_distance} + ) + ) + + result = session.execute(upd) + update_counter = result.rowcount + session.commit() + logger.warn("Updated {} ReceiverStats".format(update_counter)) + + return "ReceiverStats for {}: {} deleted, {} inserted, {} updated".format(date, deleted_counter, insert_counter, update_counter) + + +def create_country_stats(session, date, logger=None): + if logger is None: + logger = app.logger + + (start, end) = date_to_timestamps(date) + + # First kill the stats for the selected date + deleted_counter = session.query(CountryStats).filter(CountryStats.date == date).delete() + + country_stats = ( + session.query(literal(date), Country.gid, func.count(AircraftBeacon.timestamp).label("aircraft_beacon_count"), func.count(func.distinct(AircraftBeacon.receiver_id)).label("device_count")) + .filter(between(AircraftBeacon.timestamp, start, end)) + .filter(func.st_contains(Country.geom, AircraftBeacon.location)) + .group_by(Country.gid) + .subquery() + ) + + # And insert them + ins = insert(CountryStats).from_select([CountryStats.date, CountryStats.country_id, CountryStats.aircraft_beacon_count, CountryStats.device_count], country_stats) + res = session.execute(ins) + insert_counter = res.rowcount + session.commit() + + +def update_device_stats_jumps(session, date, logger=None): + """Update device stats jumps.""" + + if logger is None: + logger = app.logger + + (start, end) = date_to_timestamps(date) + + # speed limits in m/s (values above indicates a unplausible position / jump) + max_horizontal_speed = 1000 + max_vertical_speed = 100 + max_jumps = 10 # threshold for an 'ambiguous' device + + # find consecutive positions for a device + sq = ( + session.query( + AircraftBeacon.device_id, + AircraftBeacon.timestamp, + func.lead(AircraftBeacon.timestamp).over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.timestamp).label("timestamp_next"), + AircraftBeacon.location_wkt, + func.lead(AircraftBeacon.location_wkt).over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.timestamp).label("location_next"), + AircraftBeacon.altitude, + func.lead(AircraftBeacon.altitude).over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.timestamp).label("altitude_next"), + ) + .filter(and_(between(AircraftBeacon.timestamp, start, end), AircraftBeacon.error_count == 0)) + .subquery() + ) + + # calc vertial and horizontal speed between points + sq2 = ( + session.query( + sq.c.device_id, + (func.st_distancesphere(sq.c.location_next, sq.c.location) / (func.extract("epoch", sq.c.timestamp_next) - func.extract("epoch", sq.c.timestamp))).label("horizontal_speed"), + ((sq.c.altitude_next - sq.c.altitude) / (func.extract("epoch", sq.c.timestamp_next) - func.extract("epoch", sq.c.timestamp))).label("vertical_speed"), + ) + .filter(and_(sq.c.timestamp != null(), sq.c.timestamp_next != null(), sq.c.timestamp < sq.c.timestamp_next)) + .subquery() + ) + + # ... and find and count 'jumps' + sq3 = ( + session.query(sq2.c.device_id, func.sum(case([(or_(func.abs(sq2.c.horizontal_speed) > max_horizontal_speed, func.abs(sq2.c.vertical_speed) > max_vertical_speed), 1)], else_=0)).label("jumps")) + .group_by(sq2.c.device_id) + .subquery() + ) + + upd = update(DeviceStats).where(and_(DeviceStats.date == date, DeviceStats.device_id == sq3.c.device_id)).values({"ambiguous": sq3.c.jumps > max_jumps, "jumps": sq3.c.jumps}) + + result = session.execute(upd) + update_counter = result.rowcount + session.commit() + logger.warn("Updated {} DeviceStats jumps".format(update_counter)) + + return "DeviceStats jumps for {}: {} updated".format(date, update_counter) + + +def create_relation_stats(session, date, logger=None): + """Add/update relation stats.""" + + if logger is None: + logger = app.logger + + (start, end) = date_to_timestamps(date) + + # First kill the stats for the selected date + deleted_counter = session.query(RelationStats).filter(RelationStats.date == date).delete() + + # Calculate stats for selected day + relation_stats = ( + session.query(literal(date), AircraftBeacon.device_id, AircraftBeacon.receiver_id, func.max(AircraftBeacon.quality), func.count(AircraftBeacon.timestamp)) + .filter( + and_( + between(AircraftBeacon.timestamp, start, end), + AircraftBeacon.distance > 1000, + AircraftBeacon.error_count == 0, + AircraftBeacon.quality <= MAX_PLAUSIBLE_QUALITY, + AircraftBeacon.ground_speed > 10, + ) + ) + .group_by(literal(date), AircraftBeacon.device_id, AircraftBeacon.receiver_id) + .subquery() + ) + + # And insert them + ins = insert(RelationStats).from_select([RelationStats.date, RelationStats.device_id, RelationStats.receiver_id, RelationStats.quality, RelationStats.beacon_count], relation_stats) + res = session.execute(ins) + insert_counter = res.rowcount + session.commit() + logger.warn("RelationStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter)) + + return "RelationStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter) + + +def update_qualities(session, date, logger=None): + """Calculate relative qualities of receivers and devices.""" + + if logger is None: + logger = app.logger + + # Calculate avg quality of devices + dev_sq = session.query(RelationStats.device_id, func.avg(RelationStats.quality).label("quality")).filter(RelationStats.date == date).group_by(RelationStats.device_id).subquery() + + dev_upd = update(DeviceStats).where(and_(DeviceStats.date == date, DeviceStats.device_id == dev_sq.c.device_id)).values({"quality": dev_sq.c.quality}) + + dev_result = session.execute(dev_upd) + dev_update_counter = dev_result.rowcount + session.commit() + logger.warn("Updated {} DeviceStats: quality".format(dev_update_counter)) + + # Calculate avg quality of receivers + rec_sq = session.query(RelationStats.receiver_id, func.avg(RelationStats.quality).label("quality")).filter(RelationStats.date == date).group_by(RelationStats.receiver_id).subquery() + + rec_upd = update(ReceiverStats).where(and_(ReceiverStats.date == date, ReceiverStats.receiver_id == rec_sq.c.receiver_id)).values({"quality": rec_sq.c.quality}) + + rec_result = session.execute(rec_upd) + rec_update_counter = rec_result.rowcount + session.commit() + logger.warn("Updated {} ReceiverStats: quality".format(rec_update_counter)) + + # Calculate quality_offset of devices + dev_sq = ( + session.query( + RelationStats.device_id, (func.sum(RelationStats.beacon_count * (RelationStats.quality - ReceiverStats.quality)) / (func.sum(RelationStats.beacon_count))).label("quality_offset") + ) + .filter(RelationStats.date == date) + .filter(and_(RelationStats.receiver_id == ReceiverStats.receiver_id, RelationStats.date == ReceiverStats.date)) + .group_by(RelationStats.device_id) + .subquery() + ) + + dev_upd = update(DeviceStats).where(and_(DeviceStats.date == date, DeviceStats.device_id == dev_sq.c.device_id)).values({"quality_offset": dev_sq.c.quality_offset}) + + dev_result = session.execute(dev_upd) + dev_update_counter = dev_result.rowcount + session.commit() + logger.warn("Updated {} DeviceStats: quality_offset".format(dev_update_counter)) + + # Calculate quality_offset of receivers + rec_sq = ( + session.query( + RelationStats.receiver_id, (func.sum(RelationStats.beacon_count * (RelationStats.quality - DeviceStats.quality)) / (func.sum(RelationStats.beacon_count))).label("quality_offset") + ) + .filter(RelationStats.date == date) + .filter(and_(RelationStats.device_id == DeviceStats.device_id, RelationStats.date == DeviceStats.date)) + .group_by(RelationStats.receiver_id) + .subquery() + ) + + rec_upd = update(ReceiverStats).where(and_(ReceiverStats.date == date, ReceiverStats.receiver_id == rec_sq.c.receiver_id)).values({"quality_offset": rec_sq.c.quality_offset}) + + rec_result = session.execute(rec_upd) + rec_update_counter = rec_result.rowcount + session.commit() + logger.warn("Updated {} ReceiverStats: quality_offset".format(rec_update_counter)) + + return "Updated {} DeviceStats and {} ReceiverStats".format(dev_update_counter, rec_update_counter) + + +def update_receivers(session, logger=None): + """Update receivers with stats.""" + + if logger is None: + logger = app.logger + + receiver_stats = ( + session.query( + distinct(ReceiverStats.receiver_id).label("receiver_id"), + func.first_value(ReceiverStats.firstseen) + .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.firstseen == null(), None)], else_=ReceiverStats.date).asc().nullslast()) + .label("firstseen"), + func.first_value(ReceiverStats.lastseen) + .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.lastseen == null(), None)], else_=ReceiverStats.date).desc().nullslast()) + .label("lastseen"), + func.first_value(ReceiverStats.location_wkt) + .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.location_wkt == null(), None)], else_=ReceiverStats.date).desc().nullslast()) + .label("location_wkt"), + func.first_value(ReceiverStats.altitude) + .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.altitude == null(), None)], else_=ReceiverStats.date).desc().nullslast()) + .label("altitude"), + func.first_value(ReceiverStats.version) + .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.version == null(), None)], else_=ReceiverStats.date).desc().nullslast()) + .label("version"), + func.first_value(ReceiverStats.platform) + .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.platform == null(), None)], else_=ReceiverStats.date).desc().nullslast()) + .label("platform"), + ) + .order_by(ReceiverStats.receiver_id) + .subquery() + ) + + upd = ( + update(Receiver) + .where(and_(Receiver.id == receiver_stats.c.receiver_id)) + .values( + { + "firstseen": receiver_stats.c.firstseen, + "lastseen": receiver_stats.c.lastseen, + "location": receiver_stats.c.location_wkt, + "altitude": receiver_stats.c.altitude, + "version": receiver_stats.c.version, + "platform": receiver_stats.c.platform, + } + ) + ) + + result = session.execute(upd) + update_counter = result.rowcount + session.commit() + logger.warn("Updated {} Receivers".format(update_counter)) + + return "Updated {} Receivers".format(update_counter) + + +def update_devices(session, logger=None): + """Update devices with stats.""" + + if logger is None: + logger = app.logger + + device_stats = ( + session.query( + distinct(DeviceStats.device_id).label("device_id"), + func.first_value(DeviceStats.name).over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.name == null(), None)], else_=DeviceStats.date).desc().nullslast()).label("name"), + func.first_value(DeviceStats.firstseen) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.firstseen == null(), None)], else_=DeviceStats.date).asc().nullslast()) + .label("firstseen"), + func.max(DeviceStats.lastseen) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.lastseen == null(), None)], else_=DeviceStats.date).desc().nullslast()) + .label("lastseen"), + func.first_value(DeviceStats.aircraft_type) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.aircraft_type == null(), None)], else_=DeviceStats.date).desc().nullslast()) + .label("aircraft_type"), + func.first_value(DeviceStats.stealth) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.stealth == null(), None)], else_=DeviceStats.date).desc().nullslast()) + .label("stealth"), + func.first_value(DeviceStats.software_version) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.software_version == null(), None)], else_=DeviceStats.date).desc().nullslast()) + .label("software_version"), + func.first_value(DeviceStats.hardware_version) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.hardware_version == null(), None)], else_=DeviceStats.date).desc().nullslast()) + .label("hardware_version"), + func.first_value(DeviceStats.real_address) + .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.real_address == null(), None)], else_=DeviceStats.date).desc().nullslast()) + .label("real_address"), + ) + .order_by(DeviceStats.device_id) + .subquery() + ) + + upd = ( + update(Device) + .where(and_(Device.id == device_stats.c.device_id)) + .values( + { + "name": device_stats.c.name, + "firstseen": device_stats.c.firstseen, + "lastseen": device_stats.c.lastseen, + "aircraft_type": device_stats.c.aircraft_type, + "stealth": device_stats.c.stealth, + "software_version": device_stats.c.software_version, + "hardware_version": device_stats.c.hardware_version, + "real_address": device_stats.c.real_address, + } + ) + ) + + result = session.execute(upd) + update_counter = result.rowcount + session.commit() + logger.warn("Updated {} Devices".format(update_counter)) + + return "Updated {} Devices".format(update_counter) diff --git a/app/collect/takeoff_landings.py b/app/collect/takeoff_landings.py new file mode 100644 index 0000000..a69078f --- /dev/null +++ b/app/collect/takeoff_landings.py @@ -0,0 +1,144 @@ +from datetime import timedelta + +from sqlalchemy import and_, or_, insert, between, exists +from sqlalchemy.sql import func, null +from sqlalchemy.sql.expression import case + +from app.model import AircraftBeacon, TakeoffLanding, Airport + +from app import app + + +def update_entries(session, start, end, logger=None): + """Compute takeoffs and landings.""" + + if logger is None: + logger = app.logger + + logger.info("Compute takeoffs and landings.") + + # considered time interval should not exceed a complete day + if end - start > timedelta(days=1): + abort_message = "TakeoffLanding: timeinterval start='{}' and end='{}' is too big.".format(start, end) + logger.warn(abort_message) + return abort_message + + # check if we have any airport + airports_query = session.query(Airport).limit(1) + if not airports_query.all(): + abort_message = "TakeoffLanding: Cannot calculate takeoff and landings without any airport! Please import airports first." + logger.warn(abort_message) + return abort_message + + # takeoff / landing detection is based on 3 consecutive points all below a certain altitude AGL + takeoff_speed = 55 # takeoff detection: 1st point below, 2nd and 3rd above this limit + landing_speed = 40 # landing detection: 1st point above, 2nd and 3rd below this limit + min_takeoff_climb_rate = -5 # takeoff detection: glider should not sink too much + max_landing_climb_rate = 5 # landing detection: glider should not climb too much + duration = 100 # the points must not exceed this duration + radius = 5000 # the points must not exceed this radius around the 2nd point + max_agl = 200 # takeoff / landing must not exceed this altitude AGL + + # get beacons for selected time range, one per device_id and timestamp + sq = ( + session.query(AircraftBeacon) + .distinct(AircraftBeacon.device_id, AircraftBeacon.timestamp) + .order_by(AircraftBeacon.device_id, AircraftBeacon.timestamp, AircraftBeacon.error_count) + .filter(AircraftBeacon.agl < max_agl) + .filter(between(AircraftBeacon.timestamp, start, end)) + .subquery() + ) + + # make a query with current, previous and next position + sq2 = session.query( + sq.c.device_id, + func.lag(sq.c.device_id).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("device_id_prev"), + func.lead(sq.c.device_id).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("device_id_next"), + sq.c.timestamp, + func.lag(sq.c.timestamp).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("timestamp_prev"), + func.lead(sq.c.timestamp).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("timestamp_next"), + sq.c.location, + func.lag(sq.c.location).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("location_wkt_prev"), + func.lead(sq.c.location).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("location_wkt_next"), + sq.c.track, + func.lag(sq.c.track).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("track_prev"), + func.lead(sq.c.track).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("track_next"), + sq.c.ground_speed, + func.lag(sq.c.ground_speed).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("ground_speed_prev"), + func.lead(sq.c.ground_speed).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("ground_speed_next"), + sq.c.altitude, + func.lag(sq.c.altitude).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("altitude_prev"), + func.lead(sq.c.altitude).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("altitude_next"), + sq.c.climb_rate, + func.lag(sq.c.climb_rate).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("climb_rate_prev"), + func.lead(sq.c.climb_rate).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label("climb_rate_next"), + ).subquery() + + # consider only positions with predecessor and successor and limit distance and duration between points + sq3 = ( + session.query(sq2) + .filter(and_(sq2.c.device_id_prev != null(), sq2.c.device_id_next != null())) + .filter(and_(func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_prev) < radius, func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_next) < radius)) + .filter(sq2.c.timestamp_next - sq2.c.timestamp_prev < timedelta(seconds=duration)) + .subquery() + ) + + # find possible takeoffs and landings + sq4 = ( + session.query( + sq3.c.timestamp, + case( + [ + (sq3.c.ground_speed > takeoff_speed, sq3.c.location_wkt_prev), # on takeoff we take the location from the previous fix because it is nearer to the airport + (sq3.c.ground_speed <= takeoff_speed, sq3.c.location), + ] + ).label("location"), + case([(sq3.c.ground_speed > landing_speed, sq3.c.track), (sq3.c.ground_speed <= landing_speed, sq3.c.track_prev)]).label( + "track" + ), # on landing we take the track from the previous fix because gliders tend to leave the runway quickly + sq3.c.ground_speed, + sq3.c.altitude, + case([(sq3.c.ground_speed > takeoff_speed, True), (sq3.c.ground_speed < landing_speed, False)]).label("is_takeoff"), + sq3.c.device_id, + ) + .filter( + or_( + and_(sq3.c.ground_speed_prev < takeoff_speed, sq3.c.ground_speed > takeoff_speed, sq3.c.ground_speed_next > takeoff_speed, sq3.c.climb_rate > min_takeoff_climb_rate), # takeoff + and_(sq3.c.ground_speed_prev > landing_speed, sq3.c.ground_speed < landing_speed, sq3.c.ground_speed_next < landing_speed, sq3.c.climb_rate < max_landing_climb_rate), # landing + ) + ) + .subquery() + ) + + # consider them if the are near airports ... + sq5 = ( + session.query( + sq4.c.timestamp, sq4.c.track, sq4.c.is_takeoff, sq4.c.device_id, Airport.id.label("airport_id"), func.ST_DistanceSphere(sq4.c.location, Airport.location_wkt).label("airport_distance") + ) + .filter(and_(func.ST_Within(sq4.c.location, Airport.border), between(Airport.style, 2, 5))) + .subquery() + ) + + # ... and take the nearest airport + sq6 = ( + session.query(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_id) + .distinct(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id) + .order_by(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_distance) + .subquery() + ) + + # consider them only if they are not already existing in db + takeoff_landing_query = session.query(sq6).filter( + ~exists().where(and_(TakeoffLanding.timestamp == sq6.c.timestamp, TakeoffLanding.device_id == sq6.c.device_id, TakeoffLanding.airport_id == sq6.c.airport_id)) + ) + + # ... and save them + ins = insert(TakeoffLanding).from_select((TakeoffLanding.timestamp, TakeoffLanding.track, TakeoffLanding.is_takeoff, TakeoffLanding.device_id, TakeoffLanding.airport_id), takeoff_landing_query) + + result = session.execute(ins) + session.commit() + insert_counter = result.rowcount + + finish_message = "TakeoffLandings: {} inserted".format(insert_counter) + logger.info(finish_message) + return finish_message diff --git a/ogn_python/commands/__init__.py b/app/commands/__init__.py similarity index 94% rename from ogn_python/commands/__init__.py rename to app/commands/__init__.py index 469306a..ff3abc5 100644 --- a/ogn_python/commands/__init__.py +++ b/app/commands/__init__.py @@ -1,4 +1,4 @@ -from ogn_python import app +from app import app from .database import user_cli as database_cli from .export import user_cli as export_cli diff --git a/ogn_python/commands/database.py b/app/commands/database.py similarity index 62% rename from ogn_python/commands/database.py rename to app/commands/database.py index 9def6f8..0410f4e 100644 --- a/ogn_python/commands/database.py +++ b/app/commands/database.py @@ -4,14 +4,14 @@ import click from datetime import datetime, timedelta from sqlalchemy.sql import func -from ogn_python.collect.database import update_device_infos, update_country_code -from ogn_python.model import * -from ogn_python.utils import get_airports, get_days +from app.collect.database import update_device_infos, update_country_code +from app.model import * +from app.utils import get_airports, get_days -from ogn_python import app -from ogn_python import db +from app import app +from app import db -user_cli = AppGroup('database') +user_cli = AppGroup("database") user_cli.help = "Database creation and handling." @@ -22,7 +22,7 @@ def get_database_days(start, end): """Returns the first and the last day in aircraft_beacons table.""" if start is None and end is None: - days_from_db = db.session.query(func.min(AircraftBeacon.timestamp).label('first_day'), func.max(AircraftBeacon.timestamp).label('last_day')).one() + days_from_db = db.session.query(func.min(AircraftBeacon.timestamp).label("first_day"), func.max(AircraftBeacon.timestamp).label("last_day")).one() start = days_from_db[0].date() end = days_from_db[1].date() else: @@ -34,40 +34,40 @@ def get_database_days(start, end): return days -@user_cli.command('info') +@user_cli.command("info") def info(): print(app.config) - print(app.config['SQLALCHEMY_DATABASE_URI']) + print(app.config["SQLALCHEMY_DATABASE_URI"]) -@user_cli.command('init') +@user_cli.command("init") def init(): """Initialize the database.""" from alembic.config import Config from alembic import command - db.session.execute('CREATE EXTENSION IF NOT EXISTS postgis;') - db.session.execute('CREATE EXTENSION IF NOT EXISTS btree_gist;') + db.session.execute("CREATE EXTENSION IF NOT EXISTS postgis;") + db.session.execute("CREATE EXTENSION IF NOT EXISTS btree_gist;") db.session.commit() db.create_all() - #alembic_cfg = Config(ALEMBIC_CONFIG_FILE) - #command.stamp(alembic_cfg, "head") + # alembic_cfg = Config(ALEMBIC_CONFIG_FILE) + # command.stamp(alembic_cfg, "head") print("Done.") -@user_cli.command('init_timescaledb') +@user_cli.command("init_timescaledb") def init_timescaledb(): """Initialize TimescaleDB features.""" - db.session.execute('CREATE EXTENSION IF NOT EXISTS timescaledb;') + db.session.execute("CREATE EXTENSION IF NOT EXISTS timescaledb;") db.session.execute("SELECT create_hypertable('aircraft_beacons', 'timestamp', chunk_target_size => '2GB', if_not_exists => TRUE);") db.session.execute("SELECT create_hypertable('receiver_beacons', 'timestamp', chunk_target_size => '2GB', if_not_exists => TRUE);") db.session.commit() -@user_cli.command('upgrade') +@user_cli.command("upgrade") def upgrade(): """Upgrade database to the latest version.""" @@ -75,21 +75,21 @@ def upgrade(): from alembic import command alembic_cfg = Config(ALEMBIC_CONFIG_FILE) - command.upgrade(alembic_cfg, 'head') + command.upgrade(alembic_cfg, "head") -@user_cli.command('drop') -@click.option('--sure', default='n') +@user_cli.command("drop") +@click.option("--sure", default="n") def drop(sure): """Drop all tables.""" - if sure == 'y': + if sure == "y": db.drop_all() - print('Dropped all tables.') + print("Dropped all tables.") else: print("Add argument '--sure y' to drop all tables.") -@user_cli.command('import_ddb') +@user_cli.command("import_ddb") def import_ddb(): """Import registered devices from the DDB.""" @@ -98,33 +98,29 @@ def import_ddb(): print("Imported %i devices." % counter) -@user_cli.command('import_file') -@click.argument('path') -def import_file(path='tests/custom_ddb.txt'): +@user_cli.command("import_file") +@click.argument("path") +def import_file(path="tests/custom_ddb.txt"): """Import registered devices from a local file.""" print("Import registered devices from '{}'...".format(path)) - counter = update_device_infos(db.session, - DeviceInfoOrigin.user_defined, - path=path) + counter = update_device_infos(db.session, DeviceInfoOrigin.user_defined, path=path) print("Imported %i devices." % counter) -@user_cli.command('import_flarmnet') -@click.argument('path') +@user_cli.command("import_flarmnet") +@click.argument("path") def import_flarmnet(path=None): """Import registered devices from a local file.""" print("Import registered devices from '{}'...".format("internet" if path is None else path)) - counter = update_device_infos(db.session, - DeviceInfoOrigin.flarmnet, - path=path) + counter = update_device_infos(db.session, DeviceInfoOrigin.flarmnet, path=path) print("Imported %i devices." % counter) -@user_cli.command('import_airports') -@click.argument('path') -def import_airports(path='tests/SeeYou.cup'): +@user_cli.command("import_airports") +@click.argument("path") +def import_airports(path="tests/SeeYou.cup"): """Import airports from a ".cup" file""" print("Import airports from '{}'...".format(path)) @@ -136,7 +132,7 @@ def import_airports(path='tests/SeeYou.cup'): print("Imported {} airports.".format(len(airports))) -@user_cli.command('update_country_codes') +@user_cli.command("update_country_codes") def update_country_codes(): """Update country codes of all receivers.""" diff --git a/ogn_python/commands/export.py b/app/commands/export.py similarity index 57% rename from ogn_python/commands/export.py rename to app/commands/export.py index 6ce476c..86fcc30 100644 --- a/ogn_python/commands/export.py +++ b/app/commands/export.py @@ -6,14 +6,14 @@ import re import csv from aerofiles.igc import Writer -from ogn_python.model import * -from ogn_python import db +from app.model import * +from app import db -user_cli = AppGroup('export') +user_cli = AppGroup("export") user_cli.help = "Export data in several file formats." -@user_cli.command('cup') +@user_cli.command("cup") def cup(): """Export receiver waypoints as '.cup'.""" @@ -49,65 +49,61 @@ def cup(): """ results = db.session.execute(sql) - with open('receivers.cup', 'w') as outfile: + with open("receivers.cup", "w") as outfile: outcsv = csv.writer(outfile) outcsv.writerow(results.keys()) outcsv.writerows(results.fetchall()) -@user_cli.command('igc') -@click.argument('address') -@click.argument('date') + +@user_cli.command("igc") +@click.argument("address") +@click.argument("date") def igc(address, date): """Export igc file for
at .""" - if not re.match('.{6}', address): + if not re.match(".{6}", address): print("Address {} not valid.".format(address)) return - if not re.match('\d{4}-\d{2}-\d{2}', date): + if not re.match("\d{4}-\d{2}-\d{2}", date): print("Date {} not valid.".format(date)) return - device_id = db.session.query(Device.id) \ - .filter(Device.address == address) \ - .first() + device_id = db.session.query(Device.id).filter(Device.address == address).first() - if (device_id is None): + if device_id is None: print("Device with address '{}' not found.".format(address)) return - with open('sample.igc', 'wb') as fp: + with open("sample.igc", "wb") as fp: writer = Writer(fp) - writer.write_headers({ - 'manufacturer_code': 'OGN', - 'logger_id': 'OGN', - 'date': datetime.date(1987, 2, 24), - 'fix_accuracy': 50, - 'pilot': 'Konstantin Gruendger', - 'copilot': '', - 'glider_type': 'Duo Discus', - 'glider_id': 'D-KKHH', - 'firmware_version': '2.2', - 'hardware_version': '2', - 'logger_type': 'LXNAVIGATION,LX8000F', - 'gps_receiver': 'uBLOX LEA-4S-2,16,max9000m', - 'pressure_sensor': 'INTERSEMA,MS5534A,max10000m', - 'competition_id': '2H', - 'competition_class': 'Doubleseater', - }) + writer.write_headers( + { + "manufacturer_code": "OGN", + "logger_id": "OGN", + "date": datetime.date(1987, 2, 24), + "fix_accuracy": 50, + "pilot": "Konstantin Gruendger", + "copilot": "", + "glider_type": "Duo Discus", + "glider_id": "D-KKHH", + "firmware_version": "2.2", + "hardware_version": "2", + "logger_type": "LXNAVIGATION,LX8000F", + "gps_receiver": "uBLOX LEA-4S-2,16,max9000m", + "pressure_sensor": "INTERSEMA,MS5534A,max10000m", + "competition_id": "2H", + "competition_class": "Doubleseater", + } + ) - points = db.session.query(AircraftBeacon) \ - .filter(AircraftBeacon.device_id == device_id) \ - .filter(AircraftBeacon.timestamp > date + ' 00:00:00') \ - .filter(AircraftBeacon.timestamp < date + ' 23:59:59') \ + points = ( + db.session.query(AircraftBeacon) + .filter(AircraftBeacon.device_id == device_id) + .filter(AircraftBeacon.timestamp > date + " 00:00:00") + .filter(AircraftBeacon.timestamp < date + " 23:59:59") .order_by(AircraftBeacon.timestamp) + ) for point in points.all(): - writer.write_fix( - point.timestamp.time(), - latitude=point.location.latitude, - longitude=point.location.longitude, - valid=True, - pressure_alt=point.altitude, - gps_alt=point.altitude, - ) + writer.write_fix(point.timestamp.time(), latitude=point.location.latitude, longitude=point.location.longitude, valid=True, pressure_alt=point.altitude, gps_alt=point.altitude) diff --git a/ogn_python/commands/flights.py b/app/commands/flights.py similarity index 89% rename from ogn_python/commands/flights.py rename to app/commands/flights.py index c963297..5c81f63 100644 --- a/ogn_python/commands/flights.py +++ b/app/commands/flights.py @@ -4,15 +4,15 @@ import click from datetime import datetime from tqdm import tqdm -from ogn_python.commands.database import get_database_days -from ogn_python import db +from app.commands.database import get_database_days +from app import db -user_cli = AppGroup('flights') +user_cli = AppGroup("flights") user_cli.help = "Create 2D flight paths from data." -NOTHING = '' -CONTEST_RELEVANT = 'AND agl < 1000' -LOW_PASS = 'AND agl < 50 and ground_speed > 250' +NOTHING = "" +CONTEST_RELEVANT = "AND agl < 1000" +LOW_PASS = "AND agl < 50 and ground_speed > 250" def compute_gaps(session, date): @@ -46,7 +46,9 @@ def compute_gaps(session, date): ) sq3 GROUP BY sq3.device_id ON CONFLICT DO NOTHING; - """.format(date=date.strftime('%Y-%m-%d')) + """.format( + date=date.strftime("%Y-%m-%d") + ) session.execute(query) session.commit() @@ -111,17 +113,17 @@ def compute_flights2d(session, date, flight_type): ) sq5 GROUP BY sq5.device_id ON CONFLICT DO NOTHING; - """.format(date=date.strftime('%Y-%m-%d'), - flight_type=flight_type, - filter=filter) + """.format( + date=date.strftime("%Y-%m-%d"), flight_type=flight_type, filter=filter + ) session.execute(query) session.commit() -@user_cli.command('create') -@click.argument('start') -@click.argument('end') -@click.argument('flight_type', type=click.INT) +@user_cli.command("create") +@click.argument("start") +@click.argument("end") +@click.argument("flight_type", type=click.INT) def create(start, end, flight_type): """Compute flights. Flight type: 0: all flights, 1: below 1000m AGL, 2: below 50m AGL + faster than 250 km/h, 3: inverse coverage'""" @@ -129,7 +131,7 @@ def create(start, end, flight_type): pbar = tqdm(days) for single_date in pbar: - pbar.set_description(datetime.strftime(single_date, '%Y-%m-%d')) + pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d")) if flight_type <= 2: result = compute_flights2d(session=db.session, date=single_date, flight_type=flight_type) else: diff --git a/ogn_python/commands/gateway.py b/app/commands/gateway.py similarity index 59% rename from ogn_python/commands/gateway.py rename to app/commands/gateway.py index 2d1039c..d880808 100644 --- a/ogn_python/commands/gateway.py +++ b/app/commands/gateway.py @@ -2,33 +2,33 @@ from flask.cli import AppGroup import click from ogn.client import AprsClient -from ogn_python.gateway.bulkimport import ContinuousDbFeeder +from app.gateway.bulkimport import ContinuousDbFeeder -from ogn_python import app +from app import app -user_cli = AppGroup('gateway') +user_cli = AppGroup("gateway") user_cli.help = "Connection to APRS servers." -@user_cli.command('run') -def run(aprs_user='anon-dev'): +@user_cli.command("run") +def run(aprs_user="anon-dev"): """Run the aprs client.""" saver = ContinuousDbFeeder() # User input validation if len(aprs_user) < 3 or len(aprs_user) > 9: - print('aprs_user must be a string of 3-9 characters.') + print("aprs_user must be a string of 3-9 characters.") return - app.logger.warning('Start ogn gateway') + app.logger.warning("Start ogn gateway") client = AprsClient(aprs_user) client.connect() try: client.run(callback=saver.add, autoreconnect=True) except KeyboardInterrupt: - app.logger.warning('\nStop ogn gateway') + app.logger.warning("\nStop ogn gateway") saver.flush() client.disconnect() diff --git a/app/commands/logbook.py b/app/commands/logbook.py new file mode 100644 index 0000000..01d5e81 --- /dev/null +++ b/app/commands/logbook.py @@ -0,0 +1,119 @@ +from flask.cli import AppGroup +import click + +from datetime import datetime + +from app.collect.logbook import update_entries as logbook_update_entries +from app.collect.takeoff_landings import update_entries as takeoff_landings_update_entries +from app.model import Airport, Logbook +from sqlalchemy.sql import func +from tqdm import tqdm +from app.commands.database import get_database_days +from app.utils import date_to_timestamps + +from app import db + +user_cli = AppGroup("logbook") +user_cli.help = "Handling of logbook data." + + +@user_cli.command("compute_takeoff_landing") +@click.argument("start") +@click.argument("end") +def compute_takeoff_landing(start, end): + """Compute takeoffs and landings.""" + + days = get_database_days(start, end) + + pbar = tqdm(days) + for single_date in pbar: + pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d")) + (start, end) = date_to_timestamps(single_date) + result = takeoff_landings_update_entries(session=db.session, start=start, end=end) + + +@user_cli.command("compute_logbook") +@click.argument("start") +@click.argument("end") +def compute_logbook(start, end): + """Compute logbook.""" + + days = get_database_days(start, end) + + pbar = tqdm(days) + for single_date in pbar: + pbar.set_description(single_date.strftime("%Y-%m-%d")) + result = logbook_update_entries(session=db.session, date=single_date) + + +@user_cli.command("show") +@click.argument("airport_name") +@click.argument("date") +def show(airport_name, date=None): + """Show a logbook for .""" + airport = db.session.query(Airport).filter(Airport.name == airport_name).first() + + if airport is None: + print('Airport "{}" not found.'.format(airport_name)) + return + + or_args = [] + if date is not None: + date = datetime.strptime(date, "%Y-%m-%d") + (start, end) = date_to_timestamps(date) + or_args = [db.between(Logbook.reftime, start, end)] + + # get all logbook entries and add device and airport infos + logbook_query = ( + db.session.query(func.row_number().over(order_by=Logbook.reftime).label("row_number"), Logbook) + .filter(*or_args) + .filter(db.or_(Logbook.takeoff_airport_id == airport.id, Logbook.landing_airport_id == airport.id)) + .order_by(Logbook.reftime) + ) + + # ... and finally print out the logbook + print("--- Logbook ({}) ---".format(airport_name)) + + def none_datetime_replacer(datetime_object): + return "--:--:--" if datetime_object is None else datetime_object.time() + + def none_track_replacer(track_object): + return "--" if track_object is None else round(track_object / 10.0) + + def none_timedelta_replacer(timedelta_object): + return "--:--:--" if timedelta_object is None else timedelta_object + + def none_registration_replacer(device_object): + return "[" + device_object.address + "]" if len(device_object.infos) == 0 else device_object.infos[0].registration + + def none_aircraft_replacer(device_object): + return "(unknown)" if len(device_object.infos) == 0 else device_object.infos[0].aircraft + + def airport_marker(logbook_object): + if logbook_object.takeoff_airport is not None and logbook_object.takeoff_airport.name is not airport.name: + return "FROM: {}".format(logbook_object.takeoff_airport.name) + elif logbook_object.landing_airport is not None and logbook_object.landing_airport.name is not airport.name: + return "TO: {}".format(logbook_object.landing_airport.name) + else: + return "" + + def none_altitude_replacer(logbook_object): + return "?" if logbook_object.max_altitude is None else "{:5d}m ({:+5d}m)".format(logbook_object.max_altitude, logbook_object.max_altitude - logbook_object.takeoff_airport.altitude) + + for [row_number, logbook] in logbook_query.all(): + print( + "%3d. %10s %8s (%2s) %8s (%2s) %8s %15s %8s %17s %20s" + % ( + row_number, + logbook.reftime.date(), + none_datetime_replacer(logbook.takeoff_timestamp), + none_track_replacer(logbook.takeoff_track), + none_datetime_replacer(logbook.landing_timestamp), + none_track_replacer(logbook.landing_track), + none_timedelta_replacer(logbook.duration), + none_altitude_replacer(logbook), + none_registration_replacer(logbook.device), + none_aircraft_replacer(logbook.device), + airport_marker(logbook), + ) + ) diff --git a/ogn_python/commands/stats.py b/app/commands/stats.py similarity index 59% rename from ogn_python/commands/stats.py rename to app/commands/stats.py index c2af4ca..91ec5ee 100644 --- a/ogn_python/commands/stats.py +++ b/app/commands/stats.py @@ -4,24 +4,31 @@ import click from datetime import datetime from tqdm import tqdm -from ogn_python.commands.database import get_database_days +from app.commands.database import get_database_days -from ogn_python.collect.stats import create_device_stats, create_receiver_stats, create_relation_stats, create_country_stats,\ - update_qualities, update_receivers as update_receivers_command, update_devices as update_devices_command,\ - update_device_stats_jumps +from app.collect.stats import ( + create_device_stats, + create_receiver_stats, + create_relation_stats, + create_country_stats, + update_qualities, + update_receivers as update_receivers_command, + update_devices as update_devices_command, + update_device_stats_jumps, +) -from ogn_python.collect.ognrange import update_entries as update_receiver_coverages +from app.collect.ognrange import update_entries as update_receiver_coverages -from ogn_python import db +from app import db -user_cli = AppGroup('stats') +user_cli = AppGroup("stats") user_cli.help = "Handling of statistical data." -@user_cli.command('create') -@click.argument('start') -@click.argument('end') +@user_cli.command("create") +@click.argument("start") +@click.argument("end") def create(start, end): """Create DeviceStats, ReceiverStats and RelationStats.""" @@ -29,16 +36,17 @@ def create(start, end): pbar = tqdm(days) for single_date in pbar: - pbar.set_description(datetime.strftime(single_date, '%Y-%m-%d')) + pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d")) result = create_device_stats(session=db.session, date=single_date) result = update_device_stats_jumps(session=db.session, date=single_date) result = create_receiver_stats(session=db.session, date=single_date) result = create_relation_stats(session=db.session, date=single_date) result = update_qualities(session=db.session, date=single_date) -@user_cli.command('create_country') -@click.argument('start') -@click.argument('end') + +@user_cli.command("create_country") +@click.argument("start") +@click.argument("end") def create_country(start, end): """Create CountryStats.""" @@ -46,22 +54,29 @@ def create_country(start, end): pbar = tqdm(days) for single_date in pbar: - pbar.set_description(datetime.strftime(single_date, '%Y-%m-%d')) + pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d")) result = create_country_stats(session=db.session, date=single_date) -from ogn_python.model import * -@user_cli.command('update_devices_name') + +from app.model import * + + +@user_cli.command("update_devices_name") def update_devices_name(): """Update Devices name.""" device_ids = db.session.query(Device.id).all() for device_id in tqdm(device_ids): - db.session.execute("update devices d set name = sq.name from ( select * from aircraft_beacons ab where ab.device_id = {} limit 1) sq where d.id = sq.device_id and d.name is null or d.name = 'ICA3D3CC4';".format(device_id[0])) + db.session.execute( + "update devices d set name = sq.name from ( select * from aircraft_beacons ab where ab.device_id = {} limit 1) sq where d.id = sq.device_id and d.name is null or d.name = 'ICA3D3CC4';".format( + device_id[0] + ) + ) db.session.commit() -@user_cli.command('update_receivers') +@user_cli.command("update_receivers") def update_receivers(): """Update receivers with data from stats.""" @@ -69,7 +84,7 @@ def update_receivers(): print(result) -@user_cli.command('update_devices') +@user_cli.command("update_devices") def update_devices(): """Update devices with data from stats.""" @@ -77,9 +92,9 @@ def update_devices(): print(result) -@user_cli.command('update_mgrs') -@click.argument('start') -@click.argument('end') +@user_cli.command("update_mgrs") +@click.argument("start") +@click.argument("end") def update_mgrs(start, end): """Create location_mgrs_short.""" @@ -87,23 +102,25 @@ def update_mgrs(start, end): pbar = tqdm(days) for single_date in pbar: - datestr = datetime.strftime(single_date, '%Y-%m-%d') + datestr = datetime.strftime(single_date, "%Y-%m-%d") pbar.set_description(datestr) for pbar2 in tqdm(["{:02d}:{:02d}".format(hh, mm) for hh in range(0, 24) for mm in range(0, 60)]): sql = """ UPDATE aircraft_beacons SET location_mgrs_short = left(location_mgrs, 5) || substring(location_mgrs, 6, 2) || substring(location_mgrs, 11, 2) WHERE timestamp BETWEEN '{0} {1}:00' and '{0} {1}:59' AND location_mgrs_short IS NULL; - """.format(datestr, pbar2) + """.format( + datestr, pbar2 + ) - #print(sql) + # print(sql) db.session.execute(sql) db.session.commit() -@user_cli.command('create_ognrange') -@click.argument('start') -@click.argument('end') +@user_cli.command("create_ognrange") +@click.argument("start") +@click.argument("end") def create_ognrange(start=None, end=None): """Create receiver coverage stats for Melissas ognrange.""" @@ -111,5 +128,5 @@ def create_ognrange(start=None, end=None): pbar = tqdm(days) for single_date in pbar: - pbar.set_description(datetime.strftime(single_date, '%Y-%m-%d')) + pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d")) result = update_receiver_coverages(session=db.session, date=single_date) diff --git a/ogn_python/config/__init__.py b/app/config/__init__.py similarity index 100% rename from ogn_python/config/__init__.py rename to app/config/__init__.py diff --git a/app/config/default.py b/app/config/default.py new file mode 100644 index 0000000..9ffe16d --- /dev/null +++ b/app/config/default.py @@ -0,0 +1,27 @@ +SECRET_KEY = "i-like-ogn" + +SQLALCHEMY_DATABASE_URI = "postgresql://postgres@localhost:5432/ogn" +SQLALCHEMY_TRACK_MODIFICATIONS = False + +# Flask-Cache stuff +CACHE_TYPE = "simple" +CACHE_DEFAULT_TIMEOUT = 300 + +# Celery stuff +CELERY_BROKER_URL = "redis://localhost:6379/0" +CELERY_RESULT_BACKEND = "redis://localhost:6379/0" + + +from celery.schedules import crontab +from datetime import timedelta + +CELERYBEAT_SCHEDULE = { + "update-ddb": {"task": "import_ddb", "schedule": timedelta(hours=1)}, + "update-country-codes": {"task": "update_receivers_country_code", "schedule": timedelta(days=1)}, + "update-takeoff-and-landing": {"task": "update_takeoff_landings", "schedule": timedelta(hours=1), "kwargs": {"last_minutes": 90}}, + "update-logbook": {"task": "update_logbook_entries", "schedule": timedelta(hours=2), "kwargs": {"day_offset": 0}}, + "update-max-altitudes": {"task": "update_logbook_max_altitude", "schedule": timedelta(hours=1), "kwargs": {"day_offset": 0}}, + "update-stats-daily": {"task": "update_stats", "schedule": crontab(hour=0, minute=5), "kwargs": {"day_offset": -1}}, + "update-logbook-daily": {"task": "update_logbook_entries", "schedule": crontab(hour=1, minute=0), "kwargs": {"day_offset": -1}}, + "purge_old_data": {"task": "purge_old_data", "schedule": timedelta(hours=1), "kwargs": {"max_hours": 48}}, +} diff --git a/app/config/test.py b/app/config/test.py new file mode 100644 index 0000000..8bea0a8 --- /dev/null +++ b/app/config/test.py @@ -0,0 +1,6 @@ +SQLALCHEMY_DATABASE_URI = "postgresql://postgres@localhost:5432/ogn_test" +SQLALCHEMY_TRACK_MODIFICATIONS = False + +# Celery stuff +CELERY_BROKER_URL = "redis://localhost:6379/0" +CELERY_RESULT_BACKEND = "redis://localhost:6379/0" diff --git a/ogn_python/flask_celery.py b/app/flask_celery.py similarity index 62% rename from ogn_python/flask_celery.py rename to app/flask_celery.py index e9cfba6..8fb150c 100644 --- a/ogn_python/flask_celery.py +++ b/app/flask_celery.py @@ -1,11 +1,8 @@ from celery import Celery + def make_celery(app): - celery = Celery( - app.import_name, - backend=app.config['CELERY_RESULT_BACKEND'], - broker=app.config['CELERY_BROKER_URL'] - ) + celery = Celery(app.import_name, backend=app.config["CELERY_RESULT_BACKEND"], broker=app.config["CELERY_BROKER_URL"]) celery.conf.update(app.config) class ContextTask(celery.Task): @@ -14,4 +11,4 @@ def make_celery(app): return self.run(*args, **kwargs) celery.Task = ContextTask - return celery \ No newline at end of file + return celery diff --git a/ogn_python/gateway/__init__.py b/app/gateway/__init__.py similarity index 100% rename from ogn_python/gateway/__init__.py rename to app/gateway/__init__.py diff --git a/app/gateway/bulkimport.py b/app/gateway/bulkimport.py new file mode 100644 index 0000000..fc5e098 --- /dev/null +++ b/app/gateway/bulkimport.py @@ -0,0 +1,407 @@ +from datetime import datetime, timedelta +from io import StringIO + +from flask.cli import AppGroup +import click +from tqdm import tqdm +from mgrs import MGRS + +from ogn.parser import parse, ParseError + +from app.model import AircraftBeacon, ReceiverBeacon, Location +from app.utils import open_file +from app.gateway.process_tools import * + +from app import db +from app import app + +user_cli = AppGroup("bulkimport") +user_cli.help = "Tools for accelerated data import." + + +# define message types we want to proceed +AIRCRAFT_BEACON_TYPES = ["aprs_aircraft", "flarm", "tracker", "fanet", "lt24", "naviter", "skylines", "spider", "spot"] +RECEIVER_BEACON_TYPES = ["aprs_receiver", "receiver"] + +# define fields we want to proceed +BEACON_KEY_FIELDS = ["name", "receiver_name", "timestamp"] +AIRCRAFT_BEACON_FIELDS = [ + "location", + "altitude", + "dstcall", + "relay", + "track", + "ground_speed", + "address_type", + "aircraft_type", + "stealth", + "address", + "climb_rate", + "turn_rate", + "signal_quality", + "error_count", + "frequency_offset", + "gps_quality_horizontal", + "gps_quality_vertical", + "software_version", + "hardware_version", + "real_address", + "signal_power", + "distance", + "radial", + "quality", + "location_mgrs", + "location_mgrs_short", + "agl", + "receiver_id", + "device_id", +] +RECEIVER_BEACON_FIELDS = [ + "location", + "altitude", + "dstcall", + "relay", + "version", + "platform", + "cpu_load", + "free_ram", + "total_ram", + "ntp_error", + "rt_crystal_correction", + "voltage", + "amperage", + "cpu_temp", + "senders_visible", + "senders_total", + "rec_input_noise", + "senders_signal", + "senders_messages", + "good_senders_signal", + "good_senders", + "good_and_bad_senders", +] + + +myMGRS = MGRS() + + +def string_to_message(raw_string, reference_date): + global receivers + + try: + message = parse(raw_string, reference_date) + except NotImplementedError as e: + app.logger.error("No parser implemented for message: {}".format(raw_string)) + return None + except ParseError as e: + app.logger.error("Parsing error with message: {}".format(raw_string)) + return None + except TypeError as e: + app.logger.error("TypeError with message: {}".format(raw_string)) + return None + except Exception as e: + app.logger.error("Other Exception with string: {}".format(raw_string)) + return None + + # update reference receivers and distance to the receiver + if message["aprs_type"] == "position": + if message["beacon_type"] in AIRCRAFT_BEACON_TYPES + RECEIVER_BEACON_TYPES: + latitude = message["latitude"] + longitude = message["longitude"] + + location = Location(longitude, latitude) + message["location"] = location.to_wkt() + location_mgrs = myMGRS.toMGRS(latitude, longitude).decode("utf-8") + message["location_mgrs"] = location_mgrs + message["location_mgrs_short"] = location_mgrs[0:5] + location_mgrs[5:7] + location_mgrs[10:12] + + if message["beacon_type"] in AIRCRAFT_BEACON_TYPES and "gps_quality" in message: + if message["gps_quality"] is not None and "horizontal" in message["gps_quality"]: + message["gps_quality_horizontal"] = message["gps_quality"]["horizontal"] + message["gps_quality_vertical"] = message["gps_quality"]["vertical"] + del message["gps_quality"] + + # TODO: Fix python-ogn-client 0.91 + if "senders_messages" in message and message["senders_messages"] is not None: + message["senders_messages"] = int(message["senders_messages"]) + if "good_senders" in message and message["good_senders"] is not None: + message["good_senders"] = int(message["good_senders"]) + if "good_and_bad_senders" in message and message["good_and_bad_senders"] is not None: + message["good_and_bad_senders"] = int(message["good_and_bad_senders"]) + + return message + + +class ContinuousDbFeeder: + def __init__(self,): + self.postfix = "continuous_import" + self.last_flush = datetime.utcnow() + self.last_add_missing = datetime.utcnow() + self.last_transfer = datetime.utcnow() + + self.aircraft_buffer = StringIO() + self.receiver_buffer = StringIO() + + create_tables(self.postfix) + create_indices(self.postfix) + + def add(self, raw_string): + message = string_to_message(raw_string, reference_date=datetime.utcnow()) + + if message is None or ("raw_message" in message and message["raw_message"][0] == "#") or "beacon_type" not in message: + return + + if message["beacon_type"] in AIRCRAFT_BEACON_TYPES: + complete_message = ",".join([str(message[k]) if k in message and message[k] is not None else "\\N" for k in BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS]) + self.aircraft_buffer.write(complete_message) + self.aircraft_buffer.write("\n") + elif message["beacon_type"] in RECEIVER_BEACON_TYPES: + complete_message = ",".join([str(message[k]) if k in message and message[k] is not None else "\\N" for k in BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS]) + self.receiver_buffer.write(complete_message) + self.receiver_buffer.write("\n") + else: + app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"])) + return + + if datetime.utcnow() - self.last_flush >= timedelta(seconds=20): + self.flush() + self.prepare() + + self.aircraft_buffer = StringIO() + self.receiver_buffer = StringIO() + + self.last_flush = datetime.utcnow() + + if datetime.utcnow() - self.last_add_missing >= timedelta(seconds=60): + self.add_missing() + self.last_add_missing = datetime.utcnow() + + if datetime.utcnow() - self.last_transfer >= timedelta(seconds=30): + self.transfer() + self.delete_beacons() + self.last_transfer = datetime.utcnow() + + def flush(self): + self.aircraft_buffer.seek(0) + self.receiver_buffer.seek(0) + + connection = db.engine.raw_connection() + cursor = connection.cursor() + cursor.copy_from(self.aircraft_buffer, "aircraft_beacons_{0}".format(self.postfix), sep=",", columns=BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS) + cursor.copy_from(self.receiver_buffer, "receiver_beacons_{0}".format(self.postfix), sep=",", columns=BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS) + connection.commit() + + self.aircraft_buffer = StringIO() + self.receiver_buffer = StringIO() + + def add_missing(self): + add_missing_receivers(self.postfix) + add_missing_devices(self.postfix) + + def prepare(self): + # make receivers complete + update_receiver_beacons(self.postfix) + update_receiver_location(self.postfix) + + # make devices complete + update_aircraft_beacons(self.postfix) + + def transfer(self): + # tranfer beacons + transfer_aircraft_beacons(self.postfix) + transfer_receiver_beacons(self.postfix) + + def delete_beacons(self): + # delete already transfered beacons + delete_receiver_beacons(self.postfix) + delete_aircraft_beacons(self.postfix) + + +class FileDbFeeder: + def __init__(self): + self.postfix = "continuous_import" + self.last_flush = datetime.utcnow() + + self.aircraft_buffer = StringIO() + self.receiver_buffer = StringIO() + + create_tables(self.postfix) + create_indices(self.postfix) + + def add(self, raw_string): + message = string_to_message(raw_string, reference_date=datetime.utcnow()) + + if message is None or ("raw_message" in message and message["raw_message"][0] == "#") or "beacon_type" not in message: + return + + if message["beacon_type"] in AIRCRAFT_BEACON_TYPES: + complete_message = ",".join([str(message[k]) if k in message and message[k] is not None else "\\N" for k in BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS]) + self.aircraft_buffer.write(complete_message) + self.aircraft_buffer.write("\n") + elif message["beacon_type"] in RECEIVER_BEACON_TYPES: + complete_message = ",".join([str(message[k]) if k in message and message[k] is not None else "\\N" for k in BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS]) + self.receiver_buffer.write(complete_message) + self.receiver_buffer.write("\n") + else: + app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"])) + return + + def prepare(self): + # make receivers complete + add_missing_receivers(self.postfix) + update_receiver_location(self.postfix) + + # make devices complete + add_missing_devices(self.postfix) + + # prepare beacons for transfer + create_indices(self.postfix) + update_receiver_beacons_bigdata(self.postfix) + update_aircraft_beacons_bigdata(self.postfix) + + +def get_aircraft_beacons_postfixes(): + """Get the postfixes from imported aircraft_beacons logs.""" + + postfixes = db.session.execute( + """ + SELECT DISTINCT(RIGHT(tablename, 8)) + FROM pg_catalog.pg_tables + WHERE schemaname = 'public' AND tablename LIKE 'aircraft\_beacons\_20______' + ORDER BY RIGHT(tablename, 10); + """ + ).fetchall() + + return [postfix for postfix in postfixes] + + +def export_to_path(postfix): + import os, gzip + + aircraft_beacons_file = os.path.join(path, "aircraft_beacons_{0}.csv.gz".format(postfix)) + with gzip.open(aircraft_beacons_file, "wt", encoding="utf-8") as gzip_file: + self.cur.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format(self.get_merged_aircraft_beacons_subquery()), gzip_file) + receiver_beacons_file = os.path.join(path, "receiver_beacons_{0}.csv.gz".format(postfix)) + with gzip.open(receiver_beacons_file, "wt") as gzip_file: + self.cur.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format(self.get_merged_receiver_beacons_subquery()), gzip_file) + + +def convert(sourcefile, datestr, saver): + from app.gateway.process import string_to_message + from app.gateway.process_tools import AIRCRAFT_BEACON_TYPES, RECEIVER_BEACON_TYPES + from datetime import datetime + + fin = open_file(sourcefile) + + # get total lines of the input file + total_lines = 0 + for line in fin: + total_lines += 1 + fin.seek(0) + + current_line = 0 + steps = 100000 + reference_date = datetime.strptime(datestr + " 12:00:00", "%Y-%m-%d %H:%M:%S") + + pbar = tqdm(fin, total=total_lines) + for line in pbar: + pbar.set_description("Importing {}".format(sourcefile)) + + current_line += 1 + if current_line % steps == 0: + saver.flush() + + message = string_to_message(line.strip(), reference_date=reference_date) + if message is None: + continue + + dictfilt = lambda x, y: dict([(i, x[i]) for i in x if i in set(y)]) + + try: + if message["beacon_type"] in AIRCRAFT_BEACON_TYPES: + message = dictfilt( + message, + ( + "beacon_type", + "aprs_type", + "location_wkt", + "altitude", + "name", + "dstcall", + "relay", + "receiver_name", + "timestamp", + "track", + "ground_speed", + "address_type", + "aircraft_type", + "stealth", + "address", + "climb_rate", + "turn_rate", + "signal_quality", + "error_count", + "frequency_offset", + "gps_quality_horizontal", + "gps_quality_vertical", + "software_version", + "hardware_version", + "real_address", + "signal_power", + "distance", + "radial", + "quality", + "agl", + "location_mgrs", + "location_mgrs_short", + "receiver_id", + "device_id", + ), + ) + + beacon = AircraftBeacon(**message) + elif message["beacon_type"] in RECEIVER_BEACON_TYPES: + if "rec_crystal_correction" in message: + del message["rec_crystal_correction"] + del message["rec_crystal_correction_fine"] + beacon = ReceiverBeacon(**message) + saver.add(beacon) + except Exception as e: + print(e) + + saver.flush() + fin.close() + + +@user_cli.command("file_import") +@click.argument("path") +def file_import(path): + """Import APRS logfiles into separate logfile tables.""" + + import os + import re + + # Get Filepaths and dates to import + results = list() + for (root, dirs, files) in os.walk(path): + for file in sorted(files): + match = re.match("OGN_log\.txt_([0-9]{4}\-[0-9]{2}\-[0-9]{2})\.gz$", file) + if match: + results.append({"filepath": os.path.join(root, file), "datestr": match.group(1)}) + + with LogfileDbSaver() as saver: + already_imported = saver.get_datestrs() + + results = list(filter(lambda x: x["datestr"] not in already_imported, results)) + + pbar = tqdm(results) + for result in pbar: + filepath = result["filepath"] + datestr = result["datestr"] + pbar.set_description("Importing data for {}".format(datestr)) + + saver.set_datestr(datestr) + saver.create_tables() + convert(filepath, datestr, saver) + saver.add_missing_devices() + saver.add_missing_receivers() diff --git a/ogn_python/gateway/process_tools.py b/app/gateway/process_tools.py similarity index 91% rename from ogn_python/gateway/process_tools.py rename to app/gateway/process_tools.py index 35f0367..4e9d927 100644 --- a/ogn_python/gateway/process_tools.py +++ b/app/gateway/process_tools.py @@ -1,4 +1,5 @@ -from ogn_python import db +from app import db + def create_tables(postfix): """Create tables for log file import.""" @@ -11,42 +12,55 @@ def create_tables(postfix): def create_indices(postfix): """Creates indices for aircraft- and receiver-beacons.""" - db.session.execute(""" + db.session.execute( + """ CREATE INDEX IF NOT EXISTS ix_aircraft_beacons_{0}_device_id ON "aircraft_beacons_{0}" (device_id NULLS FIRST); CREATE INDEX IF NOT EXISTS ix_aircraft_beacons_{0}_receiver_id ON "aircraft_beacons_{0}" (receiver_id NULLS FIRST); CREATE INDEX IF NOT EXISTS ix_aircraft_beacons_{0}_timestamp_name_receiver_name ON "aircraft_beacons_{0}" (timestamp, name, receiver_name); CREATE INDEX IF NOT EXISTS ix_receiver_beacons_{0}_timestamp_name_receiver_name ON "receiver_beacons_{0}" (timestamp, name, receiver_name); - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def create_indices_bigdata(postfix): """Creates indices for aircraft- and receiver-beacons.""" - db.session.execute(""" + db.session.execute( + """ CREATE INDEX IF NOT EXISTS ix_aircraft_beacons_{0}_timestamp_name_receiver_name ON "aircraft_beacons_{0}" (timestamp, name, receiver_name); CREATE INDEX IF NOT EXISTS ix_receiver_beacons_{0}_timestamp_name_receiver_name ON "receiver_beacons_{0}" (timestamp, name, receiver_name); - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def add_missing_devices(postfix): """Add missing devices.""" - db.session.execute(""" + db.session.execute( + """ INSERT INTO devices(address) SELECT DISTINCT (ab.address) FROM "aircraft_beacons_{0}" AS ab WHERE ab.address IS NOT NULL AND NOT EXISTS (SELECT 1 FROM devices AS d WHERE d.address = ab.address) ORDER BY ab.address; - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def add_missing_receivers(postfix): """Add missing receivers.""" - db.session.execute(""" + db.session.execute( + """ INSERT INTO receivers(name) SELECT DISTINCT (rb.name) FROM "receiver_beacons_{0}" AS rb @@ -58,14 +72,18 @@ def add_missing_receivers(postfix): FROM "aircraft_beacons_{0}" AS ab WHERE NOT EXISTS (SELECT 1 FROM receivers AS r WHERE r.name = ab.receiver_name) ORDER BY ab.receiver_name; - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def update_receiver_location(postfix): """Updates the receiver location. We need this because we want the actual location for distance calculations.""" - db.session.execute(""" + db.session.execute( + """ UPDATE receivers AS r SET location = sq.location, @@ -77,19 +95,26 @@ def update_receiver_location(postfix): ORDER BY rb.receiver_id, rb.timestamp ) AS sq WHERE r.id = sq.receiver_id; - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def update_receiver_beacons(postfix): """Updates the foreign keys.""" - db.session.execute(""" + db.session.execute( + """ UPDATE receiver_beacons_{0} AS rb SET receiver_id = r.id FROM receivers AS r WHERE rb.receiver_id IS NULL AND rb.name = r.name; - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() @@ -97,7 +122,8 @@ def update_receiver_beacons_bigdata(postfix): """Updates the foreign keys. Due to performance reasons we use a new table instead of updating the old.""" - db.session.execute(""" + db.session.execute( + """ SELECT rb.location, rb.altitude, rb.name, rb.receiver_name, rb.dstcall, rb.timestamp, @@ -112,7 +138,10 @@ def update_receiver_beacons_bigdata(postfix): DROP TABLE IF EXISTS "receiver_beacons_{0}"; ALTER TABLE "receiver_beacons_{0}_temp" RENAME TO "receiver_beacons_{0}"; - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() @@ -120,7 +149,8 @@ def update_aircraft_beacons(postfix): """Updates the foreign keys and calculates distance/radial and quality and computes the altitude above ground level. Elevation data has to be in the table 'elevation' with srid 4326.""" - db.session.execute(""" + db.session.execute( + """ UPDATE aircraft_beacons_{0} AS ab SET device_id = d.id, @@ -135,7 +165,10 @@ def update_aircraft_beacons(postfix): FROM devices AS d, receivers AS r, elevation AS e WHERE ab.device_id IS NULL and ab.receiver_id IS NULL AND ab.address = d.address AND ab.receiver_name = r.name AND ST_Intersects(e.rast, ab.location); - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() @@ -144,7 +177,8 @@ def update_aircraft_beacons_bigdata(postfix): Elevation data has to be in the table 'elevation' with srid 4326. Due to performance reasons we use a new table instead of updating the old.""" - db.session.execute(""" + db.session.execute( + """ SELECT ab.location, ab.altitude, ab.name, ab.dstcall, ab.relay, ab.receiver_name, ab.timestamp, ab.track, ab.ground_speed, @@ -170,14 +204,18 @@ def update_aircraft_beacons_bigdata(postfix): DROP TABLE IF EXISTS "aircraft_beacons_{0}"; ALTER TABLE "aircraft_beacons_{0}_temp" RENAME TO "aircraft_beacons_{0}"; - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def delete_receiver_beacons(postfix): """Delete beacons from table.""" - db.session.execute(""" + db.session.execute( + """ DELETE FROM receiver_beacons_continuous_import AS rb USING ( SELECT name, receiver_name, timestamp @@ -185,14 +223,18 @@ def delete_receiver_beacons(postfix): WHERE receiver_id IS NOT NULL ) AS sq WHERE rb.name = sq.name AND rb.receiver_name = sq.receiver_name AND rb.timestamp = sq.timestamp - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() def delete_aircraft_beacons(postfix): """Delete beacons from table.""" - db.session.execute(""" + db.session.execute( + """ DELETE FROM aircraft_beacons_continuous_import AS ab USING ( SELECT name, receiver_name, timestamp @@ -200,7 +242,10 @@ def delete_aircraft_beacons(postfix): WHERE receiver_id IS NOT NULL and device_id IS NOT NULL ) AS sq WHERE ab.name = sq.name AND ab.receiver_name = sq.receiver_name AND ab.timestamp = sq.timestamp - """.format(postfix)) + """.format( + postfix + ) + ) db.session.commit() @@ -247,7 +292,9 @@ def get_merged_aircraft_beacons_subquery(postfix): FROM "aircraft_beacons_{0}" AS ab GROUP BY timestamp, name, receiver_name ORDER BY timestamp, name, receiver_name - """.format(postfix) + """.format( + postfix + ) def get_merged_receiver_beacons_subquery(postfix): @@ -285,7 +332,9 @@ def get_merged_receiver_beacons_subquery(postfix): FROM "receiver_beacons_{0}" AS rb GROUP BY timestamp, name, receiver_name ORDER BY timestamp, name, receiver_name - """.format(postfix) + """.format( + postfix + ) def transfer_aircraft_beacons(postfix): @@ -298,7 +347,9 @@ def transfer_aircraft_beacons(postfix): FROM ({}) sq WHERE sq.receiver_id IS NOT NULL AND sq.device_id IS NOT NULL ON CONFLICT DO NOTHING; - """.format(get_merged_aircraft_beacons_subquery(postfix)) + """.format( + get_merged_aircraft_beacons_subquery(postfix) + ) db.session.execute(query) db.session.commit() @@ -317,7 +368,9 @@ def transfer_receiver_beacons(postfix): FROM ({}) sq WHERE sq.receiver_id IS NOT NULL ON CONFLICT DO NOTHING; - """.format(get_merged_receiver_beacons_subquery(postfix)) + """.format( + get_merged_receiver_beacons_subquery(postfix) + ) db.session.execute(query) db.session.commit() diff --git a/app/live_routes.py b/app/live_routes.py new file mode 100644 index 0000000..fad9d7a --- /dev/null +++ b/app/live_routes.py @@ -0,0 +1,90 @@ +from flask import request, render_template, make_response, send_file +from flask_cors import cross_origin + +from app.backend.liveglidernet import rec, lxml + +from app import app +from app import db +from app import cache + + +@app.route("/live.html") +@cross_origin() +def live(): + return render_template("ogn_live.html", host=request.host) + + +@app.route("/rec.php") +def rec_php(): + a = request.args.get("a") + z = request.args.get("z") + + xml = rec() + resp = app.make_response(xml) + resp.mimetype = "text/xml" + return resp + + +@app.route("/lxml.php") +def lxml_php(): + a = request.args.get("a") + b = request.args.get("b") + c = request.args.get("c") + d = request.args.get("d") + e = request.args.get("e") + z = request.args.get("z") + + xml = lxml() + resp = app.make_response(xml) + resp.mimetype = "text/xml" + return resp + + +@app.route("/pict/") +def pict(filename): + return app.send_static_file("ognlive/pict/" + filename) + + +@app.route("/favicon.gif") +def favicon_gif(): + return app.send_static_file("ognlive/pict/favicon.gif") + + +@app.route("/horizZoomControl.js") +def horizZoomControl_js(): + return app.send_static_file("ognlive/horizZoomControl.js") + + +@app.route("/barogram.js") +def barogram_js(): + return app.send_static_file("ognlive/barogram.js") + + +@app.route("/util.js") +def util_js(): + return app.send_static_file("ognlive/util.js") + + +@app.route("/ogn.js") +def ogn_js(): + return app.send_static_file("ognlive/ogn.js") + + +@app.route("/ol.js") +def ol_js(): + return app.send_static_file("ognlive/ol.js") + + +@app.route("/osm.js") +def osm_js(): + return app.send_static_file("ognlive/osm.js") + + +@app.route("/ol.css") +def ol_css(): + return app.send_static_file("ognlive/ol.css") + + +@app.route("/osm.css") +def osm_css(): + return app.send_static_file("ognlive/osm.css") diff --git a/ogn_python/model/__init__.py b/app/model/__init__.py similarity index 100% rename from ogn_python/model/__init__.py rename to app/model/__init__.py diff --git a/ogn_python/model/aircraft_beacon.py b/app/model/aircraft_beacon.py similarity index 58% rename from ogn_python/model/aircraft_beacon.py rename to app/model/aircraft_beacon.py index acf5858..313100f 100644 --- a/ogn_python/model/aircraft_beacon.py +++ b/app/model/aircraft_beacon.py @@ -1,7 +1,7 @@ from sqlalchemy.sql import func from .beacon import Beacon -from ogn_python import db +from app import db class AircraftBeacon(Beacon): @@ -28,21 +28,21 @@ class AircraftBeacon(Beacon): # Calculated values distance = db.Column(db.Float(precision=2)) radial = db.Column(db.SmallInteger) - quality = db.Column(db.Float(precision=2)) # signal quality normalized to 10km - location_mgrs = db.Column(db.String(15)) # full mgrs (15 chars) - location_mgrs_short = db.Column(db.String(9)) # reduced mgrs (9 chars), e.g. used for melissas range tool + quality = db.Column(db.Float(precision=2)) # signal quality normalized to 10km + location_mgrs = db.Column(db.String(15)) # full mgrs (15 chars) + location_mgrs_short = db.Column(db.String(9)) # reduced mgrs (9 chars), e.g. used for melissas range tool agl = db.Column(db.Float(precision=2)) # Relations - receiver_id = db.Column(db.Integer, db.ForeignKey('receivers.id', ondelete='SET NULL')) - receiver = db.relationship('Receiver', foreign_keys=[receiver_id], backref='aircraft_beacons') + receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="SET NULL")) + receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref="aircraft_beacons") - device_id = db.Column(db.Integer, db.ForeignKey('devices.id', ondelete='SET NULL')) - device = db.relationship('Device', foreign_keys=[device_id], backref='aircraft_beacons') + device_id = db.Column(db.Integer, db.ForeignKey("devices.id", ondelete="SET NULL")) + device = db.relationship("Device", foreign_keys=[device_id], backref="aircraft_beacons") # Multi-column indices - db.Index('ix_aircraft_beacons_receiver_id_distance', 'receiver_id', 'distance') - db.Index('ix_aircraft_beacons_device_id_timestamp', 'device_id', 'timestamp') + db.Index("ix_aircraft_beacons_receiver_id_distance", "receiver_id", "distance") + db.Index("ix_aircraft_beacons_device_id_timestamp", "device_id", "timestamp") def __repr__(self): return "" % ( @@ -61,49 +61,48 @@ class AircraftBeacon(Beacon): self.hardware_version, self.real_address, self.signal_power, - self.distance, self.radial, self.quality, self.location_mgrs, - self.location_mgrs_short) + self.location_mgrs_short, + ) @classmethod def get_columns(self): - return ['location', - 'altitude', - 'name', - 'dstcall', - 'relay', - 'receiver_name', - 'timestamp', - 'track', - 'ground_speed', - - #'raw_message', - #'reference_timestamp', - - 'address_type', - 'aircraft_type', - 'stealth', - 'address', - 'climb_rate', - 'turn_rate', - 'signal_quality', - 'error_count', - 'frequency_offset', - 'gps_quality_horizontal', - 'gps_quality_vertical', - 'software_version', - 'hardware_version', - 'real_address', - 'signal_power', - - 'distance', - 'radial', - 'quality', - 'location_mgrs', - 'location_mgrs_short'] + return [ + "location", + "altitude", + "name", + "dstcall", + "relay", + "receiver_name", + "timestamp", + "track", + "ground_speed", + #'raw_message', + #'reference_timestamp', + "address_type", + "aircraft_type", + "stealth", + "address", + "climb_rate", + "turn_rate", + "signal_quality", + "error_count", + "frequency_offset", + "gps_quality_horizontal", + "gps_quality_vertical", + "software_version", + "hardware_version", + "real_address", + "signal_power", + "distance", + "radial", + "quality", + "location_mgrs", + "location_mgrs_short", + ] def get_values(self): return [ @@ -116,10 +115,8 @@ class AircraftBeacon(Beacon): self.timestamp, self.track, self.ground_speed, - - #self.raw_message, - #self.reference_timestamp, - + # self.raw_message, + # self.reference_timestamp, self.address_type, self.aircraft_type, self.stealth, @@ -135,13 +132,13 @@ class AircraftBeacon(Beacon): self.hardware_version, self.real_address, self.signal_power, - self.distance, self.radial, self.quality, self.location_mgrs, - self.location_mgrs_short] + self.location_mgrs_short, + ] -db.Index('ix_aircraft_beacons_date_device_id_address', func.date(AircraftBeacon.timestamp), AircraftBeacon.device_id, AircraftBeacon.address) -db.Index('ix_aircraft_beacons_date_receiver_id_distance', func.date(AircraftBeacon.timestamp), AircraftBeacon.receiver_id, AircraftBeacon.distance) +db.Index("ix_aircraft_beacons_date_device_id_address", func.date(AircraftBeacon.timestamp), AircraftBeacon.device_id, AircraftBeacon.address) +db.Index("ix_aircraft_beacons_date_receiver_id_distance", func.date(AircraftBeacon.timestamp), AircraftBeacon.receiver_id, AircraftBeacon.distance) diff --git a/ogn_python/model/aircraft_type.py b/app/model/aircraft_type.py similarity index 100% rename from ogn_python/model/aircraft_type.py rename to app/model/aircraft_type.py diff --git a/ogn_python/model/airport.py b/app/model/airport.py similarity index 83% rename from ogn_python/model/airport.py rename to app/model/airport.py index 866fd61..31453df 100644 --- a/ogn_python/model/airport.py +++ b/app/model/airport.py @@ -1,6 +1,6 @@ from geoalchemy2.types import Geometry -from ogn_python import db +from app import db class Airport(db.Model): @@ -8,7 +8,7 @@ class Airport(db.Model): id = db.Column(db.Integer, primary_key=True) - location_wkt = db.Column('location', Geometry('POINT', srid=4326)) + location_wkt = db.Column("location", Geometry("POINT", srid=4326)) altitude = db.Column(db.Float(precision=2)) name = db.Column(db.String, index=True) @@ -20,7 +20,7 @@ class Airport(db.Model): runway_length = db.Column(db.SmallInteger) frequency = db.Column(db.Float(precision=2)) - border = db.Column('border', Geometry('POLYGON', srid=4326)) + border = db.Column("border", Geometry("POLYGON", srid=4326)) def __repr__(self): return "" % ( @@ -34,4 +34,5 @@ class Airport(db.Model): self.altitude, self.runway_direction, self.runway_length, - self.frequency) + self.frequency, + ) diff --git a/ogn_python/model/beacon.py b/app/model/beacon.py similarity index 93% rename from ogn_python/model/beacon.py rename to app/model/beacon.py index a394f8c..c23ebeb 100644 --- a/ogn_python/model/beacon.py +++ b/app/model/beacon.py @@ -5,12 +5,12 @@ from sqlalchemy.ext.hybrid import hybrid_property from .geo import Location -from ogn_python import db +from app import db class Beacon(AbstractConcreteBase, db.Model): # APRS data - location_wkt = db.Column('location', Geometry('POINT', srid=4326)) + location_wkt = db.Column("location", Geometry("POINT", srid=4326)) altitude = db.Column(db.Float(precision=2)) name = db.Column(db.String, primary_key=True, nullable=True) diff --git a/ogn_python/model/country.py b/app/model/country.py similarity index 64% rename from ogn_python/model/country.py rename to app/model/country.py index 2b16304..1fce5c4 100644 --- a/ogn_python/model/country.py +++ b/app/model/country.py @@ -1,6 +1,6 @@ from geoalchemy2.types import Geometry -from ogn_python import db +from app import db class Country(db.Model): @@ -21,18 +21,7 @@ class Country(db.Model): lon = db.Column(db.Float) lat = db.Column(db.Float) - geom = db.Column('geom', Geometry('MULTIPOLYGON', srid=4326)) + geom = db.Column("geom", Geometry("MULTIPOLYGON", srid=4326)) def __repr__(self): - return "" % ( - self.fips, - self.iso2, - self.iso3, - self.un, - self.name, - self.area, - self.pop2005, - self.region, - self.subregion, - self.lon, - self.lat) + return "" % (self.fips, self.iso2, self.iso3, self.un, self.name, self.area, self.pop2005, self.region, self.subregion, self.lon, self.lat) diff --git a/ogn_python/model/country_stats.py b/app/model/country_stats.py similarity index 51% rename from ogn_python/model/country_stats.py rename to app/model/country_stats.py index c9e3ece..9f5c9bc 100644 --- a/ogn_python/model/country_stats.py +++ b/app/model/country_stats.py @@ -1,4 +1,4 @@ -from ogn_python import db +from app import db class CountryStats(db.Model): @@ -13,6 +13,5 @@ class CountryStats(db.Model): device_count = db.Column(db.Integer) # Relations - country_id = db.Column(db.Integer, db.ForeignKey('countries.gid', ondelete='SET NULL'), index=True) - country = db.relationship('Country', foreign_keys=[country_id], backref=db.backref('stats', order_by='CountryStats.date.asc()')) - + country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="SET NULL"), index=True) + country = db.relationship("Country", foreign_keys=[country_id], backref=db.backref("stats", order_by="CountryStats.date.asc()")) diff --git a/ogn_python/model/device.py b/app/model/device.py similarity index 69% rename from ogn_python/model/device.py rename to app/model/device.py index e1317c8..7bcd098 100644 --- a/ogn_python/model/device.py +++ b/app/model/device.py @@ -2,17 +2,17 @@ import datetime from sqlalchemy.ext.hybrid import hybrid_property -from ogn_python import db +from app import db from .device_info import DeviceInfo class Device(db.Model): - __tablename__ = 'devices' + __tablename__ = "devices" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, index=True) - #address = db.Column(db.String(6), index=True) + # address = db.Column(db.String(6), index=True) address = db.Column(db.String, index=True) firstseen = db.Column(db.DateTime, index=True) lastseen = db.Column(db.DateTime, index=True) @@ -23,26 +23,16 @@ class Device(db.Model): real_address = db.Column(db.String(6)) def __repr__(self): - return "" % ( - self.address, - self.aircraft_type, - self.stealth, - self.software_version, - self.hardware_version, - self.real_address) + return "" % (self.address, self.aircraft_type, self.stealth, self.software_version, self.hardware_version, self.real_address) @hybrid_property def info(self): - query = db.session.query(DeviceInfo) \ - .filter(DeviceInfo.address == self.address) \ - .order_by(DeviceInfo.address_origin) + query = db.session.query(DeviceInfo).filter(DeviceInfo.address == self.address).order_by(DeviceInfo.address_origin) return query.first() def get_infos(self): - query = db.session.query(DeviceInfo) \ - .filter(DeviceInfo.address == self.address) \ - .order_by(DeviceInfo.address_origin) + query = db.session.query(DeviceInfo).filter(DeviceInfo.address == self.address).order_by(DeviceInfo.address_origin) return [info for info in query.all()] @@ -62,7 +52,7 @@ class Device(db.Model): } def expiry_date(self): - if self.name.startswith('FLR'): + if self.name.startswith("FLR"): if self.software_version in self.EXPIRY_DATES: return self.EXPIRY_DATES[self.software_version] else: diff --git a/ogn_python/model/device_info.py b/app/model/device_info.py similarity index 83% rename from ogn_python/model/device_info.py rename to app/model/device_info.py index 88ed4d9..e0d134a 100644 --- a/ogn_python/model/device_info.py +++ b/app/model/device_info.py @@ -1,12 +1,12 @@ -from ogn_python import db +from app import db class DeviceInfo(db.Model): - __tablename__ = 'device_infos' + __tablename__ = "device_infos" id = db.Column(db.Integer, primary_key=True) address_type = None - #address = db.Column(db.String(6), index=True) + # address = db.Column(db.String(6), index=True) address = db.Column(db.String, index=True) aircraft = db.Column(db.String) registration = db.Column(db.String(7)) @@ -27,4 +27,5 @@ class DeviceInfo(db.Model): self.tracked, self.identified, self.aircraft_type, - self.address_origin) + self.address_origin, + ) diff --git a/ogn_python/model/device_info_origin.py b/app/model/device_info_origin.py similarity index 64% rename from ogn_python/model/device_info_origin.py rename to app/model/device_info_origin.py index 9a3a565..db31572 100644 --- a/ogn_python/model/device_info_origin.py +++ b/app/model/device_info_origin.py @@ -8,15 +8,15 @@ class DeviceInfoOrigin: if origin in [0, 1, 2, 3]: self.origin = origin else: - raise ValueError('no address origin with id {} known'.format(origin)) + raise ValueError("no address origin with id {} known".format(origin)) def name(self): if self.origin == self.unknown: - return 'unknown' + return "unknown" elif self.origin == self.ogn_ddb: - return 'OGN-DDB' + return "OGN-DDB" elif self.origin == self.flarmnet: - return 'FlarmNet' + return "FlarmNet" elif self.origin == self.user_defined: - return 'user-defined' - return '' + return "user-defined" + return "" diff --git a/ogn_python/model/device_stats.py b/app/model/device_stats.py similarity index 74% rename from ogn_python/model/device_stats.py rename to app/model/device_stats.py index 8865a7d..5c3ac00 100644 --- a/ogn_python/model/device_stats.py +++ b/app/model/device_stats.py @@ -1,4 +1,4 @@ -from ogn_python import db +from app import db class DeviceStats(db.Model): @@ -40,15 +40,11 @@ class DeviceStats(db.Model): quality_ranking_country = db.Column(db.Integer) # Relations - device_id = db.Column(db.Integer, db.ForeignKey('devices.id', ondelete='SET NULL'), index=True) - device = db.relationship('Device', foreign_keys=[device_id], backref=db.backref('stats', order_by='DeviceStats.date.asc()')) + device_id = db.Column(db.Integer, db.ForeignKey("devices.id", ondelete="SET NULL"), index=True) + device = db.relationship("Device", foreign_keys=[device_id], backref=db.backref("stats", order_by="DeviceStats.date.asc()")) def __repr__(self): - return "" % ( - self.date, - self.receiver_count, - self.aircraft_beacon_count, - self.max_altitude) + return "" % (self.date, self.receiver_count, self.aircraft_beacon_count, self.max_altitude) -db.Index('ix_device_stats_date_device_id', DeviceStats.date, DeviceStats.device_id) +db.Index("ix_device_stats_date_device_id", DeviceStats.date, DeviceStats.device_id) diff --git a/app/model/flights2d.py b/app/model/flights2d.py new file mode 100644 index 0000000..8f47f06 --- /dev/null +++ b/app/model/flights2d.py @@ -0,0 +1,24 @@ +from geoalchemy2.types import Geometry + +from app import db + + +class Flight2D(db.Model): + __tablename__ = "flights2d" + + date = db.Column(db.Date, primary_key=True) + flight_type = db.Column(db.SmallInteger, primary_key=True) + + path_wkt = db.Column("path", Geometry("MULTILINESTRING", srid=4326)) + path_simple_wkt = db.Column("path_simple", Geometry("MULTILINESTRING", srid=4326)) # this is the path simplified with ST_Simplify(path, 0.0001) + + # Relations + device_id = db.Column(db.Integer, db.ForeignKey("devices.id", ondelete="SET NULL"), primary_key=True) + device = db.relationship("Device", foreign_keys=[device_id], backref="flights2d") + + def __repr__(self): + return "" % (self.date, self.path_wkt, self.path_simple_wkt) + + +db.Index("ix_flights2d_date_device_id", Flight2D.date, Flight2D.device_id) +# db.Index('ix_flights2d_date_path', Flight2D.date, Flight2D.path_wkt) --> CREATE INDEX ix_flights2d_date_path ON flights2d USING GIST("date", path) diff --git a/ogn_python/model/geo.py b/app/model/geo.py similarity index 59% rename from ogn_python/model/geo.py rename to app/model/geo.py index 69794d4..15fb615 100644 --- a/ogn_python/model/geo.py +++ b/app/model/geo.py @@ -6,10 +6,10 @@ class Location: self.latitude = lat def to_wkt(self): - return 'SRID=4326;POINT({0} {1})'.format(self.longitude, self.latitude) + return "SRID=4326;POINT({0} {1})".format(self.longitude, self.latitude) def __str__(self): - return '{0: 7.4f}, {1:8.4f}'.format(self.latitude, self.longitude) + return "{0: 7.4f}, {1:8.4f}".format(self.latitude, self.longitude) def as_dict(self): - return {'latitude': round(self.latitude, 8), 'longitude': round(self.longitude, 8)} + return {"latitude": round(self.latitude, 8), "longitude": round(self.longitude, 8)} diff --git a/ogn_python/model/logbook.py b/app/model/logbook.py similarity index 58% rename from ogn_python/model/logbook.py rename to app/model/logbook.py index 40eef16..ce1376d 100644 --- a/ogn_python/model/logbook.py +++ b/app/model/logbook.py @@ -1,10 +1,10 @@ from sqlalchemy.ext.hybrid import hybrid_property -from ogn_python import db +from app import db class Logbook(db.Model): - __tablename__ = 'logbook' + __tablename__ = "logbook" id = db.Column(db.Integer, primary_key=True) @@ -16,14 +16,14 @@ class Logbook(db.Model): max_altitude = db.Column(db.Float(precision=2)) # Relations - takeoff_airport_id = db.Column(db.Integer, db.ForeignKey('airports.id', ondelete='CASCADE'), index=True) - takeoff_airport = db.relationship('Airport', foreign_keys=[takeoff_airport_id]) + takeoff_airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True) + takeoff_airport = db.relationship("Airport", foreign_keys=[takeoff_airport_id]) - landing_airport_id = db.Column(db.Integer, db.ForeignKey('airports.id', ondelete='CASCADE'), index=True) - landing_airport = db.relationship('Airport', foreign_keys=[landing_airport_id]) + landing_airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True) + landing_airport = db.relationship("Airport", foreign_keys=[landing_airport_id]) - device_id = db.Column(db.Integer, db.ForeignKey('devices.id', ondelete='CASCADE'), index=True) - device = db.relationship('Device', foreign_keys=[device_id], backref=db.backref('logbook', order_by='Logbook.reftime')) + device_id = db.Column(db.Integer, db.ForeignKey("devices.id", ondelete="CASCADE"), index=True) + device = db.relationship("Device", foreign_keys=[device_id], backref=db.backref("logbook", order_by="Logbook.reftime")) @hybrid_property def duration(self): diff --git a/ogn_python/model/receiver.py b/app/model/receiver.py similarity index 68% rename from ogn_python/model/receiver.py rename to app/model/receiver.py index 97d5e71..cfae2a4 100644 --- a/ogn_python/model/receiver.py +++ b/app/model/receiver.py @@ -3,7 +3,7 @@ from geoalchemy2.types import Geometry from .geo import Location -from ogn_python import db +from app import db class Receiver(db.Model): @@ -11,7 +11,7 @@ class Receiver(db.Model): id = db.Column(db.Integer, primary_key=True) - location_wkt = db.Column('location', Geometry('POINT', srid=4326)) + location_wkt = db.Column("location", Geometry("POINT", srid=4326)) altitude = db.Column(db.Float(precision=2)) name = db.Column(db.String(9), index=True) @@ -21,8 +21,8 @@ class Receiver(db.Model): platform = db.Column(db.String) # Relations - country_id = db.Column(db.Integer, db.ForeignKey('countries.gid', ondelete='SET NULL'), index=True) - country = db.relationship('Country', foreign_keys=[country_id], backref=db.backref('receivers', order_by='Receiver.name.asc()')) + country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="SET NULL"), index=True) + country = db.relationship("Country", foreign_keys=[country_id], backref=db.backref("receivers", order_by="Receiver.name.asc()")) @property def location(self): diff --git a/ogn_python/model/receiver_beacon.py b/app/model/receiver_beacon.py similarity index 71% rename from ogn_python/model/receiver_beacon.py rename to app/model/receiver_beacon.py index e50fdb5..45f1901 100644 --- a/ogn_python/model/receiver_beacon.py +++ b/app/model/receiver_beacon.py @@ -1,7 +1,7 @@ from sqlalchemy.sql import func from .beacon import Beacon -from ogn_python import db +from app import db class ReceiverBeacon(Beacon): @@ -35,11 +35,11 @@ class ReceiverBeacon(Beacon): user_comment = None # Relations - receiver_id = db.Column(db.Integer, db.ForeignKey('receivers.id', ondelete='SET NULL')) - receiver = db.relationship('Receiver', foreign_keys=[receiver_id], backref='receiver_beacons') + receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="SET NULL")) + receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref="receiver_beacons") # Multi-column indices - db.Index('ix_receiver_beacons_receiver_id_name', 'receiver_id', 'name') + db.Index("ix_receiver_beacons_receiver_id_name", "receiver_id", "name") def __repr__(self): return "" % ( @@ -60,38 +60,39 @@ class ReceiverBeacon(Beacon): self.senders_messages, self.good_senders_signal, self.good_senders, - self.good_and_bad_senders) + self.good_and_bad_senders, + ) @classmethod def get_columns(self): - return ['location', - 'altitude', - 'name', - 'dstcall', - 'receiver_name', - 'timestamp', - - # 'raw_message', - # 'reference_timestamp', - - 'version', - 'platform', - 'cpu_load', - 'free_ram', - 'total_ram', - 'ntp_error', - 'rt_crystal_correction', - 'voltage', - 'amperage', - 'cpu_temp', - 'senders_visible', - 'senders_total', - 'rec_input_noise', - 'senders_signal', - 'senders_messages', - 'good_senders_signal', - 'good_senders', - 'good_and_bad_senders'] + return [ + "location", + "altitude", + "name", + "dstcall", + "receiver_name", + "timestamp", + # 'raw_message', + # 'reference_timestamp', + "version", + "platform", + "cpu_load", + "free_ram", + "total_ram", + "ntp_error", + "rt_crystal_correction", + "voltage", + "amperage", + "cpu_temp", + "senders_visible", + "senders_total", + "rec_input_noise", + "senders_signal", + "senders_messages", + "good_senders_signal", + "good_senders", + "good_and_bad_senders", + ] def get_values(self): return [ @@ -101,10 +102,8 @@ class ReceiverBeacon(Beacon): self.dstcall, self.receiver_name, self.timestamp, - # self.raw_message, # self.reference_timestamp, - self.version, self.platform, self.cpu_load, @@ -122,7 +121,8 @@ class ReceiverBeacon(Beacon): int(self.senders_messages) if self.senders_messages else None, self.good_senders_signal, int(self.good_senders) if self.good_senders else None, - int(self.good_and_bad_senders) if self.good_and_bad_senders else None] + int(self.good_and_bad_senders) if self.good_and_bad_senders else None, + ] -db.Index('ix_receiver_beacons_date_receiver_id', func.date(ReceiverBeacon.timestamp), ReceiverBeacon.receiver_id) +db.Index("ix_receiver_beacons_date_receiver_id", func.date(ReceiverBeacon.timestamp), ReceiverBeacon.receiver_id) diff --git a/ogn_python/model/receiver_coverage.py b/app/model/receiver_coverage.py similarity index 56% rename from ogn_python/model/receiver_coverage.py rename to app/model/receiver_coverage.py index 5883039..c0b543a 100644 --- a/ogn_python/model/receiver_coverage.py +++ b/app/model/receiver_coverage.py @@ -1,4 +1,4 @@ -from ogn_python import db +from app import db class ReceiverCoverage(db.Model): @@ -15,9 +15,9 @@ class ReceiverCoverage(db.Model): device_count = db.Column(db.SmallInteger) # Relations - receiver_id = db.Column(db.Integer, db.ForeignKey('receivers.id', ondelete='SET NULL'), primary_key=True) - receiver = db.relationship('Receiver', foreign_keys=[receiver_id], backref=db.backref('receiver_coverages', order_by='ReceiverCoverage.date.asc()')) + receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="SET NULL"), primary_key=True) + receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref=db.backref("receiver_coverages", order_by="ReceiverCoverage.date.asc()")) -db.Index('ix_receiver_coverages_date_receiver_id', ReceiverCoverage.date, ReceiverCoverage.receiver_id) -db.Index('ix_receiver_coverages_receiver_id_date', ReceiverCoverage.receiver_id, ReceiverCoverage.date) +db.Index("ix_receiver_coverages_date_receiver_id", ReceiverCoverage.date, ReceiverCoverage.receiver_id) +db.Index("ix_receiver_coverages_receiver_id_date", ReceiverCoverage.receiver_id, ReceiverCoverage.date) diff --git a/ogn_python/model/receiver_stats.py b/app/model/receiver_stats.py similarity index 71% rename from ogn_python/model/receiver_stats.py rename to app/model/receiver_stats.py index b66a795..6001075 100644 --- a/ogn_python/model/receiver_stats.py +++ b/app/model/receiver_stats.py @@ -1,6 +1,6 @@ from geoalchemy2.types import Geometry -from ogn_python import db +from app import db class ReceiverStats(db.Model): @@ -13,7 +13,7 @@ class ReceiverStats(db.Model): # Static data firstseen = db.Column(db.DateTime, index=True) lastseen = db.Column(db.DateTime, index=True) - location_wkt = db.Column('location', Geometry('POINT', srid=4326)) + location_wkt = db.Column("location", Geometry("POINT", srid=4326)) altitude = db.Column(db.Float(precision=2)) version = db.Column(db.String) platform = db.Column(db.String) @@ -34,8 +34,8 @@ class ReceiverStats(db.Model): quality_ranking = db.Column(db.Integer) # Relations - receiver_id = db.Column(db.Integer, db.ForeignKey('receivers.id', ondelete='SET NULL'), index=True) - receiver = db.relationship('Receiver', foreign_keys=[receiver_id], backref=db.backref('stats', order_by='ReceiverStats.date.asc()')) + receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="SET NULL"), index=True) + receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref=db.backref("stats", order_by="ReceiverStats.date.asc()")) -db.Index('ix_receiver_stats_date_receiver_id', ReceiverStats.date, ReceiverStats.receiver_id) +db.Index("ix_receiver_stats_date_receiver_id", ReceiverStats.date, ReceiverStats.receiver_id) diff --git a/app/model/relation_stats.py b/app/model/relation_stats.py new file mode 100644 index 0000000..2bddebb --- /dev/null +++ b/app/model/relation_stats.py @@ -0,0 +1,26 @@ +from app import db + + +class RelationStats(db.Model): + __tablename__ = "relation_stats" + + id = db.Column(db.Integer, primary_key=True) + + date = db.Column(db.Date) + + # Statistic data + quality = db.Column(db.Float(precision=2)) + beacon_count = db.Column(db.Integer) + + # Relations + device_id = db.Column(db.Integer, db.ForeignKey("devices.id", ondelete="SET NULL"), index=True) + device = db.relationship("Device", foreign_keys=[device_id], backref="relation_stats") + receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="SET NULL"), index=True) + receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref="relation_stats") + + def __repr__(self): + return "" % (self.date, self.quality, self.beacon_count) + + +db.Index("ix_relation_stats_date_device_id", RelationStats.date, RelationStats.device_id, RelationStats.receiver_id) +db.Index("ix_relation_stats_date_receiver_id", RelationStats.date, RelationStats.receiver_id, RelationStats.device_id) diff --git a/app/model/takeoff_landing.py b/app/model/takeoff_landing.py new file mode 100644 index 0000000..3bfa966 --- /dev/null +++ b/app/model/takeoff_landing.py @@ -0,0 +1,16 @@ +from app import db + + +class TakeoffLanding(db.Model): + __tablename__ = "takeoff_landings" + + device_id = db.Column(db.Integer, db.ForeignKey("devices.id", ondelete="SET NULL"), primary_key=True) + airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="SET NULL"), primary_key=True) + timestamp = db.Column(db.DateTime, primary_key=True) + + is_takeoff = db.Column(db.Boolean) + track = db.Column(db.SmallInteger) + + # Relations + airport = db.relationship("Airport", foreign_keys=[airport_id], backref="takeoff_landings") + device = db.relationship("Device", foreign_keys=[device_id], backref="takeoff_landings", order_by="TakeoffLanding.timestamp") diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..2256ec4 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,197 @@ +import datetime + +from flask import request, render_template, send_file + +from app import app +from app import db +from app import cache + +from app.model import * + + +@cache.cached(key_prefix="countries_in_receivers") +def get_countries_in_receivers(): + query = db.session.query(Country.iso2).filter(Country.gid == Receiver.country_id).order_by(Country.iso2).distinct(Country.iso2) + + return [{"iso2": country[0]} for country in query.all()] + + +@cache.cached(key_prefix="countries_in_logbook") +def get_countries_in_logbook(): + query = db.session.query(Country.iso2).filter(Country.iso2 == Airport.country_code).filter(Logbook.takeoff_airport_id == Airport.id).order_by(Country.iso2).distinct(Country.iso2) + + return [{"iso2": country[0]} for country in query.all()] + + +@cache.memoize() +def get_airports_in_country(sel_country): + query = db.session.query(Airport.id, Airport.name).filter(Airport.country_code == sel_country).filter(Logbook.takeoff_airport_id == Airport.id).order_by(Airport.name).distinct(Airport.name) + + return [{"id": airport[0], "name": airport[1]} for airport in query.all()] + + +@cache.memoize() +def get_dates_for_airport(sel_airport): + query = ( + db.session.query(db.func.date(Logbook.reftime), db.func.count(Logbook.id).label("logbook_count")) + .filter(Airport.id == sel_airport) + .filter(db.or_(Airport.id == Logbook.takeoff_airport_id, Airport.id == Logbook.landing_airport_id)) + .group_by(db.func.date(Logbook.reftime)) + .order_by(db.func.date(Logbook.reftime).desc()) + ) + + return [{"date": date, "logbook_count": logbook_count} for (date, logbook_count) in query.all()] + + +@app.route("/") +@app.route("/index.html") +def index(): + return render_template("base.html") + + +@app.route("/devices.html", methods=["GET", "POST"]) +def devices(): + devices = db.session.query(Device).order_by(Device.address).limit(100) + return render_template("devices.html", devices=devices) + + +@app.route("/device_detail.html", methods=["GET", "POST"]) +def device_detail(): + device_id = request.args.get("id") + device = db.session.query(Device).filter(Device.id == device_id).one() + + return render_template("device_detail.html", title="Device", device=device) + + +@app.route("/receivers.html") +def receivers(): + sel_country = request.args.get("country") + + countries = get_countries_in_receivers() + + # Get receiver selection list + if sel_country: + receivers = db.session.query(Receiver).filter(db.and_(Receiver.country_id == Country.gid, Country.iso2 == sel_country)).order_by(Receiver.name) + else: + receivers = db.session.query(Receiver).order_by(Receiver.name) + + return render_template("receivers.html", title="Receivers", sel_country=sel_country, countries=countries, receivers=receivers) + + +@app.route("/receiver_detail.html") +def receiver_detail(): + sel_receiver_id = request.args.get("receiver_id") + + receiver = db.session.query(Receiver).filter(Receiver.id == sel_receiver_id).one() + + airport = ( + db.session.query(Airport) + .filter( + db.and_( + Receiver.id == sel_receiver_id, + db.func.st_contains(db.func.st_buffer(Receiver.location_wkt, 0.5), Airport.location_wkt), + db.func.st_distance_sphere(Airport.location_wkt, Receiver.location_wkt) < 1000, + ) + ) + .filter(Airport.style.in_((2, 4, 5))) + ) + return render_template("receiver_detail.html", title="Receiver Detail", receiver=receiver, airport=airport.first()) + + +@app.route("/airports.html", methods=["GET", "POST"]) +def airports(): + sel_country = request.args.get("country") + + countries = get_countries_in_logbook() + + if sel_country: + airports = get_airports_in_country(sel_country) + else: + airports = [] + + page = request.args.get("page", 1, type=int) + + return render_template("airports.html", sel_country=sel_country, countries=countries, airports=airports) + + +@app.route("/airport_detail.html") +def airport_detail(): + sel_airport = request.args.get("airport") + + airport = db.session.query(Airport).filter(Airport.id == sel_airport) + + devices = db.session.query(Device).join(Logbook).filter(Logbook.takeoff_airport_id == sel_airport).order_by(Device.address) + + return render_template("airport_detail.html", title="Airport Detail", airport=airport.one(), devices=devices) + + +@app.route("/logbook.html", methods=["GET", "POST"]) +def logbook(): + sel_country = request.args.get("country") + sel_airport = request.args.get("airport") + sel_date = request.args.get("date") + + sel_device_id = request.args.get("device_id") + + countries = get_countries_in_logbook() + + if sel_country: + airports = get_airports_in_country(sel_country) + else: + airports = [] + + if sel_airport: + sel_airport = int(sel_airport) + if sel_airport not in [airport["id"] for airport in airports]: + sel_airport = None + sel_date = None + dates = get_dates_for_airport(sel_airport) + else: + dates = [] + + if sel_date: + sel_date = datetime.datetime.strptime(sel_date, "%Y-%m-%d").date() + if sel_date not in [entry["date"] for entry in dates]: + sel_date = dates[0]["date"] + elif len(dates) > 0: + sel_date = dates[0]["date"] + + # Get Logbook + filters = [] + if sel_airport: + filters.append(db.or_(Logbook.takeoff_airport_id == sel_airport, Logbook.landing_airport_id == sel_airport)) + + if sel_date: + filters.append(db.func.date(Logbook.reftime) == sel_date) + + if sel_device_id: + filters.append(Logbook.device_id == sel_device_id) + + if len(filters) > 0: + logbook = db.session.query(Logbook).filter(*filters).order_by(Logbook.reftime) + else: + logbook = None + + return render_template("logbook.html", title="Logbook", sel_country=sel_country, countries=countries, sel_airport=sel_airport, airports=airports, sel_date=sel_date, dates=dates, logbook=logbook) + + +@app.route("/download.html") +def download_flight(): + from io import StringIO + + buffer = StringIO() + buffer.write("Moin moin\nAlter Verwalter") + buffer.seek(0) + + return send_file(buffer, as_attachment=True, attachment_filename="wtf.igc", mimetype="text/plain") + + +@app.route("/statistics.html") +def statistics(): + + today = datetime.date.today() + today = datetime.date(2018, 7, 31) + + receiverstats = db.session.query(ReceiverStats).filter(ReceiverStats.date == today) + + return render_template("statistics.html", title="Receiver Statistics", receiverstats=receiverstats) diff --git a/ogn_python/static/css/flags/LICENSE b/app/static/css/flags/LICENSE similarity index 100% rename from ogn_python/static/css/flags/LICENSE rename to app/static/css/flags/LICENSE diff --git a/ogn_python/static/css/flags/flags.css b/app/static/css/flags/flags.css similarity index 100% rename from ogn_python/static/css/flags/flags.css rename to app/static/css/flags/flags.css diff --git a/ogn_python/static/css/flags/flags.png b/app/static/css/flags/flags.png similarity index 100% rename from ogn_python/static/css/flags/flags.png rename to app/static/css/flags/flags.png diff --git a/ogn_python/static/files/WineButton.png b/app/static/files/WineButton.png similarity index 100% rename from ogn_python/static/files/WineButton.png rename to app/static/files/WineButton.png diff --git a/ogn_python/static/files/bootstrap/LICENSE.md b/app/static/files/bootstrap/LICENSE.md similarity index 100% rename from ogn_python/static/files/bootstrap/LICENSE.md rename to app/static/files/bootstrap/LICENSE.md diff --git a/ogn_python/static/files/bootstrap/bootstrap.colorpickersliders.css b/app/static/files/bootstrap/bootstrap.colorpickersliders.css similarity index 100% rename from ogn_python/static/files/bootstrap/bootstrap.colorpickersliders.css rename to app/static/files/bootstrap/bootstrap.colorpickersliders.css diff --git a/ogn_python/static/files/bootstrap/bootstrap.colorpickersliders.js b/app/static/files/bootstrap/bootstrap.colorpickersliders.js similarity index 100% rename from ogn_python/static/files/bootstrap/bootstrap.colorpickersliders.js rename to app/static/files/bootstrap/bootstrap.colorpickersliders.js diff --git a/ogn_python/static/files/bootstrap/bootstrap.colorpickersliders.nocielch.js b/app/static/files/bootstrap/bootstrap.colorpickersliders.nocielch.js similarity index 100% rename from ogn_python/static/files/bootstrap/bootstrap.colorpickersliders.nocielch.js rename to app/static/files/bootstrap/bootstrap.colorpickersliders.nocielch.js diff --git a/ogn_python/static/files/bootstrap/typeahead.bundle.js b/app/static/files/bootstrap/typeahead.bundle.js similarity index 100% rename from ogn_python/static/files/bootstrap/typeahead.bundle.js rename to app/static/files/bootstrap/typeahead.bundle.js diff --git a/ogn_python/static/files/heatmap2.js b/app/static/files/heatmap2.js similarity index 100% rename from ogn_python/static/files/heatmap2.js rename to app/static/files/heatmap2.js diff --git a/ogn_python/static/files/maptiles2.js b/app/static/files/maptiles2.js similarity index 100% rename from ogn_python/static/files/maptiles2.js rename to app/static/files/maptiles2.js diff --git a/ogn_python/static/files/mgrs.min.js b/app/static/files/mgrs.min.js similarity index 100% rename from ogn_python/static/files/mgrs.min.js rename to app/static/files/mgrs.min.js diff --git a/ogn_python/static/files/ol/ol.css b/app/static/files/ol/ol.css similarity index 100% rename from ogn_python/static/files/ol/ol.css rename to app/static/files/ol/ol.css diff --git a/ogn_python/static/files/ol/ol.js b/app/static/files/ol/ol.js similarity index 100% rename from ogn_python/static/files/ol/ol.js rename to app/static/files/ol/ol.js diff --git a/ogn_python/static/files/style.css b/app/static/files/style.css similarity index 100% rename from ogn_python/static/files/style.css rename to app/static/files/style.css diff --git a/ogn_python/static/files/tinycolor.js b/app/static/files/tinycolor.js similarity index 100% rename from ogn_python/static/files/tinycolor.js rename to app/static/files/tinycolor.js diff --git a/ogn_python/static/files/url.js.sample b/app/static/files/url.js.sample similarity index 100% rename from ogn_python/static/files/url.js.sample rename to app/static/files/url.js.sample diff --git a/ogn_python/static/img/Blank.gif b/app/static/img/Blank.gif similarity index 100% rename from ogn_python/static/img/Blank.gif rename to app/static/img/Blank.gif diff --git a/ogn_python/static/img/Transparent.gif b/app/static/img/Transparent.gif similarity index 100% rename from ogn_python/static/img/Transparent.gif rename to app/static/img/Transparent.gif diff --git a/ogn_python/static/ognlive/barogram.js b/app/static/ognlive/barogram.js similarity index 100% rename from ogn_python/static/ognlive/barogram.js rename to app/static/ognlive/barogram.js diff --git a/ogn_python/static/ognlive/horizZoomControl.js b/app/static/ognlive/horizZoomControl.js similarity index 100% rename from ogn_python/static/ognlive/horizZoomControl.js rename to app/static/ognlive/horizZoomControl.js diff --git a/ogn_python/static/ognlive/ogn.js b/app/static/ognlive/ogn.js similarity index 100% rename from ogn_python/static/ognlive/ogn.js rename to app/static/ognlive/ogn.js diff --git a/ogn_python/static/ognlive/ol.css b/app/static/ognlive/ol.css similarity index 100% rename from ogn_python/static/ognlive/ol.css rename to app/static/ognlive/ol.css diff --git a/ogn_python/static/ognlive/ol.js b/app/static/ognlive/ol.js similarity index 100% rename from ogn_python/static/ognlive/ol.js rename to app/static/ognlive/ol.js diff --git a/ogn_python/static/ognlive/osm.css b/app/static/ognlive/osm.css similarity index 100% rename from ogn_python/static/ognlive/osm.css rename to app/static/ognlive/osm.css diff --git a/ogn_python/static/ognlive/pict/OGN.png b/app/static/ognlive/pict/OGN.png similarity index 100% rename from ogn_python/static/ognlive/pict/OGN.png rename to app/static/ognlive/pict/OGN.png diff --git a/ogn_python/static/ognlive/pict/OGN_b.png b/app/static/ognlive/pict/OGN_b.png similarity index 100% rename from ogn_python/static/ognlive/pict/OGN_b.png rename to app/static/ognlive/pict/OGN_b.png diff --git a/ogn_python/static/ognlive/pict/OGN_g.png b/app/static/ognlive/pict/OGN_g.png similarity index 100% rename from ogn_python/static/ognlive/pict/OGN_g.png rename to app/static/ognlive/pict/OGN_g.png diff --git a/ogn_python/static/ognlive/pict/OGN_o.png b/app/static/ognlive/pict/OGN_o.png similarity index 100% rename from ogn_python/static/ognlive/pict/OGN_o.png rename to app/static/ognlive/pict/OGN_o.png diff --git a/ogn_python/static/ognlive/pict/OGN_p.png b/app/static/ognlive/pict/OGN_p.png similarity index 100% rename from ogn_python/static/ognlive/pict/OGN_p.png rename to app/static/ognlive/pict/OGN_p.png diff --git a/ogn_python/static/ognlive/pict/OGN_r.png b/app/static/ognlive/pict/OGN_r.png similarity index 100% rename from ogn_python/static/ognlive/pict/OGN_r.png rename to app/static/ognlive/pict/OGN_r.png diff --git a/ogn_python/static/ognlive/pict/OpenPortGuideLogo_32.png b/app/static/ognlive/pict/OpenPortGuideLogo_32.png similarity index 100% rename from ogn_python/static/ognlive/pict/OpenPortGuideLogo_32.png rename to app/static/ognlive/pict/OpenPortGuideLogo_32.png diff --git a/ogn_python/static/ognlive/pict/a.gif b/app/static/ognlive/pict/a.gif similarity index 100% rename from ogn_python/static/ognlive/pict/a.gif rename to app/static/ognlive/pict/a.gif diff --git a/ogn_python/static/ognlive/pict/bin.gif b/app/static/ognlive/pict/bin.gif similarity index 100% rename from ogn_python/static/ognlive/pict/bin.gif rename to app/static/ognlive/pict/bin.gif diff --git a/ogn_python/static/ognlive/pict/c1.gif b/app/static/ognlive/pict/c1.gif similarity index 100% rename from ogn_python/static/ognlive/pict/c1.gif rename to app/static/ognlive/pict/c1.gif diff --git a/ogn_python/static/ognlive/pict/c2.gif b/app/static/ognlive/pict/c2.gif similarity index 100% rename from ogn_python/static/ognlive/pict/c2.gif rename to app/static/ognlive/pict/c2.gif diff --git a/ogn_python/static/ognlive/pict/c3.gif b/app/static/ognlive/pict/c3.gif similarity index 100% rename from ogn_python/static/ognlive/pict/c3.gif rename to app/static/ognlive/pict/c3.gif diff --git a/ogn_python/static/ognlive/pict/c4.gif b/app/static/ognlive/pict/c4.gif similarity index 100% rename from ogn_python/static/ognlive/pict/c4.gif rename to app/static/ognlive/pict/c4.gif diff --git a/ogn_python/static/ognlive/pict/c5.gif b/app/static/ognlive/pict/c5.gif similarity index 100% rename from ogn_python/static/ognlive/pict/c5.gif rename to app/static/ognlive/pict/c5.gif diff --git a/ogn_python/static/ognlive/pict/cancel-5.png b/app/static/ognlive/pict/cancel-5.png similarity index 100% rename from ogn_python/static/ognlive/pict/cancel-5.png rename to app/static/ognlive/pict/cancel-5.png diff --git a/ogn_python/static/ognlive/pict/close.png b/app/static/ognlive/pict/close.png similarity index 100% rename from ogn_python/static/ognlive/pict/close.png rename to app/static/ognlive/pict/close.png diff --git a/ogn_python/static/ognlive/pict/cordon.gif b/app/static/ognlive/pict/cordon.gif similarity index 100% rename from ogn_python/static/ognlive/pict/cordon.gif rename to app/static/ognlive/pict/cordon.gif diff --git a/ogn_python/static/ognlive/pict/dbarrow.gif b/app/static/ognlive/pict/dbarrow.gif similarity index 100% rename from ogn_python/static/ognlive/pict/dbarrow.gif rename to app/static/ognlive/pict/dbarrow.gif diff --git a/ogn_python/static/ognlive/pict/drapd.gif b/app/static/ognlive/pict/drapd.gif similarity index 100% rename from ogn_python/static/ognlive/pict/drapd.gif rename to app/static/ognlive/pict/drapd.gif diff --git a/ogn_python/static/ognlive/pict/drape.gif b/app/static/ognlive/pict/drape.gif similarity index 100% rename from ogn_python/static/ognlive/pict/drape.gif rename to app/static/ognlive/pict/drape.gif diff --git a/ogn_python/static/ognlive/pict/drapf.gif b/app/static/ognlive/pict/drapf.gif similarity index 100% rename from ogn_python/static/ognlive/pict/drapf.gif rename to app/static/ognlive/pict/drapf.gif diff --git a/ogn_python/static/ognlive/pict/drapi.gif b/app/static/ognlive/pict/drapi.gif similarity index 100% rename from ogn_python/static/ognlive/pict/drapi.gif rename to app/static/ognlive/pict/drapi.gif diff --git a/ogn_python/static/ognlive/pict/drapn.gif b/app/static/ognlive/pict/drapn.gif similarity index 100% rename from ogn_python/static/ognlive/pict/drapn.gif rename to app/static/ognlive/pict/drapn.gif diff --git a/ogn_python/static/ognlive/pict/draps.gif b/app/static/ognlive/pict/draps.gif similarity index 100% rename from ogn_python/static/ognlive/pict/draps.gif rename to app/static/ognlive/pict/draps.gif diff --git a/ogn_python/static/ognlive/pict/eye.gif b/app/static/ognlive/pict/eye.gif similarity index 100% rename from ogn_python/static/ognlive/pict/eye.gif rename to app/static/ognlive/pict/eye.gif diff --git a/ogn_python/static/ognlive/pict/favicon.gif b/app/static/ognlive/pict/favicon.gif similarity index 100% rename from ogn_python/static/ognlive/pict/favicon.gif rename to app/static/ognlive/pict/favicon.gif diff --git a/ogn_python/static/ognlive/pict/h1.gif b/app/static/ognlive/pict/h1.gif similarity index 100% rename from ogn_python/static/ognlive/pict/h1.gif rename to app/static/ognlive/pict/h1.gif diff --git a/ogn_python/static/ognlive/pict/h2.gif b/app/static/ognlive/pict/h2.gif similarity index 100% rename from ogn_python/static/ognlive/pict/h2.gif rename to app/static/ognlive/pict/h2.gif diff --git a/ogn_python/static/ognlive/pict/h3.gif b/app/static/ognlive/pict/h3.gif similarity index 100% rename from ogn_python/static/ognlive/pict/h3.gif rename to app/static/ognlive/pict/h3.gif diff --git a/ogn_python/static/ognlive/pict/hel.png b/app/static/ognlive/pict/hel.png similarity index 100% rename from ogn_python/static/ognlive/pict/hel.png rename to app/static/ognlive/pict/hel.png diff --git a/ogn_python/static/ognlive/pict/ico.gif b/app/static/ognlive/pict/ico.gif similarity index 100% rename from ogn_python/static/ognlive/pict/ico.gif rename to app/static/ognlive/pict/ico.gif diff --git a/ogn_python/static/ognlive/pict/ico.png b/app/static/ognlive/pict/ico.png similarity index 100% rename from ogn_python/static/ognlive/pict/ico.png rename to app/static/ognlive/pict/ico.png diff --git a/ogn_python/static/ognlive/pict/l1.gif b/app/static/ognlive/pict/l1.gif similarity index 100% rename from ogn_python/static/ognlive/pict/l1.gif rename to app/static/ognlive/pict/l1.gif diff --git a/ogn_python/static/ognlive/pict/left-3.png b/app/static/ognlive/pict/left-3.png similarity index 100% rename from ogn_python/static/ognlive/pict/left-3.png rename to app/static/ognlive/pict/left-3.png diff --git a/ogn_python/static/ognlive/pict/m.gif b/app/static/ognlive/pict/m.gif similarity index 100% rename from ogn_python/static/ognlive/pict/m.gif rename to app/static/ognlive/pict/m.gif diff --git a/ogn_python/static/ognlive/pict/min.png b/app/static/ognlive/pict/min.png similarity index 100% rename from ogn_python/static/ognlive/pict/min.png rename to app/static/ognlive/pict/min.png diff --git a/ogn_python/static/ognlive/pict/mm.gif b/app/static/ognlive/pict/mm.gif similarity index 100% rename from ogn_python/static/ognlive/pict/mm.gif rename to app/static/ognlive/pict/mm.gif diff --git a/ogn_python/static/ognlive/pict/mmm.gif b/app/static/ognlive/pict/mmm.gif similarity index 100% rename from ogn_python/static/ognlive/pict/mmm.gif rename to app/static/ognlive/pict/mmm.gif diff --git a/ogn_python/static/ognlive/pict/mod.gif b/app/static/ognlive/pict/mod.gif similarity index 100% rename from ogn_python/static/ognlive/pict/mod.gif rename to app/static/ognlive/pict/mod.gif diff --git a/ogn_python/static/ognlive/pict/n.gif b/app/static/ognlive/pict/n.gif similarity index 100% rename from ogn_python/static/ognlive/pict/n.gif rename to app/static/ognlive/pict/n.gif diff --git a/ogn_python/static/ognlive/pict/ogn-logo-ani.gif b/app/static/ognlive/pict/ogn-logo-ani.gif similarity index 100% rename from ogn_python/static/ognlive/pict/ogn-logo-ani.gif rename to app/static/ognlive/pict/ogn-logo-ani.gif diff --git a/ogn_python/static/ognlive/pict/p.gif b/app/static/ognlive/pict/p.gif similarity index 100% rename from ogn_python/static/ognlive/pict/p.gif rename to app/static/ognlive/pict/p.gif diff --git a/ogn_python/static/ognlive/pict/plu.png b/app/static/ognlive/pict/plu.png similarity index 100% rename from ogn_python/static/ognlive/pict/plu.png rename to app/static/ognlive/pict/plu.png diff --git a/ogn_python/static/ognlive/pict/pp.gif b/app/static/ognlive/pict/pp.gif similarity index 100% rename from ogn_python/static/ognlive/pict/pp.gif rename to app/static/ognlive/pict/pp.gif diff --git a/ogn_python/static/ognlive/pict/ppp.gif b/app/static/ognlive/pict/ppp.gif similarity index 100% rename from ogn_python/static/ognlive/pict/ppp.gif rename to app/static/ognlive/pict/ppp.gif diff --git a/ogn_python/static/ognlive/pict/rec.png b/app/static/ognlive/pict/rec.png similarity index 100% rename from ogn_python/static/ognlive/pict/rec.png rename to app/static/ognlive/pict/rec.png diff --git a/ogn_python/static/ognlive/pict/rec0.png b/app/static/ognlive/pict/rec0.png similarity index 100% rename from ogn_python/static/ognlive/pict/rec0.png rename to app/static/ognlive/pict/rec0.png diff --git a/ogn_python/static/ognlive/pict/rec1.png b/app/static/ognlive/pict/rec1.png similarity index 100% rename from ogn_python/static/ognlive/pict/rec1.png rename to app/static/ognlive/pict/rec1.png diff --git a/ogn_python/static/ognlive/pict/recy.png b/app/static/ognlive/pict/recy.png similarity index 100% rename from ogn_python/static/ognlive/pict/recy.png rename to app/static/ognlive/pict/recy.png diff --git a/ogn_python/static/ognlive/pict/redo-6.png b/app/static/ognlive/pict/redo-6.png similarity index 100% rename from ogn_python/static/ognlive/pict/redo-6.png rename to app/static/ognlive/pict/redo-6.png diff --git a/ogn_python/static/ognlive/pict/right-3.png b/app/static/ognlive/pict/right-3.png similarity index 100% rename from ogn_python/static/ognlive/pict/right-3.png rename to app/static/ognlive/pict/right-3.png diff --git a/ogn_python/static/ognlive/pict/tra.gif b/app/static/ognlive/pict/tra.gif similarity index 100% rename from ogn_python/static/ognlive/pict/tra.gif rename to app/static/ognlive/pict/tra.gif diff --git a/ogn_python/static/ognlive/pict/yn0.gif b/app/static/ognlive/pict/yn0.gif similarity index 100% rename from ogn_python/static/ognlive/pict/yn0.gif rename to app/static/ognlive/pict/yn0.gif diff --git a/ogn_python/static/ognlive/pict/yn1.gif b/app/static/ognlive/pict/yn1.gif similarity index 100% rename from ogn_python/static/ognlive/pict/yn1.gif rename to app/static/ognlive/pict/yn1.gif diff --git a/ogn_python/static/ognlive/pict/z.gif b/app/static/ognlive/pict/z.gif similarity index 100% rename from ogn_python/static/ognlive/pict/z.gif rename to app/static/ognlive/pict/z.gif diff --git a/ogn_python/static/ognlive/util.js b/app/static/ognlive/util.js similarity index 100% rename from ogn_python/static/ognlive/util.js rename to app/static/ognlive/util.js diff --git a/ogn_python/templates/airport_detail.html b/app/templates/airport_detail.html similarity index 100% rename from ogn_python/templates/airport_detail.html rename to app/templates/airport_detail.html diff --git a/ogn_python/templates/airports.html b/app/templates/airports.html similarity index 100% rename from ogn_python/templates/airports.html rename to app/templates/airports.html diff --git a/ogn_python/templates/base.html b/app/templates/base.html similarity index 72% rename from ogn_python/templates/base.html rename to app/templates/base.html index 6e9e273..afa2f28 100644 --- a/ogn_python/templates/base.html +++ b/app/templates/base.html @@ -19,12 +19,12 @@ @@ -50,7 +50,7 @@ {% block app_content %}{% endblock %} {%- block footer %} - +
© 2019 The OGN Team
{%- endblock footer %} {% endblock %} diff --git a/ogn_python/templates/device_detail.html b/app/templates/device_detail.html similarity index 100% rename from ogn_python/templates/device_detail.html rename to app/templates/device_detail.html diff --git a/ogn_python/templates/devices.html b/app/templates/devices.html similarity index 100% rename from ogn_python/templates/devices.html rename to app/templates/devices.html diff --git a/ogn_python/templates/logbook.html b/app/templates/logbook.html similarity index 100% rename from ogn_python/templates/logbook.html rename to app/templates/logbook.html diff --git a/ogn_python/templates/ogn_live.html b/app/templates/ogn_live.html similarity index 100% rename from ogn_python/templates/ogn_live.html rename to app/templates/ogn_live.html diff --git a/ogn_python/templates/ognrange.html b/app/templates/ognrange.html similarity index 100% rename from ogn_python/templates/ognrange.html rename to app/templates/ognrange.html diff --git a/ogn_python/templates/receiver_detail.html b/app/templates/receiver_detail.html similarity index 100% rename from ogn_python/templates/receiver_detail.html rename to app/templates/receiver_detail.html diff --git a/ogn_python/templates/receivers.html b/app/templates/receivers.html similarity index 100% rename from ogn_python/templates/receivers.html rename to app/templates/receivers.html diff --git a/ogn_python/templates/statistics.html b/app/templates/statistics.html similarity index 100% rename from ogn_python/templates/statistics.html rename to app/templates/statistics.html diff --git a/ogn_python/utils.py b/app/utils.py similarity index 62% rename from ogn_python/utils.py rename to app/utils.py index bd6f7e6..a96d33d 100644 --- a/ogn_python/utils.py +++ b/app/utils.py @@ -14,9 +14,7 @@ DDB_URL = "http://ddb.glidernet.org/download/?t=1" FLARMNET_URL = "http://www.flarmnet.org/files/data.fln" -address_prefixes = {'F': 'FLR', - 'O': 'OGN', - 'I': 'ICA'} +address_prefixes = {"F": "FLR", "O": "OGN", "I": "ICA"} nm2m = 1852 mi2m = 1609.34 @@ -36,10 +34,10 @@ def date_to_timestamps(date): def get_ddb(csv_file=None, address_origin=DeviceInfoOrigin.unknown): if csv_file is None: r = requests.get(DDB_URL) - rows = '\n'.join(i for i in r.text.splitlines() if i[0] != '#') + rows = "\n".join(i for i in r.text.splitlines() if i[0] != "#") else: - r = open(csv_file, 'r') - rows = ''.join(i for i in r.readlines() if i[0] != '#') + r = open(csv_file, "r") + rows = "".join(i for i in r.readlines() if i[0] != "#") data = csv.reader(StringIO(rows), quotechar="'", quoting=csv.QUOTE_ALL) @@ -51,8 +49,8 @@ def get_ddb(csv_file=None, address_origin=DeviceInfoOrigin.unknown): device_info.aircraft = row[2] device_info.registration = row[3] device_info.competition = row[4] - device_info.tracked = row[5] == 'Y' - device_info.identified = row[6] == 'Y' + device_info.tracked = row[5] == "Y" + device_info.identified = row[6] == "Y" device_info.aircraft_type = int(row[7]) device_info.address_origin = address_origin @@ -64,10 +62,10 @@ def get_ddb(csv_file=None, address_origin=DeviceInfoOrigin.unknown): def get_flarmnet(fln_file=None, address_origin=DeviceInfoOrigin.flarmnet): if fln_file is None: r = requests.get(FLARMNET_URL) - rows = [bytes.fromhex(line).decode('latin1') for line in r.text.split('\n') if len(line) == 172] + rows = [bytes.fromhex(line).decode("latin1") for line in r.text.split("\n") if len(line) == 172] else: - with open(fln_file, 'r') as file: - rows = [bytes.fromhex(line.strip()).decode('latin1') for line in file.readlines() if len(line) == 172] + with open(fln_file, "r") as file: + rows = [bytes.fromhex(line.strip()).decode("latin1") for line in file.readlines() if len(line) == 172] device_infos = list() for row in rows: @@ -96,43 +94,43 @@ def get_airports(cupfile): for line in f: try: for waypoint in Reader([line]): - if waypoint['style'] > 5: # reject unlandable places + if waypoint["style"] > 5: # reject unlandable places continue airport = Airport() - airport.name = waypoint['name'] - airport.code = waypoint['code'] - airport.country_code = waypoint['country'] - airport.style = waypoint['style'] - airport.description = waypoint['description'] - location = Location(waypoint['longitude'], waypoint['latitude']) + airport.name = waypoint["name"] + airport.code = waypoint["code"] + airport.country_code = waypoint["country"] + airport.style = waypoint["style"] + airport.description = waypoint["description"] + location = Location(waypoint["longitude"], waypoint["latitude"]) airport.location_wkt = location.to_wkt() - airport.altitude = waypoint['elevation']['value'] - if (waypoint['elevation']['unit'] == 'ft'): + airport.altitude = waypoint["elevation"]["value"] + if waypoint["elevation"]["unit"] == "ft": airport.altitude = airport.altitude * FEETS_TO_METER - airport.runway_direction = waypoint['runway_direction'] - airport.runway_length = waypoint['runway_length']['value'] - if (waypoint['runway_length']['unit'] == 'nm'): + airport.runway_direction = waypoint["runway_direction"] + airport.runway_length = waypoint["runway_length"]["value"] + if waypoint["runway_length"]["unit"] == "nm": airport.altitude = airport.altitude * nm2m - elif (waypoint['runway_length']['unit'] == 'ml'): + elif waypoint["runway_length"]["unit"] == "ml": airport.altitude = airport.altitude * mi2m - airport.frequency = waypoint['frequency'] + airport.frequency = waypoint["frequency"] airports.append(airport) except AttributeError as e: - print('Failed to parse line: {} {}'.format(line, e)) + print("Failed to parse line: {} {}".format(line, e)) return airports def open_file(filename): """Opens a regular or unzipped textfile for reading.""" - f = open(filename, 'rb') + f = open(filename, "rb") a = f.read(2) f.close() - if (a == b'\x1f\x8b'): - f = gzip.open(filename, 'rt', encoding="latin-1") + if a == b"\x1f\x8b": + f = gzip.open(filename, "rt", encoding="latin-1") return f else: - f = open(filename, 'rt', encoding="latin-1") + f = open(filename, "rt", encoding="latin-1") return f diff --git a/ogn_python.py b/ogn_python.py new file mode 100644 index 0000000..74929ed --- /dev/null +++ b/ogn_python.py @@ -0,0 +1,5 @@ +from app import app + + +if __name__ == '__main__': + app.run() diff --git a/ogn_python/app.py b/ogn_python/app.py deleted file mode 100644 index 92fb3a3..0000000 --- a/ogn_python/app.py +++ /dev/null @@ -1,6 +0,0 @@ -from ogn_python import app -from ogn_python import routes -from ogn_python import commands - -if __name__ == '__main__': - app.run() diff --git a/ogn_python/backend/liveglidernet.py b/ogn_python/backend/liveglidernet.py deleted file mode 100644 index 4d36552..0000000 --- a/ogn_python/backend/liveglidernet.py +++ /dev/null @@ -1,104 +0,0 @@ -from datetime import datetime, timedelta, timezone, date - -from ogn_python.model import AircraftBeacon, Device, Receiver - -from ogn_python import db -from ogn_python.model.receiver_beacon import ReceiverBeacon - - -def utc_to_local(utc_dt): - return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None) - - -def encode(address): - return 'xx' + address - - -def decode(code): - return code[2:9] - - -def rec(min_timestamp, min_online_timestamp): - last_seen_query = db.session.query(ReceiverBeacon) \ - .filter(ReceiverBeacon.timestamp > min_timestamp) \ - .order_by(ReceiverBeacon.receiver_id, ReceiverBeacon.timestamp) \ - .distinct(ReceiverBeacon.receiver_id) - - lines = [] - lines.append('') - lines.append('') - lines.append('') - for receiver_beacon in last_seen_query: - if receiver_beacon.location == None or receiver_beacon.name.startswith('FNB'): - continue - lines.append('' - .format(receiver_beacon.name, receiver_beacon.location.latitude, receiver_beacon.location.longitude, receiver_beacon.timestamp < min_online_timestamp)) - - lines.append('') - xml = '\n'.join(lines) - - return xml - - -def lxml(show_offline=False, lat_max=90, lat_min=-90, lon_max=180, lon_min=-180): - - timestamp_range_filter = [db.between(AircraftBeacon.timestamp, datetime(2018, 7, 31, 11, 55, 0), datetime(2018, 7, 31, 12, 5, 0))] - - last_seen_query = db.session.query(AircraftBeacon) \ - .filter(*timestamp_range_filter) \ - .order_by(AircraftBeacon.device_id, AircraftBeacon.timestamp) \ - .distinct(AircraftBeacon.device_id) \ - - lines = list() - lines.append('') - lines.append('') - - for aircraft_beacon in last_seen_query: - device = aircraft_beacon.device - - code = encode(device.address) - - if device.info: - if (not device.info.tracked or not device.info.identified): - continue - - if not device.info.competition: - competition = device.info.registration[-2:] - else: - competition = device.info.competition - - if not device.info.registration: - registration = '???' - else: - registration = device.info.registration - - address = device.address - - else: - competition = ('_' + code[-2:]).lower() - registration = code - address = 0 - - elapsed_time = datetime.utcnow() - aircraft_beacon.timestamp - elapsed_seconds = int(elapsed_time.total_seconds()) - - lines.append(' ' - .format(aircraft_beacon.location.latitude, - aircraft_beacon.location.longitude, - competition, - registration, - int(aircraft_beacon.altitude), - utc_to_local(aircraft_beacon.timestamp).strftime("%H:%M:%S"), - elapsed_seconds, - int(aircraft_beacon.track), - int(aircraft_beacon.ground_speed), - int(aircraft_beacon.climb_rate * 10) / 10, - aircraft_beacon.aircraft_type, - aircraft_beacon.receiver_name, - address, - code)) - - lines.append('') - xml = '\n'.join(lines) - - return xml \ No newline at end of file diff --git a/ogn_python/backend/ognrange.py b/ogn_python/backend/ognrange.py deleted file mode 100644 index 31038b7..0000000 --- a/ogn_python/backend/ognrange.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -from datetime import datetime, timedelta - -from sqlalchemy import func, case -from sqlalchemy.sql.expression import label -from ogn_python.model import Receiver, ReceiverCoverage - -from ogn_python import db - - -def alchemyencoder(obj): - """JSON encoder function for SQLAlchemy special classes.""" - - import decimal - from datetime import datetime - if isinstance(obj, datetime): - return obj.strftime('%Y-%m-%d %H:%M') - elif isinstance(obj, decimal.Decimal): - return float(obj) - - -def stations2_filtered_pl(start, end): - last_10_minutes = datetime.utcnow() - timedelta(minutes=10) - - query = db.session.query( - Receiver.name.label('s'), - label('lt', func.round(func.ST_Y(Receiver.location_wkt) * 10000) / 10000), - label('lg', func.round(func.ST_X(Receiver.location_wkt) * 10000) / 10000), - case([(Receiver.lastseen > last_10_minutes, "U")], - else_="D").label('u'), - Receiver.lastseen.label('ut'), - label('v', Receiver.version + '.' + Receiver.platform)) \ - .order_by(Receiver.lastseen) \ - .filter(db.or_(db.and_(start < Receiver.firstseen, end > Receiver.firstseen), - db.and_(start < Receiver.lastseen, end > Receiver.lastseen))) - - res = db.session.execute(query) - stations = json.dumps({'stations': [dict(r) for r in res]}, default=alchemyencoder) - - return stations - - -def max_tile_mgrs_pl(station, start, end, squares): - query = db.session.query( - func.right(ReceiverCoverage.location_mgrs_short, 4), - func.count(ReceiverCoverage.location_mgrs_short) - ) \ - .filter(db.and_(Receiver.id == ReceiverCoverage.receiver_id, Receiver.name == station)) \ - .filter(ReceiverCoverage.location_mgrs_short.like(squares + '%')) \ - .group_by(func.right(ReceiverCoverage.location_mgrs_short, 4)) - - res = {'t': squares, - 'p': ['{}/{}'.format(r[0], r[1]) for r in query.all()]} - return json.dumps(res) \ No newline at end of file diff --git a/ogn_python/collect/logbook.py b/ogn_python/collect/logbook.py deleted file mode 100644 index 70ae719..0000000 --- a/ogn_python/collect/logbook.py +++ /dev/null @@ -1,172 +0,0 @@ -from sqlalchemy import and_, or_, insert, update, exists, between -from sqlalchemy.sql import func, null -from sqlalchemy.sql.expression import true, false - -from ogn_python.model import TakeoffLanding, Logbook, AircraftBeacon -from ogn_python.utils import date_to_timestamps - -from ogn_python import app - - -def update_entries(session, date, logger=None): - """Add/update logbook entries.""" - - if logger is None: - logger = app.logger - - logger.info("Compute logbook.") - - # limit time range to given date and set window partition and window order - (start, end) = date_to_timestamps(date) - pa = (TakeoffLanding.device_id) - wo = and_(TakeoffLanding.device_id, - TakeoffLanding.airport_id, - TakeoffLanding.timestamp) - - # make a query with current, previous and next "takeoff_landing" event, so we can find complete flights - sq = session.query( - TakeoffLanding.device_id, - func.lag(TakeoffLanding.device_id).over(partition_by=pa, order_by=wo).label('device_id_prev'), - func.lead(TakeoffLanding.device_id).over(partition_by=pa, order_by=wo).label('device_id_next'), - TakeoffLanding.timestamp, - func.lag(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label('timestamp_prev'), - func.lead(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label('timestamp_next'), - TakeoffLanding.track, - func.lag(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label('track_prev'), - func.lead(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label('track_next'), - TakeoffLanding.is_takeoff, - func.lag(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label('is_takeoff_prev'), - func.lead(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label('is_takeoff_next'), - TakeoffLanding.airport_id, - func.lag(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label('airport_id_prev'), - func.lead(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label('airport_id_next')) \ - .filter(between(TakeoffLanding.timestamp, start, end)) \ - .subquery() - - # find complete flights - complete_flight_query = session.query( - sq.c.timestamp.label('reftime'), - sq.c.device_id.label('device_id'), - sq.c.timestamp.label('takeoff_timestamp'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'), - sq.c.timestamp_next.label('landing_timestamp'), sq.c.track_next.label('landing_track'), sq.c.airport_id_next.label('landing_airport_id')) \ - .filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) - - # find landings without start - only_landings_query = session.query( - sq.c.timestamp.label('reftime'), - sq.c.device_id.label('device_id'), - null().label('takeoff_timestamp'), null().label('takeoff_track'), null().label('takeoff_airport_id'), - sq.c.timestamp.label('landing_timestamp'), sq.c.track.label('landing_track'), sq.c.airport_id.label('landing_airport_id')) \ - .filter(sq.c.is_takeoff == false()) \ - .filter(or_(sq.c.is_takeoff_prev == false(), - sq.c.is_takeoff_prev == null())) - - # find starts without landing - only_starts_query = session.query( - sq.c.timestamp.label('reftime'), - sq.c.device_id.label('device_id'), - sq.c.timestamp.label('takeoff_timestamp'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'), - null().label('landing_timestamp'), null().label('landing_track'), null().label('landing_airport_id')) \ - .filter(sq.c.is_takeoff == true()) \ - .filter(or_(sq.c.is_takeoff_next == true(), - sq.c.is_takeoff_next == null())) - - # unite all computated flights - union_query = complete_flight_query.union( - only_landings_query, - only_starts_query) \ - .subquery() - - # if a logbook entry exist --> update it - upd = update(Logbook) \ - .where(and_(Logbook.device_id == union_query.c.device_id, - union_query.c.takeoff_airport_id != null(), - union_query.c.landing_airport_id != null(), - or_(and_(Logbook.takeoff_airport_id == union_query.c.takeoff_airport_id, - Logbook.takeoff_timestamp == union_query.c.takeoff_timestamp, - Logbook.landing_airport_id == null()), - and_(Logbook.takeoff_airport_id == null(), - Logbook.landing_airport_id == union_query.c.landing_airport_id, - Logbook.landing_timestamp == union_query.c.landing_timestamp)))) \ - .values({"reftime": union_query.c.reftime, - "takeoff_timestamp": union_query.c.takeoff_timestamp, - "takeoff_track": union_query.c.takeoff_track, - "takeoff_airport_id": union_query.c.takeoff_airport_id, - "landing_timestamp": union_query.c.landing_timestamp, - "landing_track": union_query.c.landing_track, - "landing_airport_id": union_query.c.landing_airport_id}) - - result = session.execute(upd) - update_counter = result.rowcount - session.commit() - logger.debug("Updated logbook entries: {}".format(update_counter)) - - # if a logbook entry doesnt exist --> insert it - new_logbook_entries = session.query(union_query) \ - .filter(~exists().where( - and_(Logbook.device_id == union_query.c.device_id, - or_(and_(Logbook.takeoff_airport_id == union_query.c.takeoff_airport_id, - Logbook.takeoff_timestamp == union_query.c.takeoff_timestamp), - and_(Logbook.takeoff_airport_id == null(), - union_query.c.takeoff_airport_id == null())), - or_(and_(Logbook.landing_airport_id == union_query.c.landing_airport_id, - Logbook.landing_timestamp == union_query.c.landing_timestamp), - and_(Logbook.landing_airport_id == null(), - union_query.c.landing_airport_id == null()))))) - - ins = insert(Logbook).from_select((Logbook.reftime, - Logbook.device_id, - Logbook.takeoff_timestamp, - Logbook.takeoff_track, - Logbook.takeoff_airport_id, - Logbook.landing_timestamp, - Logbook.landing_track, - Logbook.landing_airport_id), - new_logbook_entries) - - result = session.execute(ins) - insert_counter = result.rowcount - session.commit() - - finish_message = "Logbook: {} inserted, {} updated".format(insert_counter, update_counter) - logger.debug(finish_message) - return finish_message - - -def update_max_altitudes(session, date, logger=None): - """Add max altitudes in logbook when flight is complete (takeoff and landing).""" - - if logger is None: - logger = app.logger - - logger.info("Update logbook max altitude.") - - if session is None: - session = app.session - - (start, end) = date_to_timestamps(date) - - logbook_entries = session.query(Logbook.id) \ - .filter(and_(Logbook.takeoff_timestamp != null(), Logbook.landing_timestamp != null(), Logbook.max_altitude == null())) \ - .filter(between(Logbook.reftime, start, end)) \ - .subquery() - - max_altitudes = session.query(Logbook.id, func.max(AircraftBeacon.altitude).label('max_altitude')) \ - .filter(Logbook.id == logbook_entries.c.id) \ - .filter(and_(AircraftBeacon.device_id == Logbook.device_id, - AircraftBeacon.timestamp >= Logbook.takeoff_timestamp, - AircraftBeacon.timestamp <= Logbook.landing_timestamp)) \ - .group_by(Logbook.id) \ - .subquery() - - update_logbook = session.query(Logbook) \ - .filter(Logbook.id == max_altitudes.c.id) \ - .update({ - Logbook.max_altitude: max_altitudes.c.max_altitude}, - synchronize_session='fetch') - - session.commit() - - finish_message = "Logbook (altitude): {} entries updated.".format(update_logbook) - logger.info(finish_message) - return finish_message diff --git a/ogn_python/collect/ognrange.py b/ogn_python/collect/ognrange.py deleted file mode 100644 index ebddfd6..0000000 --- a/ogn_python/collect/ognrange.py +++ /dev/null @@ -1,86 +0,0 @@ -from sqlalchemy import Date -from sqlalchemy import and_, insert, update, exists, between -from sqlalchemy.sql import func, null - -from ogn_python.model import AircraftBeacon, ReceiverCoverage -from ogn_python.utils import date_to_timestamps - -from ogn_python import app - - -def update_entries(session, date, logger=None): - """Create receiver coverage stats for Melissas ognrange.""" - - if logger is None: - logger = app.logger - - logger.info("Compute receiver coverages.") - - (start, end) = date_to_timestamps(date) - - # Filter aircraft beacons - sq = session.query(AircraftBeacon.location_mgrs_short, - AircraftBeacon.receiver_id, - AircraftBeacon.signal_quality, - AircraftBeacon.altitude, - AircraftBeacon.device_id) \ - .filter(and_(between(AircraftBeacon.timestamp, start, end), - AircraftBeacon.location_mgrs_short != null(), - AircraftBeacon.receiver_id != null(), - AircraftBeacon.device_id != null())) \ - .subquery() - - # ... and group them by reduced MGRS, receiver and date - query = session.query(sq.c.location_mgrs_short, - sq.c.receiver_id, - func.cast(date, Date).label('date'), - func.max(sq.c.signal_quality).label('max_signal_quality'), - func.min(sq.c.altitude).label('min_altitude'), - func.max(sq.c.altitude).label('max_altitude'), - func.count(sq.c.altitude).label('aircraft_beacon_count'), - func.count(func.distinct(sq.c.device_id)).label('device_count')) \ - .group_by(sq.c.location_mgrs_short, - sq.c.receiver_id) \ - .subquery() - - # if a receiver coverage entry exist --> update it - upd = update(ReceiverCoverage) \ - .where(and_(ReceiverCoverage.location_mgrs_short == query.c.location_mgrs_short, - ReceiverCoverage.receiver_id == query.c.receiver_id, - ReceiverCoverage.date == date)) \ - .values({"max_signal_quality": query.c.max_signal_quality, - "min_altitude": query.c.min_altitude, - "max_altitude": query.c.max_altitude, - "aircraft_beacon_count": query.c.aircraft_beacon_count, - "device_count": query.c.device_count}) - - result = session.execute(upd) - update_counter = result.rowcount - session.commit() - logger.debug("Updated receiver coverage entries: {}".format(update_counter)) - - # if a receiver coverage entry doesnt exist --> insert it - new_coverage_entries = session.query(query) \ - .filter(~exists().where( - and_(ReceiverCoverage.location_mgrs_short == query.c.location_mgrs_short, - ReceiverCoverage.receiver_id == query.c.receiver_id, - ReceiverCoverage.date == date))) - - ins = insert(ReceiverCoverage).from_select(( - ReceiverCoverage.location_mgrs_short, - ReceiverCoverage.receiver_id, - ReceiverCoverage.date, - ReceiverCoverage.max_signal_quality, - ReceiverCoverage.min_altitude, - ReceiverCoverage.max_altitude, - ReceiverCoverage.aircraft_beacon_count, - ReceiverCoverage.device_count), - new_coverage_entries) - - result = session.execute(ins) - insert_counter = result.rowcount - session.commit() - - finish_message = "ReceiverCoverage: {} inserted, {} updated".format(insert_counter, update_counter) - logger.debug(finish_message) - return finish_message diff --git a/ogn_python/collect/stats.py b/ogn_python/collect/stats.py deleted file mode 100644 index 6782148..0000000 --- a/ogn_python/collect/stats.py +++ /dev/null @@ -1,470 +0,0 @@ -from sqlalchemy import insert, distinct, between, literal -from sqlalchemy.sql import null, and_, func, or_, update -from sqlalchemy.sql.expression import case - -from ogn_python.model import AircraftBeacon, DeviceStats, Country, CountryStats, ReceiverStats, RelationStats, Receiver, Device - -from ogn_python.model.receiver_beacon import ReceiverBeacon -from ogn_python.utils import date_to_timestamps - -from ogn_python import app - -# 40dB@10km is enough for 640km -MAX_PLAUSIBLE_QUALITY = 40 - - -def create_device_stats(session, date, logger=None): - """Add/update device stats.""" - - if logger is None: - logger = app.logger - - (start, end) = date_to_timestamps(date) - - # First kill the stats for the selected date - deleted_counter = session.query(DeviceStats) \ - .filter(DeviceStats.date == date) \ - .delete() - - # Since "distinct count" does not work in window functions we need a work-around for receiver counting - sq = session.query(AircraftBeacon, - func.dense_rank() - .over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.receiver_id) - .label('dr')) \ - .filter(and_(between(AircraftBeacon.timestamp, start, end), AircraftBeacon.device_id != null())) \ - .filter(or_(AircraftBeacon.error_count == 0, AircraftBeacon.error_count == null())) \ - .subquery() - - # Calculate stats, firstseen, lastseen and last values != NULL - device_stats = session.query( - distinct(sq.c.device_id).label('device_id'), - literal(date).label('date'), - func.max(sq.c.dr) - .over(partition_by=sq.c.device_id) - .label('receiver_count'), - func.max(sq.c.altitude) - .over(partition_by=sq.c.device_id) - .label('max_altitude'), - func.count(sq.c.device_id) - .over(partition_by=sq.c.device_id) - .label('aircraft_beacon_count'), - func.first_value(sq.c.name) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.name == null(), None)], else_=sq.c.timestamp).asc().nullslast()) - .label('name'), - func.first_value(sq.c.timestamp) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).asc().nullslast()) - .label('firstseen'), - func.first_value(sq.c.timestamp) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('lastseen'), - func.first_value(sq.c.aircraft_type) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.aircraft_type == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('aircraft_type'), - func.first_value(sq.c.stealth) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.stealth == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('stealth'), - func.first_value(sq.c.software_version) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.software_version == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('software_version'), - func.first_value(sq.c.hardware_version) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.hardware_version == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('hardware_version'), - func.first_value(sq.c.real_address) - .over(partition_by=sq.c.device_id, order_by=case([(sq.c.real_address == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('real_address')) \ - .subquery() - - # And insert them - ins = insert(DeviceStats).from_select( - [DeviceStats.device_id, DeviceStats.date, DeviceStats.receiver_count, DeviceStats.max_altitude, DeviceStats.aircraft_beacon_count, DeviceStats.name, - DeviceStats.firstseen, DeviceStats.lastseen, DeviceStats.aircraft_type, DeviceStats.stealth, - DeviceStats.software_version, DeviceStats.hardware_version, DeviceStats.real_address], - device_stats) - res = session.execute(ins) - insert_counter = res.rowcount - session.commit() - logger.debug("DeviceStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter)) - - return "DeviceStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter) - - -def create_receiver_stats(session, date, logger=None): - """Add/update receiver stats.""" - - if logger is None: - logger = app.logger - - (start, end) = date_to_timestamps(date) - - # First kill the stats for the selected date - deleted_counter = session.query(ReceiverStats) \ - .filter(ReceiverStats.date == date) \ - .delete() - - # Select one day - sq = session.query(ReceiverBeacon) \ - .filter(between(ReceiverBeacon.timestamp, start, end)) \ - .subquery() - - # Calculate stats, firstseen, lastseen and last values != NULL - receiver_stats = session.query( - distinct(sq.c.receiver_id).label('receiver_id'), - literal(date).label('date'), - func.first_value(sq.c.timestamp) - .over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).asc().nullslast()) - .label('firstseen'), - func.first_value(sq.c.timestamp) - .over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.timestamp == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('lastseen'), - func.first_value(sq.c.location) - .over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.location == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('location_wkt'), - func.first_value(sq.c.altitude) - .over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.altitude == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('altitude'), - func.first_value(sq.c.version) - .over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.version == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('version'), - func.first_value(sq.c.platform) - .over(partition_by=sq.c.receiver_id, order_by=case([(sq.c.platform == null(), None)], else_=sq.c.timestamp).desc().nullslast()) - .label('platform')) \ - .subquery() - - # And insert them - ins = insert(ReceiverStats).from_select( - [ReceiverStats.receiver_id, ReceiverStats.date, ReceiverStats.firstseen, ReceiverStats.lastseen, ReceiverStats.location_wkt, ReceiverStats.altitude, ReceiverStats.version, ReceiverStats.platform], - receiver_stats) - res = session.execute(ins) - insert_counter = res.rowcount - session.commit() - logger.warn("ReceiverStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter)) - - # Update aircraft_beacon_count, aircraft_count and max_distance - aircraft_beacon_stats = session.query(AircraftBeacon.receiver_id, - func.count(AircraftBeacon.timestamp).label('aircraft_beacon_count'), - func.count(func.distinct(AircraftBeacon.device_id)).label('aircraft_count'), - func.max(AircraftBeacon.distance).label('max_distance')) \ - .filter(and_(between(AircraftBeacon.timestamp, start, end), - AircraftBeacon.error_count == 0, - AircraftBeacon.quality <= MAX_PLAUSIBLE_QUALITY, - AircraftBeacon.relay == null())) \ - .group_by(AircraftBeacon.receiver_id) \ - .subquery() - - upd = update(ReceiverStats) \ - .where(and_(ReceiverStats.date == date, - ReceiverStats.receiver_id == aircraft_beacon_stats.c.receiver_id)) \ - .values({'aircraft_beacon_count': aircraft_beacon_stats.c.aircraft_beacon_count, - 'aircraft_count': aircraft_beacon_stats.c.aircraft_count, - 'max_distance': aircraft_beacon_stats.c.max_distance}) - - result = session.execute(upd) - update_counter = result.rowcount - session.commit() - logger.warn("Updated {} ReceiverStats".format(update_counter)) - - return "ReceiverStats for {}: {} deleted, {} inserted, {} updated".format(date, deleted_counter, insert_counter, update_counter) - - -def create_country_stats(session, date, logger=None): - if logger is None: - logger = app.logger - - (start, end) = date_to_timestamps(date) - - # First kill the stats for the selected date - deleted_counter = session.query(CountryStats) \ - .filter(CountryStats.date == date) \ - .delete() - - country_stats = session.query(literal(date), Country.gid, - func.count(AircraftBeacon.timestamp).label('aircraft_beacon_count'), \ - func.count(func.distinct(AircraftBeacon.receiver_id)).label('device_count')) \ - .filter(between(AircraftBeacon.timestamp, start, end)) \ - .filter(func.st_contains(Country.geom, AircraftBeacon.location)) \ - .group_by(Country.gid) \ - .subquery() - - # And insert them - ins = insert(CountryStats).from_select( - [CountryStats.date, CountryStats.country_id, CountryStats.aircraft_beacon_count, CountryStats.device_count], - country_stats) - res = session.execute(ins) - insert_counter = res.rowcount - session.commit() - - -def update_device_stats_jumps(session, date, logger=None): - """Update device stats jumps.""" - - if logger is None: - logger = app.logger - - (start, end) = date_to_timestamps(date) - - # speed limits in m/s (values above indicates a unplausible position / jump) - max_horizontal_speed = 1000 - max_vertical_speed = 100 - max_jumps = 10 # threshold for an 'ambiguous' device - - # find consecutive positions for a device - sq = session.query(AircraftBeacon.device_id, - AircraftBeacon.timestamp, - func.lead(AircraftBeacon.timestamp).over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.timestamp).label('timestamp_next'), - AircraftBeacon.location_wkt, - func.lead(AircraftBeacon.location_wkt).over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.timestamp).label('location_next'), - AircraftBeacon.altitude, - func.lead(AircraftBeacon.altitude).over(partition_by=AircraftBeacon.device_id, order_by=AircraftBeacon.timestamp).label('altitude_next')) \ - .filter(and_(between(AircraftBeacon.timestamp, start, end), - AircraftBeacon.error_count == 0)) \ - .subquery() - - # calc vertial and horizontal speed between points - sq2 = session.query(sq.c.device_id, - (func.st_distancesphere(sq.c.location_next, sq.c.location) / (func.extract('epoch', sq.c.timestamp_next) - func.extract('epoch', sq.c.timestamp))).label('horizontal_speed'), - ((sq.c.altitude_next - sq.c.altitude) / (func.extract('epoch', sq.c.timestamp_next) - func.extract('epoch', sq.c.timestamp))).label('vertical_speed')) \ - .filter(and_(sq.c.timestamp != null(), - sq.c.timestamp_next != null(), - sq.c.timestamp < sq.c.timestamp_next)) \ - .subquery() - - # ... and find and count 'jumps' - sq3 = session.query(sq2.c.device_id, - func.sum(case([(or_(func.abs(sq2.c.horizontal_speed) > max_horizontal_speed, func.abs(sq2.c.vertical_speed) > max_vertical_speed), 1)], else_=0)).label('jumps')) \ - .group_by(sq2.c.device_id) \ - .subquery() - - upd = update(DeviceStats) \ - .where(and_(DeviceStats.date == date, - DeviceStats.device_id == sq3.c.device_id)) \ - .values({'ambiguous': sq3.c.jumps > max_jumps, - 'jumps': sq3.c.jumps}) - - result = session.execute(upd) - update_counter = result.rowcount - session.commit() - logger.warn("Updated {} DeviceStats jumps".format(update_counter)) - - return "DeviceStats jumps for {}: {} updated".format(date, update_counter) - - -def create_relation_stats(session, date, logger=None): - """Add/update relation stats.""" - - if logger is None: - logger = app.logger - - (start, end) = date_to_timestamps(date) - - # First kill the stats for the selected date - deleted_counter = session.query(RelationStats) \ - .filter(RelationStats.date == date) \ - .delete() - - # Calculate stats for selected day - relation_stats = session.query( - literal(date), - AircraftBeacon.device_id, - AircraftBeacon.receiver_id, - func.max(AircraftBeacon.quality), - func.count(AircraftBeacon.timestamp) - ) \ - .filter(and_(between(AircraftBeacon.timestamp, start, end), - AircraftBeacon.distance > 1000, - AircraftBeacon.error_count == 0, - AircraftBeacon.quality <= MAX_PLAUSIBLE_QUALITY, - AircraftBeacon.ground_speed > 10)) \ - .group_by(literal(date), AircraftBeacon.device_id, AircraftBeacon.receiver_id) \ - .subquery() - - # And insert them - ins = insert(RelationStats).from_select( - [RelationStats.date, RelationStats.device_id, RelationStats.receiver_id, RelationStats.quality, RelationStats.beacon_count], - relation_stats) - res = session.execute(ins) - insert_counter = res.rowcount - session.commit() - logger.warn("RelationStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter)) - - return "RelationStats for {}: {} deleted, {} inserted".format(date, deleted_counter, insert_counter) - - -def update_qualities(session, date, logger=None): - """Calculate relative qualities of receivers and devices.""" - - if logger is None: - logger = app.logger - - # Calculate avg quality of devices - dev_sq = session.query(RelationStats.device_id, - func.avg(RelationStats.quality).label('quality')) \ - .filter(RelationStats.date == date) \ - .group_by(RelationStats.device_id) \ - .subquery() - - dev_upd = update(DeviceStats) \ - .where(and_(DeviceStats.date == date, - DeviceStats.device_id == dev_sq.c.device_id)) \ - .values({'quality': dev_sq.c.quality}) - - dev_result = session.execute(dev_upd) - dev_update_counter = dev_result.rowcount - session.commit() - logger.warn("Updated {} DeviceStats: quality".format(dev_update_counter)) - - # Calculate avg quality of receivers - rec_sq = session.query(RelationStats.receiver_id, - func.avg(RelationStats.quality).label('quality')) \ - .filter(RelationStats.date == date) \ - .group_by(RelationStats.receiver_id) \ - .subquery() - - rec_upd = update(ReceiverStats) \ - .where(and_(ReceiverStats.date == date, - ReceiverStats.receiver_id == rec_sq.c.receiver_id)) \ - .values({'quality': rec_sq.c.quality}) - - rec_result = session.execute(rec_upd) - rec_update_counter = rec_result.rowcount - session.commit() - logger.warn("Updated {} ReceiverStats: quality".format(rec_update_counter)) - - # Calculate quality_offset of devices - dev_sq = session.query(RelationStats.device_id, - (func.sum(RelationStats.beacon_count * (RelationStats.quality - ReceiverStats.quality)) / (func.sum(RelationStats.beacon_count))).label('quality_offset')) \ - .filter(RelationStats.date == date) \ - .filter(and_(RelationStats.receiver_id == ReceiverStats.receiver_id, - RelationStats.date == ReceiverStats.date)) \ - .group_by(RelationStats.device_id) \ - .subquery() - - dev_upd = update(DeviceStats) \ - .where(and_(DeviceStats.date == date, - DeviceStats.device_id == dev_sq.c.device_id)) \ - .values({'quality_offset': dev_sq.c.quality_offset}) - - dev_result = session.execute(dev_upd) - dev_update_counter = dev_result.rowcount - session.commit() - logger.warn("Updated {} DeviceStats: quality_offset".format(dev_update_counter)) - - # Calculate quality_offset of receivers - rec_sq = session.query(RelationStats.receiver_id, - (func.sum(RelationStats.beacon_count * (RelationStats.quality - DeviceStats.quality)) / (func.sum(RelationStats.beacon_count))).label('quality_offset')) \ - .filter(RelationStats.date == date) \ - .filter(and_(RelationStats.device_id == DeviceStats.device_id, - RelationStats.date == DeviceStats.date)) \ - .group_by(RelationStats.receiver_id) \ - .subquery() - - rec_upd = update(ReceiverStats) \ - .where(and_(ReceiverStats.date == date, - ReceiverStats.receiver_id == rec_sq.c.receiver_id)) \ - .values({'quality_offset': rec_sq.c.quality_offset}) - - rec_result = session.execute(rec_upd) - rec_update_counter = rec_result.rowcount - session.commit() - logger.warn("Updated {} ReceiverStats: quality_offset".format(rec_update_counter)) - - return "Updated {} DeviceStats and {} ReceiverStats".format(dev_update_counter, rec_update_counter) - - -def update_receivers(session, logger=None): - """Update receivers with stats.""" - - if logger is None: - logger = app.logger - - receiver_stats = session.query( - distinct(ReceiverStats.receiver_id).label('receiver_id'), - func.first_value(ReceiverStats.firstseen) - .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.firstseen == null(), None)], else_=ReceiverStats.date).asc().nullslast()) - .label('firstseen'), - func.first_value(ReceiverStats.lastseen) - .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.lastseen == null(), None)], else_=ReceiverStats.date).desc().nullslast()) - .label('lastseen'), - func.first_value(ReceiverStats.location_wkt) - .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.location_wkt == null(), None)], else_=ReceiverStats.date).desc().nullslast()) - .label('location_wkt'), - func.first_value(ReceiverStats.altitude) - .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.altitude == null(), None)], else_=ReceiverStats.date).desc().nullslast()) - .label('altitude'), - func.first_value(ReceiverStats.version) - .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.version == null(), None)], else_=ReceiverStats.date).desc().nullslast()) - .label('version'), - func.first_value(ReceiverStats.platform) - .over(partition_by=ReceiverStats.receiver_id, order_by=case([(ReceiverStats.platform == null(), None)], else_=ReceiverStats.date).desc().nullslast()) - .label('platform')) \ - .order_by(ReceiverStats.receiver_id) \ - .subquery() - - upd = update(Receiver) \ - .where(and_(Receiver.id == receiver_stats.c.receiver_id)) \ - .values({'firstseen': receiver_stats.c.firstseen, - 'lastseen': receiver_stats.c.lastseen, - 'location': receiver_stats.c.location_wkt, - 'altitude': receiver_stats.c.altitude, - 'version': receiver_stats.c.version, - 'platform': receiver_stats.c.platform}) - - result = session.execute(upd) - update_counter = result.rowcount - session.commit() - logger.warn("Updated {} Receivers".format(update_counter)) - - return "Updated {} Receivers".format(update_counter) - - -def update_devices(session, logger=None): - """Update devices with stats.""" - - if logger is None: - logger = app.logger - - device_stats = session.query( - distinct(DeviceStats.device_id).label('device_id'), - func.first_value(DeviceStats.name) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.name == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('name'), - func.first_value(DeviceStats.firstseen) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.firstseen == null(), None)], else_=DeviceStats.date).asc().nullslast()) - .label('firstseen'), - func.max(DeviceStats.lastseen) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.lastseen == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('lastseen'), - func.first_value(DeviceStats.aircraft_type) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.aircraft_type == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('aircraft_type'), - func.first_value(DeviceStats.stealth) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.stealth == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('stealth'), - func.first_value(DeviceStats.software_version) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.software_version == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('software_version'), - func.first_value(DeviceStats.hardware_version) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.hardware_version == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('hardware_version'), - func.first_value(DeviceStats.real_address) - .over(partition_by=DeviceStats.device_id, order_by=case([(DeviceStats.real_address == null(), None)], else_=DeviceStats.date).desc().nullslast()) - .label('real_address')) \ - .order_by(DeviceStats.device_id) \ - .subquery() - - upd = update(Device) \ - .where(and_(Device.id == device_stats.c.device_id)) \ - .values({'name': device_stats.c.name, - 'firstseen': device_stats.c.firstseen, - 'lastseen': device_stats.c.lastseen, - 'aircraft_type': device_stats.c.aircraft_type, - 'stealth': device_stats.c.stealth, - 'software_version': device_stats.c.software_version, - 'hardware_version': device_stats.c.hardware_version, - 'real_address': device_stats.c.real_address}) - - result = session.execute(upd) - update_counter = result.rowcount - session.commit() - logger.warn("Updated {} Devices".format(update_counter)) - - return "Updated {} Devices".format(update_counter) diff --git a/ogn_python/collect/takeoff_landings.py b/ogn_python/collect/takeoff_landings.py deleted file mode 100644 index b994f72..0000000 --- a/ogn_python/collect/takeoff_landings.py +++ /dev/null @@ -1,145 +0,0 @@ -from datetime import timedelta - -from sqlalchemy import and_, or_, insert, between, exists -from sqlalchemy.sql import func, null -from sqlalchemy.sql.expression import case - -from ogn_python.model import AircraftBeacon, TakeoffLanding, Airport - -from ogn_python import app - - -def update_entries(session, start, end, logger=None): - """Compute takeoffs and landings.""" - - if logger is None: - logger = app.logger - - logger.info("Compute takeoffs and landings.") - - # considered time interval should not exceed a complete day - if end - start > timedelta(days=1): - abort_message = "TakeoffLanding: timeinterval start='{}' and end='{}' is too big.".format(start, end) - logger.warn(abort_message) - return abort_message - - # check if we have any airport - airports_query = session.query(Airport).limit(1) - if not airports_query.all(): - abort_message = "TakeoffLanding: Cannot calculate takeoff and landings without any airport! Please import airports first." - logger.warn(abort_message) - return abort_message - - # takeoff / landing detection is based on 3 consecutive points all below a certain altitude AGL - takeoff_speed = 55 # takeoff detection: 1st point below, 2nd and 3rd above this limit - landing_speed = 40 # landing detection: 1st point above, 2nd and 3rd below this limit - min_takeoff_climb_rate = -5 # takeoff detection: glider should not sink too much - max_landing_climb_rate = 5 # landing detection: glider should not climb too much - duration = 100 # the points must not exceed this duration - radius = 5000 # the points must not exceed this radius around the 2nd point - max_agl = 200 # takeoff / landing must not exceed this altitude AGL - - # get beacons for selected time range, one per device_id and timestamp - sq = session.query(AircraftBeacon) \ - .distinct(AircraftBeacon.device_id, AircraftBeacon.timestamp) \ - .order_by(AircraftBeacon.device_id, AircraftBeacon.timestamp, AircraftBeacon.error_count) \ - .filter(AircraftBeacon.agl < max_agl) \ - .filter(between(AircraftBeacon.timestamp, start, end)) \ - .subquery() - - # make a query with current, previous and next position - sq2 = session.query( - sq.c.device_id, - func.lag(sq.c.device_id).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('device_id_prev'), - func.lead(sq.c.device_id).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('device_id_next'), - sq.c.timestamp, - func.lag(sq.c.timestamp).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('timestamp_prev'), - func.lead(sq.c.timestamp).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('timestamp_next'), - sq.c.location, - func.lag(sq.c.location).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('location_wkt_prev'), - func.lead(sq.c.location).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('location_wkt_next'), - sq.c.track, - func.lag(sq.c.track).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('track_prev'), - func.lead(sq.c.track).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('track_next'), - sq.c.ground_speed, - func.lag(sq.c.ground_speed).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('ground_speed_prev'), - func.lead(sq.c.ground_speed).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('ground_speed_next'), - sq.c.altitude, - func.lag(sq.c.altitude).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('altitude_prev'), - func.lead(sq.c.altitude).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('altitude_next'), - sq.c.climb_rate, - func.lag(sq.c.climb_rate).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('climb_rate_prev'), - func.lead(sq.c.climb_rate).over(partition_by=sq.c.device_id, order_by=sq.c.timestamp).label('climb_rate_next')) \ - .subquery() - - # consider only positions with predecessor and successor and limit distance and duration between points - sq3 = session.query(sq2) \ - .filter(and_(sq2.c.device_id_prev != null(), - sq2.c.device_id_next != null())) \ - .filter(and_(func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_prev) < radius, - func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_next) < radius)) \ - .filter(sq2.c.timestamp_next - sq2.c.timestamp_prev < timedelta(seconds=duration)) \ - .subquery() - - # find possible takeoffs and landings - sq4 = session.query( - sq3.c.timestamp, - case([(sq3.c.ground_speed > takeoff_speed, sq3.c.location_wkt_prev), # on takeoff we take the location from the previous fix because it is nearer to the airport - (sq3.c.ground_speed <= takeoff_speed, sq3.c.location)]).label('location'), - case([(sq3.c.ground_speed > landing_speed, sq3.c.track), - (sq3.c.ground_speed <= landing_speed, sq3.c.track_prev)]).label('track'), # on landing we take the track from the previous fix because gliders tend to leave the runway quickly - sq3.c.ground_speed, - sq3.c.altitude, - case([(sq3.c.ground_speed > takeoff_speed, True), - (sq3.c.ground_speed < landing_speed, False)]).label('is_takeoff'), - sq3.c.device_id) \ - .filter(or_(and_(sq3.c.ground_speed_prev < takeoff_speed, # takeoff - sq3.c.ground_speed > takeoff_speed, - sq3.c.ground_speed_next > takeoff_speed, - sq3.c.climb_rate > min_takeoff_climb_rate), - and_(sq3.c.ground_speed_prev > landing_speed, # landing - sq3.c.ground_speed < landing_speed, - sq3.c.ground_speed_next < landing_speed, - sq3.c.climb_rate < max_landing_climb_rate))) \ - .subquery() - - # consider them if the are near airports ... - sq5 = session.query( - sq4.c.timestamp, - sq4.c.track, - sq4.c.is_takeoff, - sq4.c.device_id, - Airport.id.label('airport_id'), - func.ST_DistanceSphere(sq4.c.location, Airport.location_wkt).label('airport_distance')) \ - .filter(and_(func.ST_Within(sq4.c.location, Airport.border), - between(Airport.style, 2, 5))) \ - .subquery() - - # ... and take the nearest airport - sq6 = session.query(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_id) \ - .distinct(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id) \ - .order_by(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.device_id, sq5.c.airport_distance) \ - .subquery() - - # consider them only if they are not already existing in db - takeoff_landing_query = session.query(sq6) \ - .filter(~exists().where( - and_(TakeoffLanding.timestamp == sq6.c.timestamp, - TakeoffLanding.device_id == sq6.c.device_id, - TakeoffLanding.airport_id == sq6.c.airport_id))) - - # ... and save them - ins = insert(TakeoffLanding).from_select((TakeoffLanding.timestamp, - TakeoffLanding.track, - TakeoffLanding.is_takeoff, - TakeoffLanding.device_id, - TakeoffLanding.airport_id), - takeoff_landing_query) - - result = session.execute(ins) - session.commit() - insert_counter = result.rowcount - - finish_message = "TakeoffLandings: {} inserted".format(insert_counter) - logger.info(finish_message) - return finish_message diff --git a/ogn_python/commands/logbook.py b/ogn_python/commands/logbook.py deleted file mode 100644 index cef50ef..0000000 --- a/ogn_python/commands/logbook.py +++ /dev/null @@ -1,117 +0,0 @@ -from flask.cli import AppGroup -import click - -from datetime import datetime - -from ogn_python.collect.logbook import update_entries as logbook_update_entries -from ogn_python.collect.takeoff_landings import update_entries as takeoff_landings_update_entries -from ogn_python.model import Airport, Logbook -from sqlalchemy.sql import func -from tqdm import tqdm -from ogn_python.commands.database import get_database_days -from ogn_python.utils import date_to_timestamps - -from ogn_python import db - -user_cli = AppGroup('logbook') -user_cli.help = "Handling of logbook data." - - -@user_cli.command('compute_takeoff_landing') -@click.argument('start') -@click.argument('end') -def compute_takeoff_landing(start, end): - """Compute takeoffs and landings.""" - - days = get_database_days(start, end) - - pbar = tqdm(days) - for single_date in pbar: - pbar.set_description(datetime.strftime(single_date, '%Y-%m-%d')) - (start, end) = date_to_timestamps(single_date) - result = takeoff_landings_update_entries(session=db.session, start=start, end=end) - - -@user_cli.command('compute_logbook') -@click.argument('start') -@click.argument('end') -def compute_logbook(start, end): - """Compute logbook.""" - - days = get_database_days(start, end) - - pbar = tqdm(days) - for single_date in pbar: - pbar.set_description(single_date.strftime('%Y-%m-%d')) - result = logbook_update_entries(session=db.session, date=single_date) - - -@user_cli.command('show') -@click.argument('airport_name') -@click.argument('date') -def show(airport_name, date=None): - """Show a logbook for .""" - airport = db.session.query(Airport) \ - .filter(Airport.name == airport_name) \ - .first() - - if (airport is None): - print('Airport "{}" not found.'.format(airport_name)) - return - - or_args = [] - if date is not None: - date = datetime.strptime(date, "%Y-%m-%d") - (start, end) = date_to_timestamps(date) - or_args = [db.between(Logbook.reftime, start, end)] - - # get all logbook entries and add device and airport infos - logbook_query = db.session.query(func.row_number().over(order_by=Logbook.reftime).label('row_number'), - Logbook) \ - .filter(*or_args) \ - .filter(db.or_(Logbook.takeoff_airport_id == airport.id, - Logbook.landing_airport_id == airport.id)) \ - .order_by(Logbook.reftime) - - # ... and finally print out the logbook - print('--- Logbook ({}) ---'.format(airport_name)) - - def none_datetime_replacer(datetime_object): - return '--:--:--' if datetime_object is None else datetime_object.time() - - def none_track_replacer(track_object): - return '--' if track_object is None else round(track_object / 10.0) - - def none_timedelta_replacer(timedelta_object): - return '--:--:--' if timedelta_object is None else timedelta_object - - def none_registration_replacer(device_object): - return '[' + device_object.address + ']' if len(device_object.infos) == 0 else device_object.infos[0].registration - - def none_aircraft_replacer(device_object): - return '(unknown)' if len(device_object.infos) == 0 else device_object.infos[0].aircraft - - def airport_marker(logbook_object): - if logbook_object.takeoff_airport is not None and logbook_object.takeoff_airport.name is not airport.name: - return ('FROM: {}'.format(logbook_object.takeoff_airport.name)) - elif logbook_object.landing_airport is not None and logbook_object.landing_airport.name is not airport.name: - return ('TO: {}'.format(logbook_object.landing_airport.name)) - else: - return ('') - - def none_altitude_replacer(logbook_object): - return "?" if logbook_object.max_altitude is None else "{:5d}m ({:+5d}m)".format(logbook_object.max_altitude, logbook_object.max_altitude - logbook_object.takeoff_airport.altitude) - - for [row_number, logbook] in logbook_query.all(): - print('%3d. %10s %8s (%2s) %8s (%2s) %8s %15s %8s %17s %20s' % ( - row_number, - logbook.reftime.date(), - none_datetime_replacer(logbook.takeoff_timestamp), - none_track_replacer(logbook.takeoff_track), - none_datetime_replacer(logbook.landing_timestamp), - none_track_replacer(logbook.landing_track), - none_timedelta_replacer(logbook.duration), - none_altitude_replacer(logbook), - none_registration_replacer(logbook.device), - none_aircraft_replacer(logbook.device), - airport_marker(logbook))) diff --git a/ogn_python/config/default.py b/ogn_python/config/default.py deleted file mode 100644 index 503fd1e..0000000 --- a/ogn_python/config/default.py +++ /dev/null @@ -1,57 +0,0 @@ -SECRET_KEY = 'i-like-ogn' - -SQLALCHEMY_DATABASE_URI = 'postgresql://postgres@localhost:5432/ogn' -SQLALCHEMY_TRACK_MODIFICATIONS = False - -# Flask-Cache stuff -CACHE_TYPE = 'simple' -CACHE_DEFAULT_TIMEOUT = 300 - -# Celery stuff -CELERY_BROKER_URL = 'redis://localhost:6379/0' -CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' - - -from celery.schedules import crontab -from datetime import timedelta - -CELERYBEAT_SCHEDULE = { - 'update-ddb': { - 'task': 'import_ddb', - 'schedule': timedelta(hours=1), - }, - 'update-country-codes': { - 'task': 'update_receivers_country_code', - 'schedule': timedelta(days=1), - }, - 'update-takeoff-and-landing': { - 'task': 'update_takeoff_landings', - 'schedule': timedelta(hours=1), - 'kwargs': {'last_minutes': 90}, - }, - 'update-logbook': { - 'task': 'update_logbook_entries', - 'schedule': timedelta(hours=2), - 'kwargs': {'day_offset': 0}, - }, - 'update-max-altitudes': { - 'task': 'update_logbook_max_altitude', - 'schedule': timedelta(hours=1), - 'kwargs': {'day_offset': 0}, - }, - 'update-stats-daily': { - 'task': 'update_stats', - 'schedule': crontab(hour=0, minute=5), - 'kwargs': {'day_offset': -1}, - }, - 'update-logbook-daily': { - 'task': 'update_logbook_entries', - 'schedule': crontab(hour=1, minute=0), - 'kwargs': {'day_offset': -1}, - }, - 'purge_old_data': { - 'task': 'purge_old_data', - 'schedule': timedelta(hours=1), - 'kwargs': {'max_hours': 48} - }, -} diff --git a/ogn_python/config/test.py b/ogn_python/config/test.py deleted file mode 100644 index a3afda1..0000000 --- a/ogn_python/config/test.py +++ /dev/null @@ -1,6 +0,0 @@ -SQLALCHEMY_DATABASE_URI = 'postgresql://postgres@localhost:5432/ogn_test' -SQLALCHEMY_TRACK_MODIFICATIONS = False - -# Celery stuff -CELERY_BROKER_URL = 'redis://localhost:6379/0' -CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' diff --git a/ogn_python/gateway/bulkimport.py b/ogn_python/gateway/bulkimport.py deleted file mode 100644 index e14426f..0000000 --- a/ogn_python/gateway/bulkimport.py +++ /dev/null @@ -1,322 +0,0 @@ -from datetime import datetime, timedelta -from io import StringIO - -from flask.cli import AppGroup -import click -from tqdm import tqdm -from mgrs import MGRS - -from ogn.parser import parse, ParseError - -from ogn_python.model import AircraftBeacon, ReceiverBeacon, Location -from ogn_python.utils import open_file -from ogn_python.gateway.process_tools import * - -from ogn_python import db -from ogn_python import app - -user_cli = AppGroup('bulkimport') -user_cli.help = "Tools for accelerated data import." - - -# define message types we want to proceed -AIRCRAFT_BEACON_TYPES = ['aprs_aircraft', 'flarm', 'tracker', 'fanet', 'lt24', 'naviter', 'skylines', 'spider', 'spot'] -RECEIVER_BEACON_TYPES = ['aprs_receiver', 'receiver'] - -# define fields we want to proceed -BEACON_KEY_FIELDS = ['name', 'receiver_name', 'timestamp'] -AIRCRAFT_BEACON_FIELDS = ['location', 'altitude', 'dstcall', 'relay', 'track', 'ground_speed', 'address_type', 'aircraft_type', 'stealth', 'address', 'climb_rate', 'turn_rate', 'signal_quality', 'error_count', 'frequency_offset', 'gps_quality_horizontal', 'gps_quality_vertical', 'software_version', 'hardware_version', 'real_address', 'signal_power', 'distance', 'radial', 'quality', 'location_mgrs', 'location_mgrs_short', 'agl', 'receiver_id', 'device_id'] -RECEIVER_BEACON_FIELDS = ['location', 'altitude', 'dstcall', 'relay', 'version', 'platform', 'cpu_load', 'free_ram', 'total_ram', 'ntp_error', 'rt_crystal_correction', 'voltage', 'amperage', 'cpu_temp', 'senders_visible', 'senders_total', 'rec_input_noise', 'senders_signal', 'senders_messages', 'good_senders_signal', 'good_senders', 'good_and_bad_senders'] - - -myMGRS = MGRS() - - -def string_to_message(raw_string, reference_date): - global receivers - - try: - message = parse(raw_string, reference_date) - except NotImplementedError as e: - app.logger.error('No parser implemented for message: {}'.format(raw_string)) - return None - except ParseError as e: - app.logger.error('Parsing error with message: {}'.format(raw_string)) - return None - except TypeError as e: - app.logger.error('TypeError with message: {}'.format(raw_string)) - return None - except Exception as e: - app.logger.error('Other Exception with string: {}'.format(raw_string)) - return None - - # update reference receivers and distance to the receiver - if message['aprs_type'] == 'position': - if message['beacon_type'] in AIRCRAFT_BEACON_TYPES + RECEIVER_BEACON_TYPES: - latitude = message['latitude'] - longitude = message['longitude'] - - location = Location(longitude, latitude) - message['location'] = location.to_wkt() - location_mgrs = myMGRS.toMGRS(latitude, longitude).decode('utf-8') - message['location_mgrs'] = location_mgrs - message['location_mgrs_short'] = location_mgrs[0:5] + location_mgrs[5:7] + location_mgrs[10:12] - - if message['beacon_type'] in AIRCRAFT_BEACON_TYPES and 'gps_quality' in message: - if message['gps_quality'] is not None and 'horizontal' in message['gps_quality']: - message['gps_quality_horizontal'] = message['gps_quality']['horizontal'] - message['gps_quality_vertical'] = message['gps_quality']['vertical'] - del message['gps_quality'] - - # TODO: Fix python-ogn-client 0.91 - if 'senders_messages' in message and message['senders_messages'] is not None: - message['senders_messages'] = int(message['senders_messages']) - if 'good_senders' in message and message['good_senders'] is not None: - message['good_senders'] = int(message['good_senders']) - if 'good_and_bad_senders' in message and message['good_and_bad_senders'] is not None: - message['good_and_bad_senders'] = int(message['good_and_bad_senders']) - - return message - - -class ContinuousDbFeeder: - def __init__(self,): - self.postfix = 'continuous_import' - self.last_flush = datetime.utcnow() - self.last_add_missing = datetime.utcnow() - self.last_transfer = datetime.utcnow() - - self.aircraft_buffer = StringIO() - self.receiver_buffer = StringIO() - - create_tables(self.postfix) - create_indices(self.postfix) - - def add(self, raw_string): - message = string_to_message(raw_string, reference_date=datetime.utcnow()) - - if message is None or ('raw_message' in message and message['raw_message'][0] == '#') or 'beacon_type' not in message: - return - - if message['beacon_type'] in AIRCRAFT_BEACON_TYPES: - complete_message = ','.join([str(message[k]) if k in message and message[k] is not None else '\\N' for k in BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS]) - self.aircraft_buffer.write(complete_message) - self.aircraft_buffer.write('\n') - elif message['beacon_type'] in RECEIVER_BEACON_TYPES: - complete_message = ','.join([str(message[k]) if k in message and message[k] is not None else '\\N' for k in BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS]) - self.receiver_buffer.write(complete_message) - self.receiver_buffer.write('\n') - else: - app.logger.error("Ignore beacon_type: {}".format(message['beacon_type'])) - return - - if datetime.utcnow() - self.last_flush >= timedelta(seconds=20): - self.flush() - self.prepare() - - self.aircraft_buffer = StringIO() - self.receiver_buffer = StringIO() - - self.last_flush = datetime.utcnow() - - if datetime.utcnow() - self.last_add_missing >= timedelta(seconds=60): - self.add_missing() - self.last_add_missing = datetime.utcnow() - - if datetime.utcnow() - self.last_transfer >= timedelta(seconds=30): - self.transfer() - self.delete_beacons() - self.last_transfer = datetime.utcnow() - - - def flush(self): - self.aircraft_buffer.seek(0) - self.receiver_buffer.seek(0) - - connection = db.engine.raw_connection() - cursor = connection.cursor() - cursor.copy_from(self.aircraft_buffer, 'aircraft_beacons_{0}'.format(self.postfix), sep=',', columns=BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS) - cursor.copy_from(self.receiver_buffer, 'receiver_beacons_{0}'.format(self.postfix), sep=',', columns=BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS) - connection.commit() - - self.aircraft_buffer = StringIO() - self.receiver_buffer = StringIO() - - def add_missing(self): - add_missing_receivers(self.postfix) - add_missing_devices(self.postfix) - - def prepare(self): - # make receivers complete - update_receiver_beacons(self.postfix) - update_receiver_location(self.postfix) - - # make devices complete - update_aircraft_beacons(self.postfix) - - def transfer(self): - # tranfer beacons - transfer_aircraft_beacons(self.postfix) - transfer_receiver_beacons(self.postfix) - - def delete_beacons(self): - # delete already transfered beacons - delete_receiver_beacons(self.postfix) - delete_aircraft_beacons(self.postfix) - - -class FileDbFeeder(): - def __init__(self): - self.postfix = 'continuous_import' - self.last_flush = datetime.utcnow() - - self.aircraft_buffer = StringIO() - self.receiver_buffer = StringIO() - - create_tables(self.postfix) - create_indices(self.postfix) - - def add(self, raw_string): - message = string_to_message(raw_string, reference_date=datetime.utcnow()) - - if message is None or ('raw_message' in message and message['raw_message'][0] == '#') or 'beacon_type' not in message: - return - - if message['beacon_type'] in AIRCRAFT_BEACON_TYPES: - complete_message = ','.join([str(message[k]) if k in message and message[k] is not None else '\\N' for k in BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS]) - self.aircraft_buffer.write(complete_message) - self.aircraft_buffer.write('\n') - elif message['beacon_type'] in RECEIVER_BEACON_TYPES: - complete_message = ','.join([str(message[k]) if k in message and message[k] is not None else '\\N' for k in BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS]) - self.receiver_buffer.write(complete_message) - self.receiver_buffer.write('\n') - else: - app.logger.error("Ignore beacon_type: {}".format(message['beacon_type'])) - return - - def prepare(self): - # make receivers complete - add_missing_receivers(self.postfix) - update_receiver_location(self.postfix) - - # make devices complete - add_missing_devices(self.postfix) - - # prepare beacons for transfer - create_indices(self.postfix) - update_receiver_beacons_bigdata(self.postfix) - update_aircraft_beacons_bigdata(self.postfix) - - -def get_aircraft_beacons_postfixes(): - """Get the postfixes from imported aircraft_beacons logs.""" - - postfixes = db.session.execute(""" - SELECT DISTINCT(RIGHT(tablename, 8)) - FROM pg_catalog.pg_tables - WHERE schemaname = 'public' AND tablename LIKE 'aircraft\_beacons\_20______' - ORDER BY RIGHT(tablename, 10); - """).fetchall() - - return [postfix for postfix in postfixes] - - - - - -def export_to_path(postfix): - import os, gzip - aircraft_beacons_file = os.path.join(path, 'aircraft_beacons_{0}.csv.gz'.format(postfix)) - with gzip.open(aircraft_beacons_file, 'wt', encoding='utf-8') as gzip_file: - self.cur.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format(self.get_merged_aircraft_beacons_subquery()), gzip_file) - receiver_beacons_file = os.path.join(path, 'receiver_beacons_{0}.csv.gz'.format(postfix)) - with gzip.open(receiver_beacons_file, 'wt') as gzip_file: - self.cur.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format(self.get_merged_receiver_beacons_subquery()), gzip_file) - - - -def convert(sourcefile, datestr, saver): - from ogn_python.gateway.process import string_to_message - from ogn_python.gateway.process_tools import AIRCRAFT_BEACON_TYPES, RECEIVER_BEACON_TYPES - from datetime import datetime - - fin = open_file(sourcefile) - - # get total lines of the input file - total_lines = 0 - for line in fin: - total_lines += 1 - fin.seek(0) - - current_line = 0 - steps = 100000 - reference_date = datetime.strptime(datestr + ' 12:00:00', '%Y-%m-%d %H:%M:%S') - - pbar = tqdm(fin, total=total_lines) - for line in pbar: - pbar.set_description('Importing {}'.format(sourcefile)) - - current_line += 1 - if current_line % steps == 0: - saver.flush() - - message = string_to_message(line.strip(), reference_date=reference_date) - if message is None: - continue - - dictfilt = lambda x, y: dict([(i, x[i]) for i in x if i in set(y)]) - - try: - if message['beacon_type'] in AIRCRAFT_BEACON_TYPES: - message = dictfilt(message, ('beacon_type', 'aprs_type', 'location_wkt', 'altitude', 'name', 'dstcall', 'relay', 'receiver_name', 'timestamp', 'track', 'ground_speed', - 'address_type', 'aircraft_type', 'stealth', 'address', 'climb_rate', 'turn_rate', 'signal_quality', 'error_count', 'frequency_offset', 'gps_quality_horizontal', 'gps_quality_vertical', 'software_version', 'hardware_version', 'real_address', 'signal_power', - 'distance', 'radial', 'quality', 'agl', 'location_mgrs', 'location_mgrs_short', - 'receiver_id', 'device_id')) - - beacon = AircraftBeacon(**message) - elif message['beacon_type'] in RECEIVER_BEACON_TYPES: - if 'rec_crystal_correction' in message: - del message['rec_crystal_correction'] - del message['rec_crystal_correction_fine'] - beacon = ReceiverBeacon(**message) - saver.add(beacon) - except Exception as e: - print(e) - - saver.flush() - fin.close() - - -@user_cli.command('file_import') -@click.argument('path') -def file_import(path): - """Import APRS logfiles into separate logfile tables.""" - - import os - import re - - # Get Filepaths and dates to import - results = list() - for (root, dirs, files) in os.walk(path): - for file in sorted(files): - match = re.match('OGN_log\.txt_([0-9]{4}\-[0-9]{2}\-[0-9]{2})\.gz$', file) - if match: - results.append({'filepath': os.path.join(root, file), - 'datestr': match.group(1)}) - - with LogfileDbSaver() as saver: - already_imported = saver.get_datestrs() - - results = list(filter(lambda x: x['datestr'] not in already_imported, results)) - - pbar = tqdm(results) - for result in pbar: - filepath = result['filepath'] - datestr = result['datestr'] - pbar.set_description("Importing data for {}".format(datestr)) - - saver.set_datestr(datestr) - saver.create_tables() - convert(filepath, datestr, saver) - saver.add_missing_devices() - saver.add_missing_receivers() diff --git a/ogn_python/live_routes.py b/ogn_python/live_routes.py deleted file mode 100644 index ecc1b4e..0000000 --- a/ogn_python/live_routes.py +++ /dev/null @@ -1,90 +0,0 @@ -from flask import request, render_template, make_response, send_file -from flask_cors import cross_origin - -from ogn_python.backend.liveglidernet import rec, lxml - -from ogn_python import app -from ogn_python import db -from ogn_python import cache - - -@app.route('/live.html') -@cross_origin() -def live(): - return render_template('ogn_live.html', host=request.host) - - -@app.route('/rec.php') -def rec_php(): - a = request.args.get('a') - z = request.args.get('z') - - xml = rec() - resp = app.make_response(xml) - resp.mimetype = "text/xml" - return resp - - -@app.route('/lxml.php') -def lxml_php(): - a = request.args.get('a') - b = request.args.get('b') - c = request.args.get('c') - d = request.args.get('d') - e = request.args.get('e') - z = request.args.get('z') - - xml = lxml() - resp = app.make_response(xml) - resp.mimetype = "text/xml" - return resp - - -@app.route('/pict/') -def pict(filename): - return app.send_static_file('ognlive/pict/' + filename) - - -@app.route('/favicon.gif') -def favicon_gif(): - return app.send_static_file('ognlive/pict/favicon.gif') - - -@app.route('/horizZoomControl.js') -def horizZoomControl_js(): - return app.send_static_file('ognlive/horizZoomControl.js') - - -@app.route('/barogram.js') -def barogram_js(): - return app.send_static_file('ognlive/barogram.js') - - -@app.route('/util.js') -def util_js(): - return app.send_static_file('ognlive/util.js') - - -@app.route('/ogn.js') -def ogn_js(): - return app.send_static_file('ognlive/ogn.js') - - -@app.route('/ol.js') -def ol_js(): - return app.send_static_file('ognlive/ol.js') - - -@app.route('/osm.js') -def osm_js(): - return app.send_static_file('ognlive/osm.js') - - -@app.route('/ol.css') -def ol_css(): - return app.send_static_file('ognlive/ol.css') - - -@app.route('/osm.css') -def osm_css(): - return app.send_static_file('ognlive/osm.css') diff --git a/ogn_python/model/flights2d.py b/ogn_python/model/flights2d.py deleted file mode 100644 index 2dc3128..0000000 --- a/ogn_python/model/flights2d.py +++ /dev/null @@ -1,27 +0,0 @@ -from geoalchemy2.types import Geometry - -from ogn_python import db - - -class Flight2D(db.Model): - __tablename__ = "flights2d" - - date = db.Column(db.Date, primary_key=True) - flight_type = db.Column(db.SmallInteger, primary_key=True) - - path_wkt = db.Column('path', Geometry('MULTILINESTRING', srid=4326)) - path_simple_wkt = db.Column('path_simple', Geometry('MULTILINESTRING', srid=4326)) # this is the path simplified with ST_Simplify(path, 0.0001) - - # Relations - device_id = db.Column(db.Integer, db.ForeignKey('devices.id', ondelete='SET NULL'), primary_key=True) - device = db.relationship('Device', foreign_keys=[device_id], backref='flights2d') - - def __repr__(self): - return "" % ( - self.date, - self.path_wkt, - self.path_simple_wkt) - - -db.Index('ix_flights2d_date_device_id', Flight2D.date, Flight2D.device_id) -#db.Index('ix_flights2d_date_path', Flight2D.date, Flight2D.path_wkt) --> CREATE INDEX ix_flights2d_date_path ON flights2d USING GIST("date", path) diff --git a/ogn_python/model/relation_stats.py b/ogn_python/model/relation_stats.py deleted file mode 100644 index 413b755..0000000 --- a/ogn_python/model/relation_stats.py +++ /dev/null @@ -1,29 +0,0 @@ -from ogn_python import db - - -class RelationStats(db.Model): - __tablename__ = "relation_stats" - - id = db.Column(db.Integer, primary_key=True) - - date = db.Column(db.Date) - - # Statistic data - quality = db.Column(db.Float(precision=2)) - beacon_count = db.Column(db.Integer) - - # Relations - device_id = db.Column(db.Integer, db.ForeignKey('devices.id', ondelete='SET NULL'), index=True) - device = db.relationship('Device', foreign_keys=[device_id], backref='relation_stats') - receiver_id = db.Column(db.Integer, db.ForeignKey('receivers.id', ondelete='SET NULL'), index=True) - receiver = db.relationship('Receiver', foreign_keys=[receiver_id], backref='relation_stats') - - def __repr__(self): - return "" % ( - self.date, - self.quality, - self.beacon_count) - - -db.Index('ix_relation_stats_date_device_id', RelationStats.date, RelationStats.device_id, RelationStats.receiver_id) -db.Index('ix_relation_stats_date_receiver_id', RelationStats.date, RelationStats.receiver_id, RelationStats.device_id) diff --git a/ogn_python/model/takeoff_landing.py b/ogn_python/model/takeoff_landing.py deleted file mode 100644 index a463c5c..0000000 --- a/ogn_python/model/takeoff_landing.py +++ /dev/null @@ -1,16 +0,0 @@ -from ogn_python import db - - -class TakeoffLanding(db.Model): - __tablename__ = 'takeoff_landings' - - device_id = db.Column(db.Integer, db.ForeignKey('devices.id', ondelete='SET NULL'), primary_key=True) - airport_id = db.Column(db.Integer, db.ForeignKey('airports.id', ondelete='SET NULL'), primary_key=True) - timestamp = db.Column(db.DateTime, primary_key=True) - - is_takeoff = db.Column(db.Boolean) - track = db.Column(db.SmallInteger) - - # Relations - airport = db.relationship('Airport', foreign_keys=[airport_id], backref='takeoff_landings') - device = db.relationship('Device', foreign_keys=[device_id], backref='takeoff_landings', order_by='TakeoffLanding.timestamp') diff --git a/ogn_python/navigation.py b/ogn_python/navigation.py deleted file mode 100644 index ec1ce83..0000000 --- a/ogn_python/navigation.py +++ /dev/null @@ -1,14 +0,0 @@ -from flask_nav import Nav -from flask_nav.elements import * - -nav = Nav() - -# registers the "top" menubar -nav.register_element('top_menubar', Navbar( - View('Home', 'index'), - View('Devices', 'devices'), - View('Receivers', 'receivers'), - View('Airports', 'airports'), - View('Logbook', 'logbook'), - View('Statistics', 'statistics'), -)) diff --git a/ogn_python/routes.py b/ogn_python/routes.py deleted file mode 100644 index f9c688b..0000000 --- a/ogn_python/routes.py +++ /dev/null @@ -1,243 +0,0 @@ -import datetime - -from flask import request, render_template, send_file -from flask_cors import cross_origin - -from ogn_python import app -from ogn_python import db -from ogn_python import cache - -from ogn_python.model import * - - -@cache.cached(key_prefix='countries_in_receivers') -def get_countries_in_receivers(): - query = db.session.query(Country.iso2) \ - .filter(Country.gid == Receiver.country_id) \ - .order_by(Country.iso2) \ - .distinct(Country.iso2) - - return [{'iso2': country[0]} for country in query.all()] - - -@cache.cached(key_prefix='countries_in_logbook') -def get_countries_in_logbook(): - query = db.session.query(Country.iso2) \ - .filter(Country.iso2 == Airport.country_code) \ - .filter(Logbook.takeoff_airport_id == Airport.id) \ - .order_by(Country.iso2) \ - .distinct(Country.iso2) - - return [{'iso2': country[0]} for country in query.all()] - - -@cache.memoize() -def get_airports_in_country(sel_country): - query = db.session.query(Airport.id, Airport.name) \ - .filter(Airport.country_code == sel_country) \ - .filter(Logbook.takeoff_airport_id == Airport.id) \ - .order_by(Airport.name) \ - .distinct(Airport.name) - - return [{'id': airport[0], 'name': airport[1]} for airport in query.all()] - - -@cache.memoize() -def get_dates_for_airport(sel_airport): - query = db.session.query(db.func.date(Logbook.reftime), db.func.count(Logbook.id).label('logbook_count')) \ - .filter(Airport.id == sel_airport) \ - .filter(db.or_(Airport.id == Logbook.takeoff_airport_id, Airport.id == Logbook.landing_airport_id)) \ - .group_by(db.func.date(Logbook.reftime)) \ - .order_by(db.func.date(Logbook.reftime).desc()) - - return [{'date': date, 'logbook_count': logbook_count} for (date, logbook_count) in query.all()] - - -@app.route('/') -@app.route('/index.html') -def index(): - return render_template('base.html') - - -@app.route('/devices.html', methods=['GET', 'POST']) -def devices(): - devices = db.session.query(Device) \ - .order_by(Device.address) \ - .limit(100) - return render_template('devices.html', devices=devices) - - -@app.route('/device_detail.html', methods=['GET', 'POST']) -def device_detail(): - device_id = request.args.get('id') - device = db.session.query(Device) \ - .filter(Device.id == device_id) \ - .one() - - return render_template('device_detail.html', - title='Device', - device=device) - - -@app.route('/receivers.html') -def receivers(): - sel_country = request.args.get('country') - - countries = get_countries_in_receivers() - - # Get receiver selection list - if sel_country: - receivers = db.session.query(Receiver) \ - .filter(db.and_(Receiver.country_id == Country.gid, Country.iso2 == sel_country)) \ - .order_by(Receiver.name) - else: - receivers = db.session.query(Receiver) \ - .order_by(Receiver.name) - - return render_template('receivers.html', - title='Receivers', - sel_country=sel_country, - countries=countries, - receivers=receivers) - - -@app.route('/receiver_detail.html') -def receiver_detail(): - sel_receiver_id = request.args.get('receiver_id') - - receiver = db.session.query(Receiver) \ - .filter(Receiver.id == sel_receiver_id) \ - .one() - - airport = db.session.query(Airport) \ - .filter(db.and_(Receiver.id == sel_receiver_id, - db.func.st_contains(db.func.st_buffer(Receiver.location_wkt, 0.5), Airport.location_wkt), - db.func.st_distance_sphere(Airport.location_wkt, Receiver.location_wkt) < 1000)) \ - .filter(Airport.style.in_((2,4,5))) \ - - return render_template('receiver_detail.html', - title='Receiver Detail', - receiver=receiver, - airport=airport.first()) - - -@app.route('/airports.html', methods=['GET', 'POST']) -def airports(): - sel_country = request.args.get('country') - - countries = get_countries_in_logbook() - - if sel_country: - airports = get_airports_in_country(sel_country) - else: - airports = [] - - page = request.args.get('page', 1, type=int) - - return render_template('airports.html', - sel_country=sel_country, - countries=countries, - airports=airports) - - -@app.route('/airport_detail.html') -def airport_detail(): - sel_airport = request.args.get('airport') - - airport = db.session.query(Airport) \ - .filter(Airport.id == sel_airport) - - devices = db.session.query(Device).join(Logbook) \ - .filter(Logbook.takeoff_airport_id == sel_airport) \ - .order_by(Device.address) - - return render_template('airport_detail.html', - title='Airport Detail', - airport=airport.one(), - devices=devices) - - -@app.route('/logbook.html', methods=['GET', 'POST']) -def logbook(): - sel_country = request.args.get('country') - sel_airport = request.args.get('airport') - sel_date = request.args.get('date') - - sel_device_id = request.args.get('device_id') - - countries = get_countries_in_logbook() - - if sel_country: - airports = get_airports_in_country(sel_country) - else: - airports = [] - - if sel_airport: - sel_airport = int(sel_airport) - if sel_airport not in [airport['id'] for airport in airports]: - sel_airport = None - sel_date = None - dates = get_dates_for_airport(sel_airport) - else: - dates = [] - - if sel_date: - sel_date = datetime.datetime.strptime(sel_date, '%Y-%m-%d').date() - if sel_date not in [entry['date'] for entry in dates]: - sel_date = dates[0]['date'] - elif len(dates) > 0: - sel_date = dates[0]['date'] - - # Get Logbook - filters = [] - if sel_airport: - filters.append(db.or_(Logbook.takeoff_airport_id == sel_airport, Logbook.landing_airport_id == sel_airport)) - - if sel_date: - filters.append(db.func.date(Logbook.reftime) == sel_date) - - if sel_device_id: - filters.append(Logbook.device_id == sel_device_id) - - if len(filters) > 0: - logbook = db.session.query(Logbook) \ - .filter(*filters) \ - .order_by(Logbook.reftime) - else: - logbook = None - - return render_template('logbook.html', - title='Logbook', - sel_country=sel_country, - countries=countries, - sel_airport=sel_airport, - airports=airports, - sel_date=sel_date, - dates=dates, - logbook=logbook) - -@app.route('/download.html') -def download_flight(): - from io import StringIO - buffer = StringIO() - buffer.write('Moin moin\nAlter Verwalter') - buffer.seek(0) - - return send_file(buffer, - as_attachment=True, - attachment_filename='wtf.igc', - mimetype = 'text/plain') - - -@app.route('/statistics.html') -def statistics(): - - today = datetime.date.today() - today = datetime.date(2018, 7, 31) - - receiverstats = db.session.query(ReceiverStats) \ - .filter(ReceiverStats.date == today) - - return render_template('statistics.html', - title='Receiver Statistics', - receiverstats=receiverstats) diff --git a/tests/backend/test_backends.py b/tests/backend/test_backends.py index 51b6b13..2ab2b4a 100644 --- a/tests/backend/test_backends.py +++ b/tests/backend/test_backends.py @@ -8,10 +8,10 @@ from xmlunittest import XmlTestMixin from tests.base import TestBaseDB, db -from ogn_python.model import AircraftBeacon, AircraftType, Receiver, Device, DeviceInfo, ReceiverCoverage +from app.model import AircraftBeacon, AircraftType, Receiver, Device, DeviceInfo, ReceiverCoverage -from ogn_python.backend.liveglidernet import rec, lxml -from ogn_python.backend.ognrange import stations2_filtered_pl, max_tile_mgrs_pl +from app.backend.liveglidernet import rec, lxml +from app.backend.ognrange import stations2_filtered_pl, max_tile_mgrs_pl class TestDB(TestBaseDB, XmlTestMixin): @@ -19,50 +19,100 @@ class TestDB(TestBaseDB, XmlTestMixin): super().setUp() # Prepare Beacons - self.r01 = Receiver(name='Koenigsdf', location_wkt='0101000020E610000061E8FED7A6EE26407F20661C10EA4740', lastseen='2017-12-20 10:00:00', altitude=601, version='0.2.5', platform='ARM') - self.r02 = Receiver(name='Bene', location_wkt='0101000020E6100000D5E76A2BF6C72640D4063A6DA0DB4740', lastseen='2017-12-20 09:45:00', altitude=609, version='0.2.7', platform='x64') - self.r03 = Receiver(name='Ohlstadt', location_wkt='0101000020E6100000057E678EBF772640A142883E32D44740', lastseen='2017-12-20 10:05:00', altitude=655, version='0.2.6', platform='ARM') + self.r01 = Receiver(name="Koenigsdf", location_wkt="0101000020E610000061E8FED7A6EE26407F20661C10EA4740", lastseen="2017-12-20 10:00:00", altitude=601, version="0.2.5", platform="ARM") + self.r02 = Receiver(name="Bene", location_wkt="0101000020E6100000D5E76A2BF6C72640D4063A6DA0DB4740", lastseen="2017-12-20 09:45:00", altitude=609, version="0.2.7", platform="x64") + self.r03 = Receiver(name="Ohlstadt", location_wkt="0101000020E6100000057E678EBF772640A142883E32D44740", lastseen="2017-12-20 10:05:00", altitude=655, version="0.2.6", platform="ARM") db.session.add(self.r01) db.session.add(self.r02) db.session.add(self.r03) db.session.commit() - self.d01 = Device(address='DD4711', lastseen='2017-12-20 10:00:02') - self.d02 = Device(address='DD0815', lastseen='2017-12-20 09:56:00') + self.d01 = Device(address="DD4711", lastseen="2017-12-20 10:00:02") + self.d02 = Device(address="DD0815", lastseen="2017-12-20 09:56:00") db.session.add(self.d01) db.session.add(self.d02) db.session.commit() - self.di01 = DeviceInfo(registration='D-4711', competition='Hi', tracked=True, identified=True) + self.di01 = DeviceInfo(registration="D-4711", competition="Hi", tracked=True, identified=True) db.session.add(self.di01) db.session.commit() - self.ab11 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', location_wkt='0101000020E6100000211FF46C56ED26402650D7EDC6E94740', aircraft_type=AircraftType.glider_or_motor_glider, timestamp='2017-12-20 10:00:01', track=105, ground_speed=57, climb_rate=-0.5, device_id=self.d01.id) - self.ab12 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', location_wkt='0101000020E6100000806DEA295FED2640347D898BB6E94740', aircraft_type=AircraftType.glider_or_motor_glider, timestamp='2017-12-20 10:00:02', track=123, ground_speed=55, climb_rate=-0.4, altitude=209, device_id=self.d01.id) - self.ab21 = AircraftBeacon(name='FLRDD0815', receiver_name='Koenigsdf', location_wkt='0101000020E6100000F38B25BF58F22640448B6CE7FBE94740', aircraft_type=AircraftType.powered_aircraft, timestamp='2017-12-20 09:54:30', track=280, ground_speed=80, climb_rate=-2.9, device_id=self.d02.id) - self.ab22 = AircraftBeacon(name='FLRDD0815', receiver_name='Bene', location_wkt='0101000020E6100000A5E8482EFFF12640DC1EAA16FEE94740', aircraft_type=AircraftType.powered_aircraft, timestamp='2017-12-20 09:56:00', track=270, ground_speed=77, climb_rate=-1.5, altitude=543, device_id=self.d02.id) + self.ab11 = AircraftBeacon( + name="FLRDD4711", + receiver_name="Koenigsdf", + location_wkt="0101000020E6100000211FF46C56ED26402650D7EDC6E94740", + aircraft_type=AircraftType.glider_or_motor_glider, + timestamp="2017-12-20 10:00:01", + track=105, + ground_speed=57, + climb_rate=-0.5, + device_id=self.d01.id, + ) + self.ab12 = AircraftBeacon( + name="FLRDD4711", + receiver_name="Koenigsdf", + location_wkt="0101000020E6100000806DEA295FED2640347D898BB6E94740", + aircraft_type=AircraftType.glider_or_motor_glider, + timestamp="2017-12-20 10:00:02", + track=123, + ground_speed=55, + climb_rate=-0.4, + altitude=209, + device_id=self.d01.id, + ) + self.ab21 = AircraftBeacon( + name="FLRDD0815", + receiver_name="Koenigsdf", + location_wkt="0101000020E6100000F38B25BF58F22640448B6CE7FBE94740", + aircraft_type=AircraftType.powered_aircraft, + timestamp="2017-12-20 09:54:30", + track=280, + ground_speed=80, + climb_rate=-2.9, + device_id=self.d02.id, + ) + self.ab22 = AircraftBeacon( + name="FLRDD0815", + receiver_name="Bene", + location_wkt="0101000020E6100000A5E8482EFFF12640DC1EAA16FEE94740", + aircraft_type=AircraftType.powered_aircraft, + timestamp="2017-12-20 09:56:00", + track=270, + ground_speed=77, + climb_rate=-1.5, + altitude=543, + device_id=self.d02.id, + ) db.session.add(self.ab11) db.session.add(self.ab12) db.session.add(self.ab21) db.session.add(self.ab22) db.session.commit() - self.rc11 = ReceiverCoverage(location_mgrs_short='32TPU8312', date=date(2017, 12, 20), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r01) - self.rc12 = ReceiverCoverage(location_mgrs_short='32TPU8434', date=date(2017, 12, 20), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r01) - self.rc12 = ReceiverCoverage(location_mgrs_short='32TPU8434', date=date(2017, 12, 21), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r01) - self.rc21 = ReceiverCoverage(location_mgrs_short='32TPU8512', date=date(2017, 12, 20), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r02) + self.rc11 = ReceiverCoverage( + location_mgrs_short="32TPU8312", date=date(2017, 12, 20), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r01 + ) + self.rc12 = ReceiverCoverage( + location_mgrs_short="32TPU8434", date=date(2017, 12, 20), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r01 + ) + self.rc12 = ReceiverCoverage( + location_mgrs_short="32TPU8434", date=date(2017, 12, 21), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r01 + ) + self.rc21 = ReceiverCoverage( + location_mgrs_short="32TPU8512", date=date(2017, 12, 20), max_signal_quality=10, max_altitude=1000, min_altitude=600, aircraft_beacon_count=20, device_count=2, receiver=self.r02 + ) db.session.add(self.rc11) db.session.add(self.rc12) db.session.add(self.rc21) db.session.commit() - @unittest.skip('broken') + @unittest.skip("broken") def test_rec(self): - data = rec(min_timestamp=datetime(2017, 12, 19, 10, 0), min_online_timestamp=datetime(2017, 12, 20, 10, 0)).encode(encoding='utf-8') + data = rec(min_timestamp=datetime(2017, 12, 19, 10, 0), min_online_timestamp=datetime(2017, 12, 20, 10, 0)).encode(encoding="utf-8") # Check the document root = self.assertXmlDocument(data) - self.assertXmlNode(root, tag='markers') + self.assertXmlNode(root, tag="markers") self.assertXpathsOnlyOne(root, ('./m[@a="Koenigsdf"]', './m[@a="Bene"]', './m[@a="Ohlstadt"]')) # Check the complete document @@ -73,13 +123,15 @@ class TestDB(TestBaseDB, XmlTestMixin): - """.encode(encoding='utf-8') + """.encode( + encoding="utf-8" + ) self.assertXmlEquivalentOutputs(data, expected) - @unittest.skip('broken') + @unittest.skip("broken") def test_lxml(self): - data = lxml().encode(encoding='utf-8') + data = lxml().encode(encoding="utf-8") # Check the complete document expected = """ @@ -87,11 +139,13 @@ class TestDB(TestBaseDB, XmlTestMixin): - """.encode(encoding='utf-8') + """.encode( + encoding="utf-8" + ) self.assertXmlEquivalentOutputs(data, expected) - @mock.patch('ogn_python.backend.ognrange.datetime') + @mock.patch("app.backend.ognrange.datetime") def test_stations2_filtered_pl(self, datetime_mock): datetime_mock.utcnow.return_value = datetime(2017, 12, 20, 10, 0) @@ -105,7 +159,7 @@ class TestDB(TestBaseDB, XmlTestMixin): s2 = stations[1] s3 = stations[2] - self.assertEqual(s1["s"], 'Bene') + self.assertEqual(s1["s"], "Bene") self.assertEqual(s1["lt"], 47.7158) self.assertEqual(s1["lg"], 11.3906) self.assertEqual(s1["u"], "D") # Down, because last beacon > 10min. ago @@ -113,7 +167,7 @@ class TestDB(TestBaseDB, XmlTestMixin): # self.assertEqual(s1["b"], 0) self.assertEqual(s1["v"], "0.2.7.x64") - self.assertEqual(s2["s"], 'Koenigsdf') + self.assertEqual(s2["s"], "Koenigsdf") self.assertEqual(s2["lt"], 47.8286) self.assertEqual(s2["lg"], 11.4661) self.assertEqual(s2["u"], "U") @@ -121,24 +175,24 @@ class TestDB(TestBaseDB, XmlTestMixin): # self.assertEqual(s2["b"], 0) self.assertEqual(s2["v"], "0.2.5.ARM") - self.assertEqual(s3["s"], 'Ohlstadt') + self.assertEqual(s3["s"], "Ohlstadt") def test_max_tile_mgrs_pl(self): - result = max_tile_mgrs_pl(station='Koenigsdf', start=date(2017, 12, 15), end=date(2017, 12, 25), squares='32TPU') + result = max_tile_mgrs_pl(station="Koenigsdf", start=date(2017, 12, 15), end=date(2017, 12, 25), squares="32TPU") data = json.loads(result) - self.assertEqual(data['t'], '32TPU') - self.assertEqual(data['p'][0], '8312/1') - self.assertEqual(data['p'][1], '8434/2') + self.assertEqual(data["t"], "32TPU") + self.assertEqual(data["p"][0], "8312/1") + self.assertEqual(data["p"][1], "8434/2") - result = max_tile_mgrs_pl(station='Bene', start=date(2017, 12, 15), end=date(2017, 12, 25), squares='32TPU') + result = max_tile_mgrs_pl(station="Bene", start=date(2017, 12, 15), end=date(2017, 12, 25), squares="32TPU") data = json.loads(result) - self.assertEqual(data['t'], '32TPU') - self.assertEqual(data['p'][0], '8512/1') + self.assertEqual(data["t"], "32TPU") + self.assertEqual(data["p"][0], "8512/1") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/base.py b/tests/base.py index 9ee150e..9d81e03 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,15 +1,15 @@ import unittest import os -os.environ['OGN_CONFIG_MODULE'] = 'config/test.py' +os.environ["OGN_CONFIG_MODULE"] = "config/test.py" -from ogn_python import db # noqa: E402 +from app import db # noqa: E402 class TestBaseDB(unittest.TestCase): @classmethod def setUpClass(cls): - db.session.execute('CREATE EXTENSION IF NOT EXISTS postgis;') + db.session.execute("CREATE EXTENSION IF NOT EXISTS postgis;") db.session.commit() db.drop_all() db.create_all() @@ -18,7 +18,8 @@ class TestBaseDB(unittest.TestCase): pass def tearDown(self): - db.session.execute(""" + db.session.execute( + """ DELETE FROM aircraft_beacons; DELETE FROM receiver_beacons; DELETE FROM takeoff_landings; @@ -28,8 +29,9 @@ class TestBaseDB(unittest.TestCase): DELETE FROM receiver_stats; DELETE FROM receivers; DELETE FROM devices; - """) + """ + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/collect/test_database.py b/tests/collect/test_database.py index b874ae8..4253a1f 100644 --- a/tests/collect/test_database.py +++ b/tests/collect/test_database.py @@ -2,26 +2,26 @@ import unittest from tests.base import TestBaseDB, db -from ogn_python.model import AircraftBeacon -from ogn_python.collect.database import upsert +from app.model import AircraftBeacon +from app.collect.database import upsert class TestDatabase(TestBaseDB): - @unittest.skip('wip') + @unittest.skip("wip") def test_insert_duplicate_beacons(self): - row1 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:51:00', 'ground_speed': None} - row2 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:52:00', 'ground_speed': 0} - row3 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:53:00', 'ground_speed': 1} - row4 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:54:00', 'ground_speed': None} + row1 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:51:00", "ground_speed": None} + row2 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:52:00", "ground_speed": 0} + row3 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:53:00", "ground_speed": 1} + row4 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:54:00", "ground_speed": None} - upsert(session=db.session, model=AircraftBeacon, rows=[row1, row2, row3, row4], update_cols=['ground_speed']) + upsert(session=db.session, model=AircraftBeacon, rows=[row1, row2, row3, row4], update_cols=["ground_speed"]) - row5 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:51:00', 'ground_speed': 2} - row6 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:52:00', 'ground_speed': 3} - row7 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:53:00', 'ground_speed': None} - row8 = {'name': 'FLRDD0815', 'receiver_name': 'Koenigsdf', 'timestamp': '2019-01-26 11:54:00', 'ground_speed': None} + row5 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:51:00", "ground_speed": 2} + row6 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:52:00", "ground_speed": 3} + row7 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:53:00", "ground_speed": None} + row8 = {"name": "FLRDD0815", "receiver_name": "Koenigsdf", "timestamp": "2019-01-26 11:54:00", "ground_speed": None} - upsert(session=db.session, model=AircraftBeacon, rows=[row5, row6, row7, row8], update_cols=['ground_speed']) + upsert(session=db.session, model=AircraftBeacon, rows=[row5, row6, row7, row8], update_cols=["ground_speed"]) result = db.session.query(AircraftBeacon).order_by(AircraftBeacon.timestamp).all() self.assertEqual(result[0].ground_speed, 2) @@ -30,5 +30,5 @@ class TestDatabase(TestBaseDB): self.assertEqual(result[3].ground_speed, None) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/collect/test_logbook.py b/tests/collect/test_logbook.py index 83c1e62..62020cf 100644 --- a/tests/collect/test_logbook.py +++ b/tests/collect/test_logbook.py @@ -3,18 +3,18 @@ import unittest from tests.base import TestBaseDB, db -from ogn_python.model import Logbook, Airport, Device, TakeoffLanding -from ogn_python.collect.logbook import update_entries +from app.model import Logbook, Airport, Device, TakeoffLanding +from app.collect.logbook import update_entries class TestLogbook(TestBaseDB): def setUp(self): # Create basic data and insert - self.dd0815 = Device(address='DD0815') - self.dd4711 = Device(address='DD4711') + self.dd0815 = Device(address="DD0815") + self.dd4711 = Device(address="DD4711") - self.koenigsdorf = Airport(name='Koenigsdorf') - self.ohlstadt = Airport(name='Ohlstadt') + self.koenigsdorf = Airport(name="Koenigsdorf") + self.ohlstadt = Airport(name="Ohlstadt") db.session.add(self.dd0815) db.session.add(self.dd4711) @@ -24,10 +24,10 @@ class TestLogbook(TestBaseDB): db.session.commit() # Prepare takeoff and landings - self.takeoff_koenigsdorf_dd0815 = TakeoffLanding(is_takeoff=True, timestamp='2016-06-01 10:00:00', airport_id=self.koenigsdorf.id, device_id=self.dd0815.id) - self.landing_koenigsdorf_dd0815 = TakeoffLanding(is_takeoff=False, timestamp='2016-06-01 10:05:00', airport_id=self.koenigsdorf.id, device_id=self.dd0815.id) - self.landing_koenigsdorf_dd0815_later = TakeoffLanding(is_takeoff=False, timestamp='2016-06-02 10:05:00', airport_id=self.koenigsdorf.id, device_id=self.dd0815.id) - self.takeoff_ohlstadt_dd4711 = TakeoffLanding(is_takeoff=True, timestamp='2016-06-01 10:00:00', airport_id=self.ohlstadt.id, device_id=self.dd4711.id) + self.takeoff_koenigsdorf_dd0815 = TakeoffLanding(is_takeoff=True, timestamp="2016-06-01 10:00:00", airport_id=self.koenigsdorf.id, device_id=self.dd0815.id) + self.landing_koenigsdorf_dd0815 = TakeoffLanding(is_takeoff=False, timestamp="2016-06-01 10:05:00", airport_id=self.koenigsdorf.id, device_id=self.dd0815.id) + self.landing_koenigsdorf_dd0815_later = TakeoffLanding(is_takeoff=False, timestamp="2016-06-02 10:05:00", airport_id=self.koenigsdorf.id, device_id=self.dd0815.id) + self.takeoff_ohlstadt_dd4711 = TakeoffLanding(is_takeoff=True, timestamp="2016-06-01 10:00:00", airport_id=self.ohlstadt.id, device_id=self.dd4711.id) def get_logbook_entries(self): return db.session.query(Logbook).order_by(Logbook.takeoff_airport_id, Logbook.reftime).all() @@ -161,5 +161,5 @@ class TestLogbook(TestBaseDB): self.assertEqual(entries, entries2) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/collect/test_ognrange.py b/tests/collect/test_ognrange.py index 690361f..e01b001 100644 --- a/tests/collect/test_ognrange.py +++ b/tests/collect/test_ognrange.py @@ -3,8 +3,8 @@ import unittest from tests.base import TestBaseDB, db -from ogn_python.model import AircraftBeacon, Receiver, ReceiverCoverage, Device -from ogn_python.collect.ognrange import update_entries +from app.model import AircraftBeacon, Receiver, ReceiverCoverage, Device +from app.collect.ognrange import update_entries class TestOGNrange(TestBaseDB): @@ -12,11 +12,11 @@ class TestOGNrange(TestBaseDB): super().setUp() # Create basic data and insert - self.dd0815 = Device(address='DD0815') - self.dd4711 = Device(address='DD4711') + self.dd0815 = Device(address="DD0815") + self.dd4711 = Device(address="DD4711") - self.r01 = Receiver(name='Koenigsdf') - self.r02 = Receiver(name='Bene') + self.r01 = Receiver(name="Koenigsdf") + self.r02 = Receiver(name="Bene") db.session.add(self.dd0815) db.session.add(self.dd4711) @@ -26,8 +26,12 @@ class TestOGNrange(TestBaseDB): db.session.commit() # Create beacons and insert - self.ab01 = AircraftBeacon(name='FLRDD0815', receiver_name='Koenigsdf', device_id=self.dd0815.id, receiver_id=self.r01.id, timestamp='2017-12-10 10:00:00', location_mgrs_short='89ABC1267', altitude=800) - self.ab02 = AircraftBeacon(name='FLRDD0815', receiver_name='Koenigsdf', device_id=self.dd0815.id, receiver_id=self.r01.id, timestamp='2017-12-10 10:00:01', location_mgrs_short='89ABC1267', altitude=850) + self.ab01 = AircraftBeacon( + name="FLRDD0815", receiver_name="Koenigsdf", device_id=self.dd0815.id, receiver_id=self.r01.id, timestamp="2017-12-10 10:00:00", location_mgrs_short="89ABC1267", altitude=800 + ) + self.ab02 = AircraftBeacon( + name="FLRDD0815", receiver_name="Koenigsdf", device_id=self.dd0815.id, receiver_id=self.r01.id, timestamp="2017-12-10 10:00:01", location_mgrs_short="89ABC1267", altitude=850 + ) db.session.add(self.ab01) db.session.add(self.ab02) db.session.commit() @@ -38,11 +42,11 @@ class TestOGNrange(TestBaseDB): coverages = db.session.query(ReceiverCoverage).all() self.assertEqual(len(coverages), 1) coverage = coverages[0] - self.assertEqual(coverage.location_mgrs_short, '89ABC1267') + self.assertEqual(coverage.location_mgrs_short, "89ABC1267") self.assertEqual(coverage.receiver_id, self.r01.id) self.assertEqual(coverage.min_altitude, 800) self.assertEqual(coverage.max_altitude, 850) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/collect/test_stats.py b/tests/collect/test_stats.py index 2cfd541..a2215df 100644 --- a/tests/collect/test_stats.py +++ b/tests/collect/test_stats.py @@ -3,9 +3,9 @@ import unittest from tests.base import TestBaseDB, db -from ogn_python.model import AircraftBeacon, ReceiverBeacon, Receiver, Device, DeviceStats +from app.model import AircraftBeacon, ReceiverBeacon, Receiver, Device, DeviceStats -from ogn_python.collect.stats import create_device_stats +from app.collect.stats import create_device_stats class TestStats(TestBaseDB): @@ -13,21 +13,21 @@ class TestStats(TestBaseDB): super().setUp() # Prepare Beacons - self.ab01 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', timestamp='2017-12-10 10:00:01') - self.ab02 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', timestamp='2017-12-10 10:00:02') - self.ab03 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', timestamp='2017-12-10 10:00:03') - self.ab04 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', timestamp='2017-12-10 10:00:04') - self.ab05 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', timestamp='2017-12-10 10:00:05') - self.ab06 = AircraftBeacon(name='FLRDD4711', receiver_name='Koenigsdf', timestamp='2017-12-10 10:00:05') + self.ab01 = AircraftBeacon(name="FLRDD4711", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:01") + self.ab02 = AircraftBeacon(name="FLRDD4711", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:02") + self.ab03 = AircraftBeacon(name="FLRDD4711", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:03") + self.ab04 = AircraftBeacon(name="FLRDD4711", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:04") + self.ab05 = AircraftBeacon(name="FLRDD4711", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:05") + self.ab06 = AircraftBeacon(name="FLRDD4711", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:05") - self.rb01 = ReceiverBeacon(name='Koenigsdf', receiver_name='GLIDERN1', timestamp='2017-12-10 09:55:00', altitude=601, version='0.2.5', platform='ARM') - self.rb02 = ReceiverBeacon(name='Koenigsdf', receiver_name='GLIDERN1', timestamp='2017-12-10 10:00:00', altitude=601, version='0.2.7', platform='ARM') - self.rb03 = ReceiverBeacon(name='Koenigsdf', receiver_name='GLIDERN1', timestamp='2017-12-10 10:05:00', altitude=601, version='0.2.6', platform='ARM') + self.rb01 = ReceiverBeacon(name="Koenigsdf", receiver_name="GLIDERN1", timestamp="2017-12-10 09:55:00", altitude=601, version="0.2.5", platform="ARM") + self.rb02 = ReceiverBeacon(name="Koenigsdf", receiver_name="GLIDERN1", timestamp="2017-12-10 10:00:00", altitude=601, version="0.2.7", platform="ARM") + self.rb03 = ReceiverBeacon(name="Koenigsdf", receiver_name="GLIDERN1", timestamp="2017-12-10 10:05:00", altitude=601, version="0.2.6", platform="ARM") - self.r01 = Receiver(name='Koenigsdf') - self.r02 = Receiver(name='Bene') + self.r01 = Receiver(name="Koenigsdf") + self.r02 = Receiver(name="Bene") - self.d01 = Device(address='DD4711') + self.d01 = Device(address="DD4711") db.session.add(self.r01) db.session.add(self.d01) @@ -50,7 +50,7 @@ class TestStats(TestBaseDB): self.assertEqual(devicestats[0].max_altitude, None) self.assertEqual(devicestats[0].receiver_count, 1) self.assertEqual(devicestats[0].aircraft_beacon_count, 1) - self.assertEqual(devicestats[0].date, datetime.strptime('2017-12-10', '%Y-%m-%d').date()) + self.assertEqual(devicestats[0].date, datetime.strptime("2017-12-10", "%Y-%m-%d").date()) self.assertEqual(devicestats[0].firstseen, datetime(2017, 12, 10, 10, 0, 1)) self.assertEqual(devicestats[0].lastseen, datetime(2017, 12, 10, 10, 0, 1)) self.assertEqual(devicestats[0].aircraft_type, None) @@ -77,7 +77,7 @@ class TestStats(TestBaseDB): self.assertEqual(devicestats[0].max_altitude, 200) self.assertEqual(devicestats[0].receiver_count, 1) self.assertEqual(devicestats[0].aircraft_beacon_count, 2) - self.assertEqual(devicestats[0].date, datetime.strptime('2017-12-10', '%Y-%m-%d').date()) + self.assertEqual(devicestats[0].date, datetime.strptime("2017-12-10", "%Y-%m-%d").date()) self.assertEqual(devicestats[0].firstseen, datetime(2017, 12, 10, 10, 0, 1)) self.assertEqual(devicestats[0].lastseen, datetime(2017, 12, 10, 10, 0, 2)) self.assertEqual(devicestats[0].aircraft_type, 3) @@ -103,7 +103,7 @@ class TestStats(TestBaseDB): self.assertEqual(devicestats[0].max_altitude, 200) self.assertEqual(devicestats[0].receiver_count, 1) self.assertEqual(devicestats[0].aircraft_beacon_count, 2) - self.assertEqual(devicestats[0].date, datetime.strptime('2017-12-10', '%Y-%m-%d').date()) + self.assertEqual(devicestats[0].date, datetime.strptime("2017-12-10", "%Y-%m-%d").date()) self.assertEqual(devicestats[0].firstseen, datetime(2017, 12, 10, 10, 0, 1)) self.assertEqual(devicestats[0].lastseen, datetime(2017, 12, 10, 10, 0, 2)) self.assertEqual(devicestats[0].aircraft_type, 3) @@ -118,7 +118,7 @@ class TestStats(TestBaseDB): self.ab04.altitude = 250 self.ab04.software_version = 6.01 self.ab04.hardware_version = 15 - self.ab04.real_address = 'DDALFA' + self.ab04.real_address = "DDALFA" db.session.add(self.ab04) db.session.commit() @@ -131,14 +131,14 @@ class TestStats(TestBaseDB): self.assertEqual(devicestats[0].max_altitude, 250) self.assertEqual(devicestats[0].receiver_count, 2) self.assertEqual(devicestats[0].aircraft_beacon_count, 3) - self.assertEqual(devicestats[0].date, datetime.strptime('2017-12-10', '%Y-%m-%d').date()) + self.assertEqual(devicestats[0].date, datetime.strptime("2017-12-10", "%Y-%m-%d").date()) self.assertEqual(devicestats[0].firstseen, datetime(2017, 12, 10, 10, 0, 1)) self.assertEqual(devicestats[0].lastseen, datetime(2017, 12, 10, 10, 0, 4)) self.assertEqual(devicestats[0].aircraft_type, 3) self.assertEqual(devicestats[0].stealth, False) self.assertEqual(devicestats[0].software_version, 6.01) self.assertEqual(devicestats[0].hardware_version, 15) - self.assertEqual(devicestats[0].real_address, 'DDALFA') + self.assertEqual(devicestats[0].real_address, "DDALFA") # Compute 5. beacon: lower altitude, stealth self.ab05.device = self.d01 @@ -157,14 +157,14 @@ class TestStats(TestBaseDB): self.assertEqual(devicestats[0].max_altitude, 250) self.assertEqual(devicestats[0].receiver_count, 2) self.assertEqual(devicestats[0].aircraft_beacon_count, 4) - self.assertEqual(devicestats[0].date, datetime.strptime('2017-12-10', '%Y-%m-%d').date()) + self.assertEqual(devicestats[0].date, datetime.strptime("2017-12-10", "%Y-%m-%d").date()) self.assertEqual(devicestats[0].firstseen, datetime(2017, 12, 10, 10, 0, 1)) self.assertEqual(devicestats[0].lastseen, datetime(2017, 12, 10, 10, 0, 5)) self.assertEqual(devicestats[0].aircraft_type, 3) self.assertEqual(devicestats[0].stealth, True) self.assertEqual(devicestats[0].software_version, 6.01) self.assertEqual(devicestats[0].hardware_version, 15) - self.assertEqual(devicestats[0].real_address, 'DDALFA') + self.assertEqual(devicestats[0].real_address, "DDALFA") # Compute 6. beacon: beacon from past, greater altitude, newer version self.ab06.device = self.d01 @@ -184,15 +184,15 @@ class TestStats(TestBaseDB): self.assertEqual(devicestats[0].max_altitude, 300) self.assertEqual(devicestats[0].receiver_count, 2) self.assertEqual(devicestats[0].aircraft_beacon_count, 5) - self.assertEqual(devicestats[0].date, datetime.strptime('2017-12-10', '%Y-%m-%d').date()) + self.assertEqual(devicestats[0].date, datetime.strptime("2017-12-10", "%Y-%m-%d").date()) self.assertEqual(devicestats[0].firstseen, datetime(2017, 12, 10, 9, 59, 50)) self.assertEqual(devicestats[0].lastseen, datetime(2017, 12, 10, 10, 0, 5)) self.assertEqual(devicestats[0].aircraft_type, 3) self.assertEqual(devicestats[0].stealth, True) self.assertEqual(devicestats[0].software_version, 6.01) self.assertEqual(devicestats[0].hardware_version, 15) - self.assertEqual(devicestats[0].real_address, 'DDALFA') + self.assertEqual(devicestats[0].real_address, "DDALFA") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/collect/test_takeoff_landing.py b/tests/collect/test_takeoff_landing.py index 63b54e3..d099d7e 100644 --- a/tests/collect/test_takeoff_landing.py +++ b/tests/collect/test_takeoff_landing.py @@ -3,9 +3,9 @@ import unittest from tests.base import TestBaseDB, db -from ogn_python.model import TakeoffLanding +from app.model import TakeoffLanding -from ogn_python.collect.takeoff_landings import update_entries +from app.collect.takeoff_landings import update_entries class TestTakeoffLanding(TestBaseDB): @@ -24,63 +24,154 @@ class TestTakeoffLanding(TestBaseDB): def test_broken_rope(self): """Fill the db with a winch launch where the rope breaks. The algorithm should detect one takeoff and one landing.""" - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',604,'2016-07-02 10:47:12',0,0,0,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',605,'2016-07-02 10:47:32',0,0,-0.096520193,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:47:52',0,0,-0.096520193,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:48:12',0,0,-0.096520193,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000001B2FDD2406F12640E53C762AF3E94740',606,'2016-07-02 10:48:24',284,51.85598112,0.299720599,0.1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000F594AFDEBBF02640623583E5F5E94740',610,'2016-07-02 10:48:26',282,88.89596764,4.729489459,-0.2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000001C0DE02D90F026401564F188F7E94740',619,'2016-07-02 10:48:27',281,94.45196562,10.66294133,-0.3)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000ABF1D24D62F02640E12D90A0F8E94740',632,'2016-07-02 10:48:28',278,88.89596764,15.59055118,-0.7)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000069FD40CC38F02640C7925F2CF9E94740',650,'2016-07-02 10:48:29',273,83.33996966,18.90779782,-0.7)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000002709AF4A0FF02640C7925F2CF9E94740',670,'2016-07-02 10:48:30',272,79.63597101,20.72136144,-0.3)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000007AA85AF8E7EF2640C7925F2CF9E94740',691,'2016-07-02 10:48:31',269,79.63597101,21.02108204,-0.4)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000068DB43D5C2EF2640E12D90A0F8E94740',712,'2016-07-02 10:48:32',267,74.07997303,21.62560325,-0.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000EDA16AE19FEF2640FBC8C014F8E94740',728,'2016-07-02 10:48:33',266,68.52397506,12.36982474,-0.1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000000AFCCE1C7FEF26401564F188F7E94740',733,'2016-07-02 10:48:34',266,68.52397506,2.21488443,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000275633585EEF26402FFF21FDF6E94740',731,'2016-07-02 10:48:35',267,68.52397506,-3.916687833,0.2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000015891C3539EF26402FFF21FDF6E94740',726,'2016-07-02 10:48:36',270,74.07997303,-6.329692659,1.1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000E63FA4DFBEEE264078C1CDCFFAE94740',712,'2016-07-02 10:48:39',280,88.89596764,-2.611125222,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000004FF9EABD0BEE2640448B6CE7FBE94740',706,'2016-07-02 10:48:43',256,90.74796697,-0.198120396,-2.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000046B921B3A0ED264003E78C28EDE94740',706,'2016-07-02 10:48:46',218,92.59996629,-0.198120396,-1.6)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000005C58F3177ED2640900C4C81DFE94740',703,'2016-07-02 10:48:48',202,96.30396495,-1.402082804,-1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000211FF46C56ED26402650D7EDC6E94740',702,'2016-07-02 10:48:51',188,100.0079636,0.502921006,-1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000806DEA295FED2640347D898BB6E94740',704,'2016-07-02 10:48:53',166,100.0079636,0.802641605,-2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000337D898BB6ED26401383C0CAA1E94740',703,'2016-07-02 10:48:56',133,101.8599629,-1.803403607,-1.7)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000000C05593CE2ED2640FDF675E09CE94740',700,'2016-07-02 10:48:57',123,103.7119622,-2.611125222,-1.4)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000F0CCF1F778EE26409FA87F2394E94740',693,'2016-07-02 10:49:00',105,111.1199596,-2.809245618,-0.6)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000C9073D9B55EF2640BD5296218EE94740',687,'2016-07-02 10:49:04',97,112.9719589,-1.605283211,-0.1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000006F8104C5EF26400C24287E8CE94740',682,'2016-07-02 10:49:06',97,114.8239582,-2.407924816,-0.2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A0648535A8F02640F597DD9387E94740',676,'2016-07-02 10:49:10',97,118.5279569,-1.402082804,0.1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000D70FC48C03F22640621386EE7FE94740',672,'2016-07-02 10:49:16',97,116.6759575,-1.000762002,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A72C431CEBF22640CB7F48BF7DE94740',666,'2016-07-02 10:49:20',84,114.8239582,-1.605283211,-1.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000BFCAA145B6F32640BD5296218EE94740',662,'2016-07-02 10:49:24',49,111.1199596,-1.203962408,-1.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000074DA40A70DF4264077E09C11A5E94740',659,'2016-07-02 10:49:27',23,107.4159609,-1.402082804,-1.4)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009AE3EFF11CF42640347D898BB6E94740',656,'2016-07-02 10:49:29',4,101.8599629,-0.797561595,-1.8)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000074DA40A70DF426402650D7EDC6E94740',654,'2016-07-02 10:49:31',347,101.8599629,-1.706883414,-1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000156A4DF38EF3264086EE7F6DEAE94740',649,'2016-07-02 10:49:36',312,98.15596427,-1.503683007,-1.4)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000FAEDEBC039F32640E53C762AF3E94740',644,'2016-07-02 10:49:38',295,96.30396495,-3.012446025,-1.2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000B04A0F30E0F22640FBC8C014F8E94740',635,'2016-07-02 10:49:40',284,94.45196562,-5.125730251,-0.7)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000F38B25BF58F22640448B6CE7FBE94740',623,'2016-07-02 10:49:43',279,92.59996629,-2.809245618,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A5E8482EFFF12640DC1EAA16FEE94740',617,'2016-07-02 10:49:45',279,88.89596764,-3.312166624,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009F17012859F12640F0AAF40003EA4740',607,'2016-07-02 10:49:49',279,81.48797034,-1.300482601,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000004B5658830AF12640873E323005EA4740',607,'2016-07-02 10:49:51',278,74.07997303,-0.294640589,-0.1)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A0648535A8F0264006373FEB07EA4740',605,'2016-07-02 10:49:54',280,61.11597775,-0.096520193,0.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000C74B378941F02640E88C28ED0DEA4740',604,'2016-07-02 10:49:58',292,48.15198247,0.101600203,0.4)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000001B5A643BDFEF264045DB1EAA16EA4740',604,'2016-07-02 10:50:04',302,25.92799056,0.203200406,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000042D2948AB3EF264074029A081BEA4740',604,'2016-07-02 10:50:10',300,5.555997978,0.101600203,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000013AB192CAFEF264074029A081BEA4740',603,'2016-07-02 10:50:16',0,0,-0.096520193,0)") + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',604,'2016-07-02 10:47:12',0,0,0,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',605,'2016-07-02 10:47:32',0,0,-0.096520193,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:47:52',0,0,-0.096520193,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:48:12',0,0,-0.096520193,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000001B2FDD2406F12640E53C762AF3E94740',606,'2016-07-02 10:48:24',284,51.85598112,0.299720599,0.1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000F594AFDEBBF02640623583E5F5E94740',610,'2016-07-02 10:48:26',282,88.89596764,4.729489459,-0.2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000001C0DE02D90F026401564F188F7E94740',619,'2016-07-02 10:48:27',281,94.45196562,10.66294133,-0.3)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000ABF1D24D62F02640E12D90A0F8E94740',632,'2016-07-02 10:48:28',278,88.89596764,15.59055118,-0.7)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000069FD40CC38F02640C7925F2CF9E94740',650,'2016-07-02 10:48:29',273,83.33996966,18.90779782,-0.7)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000002709AF4A0FF02640C7925F2CF9E94740',670,'2016-07-02 10:48:30',272,79.63597101,20.72136144,-0.3)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000007AA85AF8E7EF2640C7925F2CF9E94740',691,'2016-07-02 10:48:31',269,79.63597101,21.02108204,-0.4)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000068DB43D5C2EF2640E12D90A0F8E94740',712,'2016-07-02 10:48:32',267,74.07997303,21.62560325,-0.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000EDA16AE19FEF2640FBC8C014F8E94740',728,'2016-07-02 10:48:33',266,68.52397506,12.36982474,-0.1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000000AFCCE1C7FEF26401564F188F7E94740',733,'2016-07-02 10:48:34',266,68.52397506,2.21488443,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000275633585EEF26402FFF21FDF6E94740',731,'2016-07-02 10:48:35',267,68.52397506,-3.916687833,0.2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000015891C3539EF26402FFF21FDF6E94740',726,'2016-07-02 10:48:36',270,74.07997303,-6.329692659,1.1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000E63FA4DFBEEE264078C1CDCFFAE94740',712,'2016-07-02 10:48:39',280,88.89596764,-2.611125222,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000004FF9EABD0BEE2640448B6CE7FBE94740',706,'2016-07-02 10:48:43',256,90.74796697,-0.198120396,-2.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000046B921B3A0ED264003E78C28EDE94740',706,'2016-07-02 10:48:46',218,92.59996629,-0.198120396,-1.6)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000005C58F3177ED2640900C4C81DFE94740',703,'2016-07-02 10:48:48',202,96.30396495,-1.402082804,-1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000211FF46C56ED26402650D7EDC6E94740',702,'2016-07-02 10:48:51',188,100.0079636,0.502921006,-1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000806DEA295FED2640347D898BB6E94740',704,'2016-07-02 10:48:53',166,100.0079636,0.802641605,-2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000337D898BB6ED26401383C0CAA1E94740',703,'2016-07-02 10:48:56',133,101.8599629,-1.803403607,-1.7)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000000C05593CE2ED2640FDF675E09CE94740',700,'2016-07-02 10:48:57',123,103.7119622,-2.611125222,-1.4)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000F0CCF1F778EE26409FA87F2394E94740',693,'2016-07-02 10:49:00',105,111.1199596,-2.809245618,-0.6)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000C9073D9B55EF2640BD5296218EE94740',687,'2016-07-02 10:49:04',97,112.9719589,-1.605283211,-0.1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000006F8104C5EF26400C24287E8CE94740',682,'2016-07-02 10:49:06',97,114.8239582,-2.407924816,-0.2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A0648535A8F02640F597DD9387E94740',676,'2016-07-02 10:49:10',97,118.5279569,-1.402082804,0.1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000D70FC48C03F22640621386EE7FE94740',672,'2016-07-02 10:49:16',97,116.6759575,-1.000762002,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A72C431CEBF22640CB7F48BF7DE94740',666,'2016-07-02 10:49:20',84,114.8239582,-1.605283211,-1.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000BFCAA145B6F32640BD5296218EE94740',662,'2016-07-02 10:49:24',49,111.1199596,-1.203962408,-1.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000074DA40A70DF4264077E09C11A5E94740',659,'2016-07-02 10:49:27',23,107.4159609,-1.402082804,-1.4)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009AE3EFF11CF42640347D898BB6E94740',656,'2016-07-02 10:49:29',4,101.8599629,-0.797561595,-1.8)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000074DA40A70DF426402650D7EDC6E94740',654,'2016-07-02 10:49:31',347,101.8599629,-1.706883414,-1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000156A4DF38EF3264086EE7F6DEAE94740',649,'2016-07-02 10:49:36',312,98.15596427,-1.503683007,-1.4)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000FAEDEBC039F32640E53C762AF3E94740',644,'2016-07-02 10:49:38',295,96.30396495,-3.012446025,-1.2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000B04A0F30E0F22640FBC8C014F8E94740',635,'2016-07-02 10:49:40',284,94.45196562,-5.125730251,-0.7)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000F38B25BF58F22640448B6CE7FBE94740',623,'2016-07-02 10:49:43',279,92.59996629,-2.809245618,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A5E8482EFFF12640DC1EAA16FEE94740',617,'2016-07-02 10:49:45',279,88.89596764,-3.312166624,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000009F17012859F12640F0AAF40003EA4740',607,'2016-07-02 10:49:49',279,81.48797034,-1.300482601,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000004B5658830AF12640873E323005EA4740',607,'2016-07-02 10:49:51',278,74.07997303,-0.294640589,-0.1)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000A0648535A8F0264006373FEB07EA4740',605,'2016-07-02 10:49:54',280,61.11597775,-0.096520193,0.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E6100000C74B378941F02640E88C28ED0DEA4740',604,'2016-07-02 10:49:58',292,48.15198247,0.101600203,0.4)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E61000001B5A643BDFEF264045DB1EAA16EA4740',604,'2016-07-02 10:50:04',302,25.92799056,0.203200406,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000042D2948AB3EF264074029A081BEA4740',604,'2016-07-02 10:50:10',300,5.555997978,0.101600203,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7','0101000020E610000013AB192CAFEF264074029A081BEA4740',603,'2016-07-02 10:50:16',0,0,-0.096520193,0)" + ) db.session.execute("UPDATE aircraft_beacons SET device_id = d.id, agl = altitude - 600 FROM devices d WHERE d.address='DDEFF7'") db.session.commit() # find the takeoff and the landing update_entries(db.session, start=datetime.datetime(2016, 7, 2, 0, 0, 0), end=datetime.datetime(2016, 7, 2, 23, 59, 59)) - takeoff_landing_query = db.session.query(TakeoffLanding) \ - .filter(db.between(TakeoffLanding.timestamp, datetime.datetime(2016, 7, 2, 0, 0, 0), datetime.datetime(2016, 7, 2, 23, 59, 59))) + takeoff_landing_query = db.session.query(TakeoffLanding).filter(db.between(TakeoffLanding.timestamp, datetime.datetime(2016, 7, 2, 0, 0, 0), datetime.datetime(2016, 7, 2, 23, 59, 59))) self.assertEqual(len(takeoff_landing_query.all()), 2) for entry in takeoff_landing_query.all(): - self.assertEqual(entry.airport.name, 'Koenigsdorf') + self.assertEqual(entry.airport.name, "Koenigsdorf") # we should not find the takeoff and the landing again update_entries(db.session, start=datetime.datetime(2016, 7, 2, 0, 0, 0), end=datetime.datetime(2016, 7, 2, 23, 59, 59)) @@ -89,88 +180,224 @@ class TestTakeoffLanding(TestBaseDB): def test_broken_rope_with_stall(self): """Here we have a broken rope where the glider passes again the threshold for take off.""" - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',597,'2019-04-13 09:20:14',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',595,'2019-04-13 09:20:23',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',595,'2019-04-13 09:20:29',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:21:01',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:21:02',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',589,'2019-04-13 09:21:13',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',589,'2019-04-13 09:21:29',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',590,'2019-04-13 09:21:48',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:22:02',0,0,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',592,'2019-04-13 09:22:22',0,0,0.1016,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000ED0DBE3099EA2640CA32C4B12EEA4740',593,'2019-04-13 09:22:40',102,25.925552,0.2032,0.60000002)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000026E4839ECDEA26401904560E2DEA4740',594,'2019-04-13 09:22:42',100,68.517532,0.2032,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D044D8F0F4EA2640513AB7F62BEA4740',595,'2019-04-13 09:22:43',101,81.480309,1.91008,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000025396A721EEB2640A00B49532AEA4740',600,'2019-04-13 09:22:44',100,90.739433,5.6337199,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000009E8B4814EEB2640CA41AA3B29EA4740',608,'2019-04-13 09:22:45',100,88.887611,9.2557602,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000087084327AEB264019133C9827EA4740',620,'2019-04-13 09:22:46',99,87.035782,12.3698,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000246416B4A3EB264052499D8026EA4740',634,'2019-04-13 09:22:47',97,83.33213,15.2908,-0.89999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000007958A835CDEB264067E4CDF425EA4740',650,'2019-04-13 09:22:48',94,79.628487,16.093439,-2.0999999)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CE4C3AB7F6EB264067E4CDF425EA4740',667,'2019-04-13 09:22:49',91,75.924835,16.89608,-0.89999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000248613AB19EC264067E4CDF425EA4740',684,'2019-04-13 09:22:50',91,72.221184,17.20088,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005C532ACE3EEC264067E4CDF425EA4740',701,'2019-04-13 09:22:51',90,68.517532,16.89608,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000003FF9C5925FEC264067E4CDF425EA4740',718,'2019-04-13 09:22:52',91,68.517532,16.19504,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000229F615780EC264052499D8026EA4740',733,'2019-04-13 09:22:53',89,59.258408,14.28496,-1.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B11D82BD9CEC264052499D8026EA4740',741,'2019-04-13 09:22:54',89,57.406582,3.62204,0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000789CA223B9EC264067E4CDF425EA4740',736,'2019-04-13 09:22:55',88,53.70293,-8.3413601,0.89999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B0AE00B9D7EC264052499D8026EA4740',724,'2019-04-13 09:22:56',89,62.962055,-14.5796,0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000E97B17DCFCEC264052499D8026EA4740',710,'2019-04-13 09:22:57',92,85.18396,-12.1666,1.8)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CC2A62EB2CED26408A7FFE6825EA4740',703,'2019-04-13 09:22:58',96,99.998558,-5.92836,2.0999999)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000936DEA295FED2640B5B55F5124EA4740',701,'2019-04-13 09:22:59',102,99.998558,0.40132001,2.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CB10C7BAB8ED2640D95F764F1EEA4740',704,'2019-04-13 09:23:01',116,92.591263,2.21488,5.6999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000002005593CE2ED2640AE38FBF019EA4740',707,'2019-04-13 09:23:02',133,88.887611,2.8143201,7.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000925CFE43FAED2640E77D426313EA4740',709,'2019-04-13 09:23:03',147,88.887611,1.50876,6.9000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CA65AD8E09EE26404BF9EABD0BEA4740',710,'2019-04-13 09:23:04',159,88.887611,0.60452002,6.9000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000003DF9EABD0BEE2640448B6CE7FBE94740',709,'2019-04-13 09:23:06',183,92.591263,-0.79755998,5.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005917B7D100EE2640CBA145B6F3E94740',707,'2019-04-13 09:23:07',192,94.443085,-2.1082001,3.3)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000076711B0DE0ED2640A098966BE4E94740',701,'2019-04-13 09:23:09',196,99.998558,-2.61112,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000AF25E4839EED2640E08D2B1BC3E94740',695,'2019-04-13 09:23:13',202,105.55404,0.1016,1.5)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000002152DD4931ED2640AF16FEF9A3E94740',696,'2019-04-13 09:23:17',214,103.70221,-0.39624,2.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000EA62C92F96EC264021BF58F28BE94740',696,'2019-04-13 09:23:21',236,105.55404,0.1016,2.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005CC2ABD203EC26404478557A80E94740',694,'2019-04-13 09:23:24',249,107.40586,-1.2039599,2.0999999)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000004182E2C798EB26402FEC0A907BE94740',690,'2019-04-13 09:23:26',256,111.10951,-2.2098,2.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000098A1F63EEEA26407DBD9CEC79E94740',685,'2019-04-13 09:23:29',268,114.81316,-1.00076,1.8)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D1915CFE43EA2640E11A79337DE94740',684,'2019-04-13 09:23:32',277,112.96133,-0.79755998,0.89999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000044BE55C4D6E926404478557A80E94740',682,'2019-04-13 09:23:34',280,114.81316,-2.0065999,0.60000002)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000029ED0DBE30E92640932B1BC389E94740',675,'2019-04-13 09:23:37',292,118.51682,-1.2039599,2.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D467FD40CCE826409AA87F2394E94740',675,'2019-04-13 09:23:39',307,114.81316,0.80264002,4.1999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D49AE61DA7E826404BC8073D9BE94740',677,'2019-04-13 09:23:40',316,112.96133,2.0116799,5.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000009CC420B072E826403D9B559FABE94740',680,'2019-04-13 09:23:42',339,103.70221,1.0058399,5.4000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000002A762AF369E8264019D3728DBCE94740',681,'2019-04-13 09:23:44',358,96.294907,0.2032,4.1999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000F1F44A5986E82640992A1895D4E94740',679,'2019-04-13 09:23:47',10,94.443085,-2.2098,0.89999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000007F2E244DA9E826401982BD9CECE94740',671,'2019-04-13 09:23:50',14,96.294907,-2.2098,0.60000002)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D3EFCCF1F7E8264099ACB00615EA4740',662,'2019-04-13 09:23:55',21,103.70221,-2.7127199,1.2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000028B1759646E92640513AB7F62BEA4740',655,'2019-04-13 09:23:58',40,103.70221,-1.905,4.1999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000009A99999999E9264059B71B5736EA4740',652,'2019-04-13 09:24:00',60,99.998558,-1.2039599,5.0999999)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000448B6CE7FBE9264091DE96B53AEA4740',649,'2019-04-13 09:24:02',78,98.146736,-2.5095201,4.1999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000000AA4BA9362EA264091DE96B53AEA4740',643,'2019-04-13 09:24:04',93,98.146736,-2.8092401,3)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B4958DE1C4EA26402E81BA6E37EA4740',636,'2019-04-13 09:24:06',100,98.146736,-3.71856,1.2)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005D6DC5FEB2EB2640B597933D2FEA4740',619,'2019-04-13 09:24:11',100,94.443085,-3.71856,-0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005BB1BFEC9EEC2640EEDCDAAF28EA4740',602,'2019-04-13 09:24:16',98,96.294907,-2.7127199,0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B003E78C28ED2640A01A2FDD24EA4740',598,'2019-04-13 09:24:19',98,88.887611,-0.70104003,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000009298966BE4ED26408A8EE4F21FEA4740',597,'2019-04-13 09:24:24',100,59.258408,-0.096519999,0.30000001)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000075C601E130EE2640EEFAA6C31DEA4740',596,'2019-04-13 09:24:28',86,25.925552,0,-4.1999998)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000091B1E4174BEE26408A8EE4F21FEA4740',597,'2019-04-13 09:24:31',66,14.814602,-0.096519999,-3)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000001F27563358EE26402722222222EA4740',597,'2019-04-13 09:24:38',0,0,0.1016,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CAFFDAD453EE26402722222222EA4740',598,'2019-04-13 09:24:58',0,0,0.1016,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000586C9DA551EE26402722222222EA4740',597,'2019-04-13 09:25:18',0,0,0.1016,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000003098A1F63EE2640EEEBC03923EA4740',596,'2019-04-13 09:25:36',54,1.8518252,0.1016,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000003CDF1F778EE2640A01A2FDD24EA4740',594,'2019-04-13 09:25:48',76,1.8518252,-0.096519999,0)") - db.session.execute("INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000001FF46C567DEE2640A01A2FDD24EA4740',593,'2019-04-13 09:25:59',0,0,-0.096519999,0)") + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',597,'2019-04-13 09:20:14',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',595,'2019-04-13 09:20:23',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',595,'2019-04-13 09:20:29',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:21:01',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:21:02',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',589,'2019-04-13 09:21:13',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',589,'2019-04-13 09:21:29',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',590,'2019-04-13 09:21:48',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:22:02',0,0,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',592,'2019-04-13 09:22:22',0,0,0.1016,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000ED0DBE3099EA2640CA32C4B12EEA4740',593,'2019-04-13 09:22:40',102,25.925552,0.2032,0.60000002)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000026E4839ECDEA26401904560E2DEA4740',594,'2019-04-13 09:22:42',100,68.517532,0.2032,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D044D8F0F4EA2640513AB7F62BEA4740',595,'2019-04-13 09:22:43',101,81.480309,1.91008,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000025396A721EEB2640A00B49532AEA4740',600,'2019-04-13 09:22:44',100,90.739433,5.6337199,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000009E8B4814EEB2640CA41AA3B29EA4740',608,'2019-04-13 09:22:45',100,88.887611,9.2557602,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000087084327AEB264019133C9827EA4740',620,'2019-04-13 09:22:46',99,87.035782,12.3698,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000246416B4A3EB264052499D8026EA4740',634,'2019-04-13 09:22:47',97,83.33213,15.2908,-0.89999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000007958A835CDEB264067E4CDF425EA4740',650,'2019-04-13 09:22:48',94,79.628487,16.093439,-2.0999999)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CE4C3AB7F6EB264067E4CDF425EA4740',667,'2019-04-13 09:22:49',91,75.924835,16.89608,-0.89999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000248613AB19EC264067E4CDF425EA4740',684,'2019-04-13 09:22:50',91,72.221184,17.20088,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005C532ACE3EEC264067E4CDF425EA4740',701,'2019-04-13 09:22:51',90,68.517532,16.89608,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000003FF9C5925FEC264067E4CDF425EA4740',718,'2019-04-13 09:22:52',91,68.517532,16.19504,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000229F615780EC264052499D8026EA4740',733,'2019-04-13 09:22:53',89,59.258408,14.28496,-1.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B11D82BD9CEC264052499D8026EA4740',741,'2019-04-13 09:22:54',89,57.406582,3.62204,0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000789CA223B9EC264067E4CDF425EA4740',736,'2019-04-13 09:22:55',88,53.70293,-8.3413601,0.89999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B0AE00B9D7EC264052499D8026EA4740',724,'2019-04-13 09:22:56',89,62.962055,-14.5796,0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000E97B17DCFCEC264052499D8026EA4740',710,'2019-04-13 09:22:57',92,85.18396,-12.1666,1.8)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CC2A62EB2CED26408A7FFE6825EA4740',703,'2019-04-13 09:22:58',96,99.998558,-5.92836,2.0999999)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000936DEA295FED2640B5B55F5124EA4740',701,'2019-04-13 09:22:59',102,99.998558,0.40132001,2.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CB10C7BAB8ED2640D95F764F1EEA4740',704,'2019-04-13 09:23:01',116,92.591263,2.21488,5.6999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000002005593CE2ED2640AE38FBF019EA4740',707,'2019-04-13 09:23:02',133,88.887611,2.8143201,7.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000925CFE43FAED2640E77D426313EA4740',709,'2019-04-13 09:23:03',147,88.887611,1.50876,6.9000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CA65AD8E09EE26404BF9EABD0BEA4740',710,'2019-04-13 09:23:04',159,88.887611,0.60452002,6.9000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000003DF9EABD0BEE2640448B6CE7FBE94740',709,'2019-04-13 09:23:06',183,92.591263,-0.79755998,5.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005917B7D100EE2640CBA145B6F3E94740',707,'2019-04-13 09:23:07',192,94.443085,-2.1082001,3.3)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000076711B0DE0ED2640A098966BE4E94740',701,'2019-04-13 09:23:09',196,99.998558,-2.61112,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000AF25E4839EED2640E08D2B1BC3E94740',695,'2019-04-13 09:23:13',202,105.55404,0.1016,1.5)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000002152DD4931ED2640AF16FEF9A3E94740',696,'2019-04-13 09:23:17',214,103.70221,-0.39624,2.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000EA62C92F96EC264021BF58F28BE94740',696,'2019-04-13 09:23:21',236,105.55404,0.1016,2.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005CC2ABD203EC26404478557A80E94740',694,'2019-04-13 09:23:24',249,107.40586,-1.2039599,2.0999999)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000004182E2C798EB26402FEC0A907BE94740',690,'2019-04-13 09:23:26',256,111.10951,-2.2098,2.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000098A1F63EEEA26407DBD9CEC79E94740',685,'2019-04-13 09:23:29',268,114.81316,-1.00076,1.8)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D1915CFE43EA2640E11A79337DE94740',684,'2019-04-13 09:23:32',277,112.96133,-0.79755998,0.89999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000044BE55C4D6E926404478557A80E94740',682,'2019-04-13 09:23:34',280,114.81316,-2.0065999,0.60000002)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000029ED0DBE30E92640932B1BC389E94740',675,'2019-04-13 09:23:37',292,118.51682,-1.2039599,2.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D467FD40CCE826409AA87F2394E94740',675,'2019-04-13 09:23:39',307,114.81316,0.80264002,4.1999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D49AE61DA7E826404BC8073D9BE94740',677,'2019-04-13 09:23:40',316,112.96133,2.0116799,5.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000009CC420B072E826403D9B559FABE94740',680,'2019-04-13 09:23:42',339,103.70221,1.0058399,5.4000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000002A762AF369E8264019D3728DBCE94740',681,'2019-04-13 09:23:44',358,96.294907,0.2032,4.1999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000F1F44A5986E82640992A1895D4E94740',679,'2019-04-13 09:23:47',10,94.443085,-2.2098,0.89999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000007F2E244DA9E826401982BD9CECE94740',671,'2019-04-13 09:23:50',14,96.294907,-2.2098,0.60000002)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000D3EFCCF1F7E8264099ACB00615EA4740',662,'2019-04-13 09:23:55',21,103.70221,-2.7127199,1.2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000028B1759646E92640513AB7F62BEA4740',655,'2019-04-13 09:23:58',40,103.70221,-1.905,4.1999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000009A99999999E9264059B71B5736EA4740',652,'2019-04-13 09:24:00',60,99.998558,-1.2039599,5.0999999)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000448B6CE7FBE9264091DE96B53AEA4740',649,'2019-04-13 09:24:02',78,98.146736,-2.5095201,4.1999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000000AA4BA9362EA264091DE96B53AEA4740',643,'2019-04-13 09:24:04',93,98.146736,-2.8092401,3)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B4958DE1C4EA26402E81BA6E37EA4740',636,'2019-04-13 09:24:06',100,98.146736,-3.71856,1.2)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005D6DC5FEB2EB2640B597933D2FEA4740',619,'2019-04-13 09:24:11',100,94.443085,-3.71856,-0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000005BB1BFEC9EEC2640EEDCDAAF28EA4740',602,'2019-04-13 09:24:16',98,96.294907,-2.7127199,0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000B003E78C28ED2640A01A2FDD24EA4740',598,'2019-04-13 09:24:19',98,88.887611,-0.70104003,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000009298966BE4ED26408A8EE4F21FEA4740',597,'2019-04-13 09:24:24',100,59.258408,-0.096519999,0.30000001)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000075C601E130EE2640EEFAA6C31DEA4740',596,'2019-04-13 09:24:28',86,25.925552,0,-4.1999998)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000091B1E4174BEE26408A8EE4F21FEA4740',597,'2019-04-13 09:24:31',66,14.814602,-0.096519999,-3)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000001F27563358EE26402722222222EA4740',597,'2019-04-13 09:24:38',0,0,0.1016,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000CAFFDAD453EE26402722222222EA4740',598,'2019-04-13 09:24:58',0,0,0.1016,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E6100000586C9DA551EE26402722222222EA4740',597,'2019-04-13 09:25:18',0,0,0.1016,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000003098A1F63EE2640EEEBC03923EA4740',596,'2019-04-13 09:25:36',54,1.8518252,0.1016,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E610000003CDF1F778EE2640A01A2FDD24EA4740',594,'2019-04-13 09:25:48',76,1.8518252,-0.096519999,0)" + ) + db.session.execute( + "INSERT INTO aircraft_beacons(name, receiver_name, address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','0101000020E61000001FF46C567DEE2640A01A2FDD24EA4740',593,'2019-04-13 09:25:59',0,0,-0.096519999,0)" + ) db.session.execute("UPDATE aircraft_beacons SET device_id = d.id, agl = altitude - 600 FROM devices d WHERE d.address='DDAC7C'") db.session.commit() # find the takeoff and the landing update_entries(db.session, start=datetime.datetime(2019, 4, 13, 0, 0, 0), end=datetime.datetime(2019, 4, 13, 23, 59, 59)) - takeoff_landings = db.session.query(TakeoffLanding) \ - .filter(db.between(TakeoffLanding.timestamp, datetime.datetime(2019, 4, 13, 0, 0, 0), datetime.datetime(2019, 4, 13, 23, 59, 59))) \ - .all() + takeoff_landings = db.session.query(TakeoffLanding).filter(db.between(TakeoffLanding.timestamp, datetime.datetime(2019, 4, 13, 0, 0, 0), datetime.datetime(2019, 4, 13, 23, 59, 59))).all() self.assertEqual(len(takeoff_landings), 2) for entry in takeoff_landings: - self.assertEqual(entry.airport.name, 'Koenigsdorf') + self.assertEqual(entry.airport.name, "Koenigsdorf") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/commands/test_database.py b/tests/commands/test_database.py index 91c6c18..f82e39d 100644 --- a/tests/commands/test_database.py +++ b/tests/commands/test_database.py @@ -3,21 +3,21 @@ import os from tests.base import TestBaseDB, db -from ogn_python.model import DeviceInfo -from ogn_python.commands.database import import_file +from app.model import DeviceInfo +from app.commands.database import import_file -from ogn_python import app +from app import app class TestDatabase(TestBaseDB): def test_import_ddb_file(self): runner = app.test_cli_runner() - result = runner.invoke(import_file, [os.path.dirname(__file__) + '/../custom_ddb.txt']) + result = runner.invoke(import_file, [os.path.dirname(__file__) + "/../custom_ddb.txt"]) self.assertEqual(result.exit_code, 0) device_infos = db.session.query(DeviceInfo).all() self.assertEqual(len(device_infos), 6) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/model/all_classes.py b/tests/model/all_classes.py index 350e18e..09af68a 100644 --- a/tests/model/all_classes.py +++ b/tests/model/all_classes.py @@ -3,16 +3,16 @@ import os import unittest import inspect -os.environ['OGN_CONFIG_MODULE'] = 'config/test.py' +os.environ["OGN_CONFIG_MODULE"] = "config/test.py" -import ogn_python.model # noqa: E402 +import app.model # noqa: E402 class TestStringMethods(unittest.TestCase): def test_string(self): try: - for name, obj in inspect.getmembers(ogn_python.model): + for name, obj in inspect.getmembers(app.model): print("Testing: {}".format(name)) if inspect.isclass(obj): print(obj()) @@ -20,5 +20,5 @@ class TestStringMethods(unittest.TestCase): raise AssertionError(e) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/model/test_device.py b/tests/model/test_device.py index e6ec6f7..436589f 100644 --- a/tests/model/test_device.py +++ b/tests/model/test_device.py @@ -3,14 +3,14 @@ import datetime import unittest from tests.base import TestBaseDB, db -from ogn_python.model import Device, DeviceInfo +from app.model import Device, DeviceInfo class TestStringMethods(TestBaseDB): def test_device_info(self): - device = Device(name='FLRDD0815', address='DD0815') - device_info1 = DeviceInfo(address='DD0815', address_origin=1, registration='D-0815') - device_info2 = DeviceInfo(address='DD0815', address_origin=2, registration='15') + device = Device(name="FLRDD0815", address="DD0815") + device_info1 = DeviceInfo(address="DD0815", address_origin=1, registration="D-0815") + device_info2 = DeviceInfo(address="DD0815", address_origin=2, registration="15") db.session.add(device) db.session.add(device_info1) @@ -20,10 +20,10 @@ class TestStringMethods(TestBaseDB): self.assertEqual(device.info, device_info1) def test_expiry_date(self): - device = Device(name='FLRDD0815', address='DD0815', software_version=6.42) + device = Device(name="FLRDD0815", address="DD0815", software_version=6.42) self.assertEqual(device.expiry_date(), datetime.date(2019, 10, 31)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_utils.py b/tests/test_utils.py index 166cf26..30af1b6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,8 +2,8 @@ import os import unittest from datetime import date -from ogn_python.model import AircraftType -from ogn_python.utils import get_days, get_ddb, get_trackable, get_airports +from app.model import AircraftType +from app.utils import get_days, get_ddb, get_trackable, get_airports class TestStringMethods(unittest.TestCase): @@ -18,27 +18,27 @@ class TestStringMethods(unittest.TestCase): self.assertGreater(len(devices), 1000) def test_get_ddb_from_file(self): - devices = get_ddb(os.path.dirname(__file__) + '/custom_ddb.txt') + devices = get_ddb(os.path.dirname(__file__) + "/custom_ddb.txt") self.assertEqual(len(devices), 6) device = devices[0] - self.assertEqual(device.address, 'DD4711') - self.assertEqual(device.aircraft, 'HK36 TTC') - self.assertEqual(device.registration, 'D-EULE') - self.assertEqual(device.competition, 'CU') + self.assertEqual(device.address, "DD4711") + self.assertEqual(device.aircraft, "HK36 TTC") + self.assertEqual(device.registration, "D-EULE") + self.assertEqual(device.competition, "CU") self.assertTrue(device.tracked) self.assertTrue(device.identified) self.assertEqual(device.aircraft_type, AircraftType.glider_or_motor_glider) def test_get_trackable(self): - devices = get_ddb(os.path.dirname(__file__) + '/custom_ddb.txt') + devices = get_ddb(os.path.dirname(__file__) + "/custom_ddb.txt") trackable = get_trackable(devices) self.assertEqual(len(trackable), 4) - self.assertIn('FLRDD4711', trackable) - self.assertIn('FLRDD0815', trackable) - self.assertIn('OGNDEADBE', trackable) - self.assertIn('ICA999999', trackable) + self.assertIn("FLRDD4711", trackable) + self.assertIn("FLRDD0815", trackable) + self.assertIn("OGNDEADBE", trackable) + self.assertIn("ICA999999", trackable) def test_get_airports(self): - airports = get_airports(os.path.dirname(__file__) + '/SeeYou.cup') + airports = get_airports(os.path.dirname(__file__) + "/SeeYou.cup") self.assertGreater(len(airports), 1000)