| 
2 | 2 | from __future__ import annotations  | 
3 | 3 | 
 
  | 
4 | 4 | import asyncio  | 
 | 5 | +import math  | 
5 | 6 | import socket  | 
6 | 7 | from dataclasses import dataclass, field  | 
7 | 8 | from datetime import datetime, timedelta, timezone  | 
@@ -220,6 +221,130 @@ def _convert_state(state: list[Any]) -> dict[str, Any]:  | 
220 | 221 | 
 
  | 
221 | 222 |         return dict(zip(keys, state, strict=True))  | 
222 | 223 | 
 
  | 
 | 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 | + | 
223 | 348 |     async def close(self) -> None:  | 
224 | 349 |         """Close open client session."""  | 
225 | 350 |         if self.session and self._close_session:  | 
 | 
0 commit comments