|
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