Mercurial > gemma
diff pkg/controllers/fwa.go @ 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 |
line wrap: on
line diff
--- 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 +}