Skip to content

Instantly share code, notes, and snippets.

@spencerahill
Last active April 30, 2018 22:52
Show Gist options
  • Save spencerahill/0594d42e815476276be52e74b7ce7870 to your computer and use it in GitHub Desktop.
Save spencerahill/0594d42e815476276be52e74b7ce7870 to your computer and use it in GitHub Desktop.
longitude package with Longitude class, with tests
from .longitude import Longitude
#!/usr/bin/env python
"""Functionality relating to parsing and comparing longitudes."""
import numpy as np
# from .internal_names import LON_STR
LON_STR = 'lon'
def lon_to_0360(lon):
"""Convert longitude(s) to be within [0, 360).
The Eastern hemisphere corresponds to 0 <= lon < 180, and the Western
Hemisphere corresponds to 180 <= lon < 360.
Parameters
----------
lon : scalar or sequence of scalars
One or more longitude values to be converted to lie in the [0, 360)
range
Returns
-------
If ``lon`` is a scalar, then a scalar of the same type in the range [0,
360). If ``lon`` is array-like, then an array-like of the same type
with each element a scalar in the range [0, 360).
"""
quotient = lon // 360
return lon - quotient*360
def _lon_in_west_hem(lon):
if lon_to_0360(lon) >= 180:
return True
else:
return False
def lon_to_pm180(lon):
"""Convert longitude(s) to be within [-180, 180).
The Eastern hemisphere corresponds to 0 <= lon < 180, and the Western
Hemisphere corresponds to -180 <= lon < 0.
Parameters
----------
lon : scalar or sequence of scalars
One or more longitude values to be converted to lie in the [-180, 180)
range
Returns
-------
If ``lon`` is a scalar, then a scalar of the same type in the range
[-180, 180). If ``lon`` is array-like, then an array-like of the same
type with each element a scalar in the range [-180, 180).
"""
lon0360 = lon_to_0360(lon)
if _lon_in_west_hem(lon0360):
return lon0360 - 360
else:
return lon0360
def _maybe_cast_to_lon(obj):
try:
return Longitude(obj)
except ValueError:
return obj
def other_to_lon(func):
def func_other_to_lon(self, other):
return func(self, _maybe_cast_to_lon(other))
return func_other_to_lon
class Longitude(object):
def __init__(self, value):
try:
val_as_float = float(value)
except (ValueError, TypeError):
if not isinstance(value, str):
raise ValueError('value must be a scalar or a string')
if value[-1].lower() not in ('w', 'e'):
raise ValueError("string inputs must end in 'e' or 'w'")
try:
lon_value = float(value[:-1])
except ValueError:
raise ValueError('improperly formatted string')
if (lon_value < 0) or (lon_value > 180):
raise ValueError('Value given as strings with hemisphere '
'identifier must have numerical values '
'within 0 and +180. Value given: '
'{}'.format(lon_value))
self._longitude = lon_value
self._hemisphere = value[-1].upper()
else:
lon_pm180 = lon_to_pm180(val_as_float)
if _lon_in_west_hem(val_as_float):
self._longitude = abs(lon_pm180)
self._hemisphere = 'W'
else:
self._longitude = lon_pm180
self._hemisphere = 'E'
@property
def longitude(self):
return self._longitude
@longitude.setter
def longitude(self, value):
raise ValueError("'longitude' property cannot be modified after "
"Longitude object has been created.")
@property
def hemisphere(self):
return self._hemisphere
@hemisphere.setter
def hemisphere(self, value):
raise ValueError("'hemisphere' property cannot be modified after "
"Longitude object has been created.")
def __repr__(self):
return "Longitude('{0}{1}')".format(self.longitude, self.hemisphere)
@other_to_lon
def __eq__(self, other):
if isinstance(other, Longitude):
cond = (self.hemisphere == other.hemisphere and
self.longitude == other.longitude)
if cond:
return True
else:
return False
else:
return np.equal(other, self)
@other_to_lon
def __lt__(self, other):
if isinstance(other, Longitude):
if self.hemisphere == 'W':
if other.hemisphere == 'E':
return True
else:
return self.longitude > other.longitude
else:
if other.hemisphere == 'W':
return False
else:
return self.longitude < other.longitude
else:
return np.greater(other, self)
@other_to_lon
def __gt__(self, other):
if isinstance(other, Longitude):
if self.hemisphere == 'W':
if other.hemisphere == 'E':
return False
else:
return self.longitude < other.longitude
else:
if other.hemisphere == 'W':
return True
else:
return self.longitude > other.longitude
else:
return np.less(other, self)
@other_to_lon
def __le__(self, other):
if isinstance(other, Longitude):
return self < other or self == other
else:
return np.greater_equal(other, self)
@other_to_lon
def __ge__(self, other):
if isinstance(other, Longitude):
return self > other or self == other
else:
return np.less_equal(other, self)
def to_0360(self):
"""Convert longitude to its numerical value within [0, 360)."""
if self.hemisphere == 'W':
return -1*self.longitude + 360
else:
return self.longitude
def to_pm180(self):
"""Convert longitude to its numerical value within [-180, 180)."""
if self.hemisphere == 'W':
return -1*self.longitude
else:
return self.longitude
@other_to_lon
def __add__(self, other):
return Longitude(self.to_0360() + other.to_0360())
@other_to_lon
def __sub__(self, other):
return Longitude(self.to_0360() - other.to_0360())
if __name__ == '__main__':
pass
#!/usr/bin/env python
import numpy as np
import pytest
from longitude import Longitude
from longitude.longitude import _maybe_cast_to_lon
_good_init_vals_attrs_objs = {
-10: [10, 'W', Longitude('10W')],
190.2: [169.8, 'W', Longitude('169.8W')],
25: [25, 'E', Longitude('25E')],
365: [5, 'E', Longitude('5E')],
'45.5e': [45.5, 'E', Longitude('45.5E')],
'22.2w': [22.2, 'W', Longitude('22.2W')],
'0': [0, 'E', Longitude('0E')],
}
_bad_init_vals = ['10ee', '-20e', '190w', None, 'abc', {'a': 1}]
@pytest.mark.parametrize(('val', 'attrs_and_obj'),
zip(_good_init_vals_attrs_objs.keys(),
_good_init_vals_attrs_objs.values()))
def test_longitude_init_good(val, attrs_and_obj):
obj = Longitude(val)
expected_lon = attrs_and_obj[0]
expected_hem = attrs_and_obj[1]
expected_obj = attrs_and_obj[2]
assert obj.longitude == expected_lon
assert obj.hemisphere == expected_hem
assert Longitude(val) == expected_obj
def test_longitude_properties():
lon = Longitude(5)
with pytest.raises(ValueError):
lon.longitude = 10
lon.hemisphere = 'W'
@pytest.mark.parametrize(
('obj', 'expected_val'),
[(Longitude('10w'), "Longitude('10.0W')"),
(Longitude(0), "Longitude('0.0E')"),
(Longitude(180), "Longitude('180.0W')")])
def test_longitude_repr(obj, expected_val):
assert obj.__repr__() == expected_val
@pytest.mark.parametrize('bad_val', _bad_init_vals)
def test_longitude_init_bad(bad_val):
with pytest.raises(ValueError):
Longitude(bad_val)
@pytest.mark.parametrize('val', _good_init_vals_attrs_objs.keys())
def test_maybe_cast_to_lon_good(val):
assert isinstance(_maybe_cast_to_lon(val), Longitude)
@pytest.mark.parametrize('bad_val', _bad_init_vals)
def test_maybe_cast_to_lon_bad(bad_val):
assert isinstance(_maybe_cast_to_lon(bad_val), type(bad_val))
@pytest.mark.parametrize(
('obj1', 'obj2'),
[(Longitude('100W'), Longitude('100W')),
(Longitude('90E'), Longitude('90E')),
(Longitude(0), Longitude(0)),
(Longitude('0E'), 0),
(Longitude('0E'), 720),
(Longitude('0E'), [0, 720])])
def test_lon_eq(obj1, obj2):
assert np.all(obj1 == obj2)
@pytest.mark.parametrize(
('obj1', 'obj2'),
[(Longitude('100W'), Longitude('90W')),
(Longitude('90E'), Longitude('100E')),
(Longitude('10W'), Longitude('0E')),
(Longitude('0E'), 10),
(Longitude('0E'), [5, 10])])
def test_lon_lt(obj1, obj2):
assert np.all(obj1 < obj2)
@pytest.mark.parametrize(
('obj1', 'obj2'),
[(Longitude('90W'), Longitude('100W')),
(Longitude('100E'), Longitude('90E')),
(Longitude('0E'), Longitude('10W')),
(Longitude('0E'), -10),
(Longitude('0E'), [-10, -5])])
def test_lon_gt(obj1, obj2):
assert np.all(obj1 > obj2)
@pytest.mark.parametrize(
('obj1', 'obj2'),
[(Longitude('100W'), Longitude('100W')),
(Longitude('90E'), Longitude('90E')),
(Longitude(0), Longitude(0)),
(Longitude('100W'), Longitude('90W')),
(Longitude('90E'), Longitude('100E')),
(Longitude('10W'), Longitude('0E')),
(Longitude('0E'), 10),
(Longitude('0E'), [10, 0])])
def test_lon_leq(obj1, obj2):
assert np.all(obj1 <= obj2)
@pytest.mark.parametrize(
('obj1', 'obj2'),
[(Longitude('100W'), Longitude('100W')),
(Longitude('90E'), Longitude('90E')),
(Longitude(0), Longitude(0)),
(Longitude('90W'), Longitude('100W')),
(Longitude('100E'), Longitude('90E')),
(Longitude('0E'), Longitude('10W')),
(Longitude('0E'), -10),
(Longitude('0E'), [0, -10])])
def test_lon_geq(obj1, obj2):
assert np.all(obj1 >= obj2)
@pytest.mark.parametrize(
('obj', 'expected_val'),
[(Longitude('100W'), 260),
(Longitude(0), 0),
(Longitude('20E'), 20)])
def test_to_0360(obj, expected_val):
assert obj.to_0360() == expected_val
@pytest.mark.parametrize(
('obj', 'expected_val'),
[(Longitude('100W'), -100),
(Longitude(0), 0),
(Longitude('20E'), 20)])
def test_to_pm180(obj, expected_val):
assert obj.to_pm180() == expected_val
@pytest.mark.parametrize(
('obj1', 'obj2', 'expected_val'),
[(Longitude(1), Longitude(1), Longitude(2)),
(Longitude(175), Longitude(10), Longitude('175W'))])
def test_lon_add(obj1, obj2, expected_val):
assert obj1 + obj2 == expected_val
@pytest.mark.parametrize(
('obj1', 'obj2', 'expected_val'),
[(Longitude(1), Longitude(1), Longitude(0)),
(Longitude(185), Longitude(10), Longitude('175E')),
(Longitude(370), Longitude(20), Longitude('10W'))])
def test_lon_sub(obj1, obj2, expected_val):
assert obj1 - obj2 == expected_val
if __name__ == '__main__':
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment