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
+}