Exemplo n.º 1
0
 def derive(self,
            afr_takeoff_fuel=A('AFR Takeoff Fuel'),
            liftoff_fuel_qty=KPV('Fuel Qty At Liftoff')):
     if afr_takeoff_fuel:
         #TODO: Validate that the AFR record is more accurate than the
         #flight data if available.
         self.set_flight_attr(afr_takeoff_fuel.value)
     else:
         fuel_qty_kpv = liftoff_fuel_qty.get_first()
         if fuel_qty_kpv:
             self.set_flight_attr(fuel_qty_kpv.value)
Exemplo n.º 2
0
 def derive(self, liftoff_gross_weight=KPV('Gross Weight At Liftoff')):
     first_gross_weight = liftoff_gross_weight.get_first()
     if first_gross_weight:
         self.set_flight_attr(first_gross_weight.value)
     else:
         # There is not a 'Gross Weight At Liftoff' KPV. Since it is sourced
         # from 'Gross Weight Smoothed', gross weight at liftoff should not
         # be masked.
         self.warning("No '%s' KPVs, '%s' attribute will be None.",
                      liftoff_gross_weight.name, self.name)
         self.set_flight_attr(None)
 def derive(self, touchdown_gross_weight=KPV('Gross Weight At Touchdown')):
     last_gross_weight = touchdown_gross_weight.get_last()
     if last_gross_weight:
         self.set_flight_attr(last_gross_weight.value)
     else:
         # There is not a 'Gross Weight At Touchdown' KPV. Since it is
         # sourced from 'Gross Weight Smoothed', gross weight at touchdown
         # should not be masked. Are there no Touchdown KTIs?
         self.warning("No '%s' KPVs, '%s' attribute will be None.",
                      touchdown_gross_weight.name, self.name)
         self.set_flight_attr(None)
Exemplo n.º 4
0
    def setUp(self):
        self.node_class = ApproachInformation
        self.alt_aal = P(name='Altitude AAL', array=np.ma.array([
            10, 5, 0, 0, 5, 10, 20, 30, 40, 50,      # Touch & Go
            50, 45, 30, 35, 30, 30, 35, 40, 40, 40,  # Go Around
            30, 20, 10, 0, 0, 0, 0, 0, 0, 0,         # Landing
        ]))
        self.app = ApproachAndLanding()
        self.fast = S(name='Fast', items=[
            Section(name='Fast', slice=slice(0, 22), start_edge=0,
                    stop_edge=22.5),
        ])

        self.land_hdg = KPV(name='Heading During Landing', items=[
            KeyPointValue(index=22, value=60),
        ])
        self.land_lat = KPV(name='Latitude At Touchdown', items=[
            KeyPointValue(index=22, value=10),
        ])
        self.land_lon = KPV(name='Longitude At Touchdown', items=[
            KeyPointValue(index=22, value=-2),
        ])
        self.appr_hdg = KPV(name='Heading At Lowest Altitude During Approach', items=[
            KeyPointValue(index=5, value=25),
            KeyPointValue(index=12, value=35),
        ])
        self.appr_lat = KPV(name='Latitude At Lowest Altitude During Approach', items=[
            KeyPointValue(index=5, value=8),
        ])
        self.appr_lon = KPV(name='Longitude At Lowest Altitude During Approach', items=[
            KeyPointValue(index=5, value=4),
        ])
        self.land_afr_apt_none = A(name='AFR Landing Airport', value=None)
        self.land_afr_rwy_none = A(name='AFR Landing Runway', value=None)
    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)
Exemplo n.º 6
0
    def test_derive__bilbao(self):
        fdr_apt = A(name='FDR Takeoff Airport', value=airports['airports']['004'])
        toff_hdg = KPV(name='Heading During Takeoff', items=[
            KeyPointValue(index=710.4453125, value=299.53125),
        ])
        toff_lat = KPV(name='Latitude At Liftoff', items=[
            KeyPointValue(index=724.50390625, value=43.3009835333),
        ])
        toff_lon = KPV(name='Longitude At Liftoff', items=[
            KeyPointValue(index=724.50390625, value=-2.91060447693),
        ])
        accel_start_lat = KPV(name='Latitude Takeoff Acceleration Start', items=[
            KeyPointValue(index=695.76852498, value=43.2960891724),
        ])
        accel_start_lon = KPV(name='Longitude Takeoff Acceleration Start', items=[
            KeyPointValue(index=695.76852498, value=-2.89747238159),
        ])
        precise = A(name='Precise Positioning', value=True)

        node = self.node_class()
        node.derive(fdr_apt, None, toff_hdg, toff_lat, toff_lon, accel_start_lat,
                   accel_start_lon, precise)

        self.assertEqual(node.value['identifier'], '30')
Exemplo n.º 7
0
 def test_derive(self):
     takeoff_fuel = TakeoffFuel()
     takeoff_fuel.set_flight_attr = Mock()
     # Only 'AFR Takeoff Fuel' dependency.
     afr_takeoff_fuel = A('AFR Takeoff Fuel', value=100)
     takeoff_fuel.derive(afr_takeoff_fuel, None)
     takeoff_fuel.set_flight_attr.assert_called_once_with(100)
     # Only 'Fuel Qty At Liftoff' dependency.
     fuel_qty_at_liftoff = KPV('Fuel Qty At Liftoff',
                               items=[KeyPointValue(132, 200)])
     takeoff_fuel.set_flight_attr = Mock()
     takeoff_fuel.derive(None, fuel_qty_at_liftoff)
     takeoff_fuel.set_flight_attr.assert_called_once_with(200)
     # Both, 'AFR Takeoff Fuel' used.
     takeoff_fuel.set_flight_attr = Mock()
     takeoff_fuel.derive(afr_takeoff_fuel, fuel_qty_at_liftoff)
     takeoff_fuel.set_flight_attr.assert_called_once_with(100)
Exemplo n.º 8
0
    def test_derive__ils_sidestep(self, get_nearest_airport):
        approaches = self.node_class()
        approaches._lookup_airport_and_runway = Mock()
        approaches._lookup_airport_and_runway.return_value = [None, None]

        slices = [slice(15, 25)]
        self.app.create_phases(slices)

        appr_ils_freq = KPV(name='ILS Frequency During Approach',
                            items=[
                                KeyPointValue(index=15, value=109.5),
                                KeyPointValue(index=24, value=110.9),
                            ])

        approaches.derive(self.app,
                          self.alt_aal,
                          self.fast,
                          self.land_hdg,
                          self.land_lat,
                          self.land_lon,
                          self.appr_hdg,
                          self.appr_lat,
                          self.appr_lon,
                          appr_ils_freq=appr_ils_freq,
                          land_afr_apt=self.land_afr_apt_none,
                          land_afr_rwy=self.land_afr_rwy_none)
        self.assertEqual(approaches, [
            ApproachItem('LANDING',
                         slice(15, 25),
                         lowest_lat=self.land_lat[0].value,
                         lowest_lon=self.land_lon[0].value,
                         lowest_hdg=self.land_hdg[0].value,
                         ils_freq=110.9)
        ])

        approaches._lookup_airport_and_runway.assert_has_calls([
            call(_slice=slices[0],
                 lowest_hdg=self.land_hdg[0].value,
                 lowest_lat=self.land_lat[0].value,
                 lowest_lon=self.land_lon[0].value,
                 appr_ils_freq=110.9,
                 precise=False,
                 land_afr_apt=self.land_afr_apt_none,
                 land_afr_rwy=self.land_afr_rwy_none,
                 hint='landing'),
        ])
