Skip to content

Commit 5ae29f8

Browse files
committed
Add bounding box to states call
1 parent 6ce7a46 commit 5ae29f8

File tree

6 files changed

+108
-8
lines changed

6 files changed

+108
-8
lines changed

src/python_opensky/const.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,9 @@ class AircraftCategory(int, Enum):
3535
POINT_OBSTACLE = 18
3636
CLUSTER_OBSTACLE = 19
3737
LINE_OBSTACLE = 20
38+
39+
40+
MIN_LATITUDE = "lamin"
41+
MAX_LATITUDE = "lamax"
42+
MIN_LONGITUDE = "lomin"
43+
MAX_LONGITUDE = "lomax"

src/python_opensky/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ class OpenSkyError(Exception):
77

88
class OpenSkyConnectionError(OpenSkyError):
99
"""OpenSky connection exception."""
10+
11+
12+
class OpenSkyCoordinateError(OpenSkyError):
13+
"""OpenSky coordinate exception."""

src/python_opensky/models.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Asynchronous Python client for the OpenSky API."""
22
from __future__ import annotations
33

4+
from dataclasses import dataclass
5+
46
from pydantic import BaseModel, Field
57

68
from .const import AircraftCategory, PositionSource
9+
from .exceptions import OpenSkyCoordinateError
710

811

912
class StatesResponse(BaseModel):
@@ -60,15 +63,34 @@ class StateVector(BaseModel):
6063
category: AircraftCategory = Field(...)
6164

6265

63-
class BoundingBox(BaseModel):
66+
@dataclass
67+
class BoundingBox:
6468
"""Bounding box for retrieving state vectors."""
6569

66-
min_latitude: float = Field(...)
67-
max_latitude: float = Field(...)
68-
min_longitude: float = Field(...)
69-
max_longitude: float = Field(...)
70+
min_latitude: float
71+
max_latitude: float
72+
min_longitude: float
73+
max_longitude: float
74+
75+
def validate(self) -> None:
76+
"""Validate if the latitude and longitude are correct."""
77+
self._check_latitude(self.min_latitude)
78+
self._check_latitude(self.max_latitude)
79+
self._check_longitude(self.min_longitude)
80+
self._check_longitude(self.max_longitude)
81+
82+
@staticmethod
83+
def _check_latitude(degrees: float) -> None:
84+
if degrees < -90 or degrees > 90:
85+
msg = f"Invalid latitude {degrees}! Must be in [-90, 90]."
86+
raise OpenSkyCoordinateError(msg)
87+
88+
@staticmethod
89+
def _check_longitude(degrees: float) -> None:
90+
if degrees < -180 or degrees > 180:
91+
msg = f"Invalid longitude {degrees}! Must be in [-180, 180]."
92+
raise OpenSkyCoordinateError(msg)
7093

7194

7295
StatesResponse.update_forward_refs()
7396
StateVector.update_forward_refs()
74-
BoundingBox.update_forward_refs()

src/python_opensky/opensky.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
from aiohttp.hdrs import METH_GET
1313
from yarl import URL
1414

15+
from .const import MAX_LATITUDE, MAX_LONGITUDE, MIN_LATITUDE, MIN_LONGITUDE
1516
from .exceptions import OpenSkyConnectionError, OpenSkyError
16-
from .models import StatesResponse
17+
from .models import BoundingBox, StatesResponse
1718

1819

1920
@dataclass
@@ -100,13 +101,20 @@ async def _request(
100101

101102
return cast(dict[str, Any], await response.json())
102103

103-
async def states(self) -> StatesResponse:
104+
async def states(self, bounding_box: BoundingBox | None = None) -> StatesResponse:
104105
"""Retrieve state vectors for a given time."""
105106
params = {
106107
"time": 0,
107108
"extended": "true",
108109
}
109110

111+
if bounding_box:
112+
bounding_box.validate()
113+
params[MIN_LATITUDE] = bounding_box.min_latitude
114+
params[MAX_LATITUDE] = bounding_box.max_latitude
115+
params[MIN_LONGITUDE] = bounding_box.min_longitude
116+
params[MAX_LONGITUDE] = bounding_box.max_longitude
117+
110118
data = await self._request("states/all", data=params)
111119

112120
keys = [

tests/test_models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Tests for the OpenSky Library."""
2+
import pytest
3+
4+
from python_opensky import BoundingBox
5+
from python_opensky.exceptions import OpenSkyCoordinateError
6+
7+
8+
def test_degrees() -> None:
9+
"""Test if validation passes."""
10+
box: BoundingBox = BoundingBox(
11+
min_latitude=0,
12+
max_latitude=0,
13+
min_longitude=0,
14+
max_longitude=0,
15+
)
16+
box.validate()
17+
box = BoundingBox(
18+
min_latitude=-91,
19+
max_latitude=0,
20+
min_longitude=0,
21+
max_longitude=0,
22+
)
23+
with pytest.raises(OpenSkyCoordinateError):
24+
box.validate()
25+
box = BoundingBox(
26+
min_latitude=0,
27+
max_latitude=0,
28+
min_longitude=-181,
29+
max_longitude=0,
30+
)
31+
with pytest.raises(OpenSkyCoordinateError):
32+
box.validate()

tests/test_states.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from python_opensky import (
1010
AircraftCategory,
11+
BoundingBox,
1112
OpenSky,
1213
OpenSkyConnectionError,
1314
OpenSkyError,
@@ -61,6 +62,33 @@ async def test_states(
6162
await opensky.close()
6263

6364

65+
async def test_states_with_bounding_box(
66+
aresponses: ResponsesMockServer,
67+
) -> None:
68+
"""Test retrieving states."""
69+
aresponses.add(
70+
OPENSKY_URL,
71+
"/api/states/all?time=0&extended=true&lamin=0&lamax=0&lomin=0&lomax=0",
72+
"GET",
73+
aresponses.Response(
74+
status=200,
75+
headers={"Content-Type": "application/json"},
76+
text=load_fixture("states.json"),
77+
),
78+
match_querystring=True,
79+
)
80+
async with aiohttp.ClientSession() as session:
81+
opensky = OpenSky(session=session)
82+
bounding_box = BoundingBox(
83+
min_latitude=0,
84+
max_latitude=0,
85+
min_longitude=0,
86+
max_longitude=0,
87+
)
88+
await opensky.states(bounding_box=bounding_box)
89+
await opensky.close()
90+
91+
6492
async def test_new_session(
6593
aresponses: ResponsesMockServer,
6694
) -> None:

0 commit comments

Comments
 (0)