comparison pkg/controllers/gauges.go @ 2762:f95ec0bb565c

Added endpoint to deliver average waterlevels for a given gauge. GET /api/data/average-waterlevels/{GAUGE}?from={FROM}&to={TO} from and to are optional and defaults to end of today and going a year backwards. Output is CSV in form of #date,#min,#max,#mean,#median,#Q25,#Q75
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 21 Mar 2019 18:07:49 +0100
parents 87aed4f9b1b8
children a06d11d1f0b3
comparison
equal deleted inserted replaced
2761:71e7237110ba 2762:f95ec0bb565c
17 "database/sql" 17 "database/sql"
18 "encoding/csv" 18 "encoding/csv"
19 "fmt" 19 "fmt"
20 "log" 20 "log"
21 "net/http" 21 "net/http"
22 "sort"
22 "strconv" 23 "strconv"
23 "time" 24 "time"
24 25
25 "github.com/gorilla/mux" 26 "github.com/gorilla/mux"
27 "gonum.org/v1/gonum/stat"
26 28
27 "gemma.intevation.de/gemma/pkg/common" 29 "gemma.intevation.de/gemma/pkg/common"
28 "gemma.intevation.de/gemma/pkg/middleware" 30 "gemma.intevation.de/gemma/pkg/middleware"
29 "gemma.intevation.de/gemma/pkg/models" 31 "gemma.intevation.de/gemma/pkg/models"
30 ) 32 )
54 56
55 selectWaterlevelsSQL = ` 57 selectWaterlevelsSQL = `
56 SELECT 58 SELECT
57 measure_date, 59 measure_date,
58 water_level, 60 water_level,
59 predicted 61 predicted,
60 FROM waterway.gauge_measurements 62 FROM waterway.gauge_measurements
61 WHERE 63 WHERE
62 ` 64 `
65 selectWaterlevelsMeasuredSQL = `
66 SELECT
67 measure_date,
68 water_level
69 FROM waterway.gauge_measurements
70 WHERE
71 NOT predicted
72 AND fk_gauge_id = (
73 $1::char(2),
74 $2::char(3),
75 $3::char(5),
76 $4::char(5),
77 $5::int
78 )
79 AND
80 measure_date BETWEEN
81 $6::timestamp with time zone AND $7::timestamp with time zone
82 ORDER BY measure_date
83 `
63 ) 84 )
85
86 func averageWaterlevels(rw http.ResponseWriter, req *http.Request) {
87 gauge := mux.Vars(req)["gauge"]
88
89 isrs, err := models.IsrsFromString(gauge)
90 if err != nil {
91 http.Error(
92 rw, fmt.Sprintf("error: Invalid ISRS code: %v", err),
93 http.StatusBadRequest)
94 return
95 }
96
97 var from, to time.Time
98
99 if t := req.FormValue("to"); t != "" {
100 var err error
101 if to, err = time.ParseInLocation(common.DateFormat, t, time.UTC); err != nil {
102 http.Error(
103 rw, fmt.Sprintf("error: bad from date: %v", err),
104 http.StatusBadRequest)
105 return
106 }
107 } else {
108 y, m, d := time.Now().Date()
109 to = time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
110 }
111
112 if f := req.FormValue("from"); f != "" {
113 var err error
114 if from, err = time.ParseInLocation(common.DateFormat, f, time.UTC); err != nil {
115 http.Error(
116 rw, fmt.Sprintf("error: bad from date: %v", err),
117 http.StatusBadRequest)
118 return
119 }
120 } else {
121 from = to.AddDate(-1, 0, 0)
122 }
123
124 to = to.AddDate(0, 0, 1).Add(-time.Nanosecond)
125
126 if to.Before(from) {
127 from, to = to, from
128 }
129
130 conn := middleware.GetDBConn(req)
131
132 ctx := req.Context()
133
134 rows, err := conn.QueryContext(
135 ctx,
136 selectWaterlevelsMeasuredSQL,
137 isrs.CountryCode,
138 isrs.LoCode,
139 isrs.FairwaySection,
140 isrs.Orc,
141 isrs.Hectometre,
142 from, to,
143 )
144 if err != nil {
145 http.Error(
146 rw, fmt.Sprintf("error: %v", err),
147 http.StatusInternalServerError)
148 return
149 }
150 defer rows.Close()
151
152 rw.Header().Add("Content-Type", "text/csv")
153
154 out := csv.NewWriter(rw)
155
156 var last time.Time
157 var values []float64
158
159 record := []string{
160 "#date",
161 "#min",
162 "#max",
163 "#mean",
164 "#median",
165 "#q25",
166 "#q75",
167 }
168
169 if err := out.Write(record); err != nil {
170 log.Printf("error: %v\n", err)
171 // Too late for an HTTP error code.
172 return
173 }
174
175 format := func(v float64) string {
176 return strconv.FormatFloat(v, 'f', -1, 64)
177 }
178
179 write := func() error {
180 if len(values) > 0 {
181 sort.Float64s(values)
182 // date
183 record[0] = last.Format(common.DateFormat)
184 // min
185 record[1] = format(values[0])
186 // max
187 record[2] = format(values[len(values)-1])
188 // mean
189 record[3] = format(stat.Mean(values, nil))
190 // median
191 record[4] = format(values[len(values)/2])
192 // Q25
193 record[5] = format(
194 stat.Quantile(0.25, stat.Empirical, values, nil))
195 // Q75
196 record[6] = format(
197 stat.Quantile(0.75, stat.Empirical, values, nil))
198
199 err := out.Write(record)
200 values = values[:0]
201 return err
202 }
203 return nil
204 }
205
206 for rows.Next() {
207 var (
208 date time.Time
209 value float64
210 )
211 if err := rows.Scan(&date, &value); err != nil {
212 log.Printf("error: %v\n", err)
213 // Too late for an HTTP error code.
214 return
215 }
216 oy, om, od := last.Date()
217 ny, nm, nd := date.Date()
218 if oy != ny || om != nm || od != nd {
219 if err := write(); err != nil {
220 log.Printf("error: %v\n", err)
221 // Too late for an HTTP error code.
222 return
223 }
224 last = date
225 } else {
226 values = append(values, value)
227 }
228 }
229 write()
230
231 if err := rows.Err(); err != nil {
232 log.Printf("error: %v", err)
233 // Too late for an HTTP error code.
234 return
235 }
236
237 out.Flush()
238 if err := out.Error(); err != nil {
239 log.Printf("error: %v", err)
240 // Too late for an HTTP error code.
241 return
242 }
243
244 }
64 245
65 func nashSutcliffe( 246 func nashSutcliffe(
66 _ interface{}, 247 _ interface{},
67 req *http.Request, 248 req *http.Request,
68 conn *sql.Conn, 249 conn *sql.Conn,