示例#1
0
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)
示例#2
0
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
示例#3
0
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)
示例#4
0
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
示例#5
0
    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)
示例#6
0
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
示例#7
0
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))
示例#8
0
    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)
示例#9
0
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
示例#10
0
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
示例#11
0
    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)
示例#12
0
    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')
示例#13
0
    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)
示例#14
0
    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)