-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiss.go
154 lines (138 loc) · 3.87 KB
/
iss.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
package iss
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/shopspring/decimal"
)
// response represents response data received
// upon successful call to the ISS API.
type response struct {
Timestamp int64
Message string
ISSPosition struct {
Lat string `json:"latitude"`
Long string `json:"longitude"`
} `json:"iss_position"`
}
// Position represents geographical coordinates
// of the International Space Station for the given time.
type Position struct {
Lat decimal.Decimal
Long decimal.Decimal
}
// Client is the International Space Station (ISS) client.
//
// More information about the ISS API can be obtained on
// the website: http://open-notify.org/Open-Notify-API/
type Client struct {
BaseURL string
HTTPClient *http.Client
}
type option func(*Client) error
// WithHTTPClient is a func option that configures
// ISS client to use custom HTTP client.
func WithHTTPClient(hc *http.Client) option {
return func(c *Client) error {
if hc == nil {
return errors.New("nil http client")
}
c.HTTPClient = hc
return nil
}
}
// WithBaseURL is a func option that configures
// ISS client to use custom ISS URL.
func WithBaseURL(s string) option {
return func(c *Client) error {
if s == "" {
return errors.New("empty URL string")
}
c.BaseURL = s
return nil
}
}
// New returns a default ISS Client API or error
// if one provided func option returns err.
func New(opts ...option) (*Client, error) {
c := Client{
BaseURL: "http://api.open-notify.org/iss-now.json",
HTTPClient: &http.Client{
Timeout: time.Second * 10,
},
}
for _, opt := range opts {
if err := opt(&c); err != nil {
return nil, err
}
}
return &c, nil
}
// GetPosition returns International Space Station coordinates
// (latitude/longitude) at the time of the request.
func (c Client) GetPosition() (Position, error) {
req, err := http.NewRequest(http.MethodGet, c.BaseURL, nil)
if err != nil {
return Position{}, err
}
req.Header.Add("Content-Type", "application/json")
res, err := c.HTTPClient.Do(req)
if err != nil {
return Position{}, fmt.Errorf("calling ISS API: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return Position{}, fmt.Errorf("unexpected response status code: %v", res.StatusCode)
}
return parseResponse(res.Body)
}
func parseResponse(r io.Reader) (Position, error) {
var res response
if err := json.NewDecoder(r).Decode(&res); err != nil {
return Position{}, err
}
lat, err := toDecimal(res.ISSPosition.Lat)
if err != nil {
return Position{}, err
}
long, err := toDecimal(res.ISSPosition.Long)
if err != nil {
return Position{}, err
}
return Position{Lat: lat, Long: long}, nil
}
func toDecimal(coordinate string) (decimal.Decimal, error) {
coordinate64, err := strconv.ParseFloat(coordinate, 64)
if err != nil {
return decimal.Decimal{}, fmt.Errorf("converting coordinate %s to decimal %w", coordinate, err)
}
return decimal.NewFromFloatWithExponent(coordinate64, -4), nil
}
// GetPosition returns current position of the International Space Station
// It returns latitude and longitude coordinates or error if the position
// cannot be checked at the time.
func GetPosition() (decimal.Decimal, decimal.Decimal, error) {
issClient, err := New()
if err != nil {
return decimal.Decimal{}, decimal.Decimal{}, fmt.Errorf("creating ISS client %w", err)
}
p, err := issClient.GetPosition()
if err != nil {
return decimal.Decimal{}, decimal.Decimal{}, fmt.Errorf("retrieving ISS position, %w", err)
}
return p.Lat, p.Long, nil
}
// GetPositionAsStrings returns current position of the ISS.
// The lat and long are represented as strings. It returns
// an error is the position of the ISS cannot be obtained.
func GetPositionAsStrings() (string, string, error) {
lat, long, err := GetPosition()
if err != nil {
return "", "", err
}
return lat.String(), long.String(), nil
}