Skill Tracer
Skills › Scientific Agent Skills › astropy › coordinates.md

coordinates.md

298 lines · skills/astropy/references/coordinates.md at 5f2bc03

Open on GitHub
1# Astronomical Coordinates (astropy.coordinates)
2
3The `astropy.coordinates` package provides tools for representing celestial coordinates and transforming between different coordinate systems.
4

Astronomical Coordinates (astropy.coordinates)

The astropy.coordinates package provides tools for representing celestial coordinates and transforming between different coordinate systems.

Raw lines 1 to 4
5## Creating Coordinates with SkyCoord
6
7The high-level `SkyCoord` class is the recommended interface:
8
9```python
10from astropy import units as u
11from astropy.coordinates import SkyCoord
13# Decimal degrees
14c = SkyCoord(ra=10.625*u.degree, dec=41.2*u.degree, frame='icrs')
16# Sexagesimal strings
17c = SkyCoord(ra='00h42m30s', dec='+41d12m00s', frame='icrs')
19# Mixed formats
20c = SkyCoord('00h42.5m +41d12m', unit=(u.hourangle, u.deg))
22# Galactic coordinates
23c = SkyCoord(l=120.5*u.degree, b=-23.4*u.degree, frame='galactic')
24```

Creating Coordinates with SkyCoord

The high-level SkyCoord class is the recommended interface:

from astropy import units as u
from astropy.coordinates import SkyCoord

# Decimal degrees
c = SkyCoord(ra=10.625*u.degree, dec=41.2*u.degree, frame='icrs')

# Sexagesimal strings
c = SkyCoord(ra='00h42m30s', dec='+41d12m00s', frame='icrs')

# Mixed formats
c = SkyCoord('00h42.5m +41d12m', unit=(u.hourangle, u.deg))

# Galactic coordinates
c = SkyCoord(l=120.5*u.degree, b=-23.4*u.degree, frame='galactic')
Raw lines 5 to 25
26## Array Coordinates
28Process multiple coordinates efficiently using arrays:
30```python
31# Create array of coordinates
32coords = SkyCoord(ra=[10, 11, 12]*u.degree,
33 dec=[41, -5, 42]*u.degree)
35# Access individual elements
36coords[0]
37coords[1:3]
39# Array operations
40coords.shape
41len(coords)
42```

Array Coordinates

Process multiple coordinates efficiently using arrays:

# Create array of coordinates
coords = SkyCoord(ra=[10, 11, 12]*u.degree,
                  dec=[41, -5, 42]*u.degree)

# Access individual elements
coords[0]
coords[1:3]

# Array operations
coords.shape
len(coords)
Raw lines 26 to 43
44## Accessing Components
46```python
47c = SkyCoord(ra=10.68*u.degree, dec=41.27*u.degree, frame='icrs')
49# Access coordinates
50c.ra # <Longitude 10.68 deg>
51c.dec # <Latitude 41.27 deg>
52c.ra.hour # Convert to hours
53c.ra.hms # Hours, minutes, seconds tuple
54c.dec.dms # Degrees, arcminutes, arcseconds tuple
55```

Accessing Components

c = SkyCoord(ra=10.68*u.degree, dec=41.27*u.degree, frame='icrs')

# Access coordinates
c.ra        # <Longitude 10.68 deg>
c.dec       # <Latitude 41.27 deg>
c.ra.hour   # Convert to hours
c.ra.hms    # Hours, minutes, seconds tuple
c.dec.dms   # Degrees, arcminutes, arcseconds tuple
Raw lines 44 to 56
57## String Formatting
59```python
60c.to_string('decimal') # '10.68 41.27'
61c.to_string('dms') # '10d40m48s 41d16m12s'
62c.to_string('hmsdms') # '00h42m43.2s +41d16m12s'
64# Custom formatting
65c.ra.to_string(unit=u.hourangle, sep=':', precision=2)
66```

String Formatting

c.to_string('decimal')      # '10.68 41.27'
c.to_string('dms')          # '10d40m48s 41d16m12s'
c.to_string('hmsdms')       # '00h42m43.2s +41d16m12s'

