Esempio n. 1
0
    def test_SpherePath_calculate_path_leg_distance(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)

        # Start point
        distance_0 = ecef_path.calculate_path_leg_distance(ecef_points[0], 0)
        self.assertEqual(distance_0, 0.0)

        # Point at end of a straight leg
        distance_1_0 = ecef_path.calculate_path_leg_distance(ecef_points[1], 0)
        assert_almost_equal(rad2nm(distance_1_0), EXPECTED_PATH_LENGTHS[1])

        # Point at start of a straight leg
        distance_1_1 = ecef_path.calculate_path_leg_distance(ecef_points[1], 1)
        self.assertEqual(distance_0, 0.0)

        # Point in middle of a straight leg
        pos_1_5 = ecef_path.calculate_position(1, 0.5)
        distance_1_5 = ecef_path.calculate_path_leg_distance(pos_1_5, 1)
        assert_almost_equal(rad2nm(distance_1_5),
                            0.5 * EXPECTED_PATH_LENGTHS[2])

        # Point toward end of a turning leg
        pos_1_9 = ecef_path.calculate_position(1, 0.99)
        distance_1_9 = ecef_path.calculate_path_leg_distance(pos_1_9, 1)
        assert_almost_equal(rad2nm(distance_1_9),
                            0.99 * EXPECTED_PATH_LENGTHS[2])

        # Before start of a turning leg
        distance_2_9 = ecef_path.calculate_path_leg_distance(pos_1_9, 2)
        assert_almost_equal(rad2nm(distance_2_9), -0.57845277761762326)

        # Point toward start of a turning leg
        pos_2_1 = ecef_path.calculate_position(2, 0.01)
        distance_2_1 = ecef_path.calculate_path_leg_distance(pos_2_1, 2)
        assert_almost_equal(rad2nm(distance_2_1),
                            0.01 * EXPECTED_PATH_LENGTHS[3])

        # Past the ned of a turning leg
        distance_1_2 = ecef_path.calculate_path_leg_distance(pos_2_1, 1)
        self.assertTrue(rad2nm(distance_1_2) > EXPECTED_PATH_LENGTHS[2])
        assert_almost_equal(rad2nm(distance_1_2), 58.980918943627351)

        # Point along a turning leg
        pos_2_75 = ecef_path.calculate_position(2, 0.75)
        distance_2_75 = ecef_path.calculate_path_leg_distance(pos_2_75, 2)
        assert_almost_equal(rad2nm(distance_2_75),
                            0.75 * EXPECTED_PATH_LENGTHS[3])

        # Last point
        distance_last = ecef_path.calculate_path_leg_distance(
            ecef_points[-1], 10)
        assert_almost_equal(rad2nm(distance_last), EXPECTED_PATH_LENGTHS[11])

        # # A waypoint not on path
        distance_2_0 = ecef_path.calculate_path_leg_distance(ecef_points[2], 1)
        assert_almost_equal(rad2nm(distance_2_0),
                            EXPECTED_PATH_LENGTHS[2],
                            decimal=4)
Esempio n. 2
0
    def test_SpherefPath_init(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)
        self.assertEqual(len(ecef_points), 12)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)
        self.assertEqual(len(ecef_path.points), 12)
        # assert_array_almost_equal(ecef_path.points, ecef_points)
        self.assertEqual(len(ecef_path.turn_initiation_distances), 12)
        assert_array_almost_equal(ecef_path.turn_initiation_distances,
                                  TURN_DISTANCES)
        self.assertEqual(len(ecef_path.leg_lengths), 12)
        assert_array_almost_equal(rad2nm(ecef_path.leg_lengths),
                                  EXPECTED_LEG_LENGTHS)
        self.assertEqual(len(ecef_path.turn_angles), 12)
        assert_array_almost_equal(np.rad2deg(ecef_path.turn_angles),
                                  EXPECTED_TURN_ANGLES)
        self.assertEqual(len(ecef_path.path_lengths), 12)
        assert_array_almost_equal(rad2nm(ecef_path.path_lengths),
                                  EXPECTED_PATH_LENGTHS)

        lats, lons = ecef_path.point_lat_longs()
        assert_array_almost_equal(lats, ROUTE_LATS)
        assert_array_almost_equal(lons, ROUTE_LONS)

        assert_array_almost_equal(ecef_path.turn_initiation_distances_nm(),
                                  rad2nm(TURN_DISTANCES))
