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