def vincinv_utm(zone1, east1, north1, zone2, east2, north2, hemisphere1='south', hemisphere2='south', ellipsoid=grs80): """ Perform Vincentys Inverse Computation using UTM Grid Coordinates :param zone1: Point 1 Zone Number - 1 to 60 :param east1: Point 1 Easting (m, within 3330km of Central Meridian) :param north1: Point 1 Northing (m, 0 to 10,000,000m) :param zone2: Point 2 Zone Number - 1 to 60 :param east2: Point 2 Easting (m, within 3330km of Central Meridian) :param north2: Point 2 Northing (m, 0 to 10,000,000m) :param hemisphere1: Point 1 Hemisphere: String - 'North' or 'South'(default) :param hemisphere2: Point 2 Hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default: GRS80) :return: ell_dist: Ellipsoidal Distance between Points 1 and 2 (m), azimuth1to2: Azimuth from Point 1 to 2 (decimal degrees), azimuth2to1: Azimuth from Point 2 to 1 (decimal degrees) """ # Convert utm to geographic pt1 = grid2geo(zone1, east1, north1, hemisphere1, ellipsoid) pt2 = grid2geo(zone2, east2, north2, hemisphere2, ellipsoid) # Use vincinv return vincinv(pt1[0], pt1[1], pt2[0], pt2[1], ellipsoid)
def vincdir_utm(zone1, east1, north1, grid1to2, grid_dist, hemisphere='south', ellipsoid=grs80): """ Perform Vincenty's Direct Computation using UTM Grid Coordinates, a grid bearing and grid distance. Note: Point 2 UTM Coordinates use the Zone specified for Point 1, even if Point 2 would typically be computed in a different zone. This keeps the grid bearings and line scale factor all relative to the same UTM Zone. :param zone1: Point 1 Zone Number - 1 to 60 :param east1: Point 1 Easting (m, within 3330km of Central Meridian) :param north1: Point 1 Northing (m, 0 to 10,000,000m) :param grid1to2: Grid Bearing from Point 1 to 2 (decimal degrees), :param grid_dist: UTM Grid Distance between Points 1 and 2 (m) :param hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default: GRS80) :return: zone2: Point 2 Zone Number - 1 to 60 east2: Point 2 Easting (m, within 3330km of Central Meridian) north2: Point 2 Northing (m, 0 to 10,000,000m) grid2to1: Grid Bearing from Point 2 to 1 (decimal degrees) lsf: Line Scale Factor (for Point 1 Zone) """ # Convert angle to decimal degrees (if required) grid1to2 = angular_typecheck(grid1to2) # Convert UTM Coords to Geographic lat1, lon1, psf1, gridconv1 = grid2geo(zone1, east1, north1, hemisphere, ellipsoid) # Convert Grid Bearing to Geodetic Azimuth az1to2 = grid1to2 - gridconv1 # Estimate Line Scale Factor (LSF) zone2, east2, north2 = (zone1, *radiations(east1, north1, grid1to2, grid_dist)) lsf = line_sf(zone1, east1, north1, zone2, east2, north2) # Iteratively estimate Pt 2 Coordinates, refining LSF each time lsf_diff = 1 max_iter = 10 while lsf_diff > 1e-9: lsf_previous = lsf lat2, lon2, az2to1 = vincdir(lat1, lon1, az1to2, grid_dist / lsf, ellipsoid) (hemisphere2, zone2, east2, north2, psf2, gridconv2) = geo2grid(lat2, lon2, zone1, ellipsoid) lsf = line_sf(zone1, east1, north1, zone2, east2, north2, hemisphere, ellipsoid) lsf_diff = abs(lsf_previous - lsf) # Compute Grid Bearing for Station 2 lat2, lon2, psf2, gridconv2 = grid2geo(zone2, east2, north2, hemisphere, ellipsoid) grid2to1 = az2to1 + gridconv2 return zone2, east2, north2, grid2to1, lsf
def vincinv_utm(zone1, east1, north1, zone2, east2, north2, hemisphere1='south', hemisphere2='south', ellipsoid=grs80): # Convert utm to geographic pt1 = grid2geo(zone1, east1, north1, hemisphere1, ellipsoid) pt2 = grid2geo(zone2, east2, north2, hemisphere2, ellipsoid) # Use vincinv return vincinv(pt1[0], pt1[1], pt2[0], pt2[1], ellipsoid)
def mga2020_to_mga94(zone, east, north, ell_ht=False, vcv=None): """ Performs conformal transformation of Map Grid of Australia 2020 to Map Grid of Australia 1994 Coordinates using the reverse form of the GDA2020 Tech Manual v1.2 7 parameter similarity transformation parameters :param zone: Zone Number - 1 to 60 :param east: Easting (m, within 3330km of Central Meridian) :param north: Northing (m, 0 to 10,000,000m) :param ell_ht: Ellipsoid Height (m) (optional) :param vcv: Optional 3*3 numpy array in local enu units to propagate tf uncertainty :return: MGA1994 Zone, Easting, Northing, Ellipsoid Height (if none provided, returns 0), and vcv matrix """ lat, lon, psf, gridconv = grid2geo(zone, east, north) if ell_ht is False: ell_ht_in = 0 else: ell_ht_in = ell_ht if vcv is not None: vcv = vcv_local2cart(vcv, lat, lon) x94, y94, z94 = llh2xyz(lat, lon, ell_ht_in) x20, y20, z20, vcv94 = conform7(x94, y94, z94, -gda94_to_gda2020, vcv=vcv) lat, lon, ell_ht_out = xyz2llh(x20, y20, z20) if vcv94 is not None: vcv94 = vcv_cart2local(vcv94, lat, lon) if ell_ht is False: ell_ht_out = 0 hemisphere, zone20, east20, north20, psf, gridconv = geo2grid(lat, lon) return zone20, east20, north20, round(ell_ht_out, 4), vcv94
def test_geo_grid_transform_interoperability(self): abs_path = os.path.abspath(os.path.dirname(__file__)) test_geo_coords = np.genfromtxt(os.path.join( abs_path, 'resources/Test_Conversion_Geo.csv'), delimiter=',', dtype='S4,f8,f8', names=['site', 'lat', 'lon']) test_grid_coords = np.genfromtxt( os.path.join(abs_path, 'resources/Test_Conversion_Grid.csv'), delimiter=',', 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']])) 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()))) np.testing.assert_almost_equal( gridded_geo[:, 2:4].astype(float), np.array(test_grid_coords[['east', 'north']].tolist()), decimal=3)
def vincdir_utm(zone1, east1, north1, azimuth1to2, ell_dist, hemisphere1='south', ellipsoid=grs80): """ Perform Vincentys Direct Computation using UTM Grid coordinates :param zone1: Point 1 Zone Number - 1 to 60 :param east1: Point 1 Easting (m, within 3330km of Central Meridian) :param north1: Point 1 Northing (m, 0 to 10,000,000m) :param azimuth1to2: Azimuth from Point 1 to 2 (decimal degrees) :type azimuth1to2: float (decimal degrees), DMSAngle or DDMAngle :param ell_dist: Ellipsoidal Distance between Points 1 and 2 (metres) :param hemisphere1: Point 1 Hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default: GRS80) :return: Hemisphere, zone, easting and northing of Point 2, Azimuth from Point 2 to 1 (decimal degrees) :rtype: tuple """ # Convert angle to decimal degrees (if required) azimuth1to2 = angular_typecheck(azimuth1to2) # Convert utm to geographic pt1 = grid2geo(zone1, east1, north1, hemisphere1) # Use vincdir lat2, lon2, azimuth2to1 = vincdir(pt1[0], pt1[1], azimuth1to2, ell_dist, ellipsoid) # Convert geographic to utm hemisphere2, zone2, east2, north2, psf2, gc2 = geo2grid(lat2, lon2) return hemisphere2, zone2, east2, north2, azimuth2to1
def line_sf(zone1, east1, north1, zone2, east2, north2, hemisphere='south', ellipsoid=grs80, projection=utm): """ Computes Line Scale Factor for a pair of Transverse Mercator Coordinates Ref: Deakin 2010, Traverse Computations on the Ellipsoid and on the Universal Transverse Mercator Projection, pp 35 http://www.mygeodesy.id.au/documents/Trav_Comp_V2.1.pdf :param zone1: Station 1 Zone Number - 1 to 60 :param east1: Station 1 Easting (m, within 3330km of Central Meridian) :param north1: Station 1 Northing (m, 0 to 10,000,000m) :param zone2: Station 2 Zone Number - 1 to 60 :param east2: Station 2 Easting (m, within 3330km of Central Meridian) :param north2: Station 2 Northing (m, 0 to 10,000,000m) :param hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default GRS80) :param projection: Projection Object (default Universal Transverse Mercator) :return: Line Scale Factor (relative to Zone 1 if different zones are entered) """ # Re-project cross-zone coordinate to same UTM zone if zone1 != zone2: # Re-project Station 2 Coordinates to same zone as Station 1 stn2_geo = grid2geo(zone2, east2, north2, hemisphere, ellipsoid) stn2_zone1 = geo2grid(stn2_geo[0], stn2_geo[1], zone1, ellipsoid) zone2 = stn2_zone1[1] east2 = stn2_zone1[2] north2 = stn2_zone1[3] # Comute easting distances from Central Meridian eastofcm1 = east1 - projection.falseeast eastofcm2 = east2 - projection.falseeast # Compute Mean Latitude lat1 = grid2geo(zone1, east1, north1, hemisphere, ellipsoid)[0] lat2 = grid2geo(zone2, east2, north2, hemisphere, ellipsoid)[0] lat_mean = (lat1 + lat2) / 2 # Compute Line Scale Factor (Deakin 2010 Eq. 13) r_sq_m = (rho(lat_mean, ellipsoid) * nu(lat_mean, ellipsoid) * projection.cmscale ** 2) k1 = ((eastofcm1 ** 2 + eastofcm1 * eastofcm2 + eastofcm2 ** 2) / (6 * r_sq_m)) k2 = ((eastofcm1 ** 2 + eastofcm1 * eastofcm2 + eastofcm2 ** 2) / (36 * r_sq_m)) return projection.cmscale * (1 + k1 * (1 + k2))
def test_enu2xyz(self): MOBS_MGA2020 = (55, 321820.085, 5811181.510, 40.570) MOBS_MGA1994 = (55, 321819.594, 5811180.038, 40.659) # Convert UTM Projection Coordinates to Geographic Coordinates MOBS_GDA2020 = grid2geo(MOBS_MGA2020[0], MOBS_MGA2020[1], MOBS_MGA2020[2]) MOBS_GDA1994 = grid2geo(MOBS_MGA1994[0], MOBS_MGA1994[1], MOBS_MGA1994[2]) # Convert Geographic Coordinates to Cartesian XYZ Coordinates MOBS_GDA2020_XYZ = llh2xyz(MOBS_GDA2020[0], MOBS_GDA2020[1], MOBS_MGA2020[3]) MOBS_GDA1994_XYZ = llh2xyz(MOBS_GDA1994[0], MOBS_GDA1994[1], MOBS_MGA1994[3]) # Generate Vector Between UTM Projection Coordinates mga_vector = [ MOBS_MGA2020[1] - MOBS_MGA1994[1], MOBS_MGA2020[2] - MOBS_MGA1994[2], MOBS_MGA2020[3] - MOBS_MGA1994[3] ] # Generate Vector Between Cartesian XYZ Coordinates xyz_vector = (MOBS_GDA2020_XYZ[0] - MOBS_GDA1994_XYZ[0], MOBS_GDA2020_XYZ[1] - MOBS_GDA1994_XYZ[1], MOBS_GDA2020_XYZ[2] - MOBS_GDA1994_XYZ[2]) # Rotate UTM Projection Vector by Grid Convergence grid_dist, grid_brg = rect2polar(mga_vector[0], mga_vector[1]) local_east, local_north = polar2rect(grid_dist, grid_brg - MOBS_GDA2020[3]) local_vector = (local_east, local_north, mga_vector[2]) # Calculate XYZ Vector using Local Vector Components x, y, z = enu2xyz(MOBS_GDA2020[0], MOBS_GDA2020[1], *local_vector) self.assertAlmostEqual(x, xyz_vector[0], 4) self.assertAlmostEqual(y, xyz_vector[1], 4) self.assertAlmostEqual(z, xyz_vector[2], 4) # Calculate Local Vector using XYZ Vector Components e, n, u = xyz2enu(MOBS_GDA2020[0], MOBS_GDA2020[1], *xyz_vector) self.assertAlmostEqual(e, local_vector[0], 4) self.assertAlmostEqual(n, local_vector[1], 4) self.assertAlmostEqual(u, local_vector[2], 4)
def vincdir_utm(zone1, east1, north1, azimuth1to2, ell_dist, hemisphere1='south', ellipsoid=grs80): # Convert utm to geographic pt1 = grid2geo(zone1, east1, north1, hemisphere1) # Use vincdir lat2, lon2, azimuth2to1 = vincdir(pt1[0], pt1[1], azimuth1to2, ell_dist, ellipsoid) # Convert geographic to utm hemisphere2, zone2, east2, north2, psf2, gc2 = geo2grid(lat2, lon2) return hemisphere2, zone2, east2, north2, azimuth2to1
def vincinv_utm(zone1, east1, north1, zone2, east2, north2, hemisphere='south', ellipsoid=grs80): """ Perform Vincentys Inverse Computation using UTM Grid Coordinates Note: Where coordinates from different zones are used, UTM Grid Distance is relative to Point 1's Zone. Grid Bearings of Points 1 and 2 are relative to each of their respective Zones. :param zone1: Point 1 Zone Number - 1 to 60 :param east1: Point 1 Easting (m, within 3330km of Central Meridian) :param north1: Point 1 Northing (m, 0 to 10,000,000m) :param zone2: Point 2 Zone Number - 1 to 60 :param east2: Point 2 Easting (m, within 3330km of Central Meridian) :param north2: Point 2 Northing (m, 0 to 10,000,000m) :param hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default: GRS80) :return: grid_dist: UTM Grid Distance between Points 1 and 2 (m), grid1to2: Grid Bearing from Point 1 to 2 (decimal degrees), grid2to1: Grid Bearing from Point 2 to 1 (decimal degrees) lsf: Line Scale Factor (for Point 1 Zone) """ # Convert utm to geographic pt1 = grid2geo(zone1, east1, north1, hemisphere, ellipsoid) pt2 = grid2geo(zone2, east2, north2, hemisphere, ellipsoid) ell_dist, az1to2, az2to1 = vincinv(pt1[0], pt1[1], pt2[0], pt2[1], ellipsoid) # Compute Grid Distance using Line Scale Factor lsf = line_sf(zone1, east1, north1, zone2, east2, north2, hemisphere, ellipsoid) grid_dist = ell_dist * lsf # Compute Grid Bearings using Grid Convergence grid1to2 = az1to2 + pt1[3] grid2to1 = az2to1 + pt2[3] return grid_dist, grid1to2, grid2to1, lsf
def geo(self, ellipsoid=grs80, notation=DECAngle): """ :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :param notation: Latitude and Longitude Angle Notation format :type notation: geodepy.angle class or float :return: """ if self.hemi_north: hemi_str = 'north' else: hemi_str = 'south' lat, lon, psf, grid_conv = grid2geo(self.zone, self.east, self.north, hemi_str, ellipsoid) if notation is DECAngle: lat = DECAngle(lat) lon = DECAngle(lon) elif notation is HPAngle: lat = DECAngle(lat).hpa() lon = DECAngle(lon).hpa() elif notation is GONAngle: lat = DECAngle(lat).gona() lon = DECAngle(lon).gona() elif notation is DMSAngle: lat = DECAngle(lat).dms() lon = DECAngle(lon).dms() elif notation is DDMAngle: lat = DECAngle(lat).ddm() lon = DECAngle(lon).ddm() elif notation is float: pass # geodepy.convert.grid2geo returns float dec degrees else: raise ValueError(f'CoordTM.geo() notation requires class float or ' f'class from geodepy.angles module. ' f'Supplied: {notation}') return CoordGeo(lat, lon, self.ell_ht, self.orth_ht)
def test_grid2geo(self): abs_path = os.path.abspath(os.path.dirname(__file__)) testdata = read_dnacoord( os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) for coord in testdata: coord.converthptodd() latcomp, longcomp, psf, grid_conv = grid2geo( coord.zone, coord.easting, coord.northing) self.assertLess(abs(latcomp - coord.lat), 5e-9) self.assertLess(abs(longcomp - coord.long), 5e-9) # Test North and South Hemisphere Output north_ex = (50, 573976.8747, 3867822.4539, 'North') south_ex = (50, 573976.8747, 6132177.5461, 'South') north_geo = grid2geo(*north_ex) south_geo = grid2geo(*south_ex) self.assertEqual(north_geo[0], -south_geo[0]) self.assertEqual(north_geo[1], south_geo[1]) self.assertEqual(north_geo[2], south_geo[2]) self.assertEqual(north_geo[3], -south_geo[3]) # Test Input Validation with self.assertRaises(ValueError): grid2geo(-1, 0, 500000) with self.assertRaises(ValueError): grid2geo(61, 0, 500000) with self.assertRaises(ValueError): grid2geo(0, -2830001, 500000) with self.assertRaises(ValueError): grid2geo(0, 3830001, 500000) with self.assertRaises(ValueError): grid2geo(0, 0, -1) with self.assertRaises(ValueError): grid2geo(0, 0, 10000001) with self.assertRaises(ValueError): grid2geo(0, 0, 500000, 'fail')
def test_grid2geo(self): abs_path = os.path.abspath(os.path.dirname(__file__)) testdata = read_dnacoord(os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) for coord in testdata: coord.converthptodd() latcomp, longcomp, psf, grid_conv = grid2geo(coord.zone, coord.easting, coord.northing) self.assertLess(abs(latcomp - coord.lat), 5e-9) self.assertLess(abs(longcomp - coord.long), 5e-9) # Test North and South Hemisphere Output north_ex = (50, 573976.8747, 3867822.4539, 'North') south_ex = (50, 573976.8747, 6132177.5461, 'South') north_geo = grid2geo(*north_ex) south_geo = grid2geo(*south_ex) self.assertEqual(north_geo[0], -south_geo[0]) self.assertEqual(north_geo[1], south_geo[1]) self.assertEqual(north_geo[2], south_geo[2]) self.assertEqual(north_geo[3], -south_geo[3]) # Test Input Validation with self.assertRaises(ValueError): grid2geo(-1, 0, 500000) with self.assertRaises(ValueError): grid2geo(61, 0, 500000) with self.assertRaises(ValueError): grid2geo(0, -2830001, 500000) with self.assertRaises(ValueError): grid2geo(0, 3830001, 500000) with self.assertRaises(ValueError): grid2geo(0, 0, -1) with self.assertRaises(ValueError): grid2geo(0, 0, 10000001) with self.assertRaises(ValueError): grid2geo(0, 0, 500000, 'fail') # test ValueError raised if not a valid ISG zone with self.assertRaises(ValueError): grid2geo(zone=530, east=300000, north=1500000, ellipsoid=ans, prj=isg) # test UserWarning raised if prj/ellipsoid combination isn't recommended with self.assertWarns(UserWarning): grid2geo(zone=551, east=300000, north=1500000, ellipsoid=grs80, prj=isg)
def test_geo2grid(self): # Single Point Test hem, zone, east, north, psf, grid_conv = geo2grid(hp2dec(-37.482667598), hp2dec(144.581644114)) self.assertEqual(hem, 'South') self.assertEqual(zone, 55) self.assertAlmostEqual(east, 321405.5592, 3) self.assertAlmostEqual(north, 5813614.1613, 3) self.assertAlmostEqual(psf, 0.99999287, 8) self.assertAlmostEqual(grid_conv, -1.2439811331, 9) # Test DMSAngle Input (hem, zone, east, north, psf, grid_conv) = geo2grid(DMSAngle(-37, 48, 26.67598), DMSAngle(144, 58, 16.44114)) self.assertEqual(hem, 'South') self.assertEqual(zone, 55) self.assertAlmostEqual(east, 321405.5592, 3) self.assertAlmostEqual(north, 5813614.1613, 3) self.assertAlmostEqual(psf, 0.99999287, 8) self.assertAlmostEqual(grid_conv, -1.2439811331, 9) # Test DDMAngle Input (hem, zone, east, north, psf, grid_conv) = geo2grid(DDMAngle(-37, 48.4445997), DDMAngle(144, 58.274019)) self.assertEqual(hem, 'South') self.assertEqual(zone, 55) self.assertAlmostEqual(east, 321405.5592, 3) self.assertAlmostEqual(north, 5813614.1613, 3) self.assertAlmostEqual(psf, 0.99999287, 8) self.assertAlmostEqual(grid_conv, -1.2439811331, 9) abs_path = os.path.abspath(os.path.dirname(__file__)) # Test various coordinates in Australia testdata = read_dnacoord(os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) for coord in testdata: coord.converthptodd() hem, zonecomp, eastcomp, northcomp, psf, grid_conv = geo2grid(coord.lat, coord.long) self.assertEqual(zonecomp, coord.zone) self.assertLess(abs(eastcomp - coord.easting), 4e-4) self.assertLess((northcomp - coord.northing), 4e-4) # Test North and South Hemisphere Output north_ex = (DMSAngle(34, 57, 00.79653).dec(), DMSAngle(117, 48, 36.68783).dec()) south_ex = (DMSAngle(-34, 57, 00.79653).dec(), DMSAngle(117, 48, 36.68783).dec()) north_grid = geo2grid(north_ex[0], north_ex[1]) south_grid = geo2grid(south_ex[0], south_ex[1]) self.assertEqual(north_grid[0], 'North') self.assertEqual(south_grid[0], 'South') self.assertEqual(north_grid[1], south_grid[1]) # Zone self.assertEqual(north_grid[2], south_grid[2]) # Easting self.assertEqual(north_grid[3], 10000000 - south_grid[3]) # Northing self.assertEqual(north_grid[4], south_grid[4]) # PSF self.assertEqual(north_grid[5], -south_grid[5]) # Grid Convergence # Test Input Validation with self.assertRaises(ValueError): geo2grid(0, 45, -1) with self.assertRaises(ValueError): geo2grid(0, 45, 61) with self.assertRaises(ValueError): geo2grid(-81, 45, 0) with self.assertRaises(ValueError): geo2grid(85, 45, 0) with self.assertRaises(ValueError): geo2grid(0, -181, 0) with self.assertRaises(ValueError): geo2grid(0, 181, 0) # test ValueError raised if not a valid ISG zone with self.assertRaises(ValueError): grid2geo(zone=530, east=300000, north=1500000, ellipsoid=ans, prj=isg) # test UserWarning raised if prj/ellipsoid combination isn't recommended with self.assertWarns(UserWarning): grid2geo(zone=551, east=300000, north=1500000, ellipsoid=grs80, prj=isg)