Esempio n. 3
0
    def test_calculate_path_cross_track_distance(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)

        assert_almost_equal(
            ecef_path.calculate_path_cross_track_distance(ecef_points[0], 0),
            0.0)
        assert_almost_equal(
            ecef_path.calculate_path_cross_track_distance(ecef_points[1], 1),
            0.0)

        xtd_2_1 = ecef_path.calculate_path_cross_track_distance(
            ecef_points[2], 1)
        assert_almost_equal(rad2nm(xtd_2_1), 4.1416795658323213)

        xtd_2_2 = ecef_path.calculate_path_cross_track_distance(
            ecef_points[2], 2)
        assert_almost_equal(rad2nm(xtd_2_2), 4.1416795658323213)

        xtd_3_2 = ecef_path.calculate_path_cross_track_distance(
            ecef_points[3], 2)
        assert_almost_equal(rad2nm(xtd_3_2), 8.2824069920496726)

        xtd_3_3 = ecef_path.calculate_path_cross_track_distance(
            ecef_points[3], 3)
        assert_almost_equal(rad2nm(xtd_3_3), 8.2824069920496726)
Esempio n. 4
0
    def test_SpherePath_section_distances_and_types(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)

        path_distances = rad2nm(ecef_path.path_distances())

        distances, types = ecef_path.section_distances_and_types()
        self.assertEqual(len(distances), len(ecef_path) + 8)
        self.assertEqual(len(types), len(ecef_path) + 8)

        self.assertEqual(distances[0], 0.0)
        self.assertEqual(types[0], PointType.Waypoint)

        self.assertEqual(distances[1], path_distances[1])
        self.assertEqual(types[1], PointType.Waypoint)

        self.assertTrue(distances[2] < path_distances[2])
        self.assertEqual(types[2], PointType.TurnStart)

        self.assertTrue(distances[3] > path_distances[2])
        self.assertEqual(types[3], PointType.TurnFinish)

        self.assertEqual(distances[-2], path_distances[-2])
        self.assertEqual(types[-2], PointType.Waypoint)

        self.assertEqual(distances[-1], path_distances[-1])
        self.assertEqual(types[-1], PointType.Waypoint)
Esempio n. 5
0
    def test_SpherePath_path_distances(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)

        path_distances = ecef_path.path_distances()
        self.assertEqual(len(path_distances), 12)

        assert_array_almost_equal(rad2nm(path_distances),
                                  EXPECTED_PATH_DISTANCES)
Esempio n. 6
0
    def test_EcefPath_path_distances(self):
        ecef_points = calculate_EcefPoints(ROUTE_LATS, ROUTE_LONS)

        ecef_path = EcefPath(ecef_points, TURN_DISTANCES)

        path_distances = ecef_path.path_distances()
        self.assertEqual(len(path_distances), 12)

        assert_array_almost_equal(rad2nm(path_distances),
                                  EXPECTED_PATH_DISTANCES)
Esempio n. 7
0
    def test_SpherePath_calculate_path_distances(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)

        distances = rad2nm(
            ecef_path.calculate_path_distances(ecef_points,
                                               ACROSS_TRACK_TOLERANCE))
        self.assertEqual(len(distances), len(ecef_points))
        self.assertEqual(distances[0], 0.0)
        assert_almost_equal(distances[11],
                            EXPECTED_PATH_DISTANCES[11],
                            decimal=6)
        assert_array_almost_equal(distances, EXPECTED_PATH_DISTANCES)
Esempio n. 8
0
    def test_EcefPath_calculate_cross_track_distances(self):
        ecef_points = calculate_EcefPoints(ROUTE_LATS, ROUTE_LONS)

        ecef_path = EcefPath(ecef_points, TURN_DISTANCES)

        distances = rad2nm(
            ecef_path.calculate_path_distances(ecef_points,
                                               ACROSS_TRACK_TOLERANCE))
        self.assertEqual(len(distances), len(ecef_points))

        xtds = ecef_path.calculate_cross_track_distances(
            ecef_points, distances)
        self.assertEqual(len(xtds), len(ecef_points))
        assert_almost_equal(xtds[0], 0.0)
        assert_almost_equal(xtds[1], 0.0)
        assert_almost_equal(xtds[2], 4.1416795658323213)  # 10NM TID
        assert_almost_equal(xtds[3], 8.2824069920496726)  # 20NM TID
        assert_almost_equal(xtds[-1], 0.0)
