changeset 8948:a4f1ac81f26d

Work on SINFO-FlowDepthMinMax. Also rework of result row stuff, in order to reduce abstraction, using result type concept
author gernotbelger
date Wed, 14 Mar 2018 14:10:32 +0100
parents 86650594f051
children 09e4a4909814
files artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/SInfoI18NStrings.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResults.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoExporter.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoLineProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoResultRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractTkhCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractTkhResultRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/D50Processor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/FlowDepthProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultFacet.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultType.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/TauProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/TkhProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/VelocityProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResults.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculator.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculationResults.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxExporter.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResults.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhExporter.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhResultRow.java
diffstat 32 files changed, 713 insertions(+), 661 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/SInfoI18NStrings.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/SInfoI18NStrings.java	Wed Mar 14 14:10:32 2018 +0100
@@ -52,6 +52,18 @@
 
     String CSV_SOUNDING_HEADER = "sinfo.export.flow_depth.csv.header.sounding";
 
+    String CSV_FLOWDEPTH_HEADER = "sinfo.export.flow_depth.csv.header.flowdepth";
+
+    String CSV_FLOWDEPTHTKH_HEADER = "sinfo.export.flow_depth.csv.header.flowdepthTkh";
+
+    String CSV_FLOWDEPTH_MIN_HEADER = "sinfo.export.flow_depth_minmax.csv.header.min";
+
+    String CSV_FLOWDEPTH_MAX_HEADER = "sinfo.export.flow_depth_minmax.csv.header.max";
+
+    String CSV_TKH_HEADER = "sinfo.export.tkh.csv.header.tkh";
+
+    String CSV_TKHKIND_HEADER = "sinfo.export.tkh.csv.header.tkhkind";
+
     String CSV_META_HEADER_WATERLEVEL = "sinfo.export.flow_depth.csv.meta.header.waterlevel";
 
     String CSV_META_HEADER_WATERLEVEL_NAME = "sinfo.export.flow_depth.csv.meta.header.waterlevel.name";
@@ -68,8 +80,14 @@
 
     String CSV_MEAN_BED_HEIGHT_HEADER_SHORT = "sinfo.export.flow_depth.csv.header.mean_bed_height.short";
 
+    String PREFIX_TKH_KIND = "sinfo.export.tkh.soilkind.";
+
+    String UNIT_NONE = "-";
+
     String UNIT_M = "m";
 
+    String UNIT_KM = "km";
+
     String UNIT_CM = "cm";
 
     String UNIT_CUBIC_M = "m³/s";
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResult.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResult.java	Wed Mar 14 14:10:32 2018 +0100
@@ -13,23 +13,26 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 
+import gnu.trove.TDoubleArrayList;
+
 /**
  * @author Gernot Belger
  */
