Пример #1
0
def geo_locate(hdf, items):
    '''
    Translate KeyTimeInstance into GeoKeyTimeInstance namedtuples
    '''
    if 'Latitude Smoothed' not in hdf.valid_param_names() \
       or 'Longitude Smoothed' not in hdf.valid_param_names():
        logger.warning("Could not geo-locate as either 'Latitude Smoothed' or "
                       "'Longitude Smoothed' were not found within the hdf.")
        return items
    
    lat_hdf = hdf['Latitude Smoothed']
    lon_hdf = hdf['Longitude Smoothed']
    
    if (not lat_hdf.array.count()) or (not lon_hdf.array.count()):
        logger.warning("Could not geo-locate as either 'Latitude Smoothed' or "
                       "'Longitude Smoothed' have no unmasked values.")
        return items
    
    lat_pos = derived_param_from_hdf(lat_hdf)
    lon_pos = derived_param_from_hdf(lon_hdf)
    
    # We want to place start of flight and end of flight markers at the ends
    # of the data which may extend more than REPAIR_DURATION seconds beyond
    # the end of the valid data. Hence by setting this to None and
    # extrapolate=True we achieve this goal.
    lat_pos.array = repair_mask(lat_pos.array, repair_duration=None, extrapolate=True)
    lon_pos.array = repair_mask(lon_pos.array, repair_duration=None, extrapolate=True)
    
    for item in itertools.chain.from_iterable(six.itervalues(items)):
        item.latitude = lat_pos.at(item.index) or None
        item.longitude = lon_pos.at(item.index) or None
    return items
 def derive(self,
            gl=M('Gear (L) Position'),
            gn=M('Gear (N) Position'),
            gr=M('Gear (R) Position'),
            gc=M('Gear (C) Position')):
     up_state = vstack_params_where_state(
         (gl, 'Up'),
         (gn, 'Up'),
         (gr, 'Up'),
         (gc, 'Up'),
     ).all(axis=0)
     down_state = vstack_params_where_state(
         (gl, 'Down'),
         (gn, 'Down'),
         (gr, 'Down'),
         (gc, 'Down'),
     ).all(axis=0)
     transit_state = vstack_params_where_state(
         (gl, 'In Transit'),
         (gn, 'In Transit'),
         (gr, 'In Transit'),
         (gc, 'In Transit'),
     ).any(axis=0)
     param = first_valid_parameter(gl, gn, gr, gc)
     self.array = np_ma_masked_zeros_like(param.array)
     self.array[repair_mask(up_state, repair_duration=None)] = 'Up'
     self.array[repair_mask(down_state, repair_duration=None)] = 'Down'
     self.array[repair_mask(transit_state,
                            repair_duration=None)] = 'In Transit'
     self.array = nearest_neighbour_mask_repair(self.array)
Пример #3
0
def geo_locate(hdf, items):
    '''
    Translate KeyTimeInstance into GeoKeyTimeInstance namedtuples
    '''
    if 'Latitude Smoothed' not in hdf.valid_param_names() \
       or 'Longitude Smoothed' not in hdf.valid_param_names():
        logger.warning("Could not geo-locate as either 'Latitude Smoothed' or "
                       "'Longitude Smoothed' were not found within the hdf.")
        return items
    
    lat_hdf = hdf['Latitude Smoothed']
    lon_hdf = hdf['Longitude Smoothed']
    
    if (not lat_hdf.array.count()) or (not lon_hdf.array.count()):
        logger.warning("Could not geo-locate as either 'Latitude Smoothed' or "
                       "'Longitude Smoothed' have no unmasked values.")
        return items
    
    lat_pos = derived_param_from_hdf(lat_hdf)
    lon_pos = derived_param_from_hdf(lon_hdf)
    
    # We want to place start of flight and end of flight markers at the ends
    # of the data which may extend more than REPAIR_DURATION seconds beyond
    # the end of the valid data. Hence by setting this to None and
    # extrapolate=True we achieve this goal.
    lat_pos.array = repair_mask(lat_pos.array, repair_duration=None, extrapolate=True)
    lon_pos.array = repair_mask(lon_pos.array, repair_duration=None, extrapolate=True)
    
    for item in itertools.chain.from_iterable(items.itervalues()):
        item.latitude = lat_pos.at(item.index) or None
        item.longitude = lon_pos.at(item.index) or None
    return items
Пример #4
0
    def derive(self,
               src_1=P('Latitude (1)'),
               src_2=P('Latitude (2)'),
               src_3=P('Latitude (3)')):

        sources = [
            deepcopy(source) for source in [src_1, src_2, src_3] if source is not None \
            and np.count_nonzero(np.ma.compressed(source.array)) > len(source.array)/2
        ]
        for source in sources:
            source.array = repair_mask(source.array)

        if len(sources) == 1:
            self.offset = sources[0].offset
            self.frequency = sources[0].frequency
            self.array = sources[0].array

        elif len(sources) == 2:
            self.array, self.frequency, self.offset = blend_two_parameters(
                sources[0], sources[1])

        elif len(sources) > 2:
            self.offset = 0.0
            self.frequency = 1.0
            self.array = blend_parameters(sources,
                                          offset=self.offset,
                                          frequency=self.frequency)
        else:
            self.array = np_ma_masked_zeros_like(src_1.array)
    def derive(self,
               src_1=P('Longitude (1)'),
               src_2=P('Longitude (2)'),
               src_3=P('Longitude (3)')):

        sources = [
            source for source in [src_1, src_2, src_3] if source is not None \
            and np.count_nonzero(source.array) > len(source.array)/2
        ]
        if len(sources) > 1:
            for source in sources:
                source.array = repair_mask(
                    straighten_longitude(source.array) + 180.0
                )

        if len(sources) == 1:
            self.offset = sources[0].offset
            self.frequency = sources[0].frequency
            self.array = sources[0].array

        elif len(sources) == 2:
            blended, self.frequency, self.offset = blend_two_parameters(
                sources[0], sources[1]
            )
            self.array = blended % 360 - 180.0

        elif len(sources) > 2:
            self.offset = 0.0
            self.frequency = 1.0
            blended = blend_parameters(sources, offset=self.offset,
                                       frequency=self.frequency)
            self.array = blended % 360 - 180.0
Пример #6
0
    def derive(self,
               src_1=P('Longitude (1)'),
               src_2=P('Longitude (2)'),
               src_3=P('Longitude (3)')):

        sources = [
            source for source in [src_1, src_2, src_3] if source is not None \
            and np.count_nonzero(source.array) > len(source.array)/2
        ]
        if len(sources) > 1:
            for source in sources:
                source.array = repair_mask(
                    straighten_longitude(source.array) + 180.0)

        if len(sources) == 1:
            self.offset = sources[0].offset
            self.frequency = sources[0].frequency
            self.array = sources[0].array

        elif len(sources) == 2:
            blended, self.frequency, self.offset = blend_two_parameters(
                sources[0], sources[1])
            self.array = blended % 360 - 180.0

        elif len(sources) > 2:
            self.offset = 0.0
            self.frequency = 1.0
            blended = blend_parameters(sources,
                                       offset=self.offset,
                                       frequency=self.frequency)
            self.array = blended % 360 - 180.0

        else:
            self.array = np_ma_masked_zeros_like(src_1.array)
def geo_locate(hdf, items):
    '''
    Translate KeyTimeInstance into GeoKeyTimeInstance namedtuples
    '''
    if 'Latitude Smoothed' not in hdf.valid_param_names() \
       or 'Longitude Smoothed' not in hdf.valid_param_names():
        logger.warning("Could not geo-locate as either 'Latitude Smoothed' or "
                       "'Longitude Smoothed' were not found within the hdf.")
        return items

    lat_pos = derived_param_from_hdf(hdf['Latitude Smoothed'])
    lon_pos = derived_param_from_hdf(hdf['Longitude Smoothed'])
    lat_rep = repair_mask(lat_pos, extrapolate=True)
    lon_rep = repair_mask(lon_pos, extrapolate=True)
    for item in items:
        item.latitude = lat_rep.at(item.index) or None
        item.longitude = lon_rep.at(item.index) or None
    return items
