Skip to content

Commit

Permalink
Improve handling of negative values in DMSAngle and DDMAngle classes …
Browse files Browse the repository at this point in the history
…where degree is supplied as integer by introducing positive as a kwarg; create mod operators for DMSAngle and DDMAngle; update related dec2dms(), dec2ddm(), hp2dms(), hp2ddm() functions; update associated tests.
  • Loading branch information
nicgowans committed Feb 13, 2025
1 parent 6af0d51 commit a4f9a44
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 48 deletions.
87 changes: 52 additions & 35 deletions geodepy/angles.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,33 +533,38 @@ class DMSAngle(object):
"""
Class for working with angles in Degrees, Minutes and Seconds format
"""
def __init__(self, degree, minute=0, second=0.0):
def __init__(self, degree, minute=0, second=0.0, positive=None):
"""
:param degree: Angle: whole degrees component (floats truncated)
Alt: formatted string '±DDD MM SS.SSS'
:type degree: float | str
:param minute: Angle: whole minutes component (floats truncated)
:type minute: float
:param second: Angle: seconds component (floats preserved)
:type second: float
:param positive: Optional. True is positive, False is negative. Evaluated from deg/min/sec where None
:type positive: bool
"""
# evaluate sign
if positive is False or str(degree)[0] == '-':
self.positive = False
else:
self.positive = True

# check sign provided for minute and second where positive not provided and degree is int == 0
if degree == 0 and positive is None:
if minute < 0:
self.positive = False
elif second < 0:
self.positive = False

# Convert formatted string 'DDD MM SS.SSS' to DMSAngle
if type(degree) == str:
str_pts = degree.split()
degree = int(str_pts[0])
minute = int(str_pts[1])
second = float(str_pts[2])
# Set sign of object based on sign of any variable
if degree == 0:
if str(degree)[0] == '-':
self.positive = False
elif minute < 0:
self.positive = False
elif second < 0:
self.positive = False
else:
self.positive = True
elif degree > 0:
self.positive = True
else: # degree < 0
self.positive = False

self.degree = abs(int(degree))
self.minute = abs(int(minute))
self.second = abs(second)
Expand Down Expand Up @@ -656,6 +661,9 @@ def __round__(self, n=None):
else:
return -DMSAngle(self.degree, self.minute, round(self.second, n))

def __mod__(self, other):
return dec2dms(self.dec() % other)

def rad(self):
"""
Convert to Radians
Expand Down Expand Up @@ -734,28 +742,33 @@ class DDMAngle(object):
"""
Class for working with angles in Degrees, Decimal Minutes format
"""
def __init__(self, degree, minute=0.0):
def __init__(self, degree, minute=0.0, positive=None):
"""
:param degree: Angle: whole degrees component (floats truncated)
:param minute: Angle:minutes component (floats preserved)
:type degree: float | str
:param minute: Angle: minutes component (floats preserved)
:type minute: float
:param positive: Optional. True is positive, False is negative. Evaluated from deg/min/sec where None
:type positive: bool
"""

# evaluate sign
if positive is False or str(degree)[0] == '-':
self.positive = False
else:
self.positive = True

# check sign provided for minute where positive not provided and degree is int == 0
if degree == 0 and positive is None:
if minute < 0:
self.positive = False

# Convert formatted string 'DDD MM.MMMM' to DDMAngle
if type(degree) == str:
str_pts = degree.split(' ')
degree = int(str_pts[0])
minute = float(str_pts[1])
# Set sign of object based on sign of any variable
if degree == 0:
if str(degree)[0] == '-':
self.positive = False
elif minute < 0:
self.positive = False
else:
self.positive = True
elif degree > 0:
self.positive = True
else: # degree < 0
self.positive = False

self.degree = abs(int(degree))
self.minute = abs(minute)

Expand Down Expand Up @@ -849,6 +862,10 @@ def __round__(self, n=None):
else:
return DDMAngle(-self.degree, -round(self.minute, n))

def __mod__(self, other):
return dec2ddm(self.dec() % other)


def rad(self):
"""
Convert to Radians
Expand Down Expand Up @@ -1001,8 +1018,8 @@ def dec2dms(dec):
"""
minute, second = divmod(abs(dec) * 3600, 60)
degree, minute = divmod(minute, 60)
return (DMSAngle(degree, minute, second) if dec >= 0
else DMSAngle(-degree, minute, second))
return (DMSAngle(degree, minute, second, positive=True) if dec >= 0
else DMSAngle(degree, minute, second, positive=False))


def dec2ddm(dec):
Expand All @@ -1016,7 +1033,7 @@ def dec2ddm(dec):
minute, second = divmod(abs(dec) * 3600, 60)
degree, minute = divmod(minute, 60)
minute = minute + (second / 60)
return DDMAngle(degree, minute) if dec >= 0 else DDMAngle(-degree, minute)
return DDMAngle(degree, minute, positive=True) if dec >= 0 else DDMAngle(degree, minute, positive=False)


# Functions converting from Hewlett-Packard (HP) format to other formats
Expand Down Expand Up @@ -1102,8 +1119,8 @@ def hp2dms(hp):
"""
degmin, second = divmod(abs(hp) * 1000, 10)
degree, minute = divmod(degmin, 100)
return (DMSAngle(degree, minute, second * 10) if hp >= 0
else DMSAngle(-degree, minute, second * 10))
return (DMSAngle(degree, minute, second * 10, positive=True) if hp >= 0
else DMSAngle(degree, minute, second * 10, positive=False))


