Merge pull request #56 from Meisterschueler/logbook

Logbook
pull/58/head
Meisterschueler 2016-07-14 22:51:23 +02:00 zatwierdzone przez GitHub
commit 2050400fca
16 zmienionych plików z 657 dodań i 264 usunięć

Wyświetl plik

@ -1,10 +1,18 @@
language: python
env:
- OGN_CONFIG_MODULE='config.test'
python:
- 3.4
services:
- postgresql
before_script:
- flake8 tests ogn
- psql -c 'CREATE DATABASE ogn_test;' -U postgres
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d ogn_test
script:
- nosetests --with-coverage --cover-package=ogn

Wyświetl plik

@ -102,15 +102,22 @@ available commands:
run Run the aprs client.
[logbook]
compute Compute takeoffs and landings.
compute_logbook Compute logbook.
compute_takeoff_landingCompute takeoffs and landings.
show Show a logbook for <airport_name>.
[show.airport]
list_all Show a list of all airports.
[show.devices]
[show.deviceinfos]
stats Show some stats on registered devices.
[show.devices]
aircraft_type_stats Show stats about aircraft types used by devices.
hardware_stats Show stats about hardware version used by devices.
software_stats Show stats about software version used by devices.
stealth_stats Show stats about stealth flag set by devices.
[show.receiver]
hardware_stats Show some statistics of receiver hardware.
list_all Show a list of all receivers.

Wyświetl plik