# Custom formatting
c.ra.to_string(unit=u.hourangle, sep=':', precision=2)
Raw lines 57 to 67
68## Coordinate Transformations
70Transform between reference frames:
72```python
73c_icrs = SkyCoord(ra=10.68*u.degree, dec=41.27*u.degree, frame='icrs')
75# Simple transformations (as attributes)
76c_galactic = c_icrs.galactic
77c_fk5 = c_icrs.fk5
78c_fk4 = c_icrs.fk4
80# Explicit transformations
81c_icrs.transform_to('galactic')
82from astropy.coordinates import FK5
83c_icrs.transform_to(FK5(equinox='J1975')) # Custom frame parameters
84```

Coordinate Transformations

Transform between reference frames:

c_icrs = SkyCoord(ra=10.68*u.degree, dec=41.27*u.degree, frame='icrs')

# Simple transformations (as attributes)
c_galactic = c_icrs.galactic
c_fk5 = c_icrs.fk5
c_fk4 = c_icrs.fk4

# Explicit transformations
c_icrs.transform_to('galactic')
from astropy.coordinates import FK5
c_icrs.transform_to(FK5(equinox='J1975'))  # Custom frame parameters
Raw lines 68 to 85
86## Common Coordinate Frames

Common Coordinate Frames

Raw lines 86 to 87
88### Celestial Frames
89- **ICRS**: International Celestial Reference System (default, most common)
90- **FK5**: Fifth Fundamental Catalogue (equinox J2000.0 by default)
91- **FK4**: Fourth Fundamental Catalogue (older, requires equinox specification)
92- **GCRS**: Geocentric Celestial Reference System
93- **CIRS**: Celestial Intermediate Reference System

Celestial Frames

  • ICRS: International Celestial Reference System (default, most common)
  • FK5: Fifth Fundamental Catalogue (equinox J2000.0 by default)
  • FK4: Fourth Fundamental Catalogue (older, requires equinox specification)
  • GCRS: Geocentric Celestial Reference System
  • CIRS: Celestial Intermediate Reference System
Raw lines 88 to 94
95### Galactic Frames
96- **Galactic**: IAU 1958 galactic coordinates
97- **Supergalactic**: De Vaucouleurs supergalactic coordinates
98- **Galactocentric**: Galactic center-based 3D coordinates

Galactic Frames

  • Galactic: IAU 1958 galactic coordinates
  • Supergalactic: De Vaucouleurs supergalactic coordinates
  • Galactocentric: Galactic center-based 3D coordinates
Raw lines 95 to 99
100### Horizontal Frames
101- **AltAz**: Altitude-azimuth (observer-dependent)
102- **HADec**: Hour angle-declination

Horizontal Frames

  • AltAz: Altitude-azimuth (observer-dependent)
  • HADec: Hour angle-declination
Raw lines 100 to 103
104### Ecliptic Frames
105- **GeocentricMeanEcliptic**: Geocentric mean ecliptic
106- **BarycentricMeanEcliptic**: Barycentric mean ecliptic
107- **HeliocentricMeanEcliptic**: Heliocentric mean ecliptic

Ecliptic Frames

  • GeocentricMeanEcliptic: Geocentric mean ecliptic
  • BarycentricMeanEcliptic: Barycentric mean ecliptic
  • HeliocentricMeanEcliptic: Heliocentric mean ecliptic
Raw lines 104 to 108
109## Observer-Dependent Transformations
111For altitude-azimuth coordinates, specify observation time and location:
113```python
114from astropy.time import Time
115from astropy.coordinates import EarthLocation, AltAz
117# Define observer location
118observing_location = EarthLocation(lat=40.8*u.deg, lon=-121.5*u.deg, height=1060*u.m)
119# Named sites are an optional network/cached-registry lookup, not needed here.
121# Define observation time
122observing_time = Time('2023-01-15 23:00:00', scale='utc')
124# Transform to alt-az
125aa_frame = AltAz(obstime=observing_time, location=observing_location, pressure=0*u.hPa)
126aa = c_icrs.transform_to(aa_frame)
128print(f"Altitude: {aa.alt}")
129print(f"Azimuth: {aa.az}")
130```
132`pressure=0` gives unrefracted AltAz. Refraction requires measured weather inputs
133and becomes unreliable near/below the horizon; it is not a universal correction.
134Record geodetic datum, site height, UTC time, IERS data version and ephemeris. For
135offline runs set `iers.conf.auto_download = False`; check data coverage and warnings.

