changeset 3119:ad5a00ccd276

Display Available Fairway Depths: More controller code. The steppings (monthly, quarterly, yearly) through the measurements are still missing.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 29 Apr 2019 11:36:00 +0200
parents 4dcbf9e9013c
children a77d0eafeccc
files pkg/controllers/bottlenecks.go pkg/controllers/routes.go
diffstat 2 files changed, 167 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/bottlenecks.go	Sun Apr 28 22:26:48 2019 +0200
+++ b/pkg/controllers/bottlenecks.go	Mon Apr 29 11:36:00 2019 +0200
@@ -16,6 +16,7 @@
 import (
 	"context"
 	"database/sql"
+	"encoding/csv"
 	"fmt"
 	"log"
 	"net/http"
@@ -27,6 +28,7 @@
 	"github.com/gorilla/mux"
 
 	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/middleware"
 )
 
 const (
@@ -80,6 +82,15 @@
   grwl.depth_reference like 'MW%'
 )
 `
+	selectGaugeLDCSQL = `
+SELECT
+  grwl.depth_reference,
+  grwl.value
+FROM waterway.gauges_reference_water_levels grwl JOIN
+     waterway.bottlenecks bns
+	 ON bns.fk_g_fid = grwl.gauge_id
+WHERE bns.objnam = $1 AND grwl.depth_reference like 'LDC%'
+`
 )
 
 type (
@@ -294,6 +305,23 @@
 	return ms, nil
 }
 
+func loadLDCReferenceValue(
+	ctx context.Context,
+	conn *sql.Conn,
+	bottleneck string,
+) ([]referenceValue, error) {
+
+	var value float64
+	err := conn.QueryRowContext(ctx, selectGaugeLDCSQL, bottleneck).Scan(&value)
+	switch {
+	case err == sql.ErrNoRows:
+		return nil, nil
+	case err != nil:
+		return nil, err
+	}
+	return []referenceValue{{0, value}}, nil
+}
+
 func loadLNWLReferenceValues(
 	ctx context.Context,
 	conn *sql.Conn,
@@ -485,18 +513,14 @@
 	return
 }
 
-func bottleneckAvailableFairwayDepth(
-	_ interface{},
-	req *http.Request,
-	conn *sql.Conn,
-) (jr JSONResult, err error) {
+func bottleneckAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) {
+
 	bn := mux.Vars(req)["objnam"]
 
 	if bn == "" {
-		err = JSONError{
-			Code:    http.StatusBadRequest,
-			Message: "Missing objnam of bottleneck",
-		}
+		http.Error(
+			rw, "Missing objnam of bottleneck",
+			http.StatusBadRequest)
 		return
 	}
 
@@ -510,10 +534,9 @@
 		case "yearly":
 			mode = 2
 		default:
-			err = JSONError{
-				Code:    http.StatusBadRequest,
-				Message: "Unknown 'mode' value",
-			}
+			http.Error(
+				rw, fmt.Sprintf("Unknown 'mode' value %s.", m),
+				http.StatusBadRequest)
 			return
 		}
 	}
@@ -521,7 +544,11 @@
 	var from, to time.Time
 
 	if f := req.FormValue("from"); f != "" {
-		if from, err = parseTime(f, "from"); err != nil {
+		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 {
@@ -529,7 +556,11 @@
 	}
 
 	if t := req.FormValue("to"); t != "" {
-		if to, err = parseTime(t, "to"); err != nil {
+		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 {
@@ -544,16 +575,131 @@
 
 	var los int
 	if l := req.FormValue("los"); l != "" {
-		if los, err = parseInt(l, "los"); err != nil {
+		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
 	}
 
-	// TODO: Implement me!
+	conn := middleware.GetDBConn(req)
+	ctx := req.Context()
+
+	// load the measurements
+	ms, err := loadDepthValues(ctx, conn, bn, los, from, to)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("Loading measurements failed: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+
+	ldcRefs, err := loadLDCReferenceValue(ctx, conn, bn)
+	if err != nil {
+		http.Error(
+			rw, fmt.Sprintf("Loading LDC failed: %v.", err),
+			http.StatusInternalServerError)
+		return
+	}
+	if len(ldcRefs) == 0 {
+		http.Error(rw, "No LDC found", http.StatusNotFound)
+		return
+	}
+
+	rw.Header().Add("Content-Type", "text/csv")
+
+	out := csv.NewWriter(rw)
+
+	// label, classes, lnwl
+	record := make([]string, 1+1+len(afdRefs)+1)
+	record[0] = "#label"
+	record[1] = "# >= LDC [h]"
+	for i, v := range afdRefs {
+		if i == 0 {
+			record[2] = fmt.Sprintf("# < %.2f [h]", v.value)
+		}
+		record[i+3] = fmt.Sprintf("# >= %.2f [h]", v.value)
+	}
+
+	if err := out.Write(record); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	interval := intervals[mode](from, to)
+
+	for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() {
+
+		// Find good starting point
+		idx := sort.Search(len(ms), func(i int) bool {
+			return !ms[i].when.After(pfrom)
+		})
+		if idx > 0 {
+			idx--
+		}
+		samples := ms[idx:]
 
-	_, _ = los, mode
+		ranges := samples.classifyAvailMeasurements(
+			pfrom, pto,
+			afdRefs,
+			(*availMeasurement).getDepth,
+		)
+
+		ldc := samples.classifyAvailMeasurements(
+			pfrom, pto,
+			ldcRefs,
+			(*availMeasurement).getDepth,
+		)
+
+		record[0] = label
+		record[1] = fmt.Sprintf("%.3f", ldc[1].Hours()/24)
+
+		for i, d := range ranges {
+			record[2+i] = fmt.Sprintf("%.3f", d.Hours()/24)
+		}
+
+		if err := out.Write(record); err != nil {
+			// Too late for HTTP status message.
+			log.Printf("error: %v\n", err)
+			return
+		}
+	}
 
-	return
+	out.Flush()
+	if err := out.Error(); err != nil {
+		// Too late for HTTP status message.
+		log.Printf("error: %v\n", err)
+	}
+}
+
+var intervals = []func(time.Time, time.Time) func() (time.Time, time.Time, string){
+	monthly,
+	quarterly,
+	yearly,
 }
+
+func monthly(from, to time.Time) func() (time.Time, time.Time, string) {
+	return func() (time.Time, time.Time, string) {
+		// TODO: Implement me!
+		return time.Time{}, time.Time{}, ""
+	}
+}
+
+func quarterly(from, to time.Time) func() (time.Time, time.Time, string) {
+	return func() (time.Time, time.Time, string) {
+		// TODO: Implement me!
+		return time.Time{}, time.Time{}, ""
+	}
+}
+
+func yearly(from, to time.Time) func() (time.Time, time.Time, string) {
+	return func() (time.Time, time.Time, string) {
+		// TODO: Implement me!
+		return time.Time{}, time.Time{}, ""
+	}
+}
--- a/pkg/controllers/routes.go	Sun Apr 28 22:26:48 2019 +0200
+++ b/pkg/controllers/routes.go	Mon Apr 29 11:36:00 2019 +0200
@@ -307,9 +307,8 @@
 		Handle: bottleneckAvailabilty,
 	})).Methods(http.MethodGet)
 
-	api.Handle("/data/bottleneck/fairway-depth/{objnam}", any(&JSONHandler{
-		Handle: bottleneckAvailableFairwayDepth,
-	})).Methods(http.MethodGet)
+	api.Handle("/data/bottleneck/fairway-depth/{objnam}", any(
+		middleware.DBConn(http.HandlerFunc(bottleneckAvailableFairwayDepth)))).Methods(http.MethodGet)
 
 	api.Handle("/data/waterlevels/{gauge}", any(
 		middleware.DBConn(http.HandlerFunc(waterlevels)))).Methods(http.MethodGet)