Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebase and tweaks to "Fix forcing zones around equator and add force_northern in from_latlon" #124

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
56 changes: 56 additions & 0 deletions test/test_utm.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,12 @@ def test_force_zone(lat, lon, utm, utm_kw, expected_number, expected_letter):
assert result[3].upper() == expected_letter.upper()


def assert_equal_lat(result, expected_lat, northern=None):
args = result[:3] if northern else result[:4]
lat, _ = UTM.to_latlon(*args, northern=northern, strict=False)
assert lat == pytest.approx(expected_lat, abs=0.001)


def assert_equal_lon(result, expected_lon):
_, lon = UTM.to_latlon(*result[:4], strict=False)
assert lon == pytest.approx(expected_lon, abs=0.001)
Expand All @@ -396,6 +402,56 @@ def test_force_west():
assert_equal_lon(UTM.from_latlon(0, -179.9, 60, "N"), -179.9)


def test_force_north():
# Force southern point to northern zone letter
assert_equal_lat(UTM.from_latlon(-0.1, 0, 31, 'N'), -0.1)

# Again, using force northern
assert_equal_lat(
UTM.from_latlon(-0.1, 0, 31, force_northern=True), -0.1, northern=True)


def test_force_south():
# Force northern point to southern zone letter
assert_equal_lat(UTM.from_latlon(0.1, 0, 31, 'M'), 0.1)

# Again, using force northern as False
assert_equal_lat(
UTM.from_latlon(0.1, 0, 31, force_northern=True), 0.1, northern=True)


@pytest.mark.skipif(not use_numpy, reason="numpy not installed")
@pytest.mark.parametrize("zone", ('N', 'M'))
def test_force_numpy(zone):
# Point above and below equator
lats = np.array([-0.1, 0.1])

with pytest.raises(ValueError,
match="latitudes must all have the same sign"):
UTM.from_latlon(lats, np.array([0, 0]), 31, zone)


@pytest.mark.skipif(not use_numpy, reason="numpy not installed")
@pytest.mark.parametrize("force_northern", (True, False))
def test_force_numpy_force_northern_true(force_northern):
# Point above and below equator
lats = np.array([-0.1, 0.1])

result = UTM.from_latlon(
lats, np.array([0, 0]), force_northern=force_northern)
for expected_lat, easting, northing in zip(lats, *result[:2]):
assert_equal_lat(
(easting, northing, result[2], result[3]), expected_lat,
northern=force_northern)


def test_force_both():
# Force both letter and northern not allowed
with pytest.raises(ValueError, match="set either force_zone_letter or "
"force_northern, but not both"):
UTM.from_latlon(-0.1, 0, 31, 'N', True)


def test_version():
assert isinstance(UTM.__version__, str) and "." in UTM.__version__

Expand Down
30 changes: 18 additions & 12 deletions utm/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,6 @@ def mixed_signs(x):
return use_numpy and mathlib.min(x) < 0 and mathlib.max(x) >= 0


def negative(x):
if use_numpy:
return mathlib.max(x) < 0
return x < 0


def mod_angle(value):
"""Returns angle in radians to be between -pi and pi"""
return (value + mathlib.pi) % (2 * mathlib.pi) - mathlib.pi
Expand All @@ -97,7 +91,8 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
designators can be seen in [1]_

northern: bool
You can set True or False to set this parameter. Default is None
You can set True (North) or False (South) as an alternative to
providing a zone letter. Default is None

strict: bool
Raise an OutOfRangeError if outside of bounds
Expand All @@ -116,7 +111,6 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
"""
if not zone_letter and northern is None:
raise ValueError('either zone_letter or northern needs to be set')

elif zone_letter and northern is not None:
raise ValueError('set either zone_letter or northern, but not both')

Expand Down Expand Up @@ -184,7 +178,7 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
mathlib.degrees(longitude))


def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=None):
def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=None, force_northern=None):
"""This function converts Latitude and Longitude to UTM coordinate

Parameters
Expand All @@ -204,6 +198,11 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
You may force conversion to be included within one UTM zone
letter. For more information see utmzones [1]_

force_northern: bool
You can set True (North) or False (South) as an alternative to
forcing with a zone letter. When set, the returned zone_letter will
be None. Default is None

Returns
-------
easting: float or NumPy array
Expand All @@ -227,6 +226,8 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
raise OutOfRangeError('latitude out of range (must be between 80 deg S and 84 deg N)')
if not in_bounds(longitude, -180, 180):
raise OutOfRangeError('longitude out of range (must be between 180 deg W and 180 deg E)')
if force_zone_letter and force_northern is not None:
raise ValueError('set either force_zone_letter or force_northern, but not both')
if force_zone_number is not None:
check_valid_zone(force_zone_number, force_zone_letter)

Expand All @@ -243,11 +244,16 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
else:
zone_number = force_zone_number

if force_zone_letter is None:
if force_zone_letter is None and force_northern is None:
zone_letter = latitude_to_zone_letter(latitude)
else:
zone_letter = force_zone_letter

if force_northern is None:
northern = (zone_letter >= 'N')
else:
northern = force_northern

lon_rad = mathlib.radians(longitude)
central_lon = zone_number_to_central_longitude(zone_number)
central_lon_rad = mathlib.radians(central_lon)
Expand Down Expand Up @@ -275,9 +281,9 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
a4 / 24 * (5 - lat_tan2 + 9 * c + 4 * c**2) +
a6 / 720 * (61 - 58 * lat_tan2 + lat_tan4 + 600 * c - 330 * E_P2)))

if mixed_signs(latitude):
if force_northern is None and mixed_signs(latitude):
heathhenley marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("latitudes must all have the same sign")
elif negative(latitude):
elif not northern:
northing += 10000000

return easting, northing, zone_number, zone_letter
Expand Down