Mercurial > gemma
view pkg/controllers/bottlenecks.go @ 3019:4b9e83cf82ea
client: removed unnecessary css class
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Thu, 11 Apr 2019 17:47:04 +0200 |
parents | 7c301ff449bc |
children | 84e6577a474b |
line wrap: on
line source
// 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> package controllers import ( "database/sql" "fmt" "log" "net/http" "sort" "strconv" "strings" "time" "gemma.intevation.de/gemma/pkg/common" "github.com/gorilla/mux" ) const ( selectAvailableDepthSQL = ` WITH data AS ( SELECT efa.measure_date, efa.available_depth_value, efa.water_level_value FROM waterway.effective_fairway_availability efa JOIN waterway.fairway_availability fa ON efa.fairway_availability_id = fa.id JOIN waterway.bottlenecks bn ON fa.bottleneck_id = bn.id WHERE bn.objnam = $1 AND efa.level_of_service = $2 AND efa.measure_type = 'Measured' AND efa.available_depth_value IS NOT NULL AND efa.water_level_value IS NOT NULL ), before AS ( SELECT * FROM data WHERE measure_date < $3 ORDER BY measure_date DESC LIMIT 1 ), inside AS ( SELECT * FROM data WHERE measure_date BETWEEN $3 AND $4 ), after AS ( SELECT * FROM data WHERE measure_date > $4 ORDER BY measure_date LIMIT 1 ) SELECT * FROM before UNION ALL SELECT * FROM inside UNION ALL SELECT * FROM after ORDER BY measure_date ` selectGaugeLevelsSQL = ` 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 'HDC%' OR grwl.depth_reference like 'LDC%' OR grwl.depth_reference like 'MW%' ) ` ) type ( availReferenceValue struct { level int value int } availMeasurement struct { when time.Time depth int value int } ) func min(a, b int) int { if a < b { return a } return b } func max(a, b int) int { if a > b { return a } return b } func interpolate( m1, m2 *availMeasurement, diff time.Duration, ) int { tdiff := m2.when.Sub(m1.when) // f(0) = m1.value // f(tdiff) = m2.value // f(x) = m*x + b // m1.value = m*0 + b <=> b = m1.value // m2.value = m*tdiff + b <=> m = (m2.value - b)/tdiff // f(diff) = diff*(m2.value - m1.value)/tdiff + m1.value return int(diff*time.Duration(m2.value-m1.value)/tdiff) + m1.value } func classifyAvailMeasurements( from, to time.Time, measurements []availMeasurement, classes []availReferenceValue, ) []time.Duration { results := make([]time.Duration, len(classes)+2) if from.Before(measurements[0].when) { results[len(results)-1] = measurements[0].when.Sub(from) from = measurements[0].when } if to.After(measurements[len(measurements)-1].when) { results[len(results)-1] += to.Sub(measurements[len(measurements)-1].when) to = measurements[len(measurements)-1].when } for i := 0; i < len(measurements)-1; i++ { p1 := &measurements[i] p2 := &measurements[i+1] tdiff := p2.when.Sub(p1.when) if tdiff <= 0 { continue } if from.After(p2.when) || to.Before(p1.when) { continue } if from.After(p1.when) { tdiff2 := from.Sub(p1.when) vf := interpolate(p1, p2, tdiff2) p1 = &availMeasurement{when: from, value: vf} tdiff = p2.when.Sub(from) } if to.Before(p2.when) { tdiff2 := p2.when.Sub(to) vt := interpolate(p1, p2, tdiff2) p2 = &availMeasurement{when: to, value: vt} tdiff = p2.when.Sub(p1.when) } if max(p1.value, p2.value) <= classes[0].value { results[0] += tdiff continue } if min(p1.value, p2.value) > classes[len(classes)-1].value { results[len(results)-2] += tdiff continue } // TODO: Do the real classes. } return results } func bottleneckAvailabilty( _ interface{}, req *http.Request, conn *sql.Conn, ) (jr JSONResult, err error) { bn := mux.Vars(req)["objnam"] if bn == "" { err = JSONError{ Code: http.StatusBadRequest, Message: "Missing objnam of bottleneck", } return } ctx := req.Context() loadReferenceValues := func() ([]availReferenceValue, error) { rows, err := conn.QueryContext(ctx, selectGaugeLevelsSQL, bn) if err != nil { return nil, err } defer rows.Close() var levels []availReferenceValue loop: for rows.Next() { var what string var value int if err := rows.Scan(&what, &value); err != nil { return nil, err } var level int switch { case strings.HasPrefix(what, "LDC"): level = 0 case strings.HasPrefix(what, "MW"): level = 1 case strings.HasPrefix(what, "HDC"): level = 2 default: return nil, fmt.Errorf("Unexpected reference level type '%s'", what) } for i := range levels { if levels[i].level == level { levels[i].value = value continue loop } } levels = append(levels, availReferenceValue{level: level, value: value}) } if err := rows.Err(); err != nil { return nil, err } sort.Slice(levels, func(i, j int) bool { return levels[i].level < levels[j].level }) return levels, nil } var refVals []availReferenceValue if refVals, err = loadReferenceValues(); err != nil { return } if len(refVals) == 0 { err = JSONError{ Code: http.StatusNotFound, Message: "No gauge reference values found for bottleneck", } } var from, to time.Time if f := req.FormValue("from"); f != "" { if from, err = time.Parse(common.TimeFormat, f); err != nil { err = JSONError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Invalid time format for 'from' field: %v", err), } return } from = from.UTC() } else { from = time.Now().AddDate(-1, 0, 0).UTC() } if t := req.FormValue("to"); t != "" { if to, err = time.Parse(common.TimeFormat, t); err != nil { err = JSONError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Invalid time format for 'from' field: %v", err), } return } to = to.UTC() } else { to = from.AddDate(1, 0, 0).UTC() } if to.Before(from) { to, from = from, to } log.Printf("info: time interval: (%v - %v)\n", from, to) var los int if l := req.FormValue("los"); l != "" { if los, err = strconv.Atoi(l); err != nil { err = JSONError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Invalid value for field 'los': %v", err), } return } } else { los = 1 } loadDepthValues := func() ([]availMeasurement, error) { rows, err := conn.QueryContext( ctx, selectAvailableDepthSQL, bn, los, from, to) if err != nil { return nil, err } defer rows.Close() var ms []availMeasurement for rows.Next() { var m availMeasurement if err := rows.Scan(&m.when, &m.depth, &m.value); err != nil { return nil, err } m.when = m.when.UTC() ms = append(ms, m) } if err := rows.Err(); err != nil { return nil, err } return ms, nil } var ms []availMeasurement if ms, err = loadDepthValues(); err != nil { return } if len(ms) == 0 { err = JSONError{ Code: http.StatusNotFound, Message: "No available fairway depth values found", } return } // TODO: Calculate the ranges. type outputLevel struct { Level string `json:"level"` Value int `json:"value"` } type output struct { Levels []outputLevel `json:"levels"` } out := output{} for i := range refVals { var level string switch refVals[i].level { case 0: level = "LDC" case 1: level = "MW" case 2: level = "HDC" } out.Levels = append(out.Levels, outputLevel{ Level: level, Value: refVals[i].value, }) } jr = JSONResult{Result: &out} return }