Esempio n. 1
0
def get_aircraft_info(tail_number):
    '''
    Fetch aircraft info from settings.API_HANDLER or from LOCAL_API_HANDLER
    if there is an API_ERROR raised.
    
    :param tail_number: Aircraft tail registration
    :type tail_number: string
    :returns: Aircraft information key:value pairs
    :rtype: dict
    '''
    # Fetch aircraft info through the API.
    api_handler = get_api_handler(settings.API_HANDLER)
    
    try:
        aircraft_info = api_handler.get_aircraft(tail_number)
    except APIError:
        if settings.API_HANDLER == settings.LOCAL_API_HANDLER:
            raise
        # Fallback to the local API handler.
        logger.info(
            "Aircraft '%s' could not be found with '%s' API handler. "
            "Falling back to '%s'.", tail_number, settings.API_HANDLER,
            settings.LOCAL_API_HANDLER)
        api_handler = get_api_handler(settings.LOCAL_API_HANDLER)
        aircraft_info = api_handler.get_aircraft(tail_number)
    logger.info("Using aircraft_info provided by '%s' '%s'.",
                api_handler.__class__.__name__, aircraft_info)        
    return aircraft_info
    def derive(self,
               dest=P('Destination'),
               afr_dest=A('AFR Destination Airport')):
        '''
        Requires an ASCII destination parameter recording either the airport's
        ICAO or IATA code.
        '''
        if not dest or dest.array.dtype.type is not np.string_:
            if afr_dest:
                self.set_flight_attr(afr_dest.value)
            return

        value = most_common_value(dest.array, threshold=0.45)
        if value is None or not value.isalpha():
            return

        api = get_api_handler(settings.API_HANDLER)
        try:
            airport = api.get_airport(value)
        except NotFoundError:
            self.warning('No destination airport found for %s.', value)
            return

        self.debug('Detected destination airport: %s', airport)
        self.set_flight_attr(airport)
    def derive(self, dest=P('Destination'),
               afr_dest=A('AFR Destination Airport')):
        '''
        Requires an ASCII destination parameter recording either the airport's
        ICAO or IATA code.
        '''
        if not dest or dest.array.dtype.type is not np.string_:
            if afr_dest:
                self.set_flight_attr(afr_dest.value)
            return

        # XXX: Slow, but Destination should be sampled infrequently.
        value, count = next(reversed(sorted(Counter(dest.array).items(),
                                            key=itemgetter(1))))
        # Q: Should we validate that the value is 3-4 characters long?
        if value is np.ma.masked or count < len(dest.array) * 0.45:
            return

        api = get_api_handler(settings.API_HANDLER)
        try:
            airport = api.get_airport(value)
        except NotFoundError:
            self.warning('No destination airport found for %s.', value)
            return

        self.debug('Detected destination airport: %s', airport)
        self.set_flight_attr(airport)
    def derive(self, dest=P('Destination'),
               afr_dest=A('AFR Destination Airport')):
        '''
        Requires an ASCII destination parameter recording either the airport's
        ICAO or IATA code.
        '''
        if not dest or dest.array.dtype.type is not np.string_:
            if afr_dest:
                self.set_flight_attr(afr_dest.value)
            return

        # XXX: Slow, but Destination should be sampled infrequently.
        value, count = next(reversed(sorted(Counter(dest.array).items(),
                                            key=itemgetter(1))))
        # Q: Should we validate that the value is 3-4 characters long?
        if value is np.ma.masked or count < len(dest.array) * 0.45:
            return

        api = get_api_handler(settings.API_HANDLER)
        try:
            airport = api.get_airport(value)
        except NotFoundError:
            self.warning('No destination airport found for %s.', value)
            return

        self.debug('Detected destination airport: %s', airport)
        self.set_flight_attr(airport)
 def lookup_airport(self, lat_source, lon_source):
     lat = lat_source.get_first()
     lon = lon_source.get_first()
     if lat and lon:
         api = get_api_handler(settings.API_HANDLER)
         try:
             airport = api.get_nearest_airport(lat.value, lon.value)
         except NotFoundError:
             msg = 'No takeoff airport found near coordinates (%f, %f).'
             self.warning(msg, lat.value, lon.value)
             # No airport was found, so fall through and try AFR.
         else:
             self.info('Detected takeoff airport: %s from coordinates (%f, %f)', airport.get('code'), lat.value, lon.value)
             self.set_flight_attr(airport)
             return True  # We found an airport, so finish here.
     else:
         self.warning('No coordinates for looking up takeoff airport.')
 def lookup_airport(self, lat_source, lon_source):
     lat = lat_source.get_first()
     lon = lon_source.get_first()
     if lat and lon:
         api = get_api_handler(settings.API_HANDLER)
         try:
             airport = api.get_nearest_airport(lat.value, lon.value)
         except NotFoundError:
             msg = 'No takeoff airport found near coordinates (%f, %f).'
             self.warning(msg, lat.value, lon.value)
             # No airport was found, so fall through and try AFR.
         else:
             self.info(
                 'Detected takeoff airport: %s from coordinates (%f, %f)',
                 airport.get('code'), lat.value, lon.value)
             self.set_flight_attr(airport)
             return True  # We found an airport, so finish here.
     else:
         self.warning('No coordinates for looking up takeoff airport.')
    def derive(self,
               land_lat=KPV('Latitude At Touchdown'),
               land_lon=KPV('Longitude At Touchdown'),
               land_afr_apt=A('AFR Landing Airport')):
        '''
        '''
        # 1. If we have latitude and longitude, look for the nearest airport:
        if land_lat and land_lon:
            lat = land_lat.get_last()
            lon = land_lon.get_last()
            if lat and lon:
                api = get_api_handler(settings.API_HANDLER)
                try:
                    airport = api.get_nearest_airport(lat.value, lon.value)
                except NotFoundError:
                    msg = 'No landing airport found near coordinates (%f, %f).'
                    self.warning(msg, lat.value, lon.value)
                    # No airport was found, so fall through and try AFR.
                else:
                    self.info(
                        'Detected landing airport: %s from coordinates (%f, %f)',
                        airport.get('code'), lat.value, lon.value)
                    self.set_flight_attr(airport)
                    return  # We found an airport, so finish here.
            else:
                self.warning('No coordinates for looking up landing airport.')
                # No suitable coordinates, so fall through and try AFR.

        # 2. If we have an airport provided in achieved flight record, use it:
        if land_afr_apt:
            airport = land_afr_apt.value
            self.debug('Using landing airport from AFR: %s', airport)
            self.set_flight_attr(airport)
            return  # We found an airport in the AFR, so finish here.

        # 3. After all that, we still couldn't determine an airport...
        self.error('Unable to determine airport at landing!')
        self.set_flight_attr(None)
    def derive(self,
            toff_lat=KPV('Latitude At Liftoff'),
            toff_lon=KPV('Longitude At Liftoff'),
            toff_afr_apt=A('AFR Takeoff Airport')):
        '''
        '''
        # 1. If we have latitude and longitude, look for the nearest airport:
        if toff_lat and toff_lon:
            lat = toff_lat.get_first()
            lon = toff_lon.get_first()
            if lat and lon:
                api = get_api_handler(settings.API_HANDLER)
                try:
                    airport = api.get_nearest_airport(lat.value, lon.value)
                except NotFoundError:
                    msg = 'No takeoff airport found near coordinates (%f, %f).'
                    self.warning(msg, lat.value, lon.value)
                    # No airport was found, so fall through and try AFR.
                else:
                    self.debug('Detected takeoff airport: %s', airport)
                    self.set_flight_attr(airport)
                    return  # We found an airport, so finish here.
            else:
                self.warning('No coordinates for looking up takeoff airport.')
                # No suitable coordinates, so fall through and try AFR.

        # 2. If we have an airport provided in achieved flight record, use it:
        if toff_afr_apt:
            airport = toff_afr_apt.value
            self.debug('Using takeoff airport from AFR: %s', airport)
            self.set_flight_attr(airport)
            return  # We found an airport in the AFR, so finish here.

        # 3. After all that, we still couldn't determine an airport...
        self.error('Unable to determine airport at takeoff!')
        self.set_flight_attr(None)
    def derive(self, dest=P('Destination'),
               afr_dest=A('AFR Destination Airport')):
        '''
        Requires an ASCII destination parameter recording either the airport's
        ICAO or IATA code.
        '''
        if not dest or dest.array.dtype.type is not np.string_:
            if afr_dest:
                self.set_flight_attr(afr_dest.value)
            return

        value = most_common_value(dest.array, threshold=0.45)
        if value is None or not value.isalpha():
            return

        api = get_api_handler(settings.API_HANDLER)
        try:
            airport = api.get_airport(value)
        except NotFoundError:
            self.warning('No destination airport found for %s.', value)
            return

        self.debug('Detected destination airport: %s', airport)
        self.set_flight_attr(airport)
    def derive(self,
               toff_fdr_apt=A('FDR Takeoff Airport'),
               toff_afr_rwy=A('AFR Takeoff Runway'),
               toff_hdg=KPV('Heading During Takeoff'),
               toff_lat=KPV('Latitude At Liftoff'),
               toff_lon=KPV('Longitude At Liftoff'),
               precision=A('Precise Positioning')):
        '''
        '''
        fallback = False
        precise = bool(getattr(precision, 'value', False))

        try:
            airport = int(toff_fdr_apt.value['id'])
        except (AttributeError, KeyError, TypeError, ValueError):
            self.warning('Invalid airport... Fallback to AFR Takeoff Runway.')
            fallback = True

        try:
            heading = toff_hdg.get_first().value
            if heading is None:
                raise ValueError
        except (AttributeError, ValueError):
            self.warning('Invalid heading... Fallback to AFR Takeoff Runway.')
            fallback = True

        # 1. If we have airport and heading, look for the nearest runway:
        if not fallback:
            kwargs = {}

            # Even if we do not have precise latitude and longitude
            # information, we still use this for the takeoff runway detection
            # as it is often accurate at the start of a flight, and in the
            # absence of an ILS tuned frequency we have no better option. (We
            # did consider using the last direction of turn onto the runway,
            # but this would require an airport database with terminal and
            # taxiway details that was not felt justified).
            if toff_lat and toff_lon:
                lat = toff_lat.get_first()
                lon = toff_lon.get_first()
                if lat and lon:
                    kwargs.update(
                        latitude=lat.value,
                        longitude=lon.value,
                    )
                else:
                    self.warning('No coordinates for takeoff runway lookup.')
            if not precise:
                kwargs.update(hint='takeoff')

            api = get_api_handler(settings.API_HANDLER)
            try:
                runway = api.get_nearest_runway(airport, heading, **kwargs)
            except NotFoundError:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport, heading, kwargs)
                # No runway was found, so fall through and try AFR.
            else:
                self.info(
                    'Detected takeoff runway: %s for airport #%d @ %03.1f deg with %s',
                    runway['identifier'], airport, heading, kwargs)
                self.set_flight_attr(runway)
                return  # We found a runway, so finish here.

        # 2. If we have a runway provided in achieved flight record, use it:
        if toff_afr_rwy:
            runway = toff_afr_rwy.value
            self.debug('Using takeoff runway from AFR: %s', runway)
            self.set_flight_attr(runway)
            return  # We found a runway in the AFR, so finish here.

        # 3. After all that, we still couldn't determine a runway...
        self.error('Unable to determine runway at takeoff!')
        self.set_flight_attr(None)
    def derive(self,
               land_fdr_apt=A('FDR Landing Airport'),
               land_afr_rwy=A('AFR Landing Runway'),
               land_hdg=KPV('Heading During Landing'),
               land_lat=KPV('Latitude At Touchdown'),
               land_lon=KPV('Longitude At Touchdown'),
               precision=A('Precise Positioning'),
               approaches=S('Approach And Landing'),
               ils_freq_on_app=KPV('ILS Frequency During Approach')):
        '''
        '''
        fallback = False
        precise = bool(getattr(precision, 'value', False))

        try:
            airport = int(land_fdr_apt.value['id'])
        except (AttributeError, KeyError, TypeError, ValueError):
            self.warning('Invalid airport... Fallback to AFR Landing Runway.')
            fallback = True

        try:
            heading = land_hdg.get_last().value
            if heading is None:
                raise ValueError
        except (AttributeError, ValueError):
            self.warning('Invalid heading... Fallback to AFR Landing Runway.')
            fallback = True

        try:
            landing = approaches.get_last()
            if landing is None:
                raise ValueError
        except (AttributeError, ValueError):
            self.warning('No approaches... Fallback to AFR Landing Runway.')
            # Don't set fallback - can still attempt to use heading only...

        # 1. If we have airport and heading, look for the nearest runway:
        if not fallback:
            kwargs = {}

            # The last approach is assumed to be the landing.
            # XXX: Last approach may not be landing for partial data?!
            if ils_freq_on_app:
                if landing.start_edge:
                    ils_app_slice = slice(landing.start_edge,
                                          landing.slice.stop)
                else:
                    ils_app_slice = landing.slice
                ils_freq = ils_freq_on_app.get_last(within_slice=ils_app_slice)
                if ils_freq:
                    kwargs.update(ils_freq=ils_freq.value)
            '''
            Original comment:
            # We only provide coordinates when looking up a landing runway if
            # the recording of latitude and longitude on the aircraft is
            # precise. Inertial recordings are too inaccurate to pinpoint the
            # correct runway and we use ILS frequencies if possible to get a
            # more exact match.

            Revised comment:
            In the absence of an ILS frequency, the recorded latitude and longitude
            is better than nothing and reduces the chance of identifying the wrong runway.
            '''
            if (precise or
                    not ils_freq_on_app) and landing and land_lat and land_lon:
                lat = land_lat.get_last(within_slice=landing.slice)
                lon = land_lon.get_last(within_slice=landing.slice)
                if lat and lon:
                    kwargs.update(
                        latitude=lat.value,
                        longitude=lon.value,
                    )
                else:
                    self.warning('No coordinates for landing runway lookup.')
            else:
                kwargs.update(hint='landing')

            api = get_api_handler(settings.API_HANDLER)
            try:
                runway = api.get_nearest_runway(airport, heading, **kwargs)
            except NotFoundError:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport, heading, kwargs)
                # No runway was found, so fall through and try AFR.
                if 'ils_freq' in kwargs:
                    # This is a trap for airports where the ILS data is not
                    # available, but the aircraft approached with the ILS
                    # tuned. A good prompt for an omission in the database.
                    self.warning('Fix database? No runway but ILS was tuned.')
            else:
                self.info(
                    'Detected landing runway: %s for airport #%d @ %03.1f deg with %s',
                    runway['identifier'], airport, heading, kwargs)
                self.set_flight_attr(runway)
                return  # We found a runway, so finish here.

        # 2. If we have a runway provided in achieved flight record, use it:
        if land_afr_rwy:
            runway = land_afr_rwy.value
            self.debug('Using landing runway from AFR: %s', runway)
            self.set_flight_attr(runway)
            return  # We found a runway in the AFR, so finish here.

        # 3. After all that, we still couldn't determine a runway...
        self.error('Unable to determine runway at landing!')
        self.set_flight_attr(None)
    def derive(self,
            toff_fdr_apt=A('FDR Takeoff Airport'),
            toff_afr_rwy=A('AFR Takeoff Runway'),
            toff_hdg=KPV('Heading During Takeoff'),
            toff_lat=KPV('Latitude At Liftoff'),
            toff_lon=KPV('Longitude At Liftoff'),
            precision=A('Precise Positioning')):
        '''
        '''
        fallback = False
        precise = bool(getattr(precision, 'value', False))

        try:
            airport = int(toff_fdr_apt.value['id'])
        except (AttributeError, KeyError, TypeError, ValueError):
            self.warning('Invalid airport... Fallback to AFR Takeoff Runway.')
            fallback = True

        try:
            heading = toff_hdg.get_first().value
            if heading is None:
                raise ValueError
        except (AttributeError, ValueError):
            self.warning('Invalid heading... Fallback to AFR Takeoff Runway.')
            fallback = True

        # 1. If we have airport and heading, look for the nearest runway:
        if not fallback:
            kwargs = {}

            # Even if we do not have precise latitude and longitude
            # information, we still use this for the takeoff runway detection
            # as it is often accurate at the start of a flight, and in the
            # absence of an ILS tuned frequency we have no better option. (We
            # did consider using the last direction of turn onto the runway,
            # but this would require an airport database with terminal and
            # taxiway details that was not felt justified).
            if toff_lat and toff_lon:
                lat = toff_lat.get_first()
                lon = toff_lon.get_first()
                if lat and lon:
                    kwargs.update(
                        latitude=lat.value,
                        longitude=lon.value,
                    )
                else:
                    self.warning('No coordinates for takeoff runway lookup.')
            if not precise:
                kwargs.update(hint='takeoff')

            api = get_api_handler(settings.API_HANDLER)
            try:
                runway = api.get_nearest_runway(airport, heading, **kwargs)
            except NotFoundError:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport, heading, kwargs)
                # No runway was found, so fall through and try AFR.
            else:
                self.debug('Detected takeoff runway: %s', runway)
                self.set_flight_attr(runway)
                return  # We found a runway, so finish here.

        # 2. If we have a runway provided in achieved flight record, use it:
        if toff_afr_rwy:
            runway = toff_afr_rwy.value
            self.debug('Using takeoff runway from AFR: %s', runway)
            self.set_flight_attr(runway)
            return  # We found a runway in the AFR, so finish here.

        # 3. After all that, we still couldn't determine a runway...
        self.error('Unable to determine runway at takeoff!')
        self.set_flight_attr(None)
    def derive(self,
            land_fdr_apt=A('FDR Landing Airport'),
            land_afr_rwy=A('AFR Landing Runway'),
            land_hdg=KPV('Heading During Landing'),
            land_lat=KPV('Latitude At Touchdown'),
            land_lon=KPV('Longitude At Touchdown'),
            precision=A('Precise Positioning'),
            approaches=S('Approach And Landing'),
            ils_freq_on_app=KPV('ILS Frequency During Approach')):
        '''
        '''
        fallback = False
        precise = bool(getattr(precision, 'value', False))

        try:
            airport = int(land_fdr_apt.value['id'])
        except (AttributeError, KeyError, TypeError, ValueError):
            self.warning('Invalid airport... Fallback to AFR Landing Runway.')
            fallback = True

        try:
            heading = land_hdg.get_last().value
            if heading is None:
                raise ValueError
        except (AttributeError, ValueError):
            self.warning('Invalid heading... Fallback to AFR Landing Runway.')
            fallback = True

        try:
            landing = approaches.get_last()
            if landing is None:
                raise ValueError
        except (AttributeError, ValueError):
            self.warning('No approaches... Fallback to AFR Landing Runway.')
            # Don't set fallback - can still attempt to use heading only...

        # 1. If we have airport and heading, look for the nearest runway:
        if not fallback:
            kwargs = {}

            # The last approach is assumed to be the landing.
            # XXX: Last approach may not be landing for partial data?!
            if ils_freq_on_app:
                ils_freq = ils_freq_on_app.get_last(within_slice=landing.slice)
                if ils_freq:
                    kwargs.update(ils_freq=ils_freq.value)

            # We only provide coordinates when looking up a landing runway if
            # the recording of latitude and longitude on the aircraft is
            # precise. Inertial recordings are too inaccurate to pinpoint the
            # correct runway and we use ILS frequencies if possible to get a
            # more exact match.
            if precise and landing and land_lat and land_lon:
                lat = land_lat.get_last(within_slice=landing.slice)
                lon = land_lon.get_last(within_slice=landing.slice)
                if lat and lon:
                    kwargs.update(
                        latitude=lat.value,
                        longitude=lon.value,
                    )
                else:
                    self.warning('No coordinates for landing runway lookup.')
            else:
                kwargs.update(hint='landing')

            api = get_api_handler(settings.API_HANDLER)
            try:
                runway = api.get_nearest_runway(airport, heading, **kwargs)
            except NotFoundError:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport, heading, kwargs)
                # No runway was found, so fall through and try AFR.
                if 'ils_freq' in kwargs:
                    # This is a trap for airports where the ILS data is not
                    # available, but the aircraft approached with the ILS
                    # tuned. A good prompt for an omission in the database.
                    self.warning('Fix database? No runway but ILS was tuned.')
            else:
                self.debug('Detected landing runway: %s', runway)
                self.set_flight_attr(runway)
                return  # We found a runway, so finish here.

        # 2. If we have a runway provided in achieved flight record, use it:
        if land_afr_rwy:
            runway = land_afr_rwy.value
            self.debug('Using landing runway from AFR: %s', runway)
            self.set_flight_attr(runway)
            return  # We found a runway in the AFR, so finish here.

        # 3. After all that, we still couldn't determine a runway...
        self.error('Unable to determine runway at landing!')
        self.set_flight_attr(None)
