Skip to content

Commit 7f99d6b

Browse files
Merge pull request #1 from jonathonpsmith/add_isg
Introduction of integrated projection handling and ISG grid/geo conversion capability
2 parents f22fb4e + 4aa33ab commit 7f99d6b

File tree

3 files changed

+85
-25
lines changed

3 files changed

+85
-25
lines changed

geodepy/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def __init__(self, falseeast, falsenorth, cmscale, zonewidth, initialcm):
6666

6767

6868
utm = Projection(500000, 10000000, 0.9996, 6, -177)
69+
isg = Projection(300000, 5000000, 0.99994, 2, -177)
6970

7071

7172
# Helmert 14 parameter transformation

geodepy/convert.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from math import (sin, cos, atan2, radians, degrees,
99
sqrt, cosh, sinh, tan, atan, log)
1010
import datetime
11-
from geodepy.constants import utm, grs80
11+
import warnings
12+
from geodepy.constants import utm, isg, grs80, ans
1213
from geodepy.angles import (DECAngle, HPAngle, GONAngle, DMSAngle, DDMAngle,
1314
dec2hp, dec2hpa, dec2gon, dec2gona,
1415
dec2dms, dec2ddm,
@@ -19,11 +20,6 @@
1920
dd2sec, angular_typecheck)
2021

2122