@ -11,10 +11,18 @@ CELERYBEAT_SCHEDULE = {
'task': 'ogn.collect.database.import_ddb',
'schedule': timedelta(minutes=15),
},
'update-logbook': {
'update-takeoff-and-landing': {
'task': 'ogn.collect.logbook.compute_takeoff_and_landing',
'schedule': timedelta(minutes=15),
},
'update-logbook': {
'task': 'ogn.collect.logbook.compute_logbook',
'schedule': timedelta(minutes=1),
},
'update-altitudes': {
'task': 'ogn.collect.logbook.compute_altitudes',
'schedule': timedelta(minutes=1),
},
'update-receiver-table': {
'task': 'ogn.collect.receiver.update_receivers',
'schedule': timedelta(minutes=15),

9
config/test.py 100644
Wyświetl plik

@ -0,0 +1,9 @@
SQLALCHEMY_DATABASE_URI = 'postgresql://postgres@localhost:5432/ogn_test'
BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERYBEAT_SCHEDULE = {}
CELERY_TIMEZONE = 'UTC'

Wyświetl plik

@ -27,6 +27,7 @@ def close_db(signal, sender):
app = Celery('ogn.collect',
include=["ogn.collect.database",
"ogn.collect.logbook",
"ogn.collect.takeoff_landing",
"ogn.collect.receiver"
])

Wyświetl plik

@ -9,11 +9,14 @@ from ogn.collect.celery import app
logger = get_task_logger(__name__)
def update_device_infos(session, address_origin, device_infos):
def delete_device_infos(session, address_origin):
session.query(DeviceInfo) \
.filter(DeviceInfo.address_origin == address_origin) \
.delete()
session.commit()
def update_device_infos(session, device_infos):
session.bulk_save_objects(device_infos)
session.commit()
@ -25,8 +28,10 @@ def import_ddb():
"""Import registered devices from the DDB."""
logger.info("Import registered devices fom the DDB...")
counter = update_device_infos(app.session, AddressOrigin.ogn_ddb,
get_ddb())
address_origin = AddressOrigin.ogn_ddb
delete_device_infos(app.session, address_origin)
counter = update_device_infos(app.session, get_ddb(address_origin=address_origin))
logger.info("Imported {} devices.".format(counter))
@ -35,6 +40,8 @@ def import_file(path='tests/custom_ddb.txt'):
"""Import registered devices from a local file."""
logger.info("Import registered devices from '{}'...".format(path))
counter = update_device_infos(app.session, AddressOrigin.user_defined,
get_ddb(path))
address_origin = AddressOrigin.user_defined
delete_device_infos(app.session, address_origin)
counter = update_device_infos(app.session, get_ddb(csvfile=path, address_origin=address_origin))
logger.info("Imported {} devices.".format(counter))

Wyświetl plik

@ -1,117 +1,166 @@
from datetime import timedelta
from celery.utils.log import get_task_logger
from sqlalchemy import and_, or_, insert, update, between, exists
from sqlalchemy.sql import func, null
from sqlalchemy.sql.expression import true, false, label
from ogn.collect.celery import app
from sqlalchemy.sql import func
from sqlalchemy import and_, or_, insert, between
from sqlalchemy.sql.expression import case
from ogn.model import AircraftBeacon, TakeoffLanding, Airport
from ogn.model import TakeoffLanding, Logbook
logger = get_task_logger(__name__)
@app.task
def compute_takeoff_and_landing():
logger.info("Compute takeoffs and landings.")
def compute_logbook_entries(session=None):
logger.info("Compute logbook.")
# takeoff / landing detection is based on 3 consecutive points
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
duration = 100 # the points must not exceed this duration
radius = 0.05 # the points must not exceed this radius (degree!) around the 2nd point
if session is None:
session = app.session
# takeoff / landing has to be near an airport
airport_radius = 0.025 # takeoff / landing must not exceed this radius (degree!) around the airport
airport_delta = 100 # takeoff / landing must not exceed this altitude offset above/below the airport
or_args = [between(TakeoffLanding.timestamp, '2016-06-28 00:00:00', '2016-06-28 23:59:59')]
or_args = []
# max AircraftBeacon id offset computed per function call
max_id_offset = 500000
# 'wo' is the window order for the sql window function
wo = and_(func.date(TakeoffLanding.timestamp),
TakeoffLanding.device_id,
TakeoffLanding.timestamp,
TakeoffLanding.airport_id)
# get the last AircraftBeacon used for TakeoffLanding and start from there
last_takeoff_landing_query = app.session.query(func.max(TakeoffLanding.id).label('max_id')) \
# make a query with current, previous and next "takeoff_landing" event, so we can find complete flights
sq = session.query(
TakeoffLanding.device_id,
func.lag(TakeoffLanding.device_id).over(order_by=wo).label('device_id_prev'),
func.lead(TakeoffLanding.device_id).over(order_by=wo).label('device_id_next'),
TakeoffLanding.timestamp,
func.lag(TakeoffLanding.timestamp).over(order_by=wo).label('timestamp_prev'),
func.lead(TakeoffLanding.timestamp).over(order_by=wo).label('timestamp_next'),
TakeoffLanding.track,
func.lag(TakeoffLanding.track).over(order_by=wo).label('track_prev'),
func.lead(TakeoffLanding.track).over(order_by=wo).label('track_next'),
TakeoffLanding.is_takeoff,
func.lag(TakeoffLanding.is_takeoff).over(order_by=wo).label('is_takeoff_prev'),
func.lead(TakeoffLanding.is_takeoff).over(order_by=wo).label('is_takeoff_next'),
TakeoffLanding.airport_id,
func.lag(TakeoffLanding.airport_id).over(order_by=wo).label('airport_id_prev'),
func.lead(TakeoffLanding.airport_id).over(order_by=wo).label('airport_id_next')) \
.filter(*or_args) \
.subquery()
last_used_aircraft_beacon_query = app.session.query(AircraftBeacon.id) \
.filter(TakeoffLanding.id == last_takeoff_landing_query.c.max_id) \
.filter(and_(AircraftBeacon.timestamp == TakeoffLanding.timestamp,
AircraftBeacon.device_id == TakeoffLanding.device_id))
# find complete flights (with takeoff and landing on the same day)
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'),
label('duration', sq.c.timestamp_next - sq.c.timestamp)) \
.filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) \
.filter(sq.c.device_id == sq.c.device_id_next) \
.filter(func.date(sq.c.timestamp_next) == func.date(sq.c.timestamp))
last_used_aircraft_beacon_id = last_used_aircraft_beacon_query.first()
if last_used_aircraft_beacon_id is None:
aircraft_beacon_id_start = 0
else:
aircraft_beacon_id_start = last_used_aircraft_beacon_id[0] + 1
# split complete flights (with takeoff and landing on different days) into one takeoff and one landing
split_start_query = session.query(
sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
sq.c.timestamp.label('takeoff_timestamp'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'),
null().label('landing_timestamp'), null().label('landing_track'), null().label('landing_airport_id'),
null().label('duration')) \
.filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) \
.filter(sq.c.device_id == sq.c.device_id_next) \
.filter(func.date(sq.c.timestamp_next) != func.date(sq.c.timestamp))
# make a query with current, previous and next position
sq = app.session.query(
AircraftBeacon.timestamp,
func.lag(AircraftBeacon.timestamp).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('timestamp_prev'),
func.lead(AircraftBeacon.timestamp).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('timestamp_next'),
AircraftBeacon.location_wkt,
func.lag(AircraftBeacon.location_wkt).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('location_wkt_prev'),
func.lead(AircraftBeacon.location_wkt).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('location_wkt_next'),
AircraftBeacon.track,
func.lag(AircraftBeacon.track).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('track_prev'),
func.lead(AircraftBeacon.track).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('track_next'),
AircraftBeacon.ground_speed,
func.lag(AircraftBeacon.ground_speed).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('ground_speed_prev'),
func.lead(AircraftBeacon.ground_speed).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('ground_speed_next'),
AircraftBeacon.altitude,
func.lag(AircraftBeacon.altitude).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('altitude_prev'),
func.lead(AircraftBeacon.altitude).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('altitude_next'),
AircraftBeacon.device_id,
func.lag(AircraftBeacon.device_id).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('device_id_prev'),
func.lead(AircraftBeacon.device_id).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('device_id_next')) \
.filter(between(AircraftBeacon.id, aircraft_beacon_id_start, aircraft_beacon_id_start + max_id_offset)) \
split_landing_query = session.query(
sq.c.timestamp_next.label('reftime'),
sq.c.device_id.label('device_id'),
null().label('takeoff_timestamp'), null().label('takeoff_track'), null().label('takeoff_airport_id'),
sq.c.timestamp_next.label('landing_timestamp'), sq.c.track_next.label('landing_track'), sq.c.airport_id_next.label('landing_airport_id'),
null().label('duration')) \
.filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) \
.filter(sq.c.device_id == sq.c.device_id_next) \
.filter(func.date(sq.c.timestamp_next) != func.date(sq.c.timestamp))
# find landings without start
only_landings_query = session.query(
sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
null().label('takeoff_timestamp'), null().label('takeoff_track'), null().label('takeoff_airport_id'),
sq.c.timestamp.label('landing_timestamp'), sq.c.track.label('landing_track'), sq.c.airport_id.label('landing_airport_id'),
null().label('duration')) \
.filter(sq.c.is_takeoff == false()) \
.filter(or_(sq.c.device_id != sq.c.device_id_prev,
sq.c.is_takeoff_prev == false(),
sq.c.is_takeoff_prev == null()))
# find starts without landing
only_starts_query = session.query(
sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
sq.c.timestamp.label('takeoff_timestamp'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'),
null().label('landing_timestamp'), null().label('landing_track'), null().label('landing_airport_id'),
null().label('duration')) \
.filter(sq.c.is_takeoff == true()) \
.filter(or_(sq.c.device_id != sq.c.device_id_next,
sq.c.is_takeoff_next == true(),
sq.c.is_takeoff_next == null()))
# unite all computated flights
union_query = complete_flight_query.union(
split_start_query,
split_landing_query,
only_landings_query,
only_starts_query) \
.subquery()
# find possible takeoffs and landings
sq2 = app.session.query(
sq.c.timestamp,
case([(sq.c.ground_speed > takeoff_speed, sq.c.location_wkt_prev), # on takeoff we take the location from the previous fix because it is nearer to the airport
(sq.c.ground_speed < landing_speed, sq.c.location)]).label('location'),
case([(sq.c.ground_speed > takeoff_speed, sq.c.track),
(sq.c.ground_speed < landing_speed, sq.c.track_prev)]).label('track'), # on landing we take the track from the previous fix because gliders tend to leave the runway quickly
sq.c.ground_speed,
sq.c.altitude,
case([(sq.c.ground_speed > takeoff_speed, True),
(sq.c.ground_speed < landing_speed, False)]).label('is_takeoff'),
sq.c.device_id) \
.filter(sq.c.device_id_prev == sq.c.device_id == sq.c.device_id_next) \
.filter(or_(and_(sq.c.ground_speed_prev < takeoff_speed, # takeoff
sq.c.ground_speed > takeoff_speed,
sq.c.ground_speed_next > takeoff_speed),
and_(sq.c.ground_speed_prev > landing_speed, # landing
sq.c.ground_speed < landing_speed,
sq.c.ground_speed_next < landing_speed))) \
.filter(sq.c.timestamp_next - sq.c.timestamp_prev < timedelta(seconds=duration)) \
.filter(and_(func.ST_DFullyWithin(sq.c.location, sq.c.location_wkt_prev, radius),
func.ST_DFullyWithin(sq.c.location, sq.c.location_wkt_next, radius))) \
.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({"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,
"duration": union_query.c.duration})
# consider them if they are near a airport
takeoff_landing_query = app.session.query(
sq2.c.timestamp,
sq2.c.track,
sq2.c.is_takeoff,
sq2.c.device_id,
Airport.id) \
.filter(and_(func.ST_DFullyWithin(sq2.c.location, Airport.location_wkt, airport_radius),
between(sq2.c.altitude, Airport.altitude - airport_delta, Airport.altitude + airport_delta))) \
.filter(between(Airport.style, 2, 5))
result = session.execute(upd)
update_counter = result.rowcount
session.commit()
logger.debug("Updated logbook entries: {}".format(update_counter))
# ... 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 = app.session.execute(ins)
counter = result.rowcount
app.session.commit()
logger.debug("New takeoffs and landings: {}".format(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())))))
return counter
ins = insert(Logbook).from_select((Logbook.reftime,
Logbook.device_id,
Logbook.takeoff_timestamp,
Logbook.takeoff_track,
Logbook.takeoff_airport_id,
Logbook.landing_timestamp,
Logbook.landing_track,
Logbook.landing_airport_id,
Logbook.duration),
new_logbook_entries)
result = session.execute(ins)
insert_counter = result.rowcount
session.commit()
logger.debug("New logbook entries: {}".format(insert_counter))
return "{}/{}".format(update_counter, insert_counter)

