changeset 3439:d7ddb21f7017 fairway-avail-csv

Prepared to write the fairway availibilty as CSV, too. WIP.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Fri, 24 May 2019 11:20:15 +0200
parents df6c2973f791
children e07b18f2482e
files pkg/controllers/bottlenecks.go pkg/controllers/routes.go pkg/controllers/stretches.go
diffstat 3 files changed, 164 insertions(+), 201 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/bottlenecks.go	Fri May 24 11:15:58 2019 +0200
+++ b/pkg/controllers/bottlenecks.go	Fri May 24 11:20:15 2019 +0200
@@ -256,29 +256,46 @@
 	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 loadDepthValues(
@@ -360,71 +377,60 @@
 	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
 	}
 
-	if b := req.FormValue("breaks"); b != "" {
-		breaks = breaksToReferenceValue(b)
-	} else {
-		breaks = afdRefs
-	}
-
+	conn := middleware.GetDBConn(req)
 	ctx := req.Context()
 
-	if ldcRefs, err = loadLDCReferenceValue(ctx, conn, bn); err != nil {
+	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 {
-		err = JSONError{
-			Code:    http.StatusNotFound,
-			Message: "No gauge reference values found for bottleneck",
-		}
+		http.Error(
+			rw,
+			"No gauge reference values found for bottleneck",
+			http.StatusNotFound,
+		)
 		return
 	}
 
@@ -436,28 +442,38 @@
 	}
 
 	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
 	}
 
-	lnwl := ms.classify(
-		from, to,
-		ldcRefs,
-		(*availMeasurement).getValue,
-	)
+	interval := intervals[mode](from, to)
+
+	for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() {
+		lnwl := ms.classify(
+			pfrom, pto,
+			ldcRefs,
+			(*availMeasurement).getValue,
+		)
 
-	afd := ms.classify(
-		from, to,
-		breaks,
-		(*availMeasurement).getDepth,
-	)
+		afd := ms.classify(
+			from, to,
+			breaks,
+			(*availMeasurement).getDepth,
+		)
 
-	duration := to.Sub(from)
-	lnwlPercents := durationsToPercentage(duration, lnwl)
-	afdPercents := durationsToPercentage(duration, afd)
+		duration := pto.Sub(pfrom)
+		lnwlPercents := durationsToPercentage(duration, lnwl)
+		afdPercents := durationsToPercentage(duration, afd)
+
+		_ = lnwlPercents
+		_ = afdPercents
+	}
+
+	/* // TODO: Rewrite this
 
 	type ldcOutput struct {
 		Value float64 `json:"value"`
@@ -502,17 +518,10 @@
 	}
 
 	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":
@@ -529,8 +538,6 @@
 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 +547,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)
--- a/pkg/controllers/routes.go	Fri May 24 11:15:58 2019 +0200
+++ b/pkg/controllers/routes.go	Fri May 24 11:20:15 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)
 
--- a/pkg/controllers/stretches.go	Fri May 24 11:15:58 2019 +0200
+++ b/pkg/controllers/stretches.go	Fri May 24 11:20:15 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
+	*/
 }