Skip to content

Commit

Permalink
Account for invalid inputs, failure in subcommands
Browse files Browse the repository at this point in the history
* Generating circular path should now return a bool indicating whether
  the function completed successfully
* Tests now include invalid inputs
  • Loading branch information
Evang264 committed Nov 6, 2024
1 parent 8f7804d commit a91eced
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 22 deletions.
54 changes: 37 additions & 17 deletions modules/plot_circular_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,47 @@

def move_coordinates_by_offset(
start_point: Waypoint, offset_x: float, offset_y: float, name: str
) -> Waypoint:
) -> "tuple[bool, Waypoint | None]":
"""Given a starting waypoint and offsets x and y displacements, find the
resulting waypoint.
Args:
starting_point (Waypoint): The starting waypoint
offset_x (float): The (potentially negative) displacement in west-east
direction, in meters
offset_y (float): The (potentially negative) displacement in north-south
direction, in meters
offset_x (float): The offset in the west-east direction. Positive for
east, negative for west.
offset_y (float): The offset in the north-south direction. Positive for
north, negative for south.
name (str): The name for the resulting waypoint
Returns:
Waypoint: the resulting waypoint after being moved by offset from the
original waypoint.
tuple[bool, Waypoint | None]: Either return (False, None), indicating a
a failure in execution, or (True, waypoint), where waypoint is the
resulting waypoint.
"""
_, offset_local = DronePositionLocal.create(offset_y, offset_x, 0)
success, offset_local = DronePositionLocal.create(offset_y, offset_x, 0)
if not success:
return False, None

# Because drone_position_global_from_local requires DronePosition class,
# we need to convert Waypoint to DronePosition first
_, start_point_converted = DronePosition.create(
success, start_point_converted = DronePosition.create(
start_point.location_ground.latitude,
start_point.location_ground.longitude,
start_point.altitude,
)
if not success:
return False, None

_, end_point = drone_position_global_from_local(start_point_converted, offset_local)
success, end_point = drone_position_global_from_local(start_point_converted, offset_local)
if not success:
return False, None

return Waypoint(name, end_point.latitude, end_point.longitude, end_point.altitude)
return True, Waypoint(name, end_point.latitude, end_point.longitude, end_point.altitude)


def generate_circular_path(center: Waypoint, radius: float, num_points: int) -> "list[Waypoint]":
def generate_circular_path(
center: Waypoint, radius: float, num_points: int
) -> "tuple[bool, list[Waypoint] | None]":
"""Generate a list of `num_points` evenly-separated waypoints given a center
and radius.
Expand All @@ -55,9 +64,15 @@ def generate_circular_path(center: Waypoint, radius: float, num_points: int) ->
num_points (int): The number of waypoints to generate
Returns:
list[Waypoint]: `num_points` waypoints, evenly separated on the circular
path.
tuple[bool, list[Waypoint] | None]: Either return (False, None),
indicating a failure in execution, or (True, waypoints), where
waypoints is the list of waypoints forming a circle `radius` away
from `center`.
"""
# validate input
if radius <= 0 or num_points <= 0:
return False, None

waypoints = []

# any two consecutive points are separated by 2 * pi / n radians.
Expand All @@ -66,11 +81,16 @@ def generate_circular_path(center: Waypoint, radius: float, num_points: int) ->
rad = 2 * math.pi / num_points * i
offset_x = radius * math.cos(rad)
offset_y = radius * math.sin(rad)
waypoints.append(
move_coordinates_by_offset(center, offset_x, offset_y, f"Waypoint {i + 1}")

success, waypoint = move_coordinates_by_offset(
center, offset_x, offset_y, f"Waypoint {i + 1}"
)
if not success:
return False, None

waypoints.append(waypoint)

return waypoints
return True, waypoints


def save_waypoints_to_csv(waypoints: "list[Waypoint]", filename: str) -> None:
Expand Down
40 changes: 35 additions & 5 deletions tests/unit/test_plot_circular_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ def test_move_north_east() -> None:
offset_y = 100_000 # north
expected_point = Waypoint("End", 10.899322, 12.913195, 1)

result_point = plot_circular_path.move_coordinates_by_offset(
success, result_point = plot_circular_path.move_coordinates_by_offset(
starting_point, offset_x, offset_y, "End"
)

print(f"result_point: {result_point}")

assert success
assert isinstance(result_point, Waypoint)
assert_close_enough(result_point, expected_point)


Expand All @@ -45,8 +45,6 @@ def test_generate_circular_path() -> None:
center = Waypoint("Center", 10, 12, 1)
radius = 1_000_000
num_points = 20
waypoints = plot_circular_path.generate_circular_path(center, radius, num_points)

expected_points = [
(10.0, 21.131950912937036),
(12.77905659637457, 20.685001422236247),
Expand All @@ -70,14 +68,46 @@ def test_generate_circular_path() -> None:
(7.220943403625428, 20.685001422236247),
]

success, waypoints = plot_circular_path.generate_circular_path(center, radius, num_points)

assert success
assert isinstance(waypoints, list)
assert len(waypoints) == num_points

for i in range(num_points):
assert isinstance(waypoints[i], Waypoint)
assert_close_enough(
waypoints[i], Waypoint(f"Waypoint {i}", expected_points[i][0], expected_points[i][1], 1)
)


def test_move_north_east_invalid_inputs() -> None:
"""
Test that moving a Waypoint with 0 altitude fails
"""
success, result_point = plot_circular_path.move_coordinates_by_offset(
Waypoint("Start", 12, 36, 0), -0.2, 3.6, "End"
)
assert not success
assert result_point is None


def test_generate_circular_path_invalid_input() -> None:
"""
Test generating circular path with invalid inputs.
"""
inputs = [
(Waypoint("Center", 22.4, -6.7, -0.2), 2, 4),
(Waypoint("Center", 3.99, 12.6, 3.4), 0, 10),
(Waypoint("Center", 3, 6, 12), 500, 0),
]

for center, radius, num_points in inputs:
success, waypoints = plot_circular_path.generate_circular_path(center, radius, num_points)
assert not success
assert waypoints is None


def test_save_waypoints_to_csv(tmp_path: str) -> None:
"""
Save waypoints to a CSV file
Expand Down

0 comments on commit a91eced

Please sign in to comment.