Last active
April 30, 2018 22:52
-
-
Save spencerahill/0594d42e815476276be52e74b7ce7870 to your computer and use it in GitHub Desktop.
longitude package with Longitude class, with tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from .longitude import Longitude |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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