Пример #1
0
    def _evaluate_airports(self, airports, lowest_lat, lowest_lon, lowest_hdg, appr_ils_freq):
        '''
        pre filter cirteria on airprots
        '''
        annotated_airports = {}
        for airport in airports:
            ils_match = None
            heading_match = False
            min_rwy_start_dist = None
            for runway in airport.get('runways', []):
                if not filter_runway_heading(runway, lowest_hdg):
                    # Heading does not match runway
                    continue
                heading_match = True
                if ut.convert(runway.get('localizer', {}).get('frequency', 0), ut.KHZ, ut.MHZ) == appr_ils_freq:
                    ils_match = True
                if runway.get('start') and lowest_lat is not None and lowest_lon is not None:
                    start_dist = great_circle_distance__haversine(
                        runway['start']['latitude'],
                        runway['start']['longitude'],
                        lowest_lat, lowest_lon,
                    )
                    min_rwy_start_dist = min(min_rwy_start_dist, start_dist) if min_rwy_start_dist else start_dist

            annotated_airports[airport['id']] = {'airport': airport,
                                                 'heading_match': heading_match,
                                                 'ils_match': ils_match,
                                                 'min_rwy_start_dist': min_rwy_start_dist,
                                                 'distance': airport['distance']}

        return annotated_airports
    def _evaluate_airports(self, airports, lowest_lat, lowest_lon, lowest_hdg, appr_ils_freq):
        '''
        pre filter cirteria on airprots
        '''
        annotated_airports = {}
        for airport in airports:
            ils_match = None
            heading_match = False
            min_rwy_start_dist = None
            for runway in airport.get('runways', []):
                if not filter_runway_heading(runway, lowest_hdg):
                    # Heading does not match runway
                    continue
                heading_match = True
                if ut.convert(runway.get('localizer', {}).get('frequency', 0), ut.KHZ, ut.MHZ) == appr_ils_freq:
                    ils_match = True
                if runway.get('start') and lowest_lat is not None and lowest_lon is not None:
                    start_dist = great_circle_distance__haversine(
                        runway['start']['latitude'],
                        runway['start']['longitude'],
                        lowest_lat, lowest_lon,
                    )
                    min_rwy_start_dist = min(min_rwy_start_dist, start_dist) if min_rwy_start_dist else start_dist

            annotated_airports[airport['id']] = {'airport': airport,
                                                 'heading_match': heading_match,
                                                 'ils_match': ils_match,
                                                 'min_rwy_start_dist': min_rwy_start_dist,
                                                 'distance': airport['distance']}

        return annotated_airports
    def _get_velocity_speed(self, lookup, setting, weight=None):
        '''
        Looks up the velocity speed in the provided lookup table.

        Will use interpolation if configured and convert units if necessary.

        None will be returned if weight is outside of the table range or no
        entries are available in the table for the provided flap/conf value.

        :param lookup: The velocity speed lookup table.
        :type lookup: dict
        :param setting: Flap or conf setting to use in lookup.
        :type setting: string
        :param weight: Weight of the aircraft.
        :type weight: float
        :returns: A velocity speed value or None.
        :rtype: float
        :raises: KeyError -- when flap/conf settings is not found.
        :raises: ValueError -- when weight units cannot be converted.
        '''
        if setting not in lookup:
            msg = "Velocity speed table '%s' has no entry for flap/conf '%s'."
            arg = (self.__class__.__name__, setting)
            logger.error(msg, *arg)
            # raise KeyError(msg % arg)
            return None

        # If table which doesn't have weights return fixed value:
        if self.weight_unit is None:
            return lookup[setting]

        # Convert the aircraft weight to match the lookup table:
        weight = units.convert(weight, 'kg', self.weight_unit)

        wt = lookup['weight']
        if not min(wt) <= weight <= max(wt) or weight is np.ma.masked:
            msg = "Weight '%s' outside of range for velocity speed table '%s'."
            arg = (weight, self.__class__.__name__)
            logger.warning(msg, *arg)
            return None

        # Determine the value for the velocity speed:
        if self.interpolate:
            f = interp.interp1d(lookup['weight'], lookup[setting])
            value = f(weight)
        else:
            index = bisect_left(lookup['weight'], weight)
            value = lookup[setting][index]

        # Return a minimum speed if we have one and the value is below it:
        if self.minimum_speed is not None and value < self.minimum_speed:
            return self.minimum_speed

        return value
