refactor: move SIMBAD skill into database-lookup
Move SIMBAD content from standalone simbad-database skill into the consolidated database-lookup skill per reviewer feedback. Enhance database-lookup/references/simbad.md with detailed script format codes, full TAP table schemas, ADQL spatial query patterns, cross-matching examples, bibliography queries, and input sanitization guidance. Remove the standalone simbad-database directory (SKILL.md, api_reference, adql_reference, simbad_client.py) since all content is now covered by database-lookup. Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# SIMBAD Astronomical Database (CDS Strasbourg)
|
||||
|
||||
SIMBAD contains data on over 17 million astronomical objects beyond the Solar System, including identifications, coordinates, photometry, proper motions, parallaxes, radial velocities, spectral types, and bibliographic references.
|
||||
|
||||
## Base URLs
|
||||
|
||||
**TAP endpoint (recommended):**
|
||||
@@ -7,7 +9,7 @@
|
||||
https://simbad.cds.unistra.fr/simbad/sim-tap/sync
|
||||
```
|
||||
|
||||
**Legacy script interface:**
|
||||
**Script interface:**
|
||||
```
|
||||
https://simbad.cds.unistra.fr/simbad/sim-script
|
||||
```
|
||||
@@ -27,15 +29,24 @@ No API key required. All endpoints are public.
|
||||
### 1. TAP Queries (ADQL — recommended for programmatic use)
|
||||
|
||||
```
|
||||
GET /simbad/sim-tap/sync?request=doQuery&lang=adql&format={format}&query={ADQL}
|
||||
POST /simbad/sim-tap/sync
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
Parameters:
|
||||
REQUEST=doQuery
|
||||
LANG=ADQL
|
||||
QUERY=<adql query>
|
||||
FORMAT=json|votable|csv|tsv
|
||||
MAXREC=<max rows>
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `request` | string | `doQuery` |
|
||||
| `lang` | string | `adql` |
|
||||
| `format` | string | `json`, `votable`, `csv`, `tsv`. |
|
||||
| `query` | string | **Required.** ADQL query. |
|
||||
| `REQUEST` | string | `doQuery` |
|
||||
| `LANG` | string | `ADQL` |
|
||||
| `FORMAT` | string | `json`, `votable`, `csv`, `tsv`. |
|
||||
| `QUERY` | string | **Required.** ADQL query. |
|
||||
| `MAXREC` | int | Max rows returned. Always set to avoid downloading millions of rows. |
|
||||
|
||||
**Example — look up object by name:**
|
||||
```
|
||||
@@ -64,11 +75,6 @@ GET /simbad/sim-id?Ident={name}&output.format=votable
|
||||
| `Ident` | string | **Required.** Object name (e.g., `M31`, `Sirius`, `NGC 1275`). |
|
||||
| `output.format` | string | `votable`, `html`. |
|
||||
|
||||
**Example:**
|
||||
```
|
||||
https://simbad.cds.unistra.fr/simbad/sim-id?Ident=M31&output.format=votable
|
||||
```
|
||||
|
||||
### 3. Coordinate Query
|
||||
|
||||
```
|
||||
@@ -82,36 +88,287 @@ GET /simbad/sim-coo?Coord={coords}&Radius={radius}&Radius.unit={unit}&output.for
|
||||
| `Radius.unit` | string | `arcmin`, `arcsec`, `deg`. Default: `arcmin`. |
|
||||
| `output.format`| string | `votable`, `html`. |
|
||||
|
||||
**Example:**
|
||||
```
|
||||
https://simbad.cds.unistra.fr/simbad/sim-coo?Coord=10.684+%2B41.269&Radius=5&Radius.unit=arcmin&output.format=votable
|
||||
```
|
||||
|
||||
### 4. Script Interface (for multi-command queries)
|
||||
|
||||
```
|
||||
POST /simbad/sim-script
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
script=format+object+"%MAIN_ID+|+%RA+|+%DEC+|+%OTYPE"\nquery+id+M31
|
||||
|
||||
Body: script=<script text>
|
||||
```
|
||||
|
||||
A script consists of configuration lines followed by query commands:
|
||||
|
||||
```
|
||||
output console=off script=off
|
||||
format object "<format string>"
|
||||
query id <object name>
|
||||
```
|
||||
|
||||
**Query commands:**
|
||||
- `query id <name>` — lookup by name (e.g., `query id M31`)
|
||||
- `query coo <ra> <dec> radius=<value><unit>` — cone search (units: `d`=deg, `m`=arcmin, `s`=arcsec)
|
||||
- `query id wildcard <pattern>` — wildcard search (e.g., `query id wildcard NGC 10*`)
|
||||
- `query sample <criteria>` — criteria search (e.g., `query sample otype='Star' & Vmag < 5.0`)
|
||||
|
||||
**Multi-object queries** — include multiple `query id` lines in a single script:
|
||||
```
|
||||
output console=off script=off
|
||||
format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE"
|
||||
query id M31
|
||||
query id M42
|
||||
query id M101
|
||||
```
|
||||
|
||||
## Script Format Codes
|
||||
|
||||
Format codes define which fields appear in script output. Use inside `format object "..."`.
|
||||
|
||||
### Identification
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%IDLIST(1)` | Primary identifier | `M 31` |
|
||||
| `%IDLIST` | All identifiers | `M 31, NGC 224, UGC 454, ...` |
|
||||
| `%MAIN_ID` | Main identifier | `M 31` |
|
||||
|
||||
### Coordinates
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%COO(A D;ICRS)` | RA Dec ICRS (sexagesimal) | `00 42 44.330 +41 16 07.50` |
|
||||
| `%COO(d d;ICRS)` | RA Dec decimal degrees | `10.6847083 +41.2687500` |
|
||||
| `%COO(A D;GAL)` | Galactic coordinates | `121.1743 -21.5733` |
|
||||
|
||||
### Object Properties
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%OTYPE` | Object type (condensed) | `Galaxy` |
|
||||
| `%SP` | Spectral type | `A1V` |
|
||||
| `%MT` | Morphological type | `SA(s)b` |
|
||||
|
||||
### Photometry
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `%FLUXLIST(V)` | V-band magnitude |
|
||||
| `%FLUXLIST(B)` | B-band magnitude |
|
||||
| `%FLUXLIST(U;B;V;R;I)` | Multiple bands |
|
||||
| `%FLUXLIST(J;H;K)` | Near-infrared bands |
|
||||
|
||||
### Kinematics
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `%PM` | Proper motion (mas/yr) |
|
||||
| `%PLX` | Parallax (mas) |
|
||||
| `%RV` | Radial velocity (km/s) |
|
||||
|
||||
### Predefined Format Levels
|
||||
|
||||
```
|
||||
Basic: "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE"
|
||||
Detailed: "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE | %SP | %FLUXLIST(V)"
|
||||
Full: "%IDLIST(1) | %COO(A D;ICRS;J2000) | %OTYPE | %SP | %FLUXLIST(U;B;V;R;I;J;H;K) | %PM | %PLX | %RV | %MT"
|
||||
```
|
||||
|
||||
## Key TAP Tables
|
||||
|
||||
| Table | Description |
|
||||
|-----------------|-------------|
|
||||
| `basic` | Core data: coordinates, main_id, object type. |
|
||||
| `ident` | All known identifiers for objects. |
|
||||
| `flux` | Flux/magnitude measurements. |
|
||||
| `mesVelocities` | Radial velocity measurements. |
|
||||
| `mesDistance` | Distance measurements. |
|
||||
| `otypedef` | Object type definitions/labels. |
|
||||
| `allfluxes` | All flux data joined. |
|
||||
### `basic` — Main Object Table
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oid` | BIGINT | Internal object identifier (primary key) |
|
||||
| `main_id` | VARCHAR | Primary object identifier |
|
||||
| `ra` | DOUBLE | Right Ascension in degrees (ICRS) |
|
||||
| `dec` | DOUBLE | Declination in degrees (ICRS) |
|
||||
| `otype` | VARCHAR | Condensed object type code |
|
||||
| `sp_type` | VARCHAR | Spectral type |
|
||||
| `plx_value` | DOUBLE | Parallax in milliarcseconds |
|
||||
| `plx_err` | DOUBLE | Parallax error |
|
||||
| `pmra` | DOUBLE | Proper motion in RA (mas/yr) |
|
||||
| `pmdec` | DOUBLE | Proper motion in Dec (mas/yr) |
|
||||
| `rvz_radvel` | DOUBLE | Radial velocity (km/s) |
|
||||
| `rvz_err` | DOUBLE | Radial velocity error |
|
||||
| `galdim_majaxis` | DOUBLE | Galaxy major axis (arcmin) |
|
||||
| `galdim_minaxis` | DOUBLE | Galaxy minor axis (arcmin) |
|
||||
| `galdim_angle` | DOUBLE | Galaxy position angle (degrees) |
|
||||
|
||||
### `ident` — Identifier Table
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `id` | VARCHAR | Identifier string |
|
||||
|
||||
### `flux` — Photometric Measurements
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `filter` | VARCHAR | Filter name (U, B, V, R, I, J, H, K, u, g, r, i, z, G, etc.) |
|
||||
| `flux` | DOUBLE | Magnitude value |
|
||||
| `flux_err` | DOUBLE | Magnitude error |
|
||||
| `bibcode` | VARCHAR | Source reference bibcode |
|
||||
|
||||
### `mesDistance` — Distance Measurements
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `dist` | DOUBLE | Distance value |
|
||||
| `unit` | VARCHAR | Distance unit (pc, kpc, Mpc) |
|
||||
| `minus_err` | DOUBLE | Lower error |
|
||||
| `plus_err` | DOUBLE | Upper error |
|
||||
| `method` | VARCHAR | Measurement method |
|
||||
| `bibcode` | VARCHAR | Source reference |
|
||||
|
||||
### `has_ref` / `ref` — Bibliographic References
|
||||
|
||||
`has_ref` links objects to references (`oidref` → `basic.oid`, `oidbibref` → `ref.oidbib`).
|
||||
|
||||
| `ref` Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidbib` | BIGINT | Bibliography object ID |
|
||||
| `bibcode` | VARCHAR | ADS bibcode |
|
||||
| `title` | VARCHAR | Paper title |
|
||||
| `journal` | VARCHAR | Journal name |
|
||||
| `year` | INTEGER | Publication year |
|
||||
|
||||
### `otypedef` — Object Type Definitions
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `otype` | VARCHAR | Object type code |
|
||||
| `description` | VARCHAR | Human-readable description |
|
||||
|
||||
## Common Object Types (otype)
|
||||
|
||||
`Star`, `Galaxy`, `Pulsar`, `QSO`, `Nebula`, `GlobCluster`, `RadioSource`, `X-raySource`, `SNRemnant`
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `Star` | Star |
|
||||
| `HII` | HII region |
|
||||
| `PN` | Planetary nebula |
|
||||
| `SNR` | Supernova remnant |
|
||||
| `Galaxy` | Galaxy |
|
||||
| `AGN` | Active galactic nucleus |
|
||||
| `QSO` | Quasar |
|
||||
| `GClstr` | Galaxy cluster |
|
||||
| `GlobCl` | Globular cluster |
|
||||
| `OpCl` | Open cluster |
|
||||
| `Pulsar` | Pulsar |
|
||||
| `WD*` | White dwarf |
|
||||
| `Planet` | Extra-solar planet |
|
||||
| `**` | Double/multiple star |
|
||||
| `V*` | Variable star |
|
||||
| `X` | X-ray source |
|
||||
|
||||
## Response Format (TAP JSON)
|
||||
Query `SELECT * FROM otypedef ORDER BY otype` for the full list.
|
||||
|
||||
## ADQL Query Patterns
|
||||
|
||||
### Spatial Queries
|
||||
|
||||
**Cone search** — objects within a radius of a point:
|
||||
```sql
|
||||
SELECT main_id, ra, dec, otype
|
||||
FROM basic
|
||||
WHERE CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 83.633, 22.014, 0.5)) = 1
|
||||
```
|
||||
Parameters: `CIRCLE('ICRS', center_ra_deg, center_dec_deg, radius_deg)`
|
||||
|
||||
**Box search:**
|
||||
```sql
|
||||
SELECT main_id, ra, dec, otype
|
||||
FROM basic
|
||||
WHERE CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', 180.0, 0.0, 10.0, 5.0)) = 1
|
||||
```
|
||||
Parameters: `BOX('ICRS', center_ra, center_dec, width_deg, height_deg)`
|
||||
|
||||
**Polygon search:**
|
||||
```sql
|
||||
SELECT main_id, ra, dec
|
||||
FROM basic
|
||||
WHERE CONTAINS(POINT('ICRS', ra, dec), POLYGON('ICRS', 10.0, 40.0, 12.0, 40.0, 12.0, 42.0, 10.0, 42.0)) = 1
|
||||
```
|
||||
|
||||
**Angular distance:**
|
||||
```sql
|
||||
SELECT main_id, ra, dec,
|
||||
DISTANCE(POINT('ICRS', ra, dec), POINT('ICRS', 10.68458, 41.26917)) AS dist_deg
|
||||
FROM basic
|
||||
WHERE CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 10.68458, 41.26917, 0.1)) = 1
|
||||
ORDER BY dist_deg ASC
|
||||
```
|
||||
|
||||
### JOINs
|
||||
|
||||
```sql
|
||||
-- V-band magnitudes
|
||||
SELECT b.main_id, b.ra, b.dec, f.flux AS Vmag
|
||||
FROM basic AS b
|
||||
JOIN flux AS f ON b.oid = f.oidref
|
||||
WHERE f.filter = 'V' AND f.flux < 6.0
|
||||
ORDER BY f.flux ASC
|
||||
|
||||
-- All identifiers for an object
|
||||
SELECT b.main_id, i.id
|
||||
FROM basic AS b
|
||||
JOIN ident AS i ON b.oid = i.oidref
|
||||
WHERE b.main_id = 'M 31'
|
||||
|
||||
-- Distance measurements
|
||||
SELECT b.main_id, d.dist, d.unit, d.method
|
||||
FROM basic AS b
|
||||
JOIN mesDistance AS d ON b.oid = d.oidref
|
||||
WHERE b.main_id = 'M 31'
|
||||
```
|
||||
|
||||
### Cross-Matching Identifiers Between Catalogs
|
||||
|
||||
```sql
|
||||
SELECT b.main_id, i1.id AS hipparcos_id, i2.id AS gaia_id
|
||||
FROM basic AS b
|
||||
JOIN ident AS i1 ON b.oid = i1.oidref AND i1.id LIKE 'HIP %'
|
||||
JOIN ident AS i2 ON b.oid = i2.oidref AND i2.id LIKE 'Gaia DR3%'
|
||||
WHERE b.otype = 'Star' AND b.plx_value > 50
|
||||
```
|
||||
|
||||
### Bibliography for Objects in a Region
|
||||
|
||||
```sql
|
||||
SELECT b.main_id, r.bibcode, r.title, r.year
|
||||
FROM basic AS b
|
||||
JOIN has_ref AS hr ON b.oid = hr.oidref
|
||||
JOIN ref AS r ON hr.oidbibref = r.oidbib
|
||||
WHERE CONTAINS(POINT('ICRS', b.ra, b.dec), CIRCLE('ICRS', 83.633, -5.375, 0.5)) = 1
|
||||
AND r.year >= 2020
|
||||
ORDER BY r.year DESC
|
||||
```
|
||||
|
||||
### Aggregation
|
||||
|
||||
```sql
|
||||
-- Count objects by type in a region
|
||||
SELECT otype, COUNT(*) AS count
|
||||
FROM basic
|
||||
WHERE CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 266.417, -29.008, 1.0)) = 1
|
||||
GROUP BY otype
|
||||
HAVING COUNT(*) > 5
|
||||
ORDER BY count DESC
|
||||
|
||||
-- Average parallax by spectral class
|
||||
SELECT SUBSTRING(sp_type, 1, 1) AS sp_class, AVG(plx_value) AS mean_plx, COUNT(*) AS n
|
||||
FROM basic
|
||||
WHERE sp_type IS NOT NULL AND plx_value IS NOT NULL
|
||||
GROUP BY sp_class
|
||||
ORDER BY sp_class
|
||||
```
|
||||
|
||||
## Response Formats
|
||||
|
||||
### TAP JSON
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -126,6 +383,23 @@ script=format+object+"%MAIN_ID+|+%RA+|+%DEC+|+%OTYPE"\nquery+id+M31
|
||||
}
|
||||
```
|
||||
|
||||
### Script Response
|
||||
|
||||
Plain text with pipe-delimited fields. Lines starting with `::` are metadata — filter them out. Check for `error` or `not found` in data lines to detect failures.
|
||||
|
||||
## Rate Limits
|
||||
|
||||
No formal rate limits documented. SIMBAD requests that automated scripts include reasonable delays between queries. Very large TAP queries may time out; use `TOP N` to limit results or switch to async TAP at `/simbad/sim-tap/async`.
|
||||
No formal rate limits documented. Best practices:
|
||||
- Add `time.sleep(0.5)` between sequential script queries
|
||||
- Use TAP/ADQL for batch queries instead of looping over the script interface
|
||||
- Always set `MAXREC` or use `TOP N` in TAP queries to avoid accidentally downloading millions of rows
|
||||
- Very large TAP queries may time out; tighten `WHERE` clauses or switch to async TAP at `/simbad/sim-tap/async`
|
||||
- Use VOTable format for large TAP results (preserves data types and units better than JSON)
|
||||
|
||||
## Input Sanitization
|
||||
|
||||
When building ADQL or script queries from user-supplied object names, sanitize inputs to prevent injection:
|
||||
- Block newlines, carriage returns, tabs, quotes, semicolons, backslashes, and angle brackets in object names
|
||||
- Escape single quotes in ADQL string literals by doubling them (`'` → `''`)
|
||||
- Limit input length (128 chars is reasonable)
|
||||
- Collapse and trim whitespace
|
||||
|
||||
@@ -1,392 +0,0 @@
|
||||
---
|
||||
name: simbad-database
|
||||
description: Direct access to the SIMBAD astronomical database via REST API and TAP/ADQL. Query astronomical objects by name, coordinates, or identifier patterns. Retrieve object types, coordinates, photometry, proper motions, radial velocities, and bibliographic references for millions of objects beyond the Solar System. Use this when tasks involve identifying astronomical objects, resolving object names to coordinates, cross-matching sources, or performing complex catalog queries with ADQL.
|
||||
license: CDS Terms of Use
|
||||
metadata:
|
||||
skill-author: Steven
|
||||
---
|
||||
|
||||
# SIMBAD Database
|
||||
|
||||
## Overview
|
||||
|
||||
SIMBAD (Set of Identifications, Measurements and Bibliography for Astronomical Data) is a comprehensive astronomical database operated by the Centre de Données astronomiques de Strasbourg (CDS). It contains data on over 17 million astronomical objects beyond the Solar System, including identifications, coordinates, photometry, proper motions, parallaxes, radial velocities, spectral types, and bibliographic references.
|
||||
|
||||
SIMBAD provides two main access methods:
|
||||
1. **Script interface** (`sim-script`) — Simple queries by name, coordinates, or identifier pattern
|
||||
2. **TAP service** (`sim-tap`) — Complex queries using ADQL (Astronomical Data Query Language), a SQL-like language for astronomical databases
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when tasks involve:
|
||||
- Resolving astronomical object names to coordinates (e.g., "Where is M31?")
|
||||
- Looking up object properties: type, spectral type, magnitudes, radial velocity, parallax
|
||||
- Searching for objects near a sky position (cone search)
|
||||
- Finding objects matching an identifier pattern (e.g., all NGC objects starting with "10")
|
||||
- Querying SIMBAD tables with complex criteria using ADQL (object type, magnitude range, coordinate box, etc.)
|
||||
- Cross-referencing object identifiers across catalogs (HD, HIP, NGC, IC, 2MASS, Gaia, etc.)
|
||||
- Retrieving bibliographic references for an astronomical object
|
||||
- Building source lists for observing proposals or catalog studies
|
||||
|
||||
For Python-based coordinate transformations, FITS file handling, or cosmological calculations, use the **astropy** skill instead. SIMBAD complements astropy by providing the catalog data that astropy can then process.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using requests (direct HTTP)
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Query an object by name
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-script",
|
||||
data={"script": '\n'.join([
|
||||
"output console=off script=off",
|
||||
'format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE | %SP | %FLUXLIST(V)"',
|
||||
"query id M31",
|
||||
])}
|
||||
)
|
||||
print(response.text)
|
||||
```
|
||||
|
||||
### Using astroquery (recommended for Python workflows)
|
||||
|
||||
```python
|
||||
from astroquery.simbad import Simbad
|
||||
|
||||
result = Simbad.query_object("M31")
|
||||
print(result)
|
||||
```
|
||||
|
||||
### Using TAP/ADQL for complex queries
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
params = {
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": "SELECT TOP 10 main_id, ra, dec, otype FROM basic WHERE otype = 'Galaxy' AND ra BETWEEN 10 AND 12",
|
||||
"FORMAT": "json",
|
||||
}
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-tap/sync",
|
||||
data=params
|
||||
)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 1. Query by Object Name
|
||||
|
||||
Resolve any astronomical object name to its properties. SIMBAD recognizes catalog designations (M31, NGC 1068, HD 209458), common names (Sirius, Betelgeuse, Andromeda), and survey identifiers (2MASS, Gaia DR3, SDSS).
|
||||
|
||||
**Key operations:**
|
||||
- Get coordinates (ICRS, FK5, Galactic)
|
||||
- Get object type classification
|
||||
- Get spectral type
|
||||
- Get photometric magnitudes (U, B, V, R, I, J, H, K)
|
||||
- Get proper motion, parallax, and radial velocity
|
||||
- Get all known identifiers for an object
|
||||
|
||||
**See:** `references/api_reference.md` for format strings and output customization.
|
||||
|
||||
### 2. Coordinate (Cone) Search
|
||||
|
||||
Find all objects within a given radius of a sky position.
|
||||
|
||||
```python
|
||||
from astroquery.simbad import Simbad
|
||||
from astropy.coordinates import SkyCoord
|
||||
import astropy.units as u
|
||||
|
||||
coord = SkyCoord(ra=10.68458, dec=41.26917, unit="deg", frame="icrs")
|
||||
result = Simbad.query_region(coord, radius=5 * u.arcmin)
|
||||
print(result)
|
||||
```
|
||||
|
||||
Or via the script interface:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
script = '\n'.join([
|
||||
"output console=off script=off",
|
||||
'format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE"',
|
||||
"query coo 10.68458 +41.26917 radius=5m",
|
||||
])
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-script",
|
||||
data={"script": script}
|
||||
)
|
||||
print(response.text)
|
||||
```
|
||||
|
||||
### 3. Identifier Pattern Search
|
||||
|
||||
Search for objects matching a wildcard identifier pattern.
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
script = '\n'.join([
|
||||
"output console=off script=off",
|
||||
'format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE"',
|
||||
"query id wildcard NGC 10*",
|
||||
])
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-script",
|
||||
data={"script": script}
|
||||
)
|
||||
print(response.text)
|
||||
```
|
||||
|
||||
### 4. Advanced ADQL Queries via TAP
|
||||
|
||||
Use ADQL (a SQL dialect for astronomy) to query SIMBAD's relational tables with full filtering, joins, and aggregation.
|
||||
|
||||
**Key tables:**
|
||||
- `basic` — Core object data (main_id, ra, dec, otype, sp_type, plx, rvz_radvel, pmra, pmdec)
|
||||
- `ident` — All identifiers for each object
|
||||
- `flux` — Photometric measurements
|
||||
- `mesDistance` — Distance measurements
|
||||
- `biblio` — Bibliographic references
|
||||
- `otypedef` — Object type definitions and descriptions
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
query = """
|
||||
SELECT TOP 20 main_id, ra, dec, otype, sp_type, plx_value
|
||||
FROM basic
|
||||
WHERE otype = 'Star'
|
||||
AND plx_value > 100
|
||||
ORDER BY plx_value DESC
|
||||
"""
|
||||
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-tap/sync",
|
||||
data={
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": query,
|
||||
"FORMAT": "json",
|
||||
}
|
||||
)
|
||||
data = response.json()
|
||||
for row in data.get("data", []):
|
||||
print(row)
|
||||
```
|
||||
|
||||
**See:** `references/adql_reference.md` for table schemas, joins, and advanced query patterns.
|
||||
|
||||
### 5. Using astroquery.simbad (Recommended Python Interface)
|
||||
|
||||
The `astroquery` package provides a Pythonic interface with automatic result parsing into astropy Tables.
|
||||
|
||||
```python
|
||||
from astroquery.simbad import Simbad
|
||||
|
||||
# Add custom fields to the default query
|
||||
custom_simbad = Simbad()
|
||||
custom_simbad.add_votable_fields('sp', 'flux(V)', 'flux(B)', 'plx', 'rv_value')
|
||||
|
||||
# Query by name
|
||||
result = custom_simbad.query_object("Vega")
|
||||
print(result.colnames)
|
||||
print(result)
|
||||
|
||||
# Query by region
|
||||
from astropy.coordinates import SkyCoord
|
||||
import astropy.units as u
|
||||
|
||||
center = SkyCoord("05h23m34.5s", "-69d45m22s", frame="icrs")
|
||||
result = custom_simbad.query_region(center, radius=10 * u.arcmin)
|
||||
print(f"Found {len(result)} objects")
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# For direct HTTP access (no extra dependencies)
|
||||
uv pip install requests
|
||||
|
||||
# For the recommended astroquery interface
|
||||
uv pip install astroquery astropy
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Resolving an Object Name to Coordinates
|
||||
|
||||
```python
|
||||
from astroquery.simbad import Simbad
|
||||
|
||||
result = Simbad.query_object("Crab Nebula")
|
||||
ra = result['RA'][0]
|
||||
dec = result['DEC'][0]
|
||||
print(f"Crab Nebula: RA={ra}, DEC={dec}")
|
||||
```
|
||||
|
||||
### Getting All Known Identifiers for an Object
|
||||
|
||||
```python
|
||||
from astroquery.simbad import Simbad
|
||||
|
||||
ids = Simbad.query_objectids("M1")
|
||||
for row in ids:
|
||||
print(row['ID'])
|
||||
```
|
||||
|
||||
### Finding Bright Stars Near a Position
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
query = """
|
||||
SELECT main_id, ra, dec, otype, sp_type,
|
||||
FLUX_V as Vmag
|
||||
FROM basic
|
||||
JOIN flux ON basic.oid = flux.oidref AND flux.filter = 'V'
|
||||
WHERE CONTAINS(POINT('ICRS', ra, dec),
|
||||
CIRCLE('ICRS', 83.633, 22.014, 1.0)) = 1
|
||||
AND FLUX_V < 8
|
||||
ORDER BY FLUX_V ASC
|
||||
"""
|
||||
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-tap/sync",
|
||||
data={
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": query,
|
||||
"FORMAT": "json",
|
||||
}
|
||||
)
|
||||
for row in response.json().get("data", []):
|
||||
print(row)
|
||||
```
|
||||
|
||||
### Cross-Matching with External Catalogs
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
query = """
|
||||
SELECT b.main_id, b.ra, b.dec, b.otype, i.id AS gaia_id
|
||||
FROM basic AS b
|
||||
JOIN ident AS i ON b.oid = i.oidref
|
||||
WHERE i.id LIKE 'Gaia DR3%'
|
||||
AND b.otype = 'Planet'
|
||||
ORDER BY b.main_id
|
||||
"""
|
||||
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-tap/sync",
|
||||
data={
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": query,
|
||||
"FORMAT": "json",
|
||||
"MAXREC": 50,
|
||||
}
|
||||
)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### Retrieving Bibliography for an Object
|
||||
|
||||
```python
|
||||
from astroquery.simbad import Simbad
|
||||
|
||||
refs = Simbad.query_bibobj("M31")
|
||||
print(f"Found {len(refs)} bibliographic references for M31")
|
||||
print(refs[:5])
|
||||
```
|
||||
|
||||
## Python Implementation
|
||||
|
||||
For programmatic access, use the provided helper script `scripts/simbad_client.py` which implements:
|
||||
|
||||
- `query_object(name, output_format)` — Query by object name
|
||||
- `query_region(ra, dec, radius, max_results)` — Cone search by coordinates
|
||||
- `query_identifiers(pattern, max_results)` — Wildcard identifier search
|
||||
- `tap_query(adql_query, max_results, fmt)` — Execute ADQL via TAP
|
||||
- `get_all_identifiers(name)` — Get all catalog IDs for an object
|
||||
|
||||
**Alternative Python packages:**
|
||||
- **astroquery**: Full-featured astronomical query package (recommended)
|
||||
- **pyvo**: Generic Virtual Observatory (VO) TAP client
|
||||
|
||||
## Object Type Codes
|
||||
|
||||
SIMBAD uses a hierarchical object type classification. Common codes:
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `Star` | Star |
|
||||
| `HII` | HII region |
|
||||
| `PN` | Planetary nebula |
|
||||
| `SNR` | Supernova remnant |
|
||||
| `Galaxy` | Galaxy |
|
||||
| `AGN` | Active galactic nucleus |
|
||||
| `QSO` | Quasar |
|
||||
| `GClstr` | Galaxy cluster |
|
||||
| `GlobCl` | Globular cluster |
|
||||
| `OpCl` | Open cluster |
|
||||
| `Pulsar` | Pulsar |
|
||||
| `WD*` | White dwarf |
|
||||
| `Planet` | Extra-solar planet |
|
||||
| `**` | Double/multiple star |
|
||||
| `V*` | Variable star |
|
||||
| `X` | X-ray source |
|
||||
|
||||
Use the `otypedef` table via TAP to get the full list:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-tap/sync",
|
||||
data={
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": "SELECT * FROM otypedef ORDER BY otype",
|
||||
"FORMAT": "json",
|
||||
}
|
||||
)
|
||||
for row in response.json().get("data", []):
|
||||
print(row)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer astroquery for Python workflows**: It handles pagination, parsing, and error handling automatically
|
||||
2. **Use TAP/ADQL for complex queries**: The script interface is best for simple lookups; TAP supports joins, aggregation, and spatial queries
|
||||
3. **Respect rate limits**: SIMBAD is a free public service; avoid rapid-fire requests. Add `time.sleep(0.5)` between batch queries
|
||||
4. **Use `MAXREC` in TAP queries**: Always limit results to avoid accidentally downloading millions of rows
|
||||
5. **Check coordinate frames**: SIMBAD defaults to ICRS (J2000); be explicit about frames when cross-matching
|
||||
6. **Use `CONTAINS(POINT, CIRCLE)` for cone searches in ADQL**: This is the standard VO spatial predicate
|
||||
7. **Handle missing data**: Not all objects have all measurements; check for NULL/empty values
|
||||
8. **Verify object types**: Use the condensed object type (`otype`) for filtering, not the long description
|
||||
9. **Cache results locally**: Store frequently accessed object data to minimize API calls
|
||||
10. **Use VOTable format for large TAP results**: It preserves data types and units better than JSON
|
||||
11. **Validate user input before querying**: When building ADQL or script queries from user-supplied object names, sanitize input to prevent injection. Use the provided `simbad_client.py` which validates and escapes inputs safely.
|
||||
|
||||
## Resources
|
||||
|
||||
### scripts/
|
||||
`simbad_client.py` — Python client with helper functions for common SIMBAD operations including name queries, coordinate searches, identifier pattern matching, and TAP/ADQL queries.
|
||||
|
||||
### references/
|
||||
- `api_reference.md` — Script interface format strings, output fields, and query syntax
|
||||
- `adql_reference.md` — SIMBAD TAP table schemas, ADQL syntax, joins, spatial queries, and advanced patterns
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **SIMBAD Web Interface**: https://simbad.cds.unistra.fr/simbad/
|
||||
- **SIMBAD TAP Documentation**: https://simbad.cds.unistra.fr/simbad/tap/help/
|
||||
- **astroquery SIMBAD Module**: https://astroquery.readthedocs.io/en/latest/simbad/simbad.html
|
||||
- **ADQL Specification**: https://www.ivoa.net/documents/ADQL/
|
||||
- **CDS Portal**: https://cds.unistra.fr/
|
||||
@@ -1,373 +0,0 @@
|
||||
# SIMBAD TAP/ADQL Reference
|
||||
|
||||
SIMBAD provides a TAP (Table Access Protocol) service that accepts ADQL (Astronomical Data Query Language) queries. ADQL is a SQL-like language defined by the International Virtual Observatory Alliance (IVOA) with extensions for astronomical spatial operations.
|
||||
|
||||
## TAP Endpoint
|
||||
|
||||
```
|
||||
POST https://simbad.cds.unistra.fr/simbad/sim-tap/sync
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
Parameters:
|
||||
REQUEST=doQuery
|
||||
LANG=ADQL
|
||||
QUERY=<adql query>
|
||||
FORMAT=json|votable|csv|tsv
|
||||
MAXREC=<max rows>
|
||||
```
|
||||
|
||||
## Core Tables
|
||||
|
||||
### `basic` — Main Object Table
|
||||
|
||||
The primary table containing one row per astronomical object.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oid` | BIGINT | Internal object identifier (primary key) |
|
||||
| `main_id` | VARCHAR | Primary object identifier |
|
||||
| `ra` | DOUBLE | Right Ascension in degrees (ICRS) |
|
||||
| `dec` | DOUBLE | Declination in degrees (ICRS) |
|
||||
| `otype` | VARCHAR | Condensed object type code |
|
||||
| `sp_type` | VARCHAR | Spectral type |
|
||||
| `plx_value` | DOUBLE | Parallax in milliarcseconds |
|
||||
| `plx_err` | DOUBLE | Parallax error |
|
||||
| `pmra` | DOUBLE | Proper motion in RA (mas/yr) |
|
||||
| `pmdec` | DOUBLE | Proper motion in Dec (mas/yr) |
|
||||
| `rvz_radvel` | DOUBLE | Radial velocity (km/s) |
|
||||
| `rvz_err` | DOUBLE | Radial velocity error |
|
||||
| `galdim_majaxis` | DOUBLE | Galaxy major axis (arcmin) |
|
||||
| `galdim_minaxis` | DOUBLE | Galaxy minor axis (arcmin) |
|
||||
| `galdim_angle` | DOUBLE | Galaxy position angle (degrees) |
|
||||
|
||||
### `ident` — Identifier Table
|
||||
|
||||
All known identifiers for each object. An object can have many identifiers across different catalogs.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `id` | VARCHAR | Identifier string |
|
||||
|
||||
### `flux` — Photometric Measurements
|
||||
|
||||
Flux/magnitude measurements in various photometric bands.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `filter` | VARCHAR | Filter name (U, B, V, R, I, J, H, K, u, g, r, i, z, G, etc.) |
|
||||
| `flux` | DOUBLE | Magnitude value |
|
||||
| `flux_err` | DOUBLE | Magnitude error |
|
||||
| `bibcode` | VARCHAR | Source reference bibcode |
|
||||
|
||||
### `mesDistance` — Distance Measurements
|
||||
|
||||
Published distance measurements.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `dist` | DOUBLE | Distance value |
|
||||
| `unit` | VARCHAR | Distance unit (pc, kpc, Mpc) |
|
||||
| `minus_err` | DOUBLE | Lower error |
|
||||
| `plus_err` | DOUBLE | Upper error |
|
||||
| `method` | VARCHAR | Measurement method |
|
||||
| `bibcode` | VARCHAR | Source reference |
|
||||
|
||||
### `has_ref` — Bibliographic References
|
||||
|
||||
Links objects to their bibliographic references.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidref` | BIGINT | Reference to `basic.oid` |
|
||||
| `oidbibref` | BIGINT | Reference to `ref.oidbib` |
|
||||
|
||||
### `ref` — Reference Details
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `oidbib` | BIGINT | Bibliography object ID |
|
||||
| `bibcode` | VARCHAR | ADS bibcode |
|
||||
| `title` | VARCHAR | Paper title |
|
||||
| `journal` | VARCHAR | Journal name |
|
||||
| `year` | INTEGER | Publication year |
|
||||
|
||||
### `otypedef` — Object Type Definitions
|
||||
|
||||
Lookup table for the object type classification hierarchy.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `otype` | VARCHAR | Object type code |
|
||||
| `description` | VARCHAR | Human-readable description |
|
||||
|
||||
## ADQL Syntax
|
||||
|
||||
### Basic SELECT
|
||||
|
||||
```sql
|
||||
SELECT main_id, ra, dec, otype
|
||||
FROM basic
|
||||
WHERE otype = 'Star'
|
||||
LIMIT 10
|
||||
```
|
||||
|
||||
### TOP N (Alternative to LIMIT)
|
||||
|
||||
```sql
|
||||
SELECT TOP 10 main_id, ra, dec
|
||||
FROM basic
|
||||
WHERE otype = 'Galaxy'
|
||||
```
|
||||
|
||||
### WHERE Clauses
|
||||
|
||||
```sql
|
||||
-- Numeric comparisons
|
||||
WHERE plx_value > 50
|
||||
WHERE rvz_radvel BETWEEN -100 AND 100
|
||||
|
||||
-- String matching
|
||||
WHERE main_id = 'M 31'
|
||||
WHERE sp_type LIKE 'O%'
|
||||
WHERE otype IN ('Star', 'WD*', 'Pulsar')
|
||||
|
||||
-- NULL handling
|
||||
WHERE plx_value IS NOT NULL
|
||||
WHERE sp_type IS NOT NULL
|
||||
```
|
||||
|
||||
### JOINs
|
||||
|
||||
```sql
|
||||
-- Get V-band magnitudes with object data
|
||||
SELECT b.main_id, b.ra, b.dec, b.otype, f.flux AS Vmag
|
||||
FROM basic AS b
|
||||
JOIN flux AS f ON b.oid = f.oidref
|
||||
WHERE f.filter = 'V'
|
||||
AND f.flux < 6.0
|
||||
ORDER BY f.flux ASC
|
||||
|
||||
-- Get all identifiers for an object
|
||||
SELECT b.main_id, i.id
|
||||
FROM basic AS b
|
||||
JOIN ident AS i ON b.oid = i.oidref
|
||||
WHERE b.main_id = 'M 31'
|
||||
|
||||
-- Get distance measurements
|
||||
SELECT b.main_id, d.dist, d.unit, d.method
|
||||
FROM basic AS b
|
||||
JOIN mesDistance AS d ON b.oid = d.oidref
|
||||
WHERE b.main_id = 'M 31'
|
||||
```
|
||||
|
||||
### Spatial Queries
|
||||
|
||||
ADQL supports geometric predicates from the IVOA standard:
|
||||
|
||||
#### Cone Search (CONTAINS + CIRCLE)
|
||||
|
||||
Find objects within a radius of a point:
|
||||
|
||||
```sql
|
||||
SELECT main_id, ra, dec, otype
|
||||
FROM basic
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', ra, dec),
|
||||
CIRCLE('ICRS', 83.633, 22.014, 0.5)
|
||||
) = 1
|
||||
```
|
||||
|
||||
Parameters: `CIRCLE('ICRS', center_ra_deg, center_dec_deg, radius_deg)`
|
||||
|
||||
#### Box Search (CONTAINS + BOX)
|
||||
|
||||
```sql
|
||||
SELECT main_id, ra, dec, otype
|
||||
FROM basic
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', ra, dec),
|
||||
BOX('ICRS', 180.0, 0.0, 10.0, 5.0)
|
||||
) = 1
|
||||
```
|
||||
|
||||
Parameters: `BOX('ICRS', center_ra, center_dec, width_deg, height_deg)`
|
||||
|
||||
#### Polygon Search
|
||||
|
||||
```sql
|
||||
SELECT main_id, ra, dec
|
||||
FROM basic
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', ra, dec),
|
||||
POLYGON('ICRS', 10.0, 40.0, 12.0, 40.0, 12.0, 42.0, 10.0, 42.0)
|
||||
) = 1
|
||||
```
|
||||
|
||||
#### Distance Between Points
|
||||
|
||||
```sql
|
||||
SELECT main_id, ra, dec,
|
||||
DISTANCE(POINT('ICRS', ra, dec),
|
||||
POINT('ICRS', 10.68458, 41.26917)) AS dist_deg
|
||||
FROM basic
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', ra, dec),
|
||||
CIRCLE('ICRS', 10.68458, 41.26917, 0.1)
|
||||
) = 1
|
||||
ORDER BY dist_deg ASC
|
||||
```
|
||||
|
||||
### Aggregation
|
||||
|
||||
```sql
|
||||
-- Count objects by type
|
||||
SELECT otype, COUNT(*) AS n
|
||||
FROM basic
|
||||
GROUP BY otype
|
||||
ORDER BY n DESC
|
||||
|
||||
-- Average parallax by spectral class
|
||||
SELECT SUBSTRING(sp_type, 1, 1) AS sp_class,
|
||||
AVG(plx_value) AS mean_plx,
|
||||
COUNT(*) AS n
|
||||
FROM basic
|
||||
WHERE sp_type IS NOT NULL
|
||||
AND plx_value IS NOT NULL
|
||||
GROUP BY sp_class
|
||||
ORDER BY sp_class
|
||||
```
|
||||
|
||||
## Common Query Patterns
|
||||
|
||||
### Find Nearby Stars with Full Data
|
||||
|
||||
```sql
|
||||
SELECT b.main_id, b.ra, b.dec, b.sp_type,
|
||||
b.plx_value, b.pmra, b.pmdec, b.rvz_radvel,
|
||||
f.flux AS Vmag
|
||||
FROM basic AS b
|
||||
LEFT JOIN flux AS f ON b.oid = f.oidref AND f.filter = 'V'
|
||||
WHERE b.otype = 'Star'
|
||||
AND b.plx_value > 100
|
||||
ORDER BY b.plx_value DESC
|
||||
```
|
||||
|
||||
### Find Galaxies in a Sky Region with Redshifts
|
||||
|
||||
```sql
|
||||
SELECT main_id, ra, dec, otype, rvz_radvel,
|
||||
galdim_majaxis, galdim_minaxis
|
||||
FROM basic
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', ra, dec),
|
||||
CIRCLE('ICRS', 185.0, 12.7, 2.0)
|
||||
) = 1
|
||||
AND otype = 'Galaxy'
|
||||
AND rvz_radvel IS NOT NULL
|
||||
ORDER BY rvz_radvel ASC
|
||||
```
|
||||
|
||||
### Cross-Match Identifiers Between Catalogs
|
||||
|
||||
```sql
|
||||
SELECT b.main_id,
|
||||
i1.id AS hipparcos_id,
|
||||
i2.id AS gaia_id
|
||||
FROM basic AS b
|
||||
JOIN ident AS i1 ON b.oid = i1.oidref AND i1.id LIKE 'HIP %'
|
||||
JOIN ident AS i2 ON b.oid = i2.oidref AND i2.id LIKE 'Gaia DR3%'
|
||||
WHERE b.otype = 'Star'
|
||||
AND b.plx_value > 50
|
||||
```
|
||||
|
||||
### Get Bibliography for Objects in a Region
|
||||
|
||||
```sql
|
||||
SELECT b.main_id, r.bibcode, r.title, r.year
|
||||
FROM basic AS b
|
||||
JOIN has_ref AS hr ON b.oid = hr.oidref
|
||||
JOIN ref AS r ON hr.oidbibref = r.oidbib
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', b.ra, b.dec),
|
||||
CIRCLE('ICRS', 83.633, -5.375, 0.5)
|
||||
) = 1
|
||||
AND r.year >= 2020
|
||||
ORDER BY r.year DESC
|
||||
```
|
||||
|
||||
### Object Type Statistics in a Region
|
||||
|
||||
```sql
|
||||
SELECT otype, COUNT(*) AS count
|
||||
FROM basic
|
||||
WHERE CONTAINS(
|
||||
POINT('ICRS', ra, dec),
|
||||
CIRCLE('ICRS', 266.417, -29.008, 1.0)
|
||||
) = 1
|
||||
GROUP BY otype
|
||||
HAVING COUNT(*) > 5
|
||||
ORDER BY count DESC
|
||||
```
|
||||
|
||||
## Python Example: Full TAP Workflow
|
||||
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
def simbad_tap_query(adql, max_results=100, fmt="json"):
|
||||
"""Execute an ADQL query on SIMBAD via TAP."""
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-tap/sync",
|
||||
data={
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": adql,
|
||||
"FORMAT": fmt,
|
||||
"MAXREC": max_results,
|
||||
},
|
||||
timeout=60,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
if fmt == "json":
|
||||
return response.json()
|
||||
return response.text
|
||||
|
||||
|
||||
# Example: nearest stars with complete kinematics
|
||||
results = simbad_tap_query("""
|
||||
SELECT TOP 50 main_id, ra, dec, sp_type,
|
||||
plx_value, pmra, pmdec, rvz_radvel
|
||||
FROM basic
|
||||
WHERE plx_value > 200
|
||||
AND pmra IS NOT NULL
|
||||
AND rvz_radvel IS NOT NULL
|
||||
ORDER BY plx_value DESC
|
||||
""")
|
||||
|
||||
metadata = results.get("metadata", [])
|
||||
col_names = [m["name"] for m in metadata]
|
||||
|
||||
print(" | ".join(col_names))
|
||||
print("-" * 80)
|
||||
for row in results.get("data", []):
|
||||
print(" | ".join(str(v) for v in row))
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Synchronous TAP only**: SIMBAD supports `sync` mode (results returned directly). For very large queries, consider async TAP or use `MAXREC` to paginate.
|
||||
- **No upload tables**: SIMBAD TAP does not support uploading user tables for cross-matching. Use astroquery or CDS XMatch service for that.
|
||||
- **Coordinate precision**: SIMBAD coordinates are compiled from heterogeneous sources; check the `coo_qual` field for quality indicators.
|
||||
- **Timeout**: Queries exceeding ~60 seconds may time out. Add `TOP` or tighten `WHERE` clauses for complex queries.
|
||||
|
||||
## Resources
|
||||
|
||||
- SIMBAD TAP Help: https://simbad.cds.unistra.fr/simbad/tap/help/
|
||||
- ADQL 2.1 Specification: https://www.ivoa.net/documents/ADQL/20231215/
|
||||
- SIMBAD Table Descriptions: https://simbad.cds.unistra.fr/simbad/tap/taptable.htx
|
||||
- TAP Protocol: https://www.ivoa.net/documents/TAP/
|
||||
@@ -1,231 +0,0 @@
|
||||
# SIMBAD Script Interface API Reference
|
||||
|
||||
The SIMBAD script interface (`sim-script`) accepts plain-text scripts via HTTP POST and returns formatted results. It is the simplest way to query SIMBAD for individual objects or small batches.
|
||||
|
||||
## Endpoint
|
||||
|
||||
```
|
||||
POST https://simbad.cds.unistra.fr/simbad/sim-script
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
Body: script=<script text>
|
||||
```
|
||||
|
||||
## Script Structure
|
||||
|
||||
A SIMBAD script consists of configuration lines followed by one or more query commands:
|
||||
|
||||
```
|
||||
output console=off script=off
|
||||
format object "<format string>"
|
||||
query id <object name>
|
||||
```
|
||||
|
||||
- `output console=off script=off` — Suppresses metadata/header lines
|
||||
- `format object "<format string>"` — Defines the output columns using format codes
|
||||
- `query ...` — The actual query command
|
||||
|
||||
## Query Commands
|
||||
|
||||
### Query by Object Name
|
||||
|
||||
```
|
||||
query id <object_name>
|
||||
```
|
||||
|
||||
Examples:
|
||||
```
|
||||
query id M31
|
||||
query id Sirius
|
||||
query id NGC 1068
|
||||
query id HD 209458
|
||||
query id Gaia DR3 4111834567779557376
|
||||
```
|
||||
|
||||
### Query by Coordinates (Cone Search)
|
||||
|
||||
```
|
||||
query coo <ra> <dec> radius=<value><unit>
|
||||
```
|
||||
|
||||
Coordinate formats:
|
||||
- Decimal degrees: `query coo 10.68458 +41.26917 radius=5m`
|
||||
- Sexagesimal: `query coo 00:42:44.330 +41:16:07.50 radius=5m`
|
||||
|
||||
Radius units:
|
||||
- `d` = degrees
|
||||
- `m` = arcminutes
|
||||
- `s` = arcseconds
|
||||
|
||||
### Query by Identifier Pattern
|
||||
|
||||
```
|
||||
query id wildcard <pattern>
|
||||
```
|
||||
|
||||
The `*` wildcard matches any sequence of characters:
|
||||
```
|
||||
query id wildcard NGC 10*
|
||||
query id wildcard HD 20945*
|
||||
query id wildcard 2MASS J*
|
||||
```
|
||||
|
||||
### Query by Criteria
|
||||
|
||||
```
|
||||
query sample <criteria>
|
||||
```
|
||||
|
||||
Examples:
|
||||
```
|
||||
query sample otype='Star' & Vmag < 5.0
|
||||
query sample region(circle, 10.68 +41.27, 10m) & otype='Galaxy'
|
||||
```
|
||||
|
||||
## Format Codes
|
||||
|
||||
Format codes define which data fields appear in the output. Use them inside double quotes in the `format object` directive.
|
||||
|
||||
### Basic Identification
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%IDLIST(1)` | Primary identifier | `M 31` |
|
||||
| `%IDLIST` | All identifiers | `M 31, NGC 224, UGC 454, ...` |
|
||||
| `%MAIN_ID` | Main identifier | `M 31` |
|
||||
|
||||
### Coordinates
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%COO(A D;ICRS)` | RA Dec in ICRS (sexagesimal) | `00 42 44.330 +41 16 07.50` |
|
||||
| `%COO(A D;ICRS;J2000)` | RA Dec with epoch | `00 42 44.330 +41 16 07.50` |
|
||||
| `%COO(d d;ICRS)` | RA Dec in decimal degrees | `10.6847083 +41.2687500` |
|
||||
| `%COO(A D;GAL)` | Galactic coordinates | `121.1743 -21.5733` |
|
||||
|
||||
### Object Properties
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%OTYPE` | Object type (condensed) | `Galaxy` |
|
||||
| `%OTYPE(V)` | Object type (verbose) | `Galaxy` |
|
||||
| `%SP` | Spectral type | `A1V` |
|
||||
| `%MT` | Morphological type | `SA(s)b` |
|
||||
|
||||
### Photometry
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%FLUXLIST(V)` | V-band magnitude | `3.44` |
|
||||
| `%FLUXLIST(B)` | B-band magnitude | `4.36` |
|
||||
| `%FLUXLIST(U;B;V;R;I)` | Multiple bands | `... \| ... \| ...` |
|
||||
| `%FLUXLIST(J;H;K)` | Near-infrared bands | `... \| ... \| ...` |
|
||||
|
||||
### Kinematics
|
||||
|
||||
| Code | Description | Example Output |
|
||||
|------|-------------|----------------|
|
||||
| `%PM` | Proper motion (mas/yr) | `+5.59 -1.78` |
|
||||
| `%PLX` | Parallax (mas) | `130.23` |
|
||||
| `%RV` | Radial velocity (km/s) | `-300` |
|
||||
|
||||
### Predefined Format Levels
|
||||
|
||||
**Basic:**
|
||||
```
|
||||
format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE"
|
||||
```
|
||||
|
||||
**Detailed:**
|
||||
```
|
||||
format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE | %SP | %FLUXLIST(V)"
|
||||
```
|
||||
|
||||
**Full:**
|
||||
```
|
||||
format object "%IDLIST(1) | %COO(A D;ICRS;J2000) | %OTYPE | %SP | %FLUXLIST(U;B;V;R;I;J;H;K) | %PM | %PLX | %RV | %MT"
|
||||
```
|
||||
|
||||
## Parsing the Response
|
||||
|
||||
The response is plain text. Lines starting with `::` are metadata and should be filtered out. Data lines use the separator specified in your format string (typically `|`).
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
def query_simbad_object(name, output_format="basic"):
|
||||
format_strings = {
|
||||
"basic": "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE",
|
||||
"detailed": "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE | %SP | %FLUXLIST(V)",
|
||||
"full": "%IDLIST(1) | %COO(A D;ICRS;J2000) | %OTYPE | %SP | %FLUXLIST(U;B;V;R;I;J;H;K) | %PM | %PLX | %RV | %MT",
|
||||
}
|
||||
|
||||
fmt = format_strings.get(output_format, format_strings["basic"])
|
||||
|
||||
script = '\n'.join([
|
||||
"output console=off script=off",
|
||||
f'format object "{fmt}"',
|
||||
f"query id {name}",
|
||||
])
|
||||
|
||||
response = requests.post(
|
||||
"https://simbad.cds.unistra.fr/simbad/sim-script",
|
||||
data={"script": script},
|
||||
timeout=30,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
lines = [
|
||||
line.strip()
|
||||
for line in response.text.strip().split("\n")
|
||||
if line.strip() and not line.startswith("::")
|
||||
]
|
||||
|
||||
results = []
|
||||
for line in lines:
|
||||
parts = [p.strip() for p in line.split("|")]
|
||||
if len(parts) >= 3:
|
||||
results.append({
|
||||
"main_id": parts[0],
|
||||
"coordinates": parts[1],
|
||||
"object_type": parts[2],
|
||||
**({} if len(parts) <= 3 else {"extra": parts[3:]}),
|
||||
})
|
||||
|
||||
return results
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
SIMBAD returns errors as plain text containing keywords like `error` or `not found`. Always check the response body for these patterns:
|
||||
|
||||
```python
|
||||
text = response.text.strip()
|
||||
data_lines = [l for l in text.split("\n") if l and not l.startswith("::")]
|
||||
|
||||
if any("error" in l.lower() or "not found" in l.lower() for l in data_lines):
|
||||
print("Object not found or query error")
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
SIMBAD is a public service. While there are no strict published rate limits, best practices include:
|
||||
|
||||
- Add `time.sleep(0.5)` between sequential queries
|
||||
- Use TAP/ADQL for batch queries instead of looping over the script interface
|
||||
- Cache results for repeated lookups
|
||||
- Use a `timeout=30` on requests
|
||||
|
||||
## Multiple Object Queries
|
||||
|
||||
You can query multiple objects in a single script:
|
||||
|
||||
```
|
||||
output console=off script=off
|
||||
format object "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE"
|
||||
query id M31
|
||||
query id M42
|
||||
query id M101
|
||||
```
|
||||
|
||||
Each query produces its own output block separated by SIMBAD metadata lines.
|
||||
@@ -1,367 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SIMBAD Database Client
|
||||
|
||||
A Python client for querying the SIMBAD astronomical database via both the
|
||||
script interface and the TAP/ADQL service.
|
||||
|
||||
Usage examples:
|
||||
# Query an object by name
|
||||
results = query_object("M31", output_format="detailed")
|
||||
|
||||
# Cone search around coordinates
|
||||
results = query_region(10.68458, 41.26917, radius=5.0, max_results=20)
|
||||
|
||||
# Search by identifier pattern
|
||||
results = query_identifiers("NGC 10*", max_results=10)
|
||||
|
||||
# Run an ADQL query via TAP
|
||||
data = tap_query("SELECT TOP 10 main_id, ra, dec FROM basic WHERE otype='Galaxy'")
|
||||
|
||||
# Get all identifiers for an object
|
||||
ids = get_all_identifiers("M1")
|
||||
"""
|
||||
|
||||
import requests
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from typing import List, Dict, Optional, Any
|
||||
|
||||
|
||||
SCRIPT_URL = "https://simbad.cds.unistra.fr/simbad/sim-script"
|
||||
TAP_URL = "https://simbad.cds.unistra.fr/simbad/sim-tap/sync"
|
||||
|
||||
# Characters that could enable script/SQL injection - must not appear in user input
|
||||
_FORBIDDEN_IN_NAME = frozenset("\n\r\t'\"\\;<>")
|
||||
|
||||
|
||||
def _sanitize_object_name(name: str) -> str:
|
||||
"""Validate and sanitize object name for script interface (no injection)."""
|
||||
if not name or not isinstance(name, str):
|
||||
raise ValueError("Object name must be a non-empty string")
|
||||
name = " ".join(name.split()) # collapse whitespace
|
||||
if len(name) > 128:
|
||||
raise ValueError("Object name too long")
|
||||
if any(c in _FORBIDDEN_IN_NAME for c in name):
|
||||
raise ValueError("Object name contains disallowed characters")
|
||||
return name.strip()
|
||||
|
||||
|
||||
def _sanitize_adql_string(s: str) -> str:
|
||||
"""Escape single quotes for safe use in ADQL string literals."""
|
||||
if not s or not isinstance(s, str):
|
||||
raise ValueError("Identifier must be a non-empty string")
|
||||
if len(s) > 128:
|
||||
raise ValueError("Identifier too long")
|
||||
# Block newlines, semicolons, backslashes (injection vectors)
|
||||
bad = frozenset("\n\r\t\\;<>\"")
|
||||
if any(c in bad for c in s):
|
||||
raise ValueError("Identifier contains disallowed characters")
|
||||
return s.replace("'", "''") # ADQL string literal escape
|
||||
|
||||
|
||||
FORMAT_STRINGS = {
|
||||
"basic": "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE",
|
||||
"detailed": "%IDLIST(1) | %COO(A D;ICRS) | %OTYPE | %SP | %FLUXLIST(V)",
|
||||
"full": (
|
||||
"%IDLIST(1) | %COO(A D;ICRS;J2000) | %OTYPE | %SP "
|
||||
"| %FLUXLIST(U;B;V;R;I;J;H;K) | %PM | %PLX | %RV | %MT"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _execute_script(script: str, timeout: int = 30) -> str:
|
||||
"""Send a script to the SIMBAD sim-script endpoint and return raw text."""
|
||||
response = requests.post(SCRIPT_URL, data={"script": script}, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
return response.text.strip()
|
||||
|
||||
|
||||
def _parse_script_response(
|
||||
text: str, max_results: Optional[int] = None
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Parse pipe-delimited SIMBAD script output into a list of dicts."""
|
||||
lines = [
|
||||
line.strip()
|
||||
for line in text.split("\n")
|
||||
if line.strip() and not line.startswith("::")
|
||||
]
|
||||
|
||||
if any("error" in l.lower() or "not found" in l.lower() for l in lines):
|
||||
return []
|
||||
|
||||
results = []
|
||||
for line in lines[:max_results] if max_results else lines:
|
||||
parts = [p.strip() for p in line.split("|")]
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
entry = {
|
||||
"main_id": parts[0],
|
||||
"coordinates": parts[1],
|
||||
"object_type": parts[2],
|
||||
}
|
||||
if len(parts) > 3:
|
||||
entry["spectral_type"] = parts[3]
|
||||
if len(parts) > 4:
|
||||
entry["flux"] = parts[4]
|
||||
if len(parts) > 5:
|
||||
entry["additional"] = parts[5:]
|
||||
|
||||
results.append(entry)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def query_object(
|
||||
name: str, output_format: str = "basic"
|
||||
) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Query SIMBAD for an astronomical object by name.
|
||||
|
||||
Args:
|
||||
name: Object name (e.g., "M31", "Sirius", "NGC 1068")
|
||||
output_format: "basic", "detailed", or "full"
|
||||
|
||||
Returns:
|
||||
List of result dicts with keys like main_id, coordinates, object_type, etc.
|
||||
"""
|
||||
safe_name = _sanitize_object_name(name)
|
||||
fmt = FORMAT_STRINGS.get(output_format, FORMAT_STRINGS["basic"])
|
||||
script = "\n".join([
|
||||
"output console=off script=off",
|
||||
f'format object "{fmt}"',
|
||||
f"query id {safe_name}",
|
||||
])
|
||||
text = _execute_script(script)
|
||||
return _parse_script_response(text)
|
||||
|
||||
|
||||
def query_region(
|
||||
ra: float,
|
||||
dec: float,
|
||||
radius: float = 1.0,
|
||||
output_format: str = "basic",
|
||||
max_results: int = 10,
|
||||
) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Cone search around a sky position.
|
||||
|
||||
Args:
|
||||
ra: Right Ascension in degrees (0-360)
|
||||
dec: Declination in degrees (-90 to +90)
|
||||
radius: Search radius in arcminutes
|
||||
output_format: "basic", "detailed", or "full"
|
||||
max_results: Maximum number of results
|
||||
|
||||
Returns:
|
||||
List of result dicts
|
||||
"""
|
||||
fmt = FORMAT_STRINGS.get(output_format, FORMAT_STRINGS["basic"])
|
||||
script = "\n".join([
|
||||
"output console=off script=off",
|
||||
f'format object "{fmt}"',
|
||||
f"query coo {ra} {dec} radius={radius}m",
|
||||
])
|
||||
text = _execute_script(script)
|
||||
return _parse_script_response(text, max_results=max_results)
|
||||
|
||||
|
||||
def query_identifiers(
|
||||
pattern: str,
|
||||
output_format: str = "basic",
|
||||
max_results: int = 10,
|
||||
) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Search for objects matching a wildcard identifier pattern.
|
||||
|
||||
Args:
|
||||
pattern: Identifier with wildcards (e.g., "NGC 10*", "HD 20945*")
|
||||
output_format: "basic", "detailed", or "full"
|
||||
max_results: Maximum number of results
|
||||
|
||||
Returns:
|
||||
List of result dicts
|
||||
"""
|
||||
safe_pattern = _sanitize_object_name(pattern)
|
||||
fmt = FORMAT_STRINGS.get(output_format, FORMAT_STRINGS["basic"])
|
||||
script = "\n".join([
|
||||
"output console=off script=off",
|
||||
f'format object "{fmt}"',
|
||||
f"query id wildcard {safe_pattern}",
|
||||
])
|
||||
text = _execute_script(script)
|
||||
return _parse_script_response(text, max_results=max_results)
|
||||
|
||||
|
||||
def tap_query(
|
||||
adql_query: str,
|
||||
max_results: int = 100,
|
||||
fmt: str = "json",
|
||||
) -> Any:
|
||||
"""
|
||||
Execute an ADQL query on SIMBAD via TAP.
|
||||
|
||||
Args:
|
||||
adql_query: ADQL query string
|
||||
max_results: Maximum number of rows
|
||||
fmt: Output format ("json" or "votable")
|
||||
|
||||
Returns:
|
||||
Parsed JSON dict (if fmt="json") or raw VOTable string
|
||||
"""
|
||||
params = {
|
||||
"REQUEST": "doQuery",
|
||||
"LANG": "ADQL",
|
||||
"QUERY": adql_query,
|
||||
"FORMAT": fmt,
|
||||
"MAXREC": max_results,
|
||||
}
|
||||
response = requests.post(TAP_URL, data=params, timeout=60)
|
||||
response.raise_for_status()
|
||||
|
||||
if fmt == "json":
|
||||
try:
|
||||
return response.json()
|
||||
except ValueError:
|
||||
return response.text
|
||||
return response.text
|
||||
|
||||
|
||||
def get_all_identifiers(name: str) -> List[str]:
|
||||
"""
|
||||
Retrieve all known catalog identifiers for an object.
|
||||
|
||||
Args:
|
||||
name: Object name (e.g., "M31")
|
||||
|
||||
Returns:
|
||||
List of identifier strings
|
||||
"""
|
||||
safe_name = _sanitize_adql_string(name)
|
||||
result = tap_query(
|
||||
f"SELECT i.id FROM ident AS i "
|
||||
f"JOIN basic AS b ON i.oidref = b.oid "
|
||||
f"WHERE b.main_id = '{safe_name}'",
|
||||
max_results=500,
|
||||
fmt="json",
|
||||
)
|
||||
|
||||
if isinstance(result, dict) and "data" in result:
|
||||
return [row[0] for row in result["data"]]
|
||||
return []
|
||||
|
||||
|
||||
def batch_query_objects(
|
||||
names: List[str],
|
||||
output_format: str = "basic",
|
||||
delay: float = 0.5,
|
||||
) -> Dict[str, List[Dict[str, str]]]:
|
||||
"""
|
||||
Query multiple objects with rate-limiting.
|
||||
|
||||
Args:
|
||||
names: List of object names
|
||||
output_format: "basic", "detailed", or "full"
|
||||
delay: Seconds to wait between queries
|
||||
|
||||
Returns:
|
||||
Dict mapping object name to its result list
|
||||
"""
|
||||
results = {}
|
||||
for name in names:
|
||||
results[name] = query_object(name, output_format)
|
||||
if delay > 0:
|
||||
time.sleep(delay)
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
"""Command-line interface for SIMBAD queries."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Query the SIMBAD astronomical database",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s --object "M31" --format detailed
|
||||
%(prog)s --region 10.68 41.27 --radius 5.0 --max 20
|
||||
%(prog)s --pattern "NGC 10*" --max 5
|
||||
%(prog)s --adql "SELECT TOP 10 main_id, ra, dec FROM basic WHERE otype='Galaxy'"
|
||||
%(prog)s --identifiers "M1"
|
||||
""",
|
||||
)
|
||||
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument("--object", "-o", help="Query object by name")
|
||||
group.add_argument(
|
||||
"--region",
|
||||
"-r",
|
||||
nargs=2,
|
||||
type=float,
|
||||
metavar=("RA", "DEC"),
|
||||
help="Cone search at RA DEC (degrees)",
|
||||
)
|
||||
group.add_argument("--pattern", "-p", help="Identifier wildcard pattern")
|
||||
group.add_argument("--adql", "-a", help="ADQL query for TAP")
|
||||
group.add_argument("--identifiers", "-i", help="Get all IDs for an object")
|
||||
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
"-f",
|
||||
default="basic",
|
||||
choices=["basic", "detailed", "full"],
|
||||
help="Output detail level (default: basic)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--radius",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Search radius in arcminutes (default: 1.0)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Max results (default: 10)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.object:
|
||||
results = query_object(args.object, args.format)
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
elif args.region:
|
||||
ra, dec = args.region
|
||||
results = query_region(
|
||||
ra, dec, radius=args.radius,
|
||||
output_format=args.format, max_results=args.max,
|
||||
)
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
elif args.pattern:
|
||||
results = query_identifiers(
|
||||
args.pattern, output_format=args.format, max_results=args.max,
|
||||
)
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
elif args.adql:
|
||||
results = tap_query(args.adql, max_results=args.max)
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
elif args.identifiers:
|
||||
ids = get_all_identifiers(args.identifiers)
|
||||
for ident in ids:
|
||||
print(ident)
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user