Пример #8
0
def geo_locate(hdf, items):
    '''
    Translate KeyTimeInstance into GeoKeyTimeInstance namedtuples
    '''
    if 'Latitude Smoothed' not in hdf.valid_param_names() \
       or 'Longitude Smoothed' not in hdf.valid_param_names():
        logger.warning("Could not geo-locate as either 'Latitude Smoothed' or "
                       "'Longitude Smoothed' were not found within the hdf.")
        return items

    lat_pos = derived_param_from_hdf(hdf['Latitude Smoothed'])
    lon_pos = derived_param_from_hdf(hdf['Longitude Smoothed'])
    lat_pos.array = repair_mask(lat_pos.array, extrapolate=True)
    lon_pos.array = repair_mask(lon_pos.array, extrapolate=True)
    for item in items:
        item.latitude = lat_pos.at(item.index) or None
        item.longitude = lon_pos.at(item.index) or None
    return items
 def derive(self, airspeed=P('Airspeed True'), grounded=S('Grounded')):
     for section in grounded:  # zero out travel on the ground
         airspeed.array[section.slice] = 0.0  # this is already a copy
     repaired_array = repair_mask(
         airspeed.array)  # to avoid integration hiccups
     adist = integrate(repaired_array,
                       airspeed.frequency,
                       scale=1.0 / 3600.0)
     self.array = adist
def _rate_of_turn(heading):
    '''
    Create rate of turn from heading.

    :param heading: Heading parameter.
    :type heading: Parameter
    '''
    heading.array = repair_mask(straighten_headings(heading.array),
                                repair_duration=None)
    rate_of_turn = np.ma.abs(rate_of_change(heading, 8))
    rate_of_turn_masked = \
        np.ma.masked_greater(rate_of_turn,
                             settings.HEADING_RATE_SPLITTING_THRESHOLD)
    return rate_of_turn_masked
def _rate_of_turn(heading):
    '''
    Create rate of turn from heading.

    :param heading: Heading parameter.
    :type heading: Parameter
    '''
    heading.array = repair_mask(straighten_headings(heading.array),
                                repair_duration=None)
    rate_of_turn = np.ma.abs(rate_of_change(heading, 8))
    rate_of_turn_masked = \
        np.ma.masked_greater(rate_of_turn,
                             settings.HEADING_RATE_SPLITTING_THRESHOLD)
    return rate_of_turn_masked
Пример #12
0
    def derive(self,
               alt_agl=P('Altitude AGL'),
               airs=S('Airborne'),
               gspd=P('Groundspeed'),
               trans_hfs=S('Transition Hover To Flight'),
               trans_fhs=S('Transition Flight To Hover')):

        low_flights = []
        hovers = []

        for air in airs:
            lows = slices_below(alt_agl.array[air.slice],
                                HOVER_HEIGHT_LIMIT)[1]
            for low in lows:
                if np.ma.min(alt_agl.array[shift_slice(
                        low, air.slice.start)]) <= HOVER_MIN_HEIGHT:
                    low_flights.extend([shift_slice(low, air.slice.start)])

        repaired_gspd = repair_mask(gspd.array,
                                    frequency=gspd.hz,
                                    repair_duration=8,
                                    method='fill_start')

        slows = slices_below(repaired_gspd, HOVER_GROUNDSPEED_LIMIT)[1]
        low_flights = slices_and(low_flights, slows)
        # Remove periods identified already as transitions.
        for low_flight in low_flights:
            if trans_fhs:
                for trans_fh in trans_fhs:
                    if slices_overlap(low_flight, trans_fh.slice):
                        low_flight = slice(trans_fh.slice.stop,
                                           low_flight.stop)

            if trans_hfs:
                for trans_hf in trans_hfs:
                    if slices_overlap(low_flight, trans_hf.slice):
                        low_flight = slice(low_flight.start,
                                           trans_hf.slice.start)

            hovers.extend([low_flight])

        # Exclude transition periods and trivial periods of operation.
        self.create_phases(
            filter_slices_duration(hovers,
                                   HOVER_MIN_DURATION,
                                   frequency=alt_agl.frequency))
    def derive(self, alt_agl=P('Altitude AGL'),
               airs=S('Airborne'),
               gspd=P('Groundspeed'),
               trans_hfs=S('Transition Hover To Flight'),
               trans_fhs=S('Transition Flight To Hover')):

        low_flights = []
        hovers = []

        for air in airs:
            lows = slices_below(alt_agl.array[air.slice], HOVER_HEIGHT_LIMIT)[1]
            for low in lows:
                if np.ma.min(alt_agl.array[shift_slice(low, air.slice.start)]) <= HOVER_MIN_HEIGHT:
                    low_flights.extend([shift_slice(low, air.slice.start)])

        repaired_gspd = repair_mask(gspd.array, frequency=gspd.hz,
                                    repair_duration=8, method='fill_start')

        slows = slices_below(repaired_gspd, HOVER_GROUNDSPEED_LIMIT)[1]
        low_flights = slices_and(low_flights, slows)
        # Remove periods identified already as transitions.
        for low_flight in low_flights:
            if trans_fhs:
                for trans_fh in trans_fhs:
                    if slices_overlap(low_flight, trans_fh.slice):
                        low_flight = slice(trans_fh.slice.stop, low_flight.stop)

            if trans_hfs:
                for trans_hf in trans_hfs:
                    if slices_overlap(low_flight, trans_hf.slice):
                        low_flight = slice(low_flight.start, trans_hf.slice.start)

            hovers.extend([low_flight])

        # Exclude transition periods and trivial periods of operation.
        self.create_phases(filter_slices_duration(hovers, HOVER_MIN_DURATION, frequency=alt_agl.frequency))
 def derive(self, airspeed=P('Airspeed True'), grounded=S('Grounded') ):
     for section in grounded:                      # zero out travel on the ground
         airspeed.array[section.slice]=0.0         # this is already a copy 
     repaired_array = repair_mask(airspeed.array)  # to avoid integration hiccups 
     adist      = integrate( repaired_array, airspeed.frequency, scale=1.0/3600.0 )
     self.array = adist
Пример #15
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,
            )
Пример #16
0
    def derive(
        self,
        air_spd=P('Airspeed'),
        flap=P('Flap'),
        #conf=P('Configuration'),
        gw=P('Gross Weight Smoothed'),
        touchdowns=KTI('Touchdown'),
        series=A('Series'),
        family=A('Family'),
        engine=A('Engine Series'),
        engine_type=A('Engine Type'),
        eng_np=P('Eng (*) Np Avg'),
        vref=P('Vref'),
        afr_vref=A('AFR Vref'),
        approaches=S('Approach And Landing')):

        if vref:
            # Use recorded Vref parameter:
            self.array = vref.array
        elif afr_vref:
            # Use provided Vref from achieved flight record:
            afr_vspeed = afr_vref
            self.array = np.ma.zeros(len(air_spd.array), np.double)
            self.array.mask = True
            for approach in approaches:
                self.array[approach.slice] = afr_vspeed.value
        else:
            # Use speed card lookups

            x = map(lambda x: x.value
                    if x else None, (series, family, engine, engine_type))

            vspeed_class_test = get_vspeed_map_mitre(*x)

            if vspeed_class_test:
                vspeed_class = vspeed_class_test
            else:
                vspeed_class = get_vspeed_map(*x)

            if gw is not None:  # and you must have eng_np
                try:
                    # Allow up to 2 superframe values to be repaired:
                    # (64 * 2 = 128 + a bit)
                    repaired_gw = repair_mask(gw.array,
                                              repair_duration=130,
                                              copy=True,
                                              extrapolate=True)
                except:
                    self.warning(
                        "'Airspeed Reference' will be fully masked "
                        "because 'Gross Weight Smoothed' array could not be "
                        "repaired.")
                    return

                setting_param = flap  #or conf
                vspeed_table = vspeed_class()
                for approach in approaches:
                    _slice = approach.slice
                    '''TODO: Only uses max Vref setting, doesn't account for late config changes'''
                    index = np.ma.argmax(setting_param.array[_slice])
                    setting = setting_param.array[_slice][index]
                    weight = repaired_gw[_slice][
                        index] if gw is not None else None
                    if setting in vspeed_table.vref_settings:
                        ##and is_index_within_slice(touchdowns.get_last().index, _slice):
                        # setting in vspeed table:
                        vspeed = vspeed_table.vref(setting, weight)
                    else:
                        ''' Do not like the default of using max Vref for go arounds... '''
                        ## No landing and max setting not in vspeed table:
                        #if setting_param.name == 'Flap':
                        #setting = max(get_flap_map(series.value, family.value))
                        #else:
                        #setting = max(get_conf_map(series.value, family.value).keys())
                        #vspeed = vspeed_table.vref(setting, weight)
                        self.warning(
                            "'Airspeed Reference' will be fully masked "
                            "because Vref lookup table does not have corresponding values."
                        )
                        return
                    self.array = np_ma_masked_zeros_like(air_spd.array)
                    self.array[_slice] = vspeed
 def derive(self, nr=P('Nr')):
     self.array = np.ma.where(
         repair_mask(nr.array) > ROTORS_TURNING, 'Running', 'Not Running')
