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)
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)
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)
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')
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)
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'), ])
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)
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')
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)
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
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
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
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