Compare commits

...

2 Commits

Author SHA1 Message Date
Erik f4767bca4f Fix stale variable name 2026-06-08 08:25:02 +02:00
Erik b71d47af2b Make zone.async_in_zones prioritize the smallest zone 2026-06-05 15:40:16 +02:00
2 changed files with 27 additions and 26 deletions
+19 -18
View File
@@ -112,15 +112,17 @@ def async_in_zones(
) -> tuple[State | None, list[str]]:
"""Find zones which contain the given latitude and longitude.
Returns a tuple of the closest active zone and a list of all zones which
contain the given latitude and longitude. The list of zones is sorted by
distance and then by radius so that the closest and smallest zone is first.
Returns a tuple of the active zone and a list of all zones which contain the
given latitude and longitude. The active zone is the smallest containing
zone, using distance to the zone center as a tie breaker. The list of zones
is sorted by radius and then by distance so that the smallest and closest
zone is first.
This method must be run in the event loop.
"""
# Sort entity IDs so that we are deterministic if equal distance to 2 zones
min_radius: float = sys.maxsize
min_dist: float = sys.maxsize
closest: State | None = None
active_zone: State | None = None
zones: list[tuple[str, float, float]] = []
# This can be called before async_setup by device tracker
@@ -157,24 +159,23 @@ def async_in_zones(
if zone_attrs.get(ATTR_PASSIVE):
continue
# If have a closest and its not closer than the closest skip it
if closest and not (
zone_dist < min_dist
or (
# If same distance, prefer smaller zone
zone_dist == min_dist and zone_radius < closest.attributes[ATTR_RADIUS]
)
# Prefer the smallest zone, using distance to its center as a tie
# breaker. Skip this zone if it is not smaller and not equally sized but
# closer than the current best.
if active_zone and not (
zone_radius < min_radius
or (zone_radius == min_radius and zone_dist < min_dist)
):
continue
# We got here which means it closer than the previous known closest
# or equal distance but this one is smaller.
min_radius = zone_radius
min_dist = zone_dist
closest = zone
active_zone = zone
# Sort by distance and then by radius so the closest and smallest zone is first.
zones.sort(key=lambda x: (x[1], x[2]))
return (closest, [itm[0] for itm in zones])
# Sort by radius and then by distance so the smallest and closest zone is
# first.
zones.sort(key=lambda x: (x[2], x[1]))
return (active_zone, [itm[0] for itm in zones])
def async_get_enclosing_zones(hass: HomeAssistant, zone_entity_id: str) -> list[str]:
+8 -8
View File
@@ -221,14 +221,14 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance_2(
assert in_zones == ["zone.smallest_zone"]
async def test_active_zone_prefers_closer_zone_over_smaller_zone(
async def test_active_zone_prefers_smaller_zone_over_closer_zone(
hass: HomeAssistant,
) -> None:
"""Test the closest containing zone wins over a smaller, farther one.
"""Test the smallest containing zone wins over a larger, closer one.
A larger zone is centered on the point (distance 0) while a smaller zone is
offset but still contains the point. The larger zone is closer to its center,
so it is preferred.
offset but still contains the point. The smaller zone is farther from its
center, but it is preferred because it is smaller.
"""
latitude = 32.880600
longitude = -117.237561
@@ -245,7 +245,7 @@ async def test_active_zone_prefers_closer_zone_over_smaller_zone(
},
{
# Offset ~111 m north; its 200 m radius still contains the
# point. Smaller than Big Zone, but farther from its center.
# point. Farther from its center than Big Zone, but smaller.
"name": "Small Zone",
"latitude": latitude + 0.001,
"longitude": longitude,
@@ -256,10 +256,10 @@ async def test_active_zone_prefers_closer_zone_over_smaller_zone(
)
active_zone = zone.async_active_zone(hass, latitude, longitude)
assert active_zone.entity_id == "zone.big_zone"
assert active_zone.entity_id == "zone.small_zone"
active_zone, in_zones = zone.async_in_zones(hass, latitude, longitude)
assert active_zone.entity_id == "zone.big_zone"
assert in_zones == ["zone.big_zone", "zone.small_zone"]
assert active_zone.entity_id == "zone.small_zone"
assert in_zones == ["zone.small_zone", "zone.big_zone"]
async def test_in_zone_works_for_passive_zones(hass: HomeAssistant) -> None: