-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgpgga.go
172 lines (139 loc) · 3.81 KB
/
gpgga.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
package nmea
import (
"fmt"
"strconv"
"strings"
"time"
)
// Examples:
// $GPGGA,015540.000,3150.68378,N,11711.93139,E,1,17,0.6,0051.6,M,0.0,M,,*58
func NewGPGGA(m Message) *GPGGA {
return &GPGGA{Message: m}
}
type GPGGA struct {
Message
TimeUTC time.Time // Aggregation of TimeUTC data field
Latitude LatLong // In decimal format
Longitude LatLong // In decimal format
QualityIndicator QualityIndicator
NbOfSatellitesUsed uint64
HDOP float64
Altitude float64
GeoIDSep *float64
// FIXME: Manage field below when I found a sample with no-empty data
// DGPSAge *uint64
// DGPSiStationId *string
}
func (m *GPGGA) parse() (err error) {
if len(m.Fields) != 14 {
return fmt.Errorf("Incomplete GPGGA message, not enougth data fields (got: %d, wanted: %d)", len(m.Fields), 14)
}
// Validate fixed field
for i, v := range map[int]string{9: "M", 11: "M"} {
if m.Fields[i] != v {
return fmt.Errorf("Invalid fixed field at %d (got: %s, wanted: %s)", i+1, m.Fields[i], v)
}
}
if m.TimeUTC, err = time.Parse("150405.000", m.Fields[0]); err != nil {
return m.Error(fmt.Errorf("Unable to parse time UTC from data field (got: %s)", m.Fields[0]))
}
if latitude := strings.TrimSpace(strings.Join(m.Fields[1:3], " ")); len(latitude) > 0 {
if m.Latitude, err = NewLatLong(latitude); err != nil {
return m.Error(err)
}
}
if longitude := strings.TrimSpace(strings.Join(m.Fields[3:5], " ")); len(longitude) > 0 {
if m.Longitude, err = NewLatLong(longitude); err != nil {
return m.Error(err)
}
}
if m.QualityIndicator, err = ParseQualityIndicator(m.Fields[5]); err != nil {
return m.Error(err)
}
if m.NbOfSatellitesUsed, err = strconv.ParseUint(m.Fields[6], 10, 0); err != nil {
return m.Error(err)
}
if hdop := m.Fields[7]; len(hdop) > 0 {
if m.HDOP, err = strconv.ParseFloat(hdop, 64); err != nil {
return m.Error(err)
}
}
if altitude := m.Fields[8]; len(altitude) > 0 {
if m.Altitude, err = strconv.ParseFloat(altitude, 64); err != nil {
return m.Error(err)
}
}
if geoIDSep := m.Fields[10]; len(geoIDSep) > 0 {
id, err := strconv.ParseFloat(geoIDSep, 64)
if err != nil {
return m.Error(err)
}
m.GeoIDSep = &id
}
return nil
}
func (m GPGGA) Serialize() string { // Implement NMEA interface
hdr := TypeIDs["GPGGA"]
fields := make([]string, 0)
fields = append(fields, m.TimeUTC.Format("150405.000"),
strings.Trim(m.Latitude.Serialize(), "0"), m.Latitude.CardinalPoint(true).String(),
strings.Trim(m.Longitude.Serialize(), "0"), m.Longitude.CardinalPoint(false).String(),
strconv.Itoa(int(m.QualityIndicator)),
strconv.Itoa(int(m.NbOfSatellitesUsed)),
)
if m.HDOP > 0 {
fields = append(fields, fmt.Sprintf("%.1f", m.HDOP))
} else {
fields = append(fields, "")
}
if m.Altitude > 0 {
fields = append(fields, PrependXZero(m.Altitude, "%.1f", 4))
} else {
fields = append(fields, "")
}
fields = append(fields, "M")
if m.GeoIDSep != nil {
fields = append(fields, fmt.Sprintf("%.1f", *m.GeoIDSep))
} else {
fields = append(fields, "")
}
fields = append(fields,
"M",
"", // DGPSAge always empty ?
"", // DGPSiStationId always empty ?
)
msg := Message{Type: hdr, Fields: fields}
msg.Checksum = msg.ComputeChecksum()
return msg.Serialize()
}
const (
InvalidIndicator = iota
GNSSS
DGPS
)
type QualityIndicator int
func (s QualityIndicator) String() string {
switch s {
case InvalidIndicator:
return "invalid"
case GNSSS:
return "GNSS fix"
case DGPS:
return "DGPS fix"
default:
return "unknow"
}
}
func ParseQualityIndicator(raw string) (qi QualityIndicator, err error) {
i, err := strconv.ParseInt(raw, 10, 0)
if err != nil {
return
}
qi = QualityIndicator(i)
switch qi {
case InvalidIndicator, GNSSS, DGPS:
default:
err = fmt.Errorf("unknow value")
}
return
}