comparison 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
comparison
equal deleted inserted replaced
5202:fbc79c8459b4 5203:355195a90298
17 "context" 17 "context"
18 "database/sql" 18 "database/sql"
19 "fmt" 19 "fmt"
20 "log" 20 "log"
21 "net/http" 21 "net/http"
22 "sort"
22 "time" 23 "time"
23 24
24 "github.com/gorilla/mux" 25 "github.com/gorilla/mux"
25 26
26 "gemma.intevation.de/gemma/pkg/common" 27 "gemma.intevation.de/gemma/pkg/common"
108 upper time.Time 109 upper time.Time
109 } 110 }
110 111
111 ldc struct { 112 ldc struct {
112 timeRange 113 timeRange
113 value float64 114 value []float64
114 } 115 }
116
117 ldcs []*ldc
115 118
116 limitingValidity struct { 119 limitingValidity struct {
117 timeRange 120 timeRange
118 limiting string 121 limiting func(*availMeasurement) float64
119 ldcs []*ldc 122 ldcs ldcs
120 } 123 }
121 124
122 limitingValidities []limitingValidity 125 limitingValidities []limitingValidity
123 126
124 availMeasurement struct { 127 availMeasurement struct {
134 id string 137 id string
135 validities limitingValidities 138 validities limitingValidities
136 measurements availMeasurements 139 measurements availMeasurements
137 } 140 }
138 ) 141 )
142
143 func (ls ldcs) find(from, to time.Time) *ldc {
144 for _, l := range ls {
145 if l.intersects(from, to) {
146 return l
147 }
148 }
149 return nil
150 }
139 151
140 func fairwayAvailability(rw http.ResponseWriter, req *http.Request) { 152 func fairwayAvailability(rw http.ResponseWriter, req *http.Request) {
141 153
142 from, to, ok := parseFromTo(rw, req) 154 from, to, ok := parseFromTo(rw, req)
143 if !ok { 155 if !ok {
201 return 213 return
202 } 214 }
203 215
204 } 216 }
205 217
206 // TODO: Implement me! 218 // For every day on every bottleneck we need to find out if this day is valid.
219 validities := make([]func(time.Time, time.Time) *limitingValidity, len(bottlenecks))
220 for i := range bottlenecks {
221 validities[i] = bottlenecks[i].validities.find()
222 }
223
224 var shipableDays int
225
226 // We step through the time in steps of one day.
227 for current := from; current.Before(to); {
228
229 next := current.AddDate(0, 0, 1)
230
231 shipable := true
232
233 // over all bottlenecks
234 for i, validity := range validities {
235
236 if vs := validity(current, next); vs != nil {
237
238 // Let's see if we have a LDC for this day.
239 ldc := vs.ldcs.find(current, next)
240 if ldc == nil {
241 // TODO: log missing LCD
242 continue
243 }
244
245 result := bottlenecks[i].measurements.classify(
246 current, next,
247 ldc.value,
248 vs.limiting)
249
250 if result[1] < 12*time.Hour {
251 shipable = false
252 break
253 }
254 }
255 }
256
257 if shipable {
258 shipableDays++
259 }
260 // TODO: depending on mode write out results.
261
262 current = next
263 }
207 } 264 }
208 265
209 func dusk(t time.Time) time.Time { 266 func dusk(t time.Time) time.Time {
210 return time.Date( 267 return time.Date(
211 t.Year(), 268 t.Year(),
268 } 325 }
269 return nil 326 return nil
270 } 327 }
271 } 328 }
272 329
330 func limitingFactor(limiting string) func(*availMeasurement) float64 {
331 switch limiting {
332 case "depth":
333 return (*availMeasurement).getDepth
334 case "width":
335 return (*availMeasurement).getWidth
336 default:
337 log.Printf("warn: unknown limitation '%s'. default to 'depth'\n", limiting)
338 return (*availMeasurement).getDepth
339 }
340 }
341
273 func loadLimitingValidities( 342 func loadLimitingValidities(
274 ctx context.Context, 343 ctx context.Context,
275 conn *sql.Conn, 344 conn *sql.Conn,
276 bottleneckID string, 345 bottleneckID string,
277 from, to time.Time, 346 from, to time.Time,
289 } 358 }
290 defer rows.Close() 359 defer rows.Close()
291 360
292 for rows.Next() { 361 for rows.Next() {
293 var lv limitingValidity 362 var lv limitingValidity
363 var access string
294 if err := rows.Scan( 364 if err := rows.Scan(
295 &lv.limiting, 365 &access,
296 &lv.lower, 366 &lv.lower,
297 &lv.upper, 367 &lv.upper,
298 ); err != nil { 368 ); err != nil {
299 return nil, err 369 return nil, err
300 } 370 }
301 lv.toUTC() 371 lv.toUTC()
372 lv.limiting = limitingFactor(access)
302 lvs = append(lvs, lv) 373 lvs = append(lvs, lv)
303 } 374 }
304 375
305 return lvs, rows.Err() 376 return lvs, rows.Err()
306 } 377 }
398 if err != nil { 469 if err != nil {
399 return err 470 return err
400 } 471 }
401 defer rows.Close() 472 defer rows.Close()
402 for rows.Next() { 473 for rows.Next() {
403 var l ldc 474 l := ldc{value: []float64{0}}
404 if err := rows.Scan(&l.lower, &l.upper, &l.value); err != nil { 475 if err := rows.Scan(&l.lower, &l.upper, &l.value[0]); err != nil {
405 return err 476 return err
406 } 477 }
407 l.toUTC() 478 l.toUTC()
408 for i := range bn.validities { 479 for i := range bn.validities {
409 vs := bn.validities[i] 480 vs := bn.validities[i]
450 return err 521 return err
451 } 522 }
452 bn.measurements = ms 523 bn.measurements = ms
453 return nil 524 return nil
454 } 525 }
526
527 func (measurement *availMeasurement) getDepth() float64 {
528 return float64(measurement.depth)
529 }
530
531 func (measurement *availMeasurement) getValue() float64 {
532 return float64(measurement.value)
533 }
534
535 func (measurement *availMeasurement) getWidth() float64 {
536 return float64(measurement.width)
537 }
538
539 func (measurements availMeasurements) classify(
540 from, to time.Time,
541 breaks []float64,
542 access func(*availMeasurement) float64,
543 ) []time.Duration {
544
545 if len(breaks) == 0 {
546 return []time.Duration{}
547 }
548
549 result := make([]time.Duration, len(breaks)+1)
550 classes := make([]float64, len(breaks)+2)
551 values := make([]time.Time, len(classes))
552
553 // Add sentinels
554 classes[0] = breaks[0] - 9999
555 classes[len(classes)-1] = breaks[len(breaks)-1] + 9999
556 for i := range breaks {
557 classes[i+1] = breaks[i]
558 }
559
560 idx := sort.Search(len(measurements), func(i int) bool {
561 // All values before from can be ignored.
562 return !measurements[i].when.Before(from)
563 })
564
565 if idx >= len(measurements) {
566 return result
567 }
568
569 // Be safe for interpolation.
570 if idx > 0 {
571 idx--
572 }
573
574 measurements = measurements[idx:]
575
576 for i := 0; i < len(measurements)-1; i++ {
577 p1 := &measurements[i]
578 p2 := &measurements[i+1]
579
580 if p1.when.After(to) {
581 return result
582 }
583
584 if p2.when.Before(from) {
585 continue
586 }
587
588 if p2.when.Sub(p1.when).Hours() > 1.5 {
589 // Don't interpolate ranges bigger then one and a half hour
590 continue
591 }
592
593 lo, hi := common.MaxTime(p1.when, from), common.MinTime(p2.when, to)
594
595 m1, m2 := access(p1), access(p2)
596 if m1 == m2 { // The whole interval is in only one class.
597 for j := 0; j < len(classes)-1; j++ {
598 if classes[j] <= m1 && m1 <= classes[j+1] {
599 result[j] += hi.Sub(lo)
600 break
601 }
602 }
603 continue
604 }
605
606 f := common.InterpolateTime(
607 p1.when, m1,
608 p2.when, m2,
609 )
610
611 for j, c := range classes {
612 values[j] = f(c)
613 }
614
615 for j := 0; j < len(values)-1; j++ {
616 start, end := common.OrderTime(values[j], values[j+1])
617
618 if start.After(hi) || end.Before(lo) {
619 continue
620 }
621
622 start, end = common.MaxTime(start, lo), common.MinTime(end, hi)
623 result[j] += end.Sub(start)
624 }
625 }
626
627 return result
628 }