Exemplo n.º 9
0
 def test_derive(self):
     landing_fuel = LandingFuel()
     landing_fuel.set_flight_attr = Mock()
     # Only 'AFR Takeoff Fuel' dependency.
     afr_landing_fuel = A('AFR Landing Fuel', value=100)
     landing_fuel.derive(afr_landing_fuel, None)
     landing_fuel.set_flight_attr.assert_called_once_with(100)
     # Only 'Fuel Qty At Liftoff' dependency.
     fuel_qty_at_touchdown = KPV('Fuel Qty At Touchdown',
                                 items=[KeyPointValue(87, 160),
                                        KeyPointValue(132, 200)])
     landing_fuel.set_flight_attr = Mock()
     landing_fuel.derive(None, fuel_qty_at_touchdown)
     landing_fuel.set_flight_attr.assert_called_once_with(200)
     # Both, 'AFR Takeoff Fuel' used.
     landing_fuel.set_flight_attr = Mock()
     landing_fuel.derive(afr_landing_fuel, fuel_qty_at_touchdown)
     landing_fuel.set_flight_attr.assert_called_once_with(100)
Exemplo n.º 10
0
 def test_derive_afr_fallback(self, nearest_runway):
     info = {'identifier': '09L'}
     def runway_side_effect(apt, hdg, *args, **kwargs):
         if hdg == 90.0:
             return info
     nearest_runway.side_effect = runway_side_effect
     fdr_apt = A(name='FDR Takeoff Airport', value={'id': 50, 'runways':[info]})
     afr_rwy = A(name='AFR Takeoff Runway', value={'identifier': '27R'})
     hdg_a = KPV(name='Heading During Takeoff', items=[
         KeyPointValue(index=1, value=270.0),
         KeyPointValue(index=2, value=360.0),
     ])
     hdg_b = KPV(name='Heading During Takeoff', items=[
         KeyPointValue(index=1, value=90.0),
         KeyPointValue(index=2, value=180.0),
     ])
     rwy = self.node_class()
     rwy.set_flight_attr = Mock()
     # Check that the AFR airport was used and the API wasn't called:
     rwy.derive(None, None, None)
     rwy.set_flight_attr.assert_called_once_with(None)
     rwy.set_flight_attr.reset_mock()
     assert not nearest_runway.called, 'method should not have been called'
     rwy.derive(fdr_apt, afr_rwy, None)
     rwy.set_flight_attr.assert_called_once_with(afr_rwy.value)
     rwy.set_flight_attr.reset_mock()
     assert not nearest_runway.called, 'method should not have been called'
     rwy.derive(None, afr_rwy, hdg_a)
     rwy.set_flight_attr.assert_called_once_with(afr_rwy.value)
     rwy.set_flight_attr.reset_mock()
     assert not nearest_runway.called, 'method should not have been called'
     rwy.derive(None, afr_rwy, None)
     rwy.set_flight_attr.assert_called_once_with(afr_rwy.value)
     rwy.set_flight_attr.reset_mock()
     assert not nearest_runway.called, 'method should not have been called'
     # Check wrong heading triggers AFR:
     rwy.derive(fdr_apt, afr_rwy, hdg_a)
     rwy.set_flight_attr.assert_called_once_with(afr_rwy.value)
     nearest_runway.assert_called_once_with(fdr_apt.value, hdg_a.get_first().value, hint='takeoff')
     rwy.set_flight_attr.reset_mock()
     nearest_runway.reset_mock()
     rwy.derive(fdr_apt, afr_rwy, hdg_b)
     rwy.set_flight_attr.assert_called_once_with(info)
     nearest_runway.assert_called_once_with(fdr_apt.value, hdg_b.get_first().value, hint='takeoff')
