From 6d2cdacc2ba4d95ad1d80d6824adb025accfd273 Mon Sep 17 00:00:00 2001 From: Since <ax20yhum@cip.cs.fau.de> Date: Wed, 20 Dec 2017 13:54:10 +0100 Subject: [PATCH] Correctly process level fields in other runlength based formats --- header.go | 23 ++++++++++++++++------- header_test.go | 6 +++--- radolan.go | 23 ++++++++++++----------- runlength.go | 13 +++++++++---- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/header.go b/header.go index e4308c9..8206e73 100644 --- a/header.go +++ b/header.go @@ -3,7 +3,6 @@ package radolan import ( "bufio" "fmt" - "strings" "time" "unicode" ) @@ -108,15 +107,25 @@ func (c *Composite) parseHeader(reader *bufio.Reader) error { } // Parse Level - Example "LV 6 1.0 19.0 28.0 37.0 46.0 55.0" + // or "LV12-31.5-24.5-17.5-10.5 -5.5 -1.0 1.0 5.5 10.5 17.5 24.5 31.5" if lv, ok := section["LV"]; ok { - level := strings.Fields(lv) - if len(level) < 2 { - return newError("parseHeader", "invalid level count") + if len(lv) < 2 { + return newError("parseHeader", "level field too short") } - c.level = make([]DBZ, len(level)-1) - for i, f := range level[1:] { - if _, err = fmt.Sscanf(f, "%f", &c.level[i]); err != nil { + var cnt int + if _, err = fmt.Sscanf(lv[:2], "%d", &cnt); err != nil { + return newError("parseHeader", "could not parse level count: "+err.Error()) + } + + if len(lv) != cnt*5+2 { // fortran format I2 + F5.1 + return newError("parseHeader", "invalid level format: "+lv) + } + + c.level = make([]RVP6, cnt) + for i := range c.level { + n := i * 5 + if _, err = fmt.Sscanf(lv[n+2:n+7], "%f", &c.level[i]); err != nil { return newError("parseHeader", "invalid level value: "+err.Error()) } } diff --git a/header_test.go b/header_test.go index 1dd9575..1529e6a 100644 --- a/header_test.go +++ b/header_test.go @@ -21,7 +21,7 @@ type headerTestcase struct { expDy int expDataLength int expPrecision int - expLevel []DBZ + expLevel []RVP6 } func TestParseHeaderPG(t *testing.T) { @@ -42,7 +42,7 @@ func TestParseHeaderPG(t *testing.T) { ht.expDy = 460 ht.expDataLength = 22205 - 159 // BY - header_etx_length ht.expPrecision = 0 - ht.expLevel = []DBZ{1.0, 19.0, 28.0, 37.0, 46.0, 55.0} + ht.expLevel = []RVP6{1.0, 19.0, 28.0, 37.0, 46.0, 55.0} if err1 != nil || err2 != nil { t.Errorf("%s.parseHeader(): wrong testcase time.Parse", ht.expProduct) @@ -71,7 +71,7 @@ func TestParseHeaderFZ(t *testing.T) { ht.expDy = 450 ht.expDataLength = 405160 - 154 // BY - header_etx_length ht.expPrecision = -1 - ht.expLevel = []DBZ(nil) + ht.expLevel = []RVP6(nil) if err1 != nil || err2 != nil { t.Errorf("%s.parseHeader(): wrong testcase time.Parse", ht.expProduct) diff --git a/radolan.go b/radolan.go index 068c802..5262b5f 100644 --- a/radolan.go +++ b/radolan.go @@ -37,16 +37,17 @@ import ( // raw rvp-6 value (NaN if the no-data flag is set). This rvp-6 value is used differently // depending on the product type: // -// Product label || raw value "live" cloud reflectivity "live" rainfall rate -// -----------------|| +-------+ +-----+ +------+ -// (PG), FZ, ... || | rvp-6 |---ToDBZ()--->| dBZ |---PrecipitationRate()--->| mm/h | -// RX, EX || +---+---+ +-----+ +------+ -// -----------------|| - - - | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// RW, SF, ... || | +------+ -// || +----- >| mm/h | -// || | mm/d | -// -----------------|| +------+ -// aggregated precipitation +// The rvp-6 value is used differently depending on the product type: +// +// Product label | rvp-6 value represents | unit +// -------------------------+--------------------------+------------------------ +// PG, PC, PX*, ... | cloud reflectivity | dBZ +// RX, WX, EX, FZ, FX, ... | raw value | convert to dBZ with ToDBZ() +// RW, SF, ... | aggregated precipitation | mm/h or mm/d +// PR*, ... | doppler radial velocity | m/s +// +// The cloud reflectivity (in dBZ) can be converted to rainfall rate (in mm/h) +// via PrecipitationRate(). // // The cloud reflectivity factor Z is stored in its logarithmic representation dBZ: // dBZ = 10 * log(Z) @@ -81,7 +82,7 @@ type Composite struct { dataLength int // length of binary section in bytes precision int // multiplicator 10^precision for each raw value - level []DBZ // maps data value to corresponding dBZ value in runlength based formats + level []RVP6 // maps data value to corresponding rvp-6 value in runlength based formats offx float64 // horizontal projection offset offy float64 // vertical projection offset diff --git a/runlength.go b/runlength.go index 451d3fb..6d6e654 100644 --- a/runlength.go +++ b/runlength.go @@ -76,11 +76,16 @@ func (c *Composite) decodeRunlength(dst []RVP6, line []byte) error { return nil } -// rvp6Runlength converts the raw value of level based composite products to radar video -// processor values (rvp-6). NaN may be returned when the given value has no internal mapping. +// rvp6Runlength sets the value of level based composite products to radar +// video processor values (rvp-6). func (c *Composite) rvp6Runlength(value byte) RVP6 { - if c.level == nil || int(value) >= len(c.level) || value < 0 { + if value == 0 { return RVP6(math.NaN()) } - return DBZ(c.level[value]).ToRVP6() + value-- + + if int(value) >= len(c.level) { // border markings + return RVP6(math.NaN()) + } + return c.level[value] } -- GitLab