def draw_centreline(kml, rwy):
    start_lat = rwy['start']['latitude']
    start_lon = rwy['start']['longitude']
    end_lat = rwy['end']['latitude']
    end_lon = rwy['end']['longitude']
    brg, dist = bearing_and_distance(end_lat, end_lon, start_lat, start_lon)
    brgs = np.ma.array([brg])
    dists = np.ma.array([30000])
    lat_30k, lon_30k = latitudes_and_longitudes(brgs, dists, rwy['start'])
    try:
        angle = np.deg2rad(rwy['glideslope']['angle'])
    except:
        angle = np.deg2rad(3.0)
    end_height = ut.convert(30000 * np.tan(angle), ut.METER, ut.FT)
    track_config = {'name': 'ILS'}
    track_coords = []
    track_coords.append((end_lon,end_lat))
    track_coords.append((lon_30k.data[0],lat_30k.data[0], end_height))
    track_config['coords'] = track_coords
    kml.newlinestring(**track_config)
    return
Пример #5
0
def draw_centreline(kml, rwy):
    start_lat = rwy['start']['latitude']
    start_lon = rwy['start']['longitude']
    end_lat = rwy['end']['latitude']
    end_lon = rwy['end']['longitude']
    brg, dist = bearing_and_distance(end_lat, end_lon, start_lat, start_lon)
    brgs = np.ma.array([brg])
    dists = np.ma.array([30000])
    lat_30k, lon_30k = latitudes_and_longitudes(brgs, dists, rwy['start'])
    try:
        angle = np.deg2rad(rwy['glideslope']['angle'])
    except:
        angle = np.deg2rad(3.0)
    end_height = ut.convert(30000 * np.tan(angle), ut.METER, ut.FT)
    track_config = {'name': 'ILS'}
    track_coords = []
    track_coords.append((end_lon, end_lat))
    track_coords.append((lon_30k.data[0], lat_30k.data[0], end_height))
    track_config['coords'] = track_coords
    kml.newlinestring(**track_config)
    return
Пример #6
0
    def derive(
        self,
        alt_aal=P('Altitude AAL'),
        alt_agl=P('Altitude AGL'),
        ac_type=A('Aircraft Type'),
        app=S('Approach And Landing'),
        hdg=P('Heading Continuous'),
        lat=P('Latitude Prepared'),
        lon=P('Longitude Prepared'),
        ils_loc=P('ILS Localizer'),
        ils_gs=S('ILS Glideslope'),
        ils_freq=P('ILS Frequency'),
        land_afr_apt=A('AFR Landing Airport'),
        land_afr_rwy=A('AFR Landing Runway'),
        lat_land=KPV('Latitude At Touchdown'),
        lon_land=KPV('Longitude At Touchdown'),
        precision=A('Precise Positioning'),
        fast=S('Fast'),

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            appr_ils_freq = None
            runway_change = False
            offset_ils = False

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

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

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

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

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

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

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

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

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

                if loc_estab:

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

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

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

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

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

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

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

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

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

            self.create_approach(
                approach_type,
                _slice,
                runway_change=runway_change,
                offset_ils=offset_ils,
                airport=airport,
                landing_runway=landing_runway,
                approach_runway=approach_runway,
                gs_est=gs_est,
                loc_est=loc_est,
                ils_freq=appr_ils_freq,
                turnoff=turnoff,
                lowest_lat=lowest_lat,
                lowest_lon=lowest_lon,
                lowest_hdg=lowest_hdg,
            )
def 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
    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,
            )
Пример #9
0
    def derive(
            self,
            alt_aal=P('Altitude AAL'),
            alt_agl=P('Altitude AGL'),
            ac_type=A('Aircraft Type'),
            app=S('Approach And Landing'),
            hdg=P('Heading Continuous'),
            lat=P('Latitude Prepared'),
            lon=P('Longitude Prepared'),
            ils_loc=P('ILS Localizer'),
            ils_gs=S('ILS Glideslope'),
            ils_freq=P('ILS Frequency'),
            land_afr_apt=A('AFR Landing Airport'),
            land_afr_rwy=A('AFR Landing Runway'),
            lat_land=KPV('Latitude At Touchdown'),
            lon_land=KPV('Longitude At Touchdown'),
            precision=A('Precise Positioning'),
            fast=S('Fast'),
    ):

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

        alt = alt_agl if ac_type == helicopter else alt_aal

        app_slices = app.get_slices()

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

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

            turnoff = None
            if landing:
                search_end = fast.get_surrounding(_slice.start)
                if search_end 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 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.
            # This function may be expanded to cater for rig approaches in future.
            heliport = is_heliport(ac_type, airport, landing_runway)

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

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

            appr_ils_freq = None
            runway_change = False
            offset_ils = False

            # Do we have a recorded ILS frequency? If so, what was it tuned to at the start of the approach??
            if ils_freq:
                appr_ils_freq = 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(loc_end, loc_estab, -1))
                        if loc_end_2_dots:
                            loc_end = loc_end_2_dots

                    loc_est = slice(loc_estab, loc_end + 1)

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

            gs_est = None
            if loc_est and '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,
            )