def hp2ddm(hp):
Expand All @@ -1117,7 +1134,7 @@ def hp2ddm(hp):
degmin, second = divmod(abs(hp) * 1000, 10)
degree, minute = divmod(degmin, 100)
minute = minute + (second / 6)
return DDMAngle(degree, minute) if hp >= 0 else DDMAngle(-degree, minute)
return DDMAngle(degree, minute, positive=True) if hp >= 0 else DDMAngle(degree, minute, positive=False)


# Functions converting from Gradians format to other formats
Expand Down
37 changes: 24 additions & 13 deletions geodepy/tests/test_angles.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,77 +12,88 @@
dd2sec, angular_typecheck)

rad_exs = [radians(123.74875), radians(12.575), radians(-12.575),
radians(0.0525), radians(0.005)]
radians(0.0525), radians(0.005), radians(-0.005)]

dec_ex = 123.74875
dec_ex2 = 12.575
dec_ex3 = -12.575
dec_ex4 = 0.0525
dec_ex5 = 0.005
dec_exs = [dec_ex, dec_ex2, dec_ex3, dec_ex4, dec_ex5]
dec_ex6 = -0.005
dec_exs = [dec_ex, dec_ex2, dec_ex3, dec_ex4, dec_ex5, dec_ex6]

deca_ex = DECAngle(123.74875)
deca_ex2 = DECAngle(12.575)
deca_ex3 = DECAngle(-12.575)
deca_ex4 = DECAngle(0.0525)
deca_ex5 = DECAngle(0.005)
deca_exs = [deca_ex, deca_ex2, deca_ex3, deca_ex4, deca_ex5]
deca_ex6 = DECAngle(-0.005)
deca_exs = [deca_ex, deca_ex2, deca_ex3, deca_ex4, deca_ex5, deca_ex6]

hp_ex = 123.44555
hp_ex2 = 12.3430
hp_ex3 = -12.3430
hp_ex4 = 0.0309
hp_ex5 = 0.0018
hp_exs = [hp_ex, hp_ex2, hp_ex3, hp_ex4, hp_ex5]
hp_ex6 = -0.0018
hp_exs = [hp_ex, hp_ex2, hp_ex3, hp_ex4, hp_ex5, hp_ex6]

hpa_ex = HPAngle(123.44555)
hpa_ex2 = HPAngle(12.3430)
hpa_ex3 = HPAngle(-12.3430)
hpa_ex4 = HPAngle(0.0309)
hpa_ex5 = HPAngle(0.0018)
hpa_exs = [hpa_ex, hpa_ex2, hpa_ex3, hpa_ex4, hpa_ex5]
hpa_ex6 = HPAngle(-0.0018)
hpa_exs = [hpa_ex, hpa_ex2, hpa_ex3, hpa_ex4, hpa_ex5, hpa_ex6]

dms_ex = DMSAngle(123, 44, 55.5)
dms_ex2 = DMSAngle(12, 34, 30)
dms_ex3 = DMSAngle(-12, -34, -30)
dms_ex4 = DMSAngle(0, 3, 9)
dms_ex5 = DMSAngle(0, 0, 18)
dms_exs = [dms_ex, dms_ex2, dms_ex3, dms_ex4, dms_ex5]
# dms_ex6 = DMSAngle(-0, 0, -18)
dms_ex6 = DMSAngle(0, 0, 18, positive=False)
dms_exs = [dms_ex, dms_ex2, dms_ex3, dms_ex4, dms_ex5, dms_ex6]

