Mercurial > gemma
changeset 5203:355195a90298 new-fwa
Start calculting the navigability. TODO: accumulate and do output.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Fri, 08 May 2020 18:59:14 +0200 |
parents | fbc79c8459b4 |
children | 7ca9e6c9a203 |
files | pkg/controllers/bottlenecks.go pkg/controllers/fwa.go |
diffstat | 2 files changed, 181 insertions(+), 123 deletions(-) [+] |
line wrap: on
line diff
--- a/pkg/controllers/bottlenecks.go Fri May 08 15:59:44 2020 +0200 +++ b/pkg/controllers/bottlenecks.go Fri May 08 18:59:14 2020 +0200 @@ -21,7 +21,6 @@ "fmt" "log" "net/http" - "sort" "strconv" "strings" "time" @@ -98,30 +97,6 @@ 250, } -func (measurement *availMeasurement) getDepth() float64 { - return float64(measurement.depth) -} - -func (measurement *availMeasurement) getValue() float64 { - return float64(measurement.value) -} - -func (measurement *availMeasurement) getWidth() float64 { - return float64(measurement.width) -} - -func limitingFactor(limiting string) func(*availMeasurement) float64 { - switch limiting { - case "depth": - return (*availMeasurement).getDepth - case "width": - return (*availMeasurement).getWidth - default: - log.Printf("warn: unknown limitation '%s'. default to 'depth'\n", limiting) - return (*availMeasurement).getDepth - } -} - // According to clarification, it has to be assumed, that at times // with no data, the best case (which by convention is the highest // class created by classify()) should be assumed. That is due to the @@ -156,97 +131,6 @@ return classified } -func (measurements availMeasurements) classify( - from, to time.Time, - breaks []float64, - access func(*availMeasurement) float64, -) []time.Duration { - - if len(breaks) == 0 { - return []time.Duration{} - } - - result := make([]time.Duration, len(breaks)+1) - classes := make([]float64, len(breaks)+2) - values := make([]time.Time, len(classes)) - - // Add sentinels - classes[0] = breaks[0] - 9999 - classes[len(classes)-1] = breaks[len(breaks)-1] + 9999 - for i := range breaks { - classes[i+1] = breaks[i] - } - - idx := sort.Search(len(measurements), func(i int) bool { - // All values before from can be ignored. - return !measurements[i].when.Before(from) - }) - - if idx >= len(measurements) { - return optimisticPadClassification(from, to, result) - } - - // Be safe for interpolation. - if idx > 0 { - idx-- - } - - measurements = measurements[idx:] - - for i := 0; i < len(measurements)-1; i++ { - p1 := &measurements[i] - p2 := &measurements[i+1] - - if p1.when.After(to) { - return optimisticPadClassification(from, to, result) - } - - if p2.when.Before(from) { - continue - } - - if p2.when.Sub(p1.when).Hours() > 1.5 { - // Don't interpolate ranges bigger then one and a half hour - continue - } - - lo, hi := common.MaxTime(p1.when, from), common.MinTime(p2.when, to) - - m1, m2 := access(p1), access(p2) - if m1 == m2 { // The whole interval is in only one class. - for j := 0; j < len(classes)-1; j++ { - if classes[j] <= m1 && m1 <= classes[j+1] { - result[j] += hi.Sub(lo) - break - } - } - continue - } - - f := common.InterpolateTime( - p1.when, m1, - p2.when, m2, - ) - - for j, c := range classes { - values[j] = f(c) - } - - for j := 0; j < len(values)-1; j++ { - start, end := common.OrderTime(values[j], values[j+1]) - - if start.After(hi) || end.Before(lo) { - continue - } - - start, end = common.MaxTime(start, lo), common.MinTime(end, hi) - result[j] += end.Sub(start) - } - } - - return optimisticPadClassification(from, to, result) -} - func durationsToPercentage(duration time.Duration, classes []time.Duration) []float64 { percents := make([]float64, len(classes)) total := 100 / duration.Seconds()
--- a/pkg/controllers/fwa.go Fri May 08 15:59:44 2020 +0200 +++ b/pkg/controllers/fwa.go Fri May 08 18:59:14 2020 +0200 @@ -19,6 +19,7 @@ "fmt" "log" "net/http" + "sort" "time" "github.com/gorilla/mux" @@ -110,13 +111,15 @@ ldc struct { timeRange - value float64 + value []float64 } + ldcs []*ldc + limitingValidity struct { timeRange - limiting string - ldcs []*ldc + limiting func(*availMeasurement) float64 + ldcs ldcs } limitingValidities []limitingValidity @@ -137,6 +140,15 @@ } ) +func (ls ldcs) find(from, to time.Time) *ldc { + for _, l := range ls { + if l.intersects(from, to) { + return l + } + } + return nil +} + func fairwayAvailability(rw http.ResponseWriter, req *http.Request) { from, to, ok := parseFromTo(rw, req) @@ -203,7 +215,52 @@ } - // TODO: Implement me! + // For every day on every bottleneck we need to find out if this day is valid. + validities := make([]func(time.Time, time.Time) *limitingValidity, len(bottlenecks)) + for i := range bottlenecks { + validities[i] = bottlenecks[i].validities.find() + } + + var shipableDays int + + // We step through the time in steps of one day. + for current := from; current.Before(to); { + + next := current.AddDate(0, 0, 1) + + shipable := true + + // over all bottlenecks + for i, validity := range validities { + + if vs := validity(current, next); vs != nil { + + // Let's see if we have a LDC for this day. + ldc := vs.ldcs.find(current, next) + if ldc == nil { + // TODO: log missing LCD + continue + } + + result := bottlenecks[i].measurements.classify( + current, next, + ldc.value, + vs.limiting) + + if result[1] < 12*time.Hour { + shipable = false + break + } + } + } + + if shipable { + shipableDays++ + } + // TODO: depending on mode write out results. + + current = next + } } func dusk(t time.Time) time.Time { @@ -270,6 +327,18 @@ } } +func limitingFactor(limiting string) func(*availMeasurement) float64 { + switch limiting { + case "depth": + return (*availMeasurement).getDepth + case "width": + return (*availMeasurement).getWidth + default: + log.Printf("warn: unknown limitation '%s'. default to 'depth'\n", limiting) + return (*availMeasurement).getDepth + } +} + func loadLimitingValidities( ctx context.Context, conn *sql.Conn, @@ -291,14 +360,16 @@ for rows.Next() { var lv limitingValidity + var access string if err := rows.Scan( - &lv.limiting, + &access, &lv.lower, &lv.upper, ); err != nil { return nil, err } lv.toUTC() + lv.limiting = limitingFactor(access) lvs = append(lvs, lv) } @@ -400,8 +471,8 @@ } defer rows.Close() for rows.Next() { - var l ldc - if err := rows.Scan(&l.lower, &l.upper, &l.value); err != nil { + l := ldc{value: []float64{0}} + if err := rows.Scan(&l.lower, &l.upper, &l.value[0]); err != nil { return err } l.toUTC() @@ -452,3 +523,106 @@ bn.measurements = ms return nil } + +func (measurement *availMeasurement) getDepth() float64 { + return float64(measurement.depth) +} + +func (measurement *availMeasurement) getValue() float64 { + return float64(measurement.value) +} + +func (measurement *availMeasurement) getWidth() float64 { + return float64(measurement.width) +} + +func (measurements availMeasurements) classify( + from, to time.Time, + breaks []float64, + access func(*availMeasurement) float64, +) []time.Duration { + + if len(breaks) == 0 { + return []time.Duration{} + } + + result := make([]time.Duration, len(breaks)+1) + classes := make([]float64, len(breaks)+2) + values := make([]time.Time, len(classes)) + + // Add sentinels + classes[0] = breaks[0] - 9999 + classes[len(classes)-1] = breaks[len(breaks)-1] + 9999 + for i := range breaks { + classes[i+1] = breaks[i] + } + + idx := sort.Search(len(measurements), func(i int) bool { + // All values before from can be ignored. + return !measurements[i].when.Before(from) + }) + + if idx >= len(measurements) { + return result + } + + // Be safe for interpolation. + if idx > 0 { + idx-- + } + + measurements = measurements[idx:] + + for i := 0; i < len(measurements)-1; i++ { + p1 := &measurements[i] + p2 := &measurements[i+1] + + if p1.when.After(to) { + return result + } + + if p2.when.Before(from) { + continue + } + + if p2.when.Sub(p1.when).Hours() > 1.5 { + // Don't interpolate ranges bigger then one and a half hour + continue + } + + lo, hi := common.MaxTime(p1.when, from), common.MinTime(p2.when, to) + + m1, m2 := access(p1), access(p2) + if m1 == m2 { // The whole interval is in only one class. + for j := 0; j < len(classes)-1; j++ { + if classes[j] <= m1 && m1 <= classes[j+1] { + result[j] += hi.Sub(lo) + break + } + } + continue + } + + f := common.InterpolateTime( + p1.when, m1, + p2.when, m2, + ) + + for j, c := range classes { + values[j] = f(c) + } + + for j := 0; j < len(values)-1; j++ { + start, end := common.OrderTime(values[j], values[j+1]) + + if start.After(hi) || end.Before(lo) { + continue + } + + start, end = common.MaxTime(start, lo), common.MinTime(end, hi) + result[j] += end.Sub(start) + } + } + + return result +}