Observer-Dependent Transformations

For altitude-azimuth coordinates, specify observation time and location:

from astropy.time import Time
from astropy.coordinates import EarthLocation, AltAz

# Define observer location
observing_location = EarthLocation(lat=40.8*u.deg, lon=-121.5*u.deg, height=1060*u.m)
# Named sites are an optional network/cached-registry lookup, not needed here.

# Define observation time
observing_time = Time('2023-01-15 23:00:00', scale='utc')

# Transform to alt-az
aa_frame = AltAz(obstime=observing_time, location=observing_location, pressure=0*u.hPa)
aa = c_icrs.transform_to(aa_frame)

print(f"Altitude: {aa.alt}")
print(f"Azimuth: {aa.az}")

pressure=0 gives unrefracted AltAz. Refraction requires measured weather inputs and becomes unreliable near/below the horizon; it is not a universal correction. Record geodetic datum, site height, UTC time, IERS data version and ephemeris. For offline runs set iers.conf.auto_download = False; check data coverage and warnings.

Raw lines 109 to 136
137## Working with Distances
139Add distance information for 3D coordinates:
141```python
142# With distance
143c = SkyCoord(ra=10*u.degree, dec=9*u.degree, distance=770*u.kpc, frame='icrs')
145# Access 3D Cartesian coordinates
146c.cartesian.x
147c.cartesian.y
148c.cartesian.z
150# Distance from origin
151c.distance
153# 3D separation
154c1 = SkyCoord(ra=10*u.degree, dec=9*u.degree, distance=10*u.pc)
155c2 = SkyCoord(ra=11*u.degree, dec=10*u.degree, distance=11.5*u.pc)
156sep_3d = c1.separation_3d(c2) # 3D distance
157```

Working with Distances

Add distance information for 3D coordinates:

# With distance
c = SkyCoord(ra=10*u.degree, dec=9*u.degree, distance=770*u.kpc, frame='icrs')

# Access 3D Cartesian coordinates
c.cartesian.x
c.cartesian.y
c.cartesian.z

# Distance from origin
c.distance

# 3D separation
c1 = SkyCoord(ra=10*u.degree, dec=9*u.degree, distance=10*u.pc)
c2 = SkyCoord(ra=11*u.degree, dec=10*u.degree, distance=11.5*u.pc)
sep_3d = c1.separation_3d(c2)  # 3D distance
Raw lines 137 to 158
159## Angular Separation
161Calculate on-sky separations:
163```python
164c1 = SkyCoord(ra=10*u.degree, dec=9*u.degree, frame='icrs')
165c2 = SkyCoord(ra=11*u.degree, dec=10*u.degree, frame='fk5')
167# Angular separation transforms frames; validate frame origins before comparing.
168# Non-rotation transforms can make separations observer-dependent.
169sep = c1.separation(c2)
170print(f"Separation: {sep.arcsec} arcsec")
172# Position angle
173pa = c1.position_angle(c2)
174```

Angular Separation

Calculate on-sky separations:

c1 = SkyCoord(ra=10*u.degree, dec=9*u.degree, frame='icrs')
c2 = SkyCoord(ra=11*u.degree, dec=10*u.degree, frame='fk5')

# Angular separation transforms frames; validate frame origins before comparing.
# Non-rotation transforms can make separations observer-dependent.
sep = c1.separation(c2)
print(f"Separation: {sep.arcsec} arcsec")

# Position angle
pa = c1.position_angle(c2)
Raw lines 159 to 175
176## Catalog Matching
178Match coordinates to catalog sources (requires SciPy). These fragments assume
179`ra_array` and `dec_array` are unitless degree arrays. Real catalogs need their
180frame, equinox and reference epoch verified first. Propagate proper motions with
181`apply_space_motion(new_obstime=...)` only when motion and epoch data support it.
182A nearest-neighbor result is not a probability or a unique association; many
183targets can select the same row, and `dist3d` is a unit-sphere chord without distances.
185```python
186# Single target matching
187catalog = SkyCoord(ra=ra_array*u.degree, dec=dec_array*u.degree)
188target = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
190# Find closest match
191idx, sep2d, dist3d = target.match_to_catalog_sky(catalog)
192matched_coord = catalog[idx]
194# Match with maximum separation constraint
195matches = target.separation(catalog) < 1*u.arcsec
196```