Пример #18
0
    def derive(self,
               air_spd=P('Airspeed'),
               flap=P('Flap'),
               #conf=P('Configuration'),
               gw=P('Gross Weight Smoothed'),
               touchdowns=KTI('Touchdown'),
               series=A('Series'),
               family=A('Family'),
               engine=A('Engine Series'),
               engine_type=A('Engine Type'),
               eng_np=P('Eng (*) Np Avg'),
               vref=P('Vref'),
               afr_vref=A('AFR Vref'),
               approaches=S('Approach And Landing')):

        if vref:
            # Use recorded Vref parameter:
            self.array=vref.array
        elif afr_vref:
            # Use provided Vref from achieved flight record:
            afr_vspeed = afr_vref
            self.array = np.ma.zeros(len(air_spd.array), np.double)
            self.array.mask = True
            for approach in approaches:
                self.array[approach.slice] = afr_vspeed.value
        else:
            # Use speed card lookups
            

            x = map(lambda x: x.value if x else None, (series, family, engine, engine_type))

            vspeed_class_test = get_vspeed_map_mitre(*x)
            
            if vspeed_class_test:
                vspeed_class = vspeed_class_test
            else:
                vspeed_class = get_vspeed_map(*x)

                        
            
            
            if gw is not None:  # and you must have eng_np
                try:
                    # Allow up to 2 superframe values to be repaired:
                    # (64 * 2 = 128 + a bit)
                    repaired_gw = repair_mask(gw.array, repair_duration=130,
                                              copy=True, extrapolate=True)
                except:
                    self.warning("'Airspeed Reference' will be fully masked "
                                 "because 'Gross Weight Smoothed' array could not be "
                                 "repaired.")
                    return

                setting_param = flap #or conf
                vspeed_table = vspeed_class()
                for approach in approaches:
                    _slice = approach.slice
                    '''TODO: Only uses max Vref setting, doesn't account for late config changes'''
                    index = np.ma.argmax(setting_param.array[_slice])
                    setting = setting_param.array[_slice][index]
                    weight = repaired_gw[_slice][index] if gw is not None else None
                    if setting in vspeed_table.vref_settings:
                       ##and is_index_within_slice(touchdowns.get_last().index, _slice):
                        # setting in vspeed table:
                        vspeed = vspeed_table.vref(setting, weight)
                    else:
                        ''' Do not like the default of using max Vref for go arounds... '''
                        ## No landing and max setting not in vspeed table:
                        #if setting_param.name == 'Flap':
                            #setting = max(get_flap_map(series.value, family.value))
                        #else:
                            #setting = max(get_conf_map(series.value, family.value).keys())
                            #vspeed = vspeed_table.vref(setting, weight)
                        self.warning("'Airspeed Reference' will be fully masked "
                                 "because Vref lookup table does not have corresponding values.")
                        return
                    self.array = np_ma_masked_zeros_like(air_spd.array)
                    self.array[_slice] = vspeed
 def derive(self, nr=P('Nr')):
     self.array = np.ma.where(repair_mask(nr.array) > ROTORS_TURNING, 'Running', 'Not Running')
def track_to_kml(hdf_path, kti_list, kpv_list, approach_list,
                 plot_altitude=None, dest_path=None):
    '''
    Plot results of process_flight onto a KML track.

    :param flight_attrs: List of Flight Attributes
    :type flight_attrs: list
    :param plot_altitude: Name of Altitude parameter to use in KML
    :type plot_altitude: String
    '''
    one_hz = Parameter()
    kml = simplekml.Kml()
    with hdf_file(hdf_path) as hdf:
        # Latitude param, Longitude param, track name, colour
        coord_params = (
            {'lat': 'Latitude Smoothed',
             'lon': 'Longitude Smoothed',
             'track': 'Smoothed',
             'colour': 'ff7fff7f'},
            {'lat': 'Latitude Prepared',
             'lon': 'Longitude Prepared',
             'track': 'Prepared',
             'colour': 'ffA11EB3'},
            {'lat': 'Latitude',
             'lon': 'Longitude',
             'track': 'Recorded',
             'colour': 'ff0000ff'},
            {'lat': 'Latitude (Coarse)',
             'lon': 'Longitude (Coarse)',
             'track': 'Coarse',
             'colour': 'ff0000ff'},
        )
        altitude_absolute_params = ('Altitude Visualization With Ground Offset',
                                    'Altitude QNH', 'Altitude STD',
                                    'Altitude AAL')
        altitude_relative_params = ('Altitude Radio',)
        # Check latitude and longitude pair exist.
        if not any(c['lat'] in hdf and c['lon'] in hdf for c in coord_params):
            logger.error("Cannot write track as coordinate paarmeters not in hdf")
            return False
        # Choose best altitude parameter if not specified.
        if not plot_altitude:
            altitude_params = itertools.chain(altitude_absolute_params,
                                              altitude_relative_params)
            try:
                plot_altitude = next(p for p in altitude_params if p in hdf)
            except StopIteration:
                logger.warning("Disabling altitude on KML plot as it is "
                               "unavailable.")
        # Get altitude param from hdf.
        if plot_altitude and plot_altitude in hdf.keys():
            alt = derived_param_from_hdf(hdf[plot_altitude]).get_aligned(one_hz)
            alt.array = repair_mask(alt.array, frequency=alt.frequency, repair_duration=None)
            alt.array = ut.convert(alt.array, ut.FT, ut.METER)
        else:
            alt = None

        if plot_altitude in altitude_absolute_params:
            altitude_mode = simplekml.constants.AltitudeMode.absolute
        elif plot_altitude in altitude_relative_params:
            altitude_mode = simplekml.constants.AltitudeMode.relativetoground
        else:
            altitude_mode = simplekml.constants.AltitudeMode.clamptoground

        ## Get best latitude and longitude parameters.
        best_lat = None
        best_lon = None

        for coord_config in coord_params:
            lat_name = coord_config['lat']
            lon_name = coord_config['lon']
            if not lat_name in hdf or not lon_name in hdf:
                continue
            lat = hdf[lat_name]
            lon = hdf[lon_name]
            best = not best_lat or not best_lon
            add_track(kml, coord_config['track'], lat, lon,
                      coord_config['colour'], alt_param=alt,
                      alt_mode=altitude_mode,
                      visible=best)
            add_track(kml, coord_config['track'] + ' On Ground', lat, lon,
                      coord_config['colour'], visible=best)
            if best:
                best_lat = derived_param_from_hdf(lat).get_aligned(one_hz)
                best_lon = derived_param_from_hdf(lon).get_aligned(one_hz)

    # Add KTIs.
    for kti in kti_list:
        kti_point_values = {'name': kti.name}

        if not KEEP_KTIS and kti.name in SKIP_KTIS:
            continue
        elif len(KEEP_KTIS)>0 and (kti.name not in KEEP_KTIS):
            continue

        altitude = alt.at(kti.index) if alt else None
        kti_point_values['altitudemode'] = altitude_mode
        if altitude:
            kti_point_values['coords'] = ((kti.longitude, kti.latitude, altitude),)
        else:
            kti_point_values['coords'] = ((kti.longitude, kti.latitude),)
        kml.newpoint(**kti_point_values)

    # Add KPVs.
    for kpv in kpv_list:

        # Trap kpvs with invalid latitude or longitude data (normally happens
        # at the start of the data where accelerometer offsets are declared,
        # and this avoids casting kpvs into the Atlantic.
        kpv_lat = best_lat.at(kpv.index)
        kpv_lon = best_lon.at(kpv.index)
        if kpv_lat is None or kpv_lon is None or \
           (kpv_lat == 0.0 and kpv_lon == 0.0):
            continue

        if not KEEP_KPVS and kpv.name in SKIP_KPVS:
            continue
        elif len(KEEP_KPVS)>0 and (kpv.name not in KEEP_KPVS):
            continue

        style = simplekml.Style()
        style.iconstyle.color = simplekml.Color.red
        kpv_point_values = {'name': '%s (%.3f)' % (kpv.name, kpv.value)}
        altitude = alt.at(kpv.index) if alt else None
        kpv_point_values['altitudemode'] = altitude_mode
        if altitude:
            kpv_point_values['coords'] = ((kpv_lon, kpv_lat, altitude),)
        else:
            kpv_point_values['coords'] = ((kpv_lon, kpv_lat),)

        pnt = kml.newpoint(**kpv_point_values)
        pnt.style = style

    # Add approach centre lines.
    for app in approach_list:
        try:
            draw_centreline(kml, app.runway)
        except:
            pass

    if not dest_path:
        dest_path = hdf_path + ".kml"
    kml.save(dest_path)
    return dest_path