Esempio n. 9
0
    def test_calculate_speeds(self):

        speeds = calculate_speeds(ELAPSED_TIMES, rad2nm(DISTANCES))
        self.assertEqual(len(speeds), len(ELAPSED_TIMES))
        assert_almost_equal(speeds[0], 334.99364794520545)
        assert_almost_equal(speeds[-1], 124.46279999998069)
Esempio n. 10
0
def analyse_trajectory(flight_id,
                       points_df,
                       across_track_tolerance,
                       method,
                       N=DEFAULT_MOVING_MEDIAN_SAMPLES,
                       M=DEFAULT_MOVING_AVERAGE_SAMPLES,
                       max_duration=DEFAULT_SPEED_MAX_DURATION):
    """
    Analyses and smooths positions in points_df.

    The function:
        - derives the horizontal path from the points_df latitudes and longitudes,
        - calculates the average time between positions
        - determines positions where the aircraft was cruising
        - classifys the trajectories vertical profile
        - derives and smooths the time profile,
        - derives and smooths the altitude profile,
        - constructs and retruns a SmoothedTrajectory containing the flight id,
         smoothed horizontal path, time profile and altitude profile.

    Parameters
    ----------
    flight_id: string
        The id of the flight.

    points_df: a pandas DataFrame
        A DataFrame containing raw positions for a flight, sorted in time order.

    across_track_tolerance: float
        The maximum across track distance[Nautical Miles], default: 0.25 NM.

    method: string
        The smoothing method to use: 'mas', 'lm', 'trf' 'dogbox'

    N : integer
        The number of samples to consider for the speed moving median filter, default 5.

    M : integer
        The number of samples to consider for the speed moving average filter, default 5.

    max_duration: float
        The maximum time between points to smooth when calculating speed, default 120 [Seconds].

    Returns
    -------
    smoothed_trajectoy: SmoothedTrajectory
        The SmoothedTrajectory containing the flight id, smoothed horizontal path,
        time profile and altitude profile.

    metrics: list
        A list containing the flight id and trajectory quality metrics.

    """
    # calculate the position period as seconds per point
    times = points_df['TIME'].values
    duration = calculate_delta_time(times[0], times[-1])
    position_period = duration / (len(points_df) - 1)

    # convert across_track_tolerance to radians
    across_track_radians = np.deg2rad(across_track_tolerance / 60.0)
    ecef_points = global_Point3d(points_df['LAT'].values,
                                 points_df['LON'].values)

    # derive the EcefPath
    path = derive_horizontal_path(ecef_points, across_track_radians)
    horizontal_path_distances = rad2nm(path.path_distances())

    # Ensure that the path is long enough
    if horizontal_path_distances[-1] < across_track_tolerance:
        raise ValueError("Path is short")

    lats, lons = path.point_lat_longs()
    tads = path.turn_initiation_distances_nm()
    hpath = HorizontalPath(lats, lons, tads)

    # Calculate distances of positions along the ECEF path in Nautical Miles
    path_distances = rad2nm(
        path.calculate_path_distances(ecef_points, across_track_radians))

    # Raise an exception if the path_distances are shorter than the horizontal_path
    horizontal_path_length = horizontal_path_distances[
        -1] - across_track_tolerance
    points_path_length = path_distances[-1]
    if points_path_length < horizontal_path_length:
        raise ValueError("Horizontal path distances are short")

    # Sort positions by path distance then time
    sorted_df = pd.DataFrame({
        'distance': path_distances,
        'time': times,
        'altitude': points_df['ALT'].values,
        'points': ecef_points
    })
    sorted_df.sort_values(by=['distance', 'time'], inplace=True)

    # determine whether the position order has changed
    sorted_path_distances = sorted_df['distance'].values
    unordered = (path_distances != sorted_path_distances).any()

    # find the indicies of starts and finishes of cruising sections and
    # classify the trajectory altitude profile
    altitudes = sorted_df['altitude'].values
    cruise_indicies = find_cruise_sections(altitudes)

    # calculate standard deviation and maximum across track error
    xtds = path.calculate_cross_track_distances(ecef_points, path_distances)
    xte_sd = xtds.std()
    max_xte, max_xte_index = find_most_extreme_value(xtds)
    max_xte = abs(max_xte)

    # Find duplicate positions, i.e. postions with across_track_tolerance of each other
    duplicate_positions = find_duplicate_values(sorted_path_distances,
                                                across_track_tolerance)

    # determine whether to smooth time with speed or scipy cuvre fit
    if method in CURVE_FIT_METHODS:
        timep, time_sd, max_time_diff, max_time_index = analyse_times(
            sorted_path_distances, sorted_df['time'].values,
            duplicate_positions, method)
    else:
        timep, time_sd, max_time_diff, max_time_index = analyse_speeds(
            sorted_path_distances, sorted_df['time'].values,
            duplicate_positions, N, M, max_duration)
    max_time_diff = abs(max_time_diff)

    altp, alt_sd, max_alt = analyse_altitudes(sorted_path_distances, altitudes,
                                              cruise_indicies)
    alt_profile_type = altp.type()

    # Calculate average periods in the climb, cruise and descent phases
    toc_distance = altp.top_of_climb_distance()
    tod_distance = altp.top_of_descent_distance()

    climb_period = timep.calculate_average_period(0.0, toc_distance)
    cruise_period = timep.calculate_average_period(toc_distance, tod_distance)
    descent_period = timep.calculate_average_period(tod_distance,
                                                    timep.distances[-1])

    return SmoothedTrajectory(flight_id, hpath, timep, altp), \
        [flight_id, int(alt_profile_type), position_period, climb_period,
         cruise_period, descent_period, int(unordered), time_sd, max_time_diff,
         max_time_index, xte_sd, max_xte, max_xte_index, alt_sd, max_alt]
