Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: '3.7'
python-version: '3.13'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
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
11 changes: 7 additions & 4 deletions geodepy/tests/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ def test_geo_grid_transform_interoperability(self):
dtype='S4,i4,f8,f8',
names=['site', 'zone', 'east', 'north'])

geoed_grid = np.array(list(grid2geo(*x) for x in test_grid_coords[['zone', 'east', 'north']]))
geoed_grid = np.array([grid2geo(*x) for x in test_grid_coords[['zone', 'east', 'north']]])
np.testing.assert_almost_equal(geoed_grid[:, :2], hp2dec_v(np.array(test_geo_coords[['lat', 'lon']].tolist())),
decimal=8)

gridded_geo = np.stack(geo2grid(*x) for x in hp2dec_v(np.array(test_geo_coords[['lat', 'lon']].tolist())))
gridded_geo = np.stack([geo2grid(*x) for x in hp2dec_v(np.array(test_geo_coords[['lat', 'lon']].tolist()))])
np.testing.assert_almost_equal(gridded_geo[:, 2:4].astype(float),
np.array(test_grid_coords[['east', 'north']].tolist()),
decimal=3)
Expand All @@ -93,11 +93,14 @@ def test_geo_grid_transform_interoperability_isg(self):
dtype='S4,i4,f8,f8',
names=['site', 'zone', 'east', 'north'])

geoed_grid = np.array(list(grid2geo(*x, ellipsoid=ans, prj=isg) for x in test_grid_coords[['zone', 'east', 'north']]))
geoed_grid = np.array([grid2geo(*x, ellipsoid=ans, prj=isg)
for x in test_grid_coords[['zone', 'east', 'north']]])
np.testing.assert_almost_equal(geoed_grid[:, :2], np.array(test_geo_coords[['lat', 'lon']].tolist()),
decimal=8)

gridded_geo = np.stack(geo2grid(*x, ellipsoid=ans, prj=isg) for x in np.array(test_geo_coords[['lat', 'lon']].tolist()))
gridded_geo = np.stack([geo2grid(*x, ellipsoid=ans, prj=isg)
for x in np.array(test_geo_coords[['lat', 'lon']].tolist())
])
np.testing.assert_almost_equal(gridded_geo[:, 2:4].astype(float),
np.array(test_grid_coords[['east', 'north']].tolist()),
decimal=3)
Expand Down
6 changes: 3 additions & 3 deletions geodepy/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def conform7(x, y, z, trans, vcv=None):
# Conformal Transform Eq
xyz_after = translation + scale * rot_xyz
# Convert Vector to Separate Variables
xtrans = float(xyz_after[0])
ytrans = float(xyz_after[1])
ztrans = float(xyz_after[2])
xtrans = float(xyz_after[0][0])
ytrans = float(xyz_after[1][0])
ztrans = float(xyz_after[2][0])

# Transformation uncertainty propagation
# Adapted from Harvey B.R. (1998) Practical least squares and statistics for surveyors,
Expand Down