Mercurial > gemma
comparison pkg/controllers/gauges.go @ 3088:09d1ffce3d00
Reworked Nash-Sutcliffe calculation.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Sun, 21 Apr 2019 18:59:00 +0200 |
parents | cf6f595af2cb |
children | 813309225e35 |
comparison
equal
deleted
inserted
replaced
3087:014be2194bd1 | 3088:09d1ffce3d00 |
---|---|
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> | 12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> |
13 | 13 |
14 package controllers | 14 package controllers |
15 | 15 |
16 import ( | 16 import ( |
17 "context" | |
17 "database/sql" | 18 "database/sql" |
18 "encoding/csv" | 19 "encoding/csv" |
19 "fmt" | 20 "fmt" |
20 "log" | 21 "log" |
21 "math" | 22 "math" |
36 | 37 |
37 const ( | 38 const ( |
38 selectPredictedObserveredSQL = ` | 39 selectPredictedObserveredSQL = ` |
39 SELECT | 40 SELECT |
40 measure_date, | 41 measure_date, |
42 date_issue, | |
41 predicted, | 43 predicted, |
42 water_level | 44 water_level |
43 FROM waterway.gauge_measurements | 45 FROM waterway.gauge_measurements |
44 WHERE | 46 WHERE |
45 fk_gauge_id = ( | 47 fk_gauge_id = ( |
404 // Too late for an HTTP error code. | 406 // Too late for an HTTP error code. |
405 return | 407 return |
406 } | 408 } |
407 } | 409 } |
408 | 410 |
411 func parseISRS(code string) (*models.Isrs, error) { | |
412 isrs, err := models.IsrsFromString(code) | |
413 if err != nil { | |
414 return nil, JSONError{ | |
415 Code: http.StatusBadRequest, | |
416 Message: fmt.Sprintf("error: Invalid ISRS code: %v", err), | |
417 } | |
418 } | |
419 return isrs, nil | |
420 } | |
421 | |
422 type observedPredictedValues struct { | |
423 when time.Time | |
424 observed float64 | |
425 predicted common.TimedValues | |
426 } | |
427 | |
428 func loadNashSutcliffeData( | |
429 ctx context.Context, | |
430 conn *sql.Conn, | |
431 gauge *models.Isrs, | |
432 when time.Time, | |
433 ) ([]observedPredictedValues, error) { | |
434 | |
435 var rows *sql.Rows | |
436 var err error | |
437 if rows, err = conn.QueryContext( | |
438 ctx, | |
439 selectPredictedObserveredSQL, | |
440 gauge.CountryCode, | |
441 gauge.LoCode, | |
442 gauge.FairwaySection, | |
443 gauge.Orc, | |
444 gauge.Hectometre, | |
445 when, | |
446 ); err != nil { | |
447 return nil, err | |
448 } | |
449 defer rows.Close() | |
450 | |
451 var ( | |
452 hasCurrent bool | |
453 current observedPredictedValues | |
454 values []observedPredictedValues | |
455 ) | |
456 | |
457 for rows.Next() { | |
458 var ( | |
459 measureDate time.Time | |
460 issueDate time.Time | |
461 predicted bool | |
462 value float64 | |
463 ) | |
464 if err := rows.Scan( | |
465 &measureDate, | |
466 &issueDate, | |
467 &predicted, | |
468 &value, | |
469 ); err != nil { | |
470 return nil, err | |
471 } | |
472 measureDate = measureDate.UTC() | |
473 issueDate = issueDate.UTC() | |
474 | |
475 if hasCurrent { | |
476 if !current.when.Equal(measureDate) { | |
477 if !math.IsNaN(current.observed) { | |
478 values = append(values, current) | |
479 } | |
480 current = observedPredictedValues{ | |
481 observed: math.NaN(), | |
482 when: measureDate, | |
483 } | |
484 } | |
485 } else { | |
486 hasCurrent = true | |
487 current = observedPredictedValues{ | |
488 observed: math.NaN(), | |
489 when: measureDate, | |
490 } | |
491 } | |
492 | |
493 if predicted { | |
494 current.predicted = append( | |
495 current.predicted, | |
496 common.TimedValue{When: issueDate, Value: value}, | |
497 ) | |
498 } else { | |
499 current.observed = value | |
500 } | |
501 } | |
502 | |
503 if err := rows.Err(); err != nil { | |
504 return nil, err | |
505 } | |
506 | |
507 if hasCurrent && !math.IsNaN(current.observed) { | |
508 values = append(values, current) | |
509 } | |
510 | |
511 return values, nil | |
512 } | |
513 | |
409 func nashSutcliffe( | 514 func nashSutcliffe( |
410 _ interface{}, | 515 _ interface{}, |
411 req *http.Request, | 516 req *http.Request, |
412 conn *sql.Conn, | 517 conn *sql.Conn, |
413 ) (jr JSONResult, err error) { | 518 ) (jr JSONResult, err error) { |
414 gauge := mux.Vars(req)["gauge"] | 519 gauge := mux.Vars(req)["gauge"] |
415 | 520 |
416 var isrs *models.Isrs | 521 var isrs *models.Isrs |
417 if isrs, err = models.IsrsFromString(gauge); err != nil { | 522 if isrs, err = parseISRS(gauge); err != nil { |
418 err = JSONError{ | |
419 Code: http.StatusBadRequest, | |
420 Message: fmt.Sprintf("error: Invalid ISRS code: %v", err), | |
421 } | |
422 return | 523 return |
423 } | 524 } |
424 | 525 |
425 var when time.Time | 526 var when time.Time |
426 if w := req.FormValue("when"); w != "" { | 527 if w := req.FormValue("when"); w != "" { |
432 return | 533 return |
433 } | 534 } |
434 } else { | 535 } else { |
435 when = time.Now() | 536 when = time.Now() |
436 } | 537 } |
538 when = when.UTC() | |
437 | 539 |
438 ctx := req.Context() | 540 ctx := req.Context() |
439 | 541 |
440 var rows *sql.Rows | 542 var values []observedPredictedValues |
441 if rows, err = conn.QueryContext( | 543 |
442 ctx, | 544 if values, err = loadNashSutcliffeData(ctx, conn, isrs, when); err != nil { |
443 selectPredictedObserveredSQL, | 545 return |
444 isrs.CountryCode, | 546 } |
445 isrs.LoCode, | 547 |
446 isrs.FairwaySection, | 548 log.Printf("info: found %d value(s) for Nash Sutcliffe.\n", len(values)) |
447 isrs.Orc, | |
448 isrs.Hectometre, | |
449 when, | |
450 ); err != nil { | |
451 return | |
452 } | |
453 defer rows.Close() | |
454 | |
455 var measurements []common.NSMeasurement | |
456 | |
457 invalid := common.NSMeasurement{ | |
458 Predicted: math.NaN(), | |
459 Observed: math.NaN(), | |
460 } | |
461 current := invalid | |
462 | |
463 for rows.Next() { | |
464 var ( | |
465 when time.Time | |
466 predicted bool | |
467 value float64 | |
468 ) | |
469 if err = rows.Scan( | |
470 &when, | |
471 &predicted, | |
472 &value, | |
473 ); err != nil { | |
474 return | |
475 } | |
476 if !when.Equal(current.When) { | |
477 if current.Valid() { | |
478 measurements = append(measurements, current) | |
479 } | |
480 current = invalid | |
481 } | |
482 if predicted { | |
483 current.Predicted = value | |
484 } else { | |
485 current.Observed = value | |
486 } | |
487 } | |
488 | |
489 if err = rows.Err(); err != nil { | |
490 return | |
491 } | |
492 | |
493 if current.Valid() { | |
494 measurements = append(measurements, current) | |
495 } | |
496 | 549 |
497 type coeff struct { | 550 type coeff struct { |
498 Value float64 `json:"value"` | 551 Value float64 `json:"value"` |
499 Samples int `json:"samples"` | 552 Samples int `json:"samples"` |
500 Hours int `json:"hours"` | 553 Hours int `json:"hours"` |
503 type coeffs struct { | 556 type coeffs struct { |
504 When models.ImportTime `json:"when"` | 557 When models.ImportTime `json:"when"` |
505 Coeffs []coeff `json:"coeffs"` | 558 Coeffs []coeff `json:"coeffs"` |
506 } | 559 } |
507 | 560 |
561 var measurements []common.NSMeasurement | |
562 | |
508 cs := make([]coeff, 3) | 563 cs := make([]coeff, 3) |
509 for i := range cs { | 564 for i := range cs { |
510 cs[i].Hours = (i + 1) * 24 | 565 cs[i].Hours = (i + 1) * 24 |
566 back := when.Add(time.Duration(-cs[i].Hours) * time.Hour) | |
567 | |
568 for j := range values { | |
569 if predicted, ok := values[j].predicted.Interpolate(back); ok { | |
570 measurements = append(measurements, common.NSMeasurement{ | |
571 Observed: values[i].observed, | |
572 Predicted: predicted, | |
573 }) | |
574 } | |
575 } | |
576 | |
511 cs[i].Value, cs[i].Samples = common.NashSutcliffe( | 577 cs[i].Value, cs[i].Samples = common.NashSutcliffe( |
512 measurements, | 578 measurements, |
513 when, | 579 when, |
514 when.Add(time.Duration(-cs[i].Hours)*time.Hour), | 580 back, |
515 ) | 581 ) |
582 | |
583 measurements = measurements[:0] | |
516 } | 584 } |
517 | 585 |
518 jr = JSONResult{ | 586 jr = JSONResult{ |
519 Result: &coeffs{ | 587 Result: &coeffs{ |
520 When: models.ImportTime{when}, | 588 When: models.ImportTime{when}, |