Mercurial > gemma
changeset 5222:0ef27f10f9e2 new-fwa
Removed old controller code for stretches and sections.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Tue, 12 May 2020 10:40:20 +0200 |
parents | 6794e85dc2dd |
children | d4e4f7232513 |
files | pkg/controllers/stretches.go |
diffstat | 1 files changed, 0 insertions(+), 665 deletions(-) [+] |
line wrap: on
line diff
--- a/pkg/controllers/stretches.go Tue May 12 10:29:25 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,665 +0,0 @@ -// This is Free Software under GNU Affero General Public License v >= 3.0 -// without warranty, see README.md and license for details. -// -// SPDX-License-Identifier: AGPL-3.0-or-later -// License-Filename: LICENSES/AGPL-3.0.txt -// -// Copyright (C) 2019 by via donau -// – Österreichische Wasserstraßen-Gesellschaft mbH -// Software engineering by Intevation GmbH -// -// Author(s): -// * Sascha L. Teichmann <sascha.teichmann@intevation.de> -// * Sascha Wilde <wilde@intevation.de> - -package controllers - -import ( - "context" - "database/sql" - "encoding/csv" - "fmt" - "log" - "net/http" - "runtime" - "sync" - "time" - - "github.com/gorilla/mux" - - "gemma.intevation.de/gemma/pkg/common" - "gemma.intevation.de/gemma/pkg/middleware" -) - -// The following requests are taking _all_ bottlenecks into account, not only -// the currently valid ones. This is neccessary, as we are doing reports on -// arbitrary time ranges and bottlenecks currently active might have been in the -// selected time range. -// -// FIXME: the better solution would be to limit the bottlenecks to those with: -// b.validity && REQUESTED_TIME_RANGE - -const ( - selectSectionBottlenecks = ` -SELECT - distinct(b.objnam), - b.limiting -FROM waterway.sections s, waterway.bottlenecks b -WHERE ST_Intersects(b.area, s.area) - AND s.name = $1` - - selectStretchBottlenecks = ` -SELECT - distinct(b.objnam), - b.limiting -FROM users.stretches s, waterway.bottlenecks b -WHERE ST_Intersects(b.area, s.area) - AND s.name = $1` -) - -type ( - stretchBottleneck struct { - name string - limiting string - } - - stretchBottlenecks []stretchBottleneck - - fullStretchBottleneck struct { - *stretchBottleneck - measurements availMeasurements - ldc []float64 - breaks []float64 - access func(*availMeasurement) float64 - } -) - -func (bns stretchBottlenecks) contains(limiting string) bool { - for i := range bns { - if bns[i].limiting == limiting { - return true - } - } - return false -} - -func maxDuration(a time.Duration, b time.Duration) time.Duration { - if a > b { - return a - } - return b -} - -func sumClassesTo(breaks []time.Duration, to int) time.Duration { - var result time.Duration - for i := 0; i <= to; i++ { - result += breaks[i] - } - return result -} - -func aggregateClasses( - new []time.Duration, - agg []time.Duration, -) []time.Duration { - newAgg := make([]time.Duration, len(agg)) - - for i := 0; i < len(new)-1; i++ { - oldSum := sumClassesTo(agg, i) - newSum := sumClassesTo(new, i) - newAgg[i] = maxDuration(newSum, oldSum) - sumClassesTo(newAgg, i-1) - } - // adjust highest class so the sum of all classes in agg - // matches the original sum of all classes in new. - newAgg[len(new)-1] = - sumClassesTo(new, len(new)-1) - sumClassesTo(newAgg, len(new)-2) - return newAgg -} - -func loadFullStretchBottleneck( - ctx context.Context, - conn *sql.Conn, - bn *stretchBottleneck, - los int, - from, to time.Time, - depthbreaks, widthbreaks []float64, -) (*fullStretchBottleneck, error) { - measurements, err := loadDepthValues(ctx, conn, bn.name, los, from, to) - if err != nil { - return nil, err - } - ldc, err := loadLDCReferenceValue(ctx, conn, bn.name) - if err != nil { - return nil, err - } - - if len(ldc) == 0 { - return nil, fmt.Errorf("no LDC found for bottleneck: %s", bn.name) - } - - var access func(*availMeasurement) float64 - var breaks []float64 - - switch bn.limiting { - case "width": - access = (*availMeasurement).getWidth - breaks = widthbreaks - case "depth": - access = (*availMeasurement).getDepth - breaks = depthbreaks - default: - log.Printf( - "warn: unknown limitation '%s'. default to 'depth'.\n", - bn.limiting) - access = (*availMeasurement).getDepth - breaks = depthbreaks - } - - return &fullStretchBottleneck{ - stretchBottleneck: bn, - measurements: measurements, - ldc: ldc, - breaks: breaks, - access: access, - }, nil -} - -func loadStretchBottlenecks( - ctx context.Context, - conn *sql.Conn, - stretch bool, - name string, -) (stretchBottlenecks, error) { - var sql string - if stretch { - sql = selectStretchBottlenecks - } else { - sql = selectSectionBottlenecks - } - - rows, err := conn.QueryContext(ctx, sql, name) - if err != nil { - return nil, err - } - defer rows.Close() - - var bns stretchBottlenecks - - for rows.Next() { - var bn stretchBottleneck - if err := rows.Scan( - &bn.name, - &bn.limiting, - ); err != nil { - return nil, err - } - bns = append(bns, bn) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return bns, nil -} - -func stretchAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) { - - vars := mux.Vars(req) - stretch := vars["kind"] == "stretch" - name := vars["name"] - mode := parseFWAMode(req.FormValue("mode")) - - depthbreaks, widthbreaks := afdRefs, afdRefs - - from, ok := parseFormTime(rw, req, "from", 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 - } - - los, ok := parseFormInt(rw, req, "los", 1) - if !ok { - return - } - - conn := middleware.GetDBConn(req) - ctx := req.Context() - - 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 { - http.Error(rw, "No bottlenecks found.", http.StatusNotFound) - return - } - - if b := req.FormValue("depthbreaks"); b != "" { - depthbreaks = breaksToReferenceValue(b) - } - - if b := req.FormValue("widthbreaks"); b != "" { - widthbreaks = breaksToReferenceValue(b) - } - - useDepth, useWidth := bns.contains("depth"), bns.contains("width") - - if useDepth && useWidth && len(widthbreaks) != len(depthbreaks) { - http.Error( - rw, - fmt.Sprintf("class breaks lengths differ: %d != %d", - len(widthbreaks), len(depthbreaks)), - http.StatusBadRequest, - ) - return - } - - log.Printf("info: time interval: (%v - %v)\n", from, to) - - var loaded []*fullStretchBottleneck - var errors []error - - for i := range bns { - l, err := loadFullStretchBottleneck( - ctx, - conn, - &bns[i], - los, - from, to, - depthbreaks, widthbreaks, - ) - if err != nil { - log.Printf("error: %v\n", err) - errors = append(errors, err) - continue - } - loaded = append(loaded, l) - } - - if len(loaded) == 0 { - http.Error( - rw, - fmt.Sprintf("No bottleneck loaded: %v", common.JoinErrors(errors)), - http.StatusInternalServerError, - ) - return - } - - n := runtime.NumCPU() / 2 - if n == 0 { - n = 1 - } - - type result struct { - label string - from time.Time - to time.Time - ldc []time.Duration - breaks []time.Duration - } - - jobCh := make(chan *result) - - var wg sync.WaitGroup - - for i := 0; i < n; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for res := range jobCh { - - var ldc, breaks []time.Duration - - now := time.Now() - for _, bn := range loaded { - // Don't interpolate for the future - if now.Sub(res.to) < 0 { - res.to = now - } - - l := bn.measurements.classify( - res.from, res.to, - bn.ldc, - (*availMeasurement).getValue, - ) - b := bn.measurements.classify( - res.from, res.to, - bn.breaks, - bn.access, - ) - - if ldc == nil { - ldc, breaks = l, b - } else { - ldc = aggregateClasses(l, ldc) - breaks = aggregateClasses(b, breaks) - } - } - - res.ldc = ldc - res.breaks = breaks - } - }() - } - - var results []*result - - interval := intervals[mode](from, to) - - var breaks []float64 - - if useDepth { - breaks = depthbreaks - } else { - breaks = widthbreaks - } - - for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { - - res := &result{ - label: label, - from: pfrom, - to: pto, - } - results = append(results, res) - jobCh <- res - } - - close(jobCh) - wg.Wait() - - rw.Header().Add("Content-Type", "text/csv") - - out := csv.NewWriter(rw) - - // label, lnwl, classes - record := makeHeader(useDepth && useWidth, 3, breaks, 'd') - - if err := out.Write(record); err != nil { - // Too late for HTTP status message. - log.Printf("error: %v\n", err) - return - } - - empty := fmt.Sprintf("%.3f", 0.0) - for i := range record[1:] { - record[i+1] = empty - } - - for _, r := range results { - // Round to full days - ldcRounded := common.RoundToFullDays(r.ldc) - rangesRounded := common.RoundToFullDays(r.breaks) - - record[0] = r.label - for i, v := range ldcRounded { - record[1+i] = fmt.Sprintf("%d", v) - } - - for i, d := range rangesRounded { - record[3+i] = fmt.Sprintf("%d", d) - } - - if err := out.Write(record); err != nil { - // Too late for HTTP status message. - log.Printf("error: %v\n", err) - return - } - } - - out.Flush() - if err := out.Error(); err != nil { - // Too late for HTTP status message. - log.Printf("error: %v\n", err) - } -} - -func stretchAvailabilty(rw http.ResponseWriter, req *http.Request) { - - vars := mux.Vars(req) - stretch := vars["kind"] == "stretch" - name := vars["name"] - mode := parseFWAMode(req.FormValue("mode")) - - if name == "" { - http.Error( - rw, - fmt.Sprintf("Missing %s name", vars["kind"]), - http.StatusBadRequest, - ) - return - } - - from, ok := parseFormTime(rw, req, "from", 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 - } - - los, ok := parseFormInt(rw, req, "los", 1) - if !ok { - return - } - - depthbreaks, widthbreaks := afdRefs, afdRefs - - if b := req.FormValue("depthbreaks"); b != "" { - depthbreaks = breaksToReferenceValue(b) - } - - if b := req.FormValue("widthbreaks"); b != "" { - widthbreaks = breaksToReferenceValue(b) - } - - conn := middleware.GetDBConn(req) - ctx := req.Context() - - 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 { - http.Error( - rw, - "No bottlenecks found.", - http.StatusNotFound, - ) - return - } - - useDepth, useWidth := bns.contains("depth"), bns.contains("width") - - if useDepth && useWidth && len(widthbreaks) != len(depthbreaks) { - http.Error( - rw, - fmt.Sprintf("class breaks lengths differ: %d != %d", - len(widthbreaks), len(depthbreaks)), - http.StatusBadRequest, - ) - return - } - - log.Printf("info: time interval: (%v - %v)\n", from, to) - - var loaded []*fullStretchBottleneck - var errors []error - - for i := range bns { - l, err := loadFullStretchBottleneck( - ctx, - conn, - &bns[i], - los, - from, to, - depthbreaks, widthbreaks, - ) - if err != nil { - log.Printf("error: %v\n", err) - errors = append(errors, err) - continue - } - loaded = append(loaded, l) - } - - if len(loaded) == 0 { - http.Error( - rw, - fmt.Sprintf("No bottleneck loaded: %v", common.JoinErrors(errors)), - http.StatusInternalServerError, - ) - return - } - - n := runtime.NumCPU() / 2 - if n == 0 { - n = 1 - } - - type result struct { - label string - from time.Time - to time.Time - ldc []float64 - breaks []float64 - } - - jobCh := make(chan *result) - - var wg sync.WaitGroup - - for i := 0; i < n; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for res := range jobCh { - var ldc, breaks []time.Duration - - now := time.Now() - for _, bn := range loaded { - // Don't interpolate for the future - if now.Sub(res.to) < 0 { - res.to = now - } - - l := bn.measurements.classify( - res.from, res.to, - bn.ldc, - (*availMeasurement).getValue, - ) - - b := bn.measurements.classify( - res.from, res.to, - bn.breaks, - bn.access, - ) - - if ldc == nil { - ldc, breaks = l, b - } else { - ldc = aggregateClasses(l, ldc) - breaks = aggregateClasses(b, breaks) - } - } - - duration := res.to.Sub(res.from) - - res.ldc = durationsToPercentage(duration, ldc) - res.breaks = durationsToPercentage(duration, breaks) - } - }() - } - - var results []*result - - interval := intervals[mode](from, to) - - var breaks []float64 - - if useDepth { - breaks = depthbreaks - } else { - breaks = widthbreaks - } - - for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { - - res := &result{ - label: label, - from: pfrom, - to: pto, - } - results = append(results, res) - - jobCh <- res - } - - close(jobCh) - wg.Wait() - - rw.Header().Add("Content-Type", "text/csv") - - out := csv.NewWriter(rw) - - // label, lnwl, classes - record := makeHeader(useDepth && useWidth, 3, breaks, '%') - - if err := out.Write(record); err != nil { - // Too late for HTTP status message. - log.Printf("error: %v\n", err) - return - } - - empty := fmt.Sprintf("%.3f", 0.0) - for i := range record[1:] { - record[i+1] = empty - } - - for _, res := range results { - record[0] = res.label - - for i, v := range res.ldc { - record[1+i] = fmt.Sprintf("%.3f", v) - } - - for i, v := range res.breaks { - record[3+i] = fmt.Sprintf("%.3f", v) - } - - if err := out.Write(record); err != nil { - // Too late for HTTP status message. - log.Printf("error: %v\n", err) - return - } - } - - out.Flush() - if err := out.Error(); err != nil { - // Too late for HTTP status message. - log.Printf("error: %v\n", err) - } -}