Esempio n. 11
0
    def test_SpherePath_calculate_path_distance(self):
        ecef_points = global_Point3d(ROUTE_LATS, ROUTE_LONS)

        ecef_path = SpherePath(ecef_points, TURN_DISTANCES)

        # Start point
        distance_0 = ecef_path.calculate_path_distance(ecef_points[0], 0,
                                                       ACROSS_TRACK_TOLERANCE)
        self.assertEqual(distance_0, 0.0)

        # from next leg
        distance_0 = ecef_path.calculate_path_distance(ecef_points[0], 2,
                                                       ACROSS_TRACK_TOLERANCE)
        self.assertEqual(distance_0, 0.0)

        # Point at end of first, straight leg
        distance_1_0 = ecef_path.calculate_path_distance(
            ecef_points[1], 0, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(rad2nm(distance_1_0), EXPECTED_PATH_DISTANCES[1])

        # from next leg
        distance_1_0 = ecef_path.calculate_path_distance(
            ecef_points[1], 1, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(rad2nm(distance_1_0), EXPECTED_PATH_DISTANCES[1])

        # from leg after next
        distance_1_0 = ecef_path.calculate_path_distance(
            ecef_points[1], 2, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(rad2nm(distance_1_0), EXPECTED_PATH_DISTANCES[1])

        # Point in middle of first,  straight leg
        pos_1_5 = ecef_path.calculate_position(1, 0.5)
        distance_1_5 = ecef_path.calculate_path_distance(
            pos_1_5, 1, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(
            rad2nm(distance_1_5),
            EXPECTED_PATH_DISTANCES[1] + 0.5 * EXPECTED_PATH_LENGTHS[2])

        # from previous leg
        distance_1_5 = ecef_path.calculate_path_distance(
            pos_1_5, 0, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(
            rad2nm(distance_1_5),
            EXPECTED_PATH_DISTANCES[1] + 0.5 * EXPECTED_PATH_LENGTHS[2])

        # from next leg
        distance_1_5 = ecef_path.calculate_path_distance(
            pos_1_5, 2, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(
            rad2nm(distance_1_5),
            EXPECTED_PATH_DISTANCES[1] + 0.5 * EXPECTED_PATH_LENGTHS[2])

        # Point along a turning leg
        pos_2_75 = ecef_path.calculate_position(2, 0.75)
        distance_2_75 = ecef_path.calculate_path_distance(
            pos_2_75, 2, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(
            rad2nm(distance_2_75),
            EXPECTED_PATH_DISTANCES[2] + 0.75 * EXPECTED_PATH_LENGTHS[3])

        # Last point
        distance_last = ecef_path.calculate_path_distance(
            ecef_points[-1], 10, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(rad2nm(distance_last),
                            EXPECTED_PATH_DISTANCES[11],
                            decimal=6)

        # from previous leg
        distance_last = ecef_path.calculate_path_distance(
            ecef_points[-1], 9, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(rad2nm(distance_last),
                            EXPECTED_PATH_DISTANCES[11],
                            decimal=6)

        # # A waypoint not on path
        distance_2_0 = ecef_path.calculate_path_distance(
            ecef_points[2], 1, ACROSS_TRACK_TOLERANCE)
        assert_almost_equal(rad2nm(distance_2_0),
                            EXPECTED_PATH_DISTANCES[2],
                            decimal=4)
Esempio n. 12
0
    def test_SphereTurnArc_init(self):
        """Test initialisation of SphereTurnArc class."""
        ecef_point_0 = Point3d(ECEF_ICOSAHEDRON[1][0], ECEF_ICOSAHEDRON[1][1],
                               ECEF_ICOSAHEDRON[1][2])
        ecef_point_1 = Point3d(ECEF_ICOSAHEDRON[2][0], ECEF_ICOSAHEDRON[2][1],
                               ECEF_ICOSAHEDRON[2][2])
        ecef_point_2 = Point3d(ECEF_ICOSAHEDRON[3][0], ECEF_ICOSAHEDRON[3][1],
                               ECEF_ICOSAHEDRON[3][2])
        arc_0 = Arc3d(ecef_point_0, ecef_point_1)
        arc_1 = Arc3d(ecef_point_1, ecef_point_2)

        turn_0 = SphereTurnArc(arc_0, arc_1, TWENTY_NM)
        self.assertTrue(turn_0)

        #  Ensure Turn start and end points are along inbound and outbound arcs
        assert_almost_equal(distance_radians(ecef_point_1, turn_0.start),
                            TWENTY_NM)
        assert_almost_equal(distance_radians(ecef_point_1, turn_0.finish),
                            TWENTY_NM)

        assert_almost_equal(arc_0.cross_track_distance(turn_0.start), 0.0)
        assert_almost_equal(arc_1.cross_track_distance(turn_0.finish), 0.0)

        ANGLE = 0.2 * np.pi
        RADIUS = 20.0 / np.tan(ANGLE / 2.0)  # 61.553670693462841 NM

        assert_almost_equal(turn_0.angle, -ANGLE)
        assert_almost_equal(rad2nm(turn_0.radius), RADIUS)
        assert_almost_equal(rad2nm(turn_0.length()), RADIUS * ANGLE)

        assert_almost_equal(turn_0.radial_distance(turn_0.start),
                            turn_0.radius,
                            decimal=6)
        assert_almost_equal(turn_0.radial_distance(turn_0.finish),
                            turn_0.radius,
                            decimal=6)
        DISTANCE = TWENTY_NM / np.sin(ANGLE / 2.0)  # 64.721359549995796 NM
        assert_almost_equal(turn_0.radial_distance(ecef_point_1), DISTANCE)

        assert_almost_equal(turn_0.cross_track_distance(turn_0.start),
                            0.0,
                            decimal=6)
        assert_almost_equal(turn_0.cross_track_distance(turn_0.finish),
                            0.0,
                            decimal=6)
        assert_almost_equal(turn_0.cross_track_distance(ecef_point_1),
                            DISTANCE - turn_0.radius)

        self.assertEqual(turn_0.point_angle(turn_0.centre), 0.0)
        assert_almost_equal(turn_0.point_angle(turn_0.start), 0.0)
        assert_almost_equal(turn_0.point_angle(turn_0.finish),
                            turn_0.angle,
                            decimal=4)
        assert_almost_equal(turn_0.point_angle(ecef_point_1),
                            turn_0.angle / 2.0,
                            decimal=4)

        assert_almost_equal(turn_0.along_track_distance(turn_0.start), 0.0)
        assert_almost_equal(turn_0.along_track_distance(turn_0.finish),
                            turn_0.length(),
                            decimal=6)
        assert_almost_equal(turn_0.along_track_distance(ecef_point_1),
                            turn_0.length() / 2.0,
                            decimal=6)

        pos_1 = turn_0.position(turn_0.angle)
        assert_almost_equal(distance_radians(pos_1, turn_0.centre),
                            turn_0.radius)
        assert_almost_equal(distance_radians(pos_1, turn_0.finish),
                            0.0,
                            decimal=6)

        pos_2 = turn_0.position(turn_0.angle / 2.0)
        assert_almost_equal(distance_radians(pos_2, turn_0.centre),
                            turn_0.radius)
        assert_almost_equal(turn_0.point_angle(pos_2), turn_0.angle / 2.0)