Пример #21
0
def track_to_kml(hdf_path, kti_list, kpv_list, approach_list,
                 plot_altitude='Altitude QNH', dest_path=None):
    '''
    Plot results of process_flight onto a KML track.
    
    :param flight_attrs: List of Flight Attributes
    :type flight_attrs: list
    :param plot_altitude: Name of Altitude parameter to use in KML
    :type plot_altitude: String
    '''
    one_hz = Parameter()
    kml = simplekml.Kml()
    with hdf_file(hdf_path) as hdf:
        if 'Latitude Smoothed' not in hdf:
            return False
        if plot_altitude not in hdf:
            logger.warning("Disabling altitude on KML plot as it is unavailable.")
            plot_altitude = False
        if plot_altitude:
            alt = derived_param_from_hdf(hdf[plot_altitude]).get_aligned(one_hz)
            alt.array = repair_mask(alt.array, frequency=alt.frequency, repair_duration=None) / METRES_TO_FEET
        else:
            alt = None
            
        if plot_altitude in ['Altitude QNH', 'Altitude AAL', 'Altitude STD']:
            altitude_mode = simplekml.constants.AltitudeMode.absolute
        elif plot_altitude in ['Altitude Radio']:
            altitude_mode = simplekml.constants.AltitudeMode.relativetoground
        else:
            altitude_mode = simplekml.constants.AltitudeMode.clamptoground
        
        # TODO: align everything to 0 offset
        smooth_lat = derived_param_from_hdf(hdf['Latitude Smoothed']).get_aligned(one_hz)
        smooth_lon = derived_param_from_hdf(hdf['Longitude Smoothed']).get_aligned(one_hz)
        add_track(kml, 'Smoothed', smooth_lat, smooth_lon, 'ff7fff7f', 
                  alt_param=alt, alt_mode=altitude_mode)
        add_track(kml, 'Smoothed On Ground', smooth_lat, smooth_lon, 'ff7fff7f')        
    
        if 'Latitude Prepared' in hdf and 'Longitude Prepared' in hdf:
            lat = hdf['Latitude Prepared']
            lon = hdf['Longitude Prepared']
            add_track(kml, 'Prepared Track', lat, lon, 'A11EB3', visible=False)
        
        if 'Latitude' in hdf and 'Longitude' in hdf:
            lat_r = hdf['Latitude']
            lon_r = hdf['Longitude']
            # add RAW track default invisible
            add_track(kml, 'Recorded Track', lat_r, lon_r, 'ff0000ff', visible=False)

    for kti in kti_list:
        kti_point_values = {'name': kti.name}
        if kti.name in SKIP_KTIS:
            continue
        
        altitude = alt.at(kti.index) if plot_altitude else None
        kti_point_values['altitudemode'] = altitude_mode
        if altitude:
            kti_point_values['coords'] = ((kti.longitude, kti.latitude, altitude),)
        else:
            kti_point_values['coords'] = ((kti.longitude, kti.latitude),)
        kml.newpoint(**kti_point_values)
        
    
    for kpv in kpv_list:

        # Trap kpvs with invalid latitude or longitude data (normally happens
        # at the start of the data where accelerometer offsets are declared,
        # and this avoids casting kpvs into the Atlantic.
        kpv_lat = smooth_lat.at(kpv.index)
        kpv_lon = smooth_lon.at(kpv.index)
        if kpv_lat == None or kpv_lon == None or \
           (kpv_lat == 0.0 and kpv_lon == 0.0):
            continue

        if kpv.name in SKIP_KPVS:
            continue
        
        style = simplekml.Style()
        style.iconstyle.color = simplekml.Color.red
        kpv_point_values = {'name': '%s (%.3f)' % (kpv.name, kpv.value)}
        altitude = alt.at(kpv.index) if plot_altitude else None
        kpv_point_values['altitudemode'] = altitude_mode
        if altitude:
            kpv_point_values['coords'] = ((kpv_lon, kpv_lat, altitude),)
        else:
            kpv_point_values['coords'] = ((kpv_lon, kpv_lat),)
        
        pnt = kml.newpoint(**kpv_point_values)
        pnt.style = style
    
    for app in approach_list:
        try:
            draw_centreline(kml, app.runway)
        except:
            pass
                

    if not dest_path:
        dest_path = hdf_path + ".kml"
    kml.save(dest_path)
    return
    def derive(self, alt_agl=P('Altitude AGL')):

        repair_array = repair_mask(alt_agl.array, repair_duration=None)
        hyst_array = hysteresis(repair_array, 10.0)
        self.array = np.ma.where(alt_agl.array > 10.0, hyst_array, repair_array)