Wyświetl plik

@ -0,0 +1,137 @@
from datetime import timedelta
from celery.utils.log import get_task_logger
from sqlalchemy import and_, or_, insert, between
from sqlalchemy.sql import func
from sqlalchemy.sql.expression import case
from ogn.collect.celery import app
from ogn.model import AircraftBeacon, TakeoffLanding, Airport
logger = get_task_logger(__name__)
def get_aircraft_beacon_start_id(session):
# returns the last AircraftBeacon used for TakeoffLanding
last_takeoff_landing_query = session.query(func.max(TakeoffLanding.id).label('max_id')) \
.subquery()
last_used_aircraft_beacon_query = session.query(AircraftBeacon.id) \
.filter(TakeoffLanding.id == last_takeoff_landing_query.c.max_id) \
.filter(and_(AircraftBeacon.timestamp == TakeoffLanding.timestamp,
AircraftBeacon.device_id == TakeoffLanding.device_id))
last_used_aircraft_beacon_id = last_used_aircraft_beacon_query.first()
if last_used_aircraft_beacon_id is None:
min_aircraft_beacon_id = session.query(func.min(AircraftBeacon.id)).first()
if min_aircraft_beacon_id is None:
start_id = 0
else:
start_id = min_aircraft_beacon_id[0]
else:
start_id = last_used_aircraft_beacon_id[0] + 1
return start_id
@app.task
def compute_takeoff_and_landing(session=None):
logger.info("Compute takeoffs and landings.")
if session is None:
session = app.session
# takeoff / landing detection is based on 3 consecutive points
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
duration = 100 # the points must not exceed this duration
radius = 0.05 # the points must not exceed this radius (degree!) around the 2nd point
# takeoff / landing has to be near an airport
airport_radius = 0.025 # takeoff / landing must not exceed this radius (degree!) around the airport
airport_delta = 100 # takeoff / landing must not exceed this altitude offset above/below the airport
# AircraftBeacon start id and end id
aircraft_beacon_start_id = get_aircraft_beacon_start_id(session)
aircraft_beacon_end_id = aircraft_beacon_start_id + 500000
# 'wo' is the window order for the sql window function
wo = and_(AircraftBeacon.device_id,
AircraftBeacon.timestamp,
AircraftBeacon.receiver_id)
# make a query with current, previous and next position
sq = session.query(
AircraftBeacon.id,
AircraftBeacon.timestamp,
func.lag(AircraftBeacon.timestamp).over(order_by=wo).label('timestamp_prev'),
func.lead(AircraftBeacon.timestamp).over(order_by=wo).label('timestamp_next'),
AircraftBeacon.location_wkt,
func.lag(AircraftBeacon.location_wkt).over(order_by=wo).label('location_wkt_prev'),
func.lead(AircraftBeacon.location_wkt).over(order_by=wo).label('location_wkt_next'),
AircraftBeacon.track,
func.lag(AircraftBeacon.track).over(order_by=wo).label('track_prev'),
func.lead(AircraftBeacon.track).over(order_by=wo).label('track_next'),
AircraftBeacon.ground_speed,
func.lag(AircraftBeacon.ground_speed).over(order_by=wo).label('ground_speed_prev'),
func.lead(AircraftBeacon.ground_speed).over(order_by=wo).label('ground_speed_next'),
AircraftBeacon.altitude,
func.lag(AircraftBeacon.altitude).over(order_by=wo).label('altitude_prev'),
func.lead(AircraftBeacon.altitude).over(order_by=wo).label('altitude_next'),
AircraftBeacon.device_id,
func.lag(AircraftBeacon.device_id).over(order_by=wo).label('device_id_prev'),
func.lead(AircraftBeacon.device_id).over(order_by=wo).label('device_id_next')) \
.filter(between(AircraftBeacon.id, aircraft_beacon_start_id, aircraft_beacon_end_id)) \
.subquery()
# find possible takeoffs and landings
sq2 = session.query(
sq.c.id,
sq.c.timestamp,
case([(sq.c.ground_speed > takeoff_speed, sq.c.location_wkt_prev), # on takeoff we take the location from the previous fix because it is nearer to the airport
(sq.c.ground_speed < landing_speed, sq.c.location)]).label('location'),
case([(sq.c.ground_speed > takeoff_speed, sq.c.track),
(sq.c.ground_speed < landing_speed, sq.c.track_prev)]).label('track'), # on landing we take the track from the previous fix because gliders tend to leave the runway quickly
sq.c.ground_speed,
sq.c.altitude,
case([(sq.c.ground_speed > takeoff_speed, True),
(sq.c.ground_speed < landing_speed, False)]).label('is_takeoff'),
sq.c.device_id) \
.filter(sq.c.device_id_prev == sq.c.device_id == sq.c.device_id_next) \
.filter(or_(and_(sq.c.ground_speed_prev < takeoff_speed, # takeoff
sq.c.ground_speed > takeoff_speed,
sq.c.ground_speed_next > takeoff_speed),
and_(sq.c.ground_speed_prev > landing_speed, # landing
sq.c.ground_speed < landing_speed,
sq.c.ground_speed_next < landing_speed))) \
.filter(sq.c.timestamp_next - sq.c.timestamp_prev < timedelta(seconds=duration)) \
.filter(and_(func.ST_DFullyWithin(sq.c.location, sq.c.location_wkt_prev, radius),
func.ST_DFullyWithin(sq.c.location, sq.c.location_wkt_next, radius))) \
.subquery()
# consider them if they are near a airport
takeoff_landing_query = session.query(
sq2.c.timestamp,
sq2.c.track,
sq2.c.is_takeoff,
sq2.c.device_id,
Airport.id) \
.filter(and_(func.ST_DFullyWithin(sq2.c.location, Airport.location_wkt, airport_radius),
between(sq2.c.altitude, Airport.altitude - airport_delta, Airport.altitude + airport_delta))) \
.filter(between(Airport.style, 2, 5)) \
.order_by(sq2.c.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)
counter = result.rowcount
session.commit()
logger.debug("New takeoffs and landings: {}".format(counter))
return counter

