def setup(self): self.lon = Longitude([0., 45., 90., 135., 180., -180, -90, -45], u.deg, wrap_angle=180*u.deg) self.lat = Latitude([+0., 30., 60., +90., -90., -60., -30., 0.], u.deg) self.h = u.Quantity([0.1, 0.5, 1.0, -0.5, -1.0, +4.2, -11., -.1], u.m) self.location = EarthLocation.from_geodetic(self.lon, self.lat, self.h) self.x, self.y, self.z = self.location.to_geocentric()
def test_wrap_at_without_new(): """ Regression test for subtle bugs from situations where an Angle is created via numpy channels that don't do the standard __new__ but instead depend on array_finalize to set state. Longitude is used because the bug was in its _wrap_angle not getting initialized correctly """ l1 = Longitude([1] * u.deg) l2 = Longitude([2] * u.deg) l = np.concatenate([l1, l2]) assert l._wrap_angle is not None
def test_angle_repr(): assert 'Angle' in repr(Angle(0, u.deg)) assert 'Longitude' in repr(Longitude(0, u.deg)) assert 'Latitude' in repr(Latitude(0, u.deg)) a = Angle(0, u.deg) repr(a)
def to_selenodetic(self): """Convert to selenodetic coordinates (lat, lon, height). Height is in reference to a sphere with radius `_lunar_radius`, centered at the center of mass. Returns ------- (lon, lat, height) : tuple The tuple contains instances of `~astropy.coordinates.Longitude`, `~astropy.coordinates.Latitude`, and `~astropy.units.Quantity` """ self_xyz = self.to(u.meter).view(self._array_dtype, np.ndarray) self_xyz = np.atleast_2d(self_xyz) gps_p = np.sqrt(self_xyz[:, 0]**2 + self_xyz[:, 1]**2) lat = np.arctan2(self_xyz[:, 2], gps_p) lon = np.arctan2(self_xyz[:, 1], self_xyz[:, 0]) height = np.linalg.norm(self_xyz, axis=1) - self._lunar_radius return GeodeticLocation( Longitude(lon * u.radian, u.degree, wrap_angle=180. * u.degree, copy=False), Latitude(lat * u.radian, u.degree, copy=False), u.Quantity(height * u.meter, self.unit, copy=False))
def from_selenodetic(cls, lon, lat, height=0.): """ Location on the Moon, from latitude and longitude. Parameters ---------- lon : `~astropy.coordinates.Longitude` or float Lunar East longitude. Can be anything that initialises an `~astropy.coordinates.Angle` object (if float, in degrees). lat : `~astropy.coordinates.Latitude` or float Lunar latitude. Can be anything that initialises an `~astropy.coordinates.Latitude` object (if float, in degrees). height : `~astropy.units.Quantity` or float, optional Height above reference sphere (if float, in meters; default: 0). The reference sphere is a sphere of radius 1737.1 kilometers, from the center of mass of the Moon. Raises ------ astropy.units.UnitsError If the units on ``lon`` and ``lat`` are inconsistent with angular ones, or that on ``height`` with a length. ValueError If ``lon``, ``lat``, and ``height`` do not have the same shape, or Notes ----- latitude is defined relative to an equator 90 degrees off from the mean rotation axis. Longitude is defined relative to a prime meridian, which is itself given by the mean position of the "sub-Earth" point on the lunar surface. """ lon = Longitude(lon, u.degree, wrap_angle=180 * u.degree, copy=False) lat = Latitude(lat, u.degree, copy=False) # don't convert to m by default, so we can use the height unit below. if not isinstance(height, u.Quantity): height = u.Quantity(height, u.m, copy=False) if not lon.shape == lat.shape: raise ValueError("Inconsistent quantity shapes: {}, {}".format( str(lon.shape), str(lat.shape))) # get selenocentric coordinates. Have to give one-dimensional array. lunar_radius = u.Quantity(cls._lunar_radius, u.m, copy=False) Npts = lon.size xyz = np.zeros((Npts, 3)) xyz[:, 0] = ((lunar_radius + height) * np.cos(lat) * np.cos(lon)) xyz[:, 1] = ((lunar_radius + height) * np.cos(lat) * np.sin(lon)) xyz[:, 2] = ((lunar_radius + height) * np.sin(lat)) xyz = np.squeeze(xyz) self = xyz.ravel().view(cls._location_dtype, cls).reshape(xyz.shape[:-1]) self._unit = u.meter return self.to(height.unit)
def position(): lon = Longitude([0., 45., 90., 135., 180., -180, -90, -45], u.deg, wrap_angle=180*u.deg) lat = Latitude([+0., 30., 60., +90., -90., -60., -30., 0.], u.deg) h = u.Quantity([0.1, 0.5, 1.0, -0.5, -1.0, +4.2, -11., -.1], u.m) return lon, lat, h
def test_angle_format_roundtripping(): """ Ensures that the string representation of an angle can be used to create a new valid Angle. """ a1 = Angle(0, unit=u.radian) a2 = Angle(10, unit=u.degree) a3 = Angle(0.543, unit=u.degree) a4 = Angle('1d2m3.4s') assert Angle(str(a1)).degree == a1.degree assert Angle(str(a2)).degree == a2.degree assert Angle(str(a3)).degree == a3.degree assert Angle(str(a4)).degree == a4.degree # also check Longitude/Latitude ra = Longitude('1h2m3.4s') dec = Latitude('1d2m3.4s') assert_allclose(Angle(str(ra)).degree, ra.degree) assert_allclose(Angle(str(dec)).degree, dec.degree)
def test_longitude(): # Default wrapping at 360d with an array input lon = Longitude(['370d', '88d']) assert np.all(lon == Longitude(['10d', '88d'])) assert np.all(lon == Angle(['10d', '88d'])) # conserve type on unit change and keep wrap_angle (closes #1423) angle = lon.to('hourangle') assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle angle = lon[0] assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle angle = lon[1:] assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle # but not on calculations angle = lon / 2. assert np.all(angle == Angle(['5d', '44d'])) assert type(angle) is Angle assert not hasattr(angle, 'wrap_angle') angle = lon * 2. + 400 * u.deg assert np.all(angle == Angle(['420d', '576d'])) assert type(angle) is Angle # Test setting a mutable value and having it wrap lon[1] = -10 * u.deg assert np.all(lon == Angle(['10d', '350d'])) # Test wrapping and try hitting some edge cases lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) assert np.all(lon.degree == np.array([0., 90, 180, 270, 0])) lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian, wrap_angle='180d') assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) # Wrap on setting wrap_angle property (also test auto-conversion of wrap_angle to an Angle) lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) lon.wrap_angle = '180d' assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) lon = Longitude('460d') assert lon == Angle('100d') lon.wrap_angle = '90d' assert lon == Angle('-260d') # check that if we initialize a longitude with another longitude, # wrap_angle is kept by default lon2 = Longitude(lon) assert lon2.wrap_angle == lon.wrap_angle # but not if we explicitly set it lon3 = Longitude(lon, wrap_angle='180d') assert lon3.wrap_angle == 180 * u.deg # check for problem reported in #2037 about Longitude initializing to -0 lon = Longitude(0, u.deg) lonstr = lon.to_string() assert not lonstr.startswith('-') # also make sure dtype is correctly conserved assert Longitude(0, u.deg, dtype=float).dtype == np.dtype(float) assert Longitude(0, u.deg, dtype=int).dtype == np.dtype(int) # Test errors when trying to interoperate with latitudes. with pytest.raises(TypeError) as excinfo: lat = Latitude(10, 'deg') lon = Longitude(lat) assert "A Longitude angle cannot be created from a Latitude angle" in str( excinfo.value) with pytest.raises(TypeError) as excinfo: lat = Latitude(10, 'deg') lon = Longitude([20], 'deg') lon[0] = lat assert "A Latitude angle cannot be assigned to a Longitude angle" in str( excinfo.value) # Check we can work around the Lat vs Long checks by casting explicitly to Angle. lat = Latitude(10, 'deg') lon = Longitude(Angle(lat)) assert lon.value == 10.0 # Check setitem. lat = Latitude(10, 'deg') lon = Longitude([20], 'deg') lon[0] = Angle(lat) assert lon.value[0] == 10.0
def test_latitude(): with pytest.raises(ValueError): lat = Latitude(['91d', '89d']) with pytest.raises(ValueError): lat = Latitude('-91d') lat = Latitude(['90d', '89d']) # check that one can get items assert lat[0] == 90 * u.deg assert lat[1] == 89 * u.deg # and that comparison with angles works assert np.all(lat == Angle(['90d', '89d'])) # check setitem works lat[1] = 45. * u.deg assert np.all(lat == Angle(['90d', '45d'])) # but not with values out of range with pytest.raises(ValueError): lat[0] = 90.001 * u.deg with pytest.raises(ValueError): lat[0] = -90.001 * u.deg # these should also not destroy input (#1851) assert np.all(lat == Angle(['90d', '45d'])) # conserve type on unit change (closes #1423) angle = lat.to('radian') assert type(angle) is Latitude # but not on calculations angle = lat - 190 * u.deg assert type(angle) is Angle assert angle[0] == -100 * u.deg lat = Latitude('80d') angle = lat / 2. assert type(angle) is Angle assert angle == 40 * u.deg angle = lat * 2. assert type(angle) is Angle assert angle == 160 * u.deg angle = -lat assert type(angle) is Angle assert angle == -80 * u.deg # Test errors when trying to interoperate with longitudes. with pytest.raises(TypeError) as excinfo: lon = Longitude(10, 'deg') lat = Latitude(lon) assert "A Latitude angle cannot be created from a Longitude angle" in str( excinfo.value) with pytest.raises(TypeError) as excinfo: lon = Longitude(10, 'deg') lat = Latitude([20], 'deg') lat[0] = lon assert "A Longitude angle cannot be assigned to a Latitude angle" in str( excinfo.value) # Check we can work around the Lat vs Long checks by casting explicitly to Angle. lon = Longitude(10, 'deg') lat = Latitude(Angle(lon)) assert lat.value == 10.0 # Check setitem. lon = Longitude(10, 'deg') lat = Latitude([20], 'deg') lat[0] = Angle(lon) assert lat.value[0] == 10.0
def test_radec(): """ Tests creation/operations of Longitude and Latitude objects """ ''' Longitude and Latitude are objects that are subclassed from Angle. As with Angle, Longitude and Latitude can parse any unambiguous format (tuples, formatted strings, etc.). The intention is not to create an Angle subclass for every possible coordinate object (e.g. galactic l, galactic b). However, equatorial Longitude/Latitude are so prevalent in astronomy that it's worth creating ones for these units. They will be noted as "special" in the docs and use of the just the Angle class is to be used for other coordinate systems. ''' with pytest.raises(u.UnitsError): ra = Longitude("4:08:15.162342") # error - hours or degrees? with pytest.raises(u.UnitsError): ra = Longitude("-4:08:15.162342") # the "smart" initializer allows >24 to automatically do degrees, but the # Angle-based one does not # TODO: adjust in 0.3 for whatever behavior is decided on # ra = Longitude("26:34:15.345634") # unambiguous b/c hours don't go past 24 # assert_allclose(ra.degree, 26.570929342) with pytest.raises(u.UnitsError): ra = Longitude("26:34:15.345634") # ra = Longitude(68) with pytest.raises(u.UnitsError): ra = Longitude(68) with pytest.raises(u.UnitsError): ra = Longitude(12) with pytest.raises(ValueError): ra = Longitude("garbage containing a d and no units") ra = Longitude("12h43m23s") assert_allclose(ra.hour, 12.7230555556) ra = Longitude((56, 14, 52.52), unit=u.degree) # can accept tuples # TODO: again, fix based on >24 behavior # ra = Longitude((56,14,52.52)) with pytest.raises(u.UnitsError): ra = Longitude((56, 14, 52.52)) with pytest.raises(u.UnitsError): ra = Longitude((12, 14, 52)) # ambiguous w/o units ra = Longitude((12, 14, 52), unit=u.hour) ra = Longitude([56, 64, 52.2], unit=u.degree) # ...but not arrays (yet) # Units can be specified ra = Longitude("4:08:15.162342", unit=u.hour) # TODO: this was the "smart" initializer behavior - adjust in 0.3 appropriately # Where Longitude values are commonly found in hours or degrees, declination is # nearly always specified in degrees, so this is the default. # dec = Latitude("-41:08:15.162342") with pytest.raises(u.UnitsError): dec = Latitude("-41:08:15.162342") dec = Latitude("-41:08:15.162342", unit=u.degree) # same as above
def test_longitude_nan(): # Check that passing a NaN to Longitude doesn't raise a warning Longitude([0, np.nan, 1] * u.deg)
def test_longitude(): # Default wrapping at 360d with an array input lon = Longitude(['370d', '88d']) assert np.all(lon == Longitude(['10d', '88d'])) assert np.all(lon == Angle(['10d', '88d'])) # conserve type on unit change and keep wrap_angle (closes #1423) angle = lon.to('hourangle') assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle angle = lon[0] assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle angle = lon[1:] assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle # but not on calculations angle = lon / 2. assert np.all(angle == Angle(['5d', '44d'])) assert type(angle) is Angle assert not hasattr(angle, 'wrap_angle') angle = lon * 2. + 400 * u.deg assert np.all(angle == Angle(['420d', '576d'])) assert type(angle) is Angle # Test setting a mutable value and having it wrap lon[1] = -10 * u.deg assert np.all(lon == Angle(['10d', '350d'])) # Test wrapping and try hitting some edge cases lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) assert np.all(lon.degree == np.array([0., 90, 180, 270, 0])) lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian, wrap_angle='180d') assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) # Wrap on setting wrap_angle property (also test auto-conversion of wrap_angle to an Angle) lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) lon.wrap_angle = '180d' assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) lon = Longitude('460d') assert lon == Angle('100d') lon.wrap_angle = '90d' assert lon == Angle('-260d') # check that if we initialize a longitude with another longitude, # wrap_angle is kept by default lon2 = Longitude(lon) assert lon2.wrap_angle == lon.wrap_angle # but not if we explicitly set it lon3 = Longitude(lon, wrap_angle='180d') assert lon3.wrap_angle == 180 * u.deg # check for problem reported in #2037 about Longitude initializing to -0 lon = Longitude(0, u.deg) lonstr = lon.to_string() assert not lonstr.startswith('-') # also make sure dtype is correctly conserved assert Longitude(0, u.deg, dtype=float).dtype == np.dtype(float) assert Longitude(0, u.deg, dtype=int).dtype == np.dtype(int) # Test errors when trying to interoperate with latitudes. with pytest.raises(TypeError) as excinfo: lat = Latitude(10, 'deg') lon = Longitude(lat) assert "A Longitude angle cannot be created from a Latitude angle" in str(excinfo) with pytest.raises(TypeError) as excinfo: lat = Latitude(10, 'deg') lon = Longitude([20], 'deg') lon[0] = lat assert "A Latitude angle cannot be assigned to a Longitude angle" in str(excinfo) # Check we can work around the Lat vs Long checks by casting explicitly to Angle. lat = Latitude(10, 'deg') lon = Longitude(Angle(lat)) assert lon.value == 10.0 # Check setitem. lat = Latitude(10, 'deg') lon = Longitude([20], 'deg') lon[0] = Angle(lat) assert lon.value[0] == 10.0
class TestInput(): def setup(self): self.lon = Longitude([0., 45., 90., 135., 180., -180, -90, -45], u.deg, wrap_angle=180*u.deg) self.lat = Latitude([+0., 30., 60., +90., -90., -60., -30., 0.], u.deg) self.h = u.Quantity([0.1, 0.5, 1.0, -0.5, -1.0, +4.2, -11., -.1], u.m) self.location = EarthLocation.from_geodetic(self.lon, self.lat, self.h) self.x, self.y, self.z = self.location.to_geocentric() def test_default_ellipsoid(self): assert self.location.ellipsoid == EarthLocation._ellipsoid def test_geo_attributes(self): assert all(np.all(_1 == _2) for _1, _2 in zip(self.location.geodetic, self.location.to_geodetic())) assert all(np.all(_1 == _2) for _1, _2 in zip(self.location.geocentric, self.location.to_geocentric())) def test_attribute_classes(self): """Test that attribute classes are correct (and not EarthLocation)""" assert type(self.location.x) is u.Quantity assert type(self.location.y) is u.Quantity assert type(self.location.z) is u.Quantity assert type(self.location.lon) is Longitude assert type(self.location.lat) is Latitude assert type(self.location.height) is u.Quantity def test_input(self): """Check input is parsed correctly""" # units of length should be assumed geocentric geocentric = EarthLocation(self.x, self.y, self.z) assert np.all(geocentric == self.location) geocentric2 = EarthLocation(self.x.value, self.y.value, self.z.value, self.x.unit) assert np.all(geocentric2 == self.location) geodetic = EarthLocation(self.lon, self.lat, self.h) assert np.all(geodetic == self.location) geodetic2 = EarthLocation(self.lon.to_value(u.degree), self.lat.to_value(u.degree), self.h.to_value(u.m)) assert np.all(geodetic2 == self.location) geodetic3 = EarthLocation(self.lon, self.lat) assert allclose_m14(geodetic3.lon.value, self.location.lon.value) assert allclose_m14(geodetic3.lat.value, self.location.lat.value) assert not np.any(isclose_m14(geodetic3.height.value, self.location.height.value)) geodetic4 = EarthLocation(self.lon, self.lat, self.h[-1]) assert allclose_m14(geodetic4.lon.value, self.location.lon.value) assert allclose_m14(geodetic4.lat.value, self.location.lat.value) assert allclose_m14(geodetic4.height[-1].value, self.location.height[-1].value) assert not np.any(isclose_m14(geodetic4.height[:-1].value, self.location.height[:-1].value)) # check length unit preservation geocentric5 = EarthLocation(self.x, self.y, self.z, u.pc) assert geocentric5.unit is u.pc assert geocentric5.x.unit is u.pc assert geocentric5.height.unit is u.pc assert allclose_m14(geocentric5.x.to_value(self.x.unit), self.x.value) geodetic5 = EarthLocation(self.lon, self.lat, self.h.to(u.pc)) assert geodetic5.unit is u.pc assert geodetic5.x.unit is u.pc assert geodetic5.height.unit is u.pc assert allclose_m14(geodetic5.x.to_value(self.x.unit), self.x.value) def test_invalid_input(self): """Check invalid input raises exception""" # incomprehensible by either raises TypeError with pytest.raises(TypeError): EarthLocation(self.lon, self.y, self.z) # wrong units with pytest.raises(u.UnitsError): EarthLocation.from_geocentric(self.lon, self.lat, self.lat) # inconsistent units with pytest.raises(u.UnitsError): EarthLocation.from_geocentric(self.h, self.lon, self.lat) # floats without a unit with pytest.raises(TypeError): EarthLocation.from_geocentric(self.x.value, self.y.value, self.z.value) # inconsistent shape with pytest.raises(ValueError): EarthLocation.from_geocentric(self.x, self.y, self.z[:5]) # inconsistent units with pytest.raises(u.UnitsError): EarthLocation.from_geodetic(self.x, self.y, self.z) # inconsistent shape with pytest.raises(ValueError): EarthLocation.from_geodetic(self.lon, self.lat, self.h[:5]) def test_slicing(self): # test on WGS72 location, so we can check the ellipsoid is passed on locwgs72 = EarthLocation.from_geodetic(self.lon, self.lat, self.h, ellipsoid='WGS72') loc_slice1 = locwgs72[4] assert isinstance(loc_slice1, EarthLocation) assert loc_slice1.unit is locwgs72.unit assert loc_slice1.ellipsoid == locwgs72.ellipsoid == 'WGS72' assert not loc_slice1.shape with pytest.raises(TypeError): loc_slice1[0] with pytest.raises(IndexError): len(loc_slice1) loc_slice2 = locwgs72[4:6] assert isinstance(loc_slice2, EarthLocation) assert len(loc_slice2) == 2 assert loc_slice2.unit is locwgs72.unit assert loc_slice2.ellipsoid == locwgs72.ellipsoid assert loc_slice2.shape == (2,) loc_x = locwgs72['x'] assert type(loc_x) is u.Quantity assert loc_x.shape == locwgs72.shape assert loc_x.unit is locwgs72.unit def test_invalid_ellipsoid(self): # unknown ellipsoid with pytest.raises(ValueError): EarthLocation.from_geodetic(self.lon, self.lat, self.h, ellipsoid='foo') with pytest.raises(TypeError): EarthLocation(self.lon, self.lat, self.h, ellipsoid='foo') with pytest.raises(ValueError): self.location.ellipsoid = 'foo' with pytest.raises(ValueError): self.location.to_geodetic('foo') @pytest.mark.parametrize('ellipsoid', ELLIPSOIDS) def test_ellipsoid(self, ellipsoid): """Test that different ellipsoids are understood, and differ""" # check that heights differ for different ellipsoids # need different tolerance, since heights are relative to ~6000 km lon, lat, h = self.location.to_geodetic(ellipsoid) if ellipsoid == self.location.ellipsoid: assert allclose_m8(h.value, self.h.value) else: # Some heights are very similar for some; some lon, lat identical. assert not np.all(isclose_m8(h.value, self.h.value)) # given lon, lat, height, check that x,y,z differ location = EarthLocation.from_geodetic(self.lon, self.lat, self.h, ellipsoid=ellipsoid) if ellipsoid == self.location.ellipsoid: assert allclose_m14(location.z.value, self.z.value) else: assert not np.all(isclose_m14(location.z.value, self.z.value)) def test_to_value(self): loc = self.location loc_ndarray = loc.view(np.ndarray) assert np.all(loc.value == loc_ndarray) loc2 = self.location.to(u.km) loc2_ndarray = np.empty_like(loc_ndarray) for coo in 'x', 'y', 'z': loc2_ndarray[coo] = loc_ndarray[coo] / 1000. assert np.all(loc2.value == loc2_ndarray) loc2_value = self.location.to_value(u.km) assert np.all(loc2_value == loc2_ndarray)