Skip to content

Commit

Permalink
Merge pull request #105 from observerly/feature/catalog/simbad/Perfor…
Browse files Browse the repository at this point in the history
…mRadialSearch

feat: add (s *SIMBADServiceClient) PerformRadialSearch to catalog module in @observerly/skysolve
  • Loading branch information
michealroberts authored Nov 25, 2024
2 parents c5439f4 + f85d06c commit 6c3ebea
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 9 deletions.
18 changes: 9 additions & 9 deletions pkg/catalog/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import (
/*****************************************************************************************************************/

type Source struct {
UID string `json:"uid" gaia:"source_id"` // Source ID (unique)
Designation string `json:"designation" gaia:"designation"` // Source Designation
RA float64 `json:"ra" gaia:"ra"` // Right Ascension (in degrees)
Dec float64 `json:"dec" gaia:"dec"` // Declination (in degrees)
ProperMotionRA float64 `json:"pmra" gaia:"pmra"` // Proper Motion in RA (in mas/yr)
ProperMotionDec float64 `json:"pmdec" gaia:"pmdec"` // Proper Motion in Dec (in mas/yr)
Parallax float64 `json:"parallax" gaia:"parallax"` // Parallax (in mas)
PhotometricGMeanFlux float64 `json:"flux" gaia:"phot_g_mean_mag"` // Mean Flux (in e-/s)
PhotometricGMeanMagnitude float64 `json:"magnitude" gaia:"phot_g_mean_flux"` // Mean Magnitude (in mag)
UID string `json:"uid" gaia:"source_id" simbad:"uid"` // Source ID (unique)
Designation string `json:"designation" gaia:"designation" simbad:"designation"` // Source Designation
RA float64 `json:"ra" gaia:"ra" simbad:"ra"` // Right Ascension (in degrees)
Dec float64 `json:"dec" gaia:"dec" simbad:"dec"` // Declination (in degrees)
ProperMotionRA float64 `json:"pmra" gaia:"pmra" simbad:"pmra"` // Proper Motion in RA (in mas/yr)
ProperMotionDec float64 `json:"pmdec" gaia:"pmdec" simbad:"pmdec"` // Proper Motion in Dec (in mas/yr)
Parallax float64 `json:"parallax" gaia:"parallax" simbad:"parallax"` // Parallax (in mas)
PhotometricGMeanFlux float64 `json:"flux" gaia:"phot_g_mean_flux" simbad:"flux"` // G-band Mean Flux (in e-/s)
PhotometricGMeanMagnitude float64 `json:"magnitude" gaia:"phot_g_mean_mag" simbad:"magnitude"` // G-band Mean Magnitude (in mag)
}

/*****************************************************************************************************************/
Expand Down
136 changes: 136 additions & 0 deletions pkg/catalog/simbad.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ package catalog
/*****************************************************************************************************************/

import (
"fmt"
"net/url"
"strings"
"time"

"github.com/observerly/skysolve/pkg/adql"
"github.com/observerly/skysolve/pkg/astrometry"
)

/*****************************************************************************************************************/
Expand Down Expand Up @@ -60,3 +63,136 @@ func NewSIMBADServiceClient() *SIMBADServiceClient {
}

/*****************************************************************************************************************/

const simbadRecord = "basic.oid AS uid, basic.main_id AS designation, basic.ra AS ra, basic.dec AS dec, basic.pmra AS pmra, basic.pmdec AS pmdec, basic.plx_value AS parallax, flux.flux AS flux, allfluxes.G AS magnitude"

/*****************************************************************************************************************/

func (s *SIMBADServiceClient) PerformRadialSearch(eq astrometry.ICRSEquatorialCoordinate, radius float64, limit int, threshold float64) ([]Source, error) {
// Define the ADQL query template for the SIMBAD TAP service:
// @see https://simbad.u-strasbg.fr/Pages/guide/sim-q.htx
const simbadADQLTemplate = `
SELECT TOP {{.Limit}} {{.Record}}
FROM basic
LEFT JOIN flux
ON basic.oid = flux.oidref
AND flux.filter = 'G'
LEFT JOIN allfluxes
ON basic.oid = allfluxes.oidref
WHERE CONTAINS(
POINT('ICRS', basic.ra, basic.dec),
CIRCLE('ICRS', {{.RA}}, {{.Dec}}, {{.Radius}})
) = 1
ORDER BY magnitude ASC;
`

// Set the query parameters:
s.Query.RA = eq.RA
s.Query.Dec = eq.Dec
s.Query.Radius = radius
s.Query.Limit = limit
s.Query.Threshold = threshold

// Construct the ADQL query from the template:
adqlQuery, err := s.BuildADQLQuery(simbadADQLTemplate, struct {
Record string
RA float64
Dec float64
Radius float64
Limit int
Threshold float64
}{
Record: simbadRecord,
RA: s.Query.RA,
Dec: s.Query.Dec,
Radius: s.Query.Radius,
Limit: s.Query.Limit,
Threshold: s.Query.Threshold,
})
if err != nil {
return nil, err
}

// Execute the query and get the response:
tapResponse, err := s.ExecuteADQLQuery(adqlQuery)

if err != nil {
return nil, err
}

var stars []Source

// Helper functions defined locally within the method to convert an unknown interface{} to float64:
toFloat64 := func(val interface{}) (float64, bool) {
v, ok := val.(float64)
return v, ok
}

for _, record := range tapResponse.Data {
// Create a new Source struct from the record:
// Initialize a new Source struct
var star Source

// Assign UID and Designation using fmt.Sprintf to handle various types:
star.UID = fmt.Sprintf("%v", record[0])
star.Designation = strings.Join(strings.Fields(fmt.Sprintf("%v", record[1])), " ")

// Safely assign RA and assig a default value if not a float64:
if ra, ok := toFloat64(record[2]); ok {
star.RA = ra
} else {
// Handle unexpected type or assign a default value:
star.RA = 0.0
}

// Safely assign Dec and assign a default value if not a float64:
if dec, ok := toFloat64(record[3]); ok {
star.Dec = dec
} else {
// Handle unexpected type or assign a default value:
star.Dec = 0.0
}

// Safely assign ProperMotionRA if not nil:
if record[4] != nil {
if pmra, ok := toFloat64(record[4]); ok {
star.ProperMotionRA = pmra
}
}

// Safely assign ProperMotionDec if not nil:
if record[5] != nil {
if pmdec, ok := toFloat64(record[4]); ok {
star.ProperMotionDec = pmdec
}
}

// Safely assign Parallax if not nil:
if record[6] != nil {
if parallax, ok := toFloat64(record[6]); ok {
star.Parallax = parallax
}
}

// Safely assign PhotometricGMeanFlux if not nil:
if record[7] != nil {
if flux, ok := toFloat64(record[7]); ok {
star.PhotometricGMeanFlux = flux
}
}

// Safely assign PhotometricGMeanMagnitude if not nil:
if record[8] != nil {
if magnitude, ok := toFloat64(record[8]); ok {
star.PhotometricGMeanMagnitude = magnitude
}
}

// Append to the stars slice of Source structs:
stars = append(stars, star)
}

return stars, nil
}

/*****************************************************************************************************************/
51 changes: 51 additions & 0 deletions pkg/catalog/simbad_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*****************************************************************************************************************/

// @author Michael Roberts <[email protected]>
// @package @observerly/skysolve
// @license Copyright © 2021-2025 observerly

/*****************************************************************************************************************/

package catalog

import (
"fmt"
"testing"

"github.com/observerly/skysolve/pkg/astrometry"
)

/*****************************************************************************************************************/

func TestSIMBADQueryExecutedSuccessfully(t *testing.T) {
var q = NewSIMBADServiceClient()

stars, err := q.PerformRadialSearch(astrometry.ICRSEquatorialCoordinate{
RA: 0,
Dec: 0,
}, 2.5, 100, 10)

if err != nil {
t.Errorf("Failed to execute query: %v", err)
}

if len(stars) == 0 {
t.Errorf("No stars returned")
}

for _, star := range stars {
// Test that the star is within the search radius:
if !IsWithinICRSPolarRadius(star.RA, star.Dec, 2.5) {
t.Errorf("Star is not within the search radius")
}

fmt.Println(star.Designation)
}

// The SIMBAD catalog is expected to return a maximum of 100 stars for this query:
if len(stars) > 100 {
t.Errorf("Too many stars returned")
}
}

/*****************************************************************************************************************/

0 comments on commit 6c3ebea

Please sign in to comment.