Wyświetl plik

@ -2,31 +2,39 @@
from datetime import timedelta, datetime
from sqlalchemy.sql import func, null
from sqlalchemy.sql import func
from sqlalchemy import and_, or_
from sqlalchemy.sql.expression import true, false, label
from sqlalchemy.orm import aliased
from ogn.model import Device, DeviceInfo, TakeoffLanding, Airport
from ogn.model import Device, DeviceInfo, TakeoffLanding, Airport, Logbook
from ogn.commands.dbutils import session
from ogn.collect.logbook import compute_takeoff_and_landing
from ogn.collect.takeoff_landing import compute_takeoff_and_landing
from ogn.collect.logbook import compute_logbook_entries
from manager import Manager
manager = Manager()
@manager.command
def compute():
def compute_takeoff_landing():
"""Compute takeoffs and landings."""
print("Compute takeoffs and landings...")
result = compute_takeoff_and_landing.delay()
counter = result.get()
print("New/recalculated takeoffs/landings: {}".format(counter))
print("New takeoffs/landings: {}".format(counter))
@manager.command
def compute_logbook():
"""Compute logbook."""
print("Compute logbook...")
result = compute_logbook_entries.delay()
counter = result.get()
print("New logbook entries: {}".format(counter))
@manager.arg('date', help='date (format: yyyy-mm-dd)')
@manager.arg('utc_delta_hours', help='delta hours to utc (for local time logs)')
@manager.command
def show(airport_name, utc_delta_hours=0, date=None):
"""Show a logbook for <airport_name>."""
@ -38,138 +46,13 @@ def show(airport_name, utc_delta_hours=0, date=None):
print('Airport "{}" not found.'.format(airport_name))
return
utc_timedelta = timedelta(hours=utc_delta_hours)
or_args = []
if date is not None:
date = datetime.strptime(date, "%Y-%m-%d")
or_args = [and_(TakeoffLanding.timestamp >= date + utc_timedelta,
TakeoffLanding.timestamp < date + timedelta(hours=24) + utc_timedelta)]
or_args = [and_(TakeoffLanding.timestamp >= date,
TakeoffLanding.timestamp < date + timedelta(hours=24))]
# make a query with current, previous and next "takeoff_landing" event, so we can find complete flights
sq = session.query(
TakeoffLanding.device_id,
func.lag(TakeoffLanding.device_id)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('device_id_prev'),
func.lead(TakeoffLanding.device_id)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('device_id_next'),
(TakeoffLanding.timestamp + utc_timedelta).label('timestamp'),
func.lag(TakeoffLanding.timestamp)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('timestamp_prev'),
func.lead(TakeoffLanding.timestamp + utc_timedelta)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('timestamp_next'),
TakeoffLanding.track,
func.lag(TakeoffLanding.track)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('track_prev'),
func.lead(TakeoffLanding.track)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('track_next'),
TakeoffLanding.is_takeoff,
func.lag(TakeoffLanding.is_takeoff)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('is_takeoff_prev'),
func.lead(TakeoffLanding.is_takeoff)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('is_takeoff_next'),
TakeoffLanding.airport_id,
func.lag(TakeoffLanding.airport_id)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('airport_id_prev'),
func.lead(TakeoffLanding.airport_id)
.over(order_by=and_(func.date(TakeoffLanding.timestamp + utc_timedelta),
TakeoffLanding.device_id,
TakeoffLanding.timestamp + utc_timedelta))
.label('airport_id_next')) \
.filter(*or_args) \
.subquery()
# find complete flights (with takeoff and landing on the same day)
complete_flight_query = session.query(sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
sq.c.timestamp.label('takeoff'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'),
sq.c.timestamp_next.label('landing'), sq.c.track_next.label('landing_track'), sq.c.airport_id_next.label('landing_airport_id'),
label('duration', sq.c.timestamp_next - sq.c.timestamp)) \
.filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) \
.filter(sq.c.device_id == sq.c.device_id_next) \
.filter(func.date(sq.c.timestamp_next) == func.date(sq.c.timestamp)) \
.filter(or_(sq.c.airport_id == airport.id,
sq.c.airport_id_next == airport.id))
# split complete flights (with takeoff and landing on different days) into one takeoff and one landing
split_start_query = session.query(sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
sq.c.timestamp.label('takeoff'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'),
null().label('landing'), null().label('landing_track'), null().label('landing_airport_id'),
null().label('duration')) \
.filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) \
.filter(sq.c.device_id == sq.c.device_id_next) \
.filter(func.date(sq.c.timestamp_next) != func.date(sq.c.timestamp)) \
.filter(and_(sq.c.airport_id == airport.id,
sq.c.airport_id_next == airport.id))
split_landing_query = session.query(sq.c.timestamp_next.label('reftime'),
sq.c.device_id.label('device_id'),
null().label('takeoff'), null().label('takeoff_track'), null().label('takeoff_airport_id'),
sq.c.timestamp_next.label('landing'), sq.c.track_next.label('landing_track'), sq.c.airport_id_next.label('landing_airport_id'),
null().label('duration')) \
.filter(and_(sq.c.is_takeoff == true(), sq.c.is_takeoff_next == false())) \
.filter(sq.c.device_id == sq.c.device_id_next) \
.filter(func.date(sq.c.timestamp_next) != func.date(sq.c.timestamp)) \
.filter(and_(sq.c.airport_id == airport.id,
sq.c.airport_id_next == airport.id))
# find landings without start
only_landings_query = session.query(sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
null().label('takeoff'), null().label('takeoff_track'), null().label('takeoff_airport_id'),
sq.c.timestamp.label('landing'), sq.c.track_next.label('landing_track'), sq.c.airport_id_next.label('landing_airport_id'),
null().label('duration')) \
.filter(sq.c.is_takeoff == false()) \
.filter(or_(sq.c.device_id != sq.c.device_id_prev,
sq.c.is_takeoff_prev == false())) \
.filter(sq.c.airport_id_next == airport.id)
# find starts without landing
only_starts_query = session.query(sq.c.timestamp.label('reftime'),
sq.c.device_id.label('device_id'),
sq.c.timestamp.label('takeoff'), sq.c.track.label('takeoff_track'), sq.c.airport_id.label('takeoff_airport_id'),
null().label('landing'), null().label('landing_track'), null().label('landing_airport_id'),
null().label('duration')) \
.filter(sq.c.is_takeoff == true()) \
.filter(or_(sq.c.device_id != sq.c.device_id_next,
sq.c.is_takeoff_next == true())) \
.filter(sq.c.airport_id == airport.id)
# unite all
union_query = complete_flight_query.union(split_start_query,
split_landing_query,
only_landings_query,
only_starts_query) \
.subquery()
# get aircraft and airport informations and sort all entries by the reference time
# get device info with highes priority
sq2 = session.query(DeviceInfo.address, func.max(DeviceInfo.address_origin).label('address_origin')) \
.group_by(DeviceInfo.address) \
.subquery()
@ -178,24 +61,21 @@ def show(airport_name, utc_delta_hours=0, date=None):
.filter(and_(DeviceInfo.address == sq2.c.address, DeviceInfo.address_origin == sq2.c.address_origin)) \
.subquery()
# get all logbook entries and add device and airport infos
takeoff_airport = aliased(Airport, name='takeoff_airport')
landing_airport = aliased(Airport, name='landing_airport')
logbook_query = session.query(union_query.c.reftime,
union_query.c.takeoff,
union_query.c.takeoff_track,
takeoff_airport,
union_query.c.landing,
union_query.c.landing_track,
landing_airport,
union_query.c.duration,
logbook_query = session.query(Logbook,
Device,
sq3.c.registration,
sq3.c.aircraft) \
.outerjoin(takeoff_airport, union_query.c.takeoff_airport_id == takeoff_airport.id) \
.outerjoin(landing_airport, union_query.c.landing_airport_id == landing_airport.id) \
.outerjoin(Device, union_query.c.device_id == Device.id) \
.filter(or_(Logbook.takeoff_airport_id == airport.id,
Logbook.landing_airport_id == airport.id)) \
.filter(*or_args) \
.outerjoin(takeoff_airport, Logbook.takeoff_airport_id == takeoff_airport.id) \
.outerjoin(landing_airport, Logbook.landing_airport_id == landing_airport.id) \
.outerjoin(Device, Logbook.device_id == Device.id) \
.outerjoin(sq3, sq3.c.address == Device.address) \
.order_by(union_query.c.reftime)
.order_by(Logbook.reftime)
# ... and finally print out the logbook
print('--- Logbook ({}) ---'.format(airport_name))
@ -223,14 +103,18 @@ def show(airport_name, utc_delta_hours=0, date=None):
else:
return ('')
for [reftime, takeoff, takeoff_track, takeoff_airport, landing, landing_track, landing_airport, duration, device, registration, aircraft] in logbook_query.all():
print('%10s %8s (%2s) %8s (%2s) %8s %8s %17s %20s' % (
reftime.date(),
none_datetime_replacer(takeoff),
none_track_replacer(takeoff_track),
none_datetime_replacer(landing),
none_track_replacer(landing_track),
none_timedelta_replacer(duration),
def none_altitude_replacer(altitude_object, airport_object):
return "?" if altitude_object is None else "{:5d}m ({:+5d}m)".format(altitude_object, altitude_object - airport_object.altitude)
for [logbook, device, registration, aircraft] in logbook_query.all():
print('%10s %8s (%2s) %8s (%2s) %8s %15s %8s %17s %20s' % (
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.max_altitude, logbook.takeoff_airport),
none_registration_replacer(device, registration),
none_aircraft_replacer(device, aircraft),
airport_marker(takeoff_airport, landing_airport)))
airport_marker(logbook.takeoff_airport, logbook.landing_airport)))

