diff --git a/ogn/collect/takeoff_landing.py b/ogn/collect/takeoff_landing.py index 8cc65a6..e4572cd 100644 --- a/ogn/collect/takeoff_landing.py +++ b/ogn/collect/takeoff_landing.py @@ -3,7 +3,7 @@ from datetime import timedelta from celery.utils.log import get_task_logger from sqlalchemy import and_, or_, insert, between, exists -from sqlalchemy.sql import func +from sqlalchemy.sql import func, null from sqlalchemy.sql.expression import case from ogn.collect.celery import app @@ -12,29 +12,6 @@ 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.") @@ -42,6 +19,12 @@ def compute_takeoff_and_landing(session=None): if session is None: session = app.session + # check if we have any airport + airports_query = session.query(Airport) + if not airports_query.all(): + logger.warn("Cannot calculate takeoff and landings without any airport! Please import airports first.") + return + # 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 @@ -52,10 +35,6 @@ def compute_takeoff_and_landing(session=None): 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, @@ -82,53 +61,55 @@ def compute_takeoff_and_landing(session=None): 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)) \ + .filter(AircraftBeacon.status == null()) \ + .subquery() + + sq2 = session.query(sq) \ + .filter(sq.c.device_id_prev == sq.c.device_id == sq.c.device_id_next) \ .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))) \ + sq3 = session.query( + sq2.c.id, + sq2.c.timestamp, + case([(sq2.c.ground_speed > takeoff_speed, sq2.c.location_wkt_prev), # on takeoff we take the location from the previous fix because it is nearer to the airport + (sq2.c.ground_speed < landing_speed, sq2.c.location)]).label('location'), + case([(sq2.c.ground_speed > takeoff_speed, sq2.c.track), + (sq2.c.ground_speed < landing_speed, sq2.c.track_prev)]).label('track'), # on landing we take the track from the previous fix because gliders tend to leave the runway quickly + sq2.c.ground_speed, + sq2.c.altitude, + case([(sq2.c.ground_speed > takeoff_speed, True), + (sq2.c.ground_speed < landing_speed, False)]).label('is_takeoff'), + sq2.c.device_id) \ + .filter(sq2.c.timestamp_next - sq2.c.timestamp_prev < timedelta(seconds=duration)) \ + .filter(and_(func.ST_DFullyWithin(sq2.c.location, sq2.c.location_wkt_prev, radius), + func.ST_DFullyWithin(sq2.c.location, sq2.c.location_wkt_next, radius))) \ + .filter(or_(and_(sq2.c.ground_speed_prev < takeoff_speed, # takeoff + sq2.c.ground_speed > takeoff_speed, + sq2.c.ground_speed_next > takeoff_speed), + and_(sq2.c.ground_speed_prev > landing_speed, # landing + sq2.c.ground_speed < landing_speed, + sq2.c.ground_speed_next < landing_speed))) \ .subquery() # consider them if they are near a airport - sq3 = session.query( - sq2.c.timestamp, - sq2.c.track, - sq2.c.is_takeoff, - sq2.c.device_id, + sq4 = session.query( + sq3.c.timestamp, + sq3.c.track, + sq3.c.is_takeoff, + sq3.c.device_id, Airport.id.label('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(and_(func.ST_DFullyWithin(sq3.c.location, Airport.location_wkt, airport_radius), + between(sq3.c.altitude, Airport.altitude - airport_delta, Airport.altitude + airport_delta))) \ .filter(between(Airport.style, 2, 5)) \ - .order_by(sq2.c.id) \ .subquery() # consider them only if they are not already existing in db - takeoff_landing_query = session.query(sq3) \ + takeoff_landing_query = session.query(sq4) \ .filter(~exists().where( - and_(TakeoffLanding.timestamp == sq3.c.timestamp, - TakeoffLanding.device_id == sq3.c.device_id, - TakeoffLanding.airport_id == sq3.c.airport_id))) + and_(TakeoffLanding.timestamp == sq4.c.timestamp, + TakeoffLanding.device_id == sq4.c.device_id, + TakeoffLanding.airport_id == sq4.c.airport_id))) # ... and save them ins = insert(TakeoffLanding).from_select((TakeoffLanding.timestamp,