# HG changeset patch # User Sascha L. Teichmann # Date 1558692939 -7200 # Node ID 3bb2d14abf8abf65782871454283ea671a4d0996 # Parent 1559566662f52d6b9a126b46b026e85865ede162# Parent 4a0fab451f877004709204a50c3462a6672099e9 Merged fairway-avail-csv into default branch. diff -r 1559566662f5 -r 3bb2d14abf8a pkg/controllers/bottlenecks.go --- a/pkg/controllers/bottlenecks.go Fri May 24 12:05:35 2019 +0200 +++ b/pkg/controllers/bottlenecks.go Fri May 24 12:15:39 2019 +0200 @@ -256,29 +256,59 @@ return percents } -func parseTime(s, what string) (time.Time, error) { - var t time.Time - var err error - if t, err = time.Parse(common.TimeFormat, s); err != nil { - return time.Time{}, JSONError{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf( - "Invalid time format for '%s' field: %v", what, err), - } +func parseFormTime( + rw http.ResponseWriter, + req *http.Request, + field string, + def time.Time, +) (time.Time, bool) { + f := req.FormValue(field) + if f == "" { + return def.UTC(), true } - return t.UTC(), nil + v, err := time.Parse(common.TimeFormat, f) + if err != nil { + http.Error( + rw, fmt.Sprintf("Invalid format for '%s': %v.", field, err), + http.StatusBadRequest, + ) + return time.Time{}, false + } + return v.UTC(), true } -func parseInt(s, what string) (int, error) { - i, err := strconv.Atoi(s) +func parseFormInt( + rw http.ResponseWriter, + req *http.Request, + field string, + def int, +) (int, bool) { + f := req.FormValue(field) + if f == "" { + return def, true + } + v, err := strconv.Atoi(f) if err != nil { - return 0, JSONError{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf( - "Invalid value for field '%s': %v", what, err), - } + http.Error( + rw, fmt.Sprintf("Invalid format for '%s': %v.", field, err), + http.StatusBadRequest, + ) + return 0, false } - return i, nil + return v, true +} + +func intervalMode(mode string) int { + switch strings.ToLower(mode) { + case "monthly": + return 0 + case "quarterly": + return 1 + case "yearly": + return 2 + default: + return 0 + } } func loadDepthValues( @@ -360,74 +390,68 @@ return values } -func bottleneckAvailabilty( - _ interface{}, - req *http.Request, - conn *sql.Conn, -) (jr JSONResult, err error) { +func bottleneckAvailabilty(rw http.ResponseWriter, req *http.Request) { + mode := intervalMode(req.FormValue("mode")) bn := mux.Vars(req)["objnam"] - var from, to time.Time - var los int - var breaks []float64 - var ldcRefs []float64 if bn == "" { - err = JSONError{ - Code: http.StatusBadRequest, - Message: "Missing objnam of bottleneck", - } + http.Error( + rw, + "Missing objnam of bottleneck", + http.StatusBadRequest, + ) return } - if f := req.FormValue("from"); f != "" { - if from, err = parseTime(f, "from"); err != nil { - return - } - } else { - from = time.Now().AddDate(-1, 0, 0).UTC() + from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0)) + if !ok { + return } - if t := req.FormValue("to"); t != "" { - if to, err = parseTime(t, "to"); err != nil { - return - } - } else { - to = from.AddDate(1, 0, 0).UTC() + to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0)) + if !ok { + return } if to.Before(from) { to, from = from, to } - if l := req.FormValue("los"); l != "" { - if los, err = parseInt(l, "los"); err != nil { - return - } - } else { - los = 1 + los, ok := parseFormInt(rw, req, "los", 1) + if !ok { + return } + conn := middleware.GetDBConn(req) + ctx := req.Context() + + ldcRefs, err := loadLDCReferenceValue(ctx, conn, bn) + if err != nil { + http.Error( + rw, + fmt.Sprintf("Internal server error: %v", err), + http.StatusInternalServerError, + ) + return + } + + if len(ldcRefs) == 0 { + http.Error( + rw, + "No gauge reference values found for bottleneck", + http.StatusNotFound, + ) + return + } + + var breaks []float64 if b := req.FormValue("breaks"); b != "" { breaks = breaksToReferenceValue(b) } else { breaks = afdRefs } - ctx := req.Context() - - if ldcRefs, err = loadLDCReferenceValue(ctx, conn, bn); err != nil { - return - } - - if len(ldcRefs) == 0 { - err = JSONError{ - Code: http.StatusNotFound, - Message: "No gauge reference values found for bottleneck", - } - return - } - log.Printf("info: time interval: (%v - %v)\n", from, to) var ms availMeasurements @@ -436,101 +460,80 @@ } if len(ms) == 0 { - err = JSONError{ - Code: http.StatusNotFound, - Message: "No available fairway depth values found", + http.Error( + rw, + "No available fairway depth values found", + http.StatusNotFound, + ) + return + } + + rw.Header().Add("Content-Type", "text/csv") + + out := csv.NewWriter(rw) + + record := make([]string, 1+2+len(breaks)+1) + record[0] = "#time" + record[1] = fmt.Sprintf("#LDC < %.3f [%%]", ldcRefs[0]) + record[2] = fmt.Sprintf("#LDC >= %.3f [%%]", ldcRefs[0]) + for i, v := range breaks { + if i == 0 { + record[3] = fmt.Sprintf("#d < %.3f [%%]", v) } + record[i+4] = fmt.Sprintf("#d >= %.3f [%%]", v) + } + + if err := out.Write(record); err != nil { + // Too late for HTTP status message. + log.Printf("error: %v\n", err) return } - lnwl := ms.classify( - from, to, - ldcRefs, - (*availMeasurement).getValue, - ) + interval := intervals[mode](from, to) - afd := ms.classify( - from, to, - breaks, - (*availMeasurement).getDepth, - ) + for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { + lnwl := ms.classify( + pfrom, pto, + ldcRefs, + (*availMeasurement).getValue, + ) - duration := to.Sub(from) - lnwlPercents := durationsToPercentage(duration, lnwl) - afdPercents := durationsToPercentage(duration, afd) + afd := ms.classify( + from, to, + breaks, + (*availMeasurement).getDepth, + ) - type ldcOutput struct { - Value float64 `json:"value"` - Below float64 `json:"below"` - Above float64 `json:"above"` - } + duration := pto.Sub(pfrom) + lnwlPercents := durationsToPercentage(duration, lnwl) + afdPercents := durationsToPercentage(duration, afd) - type intervalOutput struct { - From *float64 `json:"from,omitempty"` - To *float64 `json:"to,omitempty"` - Percent float64 `json:"percent"` - } + record[0] = label + for i, v := range lnwlPercents { + record[1+i] = fmt.Sprintf("%.3f", v) + } + for i, v := range afdPercents { + record[3+i] = fmt.Sprintf("%.3f", v) + } - type output struct { - LDC []intervalOutput `json:"ldc"` - AFD []intervalOutput `json:"afd"` - } - - out := output{ - LDC: []intervalOutput{ - {To: &ldcRefs[0], Percent: lnwlPercents[0]}, - {From: &ldcRefs[0], Percent: lnwlPercents[1]}, - }, + if err := out.Write(record); err != nil { + // Too late for HTTP status message. + log.Printf("error: %v\n", err) + return + } } - for i, percent := range afdPercents { - var from, to *float64 - switch { - case i == 0: - to = &breaks[i] - case i == len(afdPercents)-1: - from = &breaks[len(breaks)-1] - default: - from = &breaks[i-1] - to = &breaks[i] - } - out.AFD = append(out.AFD, intervalOutput{ - From: from, - To: to, - Percent: percent, - }) + out.Flush() + if err := out.Error(); err != nil { + // Too late for HTTP status message. + log.Printf("error: %v\n", err) } - - jr = JSONResult{Result: &out} return } -/* - -{ "type": "below", "value": 230, "percent": 0 }, -{ "below": 250, "value": 9 } -{ "above": 250, "value": 8.1 - -*/ - -func intervalMode(mode string) int { - switch strings.ToLower(mode) { - case "monthly": - return 0 - case "quarterly": - return 1 - case "yearly": - return 2 - default: - return 0 - } -} - func bottleneckAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) { mode := intervalMode(req.FormValue("mode")) - var from, to time.Time - var los int bn := mux.Vars(req)["objnam"] if bn == "" { @@ -540,46 +543,23 @@ return } - if f := req.FormValue("from"); f != "" { - var err error - if from, err = time.Parse(common.TimeFormat, f); err != nil { - http.Error( - rw, fmt.Sprintf("Invalid format for 'from': %v.", err), - http.StatusBadRequest) - return - } - } else { - from = time.Now().AddDate(-1, 0, 0) + from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0)) + if !ok { + return } - from = from.UTC() - if t := req.FormValue("to"); t != "" { - var err error - if to, err = time.Parse(common.TimeFormat, t); err != nil { - http.Error( - rw, fmt.Sprintf("Invalid format for 'to': %v.", err), - http.StatusBadRequest) - return - } - } else { - to = from.AddDate(1, 0, 0) + to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0)) + if !ok { + return } - to = to.UTC() if to.Before(from) { to, from = from, to } - if l := req.FormValue("los"); l != "" { - var err error - if los, err = strconv.Atoi(l); err != nil { - http.Error( - rw, fmt.Sprintf("Invalid format for 'los': %v.", err), - http.StatusBadRequest) - return - } - } else { - los = 1 + los, ok := parseFormInt(rw, req, "los", 1) + if !ok { + return } conn := middleware.GetDBConn(req) @@ -636,15 +616,16 @@ out := csv.NewWriter(rw) - // label, classes, lnwl - record := make([]string, 1+1+len(breaks)+1) - record[0] = "#label" - record[1] = "# >= LDC [h]" + // label, ldc, classes + record := make([]string, 1+2+len(breaks)+1) + record[0] = "#time" + record[1] = fmt.Sprintf("#LDC < %.3f [h]", ldcRefs[0]) + record[2] = fmt.Sprintf("#LDC >= %.3f [h]", ldcRefs[0]) for i, v := range breaks { if i == 0 { - record[2] = fmt.Sprintf("# < %.2f [h]", v) + record[3] = fmt.Sprintf("# < %.3f [h]", v) } - record[i+3] = fmt.Sprintf("# >= %.2f [h]", v) + record[i+4] = fmt.Sprintf("# >= %.3f [h]", v) } if err := out.Write(record); err != nil { @@ -669,23 +650,25 @@ for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { + ldc := ms.classify( + pfrom, pto, + ldcRefs, + access, + ) + ranges := ms.classify( pfrom, pto, breaks, access, ) - ldc := ms.classify( - pfrom, pto, - ldcRefs, - access, - ) - record[0] = label - record[1] = fmt.Sprintf("%.3f", ldc[1].Hours()) + for i, v := range ldc { + record[i+1] = fmt.Sprintf("%.3f", v.Hours()) + } for i, d := range ranges { - record[2+i] = fmt.Sprintf("%.3f", d.Hours()) + record[3+i] = fmt.Sprintf("%.3f", d.Hours()) } if err := out.Write(record); err != nil { diff -r 1559566662f5 -r 3bb2d14abf8a pkg/controllers/routes.go --- a/pkg/controllers/routes.go Fri May 24 12:05:35 2019 +0200 +++ b/pkg/controllers/routes.go Fri May 24 12:15:39 2019 +0200 @@ -310,13 +310,9 @@ })).Methods(http.MethodPut) // Handler to serve data to the client. - api.Handle("/data/bottleneck/availability/{objnam}", any(&JSONHandler{ - Handle: bottleneckAvailabilty, - })).Methods(http.MethodGet) - api.Handle("/data/{kind:stretch|section}/availability/{name}", any(&JSONHandler{ - Handle: stretchAvailabilty, - })).Methods(http.MethodGet) + api.Handle("/data/{kind:stretch|section}/availability/{name}", any( + middleware.DBConn(http.HandlerFunc(stretchAvailabilty)))).Methods(http.MethodGet) api.Handle("/data/{kind:stretch|section}/fairway-depth/{name}", any( middleware.DBConn(http.HandlerFunc(stretchAvailableFairwayDepth)))).Methods(http.MethodGet) @@ -324,6 +320,9 @@ api.Handle("/data/bottleneck/fairway-depth/{objnam}", any( middleware.DBConn(http.HandlerFunc(bottleneckAvailableFairwayDepth)))).Methods(http.MethodGet) + api.Handle("/data/bottleneck/availability/{objnam}", any( + middleware.DBConn(http.HandlerFunc(bottleneckAvailabilty)))).Methods(http.MethodGet) + api.Handle("/data/waterlevels/{gauge}", any( middleware.DBConn(http.HandlerFunc(waterlevels)))).Methods(http.MethodGet) diff -r 1559566662f5 -r 3bb2d14abf8a pkg/controllers/stretches.go --- a/pkg/controllers/stretches.go Fri May 24 12:05:35 2019 +0200 +++ b/pkg/controllers/stretches.go Fri May 24 12:15:39 2019 +0200 @@ -21,12 +21,10 @@ "log" "net/http" "runtime" - "strconv" "strings" "sync" "time" - "gemma.intevation.de/gemma/pkg/common" "gemma.intevation.de/gemma/pkg/middleware" "github.com/gorilla/mux" ) @@ -163,51 +161,26 @@ name := vars["name"] mode := intervalMode(req.FormValue("mode")) - var from, to time.Time - var los int depthbreaks, widthbreaks := afdRefs, afdRefs - if f := req.FormValue("from"); f != "" { - var err error - if from, err = time.Parse(common.TimeFormat, f); err != nil { - http.Error( - rw, fmt.Sprintf("Invalid format for 'from': %v.", err), - http.StatusBadRequest) - return - } - } else { - from = time.Now().AddDate(-1, 0, 0) + from, ok := parseFormTime(rw, req, "from", time.Now().AddDate(-1, 0, 0)) + if !ok { + return } - from = from.UTC() - if t := req.FormValue("to"); t != "" { - var err error - if to, err = time.Parse(common.TimeFormat, t); err != nil { - http.Error( - rw, fmt.Sprintf("Invalid format for 'to': %v.", err), - http.StatusBadRequest) - return - } - } else { - to = from.AddDate(1, 0, 0) + to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0)) + if !ok { + return } - to = to.UTC() if to.Before(from) { to, from = from, to } - if l := req.FormValue("los"); l != "" { - var err error - if los, err = strconv.Atoi(l); err != nil { - http.Error( - rw, fmt.Sprintf("Invalid format for 'los': %v.", err), - http.StatusBadRequest) - return - } - } else { - los = 1 + los, ok := parseFormInt(rw, req, "los", 1) + if !ok { + return } conn := middleware.GetDBConn(req) @@ -220,6 +193,7 @@ http.StatusInternalServerError) return } + if len(bns) == 0 { http.Error(rw, "No bottlenecks found.", http.StatusNotFound) return @@ -441,49 +415,42 @@ return b.String() } -func stretchAvailabilty( - _ interface{}, - req *http.Request, - conn *sql.Conn, -) (jr JSONResult, err error) { +func stretchAvailabilty(rw http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) stretch := vars["kind"] == "stretch" name := vars["name"] - var from, to time.Time - var los int - - depthbreaks, widthbreaks := afdRefs, afdRefs - - if f := req.FormValue("from"); f != "" { - if from, err = parseTime(f, "from"); err != nil { - return - } - } else { - from = time.Now().AddDate(-1, 0, 0).UTC() + if name == "" { + http.Error( + rw, + fmt.Sprintf("Missing %s name", vars["kind"]), + http.StatusBadRequest, + ) + return } - if t := req.FormValue("to"); t != "" { - if to, err = parseTime(t, "to"); err != nil { - return - } - } else { - to = from.AddDate(1, 0, 0).UTC() + from, ok := parseFormTime(rw, req, "form", time.Now().AddDate(-1, 0, 0)) + if !ok { + return + } + + to, ok := parseFormTime(rw, req, "to", from.AddDate(1, 0, 0)) + if !ok { + return } if to.Before(from) { to, from = from, to } - if l := req.FormValue("los"); l != "" { - if los, err = parseInt(l, "los"); err != nil { - return - } - } else { - los = 1 + los, ok := parseFormInt(rw, req, "los", 1) + if !ok { + return } + depthbreaks, widthbreaks := afdRefs, afdRefs + if b := req.FormValue("depthbreaks"); b != "" { depthbreaks = breaksToReferenceValue(b) } @@ -492,29 +459,35 @@ widthbreaks = breaksToReferenceValue(b) } + conn := middleware.GetDBConn(req) ctx := req.Context() - var bns stretchBottlenecks - if bns, err = loadStretchBottlenecks(ctx, conn, stretch, name); err != nil { + bns, err := loadStretchBottlenecks(ctx, conn, stretch, name) + if err != nil { + http.Error( + rw, fmt.Sprintf("DB error: %v.", err), + http.StatusInternalServerError) return } if len(bns) == 0 { - err = JSONError{ - Code: http.StatusNotFound, - Message: "No bottlenecks found.", - } + http.Error( + rw, + "No bottlenecks found.", + http.StatusNotFound, + ) return } useDepth, useWidth := bns.contains("depth"), bns.contains("width") if useDepth && useWidth && len(widthbreaks) != len(depthbreaks) { - err = JSONError{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf("class breaks lengths differ: %d != %d", + http.Error( + rw, + fmt.Sprintf("class breaks lengths differ: %d != %d", len(widthbreaks), len(depthbreaks)), - } + http.StatusBadRequest, + ) return } @@ -541,10 +514,11 @@ } if len(loaded) == 0 { - err = JSONError{ - Code: http.StatusInternalServerError, - Message: fmt.Sprintf("No bottleneck loaded: %v", joinErrors(errors)), - } + http.Error( + rw, + fmt.Sprintf("No bottleneck loaded: %v", joinErrors(errors)), + http.StatusInternalServerError, + ) return } @@ -620,6 +594,11 @@ lnwlPercents := durationsToPercentage(duration, ldc) afdPercents := durationsToPercentage(duration, afd) + _ = lnwlPercents + _ = afdPercents + + /* // TODO: Rewrite this + type ldcOutput struct { Value float64 `json:"value"` Below float64 `json:"below"` @@ -666,4 +645,5 @@ jr = JSONResult{Result: &out} return + */ }