-public abstract class AbstractSInfoCalculationResult<ROW extends AbstractSInfoResultRow> implements Serializable {
+public abstract class AbstractSInfoCalculationResult implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    private final Collection<ROW> rows;
+    private final Collection<SInfoResultRow> rows;
 
     private final String label;
 
     private final WstInfo wst;
 
-    public AbstractSInfoCalculationResult(final String label, final WstInfo wst, final Collection<ROW> rows) {
+    public AbstractSInfoCalculationResult(final String label, final WstInfo wst, final Collection<SInfoResultRow> rows) {
         this.label = label;
         this.wst = wst;
         this.rows = new ArrayList<>(rows);
@@ -43,11 +46,41 @@
         return this.wst;
     }
 
-    public final void addRow(final ROW resultRow) {
+    public final void addRow(final SInfoResultRow resultRow) {
         this.rows.add(resultRow);
     }
 
-    public final Collection<ROW> getRows() {
+    public final Collection<SInfoResultRow> getRows() {
         return Collections.unmodifiableCollection(this.rows);
     }
+
+    public final double[][] getStationPoints(final SInfoResultType type) {
+
+        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
+        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
+
+        for (final SInfoResultRow row : this.rows) {
+
+            final double station = row.getDoubleValue(SInfoResultType.station);
+            final double value = row.getDoubleValue(type);
+
+            xPoints.add(station);
+            yPoints.add(value);
+        }
+
+        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
+    }
+
+    protected final <TYPE> List<TYPE> getValues(final SInfoResultType type) {
+
+        final List<TYPE> values = new ArrayList<>();
+
+        for (final SInfoResultRow row : this.rows) {
+            @SuppressWarnings("unchecked")
+            final TYPE value = (TYPE) row.getValue(type);
+            values.add(value);
+        }
+
+        return values;
+    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResults.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResults.java	Wed Mar 14 14:10:32 2018 +0100
@@ -20,7 +20,7 @@
 /**
  * @author Gernot Belger
  */
-public abstract class AbstractSInfoCalculationResults<ROW extends AbstractSInfoResultRow, RESULT extends AbstractSInfoCalculationResult<ROW>> implements Serializable {
+public abstract class AbstractSInfoCalculationResults<RESULT extends AbstractSInfoCalculationResult> implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoExporter.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoExporter.java	Wed Mar 14 14:10:32 2018 +0100
@@ -42,7 +42,7 @@
 /**
  * @author Gernot Belger
  */
-public abstract class AbstractSInfoExporter<ROW extends AbstractSInfoResultRow, RESULT extends AbstractSInfoCalculationResult<ROW>, RESULTS extends AbstractSInfoCalculationResults<ROW, RESULT>> extends AbstractExporter {
+public abstract class AbstractSInfoExporter<RESULT extends AbstractSInfoCalculationResult, RESULTS extends AbstractSInfoCalculationResults<RESULT>> extends AbstractExporter {
 
     private static final String CSV_META_HEADER_SOUNDING = "sinfo.export.flow_depth.csv.meta.header.sounding";
 
@@ -120,9 +120,9 @@
         writeCSVResultMetadata(writer, results, result);
 
         /* nwo the value rows */
-        final Collection<ROW> rows = result.getRows();
-        for (final ROW row : rows) {
-            writeCSVRow(writer, results, row);
+        final Collection<SInfoResultRow> rows = result.getRows();
+        for (final SInfoResultRow row : rows) {
+            writeCSVRow(writer, results, result, row);
         }
     }
 
@@ -131,14 +131,14 @@
      */
     protected abstract void writeCSVResultMetadata(CSVWriter writer, RESULTS results, RESULT result);
 
-    protected final void writeCSVRow(final CSVWriter writer, final RESULTS results, final ROW row) {
+    protected final void writeCSVRow(final CSVWriter writer, final RESULTS results, final RESULT result, final SInfoResultRow row) {
         getLog().debug("writeCSVFlowDepthRow");
 
-        final String[] formattedRow = formatCSVRow(results, row);
+        final String[] formattedRow = formatCSVRow(results, result, row);
         writer.writeNext(formattedRow);
     }
 
-    protected abstract String[] formatCSVRow(RESULTS results, final ROW row);
+    protected abstract String[] formatCSVRow(RESULTS results, RESULT result, final SInfoResultRow row);
 
     @Override
     protected final void writePDF(final OutputStream outStream) {
@@ -186,18 +186,18 @@
 
     protected final void addJRTableData(final MetaAndTableJRDataSource source, final RESULTS results, final RESULT result) {
 
-        final Collection<ROW> rows = result.getRows();
+        final Collection<SInfoResultRow> rows = result.getRows();
 
-        for (final ROW row : rows) {
+        for (final SInfoResultRow row : rows) {
 
             final String[] formattedRow = formatPDFRow(results, row);
             source.addData(formattedRow);
         }
     }
 
-    protected abstract String[] formatPDFRow(RESULTS results, final ROW row);
+    protected abstract String[] formatPDFRow(RESULTS results, final SInfoResultRow row);
 
-    protected final void writeCSVGlobalMetadataDefaults(final CSVWriter writer, final AbstractSInfoCalculationResults<?, ?> results) {
+    protected final void writeCSVGlobalMetadataDefaults(final CSVWriter writer, final AbstractSInfoCalculationResults<?> results) {
 
         final String calcModeLabel = results.getCalcModeLabel();
         final RiverInfo river = results.getRiver();
@@ -261,7 +261,7 @@
             writeCSVMetaEntry(writer, SInfoI18NStrings.CSV_META_HEADER_WATERLEVEL_YEAR, Integer.toString(year));
     }
 
-    protected final void addJRMetaDataDefaults(final MetaAndTableJRDataSource source, final AbstractSInfoCalculationResults<?, ?> results) {
+    protected final void addJRMetaDataDefaults(final MetaAndTableJRDataSource source, final AbstractSInfoCalculationResults<?> results) {
 
         final RiverInfo river = results.getRiver();
         final String wstUnitName = river.getWstUnit();
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoLineProcessor.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoLineProcessor.java	Wed Mar 14 14:10:32 2018 +0100
@@ -26,7 +26,7 @@
 import org.dive4elements.river.jfree.StyledXYSeries;
 import org.dive4elements.river.themes.ThemeDocument;
 
-abstract class AbstractSInfoLineProcessor<RESULT extends AbstractSInfoCalculationResult<?>> extends AbstractSInfoProcessor {
+abstract class AbstractSInfoLineProcessor<RESULT extends AbstractSInfoCalculationResult> extends AbstractSInfoProcessor {
 
     public AbstractSInfoLineProcessor(final String i18nAxisLabel, final Set<String> handledFacetType) {
         super(i18nAxisLabel, handledFacetType);
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoResultRow.java	Wed Mar 14 14:09:33 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
- * Software engineering by
- *  Björnsen Beratende Ingenieure GmbH
- *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
- *
- * This file is Free Software under the GNU AGPL (>=v3)
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details.
- */
-package org.dive4elements.river.artifacts.sinfo.common;
-
-import java.io.Serializable;
-
-/**
- * Contains common result data of flow-depth- and tkh-calculations.
- *
- * @author Gernot Belger
- */
-public abstract class AbstractSInfoResultRow implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    private final String waterlevelLabel;
-
-    private final String gauge;
-
-    private final String location;
-
-    public AbstractSInfoResultRow(final String waterlevelLabel, final String gauge, final String location) {
-        this.waterlevelLabel = waterlevelLabel;
-        this.gauge = gauge;
-        this.location = location;
-    }
-
-    public final String getWaterlevelLabel() {
-        return this.waterlevelLabel;
-    }
-
-    public final String getGauge() {
-        return this.gauge;
-    }
-
-    public final String getLocation() {
-        return this.location;
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractTkhCalculationResult.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractTkhCalculationResult.java	Wed Mar 14 14:10:32 2018 +0100
@@ -9,7 +9,6 @@
  */
 package org.dive4elements.river.artifacts.sinfo.common;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
@@ -21,14 +20,15 @@
 /**
  * @author Gernot Belger
  */
-public abstract class AbstractTkhCalculationResult<ROW extends AbstractTkhResultRow> extends AbstractSInfoCalculationResult<ROW> {
+public abstract class AbstractTkhCalculationResult extends AbstractSInfoCalculationResult {
 
     private static final long serialVersionUID = 1L;
 
     private final boolean hasTkh;
 
-    public AbstractTkhCalculationResult(final String label, final WstInfo wst, final boolean hasTkh, final Collection<ROW> rows) {
+    public AbstractTkhCalculationResult(final String label, final WstInfo wst, final boolean hasTkh, final Collection<SInfoResultRow> rows) {
         super(label, wst, rows);
+
         this.hasTkh = hasTkh;
     }
 
@@ -36,138 +36,51 @@
         return this.hasTkh;
     }
 
-    public double[][] getFlowDepthPoints() {
-
-        final Collection<ROW> rows = getRows();
-
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getFlowDepth());
-        }
-
-        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
-    }
-
-    public double[][] getFlowDepthTkhPoints() {
-
-        final Collection<ROW> rows = getRows();
-
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getFlowDepthWithTkh());
-        }
-
-        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
-    }
-
     public final double[][] getTkhUpPoints() {
 
-        final Collection<ROW> rows = getRows();
+        final double[][] points = getStationPoints(SInfoResultType.tkhup);
+        final List<SoilKind> kinds = getValues(SInfoResultType.soilkind);
 
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-        final List<SoilKind> kinds = new ArrayList<>(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getTkhUp());
-            kinds.add(row.getTkhKind());
-        }
+        final double[] xPoints = points[0];
+        final double[] yPoints = points[1];
 
         return adjustTkhVisualization(xPoints, yPoints, kinds);
     }
 
     public final double[][] getTkhDownPoints() {
 
-        final Collection<ROW> rows = getRows();
+        final double[][] points = getStationPoints(SInfoResultType.tkhdown);
+        final List<SoilKind> kinds = getValues(SInfoResultType.soilkind);
 
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-        final List<SoilKind> kinds = new ArrayList<>(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getTkhDown());
-            kinds.add(row.getTkhKind());
-        }
+        final double[] xPoints = points[0];
+        final double[] yPoints = points[1];
 
         return adjustTkhVisualization(xPoints, yPoints, kinds);
     }
 
-    public double[][] getVelocityPoints() {
-
-        final Collection<ROW> rows = getRows();
-
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getVelocity());
-        }
-
-        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
-    }
-
-    public double[][] getD50Points() {
-
-        final Collection<ROW> rows = getRows();
-
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getD50());
-        }
-
-        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
-    }
-
-    public double[][] getTauPoints() {
-
-        final Collection<ROW> rows = getRows();
-
-        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
-
-        for (final ROW row : rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getTau());
-        }
-
-        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
-    }
-
     /**
      * the up and down points must be further adjusted for visualization, see Mail Hr. Reiß
      * basically we need to introduce extra points when the kind changes, so we get vertical lines in that case
      */
-    private double[][] adjustTkhVisualization(final TDoubleArrayList xPoints, final TDoubleArrayList yPoints, final List<SoilKind> kinds) {
+    private double[][] adjustTkhVisualization(final double[] xPoints, final double[] yPoints, final List<SoilKind> kinds) {
 
-        final TDoubleArrayList adjustedX = new TDoubleArrayList(xPoints.size());
-        final TDoubleArrayList adjustedY = new TDoubleArrayList(yPoints.size());
+        final TDoubleArrayList adjustedX = new TDoubleArrayList(xPoints.length);
+        final TDoubleArrayList adjustedY = new TDoubleArrayList(yPoints.length);
 
-        adjustedX.add(xPoints.get(0));
-        adjustedY.add(yPoints.get(0));
+        adjustedX.add(xPoints[0]);
+        adjustedY.add(yPoints[0]);
 
-        for (int i = 1; i < xPoints.size(); i++) {
+        for (int i = 1; i < xPoints.length; i++) {
 
             final SoilKind kind1 = kinds.get(i - 1);
             final SoilKind kind2 = kinds.get(i);
 
             if (kind1 != kind2) {
                 /* introduce two extra points in order to create a vertical line in the middle of the two adjacent points */
-                final double x1 = xPoints.get(i - 1);
-                final double y1 = yPoints.get(i - 1);
-                final double x2 = xPoints.get(i);
-                final double y2 = yPoints.get(i);
+                final double x1 = xPoints[i - 1];
+                final double y1 = yPoints[i - 1];
+                final double x2 = xPoints[i];
+                final double y2 = yPoints[i];
 
                 final double middleX = (x1 + x2) / 2;
 
@@ -180,8 +93,8 @@
             }
 
             /* always add the real point now */
-            adjustedX.add(xPoints.get(i));
-            adjustedY.add(yPoints.get(i));
+            adjustedX.add(xPoints[i]);
+            adjustedY.add(yPoints[i]);
         }
 
         return new double[][] { adjustedX.toNativeArray(), adjustedY.toNativeArray() };
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractTkhResultRow.java	Wed Mar 14 14:09:33 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
- * Software engineering by
- *  Björnsen Beratende Ingenieure GmbH
- *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
- *
- * This file is Free Software under the GNU AGPL (>=v3)
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details.
- */
-package org.dive4elements.river.artifacts.sinfo.common;
-
-import org.dive4elements.river.artifacts.sinfo.tkhcalculation.SoilKind;
-import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
-
-/**
- * Contains common result data of flow-depth- and tkh-calculations.
- *
- * @author Gernot Belger
- */
-public abstract class AbstractTkhResultRow extends AbstractSInfoResultRow {
-    private static final long serialVersionUID = 1L;
-
-    private final Tkh tkh;
-
-    public AbstractTkhResultRow(final Tkh tkh, final String waterlevelLabel, final String gauge, final String location) {
-        super(waterlevelLabel, gauge, location);
-        this.tkh = tkh;
-    }
-
-    public final double getStation() {
-        return this.tkh.getStation();
-    }
-
-    public final SoilKind getTkhKind() {
-        return this.tkh.getKind();
-    }
-
-    public final double getTkh() {
-        return this.tkh.getTkh();
-    }
-
-    public final double getTkhUp() {
-        return this.tkh.getUp();
-    }
-
-    public final double getTkhDown() {
-        return this.tkh.getDown();
-    }
-
-    public final double getWaterlevel() {
-        return this.tkh.getWaterlevel();
-    }
-
-    public final double getDischarge() {
-        return this.tkh.getDischarge();
-    }
-
-    public final double getMeanBedHeight() {
-        return this.tkh.getMeanBedHeight();
-    }
-
-    public final double getFlowDepth() {
-        return this.tkh.getFlowDepth();
-    }
-
-    public double getFlowDepthWithTkh() {
-        return this.tkh.getFlowDepthTkh();
-    }
-
-    public double getVelocity() {
-        return this.tkh.getVelocity();
-    }
-
-    public double getD50() {
-        return this.tkh.getD50();
-    }
-
-    public double getTau() {
-        return this.tkh.getTau();
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/D50Processor.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/D50Processor.java	Wed Mar 14 14:10:32 2018 +0100
@@ -18,7 +18,7 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 
-public final class D50Processor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult<?>> {
+public final class D50Processor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult> {
 
     // FIXME: check: filtered or not?
     public static final String FACET_TKH_D50_FILTERED = "sinfo_facet_d50.filtered";
@@ -40,16 +40,16 @@
     }
 
     @Override
-    protected double[][] doGetPoints(final AbstractTkhCalculationResult<?> data, final String facetName) {
+    protected double[][] doGetPoints(final AbstractTkhCalculationResult data, final String facetName) {
 
         if (FACET_TKH_D50_FILTERED.contentEquals(facetName))
-            return data.getD50Points();
+            return data.getStationPoints(SInfoResultType.d50);
 
         final String error = String.format("Unknown facet name: %s", facetName);
         throw new UnsupportedOperationException(error);
     }
 
-    public static Facet createD50Facet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult<?> result,
+    public static Facet createD50Facet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult result,
             final int index) {
         final String facetFlowDepthFilteredDescription = Resources.getMsg(context.getMeta(), I18N_FACET_TKH_D50_FILTERED_DESCRIPTION,
                 I18N_FACET_TKH_D50_FILTERED_DESCRIPTION, result.getLabel());
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/FlowDepthProcessor.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/FlowDepthProcessor.java	Wed Mar 14 14:10:32 2018 +0100
@@ -18,7 +18,7 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 
-public final class FlowDepthProcessor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult<?>> {
+public final class FlowDepthProcessor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult> {
 
     private static final String I18N_AXIS_LABEL = "sinfo.chart.flow_depth.section.yaxis.label";
 
@@ -47,19 +47,19 @@
     }
 
     @Override
-    protected double[][] doGetPoints(final AbstractTkhCalculationResult<?> data, final String facetName) {
+    protected double[][] doGetPoints(final AbstractTkhCalculationResult data, final String facetName) {
 
         if (FACET_FLOW_DEPTH_FILTERED.contentEquals(facetName))
-            return data.getFlowDepthPoints();
+            return data.getStationPoints(SInfoResultType.flowdepth);
 
         if (FACET_FLOW_DEPTH_TKH_FILTERED.contentEquals(facetName))
-            return data.getFlowDepthTkhPoints();
+            return data.getStationPoints(SInfoResultType.flowdepthtkh);
 
         final String error = String.format("Unknown facet name: %s", facetName);
         throw new UnsupportedOperationException(error);
     }
 
-    public static Facet createFlowDepthFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult<?> result,
+    public static Facet createFlowDepthFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult result,
             final int index) {
         final String facetFlowDepthFilteredDescription = Resources.getMsg(context.getMeta(), I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION,
                 I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION, result.getLabel());
@@ -67,7 +67,7 @@
                 ComputeType.ADVANCE, id, hash);
     }
 
-    public static Facet createFlowDepthTkhFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult<?> result,
+    public static Facet createFlowDepthTkhFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult result,
             final int index) {
         final String facetFlowDepthTkhFilteredDescription = Resources.getMsg(context.getMeta(), I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION,
                 I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION, result.getLabel());
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultFacet.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultFacet.java	Wed Mar 14 14:10:32 2018 +0100
@@ -45,7 +45,7 @@
 
         final CalculationResult res = (CalculationResult) flys.compute(context, this.hash, this.stateId, this.type, false);
 
-        final AbstractSInfoCalculationResults<?, ?> data = (AbstractSInfoCalculationResults<?, ?>) res.getData();
+        final AbstractSInfoCalculationResults<?> data = (AbstractSInfoCalculationResults<?>) res.getData();
 
         return data.getResults().get(this.index);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultRow.java	Wed Mar 14 14:10:32 2018 +0100
@@ -0,0 +1,56 @@
+/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
+ * Software engineering by
+ *  Björnsen Beratende Ingenieure GmbH
+ *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+package org.dive4elements.river.artifacts.sinfo.common;
+
+import java.io.Serializable;
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.dive4elements.artifacts.CallContext;
+
+/**
+ * Generic container for results that come in rows.
+ *
+ * @author Gernot Belger
+ */
+public final class SInfoResultRow implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final Map<SInfoResultType, Object> values = new EnumMap<>(SInfoResultType.class);
+
+    public static SInfoResultRow create() {
+        return new SInfoResultRow();
+    }
+
+    private SInfoResultRow() {
+    }
+
+    public SInfoResultRow putValue(final SInfoResultType type, final Object value) {
+
+        this.values.put(type, value);
+
+        /* chain me */
+        return this;
+    }
+
+    public String exportValue(final CallContext context, final SInfoResultType type) {
+        final Object value = this.values.get(type);
+        return type.exportValue(context, value);
+    }
+
+    public double getDoubleValue(final SInfoResultType type) {
+        final Object value = this.values.get(type);
+        return type.asDouble(value);
+    }
+
+    public Object getValue(final SInfoResultType type) {
+        return this.values.get(type);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/SInfoResultType.java	Wed Mar 14 14:10:32 2018 +0100
@@ -0,0 +1,339 @@
+/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
+ * Software engineering by
+ *  Björnsen Beratende Ingenieure GmbH
+ *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+package org.dive4elements.river.artifacts.sinfo.common;
+
+import java.text.NumberFormat;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.sinfo.SInfoI18NStrings;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.SoilKind;
+import org.dive4elements.river.utils.Formatter;
+import org.dive4elements.river.utils.RiverUtils;
+
+/**
+ * Result type for data that goes into {@link SInfoResultRow}s.
+ *
+ * @author Gernot Belger
+ */
+public enum SInfoResultType {
+
+    station(SInfoI18NStrings.UNIT_KM, SInfoI18NStrings.CSV_KM_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getWaterlevelKM(context);
+        }
+    },
+
+    waterlevel(null, SInfoI18NStrings.CSV_WATERLEVEL_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            // REMARK: other modules use Formatter.getWaterlevelW(context) instead, but that format with a variable number of
+            // digits.
+            return Formatter.getFlowDepth(context);
+        }
+    },
+
+    waterlevelLabel(SInfoI18NStrings.UNIT_NONE, SInfoI18NStrings.CSV_LABEL_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            return exportStringValue(value);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+
+    discharge(SInfoI18NStrings.UNIT_CUBIC_M, SInfoI18NStrings.CSV_DISCHARGE_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            final double roundedDischarge = RiverUtils.roundQ(doubleValue);
+            return exportDoubleValue(context, roundedDischarge);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getWaterlevelQ(context);
+        }
+    },
+
+    meanBedHeight(null, SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getMeanBedHeight(context);
+        }
+    },
+
+    soundingLabel(SInfoI18NStrings.UNIT_NONE, SInfoI18NStrings.CSV_SOUNDING_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            return exportStringValue(value);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+
+    flowdepthmin(SInfoI18NStrings.UNIT_M, SInfoI18NStrings.CSV_FLOWDEPTH_MIN_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getFlowDepth(context);
+        }
+    },
+
+    flowdepthmax(SInfoI18NStrings.UNIT_M, SInfoI18NStrings.CSV_FLOWDEPTH_MAX_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getFlowDepth(context);
+        }
+    },
+
+    gaugeLabel(SInfoI18NStrings.UNIT_NONE, SInfoI18NStrings.CSV_GAUGE_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            return exportStringValue(value);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+
+    location(SInfoI18NStrings.UNIT_NONE, SInfoI18NStrings.CSV_LOCATION_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            return exportStringValue(value);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    soilkind(SInfoI18NStrings.UNIT_NONE, SInfoI18NStrings.CSV_TKHKIND_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+
+            if (value == null)
+                return StringUtils.EMPTY;
+
+            final SoilKind kind = (SoilKind) value;
+
+            final String key = SInfoI18NStrings.PREFIX_TKH_KIND + kind.name();
+            return Resources.getMsg(context.getMeta(), key, key);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    flowdepth(SInfoI18NStrings.UNIT_M, SInfoI18NStrings.CSV_FLOWDEPTH_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getFlowDepth(context);
+        }
+    },
+    d50(null, null) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    velocity(null, null) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    tau(null, null) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    tkh(SInfoI18NStrings.UNIT_CM, SInfoI18NStrings.CSV_TKH_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getTkh(context);
+        }
+    },
+    tkhup(SInfoI18NStrings.UNIT_CM, null) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    tkhdown(SInfoI18NStrings.UNIT_CM, null) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            throw new UnsupportedOperationException();
+        }
+    },
+    flowdepthtkh(SInfoI18NStrings.UNIT_M, SInfoI18NStrings.CSV_FLOWDEPTHTKH_HEADER) {
+        @Override
+        public String exportValue(final CallContext context, final Object value) {
+            final double doubleValue = asDouble(value);
+            return exportDoubleValue(context, doubleValue);
+        }
+
+        @Override
+        protected NumberFormat createFormatter(final CallContext context) {
+            return Formatter.getFlowDepth(context);
+        }
+    };
+
+    /* Cache for formatters because Formatter will always create new formats (which is very expensive) */
+    private final Map<Locale, NumberFormat> formatters = new HashMap<>();
+
+    private final String unit;
+
+    private final String csvHeader;
+
+    private SInfoResultType(final String untit, final String csvHeader) {
+        this.unit = untit;
+        this.csvHeader = csvHeader;
+    }
+
+    public abstract String exportValue(final CallContext context, final Object value);
+
+    protected final String exportStringValue(final Object value) {
+
+        if (value == null)
+            return StringUtils.EMPTY;
+
+        if (!(value instanceof String))
+            throw new IllegalStateException();
+
+        return (String) value;
+    }
+
+    protected final double asDouble(final Object value) {
+        if (value == null)
+            return Double.NaN;
+
+        if (!(value instanceof Number))
+            throw new IllegalStateException();
+
+        final Number number = (Number) value;
+        return number.doubleValue();
+    }
+
+    protected final String exportDoubleValue(final CallContext context, final double value) {
+        if (Double.isNaN(value))
+            return StringUtils.EMPTY;
+
+        final NumberFormat formatter = getFormatter(context);
+        return formatter.format(value);
+    }
+
+    private NumberFormat getFormatter(final CallContext context) {
+        final CallMeta meta = context.getMeta();
+        final Locale locale = Resources.getLocale(meta);
+
+        if (!this.formatters.containsKey(locale))
+            this.formatters.put(locale, createFormatter(context));
+
+        return this.formatters.get(locale);
+    }
+
+    protected abstract NumberFormat createFormatter(CallContext context);
+
+    public final String getCsvHeader() {
+        return this.csvHeader;
+    }
+
+    public final String getUnit() {
+        return this.unit;
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/TauProcessor.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/TauProcessor.java	Wed Mar 14 14:10:32 2018 +0100
@@ -18,7 +18,7 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 
-public final class TauProcessor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult<?>> {
+public final class TauProcessor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult> {
 
     // FIXME: check: filtered or not?
     public static final String FACET_TKH_TAU_FILTERED = "sinfo_facet_tau.filtered";
@@ -40,16 +40,16 @@
     }
 
     @Override
-    protected double[][] doGetPoints(final AbstractTkhCalculationResult<?> data, final String facetName) {
+    protected double[][] doGetPoints(final AbstractTkhCalculationResult data, final String facetName) {
 
         if (FACET_TKH_TAU_FILTERED.contentEquals(facetName))
-            return data.getTauPoints();
+            return data.getStationPoints(SInfoResultType.tau);
 
         final String error = String.format("Unknown facet name: %s", facetName);
         throw new UnsupportedOperationException(error);
     }
 
-    public static Facet createTauFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult<?> result,
+    public static Facet createTauFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult result,
             final int index) {
         final String facetFlowDepthFilteredDescription = Resources.getMsg(context.getMeta(), I18N_FACET_TKH_TAU_FILTERED_DESCRIPTION,
                 I18N_FACET_TKH_TAU_FILTERED_DESCRIPTION, result.getLabel());
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/TkhProcessor.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/TkhProcessor.java	Wed Mar 14 14:10:32 2018 +0100
@@ -49,7 +49,7 @@
         final CallContext context = generator.getCallContext();
 
         final String facetName = bundle.getFacetName();
-        final AbstractTkhCalculationResult<?> data = (AbstractTkhCalculationResult<?>) bundle.getData(context);
+        final AbstractTkhCalculationResult data = (AbstractTkhCalculationResult) bundle.getData(context);
         if (data == null) {
             // Check has been here before so we keep it for security reasons
             // this should never happen though.
@@ -75,7 +75,7 @@
         return null;
     }
 
-    public static Facet createTkhFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult<?> result,
+    public static Facet createTkhFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult result,
             final int index) {
 
         final String facetTkhDescription = Resources.getMsg(context.getMeta(), I18N_FACET_TKH_DESCRIPTION, I18N_FACET_TKH_DESCRIPTION,
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/VelocityProcessor.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/VelocityProcessor.java	Wed Mar 14 14:10:32 2018 +0100
@@ -18,7 +18,7 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 
-public final class VelocityProcessor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult<?>> {
+public final class VelocityProcessor extends AbstractSInfoLineProcessor<AbstractTkhCalculationResult> {
 
     // FIXME: check: filtered or not?
     public static final String FACET_TKH_VELOCITY_FILTERED = "sinfo_facet_velocity.filtered";
@@ -40,16 +40,16 @@
     }
 
     @Override
-    protected double[][] doGetPoints(final AbstractTkhCalculationResult<?> data, final String facetName) {
+    protected double[][] doGetPoints(final AbstractTkhCalculationResult data, final String facetName) {
 
         if (FACET_TKH_VELOCITY_FILTERED.contentEquals(facetName))
-            return data.getVelocityPoints();
+            return data.getStationPoints(SInfoResultType.velocity);
 
         final String error = String.format("Unknown facet name: %s", facetName);
         throw new UnsupportedOperationException(error);
     }
 
-    public static Facet createVelocityFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult<?> result,
+    public static Facet createVelocityFacet(final CallContext context, final String hash, final String id, final AbstractSInfoCalculationResult result,
             final int index) {
         final String facetFlowDepthFilteredDescription = Resources.getMsg(context.getMeta(), I18N_FACET_TKH_VELOCITY_FILTERED_DESCRIPTION,
                 I18N_FACET_TKH_VELOCITY_FILTERED_DESCRIPTION, result.getLabel());
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResult.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResult.java	Wed Mar 14 14:10:32 2018 +0100
@@ -12,6 +12,7 @@
 import java.util.Collection;
 
 import org.dive4elements.river.artifacts.sinfo.common.AbstractTkhCalculationResult;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
 import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 
@@ -20,14 +21,14 @@
  *
  * @author Gernot Belger
  */
-final class FlowDepthCalculationResult extends AbstractTkhCalculationResult<FlowDepthRow> {
+final class FlowDepthCalculationResult extends AbstractTkhCalculationResult {
 
     private static final long serialVersionUID = 1L;
 
     private final BedHeightInfo sounding;
 
     public FlowDepthCalculationResult(final String label, final WstInfo wst, final BedHeightInfo sounding, final boolean hasTkh,
-            final Collection<FlowDepthRow> rows) {
+            final Collection<SInfoResultRow> rows) {
         super(label, wst, hasTkh, rows);
 
         this.sounding = sounding;
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResults.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResults.java	Wed Mar 14 14:10:32 2018 +0100
@@ -16,7 +16,7 @@
 /**
  * @author Gernot Belger
  */
-final class FlowDepthCalculationResults extends AbstractSInfoCalculationResults<FlowDepthRow, FlowDepthCalculationResult> {
+final class FlowDepthCalculationResults extends AbstractSInfoCalculationResults<FlowDepthCalculationResult> {
 
     private static final long serialVersionUID = 1L;
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculator.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculator.java	Wed Mar 14 14:10:32 2018 +0100
@@ -14,7 +14,8 @@
 
 import org.apache.commons.lang.math.DoubleRange;
 import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
-import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator;
 import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
@@ -24,7 +25,7 @@
  */
 final class FlowDepthCalculator {
 
-    private final Collection<FlowDepthRow> rows = new ArrayList<>();
+    private final Collection<SInfoResultRow> rows = new ArrayList<>();
 
     private final BedHeightsFinder bedHeight;
 
@@ -63,14 +64,21 @@
 
     private void calculateResultRow(final double station) {
 
-        final Tkh tkh = this.tkhCalculator.getTkh(station);
+        final SInfoResultRow row = SInfoResultRow.create();
+
+        row.putValue(SInfoResultType.waterlevelLabel, this.wstLabel);
+        row.putValue(SInfoResultType.soundingLabel, this.bedHeightLabel);
+
+        // REMARK: access the gauge once only during calculation
+        final String gaugeLabel = this.riverInfoProvider.findGauge(station);
+        row.putValue(SInfoResultType.gaugeLabel, gaugeLabel);
 
         // REMARK: access the location once only during calculation
         final String location = this.riverInfoProvider.getLocation(station);
+        row.putValue(SInfoResultType.location, location);
 
-        // REMARK: access the gauge once only during calculation
-        final String gaugeLabel = this.riverInfoProvider.findGauge(station);
+        this.tkhCalculator.calculateTkh(station, row);
 
-        this.rows.add(new FlowDepthRow(tkh, this.wstLabel, gaugeLabel, this.bedHeightLabel, location));
+        this.rows.add(row);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java	Wed Mar 14 14:10:32 2018 +0100
@@ -11,15 +11,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
-import org.dive4elements.river.artifacts.sinfo.SInfoI18NStrings;
 import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoExporter;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
 import org.dive4elements.river.artifacts.sinfo.util.MetaAndTableJRDataSource;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
-import org.dive4elements.river.utils.RiverUtils;
 
 import au.com.bytecode.opencsv.CSVWriter;
 
@@ -30,18 +29,12 @@
  * @author Gernot Belger
  */
 // REMARK: must be public because its registered in generators.xml
-public class FlowDepthExporter extends AbstractSInfoExporter<FlowDepthRow, FlowDepthCalculationResult, FlowDepthCalculationResults> {
+public class FlowDepthExporter extends AbstractSInfoExporter<FlowDepthCalculationResult, FlowDepthCalculationResults> {
 
     /** The log used in this exporter. */
     private static Logger log = Logger.getLogger(FlowDepthExporter.class);
 
-    private static final String CSV_FLOWDEPTHMINMAX_HEADER = "sinfo.export.flow_depth_minmax.csv.header.flowdepthminmax";
-
-    private static final String CSV_FLOWDEPTHTKHMINMAX_HEADER = "sinfo.export.flow_depth_minmax.csv.header.flowdepthTkh";
-
-    private static final String CSV_TKH_HEADER = "sinfo.export.flow_depth.csv.header.tkh";
-
-    private static final String JASPER_FILE = "/jasper/sinfo.flowdepthminmax.jasper";
+    private static final String JASPER_FILE = "/jasper/sinfo.flowdepth.jasper";
 
     @Override
     protected Logger getLog() {
@@ -74,34 +67,34 @@
      * @param river
      * @param useTkh
      */
-
     @Override
     protected void writeCSVHeader(final CSVWriter writer, final FlowDepthCalculationResults results, final RiverInfo river) {
         log.info("FlowDepthExporter.writeCSVHeader");
 
         final Collection<String> header = new ArrayList<>(11);
 
-        header.add(msg(SInfoI18NStrings.CSV_KM_HEADER));
-        header.add(msgUnit(CSV_FLOWDEPTHMINMAX_HEADER, SInfoI18NStrings.UNIT_M));
-        if (getData().isUseTkh()) {
-            header.add(msgUnit(CSV_FLOWDEPTHTKHMINMAX_HEADER, SInfoI18NStrings.UNIT_M));
-            header.add(msgUnit(CSV_TKH_HEADER, SInfoI18NStrings.UNIT_CM));
+        header.add(msg(SInfoResultType.station.getCsvHeader()));
+        header.add(msgUnit(SInfoResultType.flowdepth.getCsvHeader(), SInfoResultType.flowdepth.getUnit()));
+
+        if (results.isUseTkh()) {
+            header.add(msgUnit(SInfoResultType.flowdepthtkh.getCsvHeader(), SInfoResultType.flowdepthtkh.getUnit()));
+            header.add(msgUnit(SInfoResultType.tkh.getCsvHeader(), SInfoResultType.tkh.getUnit()));
         }
 
-        header.add(msgUnit(SInfoI18NStrings.CSV_WATERLEVEL_HEADER, river.getWstUnit()));
-        header.add(msgUnit(SInfoI18NStrings.CSV_DISCHARGE_HEADER, SInfoI18NStrings.UNIT_CUBIC_M));
-        header.add(msg(SInfoI18NStrings.CSV_LABEL_HEADER));
-        header.add(msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
-        header.add(msgUnit(SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER, river.getWstUnit()));
-        header.add(msg(SInfoI18NStrings.CSV_SOUNDING_HEADER));
-        header.add(msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
+        header.add(msgUnit(SInfoResultType.waterlevel.getCsvHeader(), river.getWstUnit()));
+        header.add(msgUnit(SInfoResultType.discharge.getCsvHeader(), SInfoResultType.discharge.getUnit()));
+        header.add(msg(SInfoResultType.waterlevelLabel.getCsvHeader()));
+        header.add(msg(SInfoResultType.gaugeLabel.getCsvHeader()));
+        header.add(msgUnit(SInfoResultType.meanBedHeight.getCsvHeader(), river.getWstUnit()));
+        header.add(msg(SInfoResultType.soundingLabel.getCsvHeader()));
+        header.add(msg(SInfoResultType.location.getCsvHeader()));
 
         writer.writeNext(header.toArray(new String[header.size()]));
     }
 
     @Override
-    protected String[] formatCSVRow(final FlowDepthCalculationResults results, final FlowDepthRow row) {
-        return formatFlowDepthRow(row);
+    protected String[] formatCSVRow(final FlowDepthCalculationResults results, final FlowDepthCalculationResult result, final SInfoResultRow row) {
+        return formatRow(row);
     }
 
     /**
@@ -109,50 +102,25 @@
      *
      * @param useTkh
      */
-    private String[] formatFlowDepthRow(final FlowDepthRow row) {
+    private String[] formatRow(final SInfoResultRow row) {
 
         final Collection<String> lines = new ArrayList<>(11);
 
-        // Fluss-km
-        lines.add(getKmFormatter().format(row.getStation()));
-
-        // Fließtiefe [m]
-        lines.add(getFlowDepthFormatter().format(row.getFlowDepth()));
+        lines.add(row.exportValue(this.context, SInfoResultType.station));
+        lines.add(row.exportValue(this.context, SInfoResultType.flowdepth));
 
         if (getData().isUseTkh()) {
-            // Fließtiefe mit TKH [m]
-            lines.add(getFlowDepthFormatter().format(row.getFlowDepthWithTkh()));
-
-            // TKH [cm]
-            lines.add(getTkhFormatter().format(row.getTkh()));
+            lines.add(row.exportValue(this.context, SInfoResultType.flowdepthtkh));
+            lines.add(row.exportValue(this.context, SInfoResultType.tkh));
         }
 
-        // Wasserstand [NN + m]
-        lines.add(getW2Formatter().format(row.getWaterlevel()));
-
-        // Q [m³/s]
-        final double discharge = row.getDischarge();
-        if (Double.isNaN(discharge))
-            lines.add(StringUtils.EMPTY);
-        else {
-            final double roundedDischarge = RiverUtils.roundQ(discharge);
-            lines.add(getQFormatter().format(roundedDischarge));
-        }
-
-        // Bezeichnung
-        lines.add(row.getWaterlevelLabel());
-
-        // Bezugspegel
-        lines.add(row.getGauge());
-
-        // Mittlere Sohlhöhe [NN + m]
-        lines.add(getMeanBedHeighFormatter().format(row.getMeanBedHeight()));
-
-        // Peilung/Epoche
-        lines.add(row.getSoundageLabel());
-
-        // Lage
-        lines.add(row.getLocation());
+        lines.add(row.exportValue(this.context, SInfoResultType.waterlevel));
+        lines.add(row.exportValue(this.context, SInfoResultType.discharge));
+        lines.add(row.exportValue(this.context, SInfoResultType.waterlevelLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.gaugeLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.meanBedHeight));
+        lines.add(row.exportValue(this.context, SInfoResultType.soundingLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.location));
 
         return lines.toArray(new String[lines.size()]);
     }
@@ -169,21 +137,22 @@
         super.addJRMetaDataDefaults(source, results);
 
         /* column headings */
-        source.addMetaData("station_header", msg(SInfoI18NStrings.CSV_KM_HEADER));
-        source.addMetaData("flowdepth_header", msg(CSV_FLOWDEPTHMINMAX_HEADER));
-        source.addMetaData("flowdepth_tkh_header", msg(CSV_FLOWDEPTHTKHMINMAX_HEADER));
-        source.addMetaData("tkh_header", msg(CSV_TKH_HEADER));
-        source.addMetaData("waterlevel_header", msg(SInfoI18NStrings.CSV_WATERLEVEL_HEADER));
-        source.addMetaData("discharge_header", msg(SInfoI18NStrings.CSV_DISCHARGE_HEADER));
-        source.addMetaData("waterlevel_name_header", msg(SInfoI18NStrings.CSV_LABEL_HEADER));
-        source.addMetaData("gauge_header", msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
-        source.addMetaData("bedheight_header", msg(SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER_SHORT));
-        source.addMetaData("sounding_name_header", msg(SInfoI18NStrings.CSV_SOUNDING_HEADER));
-        source.addMetaData("location_header", msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
+
+        source.addMetaData("station_header", msg(SInfoResultType.station.getCsvHeader()));
+        source.addMetaData("flowdepth_header", msg(SInfoResultType.flowdepth.getCsvHeader()));
+        source.addMetaData("flowdepth_tkh_header", msg(SInfoResultType.flowdepthtkh.getCsvHeader()));
+        source.addMetaData("tkh_header", msg(SInfoResultType.tkh.getCsvHeader()));
+        source.addMetaData("waterlevel_header", msg(SInfoResultType.waterlevel.getCsvHeader()));
+        source.addMetaData("discharge_header", msg(SInfoResultType.discharge.getCsvHeader()));
+        source.addMetaData("waterlevel_name_header", msg(SInfoResultType.waterlevelLabel.getCsvHeader()));
+        source.addMetaData("gauge_header", msg(SInfoResultType.gaugeLabel.getCsvHeader()));
+        source.addMetaData("bedheight_header", msg(SInfoResultType.meanBedHeight.getCsvHeader()));
+        source.addMetaData("sounding_name_header", msg(SInfoResultType.soundingLabel.getCsvHeader()));
+        source.addMetaData("location_header", msg(SInfoResultType.location.getCsvHeader()));
     }
 
     @Override
-    protected String[] formatPDFRow(final FlowDepthCalculationResults results, final FlowDepthRow row) {
-        return formatFlowDepthRow(row);
+    protected String[] formatPDFRow(final FlowDepthCalculationResults results, final SInfoResultRow row) {
+        return formatRow(row);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthRow.java	Wed Mar 14 14:09:33 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
- * Software engineering by
- *  Björnsen Beratende Ingenieure GmbH
- *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
- *
- * This file is Free Software under the GNU AGPL (>=v3)
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details.
- */
-package org.dive4elements.river.artifacts.sinfo.flowdepth;
-
-import org.dive4elements.river.artifacts.sinfo.common.AbstractTkhResultRow;
-import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
-
-/**
- * Part of {@link FlowDepthCalculationResult} which represents one calculated row of flow depth data.
- *
- * @author Gernot Belger
- */
-final class FlowDepthRow extends AbstractTkhResultRow {
-    private static final long serialVersionUID = 1L;
-
-    private final String soundingLabel;
-
-    public FlowDepthRow(final Tkh tkh, final String waterlevelLabel,
-            final String gauge, final String soundingLabel, final String location) {
-
-        super(tkh, waterlevelLabel, gauge, location);
-
-        this.soundingLabel = soundingLabel;
-    }
-
-    public String getSoundageLabel() {
-        return this.soundingLabel;
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.java	Wed Mar 14 14:10:32 2018 +0100
@@ -20,6 +20,8 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
 import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthUtils;
 import org.dive4elements.river.artifacts.sinfo.flowdepthminmax.FlowDepthMinMaxAccess.MinMaxIdPair;
 import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder;
@@ -86,7 +88,7 @@
         /* access real input data from database */
         final String wstId = minMaxPair.getWstId();
         final String minSoundingId = minMaxPair.getMinSoundingId();
-        final String maxSoundingId = minMaxPair.getMinSoundingId();
+        final String maxSoundingId = minMaxPair.getMaxSoundingId();
 
         final BedHeightsFinder minBedHeight = minSoundingId == null ? null : BedHeightsFinder.forId(this.context, minSoundingId, calcRange, problems);
         final BedHeightsFinder maxBedHeight = maxSoundingId == null ? null : BedHeightsFinder.forId(this.context, maxSoundingId, calcRange, problems);
@@ -121,7 +123,7 @@
         final String soundingLabel = buildSoundingLabel(minBedHeight, maxBedHeight);
 
         /* real calculation loop */
-        final Collection<FlowDepthMinMaxRow> rows = new ArrayList<>();
+        final Collection<SInfoResultRow> rows = new ArrayList<>();
 
         // FIXME: determine what is the spatial discretisation that we will use...
         final double[] allKms = wstKms.allKms().toNativeArray();
@@ -146,13 +148,24 @@
                 // REMARK: access the gauge once only during calculation
                 final String gaugeLabel = riverInfoProvider.findGauge(station);
 
-                rows.add(new FlowDepthMinMaxRow(station, minFlowDepth, maxFlowDepth, wst, discharge, waterlevelLabel, gaugeLabel, meanBedHeight, soundingLabel,
-                        location));
+                final SInfoResultRow row = SInfoResultRow.create().//
+                        putValue(SInfoResultType.station, station). //
+                        putValue(SInfoResultType.flowdepthmin, minFlowDepth). //
+                        putValue(SInfoResultType.flowdepthmax, maxFlowDepth). //
+                        putValue(SInfoResultType.waterlevel, wst). //
+                        putValue(SInfoResultType.discharge, discharge). //
+                        putValue(SInfoResultType.waterlevelLabel, waterlevelLabel). //
+                        putValue(SInfoResultType.gaugeLabel, gaugeLabel). //
+                        putValue(SInfoResultType.meanBedHeight, meanBedHeight). //
+                        putValue(SInfoResultType.soundingLabel, soundingLabel). //
+                        putValue(SInfoResultType.location, location);
+                rows.add(row);
             }
         }
 
         final BedHeightInfo minBedHeightInfo = minBedHeight == null ? null : minBedHeight.getInfo();
         final BedHeightInfo maxBedHeightInfo = maxBedHeight == null ? null : maxBedHeight.getInfo();
+
         return new FlowDepthMinMaxCalculationResult(label, wstInfo, minBedHeightInfo, maxBedHeightInfo, rows);
     }
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculationResult.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculationResult.java	Wed Mar 14 14:10:32 2018 +0100
@@ -12,6 +12,7 @@
 import java.util.Collection;
 
 import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoCalculationResult;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
 import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 
@@ -20,7 +21,7 @@
  *
  * @author Gernot Belger
  */
-final class FlowDepthMinMaxCalculationResult extends AbstractSInfoCalculationResult<FlowDepthMinMaxRow> {
+final class FlowDepthMinMaxCalculationResult extends AbstractSInfoCalculationResult {
 
     private static final long serialVersionUID = 1L;
 
@@ -29,7 +30,7 @@
     private final BedHeightInfo maxSounding;
 
     public FlowDepthMinMaxCalculationResult(final String label, final WstInfo wst, final BedHeightInfo minSounding, final BedHeightInfo maxSounding,
-            final Collection<FlowDepthMinMaxRow> rows) {
+            final Collection<SInfoResultRow> rows) {
         super(label, wst, rows);
 
         this.minSounding = minSounding;
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculationResults.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculationResults.java	Wed Mar 14 14:10:32 2018 +0100
@@ -16,7 +16,7 @@
 /**
  * @author Gernot Belger
  */
-final class FlowDepthMinMaxCalculationResults extends AbstractSInfoCalculationResults<FlowDepthMinMaxRow, FlowDepthMinMaxCalculationResult> {
+final class FlowDepthMinMaxCalculationResults extends AbstractSInfoCalculationResults<FlowDepthMinMaxCalculationResult> {
 
     private static final long serialVersionUID = 1L;
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxExporter.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxExporter.java	Wed Mar 14 14:10:32 2018 +0100
@@ -11,15 +11,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
-import org.dive4elements.river.artifacts.sinfo.SInfoI18NStrings;
 import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoExporter;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
 import org.dive4elements.river.artifacts.sinfo.util.MetaAndTableJRDataSource;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
-import org.dive4elements.river.utils.RiverUtils;
 
 import au.com.bytecode.opencsv.CSVWriter;
 
@@ -30,16 +29,12 @@
  * @author Gernot Belger
  */
 // REMARK: must be public because its registered in generators.xml
-public class FlowDepthMinMaxExporter extends AbstractSInfoExporter<FlowDepthMinMaxRow, FlowDepthMinMaxCalculationResult, FlowDepthMinMaxCalculationResults> {
+public class FlowDepthMinMaxExporter extends AbstractSInfoExporter<FlowDepthMinMaxCalculationResult, FlowDepthMinMaxCalculationResults> {
 
     /** The log used in this exporter. */
     private static Logger log = Logger.getLogger(FlowDepthMinMaxExporter.class);
 
-    private static final String CSV_FLOWDEPTH_MIN_HEADER = "sinfo.export.flow_depth_minmax.csv.header.min";
-
-    private static final String CSV_FLOWDEPTH_MAX_HEADER = "sinfo.export.flow_depth_minmax.csv.header.max";
-
-    private static final String JASPER_FILE = "/jasper/sinfo.flowdepth.jasper";
+    private static final String JASPER_FILE = "/jasper/sinfo.flowdepthminmax.jasper";
 
     @Override
     protected Logger getLog() {
@@ -78,72 +73,53 @@
 
         final Collection<String> header = new ArrayList<>(11);
 
-        header.add(msg(SInfoI18NStrings.CSV_KM_HEADER));
-
-        header.add(msgUnit(CSV_FLOWDEPTH_MIN_HEADER, SInfoI18NStrings.UNIT_M));
-        header.add(msgUnit(CSV_FLOWDEPTH_MAX_HEADER, SInfoI18NStrings.UNIT_M));
-
-        header.add(msgUnit(SInfoI18NStrings.CSV_WATERLEVEL_HEADER, river.getWstUnit()));
-        header.add(msgUnit(SInfoI18NStrings.CSV_DISCHARGE_HEADER, SInfoI18NStrings.UNIT_CUBIC_M));
-        header.add(msg(SInfoI18NStrings.CSV_LABEL_HEADER));
-        header.add(msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
-        header.add(msgUnit(SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER, river.getWstUnit()));
-        header.add(msg(SInfoI18NStrings.CSV_SOUNDING_HEADER));
-        header.add(msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
+        header.add(msg(SInfoResultType.station.getCsvHeader()));
+        header.add(msgUnit(SInfoResultType.flowdepthmin.getCsvHeader(), SInfoResultType.flowdepthmin.getUnit()));
+        header.add(msgUnit(SInfoResultType.flowdepthmax.getCsvHeader(), SInfoResultType.flowdepthmax.getUnit()));
+        header.add(msgUnit(SInfoResultType.waterlevel.getCsvHeader(), river.getWstUnit()));
+        header.add(msgUnit(SInfoResultType.discharge.getCsvHeader(), SInfoResultType.discharge.getUnit()));
+        header.add(msg(SInfoResultType.waterlevelLabel.getCsvHeader()));
+        header.add(msg(SInfoResultType.gaugeLabel.getCsvHeader()));
+        header.add(msgUnit(SInfoResultType.meanBedHeight.getCsvHeader(), river.getWstUnit()));
+        header.add(msg(SInfoResultType.soundingLabel.getCsvHeader()));
+        header.add(msg(SInfoResultType.location.getCsvHeader()));
 
         writer.writeNext(header.toArray(new String[header.size()]));
     }
 
     @Override
-    protected String[] formatCSVRow(final FlowDepthMinMaxCalculationResults results, final FlowDepthMinMaxRow row) {
-        return formatFlowDepthRow(row);
+    protected String[] formatCSVRow(final FlowDepthMinMaxCalculationResults results, final FlowDepthMinMaxCalculationResult result, final SInfoResultRow row) {
+        return formatRow(result, row);
     }
 
     /**
      * Format a row of a flow depth result into an array of string, both used by csv and pdf
      *
+     * @param result
+     *
      * @param useTkh
      */
-    private String[] formatFlowDepthRow(final FlowDepthMinMaxRow row) {
-
-        final Collection<String> lines = new ArrayList<>(11);
-
-        // Fluss-km
-        lines.add(getKmFormatter().format(row.getStation()));
-
-        // FIXME: spalten weglassen, wenn min oder max fehlt
-
-        // Minimale Fließtiefe [m]
-        lines.add(getFlowDepthFormatter().format(row.getMinFlowDepth()));
-        // Maximale Fließtiefe [m]
-        lines.add(getFlowDepthFormatter().format(row.getMaxFlowDepth()));
-
-        // Wasserstand [NN + m]
-        lines.add(getW2Formatter().format(row.getWaterlevel()));
+    private String[] formatRow(final FlowDepthMinMaxCalculationResult result, final SInfoResultRow row) {
 
-        // Q [m³/s]
-        final double discharge = row.getDischarge();
-        if (Double.isNaN(discharge))
-            lines.add(StringUtils.EMPTY);
-        else {
-            final double roundedDischarge = RiverUtils.roundQ(discharge);
-            lines.add(getQFormatter().format(roundedDischarge));
-        }
+        final Collection<String> lines = new ArrayList<>(10);
 
-        // Bezeichnung
-        lines.add(row.getWaterlevelLabel());
-
-        // Bezugspegel
-        lines.add(row.getGauge());
+        lines.add(row.exportValue(this.context, SInfoResultType.station));
 
-        // Mittlere Sohlhöhe [NN + m]
-        lines.add(getMeanBedHeighFormatter().format(row.getMeanBedHeight()));
+        // REMARK: null check as pdf will call this with null and in that case we show all columns (to avoid multiple jasper
+        // FIXME: does not work like this: we may have several pairs of min/max; so we need to look at all of them?
+        // templates)
+        // if (result == null || result.getMinSounding() != null)
+        lines.add(row.exportValue(this.context, SInfoResultType.flowdepthmin));
+        // if (result == null || result.getMaxSounding() != null)
+        lines.add(row.exportValue(this.context, SInfoResultType.flowdepthmax));
 
-        // Peilung/Epoche
-        lines.add(row.getSoundageLabel());
-
-        // Lage
-        lines.add(row.getLocation());
+        lines.add(row.exportValue(this.context, SInfoResultType.waterlevel));
+        lines.add(row.exportValue(this.context, SInfoResultType.discharge));
+        lines.add(row.exportValue(this.context, SInfoResultType.waterlevelLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.gaugeLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.meanBedHeight));
+        lines.add(row.exportValue(this.context, SInfoResultType.soundingLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.location));
 
         return lines.toArray(new String[lines.size()]);
     }
@@ -160,20 +136,20 @@
         super.addJRMetaDataDefaults(source, results);
 
         /* column headings */
-        source.addMetaData("station_header", msg(SInfoI18NStrings.CSV_KM_HEADER));
-        source.addMetaData("flowdepthmin_header", msg(CSV_FLOWDEPTH_MIN_HEADER));
-        source.addMetaData("flowdepthmax_header", msg(CSV_FLOWDEPTH_MAX_HEADER));
-        source.addMetaData("waterlevel_header", msg(SInfoI18NStrings.CSV_WATERLEVEL_HEADER));
-        source.addMetaData("discharge_header", msg(SInfoI18NStrings.CSV_DISCHARGE_HEADER));
-        source.addMetaData("waterlevel_name_header", msg(SInfoI18NStrings.CSV_LABEL_HEADER));
-        source.addMetaData("gauge_header", msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
-        source.addMetaData("bedheight_header", msg(SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER_SHORT));
-        source.addMetaData("sounding_name_header", msg(SInfoI18NStrings.CSV_SOUNDING_HEADER));
-        source.addMetaData("location_header", msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
+        source.addMetaData("station_header", msg(SInfoResultType.station.getCsvHeader()));
+        source.addMetaData("flowdepthmin_header", msg(SInfoResultType.flowdepthmin.getCsvHeader()));
+        source.addMetaData("flowdepthmax_header", msg(SInfoResultType.flowdepthmax.getCsvHeader()));
+        source.addMetaData("waterlevel_header", msg(SInfoResultType.waterlevel.getCsvHeader()));
+        source.addMetaData("discharge_header", msg(SInfoResultType.discharge.getCsvHeader()));
+        source.addMetaData("waterlevel_name_header", msg(SInfoResultType.waterlevelLabel.getCsvHeader()));
+        source.addMetaData("gauge_header", msg(SInfoResultType.gaugeLabel.getCsvHeader()));
+        source.addMetaData("bedheight_header", msg(SInfoResultType.meanBedHeight.getCsvHeader()));
+        source.addMetaData("sounding_name_header", msg(SInfoResultType.soundingLabel.getCsvHeader()));
+        source.addMetaData("location_header", msg(SInfoResultType.location.getCsvHeader()));
     }
 
     @Override
-    protected String[] formatPDFRow(final FlowDepthMinMaxCalculationResults results, final FlowDepthMinMaxRow row) {
-        return formatFlowDepthRow(row);
+    protected String[] formatPDFRow(final FlowDepthMinMaxCalculationResults results, final SInfoResultRow row) {
+        return formatRow(null, row);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxRow.java	Wed Mar 14 14:09:33 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
- * Software engineering by
- *  Björnsen Beratende Ingenieure GmbH
- *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
- *
- * This file is Free Software under the GNU AGPL (>=v3)
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details.
- */
-package org.dive4elements.river.artifacts.sinfo.flowdepthminmax;
-
-import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoResultRow;
-
-/**
- * Part of {@link FlowDepthMinMaxCalculationResult} which represents one calculated row of flow depth data.
- *
- * @author Gernot Belger
- */
-final class FlowDepthMinMaxRow extends AbstractSInfoResultRow {
-    private static final long serialVersionUID = 1L;
-
-    private final double station;
-
-    private final double minFlowDepth;
-
-    private final double maxFlowDepth;
-
-    private final double waterlevel;
-
-    private final double discharge;
-
-    private final double meanBedHeight;
-
-    private final String soundingLabel;
-
-    public FlowDepthMinMaxRow(final double station, final double minFlowDepth, final double maxFlowDepth, final double waterlevel, final double discharge,
-            final String waterlevelLabel, final String gauge, final double meanBedHeight, final String soundingLabel, final String location) {
-
-        super(waterlevelLabel, gauge, location);
-
-        this.station = station;
-        this.minFlowDepth = minFlowDepth;
-        this.maxFlowDepth = maxFlowDepth;
-        this.waterlevel = waterlevel;
-        this.discharge = discharge;
-        this.meanBedHeight = meanBedHeight;
-        this.soundingLabel = soundingLabel;
-    }
-
-    public String getSoundageLabel() {
-        return this.soundingLabel;
-    }
-
-    public double getStation() {
-        return this.station;
-    }
-
-    public double getMinFlowDepth() {
-        return this.minFlowDepth;
-    }
-
-    public double getMaxFlowDepth() {
-        return this.maxFlowDepth;
-    }
-
-    public double getWaterlevel() {
-        return this.waterlevel;
-    }
-
-    public double getDischarge() {
-        return this.discharge;
-    }
-
-    public double getMeanBedHeight() {
-        return this.meanBedHeight;
-    }
-
-    public String getSoundingLabel() {
-        return this.soundingLabel;
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java	Wed Mar 14 14:10:32 2018 +0100
@@ -14,6 +14,8 @@
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
 import org.dive4elements.river.model.River;
 
@@ -144,57 +146,67 @@
         }
     }
 
-    public Tkh getTkh(final double km) {
+    public void calculateTkh(final double km, final SInfoResultRow row) {
+
+        row.putValue(SInfoResultType.station, km);
 
         final SoilKind kind = getSoilKind(km);
+        row.putValue(SInfoResultType.soilkind, kind);
 
         final double wst = this.waterlevelProvider.getWaterlevel(km);
+        row.putValue(SInfoResultType.waterlevel, wst);
 
         final double meanBedHeight = this.bedHeightsProvider.getMeanBedHeight(km);
+        row.putValue(SInfoResultType.meanBedHeight, meanBedHeight);
 
         final double flowDepth = wst - meanBedHeight;
+        row.putValue(SInfoResultType.flowdepth, flowDepth);
 
         final double discharge = this.dischargeProvider.getDischarge(km);
+        row.putValue(SInfoResultType.discharge, discharge);
         if (Double.isNaN(discharge))
-            return new Tkh(km, wst, meanBedHeight, flowDepth, Double.NaN, kind);
+            return;
 
         if (!this.hasTkh())
-            return new Tkh(km, wst, meanBedHeight, flowDepth, Double.NaN, kind);
+            return;
 
         final double d50 = getBedMeasurement(km);
         if (Double.isNaN(d50))
-            return new Tkh(km, wst, meanBedHeight, flowDepth, discharge, kind);
+            return;
+        row.putValue(SInfoResultType.d50, d50);
 
         if (!this.flowVelocitiesFinder.findKmQValues(km, discharge)) {
             // TODO: ggf. station in Fehlermeldung?
             final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingVelocity", null, this.problemLabel);
             this.problems.addProblem(km, message);
             // FIXME: cumulate problems to one message?
-            return new Tkh(km, wst, meanBedHeight, flowDepth, discharge, kind);
+            return;
         }
 
         final double velocity = this.flowVelocitiesFinder.getFindVmainFound();
+        row.putValue(SInfoResultType.velocity, velocity);
+
         final double tau = this.flowVelocitiesFinder.getFindTauFound();
+        row.putValue(SInfoResultType.tau, tau);
 
         final double tkh = calculateTkh(wst - meanBedHeight, velocity, d50, tau);
-        double tkhUp;
-        double tkhDown;
+        row.putValue(SInfoResultType.tkh, tkh);
+
         switch (kind) {
         case starr:
-            tkhUp = tkh;
-            tkhDown = 0;
+            row.putValue(SInfoResultType.tkhup, tkh);
+            row.putValue(SInfoResultType.tkhdown, 0.0);
             break;
 
         case mobil:
         default:
-            tkhUp = tkh / 2;
-            tkhDown = -tkh / 2;
+            row.putValue(SInfoResultType.tkhup, tkh / 2);
+            row.putValue(SInfoResultType.tkhdown, -tkh / 2);
             break;
         }
 
-        final double flowDepthTkh = calculateFlowDepthTkh(tkhUp, kind, wst, meanBedHeight);
-
-        return new Tkh(km, wst, meanBedHeight, flowDepth, flowDepthTkh, discharge, kind, tkh, tkhUp, tkhDown, velocity, d50, tau);
+        final double flowDepthTkh = calculateFlowDepthTkh(tkh, kind, wst, meanBedHeight);
+        row.putValue(SInfoResultType.flowdepthtkh, flowDepthTkh);
     }
 
     /**
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Wed Mar 14 14:10:32 2018 +0100
@@ -28,8 +28,9 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
 import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder;
-import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
 import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator;
 import org.dive4elements.river.artifacts.sinfo.tkhcalculation.WaterlevelValuesFinder;
 import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
@@ -134,7 +135,7 @@
             return null;
         }
 
-        final Collection<TkhResultRow> rows = new ArrayList<>();
+        final Collection<SInfoResultRow> rows = new ArrayList<>();
 
         /* using wst-kms as basis, because we know that they are generated wst's with a fixed km-step */
         // FIXME: das führt dazu, das aktuell die Sohlhöhen beliebig linear interpolierrt werden. ist das immer richtig? z.b.
@@ -150,13 +151,15 @@
             if (tkhCalculator == null)
                 continue;
 
-            final Tkh tkh = tkhCalculator.getTkh(station);
+            final SInfoResultRow row = SInfoResultRow.create();
 
-            final String description = descBuilder.getDesc(wkms);
-            final String gaugeLabel = riverInfoProvider.findGauge(station);
-            final String location = riverInfoProvider.getLocation(station);
+            row.putValue(SInfoResultType.waterlevelLabel, descBuilder.getDesc(wkms));
+            row.putValue(SInfoResultType.gaugeLabel, riverInfoProvider.findGauge(station));
+            row.putValue(SInfoResultType.location, riverInfoProvider.getLocation(station));
 
-            rows.add(new TkhResultRow(tkh, description, gaugeLabel, location));
+            tkhCalculator.calculateTkh(station, row);
+
+            rows.add(row);
         }
 
         return new TkhCalculationResult(wstLabel, wstInfo, true, rows);
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResult.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResult.java	Wed Mar 14 14:10:32 2018 +0100
@@ -12,6 +12,7 @@
 import java.util.Collection;
 
 import org.dive4elements.river.artifacts.sinfo.common.AbstractTkhCalculationResult;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 
 /**
@@ -19,11 +20,11 @@
  *
  * @author Gernot Belger
  */
-final class TkhCalculationResult extends AbstractTkhCalculationResult<TkhResultRow> {
+final class TkhCalculationResult extends AbstractTkhCalculationResult {
 
     private static final long serialVersionUID = 1L;
 
-    public TkhCalculationResult(final String label, final WstInfo wst, final boolean hasTkh, final Collection<TkhResultRow> rows) {
+    public TkhCalculationResult(final String label, final WstInfo wst, final boolean hasTkh, final Collection<SInfoResultRow> rows) {
         super(label, wst, hasTkh, rows);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResults.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResults.java	Wed Mar 14 14:10:32 2018 +0100
@@ -16,7 +16,7 @@
 /**
  * @author Gernot Belger
  */
-final class TkhCalculationResults extends AbstractSInfoCalculationResults<TkhResultRow, TkhCalculationResult> {
+final class TkhCalculationResults extends AbstractSInfoCalculationResults<TkhCalculationResult> {
 
     private static final long serialVersionUID = 1L;
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhExporter.java	Wed Mar 14 14:09:33 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhExporter.java	Wed Mar 14 14:10:32 2018 +0100
@@ -12,12 +12,12 @@
 import java.util.Collection;
 
 import org.apache.log4j.Logger;
-import org.dive4elements.river.artifacts.sinfo.SInfoI18NStrings;
 import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoExporter;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.util.MetaAndTableJRDataSource;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
-import org.dive4elements.river.utils.RiverUtils;
 
 import au.com.bytecode.opencsv.CSVWriter;
 
@@ -27,7 +27,7 @@
  * @author Gernot Belger
  */
 // REMARK: must be public because its registered in generators.xml
-public class TkhExporter extends AbstractSInfoExporter<TkhResultRow, TkhCalculationResult, TkhCalculationResults> {
+public class TkhExporter extends AbstractSInfoExporter<TkhCalculationResult, TkhCalculationResults> {
 
     private static enum ExportMode {
         pdf, csv
@@ -38,12 +38,7 @@
 
     private static final String CSV_META_CALCULATION_FORMULA = "sinfo.export.tkh.calculation.formula";
 
-    private static final String CSV_TKH_HEADER = "sinfo.export.tkh.csv.header.tkh";
-
-    private static final String CSV_TKHKIND_HEADER = "sinfo.export.tkh.csv.header.tkhkind";
-
-    private static final String PREFIX_TKH_KIND = "sinfo.export.tkh.soilkind.";
-
+    // FIXME: use pgetPdfHeader instead of getCsvHeader
     private static final String CSV_MEAN_BED_HEIGHT_HEADER_SHORT = "sinfo.export.flow_depth.csv.header.mean_bed_height.short";
 
     private static final String JASPER_FILE = "/jasper/sinfo.flowdepth.jasper";
@@ -75,20 +70,19 @@
 
         final Collection<String> header = new ArrayList<>(11);
 
-        header.add(msg(SInfoI18NStrings.CSV_KM_HEADER));
-        header.add(msgUnit(CSV_TKH_HEADER, SInfoI18NStrings.UNIT_CM));
-        header.add(msg(CSV_TKHKIND_HEADER));
-        header.add(msgUnit(SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER, river.getWstUnit()));
-
-        header.add(msgUnit(SInfoI18NStrings.CSV_WATERLEVEL_HEADER, river.getWstUnit()));
-        header.add(msgUnit(SInfoI18NStrings.CSV_DISCHARGE_HEADER, SInfoI18NStrings.UNIT_CUBIC_M));
+        header.add(msg(SInfoResultType.station.getCsvHeader()));
+        header.add(msgUnit(SInfoResultType.tkh.getCsvHeader(), SInfoResultType.tkh.getUnit()));
+        header.add(msg(SInfoResultType.soilkind.getCsvHeader()));
+        header.add(msgUnit(SInfoResultType.meanBedHeight.getCsvHeader(), river.getWstUnit()));
+        header.add(msgUnit(SInfoResultType.waterlevel.getCsvHeader(), river.getWstUnit()));
+        header.add(msgUnit(SInfoResultType.discharge.getCsvHeader(), SInfoResultType.discharge.getUnit()));
 
         final String descriptionHeader = results.getDescriptionHeader();
         if (descriptionHeader != null)
             header.add(msg(descriptionHeader));
 
-        header.add(msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
-        header.add(msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
+        header.add(msg(SInfoResultType.gaugeLabel.getCsvHeader()));
+        header.add(msg(SInfoResultType.location.getCsvHeader()));
 
         writer.writeNext(header.toArray(new String[header.size()]));
     }
@@ -100,13 +94,13 @@
         final WstInfo wst = result.getWst();
         super.writeCSVWaterlevelMetadata(writer, wst);
 
-        // TODO:
+        // FIXME:
         // "# W/Pegel [cm]: " (nur bei Eingabe des Wasserstands am Pegel)
         // "# Q (m³/s): " (nur bei Eingabe des Durchflusses)
     }
 
     @Override
-    protected String[] formatCSVRow(final TkhCalculationResults results, final TkhResultRow row) {
+    protected String[] formatCSVRow(final TkhCalculationResults results, final TkhCalculationResult result, final SInfoResultRow row) {
         return formatRow(results, row, ExportMode.csv);
     }
 
@@ -117,40 +111,23 @@
      *
      * @param useTkh
      */
-    private String[] formatRow(final TkhCalculationResults results, final TkhResultRow row, final ExportMode mode) {
+    private String[] formatRow(final TkhCalculationResults results, final SInfoResultRow row, final ExportMode mode) {
 
         final Collection<String> lines = new ArrayList<>(11);
 
-        // Fluss-km
-        lines.add(getKmFormatter().format(row.getStation()));
-
-        // TKH [cm]
-        lines.add(getTkhFormatter().format(row.getTkh()));
-
-        // Einteilung der Gewässersohle (starr/mobil)
-        lines.add(msg(PREFIX_TKH_KIND + row.getTkhKind().name()));
+        lines.add(row.exportValue(this.context, SInfoResultType.station));
+        lines.add(row.exportValue(this.context, SInfoResultType.tkh));
+        lines.add(row.exportValue(this.context, SInfoResultType.soilkind));
+        lines.add(row.exportValue(this.context, SInfoResultType.meanBedHeight));
+        lines.add(row.exportValue(this.context, SInfoResultType.waterlevel));
+        lines.add(row.exportValue(this.context, SInfoResultType.discharge));
 
-        // Mittlere Sohlhöhe [NN + m]
-        lines.add(getMeanBedHeighFormatter().format(row.getMeanBedHeight()));
-
-        // Wasserstand [NN + m]
-        lines.add(getW2Formatter().format(row.getWaterlevel()));
-
-        // Q [m³/s]
-        final double discharge = row.getDischarge();
-        final double roundedDischarge = RiverUtils.roundQ(discharge);
-        lines.add(getQFormatter().format(roundedDischarge));
-
-        // Bezeichnung
         // REMARK: always export this column in pdf-mode, because WInfo also does it (no need for two jasper-templates).
         if (results.getDescriptionHeader() != null || mode == ExportMode.pdf)
-            lines.add(row.getWaterlevelLabel());
+            lines.add(row.exportValue(this.context, SInfoResultType.waterlevelLabel));
 
-        // Bezugspegel
-        lines.add(row.getGauge());
-
-        // Lage
-        lines.add(row.getLocation());
+        lines.add(row.exportValue(this.context, SInfoResultType.gaugeLabel));
+        lines.add(row.exportValue(this.context, SInfoResultType.location));
 
         return lines.toArray(new String[lines.size()]);
     }
@@ -167,24 +144,24 @@
         super.addJRMetaDataDefaults(source, results);
 
         /* column headings */
-        source.addMetaData("station_header", msg(SInfoI18NStrings.CSV_KM_HEADER));
-        source.addMetaData("tkh_header", msg(CSV_TKH_HEADER));
-        source.addMetaData("bedheight_header", msg(CSV_MEAN_BED_HEIGHT_HEADER_SHORT));
-        source.addMetaData("waterlevel_header", msg(SInfoI18NStrings.CSV_WATERLEVEL_HEADER));
-        source.addMetaData("discharge_header", msg(SInfoI18NStrings.CSV_DISCHARGE_HEADER));
+        source.addMetaData("station_header", msg( SInfoResultType.station.getCsvHeader()));
+        source.addMetaData("tkh_header", msg(SInfoResultType.tkh.getCsvHeader()));
+        source.addMetaData("bedheight_header", msg(SInfoResultType.meanBedHeight.getCsvHeader()));
+        source.addMetaData("waterlevel_header", msg(SInfoResultType.waterlevel.getCsvHeader()));
+        source.addMetaData("discharge_header", msg(SInfoResultType.discharge.getCsvHeader()));
 
         // REMARK: actually the column makes no sense if description header is null. But (software symmetry...) WINFO also
         // writes an empty column into the pdf in that case (most probably to avoid the need for two jasper templates).
         final String descriptionHeader = results.getDescriptionHeader();
-        final String waterlevelNameHeader = descriptionHeader == null ? msg(SInfoI18NStrings.CSV_LABEL_HEADER) : descriptionHeader;
+        final String waterlevelNameHeader = descriptionHeader == null ? msg(SInfoResultType.waterlevelLabel.getCsvHeader()) : descriptionHeader;
         source.addMetaData("waterlevel_name_header", waterlevelNameHeader);
 
-        source.addMetaData("gauge_header", msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
-        source.addMetaData("location_header", msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
+        source.addMetaData("gauge_header", msg(SInfoResultType.gaugeLabel.getCsvHeader()));
+        source.addMetaData("location_header", msg(SInfoResultType.location.getCsvHeader()));
     }
 
     @Override
-    protected String[] formatPDFRow(final TkhCalculationResults results, final TkhResultRow row) {
+    protected String[] formatPDFRow(final TkhCalculationResults results, final SInfoResultRow row) {
         return formatRow(results, row, ExportMode.pdf);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhResultRow.java	Wed Mar 14 14:09:33 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
- * Software engineering by
- *  Björnsen Beratende Ingenieure GmbH
- *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
- *
- * This file is Free Software under the GNU AGPL (>=v3)
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details.
- */
-package org.dive4elements.river.artifacts.sinfo.tkhstate;
-
-import org.dive4elements.river.artifacts.sinfo.common.AbstractTkhResultRow;
-import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
-
-/**
- * @author Gernot Belger
- */
-final class TkhResultRow extends AbstractTkhResultRow {
-
-    private static final long serialVersionUID = 1L;
-
-    public TkhResultRow(final Tkh tkh, final String waterlevelLabel, final String gauge, final String location) {
-        super(tkh, waterlevelLabel, gauge, location);
-    }
-}
\ No newline at end of file

http://dive4elements.wald.intevation.org