Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion android/src/toga_android/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions android/tests_backend/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions changes/3112.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added accuracy data to location results for Android, iOS, and macOS platforms.
5 changes: 4 additions & 1 deletion cocoa/src/toga_cocoa/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}


Expand Down
13 changes: 12 additions & 1 deletion core/src/toga/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
28 changes: 16 additions & 12 deletions core/tests/widgets/test_mapview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -114,31 +118,31 @@ 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
widget.pins.add(pin)
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
Expand All @@ -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")


Expand Down
5 changes: 4 additions & 1 deletion iOS/src/toga_iOS/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}


Expand Down
Loading