Mercurial > gemma
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 } |