Skip to content

Commit

Permalink
distance: add warning when altitudes are different (#387)
Browse files Browse the repository at this point in the history
  • Loading branch information
KostyaEsmukov committed Jun 21, 2020
1 parent eb773fc commit cd3fbaf
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
54 changes: 54 additions & 0 deletions geopy/distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,44 @@
>>> print((d(ne, cl) + d(cl, wa) + d(wa, pa)).miles)
3277.30439191
.. _distance_altitudes:
Currently all algorithms assume that altitudes of the points are either
zero (as in the examples above) or equal, and are relatively small.
Thus altitudes never affect the resulting distances::
>>> from geopy import distance
>>> newport_ri = (41.49008, -71.312796)
>>> cleveland_oh = (41.499498, -81.695391)
>>> print(distance.distance(newport_ri, cleveland_oh).km)
866.4554329098687
>>> newport_ri = (41.49008, -71.312796, 100)
>>> cleveland_oh = (41.499498, -81.695391, 100)
>>> print(distance.distance(newport_ri, cleveland_oh).km)
866.4554329098687
If you need to calculate distances with elevation, then for short
distances the `Euclidean distance
<https://en.wikipedia.org/wiki/Euclidean_distance>`_ formula might give
a suitable approximation::
>>> import math
>>> from geopy import distance
>>> p1 = (43.668613, 40.258916, 0.976)
>>> p2 = (43.658852, 40.250839, 1.475)
>>> flat_distance = distance.distance(p1[:2], p2[:2]).km
>>> print(flat_distance)
1.265133525952866
>>> euclidian_distance = math.sqrt(flat_distance**2 + (p2[2] - p1[2])**2)
>>> print(euclidian_distance)
1.359986705262199
.. versionchanged:: 1.23.0
Calculating distances between points with different altitudes now
causes a deprecation warning. In geopy 2.0 this will become a
``ValueError`` exception.
"""
from __future__ import division

Expand Down Expand Up @@ -146,6 +184,20 @@ def lonlat(x, y, z=0):
return Point(y, x, z)


def _ensure_same_altitude(a, b):
if abs(a.altitude - b.altitude) > 1e-6:
warnings.warn(
'Calculating distance between points with different altitudes '
'is not supported. The calculated distance would be '
'at the same elevation (as if the points had equal altitudes). '
'In geopy 2.0 this will become a ValueError exception.',
DeprecationWarning, stacklevel=3
)
# Note: non-zero equal altitudes are fine: assuming that
# the elevation is many times smaller than the Earth radius
# it won't give much error.


class Distance(object):
"""
Base for :class:`.great_circle`, :class:`.vincenty`, and
Expand Down Expand Up @@ -302,6 +354,7 @@ def __init__(self, *args, **kwargs):

def measure(self, a, b):
a, b = Point(a), Point(b)
_ensure_same_altitude(a, b)

lat1, lng1 = radians(degrees=a.latitude), radians(degrees=a.longitude)
lat2, lng2 = radians(degrees=b.latitude), radians(degrees=b.longitude)
Expand Down Expand Up @@ -408,6 +461,7 @@ def set_ellipsoid(self, ellipsoid):
# Call geographiclib routines for measure and destination
def measure(self, a, b):
a, b = Point(a), Point(b)
_ensure_same_altitude(a, b)
lat1, lon1 = a.latitude, a.longitude
lat2, lon2 = b.latitude, b.longitude

Expand Down
22 changes: 22 additions & 0 deletions test/test_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,17 @@ def test_should_compute_destination_for_half_trip_around_equator(self):
self.assertAlmostEqual(destination.latitude, 0)
self.assertAlmostEqual(destination.longitude, 180)

def test_different_altitudes_warning(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
# Different altitudes emit a warning:
self.cls((10, 10, 10), (20, 20, 15))
self.assertEqual(1, len(w))

# Equal non-zero altitudes don't warn:
self.cls((10, 10, 10), (20, 20, 10))
self.assertEqual(1, len(w))


@patch.object(VincentyDistance, '_show_deprecation_warning', False)
class TestWhenComputingVincentyDistance(CommonDistanceCases,
Expand Down Expand Up @@ -364,6 +375,17 @@ def setUp(self):
def tearDown(self):
self.cls.ELLIPSOID = self.original_ellipsoid

def test_different_altitudes_warning(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
# Different altitudes emit a warning:
self.cls((10, 10, 10), (20, 20, 15))
self.assertEqual(1, len(w))

# Equal non-zero altitudes don't warn:
self.cls((10, 10, 10), (20, 20, 10))
self.assertEqual(1, len(w))

def test_miscellaneous_high_accuracy_cases(self):

testcases = [
Expand Down

0 comments on commit cd3fbaf

Please sign in to comment.