Пример #10
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': '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)
            if np.ma.count(alt.array):
                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
Пример #11
0
    def test__convert(self):

        data = {
            # Frequency:
            (1, 'KHz', 'MHz'): 0.001,
            (1, 'KHz', 'GHz'): 0.000001,
            (1, 'MHz', 'KHz'): 1000.0,
            (1, 'MHz', 'GHz'): 0.001,
            (1, 'GHz', 'KHz'): 1000000.0,
            (1, 'GHz', 'MHz'): 1000.0,
            # Flow (Volume):
            (1, 'lb/h', 'kg/h'): 0.453592,
            (1, 'lb/h', 't/h'): 0.000453592,
            (1, 'kg/h', 'lb/h'): 2.20462,
            (1, 'kg/h', 't/h'): 0.001,
            (1, 't/h', 'lb/h'): 2204.62,
            (1, 't/h', 'kg/h'): 1000,
            # Length:
            (1, 'ft', 'm'): 0.3048,
            (1, 'ft', 'km'): 0.0003048,
            (1, 'ft', 'mi'): 0.000189394,
            (1, 'ft', 'nm'): 0.000164579,
            (1, 'm', 'ft'): 3.28084,
            (1, 'm', 'km'): 0.001,
            (1, 'm', 'mi'): 0.000621371,
            (1, 'm', 'nm'): 0.000539957,
            (1, 'km', 'ft'): 3280.84,
            (1, 'km', 'm'): 1000,
            (1, 'km', 'mi'): 0.621371,
            (1, 'km', 'nm'): 0.539957,
            (1, 'mi', 'ft'): 5280,
            (1, 'mi', 'm'): 1609.34,
            (1, 'mi', 'km'): 1.60934,
            (1, 'mi', 'nm'): 0.868976,
            (1, 'nm', 'ft'): 6076.12,
            (1, 'nm', 'm'): 1852,
            (1, 'nm', 'km'): 1.852,
            (1, 'nm', 'mi'): 1.15078,
            # Mass:
            (1, 'lb', 'kg'): 0.453592,
            (1, 'lb', 't'): 0.000453592,
            (1, 'kg', 'lb'): 2.20462,
            (1, 'kg', 't'): 0.001,
            (1, 't', 'lb'): 2204.62,
            (1, 't', 'kg'): 1000,
            # Pressure:
            (1, 'inHg', 'mB'): 33.86,
            (1, 'inHg', 'psi'): 0.4910,         # Google: 0.49109778
            (1, 'mB', 'inHg'): 0.029533,        # Google: 0.0295333727
            (1, 'mB', 'psi'): 0.0145037738,
            (1, 'psi', 'inHg'): 2.0362,         # Google: 2.03625437
            (1, 'psi', 'mB'): 68.94757,         # Google: 68.9475729
            # Speed:
            (1, 'kt', 'mph'): 1.15078,
            (1, 'kt', 'fpm'): 101.2686,
            (1, 'mph', 'kt'): 0.868976,
            (1, 'mph', 'fpm'): 88.0002,
            (1, 'fpm', 'kt'): 0.0098747300,
            (1, 'fpm', 'mph'): 0.0113636364,
            # Temperature:
            (0, u'C', u'F'): 32,
            (0, u'C', u'K'): 273.15,
            (0, u'F', u'C'): -17.7778,
            (0, u'F', u'K'): 255.372,
            (0, u'K', u'C'): -273.15,
            (0, u'K', u'F'): -459.67,
            # Time:
            (1, 'h', 'min'): 60,
            (1, 'h', 's'): 3600,
            (1, 'min', 'h'): 0.0166667,
            (1, 'min', 's'): 60,
            (1, 's', 'h'): 0.000277778,
            (1, 's', 'min'): 0.0166667,
            # Other:
            (1, 'gs-ddm', 'dots'): 11.428571428571429,
            (1, 'loc-ddm', 'dots'): 12.903225806451614,
            (1, 'mV', 'dots'): 0.01333333333333333,
        }

        for arguments, expected in data.iteritems():
            dp = max(abs(Decimal(str(expected)).as_tuple().exponent) - 1, 0)
            # Check forward conversion:
            i = arguments
            o = expected
            self.assertAlmostEqual(units.convert(*i), o, places=dp)
            # Check backward conversion:
            i = [expected] + list(arguments)[:0:-1]
            o = arguments[0]
            self.assertAlmostEqual(units.convert(*i), o, delta=0.001)