Exemplo n.º 11
0
    def derive(
        self,
        alt_aal=P('Altitude AAL'),
        alt_agl=P('Altitude AGL'),
        ac_type=A('Aircraft Type'),
        app=S('Approach And Landing'),
        hdg=P('Heading Continuous'),
        lat=P('Latitude Prepared'),
        lon=P('Longitude Prepared'),
        ils_loc=P('ILS Localizer'),
        ils_gs=S('ILS Glideslope'),
        ils_freq=P('ILS Frequency'),
        land_afr_apt=A('AFR Landing Airport'),
        land_afr_rwy=A('AFR Landing Runway'),
        lat_land=KPV('Latitude At Touchdown'),
        lon_land=KPV('Longitude At Touchdown'),
        precision=A('Precise Positioning'),
        fast=S('Fast'),

        #lat_smoothed=P('Latitude Smoothed'),
        #lon_smoothed=P('Longitude Smoothed'),
        u=P('Airspeed'),
        gspd=P('Groundspeed'),
        height_from_rig=P('Altitude ADH'),
        hdot=P('Vertical Speed'),
        roll=P('Roll'),
        heading=P('Heading'),
        distance_land=P('Distance To Landing'),
        tdwns=KTI('Touchdown'),
        offshore=M('Offshore'),
        takeoff=S('Takeoff')):

        precise = bool(getattr(precision, 'value', False))
        alt = alt_agl if ac_type == helicopter else alt_aal
        app_slices = sorted(app.get_slices())

        for index, _slice in enumerate(app_slices):
            # a) The last approach is assumed to be landing:
            if index == len(app_slices) - 1:
                approach_type = 'LANDING'
                landing = True
            # b) We have a touch and go if Altitude AAL reached zero:
            #elif np.ma.any(alt.array[_slice] <= 0):
            elif np.ma.any(alt.array[_slice.start:_slice.stop +
                                     (5 * alt.frequency)] <= 0):
                if ac_type == aeroplane:
                    approach_type = 'TOUCH_AND_GO'
                    landing = False
                elif ac_type == helicopter:
                    approach_type = 'LANDING'
                    landing = True
                else:
                    raise ValueError('Not doing hovercraft!')
            # c) In any other case we have a go-around:
            else:
                approach_type = 'GO_AROUND'
                landing = False

            # Rough reference index to allow for go-arounds
            ref_idx = index_at_value(alt.array,
                                     0.0,
                                     _slice=_slice,
                                     endpoint='nearest')

            turnoff = None
            if landing:
                search_end = fast.get_surrounding(_slice.start)
                if search_end and search_end[0].slice.stop >= ref_idx:
                    search_end = min(search_end[0].slice.stop, _slice.stop)
                else:
                    search_end = _slice.stop

                tdn_hdg = np.ma.median(hdg.array[ref_idx:search_end + 1])
                # Complex trap for the all landing heading data is masked case...
                if (tdn_hdg % 360.0) is np.ma.masked:
                    lowest_hdg = bearing_and_distance(lat.array[ref_idx],
                                                      lon.array[ref_idx],
                                                      lat.array[search_end],
                                                      lon.array[search_end])[0]
                else:
                    lowest_hdg = (tdn_hdg % 360.0).item()

                # While we're here, let's compute the turnoff index for this landing.
                head_landing = hdg.array[(ref_idx + _slice.stop) /
                                         2:_slice.stop]
                if len(head_landing) > 2:
                    peak_bend = peak_curvature(head_landing,
                                               curve_sense='Bipolar')
                    fifteen_deg = index_at_value(
                        np.ma.abs(head_landing - head_landing[0]), 15.0)
                    if peak_bend:
                        turnoff = (ref_idx + _slice.stop) / 2 + peak_bend
                    else:
                        if fifteen_deg and fifteen_deg < peak_bend:
                            turnoff = start_search + landing_turn
                        else:
                            # No turn, so just use end of landing run.
                            turnoff = _slice.stop
                else:
                    # No turn, so just use end of landing run.
                    turnoff = _slice.stop
            else:
                # We didn't land, but this is indicative of the runway heading
                lowest_hdg = (hdg.array[ref_idx] % 360.0).item()

            # Pass latitude, longitude and heading
            lowest_lat = None
            lowest_lon = None
            if lat and lon and ref_idx:
                lowest_lat = lat.array[ref_idx] or None
                lowest_lon = lon.array[ref_idx] or None
                if lowest_lat and lowest_lon and approach_type == 'GO_AROUND':
                    # Doing a go-around, we extrapolate to the threshold
                    # in case we abort the approach abeam a different airport,
                    # using the rule of three miles per thousand feet.
                    distance = np.ma.array([
                        ut.convert(alt_aal.array[ref_idx] * (3 / 1000.0),
                                   ut.NM, ut.METER)
                    ])
                    bearing = np.ma.array([lowest_hdg])
                    reference = {
                        'latitude': lowest_lat,
                        'longitude': lowest_lon
                    }
                    lat_ga, lon_ga = latitudes_and_longitudes(
                        bearing, distance, reference)
                    lowest_lat = lat_ga[0]
                    lowest_lon = lon_ga[0]

            if lat_land and lon_land and not (lowest_lat and lowest_lon):
                # use lat/lon at landing if values at ref_idx are masked
                # only interested in landing within approach slice.
                lat_land = lat_land.get(within_slice=_slice)
                lon_land = lon_land.get(within_slice=_slice)
                if lat_land and lon_land:
                    lowest_lat = lat_land[0].value or None
                    lowest_lon = lon_land[0].value or None

            kwargs = dict(
                precise=precise,
                _slice=_slice,
                lowest_lat=lowest_lat,
                lowest_lon=lowest_lon,
                lowest_hdg=lowest_hdg,
                appr_ils_freq=None,
                ac_type=ac_type,
            )

            # If the approach is a landing, pass through information from the
            # achieved flight record in case we cannot determine airport and
            # runway:
            if landing:
                kwargs.update(
                    land_afr_apt=land_afr_apt,
                    land_afr_rwy=land_afr_rwy,
                    hint='landing',
                )
            if landing or approach_type == 'GO_AROUND':
                # if we have a frequency and valid localiser signal at lowest point in approach
                appr_ils_freq = None
                if ils_freq:
                    appr_ils_freq = np.ma.round(ils_freq.array[ref_idx] or 0,
                                                2)
                if not precise and appr_ils_freq and ils_loc and np.ma.abs(
                        ils_loc.array[ref_idx]) < 2.5:
                    kwargs['appr_ils_freq'] = appr_ils_freq

            airport, landing_runway = self._lookup_airport_and_runway(**kwargs)
            if not airport and ac_type == aeroplane:
                continue

            if ac_type == aeroplane and not airport.get('runways'):
                self.error("Airport %s: contains no runways", airport['code'])

            # Simple determination of heliport.
            heliport = is_heliport(ac_type, airport, landing_runway)

            sorted_tdwns = sorted(tdwns, key=lambda touchdown: touchdown.index)
            sorted_takeoffs = sorted(takeoff.get_slices(),
                                     key=lambda tkoff: tkoff.start)

            for touchdown, tkoff in zip(sorted_tdwns, sorted_takeoffs):
                # If both the takeoff and touchdown point are offshore then we consider
                # the approach to be a 'SHUTTLING APPROACH'. Else we continue to look for
                # an 'AIRBORNE RADAR DIRECT/OVERHEAD APPROACH' or a 'STANDARD APPROACH'
                #
                # A couple of seconds are added to the end of the slice as some flights used
                # to test this had the touchdown a couple of seconds outside the approach slice
                if is_index_within_slice(
                        touchdown.index,
                        slice(_slice.start, _slice.stop + 5 * alt.frequency)):
                    if offshore and offshore.array[
                            touchdown.
                            index] == 'Offshore' and tkoff.start < touchdown.index:
                        if not distance_land:
                            if offshore.array[tkoff.start] == 'Offshore':
                                approach_type = 'SHUTTLING'
                        elif offshore.array[
                                tkoff.
                                start] == 'Offshore' and tkoff.start < len(
                                    distance_land.array
                                ) and distance_land.array[tkoff.start] <= 40:
                            approach_type = 'SHUTTLING'
                        elif height_from_rig:
                            Vy = 80.0  # Type dependent?

                            # conditions_defs is a dict of condition name : expression to evaluate pairs, listed this way for clarity
                            condition_defs = {
                                'Below 120 kts':
                                lambda p: p['Airspeed'] < 120,
                                'Below Vy+5':
                                lambda p: p['Airspeed'] < Vy + 5.0,
                                'Over Vy':
                                lambda p: p['Airspeed'] > Vy,
                                'Over Vy-5':
                                lambda p: p['Airspeed'] > Vy - 5.0,
                                'Below 70 gspd':
                                lambda p: p['Groundspeed'] < 72,
                                'Below 60 gspd':
                                lambda p: p['Groundspeed'] < 60,
                                #'Below Vy-10' : lambda p : p['Airspeed'] < Vy-10.0,
                                #'Over Vy-10' : lambda p : p['Airspeed'] > Vy-10.0,
                                #'Above 30 gspd' : lambda p : p['Groundspeed'] > 30,
                                'Over 900 ft':
                                lambda p: p['Altitude ADH'] > 900,
                                'Over 200 ft':
                                lambda p: p['Altitude ADH'] > 200,
                                'Below 1750 ft':
                                lambda p: p['Altitude ADH'] < 1750,
                                'Below 1100 ft':
                                lambda p: p['Altitude ADH'] < 1100,
                                'Over 350 ft':
                                lambda p: p['Altitude ADH'] > 350,
                                'Below 700 ft':
                                lambda p: p['Altitude ADH'] < 700,
                                'ROD < 700 fpm':
                                lambda p: p['Vertical Speed'] > -700,
                                'ROD > 200 fpm':
                                lambda p: p['Vertical Speed'] < -200,
                                'Not climbing':
                                lambda p: p['Vertical Speed'] < 200,
                                #'Over 400 ft' : lambda p : p['Altitude ADH'] > 400,
                                #'Below 1500 ft': lambda p : p['Altitude ADH'] < 1500,
                                #'Below 1300 ft': lambda p : p['Altitude ADH'] < 1300,
                                'Roll below 25 deg':
                                lambda p: valid_between(
                                    p['Roll'], -25.0, 25.0),
                                'Wings Level':
                                lambda p: valid_between(
                                    p['Roll'], -10.0, 10.0),
                                'Within 20 deg of final heading':
                                lambda p: np.ma.abs(p['head_off_final']) <
                                20.0,
                                #'Within 45 deg of downwind leg' : 'valid_between(np.ma.abs(head_off_final), 135.0, 225.0)',
                                #'15 deg off final heading' : lambda p : np.ma.abs(np.ma.abs(p['head_off_two_miles'])-15.0) < 5.0,
                                #'Heading towards oil rig' : lambda p : np.ma.abs(p['head_off_two_miles']) < 6.0,
                                'Beyond 0.7 NM':
                                lambda p: p['Distance To Landing'] > 0.7,
                                'Within 0.8 NM':
                                lambda p: p['Distance To Landing'] < 0.8,
                                'Beyond 1.5 NM':
                                lambda p: p['Distance To Landing'] > 1.5,
                                'Within 2.0 NM':
                                lambda p: p['Distance To Landing'] < 2.0,
                                'Within 3.0 NM':
                                lambda p: p['Distance To Landing'] < 3.0,
                                'Beyond 3.0 NM':
                                lambda p: p['Distance To Landing'] > 3.0,
                                'Within 10.0 NM':
                                lambda p: p['Distance To Landing'] < 10.0,
                                #'Within 1.5 NM' : lambda p : p['Distance To Landing'] < 1.5,
                            }

                            # Phase map is a dict of the flight phases with the list of conditions which must be
                            # satisfied for the phase to be active.
                            phase_map = {
                                'Circuit': [
                                    'Below 120 kts',
                                    'Over Vy',
                                    'Below 1100 ft',
                                    'Over 900 ft',
                                    'Roll below 25 deg',  # includes downwind turn
                                ],
                                'Level within 2NM': [
                                    'Below Vy+5',
                                    'Over Vy-5',
                                    'Below 1100 ft',
                                    'Over 900 ft',
                                    'Wings Level',
                                    'Within 20 deg of final heading',
                                    'Within 2.0 NM',
                                    'Beyond 1.5 NM',
                                ],
                                'Initial Descent': [
                                    'Wings Level',
                                    'Within 20 deg of final heading',
                                    'ROD < 700 fpm',
                                    'ROD > 200 fpm',
                                    'Beyond 0.7 NM',
                                    'Over 350 ft',
                                ],
                                'Final Approach': [
                                    'Wings Level',
                                    'Within 20 deg of final heading',
                                    'ROD < 700 fpm',
                                    'Within 0.8 NM',
                                    'Below 60 gspd',
                                    'Below 700 ft',
                                ],

                                # Phases for ARDA/AROA
                                #
                                # All heading conditions are commented out as the pilots usually
                                # go outside the boundaries; the other conditions seem to be
                                # enough to detect them
                                'ARDA/AROA 10 to 3': [
                                    'Within 10.0 NM',
                                    'Beyond 3.0 NM',
                                    'Below 1750 ft',
                                    'Not climbing',
                                    #'Heading towards oil rig',
                                ],
                                'ARDA/AROA Level within 3NM': [
                                    'Below 70 gspd',
                                    'Over 200 ft',
                                    'Wings Level',
                                    'Within 3.0 NM',
                                    'Beyond 1.5 NM',
                                    #'Within 20 deg of final heading',
                                ],
                                'ARDA/AROA Final': [
                                    'Not climbing',
                                    'Within 2.0 NM',
                                    #'15 deg off final heading'
                                ],
                            }
                            """
                            #Phases that can be used to tighten the conditions for ARDA/AROA
                        
                            'Radar Heading Change':['15 deg off final heading', 'Within 1.5 NM', 'Beyond 0.7 NM'],
                            'Low Approach':['Below Vy+5', 'Below 700 ft', 'Over 350 ft', 'Within 20 deg of final heading', 'Wings Level'],
                            'Low Circuit':['Below 120 kts', 'Over Vy-5', 'Below 700 ft', 'Over 350 ft', 'Roll below 25 deg']
                            """

                            approach_map = {
                                'RIG': [
                                    'Circuit', 'Level within 2NM',
                                    'Initial Descent', 'Final Approach'
                                ],
                                'AIRBORNE_RADAR': [
                                    'ARDA/AROA 10 to 3',
                                    'ARDA/AROA Level within 3NM',
                                    'ARDA/AROA Final'
                                ]
                            }

                            # Making sure the approach slice contains enough information to be able
                            # to properly identify ARDA/AROA approaches (the procedure starts from 10NM
                            # before touchdown)

                            app_slice = slice(
                                index_at_value(distance_land.array,
                                               11,
                                               _slice=slice(
                                                   0, touchdown.index)),
                                touchdown.index)

                            heading_repaired = repair_mask(
                                heading.array[app_slice],
                                frequency=heading.frequency,
                                repair_duration=np.ma.count_masked(
                                    heading.array[app_slice]),
                                copy=True,
                                extrapolate=True)

                            param_arrays = {
                                'Airspeed':
                                u.array[app_slice],
                                'Groundspeed':
                                gspd.array[app_slice],
                                'Altitude ADH':
                                height_from_rig.array[app_slice],
                                'Vertical Speed':
                                hdot.array[app_slice],
                                'Roll':
                                roll.array[app_slice],
                                'Distance To Landing':
                                distance_land.array[app_slice],
                                'Heading':
                                heading_repaired,
                                'Latitude':
                                lat.array[app_slice],
                                'Longitude':
                                lon.array[app_slice],
                            }

                            longest_approach_type, longest_approach_durn, longest_approach_slice = find_rig_approach(
                                condition_defs,
                                phase_map,
                                approach_map,
                                Vy,
                                None,
                                param_arrays,
                                debug=False)

                            if longest_approach_type is not None:
                                approach_type = longest_approach_type.upper()
                                _slice = slice(
                                    app_slice.start +
                                    longest_approach_slice.start,
                                    app_slice.stop)

            if heliport:
                self.create_approach(
                    approach_type,
                    _slice,
                    runway_change=False,
                    offset_ils=False,
                    airport=airport,
                    landing_runway=None,
                    approach_runway=None,
                    gs_est=None,
                    loc_est=None,
                    ils_freq=None,
                    turnoff=None,
                    lowest_lat=lowest_lat,
                    lowest_lon=lowest_lon,
                    lowest_hdg=lowest_hdg,
                )
                continue

            #########################################################################
            ## Analysis of fixed wing approach to a runway
            ##
            ## First step is to check the ILS frequency for the runway in use
            ## and cater for a change from the approach runway to the landing runway.
            #########################################################################

            appr_ils_freq = None
            runway_change = False
            offset_ils = False

            # Do we have a recorded ILS frequency? If so, what was it tuned to at the start of the approach??
            if ils_freq:
                appr_ils_freq = ils_freq.array[_slice.start]
            # Was this valid, and if so did the start of the approach match the landing runway?
            if appr_ils_freq and not (np.isnan(appr_ils_freq)
                                      or np.ma.is_masked(appr_ils_freq)):
                appr_ils_freq = round(appr_ils_freq, 2)
                runway_kwargs = {
                    'ilsfreq': appr_ils_freq,
                    'latitude': lowest_lat,
                    'longitude': lowest_lon,
                }
                if not precise:
                    runway_kwargs['hint'] = kwargs.get('hint', 'approach')
                approach_runway = nearest_runway(airport, lowest_hdg,
                                                 **runway_kwargs)
                # Have we have identified runways for both conditions that are both different and parallel?
                if all((approach_runway, landing_runway)) \
                   and approach_runway['id'] != landing_runway['id'] \
                   and approach_runway['identifier'][:2] == landing_runway['identifier'][:2]:
                    runway_change = True
            else:
                # Without a frequency source, we just have to hope any localizer signal is for this runway!
                approach_runway = landing_runway

            if approach_runway and 'frequency' in approach_runway['localizer']:
                if np.ma.count(ils_loc.array[_slice]) > 10:
                    if runway_change:
                        # We only use the first frequency tuned. This stops scanning across both runways if the pilot retunes.
                        loc_slice = shift_slices(
                            runs_of_ones(
                                np.ma.abs(ils_freq.array[_slice] -
                                          appr_ils_freq) < 0.001),
                            _slice.start)[0]
                    else:
                        loc_slice = _slice
                else:
                    # No localizer or inadequate data for this approach.
                    loc_slice = None
            else:
                # The approach was to a runway without an ILS, so even if it was tuned, we ignore this.
                appr_ils_freq = None
                loc_slice = None

            if np.ma.is_masked(appr_ils_freq):
                loc_slice = None
                appr_ils_freq = None
            else:
                if appr_ils_freq and loc_slice:
                    if appr_ils_freq != round(
                            ut.convert(
                                approach_runway['localizer']['frequency'],
                                ut.KHZ, ut.MHZ), 2):
                        loc_slice = None

            #######################################################################
            ## Identification of the period established on the localizer
            #######################################################################

            loc_est = None
            if loc_slice:
                valid_range = np.ma.flatnotmasked_edges(ils_loc.array[_slice])
                # I have some data to scan. Shorthand names;
                loc_start = valid_range[0] + _slice.start
                loc_end = valid_range[1] + _slice.start
                scan_back = slice(ref_idx, loc_start, -1)

                # If we are turning in, we are not interested in signals that are not related to this approach.
                # The value of 45 deg was selected to encompass Washington National airport with a 40 deg offset.
                hdg_diff = np.ma.abs(
                    np.ma.mod((hdg.array - lowest_hdg) + 180.0, 360.0) - 180.0)
                ils_hdg_45 = index_at_value(hdg_diff, 45.0, _slice=scan_back)

                # We are not interested above 1,500 ft, so may trim back the start point to that point:
                ils_alt_1500 = index_at_value(alt_aal.array,
                                              1500.0,
                                              _slice=scan_back)

                # The criteria for start of established phase is the latter of the approach phase start, the turn-in or 1500ft.
                # The "or 0" allow for flights that do not turn through 45 deg or keep below 1500ft.
                loc_start = max(loc_start, ils_hdg_45 or 0, ils_alt_1500 or 0)

                if loc_start < ref_idx:
                    # Did I get established on the localizer, and if so,
                    # when? We only look AFTER the aircraft is already within
                    # 45deg of the runway heading, below 1500ft and the data
                    # is valid for this runway. Testing that the aircraft is
                    # not just passing across the localizer is built into the
                    # ils_established function.
                    loc_estab = ils_established(ils_loc.array,
                                                slice(loc_start, ref_idx),
                                                ils_loc.hz)
                else:
                    # If localiser start is after we touchdown bail.
                    loc_estab = None

                if loc_estab:

                    # Refine the end of the localizer established phase...
                    if (approach_runway
                            and approach_runway['localizer']['is_offset']):
                        offset_ils = True
                        # The ILS established phase ends when the deviation becomes large.
                        loc_end = ils_established(ils_loc.array,
                                                  slice(
                                                      ref_idx, loc_estab, -1),
                                                  ils_loc.hz,
                                                  point='immediate')

                    elif approach_type in ['TOUCH_AND_GO', 'GO_AROUND']:
                        # We finish at the lowest point
                        loc_end = ref_idx

                    elif runway_change:
                        # Use the end of localizer phase as this already reflects the tuned frequency.
                        est_end = ils_established(ils_loc.array,
                                                  slice(loc_estab, ref_idx),
                                                  ils_loc.hz,
                                                  point='end')
                        # Make sure we dont end up with a negative slice i.e. end before we are established.
                        loc_end = min([
                            x for x in (loc_slice.stop, loc_end,
                                        est_end or np.inf) if x > loc_estab
                        ])

                    elif approach_type == 'LANDING':
                        # Just end at 2 dots where we turn off the runway
                        loc_end_2_dots = index_at_value(
                            np.ma.abs(ils_loc.array),
                            2.0,
                            _slice=slice(
                                turnoff + 5 *
                                (_slice.stop - _slice.start) / 100, loc_estab,
                                -1))
                        if loc_end_2_dots and is_index_within_slice(
                                loc_end_2_dots,
                                _slice) and not np.ma.is_masked(
                                    ils_loc.array[loc_end_2_dots]
                                ) and loc_end_2_dots > loc_estab:
                            loc_end = loc_end_2_dots
                    loc_est = slice(loc_estab, loc_end + 1)

            #######################################################################
            ## Identification of the period established on the glideslope
            #######################################################################

            gs_est = None
            if loc_est and 'glideslope' in approach_runway and ils_gs:
                # We only look for glideslope established periods if the localizer is already established.

                # The range to scan for the glideslope starts with localizer capture and ends at
                # 200ft or the minimum height point for a go-around or the end of
                # localizer established, if either is earlier.
                ils_gs_start = loc_estab
                ils_gs_200 = index_at_value(alt.array,
                                            200.0,
                                            _slice=slice(
                                                loc_end, ils_gs_start, -1))
                # The expression "ils_gs_200 or np.inf" caters for the case where the aircraft did not pass
                # through 200ft, so the result is None, in which case any other value is left to be the minimum.
                ils_gs_end = min(ils_gs_200 or np.inf, ref_idx, loc_end)

                # Look for ten seconds within half a dot
                ils_gs_estab = ils_established(ils_gs.array,
                                               slice(ils_gs_start, ils_gs_end),
                                               ils_gs.hz)

                if ils_gs_estab:
                    gs_est = slice(ils_gs_estab, ils_gs_end + 1)
            '''
            # These statements help set up test cases.
            print()
            print(airport['name'])
            print(approach_runway['identifier'])
            print(landing_runway['identifier'])
            print(_slice)
            if loc_est:
                print('Localizer established ', loc_est.start, loc_est.stop)
            if gs_est:
                print('Glideslope established ', gs_est.start, gs_est.stop)
            print()
            '''

            self.create_approach(
                approach_type,
                _slice,
                runway_change=runway_change,
                offset_ils=offset_ils,
                airport=airport,
                landing_runway=landing_runway,
                approach_runway=approach_runway,
                gs_est=gs_est,
                loc_est=loc_est,
                ils_freq=appr_ils_freq,
                turnoff=turnoff,
                lowest_lat=lowest_lat,
                lowest_lon=lowest_lon,
                lowest_hdg=lowest_hdg,
            )
    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'),
               accel_start_lat=KPV('Latitude At Takeoff Acceleration Start'),
               accel_start_lon=KPV('Longitude At Takeoff Acceleration Start'),
               precision=A('Precise Positioning'),
               lat=P('Latitude'),
               lon=P('Longitude'),
               lat_c=P('Latitude (Coarse)'),
               lon_c=P('Longitude (Coarse)')):
        '''
        '''
        fallback = False
        precise = bool(getattr(precision, 'value', False))

        if not precise and toff_afr_rwy:
            self.use_afr(toff_afr_rwy)
            return

        try:
            airport = toff_fdr_apt.value
        except AttributeError:
            self.warning('Invalid airport... Fallback to AFR Takeoff Runway.')
            fallback = True
        else:
            if airport is None:
                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

        # 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).
            lat = lat or lat_c
            lon = lon or lon_c
            lats, lons = self._takeoff_roll(
                lat, lon, accel_start_lat, accel_start_lon, toff_lat, toff_lon
            )
            if lats is not None and lons is not None:
                kwargs.update(
                    latitude=np.array(lats),
                    longitude=np.array(lons),
                )
            else:
                self.warning('No coordinates for takeoff runway lookup.')
            if not precise:
                kwargs.update(hint='takeoff')

            runway = nearest_runway(airport, heading, **kwargs)
            if not runway:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport['id'], 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['id'], heading, kwargs)
                self.set_flight_attr(runway)
                return

        if toff_afr_rwy:
            self.use_afr(toff_afr_rwy)
            return

        self.error('Unable to determine runway at takeoff!')
        self.set_flight_attr(None)
 def derive(self, touchdown_gross_weight=KPV('Gross Weight At Touchdown')):
     pass
    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)
