-
Notifications
You must be signed in to change notification settings - Fork 3
/
taf.go
197 lines (174 loc) · 5.89 KB
/
taf.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package metar
import (
"regexp"
"strconv"
"strings"
"time"
"github.com/urkk/metar/clouds"
ph "github.com/urkk/metar/phenomena"
v "github.com/urkk/metar/visibility"
"github.com/urkk/metar/wind"
)
// TemperatureForecast - Forecast Max and Min temperature
type TemperatureForecast struct {
Temp int
DateTime time.Time
IsMax bool
IsMin bool
}
// TAFMessage - Terminal Aerodrome Forecast struct
type TAFMessage struct {
rawData string // The raw TAF
COR bool // Correction of forecast due to a typo
AMD bool // Amended forecast
NIL bool // event of missing TAF
Station string // 4-letter ICAO station identifier
DateTime time.Time // Time( in ISO8601 date/time format) this TAF was issued
ValidFrom time.Time
ValidTo time.Time
CNL bool // The previously issued TAF for the period was cancelled
wind.Wind // Surface wind
// Ceiling And Visibility OK, indicating no cloud below 5,000 ft (1,500 m) or the highest minimum sector
// altitude and no cumulonimbus or towering cumulus at any level, a visibility of 10 km (6 mi) or more and no significant weather change.
CAVOK bool
v.Visibility // Horizontal visibility
ph.Phenomena // Present Weather
VerticalVisibility int // Vertical visibility (ft)
clouds.Clouds // Cloud amount and height
Temperature []TemperatureForecast // Temperature extremes
// Prevision
TREND []Trend
NotDecodedTokens []string
}
// NewTAF - creates a new TAF forecast based on the original message
func NewTAF(inputtext string) *TAFMessage {
t := &TAFMessage{
rawData: inputtext,
}
headerRx := myRegexp{regexp.MustCompile(`^(?P<taf>TAF\s)?(?P<cor>COR\s)?(?P<amd>AMD\s)?(?P<station>\w{4})\s(?P<time>\d{6}Z)(?P<nil>\sNIL)?(\s(?P<from>\d{4})/(?P<to>\d{4}))?(?P<cnl>\sCNL)?`)}
headermap := headerRx.FindStringSubmatchMap(t.rawData)
t.Station = headermap["station"]
t.DateTime, _ = time.Parse("200601021504Z", CurYearStr+CurMonthStr+headermap["time"])
t.COR = headermap["cor"] != ""
t.AMD = headermap["amd"] != ""
t.NIL = headermap["nil"] != ""
t.CNL = headermap["cnl"] != ""
if t.Station == "" && t.DateTime.IsZero() {
//not valid message?
t.NotDecodedTokens = append(t.NotDecodedTokens, t.rawData)
return t
}
if t.NIL { // End of TAF, if the forecast is lost
return t
}
t.setTimeRange(headermap["from"], headermap["to"])
if t.CNL { // End of TAF, if the forecast is cancelled
return t
}
tokens := strings.Split(t.rawData, " ")
position := 0
for key, value := range headermap {
if value != "" && key != "to" { // field "from" and "to" - it's one token (DDhh/DDhh), and they are mandatory.
position++
}
}
endposition := t.findTrendsInMessage(tokens, position)
t.decodeTAF(tokens[position:endposition])
return t
}
// RAW - returns the original message text
func (t *TAFMessage) RAW() string { return t.rawData }
func (t *TAFMessage) decodeTAF(tokens []string) {
for count := 0; count < len(tokens); {
// Surface wind. Required element
count += t.ParseWind(tokens[count])
if tokens[count] == "CAVOK" {
t.CAVOK = true
count++
} else {
count = decodeWeatherCondition(t, count, tokens)
} // !CAVOK
// Temperature
for count < len(tokens) && t.addTempForecast(tokens[count]) {
count++
}
// The token is not recognized or is located in the wrong position
if count < len(tokens) {
t.NotDecodedTokens = append(t.NotDecodedTokens, tokens[count])
count++
}
} // End main section
}
func (t *TAFMessage) addTempForecast(input string) bool {
regex := regexp.MustCompile(`^T(X|N)(M)?(\d\d)\/(\d{4}Z)`)
matches := regex.FindStringSubmatch(input)
if len(matches) > 0 {
tempf := new(TemperatureForecast)
tempf.Temp, _ = strconv.Atoi(matches[3])
if matches[2] == "M" {
tempf.Temp = -tempf.Temp
}
tempf.IsMin = matches[1] == "N"
tempf.IsMax = matches[1] == "X"
if matches[4][2:] == "24Z" {
inputString := matches[4][:2] + "23"
tempf.DateTime, _ = time.Parse("2006010215", CurYearStr+CurMonthStr+inputString)
tempf.DateTime = tempf.DateTime.Add(time.Hour)
} else {
tempf.DateTime, _ = time.Parse("2006010215Z", CurYearStr+CurMonthStr+matches[4])
}
// if date in next month
if tempf.DateTime.Day() < t.DateTime.Day() {
tempf.DateTime = tempf.DateTime.AddDate(0, 1, 0)
}
t.Temperature = append(t.Temperature, *tempf)
return true
}
return false
}
func (t *TAFMessage) setTimeRange(fromStr, toStr string) {
t.ValidFrom, _ = time.Parse("2006010215", CurYearStr+CurMonthStr+fromStr)
// hours maybe 24
if toStr[2:] == "24" {
t.ValidTo, _ = time.Parse("2006010215", CurYearStr+CurMonthStr+toStr[:2]+"23")
t.ValidTo = t.ValidTo.Add(time.Hour)
} else {
t.ValidTo, _ = time.Parse("2006010215", CurYearStr+CurMonthStr+toStr)
}
// forecast for next month
if t.ValidFrom.Day() < t.DateTime.Day() {
t.ValidFrom = t.ValidFrom.AddDate(0, 1, 0)
}
if t.ValidTo.Day() < t.DateTime.Day() {
t.ValidTo = t.ValidTo.AddDate(0, 1, 0)
}
}
func (t *TAFMessage) setVerticalVisibility(input string) bool {
regex := regexp.MustCompile(`VV(\d{3})`)
matches := regex.FindStringSubmatch(input)
if len(matches) != 0 && matches[1] != "" {
t.VerticalVisibility, _ = strconv.Atoi(matches[1])
t.VerticalVisibility *= 100
return true
}
return false
}
func (t *TAFMessage) findTrendsInMessage(tokens []string, startposition int) (endposition int) {
var trends [][]string
endposition = len(tokens)
for i := len(tokens) - 1; i > startposition; i-- {
if tokens[i] == TEMPO || tokens[i] == BECMG || strings.HasPrefix(tokens[i], "PROB") || strings.HasPrefix(tokens[i], "FM") {
if strings.HasPrefix(tokens[i-1], "PROB") {
i--
}
trends = append([][]string{tokens[i:endposition]}, trends[0:]...)
endposition = i
}
}
for _, trendstr := range trends {
if trend := parseTrendData(trendstr); trend != nil {
t.TREND = append(t.TREND, *trend)
}
}
return
}