Esempio n. 14
0
    def _lookup_airport_and_runway(self,
                                   _slice,
                                   precise,
                                   lowest_lat,
                                   lowest_lon,
                                   lowest_hdg,
                                   appr_ils_freq,
                                   land_afr_apt=None,
                                   land_afr_rwy=None,
                                   hint='approach'):
        '''
        '''
        api = get_api_handler(settings.API_HANDLER)
        kwargs = {}
        airport = None
        runway = None

        # A1. If we have latitude and longitude, look for the nearest airport:
        if lowest_lat != None and lowest_lon != None:
            kwargs.update(latitude=lowest_lat, longitude=lowest_lon)
            try:
                airport = api.get_nearest_airport(**kwargs)
            except NotFoundError:
                msg = 'No approach airport found near coordinates (%f, %f).'
                self.warning(msg, lowest_lat, lowest_lon)
                # No airport was found, so fall through and try AFR.
            else:
                self.debug('Detected approach airport: %s', airport)
        else:
            # No suitable coordinates, so fall through and try AFR.
            self.warning('No coordinates for looking up approach airport.')
            return None, None

        # A2. If and we have an airport in achieved flight record, use it:
        # NOTE: AFR data is only provided if this approach is a landing.
        if not airport and land_afr_apt:
            airport = land_afr_apt.value
            self.debug('Using approach airport from AFR: %s', airport)

        # A3. After all that, we still couldn't determine an airport...
        if not airport:
            self.error('Unable to determine airport on approach!')
            return None, None

        airport_id = int(airport['id'])

        if lowest_hdg is not None:
            #if heading is None:
            #self.warning('Invalid heading... Fallback to AFR.')
            #fallback = True

            # R1. If we have airport and heading, look for the nearest runway:
            if appr_ils_freq:
                kwargs['ils_freq'] = appr_ils_freq

                # We already have latitude and longitude in kwargs from looking up
                # the airport. If the measurments are not precise, remove them.
                if not precise:
                    kwargs['hint'] = hint
                    del kwargs['latitude']
                    del kwargs['longitude']

            try:
                runway = api.get_nearest_runway(airport_id, lowest_hdg,
                                                **kwargs)
            except NotFoundError:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport_id, lowest_hdg, kwargs)
                # No runway was found, so fall through and try AFR.
                if 'ils_freq' in kwargs:
                    # This is a trap for airports where the ILS data is not
                    # available, but the aircraft approached with the ILS
                    # tuned. A good prompt for an omission in the database.
                    self.warning('Fix database? No runway but ILS was tuned.')
            else:
                self.debug('Detected approach runway: %s', runway)
                runway = runway

        # R2. If we have a runway provided in achieved flight record, use it:
        if not runway and land_afr_rwy:
            runway = land_afr_rwy.value
            self.debug('Using approach runway from AFR: %s', runway)

        # R3. After all that, we still couldn't determine a runway...
        if not runway:
            self.error('Unable to determine runway on approach!')

        return airport, runway