Exemplo n.º 15
0
    def derive(
            self,
            tcas_ctl=M('TCAS Combined Control'),
            tcas_up=M('TCAS Up Advisory'),
            tcas_down=M('TCAS Down Advisory'),
            tcas_vert=M('TCAS Vertical Control'),
            vertspd=P('Vertical Speed'),
            ra_sections=S('TCAS RA Sections'),
            raduration=KPV('TCAS RA Warning Duration'),
    ):

        standard_vert_accel = 8.0 * 60  #  8 ft/sec^2, converted to ft/min^2
        standard_vert_accel_reversal = 11.2 * 60  # ft/sec^2 ==> ft/min^2
        standard_response_lag = 5.0  # seconds
        standard_response_lag_reversal = 2.5  # seconds
        self.array = vertspd.array * 0  #make a copy, mask and zero out
        self.array.mask = True
        required_fpm_array = vertspd.array * 0

        for ra in ra_sections:
            self.debug('TCAS RA Standard Response: in sections')
            #initialize response state
            ra_ctl_prev = tcas_ctl.array[
                ra.start_edge]  # used to check if the command has changed
            up_prev = tcas_ctl.array[
                ra.start_edge]  # used to check if the command has changed
            down_prev = tcas_ctl.array[
                ra.start_edge]  # used to check if the command has changed
            initial_vert_spd = vertspd.array[ra.start_edge]
            std_vert_spd = initial_vert_spd  # current standard response vert speed in fpm
            required_fpm = None  # nominal vertical speed in fpm required by the RA
            lag_end = ra.start_edge + standard_response_lag  # time pilot response lag ends
            acceleration = standard_vert_accel

            for t in range(int(ra.start_edge), int(ra.stop_edge) + 1):
                # set required_fpm for initial ra or a change in command
                if ra_ctl_prev != tcas_ctl.array[t] or up_prev != tcas_up.array[
                        t] or down_prev != tcas_down.array[t]:
                    if tcas_ctl.array[
                            t] == 'Up Advisory Corrective' or tcas_up.array[
                                t].lower() != 'no up advisory':
                        required_fpm = tcas_vert_spd_up(
                            tcas_up.array[t], vertspd.array[t],
                            tcas_vert.array[t])
                    elif tcas_ctl.array[
                            t] == 'Down Advisory Corrective' or tcas_down.array[
                                t].lower() != 'no down advisory':
                        required_fpm = tcas_vert_spd_down(
                            tcas_down.array[t], vertspd.array[t],
                            tcas_vert.array[t])
                    else:
                        required_fpm = vertspd.array[t]
                    if tcas_vert.array[t] == 'Reversal':
                        lag_end = t + standard_response_lag_reversal
                        acceleration = standard_vert_accel_reversal
                        initial_vert_spd = std_vert_spd
                if required_fpm is None:
                    self.warning(
                        'TCAS RA Standard Response: No required_fpm found. Take a look! '
                        + str(t))

                std_vert_spd = update_std_vert_spd(
                    t, lag_end, tcas_ctl.array[t], tcas_up.array[t],
                    tcas_down.array[t], acceleration, required_fpm,
                    std_vert_spd, initial_vert_spd, vertspd.array[t])
                self.array.data[t] = std_vert_spd
                self.array.mask[t] = False
                required_fpm_array[t] = required_fpm
                ra_ctl_prev = tcas_ctl.array[t]
                up_prev = tcas_up.array[t]
                down_prev = tcas_down.array[t]
                #end of time loop within ra section
        return
