forked from joshuaferrara/go-satellite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
spacetrack.go
182 lines (153 loc) · 3.89 KB
/
spacetrack.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package satellite
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/pkg/errors"
)
const authURL = "https://www.space-track.org/ajaxauth/login"
const baseurl = "https://www.space-track.org/basicspacedata/query/class"
type orderBy string
const asc orderBy = "asc"
const desc orderBy = "desc"
type operator string
const dq operator = ""
const lt operator = "<"
// Keeping these around in case the API interface expands to use them
// const gt operator = ">"
// const ne operator = "<>"
// const or operator = ","
// const inclusiveRange operator = "--"
// const null operator = "null-val"
// const like operator = "~~"
// const wildcard operator = "^"
// const now operator = "now"
type field string
const epoch field = "EPOCH"
const tle field = "TLE"
const noradCatID field = "NORAD_CAT_ID"
var ErrInvalidResponseCode = errors.New("Invalid response from spacetrack")
var ErrNotSingleSat = errors.New("not a single satellite returned")
// Spacetrack contains an initialised API interface to space-track.org
type Spacetrack struct {
username string
password string
}
// NewSpacetrack creates an initialised API interface to space-track.org
// https://space-track.org allows you to create a free account, however be
// aware there *are* API throttles (see https://www.space-track.org/documentation#/api )
func NewSpacetrack(username, password string) *Spacetrack {
return &Spacetrack{
username: username,
password: password,
}
}
// GetTLE generates a Satellite from the most recent TLE from space-track.org before the given time
func (s *Spacetrack) GetTLE(catid uint64, ts time.Time, gravConst Gravity) (Satellite, error) {
zero := Satellite{}
args := spacetrackArgs{
base: baseurl,
class: tle,
orderByField: epoch,
orderByDir: desc,
limit: 1,
filters: filters{{
filterType: noradCatID,
operator: dq,
value: fmt.Sprint(catid),
}, {
filterType: epoch,
operator: lt,
value: ts.Format(time.RFC3339),
}},
}
q := buildQuery(args)
vals := url.Values{}
vals.Add("identity", s.username)
vals.Add("password", s.password)
vals.Add("query", q)
client := &http.Client{}
resp, err := client.PostForm(authURL, vals)
if err != nil {
return zero, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return zero, errors.Wrap(ErrInvalidResponseCode, resp.Status)
}
respData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return zero, err
}
var sats []Satellite
if err := json.Unmarshal(respData, &sats); err != nil {
return zero, err
}
if len(sats) != 1 {
return zero, errors.Wrap(ErrNotSingleSat, fmt.Sprint(sats))
}
sat := TLEToSat(sats[0].Line1, sats[0].Line2, gravConst)
return sat, nil
}
type spacetrackArgs struct {
base string
class field
orderByField field
orderByDir orderBy
limit uint64
filters filters
}
func (s spacetrackArgs) orderBy() string {
if s.orderByField == "" {
return ""
}
if s.orderByDir == "" {
s.orderByDir = asc
}
return fmt.Sprint("orderby/", s.orderByField, " ", s.orderByDir)
}
func (s spacetrackArgs) limitString() string {
if s.limit == 0 {
return ""
}
return fmt.Sprint("limit/", s.limit)
}
type filters []filter
type filter struct {
filterType field
operator operator
value string
}
func (f filters) render() string {
results := []string{}
for _, f := range f {
results = append(results, f.render())
}
return strings.Join(results, "/")
}
func (f filter) render() string {
return fmt.Sprint(f.filterType, "/", f.operator, f.value)
}
func buildQuery(a spacetrackArgs) string {
fields := []string{
a.base,
string(a.class),
}
optionalFields := []string{
a.filters.render(),
a.orderBy(),
a.limitString(),
"emptyresult/show",
}
for _, f := range optionalFields {
if f == "" {
continue
}
fields = append(fields, f)
}
return strings.Join(fields, "/")
}