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 }