changeset 3447:3bb2d14abf8a

Merged fairway-avail-csv into default branch.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Fri, 24 May 2019 12:15:39 +0200
parents 1559566662f5 (current diff) 4a0fab451f87 (diff)
children d0278e7399fb
files
diffstat 3 files changed, 232 insertions(+), 270 deletions(-) [+]
line wrap: on
line diff
--- 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 {
--- 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)
 
--- 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
+	*/
 }