Merge pull request #151 from Meisterschueler/hr

Massive scheme modifications
pull/78/head
Meisterschueler 2020-11-22 09:03:09 +01:00 zatwierdzone przez GitHub
commit 8fab57bcf1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
133 zmienionych plików z 180538 dodań i 3933 usunięć

Wyświetl plik

@ -1,21 +1,39 @@
sudo: false
language: python
services:
- redis
- postgresql
addons:
postgresql: "11.2"
before_install:
- sudo service postgresql stop
- sudo apt-get update
- sudo apt-get remove -y postgresql\*
- sudo apt-get install postgresql-11-postgis-2.5 postgresql-client
- sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf
- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf
- sudo service postgresql start 11
install:
- pip install --upgrade pip
- pip install tox
before_script:
- sudo add-apt-repository ppa:timescale/timescaledb-ppa -y
- sudo apt-get update -q
- sudo apt-get install -y timescaledb-postgresql-11 timescaledb-tools
- sudo timescaledb-tune -yes -pg-version 11
- sudo service postgresql restart 11
- psql -U postgres -c 'CREATE DATABASE ogn_test;'
- psql -U postgres -c 'CREATE EXTENSION postgis;'
script:
- tox
addons:
postgresql: 9.6
apt:
packages:
- postgresql-9.6-postgis-2.4
matrix:
include:
@ -28,3 +46,8 @@ matrix:
after_success:
- tox -e codecov
after_failure:
- echo "Job failed..."
- sudo cat /var/log/syslog
- psql --version

Wyświetl plik

@ -34,7 +34,7 @@ It requires [PostgreSQL](http://www.postgresql.org/), [PostGIS](http://www.postg
4. Install [PostgreSQL](http://www.postgresql.org/) with [PostGIS](http://www.postgis.net/) and [TimescaleDB](https://www.timescale.com) Extension.
Create a database (use "ogn" as default, otherwise you have to modify the configuration, see below)
5. Optional: Install redis for asynchronous tasks (like takeoff/landing-detection)
5. Install redis for asynchronous tasks (like database feeding, takeoff/landing-detection, ...)
```
apt-get install redis-server
@ -53,13 +53,7 @@ It requires [PostgreSQL](http://www.postgresql.org/), [PostGIS](http://www.postg
./flask database init
```
8. Optional: Prepare tables for TimescaleDB
```
./flask database init_timescaledb
```
9. Optional: Import world border dataset (needed if you want to know the country a receiver belongs to, etc.)
8. Optional: Import world border dataset (needed if you want to know the country a receiver belongs to, etc.)
Get the [World Borders Dataset](http://thematicmapping.org/downloads/world_borders.php) and unpack it.
Then import it into your database (we use "ogn" as database name).
@ -69,40 +63,30 @@ It requires [PostgreSQL](http://www.postgresql.org/), [PostGIS](http://www.postg
psql -d ogn -c "DROP TABLE world_borders_temp;"
```
10. Get world elevation data (needed for AGL calculation)
9. Get world elevation data (needed for AGL calculation)
Sources: There are many sources for DEM data. It is important that the spatial reference system (SRID) is the same as the database which is 4326.
The [GMTED2010 Viewer](https://topotools.cr.usgs.gov/gmted_viewer/viewer.htm) provides data for the world with SRID 4326. Just download the data you need.
For Europe we can get the DEM as GeoTIFF files from the [European Environment Agency](https://land.copernicus.eu/imagery-in-situ/eu-dem/eu-dem-v1.1).
Because the SRID of these files is 3035 and we want 4326 we have to convert them (next step)
11. Optional: Convert the elevation data into correct SRID
We convert elevation from one SRID (here: 3035) to target SRID (4326):
10. Import the GeoTIFF into the elevation table:
```
gdalwarp -s_srs "EPSG:3035" -t_srs "EPSG:4326" source.tif target.tif
```
12. Import the GeoTIFF into the elevation table:
```
raster2pgsql -s 4326 -c -C -I -M -t 100x100 elevation_data.tif public.elevation | psql -d ogn
raster2pgsql *.tif -s 4326 -d -M -C -I -F -t 25x25 public.elevation | psql -d ogn
```
13. Import Airports (needed for takeoff and landing calculation). A cup file is provided under tests:
11. Import Airports (needed for takeoff and landing calculation). A cup file is provided under tests:
```
flask database import_airports tests/SeeYou.cup
```
14. Import DDB (needed for registration signs in the logbook).
12. Import DDB (needed for registration signs in the logbook).
```
flask database import_ddb
```
15. Optional: Use supervisord
13. Optional: Use supervisord
You can use [Supervisor](http://supervisord.org/) to control the complete system. In the directory deployment/supervisor
we have some configuration files to feed the database (ogn-feed), run the celery worker (celeryd), the celery beat
(celerybeatd), the celery monitor (flower), and the python wsgi server (gunicorn). All files assume that
@ -128,13 +112,13 @@ The following scripts run in the foreground and should be deamonized
- Start a task server (make sure redis is up and running)
```
celery -A app.collect worker -l info
celery -A celery_app worker -l info
```
- Start the task scheduler (make sure a task server is up and running)
```
celery -A app.collect beat -l info
celery -A celery_app beat -l info
```
### Flask - Command Line Interface
@ -173,14 +157,10 @@ Most commands are command groups, so if you execute this command you will get fu
### Available tasks
- `app.collect.celery.update_takeoff_landings` - Compute takeoffs and landings.
- `app.collect.celery.update_logbook_entries` - Add/update logbook entries.
- `app.collect.celery.update_logbook_max_altitude` - Add max altitudes in logbook when flight is complete (takeoff and landing).
- `app.collect.celery.import_ddb` - Import registered devices from the DDB.
- `app.collect.celery.update_receivers_country_code` - Update country code in receivers table if None.
- `app.collect.celery.purge_old_data` - Delete AircraftBeacons and ReceiverBeacons older than given 'age'.
- `app.collect.celery.update_stats` - Create stats and update receivers/devices with stats.
- `app.collect.celery.update_ognrange` - Create receiver coverage stats for Melissas ognrange.
- `app.tasks.update_takeoff_landings` - Compute takeoffs and landings.
- `app.tasks.celery.update_logbook_entries` - Add/update logbook entries.
- `app.tasks.celery.update_logbook_max_altitude` - Add max altitudes in logbook when flight is complete (takeoff and landing).
- `app.tasks.celery.import_ddb` - Import registered devices from the DDB.
If the task server is up and running, tasks could be started manually. Here we compute takeoffs and landings for the past 90 minutes:
@ -190,5 +170,18 @@ python3
>>>update_takeoff_landings.delay(last_minutes=90)
```
or directly from command line:
```
celery -A celery_app call takeoff_landings
```
## Notes for Raspberry Pi
For matplotlib we need several apt packages installed:
```
apt install libatlas3-base libopenjp2-7 libtiff5
```
## License
Licensed under the [AGPLv3](LICENSE).

Wyświetl plik

@ -1,36 +1,61 @@
import os
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_caching import Cache
from celery import Celery
from flask_redis import FlaskRedis
from config import configs
bootstrap = Bootstrap()
db = SQLAlchemy()
migrate = Migrate()
cache = Cache()
celery = Celery(__name__, broker='redis://localhost:6379/0')
redis_client = FlaskRedis()
celery = Celery()
def create_app(config_name='development'):
def create_app(config_name='default'):
# Initialize Flask
app = Flask(__name__)
# Load the configuration
if config_name == 'testing':
app.config.from_object('app.config.test')
else:
app.config.from_object('app.config.default')
configuration = configs[config_name]
app.config.from_object(configuration)
app.config.from_envvar("OGN_CONFIG_MODULE", silent=True)
celery.config_from_object(app.config)
# Initialize other things
bootstrap.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
cache.init_app(app)
redis_client.init_app(app)
init_celery(app)
register_blueprints(app)
return app
def register_blueprints(app):
from app.main import bp as bp_main
app.register_blueprint(bp_main)
return app
def init_celery(app=None):
app = app or create_app(os.getenv('FLASK_CONFIG') or 'default')
celery.conf.broker_url = app.config['BROKER_URL']
celery.conf.result_backend = app.config['CELERY_RESULT_BACKEND']
celery.conf.update(app.config)
class ContextTask(celery.Task):
"""Make celery tasks work with Flask app context"""
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)
celery.Task = ContextTask
return celery

Wyświetl plik

@ -1,103 +0,0 @@
from datetime import datetime, timezone
from app.model import AircraftBeacon, ReceiverBeacon
from app import db
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('<?xml version="1.0" encoding="UTF-8"?>')
lines.append("<markers>")
lines.append('<m e="0"/>')
for receiver_beacon in last_seen_query:
if receiver_beacon.location is None or receiver_beacon.name.startswith("FNB"):
continue
lines.append(
'<m a="{0}" b="{1:.7f}" c="{2:.7f}" d="{3:1d}"/>'.format(
receiver_beacon.name, receiver_beacon.location.latitude, receiver_beacon.location.longitude, receiver_beacon.timestamp < min_online_timestamp
)
)
lines.append("</markers>")
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('<?xml version="1.0" encoding="UTF-8"?>')
lines.append("<markers>")
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(
' <m a="{0:.7f},{1:.7f},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13}"/>'.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("</markers>")
xml = "\n".join(lines)
return xml

Wyświetl plik

@ -1,8 +1,6 @@
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
@ -26,11 +24,11 @@ def stations2_filtered_pl(start, end):
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"),
db.label("lt", db.func.round(db.func.ST_Y(Receiver.location_wkt) * 10000) / 10000),
db.label("lg", db.func.round(db.func.ST_X(Receiver.location_wkt) * 10000) / 10000),
db.case([(Receiver.lastseen > last_10_minutes, "U")], else_="D").label("u"),
Receiver.lastseen.label("ut"),
label("v", Receiver.version + "." + Receiver.platform),
db.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)))
@ -44,10 +42,10 @@ def stations2_filtered_pl(start, end):
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))
db.session.query(db.func.right(ReceiverCoverage.location_mgrs_short, 4), db.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))
.group_by(db.func.right(ReceiverCoverage.location_mgrs_short, 4))
)
res = {"t": squares, "p": ["{}/{}".format(r[0], r[1]) for r in query.all()]}

Wyświetl plik