Wyświetl plik

@ -2,14 +2,23 @@ from ogn.model import Airport
from ogn.commands.dbutils import session
from manager import Manager
from sqlalchemy import and_, between
manager = Manager()
@manager.arg('country_code', help='filter by country code, eg. "de" for germany')
@manager.command
def list_all():
def list_all(country_code=None):
"""Show a list of all airports."""
or_args = []
if country_code is None:
or_args = [between(Airport.style, 2, 5)]
else:
or_args = [and_(between(Airport.style, 2, 5),
Airport.country_code == country_code)]
query = session.query(Airport) \
.order_by(Airport.name)
.order_by(Airport.name) \
.filter(*or_args)
print('--- Airports ---')
for airport in query.all():

Wyświetl plik

@ -10,5 +10,6 @@ from .receiver_beacon import ReceiverBeacon
from .receiver import Receiver
from .takeoff_landing import TakeoffLanding
from .airport import Airport
from .logbook import Logbook
from .geo import Location

Wyświetl plik

@ -0,0 +1,28 @@
from sqlalchemy import Integer, DateTime, Interval, Column, ForeignKey
from sqlalchemy.orm import relationship
from .base import Base
class Logbook(Base):
__tablename__ = 'logbook'
id = Column(Integer, primary_key=True)
reftime = Column(DateTime, index=True)
takeoff_timestamp = Column(DateTime)
takeoff_track = Column(Integer)
landing_timestamp = Column(DateTime)
landing_track = Column(Integer)
duration = Column(Interval)
max_altitude = Column(Integer)
# Relations
takeoff_airport_id = Column(Integer, ForeignKey('airport.id', ondelete='CASCADE'), index=True)
takeoff_airport = relationship('Airport', foreign_keys=[takeoff_airport_id])
landing_airport_id = Column(Integer, ForeignKey('airport.id', ondelete='CASCADE'), index=True)
landing_airport = relationship('Airport', foreign_keys=[landing_airport_id])
device_id = Column(Integer, ForeignKey('device.id', ondelete='CASCADE'), index=True)
device = relationship('Device', foreign_keys=[device_id])

