Mercurial > gemma
comparison pkg/controllers/bottlenecks.go @ 3119:ad5a00ccd276
Display Available Fairway Depths: More controller code.
The steppings (monthly, quarterly, yearly) through the measurements are still missing.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 29 Apr 2019 11:36:00 +0200 |
parents | 4dcbf9e9013c |
children | 3dffeb6df4a3 |
comparison
equal
deleted
inserted
replaced
3118:4dcbf9e9013c | 3119:ad5a00ccd276 |
---|---|
14 package controllers | 14 package controllers |
15 | 15 |
16 import ( | 16 import ( |
17 "context" | 17 "context" |
18 "database/sql" | 18 "database/sql" |
19 "encoding/csv" | |
19 "fmt" | 20 "fmt" |
20 "log" | 21 "log" |
21 "net/http" | 22 "net/http" |
22 "sort" | 23 "sort" |
23 "strconv" | 24 "strconv" |
25 "time" | 26 "time" |
26 | 27 |
27 "github.com/gorilla/mux" | 28 "github.com/gorilla/mux" |
28 | 29 |
29 "gemma.intevation.de/gemma/pkg/common" | 30 "gemma.intevation.de/gemma/pkg/common" |
31 "gemma.intevation.de/gemma/pkg/middleware" | |
30 ) | 32 ) |
31 | 33 |
32 const ( | 34 const ( |
33 selectAvailableDepthSQL = ` | 35 selectAvailableDepthSQL = ` |
34 WITH data AS ( | 36 WITH data AS ( |
78 grwl.depth_reference like 'HDC%' OR | 80 grwl.depth_reference like 'HDC%' OR |
79 grwl.depth_reference like 'LDC%' OR | 81 grwl.depth_reference like 'LDC%' OR |
80 grwl.depth_reference like 'MW%' | 82 grwl.depth_reference like 'MW%' |
81 ) | 83 ) |
82 ` | 84 ` |
85 selectGaugeLDCSQL = ` | |
86 SELECT | |
87 grwl.depth_reference, | |
88 grwl.value | |
89 FROM waterway.gauges_reference_water_levels grwl JOIN | |
90 waterway.bottlenecks bns | |
91 ON bns.fk_g_fid = grwl.gauge_id | |
92 WHERE bns.objnam = $1 AND grwl.depth_reference like 'LDC%' | |
93 ` | |
83 ) | 94 ) |
84 | 95 |
85 type ( | 96 type ( |
86 referenceValue struct { | 97 referenceValue struct { |
87 level int | 98 level int |
292 } | 303 } |
293 | 304 |
294 return ms, nil | 305 return ms, nil |
295 } | 306 } |
296 | 307 |
308 func loadLDCReferenceValue( | |
309 ctx context.Context, | |
310 conn *sql.Conn, | |
311 bottleneck string, | |
312 ) ([]referenceValue, error) { | |
313 | |
314 var value float64 | |
315 err := conn.QueryRowContext(ctx, selectGaugeLDCSQL, bottleneck).Scan(&value) | |
316 switch { | |
317 case err == sql.ErrNoRows: | |
318 return nil, nil | |
319 case err != nil: | |
320 return nil, err | |
321 } | |
322 return []referenceValue{{0, value}}, nil | |
323 } | |
324 | |
297 func loadLNWLReferenceValues( | 325 func loadLNWLReferenceValues( |
298 ctx context.Context, | 326 ctx context.Context, |
299 conn *sql.Conn, | 327 conn *sql.Conn, |
300 bottleneck string, | 328 bottleneck string, |
301 ) ([]referenceValue, error) { | 329 ) ([]referenceValue, error) { |
483 | 511 |
484 jr = JSONResult{Result: &out} | 512 jr = JSONResult{Result: &out} |
485 return | 513 return |
486 } | 514 } |
487 | 515 |
488 func bottleneckAvailableFairwayDepth( | 516 func bottleneckAvailableFairwayDepth(rw http.ResponseWriter, req *http.Request) { |
489 _ interface{}, | 517 |
490 req *http.Request, | |
491 conn *sql.Conn, | |
492 ) (jr JSONResult, err error) { | |
493 bn := mux.Vars(req)["objnam"] | 518 bn := mux.Vars(req)["objnam"] |
494 | 519 |
495 if bn == "" { | 520 if bn == "" { |
496 err = JSONError{ | 521 http.Error( |
497 Code: http.StatusBadRequest, | 522 rw, "Missing objnam of bottleneck", |
498 Message: "Missing objnam of bottleneck", | 523 http.StatusBadRequest) |
499 } | |
500 return | 524 return |
501 } | 525 } |
502 | 526 |
503 var mode int | 527 var mode int |
504 if m := req.FormValue("mode"); m != "" { | 528 if m := req.FormValue("mode"); m != "" { |
508 case "quarterly": | 532 case "quarterly": |
509 mode = 1 | 533 mode = 1 |
510 case "yearly": | 534 case "yearly": |
511 mode = 2 | 535 mode = 2 |
512 default: | 536 default: |
513 err = JSONError{ | 537 http.Error( |
514 Code: http.StatusBadRequest, | 538 rw, fmt.Sprintf("Unknown 'mode' value %s.", m), |
515 Message: "Unknown 'mode' value", | 539 http.StatusBadRequest) |
516 } | |
517 return | 540 return |
518 } | 541 } |
519 } | 542 } |
520 | 543 |
521 var from, to time.Time | 544 var from, to time.Time |
522 | 545 |
523 if f := req.FormValue("from"); f != "" { | 546 if f := req.FormValue("from"); f != "" { |
524 if from, err = parseTime(f, "from"); err != nil { | 547 var err error |
548 if from, err = time.Parse(common.TimeFormat, f); err != nil { | |
549 http.Error( | |
550 rw, fmt.Sprintf("Invalid format for 'from': %v.", err), | |
551 http.StatusBadRequest) | |
525 return | 552 return |
526 } | 553 } |
527 } else { | 554 } else { |
528 from = time.Now().AddDate(-1, 0, 0).UTC() | 555 from = time.Now().AddDate(-1, 0, 0).UTC() |
529 } | 556 } |
530 | 557 |
531 if t := req.FormValue("to"); t != "" { | 558 if t := req.FormValue("to"); t != "" { |
532 if to, err = parseTime(t, "to"); err != nil { | 559 var err error |
560 if to, err = time.Parse(common.TimeFormat, t); err != nil { | |
561 http.Error( | |
562 rw, fmt.Sprintf("Invalid format for 'to': %v.", err), | |
563 http.StatusBadRequest) | |
533 return | 564 return |
534 } | 565 } |
535 } else { | 566 } else { |
536 to = from.AddDate(1, 0, 0).UTC() | 567 to = from.AddDate(1, 0, 0).UTC() |
537 } | 568 } |
542 | 573 |
543 log.Printf("info: time interval: (%v - %v)\n", from, to) | 574 log.Printf("info: time interval: (%v - %v)\n", from, to) |
544 | 575 |
545 var los int | 576 var los int |
546 if l := req.FormValue("los"); l != "" { | 577 if l := req.FormValue("los"); l != "" { |
547 if los, err = parseInt(l, "los"); err != nil { | 578 var err error |
579 if los, err = strconv.Atoi(l); err != nil { | |
580 http.Error( | |
581 rw, fmt.Sprintf("Invalid format for 'los': %v.", err), | |
582 http.StatusBadRequest) | |
548 return | 583 return |
549 } | 584 } |
550 } else { | 585 } else { |
551 los = 1 | 586 los = 1 |
552 } | 587 } |
553 | 588 |
554 // TODO: Implement me! | 589 conn := middleware.GetDBConn(req) |
555 | 590 ctx := req.Context() |
556 _, _ = los, mode | 591 |
557 | 592 // load the measurements |
558 return | 593 ms, err := loadDepthValues(ctx, conn, bn, los, from, to) |
559 } | 594 if err != nil { |
595 http.Error( | |
596 rw, fmt.Sprintf("Loading measurements failed: %v.", err), | |
597 http.StatusInternalServerError) | |
598 return | |
599 } | |
600 | |
601 ldcRefs, err := loadLDCReferenceValue(ctx, conn, bn) | |
602 if err != nil { | |
603 http.Error( | |
604 rw, fmt.Sprintf("Loading LDC failed: %v.", err), | |
605 http.StatusInternalServerError) | |
606 return | |
607 } | |
608 if len(ldcRefs) == 0 { | |
609 http.Error(rw, "No LDC found", http.StatusNotFound) | |
610 return | |
611 } | |
612 | |
613 rw.Header().Add("Content-Type", "text/csv") | |
614 | |
615 out := csv.NewWriter(rw) | |
616 | |
617 // label, classes, lnwl | |
618 record := make([]string, 1+1+len(afdRefs)+1) | |
619 record[0] = "#label" | |
620 record[1] = "# >= LDC [h]" | |
621 for i, v := range afdRefs { | |
622 if i == 0 { | |
623 record[2] = fmt.Sprintf("# < %.2f [h]", v.value) | |
624 } | |
625 record[i+3] = fmt.Sprintf("# >= %.2f [h]", v.value) | |
626 } | |
627 | |
628 if err := out.Write(record); err != nil { | |
629 // Too late for HTTP status message. | |
630 log.Printf("error: %v\n", err) | |
631 return | |
632 } | |
633 | |
634 interval := intervals[mode](from, to) | |
635 | |
636 for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { | |
637 | |
638 // Find good starting point | |
639 idx := sort.Search(len(ms), func(i int) bool { | |
640 return !ms[i].when.After(pfrom) | |
641 }) | |
642 if idx > 0 { | |
643 idx-- | |
644 } | |
645 samples := ms[idx:] | |
646 | |
647 ranges := samples.classifyAvailMeasurements( | |
648 pfrom, pto, | |
649 afdRefs, | |
650 (*availMeasurement).getDepth, | |
651 ) | |
652 | |
653 ldc := samples.classifyAvailMeasurements( | |
654 pfrom, pto, | |
655 ldcRefs, | |
656 (*availMeasurement).getDepth, | |
657 ) | |
658 | |
659 record[0] = label | |
660 record[1] = fmt.Sprintf("%.3f", ldc[1].Hours()/24) | |
661 | |
662 for i, d := range ranges { | |
663 record[2+i] = fmt.Sprintf("%.3f", d.Hours()/24) | |
664 } | |
665 | |
666 if err := out.Write(record); err != nil { | |
667 // Too late for HTTP status message. | |
668 log.Printf("error: %v\n", err) | |
669 return | |
670 } | |
671 } | |
672 | |
673 out.Flush() | |
674 if err := out.Error(); err != nil { | |
675 // Too late for HTTP status message. | |
676 log.Printf("error: %v\n", err) | |
677 } | |
678 } | |
679 | |
680 var intervals = []func(time.Time, time.Time) func() (time.Time, time.Time, string){ | |
681 monthly, | |
682 quarterly, | |
683 yearly, | |
684 } | |
685 | |
686 func monthly(from, to time.Time) func() (time.Time, time.Time, string) { | |
687 return func() (time.Time, time.Time, string) { | |
688 // TODO: Implement me! | |
689 return time.Time{}, time.Time{}, "" | |
690 } | |
691 } | |
692 | |
693 func quarterly(from, to time.Time) func() (time.Time, time.Time, string) { | |
694 return func() (time.Time, time.Time, string) { | |
695 // TODO: Implement me! | |
696 return time.Time{}, time.Time{}, "" | |
697 } | |
698 } | |
699 | |
700 func yearly(from, to time.Time) func() (time.Time, time.Time, string) { | |
701 return func() (time.Time, time.Time, string) { | |
702 // TODO: Implement me! | |
703 return time.Time{}, time.Time{}, "" | |
704 } | |
705 } |