Exemplo n.º 16
0
    def derive(self,
               alt_aal=P('Altitude AAL'),
               alt_agl=P('Altitude AGL'),
               ac_type=A('Aircraft Type'),
               app=S('Approach And Landing'),
               hdg=P('Heading Continuous'),
               lat=P('Latitude Prepared'),
               lon=P('Longitude Prepared'),
               ils_loc=P('ILS Localizer'),
               ils_gs=S('ILS Glideslope'),
               ils_freq=P('ILS Frequency'),
               land_afr_apt=A('AFR Landing Airport'),
               land_afr_rwy=A('AFR Landing Runway'),
               lat_land=KPV('Latitude At Touchdown'),
               lon_land=KPV('Longitude At Touchdown'),
               precision=A('Precise Positioning'),
               fast=S('Fast'),
               ):

        precise = bool(getattr(precision, 'value', False))

        alt = alt_agl if ac_type == helicopter else alt_aal

        app_slices = app.get_slices()

        for index, _slice in enumerate(app_slices):
            # a) The last approach is assumed to be landing:
            if index == len(app_slices) - 1:
                approach_type = 'LANDING'
                landing = True
            # b) We have a touch and go if Altitude AAL reached zero:
            elif np.ma.any(alt.array[_slice] <= 0):
                if ac_type == aeroplane:
                    approach_type = 'TOUCH_AND_GO'
                    landing = False
                elif ac_type == helicopter:
                    approach_type = 'LANDING'
                    landing = True
                else:
                    raise ValueError('Not doing hovercraft!')
            # c) In any other case we have a go-around:
            else:
                approach_type = 'GO_AROUND'
                landing = False

            # Rough reference index to allow for go-arounds
            ref_idx = index_at_value(alt.array, 0.0, _slice=_slice, endpoint='nearest')

            turnoff = None
            if landing:
                search_end = fast.get_surrounding(_slice.start)
                if search_end:
                    search_end = min(search_end[0].slice.stop, _slice.stop)
                else:
                    search_end = _slice.stop

                tdn_hdg = np.ma.median(hdg.array[ref_idx:search_end+1])
                lowest_hdg = (tdn_hdg % 360.0).item()
                
                # While we're here, let's compute the turnoff index for this landing.
                head_landing = hdg.array[(ref_idx+_slice.stop)/2:_slice.stop]
                peak_bend = peak_curvature(head_landing, curve_sense='Bipolar')
                fifteen_deg = index_at_value(
                    np.ma.abs(head_landing - head_landing[0]), 15.0)
                if peak_bend:
                    turnoff = (ref_idx+_slice.stop)/2 + peak_bend
                else:
                    if fifteen_deg and fifteen_deg < peak_bend:
                        turnoff = start_search + landing_turn
                    else:
                        # No turn, so just use end of landing run.
                        turnoff = _slice.stop
            else:
                # We didn't land, but this is indicative of the runway heading
                lowest_hdg = (hdg.array[ref_idx] % 360.0).item()

            # Pass latitude, longitude and heading
            lowest_lat = None
            lowest_lon = None
            if lat and lon and ref_idx:
                lowest_lat = lat.array[ref_idx] or None
                lowest_lon = lon.array[ref_idx] or None
            if lat_land and lon_land and not (lowest_lat and lowest_lon):
                # use lat/lon at landing if values at ref_idx are masked
                # only interested in landing within approach slice.
                lat_land = lat_land.get(within_slice=_slice)
                lon_land = lon_land.get(within_slice=_slice)
                if lat_land and lon_land:
                    lowest_lat = lat_land[0].value or None
                    lowest_lon = lon_land[0].value or None

            kwargs = dict(
                precise=precise,
                _slice=_slice,
                lowest_lat=lowest_lat,
                lowest_lon=lowest_lon,
                lowest_hdg=lowest_hdg,
                appr_ils_freq=None,
            )

            # If the approach is a landing, pass through information from the
            # achieved flight record in case we cannot determine airport and
            # runway:
            if landing:
                kwargs.update(
                    land_afr_apt=land_afr_apt,
                    land_afr_rwy=land_afr_rwy,
                    hint='landing',
                )
            if landing or approach_type == 'GO_AROUND':
                # if we have a frequency and valid localiser signal at lowest point in approach
                appr_ils_freq = None
                if ils_freq:
                    appr_ils_freq = np.ma.round(ils_freq.array[ref_idx] or 0, 2)
                if not precise and appr_ils_freq  and ils_loc and np.ma.abs(ils_loc.array[ref_idx]) < 2.5:
                    kwargs['appr_ils_freq'] = appr_ils_freq

            airport, landing_runway = self._lookup_airport_and_runway(**kwargs)
            if not airport and ac_type == aeroplane:
                continue

            if ac_type == aeroplane and not airport.get('runways'):
                self.error("Airport %s: contains no runways", airport['code'])

            # Simple determination of heliport.
            # This function may be expanded to cater for rig approaches in future.
            heliport = is_heliport(ac_type, airport, landing_runway)

            if heliport:
                self.create_approach(
                    approach_type,
                    _slice,
                    runway_change=False,
                    offset_ils=False,
                    airport=airport,
                    landing_runway=None,
                    approach_runway=None,
                    gs_est=None,
                    loc_est=None,
                    ils_freq=None,
                    turnoff=None,
                    lowest_lat=lowest_lat,
                    lowest_lon=lowest_lon,
                    lowest_hdg=lowest_hdg,
                )
                continue

            #########################################################################
            ## Analysis of fixed wing approach to a runway
            ## 
            ## First step is to check the ILS frequency for the runway in use
            ## and cater for a change from the approach runway to the landing runway.
            #########################################################################
            
            appr_ils_freq = None
            runway_change = False
            offset_ils = False
            
            # Do we have a recorded ILS frequency? If so, what was it tuned to at the start of the approach??
            if ils_freq:
                appr_ils_freq = round(ils_freq.array[_slice.start], 2)
            # Was this valid, and if so did the start of the approach match the landing runway?
            if appr_ils_freq and not np.isnan(appr_ils_freq):
                runway_kwargs = {
                    'ilsfreq': appr_ils_freq,
                    'latitude': lowest_lat,
                    'longitude': lowest_lon,
                }
                if not precise:
                    runway_kwargs['hint'] = kwargs.get('hint', 'approach')
                approach_runway = nearest_runway(airport, lowest_hdg, **runway_kwargs)
                # Have we have identified runways for both conditions that are both different and parallel?
                if all((approach_runway, landing_runway)) \
                   and approach_runway['id'] != landing_runway['id'] \
                   and approach_runway['identifier'][:2] == landing_runway['identifier'][:2]:
                    runway_change = True
            else:
                # Without a frequency source, we just have to hope any localizer signal is for this runway!
                approach_runway = landing_runway

            if approach_runway and approach_runway['localizer'].has_key('frequency'):
                if np.ma.count(ils_loc.array[_slice]) > 10:
                    if runway_change:
                        # We only use the first frequency tuned. This stops scanning across both runways if the pilot retunes.
                        loc_slice = shift_slices(runs_of_ones(np.ma.abs(ils_freq.array[_slice]-appr_ils_freq)<0.001),
                                                 _slice.start)[0]
                    else:
                        loc_slice = _slice
                else:
                    # No localizer or inadequate data for this approach.
                    loc_slice = None
            else:
                # The approach was to a runway without an ILS, so even if it was tuned, we ignore this.
                appr_ils_freq = None
                loc_slice = None

            if appr_ils_freq and loc_slice:
                if appr_ils_freq != approach_runway['localizer']['frequency']/1000.0:
                    loc_slice = None

            #######################################################################
            ## Identification of the period established on the localizer
            #######################################################################
                    
            loc_est = None
            if loc_slice:
                valid_range = np.ma.flatnotmasked_edges(ils_loc.array[_slice])
                # I have some data to scan. Shorthand names;
                loc_start = valid_range[0] + _slice.start
                loc_end = valid_range[1] + _slice.start
                scan_back = slice(ref_idx, loc_start, -1)

                # If we are turning in, we are not interested in signals that are not related to this approach.
                # The value of 45 deg was selected to encompass Washington National airport with a 40 deg offset.
                hdg_diff = np.ma.abs(np.ma.mod((hdg.array-lowest_hdg)+180.0, 360.0)-180.0)
                ils_hdg_45 = index_at_value(hdg_diff, 45.0, _slice=scan_back)
                
                # We are not interested above 1,500 ft, so may trim back the start point to that point:
                ils_alt_1500 = index_at_value(alt_aal.array, 1500.0, _slice=scan_back)
                
                # The criteria for start of established phase is the latter of the approach phase start, the turn-in or 1500ft.
                # The "or 0" allow for flights that do not turn through 45 deg or keep below 1500ft.
                loc_start = max(loc_start, ils_hdg_45 or 0, ils_alt_1500 or 0)

                if loc_start < ref_idx:
                    # Did I get established on the localizer, and if so,
                    # when? We only look AFTER the aircraft is already within
                    # 45deg of the runway heading, below 1500ft and the data
                    # is valid for this runway. Testing that the aircraft is
                    # not just passing across the localizer is built into the
                    # ils_established function.
                    loc_estab = ils_established(ils_loc.array, slice(loc_start, ref_idx), ils_loc.hz)
                else:
                    # If localiser start is after we touchdown bail.
                    loc_estab = None

                if loc_estab:
                    
                    # Refine the end of the localizer established phase...
                    if (approach_runway and approach_runway['localizer']['is_offset']):
                        offset_ils = True
                        # The ILS established phase ends when the deviation becomes large.
                        loc_end = ils_established(ils_loc.array, slice(ref_idx, loc_estab, -1), ils_loc.hz, point='immediate')

                    elif approach_type in ['TOUCH_AND_GO', 'GO_AROUND']:
                        # We finish at the lowest point
                        loc_end = ref_idx
                        
                    elif runway_change:
                        # Use the end of localizer phase as this already reflects the tuned frequency.
                        est_end = ils_established(ils_loc.array, slice(loc_estab, ref_idx), ils_loc.hz, point='end')
                        loc_end = min(loc_slice.stop, loc_end, est_end or np.inf)
    
                    elif approach_type == 'LANDING':
                        # Just end at 2 dots where we turn off the runway
                        loc_end_2_dots = index_at_value(np.ma.abs(ils_loc.array), 2.0, _slice=slice(loc_end, loc_estab, -1))
                        if loc_end_2_dots:
                            loc_end = loc_end_2_dots
                        
                    loc_est = slice(loc_estab, loc_end+1)

            #######################################################################
            ## Identification of the period established on the glideslope
            #######################################################################

            gs_est = None
            if loc_est and approach_runway.has_key('glideslope') and ils_gs:
                # We only look for glideslope established periods if the localizer is already established.

                # The range to scan for the glideslope starts with localizer capture and ends at
                # 200ft or the minimum height point for a go-around or the end of 
                # localizer established, if either is earlier.
                ils_gs_start = loc_estab
                ils_gs_200 = index_at_value(alt.array, 200.0, _slice=slice(loc_end, ils_gs_start, -1))
                # The expression "ils_gs_200 or np.inf" caters for the case where the aircraft did not pass
                # through 200ft, so the result is None, in which case any other value is left to be the minimum.
                ils_gs_end = min(ils_gs_200 or np.inf, ref_idx, loc_end)

                # Look for ten seconds within half a dot
                ils_gs_estab = ils_established(ils_gs.array, slice(ils_gs_start, ils_gs_end), ils_gs.hz)

                if ils_gs_estab:
                    gs_est = slice(ils_gs_estab, ils_gs_end+1)


            '''
            # These statements help set up test cases.
            print
            print airport['name']
            print approach_runway['identifier']
            print landing_runway['identifier']
            print _slice
            if loc_est:
                print 'Localizer established ', loc_est.start, loc_est.stop
            if gs_est:
                print 'Glideslope established ', gs_est.start, gs_est.stop
            print
            '''

            self.create_approach(
                approach_type,
                _slice,
                runway_change=runway_change,
                offset_ils=offset_ils,
                airport=airport,
                landing_runway=landing_runway,
                approach_runway=approach_runway,
                gs_est=gs_est,
                loc_est=loc_est,
                ils_freq=appr_ils_freq,
                turnoff=turnoff,
                lowest_lat=lowest_lat,
                lowest_lon=lowest_lon,
                lowest_hdg=lowest_hdg,
            )
 def derive(self,
            approaches=KPV('Approach Information'),
            land_afr_apt=App('AFR Landing Airport')):
     pass