@ -1,106 +0,0 @@
import datetime
from celery.utils.log import get_task_logger
from app.collect.takeoff_landings import update_entries as takeoff_update_entries
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 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 app.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.ognrange import update_entries as receiver_coverage_update_entries
from app import db
from app import celery
logger = get_task_logger(__name__)
@celery.task(name="update_takeoff_landings")
def update_takeoff_landings(last_minutes):
"""Compute takeoffs and landings."""
end = datetime.datetime.utcnow()
start = end - datetime.timedelta(minutes=last_minutes)
result = takeoff_update_entries(session=db.session, start=start, end=end, logger=logger)
return result
@celery.task(name="update_logbook_entries")
def update_logbook_entries(day_offset):
"""Add/update logbook entries."""
date = datetime.datetime.today() + datetime.timedelta(days=day_offset)
result = logbook_update_entries(session=db.session, date=date, logger=logger)
return result
@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)."""
date = datetime.datetime.today() + datetime.timedelta(days=day_offset)
result = logbook_update_max_altitudes(session=db.session, date=date, logger=logger)
return result
@celery.task(name="import_ddb")
def import_ddb():
"""Import registered devices from the DDB."""
result = device_infos_import_ddb(session=db.session, logger=logger)
return result
@celery.task(name="update_receivers_country_code")
def update_receivers_country_code():
"""Update country code in receivers table if None."""
result = receivers_update_country_code(session=db.session, logger=logger)
return result
@celery.task(name="purge_old_data")
def purge_old_data(max_hours):
"""Delete AircraftBeacons and ReceiverBeacons older than given 'age'."""
from app.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()
receiver_beacons_deleted = db.session.query(ReceiverBeacon).filter(ReceiverBeacon.timestamp < min_timestamp).delete()
db.session.commit()
result = "{} AircraftBeacons deleted, {} ReceiverBeacons deleted".format(aircraft_beacons_deleted, receiver_beacons_deleted)
return result
@celery.task(name="update_stats")
def update_stats(day_offset):
"""Create stats and update receivers/devices with stats."""
date = datetime.datetime.today() + datetime.timedelta(days=day_offset)
create_device_stats(session=db.session, date=date)
update_device_stats_jumps(session=db.session, date=date)
create_receiver_stats(session=db.session, date=date)
create_relation_stats(session=db.session, date=date)
update_qualities(session=db.session, date=date)
update_receivers(session=db.session)
update_devices(session=db.session)
@celery.task(name="update_ognrange")
def update_ognrange(day_offset):
"""Create receiver coverage stats for Melissas ognrange."""
date = datetime.datetime.today() + datetime.timedelta(days=day_offset)
receiver_coverage_update_entries(session=db.session, date=date)

Wyświetl plik

@ -1,12 +1,12 @@
from sqlalchemy.sql import null, and_, func, case
from sqlalchemy.dialects.postgresql import insert
from flask import current_app
from app.model import Country, DeviceInfo, DeviceInfoOrigin, Receiver
from app import db
from app.model import SenderInfo, SenderInfoOrigin, Receiver
from app.utils import get_ddb, get_flarmnet
def upsert(session, model, rows, update_cols):
def upsert(model, rows, update_cols):
"""Insert rows in model. On conflicting update columns if new value IS NOT NULL."""
table = model.__table__
@ -14,59 +14,40 @@ 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: db.case([(getattr(stmt.excluded, k) != db.null(), getattr(stmt.excluded, k))], else_=getattr(model, k)) for k in update_cols}
)
# print(compile_query(on_conflict_stmt))
session.execute(on_conflict_stmt)
return on_conflict_stmt
def update_device_infos(session, address_origin, path=None):
if address_origin == DeviceInfoOrigin.FLARMNET:
def update_device_infos(address_origin, path=None):
if address_origin == SenderInfoOrigin.FLARMNET:
device_infos = get_flarmnet(fln_file=path)
else:
device_infos = get_ddb(csv_file=path)
session.query(DeviceInfo).filter(DeviceInfo.address_origin == address_origin).delete(synchronize_session="fetch")
session.commit()
db.session.query(SenderInfo).filter(SenderInfo.address_origin == address_origin).delete(synchronize_session="fetch")
db.session.commit()
for device_info in device_infos:
device_info.address_origin = address_origin
session.bulk_save_objects(device_infos)
session.commit()
db.session.bulk_save_objects(device_infos)
db.session.commit()
return len(device_infos)
def import_ddb(session, logger=None):
def import_ddb(logger=None):
"""Import registered devices from the DDB."""
if logger is None:
logger = current_app.logger
logger.info("Import registered devices fom the DDB...")
counter = update_device_infos(session, DeviceInfoOrigin.OGN_DDB)
counter = update_device_infos(SenderInfoOrigin.OGN_DDB)
finish_message = "DeviceInfo: {} inserted.".format(counter)
logger.info(finish_message)
return finish_message
def update_country_code(session, logger=None):
"""Update country code in receivers table if None."""
if logger is None:
logger = current_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")
)
session.commit()
finish_message = "Receivers (country): {} updated".format(update_receivers)
finish_message = "SenderInfo: {} inserted.".format(counter)
logger.info(finish_message)
return finish_message

Wyświetl plik

@ -0,0 +1,116 @@
from datetime import date
from app import db
NOTHING = ""
CONTEST_RELEVANT = "AND agl < 1000"
LOW_PASS = "AND agl < 50 and ground_speed > 250"
def compute_flights(date, flight_type=0):
if flight_type == 0:
filter = NOTHING
elif flight_type == 1:
filter = CONTEST_RELEVANT
elif flight_type == 2:
filter = LOW_PASS
date_str = date.strftime("%Y-%m-%d")
query = f"""
INSERT INTO flights(date, sender_id, flight_type, multilinestring, simple_multilinestring)
SELECT '{date_str}' AS date,
s.id AS sender_id,
{flight_type} as flight_type,
st_collect(sq5.linestring order BY sq5.part) multilinestring,
st_collect(st_simplify(sq5.linestring, 0.0001) ORDER BY sq5.part) simple_multilinestring
FROM (
SELECT sq4.name,
sq4.part,
st_makeline(sq4.location ORDER BY sq4.timestamp) AS linestring
FROM (
SELECT sq3.timestamp,
sq3.location,
sq3.name,
SUM(sq3.ping) OVER (partition BY sq3.name ORDER BY sq3.timestamp) AS part
FROM (
SELECT sq2.t1 AS timestamp,
sq2.l1 AS location,
sq2.s1 AS name,
CASE
WHEN sq2.s1 = sq2.s2 AND sq2.t1 - sq2.t2 < interval'100s' AND ST_DistanceSphere(sq2.l1, sq2.l2) < 1000 THEN 0
ELSE 1
END AS ping
FROM (
SELECT sq.timestamp t1,
lag(sq.timestamp) OVER (partition BY sq.name ORDER BY sq.timestamp) t2,
sq.location l1,
lag(sq.location) OVER (partition BY sq.name ORDER BY sq.timestamp) l2,
sq.name s1,
lag(sq.name) OVER (partition BY sq.name ORDER BY sq.timestamp) s2
FROM (
SELECT DISTINCT ON (name, timestamp) name, timestamp, location
FROM sender_positions
WHERE reference_timestamp BETWEEN '{date_str} 00:00:00' AND '{date_str} 23:59:59' {filter}
ORDER BY name, timestamp, error_count
) AS sq
) AS sq2
) AS sq3
) AS sq4
GROUP BY sq4.name, sq4.part
) AS sq5
INNER JOIN senders AS s ON sq5.name = s.name
GROUP BY s.id
ON CONFLICT DO NOTHING;
"""
db.session.execute(query)
db.session.commit()
def compute_gaps(date):
date_str = date.strftime("%Y-%m-%d")
query = f"""
INSERT INTO flights(date, flight_type, sender_id, multilinestring)
SELECT '{date_str}' AS date,
3 AS flight_type,
s.id AS sender_id,
ST_Collect(sq3.path)
FROM (
SELECT sq2.s1 AS name,
ST_MakeLine(sq2.l1, sq2.l2) AS path
FROM
(
SELECT sq.timestamp t1,
LAG(sq.timestamp) OVER (PARTITION BY sq.timestamp::DATE, sq.name ORDER BY sq.timestamp) t2,
sq.location l1,
LAG(sq.location) OVER (PARTITION BY sq.timestamp::DATE, sq.name ORDER BY sq.timestamp) l2,
sq.name s1,
LAG(sq.name) OVER (PARTITION BY sq.timestamp::DATE, sq.name ORDER BY sq.timestamp) s2
FROM
(
SELECT DISTINCT ON (name, timestamp) name, timestamp, location, agl
FROM sender_positions
WHERE reference_timestamp BETWEEN '{date_str} 00:00:00' AND '{date_str} 23:59:59' AND agl > 300
ORDER BY name, timestamp, error_count
) AS sq
) AS sq2
WHERE EXTRACT(epoch FROM sq2.t1 - sq2.t2) > 300
AND ST_DistanceSphere(sq2.l1, sq2.l2) / EXTRACT(epoch FROM sq2.t1 - sq2.t2) BETWEEN 15 AND 50
) AS sq3
INNER JOIN senders AS s on sq3.name = s.name
GROUP BY s.id
ON CONFLICT DO NOTHING;
"""
db.session.execute(query)
db.session.commit()
if __name__ == '__main__':
from app import create_app
app = create_app()
with app.app_context():
result = compute_flights(date=date(2020, 10, 28))
print(result)

Wyświetl plik

@ -0,0 +1,24 @@
from datetime import datetime
from flask import current_app
from app import redis_client
from app.gateway.message_handling import sender_position_csv_strings_to_db, receiver_position_csv_strings_to_db, receiver_status_csv_strings_to_db
def transfer_from_redis_to_database():
def unmapping(string):
return string[0].decode('utf-8')
receiver_status_data = list(map(unmapping, redis_client.zpopmin('receiver_status', 100000)))
receiver_position_data = list(map(unmapping, redis_client.zpopmin('receiver_position', 100000)))
sender_status_data = list(map(unmapping, redis_client.zpopmin('sender_status', 100000)))
sender_position_data = list(map(unmapping, redis_client.zpopmin('sender_position', 100000)))
receiver_status_csv_strings_to_db(lines=receiver_status_data)
receiver_position_csv_strings_to_db(lines=receiver_position_data)
sender_position_csv_strings_to_db(lines=sender_position_data)
current_app.logger.debug(f"transfer_from_redis_to_database: rx_stat: {len(receiver_status_data):6d}\trx_pos: {len(receiver_position_data):6d}\ttx_stat: {len(sender_status_data):6d}\ttx_pos: {len(sender_position_data):6d}")
finish_message = f"Database: {len(receiver_status_data)+len(receiver_position_data)+len(sender_status_data)+len(sender_position_data)} inserted"
return finish_message

Wyświetl plik

@ -1,199 +1,382 @@
from sqlalchemy import and_, or_, insert, update, exists, between
from sqlalchemy.sql import func, null
from sqlalchemy.sql.expression import true, false
from sqlalchemy.dialects.postgresql import insert # special insert for upsert ("ON CONFLICT ...")
from flask import current_app
from app.model import TakeoffLanding, Logbook, AircraftBeacon
from app.model import Airport, Country, SenderPosition, Sender, TakeoffLanding, Logbook
from app.utils import date_to_timestamps
from datetime import datetime, timedelta
def update_entries(session, date, logger=None):
"""Add/update logbook entries."""
from app import db
if logger is None:
logger = current_app.logger
logger.info("Compute logbook.")
# takeoff / landing detection is based on 3 consecutive points
MIN_TAKEOFF_SPEED = 55 # takeoff detection: 1st point below, 2nd and 3rd above this limit
MAX_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_SINK_RATE = 5 # landing detection: glider should not climb too much
MAX_EVENT_DURATION = 100 # the points must not exceed this duration
MAX_EVENT_RADIUS = 5000 # the points must not exceed this radius around the 2nd point
MAX_EVENT_AGL = 200 # takeoff / landing must not exceed this altitude AGL
# 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
def update_takeoff_landings(start, end):
"""Compute takeoffs and landings."""
current_app.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)
current_app.logger.warn(abort_message)
return abort_message
# check if we have any airport
airports_query = db.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."
current_app.logger.warn(abort_message)
return abort_message
# get beacons for selected time range (+ buffer for duration), one per name and timestamp
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))
db.session.query(SenderPosition.name, SenderPosition.timestamp, SenderPosition.location, SenderPosition.track, db.func.coalesce(SenderPosition.ground_speed, 0.0).label("ground_speed"), SenderPosition.altitude, db.func.coalesce(SenderPosition.climb_rate, 0.0).label("climb_rate"))
.distinct(SenderPosition.name, SenderPosition.timestamp)
.order_by(SenderPosition.name, SenderPosition.timestamp, SenderPosition.error_count)
.filter(SenderPosition.agl <= MAX_EVENT_AGL)
.filter(db.between(SenderPosition.reference_timestamp, start - timedelta(seconds=MAX_EVENT_DURATION), end + timedelta(seconds=MAX_EVENT_DURATION)))
.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()))
# make a query with current, previous and next position
sq2 = db.session.query(
sq.c.name,
db.func.lag(sq.c.name).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("name_prev"),
db.func.lead(sq.c.name).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("name_next"),
sq.c.timestamp,
db.func.lag(sq.c.timestamp).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("timestamp_prev"),
db.func.lead(sq.c.timestamp).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("timestamp_next"),
sq.c.location,
db.func.lag(sq.c.location).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("location_wkt_prev"),
db.func.lead(sq.c.location).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("location_wkt_next"),
sq.c.track,
db.func.lag(sq.c.track).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("track_prev"),
db.func.lead(sq.c.track).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("track_next"),
sq.c.ground_speed,
db.func.lag(sq.c.ground_speed).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("ground_speed_prev"),
db.func.lead(sq.c.ground_speed).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("ground_speed_next"),
sq.c.altitude,
db.func.lag(sq.c.altitude).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("altitude_prev"),
db.func.lead(sq.c.altitude).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("altitude_next"),
sq.c.climb_rate,
db.func.lag(sq.c.climb_rate).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("climb_rate_prev"),
db.func.lead(sq.c.climb_rate).over(partition_by=sq.c.name, order_by=sq.c.timestamp).label("climb_rate_next"),
).subquery()
# find landings without start
# consider only positions between start and end and with predecessor and successor and limit distance and duration between points
sq3 = (
db.session.query(sq2)
.filter(db.and_(sq2.c.name_prev != db.null(), sq2.c.name_next != db.null()))
.filter(db.and_(db.func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_prev) < MAX_EVENT_RADIUS, db.func.ST_DistanceSphere(sq2.c.location, sq2.c.location_wkt_next) < MAX_EVENT_RADIUS))
.filter(sq2.c.timestamp_next - sq2.c.timestamp_prev < timedelta(seconds=MAX_EVENT_DURATION))
.filter(db.between(sq2.c.timestamp, start, end))
.subquery()
)
# find possible takeoffs and landings
sq4 = (
db.session.query(
sq3.c.timestamp,
db.case(
[
(sq3.c.ground_speed > MIN_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 <= MIN_TAKEOFF_SPEED, sq3.c.location),
]
).label("location"),
db.case([(sq3.c.ground_speed > MAX_LANDING_SPEED, sq3.c.track), (sq3.c.ground_speed <= MAX_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,
db.case([(sq3.c.ground_speed > MIN_TAKEOFF_SPEED, True), (sq3.c.ground_speed < MAX_LANDING_SPEED, False)]).label("is_takeoff"),
sq3.c.name,
)
.filter(
db.or_(
db.and_(sq3.c.ground_speed_prev < MIN_TAKEOFF_SPEED, sq3.c.ground_speed > MIN_TAKEOFF_SPEED, sq3.c.ground_speed_next > MIN_TAKEOFF_SPEED, sq3.c.climb_rate > MIN_TAKEOFF_CLIMB_RATE), # takeoff
db.and_(sq3.c.ground_speed_prev > MAX_LANDING_SPEED, sq3.c.ground_speed < MAX_LANDING_SPEED, sq3.c.ground_speed_next < MAX_LANDING_SPEED, sq3.c.climb_rate < MAX_LANDING_SINK_RATE), # landing
)
)
.subquery()
)
# get the sender id instead of the name and consider them if the are near airports ...
sq5 = (
db.session.query(
sq4.c.timestamp, sq4.c.track, sq4.c.is_takeoff, Sender.id.label("sender_id"), Airport.id.label("airport_id"), db.func.ST_DistanceSphere(sq4.c.location, Airport.location_wkt).label("airport_distance"), Airport.country_code
)
.filter(db.and_(db.func.ST_Within(sq4.c.location, Airport.border),
db.between(Airport.style, 2, 5)))
.filter(sq4.c.name == Sender.name)
.subquery()
)
# ... and take the nearest airport
sq6 = (
db.session.query(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.sender_id, sq5.c.airport_id, sq5.c.country_code)
.distinct(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.sender_id)
.order_by(sq5.c.timestamp, sq5.c.track, sq5.c.is_takeoff, sq5.c.sender_id, sq5.c.airport_distance)
.subquery()
)
# ... add the country
takeoff_landing_query = (
db.session.query(sq6.c.timestamp, sq6.c.track, sq6.c.is_takeoff, sq6.c.sender_id, sq6.c.airport_id, Country.gid)
.join(Country, sq6.c.country_code == Country.iso2, isouter=True)
.subquery()
)
# ... and save them
ins = insert(TakeoffLanding) \
.from_select((TakeoffLanding.timestamp, TakeoffLanding.track, TakeoffLanding.is_takeoff, TakeoffLanding.sender_id, TakeoffLanding.airport_id, TakeoffLanding.country_id), takeoff_landing_query) \
.on_conflict_do_nothing(index_elements=[TakeoffLanding.timestamp, TakeoffLanding.sender_id, TakeoffLanding.airport_id])
result = db.session.execute(ins)
db.session.commit()
insert_counter = result.rowcount
finish_message = "TakeoffLandings: {} inserted".format(insert_counter)
current_app.logger.info(finish_message)
return finish_message
def update_logbook(offset_days=None):
"""Add/update logbook entries."""
current_app.logger.info("Compute logbook.")
# limit time range to given date and set window partition and window order
if offset_days:
(start, end) = date_to_timestamps(datetime.utcnow() - timedelta(days=offset_days))
else:
(start, end) = date_to_timestamps(datetime.utcnow().date())
pa = TakeoffLanding.sender_id
wo = db.and_(TakeoffLanding.sender_id, TakeoffLanding.timestamp, TakeoffLanding.airport_id)
# make a query with previous, current and next "takeoff_landing" event, so we can find complete flights
sq = (
db.session.query(
TakeoffLanding.sender_id,
db.func.lag(TakeoffLanding.sender_id).over(partition_by=pa, order_by=wo).label("sender_id_prev"),
db.func.lead(TakeoffLanding.sender_id).over(partition_by=pa, order_by=wo).label("sender_id_next"),
TakeoffLanding.timestamp,
db.func.lag(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_prev"),
db.func.lead(TakeoffLanding.timestamp).over(partition_by=pa, order_by=wo).label("timestamp_next"),
TakeoffLanding.track,
db.func.lag(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_prev"),
db.func.lead(TakeoffLanding.track).over(partition_by=pa, order_by=wo).label("track_next"),
TakeoffLanding.is_takeoff,
db.func.lag(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_prev"),
db.func.lead(TakeoffLanding.is_takeoff).over(partition_by=pa, order_by=wo).label("is_takeoff_next"),
TakeoffLanding.airport_id,
db.func.lag(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_prev"),
db.func.lead(TakeoffLanding.airport_id).over(partition_by=pa, order_by=wo).label("airport_id_next")
)
.subquery()
)
# find (new) starts without landing
only_starts_query = (
db.session.query(
sq.c.sender_id.label("sender_id"),
sq.c.timestamp.label("takeoff_timestamp"),
sq.c.track.label("takeoff_track"),
sq.c.airport_id.label("takeoff_airport_id")
)
.filter(sq.c.is_takeoff == db.true())
.filter(db.or_(sq.c.is_takeoff_next == db.true(), sq.c.is_takeoff_next == db.null()))
.filter(~Logbook.query.filter(db.and_(Logbook.sender_id == sq.c.sender_id, Logbook.takeoff_timestamp == sq.c.timestamp, Logbook.takeoff_airport_id == sq.c.airport_id)).exists())
)
ins = insert(Logbook).from_select(
(
Logbook.sender_id,
Logbook.takeoff_timestamp,
Logbook.takeoff_track,
Logbook.takeoff_airport_id
),
only_starts_query,
)
result = db.session.execute(ins)
current_app.logger.debug(f"Added {result.rowcount} starts")
db.session.commit()
# find (new) 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"),
db.session.query(
sq.c.sender_id.label("sender_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()))
.filter(db.or_(sq.c.is_takeoff_prev == db.false(), sq.c.is_takeoff_prev == db.null()))
.filter(sq.c.is_takeoff == db.false())
.filter(~Logbook.query.filter(db.and_(Logbook.sender_id == sq.c.sender_id, Logbook.landing_timestamp == sq.c.timestamp, Logbook.landing_airport_id == sq.c.airport_id)).exists())
)
ins = insert(Logbook).from_select(
(
Logbook.sender_id,
Logbook.landing_timestamp,
Logbook.landing_track,
Logbook.landing_airport_id
),
only_landings_query,
)
result = db.session.execute(ins)
current_app.logger.debug(f"Added {result.rowcount} landings")
db.session.commit()
# find starts without landing
only_starts_query = (
session.query(
sq.c.timestamp.label("reftime"),
sq.c.device_id.label("device_id"),
# find complete flights
complete_flight_query = (
db.session.query(
sq.c.sender_id.label("sender_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"),
sq.c.timestamp_next.label("landing_timestamp"),
sq.c.track_next.label("landing_track"),
sq.c.airport_id_next.label("landing_airport_id"),
)
.filter(sq.c.is_takeoff == true())
.filter(or_(sq.c.is_takeoff_next == true(), sq.c.is_takeoff_next == null()))
.filter(sq.c.is_takeoff == db.true())
.filter(sq.c.is_takeoff_next == db.false())
.subquery()
)
# 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,
}
)
# insert (new) flights
new_flights_query = (
db.session.query(complete_flight_query)
.filter(~Logbook.query.filter(db.and_(Logbook.sender_id == complete_flight_query.c.sender_id, Logbook.landing_timestamp == complete_flight_query.c.landing_timestamp, Logbook.landing_airport_id == complete_flight_query.c.landing_airport_id)).exists())
.filter(~Logbook.query.filter(db.and_(Logbook.sender_id == complete_flight_query.c.sender_id, Logbook.takeoff_timestamp == complete_flight_query.c.takeoff_timestamp, Logbook.takeoff_airport_id == complete_flight_query.c.takeoff_airport_id)).exists())
)
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.sender_id,
Logbook.takeoff_timestamp,
Logbook.takeoff_track,
Logbook.takeoff_airport_id,
Logbook.landing_timestamp,
Logbook.landing_track,
Logbook.landing_airport_id,
Logbook.landing_airport_id
),
new_logbook_entries,
new_flights_query
)
result = db.session.execute(ins)
current_app.logger.debug(f"Added {result.rowcount} complete flights")
db.session.commit()
result = session.execute(ins)
insert_counter = result.rowcount
session.commit()
# update existing landing with takeoff from complete flight
upd = db.update(Logbook) \
.where(db.and_(
Logbook.sender_id == complete_flight_query.c.sender_id,
Logbook.takeoff_timestamp == db.null(),
Logbook.takeoff_airport_id == db.null(),
Logbook.landing_timestamp != db.null(),
Logbook.landing_timestamp == complete_flight_query.c.landing_timestamp,
Logbook.landing_airport_id == complete_flight_query.c.landing_airport_id
)) \
.values(takeoff_timestamp=complete_flight_query.c.takeoff_timestamp,
takeoff_track=complete_flight_query.c.takeoff_track,
takeoff_airport_id=complete_flight_query.c.takeoff_airport_id)
result = db.session.execute(upd)
current_app.logger.debug(f"Updated {result.rowcount} takeoffs to complete flights")
db.session.commit()
finish_message = "Logbook: {} inserted, {} updated".format(insert_counter, update_counter)
logger.debug(finish_message)
return finish_message
# update existing takeoff with landing from complete flight
upd = db.update(Logbook) \
.where(db.and_(
Logbook.sender_id == complete_flight_query.c.sender_id,
Logbook.takeoff_timestamp != db.null(),
Logbook.takeoff_timestamp == complete_flight_query.c.takeoff_timestamp,
Logbook.takeoff_airport_id == complete_flight_query.c.takeoff_airport_id,
Logbook.landing_timestamp == db.null(),
Logbook.landing_airport_id == db.null()
)) \
.values(landing_timestamp=complete_flight_query.c.landing_timestamp,
landing_track=complete_flight_query.c.landing_track,
landing_airport_id=complete_flight_query.c.landing_airport_id)
result = db.session.execute(upd)
current_app.logger.debug(f"Updated {result.rowcount} landings to complete flights")
db.session.commit()
return
def update_max_altitudes(session, date, logger=None):
def update_max_altitudes():
MAX_UPDATES = 60
query = """
UPDATE logbooks
SET max_altitude = sq2.max_altitude
FROM (
SELECT sq.logbook_id, MAX(sp.altitude) AS max_altitude
FROM (
SELECT
l.id AS logbook_id, s.name, l.takeoff_timestamp, l.landing_timestamp
FROM logbooks AS l
INNER JOIN senders AS s ON l.sender_id = s.id
WHERE
l.takeoff_timestamp IS NOT NULL
AND l.landing_timestamp IS NOT NULL
AND l.max_altitude IS NULL
LIMIT 1
) AS sq,
sender_positions AS sp
WHERE sp.reference_timestamp BETWEEN sq.takeoff_timestamp AND sq.landing_timestamp
AND sp.name = sq.name
GROUP BY sq.logbook_id
) AS sq2
WHERE logbooks.id = sq2.logbook_id;
"""
update_counter = 0
for _ in range(MAX_UPDATES):
result = db.session.execute(query)
db.session.commit()
return update_counter
def update_max_altitudes_orm():
"""Add max altitudes in logbook when flight is complete (takeoff and landing)."""
if logger is None:
logger = current_app.logger
logger.info("Update logbook max altitude.")
if session is None:
session = current_app.session
(start, end) = date_to_timestamps(date)
current_app.logger.info("Update logbook max altitude.")
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))
db.session.query(Logbook.id, Sender.name)
.filter(db.and_(Logbook.takeoff_timestamp != db.null(), Logbook.landing_timestamp != db.null(), Logbook.max_altitude == db.null()))
.filter(Logbook.sender_id == Sender.id)
.limit(1)
.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))
db.session.query(logbook_entries.c.id, db.func.max(SenderPosition.altitude).label("max_altitude"))
.filter(db.and_(db.between_(SenderPosition.timestamp >= Logbook.takeoff_timestamp, SenderPosition.timestamp <= Logbook.landing_timestamp), SenderPosition.name == logbook_entries.c.name))
.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")
update_logbooks = db.session.query(Logbook).filter(Logbook.id == max_altitudes.c.id).update({Logbook.max_altitude: max_altitudes.c.max_altitude}, synchronize_session="fetch")
session.commit()
db.session.commit()
finish_message = "Logbook (altitude): {} entries updated.".format(update_logbook)
logger.info(finish_message)
finish_message = "Logbook (altitude): {} entries updated.".format(update_logbooks)
return finish_message
if __name__ == '__main__':
from app import create_app
app = create_app()
with app.app_context():
result = update_takeoff_landings(start=datetime(2020, 11, 9, 10, 0, 0), end=datetime(2020, 11, 9, 15, 30, 0))
result = update_logbook()
result = update_max_altitudes_orm()
print(result)

Wyświetl plik

@ -1,104 +0,0 @@
from sqlalchemy import Date
from sqlalchemy import and_, insert, update, exists, between
from sqlalchemy.sql import func, null
from flask import current_app
from app.model import AircraftBeacon, Receiver, ReceiverCoverage
from app.utils import date_to_timestamps
def update_entries(session, date, logger=None):
"""Create receiver coverage stats for Melissas ognrange."""
if logger is None:
logger = current_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_name, AircraftBeacon.signal_quality, AircraftBeacon.altitude, AircraftBeacon.address)
.filter(and_(between(AircraftBeacon.timestamp, start, end), AircraftBeacon.location_mgrs_short != null(), AircraftBeacon.receiver_name != null(), AircraftBeacon.address != null()))
.subquery()
)
# ... and group them by reduced MGRS, receiver and date
sq2 = (
session.query(
sq.c.location_mgrs_short,
sq.c.receiver_name,
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.address)).label("device_count"),
)
.group_by(sq.c.location_mgrs_short, sq.c.receiver_name)
.subquery()
)
# Replace receiver_name with receiver_id
sq3 = (
session.query(
sq2.c.location_mgrs_short,
Receiver.id.label("receiver_id"),
sq2.c.date,
sq2.c.max_signal_quality,
sq2.c.min_altitude,
sq2.c.max_altitude,
sq2.c.aircraft_beacon_count,
sq2.c.device_count,
)
.filter(sq2.c.receiver_name == Receiver.name)
.subquery()
)
# if a receiver coverage entry exist --> update it
upd = (
update(ReceiverCoverage)
.where(and_(ReceiverCoverage.location_mgrs_short == sq3.c.location_mgrs_short, ReceiverCoverage.receiver_id == sq3.c.receiver_id, ReceiverCoverage.date == date))
.values(
{
"max_signal_quality": sq3.c.max_signal_quality,
"min_altitude": sq3.c.min_altitude,
"max_altitude": sq3.c.max_altitude,
"aircraft_beacon_count": sq3.c.aircraft_beacon_count,
"device_count": sq3.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(sq3).filter(
~exists().where(and_(ReceiverCoverage.location_mgrs_short == sq3.c.location_mgrs_short, ReceiverCoverage.receiver_id == sq3.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

Wyświetl plik

@ -1,450 +0,0 @@
from flask import current_app
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
# 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 = current_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 = current_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 = current_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 = current_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 = current_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 = current_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 = current_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 = current_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)

Wyświetl plik

@ -1,145 +0,0 @@
from datetime import timedelta
from flask import current_app
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, Device, TakeoffLanding, Airport
def update_entries(session, start, end, logger=None):
"""Compute takeoffs and landings."""
if logger is None:
logger = current_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 address and timestamp
sq = (
session.query(AircraftBeacon)
.distinct(AircraftBeacon.address, AircraftBeacon.timestamp)
.order_by(AircraftBeacon.address, 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.address,
func.lag(sq.c.address).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("address_prev"),
func.lead(sq.c.address).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("address_next"),
sq.c.timestamp,
func.lag(sq.c.timestamp).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("timestamp_prev"),
func.lead(sq.c.timestamp).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("timestamp_next"),
sq.c.location,
func.lag(sq.c.location).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("location_wkt_prev"),
func.lead(sq.c.location).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("location_wkt_next"),
sq.c.track,
func.lag(sq.c.track).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("track_prev"),
func.lead(sq.c.track).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("track_next"),
sq.c.ground_speed,
func.lag(sq.c.ground_speed).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("ground_speed_prev"),
func.lead(sq.c.ground_speed).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("ground_speed_next"),
sq.c.altitude,
func.lag(sq.c.altitude).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("altitude_prev"),
func.lead(sq.c.altitude).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("altitude_next"),
sq.c.climb_rate,
func.lag(sq.c.climb_rate).over(partition_by=sq.c.address, order_by=sq.c.timestamp).label("climb_rate_prev"),
func.lead(sq.c.climb_rate).over(partition_by=sq.c.address, 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.address_prev != null(), sq2.c.address_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.address,
)
.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()
)
# get the device id instead of the address and consider them if the are near airports ...
sq5 = (
session.query(
sq4.c.timestamp, sq4.c.track, sq4.c.is_takeoff, Device.id.label("device_id"), Airport.id.label("airport_id"), func.ST_DistanceSphere(sq4.c.location, Airport.location_wkt).label("airport_distance")
)
.filter(and_(sq4.c.address == Device.address,
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

Wyświetl plik

@ -0,0 +1,167 @@
from app import db
from app.utils import get_sql_trustworthy
SQL_TRUSTWORTHY = get_sql_trustworthy(source_table_alias='sp')
def create_views():
db.session.execute("""
DROP VIEW IF EXISTS receiver_ranking CASCADE;
CREATE VIEW receiver_ranking AS
SELECT
r.name AS receiver_name,
r.id AS receiver_id,
MAX(rs.max_distance) AS max_distance,
SUM(rs.max_normalized_quality * rs.messages_count) / SUM(rs.messages_count) AS max_normalized_quality,
SUM(rs.messages_count) AS messages_count,
COUNT(DISTINCT rs.sender_id) AS senders_count,
COUNT(DISTINCT rs.location_mgrs_short) AS coverage_count
FROM coverage_statistics AS rs
INNER JOIN receivers AS r ON rs.receiver_id = r.id
WHERE rs.date = NOW()::date AND rs.is_trustworthy IS TRUE AND rs.max_distance IS NOT NULL
GROUP BY rs.date, r.name, r.id
ORDER BY max_distance DESC;
""")
db.session.execute("""
DROP VIEW IF EXISTS sender_ranking CASCADE;
CREATE VIEW sender_ranking AS
SELECT
s.name,
s.id AS sender_id,
MAX(rs.max_distance) AS max_distance,
SUM(rs.max_normalized_quality * rs.messages_count) / SUM(rs.messages_count) AS max_normalized_quality,
SUM(rs.messages_count) AS messages_count,
COUNT(DISTINCT rs.receiver_id) AS receivers_count,
COUNT(DISTINCT rs.location_mgrs_short) AS coverage_count
FROM coverage_statistics AS rs
INNER JOIN senders AS s ON rs.sender_id = s.id
WHERE rs.date = NOW()::date AND rs.is_trustworthy IS TRUE AND rs.max_distance IS NOT NULL
GROUP BY rs.date, s.name, s.id
ORDER BY max_distance DESC;
""")
db.session.commit()
def create_timescaledb_views():
# 1. Since the reference_timestamps are strictly increasing we can set
# the parameter 'refresh_lag' to a very short time so the materialization
# starts right after the bucket is finished
# 2. The feature realtime aggregation from TimescaleDB is quite time consuming.
# So we set materialized_only=true
# --- Sender statistics ---
# These stats will be used in the daily ranking, so we make the bucket < 1d
db.session.execute(f"""
DROP VIEW IF EXISTS sender_stats_1h CASCADE;
CREATE VIEW sender_stats_1h
WITH (timescaledb.continuous, timescaledb.materialized_only=true, timescaledb.refresh_lag='5 minutes') AS
SELECT
time_bucket(INTERVAL '1 hour', sp.reference_timestamp) AS bucket,
sp.name,
({SQL_TRUSTWORTHY}) AS is_trustworthy,
COUNT(sp.*) AS beacon_count,
MAX(sp.distance) AS max_distance,
MIN(sp.altitude) AS min_altitude,
MAX(sp.altitude) AS max_altitude
FROM sender_positions AS sp
GROUP BY bucket, sp.name, is_trustworthy;
""")
# ... and just for curiosity also bucket = 1d
db.session.execute(f"""
DROP VIEW IF EXISTS sender_stats_1d CASCADE;
CREATE VIEW sender_stats_1d
WITH (timescaledb.continuous, timescaledb.materialized_only=true, timescaledb.refresh_lag='1 hour') AS
SELECT
time_bucket(INTERVAL '1 day', sp.reference_timestamp) AS bucket,
sp.name,
({SQL_TRUSTWORTHY}) AS is_trustworthy,
COUNT(sp.*) AS beacon_count,
MAX(sp.distance) AS max_distance,
MIN(sp.altitude) AS min_altitude,
MAX(sp.altitude) AS max_altitude
FROM sender_positions AS sp
GROUP BY bucket, sp.name, is_trustworthy;
""")
# --- Receiver statistics ---
# These stats will be used in the daily ranking, so we make the bucket < 1d
db.session.execute(f"""
DROP VIEW IF EXISTS receiver_stats_1h CASCADE;
CREATE VIEW receiver_stats_1h
WITH (timescaledb.continuous, timescaledb.materialized_only=true, timescaledb.refresh_lag='5 minutes') AS
SELECT
time_bucket(INTERVAL '1 hour', sp.reference_timestamp) AS bucket,
sp.receiver_name,
({SQL_TRUSTWORTHY}) AS is_trustworthy,
COUNT(sp.*) AS beacon_count,
MAX(sp.distance) AS max_distance,
MIN(sp.altitude) AS min_altitude,
MAX(sp.altitude) AS max_altitude
FROM sender_positions AS sp
GROUP BY bucket, sp.receiver_name, is_trustworthy;
""")
# ... and just for curiosity also bucket = 1d
db.session.execute(f"""
DROP VIEW IF EXISTS receiver_stats_1d CASCADE;
CREATE VIEW receiver_stats_1d
WITH (timescaledb.continuous, timescaledb.materialized_only=true, timescaledb.refresh_lag='1 hour') AS
SELECT
time_bucket(INTERVAL '1 day', sp.reference_timestamp) AS bucket,
sp.receiver_name,
({SQL_TRUSTWORTHY}) AS is_trustworthy,
COUNT(sp.*) AS beacon_count,
MAX(sp.distance) AS max_distance,
MIN(sp.altitude) AS min_altitude,
MAX(sp.altitude) AS max_altitude
FROM sender_positions AS sp
GROUP BY bucket, sp.receiver_name, is_trustworthy;
""")
# --- Relation statistics (sender <-> receiver) ---
# these stats will be used on a >= 1d basis, so we make the bucket = 1d
db.session.execute(f"""
DROP VIEW IF EXISTS relation_stats_1d CASCADE;
CREATE VIEW relation_stats_1d
WITH (timescaledb.continuous, timescaledb.materialized_only=true, timescaledb.refresh_lag='1 hour') AS
SELECT
time_bucket(INTERVAL '1 day', sp.reference_timestamp) AS bucket,
sp.name,
sp.receiver_name,
({SQL_TRUSTWORTHY}) AS is_trustworthy,
COUNT(sp.*) AS beacon_count,
MAX(sp.normalized_quality) AS max_normalized_quality,
MAX(sp.distance) AS max_distance
FROM sender_positions AS sp
GROUP BY bucket, sp.name, sp.receiver_name, is_trustworthy;
""")
db.session.commit()
"""
class MyView(db.Model):
__table__ = db.Table(
'device_stats', db.metadata,
db.Column('bucket', db.DateTime, primary_key=True),
db.Column('name', db.String, primary_key=True),
db.Column('beacon_count', db.Integer),
autoload=True,
autoload_with=db.engine
)
"""

Wyświetl plik

@ -3,7 +3,6 @@ from .export import user_cli as export_cli
from .flights import user_cli as flights_cli
from .gateway import user_cli as gateway_cli
from .logbook import user_cli as logbook_cli
from .stats import user_cli as stats_cli
def register(app):
@ -12,4 +11,3 @@ def register(app):
app.cli.add_command(flights_cli)
app.cli.add_command(gateway_cli)
app.cli.add_command(logbook_cli)
app.cli.add_command(stats_cli)

Wyświetl plik

@ -5,9 +5,10 @@ import click
from datetime import datetime
from sqlalchemy.sql import func
from app.collect.database import update_device_infos, update_country_code
from app.model import AircraftBeacon, DeviceInfoOrigin
from app.collect.database import update_device_infos
from app.model import SenderPosition, SenderInfoOrigin
from app.utils import get_airports, get_days
from app.collect.timescaledb_views import create_timescaledb_views, create_views
from app import db
@ -22,7 +23,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(SenderPosition.timestamp).label("first_day"), func.max(SenderPosition.timestamp).label("last_day")).one()
start = days_from_db[0].date()
end = days_from_db[1].date()
else:
@ -42,29 +43,26 @@ def info():
@user_cli.command("init")
def init():
"""Initialize the database."""
"""Initialize the database (with PostGIS and TimescaleDB extensions)."""
from alembic.config import Config
from alembic import command
# Create PostGIS and PostGIS extensions
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 timescaledb;")
db.session.commit()
# Create Scheme
db.create_all()
print("Done.")
@user_cli.command("init_timescaledb")
def init_timescaledb():
"""Initialize TimescaleDB features."""
db.session.execute("CREATE EXTENSION IF NOT EXISTS timescaledb;")
db.session.execute("SELECT create_hypertable('aircraft_beacons', 'timestamp', chunk_time_interval => interval '6 hours', if_not_exists => TRUE);")
db.session.execute("SELECT create_hypertable('receiver_beacons', 'timestamp', chunk_time_interval => interval '6 hours', if_not_exists => TRUE);")
# Change (sender|receiver)_positions to TimescaleDB table
db.session.execute("SELECT create_hypertable('sender_positions', 'reference_timestamp', chunk_time_interval => interval '3 hours', if_not_exists => TRUE);")
db.session.execute("SELECT create_hypertable('receiver_positions', 'reference_timestamp', chunk_time_interval => interval '1 day', if_not_exists => TRUE);")
db.session.commit()
print("Done.")
print("Initialized the database (with PostGIS and TimescaleDB extensions).")
@user_cli.command("drop")
@ -83,7 +81,7 @@ def import_ddb():
"""Import registered devices from the DDB."""
print("Import registered devices fom the DDB...")
counter = update_device_infos(db.session, DeviceInfoOrigin.OGN_DDB)
counter = update_device_infos(SenderInfoOrigin.OGN_DDB)
print("Imported %i devices." % counter)
@ -93,7 +91,7 @@ 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(SenderInfoOrigin.USER_DEFINED, path=path)
print("Imported %i devices." % counter)
@ -103,7 +101,7 @@ 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(SenderInfoOrigin.FLARMNET, path=path)
print("Imported %i devices." % counter)
@ -116,13 +114,23 @@ def import_airports(path="tests/SeeYou.cup"):
airports = get_airports(path)
db.session.bulk_save_objects(airports)
db.session.commit()
db.session.execute("UPDATE airports SET border = ST_Expand(location, 0.05)")
# TODO: SRID 4087 ist nicht korrekt, aber spherical mercator 3857 wirft hier Fehler
db.session.execute("UPDATE airports AS a SET border = ST_Transform(ST_Buffer(ST_Transform(location, 4087), 1.5 * GREATEST(500, a.runway_length)), 4326);")
db.session.commit()
print("Imported {} airports.".format(len(airports)))
@user_cli.command("update_country_codes")
def update_country_codes():
"""Update country codes of all receivers."""
@user_cli.command("create_timescaledb_views")
def cmd_create_timescaledb_views():
"""Create TimescaleDB views."""
update_country_code(session=db.session)
create_timescaledb_views()
print("Done")
@user_cli.command("create_views")
def cmd_create_views():
"""Create views."""
create_views()
print("Done")

Wyświetl plik

@ -4,15 +4,89 @@ import click
import datetime
import re
import csv
import os
from sqlalchemy.orm.exc import NoResultFound
from aerofiles.igc import Writer
from app.model import AircraftBeacon, Device
from app.model import SenderPosition, Sender
from app import db
user_cli = AppGroup("export")
user_cli.help = "Export data in several file formats."
@user_cli.command("debug_sql")
@click.argument("start")
@click.argument("end")
@click.argument("name")
def debug_sql(start, end, name):
"""Export data (sender_positions and receivers) as sql for debugging (and/or creating test cases)."""
# First: get all the positions (and the receiver names for later)
sql_sender_positions = f"""
SELECT reference_timestamp, name, receiver_name, timestamp, location, track, ground_speed, altitude, aircraft_type, climb_rate, turn_rate, distance, bearing, agl
FROM sender_positions
WHERE reference_timestamp BETWEEN '{start}' AND '{end}' AND name = '{name}'
ORDER BY reference_timestamp;
"""
receiver_names = []
sender_position_values = []
results = db.session.execute(sql_sender_positions)
for row in results:
if row[2] not in receiver_names:
receiver_names.append("'" + row[2] + "'")
row = [f"'{r}'" if r else "DEFAULT" for r in row]
sender_position_values.append(f"({','.join(row)})")
# Second: get the receivers
sql_receivers = f"""
SELECT name, location
FROM receivers
WHERE name IN ({','.join(receiver_names)});
"""
receiver_values = []
results = db.session.execute(sql_receivers)
for row in results:
row = [f"'{r}'" if r else "DEFAULT" for r in row]
receiver_values.append(f"({','.join(row)})")
# Third: get the airports
sql_airports = f"""
SELECT DISTINCT a.name, a.location, a.altitude, a.style, a.border
FROM airports AS a, receivers AS r
WHERE
r.name IN ({','.join(receiver_names)})
AND ST_Within(r.location, ST_Buffer(a.location, 0.2))
AND a.style IN (2,4,5);
"""
airport_values = []
results = db.session.execute(sql_airports)
for row in results:
row = [f"'{r}'" if r else "DEFAULT" for r in row]
airport_values.append(f"({','.join(row)})")
# Last: write all into file
with open(f'{start}_{end}_{name}.sql', 'w') as file:
file.write('/*\n')
file.write('OGN Python SQL Export\n')
file.write(f'Created by: {os.getlogin()}\n')
file.write(f'Created at: {datetime.datetime.utcnow()}\n')
file.write('*/\n\n')
file.write("INSERT INTO airports(name, location, altitude, style, border) VALUES\n")
file.write(',\n'.join(airport_values) + ';\n\n')
file.write("INSERT INTO receivers(name, location) VALUES\n")
file.write(',\n'.join(receiver_values) + ';\n\n')
file.write("INSERT INTO sender_positions(reference_timestamp, name, receiver_name, timestamp, location, track, ground_speed, altitude, aircraft_type, climb_rate, turn_rate, distance, bearing, agl) VALUES\n")
file.write(',\n'.join(sender_position_values) + ';\n\n')
@user_cli.command("cup")
def cup():
"""Export receiver waypoints as '.cup'."""
@ -60,18 +134,18 @@ def cup():
@click.argument("date")
def igc(address, date):
"""Export igc file for <address> at <date>."""
if not re.match(".{6}", address):
print("Address {} not valid.".format(address))
if not re.match("[0-9A-F]{6}", address):
print(f"Address '{address}' not valid.")
return
try:
sender = db.session.query(Sender).filter(Sender.address == address).one()
except NoResultFound as e:
print(f"No data for '{address}' in the DB")
return
if not re.match(r"\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()
if device_id is None:
print("Device with address '{}' not found.".format(address))
print(f"Date {date} not valid.")
return
with open("sample.igc", "wb") as fp:
@ -83,27 +157,26 @@ def igc(address, date):
"logger_id": "OGN",
"date": datetime.date(1987, 2, 24),
"fix_accuracy": 50,
"pilot": "Konstantin Gruendger",
"pilot": "Unknown",
"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",
"glider_type": sender.infos[0].aircraft if len(sender.infos) > 0 else '',
"glider_id": sender.infos[0].registration if len(sender.infos) > 0 else '',
"firmware_version": sender.software_version,
"hardware_version": sender.hardware_version,
"logger_type": "OGN",
"gps_receiver": "unknown",
"pressure_sensor": "unknown",
"competition_id": sender.infos[0].competition if len(sender.infos) > 0 else '',
"competition_class": "unknown",
}
)
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)
db.session.query(SenderPosition)
.filter(db.between(SenderPosition.reference_timestamp, f"{date} 00:00:00", f"{date} 23:59:59"))
.filter(SenderPosition.name == sender.name)
.order_by(SenderPosition.timestamp)
)
for point in points.all():
for point in points:
writer.write_fix(point.timestamp.time(), latitude=point.location.latitude, longitude=point.location.longitude, valid=True, pressure_alt=point.altitude, gps_alt=point.altitude)

Wyświetl plik

@ -6,119 +6,11 @@ from tqdm import tqdm
from app.commands.database import get_database_days
from app import db
from app.collect.flights import compute_flights, compute_gaps
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"
def compute_gaps(session, date):
query = """
INSERT INTO flights2d(date, flight_type, device_id, path)
SELECT '{date}' AS date,
3 AS flight_type,
sq3.device_id,
ST_Collect(sq3.path)
FROM (
SELECT sq2.d1 device_id,
ST_MakeLine(sq2.l1, sq2.l2) path
FROM
(
SELECT sq.timestamp t1,
LAG(sq.timestamp) OVER ( PARTITION BY sq.timestamp::DATE, sq.device_id ORDER BY sq.timestamp) t2,
sq.location l1,
LAG(sq.location) OVER ( PARTITION BY sq.timestamp::DATE, sq.device_id ORDER BY sq.timestamp) l2,
sq.device_id d1,
LAG(sq.device_id) OVER ( PARTITION BY sq.timestamp::DATE, sq.device_id ORDER BY sq.timestamp) d2
FROM
(
SELECT DISTINCT ON (device_id, timestamp) timestamp, device_id, location, agl
FROM aircraft_beacons
WHERE timestamp BETWEEN '{date} 00:00:00' AND '{date} 23:59:59' AND agl > 300
ORDER BY device_id, timestamp, error_count
) sq
) sq2
WHERE EXTRACT(epoch FROM sq2.t1 - sq2.t2) > 300
AND ST_DistanceSphere(sq2.l1, sq2.l2) / EXTRACT(epoch FROM sq2.t1 - sq2.t2) BETWEEN 15 AND 50
) sq3
GROUP BY sq3.device_id
ON CONFLICT DO NOTHING;
""".format(
date=date.strftime("%Y-%m-%d")
)
session.execute(query)
session.commit()
def compute_flights2d(session, date, flight_type):
if flight_type == 0:
filter = NOTHING
elif flight_type == 1:
filter = CONTEST_RELEVANT
elif flight_type == 2:
filter = LOW_PASS
query = """
INSERT INTO flights2d
(
date,
flight_type,
device_id,
path,
path_simple
)
SELECT '{date}' AS date,
{flight_type} as flight_type,
sq5.device_id,
st_collect(sq5.linestring order BY sq5.part) multilinestring,
st_collect(st_simplify(sq5.linestring, 0.0001) ORDER BY sq5.part) simple_multilinestring
FROM (
SELECT sq4.device_id,
sq4.part,
st_makeline(sq4.location ORDER BY sq4.timestamp) linestring
FROM (
SELECT sq3.timestamp,
sq3.location,
sq3.device_id,
sum(sq3.ping) OVER (partition BY sq3.device_id ORDER BY sq3.timestamp) part
FROM (
SELECT sq2.t1 AS timestamp,
sq2.l1 AS location,
sq2.d1 device_id,
CASE
WHEN sq2.t1 - sq2.t2 < interval'100s' AND ST_DistanceSphere(sq2.l1, sq2.l2) < 1000 THEN 0
ELSE 1
END AS ping
FROM (
SELECT sq.timestamp t1,
lag(sq.timestamp) OVER (partition BY sq.device_id ORDER BY sq.timestamp) t2,
sq.location l1,
lag(sq.location) OVER (partition BY sq.device_id ORDER BY sq.timestamp) l2,
sq.device_id d1,
lag(sq.device_id) OVER (partition BY sq.device_id ORDER BY sq.timestamp) d2
FROM (
SELECT DISTINCT ON (device_id, timestamp) timestamp, device_id, location
FROM aircraft_beacons
WHERE timestamp BETWEEN '{date} 00:00:00' AND '{date} 23:59:59' {filter}
ORDER BY device_id, timestamp, error_count
) sq
) sq2
) sq3
) sq4
GROUP BY sq4.device_id, sq4.part
) sq5
GROUP BY sq5.device_id
ON CONFLICT DO NOTHING;
""".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")
@ -133,6 +25,6 @@ def create(start, end, flight_type):
for single_date in pbar:
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)
result = compute_flights(date=single_date, flight_type=flight_type)
else:
result = compute_gaps(session=db.session, date=single_date)
result = compute_gaps(date=single_date)

Wyświetl plik

@ -1,49 +1,103 @@
import os
import datetime
from datetime import datetime, timezone
import time
from flask import current_app
from flask.cli import AppGroup
import click
from tqdm import tqdm
from ogn.client import AprsClient
from app.gateway.bulkimport import convert, DbFeeder
from app import redis_client
from app.gateway.beacon_conversion import aprs_string_to_message
from app.gateway.message_handling import receiver_status_message_to_csv_string, receiver_position_message_to_csv_string, sender_position_message_to_csv_string
from app.collect.gateway import transfer_from_redis_to_database
user_cli = AppGroup("gateway")
user_cli.help = "Connection to APRS servers."
@user_cli.command("run")
def run(aprs_user="anon-dev"):
"""Run the aprs client and feed the DB with incoming data."""
@click.option("--aprs_filter", default='')
def run(aprs_filter):
"""
Run the aprs client, parse the incoming data and put it to redis.
"""
# User input validation
if len(aprs_user) < 3 or len(aprs_user) > 9:
print("aprs_user must be a string of 3-9 characters.")
return
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)-17s %(levelname)-8s %(message)s')
current_app.logger.warning("Start ogn gateway")
client = AprsClient(aprs_user)
client = AprsClient(current_app.config['APRS_USER'], aprs_filter)
client.connect()
with DbFeeder(prefix='continuous_import', reference_timestamp=datetime.utcnow, reference_timestamp_autoupdate=True) as feeder:
try:
client.run(callback=lambda x: feeder.add(x), autoreconnect=True)
except KeyboardInterrupt:
current_app.logger.warning("\nStop ogn gateway")
def insert_into_redis(aprs_string):
# Convert aprs_string to message dict, add MGRS Position, flatten gps precision, etc. etc. ...
message = aprs_string_to_message(aprs_string)
if message is None:
return
# separate between tables (receiver/sender) and aprs_type (status/position)
if message['beacon_type'] in ('aprs_receiver', 'receiver'):
if message['aprs_type'] == 'status':
redis_target = 'receiver_status'
csv_string = receiver_status_message_to_csv_string(message, none_character=r'\N')
elif message['aprs_type'] == 'position':
redis_target = 'receiver_position'
csv_string = receiver_position_message_to_csv_string(message, none_character=r'\N')
else:
return
else:
if message['aprs_type'] == 'status':
return # no interesting data we want to keep
elif message['aprs_type'] == 'position':
redis_target = 'sender_position'
csv_string = sender_position_message_to_csv_string(message, none_character=r'\N')
else:
return
mapping = {csv_string: str(time.time())}
redis_client.zadd(name=redis_target, mapping=mapping, nx=True)
insert_into_redis.beacon_counter += 1
current_minute = datetime.utcnow().minute
if current_minute != insert_into_redis.last_minute:
current_app.logger.info(f"{insert_into_redis.beacon_counter:7d}/min")
insert_into_redis.beacon_counter = 0
insert_into_redis.last_minute = current_minute
insert_into_redis.beacon_counter = 0
insert_into_redis.last_minute = datetime.utcnow().minute
try:
client.run(callback=insert_into_redis, autoreconnect=True)
except KeyboardInterrupt:
current_app.logger.warning("\nStop ogn gateway")
client.disconnect()
@user_cli.command("convert")
@click.argument("path")
def file_import(path):
"""Convert APRS logfiles into csv files for fast bulk import."""
@user_cli.command("transfer")
def transfer():
"""Transfer data from redis to the database."""
logfiles = []
for (root, dirs, files) in os.walk(path):
for file in sorted(files):
logfiles.append(os.path.join(root, file))
transfer_from_redis_to_database()
for logfile in logfiles:
convert(logfile)
@user_cli.command("printout")
@click.option("--aprs_filter", default='')
def printout(aprs_filter):
"""Run the aprs client and just print out the data stream."""
current_app.logger.warning("Start ogn gateway")
client = AprsClient(current_app.config['APRS_USER'], aprs_filter=aprs_filter)
client.connect()
try:
client.run(callback=lambda x: print(f"{datetime.utcnow()}: {x}"), autoreconnect=True)
except KeyboardInterrupt:
current_app.logger.warning("\nStop ogn gateway")
client.disconnect()

Wyświetl plik

@ -3,18 +3,13 @@ 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 app.collect.logbook import update_takeoff_landings, update_logbook
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.help = "Handling of takeoff/landings and logbook data."
@user_cli.command("compute_takeoff_landing")
@ -29,7 +24,7 @@ def compute_takeoff_landing(start, end):
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)
result = update_takeoff_landings(start=start, end=end)
@user_cli.command("compute_logbook")
@ -43,77 +38,4 @@ def compute_logbook(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_name>."""
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),
)
)
result = update_logbook(date=single_date)