22-
# Universal Transverse Mercator Projection Parameters
23-
# TODO: Add this part into functions: geo2grid, grid2geo, psfandgridconv
24-
proj = utm
25-
26-
2723
def polar2rect(r, theta):
2824
"""
2925
Converts point in polar coordinates to corresponding rectangular coordinates
@@ -240,7 +236,7 @@ def beta_coeff(ellipsoid):
240236
return b2, b4, b6, b8, b10, b12, b14, b16
241237

242238

243-
def psfandgridconv(xi1, eta1, lat, lon, cm, conf_lat, ellipsoid=grs80):
239+
def psfandgridconv(xi1, eta1, lat, lon, cm, conf_lat, ellipsoid=grs80, prj=utm):
244240
"""
245241
Calculates Point Scale Factor and Grid Convergence. Used in convert.geo2grid
246242
and convert.grid2geo
@@ -270,7 +266,7 @@ def psfandgridconv(xi1, eta1, lat, lon, cm, conf_lat, ellipsoid=grs80):
270266
p += 2*r * a[r-1] * cos(2*r * xi1) * cosh(2*r * eta1)
271267
q += 2*r * a[r-1] * sin(2*r * xi1) * sinh(2*r * eta1)
272268
q = -q
273-
psf = (float(proj.cmscale)
269+
psf = (float(prj.cmscale)
274270
* (A / ellipsoid.semimaj)
275271
* sqrt(q**2 + p**2)
276272
* ((sqrt(1 + (tan(lat)**2))
@@ -289,7 +285,7 @@ def psfandgridconv(xi1, eta1, lat, lon, cm, conf_lat, ellipsoid=grs80):
289285
return psf, grid_conv
290286

291287

292-
def geo2grid(lat, lon, zone=0, ellipsoid=grs80):
288+
def geo2grid(lat, lon, zone=0, ellipsoid=grs80, prj=utm):
293289
"""
294290
Takes a geographic co-ordinate (latitude, longitude) and returns its
295291
corresponding Hemisphere, Zone and Projection Easting and Northing, Point
@@ -314,23 +310,40 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80):
314310
lon = angular_typecheck(lon)
315311
# Input Validation - UTM Extents and Values
316312
zone = int(zone)
317-
if zone < 0 or zone > 60:
318-
raise ValueError('Invalid Zone - Zones from 1 to 60')
313+
if prj == isg:
314+
if zone not in (0, 541, 542, 543, 551, 552, 553, 561, 562, 563, 572):
315+
raise ValueError('Invalid Zone - Choose from 541, 542, 543, 551, 552, 553, 561, 562, 563, 572')
316+
else:
317+
if zone < 0 or zone > 60:
318+
raise ValueError('Invalid Zone - Zones from 1 to 60')
319319

320320
if lat < -80 or lat > 84:
321321
raise ValueError('Invalid Latitude - Latitudes from -80 to +84')
322322

323323
if lon < -180 or lon > 180:
324324
raise ValueError('Invalid Longitude - Longitudes from -180 to +180')
325325

326+
if prj == isg and ellipsoid != ans:
327+
warnings.warn(message='ISG projection should be used with ANS ellipsoid', category=UserWarning)
328+
326329
A = rect_radius(ellipsoid)
327330
a = alpha_coeff(ellipsoid)
328331
lat = radians(lat)
329332
# Calculate Zone
330333
if zone == 0:
331-
zone = int((float(lon) - (
332-
proj.initialcm - (1.5 * proj.zonewidth))) / proj.zonewidth)
333-
cm = float(zone * proj.zonewidth) + (proj.initialcm - proj.zonewidth)
334+
if prj == isg:
335+
amgzone = (float(lon) - (prj.initialcm - (4.5 * prj.zonewidth))) / (prj.zonewidth * 3)
336+
subzone = int((amgzone - int(amgzone)) * 3 + 1)
337+
amgzone = int(amgzone)
338+
zone = int(f'{amgzone}{subzone}')
339+
else:
340+
zone = int((float(lon) - (prj.initialcm - (1.5 * prj.zonewidth))) / prj.zonewidth)
341+
if prj == isg:
342+
amgzone = int(str(zone)[:2])
343+
subzone = int(str(zone)[2])
344+
cm = float((amgzone - 1) * prj.zonewidth * 3 + prj.initialcm + (subzone - 2) * prj.zonewidth)
345+
else:
346+
cm = float(zone * prj.zonewidth + prj.initialcm - prj.zonewidth)
334347

335348
# Conformal Latitude
336349
sigx = (ellipsoid.ecc1 * tan(lat)) / sqrt(1 + (tan(lat) ** 2))
@@ -357,14 +370,14 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80):
357370
y = A * xi
358371

359372
# Hemisphere-dependent UTM Projection Co-ordinates
360-
east = proj.cmscale * x + proj.falseeast
373+
east = prj.cmscale * x + prj.falseeast
361374
if y < 0:
362375
hemisphere = 'South'
363-
north = proj.cmscale * y + proj.falsenorth
376+
north = prj.cmscale * y + prj.falsenorth
364377
else:
365378
hemisphere = 'North'
366379
falsenorth = 0
367-
north = proj.cmscale * y + falsenorth
380+
north = prj.cmscale * y + falsenorth
368381

369382
# Point Scale Factor and Grid Convergence
370383
psf, grid_conv = psfandgridconv(xi1, eta1, degrees(lat), lon, cm, conf_lat)
@@ -375,7 +388,7 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80):
375388
round(psf, 8), grid_conv)
376389

377390

378-
def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80):
391+
def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80, prj=utm):
379392
"""
380393
Takes a Transverse Mercator grid co-ordinate (Zone, Easting, Northing,
381394
Hemisphere) and returns its corresponding Geographic Latitude and Longitude,
@@ -393,8 +406,12 @@ def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80):
393406
"""
394407
# Input Validation - UTM Extents and Values
395408
zone = int(zone)
396-
if zone < 0 or zone > 60:
397-
raise ValueError('Invalid Zone - Zones from 1 to 60')
409+
if prj == isg:
410+
if zone not in (541, 542, 543, 551, 552, 553, 561, 562, 563, 572):
411+
raise ValueError('Invalid Zone - Choose from 541, 542, 543, 551, 552, 553, 561, 562, 563, 572')
412+
else:
413+
if zone < 0 or zone > 60:
414+
raise ValueError('Invalid Zone - Zones from 1 to 60')
398415

399416
if east < -2830000 or east > 3830000:
400417
raise ValueError('Invalid Easting - Must be within'
@@ -407,15 +424,18 @@ def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80):
407424
if h != 'north' and h != 'south':
408425
raise ValueError('Invalid Hemisphere - String, either North or South')
409426

427+
if prj == isg and ellipsoid != ans:
428+
warnings.warn(message='ISG projection should be used with ANS ellipsoid', category=UserWarning)
429+
410430
A = rect_radius(ellipsoid)
411431
b = beta_coeff(ellipsoid)
412432
# Transverse Mercator Co-ordinates
413-
x = (east - float(proj.falseeast)) / float(proj.cmscale)
433+
x = (east - float(prj.falseeast)) / float(prj.cmscale)
414434
if hemisphere.lower() == 'north':
415-
y = -(north / float(proj.cmscale))
435+
y = -(north / float(prj.cmscale))
416436
hemisign = -1
417437
else:
418-
y = (north - float(proj.falsenorth)) / float(proj.cmscale)
438+
y = (north - float(prj.falsenorth)) / float(prj.cmscale)
419439
hemisign = 1
420440

421441
# Transverse Mercator Ratios
@@ -463,7 +483,12 @@ def f1tn(tn, ecc1, ecc1sq):
463483
lat = degrees(atan(t))
464484

465485
# Compute Longitude
466-
cm = float((zone * proj.zonewidth) + proj.initialcm - proj.zonewidth)
486+
if prj == isg:
487+
amgzone = int(str(zone)[:2])
488+
subzone = int(str(zone)[2])
489+
cm = float((amgzone - 1) * prj.zonewidth * 3 + prj.initialcm + (subzone - 2) * prj.zonewidth)
490+
else:
491+
cm = float((zone * prj.zonewidth) + prj.initialcm - prj.zonewidth)
467492
long_diff = degrees(atan(sinh(eta1) / cos(xi1)))
468493
long = cm + long_diff
469494

geodepy/tests/test_convert.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import numpy as np
55
from geodepy.fileio import read_dnacoord
6+
from geodepy.constants import grs80, ans, isg
67
from geodepy.convert import (dec2hp, hp2dec, DMSAngle, DDMAngle, dec2dms,
78
dec2ddm, hp2dms, hp2ddm, dd2sec,
89
yyyydoy_to_date, date_to_yyyydoy, grid2geo,
@@ -80,6 +81,28 @@ def test_geo_grid_transform_interoperability(self):
8081
np.array(test_grid_coords[['east', 'north']].tolist()),
8182
decimal=3)
8283

84+
def test_geo_grid_transform_interoperability_isg(self):
85+
abs_path = os.path.abspath(os.path.dirname(__file__))
86+
test_geo_coords = np.genfromtxt(os.path.join(abs_path, 'resources/Test_Conversion_ISG_Geo.csv'),
87+
delimiter=',',
88+
dtype='S4,f8,f8',
89+
names=['site', 'lat', 'lon'])
90+
91+
test_grid_coords = np.genfromtxt(os.path.join(abs_path, 'resources/Test_Conversion_ISG_Grid.csv'),
92+
delimiter=',',
93+
dtype='S4,i4,f8,f8',
94+
names=['site', 'zone', 'east', 'north'])
95+
96+
geoed_grid = np.array(list(grid2geo(*x, ellipsoid=ans, prj=isg) for x in test_grid_coords[['zone', 'east', 'north']]))
97+
np.testing.assert_almost_equal(geoed_grid[:, :2], np.array(test_geo_coords[['lat', 'lon']].tolist()),
98+
decimal=8)
99+
100+
gridded_geo = np.stack(geo2grid(*x, ellipsoid=ans, prj=isg) for x in np.array(test_geo_coords[['lat', 'lon']].tolist()))
101+
np.testing.assert_almost_equal(gridded_geo[:, 2:4].astype(float),
102+
np.array(test_grid_coords[['east', 'north']].tolist()),
103+
decimal=3)
104+
105+
83106
def test_llh2xyz(self):
84107

85108
# Test of single point
@@ -197,6 +220,12 @@ def test_geo2grid(self):
197220
geo2grid(0, -181, 0)
198221
with self.assertRaises(ValueError):
199222
geo2grid(0, 181, 0)
223+
# test ValueError raised if not a valid ISG zone
224+
with self.assertRaises(ValueError):
225+
grid2geo(zone=530, east=300000, north=1500000, ellipsoid=ans, prj=isg)
226+
# test UserWarning raised if prj/ellipsoid combination isn't recommended
227+
with self.assertWarns(UserWarning):
228+
grid2geo(zone=551, east=300000, north=1500000, ellipsoid=grs80, prj=isg)
200229

201230
def test_grid2geo(self):
202231
abs_path = os.path.abspath(os.path.dirname(__file__))
@@ -233,7 +262,12 @@ def test_grid2geo(self):
233262
grid2geo(0, 0, 10000001)
234263
with self.assertRaises(ValueError):
235264
grid2geo(0, 0, 500000, 'fail')
236-
265+
# test ValueError raised if not a valid ISG zone
266+
with self.assertRaises(ValueError):
267+
grid2geo(zone=530, east=300000, north=1500000, ellipsoid=ans, prj=isg)
268+
# test UserWarning raised if prj/ellipsoid combination isn't recommended
269+
with self.assertWarns(UserWarning):
270+
grid2geo(zone=551, east=300000, north=1500000, ellipsoid=grs80, prj=isg)
237271

238272
if __name__ == '__main__':
239273
unittest.main()

0 commit comments

Comments
 (0)