Catalog Matching

Match coordinates to catalog sources (requires SciPy). These fragments assume ra_array and dec_array are unitless degree arrays. Real catalogs need their frame, equinox and reference epoch verified first. Propagate proper motions with apply_space_motion(new_obstime=...) only when motion and epoch data support it. A nearest-neighbor result is not a probability or a unique association; many targets can select the same row, and dist3d is a unit-sphere chord without distances.

# Single target matching
catalog = SkyCoord(ra=ra_array*u.degree, dec=dec_array*u.degree)
target = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)

# Find closest match
idx, sep2d, dist3d = target.match_to_catalog_sky(catalog)
matched_coord = catalog[idx]

# Match with maximum separation constraint
matches = target.separation(catalog) < 1*u.arcsec
Raw lines 176 to 197
198## Named Objects
200Retrieve coordinates from online catalogs:
202**Network note:** `SkyCoord.from_name()` sends the object name to remote name-resolution services such as Sesame/SIMBAD/NED. Do not use it with confidential target names or proprietary survey identifiers; use explicit coordinates when privacy or reproducibility matters.
204```python
205# Query by name (requires internet)
206m31 = SkyCoord.from_name("M31")
207crab = SkyCoord.from_name("Crab Nebula")
208psr = SkyCoord.from_name("PSR J1012+5307")
209```

Named Objects

Retrieve coordinates from online catalogs:

Network note: SkyCoord.from_name() sends the object name to remote name-resolution services such as Sesame/SIMBAD/NED. Do not use it with confidential target names or proprietary survey identifiers; use explicit coordinates when privacy or reproducibility matters.

# Query by name (requires internet)
m31 = SkyCoord.from_name("M31")
crab = SkyCoord.from_name("Crab Nebula")
psr = SkyCoord.from_name("PSR J1012+5307")
Raw lines 198 to 210
211## Earth Locations
213Define observer locations:
215**Network note:** `EarthLocation.of_site()` and `get_site_names()` download the
216registry if it is not cached; `refresh_cache=True` replaces the cached copy.
217There is no guaranteed bundled offline registry. `of_address()` sends the address
218to Nominatim by default, or Google when `google_api_key` is supplied, and selects
219the first result. Its default height is zero; Google elevation is optional. Use
220explicit, validated geodetic coordinates for precision work. The network examples
221below are illustrative, not executed in the offline numerical suite.
223```python
224# By coordinates
225location = EarthLocation(lat=40*u.deg, lon=-120*u.deg, height=1000*u.m)
227# By named observatory
228keck = EarthLocation.of_site('Keck Observatory')
229vlt = EarthLocation.of_site('Paranal Observatory')
231# Force a fresh observatory registry only when network access is acceptable
232keck = EarthLocation.of_site('Keck Observatory', refresh_cache=True)
234# By address (requires internet)
235location = EarthLocation.of_address('1 Observatory Road, Example City')
237# List available observatories
238EarthLocation.get_site_names()
239```

Earth Locations

Define observer locations:

Network note: EarthLocation.of_site() and get_site_names() download the registry if it is not cached; refresh_cache=True replaces the cached copy. There is no guaranteed bundled offline registry. of_address() sends the address to Nominatim by default, or Google when google_api_key is supplied, and selects the first result. Its default height is zero; Google elevation is optional. Use explicit, validated geodetic coordinates for precision work. The network examples below are illustrative, not executed in the offline numerical suite.

# By coordinates
location = EarthLocation(lat=40*u.deg, lon=-120*u.deg, height=1000*u.m)

# By named observatory
keck = EarthLocation.of_site('Keck Observatory')
vlt = EarthLocation.of_site('Paranal Observatory')

# Force a fresh observatory registry only when network access is acceptable
keck = EarthLocation.of_site('Keck Observatory', refresh_cache=True)

