1 """Module with location helpers.
3 detect_location_info and elevation are mocked by default during tests.
6 from __future__
import annotations
8 from functools
import lru_cache
10 from typing
import Any, NamedTuple
16 WHOAMI_URL =
"https://services.home-assistant.io/whoami/v1"
17 WHOAMI_URL_DEV =
"https://services-dev.home-assistant.workers.dev/whoami/v1"
24 FLATTENING = 1 / 298.257223563
26 AXIS_B = 6356752.314245
28 MILES_PER_KILOMETER = 0.621371
30 CONVERGENCE_THRESHOLD = 1e-12
34 """Tuple with location information."""
50 session: aiohttp.ClientSession,
51 ) -> LocationInfo |
None:
52 """Detect location information."""
56 data[
"use_metric"] = data[
"country_code"]
not in (
"US",
"MM",
"LR")
63 lat1: float |
None, lon1: float |
None, lat2: float, lon2: float
65 """Calculate the distance in meters between two points.
69 if lat1
is None or lon1
is None:
71 result =
vincenty((lat1, lon1), (lat2, lon2))
81 point1: tuple[float, float], point2: tuple[float, float], miles: bool =
False
83 """Vincenty formula (inverse method) to calculate the distance.
85 Result in kilometers or miles between two points on the surface of a
91 if point1[0] == point2[0]
and point1[1] == point2[1]:
94 U1 = math.atan((1 - FLATTENING) * math.tan(math.radians(point1[0])))
95 U2 = math.atan((1 - FLATTENING) * math.tan(math.radians(point2[0])))
96 L = math.radians(point2[1] - point1[1])
104 for _
in range(MAX_ITERATIONS):
105 sinLambda = math.sin(Lambda)
106 cosLambda = math.cos(Lambda)
107 sinSigma = math.sqrt(
108 (cosU2 * sinLambda) ** 2 + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ** 2
112 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda
113 sigma = math.atan2(sinSigma, cosSigma)
114 sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma
115 cosSqAlpha = 1 - sinAlpha**2
117 cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha
118 except ZeroDivisionError:
120 C = FLATTENING / 16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha))
122 Lambda = L + (1 - C) * FLATTENING * sinAlpha * (
124 + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM**2))
126 if abs(Lambda - LambdaPrev) < CONVERGENCE_THRESHOLD:
131 uSq = cosSqAlpha * (AXIS_A**2 - AXIS_B**2) / (AXIS_B**2)
132 A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)))
133 B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)))
143 cosSigma * (-1 + 2 * cos2SigmaM**2)
147 * (-3 + 4 * sinSigma ** 2)
148 * (-3 + 4 * cos2SigmaM ** 2)
153 s = AXIS_B * A * (sigma - deltaSigma)
157 s *= MILES_PER_KILOMETER
162 async
def _get_whoami(session: aiohttp.ClientSession) -> dict[str, Any] |
None:
163 """Query whoami.home-assistant.io for location data."""
165 resp = await session.get(
166 WHOAMI_URL_DEV
if HA_VERSION.endswith(
"0.dev0")
else WHOAMI_URL,
167 timeout=aiohttp.ClientTimeout(total=30),
169 except (aiohttp.ClientError, TimeoutError):
173 raw_info = await resp.json()
174 except (aiohttp.ClientError, ValueError):
178 "ip": raw_info.get(
"ip"),
179 "country_code": raw_info.get(
"country"),
180 "currency": raw_info.get(
"currency"),
181 "region_code": raw_info.get(
"region_code"),
182 "region_name": raw_info.get(
"region"),
183 "city": raw_info.get(
"city"),
184 "zip_code": raw_info.get(
"postal_code"),
185 "time_zone": raw_info.get(
"timezone"),
186 "latitude":
float(raw_info.get(
"latitude")),
187 "longitude":
float(raw_info.get(
"longitude")),
float|None vincenty(tuple[float, float] point1, tuple[float, float] point2, bool miles=False)
float|None distance(float|None lat1, float|None lon1, float lat2, float lon2)
dict[str, Any]|None _get_whoami(aiohttp.ClientSession session)
LocationInfo|None async_detect_location_info(aiohttp.ClientSession session)