Exemplo n.º 18
0
    def derive(self,
               app=S('Approach And Landing'),
               alt_aal=P('Altitude AAL'),
               fast=S('Fast'),
               land_hdg=KPV('Heading During Landing'),
               land_lat=KPV('Latitude At Touchdown'),
               land_lon=KPV('Longitude At Touchdown'),
               appr_hdg=KPV('Heading At Lowest Altitude During Approach'),
               appr_lat=KPV('Latitude At Lowest Altitude During Approach'),
               appr_lon=KPV('Longitude At Lowest Altitude During Approach'),
               loc_ests=S('ILS Localizer Established'),
               gs_ests=S('ILS Glideslope Established'),
               appr_ils_freq=KPV('ILS Frequency During Approach'),
               land_afr_apt=A('AFR Landing Airport'),
               land_afr_rwy=A('AFR Landing Runway'),
               precision=A('Precise Positioning'),
               turnoffs=KTI('Landing Turn Off Runway'),
               alt_agl=P('Altitude AGL'),
               ac_type=A('Aircraft Type')):
        precise = bool(getattr(precision, 'value', False))

        default_kwargs = {
            'precise': precise,
        }

        alt = alt_aal
        if ac_type == helicopter:
            alt = alt_agl
        app_slices = app.get_slices()

        for index, _slice in enumerate(app_slices):
            # a) The last approach is assumed to be landing:
            if index == len(app_slices) - 1:
                approach_type = 'LANDING'
                landing = True
            # b) We have a touch and go if Altitude AAL reached zero:
            elif np.ma.any(alt.array[_slice] <= 0):
                approach_type = 'TOUCH_AND_GO'
                landing = False
            # c) In any other case we have a go-around:
            else:
                approach_type = 'GO_AROUND'
                landing = False

            # Prepare arguments for looking up the airport and runway:
            kwargs = default_kwargs.copy()

            # Pass latitude, longitude and heading depending whether this
            # approach is a landing or not.
            #
            # If we are not landing, we go with the lowest point on approach.
            lat = land_lat if landing else appr_lat
            lon = land_lon if landing else appr_lon
            hdg = land_hdg if landing else appr_hdg

            lowest_lat_kpv = lat.get_first(
                within_slice=_slice) if lat else None
            lowest_lat = lowest_lat_kpv.value if lowest_lat_kpv else None
            lowest_lon_kpv = lon.get_first(
                within_slice=_slice) if lon else None
            lowest_lon = lowest_lon_kpv.value if lowest_lon_kpv else None
            # aded within_slice as we are not interested in the heading of
            # the first approach if there are multiple, we are using Approach
            # and Landing so heading on landing should fall within this slice
            lowest_hdg_kpv = hdg.get_first(
                within_slice=_slice) if hdg else None
            lowest_hdg = lowest_hdg_kpv.value if lowest_hdg_kpv else None

            kwargs.update(
                lowest_lat=lowest_lat,
                lowest_lon=lowest_lon,
                lowest_hdg=lowest_hdg,
                _slice=_slice,
            )

            # If the approach is a landing, pass through information from the
            # achieved flight record in case we cannot determine airport and
            # runway:
            if landing:
                kwargs.update(
                    land_afr_apt=land_afr_apt,
                    land_afr_rwy=land_afr_rwy,
                    hint='landing',
                )

            # Prepare approach information and populate with airport and runway
            # via API calls:
            gs_est = None
            if gs_ests:
                # If the aircraft was established on the glideslope more than
                # once during this approach, we take the last segment for the
                # approach as this is must be the one that included (or was
                # closest to) the landing
                gs_est = gs_ests.get_last(within_slice=_slice,
                                          within_use='any')
                if gs_est:
                    gs_est = gs_est.slice
            loc_est = None
            if loc_ests:
                # As for the glidepath, we take the last localizer phase as
                # this avoids working off an earlier segment if the aircraft
                # carries out a teardrop approach. See approach plate for
                # TOS 19 when approaching from the South.
                loc_est = loc_ests.get_last(within_slice=_slice,
                                            within_use='any')
                if loc_est:
                    loc_est = loc_est.slice

            # Add further details to save hunting when we need them later.
            ils_freq_kpv = (appr_ils_freq.get_last(
                within_slice=_slice) if appr_ils_freq else None)
            ils_freq = ils_freq_kpv.value if ils_freq_kpv else None
            kwargs['appr_ils_freq'] = ils_freq

            turnoff_kpv = (turnoffs.get_first(
                within_slice=_slice) if turnoffs else None)
            turnoff = turnoff_kpv.index if turnoff_kpv else None

            airport, runway = self._lookup_airport_and_runway(**kwargs)
            self.create_approach(approach_type,
                                 _slice,
                                 airport=airport,
                                 runway=runway,
                                 gs_est=gs_est,
                                 loc_est=loc_est,
                                 ils_freq=ils_freq,
                                 turnoff=turnoff,
                                 lowest_lat=lowest_lat,
                                 lowest_lon=lowest_lon,
                                 lowest_hdg=lowest_hdg)
 def derive(self, liftoff_gross_weight=KPV('Gross Weight At Liftoff')):
     pass
 def derive(self,
            afr_landing_fuel=A('AFR Landing Fuel'),
            touchdown_fuel_qty=KPV('Fuel Qty At Touchdown')):
     pass
Exemplo n.º 21
0
    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 = toff_fdr_apt.value  # FIXME
        except AttributeError:
            self.warning('Invalid airport... Fallback to AFR Takeoff Runway.')
            fallback = True
        else:
            if airport is None:
                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')

            runway = nearest_runway(airport, heading, **kwargs)
            if not runway:
                msg = 'No runway found for airport #%d @ %03.1f deg with %s.'
                self.warning(msg, airport['id'], 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['id'], 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,
            afr_takeoff_fuel=A('AFR Takeoff Fuel'),
            liftoff_fuel_qty=KPV('Fuel Qty At Liftoff')):
     pass