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
16 changes: 14 additions & 2 deletions android/src/toga_android/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

def toga_location(location):
"""Convert an Android location into a Toga LatLng and altitude."""
latlng = LatLng(location.getLatitude(), location.getLongitude())

# MSL altitude was added in API 34. We can't test this at runtime
if Build.VERSION.SDK_INT >= 34 and location.hasMslAltitude(): # pragma: no cover
altitude = location.getMslAltitudeMeters()
Expand All @@ -24,6 +22,20 @@ def toga_location(location):
else:
altitude = None

latlng = LatLng(
location.getLatitude(),
location.getLongitude(),
altitude=altitude,
horizontal_accuracy=(
location.getAccuracy() if location.hasAccuracy() else None
),
vertical_accuracy=(
location.getVerticalAccuracyMeters()
if location.hasVerticalAccuracy()
else None
),
)

return {
"location": latlng,
"altitude": altitude,
Expand Down
1 change: 1 addition & 0 deletions changes/3112.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add altitude, horizontal accuracy and vertical accuracy to LatLng.
15 changes: 9 additions & 6 deletions cocoa/src/toga_cocoa/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@

def toga_location(location):
"""Convert a Cocoa location into a Toga LatLng and altitude."""
# A non-positive vertical accuracy indicates altitude is invalid.
altitude_valid = location.verticalAccuracy > 0.0
altitude = location.altitude if altitude_valid else None

latlng = LatLng(
location.coordinate.latitude,
location.coordinate.longitude,
altitude=altitude,
horizontal_accuracy=(
location.horizontalAccuracy if location.horizontalAccuracy > 0.0 else None
),
vertical_accuracy=location.verticalAccuracy if altitude_valid else None,
)

# A vertical accuracy that non-positive indicates altitude is invalid.
if location.verticalAccuracy > 0.0:
altitude = location.altitude
else:
altitude = None

return {
"location": latlng,
"altitude": altitude,
Expand Down
45 changes: 39 additions & 6 deletions core/src/toga/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,47 @@
"""


class LatLng(NamedTuple):
"""A geographic coordinate."""
class LatLng(tuple):
"""A geographic coordinate, with optional altitude and accuracy attributes.

lat: float
"""Latitude"""
A LatLng compares equal to a 2-tuple (lat, lng). The optional
altitude, horizontal_accuracy, and vertical_accuracy attributes
are not considered for equality or hashing.
"""

lng: float
"""Longitude"""
altitude: float | None = None
"""Altitude in meters, or None if not available."""

horizontal_accuracy: float | None = None
"""Horizontal accuracy in meters, or None if not available."""

vertical_accuracy: float | None = None
"""Vertical accuracy in meters, or None if not available."""

def __new__(
cls,
lat: float,
lng: float,
*,
altitude: float | None = None,
horizontal_accuracy: float | None = None,
vertical_accuracy: float | None = None,
) -> LatLng:
self = super().__new__(cls, (lat, lng))
self.altitude = altitude
self.horizontal_accuracy = horizontal_accuracy
self.vertical_accuracy = vertical_accuracy
return self

@property
def lat(self) -> float:
"""Latitude"""
return self[0]

@property
def lng(self) -> float:
"""Longitude"""
return self[1]

def __str__(self) -> str:
return f"({self.lat:6f}, {self.lng:6f})"
Expand Down
51 changes: 50 additions & 1 deletion core/tests/test_types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
from toga.types import Position, Size
import pytest

from toga.types import LatLng, Position, Size


def test_latlng_required_properties():
"""A LatLng class has latitude and longitude values."""
LAT = -31.95
LNG = 115.86
ll = LatLng(LAT, LNG)
assert ll.lat == pytest.approx(LAT)
assert ll.lng == pytest.approx(LNG)
assert str(ll) == f"({LAT:6f}, {LNG:6f})"

assert ll == LatLng(LAT, LNG)
assert ll != LatLng(LAT, LNG + 1)

assert ll == (LAT, LNG) # Tuple equivalence for backwards-compatibility
assert ll != (LAT + 1, LNG)
assert hash(ll) == hash((LAT, LNG))

# Optional values were not filled in
assert ll.altitude is None
assert ll.horizontal_accuracy is None
assert ll.vertical_accuracy is None


def test_latlng_optional_properties():
"""A LatLng class has optional values for altitude and accuracies."""
LAT = -31.95
LNG = 115.86
ALT = 20.1
HORIZONTAL_ACC = 10.2
VERTICAL_ACC = 11.3
bare_ll = LatLng(LAT, LNG)
ll = LatLng(
LAT,
LNG,
altitude=ALT,
horizontal_accuracy=HORIZONTAL_ACC,
vertical_accuracy=VERTICAL_ACC,
)
assert ll.altitude == pytest.approx(ALT)
assert ll.horizontal_accuracy == pytest.approx(HORIZONTAL_ACC)
assert ll.vertical_accuracy == pytest.approx(VERTICAL_ACC)

assert bare_ll == ll # optional values are not used for comparison
assert bare_ll.altitude != ll.altitude
assert bare_ll.horizontal_accuracy != ll.horizontal_accuracy
assert bare_ll.vertical_accuracy != ll.vertical_accuracy


def test_position_properties():
Expand Down
11 changes: 9 additions & 2 deletions gtk/src/toga_gtk/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@

def toga_location(location):
"""Convert a ``Geoclue.Location`` into ``OnLocationChangeHandler`` kwargs."""
latlng = LatLng(location.props.latitude, location.props.longitude)
altitude = location.get_property("altitude")
altitude = location.props.altitude
latlng = LatLng(
location.props.latitude,
location.props.longitude,
altitude=altitude,
horizontal_accuracy=(
location.props.accuracy if location.props.accuracy > 0.0 else None
),
)

return {
"location": latlng,
Expand Down
15 changes: 9 additions & 6 deletions iOS/src/toga_iOS/hardware/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@

def toga_location(location):
"""Convert a Cocoa location into a Toga LatLng and altitude."""
# A non-positive vertical accuracy indicates altitude is invalid.
altitude_valid = location.verticalAccuracy > 0.0
altitude = location.altitude if altitude_valid else None

latlng = LatLng(
location.coordinate.latitude,
location.coordinate.longitude,
altitude=altitude,
horizontal_accuracy=(
location.horizontalAccuracy if location.horizontalAccuracy > 0.0 else None
),
vertical_accuracy=location.verticalAccuracy if altitude_valid else None,
)

# A vertical accuracy that non-positive indicates altitude is invalid.
if location.verticalAccuracy > 0.0:
altitude = location.altitude
else:
altitude = None

return {
"location": latlng,
"altitude": altitude,
Expand Down
Loading