dms_str = '123 44 55.5'
dms_str2 = '12 34 30'
dms_str3 = '-12 34 30'
dms_str4 = '0 3 9'
dms_str5 = '0 0 18'
dms_strs = [dms_str, dms_str2, dms_str3, dms_str4, dms_str5]
dms_str6 = '-0 0 18'
dms_strs = [dms_str, dms_str2, dms_str3, dms_str4, dms_str5, dms_str6]

ddm_ex = DDMAngle(123, 44.925)
ddm_ex2 = DDMAngle(12, 34.5)
ddm_ex3 = DDMAngle(-12, -34.5)
ddm_ex4 = DDMAngle(0, 3.15)
ddm_ex5 = DDMAngle(0, 0.3)
ddm_exs = [ddm_ex, ddm_ex2, ddm_ex3, ddm_ex4, ddm_ex5]
ddm_ex6 = DDMAngle(0, 0.3, positive=False)
ddm_exs = [ddm_ex, ddm_ex2, ddm_ex3, ddm_ex4, ddm_ex5, ddm_ex6]

ddm_str = '123 44.925'
ddm_str2 = '12 34.5'
ddm_str3 = '-12 34.5'
ddm_str4 = '0 3.15'
ddm_str5 = '0 0.3'
ddm_strs = [ddm_str, ddm_str2, ddm_str3, ddm_str4, ddm_str5]
ddm_str6 = '-0 0.3'
ddm_strs = [ddm_str, ddm_str2, ddm_str3, ddm_str4, ddm_str5, ddm_str6]

gon_ex = 137.4986111111111
gon_ex2 = 13.97222222222222
gon_ex3 = -13.97222222222222
gon_ex4 = 0.05833333333333333
gon_ex5 = 0.00555555555555555
gon_exs = [gon_ex, gon_ex2, gon_ex3, gon_ex4, gon_ex5]
gon_ex6 = -0.00555555555555555
gon_exs = [gon_ex, gon_ex2, gon_ex3, gon_ex4, gon_ex5, gon_ex6]

gona_ex = GONAngle(137.4986111111111)
gona_ex2 = GONAngle(13.97222222222222)
gona_ex3 = GONAngle(-13.97222222222222)
gona_ex4 = GONAngle(0.05833333333333333)
gona_ex5 = GONAngle(0.00555555555555555)
gona_exs = [gona_ex, gona_ex2, gona_ex3, gona_ex4, gona_ex5]
gona_ex6 = GONAngle(-0.00555555555555555)
gona_exs = [gona_ex, gona_ex2, gona_ex3, gona_ex4, gona_ex5, gona_ex6]


class TestConvert(unittest.TestCase):
Expand Down Expand Up @@ -350,7 +361,7 @@ def test_DMSAngle(self):
self.assertTrue(DMSAngle(1, 2, -3).positive)
self.assertFalse(DMSAngle(0, -1, 2).positive)
self.assertFalse(DMSAngle(0, 0, -3).positive)
self.assertTrue(DMSAngle(-0, 1, 2).positive)
self.assertFalse(DMSAngle(0, 1, 2, positive=False).positive)
self.assertFalse(DMSAngle(-0.0, 1, 2).positive)
self.assertEqual(repr(dms_ex), '{DMSAngle: +123d 44m 55.5s}')
self.assertEqual(repr(dms_ex3), '{DMSAngle: -12d 34m 30s}')
Expand Down Expand Up @@ -434,7 +445,7 @@ def test_DDMAngle(self):
self.assertTrue(DDMAngle(1, -2).positive)
self.assertTrue(DDMAngle(1, 2).positive)
self.assertFalse(DDMAngle(0, -1).positive)
self.assertTrue(DDMAngle(-0, 1).positive)
self.assertFalse(DDMAngle(0, 1, positive=False).positive)
self.assertFalse(DDMAngle(-0.0, 1).positive)
self.assertEqual(repr(ddm_ex), '{DDMAngle: +123d 44.925m}')
self.assertEqual(repr(ddm_ex3), '{DDMAngle: -12d 34.5m}')
Expand Down

0 comments on commit a4f9a44

Please sign in to comment.