Skip to content

Commit a30d993

Browse files
ENH: Refactor flight.py latitude/longitude to use inverted_haversine (#1055)
1 parent eb1ce59 commit a30d993

4 files changed

Lines changed: 73 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Attention: The newest changes should be on top -->
5959

6060
### Changed
6161

62+
- ENH: Refactor flight.py latitude/longitude to use inverted_haversine [#1055](https://github.com/RocketPy-Team/RocketPy/pull/1055)
63+
6264
### Deprecated
6365

6466
- MNT: Rename `radius` to `radius_function` in `CylindricalTank` and `SphericalTank`; old `radius=` keyword argument now raises `DeprecationWarning` [#957](https://github.com/RocketPy-Team/RocketPy/pull/957)

rocketpy/simulation/flight.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
find_closest,
2222
find_root_linear_interpolation,
2323
find_roots_cubic_function,
24+
inverted_haversine,
2425
quaternions_to_nutation,
2526
quaternions_to_precession,
2627
quaternions_to_spin,
@@ -3813,40 +3814,29 @@ def latitude(self):
38133814
"""Rocket latitude coordinate, in degrees, as a Function of
38143815
time.
38153816
"""
3816-
lat1 = np.deg2rad(self.env.latitude) # Launch lat point converted to radians
3817-
38183817
# Applies the haversine equation to find final lat/lon coordinates
3819-
latitude = np.rad2deg(
3820-
np.arcsin(
3821-
np.sin(lat1) * np.cos(self.drift[:, 1] / self.env.earth_radius)
3822-
+ np.cos(lat1)
3823-
* np.sin(self.drift[:, 1] / self.env.earth_radius)
3824-
* np.cos(np.deg2rad(self.bearing[:, 1]))
3825-
)
3818+
latitude, _ = inverted_haversine(
3819+
self.env.latitude,
3820+
self.env.longitude,
3821+
self.drift[:, 1],
3822+
self.bearing[:, 1],
3823+
self.env.earth_radius,
38263824
)
38273825
return np.column_stack((self.time, latitude))
38283826

3829-
# TODO: haversine should be defined in tools.py so we just invoke it in here.
38303827
@funcify_method("Time (s)", "Longitude (°)", "linear", "constant")
38313828
def longitude(self):
38323829
"""Rocket longitude coordinate, in degrees, as a Function of
38333830
time.
38343831
"""
3835-
lat1 = np.deg2rad(self.env.latitude) # Launch lat point converted to radians
3836-
lon1 = np.deg2rad(self.env.longitude) # Launch lon point converted to radians
3837-
38383832
# Applies the haversine equation to find final lat/lon coordinates
3839-
longitude = np.rad2deg(
3840-
lon1
3841-
+ np.arctan2(
3842-
np.sin(np.deg2rad(self.bearing[:, 1]))
3843-
* np.sin(self.drift[:, 1] / self.env.earth_radius)
3844-
* np.cos(lat1),
3845-
np.cos(self.drift[:, 1] / self.env.earth_radius)
3846-
- np.sin(lat1) * np.sin(np.deg2rad(self.latitude[:, 1])),
3847-
)
3833+
_, longitude = inverted_haversine(
3834+
self.env.latitude,
3835+
self.env.longitude,
3836+
self.drift[:, 1],
3837+
self.bearing[:, 1],
3838+
self.env.earth_radius,
38483839
)
3849-
38503840
return np.column_stack((self.time, longitude))
38513841

38523842
def get_controller_observed_variables(self):

rocketpy/tools.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,18 +425,18 @@ def inverted_haversine(lat0, lon0, distance, bearing, earth_radius=6.3781e6):
425425
lon0_rad = np.deg2rad(lon0)
426426

427427
# Apply inverted Haversine formula
428-
lat1_rad = math.asin(
429-
math.sin(lat0_rad) * math.cos(distance / earth_radius)
430-
+ math.cos(lat0_rad)
431-
* math.sin(distance / earth_radius)
432-
* math.cos(math.radians(bearing))
428+
lat1_rad = np.arcsin(
429+
np.sin(lat0_rad) * np.cos(distance / earth_radius)
430+
+ np.cos(lat0_rad)
431+
* np.sin(distance / earth_radius)
432+
* np.cos(np.radians(bearing))
433433
)
434434

435-
lon1_rad = lon0_rad + math.atan2(
436-
math.sin(math.radians(bearing))
437-
* math.sin(distance / earth_radius)
438-
* math.cos(lat0_rad),
439-
math.cos(distance / earth_radius) - math.sin(lat0_rad) * math.sin(lat1_rad),
435+
lon1_rad = lon0_rad + np.arctan2(
436+
np.sin(np.radians(bearing))
437+
* np.sin(distance / earth_radius)
438+
* np.cos(lat0_rad),
439+
np.cos(distance / earth_radius) - np.sin(lat0_rad) * np.sin(lat1_rad),
440440
)
441441

442442
# Convert back to degrees and then return

tests/unit/test_tools.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
euler313_to_quaternions,
88
find_roots_cubic_function,
99
haversine,
10+
inverted_haversine,
1011
tuple_handler,
1112
)
1213

@@ -137,3 +138,50 @@ def test_invalid_pressure_conversion_factor(pressure_conversion_factor):
137138
dictionary="ECMWF",
138139
pressure_conversion_factor=pressure_conversion_factor,
139140
)
141+
142+
143+
def test_inverted_haversine_scalar():
144+
"""Test inverted_haversine with scalar arguments matches haversine distance."""
145+
# Arrange
146+
lat0, lon0 = -23.508958, -46.720080
147+
lat1, lon1 = -23.522939, -46.558253
148+
earth_radius = 6378100.0
149+
distance = haversine(lat0, lon0, lat1, lon1, earth_radius)
150+
bearing = 90.0
151+
152+
# Act
153+
lat_result, lon_result = inverted_haversine(
154+
lat0, lon0, distance, bearing, earth_radius
155+
)
156+
157+
# Assert
158+
recalculated_distance = haversine(lat0, lon0, lat_result, lon_result, earth_radius)
159+
assert recalculated_distance == pytest.approx(distance, abs=1e-2)
160+
161+
162+
def test_inverted_haversine_array():
163+
"""Test inverted_haversine with NumPy arrays returns correct array results."""
164+
# Arrange
165+
lat0, lon0 = -23.508958, -46.720080
166+
distances = np.array([0.0, 5000.0, 16591.438])
167+
bearings = np.array([0.0, 45.0, 90.0])
168+
earth_radius = 6378100.0
169+
170+
# Act
171+
lat_results, lon_results = inverted_haversine(
172+
lat0, lon0, distances, bearings, earth_radius
173+
)
174+
175+
# Assert
176+
assert isinstance(lat_results, np.ndarray)
177+
assert isinstance(lon_results, np.ndarray)
178+
assert len(lat_results) == 3
179+
assert len(lon_results) == 3
180+
181+
# Check scalar consistency for each element
182+
for i, distance in enumerate(distances):
183+
lat_scalar, lon_scalar = inverted_haversine(
184+
lat0, lon0, distance, bearings[i], earth_radius
185+
)
186+
assert lat_results[i] == pytest.approx(lat_scalar)
187+
assert lon_results[i] == pytest.approx(lon_scalar)

0 commit comments

Comments
 (0)