Пример #23
0
def track_to_kml(hdf_path,
                 kti_list,
                 kpv_list,
                 approach_list,
                 plot_altitude=None,
                 dest_path=None):
    '''
    Plot results of process_flight onto a KML track.
    
    :param flight_attrs: List of Flight Attributes
    :type flight_attrs: list
    :param plot_altitude: Name of Altitude parameter to use in KML
    :type plot_altitude: String
    '''
    one_hz = Parameter()
    kml = simplekml.Kml()
    with hdf_file(hdf_path) as hdf:
        # Latitude param, Longitude param, track name, colour
        coord_params = (
            {
                'lat': 'Latitude Smoothed',
                'lon': 'Longitude Smoothed',
                'track': 'Smoothed',
                'colour': 'ff7fff7f'
            },
            {
                'lat': 'Latitude Prepared',
                'lon': 'Longitude Prepared',
                'track': 'Prepared',
                'colour': 'A11EB3'
            },
            {
                'lat': 'Latitude',
                'lon': 'Longitude',
                'track': 'Recorded',
                'colour': 'ff0000ff'
            },
            {
                'lat': 'Latitude (Coarse)',
                'lon': 'Longitude (Coarse)',
                'track': 'Coarse',
                'colour': 'ff0000ff'
            },
        )
        altitude_absolute_params = ('Altitude QNH', 'Altitude STD',
                                    'Altitude AAL')
        altitude_relative_params = ('Altitude Radio', )
        # Check latitude and longitude pair exist.
        if not any(c['lat'] in hdf and c['lon'] in hdf for c in coord_params):
            logger.error(
                "Cannot write track as coordinate paarmeters not in hdf")
            return False
        # Choose best altitude parameter if not specified.
        if not plot_altitude:
            altitude_params = itertools.chain(altitude_absolute_params,
                                              altitude_relative_params)
            try:
                plot_altitude = next(p for p in altitude_params if p in hdf)
            except StopIteration:
                logger.warning("Disabling altitude on KML plot as it is "
                               "unavailable.")
        # Get altitude param from hdf.
        if plot_altitude:
            alt = derived_param_from_hdf(
                hdf[plot_altitude]).get_aligned(one_hz)
            alt.array = repair_mask(alt.array,
                                    frequency=alt.frequency,
                                    repair_duration=None) / METRES_TO_FEET
        else:
            alt = None

        if plot_altitude in altitude_absolute_params:
            altitude_mode = simplekml.constants.AltitudeMode.absolute
        elif plot_altitude in altitude_relative_params:
            altitude_mode = simplekml.constants.AltitudeMode.relativetoground
        else:
            altitude_mode = simplekml.constants.AltitudeMode.clamptoground

        ## Get best latitude and longitude parameters.
        best_lat = None
        best_lon = None

        for coord_config in coord_params:
            lat_name = coord_config['lat']
            lon_name = coord_config['lon']
            if not lat_name in hdf or not lon_name in hdf:
                continue
            lat = hdf[lat_name]
            lon = hdf[lon_name]
            best = not best_lat or not best_lon
            add_track(kml,
                      coord_config['track'],
                      lat,
                      lon,
                      coord_config['colour'],
                      alt_param=alt,
                      alt_mode=altitude_mode,
                      visible=best)
            add_track(kml,
                      coord_config['track'] + ' On Ground',
                      lat,
                      lon,
                      coord_config['colour'],
                      visible=best)
            if best:
                best_lat = derived_param_from_hdf(lat).get_aligned(one_hz)
                best_lon = derived_param_from_hdf(lon).get_aligned(one_hz)

    # Add KTIs.
    for kti in kti_list:
        kti_point_values = {'name': kti.name}
        if kti.name in SKIP_KTIS:
            continue

        altitude = alt.at(kti.index) if plot_altitude else None
        kti_point_values['altitudemode'] = altitude_mode
        if altitude:
            kti_point_values['coords'] = ((kti.longitude, kti.latitude,
                                           altitude), )
        else:
            kti_point_values['coords'] = ((kti.longitude, kti.latitude), )
        kml.newpoint(**kti_point_values)

    # Add KPVs.
    for kpv in kpv_list:

        # Trap kpvs with invalid latitude or longitude data (normally happens
        # at the start of the data where accelerometer offsets are declared,
        # and this avoids casting kpvs into the Atlantic.
        kpv_lat = best_lat.at(kpv.index)
        kpv_lon = best_lon.at(kpv.index)
        if kpv_lat == None or kpv_lon == None or \
           (kpv_lat == 0.0 and kpv_lon == 0.0):
            continue

        if kpv.name in SKIP_KPVS:
            continue

        style = simplekml.Style()
        style.iconstyle.color = simplekml.Color.red
        kpv_point_values = {'name': '%s (%.3f)' % (kpv.name, kpv.value)}
        altitude = alt.at(kpv.index) if plot_altitude else None
        kpv_point_values['altitudemode'] = altitude_mode
        if altitude:
            kpv_point_values['coords'] = ((kpv_lon, kpv_lat, altitude), )
        else:
            kpv_point_values['coords'] = ((kpv_lon, kpv_lat), )

        pnt = kml.newpoint(**kpv_point_values)
        pnt.style = style

    # Add approach centre lines.
    for app in approach_list:
        try:
            draw_centreline(kml, app.runway)
        except:
            pass

    if not dest_path:
        dest_path = hdf_path + ".kml"
    kml.save(dest_path)
    return dest_path
Пример #24
0
    def derive(self,
               alt_rad=P('Altitude Radio'),
               alt_aal=P('Altitude AAL'),
               alt_baro=P('Altitude STD Smoothed'),
               gog=M('Gear On Ground')):

        # If we have no Altitude Radio we will have to fall back to Altitude AAL
        if not alt_rad:
            self.array = alt_aal.array
            return

        # When was the helicopter on the ground?
        gear_on_grounds = np.ma.clump_masked(np.ma.masked_equal(gog.array, 1))
        # Find and eliminate short spikes (15 seconds) as these are most likely errors.
        short_spikes = slices_find_small_slices(gear_on_grounds,
                                                time_limit=15,
                                                hz=gog.hz)
        for slice in short_spikes:
            gog.array[slice.start:slice.stop] = 0

        # Remove slices shorter than 15 seconds as these are most likely created in error.
        gear_on_grounds = slices_remove_small_slices(gear_on_grounds,
                                                     time_limit=15,
                                                     hz=gog.hz)
        # Compute the half period which we will need.
        hp = int(alt_rad.frequency * ALTITUDE_AGL_SMOOTHING) / 2
        # We force altitude AGL to be zero when the gear shows 'Ground' state
        alt_rad_repaired = repair_mask(alt_rad.array,
                                       frequency=alt_rad.frequency,
                                       repair_duration=20.0,
                                       extrapolate=True)
        alt_agl = moving_average(np.maximum(alt_rad.array, 0.0) *
                                 (1 - gog.array.data),
                                 window=hp * 2 + 1,
                                 weightings=None)

        # Refine the baro estimates
        length = len(alt_agl) - 1
        baro_sections = np.ma.clump_masked(
            np.ma.masked_greater(alt_agl, ALTITUDE_AGL_TRANS_ALT))
        for baro_section in baro_sections:
            begin = max(baro_section.start - 1, 0)
            end = min(baro_section.stop + 1, length)
            start_diff = alt_baro.array[begin] - alt_agl[begin]
            stop_diff = alt_baro.array[end] - alt_agl[end]
            if start_diff is not np.ma.masked and stop_diff is not np.ma.masked:
                diff = np.linspace(start_diff, stop_diff, end - begin - 2)
                alt_agl[begin + 1:end -
                        1] = alt_baro.array[begin + 1:end - 1] - diff
            elif start_diff is not np.ma.masked:
                alt_agl[begin + 1:end -
                        1] = alt_baro.array[begin + 1:end - 1] - start_diff
            elif stop_diff is not np.ma.masked:
                alt_agl[begin + 1:end -
                        1] = alt_baro.array[begin + 1:end - 1] - stop_diff
            else:
                pass
        low_sections = np.ma.clump_unmasked(np.ma.masked_greater(alt_agl, 5))
        for both in slices_and(low_sections, gear_on_grounds):
            alt_agl[both] = 0.0
        '''
        # Quick visual check of the altitude agl.
        import matplotlib.pyplot as plt
        plt.plot(alt_baro.array, 'y-')
        plt.plot(alt_rad.array, 'r-')
        plt.plot(alt_agl, 'b-')
        plt.show()
        '''

        self.array = alt_agl
Пример #25
0
    def derive(self, alt_agl=P('Altitude AGL')):

        repair_array = repair_mask(alt_agl.array, repair_duration=None)
        hyst_array = hysteresis(repair_array, 10.0)
        self.array = np.ma.where(alt_agl.array > 10.0, hyst_array,
                                 repair_array)