Esempio n. 15
0
    def _lookup_airport_and_runway(self, _slice, precise, lowest_lat,
                                   lowest_lon, lowest_hdg, appr_ils_freq,
                                   land_afr_apt=None, land_afr_rwy=None,
                                   hint='approach'):
        '''
        '''
        api = get_api_handler(settings.API_HANDLER)
        kwargs = {}
        airport = None
        runway = None

        # A1. If we have latitude and longitude, look for the nearest airport:
        if lowest_lat!=None and lowest_lon!=None:
            kwargs.update(latitude=lowest_lat, longitude=lowest_lon)
            try:
                airport = api.get_nearest_airport(**kwargs)
            except NotFoundError:
                msg = 'No approach airport found near coordinates (%f, %f).'
                self.warning(msg, lowest_lat, lowest_lon)
                # No airport was found, so fall through and try AFR.
            else:
                self.debug('Detected approach airport: %s', airport)
        else:
            # No suitable coordinates, so fall through and try AFR.
            self.warning('No coordinates for looking up approach airport.')
            return None, None

        # A2. If and we have an airport in achieved flight record, use it:
        # NOTE: AFR data is only provided if this approach is a landing.
        if not airport and land_afr_apt:
            airport = land_afr_apt.value
            self.debug('Using approach airport from AFR: %s', airport)

        # A3. After all that, we still couldn't determine an airport...
        if not airport:
            self.error('Unable to determine airport on approach!')
            return None, None

        airport_id = int(airport['id'])

        if lowest_hdg is not None:
        #if heading is None:
            #self.warning('Invalid heading... Fallback to AFR.')
            #fallback = True

            # R1. If we have airport and heading, look for the nearest runway:
            if appr_ils_freq:
                ils_freq = appr_ils_freq.get_first(within_slice=_slice)
                if ils_freq:
                    kwargs['ils_freq'] = ils_freq.value

                # We already have latitude and longitude in kwargs from looking up
                # the airport. If the measurments are not precise, remove them.
                if not precise:
                    kwargs['hint'] = hint
                    del kwargs['latitude']
                    del kwargs['longitude']

            try:
                runway = api.get_nearest_runway(airport_id, lowest_hdg,
                                                **kwargs)
            except NotFoundError:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport_id, lowest_hdg, kwargs)
                # No runway was found, so fall through and try AFR.
                if 'ils_freq' in kwargs:
                    # This is a trap for airports where the ILS data is not
                    # available, but the aircraft approached with the ILS
                    # tuned. A good prompt for an omission in the database.
                    self.warning('Fix database? No runway but ILS was tuned.')
            else:
                self.debug('Detected approach runway: %s', runway)
                runway = runway

        # R2. If we have a runway provided in achieved flight record, use it:
        if not runway and land_afr_rwy:
            runway = land_afr_rwy.value
            self.debug('Using approach runway from AFR: %s', runway)

        # R3. After all that, we still couldn't determine a runway...
        if not runway:
            self.error('Unable to determine runway on approach!')

        return airport, runway
