Mercurial > gemma
view pkg/controllers/gauges.go @ 2741:87aed4f9b1b8
Added calculation of Nash Sutcliffe efficiency coefficents.
GET /api/data/nash-sutcliffe/{gauge}?when={WHEN}
'when' is optional in form of "2006-01-02T15:04:05.000" and defaults to
current server time.
curl -H "X-Gemma-Auth:$KEY" http://${server}:${port}/api/data/nash-sutcliffe/${gauge} | jq .
{
"when": "2019-03-20T10:38:05.687",
"coeffs": [
{
"value": 0,
"samples": 0,
"hours": 24
},
{
"value": 0,
"samples": 0,
"hours": 48
},
{
"value": 0,
"samples": 0,
"hours": 72
}
]
}
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Wed, 20 Mar 2019 10:47:01 +0100 |
parents | 0d7a4fdb9e12 |
children | f95ec0bb565c |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package controllers import ( "database/sql" "encoding/csv" "fmt" "log" "net/http" "strconv" "time" "github.com/gorilla/mux" "gemma.intevation.de/gemma/pkg/common" "gemma.intevation.de/gemma/pkg/middleware" "gemma.intevation.de/gemma/pkg/models" ) const ( selectPredictedObserveredSQL = ` SELECT a.measure_date AS measure_date, a.water_level AS predicted, b.water_level AS observed FROM waterway.gauge_measurements a JOIN waterway.gauge_measurements b ON a.fk_gauge_id = b.fk_gauge_id AND a.measure_date = b.measure_date AND a.predicted AND NOT b.predicted WHERE a.fk_gauge_id = ( $1::char(1), $2::char(2), $3::char(3), $4::char(4), $5::int ) AND a.measure_date BETWEEN $6::timestamp AND $6::timestamp - '72hours'::interval ORDER BY a.measure_date ` selectWaterlevelsSQL = ` SELECT measure_date, water_level, predicted FROM waterway.gauge_measurements WHERE ` ) func nashSutcliffe( _ interface{}, req *http.Request, conn *sql.Conn, ) (jr JSONResult, err error) { gauge := mux.Vars(req)["gauge"] var isrs *models.Isrs if isrs, err = models.IsrsFromString(gauge); err != nil { err = JSONError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("error: Invalid ISRS code: %v", err), } return } var when time.Time if w := req.FormValue("when"); w != "" { if when, err = time.Parse(models.ImportTimeFormat, w); err != nil { err = JSONError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("error: wrong time format: %v", err), } return } } else { when = time.Now() } ctx := req.Context() var rows *sql.Rows if rows, err = conn.QueryContext( ctx, selectPredictedObserveredSQL, isrs.CountryCode, isrs.LoCode, isrs.FairwaySection, isrs.Orc, isrs.Hectometre, when, ); err != nil { return } defer rows.Close() var measurements []common.NSMeasurement for rows.Next() { var m common.NSMeasurement if err = rows.Scan( &m.When, &m.Predicted, &m.Observed, ); err != nil { return } measurements = append(measurements, m) } if err = rows.Err(); err != nil { return } type coeff struct { Value float64 `json:"value"` Samples int `json:"samples"` Hours int `json:"hours"` } type coeffs struct { When models.ImportTime `json:"when"` Coeffs []coeff `json:"coeffs"` } cs := make([]coeff, 3) for i := range cs { cs[i].Hours = (i + 1) * 24 cs[i].Value, cs[i].Samples = common.NashSutcliffe( measurements, when, when.Add(time.Duration(-cs[i].Hours)*time.Hour), ) } jr = JSONResult{ Result: &coeffs{ When: models.ImportTime{when}, Coeffs: cs, }, } return } func waterlevels(rw http.ResponseWriter, req *http.Request) { gauge := mux.Vars(req)["gauge"] isrs, err := models.IsrsFromString(gauge) if err != nil { http.Error( rw, fmt.Sprintf("error: Invalid ISRS code: %v", err), http.StatusBadRequest) return } var fb filterBuilder fb.stmt.WriteString(selectWaterlevelsSQL) fb.cond( " fk_gauge_id = ($%d::char(2), $%d::char(3), $%d::char(5), $%d::char(5), $%d::int) ", isrs.CountryCode, isrs.LoCode, isrs.FairwaySection, isrs.Orc, isrs.Hectometre, ) if from := req.FormValue("from"); from != "" { fromTime, err := time.Parse(models.ImportTimeFormat, from) if err != nil { http.Error( rw, fmt.Sprintf("error: Invalid from time: %v", err), http.StatusBadRequest) return } fb.cond("measure_date >= $%d", fromTime) } if to := req.FormValue("to"); to != "" { toTime, err := time.Parse(models.ImportTimeFormat, to) if err != nil { http.Error( rw, fmt.Sprintf("error: Invalid from time: %v", err), http.StatusBadRequest) return } fb.cond("measure_date <= $%d", toTime) } conn := middleware.GetDBConn(req) ctx := req.Context() rows, err := conn.QueryContext(ctx, fb.stmt.String(), fb.args...) if err != nil { http.Error( rw, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) return } defer rows.Close() rw.Header().Add("Content-Type", "text/csv") out := csv.NewWriter(rw) record := make([]string, 3) for rows.Next() { var ( measureDate time.Time waterlevel float64 predicted bool ) if err := rows.Scan(&measureDate, &waterlevel, &predicted); err != nil { log.Printf("error: %v\n", err) // Too late for an HTTP error code. return } record[0] = measureDate.Format(models.ImportTimeFormat) record[1] = strconv.FormatFloat(waterlevel, 'f', -1, 64) if predicted { record[2] = "t" } else { record[2] = "f" } if err := out.Write(record); err != nil { log.Printf("error: %v", err) // Too late for an HTTP error code. return } } if err := rows.Err(); err != nil { log.Printf("error: %v", err) // Too late for an HTTP error code. return } out.Flush() if err := out.Error(); err != nil { log.Printf("error: %v", err) // Too late for an HTTP error code. return } }