Wyświetl plik

@ -1,130 +0,0 @@
from flask.cli import AppGroup
import click
from datetime import datetime
from tqdm import tqdm
from app.commands.database import get_database_days
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 app.collect.ognrange import update_entries as update_receiver_coverages
from app.model import Device
from app import db
user_cli = AppGroup("stats")
user_cli.help = "Handling of statistical data."
@user_cli.command("create")
@click.argument("start")
@click.argument("end")
def create(start, end):
"""Create DeviceStats, ReceiverStats and RelationStats."""
days = get_database_days(start, end)
pbar = tqdm(days)
for single_date in pbar:
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")
def create_country(start, end):
"""Create CountryStats."""
days = get_database_days(start, end)
pbar = tqdm(days)
for single_date in pbar:
pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d"))
result = create_country_stats(session=db.session, date=single_date)
@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.commit()
@user_cli.command("update_receivers")
def update_receivers():
"""Update receivers with data from stats."""
result = update_receivers_command(session=db.session)
print(result)
@user_cli.command("update_devices")
def update_devices():
"""Update devices with data from stats."""
result = update_devices_command(session=db.session)
print(result)
@user_cli.command("update_mgrs")
@click.argument("start")
@click.argument("end")
def update_mgrs(start, end):
"""Create location_mgrs_short."""
days = get_database_days(start, end)
pbar = tqdm(days)
for single_date in pbar:
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
)
# print(sql)
db.session.execute(sql)
db.session.commit()
@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."""
days = get_database_days(start, end)
pbar = tqdm(days)
for single_date in pbar:
pbar.set_description(datetime.strftime(single_date, "%Y-%m-%d"))
result = update_receiver_coverages(session=db.session, date=single_date)

Wyświetl plik

@ -1,28 +0,0 @@
import os
SECRET_KEY = "i-like-ogn"
SQLALCHEMY_DATABASE_URI = os.environ.get("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 = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0")
CELERY_RESULT_BACKEND = os.environ.get("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}},
}

Wyświetl plik

@ -1,7 +0,0 @@
SQLALCHEMY_DATABASE_URI = "postgresql://postgres@localhost:5432/ogn_test"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
# Celery stuff
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"

Wyświetl plik

@ -0,0 +1,52 @@
from flask import current_app
from mgrs import MGRS
from ogn.parser import parse
from app.model import AircraftType
#import rasterio as rs
#elevation_dataset = rs.open('/Volumes/LaCieBlack/Wtf4.tiff')
mgrs = MGRS()
def aprs_string_to_message(aprs_string):
try:
message = parse(aprs_string, calculate_relations=True)
except Exception as e:
current_app.logger.debug(e)
return None
if message['aprs_type'] not in ('position', 'status'):
return None
elif message['aprs_type'] == 'position':
latitude = message["latitude"]
longitude = message["longitude"]
message["location"] = "SRID=4326;POINT({} {})".format(longitude, latitude)
location_mgrs = mgrs.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 'altitude' in message and longitude >= 0.0 and longitude <= 20.0 and latitude >= 40.0 and latitude <= 60.0:
# elevation = [val[0] for val in elevation_dataset.sample(((longitude, latitude),))][0]
# message['agl'] = message['altitude'] - elevation
if 'bearing' in message:
bearing = int(message['bearing'])
message['bearing'] = bearing if bearing < 360 else 0
if "aircraft_type" in message:
message["aircraft_type"] = AircraftType(message["aircraft_type"]) if message["aircraft_type"] in AircraftType.list() else AircraftType.UNKNOWN
if "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"]
return message

Wyświetl plik

@ -1,206 +0,0 @@
import os
import re
from datetime import datetime, timedelta
from io import StringIO
from flask import current_app
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 AircraftType, Location
from app.gateway.process_tools import open_file, create_tables, drop_tables, update_aircraft_beacons_bigdata
from app import db
user_cli = AppGroup("bulkimport")
user_cli.help = "Tools for accelerated data import."
basepath = os.path.dirname(os.path.realpath(__file__))
# define message types we want to proceed
AIRCRAFT_BEACON_TYPES = ["aprs_aircraft", "flarm", "tracker", "fanet", "lt24", "naviter", "skylines", "spider", "spot", "flymaster"]
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_BEACON_FIELDS = [
"location",
"altitude",
"dstcall",
]
def initial_file_scan(file):
"""Scan file and get rowcount and first server timestamp."""
row_count = 0
timestamp = None
for row in file:
row_count += 1
if timestamp is None and row[0] == '#':
message = parse(row)
if message['aprs_type'] == 'server':
timestamp = message['timestamp']
file.seek(0)
return row_count, timestamp
class DbFeeder:
def __init__(self, postfix, reference_timestamp, auto_update_timestamp):
self.postfix = postfix
self.reference_timestamp = reference_timestamp
self.auto_update_timestamp = auto_update_timestamp
self.last_flush = datetime.utcnow()
self.aircraft_buffer = StringIO()
self.receiver_buffer = StringIO()
self.connection = db.engine.raw_connection()
self.cursor = self.connection.cursor()
self.mgrs = MGRS()
create_tables(self.postfix)
def __enter__(self):
return self
def __exit__(self, *args):
self._flush()
update_aircraft_beacons_bigdata(self.postfix)
self.connection.commit()
self.cursor.close()
self.connection.close()
def _flush(self):
self.aircraft_buffer.seek(0)
self.receiver_buffer.seek(0)
self.cursor.copy_from(self.aircraft_buffer, "aircraft_beacons_{postfix}".format(postfix=self.postfix), sep=",", columns=BEACON_KEY_FIELDS + AIRCRAFT_BEACON_FIELDS)
self.cursor.copy_from(self.receiver_buffer, "receiver_beacons_{postfix}".format(postfix=self.postfix), sep=",", columns=BEACON_KEY_FIELDS + RECEIVER_BEACON_FIELDS)
self.connection.commit()
self.aircraft_buffer = StringIO()
self.receiver_buffer = StringIO()
def add(self, raw_string):
try:
message = parse(raw_string, reference_timestamp=self.reference_timestamp)
except NotImplementedError as e:
current_app.logger.error("No parser implemented for message: {}".format(raw_string))
return
except ParseError as e:
current_app.logger.error("Parsing error with message: {}".format(raw_string))
return
except TypeError as e:
current_app.logger.error("TypeError with message: {}".format(raw_string))
return
except Exception as e:
current_app.logger.error("Other Exception with string: {}".format(raw_string))
return
if message['aprs_type'] not in ('server', 'position'):
return
elif message['aprs_type'] == 'server' and self.auto_update_timestamp is True:
self.reference_timestamp = message['timestamp']
return
elif message['aprs_type'] == 'position':
latitude = message["latitude"]
longitude = message["longitude"]
location = Location(longitude, latitude)
message["location"] = location.to_wkt()
location_mgrs = self.mgrs.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 "aircraft_type" in message:
message["aircraft_type"] = AircraftType(message["aircraft_type"]).name if message["aircraft_type"] in AircraftType.list() else AircraftType.UNKNOWN.name
if "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"]
if 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")
elif 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")
else:
current_app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"]))
return
if datetime.utcnow() - self.last_flush >= timedelta(seconds=5):
self._flush()
self.last_flush = datetime.utcnow()
def convert(sourcefile):
with open_file(sourcefile) as filehandler:
total_lines, reference_timestamp = initial_file_scan(filehandler)
if reference_timestamp is not None:
auto_update_timestamp = True
postfix = str(reference_timestamp.total_seconds())
else:
auto_update_timestamp = False
match = re.match(r".*OGN_log\.txt_([0-9]{4}\-[0-9]{2}\-[0-9]{2})\.gz$", sourcefile)
if match:
reference_timestamp = datetime.strptime(match.group(1), "%Y-%m-%d")
postfix = reference_timestamp.strftime("%Y_%m_%d")
else:
current_app.logger.error("No reference time information. Skipping file: {}".format(sourcefile))
return
with open_file(sourcefile) as fin:
with DbFeeder(postfix=postfix, reference_timestamp=reference_timestamp, auto_update_timestamp=auto_update_timestamp) as feeder:
pbar = tqdm(fin, total=total_lines)
for line in pbar:
pbar.set_description("Importing {}".format(sourcefile))
feeder.add(raw_string=line)

Wyświetl plik

@ -0,0 +1,438 @@
import os
import time
from io import StringIO
from app import db
from app.model import AircraftType
from app.utils import get_sql_trustworthy
basepath = os.path.dirname(os.path.realpath(__file__))
# define fields we want to proceed
SENDER_POSITION_BEACON_FIELDS = [
"reference_timestamp",
"name",
"dstcall",
"relay",
"receiver_name",
"timestamp",
"location",
"track",
"ground_speed",
"altitude",
"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",
"bearing",
"normalized_quality",
"location_mgrs",
"location_mgrs_short",
"agl",
]
RECEIVER_POSITION_BEACON_FIELDS = [
"reference_timestamp",
"name",
"dstcall",
"receiver_name",
"timestamp",
"location",
"altitude",
"location_mgrs",
"location_mgrs_short",
"agl",
]
RECEIVER_STATUS_BEACON_FIELDS = [
"reference_timestamp",
"name",
"dstcall",
"receiver_name",
"timestamp",
"version",
"platform",
"cpu_temp",
"rec_input_noise",
]
def sender_position_message_to_csv_string(message, none_character=''):
"""
Convert sender_position_messages to csv string.
:param dict message: dict of sender position messages from the parser
:param str none_character: '' for a file, '\\N' for Postgresql COPY
"""
csv_string = "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20},{21},{22},{23},{24},{25},{26},{27},{28},{29},{30}\n".format(
message['reference_timestamp'],
message['name'],
message['dstcall'],
message['relay'] if 'relay' in message and message['relay'] else none_character,
message['receiver_name'],
message['timestamp'],
message['location'],
message['track'] if 'track' in message and message['track'] else none_character,
message['ground_speed'] if 'ground_speed' in message and message['ground_speed'] else none_character,
int(message['altitude']) if message['altitude'] else none_character,
message['address_type'] if 'address_type' in message and message['address_type'] else none_character, # 10
message['aircraft_type'].name if 'aircraft_type' in message and message['aircraft_type'] else AircraftType.UNKNOWN.name,
message['stealth'] if 'stealth' in message and message['stealth'] else none_character,
message['address'] if 'address' in message and message['address'] else none_character,
message['climb_rate'] if 'climb_rate' in message and message['climb_rate'] else none_character,
message['turn_rate'] if 'turn_rate' in message and message['turn_rate'] else none_character,
message['signal_quality'] if 'signal_quality' in message and message['signal_quality'] else none_character,
message['error_count'] if 'error_count' in message and message['error_count'] else none_character,
message['frequency_offset'] if 'frequency_offset' in message and message['frequency_offset'] else none_character,
message['gps_quality_horizontal'] if 'gps_quality_horizontal' in message and message['gps_quality_horizontal'] else none_character,
message['gps_quality_vertical'] if 'gps_quality_vertical' in message and message['gps_quality_vertical'] else none_character, # 20
message['software_version'] if 'software_version' in message and message['software_version'] else none_character,
message['hardware_version'] if 'hardware_version' in message and message['hardware_version'] else none_character,
message['real_address'] if 'real_address' in message and message['real_address'] else none_character,
message['signal_power'] if 'signal_power' in message and message['signal_power'] else none_character,
message['distance'] if 'distance' in message and message['distance'] else none_character,
message['bearing'] if 'bearing' in message and message['bearing'] else none_character,
message['normalized_quality'] if 'normalized_quality' in message and message['normalized_quality'] else none_character,
message['location_mgrs'],
message['location_mgrs_short'],
message['agl'] if 'agl' in message else none_character,
)
return csv_string
def receiver_position_message_to_csv_string(message, none_character=''):
csv_string = "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}\n".format(
message['reference_timestamp'],
message['name'],
message['dstcall'],
message['receiver_name'],
message['timestamp'],
message['location'],
int(message['altitude']) if message['altitude'] else none_character,
message['location_mgrs'],
message['location_mgrs_short'],
message['agl'] if 'agl' in message else none_character,
)
return csv_string
def receiver_status_message_to_csv_string(message, none_character=''):
csv_string = "{0},{1},{2},{3},{4},{5},{6},{7},{8}\n".format(
message['reference_timestamp'],
message['name'],
message['dstcall'],
message['receiver_name'],
message['timestamp'],
message['version'] if 'version' in message else none_character,
message['platform'] if 'platform' in message else none_character,
message['cpu_temp'] if 'cpu_temp' in message else none_character,
message['rec_input_noise'] if 'rec_input_noise' in message else none_character,
)
return csv_string
def sender_position_csv_strings_to_db(lines):
timestamp_string = str(time.time()).replace('.', '_')
tmp_tablename = f'sender_positions_{timestamp_string}'
connection = db.engine.raw_connection()
cursor = connection.cursor()
string_buffer = StringIO()
string_buffer.writelines(lines)
string_buffer.seek(0)
cursor.execute(f"CREATE TEMPORARY TABLE {tmp_tablename} (LIKE sender_positions) ON COMMIT DROP;")
cursor.copy_from(file=string_buffer, table=tmp_tablename, sep=",", columns=SENDER_POSITION_BEACON_FIELDS)
# Update agl
cursor.execute(f"""
UPDATE {tmp_tablename} AS tmp
SET
agl = tmp.altitude - ST_Value(e.rast, tmp.location)
FROM elevation AS e
WHERE ST_Intersects(tmp.location, e.rast);
""")
# Update sender position statistics
cursor.execute(f"""
INSERT INTO sender_position_statistics AS sps (date, dstcall, address_type, aircraft_type, stealth, software_version, hardware_version, messages_count)
SELECT
tmp.reference_timestamp::DATE AS date,
tmp.dstcall,
tmp.address_type,
tmp.aircraft_type,
tmp.stealth,
tmp.software_version,
tmp.hardware_version,
COUNT(tmp.*) AS messages_count
FROM {tmp_tablename} AS tmp
GROUP BY date, dstcall, address_type, aircraft_type, stealth, software_version, hardware_version
ON CONFLICT (date, dstcall, address_type, aircraft_type, stealth, software_version, hardware_version) DO UPDATE
SET
messages_count = EXCLUDED.messages_count + sps.messages_count;
""")
# Update senders
cursor.execute(f"""
INSERT INTO senders AS s (firstseen, lastseen, name, aircraft_type, stealth, address, software_version, hardware_version, real_address)
SELECT DISTINCT ON (tmp.name)
tmp.reference_timestamp AS firstseen,
tmp.reference_timestamp AS lastseen,
tmp.name,
tmp.aircraft_type,
tmp.stealth,
tmp.address,
tmp.software_version,
tmp.hardware_version,
tmp.real_address
FROM {tmp_tablename} AS tmp
WHERE tmp.name NOT LIKE 'RND%'
ON CONFLICT (name) DO UPDATE
SET
lastseen = GREATEST(EXCLUDED.lastseen, s.lastseen),
aircraft_type = EXCLUDED.aircraft_type,
stealth = EXCLUDED.stealth,
address = EXCLUDED.address,
software_version = COALESCE(EXCLUDED.software_version, s.software_version),
hardware_version = COALESCE(EXCLUDED.hardware_version, s.hardware_version),
real_address = COALESCE(EXCLUDED.real_address, s.real_address);
""")
# Update sender_infos FK -> senders
cursor.execute("""
UPDATE sender_infos AS si
SET sender_id = s.id
FROM senders AS s
WHERE si.sender_id IS NULL AND s.address = si.address;
""")
SQL_TRUSTWORTHY = get_sql_trustworthy(source_table_alias='tmp')
# Update coverage statistics
cursor.execute(f"""
INSERT INTO coverage_statistics AS rs (date, location_mgrs_short, sender_id, receiver_id, is_trustworthy, max_distance, max_normalized_quality, messages_count)
SELECT
tmp.reference_timestamp::DATE AS date,
tmp.location_mgrs_short,
tmp.sender_id,
tmp.receiver_id,
({SQL_TRUSTWORTHY}) AS is_trustworthy,
MAX(tmp.distance) AS max_distance,
MAX(tmp.normalized_quality) AS max_normalized_quality,
COUNT(tmp.*) AS messages_count
FROM (SELECT x.*, s.id AS sender_id, r.id AS receiver_id FROM {tmp_tablename} AS x INNER JOIN senders AS s ON x.name = s.name INNER JOIN receivers AS r ON x.receiver_name = r.name) AS tmp
GROUP BY date, location_mgrs_short, sender_id, receiver_id, is_trustworthy
ON CONFLICT (date, location_mgrs_short, sender_id, receiver_id, is_trustworthy) DO UPDATE
SET
max_distance = GREATEST(EXCLUDED.max_distance, rs.max_distance),
max_normalized_quality = GREATEST(EXCLUDED.max_normalized_quality, rs.max_normalized_quality),
messages_count = EXCLUDED.messages_count + rs.messages_count;
""")
# Insert all the beacons
all_fields = ', '.join(SENDER_POSITION_BEACON_FIELDS)
cursor.execute(f"""
INSERT INTO sender_positions ({all_fields})
SELECT {all_fields} FROM {tmp_tablename};
""")
connection.commit()
cursor.close()
connection.close()
def receiver_position_csv_strings_to_db(lines):
timestamp_string = str(time.time()).replace('.', '_')
tmp_tablename = f'receiver_positions_{timestamp_string}'
connection = db.engine.raw_connection()
cursor = connection.cursor()
string_buffer = StringIO()
string_buffer.writelines(lines)
string_buffer.seek(0)
cursor.execute(f"CREATE TEMPORARY TABLE {tmp_tablename} (LIKE receiver_positions) ON COMMIT DROP;")
cursor.copy_from(file=string_buffer, table=tmp_tablename, sep=",", columns=RECEIVER_POSITION_BEACON_FIELDS)
# Update agl
cursor.execute(f"""
UPDATE {tmp_tablename} AS tmp
SET
agl = tmp.altitude - ST_Value(e.rast, tmp.location)
FROM elevation AS e
WHERE ST_Intersects(tmp.location, e.rast);
""")
# Update receivers
cursor.execute(f"""
INSERT INTO receivers AS r (firstseen, lastseen, name, timestamp, location, altitude, agl)
SELECT DISTINCT ON (tmp.name)
tmp.reference_timestamp AS firstseen,
tmp.reference_timestamp AS lastseen,
tmp.name,
tmp.timestamp,
tmp.location,
tmp.altitude,
tmp.agl
FROM {tmp_tablename} AS tmp,
(
SELECT
tmp.name,
MAX(timestamp) AS timestamp
FROM {tmp_tablename} AS tmp
GROUP BY tmp.name
) AS sq
WHERE tmp.name = sq.name AND tmp.timestamp = sq.timestamp AND tmp.name NOT LIKE 'RND%'
ON CONFLICT (name) DO UPDATE
SET
lastseen = EXCLUDED.lastseen,
timestamp = EXCLUDED.timestamp,
location = EXCLUDED.location,
altitude = EXCLUDED.altitude,
agl = EXCLUDED.agl;
""")
# Update receiver country
cursor.execute("""
UPDATE receivers AS r
SET
country_id = c.gid
FROM countries AS c
WHERE r.country_id IS NULL AND ST_Within(r.location, c.geom);
""")
# Update receiver airport
cursor.execute("""
UPDATE receivers AS r
SET
airport_id = (
SELECT id
FROM airports AS a
WHERE
ST_Contains(a.border, r.location)
AND a.style IN (2,4,5)
ORDER BY ST_DistanceSphere(a.location, r.location)
LIMIT 1
)
WHERE r.airport_id IS NULL;
""")
# Insert all the beacons
all_fields = ', '.join(RECEIVER_POSITION_BEACON_FIELDS)
cursor.execute(f"""
INSERT INTO receiver_positions ({all_fields})
SELECT {all_fields} FROM {tmp_tablename};
""")
connection.commit()
cursor.close()
connection.close()
def receiver_status_csv_strings_to_db(lines):
timestamp_string = str(time.time()).replace('.', '_')
tmp_tablename = f'receiver_status_{timestamp_string}'
connection = db.engine.raw_connection()
cursor = connection.cursor()
string_buffer = StringIO()
string_buffer.writelines(lines)
string_buffer.seek(0)
cursor.execute(f"CREATE TEMPORARY TABLE {tmp_tablename} (LIKE receiver_statuses) ON COMMIT DROP;")
cursor.copy_from(file=string_buffer, table=tmp_tablename, sep=",", columns=RECEIVER_STATUS_BEACON_FIELDS)
# Update receivers
cursor.execute(f"""
INSERT INTO receivers AS r (firstseen, lastseen, name, timestamp, version, platform, cpu_temp, rec_input_noise)
SELECT DISTINCT ON (tmp.name)
tmp.reference_timestamp AS firstseen,
tmp.reference_timestamp AS lastseen,
tmp.name,
tmp.timestamp,
tmp.version,
tmp.platform,
tmp.cpu_temp,
tmp.rec_input_noise
FROM {tmp_tablename} AS tmp,
(
SELECT
tmp.name,
MAX(timestamp) AS timestamp
FROM {tmp_tablename} AS tmp
GROUP BY tmp.name
) AS sq
WHERE tmp.name = sq.name AND tmp.timestamp = sq.timestamp
ON CONFLICT (name) DO UPDATE
SET
lastseen = EXCLUDED.lastseen,
timestamp = EXCLUDED.timestamp,
version = EXCLUDED.version,
platform = EXCLUDED.platform,
cpu_temp = EXCLUDED.cpu_temp,
rec_input_noise = EXCLUDED.rec_input_noise;
""")
# Insert all the beacons
all_fields = ', '.join(RECEIVER_STATUS_BEACON_FIELDS)
cursor.execute(f"""
INSERT INTO receiver_statuses ({all_fields})
SELECT {all_fields} FROM {tmp_tablename};
""")
connection.commit()
cursor.close()
connection.close()

Wyświetl plik

@ -38,73 +38,10 @@ class Timer(object):
print("Elapsed: {}".format(time.time() - self.tstart))
def drop_tables(postfix):
"""Drop tables for log file import."""
db.session.execute("""
DROP TABLE IF EXISTS "aircraft_beacons_{postfix}";
DROP TABLE IF EXISTS "receiver_beacons_{postfix}";
""".format(postfix=postfix))
db.session.commit()
def create_tables(postfix):
"""Create tables for log file import."""
drop_tables(postfix)
db.session.execute("""
CREATE TABLE aircraft_beacons_{postfix} AS TABLE aircraft_beacons WITH NO DATA;
CREATE TABLE receiver_beacons_{postfix} AS TABLE receiver_beacons WITH NO DATA;
""".format(postfix=postfix))
db.session.commit()
def update_aircraft_beacons_bigdata(postfix):
"""Calculates distance/radial and quality and computes the altitude above ground level.
Due to performance reasons we use a new table instead of updating the old."""
db.session.execute("""
SELECT
ab.location, ab.altitude, ab.name, ab.dstcall, ab.relay, ab.receiver_name, ab.timestamp, ab.track, ab.ground_speed,
ab.address_type, ab.aircraft_type, ab.stealth, ab.address, ab.climb_rate, ab.turn_rate, ab.signal_quality, ab.error_count,
ab.frequency_offset, ab.gps_quality_horizontal, ab.gps_quality_vertical, ab.software_version, ab.hardware_version, ab.real_address, ab.signal_power,
ab.location_mgrs,
ab.location_mgrs_short,
CASE WHEN ab.location IS NOT NULL AND r.location IS NOT NULL THEN CAST(ST_DistanceSphere(ab.location, r.location) AS REAL) ELSE NULL END AS distance,
CASE WHEN ab.location IS NOT NULL AND r.location IS NOT NULL THEN CAST(degrees(ST_Azimuth(ab.location, r.location)) AS SMALLINT) % 360 ELSE NULL END AS radial,
CASE WHEN ab.location IS NOT NULL AND r.location IS NOT NULL AND ST_DistanceSphere(ab.location, r.location) > 0 AND ab.signal_quality IS NOT NULL
THEN CAST(signal_quality + 20*log(ST_DistanceSphere(ab.location, r.location)/10000) AS REAL)
ELSE NULL
END AS quality,
CAST((ab.altitude - subtable.elev_m) AS REAL) AS agl
INTO aircraft_beacons_{postfix}_temp
FROM
aircraft_beacons_{postfix} AS ab
JOIN LATERAL (
SELECT ab.location, MAX(ST_NearestValue(e.rast, ab.location)) as elev_m
FROM elevation e
WHERE ST_Intersects(ab.location, e.rast)
GROUP BY ab.location
) AS subtable ON TRUE,
(SELECT name, last(location, timestamp) AS location FROM receiver_beacons_{postfix} GROUP BY name) AS r
WHERE ab.receiver_name = r.name;
DROP TABLE IF EXISTS "aircraft_beacons_{postfix}";
ALTER TABLE "aircraft_beacons_{postfix}_temp" RENAME TO "aircraft_beacons_{postfix}";
""".format(postfix=postfix))
def export_to_path(postfix, path):
def export_to_path(path):
connection = db.engine.raw_connection()
cursor = connection.cursor()
aircraft_beacons_file = os.path.join(path, "aircraft_beacons_{postfix}.csv.gz".format(postfix=postfix))
aircraft_beacons_file = os.path.join(path, "sender_positions.csv.gz")
with gzip.open(aircraft_beacons_file, "wt", encoding="utf-8") as gzip_file:
cursor.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format("SELECT * FROM aircraft_beacons_{postfix}".format(postfix=postfix)), gzip_file)
receiver_beacons_file = os.path.join(path, "receiver_beacons_{postfix}.csv.gz".format(postfix=postfix))
with gzip.open(receiver_beacons_file, "wt") as gzip_file:
cursor.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format("SELECT * FROM receiver_beacons_{postfix}".format(postfix=postfix)), gzip_file)
cursor.copy_expert("COPY ({}) TO STDOUT WITH (DELIMITER ',', FORMAT CSV, HEADER, ENCODING 'UTF-8');".format("SELECT * FROM sender_positions"), gzip_file)

Wyświetl plik

@ -3,3 +3,4 @@ from flask import Blueprint
bp = Blueprint("main", __name__)
import app.main.routes
import app.main.jinja_filters

Wyświetl plik

@ -0,0 +1,64 @@
from app.main import bp
from app.model import Airport, Sender, Receiver
from flask import url_for
import time
import datetime
import math
@bp.app_template_filter()
def timestamp_to_status(timestamp):
if datetime.datetime.utcnow() - timestamp < datetime.timedelta(minutes=10):
return 'OK'
elif datetime.datetime.utcnow() - timestamp < datetime.timedelta(hours=1):
return '<b>?</b>'
else:
return '<b>OFFLINE</b>'
@bp.app_template_filter()
def to_html_link(obj):
if isinstance(obj, Airport):
airport = obj
return f"""<a href="{url_for('main.airport_detail', airport_id=airport.id)}">{airport.name}</a>"""
elif isinstance(obj, Sender):
sender = obj
if len(sender.infos) > 0 and len(sender.infos[0].registration) > 0:
return f"""<a href="{url_for('main.sender_detail', sender_id=sender.id)}">{sender.infos[0].registration}</a>"""
elif sender.address:
return f"""<a href="{url_for('main.sender_detail', sender_id=sender.id)}">[{sender.address}]</a>"""
else:
return f"""<a href="{url_for('main.sender_detail', sender_id=sender.id)}">[{sender.name}]</a>"""
elif isinstance(obj, Receiver):
receiver = obj
return f"""<a href="{url_for('main.receiver_detail', receiver_id=receiver.id)}">{receiver.name}</a>"""
elif obj is None:
return "-"
else:
raise NotImplementedError("cant apply filter 'to_html_link' to object {type(obj)}")
@bp.app_template_filter()
def to_ordinal(rad):
deg = math.degrees(rad)
if deg >= 337.5 or deg < 22.5:
return "N"
elif deg >= 22.5 and deg < 67.5:
return "NW"
elif deg >= 67.5 and deg < 112.5:
return "W"
elif deg >= 112.5 and deg < 157.5:
return "SW"
elif deg >= 157.5 and deg < 202.5:
return "S"
elif deg >= 202.5 and deg < 247.5:
return "SE"
elif deg >= 247.5 and deg < 292.5:
return "E"
elif deg >= 292.5 and deg < 337.5:
return "NE"

Wyświetl plik

@ -1,87 +0,0 @@
from flask import request, render_template, current_app
from flask_cors import cross_origin
from app.backend.liveglidernet import rec, lxml
from app.main import bp
@bp.route("/live.html")
@cross_origin()
def live():
return render_template("ogn_live.html", host=request.host)
@bp.route("/rec.php")
def rec_php():
a = request.args.get("a")
z = request.args.get("z")
xml = rec()
resp = current_app.make_response(xml)
resp.mimetype = "text/xml"
return resp
@bp.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 = current_app.make_response(xml)
resp.mimetype = "text/xml"
return resp
@bp.route("/pict/<filename>")
def pict(filename):
return current_app.send_static_file("ognlive/pict/" + filename)
@bp.route("/favicon.gif")
def favicon_gif():
return current_app.send_static_file("ognlive/pict/favicon.gif")
@bp.route("/horizZoomControl.js")
def horizZoomControl_js():
return current_app.send_static_file("ognlive/horizZoomControl.js")
@bp.route("/barogram.js")
def barogram_js():
return current_app.send_static_file("ognlive/barogram.js")
@bp.route("/util.js")
def util_js():
return current_app.send_static_file("ognlive/util.js")
@bp.route("/ogn.js")
def ogn_js():
return current_app.send_static_file("ognlive/ogn.js")
@bp.route("/ol.js")
def ol_js():
return current_app.send_static_file("ognlive/ol.js")
@bp.route("/osm.js")
def osm_js():
return current_app.send_static_file("ognlive/osm.js")
@bp.route("/ol.css")
def ol_css():
return current_app.send_static_file("ognlive/ol.css")
@bp.route("/osm.css")
def osm_css():
return current_app.send_static_file("ognlive/osm.css")

Wyświetl plik

@ -0,0 +1,44 @@
from app import db
from app.model import SenderDirectionStatistic
import random
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
def create_range_figure2(sender_id):
fig = Figure()
axis = fig.add_subplot(1, 1, 1)
xs = range(100)
ys = [random.randint(1, 50) for x in xs]
axis.plot(xs, ys)
return fig
def create_range_figure(sender_id):
sds = db.session.query(SenderDirectionStatistic) \
.filter(SenderDirectionStatistic.sender_id == sender_id) \
.order_by(SenderDirectionStatistic.directions_count.desc()) \
.limit(1) \
.one()
fig = Figure()
direction_data = sds.direction_data
max_range = max([r['max_range'] / 1000.0 for r in direction_data])
theta = np.array([i['direction'] / 180 * np.pi for i in direction_data])
radii = np.array([i['max_range'] / 1000 if i['max_range'] > 0 else 0 for i in direction_data])
width = np.array([13 / 180 * np.pi for i in direction_data])
colors = plt.cm.viridis(radii / max_range)
ax = fig.add_subplot(111, projection='polar')
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, edgecolor='b', alpha=0.5)
#ax.set_rticks([0, 25, 50, 75, 100, 125, 150])
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
fig.suptitle(f"Range between sender '{sds.sender.name}' and receiver '{sds.receiver.name}'")
return fig

Wyświetl plik

@ -1,12 +1,13 @@
import datetime
from datetime import date, time, datetime
from flask import request, render_template, send_file
from app import db
from app import cache
from app.model import Airport, Country, Device, Logbook, Receiver, ReceiverStats
from app.model import Airport, Country, Sender, SenderInfo, TakeoffLanding, Logbook, Receiver, SenderPosition, RelationStatistic, ReceiverStatistic, SenderStatistic
from app.main import bp
from app.main.matplotlib_service import create_range_figure
@cache.cached(key_prefix="countries_in_receivers")
@ -16,28 +17,26 @@ def get_countries_in_receivers():
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)
@cache.cached(key_prefix="countries_in_takeoff_landings")
def get_used_countries():
query = db.session.query(Country.iso2).filter(Country.gid == TakeoffLanding.country_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()]
def get_used_airports_by_country(sel_country):
query = db.session.query(Airport).filter(Airport.country_code == sel_country).filter(TakeoffLanding.airport_id == Airport.id).filter(TakeoffLanding.country_id == Country.gid).order_by(Airport.name).distinct(Airport.name)
return [used_airport for used_airport in query]
@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"))
db.session.query(db.func.date(Logbook.reference_timestamp), 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())
.group_by(db.func.date(Logbook.reference_timestamp))
.order_by(db.func.date(Logbook.reference_timestamp).desc())
)
return [{"date": date, "logbook_count": logbook_count} for (date, logbook_count) in query.all()]
@ -46,21 +45,56 @@ def get_dates_for_airport(sel_airport):
@bp.route("/")
@bp.route("/index.html")
def index():
return render_template("base.html")
today_beginning = datetime.combine(date.today(), time())
senders_today = db.session.query(db.func.count(Sender.id)).filter(Sender.lastseen >= today_beginning).one()[0]
receivers_today = db.session.query(db.func.count(Receiver.id)).filter(Receiver.lastseen >= today_beginning).one()[0]
takeoffs_today = db.session.query(db.func.count(TakeoffLanding.id)).filter(db.and_(TakeoffLanding.timestamp >= today_beginning, TakeoffLanding.is_takeoff is True)).one()[0]
landings_today = db.session.query(db.func.count(TakeoffLanding.id)).filter(db.and_(TakeoffLanding.timestamp >= today_beginning, TakeoffLanding.is_takeoff is False)).one()[0]
sender_positions_today = db.session.query(db.func.sum(ReceiverStatistic.messages_count)).filter(ReceiverStatistic.date == date.today()).one()[0]
sender_positions_total = db.session.query(db.func.sum(ReceiverStatistic.messages_count)).one()[0]
last_logbook_entries = db.session.query(Logbook).order_by(Logbook.reference_timestamp.desc()).limit(10)
return render_template(
"index.html",
senders_today=senders_today,
receivers_today=receivers_today,
takeoffs_today=takeoffs_today,
landings_today=landings_today,
sender_positions_today=sender_positions_today,
sender_positions_total=sender_positions_total,
logbook=last_logbook_entries)
@bp.route("/devices.html", methods=["GET", "POST"])
def devices():
devices = db.session.query(Device).order_by(Device.address)
return render_template("devices.html", devices=devices)
@bp.route("/senders.html", methods=["GET", "POST"])
def senders():
senders = db.session.query(Sender) \
.options(db.joinedload(Sender.infos)) \
.order_by(Sender.name)
return render_template("senders.html", senders=senders)
@bp.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()
@bp.route("/sender_detail.html", methods=["GET", "POST"])
def sender_detail():
sender_id = request.args.get("sender_id")
sender = db.session.query(Sender).filter(Sender.id == sender_id).one()
return render_template("device_detail.html", title="Device", device=device)
return render_template("sender_detail.html", title="Sender", sender=sender)
@bp.route("/range_view.png")
def range_view():
import io
from flask import Response
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
sender_id = request.args.get("sender_id")
fig = create_range_figure(sender_id)
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return Response(output.getvalue(), mimetype='image/png')
@bp.route("/receivers.html")
@ -71,41 +105,34 @@ def 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)
receivers = db.session.query(Receiver) \
.options(db.joinedload(Receiver.airport)) \
.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)
receivers = db.session.query(Receiver) \
.options(db.joinedload(Receiver.airport)) \
.order_by(Receiver.name)
return render_template("receivers.html", title="Receivers", sel_country=sel_country, countries=countries, receivers=receivers)
@bp.route("/receiver_detail.html")
def receiver_detail():
sel_receiver_id = request.args.get("receiver_id")
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())
receiver = db.session.query(Receiver).filter(Receiver.id == receiver_id).one()
return render_template("receiver_detail.html", title="Receiver Detail", receiver=receiver)
@bp.route("/airports.html", methods=["GET", "POST"])
def airports():
sel_country = request.args.get("country")
countries = get_countries_in_logbook()
countries = get_used_countries()
if sel_country:
airports = get_airports_in_country(sel_country)
airports = get_used_airports_by_country(sel_country)
else:
airports = []
@ -116,41 +143,41 @@ def airports():
@bp.route("/airport_detail.html")
def airport_detail():
sel_airport = request.args.get("airport")
sel_airport = request.args.get("airport_id")
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)
senders = db.session.query(Sender).join(Logbook).filter(Logbook.takeoff_airport_id == sel_airport).order_by(Sender.name)
return render_template("airport_detail.html", title="Airport Detail", airport=airport.one(), devices=devices)
return render_template("airport_detail.html", title="Airport Detail", airport=airport.one(), senders=senders)
@bp.route("/logbook.html", methods=["GET", "POST"])
def logbook():
@bp.route("/logbooks.html", methods=["GET", "POST"])
def logbooks():
sel_country = request.args.get("country")
sel_airport = request.args.get("airport")
sel_airport_id = request.args.get("airport_id")
sel_date = request.args.get("date")
sel_device_id = request.args.get("device_id")
sel_sender_id = request.args.get("sender_id")
countries = get_countries_in_logbook()
countries = get_used_countries()
if sel_country:
airports = get_airports_in_country(sel_country)
airports = get_used_airports_by_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
if sel_airport_id:
sel_airport_id = int(sel_airport_id)
if sel_airport_id not in [airport.id for airport in airports]:
sel_airport_id = None
sel_date = None
dates = get_dates_for_airport(sel_airport)
dates = get_dates_for_airport(sel_airport_id)
else:
dates = []
if sel_date:
sel_date = datetime.datetime.strptime(sel_date, "%Y-%m-%d").date()
sel_date = 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:
@ -158,21 +185,21 @@ def logbook():
# Get Logbook
filters = []
if sel_airport:
filters.append(db.or_(Logbook.takeoff_airport_id == sel_airport, Logbook.landing_airport_id == sel_airport))
if sel_airport_id:
filters.append(db.or_(Logbook.takeoff_airport_id == sel_airport_id, Logbook.landing_airport_id == sel_airport_id))
if sel_date:
filters.append(db.func.date(Logbook.reftime) == sel_date)
filters.append(db.func.date(Logbook.reference_timestamp) == sel_date)
if sel_device_id:
filters.append(Logbook.device_id == sel_device_id)
if sel_sender_id:
filters.append(Logbook.sender_id == sel_sender_id)
if len(filters) > 0:
logbook = db.session.query(Logbook).filter(*filters).order_by(Logbook.reftime)
logbooks = db.session.query(Logbook).filter(*filters).order_by(Logbook.reference_timestamp).limit(100)
else:
logbook = None
logbooks = 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)
return render_template("logbooks.html", title="Logbook", sel_country=sel_country, countries=countries, sel_airport_id=sel_airport_id, airports=airports, sel_date=sel_date, dates=dates, logbooks=logbooks)
@bp.route("/download.html")
@ -186,12 +213,27 @@ def download_flight():
return send_file(buffer, as_attachment=True, attachment_filename="wtf.igc", mimetype="text/plain")
@bp.route("/statistics.html")
def statistics():
@bp.route("/sender_ranking.html")
def sender_ranking():
sender_statistics = db.session.query(SenderStatistic) \
.filter(db.and_(SenderStatistic.date == date.today(), SenderStatistic.is_trustworthy is True)) \
.order_by(SenderStatistic.max_distance.desc()) \
.all()
today = datetime.date.today()
today = datetime.date(2018, 7, 31)
return render_template(
"sender_ranking.html",
title="Sender Ranking",
ranking=sender_statistics)
receiverstats = db.session.query(ReceiverStats).filter(ReceiverStats.date == today)
return render_template("statistics.html", title="Receiver Statistics", receiverstats=receiverstats)
@bp.route("/receiver_ranking.html")
def receiver_ranking():
receiver_statistics = db.session.query(ReceiverStatistic) \
.filter(db.and_(ReceiverStatistic.date == date.today(), ReceiverStatistic.is_trustworthy is True)) \
.order_by(ReceiverStatistic.max_distance.desc()) \
.all()
return render_template(
"receiver_ranking.html",
title="Receiver Ranking",
ranking=receiver_statistics)

Wyświetl plik

@ -1,21 +1,22 @@
# flake8: noqa
from .aircraft_type import AircraftType
from .beacon import Beacon
from .country import Country
from .country_stats import CountryStats
from .device import Device
from .device_info import DeviceInfo
from .device_info_origin import DeviceInfoOrigin
from .device_stats import DeviceStats
from .aircraft_beacon import AircraftBeacon
from .receiver_beacon import ReceiverBeacon
from .sender import Sender
from .sender_info_origin import SenderInfoOrigin
from .sender_info import SenderInfo
from .sender_position import SenderPosition
from .receiver_position import ReceiverPosition
from .receiver_status import ReceiverStatus
from .receiver import Receiver
from .receiver_stats import ReceiverStats
from .takeoff_landing import TakeoffLanding
from .airport import Airport
from .logbook import Logbook
from .receiver_coverage import ReceiverCoverage
from .relation_stats import RelationStats
from .flights2d import Flight2D
from .geo import Location
from .relation_statistic import RelationStatistic
from .coverage_statistic import CoverageStatistic
from .sender_statistic import SenderStatistic
from .receiver_statistic import ReceiverStatistic
from .sender_position_statistic import SenderPositionStatistic
from .sender_direction_statistic import SenderDirectionStatistic

Wyświetl plik

@ -1,130 +0,0 @@
from sqlalchemy.sql import func
from app import db
from .beacon import Beacon
from .aircraft_type import AircraftType
class AircraftBeacon(Beacon):
__tablename__ = "aircraft_beacons"
# Flarm specific data
address_type = db.Column(db.SmallInteger)
aircraft_type = db.Column(db.Enum(AircraftType), nullable=False, default=AircraftType.UNKNOWN)
stealth = db.Column(db.Boolean)
address = db.Column(db.String)
climb_rate = db.Column(db.Float(precision=2))
turn_rate = db.Column(db.Float(precision=2))
signal_quality = db.Column(db.Float(precision=2))
error_count = db.Column(db.SmallInteger)
frequency_offset = db.Column(db.Float(precision=2))
gps_quality_horizontal = db.Column(db.SmallInteger)
gps_quality_vertical = db.Column(db.SmallInteger)
software_version = db.Column(db.Float(precision=2))
hardware_version = db.Column(db.SmallInteger)
real_address = db.Column(db.String(6))
signal_power = db.Column(db.Float(precision=2))
proximity = None
# 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
agl = db.Column(db.Float(precision=2))
def __repr__(self):
return "<AircraftBeacon %s: %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s>" % (
self.address_type,
self.aircraft_type,
self.stealth,
self.address,
self.climb_rate,
self.turn_rate,
self.signal_quality,
self.error_count,
self.frequency_offset,
self.gps_quality_horizontal,
self.gps_quality_vertical,
self.software_version,
self.hardware_version,
self.real_address,
self.signal_power,
self.distance,
self.radial,
self.quality,
self.location_mgrs,
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",
]
def get_values(self):
return [
self.location_wkt,
int(self.altitude) if self.altitude else None,
self.name,
self.dstcall,
self.relay,
self.receiver_name,
self.timestamp,
self.track,
self.ground_speed,
# self.raw_message,
# self.reference_timestamp,
self.address_type,
self.aircraft_type,
self.stealth,
self.address,
self.climb_rate,
self.turn_rate,
self.signal_quality,
self.error_count,
self.frequency_offset,
self.gps_quality_horizontal,
self.gps_quality_vertical,
self.software_version,
self.hardware_version,
self.real_address,
self.signal_power,
self.distance,
self.radial,
self.quality,
self.location_mgrs,
self.location_mgrs_short,
]

Wyświetl plik

@ -1,45 +0,0 @@
from geoalchemy2.shape import to_shape
from geoalchemy2.types import Geometry
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.ext.hybrid import hybrid_property
from .geo import Location
from app import db
class Beacon(AbstractConcreteBase, db.Model):
# APRS data
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)
dstcall = db.Column(db.String)
relay = db.Column(db.String)
receiver_name = db.Column(db.String(9), primary_key=True, nullable=True)
timestamp = db.Column(db.DateTime, primary_key=True, nullable=True)
symboltable = None
symbolcode = None
track = db.Column(db.SmallInteger)
ground_speed = db.Column(db.Float(precision=2))
comment = None
# Type information
beacon_type = None
aprs_type = None
# Debug information
raw_message = None
reference_timestamp = None
@hybrid_property
def location(self):
if self.location_wkt is None:
return None
coords = to_shape(self.location_wkt)
return Location(lat=coords.y, lon=coords.x)
@location.expression
def location(cls):
return cls.location_wkt

Wyświetl plik

@ -1,17 +0,0 @@
from app import db
class CountryStats(db.Model):
__tablename__ = "country_stats"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
# Static data
aircraft_beacon_count = db.Column(db.Integer)
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()"))

Wyświetl plik

@ -0,0 +1,25 @@
from app import db
class CoverageStatistic(db.Model):
__tablename__ = "coverage_statistics"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
location_mgrs_short = db.Column(db.String(9))
is_trustworthy = db.Column(db.Boolean)
messages_count = db.Column(db.Integer)
max_distance = db.Column(db.Float(precision=2))
max_normalized_quality = db.Column(db.Float(precision=2))
coverages_count = db.Column(db.Integer)
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True)
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=db.backref("coverage_stats", order_by=date))
receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="CASCADE"), index=True)
receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref=db.backref("coverage_stats", order_by=date))
__table_args__ = (db.Index('idx_coverage_statistics_uc', 'date', 'location_mgrs_short', 'sender_id', 'receiver_id', 'is_trustworthy', unique=True), )

Wyświetl plik

@ -1,8 +0,0 @@
import enum
class DeviceInfoOrigin(enum.Enum):
UNKNOWN = 0
OGN_DDB = 1
FLARMNET = 2
USER_DEFINED = 3

Wyświetl plik

@ -1,52 +0,0 @@
from app import db
from .aircraft_type import AircraftType
class DeviceStats(db.Model):
__tablename__ = "device_stats"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
# Static data
name = db.Column(db.String)
firstseen = db.Column(db.DateTime)
lastseen = db.Column(db.DateTime)
aircraft_type = db.Column(db.Enum(AircraftType), nullable=False, default=AircraftType.UNKNOWN)
stealth = db.Column(db.Boolean)
software_version = db.Column(db.Float(precision=2))
hardware_version = db.Column(db.SmallInteger)
real_address = db.Column(db.String(6))
# Statistic data
max_altitude = db.Column(db.Float(precision=2))
receiver_count = db.Column(db.SmallInteger)
aircraft_beacon_count = db.Column(db.Integer)
jumps = db.Column(db.SmallInteger)
ambiguous = db.Column(db.Boolean)
quality = db.Column(db.Float(precision=2))
# Relation statistic data
quality_offset = db.Column(db.Float(precision=2))
# Ranking data
max_altitude_ranking_worldwide = db.Column(db.Integer)
max_altitude_ranking_country = db.Column(db.Integer)
receiver_count_ranking_worldwide = db.Column(db.Integer)
receiver_count_ranking_country = db.Column(db.Integer)
aircraft_beacon_count_ranking_worldwide = db.Column(db.Integer)
aircraft_beacon_count_ranking_country = db.Column(db.Integer)
quality_ranking_worldwide = db.Column(db.Integer)
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()"))
def __repr__(self):
return "<DeviceStats: %s,%s,%s,%s>" % (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)

Wyświetl plik

@ -1,24 +0,0 @@
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 "<Flight %s: %s,%s>" % (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)

Wyświetl plik

@ -1,14 +1,13 @@
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import null, case
from app import db
class Logbook(db.Model):
__tablename__ = "logbook"
__tablename__ = "logbooks"
id = db.Column(db.Integer, primary_key=True)
reftime = db.Column(db.DateTime, index=True)
takeoff_timestamp = db.Column(db.DateTime)
takeoff_track = db.Column(db.SmallInteger)
landing_timestamp = db.Column(db.DateTime)
@ -16,14 +15,20 @@ class Logbook(db.Model):
max_altitude = db.Column(db.Float(precision=2))
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True)
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=db.backref("logbook_entries", order_by=db.case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != db.null()).desc()))
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 = db.relationship("Airport", foreign_keys=[takeoff_airport_id], backref=db.backref("logbook_entries_takeoff", order_by=db.case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != db.null()).desc()))
takeoff_country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="CASCADE"), index=True)
takeoff_country = db.relationship("Country", foreign_keys=[takeoff_country_id], backref=db.backref("logbook_entries_takeoff", order_by=db.case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != db.null()).desc()))
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 = db.relationship("Airport", foreign_keys=[landing_airport_id], backref=db.backref("logbook_entries_landing", order_by=db.case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != db.null()).desc()))
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"))
landing_country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="CASCADE"), index=True)
landing_country = db.relationship("Country", foreign_keys=[landing_country_id], backref=db.backref("logbook_entries_landing", order_by=db.case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != db.null()).desc()))
@hybrid_property
def duration(self):
@ -31,4 +36,22 @@ class Logbook(db.Model):
@duration.expression
def duration(cls):
return case({False: None, True: cls.landing_timestamp - cls.takeoff_timestamp}, cls.landing_timestamp != null() and cls.takeoff_timestamp != null())
return db.case({False: None, True: cls.landing_timestamp - cls.takeoff_timestamp}, cls.landing_timestamp != db.null() and cls.takeoff_timestamp != db.null())
@hybrid_property
def reference_timestamp(self):
return self.takeoff_timestamp if self.takeoff_timestamp is not None else self.landing_timestamp
@reference_timestamp.expression
def reference_timestamp(cls):
return db.case({True: cls.takeoff_timestamp, False: cls.landing_timestamp}, cls.takeoff_timestamp != db.null())
#__table_args__ = (db.Index('idx_logbook_reference_timestamp', db.case({True: takeoff_timestamp, False: landing_timestamp}, takeoff_timestamp != db.null())),)
# FIXME: does not work...
# FIXME: this does not throw an error as the __table_args__ above, but there is no index created
#_wrapped_case = f"({db.case(whens={True: Logbook.takeoff_timestamp, False: Logbook.landing_timestamp}, value=Logbook.takeoff_timestamp != db.null())})"
#Index("idx_logbook_reference_timestamp", _wrapped_case)
# TODO:
# so execute manually: CREATE INDEX IF NOT EXISTS idx_logbook_reference_timestamp ON logbooks ((CASE takeoff_timestamp IS NULL WHEN true THEN takeoff_timestamp WHEN false THEN landing_timestamp END));

Wyświetl plik

@ -5,25 +5,37 @@ from .geo import Location
from app import db
from .airport import Airport
class Receiver(db.Model):
__tablename__ = "receivers"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(9))
location_wkt = db.Column("location", Geometry("POINT", srid=4326))
altitude = db.Column(db.Float(precision=2))
name = db.Column(db.String(9), index=True)
firstseen = db.Column(db.DateTime, index=True)
lastseen = db.Column(db.DateTime, index=True)
timestamp = db.Column(db.DateTime, index=True)
version = db.Column(db.String)
platform = db.Column(db.String)
cpu_temp = db.Column(db.Float(precision=2))
rec_input_noise = db.Column(db.Float(precision=2))
agl = db.Column(db.Float(precision=2))
# 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()"))
airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="CASCADE"), index=True)
airport = db.relationship("Airport", foreign_keys=[airport_id], backref=db.backref("receivers", order_by="Receiver.name.asc()"))
__table_args__ = (db.Index('idx_receivers_name_uc', 'name', unique=True), )
@property
def location(self):
if self.location_wkt is None:
@ -31,3 +43,14 @@ class Receiver(db.Model):
coords = to_shape(self.location_wkt)
return Location(lat=coords.y, lon=coords.x)
def airports_nearby(self):
query = (
db.session.query(Airport, db.func.st_distance_sphere(self.location_wkt, Airport.location_wkt), db.func.st_azimuth(self.location_wkt, Airport.location_wkt))
.filter(db.func.st_contains(db.func.st_buffer(Airport.location_wkt, 1), self.location_wkt))
.filter(Airport.style.in_((2, 4, 5)))
.order_by(db.func.st_distance_sphere(self.location_wkt, Airport.location_wkt).asc())
.limit(5)
)
airports = [(airport, distance, azimuth) for airport, distance, azimuth in query]
return airports

Wyświetl plik

@ -1,45 +0,0 @@
from .beacon import Beacon
class ReceiverBeacon(Beacon):
__tablename__ = "receiver_beacons"
# disable irrelevant aprs fields
relay = None
track = None
ground_speed = None
def __repr__(self):
return "<ReceiverBeacon %s: %s,%s,%s,%s,%s>" % (
self.name,
self.location,
self.altitude,
self.dstcall,
self.receiver_name,
self.timestamp,
)
@classmethod
def get_columns(self):
return [
"location",
"altitude",
"name",
"dstcall",
"receiver_name",
"timestamp",
# 'raw_message',
# 'reference_timestamp',
]
def get_values(self):
return [
self.location_wkt,
int(self.altitude) if self.altitude else None,
self.name,
self.dstcall,
self.receiver_name,
self.timestamp,
# self.raw_message,
# self.reference_timestamp,
]

Wyświetl plik

@ -1,23 +0,0 @@
from app import db
class ReceiverCoverage(db.Model):
__tablename__ = "receiver_coverages"
location_mgrs_short = db.Column(db.String(9), primary_key=True)
date = db.Column(db.Date, primary_key=True)
max_signal_quality = db.Column(db.Float)
max_altitude = db.Column(db.Float(precision=2))
min_altitude = db.Column(db.Float(precision=2))
aircraft_beacon_count = db.Column(db.Integer)
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()"))
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)

Wyświetl plik

@ -0,0 +1,39 @@
from geoalchemy2.types import Geometry
from app import db
class ReceiverPosition(db.Model):
__tablename__ = "receiver_positions"
reference_timestamp = db.Column(db.DateTime, primary_key=True)
# APRS data
name = db.Column(db.String)
dstcall = db.Column(db.String)
#relay = db.Column(db.String)
receiver_name = db.Column(db.String(9))
timestamp = db.Column(db.DateTime)
location = db.Column("location", Geometry("POINT", srid=4326))
symboltable = None
symbolcode = None
#track = db.Column(db.SmallInteger)
#ground_speed = db.Column(db.Float(precision=2))
altitude = db.Column(db.Float(precision=2))
comment = None
# Type information
beacon_type = None
aprs_type = None
# Debug information
raw_message = None
# Receiver specific data
user_comment = None
# Calculated values (from this software)
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))

Wyświetl plik

@ -0,0 +1,22 @@
from app import db
class ReceiverStatistic(db.Model):
__tablename__ = "receiver_statistics"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
is_trustworthy = db.Column(db.Boolean)
max_distance = db.Column(db.Float(precision=2))
max_normalized_quality = db.Column(db.Float(precision=2))
messages_count = db.Column(db.Integer)
coverages_count = db.Column(db.Integer)
senders_count = db.Column(db.Integer)
# Relations
receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="CASCADE"), index=True)
receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref=db.backref("statistics", order_by=date.desc()))
__table_args__ = (db.Index('idx_receiver_statistics_uc', 'date', 'receiver_id', 'is_trustworthy', unique=True), )

Wyświetl plik

@ -1,41 +0,0 @@
from geoalchemy2.types import Geometry
from app import db
class ReceiverStats(db.Model):
__tablename__ = "receiver_stats"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
# 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))
altitude = db.Column(db.Float(precision=2))
version = db.Column(db.String)
platform = db.Column(db.String)
# Statistic data
aircraft_beacon_count = db.Column(db.Integer)
aircraft_count = db.Column(db.SmallInteger)
max_distance = db.Column(db.Float)
quality = db.Column(db.Float(precision=2))
# Relation statistic data
quality_offset = db.Column(db.Float(precision=2))
# Ranking data
aircraft_beacon_count_ranking = db.Column(db.SmallInteger)
aircraft_count_ranking = db.Column(db.SmallInteger)
max_distance_ranking = db.Column(db.SmallInteger)
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()"))
db.Index("ix_receiver_stats_date_receiver_id", ReceiverStats.date, ReceiverStats.receiver_id)

Wyświetl plik

@ -0,0 +1,48 @@
from app import db
class ReceiverStatus(db.Model):
__tablename__ = "receiver_statuses"
reference_timestamp = db.Column(db.DateTime, primary_key=True)
# APRS data
name = db.Column(db.String)
dstcall = db.Column(db.String)
receiver_name = db.Column(db.String(9))
timestamp = db.Column(db.DateTime)
# Type information
beacon_type = None
aprs_type = None
# Debug information
raw_message = None
# Receiver specific data
version = db.Column(db.String)
platform = db.Column(db.String)
cpu_load = None
free_ram = None
total_ram = None
ntp_error = None
rt_crystal_correction = None
voltage = None
amperage = None
cpu_temp = db.Column(db.Float(precision=2))
senders_visible = None
senders_total = None
rec_crystal_correction = None
rec_crystal_correction_fine = None
rec_input_noise = db.Column(db.Float(precision=2))
senders_signal = None
senders_messages = None
good_senders_signal = None
good_senders = None
good_and_bad_senders = None
# Calculated values (from this software)
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))

Wyświetl plik

@ -0,0 +1,23 @@
from app import db
class RelationStatistic(db.Model):
__tablename__ = "relation_statistics"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
is_trustworthy = db.Column(db.Boolean)
max_distance = db.Column(db.Float(precision=2))
max_normalized_quality = db.Column(db.Float(precision=2))
messages_count = db.Column(db.Integer)
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True)
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=db.backref("relation_stats", order_by=date))
receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="CASCADE"), index=True)
receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref=db.backref("relation_stats", order_by=date))
__table_args__ = (db.Index('idx_relation_statistics_uc', 'date', 'sender_id', 'receiver_id', 'is_trustworthy', unique=True), )

Wyświetl plik

@ -1,26 +0,0 @@
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 "<RelationStats: %s,%s,%s>" % (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)

Wyświetl plik

@ -3,18 +3,16 @@ import datetime
from sqlalchemy.ext.hybrid import hybrid_property
from app import db
from .device_info import DeviceInfo
from app.model.aircraft_type import AircraftType
class Device(db.Model):
__tablename__ = "devices"
class Sender(db.Model):
__tablename__ = "senders"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
name = db.Column(db.String, index=True)
# address = db.Column(db.String(6), index=True)
address = db.Column(db.String, index=True)
address = db.Column(db.String(6), index=True)
firstseen = db.Column(db.DateTime, index=True)
lastseen = db.Column(db.DateTime, index=True)
aircraft_type = db.Column(db.Enum(AircraftType), nullable=False, default=AircraftType.UNKNOWN)
@ -23,21 +21,16 @@ class Device(db.Model):
hardware_version = db.Column(db.SmallInteger)
real_address = db.Column(db.String(6))
__table_args__ = (db.Index('idx_senders_name_uc', 'name', unique=True), )
def __repr__(self):
return "<Device: %s,%s,%s,%s,%s,%s>" % (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)
return query.first()
def get_infos(self):
query = db.session.query(DeviceInfo).filter(DeviceInfo.address == self.address).order_by(DeviceInfo.address_origin)
return [info for info in query.all()]
return "<Sender: %s,%s,%s,%s,%s,%s>" % (self.address, self.aircraft_type, self.stealth, self.software_version, self.hardware_version, self.real_address)
EXPIRY_DATES = {
7.01: datetime.date(2022, 2, 28),
7.0: datetime.date(2021, 10, 31),
6.83: datetime.date(2021, 10, 31),
6.82: datetime.date(2021, 5, 31),
6.81: datetime.date(2021, 1, 31),
6.80: datetime.date(2021, 1, 31),
6.67: datetime.date(2020, 10, 31),

Wyświetl plik

@ -0,0 +1,22 @@
from app import db
from sqlalchemy.dialects.postgresql import JSON
class SenderDirectionStatistic(db.Model):
__tablename__ = "sender_direction_statistics"
id = db.Column(db.Integer, primary_key=True)
directions_count = db.Column(db.Integer)
messages_count = db.Column(db.Integer)
direction_data = db.Column(db.JSON)
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True)
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=db.backref("direction_stats", order_by=directions_count.desc()))
receiver_id = db.Column(db.Integer, db.ForeignKey("receivers.id", ondelete="CASCADE"), index=True)
receiver = db.relationship("Receiver", foreign_keys=[receiver_id], backref=db.backref("direction_stats", order_by=directions_count.desc()))
__table_args__ = (db.Index('idx_sender_direction_statistics_uc', 'sender_id', 'receiver_id', unique=True), )

Wyświetl plik

@ -1,15 +1,16 @@
from app import db
from .device_info_origin import DeviceInfoOrigin
from .sender_info_origin import SenderInfoOrigin
from .aircraft_type import AircraftType
#from sqlalchemy.dialects.postgresql import ENUM
class DeviceInfo(db.Model):
__tablename__ = "device_infos"
class SenderInfo(db.Model):
__tablename__ = "sender_infos"
id = db.Column(db.Integer, primary_key=True)
address = db.Column(db.String(6), index=True)
address_type = None
# 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))
competition = db.Column(db.String(3))
@ -17,10 +18,14 @@ class DeviceInfo(db.Model):
identified = db.Column(db.Boolean)
aircraft_type = db.Column(db.Enum(AircraftType), nullable=False, default=AircraftType.UNKNOWN)
address_origin = db.Column(db.Enum(DeviceInfoOrigin), nullable=False, default=DeviceInfoOrigin.UNKNOWN)
address_origin = db.Column(db.Enum(SenderInfoOrigin), nullable=False, default=SenderInfoOrigin.UNKNOWN)
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id"), index=True)
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=db.backref("infos", order_by=address_origin))
def __repr__(self):
return "<DeviceInfo: %s,%s,%s,%s,%s,%s,%s,%s,%s>" % (
return "<SenderInfo: %s,%s,%s,%s,%s,%s,%s,%s,%s>" % (
self.address_type,
self.address,
self.aircraft,

Wyświetl plik

@ -0,0 +1,9 @@
import enum
class SenderInfoOrigin(enum.Enum):
# lower number == more trustworthy
USER_DEFINED = 0
OGN_DDB = 1
FLARMNET = 2
UNKNOWN = 3

Wyświetl plik

@ -0,0 +1,62 @@
from geoalchemy2.types import Geometry
from app import db
from .aircraft_type import AircraftType
class SenderPosition(db.Model):
__tablename__ = "sender_positions"
reference_timestamp = db.Column(db.DateTime, primary_key=True)
# APRS data
name = db.Column(db.String)
dstcall = db.Column(db.String)
relay = db.Column(db.String)
receiver_name = db.Column(db.String(9))
timestamp = db.Column(db.DateTime)
location = db.Column("location", Geometry("POINT", srid=4326))
symboltable = None
symbolcode = None
track = db.Column(db.SmallInteger)
ground_speed = db.Column(db.Float(precision=2))
altitude = db.Column(db.Float(precision=2))
comment = None
# Type information
beacon_type = None
aprs_type = None
# Debug information
raw_message = None
# Flarm specific data
address_type = db.Column(db.SmallInteger)
aircraft_type = db.Column(db.Enum(AircraftType), nullable=False, default=AircraftType.UNKNOWN)
stealth = db.Column(db.Boolean)
address = db.Column(db.String)
climb_rate = db.Column(db.Float(precision=2))
turn_rate = db.Column(db.Float(precision=2))
signal_quality = db.Column(db.Float(precision=2))
error_count = db.Column(db.SmallInteger)
frequency_offset = db.Column(db.Float(precision=2))
gps_quality_horizontal = db.Column(db.SmallInteger)
gps_quality_vertical = db.Column(db.SmallInteger)
software_version = db.Column(db.Float(precision=2))
hardware_version = db.Column(db.SmallInteger)
real_address = db.Column(db.String(6))
signal_power = db.Column(db.Float(precision=2))
#proximity = None
# Calculated values (from parser)
distance = db.Column(db.Float(precision=2))
bearing = db.Column(db.SmallInteger)
normalized_quality = db.Column(db.Float(precision=2)) # signal quality normalized to 10km
# Calculated values (from this software)
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))

Wyświetl plik

@ -0,0 +1,24 @@
from app import db
from .aircraft_type import AircraftType
from sqlalchemy.dialects.postgresql import ENUM
class SenderPositionStatistic(db.Model):
__tablename__ = "sender_position_statistics"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
dstcall = db.Column(db.String)
address_type = db.Column(db.SmallInteger)
aircraft_type = db.Column(ENUM(AircraftType, create_type=False), nullable=False, default=AircraftType.UNKNOWN)
stealth = db.Column(db.Boolean)
software_version = db.Column(db.Float(precision=2))
hardware_version = db.Column(db.SmallInteger)
messages_count = db.Column(db.Integer)
__table_args__ = (db.Index('idx_sender_position_statistics_uc', 'date', 'dstcall', 'address_type', 'aircraft_type', 'stealth', 'software_version', 'hardware_version', unique=True), )

Wyświetl plik

@ -0,0 +1,22 @@
from app import db
class SenderStatistic(db.Model):
__tablename__ = "sender_statistics"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date)
is_trustworthy = db.Column(db.Boolean)
max_distance = db.Column(db.Float(precision=2))
max_normalized_quality = db.Column(db.Float(precision=2))
messages_count = db.Column(db.Integer)
coverages_count = db.Column(db.Integer)
receivers_count = db.Column(db.Integer)
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"), index=True)
sender = db.relationship("Sender", foreign_keys=[sender_id], backref=db.backref("statistics", order_by=date.desc()))
__table_args__ = (db.Index('idx_sender_statistics_uc', 'date', 'sender_id', 'is_trustworthy', unique=True), )

Wyświetl plik

@ -4,13 +4,20 @@ 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)
id = db.Column(db.Integer, primary_key=True)
timestamp = db.Column(db.DateTime)
is_takeoff = db.Column(db.Boolean)
track = db.Column(db.SmallInteger)
# Relations
sender_id = db.Column(db.Integer, db.ForeignKey("senders.id", ondelete="CASCADE"))
sender = db.relationship("Sender", foreign_keys=[sender_id], backref="takeoff_landings")
airport_id = db.Column(db.Integer, db.ForeignKey("airports.id", ondelete="SET NULL"))
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")
country_id = db.Column(db.Integer, db.ForeignKey("countries.gid", ondelete="CASCADE"), index=True)
country = db.relationship("Country", foreign_keys=[country_id], backref="takeoff_landings")
__table_args__ = (db.Index('idx_takeoff_landings_uc', 'timestamp', 'sender_id', 'airport_id', unique=True), )

Wyświetl plik

@ -0,0 +1,5 @@
from .sql_tasks import update_statistics, update_sender_direction_statistics
from .orm_tasks import transfer_to_database
from .orm_tasks import update_takeoff_landings, update_logbook, update_logbook_max_altitude
from .orm_tasks import import_ddb

Wyświetl plik

@ -0,0 +1,52 @@
from datetime import datetime, timedelta
from app.collect.logbook import update_takeoff_landings as logbook_update_takeoff_landings, update_logbook as logbook_update
from app.collect.logbook import update_max_altitudes as logbook_update_max_altitudes
from app.collect.database import import_ddb as device_infos_import_ddb
from app.collect.gateway import transfer_from_redis_to_database
from app import db, celery
@celery.task(name="transfer_to_database")
def transfer_to_database():
"""Transfer APRS data from Redis to database."""
result = transfer_from_redis_to_database()
return result
@celery.task(name="update_takeoff_landings")
def update_takeoff_landings(last_minutes):
"""Compute takeoffs and landings."""
end = datetime.utcnow()
start = end - timedelta(minutes=last_minutes)
result = logbook_update_takeoff_landings(start=start, end=end)
return result
@celery.task(name="update_logbook")
def update_logbook(offset_days=None):
"""Add/update logbook entries."""
result = logbook_update(offset_days=offset_days)
return result
@celery.task(name="update_logbook_max_altitude")
def update_logbook_max_altitude():
"""Add max altitudes in logbook when flight is complete (takeoff and landing)."""
result = logbook_update_max_altitudes()
return result
@celery.task(name="import_ddb")
def import_ddb():
"""Import registered devices from the DDB."""
result = device_infos_import_ddb()
return result

Wyświetl plik

@ -0,0 +1,126 @@
from datetime import datetime, timedelta
from app import db, celery
@celery.task(name="update_statistics")
def update_statistics(date_str=None):
""" Update relation_statistics, sender_statistics, receiver_statistics (all depend on coverage_statistics)."""
if date_str is None:
date_str = datetime.utcnow().strftime("%Y-%m-%d")
# Update relation statistics
db.session.execute(f"""
DELETE FROM relation_statistics
WHERE date = '{date_str}';
INSERT INTO relation_statistics AS rs (date, sender_id, receiver_id, is_trustworthy, max_distance, max_normalized_quality, messages_count, coverages_count)
SELECT
tmp.date,
tmp.sender_id,
tmp.receiver_id,
is_trustworthy,
MAX(tmp.max_distance) AS max_distance,
MAX(tmp.max_normalized_quality) AS max_normalized_quality,
SUM(tmp.messages_count) AS messages_count,
COUNT(DISTINCT tmp.location_mgrs_short) AS coverages_count
FROM coverage_statistics AS tmp
WHERE tmp.date = '{date_str}'
GROUP BY date, sender_id, receiver_id, is_trustworthy;
""")
# Update sender statistics
db.session.execute(f"""
DELETE FROM sender_statistics
WHERE date = '{date_str}';
INSERT INTO sender_statistics AS rs (date, sender_id, is_trustworthy, max_distance, max_normalized_quality, messages_count, coverages_count, receivers_count)
SELECT
tmp.date,
tmp.sender_id,
is_trustworthy,
MAX(tmp.max_distance) AS max_distance,
MAX(tmp.max_normalized_quality) AS max_normalized_quality,
SUM(tmp.messages_count) AS messages_count,
COUNT(DISTINCT tmp.location_mgrs_short) AS coverages_count,
COUNT(DISTINCT tmp.receiver_id) AS receivers_count
FROM coverage_statistics AS tmp
WHERE tmp.date = '{date_str}'
GROUP BY date, sender_id, is_trustworthy;
""")
# Update receiver statistics
db.session.execute(f"""
DELETE FROM receiver_statistics
WHERE date = '{date_str}';
INSERT INTO receiver_statistics AS rs (date, receiver_id, is_trustworthy, max_distance, max_normalized_quality, messages_count, coverages_count, senders_count)
SELECT
tmp.date,
tmp.receiver_id,
is_trustworthy,
MAX(tmp.max_distance) AS max_distance,
MAX(tmp.max_normalized_quality) AS max_normalized_quality,
SUM(tmp.messages_count) AS messages_count,
COUNT(DISTINCT tmp.location_mgrs_short) AS coverages_count,
COUNT(DISTINCT tmp.sender_id) AS senders_count
FROM coverage_statistics AS tmp
WHERE tmp.date = '{date_str}'
GROUP BY date, receiver_id, is_trustworthy;
""")
db.session.commit()
@celery.task(name="update_sender_direction_statistics")
def update_sender_direction_statistics():
""" Update sender_direction_statistics."""
db.session.execute("""
DELETE FROM sender_direction_statistics;
INSERT INTO sender_direction_statistics(sender_id, receiver_id, directions_count, messages_count, direction_data)
SELECT
sq2.sender_id,
sq2.receiver_id,
COUNT(sq2.*) AS directions_count,
SUM(sq2.messages_count) AS messages_count,
json_agg(json_build_object('direction', direction, 'messages_count', messages_count, 'max_range', max_range)) AS direction_data
FROM (
SELECT
sq.sender_id,
sq.receiver_id,
sq.direction,
COUNT(sq.*) AS messages_count,
MAX(sq.max_range) AS max_range
FROM (
SELECT
s.id AS sender_id,
r.id AS receiver_id,
10000 * 10^(sp.normalized_quality/20.0) AS max_range,
CASE
WHEN sp.bearing-sp.track < 0
THEN CAST((sp.bearing-sp.track+360)/10 AS INTEGER)*10
ELSE CAST((sp.bearing-sp.track)/10 AS INTEGER)*10
END AS direction
FROM sender_positions AS sp
INNER JOIN senders s ON sp.name = s.name
INNER JOIN receivers r ON sp.receiver_name = r.name
WHERE
sp.track IS NOT NULL AND sp.bearing IS NOT NULL AND sp.normalized_quality IS NOT NULL
AND sp.agl >= 200
AND turn_rate BETWEEN -10.0 AND 10.0
AND climb_rate BETWEEN -3.0 AND 3.0
) AS sq
GROUP BY sq.sender_id, sq.receiver_id, sq.direction
ORDER BY sq.sender_id, sq.receiver_id, sq.direction
) AS sq2
GROUP BY sq2.sender_id, sq2.receiver_id;
""")

Wyświetl plik

@ -21,21 +21,36 @@
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Seen Devices</h3></div>
<div class="panel-heading"><h3 class="panel-title">Receivers</h3></div>
<table class="datatable table table-striped table-bordered">
<tr>
<th>Name</th>
</tr>
{% for receiver in airport.receivers %}
<tr>
<td><a href="{{ url_for('main.receiver_detail', receiver_id=receiver.id) }}">{{ receiver.name }}</a></td>
</tr>
{% endfor %}
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Seen Senders</h3></div>
<table class="datatable table table-striped table-bordered">
<tr>
<th>Address</th>
<th>Registration</th>
<th>Last takeoff/landing</th>
<th>Software version</th>
<th>Name</th>
<th>Last takeoff/landing</th>
<th>Hardware version</th>
<th>Software version</th>
</tr>
{% for device in devices %}
{% for sender in senders %}
<tr>
<td><a href="{{ url_for('main.device_detail', id=device.id) }}">{{ device.address }}</a></td>
<td>{% if device.info is none %}-{% else %}{{ device.info.registration }}{% endif %}</a></td>
<td>{% if device.takeoff_landings %}{% set last_action = device.takeoff_landings|last %}{% if last_action.is_takeoff == True %}↗{% else %}↘{% endif %} @ {{ last_action.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{% endif %}
<td>{% if device.software_version is not none %}{{ device.software_version }}{% else %}-{% endif %}</td>
<td>{{ sender|to_html_link|safe }}</td>
<td>{% if sender.takeoff_landings %}{% set last_action = sender.takeoff_landings|last %}{% if last_action.is_takeoff == True %}↗{% else %}↘{% endif %} @ {{ last_action.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{% endif %}
<td>{% if sender.hardware_version is not none %}{{ sender.hardware_version }}{% else %}-{% endif %}</td>
<td>{% if sender.software_version is not none %}{{ sender.software_version }}{% else %}-{% endif %}</td>
</tr>
{% endfor %}
</table>

Wyświetl plik

@ -23,6 +23,7 @@
<table class="datatable table table-striped table-bordered">
<tr>
<th>#</th>
<th>Country</th>
<th>Name</th>
<th>Logbook (takeoff and landings)</th>
</tr>
@ -30,8 +31,9 @@
{% for airport in airports %}
<tr>
<td>{{ loop.index }}
<td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ sel_country|lower }}" alt="{{ sel_country }}"/> <a href="{{ url_for('main.airport_detail', airport=airport.id) }}">{{ airport.name }}</a></td>
<td><a href="{{ url_for('main.logbook', country=sel_country, airport=airport.id) }}">Logbook</a></td>
<td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ sel_country|lower }}" alt="{{ sel_country }}"/></td>
<td>{{ airport|to_html_link|safe }}</td>
<td><a href="{{ url_for('main.logbooks', country=sel_country, airport_id=airport.id) }}">Logbook</a></td>
</tr>
{% endfor %}
</table>

Wyświetl plik

@ -5,7 +5,7 @@
{% endblock %}
{% block navbar %}
<link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
@ -15,16 +15,17 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">OGN DDB</a>
<a class="navbar-brand" href="#">OGN</a>
</div>
<div class="collapse navbar-collapse" id="bs-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="{{ url_for('main.index') }}">Home</a></li>
<li><a href="{{ url_for('main.devices') }}">Devices</a></li>
<li><a href="{{ url_for('main.senders') }}">Senders</a></li>
<li><a href="{{ url_for('main.receivers') }}">Receivers</a></li>
<li><a href="{{ url_for('main.airports') }}">Airports</a></li>
<li><a href="{{ url_for('main.logbook') }}">Logbook</a></li>
<li><a href="{{ url_for('main.statistics') }}">Statistics</a></li>
<li><a href="{{ url_for('main.logbooks') }}">Logbook</a></li>
<li><a href="{{ url_for('main.sender_ranking') }}">Sender Ranking</a></li>
<li><a href="{{ url_for('main.receiver_ranking') }}">Receiver Ranking</a></li>
</ul>
</div>
</div>

Wyświetl plik

@ -1,87 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Device Details</h3></div>
<table class="datatable table table-striped table-bordered">
<tr><td>Name:</td><td>{{ device.name }}</td></tr>
<tr><td>Address:</td><td>{{ device.address }}</td></tr>
<tr><td>Real Address:</td><td>{{ device.real_address }}</td></tr>
<tr><td>Stealth:</td><td>{{ device.stealth }}</td></tr>
<tr><td>Aircraft Type:</td><td>{{ device.aircraft_type }}</td></tr>
<tr><td>Software Version:</td><td>{{ device.software_version }}</td></tr>
<tr><td>Hardware Version:</td><td>{{ device.hardware_version }}</td></tr>
<tr><td>First seen:</td><td>{{ device.firstseen }}</td></tr>
<tr><td>Last seen:</td><td>{{ device.lastseen }}</td></tr>
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Device Info</h3></div>
<table class="datatable table table-striped table-bordered">
<tr>
<th>Aircraft</th>
<th>Registration</th>
<th>Competition Sign</th>
<th>Aircraft Type</th>
<th>Source</th>
</tr>
{% for info in device.get_infos() %}
<tr>
<td>{{ info.aircraft }}</td>
<td>{{ info.registration }}</td>
<td>{{ info.competition }}</td>
<td>{{ info.aircraft_type }}</td>
<td>{{ info.address_origin }}</td>
</tr>
{% endfor %}
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Logbook</h3></div>
<table class="datatable table table-striped table-bordered">
<theader>
<tr>
<th></th>
<th></th>
<th colspan="2">Airport</th>
<th colspan="2">Time UTC</th>
<th></th>
<th></th>
</tr>
<tr>
<th>#</th>
<th>Date</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Duration</th>
<th>AGL</th>
</tr>
</theader>
<tbody>
{% set ns = namespace(mydate=none) %}
{% for entry in device.logbook %}
<tr>
<td>{{ loop.index }}</td>
<td>{% if ns.mydate != entry.reftime.strftime('%Y-%m-%d') %}{% set ns.mydate = entry.reftime.strftime('%Y-%m-%d') %}{{ ns.mydate }}{% endif %}</td>
<td>{% if entry.takeoff_airport is not none %}<a href="{{ url_for('main.airport_detail', airport=entry.takeoff_airport.id) }}">{{ entry.takeoff_airport.name }}</a>{% endif %}</td>
<td>{% if entry.landing_airport is not none %}<a href="{{ url_for('main.airport_detail', airport=entry.landing_airport.id) }}">{{ entry.landing_airport.name }}</a>{% endif %}</td>
<td>{% if entry.takeoff_timestamp is not none %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_timestamp is not none %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>
<td>{% if entry.max_altitude is not none %}{{ '%0.1f'|format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -1,27 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Devices</h3></div>
<div class="panel-body">
<table class="datatable table table-striped table-bordered">
<tr>
<th>Address</th>
<th>Registration</th>
<th>Software version</th>
</tr>
{% for device in devices %}
<tr>
<td><a href="{{ url_for('main.device_detail', id=device.id) }}">{{ device.address }}</a></td>
<td>{% if device.info is not none %}{{ device.info.registration }}{% else %} - {% endif %}</td>
<td {% if device.software_version and device.software_version < 6.6 %}class="danger"{% endif %}>{% if device.software_version is not none %}{{ device.software_version }}{% else %} - {% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,78 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Today</h3></div>
<table class="datatable table table-striped table-bordered">
<theader>
<tr>
<th class="text-right">Senders</th>
<th class="text-right">Receivers</th>
<th class="text-right">Takeoffs</th>
<th class="text-right">Landings</th>
<th class="text-right">Sender Positions</th>
<th class="text-right">Sender Positions Total</th>
</tr>
</theader>
<tbody>
<tr>
<td class="text-right">{{ senders_today }}</td>
<td class="text-right">{{ receivers_today }}</td>
<td class="text-right">{{ takeoffs_today }}</td>
<td class="text-right">{{ landings_today }}</td>
<td class="text-right">{{ sender_positions_today }}</td>
<td class="text-right">{{ sender_positions_total }}</td>
</tr>
</tbody>
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Logbook</h3></div>
<table class="datatable table table-striped table-bordered">
<theader>
<tr>
<th></th>
<th></th>
<th colspan="2">Airport</th>
<th colspan="2">Time UTC</th>
<th></th>
<th></th>
</tr>
<tr>
<th>#</th>
<th>Date</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Duration</th>
<th>AGL</th>
</tr>
</theader>
<tbody>
{% set ns = namespace(mydate=none) %}
{% for entry in logbook %}
<tr>
<td>{{ loop.index }}</td>
<td>{% if ns.mydate != entry.reference_timestamp.strftime('%Y-%m-%d') %}{% set ns.mydate = entry.reference_timestamp.strftime('%Y-%m-%d') %}{{ ns.mydate }}{% endif %}</td>
<td>{% if entry.takeoff_airport is not none %}<a href="{{ url_for('main.logbooks', country=entry.takeoff_airport.country_code, airport_id=entry.takeoff_airport.id, date=entry.reference_timestamp.strftime('%Y-%m-%d')) }}">{{ entry.takeoff_airport.name }}</a>{% endif %}</td>
<td>{% if entry.landing_airport is not none %}<a href="{{ url_for('main.logbooks', country=entry.landing_airport.country_code, airport_id=entry.landing_airport.id, date=entry.reference_timestamp.strftime('%Y-%m-%d')) }}">{{ entry.landing_airport.name }}</a>{% endif %}</td>
<td>{% if entry.takeoff_timestamp is not none %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_timestamp is not none %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>
<td>{% if entry.max_altitude is not none %}{{ '%0.1f'|format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -1,75 +0,0 @@
{% extends "base.html" %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/flags/flags.css') }}"/>
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Logbook</h3></div>
<div class="panel-body">
<form>
<div class="well">
<select name="country" onchange="this.form.submit();">
<option value="">(none)</option>
{% for country in countries %}
<option value="{{ country.iso2 }}"{% if sel_country == country.iso2 %} SELECTED{% endif %}>{{ country.iso2 }}</option>
{% endfor %}
</select>
<select name="airport" onchange="this.form.submit();">
<option value="">(none)</option>
{% for airport in airports %}
<option value="{{ airport.id }}"{% if sel_airport == airport.id %} SELECTED{% endif %}>{{ airport.name }}</option>
{% endfor %}
</select>
<select name="date" onchange="this.form.submit();">
{% if dates|length %}
{% for entry in dates %}
<option value="{{ entry.date }}"{% if sel_date|string() == entry.date|string() %} SELECTED{% endif %}>{{ entry.date }} ({{ entry.logbook_count }})</option>
{% endfor %}
{% else %}
<option value="">(none)</option>
{% endif %}
</select>
</div>
</form>
{% if logbook is not none %}
<table class="datatable table table-striped table-bordered">
<tr>
<th>Nr.</th>
<th>Aircraft</th>
<th>Type</th>
<th colspan="2">Takeoff</th>
<th colspan="2">Landing</th>
<th>Time</th>
<th>AGL</th>
<th>Remark</th>
</tr>
{% for entry in logbook %}
<tr>
<td>{{ loop.index }}</td>
<td><a href="{{ url_for('main.device_detail', id=entry.device.id) }}">{% if entry.device.info is not none and entry.device.info.registration|length %}{{ entry.device.info.registration }}{% else %}[{{ entry.device.address }}]{% endif %}</a></td>
<td>{% if entry.device.info is not none and entry.device.info.aircraft|length %}{{ entry.device.info.aircraft }}{% else %}-{% endif %}</td>
<td>{% if entry.takeoff_timestamp is not none and entry.takeoff_airport.id == sel_airport %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.takeoff_track is not none and entry.takeoff_airport.id == sel_airport %} {{ '%02d' | format(entry.takeoff_track/10) }} {% endif %}</td>
<td>{% if entry.landing_timestamp is not none and entry.landing_airport.id == sel_airport %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_track is not none and entry.landing_airport.id == sel_airport %} {{ '%02d' | format(entry.landing_track/10) }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>
<td>{% if entry.max_altitude is not none %}{{ '%0.1f'|format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td>
<td>
{% if entry.takeoff_airport is not none and entry.takeoff_airport.id != sel_airport %}Take Off: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.takeoff_airport.country_code|lower }}" alt="{{ entry.takeoff_airport.country_code }}"/> <a href="{{ url_for('main.logbook', country=entry.takeoff_airport.country_code, airport=entry.takeoff_airport.id, date=sel_date) }}">{{ entry.takeoff_airport.name }}</a>
{% elif entry.landing_airport is not none and entry.landing_airport.id != sel_airport %}Landing: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.landing_airport.country_code|lower }}" alt="{{ entry.landing_airport.country_code }}"/> <a href="{{ url_for('main.logbook', country=entry.takeoff_airport.country_code, airport=entry.landing_airport.id, date=sel_date) }}">{{ entry.landing_airport.name }}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,75 @@
{% extends "base.html" %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/flags/flags.css') }}"/>
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Logbook</h3></div>
<div class="panel-body">
<form>
<div class="well">
<select name="country" onchange="this.form.submit();">
<option value="">(none)</option>
{% for country in countries %}
<option value="{{ country.iso2 }}"{% if sel_country == country.iso2 %} SELECTED{% endif %}>{{ country.iso2 }}</option>
{% endfor %}
</select>
<select name="airport_id" onchange="this.form.submit();">
<option value="">(none)</option>
{% for airport in airports %}
<option value="{{ airport.id }}"{% if sel_airport_id == airport.id %} SELECTED{% endif %}>{{ airport.name }}</option>
{% endfor %}
</select>
<select name="date" onchange="this.form.submit();">
{% if dates|length %}
{% for entry in dates %}
<option value="{{ entry.date }}"{% if sel_date|string() == entry.date|string() %} SELECTED{% endif %}>{{ entry.date }} ({{ entry.logbook_count }})</option>
{% endfor %}
{% else %}
<option value="">(none)</option>
{% endif %}
</select>
</div>
</form>
{% if logbooks is not none %}
<table class="datatable table table-striped table-bordered">
<tr>
<th>#</th>
<th>Aircraft</th>
<th>Type</th>
<th colspan="2">Takeoff</th>
<th colspan="2">Landing</th>
<th>Time</th>
<th>AGL</th>
<th>Remark</th>
</tr>
{% for entry in logbooks %}
<tr>
<td>{{ loop.index }}</td>{% set sender = entry.sender %}
<td>{{ sender|to_html_link|safe }}</td>
<td>{% if sender.infos|length > 0 and sender.infos[0].aircraft|length %}{{ sender.infos[0].aircraft }}{% else %}-{% endif %}</td>
<td>{% if entry.takeoff_timestamp is not none and entry.takeoff_airport.id == sel_airport_id %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.takeoff_track is not none and entry.takeoff_airport.id == sel_airport_id %} {{ '%02d' | format(entry.takeoff_track/10) }} {% endif %}</td>
<td>{% if entry.landing_timestamp is not none and entry.landing_airport.id == sel_airport_id %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_track is not none and entry.landing_airport.id == sel_airport_id %} {{ '%02d' | format(entry.landing_track/10) }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>
<td>{% if entry.max_altitude is not none %}{{ '%d' | format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td>
<td>
{% if entry.takeoff_airport is not none and entry.takeoff_airport.id != sel_airport_id %}Take Off: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.takeoff_airport.country_code|lower }}" alt="{{ entry.takeoff_airport.country_code }}"/> <a href="{{ url_for('main.logbooks', country=entry.takeoff_airport.country_code, airport_id=entry.takeoff_airport.id, date=sel_date) }}">{{ entry.takeoff_airport.name }}</a>
{% elif entry.landing_airport is not none and entry.landing_airport.id != sel_airport_id %}Landing: <img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.landing_airport.country_code|lower }}" alt="{{ entry.landing_airport.country_code }}"/> <a href="{{ url_for('main.logbooks', country=entry.takeoff_airport.country_code, airport_id=entry.landing_airport.id, date=sel_date) }}">{{ entry.landing_airport.name }}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
</div>
{% endblock %}

Wyświetl plik

@ -12,18 +12,38 @@
<table class="datatable table table-striped table-bordered">
<tr><td>Name:</td><td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ receiver.country.iso2|lower }}" alt="{{ receiver.country.iso2 }}"/> {{ receiver.name }}</td></tr>
<tr><td>Airport:</td>
<td>{% if airport is not none %}<img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ airport.country_code|lower }}" alt="{{ airport.country_code }}"/>
{% if airport.takeoff_landings %}<a href="{{ url_for('main.airport_detail', airport=airport.id) }}">{{ airport.name }}</a>{% else %}{{ airport.name }}{% endif %}
<td>{% if receiver.airport is not none %}<img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ receiver.airport.country_code|lower }}" alt="{{ receiver.airport.country_code }}"/>
<a href="{{ url_for('main.airport_detail', airport_id=receiver.airport.id) }}">{{ receiver.airport.name }}</a>
{% else %}-{% endif %}
</td>
</tr>
<tr><td>Version:</td><td>{{ receiver.version }}</td></tr>
<tr><td>Platform:</td><td>{{ receiver.platform }}</td></tr>
<tr><td>Altitude:</td><td>{{ receiver.altitude|int }}m</td></tr>
<tr><td>AGL:</td><td>{{ receiver.agl|int }}m</td></tr>
<tr><td>Version:</td><td>{{ receiver.version if receiver.version else '-' }}</td></tr>
<tr><td>Platform:</td><td>{{ receiver.platform if receiver.platform else '-' }}</td></tr>
<tr><td>First seen:</td><td>{{ receiver.firstseen }}</td></tr>
<tr><td>Last seen:</td><td>{{ receiver.lastseen }}</td></tr>
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Airport nearby</h3></div>
<table class="datatable table table-striped table-bordered">
<tr>
<th>#</th>
<th>Name</th>
<th class="text-right">Distance [km]</th>
</tr>
{% for (airport,distance,azimuth) in receiver.airports_nearby() %}
<tr>
<td>{{ loop.index }}</td>
<td>{% if airport.takeoff_landings|length > 0 %}{{ airport|to_html_link|safe }}{% else %}{{ airport.name }}{% endif %}</td>
<td class="text-right">{{ '%0.1f' | format(distance/1000.0) }} ({{ azimuth|to_ordinal }})</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/flags/flags.css') }}"/>
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Receiver Ranking</h3></div>
<div class="panel-body">
<table id="myTable" class="table table-striped table-bordered tablesorter tablesorter-bootstrap">
<tr>
<th>#</th>
<th>Country</th>
<th>Name</th>
<th>Airport</th>
<th class="text-right">Maximum distance [km]</th>
<th class="text-right">Maximal normalized signal quality [dB]</th>
<th class="text-right">Sender counter</th>
<th class="text-right">Coverage counter</th>
<th class="text-right">Message counter</th>
</tr>
{% for entry in ranking %}
<tr>
<td>{{ loop.index }}</td>
<td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ entry.receiver.country.iso2|lower }}" alt="{{ entry.receiver.country.iso2 }}"/></td>
<td>{{ entry.receiver|to_html_link|safe }}</a></td>
<td>{{ entry.receiver.airport|to_html_link|safe }}</a></td>
<td class="text-right">{{ '%0.1f' | format(entry.max_distance/1000.0) }}</td>
<td class="text-right">{{ '%0.1f' | format(entry.max_normalized_quality) }}</td>
<td class="text-right">{{ entry.senders_count }}</td>
<td class="text-right">{{ entry.coverages_count }}</td>
<td class="text-right">{{ entry.messages_count }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -22,14 +22,26 @@
<table class="datatable table table-striped table-bordered">
<tr>
<th>Name</th>
<th>#</th>
<th>Country</th>
<th>Name</th>
<th>Airport</th>
<th>Altitude</th>
<th>Status</th>
<th>Version</th>
<th>Platform</th>
</tr>
{% for receiver in receivers %}
<tr>
<td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ receiver.country.iso2|lower }}" alt="{{ receiver.country.iso2 }}"/> <a href="{{ url_for('main.receiver_detail', receiver_id=receiver.id) }}">{{ receiver.name }}</a></td>
<td>{{ receiver.altitude|int }} m</td>
<td>{{ loop.index }}</td>
<td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ receiver.country.iso2|lower }}" alt="{{ receiver.country.iso2 }}"/></td>
<td>{{ receiver|to_html_link|safe }}</td>
<td>{{ receiver.airport|to_html_link|safe }}</td>
<td>{{ receiver.altitude|int }} m</td>
<td>{{ receiver.lastseen|timestamp_to_status|safe }}</td>
<td>{{ receiver.version if receiver.version else '-' }}</td>
<td>{{ receiver.platform if receiver.platform else '-' }}</td>
</tr>
{% endfor %}
</table>

Wyświetl plik

@ -0,0 +1,92 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Sender Details</h3></div>
<table class="datatable table table-striped table-bordered">
<tr><td>Name:</td><td>{{ sender.name }}</td></tr>
<tr><td>Address:</td><td>{{ sender.address if sender.address else '-' }}</td></tr>
<tr><td>Real Address:</td><td>{{ sender.real_address if sender.real_address else '-' }}</td></tr>
<tr><td>Stealth:</td><td>{{ sender.stealth if sender.stealth else '-' }}</td></tr>
<tr><td>Aircraft Type:</td><td>{{ sender.aircraft_type.name }}</td></tr>
<tr><td>Software Version:</td><td>{{ sender.software_version if sender.software_version else '-' }}</td></tr>
<tr><td>Hardware Version:</td><td>{{ sender.hardware_version if sender.hardware_version else '-' }}</td></tr>
<tr><td>First seen:</td><td>{{ sender.firstseen }}</td></tr>
<tr><td>Last seen:</td><td>{{ sender.lastseen }}</td></tr>
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Sender Info</h3></div>
<table class="datatable table table-striped table-bordered">
<tr>
<th>Aircraft</th>
<th>Registration</th>
<th>Competition Sign</th>
<th>Aircraft Type</th>
<th>Source</th>
</tr>
{% for info in sender.infos %}
<tr>
<td>{{ info.aircraft }}</td>
<td>{{ info.registration }}</td>
<td>{{ info.competition }}</td>
<td>{{ info.aircraft_type.name }}</td>
<td>{{ info.address_origin.name }}</td>
</tr>
{% endfor %}
</table>
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Range View</h3></div>
<img src="{{ url_for('main.range_view', sender_id=sender.id) }}" class="img-thumbnail">
</div>
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Logbook</h3></div>
<table class="datatable table table-striped table-bordered">
<theader>
<tr>
<th></th>
<th></th>
<th colspan="2">Airport</th>
<th colspan="2">Time UTC</th>
<th></th>
<th></th>
</tr>
<tr>
<th>#</th>
<th>Date</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Duration</th>
<th>AGL</th>
</tr>
</theader>
<tbody>
{% set ns = namespace(mydate=none) %}
{% for entry in sender.logbook_entries %}
<tr>
<td>{{ loop.index }}</td>
<td>{% if ns.mydate != entry.reference_timestamp.strftime('%Y-%m-%d') %}{% set ns.mydate = entry.reference_timestamp.strftime('%Y-%m-%d') %}{{ ns.mydate }}{% endif %}</td>
<td>{% if entry.takeoff_airport is not none %}<a href="{{ url_for('main.airport_detail', airport_id=entry.takeoff_airport.id) }}">{{ entry.takeoff_airport.name }}</a>{% endif %}</td>
<td>{% if entry.landing_airport is not none %}<a href="{{ url_for('main.airport_detail', airport_id=entry.landing_airport.id) }}">{{ entry.landing_airport.name }}</a>{% endif %}</td>
<td>{% if entry.takeoff_timestamp is not none %} {{ entry.takeoff_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.landing_timestamp is not none %} {{ entry.landing_timestamp.strftime('%H:%M') }} {% endif %}</td>
<td>{% if entry.duration is not none %}{{ entry.duration }}{% endif %}</td>
<td>{% if entry.max_altitude is not none %}{{ '%0.1f'|format(entry.max_altitude - entry.takeoff_airport.altitude) }} m{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Sender Ranking</h3></div>
<div class="panel-body">
<table class="datatable table table-striped table-bordered">
<tr>
<th>#</th>
<th>Name</th>
<th>Aircraft</th>
<th class="text-right">Maximum distance [km]</th>
<th class="text-right">Maximal normalized signal quality [dB]</th>
<th class="text-right">Receiver counter</th>
<th class="text-right">Coverage counter</th>
<th class="text-right">Message counter</th>
</tr>
{% for entry in ranking %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ entry.sender|to_html_link|safe }}</a></td>
<td>{% if entry.sender.infos|length > 0 %}{{ entry.sender.infos[0].aircraft }}{% else %}-{% endif %}</td>
<td class="text-right">{{ '%0.1f' | format(entry.max_distance/1000.0) }}</td>
<td class="text-right">{{ '%0.1f' | format(entry.max_normalized_quality) }}</td>
<td class="text-right">{{ entry.receivers_count }}</td>
<td class="text-right">{{ entry.coverages_count }}</td>
<td class="text-right">{{ entry.messages_count }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">Senders</h3></div>
<div class="panel-body">
<table class="datatable table table-striped table-bordered">
<tr>
<th>#</th>
<th>Name</th>
<th>Registration</th>
<th>Software version</th>
</tr>
{% for sender in senders %}
<tr>
<td>{{ loop.index }}</td>
<td><a href="{{ url_for('main.sender_detail', sender_id=sender.id) }}">{{ sender.name }}</a></td>
<td>{{ sender.infos[0].registration if sender.infos|length > 0 else '-' }}</td>
<td {% if sender.software_version and sender.software_version < 6.6 %}class="danger"{% endif %}>{% if sender.software_version is not none %}{{ sender.software_version }}{% else %} - {% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -1,29 +0,0 @@
{% extends "base.html" %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/flags/flags.css') }}"/>
<div class="container">
<div class="panel panel-success" id="asdf">
<div class="panel-heading"><h3 class="panel-title">Devices</h3></div>
<div class="panel-body">
<table class="datatable table table-striped table-bordered">
<tr>
<th>Receiver</th>
<th>Aircrafts</th>
<th>Beacons</th>
</tr>
{% for receiverstat in receiverstats %}
<tr>
<td><img src="{{ url_for('static', filename='img/Transparent.gif') }}" class="flag flag-{{ receiverstat.receiver.country.iso2|lower }}" alt="{{ receiverstat.receiver.country.iso2 }}"/> {{ receiverstat.receiver.name }}</td>
<td>{{ receiverstat.aircraft_count }}</td>
<td>{{ receiverstat.beacon_count }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -3,11 +3,13 @@ import gzip
from io import StringIO
from datetime import datetime, timedelta
from flask import current_app
from aerofiles.seeyou import Reader
from ogn.parser.utils import FEETS_TO_METER
import requests
from .model import AircraftType, DeviceInfoOrigin, DeviceInfo, Airport, Location
from .model import AircraftType, SenderInfoOrigin, SenderInfo, Airport, Location
DDB_URL = "http://ddb.glidernet.org/download/?t=1"
@ -31,7 +33,7 @@ def date_to_timestamps(date):
return (start, end)
def get_ddb(csv_file=None, address_origin=DeviceInfoOrigin.UNKNOWN):
def get_ddb(csv_file=None, address_origin=SenderInfoOrigin.UNKNOWN):
if csv_file is None:
r = requests.get(DDB_URL)
rows = "\n".join(i for i in r.text.splitlines() if i[0] != "#")
@ -41,43 +43,43 @@ def get_ddb(csv_file=None, address_origin=DeviceInfoOrigin.UNKNOWN):
data = csv.reader(StringIO(rows), quotechar="'", quoting=csv.QUOTE_ALL)
device_infos = list()
sender_infos = list()
for row in data:
device_info = DeviceInfo()
device_info.address_type = row[0]
device_info.address = row[1]
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.aircraft_type = AircraftType(int(row[7]))
device_info.address_origin = address_origin
sender_info = SenderInfo()
sender_info.address_type = row[0]
sender_info.address = row[1]
sender_info.aircraft = row[2]
sender_info.registration = row[3]
sender_info.competition = row[4]
sender_info.tracked = row[5] == "Y"
sender_info.identified = row[6] == "Y"
sender_info.aircraft_type = AircraftType(int(row[7]))
sender_info.address_origin = address_origin
device_infos.append(device_info)
sender_infos.append(sender_info)
return device_infos
return sender_infos
def get_flarmnet(fln_file=None, address_origin=DeviceInfoOrigin.FLARMNET):
def get_flarmnet(fln_file=None, address_origin=SenderInfoOrigin.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) == 173]
else:
with open(fln_file, "r") as file:
rows = [bytes.fromhex(line.strip()).decode("latin1") for line in file.readlines() if len(line) == 172]
rows = [bytes.fromhex(line.strip()).decode("latin1") for line in file.readlines() if len(line) == 173]
device_infos = list()
sender_infos = list()
for row in rows:
device_info = DeviceInfo()
device_info.address = row[0:6].strip()
device_info.aircraft = row[48:69].strip()
device_info.registration = row[69:76].strip()
device_info.competition = row[76:79].strip()
sender_info = SenderInfo()
sender_info.address = row[0:6].strip()
sender_info.aircraft = row[48:69].strip()
sender_info.registration = row[69:76].strip()
sender_info.competition = row[76:79].strip()
device_infos.append(device_info)
sender_infos.append(sender_info)
return device_infos
return sender_infos
def get_trackable(ddb):
@ -118,7 +120,7 @@ def get_airports(cupfile):
airports.append(airport)
except AttributeError as e:
print("Failed to parse line: {} {}".format(line, e))
current_app.logger.error("Failed to parse line: {} {}".format(line, e))
return airports
@ -134,3 +136,18 @@ def open_file(filename):
else:
f = open(filename, "rt", encoding="latin-1")
return f
def get_sql_trustworthy(source_table_alias):
MIN_DISTANCE = 1000
MAX_DISTANCE = 640000
MAX_NORMALIZED_QUALITY = 40 # this is enough for > 640km
MAX_ERROR_COUNT = 5
MAX_CLIMB_RATE = 50
return f"""
({source_table_alias}.distance IS NOT NULL AND {source_table_alias}.distance BETWEEN {MIN_DISTANCE} AND {MAX_DISTANCE})
AND ({source_table_alias}.normalized_quality IS NOT NULL AND {source_table_alias}.normalized_quality < {MAX_NORMALIZED_QUALITY})
AND ({source_table_alias}.error_count IS NULL OR {source_table_alias}.error_count < {MAX_ERROR_COUNT})
AND ({source_table_alias}.climb_rate IS NULL OR {source_table_alias}.climb_rate BETWEEN -{MAX_CLIMB_RATE} AND {MAX_CLIMB_RATE})
"""

6
celery_app.py 100644
Wyświetl plik

@ -0,0 +1,6 @@
#!/usr/bin/env python
from app import init_celery
app = init_celery()
app.conf.imports = app.conf.imports + ("app.tasks",)

Wyświetl plik

@ -1,7 +0,0 @@
#!/usr/bin/env python
import os
from app import celery, create_app
from app.collect.celery import *
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
app.app_context().push()

53
config.py 100644
Wyświetl plik

@ -0,0 +1,53 @@
import os
class BaseConfig:
SECRET_KEY = "i-like-ogn"
# Flask-Cache stuff
CACHE_TYPE = "simple"
CACHE_DEFAULT_TIMEOUT = 300
# Redis stuff
REDIS_URL = "redis://localhost:6379/0"
# Celery stuff
BROKER_URL = os.environ.get("BROKER_URL", REDIS_URL)
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", REDIS_URL)
APRS_USER = "OGNPYTHON"
class DefaultConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = os.environ.get("SQLALCHEMY_DATABASE_URI", "postgresql://postgres:postgres@localhost:5432/ogn")
SQLALCHEMY_TRACK_MODIFICATIONS = False
# Celery beat stuff
from celery.schedules import crontab
from datetime import timedelta
CELERYBEAT_SCHEDULE = {
"transfer_to_database": {"task": "transfer_to_database", "schedule": timedelta(minutes=1)},
"update_statistics": {"task": "update_statistics", "schedule": timedelta(minutes=5)},
"update_takeoff_landings": {"task": "update_takeoff_landings", "schedule": timedelta(minutes=1), "kwargs": {"last_minutes": 20}},
"update_logbook": {"task": "update_logbook", "schedule": timedelta(minutes=1)},
"update_logbook_previous_day": {"task": "update_logbook", "schedule": crontab(hour=1, minute=0), "kwargs": {"day_offset": -1}},
"update_ddb_daily": {"task": "import_ddb", "schedule": timedelta(days=1)},
#"update_logbook_max_altitude": {"task": "update_logbook_max_altitude", "schedule": timedelta(minutes=1), "kwargs": {"offset_days": 0}},
#"purge_old_data": {"task": "purge_old_data", "schedule": timedelta(hours=1), "kwargs": {"max_hours": 48}},
}
class DevelopmentConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = "postgresql://postgres:postgres@localhost:5432/ogn_test"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
configs = {
'default': DefaultConfig,
'development': DevelopmentConfig,
'testing': DevelopmentConfig
}

Wyświetl plik

@ -1,37 +0,0 @@
server {
# listen on port 80 (http)
listen 80;
server_name _;
location / {
# redirect any requests to the same URL but on https
return 301 https://$host$request_uri;
}
}
server {
# listen on port 443 (https)
listen 443 ssl;
server_name _;
# location of the self-signed SSL certificate
ssl_certificate /home/ubuntu/ddb/certs/cert.pem;
ssl_certificate_key /home/ubuntu/ddb/certs/key.pem;
# write access and error logs to /var/log
access_log /var/log/ddb_access.log;
error_log /var/log/ddb_error.log;
location / {
# forward application requests to the gunicorn server
proxy_pass http://localhost:8000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
# handle static files directly, without forwarding to the application
alias /home/ubuntu/ddb/app/static;
expires 30d;
}
}

Wyświetl plik

@ -0,0 +1,20 @@
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass "http://localhost:5000";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
fastcgi_read_timeout 300s;
proxy_read_timeout 300;
}
location /static {
alias /home/pi/ogn-python/app/static/;
}
error_log /var/log/nginx/api-error.log;
access_log /var/log/nginx/api-access.log;
}

Wyświetl plik

@ -1,5 +1,5 @@
[program:celerybeat]
command=/home/pi/ogn-python/venv/bin/celery -A celery_worker.celery beat -l info
command=/home/pi/ogn-python/venv/bin/celery -A celery_app beat -l info
directory=/home/pi/ogn-python
environment=FLASK_APP=ogn_python.py

Wyświetl plik

@ -1,5 +1,5 @@
[program:celery]
command=/home/pi/ogn-python/venv/bin/celery -A celery_worker.celery worker -l info
command=/home/pi/ogn-python/venv/bin/celery -A celery_app worker -l info
directory=/home/pi/ogn-python
environment=FLASK_APP=ogn_python.py

Wyświetl plik

@ -1,6 +1,6 @@
[program:flower]
environment=OGN_CONFIG_MODULE='config/default.py'
command=/home/pi/ogn-python/venv/bin/celery flower -A app.celery --port=5555 -l info
command=/home/pi/ogn-python/venv/bin/celery flower -A celery_app --port=5555 -l info
directory=/home/pi/ogn-python
user=pi

Wyświetl plik

@ -4,7 +4,7 @@ directory=/home/pi/ogn-python
environment=FLASK_APP=ogn_python.py
user=pi
stderr_logfile=/var/log/supervisor/ogn-feeder.log
stdout_logfile=/var/log/supervisor/ogn-feeder.log
stderr_logfile=/var/log/supervisor/ogn-gateway.log
stdout_logfile=/var/log/supervisor/ogn-gateway.log
autostart=true
autorestart=true

Wyświetl plik

@ -1,76 +0,0 @@
"""Remove non position fields from AircraftBeacon and ReceiverBeacon
Revision ID: 079fe885ae20
Revises: 6c19cedf5fa7
Create Date: 2019-09-25 21:42:34.924732
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '079fe885ae20'
down_revision = '6c19cedf5fa7'
branch_labels = None
depends_on = None
def upgrade():
op.drop_constraint('aircraft_beacons_receiver_id_fkey', 'aircraft_beacons', type_='foreignkey')
op.drop_constraint('aircraft_beacons_device_id_fkey', 'aircraft_beacons', type_='foreignkey')
op.drop_column('aircraft_beacons', 'device_id')
op.drop_column('aircraft_beacons', 'receiver_id')
op.drop_constraint('receiver_beacons_receiver_id_fkey', 'receiver_beacons', type_='foreignkey')
op.drop_column('receiver_beacons', 'receiver_id')
op.drop_column('receiver_beacons', 'total_ram')
op.drop_column('receiver_beacons', 'senders_visible')
op.drop_column('receiver_beacons', 'senders_messages')
op.drop_column('receiver_beacons', 'cpu_temp')
op.drop_column('receiver_beacons', 'platform')
op.drop_column('receiver_beacons', 'rec_input_noise')
op.drop_column('receiver_beacons', 'ntp_error')
op.drop_column('receiver_beacons', 'good_senders')
op.drop_column('receiver_beacons', 'senders_total')
op.drop_column('receiver_beacons', 'cpu_load')
op.drop_column('receiver_beacons', 'free_ram')
op.drop_column('receiver_beacons', 'good_and_bad_senders')
op.drop_column('receiver_beacons', 'amperage')
op.drop_column('receiver_beacons', 'voltage')
op.drop_column('receiver_beacons', 'senders_signal')
op.drop_column('receiver_beacons', 'version')
op.drop_column('receiver_beacons', 'relay')
op.drop_column('receiver_beacons', 'rt_crystal_correction')
op.drop_column('receiver_beacons', 'good_senders_signal')
def downgrade():
op.add_column('receiver_beacons', sa.Column('receiver_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.create_foreign_key('receiver_beacons_receiver_id_fkey', 'receiver_beacons', 'receivers', ['receiver_id'], ['id'], ondelete='SET NULL')
op.add_column('receiver_beacons', sa.Column('good_senders_signal', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('rt_crystal_correction', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('relay', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('version', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('senders_signal', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('voltage', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('amperage', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('good_and_bad_senders', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('free_ram', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('cpu_load', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('senders_total', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('good_senders', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('ntp_error', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('rec_input_noise', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('platform', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('cpu_temp', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('senders_messages', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('senders_visible', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('receiver_beacons', sa.Column('total_ram', sa.REAL(), autoincrement=False, nullable=True))
op.add_column('aircraft_beacons', sa.Column('receiver_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('aircraft_beacons', sa.Column('device_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.create_foreign_key('aircraft_beacons_device_id_fkey', 'aircraft_beacons', 'devices', ['device_id'], ['id'], ondelete='SET NULL')
op.create_foreign_key('aircraft_beacons_receiver_id_fkey', 'aircraft_beacons', 'receivers', ['receiver_id'], ['id'], ondelete='SET NULL')

Wyświetl plik

@ -1,76 +0,0 @@
"""Use Enum for AircraftType
Revision ID: 6c19cedf5fa7
Revises: be9a6dad551e
Create Date: 2019-09-24 18:37:40.224279
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '6c19cedf5fa7'
down_revision = 'be9a6dad551e'
branch_labels = None
depends_on = None
aircrafttype = postgresql.ENUM('UNKNOWN', 'GLIDER_OR_MOTOR_GLIDER', 'TOW_TUG_PLANE', 'HELICOPTER_ROTORCRAFT',
'PARACHUTE', 'DROP_PLANE', 'HANG_GLIDER', 'PARA_GLIDER', 'POWERED_AIRCRAFT',
'JET_AIRCRAFT', 'FLYING_SAUCER', 'BALLOON', 'AIRSHIP', 'UNMANNED_AERIAL_VEHICLE',
'STATIC_OBJECT', name='aircrafttype')
def upgrade():
aircrafttype.create(op.get_bind())
for table in ['aircraft_beacons', 'devices']:
op.add_column(table, sa.Column('aircraft_type_enum', sa.Enum(
'UNKNOWN', 'GLIDER_OR_MOTOR_GLIDER', 'TOW_TUG_PLANE', 'HELICOPTER_ROTORCRAFT',
'PARACHUTE', 'DROP_PLANE', 'HANG_GLIDER', 'PARA_GLIDER', 'POWERED_AIRCRAFT',
'JET_AIRCRAFT', 'FLYING_SAUCER', 'BALLOON', 'AIRSHIP', 'UNMANNED_AERIAL_VEHICLE',
'STATIC_OBJECT', name='aircrafttype'), nullable=False, server_default='UNKNOWN'))
op.execute("""
UPDATE {table} SET aircraft_type_enum = 'UNKNOWN' WHERE aircraft_type = 0;
UPDATE {table} SET aircraft_type_enum = 'GLIDER_OR_MOTOR_GLIDER' WHERE aircraft_type = 1;
UPDATE {table} SET aircraft_type_enum = 'TOW_TUG_PLANE' WHERE aircraft_type = 2;
UPDATE {table} SET aircraft_type_enum = 'HELICOPTER_ROTORCRAFT' WHERE aircraft_type = 3;
UPDATE {table} SET aircraft_type_enum = 'PARACHUTE' WHERE aircraft_type = 4;
UPDATE {table} SET aircraft_type_enum = 'DROP_PLANE' WHERE aircraft_type = 5;
UPDATE {table} SET aircraft_type_enum = 'HANG_GLIDER' WHERE aircraft_type = 6;
UPDATE {table} SET aircraft_type_enum = 'PARA_GLIDER' WHERE aircraft_type = 7;
UPDATE {table} SET aircraft_type_enum = 'POWERED_AIRCRAFT' WHERE aircraft_type = 8;
UPDATE {table} SET aircraft_type_enum = 'JET_AIRCRAFT' WHERE aircraft_type = 9;
UPDATE {table} SET aircraft_type_enum = 'FLYING_SAUCER' WHERE aircraft_type = 10;
UPDATE {table} SET aircraft_type_enum = 'BALLOON' WHERE aircraft_type = 11;
UPDATE {table} SET aircraft_type_enum = 'AIRSHIP' WHERE aircraft_type = 12;
UPDATE {table} SET aircraft_type_enum = 'UNMANNED_AERIAL_VEHICLE' WHERE aircraft_type = 13;
UPDATE {table} SET aircraft_type_enum = 'STATIC_OBJECT' WHERE aircraft_type = 15;
""".format(table=table))
op.drop_column(table, 'aircraft_type')
op.alter_column(table, 'aircraft_type_enum', new_column_name='aircraft_type')
def downgrade():
for table in ['aircraft_beacons', 'devices']:
op.add_column(table, sa.Column('aircraft_type_int', sa.SmallInteger))
op.execute("""
UPDATE {table} SET aircraft_type_int = 0 WHERE aircraft_type = 'UNKNOWN';
UPDATE {table} SET aircraft_type_int = 1 WHERE aircraft_type = 'GLIDER_OR_MOTOR_GLIDER';
UPDATE {table} SET aircraft_type_int = 2 WHERE aircraft_type = 'TOW_TUG_PLANE';
UPDATE {table} SET aircraft_type_int = 3 WHERE aircraft_type = 'HELICOPTER_ROTORCRAFT';
UPDATE {table} SET aircraft_type_int = 4 WHERE aircraft_type = 'PARACHUTE';
UPDATE {table} SET aircraft_type_int = 5 WHERE aircraft_type = 'DROP_PLANE';
UPDATE {table} SET aircraft_type_int = 6 WHERE aircraft_type = 'HANG_GLIDER';
UPDATE {table} SET aircraft_type_int = 7 WHERE aircraft_type = 'PARA_GLIDER';
UPDATE {table} SET aircraft_type_int = 8 WHERE aircraft_type = 'POWERED_AIRCRAFT';
UPDATE {table} SET aircraft_type_int = 9 WHERE aircraft_type = 'JET_AIRCRAFT';
UPDATE {table} SET aircraft_type_int = 10 WHERE aircraft_type = 'FLYING_SAUCER';
UPDATE {table} SET aircraft_type_int = 11 WHERE aircraft_type = 'BALLOON';
UPDATE {table} SET aircraft_type_int = 12 WHERE aircraft_type = 'AIRSHIP';
UPDATE {table} SET aircraft_type_int = 13 WHERE aircraft_type = 'UNMANNED_AERIAL_VEHICLE';
UPDATE {table} SET aircraft_type_int = 15 WHERE aircraft_type = 'STATIC_OBJECT';
""".format(table=table))
op.drop_column(table, 'aircraft_type')
op.alter_column(table, 'aircraft_type_int', new_column_name='aircraft_type')
aircrafttype.drop(op.get_bind())

Wyświetl plik

@ -1,28 +0,0 @@
"""Remove FK relation from device to device_info
Revision ID: 885123e6a2d6
Revises: 002656878233
Create Date: 2019-04-27 14:22:30.841969
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '885123e6a2d6'
down_revision = '002656878233'
branch_labels = None
depends_on = None
def upgrade():
op.drop_index('ix_device_infos_device_id', table_name='device_infos')
op.drop_constraint('device_infos_device_id_fkey', 'device_infos', type_='foreignkey')
op.drop_column('device_infos', 'device_id')
def downgrade():
op.add_column('device_infos', sa.Column('device_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.create_foreign_key('device_infos_device_id_fkey', 'device_infos', 'devices', ['device_id'], ['id'], ondelete='SET NULL')
op.create_index('ix_device_infos_device_id', 'device_infos', ['device_id'], unique=False)

Wyświetl plik

@ -1,41 +0,0 @@
"""Use Enums for DeviceInfoOrigin
Revision ID: be9a6dad551e
Revises: 885123e6a2d6
Create Date: 2019-09-15 14:38:25.838089
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'be9a6dad551e'
down_revision = '885123e6a2d6'
branch_labels = None
depends_on = None
deviceinfoorigin = postgresql.ENUM('UNKNOWN', 'OGN_DDB', 'FLARMNET', 'USER_DEFINED', name='deviceinfoorigin')
def upgrade():
deviceinfoorigin.create(op.get_bind())
op.add_column('device_infos', sa.Column('address_origin_enum', sa.Enum('UNKNOWN', 'OGN_DDB', 'FLARMNET', 'USER_DEFINED', name='deviceinfoorigin'), nullable=False, server_default='UNKNOWN'))
op.execute("UPDATE device_infos SET address_origin_enum = 'UNKNOWN' WHERE address_origin = 0")
op.execute("UPDATE device_infos SET address_origin_enum = 'OGN_DDB' WHERE address_origin = 1")
op.execute("UPDATE device_infos SET address_origin_enum = 'FLARMNET' WHERE address_origin = 2")
op.execute("UPDATE device_infos SET address_origin_enum = 'USER_DEFINED' WHERE address_origin = 3")
op.drop_column('device_infos', 'address_origin')
op.alter_column('device_infos', 'address_origin_enum', new_column_name='address_origin')
def downgrade():
op.add_column('device_infos', sa.Column('address_origin_int', sa.SmallInteger))
op.execute("UPDATE device_infos SET address_origin_int = 0 WHERE address_origin = 'UNKNOWN'")
op.execute("UPDATE device_infos SET address_origin_int = 1 WHERE address_origin = 'OGN_DDB'")
op.execute("UPDATE device_infos SET address_origin_int = 2 WHERE address_origin = 'FLARMNET'")
op.execute("UPDATE device_infos SET address_origin_int = 3 WHERE address_origin = 'USER_DEFINED'")
op.drop_column('device_infos', 'address_origin')
op.alter_column('device_infos', 'address_origin_int', new_column_name='address_origin')
deviceinfoorigin.drop(op.get_bind())

Wyświetl plik

@ -1,2 +1,2 @@
[flake8]
ignore = F401, F841, E402, E501, E126
ignore = F401, F841, E402, E501, E126, E265

Wyświetl plik

@ -26,8 +26,11 @@ setup(
'Topic :: Scientific/Engineering :: GIS',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9-dev'
],
keywords='gliding ogn',
packages=find_packages(exclude=['tests', 'tests.*']),
@ -39,7 +42,8 @@ setup(
'Flask-WTF==0.14.3',
'Flask-Caching==1.9.0',
'geopy==2.0.0',
'celery==5.0.2',
'celery==4.4.7',
'Flask-Redis==0.4.0',
'redis==3.5.3',
'aerofiles==1.0.0',
'geoalchemy2==0.8.4',
@ -50,7 +54,8 @@ setup(
'xmlunittest==0.5.0',
'flower==0.9.5',
'tqdm==4.51.0',
'requests==2.25.0',
'requests==2.25.0',
'matplotlib==3.3.3'
],
test_require=[
'pytest==5.0.1',

Wyświetl plik

@ -1,194 +0,0 @@
import json
from datetime import datetime, date
import unittest
from unittest import mock
from xmlunittest import XmlTestMixin
from tests.base import TestBaseDB, db
from app.model import AircraftBeacon, AircraftType, Receiver, Device, DeviceInfo, ReceiverCoverage
from app.backend.liveglidernet import rec, lxml
from app.backend.ognrange import stations2_filtered_pl, max_tile_mgrs_pl
class TestDB(TestBaseDB, XmlTestMixin):
def setUp(self):
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")
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")
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)
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,
)
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,
)
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,
)
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,
)
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
)
db.session.add(self.rc11)
db.session.add(self.rc12)
db.session.add(self.rc21)
db.session.commit()
@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")
# Check the document
root = self.assertXmlDocument(data)
self.assertXmlNode(root, tag="markers")
self.assertXpathsOnlyOne(root, ('./m[@a="Koenigsdf"]', './m[@a="Bene"]', './m[@a="Ohlstadt"]'))
# Check the complete document
expected = """<?xml version="1.0" encoding="UTF-8"?>
<markers>
<m e="0"/>
<m a="Bene" b="47.7158333" c="11.3905500" d="0"/>
<m a="Koenigsdf" b="47.8286167" c="11.4661167" d="1"/>
<m a="Ohlstadt" b="47.6577833" c="11.2338833" d="1"/>
</markers>
""".encode(
encoding="utf-8"
)
self.assertXmlEquivalentOutputs(data, expected)
@unittest.skip("broken")
def test_lxml(self):
data = lxml().encode(encoding="utf-8")
# Check the complete document
expected = """<?xml version="1.0" encoding="UTF-8"?>
<markers>
<m a="47.8280667,11.4726500,_15,xxDD0815,543,09:56:00,245,270,77,-1.5,8,Bene,0,xxDD0815"/>
<m a="47.8258833,11.4636167,Hi,D-4711,209,10:00:02,3,123,55,-0.4,1,Koenigsdf,DD4711,xxDD4711"/>
</markers>
""".encode(
encoding="utf-8"
)
self.assertXmlEquivalentOutputs(data, expected)
@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)
result = stations2_filtered_pl(start=date(2017, 12, 15), end=date(2017, 12, 25))
data = json.loads(result)
stations = data["stations"]
self.assertEqual(len(stations), 3)
s1 = stations[0]
s2 = stations[1]
s3 = stations[2]
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
self.assertEqual(s1["ut"], "2017-12-20 09:45")
# self.assertEqual(s1["b"], 0)
self.assertEqual(s1["v"], "0.2.7.x64")
self.assertEqual(s2["s"], "Koenigsdf")
self.assertEqual(s2["lt"], 47.8286)
self.assertEqual(s2["lg"], 11.4661)
self.assertEqual(s2["u"], "U")
self.assertEqual(s2["ut"], "2017-12-20 10:00")
# self.assertEqual(s2["b"], 0)
self.assertEqual(s2["v"], "0.2.5.ARM")
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")
data = json.loads(result)
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")
data = json.loads(result)
self.assertEqual(data["t"], "32TPU")
self.assertEqual(data["p"][0], "8512/1")
if __name__ == "__main__":
unittest.main()

Wyświetl plik

@ -4,19 +4,177 @@ from app import create_app, db
class TestBaseDB(unittest.TestCase):
def setUp(self):
# Create app context
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.session.execute("DROP TABLE IF EXISTS elevation;")
db.session.commit()
db.drop_all()
db.session.commit()
# ... and create them again
db.session.execute("CREATE EXTENSION IF NOT EXISTS postgis;")
db.session.commit()
db.create_all()
db.session.commit()
db.session.execute("CREATE TABLE IF NOT EXISTS elevation (rast raster);")
db.session.commit()
# ... and create TimescaleDB stuff
db.session.execute("CREATE EXTENSION IF NOT EXISTS timescaledb;")
db.session.execute("SELECT create_hypertable('sender_positions', 'reference_timestamp', chunk_time_interval => interval '1 day', if_not_exists => TRUE);")
#db.session.execute("SELECT create_hypertable('sender_statuses', 'reference_timestamp', chunk_time_interval => interval '1 day', if_not_exists => TRUE);")
db.session.execute("SELECT create_hypertable('receiver_positions', 'reference_timestamp', chunk_time_interval => interval '1 day', if_not_exists => TRUE);")
db.session.execute("SELECT create_hypertable('receiver_statuses', 'reference_timestamp', chunk_time_interval => interval '1 day', if_not_exists => TRUE);")
db.session.commit()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def insert_airports_and_devices(self):
db.session.execute("INSERT INTO airports(name, location, altitude, style) VALUES('Benediktbeuren','0101000020E6100000D5E76A2BF6C72640D4063A6DA0DB4740',609,4)")
db.session.execute("INSERT INTO airports(name, location, altitude, style) VALUES('Koenigsdorf','0101000020E610000061E8FED7A6EE26407F20661C10EA4740',600,5)")
db.session.execute("INSERT INTO airports(name, location, altitude, style) VALUES('Ohlstadt','0101000020E6100000057E678EBF772640A142883E32D44740',655,5)")
db.session.execute("INSERT INTO airports(name, location, altitude, style) VALUES('Unterbuchen','0101000020E6100000462575029AF8264089F7098D4DE44740',635,3)")
db.session.execute("UPDATE airports SET border = ST_Expand(location, 0.05)")
db.session.execute("INSERT INTO senders(name, address, aircraft_type) VALUES('FLRDDEFF7', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER')")
db.session.execute("INSERT INTO senders(name, address, aircraft_type) VALUES('FLRDDAC7C', 'DDAC7C', 'GLIDER_OR_MOTOR_GLIDER')")
def insert_sender_positions_broken_rope(self):
"""Fill the db with a winch launch where the rope breaks."""
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000009668B61829F12640330E0887F1E94740',604,'2016-07-02 10:47:12','2016-07-02 10:47:12',0,0,0,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000009668B61829F12640330E0887F1E94740',605,'2016-07-02 10:47:32','2016-07-02 10:47:32',0,0,-0.096520193,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:47:52','2016-07-02 10:47:52',0,0,-0.096520193,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:48:12','2016-07-02 10:48:12',0,0,-0.096520193,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000001B2FDD2406F12640E53C762AF3E94740',606,'2016-07-02 10:48:24','2016-07-02 10:48:24',284,51.85598112,0.299720599,0.1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000F594AFDEBBF02640623583E5F5E94740',610,'2016-07-02 10:48:26','2016-07-02 10:48:26',282,88.89596764,4.729489459,-0.2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000001C0DE02D90F026401564F188F7E94740',619,'2016-07-02 10:48:27','2016-07-02 10:48:27',281,94.45196562,10.66294133,-0.3)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000ABF1D24D62F02640E12D90A0F8E94740',632,'2016-07-02 10:48:28','2016-07-02 10:48:28',278,88.89596764,15.59055118,-0.7)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000069FD40CC38F02640C7925F2CF9E94740',650,'2016-07-02 10:48:29','2016-07-02 10:48:29',273,83.33996966,18.90779782,-0.7)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000002709AF4A0FF02640C7925F2CF9E94740',670,'2016-07-02 10:48:30','2016-07-02 10:48:30',272,79.63597101,20.72136144,-0.3)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000007AA85AF8E7EF2640C7925F2CF9E94740',691,'2016-07-02 10:48:31','2016-07-02 10:48:31',269,79.63597101,21.02108204,-0.4)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000068DB43D5C2EF2640E12D90A0F8E94740',712,'2016-07-02 10:48:32','2016-07-02 10:48:32',267,74.07997303,21.62560325,-0.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000EDA16AE19FEF2640FBC8C014F8E94740',728,'2016-07-02 10:48:33','2016-07-02 10:48:33',266,68.52397506,12.36982474,-0.1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000000AFCCE1C7FEF26401564F188F7E94740',733,'2016-07-02 10:48:34','2016-07-02 10:48:34',266,68.52397506,2.21488443,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000275633585EEF26402FFF21FDF6E94740',731,'2016-07-02 10:48:35','2016-07-02 10:48:35',267,68.52397506,-3.916687833,0.2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000015891C3539EF26402FFF21FDF6E94740',726,'2016-07-02 10:48:36','2016-07-02 10:48:36',270,74.07997303,-6.329692659,1.1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000E63FA4DFBEEE264078C1CDCFFAE94740',712,'2016-07-02 10:48:39','2016-07-02 10:48:39',280,88.89596764,-2.611125222,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000004FF9EABD0BEE2640448B6CE7FBE94740',706,'2016-07-02 10:48:43','2016-07-02 10:48:43',256,90.74796697,-0.198120396,-2.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000046B921B3A0ED264003E78C28EDE94740',706,'2016-07-02 10:48:46','2016-07-02 10:48:46',218,92.59996629,-0.198120396,-1.6)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000005C58F3177ED2640900C4C81DFE94740',703,'2016-07-02 10:48:48','2016-07-02 10:48:48',202,96.30396495,-1.402082804,-1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000211FF46C56ED26402650D7EDC6E94740',702,'2016-07-02 10:48:51','2016-07-02 10:48:51',188,100.0079636,0.502921006,-1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000806DEA295FED2640347D898BB6E94740',704,'2016-07-02 10:48:53','2016-07-02 10:48:53',166,100.0079636,0.802641605,-2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000337D898BB6ED26401383C0CAA1E94740',703,'2016-07-02 10:48:56','2016-07-02 10:48:56',133,101.8599629,-1.803403607,-1.7)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000000C05593CE2ED2640FDF675E09CE94740',700,'2016-07-02 10:48:57','2016-07-02 10:48:57',123,103.7119622,-2.611125222,-1.4)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000F0CCF1F778EE26409FA87F2394E94740',693,'2016-07-02 10:49:00','2016-07-02 10:49:00',105,111.1199596,-2.809245618,-0.6)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000C9073D9B55EF2640BD5296218EE94740',687,'2016-07-02 10:49:04','2016-07-02 10:49:04',97,112.9719589,-1.605283211,-0.1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000006F8104C5EF26400C24287E8CE94740',682,'2016-07-02 10:49:06','2016-07-02 10:49:06',97,114.8239582,-2.407924816,-0.2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000A0648535A8F02640F597DD9387E94740',676,'2016-07-02 10:49:10','2016-07-02 10:49:10',97,118.5279569,-1.402082804,0.1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000D70FC48C03F22640621386EE7FE94740',672,'2016-07-02 10:49:16','2016-07-02 10:49:16',97,116.6759575,-1.000762002,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000A72C431CEBF22640CB7F48BF7DE94740',666,'2016-07-02 10:49:20','2016-07-02 10:49:20',84,114.8239582,-1.605283211,-1.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000BFCAA145B6F32640BD5296218EE94740',662,'2016-07-02 10:49:24','2016-07-02 10:49:24',49,111.1199596,-1.203962408,-1.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000074DA40A70DF4264077E09C11A5E94740',659,'2016-07-02 10:49:27','2016-07-02 10:49:27',23,107.4159609,-1.402082804,-1.4)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000009AE3EFF11CF42640347D898BB6E94740',656,'2016-07-02 10:49:29','2016-07-02 10:49:29',4,101.8599629,-0.797561595,-1.8)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000074DA40A70DF426402650D7EDC6E94740',654,'2016-07-02 10:49:31','2016-07-02 10:49:31',347,101.8599629,-1.706883414,-1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000156A4DF38EF3264086EE7F6DEAE94740',649,'2016-07-02 10:49:36','2016-07-02 10:49:36',312,98.15596427,-1.503683007,-1.4)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000FAEDEBC039F32640E53C762AF3E94740',644,'2016-07-02 10:49:38','2016-07-02 10:49:38',295,96.30396495,-3.012446025,-1.2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B04A0F30E0F22640FBC8C014F8E94740',635,'2016-07-02 10:49:40','2016-07-02 10:49:40',284,94.45196562,-5.125730251,-0.7)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000F38B25BF58F22640448B6CE7FBE94740',623,'2016-07-02 10:49:43','2016-07-02 10:49:43',279,92.59996629,-2.809245618,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000A5E8482EFFF12640DC1EAA16FEE94740',617,'2016-07-02 10:49:45','2016-07-02 10:49:45',279,88.89596764,-3.312166624,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000009F17012859F12640F0AAF40003EA4740',607,'2016-07-02 10:49:49','2016-07-02 10:49:49',279,81.48797034,-1.300482601,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000004B5658830AF12640873E323005EA4740',607,'2016-07-02 10:49:51','2016-07-02 10:49:51',278,74.07997303,-0.294640589,-0.1)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000A0648535A8F0264006373FEB07EA4740',605,'2016-07-02 10:49:54','2016-07-02 10:49:54',280,61.11597775,-0.096520193,0.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E6100000C74B378941F02640E88C28ED0DEA4740',604,'2016-07-02 10:49:58','2016-07-02 10:49:58',292,48.15198247,0.101600203,0.4)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E61000001B5A643BDFEF264045DB1EAA16EA4740',604,'2016-07-02 10:50:04','2016-07-02 10:50:04',302,25.92799056,0.203200406,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000042D2948AB3EF264074029A081BEA4740',604,'2016-07-02 10:50:10','2016-07-02 10:50:10',300,5.555997978,0.101600203,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDEFF7', 'Koenigsdf', 'DDEFF7', 'GLIDER_OR_MOTOR_GLIDER','0101000020E610000013AB192CAFEF264074029A081BEA4740',603,'2016-07-02 10:50:16','2016-07-02 10:50:16',0,0,-0.096520193,0)")
db.session.execute("UPDATE sender_positions SET agl = altitude - 602;")
db.session.commit()
def insert_sender_positions_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 sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',597,'2019-04-13 09:20:14','2019-04-13 09:20:14',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',595,'2019-04-13 09:20:23','2019-04-13 09:20:23',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',595,'2019-04-13 09:20:29','2019-04-13 09:20:29',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:21:01','2019-04-13 09:21:01',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:21:02','2019-04-13 09:21:02',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',589,'2019-04-13 09:21:13','2019-04-13 09:21:13',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',589,'2019-04-13 09:21:29','2019-04-13 09:21:29',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',590,'2019-04-13 09:21:48','2019-04-13 09:21:48',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',591,'2019-04-13 09:22:02','2019-04-13 09:22:02',0,0,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B5040FE689EA264091FC62C92FEA4740',592,'2019-04-13 09:22:22','2019-04-13 09:22:22',0,0,0.1016,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000ED0DBE3099EA2640CA32C4B12EEA4740',593,'2019-04-13 09:22:40','2019-04-13 09:22:40',102,25.925552,0.2032,0.60000002)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000026E4839ECDEA26401904560E2DEA4740',594,'2019-04-13 09:22:42','2019-04-13 09:22:42',100,68.517532,0.2032,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000D044D8F0F4EA2640513AB7F62BEA4740',595,'2019-04-13 09:22:43','2019-04-13 09:22:43',101,81.480309,1.91008,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000025396A721EEB2640A00B49532AEA4740',600,'2019-04-13 09:22:44','2019-04-13 09:22:44',100,90.739433,5.6337199,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000009E8B4814EEB2640CA41AA3B29EA4740',608,'2019-04-13 09:22:45','2019-04-13 09:22:45',100,88.887611,9.2557602,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000087084327AEB264019133C9827EA4740',620,'2019-04-13 09:22:46','2019-04-13 09:22:46',99,87.035782,12.3698,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000246416B4A3EB264052499D8026EA4740',634,'2019-04-13 09:22:47','2019-04-13 09:22:47',97,83.33213,15.2908,-0.89999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000007958A835CDEB264067E4CDF425EA4740',650,'2019-04-13 09:22:48','2019-04-13 09:22:48',94,79.628487,16.093439,-2.0999999)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000CE4C3AB7F6EB264067E4CDF425EA4740',667,'2019-04-13 09:22:49','2019-04-13 09:22:49',91,75.924835,16.89608,-0.89999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000248613AB19EC264067E4CDF425EA4740',684,'2019-04-13 09:22:50','2019-04-13 09:22:50',91,72.221184,17.20088,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000005C532ACE3EEC264067E4CDF425EA4740',701,'2019-04-13 09:22:51','2019-04-13 09:22:51',90,68.517532,16.89608,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000003FF9C5925FEC264067E4CDF425EA4740',718,'2019-04-13 09:22:52','2019-04-13 09:22:52',91,68.517532,16.19504,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000229F615780EC264052499D8026EA4740',733,'2019-04-13 09:22:53','2019-04-13 09:22:53',89,59.258408,14.28496,-1.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B11D82BD9CEC264052499D8026EA4740',741,'2019-04-13 09:22:54','2019-04-13 09:22:54',89,57.406582,3.62204,0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000789CA223B9EC264067E4CDF425EA4740',736,'2019-04-13 09:22:55','2019-04-13 09:22:55',88,53.70293,-8.3413601,0.89999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B0AE00B9D7EC264052499D8026EA4740',724,'2019-04-13 09:22:56','2019-04-13 09:22:56',89,62.962055,-14.5796,0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000E97B17DCFCEC264052499D8026EA4740',710,'2019-04-13 09:22:57','2019-04-13 09:22:57',92,85.18396,-12.1666,1.8)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000CC2A62EB2CED26408A7FFE6825EA4740',703,'2019-04-13 09:22:58','2019-04-13 09:22:58',96,99.998558,-5.92836,2.0999999)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000936DEA295FED2640B5B55F5124EA4740',701,'2019-04-13 09:22:59','2019-04-13 09:22:59',102,99.998558,0.40132001,2.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000CB10C7BAB8ED2640D95F764F1EEA4740',704,'2019-04-13 09:23:01','2019-04-13 09:23:01',116,92.591263,2.21488,5.6999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000002005593CE2ED2640AE38FBF019EA4740',707,'2019-04-13 09:23:02','2019-04-13 09:23:02',133,88.887611,2.8143201,7.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000925CFE43FAED2640E77D426313EA4740',709,'2019-04-13 09:23:03','2019-04-13 09:23:03',147,88.887611,1.50876,6.9000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000CA65AD8E09EE26404BF9EABD0BEA4740',710,'2019-04-13 09:23:04','2019-04-13 09:23:04',159,88.887611,0.60452002,6.9000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000003DF9EABD0BEE2640448B6CE7FBE94740',709,'2019-04-13 09:23:06','2019-04-13 09:23:06',183,92.591263,-0.79755998,5.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000005917B7D100EE2640CBA145B6F3E94740',707,'2019-04-13 09:23:07','2019-04-13 09:23:07',192,94.443085,-2.1082001,3.3)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000076711B0DE0ED2640A098966BE4E94740',701,'2019-04-13 09:23:09','2019-04-13 09:23:09',196,99.998558,-2.61112,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000AF25E4839EED2640E08D2B1BC3E94740',695,'2019-04-13 09:23:13','2019-04-13 09:23:13',202,105.55404,0.1016,1.5)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000002152DD4931ED2640AF16FEF9A3E94740',696,'2019-04-13 09:23:17','2019-04-13 09:23:17',214,103.70221,-0.39624,2.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000EA62C92F96EC264021BF58F28BE94740',696,'2019-04-13 09:23:21','2019-04-13 09:23:21',236,105.55404,0.1016,2.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000005CC2ABD203EC26404478557A80E94740',694,'2019-04-13 09:23:24','2019-04-13 09:23:24',249,107.40586,-1.2039599,2.0999999)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000004182E2C798EB26402FEC0A907BE94740',690,'2019-04-13 09:23:26','2019-04-13 09:23:26',256,111.10951,-2.2098,2.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000098A1F63EEEA26407DBD9CEC79E94740',685,'2019-04-13 09:23:29','2019-04-13 09:23:29',268,114.81316,-1.00076,1.8)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000D1915CFE43EA2640E11A79337DE94740',684,'2019-04-13 09:23:32','2019-04-13 09:23:32',277,112.96133,-0.79755998,0.89999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000044BE55C4D6E926404478557A80E94740',682,'2019-04-13 09:23:34','2019-04-13 09:23:34',280,114.81316,-2.0065999,0.60000002)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000029ED0DBE30E92640932B1BC389E94740',675,'2019-04-13 09:23:37','2019-04-13 09:23:37',292,118.51682,-1.2039599,2.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000D467FD40CCE826409AA87F2394E94740',675,'2019-04-13 09:23:39','2019-04-13 09:23:39',307,114.81316,0.80264002,4.1999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000D49AE61DA7E826404BC8073D9BE94740',677,'2019-04-13 09:23:40','2019-04-13 09:23:40',316,112.96133,2.0116799,5.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000009CC420B072E826403D9B559FABE94740',680,'2019-04-13 09:23:42','2019-04-13 09:23:42',339,103.70221,1.0058399,5.4000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000002A762AF369E8264019D3728DBCE94740',681,'2019-04-13 09:23:44','2019-04-13 09:23:44',358,96.294907,0.2032,4.1999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000F1F44A5986E82640992A1895D4E94740',679,'2019-04-13 09:23:47','2019-04-13 09:23:47',10,94.443085,-2.2098,0.89999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000007F2E244DA9E826401982BD9CECE94740',671,'2019-04-13 09:23:50','2019-04-13 09:23:50',14,96.294907,-2.2098,0.60000002)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000D3EFCCF1F7E8264099ACB00615EA4740',662,'2019-04-13 09:23:55','2019-04-13 09:23:55',21,103.70221,-2.7127199,1.2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000028B1759646E92640513AB7F62BEA4740',655,'2019-04-13 09:23:58','2019-04-13 09:23:58',40,103.70221,-1.905,4.1999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000009A99999999E9264059B71B5736EA4740',652,'2019-04-13 09:24:00','2019-04-13 09:24:00',60,99.998558,-1.2039599,5.0999999)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000448B6CE7FBE9264091DE96B53AEA4740',649,'2019-04-13 09:24:02','2019-04-13 09:24:02',78,98.146736,-2.5095201,4.1999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000000AA4BA9362EA264091DE96B53AEA4740',643,'2019-04-13 09:24:04','2019-04-13 09:24:04',93,98.146736,-2.8092401,3)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B4958DE1C4EA26402E81BA6E37EA4740',636,'2019-04-13 09:24:06','2019-04-13 09:24:06',100,98.146736,-3.71856,1.2)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000005D6DC5FEB2EB2640B597933D2FEA4740',619,'2019-04-13 09:24:11','2019-04-13 09:24:11',100,94.443085,-3.71856,-0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000005BB1BFEC9EEC2640EEDCDAAF28EA4740',602,'2019-04-13 09:24:16','2019-04-13 09:24:16',98,96.294907,-2.7127199,0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000B003E78C28ED2640A01A2FDD24EA4740',598,'2019-04-13 09:24:19','2019-04-13 09:24:19',98,88.887611,-0.70104003,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000009298966BE4ED26408A8EE4F21FEA4740',597,'2019-04-13 09:24:24','2019-04-13 09:24:24',100,59.258408,-0.096519999,0.30000001)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000075C601E130EE2640EEFAA6C31DEA4740',596,'2019-04-13 09:24:28','2019-04-13 09:24:28',86,25.925552,0,-4.1999998)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000091B1E4174BEE26408A8EE4F21FEA4740',597,'2019-04-13 09:24:31','2019-04-13 09:24:31',66,14.814602,-0.096519999,-3)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000001F27563358EE26402722222222EA4740',597,'2019-04-13 09:24:38','2019-04-13 09:24:38',0,0,0.1016,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000CAFFDAD453EE26402722222222EA4740',598,'2019-04-13 09:24:58','2019-04-13 09:24:58',0,0,0.1016,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E6100000586C9DA551EE26402722222222EA4740',597,'2019-04-13 09:25:18','2019-04-13 09:25:18',0,0,0.1016,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000003098A1F63EE2640EEEBC03923EA4740',596,'2019-04-13 09:25:36','2019-04-13 09:25:36',54,1.8518252,0.1016,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E610000003CDF1F778EE2640A01A2FDD24EA4740',594,'2019-04-13 09:25:48','2019-04-13 09:25:48',76,1.8518252,-0.096519999,0)")
db.session.execute("INSERT INTO sender_positions(name, receiver_name, address, aircraft_type, location, altitude, timestamp, reference_timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('FLRDDAC7C','Koenigsd2','DDAC7C','GLIDER_OR_MOTOR_GLIDER','0101000020E61000001FF46C567DEE2640A01A2FDD24EA4740',593,'2019-04-13 09:25:59','2019-04-13 09:25:59',0,0,-0.096519999,0)")
db.session.execute("UPDATE sender_positions SET agl = altitude - 602;")
db.session.commit()
if __name__ == "__main__":
unittest.main()

Wyświetl plik

@ -1,34 +0,0 @@
import unittest
from tests.base import TestBaseDB, db
from app.model import AircraftBeacon
from app.collect.database import upsert
class TestDatabase(TestBaseDB):
@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}
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}
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)
self.assertEqual(result[1].ground_speed, 3)
self.assertEqual(result[2].ground_speed, 1)
self.assertEqual(result[3].ground_speed, None)
if __name__ == "__main__":
unittest.main()

Wyświetl plik

@ -3,8 +3,8 @@ import unittest
from tests.base import TestBaseDB, db
from app.model import Logbook, Airport, Device, TakeoffLanding
from app.collect.logbook import update_entries
from app.model import Logbook, Airport, Sender, TakeoffLanding
from app.collect.logbook import update_logbook
class TestLogbook(TestBaseDB):
@ -12,8 +12,8 @@ class TestLogbook(TestBaseDB):
super().setUp()
# Create basic data and insert
self.dd0815 = Device(address="DD0815")
self.dd4711 = Device(address="DD4711")
self.dd0815 = Sender(name="FLRDD0815", address="DD0815")
self.dd4711 = Sender(name="FLRDD4711", address="DD4711")
self.koenigsdorf = Airport(name="Koenigsdorf")
self.ohlstadt = Airport(name="Ohlstadt")
@ -26,79 +26,63 @@ 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, sender_id=self.dd0815.id)
self.landing_koenigsdorf_dd0815 = TakeoffLanding(is_takeoff=False, timestamp="2016-06-01 10:05:00", airport_id=self.koenigsdorf.id, sender_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, sender_id=self.dd0815.id)
self.takeoff_ohlstadt_dd4711 = TakeoffLanding(is_takeoff=True, timestamp="2016-06-01 10:00:00", airport_id=self.ohlstadt.id, sender_id=self.dd4711.id)
def get_logbook_entries(self):
return db.session.query(Logbook).order_by(Logbook.takeoff_airport_id, Logbook.reftime).all()
return db.session.query(Logbook).order_by(Logbook.takeoff_airport_id, Logbook.reference_timestamp).all()
def test_single_takeoff(self):
db.session.add(self.takeoff_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[0].landing_airport_id, None)
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
entries2 = self.get_logbook_entries()
self.assertEqual(entries, entries2)
def test_single_landing(self):
db.session.add(self.landing_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, None)
self.assertEqual(entries[0].landing_airport_id, self.koenigsdorf.id)
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
entries2 = self.get_logbook_entries()
self.assertEqual(entries, entries2)
def test_different_takeoffs(self):
db.session.add(self.takeoff_koenigsdorf_dd0815)
db.session.add(self.takeoff_ohlstadt_dd4711)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 2)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[1].takeoff_airport_id, self.ohlstadt.id)
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
entries2 = self.get_logbook_entries()
self.assertEqual(entries, entries2)
def test_takeoff_and_landing(self):
db.session.add(self.takeoff_koenigsdorf_dd0815)
db.session.add(self.landing_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[0].landing_airport_id, self.koenigsdorf.id)
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
entries2 = self.get_logbook_entries()
self.assertEqual(entries, entries2)
@unittest.skip('needs information about airport timezone')
def test_takeoff_and_landing_on_different_days(self):
db.session.add(self.takeoff_koenigsdorf_dd0815)
db.session.add(self.landing_koenigsdorf_dd0815_later)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_entries(session=db.session, date=datetime.date(2016, 6, 2))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 2)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
@ -106,15 +90,11 @@ class TestLogbook(TestBaseDB):
self.assertEqual(entries[1].landing_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[1].reftime, self.landing_koenigsdorf_dd0815_later.timestamp)
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
entries2 = self.get_logbook_entries()
self.assertEqual(entries, entries2)
def test_update(self):
db.session.add(self.takeoff_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook(0)
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
@ -122,7 +102,7 @@ class TestLogbook(TestBaseDB):
db.session.add(self.landing_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
@ -131,7 +111,7 @@ class TestLogbook(TestBaseDB):
db.session.add(self.takeoff_ohlstadt_dd4711)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook(0)
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 2)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
@ -141,26 +121,22 @@ class TestLogbook(TestBaseDB):
db.session.add(self.landing_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, None)
self.assertEqual(entries[0].landing_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[0].reftime, self.landing_koenigsdorf_dd0815.timestamp)
self.assertEqual(entries[0].reference_timestamp, self.landing_koenigsdorf_dd0815.timestamp)
db.session.add(self.takeoff_koenigsdorf_dd0815)
db.session.commit()
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
update_logbook()
entries = self.get_logbook_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].takeoff_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[0].landing_airport_id, self.koenigsdorf.id)
self.assertEqual(entries[0].reftime, self.takeoff_koenigsdorf_dd0815.timestamp)
update_entries(session=db.session, date=datetime.date(2016, 6, 1))
entries2 = self.get_logbook_entries()
self.assertEqual(entries, entries2)
self.assertEqual(entries[0].reference_timestamp, self.takeoff_koenigsdorf_dd0815.timestamp)
if __name__ == "__main__":

Wyświetl plik

@ -1,53 +0,0 @@
from datetime import date
import unittest
from tests.base import TestBaseDB, db
from app.model import AircraftBeacon, Receiver, ReceiverCoverage, Device
from app.collect.ognrange import update_entries
class TestOGNrange(TestBaseDB):
def setUp(self):
super().setUp()
# Create basic data and insert
self.dd0815 = Device(address="DD0815")
self.dd4711 = Device(address="DD4711")
self.r01 = Receiver(name="Koenigsdf")
self.r02 = Receiver(name="Bene")
db.session.add(self.dd0815)
db.session.add(self.dd4711)
db.session.add(self.r01)
db.session.add(self.r02)
db.session.commit()
# Create beacons and insert
self.ab01 = AircraftBeacon(
name="FLRDD0815", receiver_name="Koenigsdf", timestamp="2017-12-10 10:00:00", location_mgrs_short="89ABC1267", altitude=800
)
self.ab02 = AircraftBeacon(
name="FLRDD0815", receiver_name="Koenigsdf", 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()
@unittest.skip('stats will replaced by timescaledb aggregates')
def test_update_receiver_coverage(self):
update_entries(db.session, date=date(2017, 12, 10))
coverages = db.session.query(ReceiverCoverage).all()
self.assertEqual(len(coverages), 1)
coverage = coverages[0]
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__":
unittest.main()

Wyświetl plik

@ -1,199 +0,0 @@
from datetime import datetime, date
import unittest
from tests.base import TestBaseDB, db
from app.model import AircraftBeacon, ReceiverBeacon, Receiver, Device, DeviceStats
from app.collect.stats import create_device_stats
class TestStats(TestBaseDB):
def setUp(self):
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.rb01 = ReceiverBeacon(name="Koenigsdf", receiver_name="GLIDERN1", timestamp="2017-12-10 09:55:00", altitude=601)
self.rb02 = ReceiverBeacon(name="Koenigsdf", receiver_name="GLIDERN1", timestamp="2017-12-10 10:00:00", altitude=601)
self.rb03 = ReceiverBeacon(name="Koenigsdf", receiver_name="GLIDERN1", timestamp="2017-12-10 10:05:00", altitude=601)
self.r01 = Receiver(name="Koenigsdf")
self.r02 = Receiver(name="Bene")
self.d01 = Device(address="DD4711")
db.session.add(self.r01)
db.session.add(self.d01)
db.session.commit()
@unittest.skip('stats will replaced by timescaledb aggregates')
def test_create_device_stats(self):
# Compute 1st beacon
self.ab01.device = self.d01
self.ab01.receiver = self.r01
db.session.add(self.ab01)
db.session.commit()
today = date(2017, 12, 10)
create_device_stats(db.session, date=today)
devicestats = db.session.query(DeviceStats).all()
self.assertEqual(len(devicestats), 1)
self.assertEqual(devicestats[0].device, self.d01)
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].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)
self.assertEqual(devicestats[0].stealth, None)
self.assertEqual(devicestats[0].software_version, None)
self.assertEqual(devicestats[0].hardware_version, None)
self.assertEqual(devicestats[0].real_address, None)
# Compute 2nd beacon: set altitude, aircraft_type and stealth
self.ab02.device = self.d01
self.ab02.receiver = self.r01
self.ab02.altitude = 200
self.ab02.aircraft_type = 3
self.ab02.stealth = False
db.session.add(self.ab02)
db.session.commit()
create_device_stats(db.session, date=today)
devicestats = db.session.query(DeviceStats).all()
self.assertEqual(len(devicestats), 1)
self.assertEqual(devicestats[0].device, self.d01)
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].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)
self.assertEqual(devicestats[0].stealth, False)
self.assertEqual(devicestats[0].software_version, None)
self.assertEqual(devicestats[0].hardware_version, None)
self.assertEqual(devicestats[0].real_address, None)
# Compute 3rd beacon: changed software version, but with error_count > 0
self.ab03.device = self.d01
self.ab03.receiver = self.r01
self.ab03.error_count = 1
self.ab03.software_version = 6.01
db.session.add(self.ab03)
db.session.commit()
create_device_stats(db.session, date=today)
devicestats = db.session.query(DeviceStats).all()
self.assertEqual(len(devicestats), 1)
self.assertEqual(devicestats[0].device, self.d01)
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].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)
self.assertEqual(devicestats[0].stealth, False)
self.assertEqual(devicestats[0].software_version, None)
self.assertEqual(devicestats[0].hardware_version, None)
self.assertEqual(devicestats[0].real_address, None)
# Compute 4. beacon: another receiver, greater altitude, software_version, hardware_version, real_address
self.ab04.device = self.d01
self.ab04.receiver = self.r02
self.ab04.altitude = 250
self.ab04.software_version = 6.01
self.ab04.hardware_version = 15
self.ab04.real_address = "DDALFA"
db.session.add(self.ab04)
db.session.commit()
create_device_stats(db.session, date=today)
devicestats = db.session.query(DeviceStats).all()
self.assertEqual(len(devicestats), 1)
self.assertEqual(devicestats[0].device, self.d01)
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].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")
# Compute 5. beacon: lower altitude, stealth
self.ab05.device = self.d01
self.ab05.receiver = self.r02
self.ab05.altitude = 100
self.ab05.stealth = True
db.session.add(self.ab05)
db.session.commit()
create_device_stats(db.session, date=today)
devicestats = db.session.query(DeviceStats).all()
self.assertEqual(len(devicestats), 1)
self.assertEqual(devicestats[0].device, self.d01)
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].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")
# Compute 6. beacon: beacon from past, greater altitude, newer version
self.ab06.device = self.d01
self.ab06.receiver = self.r02
self.ab06.timestamp = datetime(2017, 12, 10, 9, 59, 50)
self.ab06.altitude = 300
self.ab06.software_version = 6.02
db.session.add(self.ab06)
db.session.commit()
create_device_stats(db.session, date=today)
devicestats = db.session.query(DeviceStats).all()
self.assertEqual(len(devicestats), 1)
self.assertEqual(devicestats[0].device, self.d01)
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].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")
if __name__ == "__main__":
unittest.main()

Some files were not shown because too many files have changed in this diff Show More