Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ResolveOrExtractRAFromHeaders to internal/utils module in @observerly/skysolve #181

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions internal/utils/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*****************************************************************************************************************/

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

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

package utils

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

import (
"fmt"
"math"

"github.com/observerly/iris/pkg/fits"
)

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

func ResolveOrExtractRAFromHeaders(value float32, header fits.FITSHeader) (float32, error) {
// First, pick a candidate RA (v):
v := value

// If the candidate RA (v) is NaN, try to get it from the header:
if math.IsNaN(float64(v)) {
ra, exists := header.Floats["RA"]
if !exists {
return float32(math.NaN()), fmt.Errorf("ra header not found in the supplied FITS file")
}
v = ra.Value
}

// Validate the candidate RA (v) is a valid float32:
if math.IsNaN(float64(v)) {
return float32(math.NaN()), fmt.Errorf("ra value needs to be a valid float32")
}

// Validate the candidate RA (v) is within the range [0, 360]:
if v < 0 || v > 360 {
return float32(math.NaN()), fmt.Errorf("ra value is out of range: %f", v)
}

// Return the candidate RA (v):
return v, nil
}

/*****************************************************************************************************************/
152 changes: 152 additions & 0 deletions internal/utils/headers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*****************************************************************************************************************/

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

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

package utils

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

import (
"math"
"testing"

"github.com/observerly/iris/pkg/fits"
)

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

// newFITSHeaderWithRA is a helper that returns a fits.FITSHeader
// populated with an RA value.
func newFITSHeaderWithRA(ra float32) fits.FITSHeader {
return fits.FITSHeader{
Floats: map[string]fits.FITSHeaderFloat{
"RA": {
Value: ra,
Comment: "Right Ascension",
},
},
}
}

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

// newFITSHeaderNoRA is a helper that returns a fits.FITSHeader
// with no RA entry in the Floats map.
func newFITSHeaderNoRA() fits.FITSHeader {
return fits.FITSHeader{
Floats: map[string]fits.FITSHeaderFloat{},
}
}

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

func TestRAValueIsNotNaNAndWithinRange(t *testing.T) {
value := float32(180.0)
header := newFITSHeaderNoRA()

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if got != value {
t.Errorf("Expected %f, got %f", value, got)
}
}

func TestRAValueIsNotNaNAndOutOfRangeNegative(t *testing.T) {
value := float32(-1.0)
header := newFITSHeaderNoRA()

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err == nil {
t.Fatalf("Expected an error for negative RA, but got none")
}
if !math.IsNaN(float64(got)) {
t.Errorf("Expected NaN for out-of-range RA, got %f", got)
}
}

func TestRAValueIsNotNaNAndOutOfRangePositive(t *testing.T) {
value := float32(361.0)
header := newFITSHeaderNoRA()

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err == nil {
t.Fatalf("Expected an error for RA > 360, but got none")
}
if !math.IsNaN(float64(got)) {
t.Errorf("Expected NaN for out-of-range RA, got %f", got)
}
}

func TestRAValueIsNaNAndRAHeaderFoundAndValid(t *testing.T) {
value := float32(math.NaN())
header := newFITSHeaderWithRA(123.45)

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err != nil {
t.Fatalf("Unexpected error when RA is pulled from header: %v", err)
}
expected := float32(123.45)
if got != expected {
t.Errorf("Expected RA = %f, got %f", expected, got)
}
}

func TestRAValueIsNaNAndRAHeaderMissing(t *testing.T) {
value := float32(math.NaN())
header := newFITSHeaderNoRA()

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err == nil {
t.Fatalf("Expected an error when RA header is missing, but got none")
}
if !math.IsNaN(float64(got)) {
t.Errorf("Expected NaN when RA header is missing, got %f", got)
}
}

func TestRAValueIsNaNAndRAHeaderOutOfRangeNegative(t *testing.T) {
value := float32(math.NaN())
header := newFITSHeaderWithRA(-5.0)

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err == nil {
t.Fatalf("Expected an error for negative RA, but got none")
}
if !math.IsNaN(float64(got)) {
t.Errorf("Expected NaN for out-of-range RA, got %f", got)
}
}

func TestRAValueIsNaNAndRAHeaderOutOfRangePositive(t *testing.T) {
value := float32(math.NaN())
header := newFITSHeaderWithRA(400.0)

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err == nil {
t.Fatalf("Expected an error for RA > 360, but got none")
}
if !math.IsNaN(float64(got)) {
t.Errorf("Expected NaN for out-of-range RA, got %f", got)
}
}

func TestRAValueIsNaNAndRAHeaderNaN(t *testing.T) {
value := float32(math.NaN())
header := newFITSHeaderWithRA(float32(math.NaN()))

got, err := ResolveOrExtractRAFromHeaders(value, header)
if err == nil {
t.Fatalf("Expected an error for RA=NaN in header, but got none")
}
if !math.IsNaN(float64(got)) {
t.Errorf("Expected NaN when RA=NaN in header, got %f", got)
}
}

/*****************************************************************************************************************/
Loading