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:
StevenDillmann
2026-05-20 22:18:06 -07:00
parent 45fae4a4d9
commit 7d83e4b777
5 changed files with 303 additions and 1392 deletions

View File

@@ -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

View File

@@ -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/

View File

@@ -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/

View File

@@ -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.

View File

@@ -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()