diff --git a/android/src/toga_android/hardware/location.py b/android/src/toga_android/hardware/location.py index 2f0dce7f51..e5b723f4a9 100644 --- a/android/src/toga_android/hardware/location.py +++ b/android/src/toga_android/hardware/location.py @@ -24,11 +24,19 @@ def toga_location(location): else: altitude = None - return { + result = { "location": latlng, "altitude": altitude, } + if location.hasAccuracy(): + result["accuracy"] = location.getAccuracy() + + if location.hasVerticalAccuracy(): + result["vertical_accuracy"] = location.getVerticalAccuracyMeters() + + return result + class TogaLocationConsumer(dynamic_proxy(Consumer)): def __init__(self, impl, result): diff --git a/android/tests_backend/hardware/location.py b/android/tests_backend/hardware/location.py index 7675f25f3f..7596c29d8b 100644 --- a/android/tests_backend/hardware/location.py +++ b/android/tests_backend/hardware/location.py @@ -54,6 +54,8 @@ def add_location(self, location, altitude, cached=False): native_location = Location(LocationManager.FUSED_PROVIDER) native_location.setLatitude(location.lat) native_location.setLongitude(location.lng) + native_location.setAccuracy(10.0) # Set horizontal accuracy + native_location.setVerticalAccuracyMeters(5.0) if altitude: native_location.setAltitude(altitude) diff --git a/changes/3112.feature.rst b/changes/3112.feature.rst new file mode 100644 index 0000000000..ffcfc1d697 --- /dev/null +++ b/changes/3112.feature.rst @@ -0,0 +1 @@ +Added accuracy data to location results for Android, iOS, and macOS platforms. diff --git a/cocoa/src/toga_cocoa/hardware/location.py b/cocoa/src/toga_cocoa/hardware/location.py index 74bf8bf51e..9774051455 100644 --- a/cocoa/src/toga_cocoa/hardware/location.py +++ b/cocoa/src/toga_cocoa/hardware/location.py @@ -19,12 +19,15 @@ def toga_location(location): # A vertical accuracy that non-positive indicates altitude is invalid. if location.verticalAccuracy > 0.0: altitude = location.altitude + vertical_accuracy = location.verticalAccuracy else: altitude = None - + vertical_accuracy = None return { "location": latlng, "altitude": altitude, + "horizontalAccuracy": location.horizontalAccuracy, + "verticalAccuracy": vertical_accuracy, } diff --git a/core/src/toga/types.py b/core/src/toga/types.py index ca0fca5822..761744a5b4 100644 --- a/core/src/toga/types.py +++ b/core/src/toga/types.py @@ -24,8 +24,19 @@ class LatLng(NamedTuple): #: Longitude lng: float + #: Horizontal accuracy + horizontal_accuracy: float | None = None + + #: Vertical accuracy + vertical_accuracy: float | None = None + def __str__(self) -> str: - return f"({self.lat:6f}, {self.lng:6f})" + values = [f"{self.lat:.6f}", f"{self.lng:.6f}"] + if self.horizontal_accuracy is not None: + values.append(f"{self.horizontal_accuracy:.6f}") + if self.vertical_accuracy is not None: + values.append(f"{self.vertical_accuracy:.6f}") + return f"({', '.join(values)})" class Position(NamedTuple): diff --git a/core/tests/widgets/test_mapview.py b/core/tests/widgets/test_mapview.py index f0cd464134..857ae571da 100644 --- a/core/tests/widgets/test_mapview.py +++ b/core/tests/widgets/test_mapview.py @@ -74,21 +74,25 @@ def test_create_with_values(pins): def test_latlng_properties(): """LatLng objects behave like tuples.""" # Create a LatLng object. - pos = toga.LatLng(37.42, 42.37123456) + pos = toga.LatLng(37.42, 42.37123456, 1.0, 2.0) # String representation is clean, and clipped to 6dp - assert str(pos) == "(37.420000, 42.371235)" + assert str(pos) == "(37.420000, 42.371235, 1.000000, 2.000000)" # Values can be accessed by attribute assert pos.lat == pytest.approx(37.42) assert pos.lng == pytest.approx(42.37123456) + assert pos.horizontal_accuracy == pytest.approx(1.0) + assert pos.vertical_accuracy == pytest.approx(2.0) # Values can be accessed by position assert pos[0] == pytest.approx(37.42) assert pos[1] == pytest.approx(42.37123456) + assert pos[2] == pytest.approx(1.0) + assert pos[3] == pytest.approx(2.0) # LatLng can be compared with tuples - assert pos == pytest.approx((37.42, 42.37123456)) + assert pos == pytest.approx((37.42, 42.37123456, 1.0, 2.0)) @pytest.mark.parametrize( @@ -114,21 +118,21 @@ def test_pin_location(widget): pin = toga.MapPin((37.42, 42.37), title="TheTitle", subtitle="TheSubtitle") assert isinstance(pin.location, toga.LatLng) - assert pin.location == (37.42, 42.37) + assert pin.location == (37.42, 42.37, None, None) # Change the pin location before the pin is on the map EventLog.reset() # Pin Location can be changed with a tuple - pin.location = (23.45, 67.89) + pin.location = (23.45, 67.89, 5.0, 6.0) assert isinstance(pin.location, toga.LatLng) - assert pin.location == (23.45, 67.89) + assert pin.location == (23.45, 67.89, 5.0, 6.0) assert_action_not_performed(widget, "update pin") # Pin Location can be changed with a LatLng - pin.location = toga.LatLng(12.34, 56.78) + pin.location = toga.LatLng(12.34, 56.78, 5.0, 6.0) assert isinstance(pin.location, toga.LatLng) - assert pin.location == (12.34, 56.78) + assert pin.location == (12.34, 56.78, 5.0, 6.0) assert_action_not_performed(widget, "update pin") # Add the pin to a map @@ -136,9 +140,9 @@ def test_pin_location(widget): assert_action_performed_with(widget, "add pin", pin=pin) # Pin Location can be changed while on the map - pin.location = (23.45, 67.89) + pin.location = (23.45, 67.89, None, None) assert isinstance(pin.location, toga.LatLng) - assert pin.location == (23.45, 67.89) + assert pin.location == (23.45, 67.89, None, None) assert_action_performed_with(widget, "update pin", pin=pin) # Remove the pin from the map @@ -147,9 +151,9 @@ def test_pin_location(widget): assert_action_performed_with(widget, "remove pin", pin=pin) # Updating the location doesn't modify the map - pin.location = toga.LatLng(12.34, 56.78) + pin.location = toga.LatLng(12.34, 56.78, 1.0, 2.0) assert isinstance(pin.location, toga.LatLng) - assert pin.location == (12.34, 56.78) + assert pin.location == (12.34, 56.78, 1.0, 2.0) assert_action_not_performed(widget, "update pin") diff --git a/iOS/src/toga_iOS/hardware/location.py b/iOS/src/toga_iOS/hardware/location.py index bdf94500de..3ae3c37e04 100644 --- a/iOS/src/toga_iOS/hardware/location.py +++ b/iOS/src/toga_iOS/hardware/location.py @@ -22,12 +22,15 @@ def toga_location(location): # A vertical accuracy that non-positive indicates altitude is invalid. if location.verticalAccuracy > 0.0: altitude = location.altitude + vertical_accuracy = location.verticalAccuracy else: altitude = None - + vertical_accuracy = None return { "location": latlng, "altitude": altitude, + "horizontalAccuracy": location.horizontalAccuracy, + "verticalAccuracy": vertical_accuracy, }