def process_flight(hdf_path, tail_number, aircraft_info={},
                   start_datetime=datetime.now(), achieved_flight_record={},
                   requested=[], required=[], include_flight_attributes=True,
                   additional_modules=[]):
    '''
    Processes the HDF file (hdf_path) to derive the required_params (Nodes)
    within python modules (settings.NODE_MODULES).
    
    Note: For Flight Data Services, the definitive API is located here:
        "PolarisTaskManagement.test.tasks_mask.process_flight"
        
    :param hdf_path: Path to HDF File
    :type hdf_path: String
    :param aircraft: Aircraft specific attributes
    :type aircraft: dict   
    :param start_datetime: Datetime of the origin of the data (at index 0)
    :type start_datetime: Datetime
    :param achieved_flight_record: See API Below
    :type achieved_flight_record: Dict
    :param requested: Derived nodes to process (dependencies will also be evaluated).
    :type requested: List of Strings
    :param required: Nodes which are required, otherwise an exception will be raised.
    :type required: List of Strings
    :param include_flight_attributes: Whether to include all flight attributes
    :type include_flight_attributes: Boolean
    :param additional_modules: List of module paths to import.
    :type additional_modules: List of Strings

    :returns: See below:
    :rtype: Dict
    
    
    Sample aircraft_info
    --------------------
    {
        'Tail Number':  # Aircraft Registration
        'Identifier':  # Aircraft Ident
        'Manufacturer': # e.g. Boeing
        'Manufacturer Serial Number': #MSN
        'Model': # e.g. 737-808-ER
        'Series': # e.g. 737-800
        'Family': # e.g. 737
        'Frame': # e.g. 737-3C
        'Main Gear To Altitude Radio': # Distance in metres
        'Wing Span': # Distance in metres
    }
    
    Sample achieved_flight_record
    -----------------------------
    {
        # Simple values first, e.g. string, int, float, etc.
        'AFR Flight ID': # e.g. 1
        'AFR Flight Number': # e.g. 1234
        'AFR Type': # 'POSITIONING'
        'AFR Off Blocks Datetime': # datetime(2015,01,01,13,00)
        'AFR Takeoff Datetime': # datetime(2015,01,01,13,15)
        'AFR Takeoff Pilot': # 'Joe Bloggs'
        'AFR Takeoff Gross Weight': # weight in kg
        'AFR Takeoff Fuel': # fuel in kg
        'AFR Landing Datetime': # datetime(2015,01,01,18,45)
        'AFR Landing Pilot': # 'Joe Bloggs'
        'AFR Landing Gross Weight': # weight in kg
        'AFR Landing Fuel': # weight in kg
        'AFR On Blocks Datetime': # datetime(2015,01,01,19,00)
        'AFR V2': # V2 used at takeoff in kts
        'AFR Vapp': # Vapp used in kts
        'AFR Vref': # Vref used in kts
        # More complex data that needs to be looked up next:
        'AFR Takeoff Airport':  {
            'id': 4904, # unique id
            'name': 'Athens Intl Airport Elefterios Venizel',
            'code': {'iata': 'ATH', 'icao': 'LGAV'},
            'latitude': 37.9364,
            'longitude': 23.9445,
            'location': {'city': u'Athens', 'country': u'Greece'},
            'elevation': 266, # ft
            'magnetic_variation': 'E003186 0106',
            }
           },
        'AFR Landing Aiport': {
            'id': 1, # unique id
            'name': 'Athens Intl Airport Elefterios Venizel',
            'code': {'iata': 'ATH', 'icao': 'LGAV'},
            'latitude': 37.9364,
            'longitude': 23.9445,
            'location': {'city': u'Athens', 'country': u'Greece'},
            'elevation': 266, # ft
            'magnetic_variation': 'E003186 0106',
            }
           },
        'AFR Destination Airport': None, # if not required, or exclude this key
        'AFR Takeoff Runway': {
            'id': 1,
            'identifier': '21L',
            'magnetic_heading': 212.6,
            'strip': {
                'id': 1, 
                'length': 13123, 
                'surface': 'ASP', 
                'width': 147},
            'start': {
                'elevation': 308, 
                'latitude': 37.952425, 
                'longitude': 23.970422},
            'end': {
                'elevation': 279, 
                'latitude': 37.923511, 
                'longitude': 23.943261},
            'glideslope': {
                'angle': 3.0,
                'elevation': 282,
                'latitude': 37.9473,
                'longitude': 23.9676,
                'threshold_distance': 999},
            'localizer': {
                'beam_width': 4.5,
                'elevation': 256,
                'frequency': 111100,
                'heading': 213,
                'latitude': 37.919281,
                'longitude': 23.939294},
            },
        'AFR Landing Runway': {
            'id': 1,
            'identifier': '21L',
            'magnetic_heading': 212.6,
            'strip': {
                'id': 1, 
                'length': 13123, 
                'surface': 'ASP', 
                'width': 147},
            'start': {
                'elevation': 308, 
                'latitude': 37.952425, 
                'longitude': 23.970422},
            'end': {
                'elevation': 279, 
                'latitude': 37.923511, 
                'longitude': 23.943261},
            'glideslope': {
                'angle': 3.0,
                'elevation': 282,
                'latitude': 37.9473,
                'longitude': 23.9676,
                'threshold_distance': 999},
            'localizer': {
                'beam_width': 4.5,
                'elevation': 256,
                'frequency': 111100,
                'heading': 213,
                'latitude': 37.919281,
                'longitude': 23.939294},
            },
    }
    
    Sample Return
    -------------
    {
        'flight':[Attribute('name value')]  # sample: [Attribute('Takeoff Airport', {'id':1234, 'name':'Int. Airport'}, Attribute('Approaches', [4567,7890]), ...], 
        'kti':[GeoKeyTimeInstance('index name latitude longitude')] if lat/long available else [KeyTimeInstance('index name')]
        'kpv':[KeyPointValue('index value name slice')]
    }
    
    '''
    logger.info("Processing: %s", hdf_path)
    
    
    if aircraft_info:
        # Aircraft info has already been provided.
        logger.info(
            "Using aircraft_info dictionary passed into process_flight '%s'." %
            aircraft_info)
    else:
        # Fetch aircraft info through the API.
        api_handler = get_api_handler(settings.API_HANDLER)
        
        try:
            aircraft_info = api_handler.get_aircraft(tail_number)
        except APIError:
            if settings.API_HANDLER == settings.LOCAL_API_HANDLER:
                raise
            # Fallback to the local API handler.
            logger.info(
                "Aircraft '%s' could not be found with '%s' API handler. "
                "Falling back to '%s'.", tail_number, settings.API_HANDLER,
                settings.LOCAL_API_HANDLER)
            api_handler = get_api_handler(settings.LOCAL_API_HANDLER)
            aircraft_info = api_handler.get_aircraft(tail_number)
        
        logger.info("Using aircraft_info provided by '%s' '%s'.",
                    api_handler.__class__.__name__, aircraft_info)
    
    aircraft_info['Tail Number'] = tail_number
    
    # go through modules to get derived nodes
    node_modules = additional_modules + settings.NODE_MODULES
    derived_nodes = get_derived_nodes(node_modules)
    
    if requested:
        requested = \
            list(set(requested).intersection(set(derived_nodes)))
    else:
        # if requested isn't set, try using ALL derived_nodes!
        logger.info("No requested nodes declared, using all derived nodes")
        requested = derived_nodes.keys()
    
    # include all flight attributes as requested
    if include_flight_attributes:
        requested = list(set(
            requested + get_derived_nodes(
                ['analysis_engine.flight_attribute']).keys()))
        
    # open HDF for reading
    with hdf_file(hdf_path) as hdf:
        if hooks.PRE_FLIGHT_ANALYSIS:
            logger.info("Performing PRE_FLIGHT_ANALYSIS actions: %s", 
                         hooks.PRE_FLIGHT_ANALYSIS.func_name)
            hooks.PRE_FLIGHT_ANALYSIS(hdf, aircraft_info)
        else:
            logger.info("No PRE_FLIGHT_ANALYSIS actions to perform")
        # Track nodes. Assume that all params in HDF are from LFL(!)
        node_mgr = NodeManager(
            start_datetime, hdf.duration, hdf.valid_param_names(),
            requested, required, derived_nodes, aircraft_info,
            achieved_flight_record)
        # calculate dependency tree
        process_order, gr_st = dependency_order(node_mgr, draw=False)
        if settings.CACHE_PARAMETER_MIN_USAGE:
            # find params used more than
            for node in gr_st.nodes():
                if node in node_mgr.derived_nodes:
                    # this includes KPV/KTIs but they'll be ignored by HDF
                    qty = len(gr_st.predecessors(node))
                    if qty > settings.CACHE_PARAMETER_MIN_USAGE:
                        hdf.cache_param_list.append(node)
            logging.info("HDF set to cache parameters: %s",
                         hdf.cache_param_list)
        
        # derive parameters
        kti_list, kpv_list, section_list, approach_list, flight_attrs = \
            derive_parameters(hdf, node_mgr, process_order)
             
        # geo locate KTIs
        kti_list = geo_locate(hdf, kti_list)
        kti_list = _timestamp(start_datetime, kti_list)
        
        # geo locate KPVs
        kpv_list = geo_locate(hdf, kpv_list)
        kpv_list = _timestamp(start_datetime, kpv_list)
        
        # Store version of FlightDataAnalyser
        hdf.analysis_version = __version__
        # Store dependency tree
        hdf.dependency_tree = json_graph.dumps(gr_st)
        # Store aircraft info
        hdf.set_attr('aircraft_info', aircraft_info)
        hdf.set_attr('achieved_flight_record', achieved_flight_record)
        
    return {
        'flight' : flight_attrs, 
        'kti' : kti_list, 
        'kpv' : kpv_list,
        'approach': approach_list,
        'phases' : section_list,
    }