def gcrs_to_hcrs(gcrs_coo, hcrs_frame): if np.any(gcrs_coo.obstime != hcrs_frame.obstime): # if they GCRS obstime and HCRS obstime are not the same, we first # have to move to a GCRS where they are. frameattrs = gcrs_coo.get_frame_attr_names() frameattrs['obstime'] = hcrs_frame.obstime gcrs_coo = gcrs_coo.transform_to(GCRS(**frameattrs)) srepr = gcrs_coo.represent_as(SphericalRepresentation) gcrs_ra = srepr.lon.to_value(u.radian) gcrs_dec = srepr.lat.to_value(u.radian) # set up the astrometry context for ICRS<->GCRS and then convert to ICRS # coordinate direction obs_pv = erfa.pav2pv( gcrs_coo.obsgeoloc.get_xyz(xyz_axis=-1).to_value(u.m), gcrs_coo.obsgeovel.get_xyz(xyz_axis=-1).to_value(u.m/u.s)) jd1, jd2 = get_jd12(hcrs_frame.obstime, 'tdb') earth_pv, earth_heliocentric = prepare_earth_position_vel(gcrs_coo.obstime) astrom = erfa.apcs(jd1, jd2, obs_pv, earth_pv, earth_heliocentric) i_ra, i_dec = aticq(gcrs_ra, gcrs_dec, astrom) # convert to Quantity objects i_ra = u.Quantity(i_ra, u.radian, copy=False) i_dec = u.Quantity(i_dec, u.radian, copy=False) if gcrs_coo.data.get_name() == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer newrep = UnitSphericalRepresentation(lat=i_dec, lon=i_ra, copy=False) else: # When there is a distance, apply the parallax/offset to the # Heliocentre as the last step to ensure round-tripping with the # hcrs_to_gcrs transform # Note that the distance in intermedrep is *not* a real distance as it # does not include the offset back to the Heliocentre intermedrep = SphericalRepresentation(lat=i_dec, lon=i_ra, distance=srepr.distance, copy=False) # astrom['eh'] and astrom['em'] contain Sun to observer unit vector, # and distance, respectively. Shapes are (X) and (X,3), where (X) is the # shape resulting from broadcasting the shape of the times object # against the shape of the pv array. # broadcast em to eh and scale eh eh = astrom['eh'] * astrom['em'][..., np.newaxis] eh = CartesianRepresentation(eh, unit=u.au, xyz_axis=-1, copy=False) newrep = intermedrep.to_cartesian() + eh return hcrs_frame.realize_frame(newrep)
def altaz_to_cirs(altaz_coo, cirs_frame): usrepr = altaz_coo.represent_as(UnitSphericalRepresentation) az = usrepr.lon.to_value(u.radian) zen = PIOVER2 - usrepr.lat.to_value(u.radian) # first set up the astrometry context for ICRS<->CIRS at the altaz_coo time astrom = erfa_astrom.get().apio13(altaz_coo) # the 'A' indicates zen/az inputs cirs_ra, cirs_dec = erfa.atoiq('A', az, zen, astrom) * u.radian if isinstance(altaz_coo.data, UnitSphericalRepresentation ) or altaz_coo.cartesian.x.unit == u.one: cirs_at_aa_time = CIRS(ra=cirs_ra, dec=cirs_dec, distance=None, obstime=altaz_coo.obstime) else: # treat the output of atoiq as an "astrometric" RA/DEC, so to get the # actual RA/Dec from the observers vantage point, we have to reverse # the vector operation of cirs_to_altaz (see there for more detail) loccirs = altaz_coo.location.get_itrs( altaz_coo.obstime).transform_to(cirs_frame) astrometric_rep = SphericalRepresentation(lon=cirs_ra, lat=cirs_dec, distance=altaz_coo.distance) newrepr = astrometric_rep + loccirs.cartesian cirs_at_aa_time = CIRS(newrepr, obstime=altaz_coo.obstime) # this final transform may be a no-op if the obstimes are the same return cirs_at_aa_time.transform_to(cirs_frame)
def hcc_to_hpc(helioccoord, heliopframe): """ Convert from Heliocentic Cartesian to Helioprojective Cartesian. """ if not _observers_are_equal(helioccoord.observer, heliopframe.observer): raise ConvertError( "Cannot directly transform heliocentric coordinates to " "helioprojective coordinates for different " "observers {} and {}. See discussion in this GH issue: " "https://github.com/sunpy/sunpy/issues/2712. Try converting to " "an intermediate heliographic Stonyhurst frame.".format( helioccoord.observer, heliopframe.observer)) x = helioccoord.x.to(u.m) y = helioccoord.y.to(u.m) z = helioccoord.z.to(u.m) # d is calculated as the distance between the points # (x,y,z) and (0,0,D0). distance = np.sqrt(x**2 + y**2 + (helioccoord.observer.radius - z)**2) hpcx = np.rad2deg(np.arctan2(x, helioccoord.observer.radius - z)) hpcy = np.rad2deg(np.arcsin(y / distance)) representation = SphericalRepresentation(hpcx, hpcy, distance.to(u.km)) return heliopframe.realize_frame(representation)
def icrs_to_altaz(icrs_coo, altaz_frame): # if the data are UnitSphericalRepresentation, we can skip the distance calculations is_unitspherical = (isinstance(icrs_coo.data, UnitSphericalRepresentation) or icrs_coo.cartesian.x.unit == u.one) # first set up the astrometry context for ICRS<->AltAz astrom = erfa_astrom.get().apco(altaz_frame) # correct for parallax to find BCRS direction from observer (as in erfa.pmpx) if is_unitspherical: srepr = icrs_coo.spherical else: observer_icrs = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) srepr = (icrs_coo.cartesian - observer_icrs).represent_as(SphericalRepresentation) # convert to topocentric CIRS cirs_ra, cirs_dec = atciqz(srepr, astrom) # now perform AltAz conversion az, zen, ha, odec, ora = erfa.atioq(cirs_ra, cirs_dec, astrom) alt = PIOVER2 - zen if is_unitspherical: aa_srepr = UnitSphericalRepresentation(az << u.radian, alt << u.radian, copy=False) else: aa_srepr = SphericalRepresentation(az << u.radian, alt << u.radian, srepr.distance, copy=False) return altaz_frame.realize_frame(aa_srepr)
def __init__(self, *args, **kwargs): _rep_kwarg = kwargs.get('representation', None) wrap = kwargs.pop('wrap_longitude', True) if ('radius' in kwargs and kwargs['radius'].unit is u.one and u.allclose(kwargs['radius'], 1 * u.one)): kwargs['radius'] = RSUN_METERS.to(u.km) super(HeliographicStonyhurst, self).__init__(*args, **kwargs) # Make 3D if specified as 2D # If representation was explicitly passed, do not change the rep. if not _rep_kwarg: # If we were passed a 3D rep extract the distance, otherwise # calculate it from RSUN. if isinstance(self._data, UnitSphericalRepresentation): distance = RSUN_METERS.to(u.km) self._data = SphericalRepresentation(lat=self._data.lat, lon=self._data.lon, distance=distance) if wrap and isinstance( self._data, (UnitSphericalRepresentation, SphericalRepresentation)): self._data.lon.wrap_angle = self._default_wrap_angle
def make_3d(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the surface of the Sun. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.Helioprojective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1*u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the Sun " f"for observer '{self.observer}' " "without `obstime` being specified.") rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) # Ingore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1*b) - np.sqrt(b**2 - 4*c)) / 2 return self.realize_frame(SphericalRepresentation(lon=lon, lat=lat, distance=d))
def make_3d(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the surface of the Sun. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.Helioprojective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1 * u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the Sun " f"for observer '{self.observer}' " "without `obstime` being specified.") rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon # Check for the use of floats with lower precision than the native Python float if not set([lon.dtype.type, lat.dtype.type]).issubset( [float, np.float64, np.longdouble]): raise SunpyUserWarning( "The Helioprojective component values appear to be lower " "precision than the native Python float: " f"Tx is {lon.dtype.name}, and Ty is {lat.dtype.name}. " "To minimize precision loss, you may want to cast the values to " "`float` or `numpy.float64` via the NumPy method `.astype()`.") # Calculate the distance to the surface of the Sun using the law of cosines cos_alpha = np.cos(lat) * np.cos(lon) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * cos_alpha # Ignore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 # use the "near" solution if self._spherical_screen: sphere_center = self._spherical_screen['center'].transform_to( self).cartesian c = sphere_center.norm()**2 - self._spherical_screen['radius']**2 b = -2 * sphere_center.dot(rep) # Ignore sqrt of NaNs with np.errstate(invalid='ignore'): dd = ((-1 * b) + np.sqrt(b**2 - 4 * c)) / 2 # use the "far" solution d = np.fmin(d, dd) if self._spherical_screen['only_off_disk'] else dd return self.realize_frame( SphericalRepresentation(lon=lon, lat=lat, distance=d))
def calculate_distance(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the disk of the Sun at the rsun radius. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.HelioProjective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D if (isinstance(self._data, SphericalRepresentation) and not (self.distance.unit is u.one and u.allclose(self.distance, 1 * u.one))): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the solar disk " "for observer '{}' " "without `obstime` being specified.".format( self.observer)) rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 return self.realize_frame( SphericalRepresentation(lon=lon, lat=lat, distance=d))
def cirs_to_icrs(cirs_coo, icrs_frame): # set up the astrometry context for ICRS<->cirs and then convert to # astrometric coordinate direction astrom = erfa_astrom.get().apci(cirs_coo) srepr = cirs_coo.represent_as(SphericalRepresentation) i_ra, i_dec = aticq(srepr.without_differentials(), astrom) if cirs_coo.data.get_name() == 'unitspherical' or cirs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), lon=u.Quantity(i_ra, u.radian, copy=False), copy=False) else: # When there is a distance, apply the parallax/offset to the SSB as the # last step - ensures round-tripping with the icrs_to_cirs transform # the distance in intermedrep is *not* a real distance as it does not # include the offset back to the SSB intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), lon=u.Quantity(i_ra, u.radian, copy=False), distance=srepr.distance, copy=False) astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newrep = intermedrep + astrom_eb return icrs_frame.realize_frame(newrep)
def cirs_to_observed(cirs_coo, observed_frame): if (np.any(observed_frame.location != cirs_coo.location) or np.any(cirs_coo.obstime != observed_frame.obstime)): cirs_coo = cirs_coo.transform_to(CIRS(obstime=observed_frame.obstime, location=observed_frame.location)) # if the data are UnitSphericalRepresentation, we can skip the distance calculations is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation) or cirs_coo.cartesian.x.unit == u.one) # We used to do "astrometric" corrections here, but these are no longer necesssary # CIRS has proper topocentric behaviour usrepr = cirs_coo.represent_as(UnitSphericalRepresentation) cirs_ra = usrepr.lon.to_value(u.radian) cirs_dec = usrepr.lat.to_value(u.radian) # first set up the astrometry context for CIRS<->observed astrom = erfa_astrom.get().apio(observed_frame) if isinstance(observed_frame, AltAz): lon, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) lat = PIOVER2 - zen else: _, _, lon, lat, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) if is_unitspherical: rep = UnitSphericalRepresentation(lat=u.Quantity(lat, u.radian, copy=False), lon=u.Quantity(lon, u.radian, copy=False), copy=False) else: # since we've transformed to CIRS at the observatory location, just use CIRS distance rep = SphericalRepresentation(lat=u.Quantity(lat, u.radian, copy=False), lon=u.Quantity(lon, u.radian, copy=False), distance=cirs_coo.distance, copy=False) return observed_frame.realize_frame(rep)
def icrs_to_cirs(icrs_coo, cirs_frame): # first set up the astrometry context for ICRS<->CIRS astrom = erfa_astrom.get().apci(cirs_frame) if icrs_coo.data.get_name() == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just do the infinite-distance/no parallax calculation srepr = icrs_coo.spherical cirs_ra, cirs_dec = atciqz(srepr.without_differentials(), astrom) newrep = UnitSphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False), lon=u.Quantity(cirs_ra, u.radian, copy=False), copy=False) else: # When there is a distance, we first offset for parallax to get the # astrometric coordinate direction and *then* run the ERFA transform for # no parallax/PM. This ensures reversibility and is more sensible for # inside solar system objects astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newcart = icrs_coo.cartesian - astrom_eb srepr = newcart.represent_as(SphericalRepresentation) cirs_ra, cirs_dec = atciqz(srepr.without_differentials(), astrom) newrep = SphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False), lon=u.Quantity(cirs_ra, u.radian, copy=False), distance=srepr.distance, copy=False) return cirs_frame.realize_frame(newrep)
def hcc_to_hgs(helioccoord, heliogframe): """ Convert from Heliocentric Cartesian to Heliographic Stonyhurst. """ if not isinstance(helioccoord.observer, BaseCoordinateFrame): raise ConvertError("Cannot transform heliocentric coordinates to " "heliographic coordinates for observer '{}' " "without `obstime` being specified.".format( helioccoord.observer)) x = helioccoord.x.to(u.m) y = helioccoord.y.to(u.m) z = helioccoord.z.to(u.m) l0_rad = helioccoord.observer.lon b0_deg = helioccoord.observer.lat cosb = np.cos(np.deg2rad(b0_deg)) sinb = np.sin(np.deg2rad(b0_deg)) hecr = np.sqrt(x**2 + y**2 + z**2) hgln = np.arctan2(x, z * cosb - y * sinb) + l0_rad hglt = np.arcsin((y * cosb + z * sinb) / hecr) representation = SphericalRepresentation(np.rad2deg(hgln), np.rad2deg(hglt), hecr.to(u.km)) return heliogframe.realize_frame(representation)
def gcrs_to_hcrs(gcrs_coo, hcrs_frame): if np.any(gcrs_coo.obstime != hcrs_frame.obstime): # if they GCRS obstime and HCRS obstime are not the same, we first # have to move to a GCRS where they are. frameattrs = gcrs_coo.get_frame_attr_names() frameattrs['obstime'] = hcrs_frame.obstime gcrs_coo = gcrs_coo.transform_to(GCRS(**frameattrs)) srepr = gcrs_coo.represent_as(SphericalRepresentation) gcrs_ra = srepr.lon.to_value(u.radian) gcrs_dec = srepr.lat.to_value(u.radian) # set up the astrometry context for ICRS<->GCRS and then convert to ICRS # coordinate direction astrom = erfa_astrom.get().apcs(gcrs_coo) i_ra, i_dec = aticq(gcrs_ra, gcrs_dec, astrom) # convert to Quantity objects i_ra = u.Quantity(i_ra, u.radian, copy=False) i_dec = u.Quantity(i_dec, u.radian, copy=False) if gcrs_coo.data.get_name() == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer newrep = UnitSphericalRepresentation(lat=i_dec, lon=i_ra, copy=False) else: # When there is a distance, apply the parallax/offset to the # Heliocentre as the last step to ensure round-tripping with the # hcrs_to_gcrs transform # Note that the distance in intermedrep is *not* a real distance as it # does not include the offset back to the Heliocentre intermedrep = SphericalRepresentation(lat=i_dec, lon=i_ra, distance=srepr.distance, copy=False) # astrom['eh'] and astrom['em'] contain Sun to observer unit vector, # and distance, respectively. Shapes are (X) and (X,3), where (X) is the # shape resulting from broadcasting the shape of the times object # against the shape of the pv array. # broadcast em to eh and scale eh eh = astrom['eh'] * astrom['em'][..., np.newaxis] eh = CartesianRepresentation(eh, unit=u.au, xyz_axis=-1, copy=False) newrep = intermedrep.to_cartesian() + eh return hcrs_frame.realize_frame(newrep)
def icrs_to_gcrs(icrs_coo, gcrs_frame): # first set up the astrometry context for ICRS<->GCRS. There are a few steps... # get the position and velocity arrays for the observatory. Need to # have xyz in last dimension, and pos/vel in one-but-last. # (Note could use np.stack once our minimum numpy version is >=1.10.) obs_pv = pav2pv( gcrs_frame.obsgeoloc.get_xyz(xyz_axis=-1).to_value(u.m), gcrs_frame.obsgeovel.get_xyz(xyz_axis=-1).to_value(u.m / u.s)) # find the position and velocity of earth jd1, jd2 = get_jd12(gcrs_frame.obstime, 'tdb') earth_pv, earth_heliocentric = prepare_earth_position_vel( gcrs_frame.obstime) # get astrometry context object, astrom. astrom = erfa.apcs(jd1, jd2, obs_pv, earth_pv, earth_heliocentric) if icrs_coo.data.get_name( ) == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just do the infinite-distance/no parallax calculation usrepr = icrs_coo.represent_as(UnitSphericalRepresentation) i_ra = usrepr.lon.to_value(u.radian) i_dec = usrepr.lat.to_value(u.radian) gcrs_ra, gcrs_dec = atciqz(i_ra, i_dec, astrom) newrep = UnitSphericalRepresentation(lat=u.Quantity(gcrs_dec, u.radian, copy=False), lon=u.Quantity(gcrs_ra, u.radian, copy=False), copy=False) else: # When there is a distance, we first offset for parallax to get the # BCRS coordinate direction and *then* run the ERFA transform for no # parallax/PM. This ensures reversibility and is more sensible for # inside solar system objects astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newcart = icrs_coo.cartesian - astrom_eb srepr = newcart.represent_as(SphericalRepresentation) i_ra = srepr.lon.to_value(u.radian) i_dec = srepr.lat.to_value(u.radian) gcrs_ra, gcrs_dec = atciqz(i_ra, i_dec, astrom) newrep = SphericalRepresentation(lat=u.Quantity(gcrs_dec, u.radian, copy=False), lon=u.Quantity(gcrs_ra, u.radian, copy=False), distance=srepr.distance, copy=False) return gcrs_frame.realize_frame(newrep)
def hgs_to_hgc(hgscoord, hgcframe): """ Transform from Heliographic Stonyhurst to Heliograpic Carrington. """ c_lon = hgscoord.spherical.lon + _carrington_offset(hgscoord.obstime).to( u.deg) representation = SphericalRepresentation(c_lon, hgscoord.lat, hgscoord.radius) hgcframe = hgcframe.__class__(obstime=hgscoord.obstime) return hgcframe.realize_frame(representation)
def cirs_to_altaz(cirs_coo, altaz_frame): if np.any(cirs_coo.obstime != altaz_frame.obstime): # the only frame attribute for the current CIRS is the obstime, but this # would need to be updated if a future change allowed specifying an # Earth location algorithm or something cirs_coo = cirs_coo.transform_to(CIRS(obstime=altaz_frame.obstime)) # we use the same obstime everywhere now that we know they're the same obstime = cirs_coo.obstime # if the data are UnitSphericalRepresentation, we can skip the distance calculations is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation) or cirs_coo.cartesian.x.unit == u.one) if is_unitspherical: usrepr = cirs_coo.represent_as(UnitSphericalRepresentation) cirs_ra = usrepr.lon.to_value(u.radian) cirs_dec = usrepr.lat.to_value(u.radian) else: # compute an "astrometric" ra/dec -i.e., the direction of the # displacement vector from the observer to the target in CIRS loccirs = altaz_frame.location.get_itrs( cirs_coo.obstime).transform_to(cirs_coo) diffrepr = ( cirs_coo.cartesian - loccirs.cartesian).represent_as(UnitSphericalRepresentation) cirs_ra = diffrepr.lon.to_value(u.radian) cirs_dec = diffrepr.lat.to_value(u.radian) # first set up the astrometry context for CIRS<->AltAz astrom = erfa_astrom.get().apio13(altaz_frame) az, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) if is_unitspherical: rep = UnitSphericalRepresentation(lat=u.Quantity(PIOVER2 - zen, u.radian, copy=False), lon=u.Quantity(az, u.radian, copy=False), copy=False) else: # now we get the distance as the cartesian distance from the earth # location to the coordinate location locitrs = altaz_frame.location.get_itrs(obstime) distance = locitrs.separation_3d(cirs_coo) rep = SphericalRepresentation(lat=u.Quantity(PIOVER2 - zen, u.radian, copy=False), lon=u.Quantity(az, u.radian, copy=False), distance=distance, copy=False) return altaz_frame.realize_frame(rep)
def _sun_north_angle_to_z(frame): """ Return the angle between solar north and the Z axis of the provided frame's coordinate system and observation time. """ # Find the Sun center in HGS at the frame's observation time(s) sun_center_repr = SphericalRepresentation(0*u.deg, 0*u.deg, 0*u.km) # The representation is repeated for as many times as are in obstime prior to transformation sun_center = SkyCoord(sun_center_repr._apply('repeat', frame.obstime.size), frame=HGS, obstime=frame.obstime) # Find the Sun north in HGS at the frame's observation time(s) # Only a rough value of the solar radius is needed here because, after the cross product, # only the direction from the Sun center to the Sun north pole matters sun_north_repr = SphericalRepresentation(0*u.deg, 90*u.deg, 690000*u.km) # The representation is repeated for as many times as are in obstime prior to transformation sun_north = SkyCoord(sun_north_repr._apply('repeat', frame.obstime.size), frame=HGS, obstime=frame.obstime) # Find the Sun center and Sun north in the frame's coordinate system sky_normal = sun_center.transform_to(frame).data.to_cartesian() sun_north = sun_north.transform_to(frame).data.to_cartesian() # Use cross products to obtain the sky projections of the two vectors (rotated by 90 deg) sun_north_in_sky = sun_north.cross(sky_normal) z_in_sky = CartesianRepresentation(0, 0, 1).cross(sky_normal) # Normalize directional vectors sky_normal /= sky_normal.norm() sun_north_in_sky /= sun_north_in_sky.norm() z_in_sky /= z_in_sky.norm() # Calculate the signed angle between the two projected vectors cos_theta = sun_north_in_sky.dot(z_in_sky) sin_theta = sun_north_in_sky.cross(z_in_sky).dot(sky_normal) angle = np.arctan2(sin_theta, cos_theta).to('deg') # If there is only one time, this function's output should be scalar rather than array if angle.size == 1: angle = angle[0] return Angle(angle)
def altaz_to_icrs(altaz_coo, icrs_frame): # if the data are UnitSphericalRepresentation, we can skip the distance calculations is_unitspherical = (isinstance(altaz_coo.data, UnitSphericalRepresentation) or altaz_coo.cartesian.x.unit == u.one) usrepr = altaz_coo.represent_as(UnitSphericalRepresentation) az = usrepr.lon.to_value(u.radian) zen = PIOVER2 - usrepr.lat.to_value(u.radian) # first set up the astrometry context for ICRS<->CIRS at the altaz_coo time astrom = erfa_astrom.get().apco(altaz_coo) # Topocentric CIRS cirs_ra, cirs_dec = erfa.atoiq('A', az, zen, astrom) << u.radian if is_unitspherical: srepr = SphericalRepresentation(cirs_ra, cirs_dec, 1, copy=False) else: srepr = SphericalRepresentation(lon=cirs_ra, lat=cirs_dec, distance=altaz_coo.distance, copy=False) # BCRS (Astrometric) direction to source bcrs_ra, bcrs_dec = aticq(srepr, astrom) << u.radian # Correct for parallax to get ICRS representation if is_unitspherical: icrs_srepr = UnitSphericalRepresentation(bcrs_ra, bcrs_dec, copy=False) else: icrs_srepr = SphericalRepresentation(lon=bcrs_ra, lat=bcrs_dec, distance=altaz_coo.distance, copy=False) observer_icrs = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newrepr = icrs_srepr.to_cartesian() + observer_icrs icrs_srepr = newrepr.represent_as(SphericalRepresentation) return icrs_frame.realize_frame(icrs_srepr)
def _apply_diffrot(self, duration, rotation_model): oldrepr = self.spherical from sunpy.physics.differential_rotation import diff_rot log.debug(f"Applying {duration} of solar rotation") newlon = oldrepr.lon + diff_rot(duration, oldrepr.lat, rot_type=rotation_model, frame_time='sidereal') newrepr = SphericalRepresentation(newlon, oldrepr.lat, oldrepr.distance) return self.realize_frame(newrepr)
def hgc_to_hgs(hgccoord, hgsframe): """ Convert from Heliograpic Carrington to Heliographic Stonyhurst. """ if hgccoord.obstime: obstime = hgccoord.obstime else: obstime = hgsframe.obstime s_lon = hgccoord.spherical.lon - _carrington_offset(obstime).to(u.deg) representation = SphericalRepresentation(s_lon, hgccoord.lat, hgccoord.radius) return hgsframe.realize_frame(representation)
def gcrs_to_icrs(gcrs_coo, icrs_frame): srepr = gcrs_coo.represent_as(SphericalRepresentation) gcrs_ra = srepr.lon.to_value(u.radian) gcrs_dec = srepr.lat.to_value(u.radian) # set up the astrometry context for ICRS<->GCRS and then convert to BCRS # coordinate direction obs_pv = pav2pv( gcrs_coo.obsgeoloc.get_xyz(xyz_axis=-1).to_value(u.m), gcrs_coo.obsgeovel.get_xyz(xyz_axis=-1).to_value(u.m / u.s)) jd1, jd2 = get_jd12(gcrs_coo.obstime, 'tdb') earth_pv, earth_heliocentric = prepare_earth_position_vel(gcrs_coo.obstime) astrom = erfa.apcs(jd1, jd2, obs_pv, earth_pv, earth_heliocentric) i_ra, i_dec = aticq(gcrs_ra, gcrs_dec, astrom) if gcrs_coo.data.get_name( ) == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), lon=u.Quantity(i_ra, u.radian, copy=False), copy=False) else: # When there is a distance, apply the parallax/offset to the SSB as the # last step - ensures round-tripping with the icrs_to_gcrs transform # the distance in intermedrep is *not* a real distance as it does not # include the offset back to the SSB intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), lon=u.Quantity(i_ra, u.radian, copy=False), distance=srepr.distance, copy=False) astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newrep = intermedrep + astrom_eb return icrs_frame.realize_frame(newrep)
def hgs_to_hgc(hgscoord, hgcframe): """ Transform from Heliographic Stonyhurst to Heliograpic Carrington. """ if hgcframe.obstime is None or np.any(hgcframe.obstime != hgscoord.obstime): raise ValueError("Can not transform from Heliographic Stonyhurst to " "Heliographic Carrington, unless both frames have matching obstime.") c_lon = hgscoord.spherical.lon + _carrington_offset(hgscoord.obstime).to(u.deg) representation = SphericalRepresentation(c_lon, hgscoord.spherical.lat, hgscoord.spherical.distance) hgcframe = hgcframe.__class__(obstime=hgscoord.obstime) return hgcframe.realize_frame(representation)
def hgc_to_hgs(hgccoord, hgsframe): """ Convert from Heliograpic Carrington to Heliographic Stonyhurst. """ if hgsframe.obstime is None or np.any(hgsframe.obstime != hgccoord.obstime): raise ValueError("Can not transform from Heliographic Carrington to " "Heliographic Stonyhurst, unless both frames have matching obstime.") obstime = hgsframe.obstime s_lon = hgccoord.spherical.lon - _carrington_offset(obstime).to( u.deg) representation = SphericalRepresentation(s_lon, hgccoord.spherical.lat, hgccoord.spherical.distance) return hgsframe.realize_frame(representation)
def icrs_to_cirs(icrs_coo, cirs_frame): # first set up the astrometry context for ICRS<->CIRS jd1, jd2 = get_jd12(cirs_frame.obstime, 'tt') x, y, s = get_cip(jd1, jd2) earth_pv, earth_heliocentric = prepare_earth_position_vel( cirs_frame.obstime) # erfa.apci requests TDB but TT can be used instead of TDB without any significant impact on accuracy astrom = erfa.apci(jd1, jd2, earth_pv, earth_heliocentric, x, y, s) if icrs_coo.data.get_name( ) == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just do the infinite-distance/no parallax calculation usrepr = icrs_coo.represent_as(UnitSphericalRepresentation) i_ra = usrepr.lon.to_value(u.radian) i_dec = usrepr.lat.to_value(u.radian) cirs_ra, cirs_dec = atciqz(i_ra, i_dec, astrom) newrep = UnitSphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False), lon=u.Quantity(cirs_ra, u.radian, copy=False), copy=False) else: # When there is a distance, we first offset for parallax to get the # astrometric coordinate direction and *then* run the ERFA transform for # no parallax/PM. This ensures reversibility and is more sensible for # inside solar system objects astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newcart = icrs_coo.cartesian - astrom_eb srepr = newcart.represent_as(SphericalRepresentation) i_ra = srepr.lon.to_value(u.radian) i_dec = srepr.lat.to_value(u.radian) cirs_ra, cirs_dec = atciqz(i_ra, i_dec, astrom) newrep = SphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False), lon=u.Quantity(cirs_ra, u.radian, copy=False), distance=srepr.distance, copy=False) return cirs_frame.realize_frame(newrep)
def altaz_to_cirs(altaz_coo, cirs_frame): usrepr = altaz_coo.represent_as(UnitSphericalRepresentation) az = usrepr.lon.to_value(u.radian) zen = PIOVER2 - usrepr.lat.to_value(u.radian) lon, lat, height = altaz_coo.location.to_geodetic('WGS84') xp, yp = get_polar_motion(altaz_coo.obstime) # first set up the astrometry context for ICRS<->CIRS at the altaz_coo time jd1, jd2 = get_jd12(altaz_coo.obstime, 'utc') astrom = erfa.apio13( jd1, jd2, get_dut1utc(altaz_coo.obstime), lon.to_value(u.radian), lat.to_value(u.radian), height.to_value(u.m), xp, yp, # polar motion # all below are already in correct units because they are QuantityFrameAttribues altaz_coo.pressure.value, altaz_coo.temperature.value, altaz_coo.relative_humidity.value, altaz_coo.obswl.value) # the 'A' indicates zen/az inputs cirs_ra, cirs_dec = erfa.atoiq('A', az, zen, astrom) * u.radian if isinstance(altaz_coo.data, UnitSphericalRepresentation ) or altaz_coo.cartesian.x.unit == u.one: cirs_at_aa_time = CIRS(ra=cirs_ra, dec=cirs_dec, distance=None, obstime=altaz_coo.obstime) else: # treat the output of atoiq as an "astrometric" RA/DEC, so to get the # actual RA/Dec from the observers vantage point, we have to reverse # the vector operation of cirs_to_altaz (see there for more detail) loccirs = altaz_coo.location.get_itrs( altaz_coo.obstime).transform_to(cirs_frame) astrometric_rep = SphericalRepresentation(lon=cirs_ra, lat=cirs_dec, distance=altaz_coo.distance) newrepr = astrometric_rep + loccirs.cartesian cirs_at_aa_time = CIRS(newrepr, obstime=altaz_coo.obstime) # this final transform may be a no-op if the obstimes are the same return cirs_at_aa_time.transform_to(cirs_frame)
def make_3d(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the surface of the Sun. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.Helioprojective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1 * u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the Sun " f"for observer '{self.observer}' " "without `obstime` being specified.") rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon cos_alpha = np.cos(lat) * np.cos(lon) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * cos_alpha # Ignore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 # use the "near" solution if self._spherical_screen: sphere_center = self._spherical_screen['center'].transform_to( self).cartesian c = sphere_center.norm()**2 - self._spherical_screen['radius']**2 b = -2 * sphere_center.dot(rep) # Ignore sqrt of NaNs with np.errstate(invalid='ignore'): dd = ((-1 * b) + np.sqrt(b**2 - 4 * c)) / 2 # use the "far" solution d = np.fmin(d, dd) if self._spherical_screen['only_off_disk'] else dd return self.realize_frame( SphericalRepresentation(lon=lon, lat=lat, distance=d))
def cirs_to_icrs(cirs_coo, icrs_frame): srepr = cirs_coo.represent_as(SphericalRepresentation) cirs_ra = srepr.lon.to_value(u.radian) cirs_dec = srepr.lat.to_value(u.radian) # set up the astrometry context for ICRS<->cirs and then convert to # astrometric coordinate direction jd1, jd2 = get_jd12(cirs_coo.obstime, 'tt') x, y, s = get_cip(jd1, jd2) earth_pv, earth_heliocentric = prepare_earth_position_vel(cirs_coo.obstime) # erfa.apci requests TDB but TT can be used instead of TDB without any significant impact on accuracy astrom = erfa.apci(jd1, jd2, earth_pv, earth_heliocentric, x, y, s) i_ra, i_dec = aticq(cirs_ra, cirs_dec, astrom) if cirs_coo.data.get_name( ) == 'unitspherical' or cirs_coo.data.to_cartesian().x.unit == u.one: # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), lon=u.Quantity(i_ra, u.radian, copy=False), copy=False) else: # When there is a distance, apply the parallax/offset to the SSB as the # last step - ensures round-tripping with the icrs_to_cirs transform # the distance in intermedrep is *not* a real distance as it does not # include the offset back to the SSB intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), lon=u.Quantity(i_ra, u.radian, copy=False), distance=srepr.distance, copy=False) astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) newrep = intermedrep + astrom_eb return icrs_frame.realize_frame(newrep)
def hcc_to_hpc(helioccoord, heliopframe): """ Convert from Heliocentic Cartesian to Helioprojective Cartesian. """ x = helioccoord.x.to(u.m) y = helioccoord.y.to(u.m) z = helioccoord.z.to(u.m) # d is calculated as the distance between the points # (x,y,z) and (0,0,D0). distance = np.sqrt(x**2 + y**2 + (helioccoord.observer.radius - z)**2) hpcx = np.rad2deg(np.arctan2(x, helioccoord.observer.radius - z)) hpcy = np.rad2deg(np.arcsin(y / distance)) representation = SphericalRepresentation(hpcx, hpcy, distance.to(u.km)) return heliopframe.realize_frame(representation)
def __init__(self, *args, **kwargs): _rep_kwarg = kwargs.get('representation_type', None) if ('radius' in kwargs and kwargs['radius'].unit is u.one and u.allclose(kwargs['radius'], 1 * u.one)): kwargs['radius'] = _RSUN.to(u.km) super().__init__(*args, **kwargs) # Make 3D if specified as 2D # If representation was explicitly passed, do not change the rep. if not _rep_kwarg: # If we were passed a 3D rep extract the distance, otherwise # calculate it from _RSUN. if isinstance(self._data, UnitSphericalRepresentation): distance = _RSUN.to(u.km) self._data = SphericalRepresentation(lat=self._data.lat, lon=self._data.lon, distance=distance)
def _sun_north_angle_to_z(frame): """ Return the angle between solar north and the Z axis of the provided frame's coordinate system and observation time. """ # Find the Sun center in HGS at the frame's observation time(s) sun_center_repr = SphericalRepresentation(0 * u.deg, 0 * u.deg, 0 * u.km) # The representation is repeated for as many times as are in obstime prior to transformation sun_center = SkyCoord(sun_center_repr._apply('repeat', frame.obstime.size), frame=HGS, obstime=frame.obstime) # Find the Sun north in HGS at the frame's observation time(s) # Only a rough value of the solar radius is needed here because, after the cross product, # only the direction from the Sun center to the Sun north pole matters sun_north_repr = SphericalRepresentation(0 * u.deg, 90 * u.deg, 690000 * u.km) # The representation is repeated for as many times as are in obstime prior to transformation sun_north = SkyCoord(sun_north_repr._apply('repeat', frame.obstime.size), frame=HGS, obstime=frame.obstime) # Find the Sun center and Sun north in the frame's coordinate system sky_normal = sun_center.transform_to(frame).data.to_cartesian() sun_north = sun_north.transform_to(frame).data.to_cartesian() # Use cross products to obtain the sky projections of the two vectors (rotated by 90 deg) sun_north_in_sky = sun_north.cross(sky_normal) z_in_sky = CartesianRepresentation(0, 0, 1).cross(sky_normal) # Normalize directional vectors sky_normal /= sky_normal.norm() sun_north_in_sky /= sun_north_in_sky.norm() z_in_sky /= z_in_sky.norm() # Calculate the signed angle between the two projected vectors cos_theta = sun_north_in_sky.dot(z_in_sky) sin_theta = sun_north_in_sky.cross(z_in_sky).dot(sky_normal) angle = np.arctan2(sin_theta, cos_theta).to('deg') # If there is only one time, this function's output should be scalar rather than array if angle.size == 1: angle = angle[0] return Angle(angle)
def uniform_spherical_random_volume(size=1, max_radius=1): """Generate a random sampling of points that follow a uniform volume density distribution within a sphere. Parameters ---------- size : int The number of points to generate. max_radius : number, quantity-like, optional A dimensionless or unit-ful factor to scale the random distances. rng : `numpy.random.Generator`, optional A random number generator instance. Returns ------- rep : `~astropy.coordinates.SphericalRepresentation` The random points. """ rng = np.random # can maybe switch to this being an input later - see #11628 usph = uniform_spherical_random_surface(size=size) r = np.cbrt(rng.uniform(size=size)) * u.Quantity(max_radius, copy=False) return SphericalRepresentation(usph.lon, usph.lat, r)
def cirs_to_altaz(cirs_coo, altaz_frame): if np.any(cirs_coo.obstime != altaz_frame.obstime): # the only frame attribute for the current CIRS is the obstime, but this # would need to be updated if a future change allowed specifying an # Earth location algorithm or something cirs_coo = cirs_coo.transform_to(CIRS(obstime=altaz_frame.obstime)) # we use the same obstime everywhere now that we know they're the same obstime = cirs_coo.obstime # if the data are UnitSphericalRepresentation, we can skip the distance calculations is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation) or cirs_coo.cartesian.x.unit == u.one) if is_unitspherical: usrepr = cirs_coo.represent_as(UnitSphericalRepresentation) cirs_ra = usrepr.lon.to_value(u.radian) cirs_dec = usrepr.lat.to_value(u.radian) else: # compute an "astrometric" ra/dec -i.e., the direction of the # displacement vector from the observer to the target in CIRS loccirs = altaz_frame.location.get_itrs( cirs_coo.obstime).transform_to(cirs_coo) diffrepr = ( cirs_coo.cartesian - loccirs.cartesian).represent_as(UnitSphericalRepresentation) cirs_ra = diffrepr.lon.to_value(u.radian) cirs_dec = diffrepr.lat.to_value(u.radian) lon, lat, height = altaz_frame.location.to_geodetic('WGS84') xp, yp = get_polar_motion(obstime) # first set up the astrometry context for CIRS<->AltAz jd1, jd2 = get_jd12(obstime, 'utc') astrom = erfa.apio13( jd1, jd2, get_dut1utc(obstime), lon.to_value(u.radian), lat.to_value(u.radian), height.to_value(u.m), xp, yp, # polar motion # all below are already in correct units because they are QuantityFrameAttribues altaz_frame.pressure.value, altaz_frame.temperature.value, altaz_frame.relative_humidity.value, altaz_frame.obswl.value) az, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) if is_unitspherical: rep = UnitSphericalRepresentation(lat=u.Quantity(PIOVER2 - zen, u.radian, copy=False), lon=u.Quantity(az, u.radian, copy=False), copy=False) else: # now we get the distance as the cartesian distance from the earth # location to the coordinate location locitrs = altaz_frame.location.get_itrs(obstime) distance = locitrs.separation_3d(cirs_coo) rep = SphericalRepresentation(lat=u.Quantity(PIOVER2 - zen, u.radian, copy=False), lon=u.Quantity(az, u.radian, copy=False), distance=distance, copy=False) return altaz_frame.realize_frame(rep)
def test_representations_api(): from astropy.coordinates.representation import SphericalRepresentation, \ UnitSphericalRepresentation, PhysicsSphericalRepresentation, \ CartesianRepresentation from astropy.coordinates import Angle, Longitude, Latitude, Distance # <-----------------Classes for representation of coordinate data--------------> # These classes inherit from a common base class and internally contain Quantity # objects, which are arrays (although they may act as scalars, like numpy's # length-0 "arrays") # They can be initialized with a variety of ways that make intuitive sense. # Distance is optional. UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg) UnitSphericalRepresentation(lon=8*u.hourangle, lat=5*u.deg) SphericalRepresentation(lon=8*u.hourangle, lat=5*u.deg, distance=10*u.kpc) # In the initial implementation, the lat/lon/distance arguments to the # initializer must be in order. A *possible* future change will be to allow # smarter guessing of the order. E.g. `Latitude` and `Longitude` objects can be # given in any order. UnitSphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg)) SphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg), Distance(10, u.kpc)) # Arrays of any of the inputs are fine UnitSphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg) # Default is to copy arrays, but optionally, it can be a reference UnitSphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg, copy=False) # strings are parsed by `Latitude` and `Longitude` constructors, so no need to # implement parsing in the Representation classes UnitSphericalRepresentation(lon=Angle('2h6m3.3s'), lat=Angle('0.1rad')) # Or, you can give `Quantity`s with keywords, and they will be internally # converted to Angle/Distance c1 = SphericalRepresentation(lon=8*u.hourangle, lat=5*u.deg, distance=10*u.kpc) # Can also give another representation object with the `reprobj` keyword. c2 = SphericalRepresentation.from_representation(c1) # distance, lat, and lon typically will just match in shape SphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg, distance=[10, 11]*u.kpc) # if the inputs are not the same, if possible they will be broadcast following # numpy's standard broadcasting rules. c2 = SphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg, distance=10*u.kpc) assert len(c2.distance) == 2 # when they can't be broadcast, it is a ValueError (same as Numpy) with raises(ValueError): c2 = UnitSphericalRepresentation(lon=[8, 9, 10]*u.hourangle, lat=[5, 6]*u.deg) # It's also possible to pass in scalar quantity lists with mixed units. These # are converted to array quantities following the same rule as `Quantity`: all # elements are converted to match the first element's units. c2 = UnitSphericalRepresentation(lon=Angle([8*u.hourangle, 135*u.deg]), lat=Angle([5*u.deg, (6*np.pi/180)*u.rad])) assert c2.lat.unit == u.deg and c2.lon.unit == u.hourangle npt.assert_almost_equal(c2.lon[1].value, 9) # The Quantity initializer itself can also be used to force the unit even if the # first element doesn't have the right unit lon = u.Quantity([120*u.deg, 135*u.deg], u.hourangle) lat = u.Quantity([(5*np.pi/180)*u.rad, 0.4*u.hourangle], u.deg) c2 = UnitSphericalRepresentation(lon, lat) # regardless of how input, the `lat` and `lon` come out as angle/distance assert isinstance(c1.lat, Angle) assert isinstance(c1.lat, Latitude) # `Latitude` is an `Angle` subclass assert isinstance(c1.distance, Distance) # but they are read-only, as representations are immutable once created with raises(AttributeError): c1.lat = Latitude(5, u.deg) # Note that it is still possible to modify the array in-place, but this is not # sanctioned by the API, as this would prevent things like caching. c2.lat[:] = [0] * u.deg # possible, but NOT SUPPORTED # To address the fact that there are various other conventions for how spherical # coordinates are defined, other conventions can be included as new classes. # Later there may be other conventions that we implement - for now just the # physics convention, as it is one of the most common cases. c3 = PhysicsSphericalRepresentation(phi=120*u.deg, theta=85*u.deg, r=3*u.kpc) # first dimension must be length-3 if a lone `Quantity` is passed in. c1 = CartesianRepresentation(np.random.randn(3, 100) * u.kpc) assert c1.xyz.shape[0] == 3 assert c1.xyz.unit == u.kpc assert c1.x.shape[0] == 100 assert c1.y.shape[0] == 100 assert c1.z.shape[0] == 100 # can also give each as separate keywords CartesianRepresentation(x=np.random.randn(100)*u.kpc, y=np.random.randn(100)*u.kpc, z=np.random.randn(100)*u.kpc) # if the units don't match but are all distances, they will automatically be # converted to match `x` xarr, yarr, zarr = np.random.randn(3, 100) c1 = CartesianRepresentation(x=xarr*u.kpc, y=yarr*u.kpc, z=zarr*u.kpc) c2 = CartesianRepresentation(x=xarr*u.kpc, y=yarr*u.kpc, z=zarr*u.pc) assert c1.xyz.unit == c2.xyz.unit == u.kpc assert_allclose((c1.z / 1000) - c2.z, 0*u.kpc, atol=1e-10*u.kpc) # representations convert into other representations via `represent_as` srep = SphericalRepresentation(lon=90*u.deg, lat=0*u.deg, distance=1*u.pc) crep = srep.represent_as(CartesianRepresentation) assert_allclose(crep.x, 0*u.pc, atol=1e-10*u.pc) assert_allclose(crep.y, 1*u.pc, atol=1e-10*u.pc) assert_allclose(crep.z, 0*u.pc, atol=1e-10*u.pc)