Wyświetl plik

@ -1,2 +1,2 @@
[flake8]
ignore = E501
ignore = E501, E126

Wyświetl plik

Wyświetl plik

@ -0,0 +1,141 @@
import unittest
import os
from ogn.collect.logbook import compute_logbook_entries
class TestDB(unittest.TestCase):
session = None
engine = None
app = None
TAKEOFF_KOENIGSDF_DD0815 = "INSERT INTO takeoff_landing(device_id, airport_id, timestamp, is_takeoff) SELECT d.id, a.id, '2016-06-01 10:00:00', TRUE FROM airport a, device d WHERE a.name='Koenigsdorf' and d.address = 'DD0815'"
LANDING_KOENIGSDF_DD0815 = "INSERT INTO takeoff_landing(device_id, airport_id, timestamp, is_takeoff) SELECT d.id, a.id, '2016-06-01 10:05:00', FALSE FROM airport a, device d WHERE a.name='Koenigsdorf' and d.address = 'DD0815'"
LANDING_KOENIGSDF_DD0815_LATER = "INSERT INTO takeoff_landing(device_id, airport_id, timestamp, is_takeoff) SELECT d.id, a.id, '2016-06-02 10:05:00', FALSE FROM airport a, device d WHERE a.name='Koenigsdorf' and d.address = 'DD0815'"
TAKEOFF_OHLSTADT_DD4711 = "INSERT INTO takeoff_landing(device_id, airport_id, timestamp, is_takeoff) SELECT d.id, a.id, '2016-06-01 10:00:00', TRUE FROM airport a, device d WHERE a.name='Ohlstadt' and d.address = 'DD4711'"
def setUp(self):
os.environ['OGN_CONFIG_MODULE'] = 'config.test'
from ogn.commands.dbutils import engine, session
self.session = session
self.engine = engine
from ogn.commands.database import init
init()
session.execute("INSERT INTO device(address) VALUES ('DD0815'), ('DD4711')")
session.execute("INSERT INTO airport(name) VALUES ('Koenigsdorf'), ('Ohlstadt')")
def tearDown(self):
session = self.session
session.execute("DELETE FROM takeoff_landing")
session.execute("DELETE FROM logbook")
session.execute("DELETE FROM device")
session.execute("DELETE FROM airport")
session.commit()
pass
def test_single_takeoff(self):
session = self.session
session.execute(self.TAKEOFF_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/1')
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/0')
def test_single_landing(self):
session = self.session
session.execute(self.LANDING_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/1')
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/0')
def test_different_takeoffs(self):
session = self.session
session.execute(self.TAKEOFF_KOENIGSDF_DD0815)
session.execute(self.TAKEOFF_OHLSTADT_DD4711)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/2')
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/0')
def test_takeoff_and_landing(self):
session = self.session
session.execute(self.TAKEOFF_KOENIGSDF_DD0815)
session.execute(self.LANDING_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/1')
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/0')
def test_takeoff_and_landing_on_different_days(self):
session = self.session
session.execute(self.TAKEOFF_KOENIGSDF_DD0815)
session.execute(self.LANDING_KOENIGSDF_DD0815_LATER)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/2')
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/0')
def test_update(self):
session = self.session
session.execute(self.TAKEOFF_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/1')
session.execute(self.LANDING_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '1/0')
session.execute(self.TAKEOFF_OHLSTADT_DD4711)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/1')
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/0')
def test_update_wrong_order(self):
session = self.session
session.execute(self.LANDING_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '0/1')
session.execute(self.TAKEOFF_KOENIGSDF_DD0815)
session.commit()
entries_changed = compute_logbook_entries(session)
self.assertEqual(entries_changed, '1/0')
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -0,0 +1,104 @@
import unittest
import os
from ogn.model import TakeoffLanding
from ogn.collect.takeoff_landing import get_aircraft_beacon_start_id, compute_takeoff_and_landing
class TestDB(unittest.TestCase):
session = None
engine = None
app = None
def setUp(self):
os.environ['OGN_CONFIG_MODULE'] = 'config.test'
from ogn.commands.dbutils import engine, session
self.session = session
self.engine = engine
from ogn.commands.database import init
init()
session.execute("INSERT INTO airport(name, location, altitude, style) VALUES('Benediktbeuren','0101000020E6100000D5E76A2BF6C72640D4063A6DA0DB4740',609,4)")
session.execute("INSERT INTO airport(name, location, altitude, style) VALUES('Koenigsdorf','0101000020E610000061E8FED7A6EE26407F20661C10EA4740',600,5)")
session.execute("INSERT INTO airport(name, location, altitude, style) VALUES('Ohlstadt','0101000020E6100000057E678EBF772640A142883E32D44740',655,5)")
session.execute("INSERT INTO device(address) VALUES('DDEFF7')")
def tearDown(self):
session = self.session
session.execute("DELETE FROM takeoff_landing")
session.execute("DELETE FROM aircraft_beacon")
session.commit()
pass
def count_takeoff_and_landings(self):
session = self.session
query = session.query(TakeoffLanding)
i = 0
for takeoff_landing in query.all():
i = i + 1
print("{} {} {} {} {} {}".format(takeoff_landing.id, takeoff_landing.device_id, takeoff_landing.airport_id, takeoff_landing.timestamp, takeoff_landing.is_takeoff, takeoff_landing.track))
return i
def test_broken_rope(self):
session = self.session
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',604,'2016-07-02 10:47:12',0,0,0,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',605,'2016-07-02 10:47:32',0,0,-0.096520193,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:47:52',0,0,-0.096520193,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000009668B61829F12640330E0887F1E94740',606,'2016-07-02 10:48:12',0,0,-0.096520193,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000001B2FDD2406F12640E53C762AF3E94740',606,'2016-07-02 10:48:24',284,51.85598112,0.299720599,0.1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000F594AFDEBBF02640623583E5F5E94740',610,'2016-07-02 10:48:26',282,88.89596764,4.729489459,-0.2)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000001C0DE02D90F026401564F188F7E94740',619,'2016-07-02 10:48:27',281,94.45196562,10.66294133,-0.3)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000ABF1D24D62F02640E12D90A0F8E94740',632,'2016-07-02 10:48:28',278,88.89596764,15.59055118,-0.7)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000069FD40CC38F02640C7925F2CF9E94740',650,'2016-07-02 10:48:29',273,83.33996966,18.90779782,-0.7)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000002709AF4A0FF02640C7925F2CF9E94740',670,'2016-07-02 10:48:30',272,79.63597101,20.72136144,-0.3)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000007AA85AF8E7EF2640C7925F2CF9E94740',691,'2016-07-02 10:48:31',269,79.63597101,21.02108204,-0.4)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000068DB43D5C2EF2640E12D90A0F8E94740',712,'2016-07-02 10:48:32',267,74.07997303,21.62560325,-0.5)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000EDA16AE19FEF2640FBC8C014F8E94740',728,'2016-07-02 10:48:33',266,68.52397506,12.36982474,-0.1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000000AFCCE1C7FEF26401564F188F7E94740',733,'2016-07-02 10:48:34',266,68.52397506,2.21488443,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000275633585EEF26402FFF21FDF6E94740',731,'2016-07-02 10:48:35',267,68.52397506,-3.916687833,0.2)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000015891C3539EF26402FFF21FDF6E94740',726,'2016-07-02 10:48:36',270,74.07997303,-6.329692659,1.1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000E63FA4DFBEEE264078C1CDCFFAE94740',712,'2016-07-02 10:48:39',280,88.89596764,-2.611125222,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000004FF9EABD0BEE2640448B6CE7FBE94740',706,'2016-07-02 10:48:43',256,90.74796697,-0.198120396,-2.5)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000046B921B3A0ED264003E78C28EDE94740',706,'2016-07-02 10:48:46',218,92.59996629,-0.198120396,-1.6)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000005C58F3177ED2640900C4C81DFE94740',703,'2016-07-02 10:48:48',202,96.30396495,-1.402082804,-1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000211FF46C56ED26402650D7EDC6E94740',702,'2016-07-02 10:48:51',188,100.0079636,0.502921006,-1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000806DEA295FED2640347D898BB6E94740',704,'2016-07-02 10:48:53',166,100.0079636,0.802641605,-2)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000337D898BB6ED26401383C0CAA1E94740',703,'2016-07-02 10:48:56',133,101.8599629,-1.803403607,-1.7)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000000C05593CE2ED2640FDF675E09CE94740',700,'2016-07-02 10:48:57',123,103.7119622,-2.611125222,-1.4)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000F0CCF1F778EE26409FA87F2394E94740',693,'2016-07-02 10:49:00',105,111.1199596,-2.809245618,-0.6)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000C9073D9B55EF2640BD5296218EE94740',687,'2016-07-02 10:49:04',97,112.9719589,-1.605283211,-0.1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000006F8104C5EF26400C24287E8CE94740',682,'2016-07-02 10:49:06',97,114.8239582,-2.407924816,-0.2)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000A0648535A8F02640F597DD9387E94740',676,'2016-07-02 10:49:10',97,118.5279569,-1.402082804,0.1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000D70FC48C03F22640621386EE7FE94740',672,'2016-07-02 10:49:16',97,116.6759575,-1.000762002,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000A72C431CEBF22640CB7F48BF7DE94740',666,'2016-07-02 10:49:20',84,114.8239582,-1.605283211,-1.5)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000BFCAA145B6F32640BD5296218EE94740',662,'2016-07-02 10:49:24',49,111.1199596,-1.203962408,-1.5)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000074DA40A70DF4264077E09C11A5E94740',659,'2016-07-02 10:49:27',23,107.4159609,-1.402082804,-1.4)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000009AE3EFF11CF42640347D898BB6E94740',656,'2016-07-02 10:49:29',4,101.8599629,-0.797561595,-1.8)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000074DA40A70DF426402650D7EDC6E94740',654,'2016-07-02 10:49:31',347,101.8599629,-1.706883414,-1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000156A4DF38EF3264086EE7F6DEAE94740',649,'2016-07-02 10:49:36',312,98.15596427,-1.503683007,-1.4)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000FAEDEBC039F32640E53C762AF3E94740',644,'2016-07-02 10:49:38',295,96.30396495,-3.012446025,-1.2)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000B04A0F30E0F22640FBC8C014F8E94740',635,'2016-07-02 10:49:40',284,94.45196562,-5.125730251,-0.7)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000F38B25BF58F22640448B6CE7FBE94740',623,'2016-07-02 10:49:43',279,92.59996629,-2.809245618,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000A5E8482EFFF12640DC1EAA16FEE94740',617,'2016-07-02 10:49:45',279,88.89596764,-3.312166624,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000009F17012859F12640F0AAF40003EA4740',607,'2016-07-02 10:49:49',279,81.48797034,-1.300482601,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000004B5658830AF12640873E323005EA4740',607,'2016-07-02 10:49:51',278,74.07997303,-0.294640589,-0.1)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000A0648535A8F0264006373FEB07EA4740',605,'2016-07-02 10:49:54',280,61.11597775,-0.096520193,0.5)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E6100000C74B378941F02640E88C28ED0DEA4740',604,'2016-07-02 10:49:58',292,48.15198247,0.101600203,0.4)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E61000001B5A643BDFEF264045DB1EAA16EA4740',604,'2016-07-02 10:50:04',302,25.92799056,0.203200406,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000042D2948AB3EF264074029A081BEA4740',604,'2016-07-02 10:50:10',300,5.555997978,0.101600203,0)")
session.execute("INSERT INTO aircraft_beacon(address, location, altitude, timestamp, track, ground_speed, climb_rate, turn_rate) VALUES('DDEFF7','0101000020E610000013AB192CAFEF264074029A081BEA4740',603,'2016-07-02 10:50:16',0,0,-0.096520193,0)")
session.execute("UPDATE aircraft_beacon SET device_id = d.id FROM device d WHERE d.address='DDEFF7'")
session.commit()
print(get_aircraft_beacon_start_id(session))
compute_takeoff_and_landing(session)
self.assertEqual(self.count_takeoff_and_landings(), 2)
if __name__ == '__main__':
unittest.main()