def split_segments(hdf, aircraft_info):
    '''
    TODO: DJ suggested not to use decaying engine oil temperature.

    Notes:
     * We do not want to split on masked superframe data if mid-flight (e.g.
       short section of corrupt data) - repair_mask without defining
       repair_duration should fix that.
     * Use turning alongside engine parameters to ensure there is no movement?
     XXX: Beware of pre-masked minimums to ensure we don't split on padded
     superframes

    TODO: Use L3UQAR num power ups for difficult cases?
    '''

    segments = []
    speed, thresholds = _get_speed_parameter(hdf, aircraft_info)

    # Look for heading first
    try:
        # Fetch Heading if available
        heading = hdf.get_param('Heading', valid_only=True)
    except KeyError:
        # try Heading True, otherwise fail loudly with a KeyError
        heading = hdf.get_param('Heading True', valid_only=True)

    eng_arrays, _ = _get_eng_params(hdf, align_param=heading)

    # Look for speed
    try:
        speed_array = repair_mask(speed.array,
                                  repair_duration=None,
                                  repair_above=thresholds['speed_threshold'])
    except ValueError:
        # speed array is masked, most likely under min threshold so it did
        # not go fast.
        logger.warning("speed is entirely masked. The entire contents of "
                       "the data will be a GROUND_ONLY slice.")
        return [
            _segment_type_and_slice(speed.array, speed.frequency,
                                    heading.array, heading.frequency, 0,
                                    hdf.duration, eng_arrays, aircraft_info,
                                    thresholds, hdf)
        ]

    speed_secs = len(speed_array) / speed.frequency

    # if Segment Split parameter is in hdf file someone has already done the hard work for us
    if 'Segment Split' in hdf:
        seg_split = hdf['Segment Split']
        split_flags = np.ma.where(seg_split.array == 'Split')
        start = 0

        if split_flags:
            for split_idx in split_flags[0]:
                split_idx = split_idx / seg_split.frequency
                segments.append(
                    _segment_type_and_slice(speed_array, speed.frequency,
                                            heading.array, heading.frequency,
                                            start, split_idx, eng_arrays,
                                            aircraft_info, thresholds, hdf))
                start = split_idx
                logger.info("Split Flag found at at index '%d'.", split_idx)
            # Add remaining data to a segment.
            segments.append(
                _segment_type_and_slice(speed_array, speed.frequency,
                                        heading.array, heading.frequency,
                                        start, speed_secs, eng_arrays,
                                        aircraft_info, thresholds, hdf))
        else:
            # if no split flags use whole file.
            logger.info(
                "'Segment Split' found but no Splits found, using whole file.")
            segments.append(
                _segment_type_and_slice(speed_array, speed.frequency,
                                        heading.array, heading.frequency,
                                        start, speed_secs, eng_arrays,
                                        aircraft_info, thresholds, hdf))
        return segments

    slow_array = np.ma.masked_less_equal(speed_array,
                                         thresholds['speed_threshold'])

    speedy_slices = np.ma.clump_unmasked(slow_array)
    if len(speedy_slices) <= 1:
        logger.info(
            "There are '%d' sections of data where speed is "
            "above the splitting threshold. Therefore there can only "
            "be at maximum one flights worth of data. Creating a "
            "single segment comprising all data.", len(speedy_slices))
        # Use the first and last available unmasked values to determine segment
        # type.
        return [
            _segment_type_and_slice(speed_array, speed.frequency,
                                    heading.array, heading.frequency, 0,
                                    speed_secs, eng_arrays, aircraft_info,
                                    thresholds, hdf)
        ]

    # suppress transient changes in speed around 80 kts
    slow_slices = slices_remove_small_slices(np.ma.clump_masked(slow_array),
                                             10, speed.frequency)

    rate_of_turn = _rate_of_turn(heading)

    split_params_min, split_params_frequency \
        = _get_normalised_split_params(hdf)

    if hdf.reliable_frame_counter:
        dfc = hdf['Frame Counter']
        dfc_diff = np.ma.diff(dfc.array)
        # Mask 'Frame Counter' incrementing by 1.
        dfc_diff = np.ma.masked_equal(dfc_diff, 1)
        # Mask 'Frame Counter' overflow where the Frame Counter transitions
        # from 4095 to 0.
        # Q: This used to be 4094, are there some Frame Counters which
        # increment from 1 rather than 0 or something else?
        dfc_diff = np.ma.masked_equal(dfc_diff, -4095)
        # Gap between difference values.
        dfc_half_period = (1 / dfc.frequency) / 2
    else:
        logger.info("'Frame Counter' will not be used for splitting since "
                    "'reliable_frame_counter' is False.")
        dfc = None

    start = 0
    last_fast_index = None
    for slow_slice in slow_slices:
        if slow_slice.start == 0:
            # Do not split if slow_slice is at the beginning of the data.
            # Since we are working with masked slices, masked padded superframe
            # data will be included within the first slow_slice.
            continue
        if slow_slice.stop == len(speed_array):
            # After the loop we will add the remaining data to a segment.
            break

        if last_fast_index is not None:
            fast_duration = (slow_slice.start -
                             last_fast_index) / speed.frequency
            if fast_duration < settings.MINIMUM_FAST_DURATION:
                logger.info("Disregarding short period of fast speed %s",
                            fast_duration)
                continue

        # Get start and stop at 1Hz.
        slice_start_secs = slow_slice.start / speed.frequency
        slice_stop_secs = slow_slice.stop / speed.frequency

        slow_duration = slice_stop_secs - slice_start_secs
        if slow_duration < thresholds['min_split_duration']:
            logger.info(
                "Disregarding period of speed below '%s' "
                "since '%s' is shorter than MINIMUM_SPLIT_DURATION "
                "('%s').", thresholds['speed_threshold'], slow_duration,
                thresholds['min_split_duration'])
            continue

        last_fast_index = slow_slice.stop

        # Find split based on minimum of engine parameters.
        if split_params_min is not None:
            eng_split_index, eng_split_value = _split_on_eng_params(
                slice_start_secs, slice_stop_secs, split_params_min,
                split_params_frequency)
        else:
            eng_split_index, eng_split_value = None, None

        # Split using 'Frame Counter'.
        if dfc is not None:
            dfc_split_index = _split_on_dfc(slice_start_secs,
                                            slice_stop_secs,
                                            dfc.frequency,
                                            dfc_half_period,
                                            dfc_diff,
                                            eng_split_index=eng_split_index)
            if dfc_split_index:
                segments.append(
                    _segment_type_and_slice(speed_array, speed.frequency,
                                            heading.array, heading.frequency,
                                            start, dfc_split_index, eng_arrays,
                                            aircraft_info, thresholds, hdf))
                start = dfc_split_index
                logger.info(
                    "'Frame Counter' jumped within slow_slice '%s' "
                    "at index '%d'.", slow_slice, dfc_split_index)
                continue
            else:
                logger.info(
                    "'Frame Counter' did not jump within slow_slice "
                    "'%s'.", slow_slice)

        # Split using minimum of engine parameters.
        if eng_split_value is not None and \
           eng_split_value < settings.MINIMUM_SPLIT_PARAM_VALUE:
            logger.info(
                "Minimum of normalised split parameters ('%s') was "
                "below  ('%s') within "
                "slow_slice '%s' at index '%d'.", eng_split_value,
                settings.MINIMUM_SPLIT_PARAM_VALUE, slow_slice,
                eng_split_index)
            segments.append(
                _segment_type_and_slice(speed_array, speed.frequency,
                                        heading.array, heading.frequency,
                                        start, eng_split_index, eng_arrays,
                                        aircraft_info, thresholds, hdf))
            start = eng_split_index
            continue
        else:
            logger.info(
                "Minimum of normalised split parameters ('%s') was "
                "not below MINIMUM_SPLIT_PARAM_VALUE ('%s') within "
                "slow_slice '%s' at index '%s'.", eng_split_value,
                settings.MINIMUM_SPLIT_PARAM_VALUE, slow_slice,
                eng_split_index)

        # Split using rate of turn. Q: Should this be considered in other
        # splitting methods.
        if rate_of_turn is None:
            continue

        rot_split_index = _split_on_rot(slice_start_secs, slice_stop_secs,
                                        heading.frequency, rate_of_turn)
        if rot_split_index:
            segments.append(
                _segment_type_and_slice(speed_array, speed.frequency,
                                        heading.array, heading.frequency,
                                        start, rot_split_index, eng_arrays,
                                        aircraft_info, thresholds, hdf))
            start = rot_split_index
            logger.info(
                "Splitting at index '%s' where rate of turn was below "
                "'%s'.", rot_split_index,
                settings.HEADING_RATE_SPLITTING_THRESHOLD)
            continue
        else:
            logger.info(
                "Aircraft did not stop turning during slow_slice "
                "('%s'). Therefore a split will not be made.", slow_slice)

        #Q: Raise error here?
        logger.warning(
            "Splitting methods failed to split within slow_slice "
            "'%s'.", slow_slice)

    # Add remaining data to a segment.
    segments.append(
        _segment_type_and_slice(speed_array, speed.frequency, heading.array,
                                heading.frequency, start, speed_secs,
                                eng_arrays, aircraft_info, thresholds, hdf))
    '''
    import matplotlib.pyplot as plt
    for look in [speed_array, heading.array, dfc.array, eng_arrays]:
        plt.plot(np.linspace(0, speed_secs, len(look)), look/np.ptp(look))
    for seg in segments:
        plt.plot([seg[1].start, seg[1].stop], [-0.5,+1])
    plt.show()
    '''

    return segments
    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[slices_int(_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 = int(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[slices_int(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[slices_int((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)

            for touchdown, tkoff in zip(tdwns.get_ordered_by_index(), takeoff.get_ordered_by_index()):
                # 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[int(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[int(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[int(_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[slices_int(_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[slices_int(_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[slices_int(_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[int(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 split_segments(hdf):
    '''
    TODO: DJ suggested not to use decaying engine oil temperature.

    Notes:
     * We do not want to split on masked superframe data if mid-flight (e.g.
       short section of corrupt data) - repair_mask without defining
       repair_duration should fix that.
     * Use turning alongside engine parameters to ensure there is no movement?
     XXX: Beware of pre-masked minimums to ensure we don't split on padded
     superframes

    TODO: Use L3UQAR num power ups for difficult cases?
    '''
    airspeed = hdf['Airspeed']

    # Look for heading first
    try:
        # Fetch Heading if available
        heading = hdf.get_param('Heading', valid_only=True)
    except KeyError:
        # try Heading True, otherwise fail loudly with a KeyError
        heading = hdf.get_param('Heading True', valid_only=True)

    eng_arrays, _ = _get_eng_params(hdf, align_param=heading)

    # Look for Airspeed
    try:
        airspeed_array = repair_mask(airspeed.array, repair_duration=None,
                                     repair_above=settings.AIRSPEED_THRESHOLD)
    except ValueError:
        # Airspeed array is masked, most likely under min threshold so it did
        # not go fast.
        logger.warning("Airspeed is entirely masked. The entire contents of "
                       "the data will be a GROUND_ONLY slice.")
        return [_segment_type_and_slice(
            airspeed.array, airspeed.frequency, heading.array,
            heading.frequency, 0, hdf.duration, eng_arrays)]

    airspeed_secs = len(airspeed_array) / airspeed.frequency
    slow_array = np.ma.masked_less_equal(airspeed_array,
                                         settings.AIRSPEED_THRESHOLD)

    speedy_slices = np.ma.clump_unmasked(slow_array)
    if len(speedy_slices) <= 1:
        logger.info("There are '%d' sections of data where airspeed is "
                    "above the splitting threshold. Therefore there can only "
                    "be at maximum one flights worth of data. Creating a "
                    "single segment comprising all data.", len(speedy_slices))
        # Use the first and last available unmasked values to determine segment
        # type.
        return [_segment_type_and_slice(
            airspeed_array, airspeed.frequency, heading.array,
            heading.frequency, 0, airspeed_secs, eng_arrays)]

    slow_slices = np.ma.clump_masked(slow_array)

    rate_of_turn = _rate_of_turn(heading)

    split_params_min, split_params_frequency \
        = _get_normalised_split_params(hdf)

    if hdf.reliable_frame_counter:
        dfc = hdf['Frame Counter']
        dfc_diff = np.ma.diff(dfc.array)
        # Mask 'Frame Counter' incrementing by 1.
        dfc_diff = np.ma.masked_equal(dfc_diff, 1)
        # Mask 'Frame Counter' overflow where the Frame Counter transitions
        # from 4095 to 0.
        # Q: This used to be 4094, are there some Frame Counters which
        # increment from 1 rather than 0 or something else?
        dfc_diff = np.ma.masked_equal(dfc_diff, -4095)
        # Gap between difference values.
        dfc_half_period = (1 / dfc.frequency) / 2
    else:
        logger.info("'Frame Counter' will not be used for splitting since "
                    "'reliable_frame_counter' is False.")
        dfc = None

    segments = []
    start = 0
    last_fast_index = None
    for slow_slice in slow_slices:
        if slow_slice.start == 0:
            # Do not split if slow_slice is at the beginning of the data.
            # Since we are working with masked slices, masked padded superframe
            # data will be included within the first slow_slice.
            continue
        if slow_slice.stop == len(airspeed_array):
            # After the loop we will add the remaining data to a segment.
            break

        if last_fast_index is not None:
            fast_duration = (slow_slice.start -
                             last_fast_index) / airspeed.frequency
            if fast_duration < settings.MINIMUM_FAST_DURATION:
                logger.info("Disregarding short period of fast airspeed %s",
                            fast_duration)
                continue

        # Get start and stop at 1Hz.
        slice_start_secs = slow_slice.start / airspeed.frequency
        slice_stop_secs = slow_slice.stop / airspeed.frequency

        slow_duration = slice_stop_secs - slice_start_secs
        if slow_duration < settings.MINIMUM_SPLIT_DURATION:
            logger.info("Disregarding period of airspeed below '%s' "
                        "since '%s' is shorter than MINIMUM_SPLIT_DURATION "
                        "('%s').", settings.AIRSPEED_THRESHOLD, slow_duration,
                        settings.MINIMUM_SPLIT_DURATION)
            continue

        last_fast_index = slow_slice.stop

        # Find split based on minimum of engine parameters.
        if split_params_min is not None:
            eng_split_index, eng_split_value = _split_on_eng_params(
                slice_start_secs, slice_stop_secs, split_params_min,
                split_params_frequency)
        else:
            eng_split_index, eng_split_value = None, None

        # Split using 'Frame Counter'.
        if dfc is not None:
            dfc_split_index = _split_on_dfc(
                slice_start_secs, slice_stop_secs, dfc.frequency,
                dfc_half_period, dfc_diff, eng_split_index=eng_split_index)
            if dfc_split_index:
                segments.append(_segment_type_and_slice(
                    airspeed_array, airspeed.frequency, heading.array,
                    heading.frequency, start, dfc_split_index, eng_arrays))
                start = dfc_split_index
                logger.info("'Frame Counter' jumped within slow_slice '%s' "
                            "at index '%d'.", slow_slice, dfc_split_index)
                continue
            else:
                logger.info("'Frame Counter' did not jump within slow_slice "
                            "'%s'.", slow_slice)

        # Split using minimum of engine parameters.
        if eng_split_value is not None and \
           eng_split_value < settings.MINIMUM_SPLIT_PARAM_VALUE:
            logger.info("Minimum of normalised split parameters ('%s') was "
                        "below MINIMUM_SPLIT_PARAM_VALUE ('%s') within "
                        "slow_slice '%s' at index '%d'.",
                        eng_split_value, settings.MINIMUM_SPLIT_PARAM_VALUE,
                        slow_slice, eng_split_index)
            segments.append(_segment_type_and_slice(
                airspeed_array, airspeed.frequency, heading.array,
                heading.frequency, start, eng_split_index, eng_arrays))
            start = eng_split_index
            continue
        else:
            logger.info("Minimum of normalised split parameters ('%s') was "
                        "not below MINIMUM_SPLIT_PARAM_VALUE ('%s') within "
                        "slow_slice '%s' at index '%s'.",
                        eng_split_value, settings.MINIMUM_SPLIT_PARAM_VALUE,
                        slow_slice, eng_split_index)

        # Split using rate of turn. Q: Should this be considered in other
        # splitting methods.
        if rate_of_turn is None:
            continue

        rot_split_index = _split_on_rot(slice_start_secs, slice_stop_secs,
                                        heading.frequency, rate_of_turn)
        if rot_split_index:
            segments.append(_segment_type_and_slice(
                airspeed_array, airspeed.frequency, heading.array,
                heading.frequency, start, rot_split_index, eng_arrays))
            start = rot_split_index
            logger.info("Splitting at index '%s' where rate of turn was below "
                        "'%s'.", rot_split_index,
                        settings.HEADING_RATE_SPLITTING_THRESHOLD)
            continue
        else:
            logger.info(
                "Aircraft did not stop turning during slow_slice "
                "('%s'). Therefore a split will not be made.", slow_slice)

        #Q: Raise error here?
        logger.warning("Splitting methods failed to split within slow_slice "
                       "'%s'.", slow_slice)

    # Add remaining data to a segment.
    segments.append(_segment_type_and_slice(
        airspeed_array, airspeed.frequency, heading.array, heading.frequency,
        start, airspeed_secs, eng_arrays))
    return segments
Пример #29
0
def split_segments(hdf):
    '''
    TODO: DJ suggested not to use decaying engine oil temperature.

    Notes:
     * We do not want to split on masked superframe data if mid-flight (e.g.
       short section of corrupt data) - repair_mask without defining
       repair_duration should fix that.
     * Use turning alongside engine parameters to ensure there is no movement?
     XXX: Beware of pre-masked minimums to ensure we don't split on padded
     superframes

    TODO: Use L3UQAR num power ups for difficult cases?
    '''
    airspeed = hdf['Airspeed']
    try:
        airspeed_array = repair_mask(airspeed.array, repair_duration=None,
                                     repair_above=settings.AIRSPEED_THRESHOLD)
    except ValueError:
        # Airspeed array is masked, most likely under min threshold so it did
        # not go fast.
        logger.warning("Airspeed is entirely masked. The entire contents of "
                       "the data will be a GROUND_ONLY slice.")
        #TODO: Return "NO_MOVEMENT" when supported
        return [('GROUND_ONLY', slice(0, hdf.duration))]

    try:
        # Fetch Heading if available
        heading = hdf.get_param('Heading', valid_only=True)
    except KeyError:
        # try Heading True, otherwise fail loudly with a KeyError
        heading = hdf.get_param('Heading True', valid_only=True)

    airspeed_secs = len(airspeed_array) / airspeed.frequency
    slow_array = np.ma.masked_less_equal(airspeed_array,
                                         settings.AIRSPEED_THRESHOLD)

    speedy_slices = np.ma.clump_unmasked(slow_array)
    if len(speedy_slices) <= 1:
        logger.info("There are '%d' sections of data where airspeed is "
                    "above the splitting threshold. Therefore there can only "
                    "be at maximum one flights worth of data. Creating a "
                    "single segment comprising all data.", len(speedy_slices))
        # Use the first and last available unmasked values to determine segment
        # type.
        return [_segment_type_and_slice(
            airspeed_array, airspeed.frequency, heading.array,
            heading.frequency, 0, airspeed_secs)]

    slow_slices = np.ma.clump_masked(slow_array)

    rate_of_turn = _rate_of_turn(heading)

    split_params_min, split_params_frequency \
        = _get_normalised_split_params(hdf)

    if hdf.reliable_frame_counter:
        dfc = hdf['Frame Counter']
        dfc_diff = np.ma.diff(dfc.array)
        # Mask 'Frame Counter' incrementing by 1.
        dfc_diff = np.ma.masked_equal(dfc_diff, 1)
        # Mask 'Frame Counter' overflow where the Frame Counter transitions
        # from 4095 to 0.
        # Q: This used to be 4094, are there some Frame Counters which
        # increment from 1 rather than 0 or something else?
        dfc_diff = np.ma.masked_equal(dfc_diff, -4095)
        # Gap between difference values.
        dfc_half_period = (1 / dfc.frequency) / 2
    else:
        logger.info("'Frame Counter' will not be used for splitting since "
                    "'reliable_frame_counter' is False.")
        dfc = None

    segments = []
    start = 0
    last_fast_index = None
    for slow_slice in slow_slices:
        if slow_slice.start == 0:
            # Do not split if slow_slice is at the beginning of the data.
            # Since we are working with masked slices, masked padded superframe
            # data will be included within the first slow_slice.
            continue
        if slow_slice.stop == len(airspeed_array):
            # After the loop we will add the remaining data to a segment.
            break

        if last_fast_index is not None:
            fast_duration = (slow_slice.start -
                             last_fast_index) / airspeed.frequency
            if fast_duration < settings.MINIMUM_FAST_DURATION:
                logger.info("Disregarding short period of fast airspeed %s",
                            fast_duration)
                continue

        # Get start and stop at 1Hz.
        slice_start_secs = slow_slice.start / airspeed.frequency
        slice_stop_secs = slow_slice.stop / airspeed.frequency

        slow_duration = slice_stop_secs - slice_start_secs
        if slow_duration < settings.MINIMUM_SPLIT_DURATION:
            logger.info("Disregarding period of airspeed below '%s' "
                        "since '%s' is shorter than MINIMUM_SPLIT_DURATION "
                        "('%s').", settings.AIRSPEED_THRESHOLD, slow_duration,
                        settings.MINIMUM_SPLIT_DURATION)
            continue

        last_fast_index = slow_slice.stop

        # Find split based on minimum of engine parameters.
        if split_params_min is not None:
            eng_split_index, eng_split_value = _split_on_eng_params(
                slice_start_secs, slice_stop_secs, split_params_min,
                split_params_frequency)
        else:
            eng_split_index, eng_split_value = None, None

        # Split using 'Frame Counter'.
        if dfc is not None:
            dfc_split_index = _split_on_dfc(
                slice_start_secs, slice_stop_secs, dfc.frequency,
                dfc_half_period, dfc_diff, eng_split_index=eng_split_index)
            if dfc_split_index:
                segments.append(_segment_type_and_slice(
                    airspeed_array, airspeed.frequency, heading.array,
                    heading.frequency, start, dfc_split_index))
                start = dfc_split_index
                logger.info("'Frame Counter' jumped within slow_slice '%s' "
                            "at index '%d'.", slow_slice, dfc_split_index)
                continue
            else:
                logger.info("'Frame Counter' did not jump within slow_slice "
                            "'%s'.", slow_slice)

        # Split using minimum of engine parameters.
        if eng_split_value is not None and \
           eng_split_value < settings.MINIMUM_SPLIT_PARAM_VALUE:
            logger.info("Minimum of normalised split parameters ('%s') was "
                        "below MINIMUM_SPLIT_PARAM_VALUE ('%s') within "
                        "slow_slice '%s' at index '%d'.",
                        eng_split_value, settings.MINIMUM_SPLIT_PARAM_VALUE,
                        slow_slice, eng_split_index)
            segments.append(_segment_type_and_slice(
                airspeed_array, airspeed.frequency, heading.array,
                heading.frequency, start, eng_split_index))
            start = eng_split_index
            continue
        else:
            logger.info("Minimum of normalised split parameters ('%s') was "
                        "not below MINIMUM_SPLIT_PARAM_VALUE ('%s') within "
                        "slow_slice '%s' at index '%s'.",
                        eng_split_value, settings.MINIMUM_SPLIT_PARAM_VALUE,
                        slow_slice, eng_split_index)

        # Split using rate of turn. Q: Should this be considered in other
        # splitting methods.
        if rate_of_turn is None:
            continue

        rot_split_index = _split_on_rot(slice_start_secs, slice_stop_secs,
                                        heading.frequency, rate_of_turn)
        if rot_split_index:
            segments.append(_segment_type_and_slice(
                airspeed_array, airspeed.frequency, heading.array,
                heading.frequency, start, rot_split_index))
            start = rot_split_index
            logger.info("Splitting at index '%s' where rate of turn was below "
                        "'%s'.", rot_split_index,
                        settings.RATE_OF_TURN_SPLITTING_THRESHOLD)
            continue
        else:
            logger.info(
                "Aircraft did not stop turning during slow_slice "
                "('%s'). Therefore a split will not be made.", slow_slice)

        #Q: Raise error here?
        logger.warning("Splitting methods failed to split within slow_slice "
                       "'%s'.", slow_slice)

    # Add remaining data to a segment.
    segments.append(_segment_type_and_slice(
        airspeed_array, airspeed.frequency, heading.array, heading.frequency,
        start, airspeed_secs))
    return segments