# By address (requires internet)
location = EarthLocation.of_address('1 Observatory Road, Example City')

# List available observatories
EarthLocation.get_site_names()
Raw lines 211 to 240
241## Velocity Information
243Include proper motion and radial velocity:
245```python
246# Proper motion
247c = SkyCoord(ra=10*u.degree, dec=41*u.degree,
248 pm_ra_cosdec=15*u.mas/u.yr,
249 pm_dec=5*u.mas/u.yr,
250 distance=150*u.pc)
252# Radial velocity
253c = SkyCoord(ra=10*u.degree, dec=41*u.degree,
254 radial_velocity=20*u.km/u.s)
256# Both
257c = SkyCoord(ra=10*u.degree, dec=41*u.degree, distance=150*u.pc,
258 pm_ra_cosdec=15*u.mas/u.yr, pm_dec=5*u.mas/u.yr,
259 radial_velocity=20*u.km/u.s)
260```

Velocity Information

Include proper motion and radial velocity:

# Proper motion
c = SkyCoord(ra=10*u.degree, dec=41*u.degree,
             pm_ra_cosdec=15*u.mas/u.yr,
             pm_dec=5*u.mas/u.yr,
             distance=150*u.pc)

# Radial velocity
c = SkyCoord(ra=10*u.degree, dec=41*u.degree,
             radial_velocity=20*u.km/u.s)

# Both
c = SkyCoord(ra=10*u.degree, dec=41*u.degree, distance=150*u.pc,
             pm_ra_cosdec=15*u.mas/u.yr, pm_dec=5*u.mas/u.yr,
             radial_velocity=20*u.km/u.s)
Raw lines 241 to 261
262## Representation Types
264Switch between coordinate representations:
266```python
267# Cartesian representation
268c = SkyCoord(x=1*u.kpc, y=2*u.kpc, z=3*u.kpc,
269 representation_type='cartesian', frame='icrs')
271# Change representation
272c.representation_type = 'cylindrical'
273c.rho # Cylindrical radius
274c.phi # Azimuthal angle
275c.z # Height
277# Spherical (default for most frames)
278c.representation_type = 'spherical'
279```

Representation Types

Switch between coordinate representations:

# Cartesian representation
c = SkyCoord(x=1*u.kpc, y=2*u.kpc, z=3*u.kpc,
             representation_type='cartesian', frame='icrs')

# Change representation
c.representation_type = 'cylindrical'
c.rho  # Cylindrical radius
c.phi  # Azimuthal angle
c.z    # Height

# Spherical (default for most frames)
c.representation_type = 'spherical'
Raw lines 262 to 280
281## Performance Tips
2831. **Use arrays, not loops**: Process multiple coordinates as single array
2842. **Pre-compute frames**: Reuse frame objects for multiple transformations
2853. **Use broadcasting**: Efficiently transform many positions across many times
2864. **Validate interpolation**: `ErfaAstromInterpolator` can accelerate dense time
287 sampling; choose the interval against the required astrometric error budget.
289```python
290# Fast approach
291coords = SkyCoord(ra=ra_array*u.degree, dec=dec_array*u.degree)
292coords_transformed = coords.transform_to('galactic')
294# Slow approach (avoid)
295for ra, dec in zip(ra_array, dec_array):
296 c = SkyCoord(ra=ra*u.degree, dec=dec*u.degree)
297 c_transformed = c.transform_to('galactic')
298```

Performance Tips

  1. Use arrays, not loops: Process multiple coordinates as single array
  2. Pre-compute frames: Reuse frame objects for multiple transformations
  3. Use broadcasting: Efficiently transform many positions across many times
  4. Validate interpolation: ErfaAstromInterpolator can accelerate dense time sampling; choose the interval against the required astrometric error budget.
# Fast approach
coords = SkyCoord(ra=ra_array*u.degree, dec=dec_array*u.degree)
coords_transformed = coords.transform_to('galactic')

# Slow approach (avoid)
for ra, dec in zip(ra_array, dec_array):
    c = SkyCoord(ra=ra*u.degree, dec=dec*u.degree)
    c_transformed = c.transform_to('galactic')
Raw lines 281 to 298