Skip to content

Commit 02ae55b

Browse files
authored
Merge pull request #28 from joostlek/radius
Add methods to calculate bounding box from radius
2 parents a0cb2df + 033c642 commit 02ae55b

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

src/python_opensky/opensky.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
import asyncio
5+
import math
56
import socket
67
from dataclasses import dataclass, field
78
from datetime import datetime, timedelta, timezone
@@ -220,6 +221,130 @@ def _convert_state(state: list[Any]) -> dict[str, Any]:
220221

221222
return dict(zip(keys, state, strict=True))
222223

224+
@staticmethod
225+
# pylint: disable=too-many-locals
226+
def calculate_point(
227+
latitude: float,
228+
longitude: float,
229+
distance: float,
230+
degrees: float,
231+
) -> tuple[float, float]:
232+
"""Calculate a point from an origin point, direction in degrees and distance."""
233+
# ruff: noqa: N806
234+
# pylint: disable=invalid-name
235+
pi_d4 = math.atan(1.0)
236+
two_pi = pi_d4 * 8.0
237+
latitude = latitude * pi_d4 / 45.0
238+
longitude = longitude * pi_d4 / 45.0
239+
degrees = degrees * pi_d4 / 45.0
240+
if degrees < 0.0:
241+
degrees = degrees + two_pi
242+
if degrees > two_pi:
243+
degrees = degrees - two_pi
244+
axis_a = 6378137
245+
flattening = 1 / 298.257223563
246+
axis_b = axis_a * (1.0 - flattening)
247+
tan_u1 = (1 - flattening) * math.tan(latitude)
248+
u1 = math.atan(tan_u1)
249+
sigma1 = math.atan2(tan_u1, math.cos(degrees))
250+
sinalpha = math.cos(u1) * math.sin(degrees)
251+
cosalpha_sq = 1.0 - sinalpha * sinalpha
252+
u2 = cosalpha_sq * (axis_a * axis_a - axis_b * axis_b) / (axis_b * axis_b)
253+
A = 1.0 + (u2 / 16384) * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)))
254+
B = (u2 / 1024) * (256 + u2 * (-128 + u2 * (74 - 47 * u2)))
255+
# Starting with the approx
256+
sigma = distance / (axis_b * A)
257+
last_sigma = 2.0 * sigma + 2.0 # something impossible
258+
259+
# Iterate the following 3 eqs until no sig change in sigma
260+
# two_sigma_m , delta_sigma
261+
while abs((last_sigma - sigma) / sigma) > 1.0e-9:
262+
two_sigma_m = 2 * sigma1 + sigma
263+
delta_sigma = (
264+
B
265+
* math.sin(sigma)
266+
* (
267+
math.cos(two_sigma_m)
268+
+ (B / 4)
269+
* (
270+
math.cos(sigma)
271+
* (
272+
-1
273+
+ 2 * math.pow(math.cos(two_sigma_m), 2)
274+
- (B / 6)
275+
* math.cos(two_sigma_m)
276+
* (-3 + 4 * math.pow(math.sin(sigma), 2))
277+
* (-3 + 4 * math.pow(math.cos(two_sigma_m), 2))
278+
)
279+
)
280+
)
281+
)
282+
last_sigma = sigma
283+
sigma = (distance / (axis_b * A)) + delta_sigma
284+
phi2 = math.atan2(
285+
(
286+
math.sin(u1) * math.cos(sigma)
287+
+ math.cos(u1) * math.sin(sigma) * math.cos(degrees)
288+
),
289+
(
290+
(1 - flattening)
291+
* math.sqrt(
292+
math.pow(sinalpha, 2)
293+
+ pow(
294+
math.sin(u1) * math.sin(sigma)
295+
- math.cos(u1) * math.cos(sigma) * math.cos(degrees),
296+
2,
297+
),
298+
)
299+
),
300+
)
301+
lembda = math.atan2(
302+
(math.sin(sigma) * math.sin(degrees)),
303+
(
304+
math.cos(u1) * math.cos(sigma)
305+
- math.sin(u1) * math.sin(sigma) * math.cos(degrees)
306+
),
307+
)
308+
C = (flattening / 16) * cosalpha_sq * (4 + flattening * (4 - 3 * cosalpha_sq))
309+
omega = lembda - (1 - C) * flattening * sinalpha * (
310+
sigma
311+
+ C
312+
* math.sin(sigma)
313+
* (
314+
math.cos(two_sigma_m)
315+
+ C * math.cos(sigma) * (-1 + 2 * math.pow(math.cos(two_sigma_m), 2))
316+
)
317+
)
318+
lembda2 = longitude + omega
319+
math.atan2(
320+
sinalpha,
321+
(
322+
-math.sin(u1) * math.sin(sigma)
323+
+ math.cos(u1) * math.cos(sigma) * math.cos(degrees)
324+
),
325+
)
326+
phi2 = phi2 * 45.0 / pi_d4
327+
lembda2 = lembda2 * 45.0 / pi_d4
328+
return phi2, lembda2
329+
330+
@staticmethod
331+
def get_bounding_box(
332+
latitude: float,
333+
longitude: float,
334+
radius: float,
335+
) -> BoundingBox:
336+
"""Get bounding box from radius and a point."""
337+
north = OpenSky.calculate_point(latitude, longitude, radius, 0)
338+
east = OpenSky.calculate_point(latitude, longitude, radius, 90)
339+
south = OpenSky.calculate_point(latitude, longitude, radius, 180)
340+
west = OpenSky.calculate_point(latitude, longitude, radius, 270)
341+
return BoundingBox(
342+
min_latitude=min(north[0], south[0]) + latitude,
343+
max_latitude=max(north[0], south[0]) + latitude,
344+
min_longitude=min(east[1], west[1]) + longitude,
345+
max_longitude=max(east[1], west[1]) + longitude,
346+
)
347+
223348
async def close(self) -> None:
224349
"""Close open client session."""
225350
if self.session and self._close_session:

tests/test_radius.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Tests for the OpenSky Library."""
2+
3+
4+
from python_opensky import (
5+
OpenSky,
6+
)
7+
8+
9+
async def test_calculating_bounding_box() -> None:
10+
"""Test calculating bounding box."""
11+
bounding_box = OpenSky.get_bounding_box(0.0, 0.0, 25000)
12+
assert bounding_box.min_latitude == -0.22609235747829648
13+
assert bounding_box.max_latitude == 0.22609235747829648
14+
assert bounding_box.min_longitude == -0.22457882102988042
15+
assert bounding_box.max_longitude == 0.22457882102988042
16+
17+
18+
async def test_calculating_direction() -> None:
19+
"""Test calculating direction."""
20+
second_point = OpenSky.calculate_point(0.0, 0.0, 25000.0, -180)
21+
assert second_point == (-0.22609235747829648, 2.7503115231199028e-17)
22+
second_point = OpenSky.calculate_point(0.0, 0.0, 25000.0, 361)
23+
assert second_point == (0.22605792234324162, 0.003919461063277522)

0 commit comments

Comments
 (0)