# HG changeset patch # User Sascha L. Teichmann # Date 1556530560 -7200 # Node ID ad5a00ccd2766f78476b537318077b1ff7d80522 # Parent 4dcbf9e9013c34f29264505871c11b8172d7e2be Display Available Fairway Depths: More controller code. The steppings (monthly, quarterly, yearly) through the measurements are still missing. diff -r 4dcbf9e9013c -r ad5a00ccd276 pkg/controllers/bottlenecks.go --- 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{}, "" + } +} diff -r 4dcbf9e9013c -r ad5a00ccd276 pkg/controllers/routes.go --- 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)