changeset 8915:d9dbf0b74bc2

Refaktoring of flow depth calculation, extracting tkh part. First implementation of tkh calculation.
author gernotbelger
date Wed, 28 Feb 2018 17:27:15 +0100 (2018-02-28)
parents e3519c3e7a0a
children 5d5d0051723f
files artifacts/doc/conf/artifacts/sinfo.xml artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoResultRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/RiverInfoProvider.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/BedHeightStationComparator.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/BedQualityD50KmValueFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthAccess.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.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/FlowDepthRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowVelocityModelKmValueFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/SoilKind.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/SoilKindKmValueFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkh/TkhState.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkh/WinfoArtifactWrapper.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/BedQualityD50KmValueFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/DischargeValuesFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/FlowVelocityModelKmValueFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/SoilKind.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/SoilKindKmValueFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/Tkh.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhAccess.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/TkhResultRow.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhState.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/WinfoArtifactWrapper.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/CalculationUtils.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/WstInfo.java artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelData.java
diffstat 35 files changed, 2365 insertions(+), 1480 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/doc/conf/artifacts/sinfo.xml	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/doc/conf/artifacts/sinfo.xml	Wed Feb 28 17:27:15 2018 +0100
@@ -118,7 +118,7 @@
       <condition data="calculation_mode" value="sinfo_calc_transport_bodies_heights" operator="equal"/>
     </transition>
 
-    <state id="state.sinfo.transport_bodies_heights" description="state.sinfo.transport_bodies_heights" state="org.dive4elements.river.artifacts.sinfo.tkh.TkhState" helpText="help.state.sinfo.transport_bodies_heights">
+    <state id="state.sinfo.transport_bodies_heights" description="state.sinfo.transport_bodies_heights" state="org.dive4elements.river.artifacts.sinfo.tkhstate.TkhState" helpText="help.state.sinfo.transport_bodies_heights">
       <outputmodes>
         <!-- <outputmode name="sinfo_flow_depth" description="output.flow_depth" mime-type="image/png" type="chart"> <facets> <facet name="sinfo_flow_depth.filtered" description="Facet for mean flow depth, filtered by current zoom state"/> <facet name="sinfo_flow_depth.tkh.filtered" description="Facet for mean flow depth including tkh, filtered by current zoom state"/> <facet name="sinfo_flow_depth.tkh" description="Facet for tkh"/> <facet name="longitudinal_section.annotations" description="facet.longitudinal_section.annotations"/> 
           </facets> </outputmode> <outputmode name="sinfo_flowdepth_export" description="output.sinfo_flowdepth_export" mime-type="text/plain" type="export"> <facets> <facet name="csv" description="facet.sinfo_flowdepth_export.csv"/> <facet name="pdf" description="facet.sinfo_flowdepth_export.pdf"/> </facets> </outputmode> <outputmode name="sinfo_flowdepth_report" description="output.sinfo_flowdepth_report" mime-type="text/xml" type="report"> <facets> <facet name="report" description="facet.sinfo_flowdepth_report"/> 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoCalculationResult.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,134 @@
+/** 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.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.SoilKind;
+import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * @author Gernot Belger
+ */
+public abstract class AbstractSInfoCalculationResult<ROW extends AbstractSInfoResultRow> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Collection<ROW> rows;
+
+    private final String label;
+
+    private final boolean hasTkh;
+
+    private final WstInfo wst;
+
+    public AbstractSInfoCalculationResult(final String label, final WstInfo wst, final boolean hasTkh, final Collection<ROW> rows) {
+        this.label = label;
+        this.wst = wst;
+        this.hasTkh = hasTkh;
+        this.rows = new ArrayList<>(rows);
+    }
+
+    public final String getLabel() {
+        return this.label;
+    }
+
+    public final boolean hasTkh() {
+        return this.hasTkh;
+    }
+
+    public final WstInfo getWst() {
+        return this.wst;
+    }
+
+    public final void addRow(final ROW resultRow) {
+        this.rows.add(resultRow);
+    }
+
+    public final Collection<ROW> getRows() {
+        return Collections.unmodifiableCollection(this.rows);
+    }
+
+    public final double[][] getTkhUpPoints() {
+        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
+        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
+        final List<SoilKind> kinds = new ArrayList<>(this.rows.size());
+
+        for (final ROW row : this.rows) {
+            xPoints.add(row.getStation());
+            yPoints.add(row.getTkhUp());
+            kinds.add(row.getTkhKind());
+        }
+
+        return adjustTkhVisualization(xPoints, yPoints, kinds);
+    }
+
+    public final double[][] getTkhDownPoints() {
+        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
+        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
+        final List<SoilKind> kinds = new ArrayList<>(this.rows.size());
+
+        for (final ROW row : this.rows) {
+            xPoints.add(row.getStation());
+            yPoints.add(row.getTkhDown());
+            kinds.add(row.getTkhKind());
+        }
+
+        return adjustTkhVisualization(xPoints, yPoints, kinds);
+    }
+
+    /**
+     * 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) {
+
+        final TDoubleArrayList adjustedX = new TDoubleArrayList(xPoints.size());
+        final TDoubleArrayList adjustedY = new TDoubleArrayList(yPoints.size());
+
+        adjustedX.add(xPoints.get(0));
+        adjustedY.add(yPoints.get(0));
+
+        for (int i = 1; i < xPoints.size(); 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 middleX = (x1 + x2) / 2;
+
+                // REMARK: we can't produce a 100% vertical line, as the area-renderer will not work correctly
+                adjustedX.add(middleX - 0.0001);
+                adjustedY.add(y1);
+
+                adjustedX.add(middleX + 0.0001);
+                adjustedY.add(y2);
+            }
+
+            /* always add the real point now */
+            adjustedX.add(xPoints.get(i));
+            adjustedY.add(yPoints.get(i));
+        }
+
+        return new double[][] { adjustedX.toNativeArray(), adjustedY.toNativeArray() };
+    }
+}
\ 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/AbstractSInfoResultRow.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,83 @@
+/** 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 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 AbstractSInfoResultRow implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final Tkh tkh;
+
+    private final String waterlevelLabel;
+
+    private final String gauge;
+
+    private final String location;
+
+    public AbstractSInfoResultRow(final Tkh tkh, final String waterlevelLabel, final String gauge, final String location) {
+        this.tkh = tkh;
+        this.waterlevelLabel = waterlevelLabel;
+        this.gauge = gauge;
+        this.location = location;
+    }
+
+    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 String getWaterlevelLabel() {
+        return this.waterlevelLabel;
+    }
+
+    public final String getGauge() {
+        return this.gauge;
+    }
+
+    public final double getMeanBedHeight() {
+        return this.tkh.getMeanBedHeight();
+    }
+
+    public final String getLocation() {
+        return this.location;
+    }
+}
\ 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/RiverInfoProvider.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,116 @@
+/** 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.util.List;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.model.LocationProvider;
+import org.dive4elements.river.artifacts.model.WKms;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.states.WaterlevelData;
+import org.dive4elements.river.model.Gauge;
+import org.dive4elements.river.model.River;
+import org.dive4elements.river.utils.GaugeIndex;
+
+/**
+ * @author Gernot Belger
+ *
+ */
+public final class RiverInfoProvider {
+
+    private static final String CSV_NOT_IN_GAUGE_RANGE = "export.waterlevel.csv.not.in.gauge.range";
+
+    private final River river;
+    private final GaugeIndex gaugeIndex;
+    private final Gauge refGauge;
+    private final boolean showAllGauges;
+    private final String notinrange;
+
+    public static RiverInfoProvider forRange(final CallContext context, final River river, final DoubleRange calcRange) {
+
+        final List<Gauge> gauges = river.determineGauges(calcRange.getMinimumDouble(), calcRange.getMaximumDouble());
+        final GaugeIndex gaugeIndex = new GaugeIndex(gauges);
+
+        final String notinrange = Resources.getMsg(context.getMeta(), CSV_NOT_IN_GAUGE_RANGE, CSV_NOT_IN_GAUGE_RANGE);
+
+        return new RiverInfoProvider(notinrange, river, false, gaugeIndex, null);
+    }
+
+    private RiverInfoProvider(final String notinrange, final River river, final boolean showAllGauges, final GaugeIndex gaugeIndex, final Gauge refGauge) {
+        this.notinrange = notinrange;
+        this.river = river;
+        this.showAllGauges = showAllGauges;
+        this.gaugeIndex = gaugeIndex;
+        this.refGauge = refGauge;
+    }
+
+    public RiverInfoProvider forWaterlevel(final WaterlevelData waterlevel) {
+        final WKms wstKms = waterlevel.getWkms();
+        final Gauge waterlevelRefGauge = findReferenceGauge(wstKms);
+        final boolean waterlevelShowAllGauges = waterlevel.isShowAllGauges();
+
+        return new RiverInfoProvider(this.notinrange, this.river, waterlevelShowAllGauges, this.gaugeIndex, waterlevelRefGauge);
+    }
+
+    /**
+     * Re-determines the reference gauge, in the same way as the WaterlevelArtifact would do it
+     */
+    private Gauge findReferenceGauge(final WKms wkms) {
+
+        final double[] wstFromTo = findWstFromTo(wkms);
+        return this.river.determineRefGauge(wstFromTo, true);
+    }
+
+    private static double[] findWstFromTo(final WKms wkms) {
+
+        final double from = wkms.getKm(0);
+        final double to = wkms.getKm(wkms.size() - 1);
+
+        final boolean waterIncreasing = wkms.guessWaterIncreasing();
+        if (waterIncreasing)
+            return new double[] { to, from };
+
+        return new double[] { from, to };
+    }
+
+    public String getLocation(final double km) {
+        return LocationProvider.getLocation(this.river.getName(), km);
+    }
+
+    public String findGauge(final double km) {
+        // REMARK: access the gauge once only during calculation
+        final Gauge gauge = getGauge(km);
+
+        return gauge == null ? this.notinrange : gauge.getName();
+    }
+
+    private Gauge getGauge(final double km) {
+
+        // REMARK: using same logic as in WaterlevelExporter here
+
+        if (this.showAllGauges)
+            return this.gaugeIndex.findGauge(km);
+
+        if (this.refGauge.getRange().contains(km))
+            return this.refGauge;
+
+        return null;
+    }
+
+    public String getReferenceGauge() {
+        return this.refGauge == null ? this.notinrange : this.refGauge.getName();
+    }
+
+    public River getRiver() {
+        return this.river;
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/BedHeightStationComparator.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +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 java.util.Comparator;
-
-import org.dive4elements.river.model.BedHeightValue;
-
-/**
- * @author Gernot Belger
- */
-public class BedHeightStationComparator implements Comparator<BedHeightValue> {
-
-    @Override
-    public int compare(final BedHeightValue o1, final BedHeightValue o2) {
-        final Double s1 = o1.getStation();
-        final Double s2 = o2.getStation();
-        return Double.compare(s1, s2);
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/BedQualityD50KmValueFinder.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,235 +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 java.util.Date;
-import java.util.List;
-
-import org.apache.commons.lang.math.DoubleRange;
-import org.apache.commons.math.ArgumentOutsideDomainException;
-import org.apache.commons.math.analysis.interpolation.LinearInterpolator;
-import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
-import org.apache.log4j.Logger;
-import org.dive4elements.river.artifacts.math.Utils;
-import org.dive4elements.river.artifacts.model.DateRange;
-import org.dive4elements.river.backend.SedDBSessionHolder;
-import org.dive4elements.river.model.River;
-import org.dive4elements.river.utils.DoubleUtil;
-import org.hibernate.SQLQuery;
-import org.hibernate.Session;
-import org.hibernate.type.StandardBasicTypes;
-
-import gnu.trove.TDoubleArrayList;
-
-/**
- * Searchable sorted km array with parallel bed measurements value array and linear interpolation for km and d50 between the array elements.<br />
- * <br />
- * See comment of SQL command on how the values are filtered and aggregated.
- * 
- * @author Matthias Schäfer
- *
- */
-public class BedQualityD50KmValueFinder {
-
-    /***** INNER CLASSES *****/
-    
-    /**
-     * A bed measurements aggregate with its d50 characteristic grain diameter 
-     */
-    private class D50Measurement {
-        private double km;
-        public double getKm() {
-            return km;
-        }
-        private Date mindate;
-        public Date getMinDate() {
-            return mindate;
-        }
-        private Date maxdate;
-        public Date getMaxDate() {
-            return maxdate;
-        }
-        private int cnt;
-        public int getCnt() {
-            return cnt;
-        }
-        private double mindepth;
-        public double getMinDepth() {
-            return mindepth;
-        }
-        private double maxdepth;
-        public double getMaxDepth() {
-            return maxdepth;
-        }
-        private double d50;
-        /**
-         * D50 in m
-         */
-        public double getD50() {
-            return d50;
-        }
-        /**
-         * Parameter constructor
-         */
-        public D50Measurement(double km, Date mindate, Date maxdate, int cnt, double mindepth, double maxdepth, double d50mm) {
-            this.km = km;
-            this.mindate = mindate;
-            this.maxdate = maxdate;
-            this.cnt = cnt;
-            this.mindepth = mindepth;
-            this.maxdepth = maxdepth;
-            this.d50 = d50mm / 1000;
-        }
-
-        /**
-         * Query result row constructor
-         */
-        public D50Measurement(Object[] tuple, String[] aliases) {
-            km = 0;
-            mindate = null;
-            maxdate = null;
-            cnt = 0;
-            mindepth = Double.NaN;
-            maxdepth = Double.NaN;
-            d50 = Double.NaN;
-            for (int i = 0; i < tuple.length; ++i) {
-                if (tuple[i] == null)
-                    continue;
-                switch (aliases[i]) {
-                case "km":
-                    km = ((Number) tuple[i]).doubleValue();
-                    break;
-                case "mindate":
-                    mindate = (Date) tuple[i];
-                    break;
-                case "maxdate":
-                    maxdate = (Date) tuple[i];
-                    break;
-                case "cnt":
-                     cnt = ((Number) tuple[i]).intValue();
-                   break;
-                case "mindepth":
-                     mindepth = ((Number) tuple[i]).doubleValue();
-                   break;
-                case "maxdepth":
-                    maxdepth = ((Number) tuple[i]).doubleValue();
-                    break;
-                case "d50":
-                    d50 = ((Number) tuple[i]).doubleValue() / 1000;     // mm to m
-                    break;
-                default:
-                    break;
-                }
-            }
-        }
-    }
-    
-    /***** FIELDS *****/
-    
-    /**
-     * Private log to use here.
-     */
-    private static Logger log = Logger.getLogger(BedQualityD50KmValueFinder.class);
-
-    /**
-     * Query that aggregates by km for a km range and a time period all sub layer bed measurements with their d50<br />
-     * <br />
-     * A km may have bed measurements for multiple dates, multiple distances from the river bank, and multiple depth layers.
-     * The query filters by km range, time period and layer (sub layer: below bed to max. 50 cm depth).
-     * Those measurements are then grouped by km, and the D50 aggregated as average value.
-     */
-    private static final String SQL_BED_D50_SUBLAYER_MEASUREMENT =
-            "SELECT t.km, MIN(t.datum) AS mindate, MAX(t.datum) AS maxdate, COUNT(*) AS cnt,"
-                + " MIN(p.tiefevon) AS mindepth, MAX(p.tiefebis) AS maxdepth, AVG(a.d50) AS d50"
-                + " FROM sohltest t INNER JOIN station s ON t.stationid = s.stationid"
-                + "    INNER JOIN gewaesser g ON s.gewaesserid = g.gewaesserid"
-                + "    INNER JOIN sohlprobe p ON t.sohltestid = p.sohltestid"
-                + "    INNER JOIN siebanalyse a ON p.sohlprobeid = a.sohlprobeid"
-                + " WHERE (g.name = :name) AND (s.km BETWEEN :fromkm - 0.0001 AND :tokm + 0.0001)"
-                + "    AND (p.tiefevon > 0.0) AND (p.tiefebis <= 0.5)"
-                + "    AND (t.datum BETWEEN :fromdate AND :todate)"
-                + " GROUP BY t.km"
-                + " ORDER BY t.km";
-    private static final String[] SQL_BED_D50_SELECT_ALIAS = {"km", "mindate", "maxdate", "cnt", "mindepth", "maxdepth", "d50"};
-
-    /**
-     * Real linear interpolator for kms and d50 values
-     */
-    private PolynomialSplineFunction interpolator;
-    
-    
-    /***** METHODS *****/
-    
-    /**
-     * Returns the d50 value interpolated according to a km
-     * @return d50 (mm) of the km, or NaN
-     */
-    public double findD50(double km) throws ArgumentOutsideDomainException {
-        return interpolator.value(km);
-        /* ohne interpolation:
-        if ((kms == null) || (kms.size() == 0))
-            return Double.NaN;
-        int i = kms.binarySearch(km);
-        if (i >= 0)
-            return values.get(i);
-        i = -i - 1;
-        if ((i - 1 >= 0) && Utils.epsilonEquals(km, kms.get(i - 1), 0.0001))
-            return values.get(i - 1);
-        else if ((i >= 0) && (i <= kms.size() - 1) && Utils.epsilonEquals(km, kms.get(i), 0.0001))
-            return values.get(i);
-        else
-            return Double.NaN; */
-    }
-    
-    /**
-     * Loads the range of the river's kms with their associated values.
-     * @return Whether the load has been successful
-     */
-    public boolean loadValues(final River river, final DoubleRange kmRange, final DateRange dateRange) {
-        log.debug(String.format("loadValues km %.3f - %.3f %tF - %tF", kmRange.getMinimumDouble(), kmRange.getMaximumDouble(), dateRange.getFrom(), dateRange.getTo()));
-        Session session = SedDBSessionHolder.HOLDER.get();
-        SQLQuery sqlQuery = session.createSQLQuery(SQL_BED_D50_SUBLAYER_MEASUREMENT)
-                .addScalar("km", StandardBasicTypes.DOUBLE)
-                .addScalar("mindate", StandardBasicTypes.DATE)
-                .addScalar("maxdate", StandardBasicTypes.DATE)
-                .addScalar("cnt", StandardBasicTypes.INTEGER)
-                .addScalar("mindepth", StandardBasicTypes.DOUBLE)
-                .addScalar("maxdepth", StandardBasicTypes.DOUBLE)
-                .addScalar("d50", StandardBasicTypes.DOUBLE);
-        String seddbRiver = river.nameForSeddb();
-        sqlQuery.setString("name", seddbRiver);
-        sqlQuery.setDouble("fromkm", kmRange.getMinimumDouble());
-        sqlQuery.setDouble("tokm", kmRange.getMaximumDouble());
-        sqlQuery.setDate("fromdate", dateRange.getFrom());
-        sqlQuery.setDate("todate", dateRange.getTo());
-        @SuppressWarnings("unchecked")
-        final List<Object[]> rows = sqlQuery.list();
-        final double[] kms = new double[rows.size()];
-        final double[] values = new double[rows.size()];
-        D50Measurement measurement;
-        int i = -1;
-        for (Object[] row : rows) {
-            measurement = new D50Measurement(row, SQL_BED_D50_SELECT_ALIAS);
-            i++;
-            kms[i] = measurement.getKm();
-            values[i] = measurement.getD50();
-            log.debug(String.format("loadValues km %.3f d50(mm) %.1f count %d", kms[i], values[i], measurement.getCnt()));
-        }
-        try {
-            interpolator = new LinearInterpolator().interpolate(kms, values);
-            return true;
-        } catch (Exception e) {
-            interpolator = null;
-            return false;
-        }
-    }
-
-}
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthAccess.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthAccess.java	Wed Feb 28 17:27:15 2018 +0100
@@ -1,6 +1,6 @@
 /* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
- * Software engineering by 
- *  Björnsen Beratende Ingenieure GmbH 
+ * 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)
@@ -14,6 +14,7 @@
 import java.util.Collection;
 import java.util.Collections;
 
+import org.apache.commons.lang.math.DoubleRange;
 import org.dive4elements.river.artifacts.access.RangeAccess;
 import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
 import org.dive4elements.river.artifacts.sinfo.SinfoCalcMode;
@@ -21,68 +22,71 @@
 
 /**
  * Access to the flow depth calculation type specific SInfo artifact data.
- * REMARK: this class is NOT intended to be hold in the results (or anywhere else), in order to avoid a permanent reference to the artifact instance. 
+ * REMARK: this class is NOT intended to be hold in the results (or anywhere else), in order to avoid a permanent
+ * reference to the artifact instance.
  * Hence we do NOT cache any data.
- * 
+ *
  * @author Gernot Belger
  */
-public class FlowDepthAccess
-extends      RangeAccess
-{
-	public static class DifferencesPair
-	{
-		private final String wstId;
-		private final String soundingId;
-
-		public DifferencesPair( final String wstId, final String soundingId ) {
-			this.wstId = wstId;
-			this.soundingId = soundingId;
-		}
-		
-		public String getWstId() {
-			return this.wstId;
-		}
-
-		public String getSoundingId() {
-			return this.soundingId;
-		}
-	}
-
-	private static final String FIELD_USE_TKH = "use_transport_bodies"; //$NON-NLS-1$
-
-	public FlowDepthAccess(final SINFOArtifact artifact) {
-		super(artifact);
+final class FlowDepthAccess extends RangeAccess {
+    public static class DifferencesPair {
+        private final String wstId;
+        private final String soundingId;
 
-		/* assert calculation mode */
-		final SinfoCalcMode calculationMode = artifact.getCalculationMode();
-		assert(calculationMode == SinfoCalcMode.sinfo_calc_flow_depth);
-	}
-
-	public boolean isUseTransportBodies() {
-		final Boolean useTkh = artifact.getDataAsBoolean( FIELD_USE_TKH );
-		return useTkh == null ? false : useTkh;
-	}
-
-	public Collection<DifferencesPair> getDifferencePairs() {
-
-		final Collection<DifferencesPair> diffPairs = new ArrayList<>();
+        public DifferencesPair(final String wstId, final String soundingId) {
+            this.wstId = wstId;
+            this.soundingId = soundingId;
+        }
 
-		 final String diffids = super.getString("diffids");
-		 if( diffids == null )
-		 {
-			 // Should never happen as this is handled by the ui
-			 return Collections.emptyList();
-		 }
+        public String getWstId() {
+            return this.wstId;
+        }
 
-		 // FIXME: this way of parsing the datacage-ids is repeated all over flys!
-		 final String datas[] = diffids.split("#");
-		 for(int i = 0; i < datas.length; i+=2) {
-			 final String leftId = StringUtil.unbracket( datas[i] );
-			 final String rightId = StringUtil.unbracket( datas[i+1] );
+        public String getSoundingId() {
+            return this.soundingId;
+        }
+    }
 
-			 diffPairs.add(new DifferencesPair(leftId, rightId));
-		 }
+    private static final String FIELD_USE_TKH = "use_transport_bodies"; //$NON-NLS-1$
 
-		return Collections.unmodifiableCollection(diffPairs);
-	}
+    public FlowDepthAccess(final SINFOArtifact artifact) {
+        super(artifact);
+
+        /* assert calculation mode */
+        final SinfoCalcMode calculationMode = artifact.getCalculationMode();
+        assert (calculationMode == SinfoCalcMode.sinfo_calc_flow_depth);
+    }
+
+    public DoubleRange getRange() {
+        final double from = getFrom();
+        final double to = getTo();
+        return new DoubleRange(from, to);
+    }
+
+    public boolean isUseTransportBodies() {
+        final Boolean useTkh = this.artifact.getDataAsBoolean(FIELD_USE_TKH);
+        return useTkh == null ? false : useTkh;
+    }
+
+    public Collection<DifferencesPair> getDifferencePairs() {
+
+        final Collection<DifferencesPair> diffPairs = new ArrayList<>();
+
+        final String diffids = super.getString("diffids");
+        if (diffids == null) {
+            // Should never happen as this is handled by the ui
+            return Collections.emptyList();
+        }
+
+        // FIXME: this way of parsing the datacage-ids is repeated all over flys!
+        final String datas[] = diffids.split("#");
+        for (int i = 0; i < datas.length; i += 2) {
+            final String leftId = StringUtil.unbracket(datas[i]);
+            final String rightId = StringUtil.unbracket(datas[i + 1]);
+
+            diffPairs.add(new DifferencesPair(leftId, rightId));
+        }
+
+        return Collections.unmodifiableCollection(diffPairs);
+    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.java	Wed Feb 28 17:27:15 2018 +0100
@@ -9,49 +9,33 @@
  */
 package org.dive4elements.river.artifacts.sinfo.flowdepth;
 
-import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
 
 import org.apache.commons.lang.math.DoubleRange;
-import org.apache.commons.math.FunctionEvaluationException;
-import org.apache.commons.math.analysis.UnivariateRealFunction;
-import org.dive4elements.artifacts.ArtifactDatabase;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.BedHeightsArtifact;
 import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.CalculationResult;
-import org.dive4elements.river.artifacts.model.DateRange;
-import org.dive4elements.river.artifacts.model.LocationProvider;
-import org.dive4elements.river.artifacts.model.QKms;
 import org.dive4elements.river.artifacts.model.WKms;
 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.flowdepth.FlowDepthAccess.DifferencesPair;
-import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator;
+import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
+import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 import org.dive4elements.river.artifacts.states.WaterlevelData;
 import org.dive4elements.river.artifacts.states.WaterlevelFetcher;
-import org.dive4elements.river.model.BedHeight;
-import org.dive4elements.river.model.BedHeightValue;
-import org.dive4elements.river.model.Gauge;
 import org.dive4elements.river.model.River;
-import org.dive4elements.river.utils.DoubleUtil;
-import org.dive4elements.river.utils.GaugeIndex;
 import org.dive4elements.river.utils.RiverUtils;
 
 class FlowDepthCalculation {
 
     // private static Logger log = Logger.getLogger(FlowDepthCalculation.class);
 
-    private static final int VALID_BED_MEASUREMENT_YEARS = 20;
-
-    private static final String CSV_NOT_IN_GAUGE_RANGE = "export.waterlevel.csv.not.in.gauge.range";
-
     private final CallContext context;
 
     public FlowDepthCalculation(final CallContext context) {
@@ -60,12 +44,7 @@
 
     public CalculationResult calculate(final SINFOArtifact sinfo) {
 
-        /*
-         * find the user of this artifact, sadly this is not part of the calling context, so instead we determine the
-         * owner oft the artifact
-         */
-        final ArtifactDatabase database = this.context.getDatabase();
-        final String user = database.findArtifactUser(sinfo.identifier());
+        final String user = CalculationUtils.findArtifactUser(this.context, sinfo);
 
         /* access input data */
         final FlowDepthAccess access = new FlowDepthAccess(sinfo);
@@ -74,24 +53,21 @@
 
         final Collection<DifferencesPair> diffPairs = access.getDifferencePairs();
 
-        final double from = access.getFrom();
-        final double to = access.getTo();
-        final DoubleRange calcRange = new DoubleRange(from, to);
+        final DoubleRange calcRange = access.getRange();
 
         final boolean useTkh = access.isUseTransportBodies();
 
         /* calculate results for each diff pair */
         final Calculation problems = new Calculation();
 
-        final List<Gauge> gauges = river.determineGauges(from, to);
-        final GaugeIndex gaugeIndex = new GaugeIndex(gauges);
+        final RiverInfoProvider infoProvider = RiverInfoProvider.forRange(this.context, river, calcRange);
 
         final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name());
 
         final FlowDepthCalculationResults results = new FlowDepthCalculationResults(calcModeLabel, user, riverInfo, calcRange, useTkh);
 
         for (final DifferencesPair diffPair : diffPairs) {
-            final FlowDepthCalculationResult result = calculateResult(river, calcRange, diffPair, problems, gaugeIndex, useTkh);
+            final FlowDepthCalculationResult result = calculateResult(calcRange, diffPair, problems, infoProvider, useTkh);
             if (result != null)
                 results.addResult(result);
         }
@@ -101,15 +77,17 @@
 
     /**
      * Calculates one W-MSH differences pair.
+     *
+     * @param infoProvider
      */
-    private FlowDepthCalculationResult calculateResult(final River river, final DoubleRange calcRange, final DifferencesPair diffPair,
-            final Calculation problems, final GaugeIndex gaugeIndex, final boolean useTkh) {
+    private FlowDepthCalculationResult calculateResult(final DoubleRange calcRange, final DifferencesPair diffPair,
+            final Calculation problems, final RiverInfoProvider infoProvider, final boolean useTkh) {
 
         /* access real input data from database */
         final String soundingId = diffPair.getSoundingId();
         final String wstId = diffPair.getWstId();
 
-        final BedHeight bedHeight = loadBedHeight(soundingId);
+        final BedHeightsFinder bedHeight = loadBedHeight(soundingId, calcRange);
         if (bedHeight == null) {
             final String message = Resources.format(this.context.getMeta(), "Failed to access sounding with id '{0}'", soundingId);
             problems.addProblem(message);
@@ -126,223 +104,29 @@
         final WKms wstKms = waterlevel.getWkms();
 
         final String wspLabel = wstKms.getName();
-        final String soundingLabel = bedHeight.getDescription();
+        final String soundingLabel = bedHeight.getInfo().getDescription();
         final String label = String.format("%s - %s", wspLabel, soundingLabel);
 
-        checkYearDifference(label, waterlevel, bedHeight, problems);
+        checkYearDifference(label, waterlevel, bedHeight.getInfo().getYear(), problems);
         checkWaterlevelDiscretisation(wstKms, calcRange, problems);
         // TODO: prüfen, ob sohlhöhen die calcRange abdecken/überschneiden
 
         /* re-determine the reference gauge, in the same way as the WaterlevelArtifact would do it */
-        final String notinrange = Resources.getMsg(this.context.getMeta(), CSV_NOT_IN_GAUGE_RANGE, CSV_NOT_IN_GAUGE_RANGE);
-
-        final Gauge refGauge = waterlevel.findReferenceGauge(river);
-        final String refGaugeName = refGauge == null ? notinrange : refGauge.getName();
-
-        final BedHeightInfo sounding = BedHeightInfo.from(bedHeight);
-        final int wspYear = waterlevel.getYear();
-        final WstInfo wstInfo = new WstInfo(wspLabel, wspYear, refGaugeName);
-
-        final FlowDepthCalculationResult resultData = new FlowDepthCalculationResult(label, wstInfo, sounding);
-
-        boolean doCalcTkh = useTkh;
-        if (doCalcTkh && !(wstKms instanceof QKms)) {
-            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, label);
-            problems.addProblem(message);
-            doCalcTkh = false;
-        }
-
-        BedQualityD50KmValueFinder bedMeasurementsFinder = null;
-        if (doCalcTkh) {
-            bedMeasurementsFinder = loadBedMeasurements(river, calcRange, sounding.getYear().intValue());
-            if (bedMeasurementsFinder == null) {
-                final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingSoilKind", null, label);
-                problems.addProblem(message);
-                doCalcTkh = false;
-            }
-        }
-
-        final String bedHeightLabel = bedHeight.getDescription();
-        final String wstLabel = wstKms.getName();
-
-        final UnivariateRealFunction wstInterpolator = DoubleUtil.getLinearInterpolator(wstKms.allKms(), wstKms.allWs());
-        UnivariateRealFunction qInterpolator = null;
-        DoubleRange qRange = null;
-        if (doCalcTkh) {
-            qInterpolator = DoubleUtil.getLinearInterpolator(((QKms) wstKms).allKms(), ((QKms) wstKms).allQs());
-            if (qInterpolator != null)
-                qRange = new DoubleRange(((QKms) wstKms).allQs().min(), ((QKms) wstKms).allQs().max());
-            else {
-                final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, label);
-                problems.addProblem(message);
-                doCalcTkh = false;
-            }
-        }
-
-        // FIXME: sort by station first, but in what direction?
-        // FIXME: using river.getKmUp()?
-        final List<BedHeightValue> values = bedHeight.getValues();
-
-        final List<BedHeightValue> sortedValues = new ArrayList<>(values);
-        Collections.sort(sortedValues, new BedHeightStationComparator());
-
-        // FIXME: wie wird ggf. interpoliert? prüfung ob werte vorhanden?
-        /* SoilKind lastKind = SoilKind.mobil; */
-        SoilKindKmValueFinder soilKindFinder = null;
-        if (doCalcTkh) {
-            soilKindFinder = new SoilKindKmValueFinder();
-            if (!soilKindFinder.loadValues(river, calcRange)) {
-                doCalcTkh = false;
-                final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingSoilKind", null, label);
-                problems.addProblem(message);
-            }
-        }
-
-        FlowVelocityModelKmValueFinder flowVelocitiesFinder = null;
-        if (doCalcTkh) {
-            flowVelocitiesFinder = new FlowVelocityModelKmValueFinder();
-            if (!flowVelocitiesFinder.loadValues(river, calcRange, qRange)) {
-                doCalcTkh = false;
-                final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingVelocity", null, label);
-                problems.addProblem(message);
-            }
-        }
-
-        for (final BedHeightValue bedHeightValue : sortedValues) {
-
-            final Double station = bedHeightValue.getStation();
-            if (station == null || station.isNaN())
-                continue;
-
-            final Double meanBedHeightDbl = bedHeightValue.getHeight();
-            if (meanBedHeightDbl == null || meanBedHeightDbl.isNaN())
-                continue;
-
-            final double km = station;
-            final double meanBedHeight = meanBedHeightDbl;
-
-            if (!calcRange.containsDouble(km))
-                continue;
+        final RiverInfoProvider riverInfoProvider = infoProvider.forWaterlevel(waterlevel);
 
-            try {
-                // FIXME: check out of range
-                final double wst = wstInterpolator.value(km);
-
-                final double flowDepth = wst - meanBedHeight;
-
-                // FIXME: piecewise constant interpolation?
-                // final double discharge = wstKms instanceof QKms ? ((QKms) wstKms).getQ(i) : Double.NaN;
-                double discharge;
-                if (qInterpolator != null)
-                    discharge = qInterpolator.value(km);
-                else
-                    discharge = Double.NaN;
-
-                // Calculate tkh
-                double tkh = 0;
-                if (doCalcTkh) {
-                    double d50 = Double.NaN;
-                    try {
-                        d50 = bedMeasurementsFinder.findD50(km);
-                    }
-                    catch (final Exception e) {
-                        final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingD50", null, label);
-                        problems.addProblem(km, message);
-                        // FIXME: cumulate problems to one message?
-                    }
-                    if (!Double.isNaN(d50)) {
-                        if (flowVelocitiesFinder.findKmQValues(km, discharge)) {
-                            tkh = calculateTkh(wst - meanBedHeight, flowVelocitiesFinder.getFindVmainFound(), d50, flowVelocitiesFinder.getFindTauFound());
-                            if (!Double.isNaN(tkh) && (tkh < 0))
-                                tkh = 0;
-                            /*
-                             * log.debug(String.format("calculateTkh km %.3f q %.0f w %.2f mbh %.2f vm %.1f tau %.1f d50(mm) %.1f tkh(cm) %.1f",
-                             * km, discharge, wst, meanBedHeight, flowVelocitiesFinder.getFindVmainFound(), flowVelocitiesFinder.getFindTauFound(),
-                             * d50*1000, tkh));
-                             */
-                        } else {
-                            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, label);
-                            problems.addProblem(km, message);
-                            // FIXME: cumulate problems to one message?
-                        }
-                    } else
-                        tkh = Double.NaN;
-                }
+        final int wspYear = waterlevel.getYear();
+        final WstInfo wstInfo = new WstInfo(wspLabel, wspYear, riverInfoProvider.getReferenceGauge());
 
-                // Soil kind
-                SoilKind kind = SoilKind.mobil;
-                if (doCalcTkh) {
-                    try {
-                        kind = soilKindFinder.findSoilKind(km);
-                    }
-                    catch (final Exception e) {
-                        final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingSoilKind", null, label);
-                        problems.addProblem(km, message);
-                        // FIXME: cumulate problems to one message?
-                    }
-                }
-
-                final double flowDepthTkh;
-                final double tkhUp;
-                final double tkhDown;
-                switch (kind) {
-                case starr:
-                    flowDepthTkh = wst - (meanBedHeight + tkh / 100);
-                    tkhUp = tkh;
-                    tkhDown = 0;
-                    break;
+        final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wstKms);
 
-                case mobil:
-                default:
-                    flowDepthTkh = wst - (meanBedHeight + tkh / 200);
-                    tkhUp = tkh / 2;
-                    tkhDown = -tkh / 2;
-                    break;
-                }
-
-                // REMARK: access the location once only during calculation
-                final String location = LocationProvider.getLocation(river.getName(), km);
+        final River river = riverInfoProvider.getRiver();
+        final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(useTkh, this.context, problems, label, river, calcRange, dischargeProvider,
+                bedHeight);
 
-                // REMARK: access the gauge once only during calculation
-                final Gauge gauge = findGauge(waterlevel, refGauge, gaugeIndex, km);
-
-                final String gaugeLabel = gauge == null ? notinrange : gauge.getName();
-
-                resultData.addRow(km, flowDepth, flowDepthTkh, kind, tkh, tkhUp, tkhDown, wst, discharge, wstLabel, gaugeLabel, meanBedHeight, bedHeightLabel,
-                        location);
-            }
-            catch (final FunctionEvaluationException e) {
-                /* should only happen if out of range */
-                e.printStackTrace();
-                /* simply ignore */
-            }
-        }
-
-        return resultData;
+        final FlowDepthCalculator calculator = new FlowDepthCalculator(riverInfoProvider, wstKms, dischargeProvider, bedHeight, tkhCalculator);
+        return calculator.execute(label, wstInfo, calcRange);
     }
 
-    /**
-     * Sohlbeschaffenheit (D50 Korndurchmesser aus Seddb)
-     * Abhängig von Peiljahr
-     */
-    private BedQualityD50KmValueFinder loadBedMeasurements(final River river, final DoubleRange kmRange, final int soundingYear) {
-
-        /* construct valid measurement time range */
-        final Calendar cal = Calendar.getInstance();
-        cal.clear();
-
-        cal.set(soundingYear - VALID_BED_MEASUREMENT_YEARS, 0, 1);
-        final Date startTime = cal.getTime();
-
-        cal.set(soundingYear + VALID_BED_MEASUREMENT_YEARS, 11, 31);
-        final Date endTime = cal.getTime();
-
-        final BedQualityD50KmValueFinder finder = new BedQualityD50KmValueFinder();
-        if (finder.loadValues(river, kmRange, new DateRange(startTime, endTime)))
-            return finder;
-        else
-            return null;
-    }
 
     /**
      * Checks the year difference between waterlevels and sounding, and issues a warning if too big.
@@ -353,9 +137,7 @@
      * 1918 ≤ X < 1958 ± 12
      * X < 1918 ± 25
      */
-    private void checkYearDifference(final String label, final WaterlevelData waterlevel, final BedHeight sounding, final Calculation problems) {
-
-        final Integer soundingYear = sounding.getYear();
+    private void checkYearDifference(final String label, final WaterlevelData waterlevel, final Integer soundingYear, final Calculation problems) {
         if (soundingYear == null)
             return;
 
@@ -388,21 +170,6 @@
         return 3;
     }
 
-    private Gauge findGauge(final WaterlevelData waterlevel, final Gauge refGauge, final GaugeIndex gaugeIndex, final double km) {
-
-        // REMARK: using same logic as in WaterlevelExporter here
-
-        final boolean showAllGauges = waterlevel.isShowAllGauges();
-
-        if (showAllGauges)
-            return gaugeIndex.findGauge(km);
-
-        if (refGauge.getRange().contains(km))
-            return refGauge;
-
-        return null;
-    }
-
     /* Checks if the discretisation of the waterlevel exceeds 1000m */
 
     private void checkWaterlevelDiscretisation(final WKms wstKms, final DoubleRange calcRange, final Calculation problems) {
@@ -424,7 +191,7 @@
         }
     }
 
-    private BedHeight loadBedHeight(final String soundingId) {
+    private BedHeightsFinder loadBedHeight(final String soundingId, final DoubleRange calcRange) {
 
         // REMARK: absolutely unbelievable....
         // The way how bed-heights (and other data too) is accessed is different for nearly every calculation-type
@@ -438,6 +205,11 @@
         final BedHeightsArtifact artifact = (BedHeightsArtifact) RiverUtils.getArtifact(parts[0], this.context);
 
         final Integer bedheightId = artifact.getDataAsInteger("height_id");
+        if (bedheightId == null) {
+            // FIXME: error message!
+            return null;
+        }
+
         // REMARK: this only works with type 'single'; unclear on how to distinguish from epoch data (or whatever the
         // other type means)
         // Luckily, the requirement is to only access 'single' data here.
@@ -449,35 +221,6 @@
         // BedHeightFactory uses its own (direct) way of accessing the data, with its own implemented data classes.
         // return BedHeightFactory.getHeight(bedheightType, bedheightId, from, to);
 
-        return BedHeight.getBedHeightById(bedheightId);
-    }
-
-    /**
-     * Calculates a transport body height
-     *
-     * @param h
-     *            flow depth in m
-     * @param vm
-     *            flow velocity in m
-     * @param d50
-     *            grain diameter D50 in m (!)
-     * @param tau
-     *            shear stress in N/m^2
-     * @return transport body height in cm (!)
-     */
-    private double calculateTkh(final double h, final double vm, final double d50, final double tau) {
-        final double PHYS_G = 9.81;
-        final double PHYS_SPECGRAV_S = 2.6;
-        final double PHYS_VELOCCOEFF_N = 6;
-        final double PHYS_FORMCOEFF_ALPHA = 0.7;
-        final double PHYS_VISCOSITY_NUE = 1.3e-6;
-        final double PHYS_GRAIN_DENSITY_RHOS = 2603;
-        final double PHYS_WATER_DENSITY_RHO = 999.97;
-
-        final double froude = vm / Math.sqrt(PHYS_G * h);
-        final double partReynolds = Math.sqrt((PHYS_SPECGRAV_S - 1) * PHYS_G * d50) / PHYS_VISCOSITY_NUE * d50;
-        final double critShields = 0.22 * Math.pow(partReynolds, -0.6) + 0.06 * Math.pow(10, 7.7 * Math.pow(partReynolds, -0.6));
-        final double critTau = critShields * (PHYS_GRAIN_DENSITY_RHOS - PHYS_WATER_DENSITY_RHO) * PHYS_G * d50;
-        return 100 * h * (1 - Math.pow(froude, 2)) / (2 * PHYS_VELOCCOEFF_N * PHYS_FORMCOEFF_ALPHA) * (1 - critTau / tau);
+        return BedHeightsFinder.forId(bedheightId, calcRange);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResult.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResult.java	Wed Feb 28 17:27:15 2018 +0100
@@ -9,12 +9,9 @@
  */
 package org.dive4elements.river.artifacts.sinfo.flowdepth;
 
-import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 
+import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoCalculationResult;
 import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 
@@ -25,53 +22,31 @@
  *
  * @author Gernot Belger
  */
-class FlowDepthCalculationResult implements Serializable {
+final class FlowDepthCalculationResult extends AbstractSInfoCalculationResult<FlowDepthRow> {
 
     private static final long serialVersionUID = 1L;
 
-    private final Collection<FlowDepthRow> rows = new ArrayList<>();
-
-    private final String label;
-
     private final BedHeightInfo sounding;
 
-    private final WstInfo wst;
-
-    public FlowDepthCalculationResult(final String label, final WstInfo wst, final BedHeightInfo sounding) {
-        this.label = label;
-        this.wst = wst;
-        this.sounding = sounding;
-    }
+    public FlowDepthCalculationResult(final String label, final WstInfo wst, final BedHeightInfo sounding, final boolean hasTkh,
+            final Collection<FlowDepthRow> rows) {
+        super(label, wst, hasTkh, rows);
 
-    public void addRow(final double station, final double flowDepth, final double flowDepthWithTkh, final SoilKind tkhKind, final double tkh,
-            final double tkhUp, final double tkhDown, final double waterlevel, final double discharge, final String waterlevelLabel, final String gauge,
-            final double meanBedHeight, final String sondageLabel, final String location) {
-        this.rows.add(new FlowDepthRow(station, flowDepth, flowDepthWithTkh, tkhKind, tkh, tkhUp, tkhDown, waterlevel, discharge, waterlevelLabel, gauge,
-                meanBedHeight, sondageLabel, location));
-    }
-
-    public String getLabel() {
-        return this.label;
-    }
-
-    public WstInfo getWst() {
-        return this.wst;
+        this.sounding = sounding;
     }
 
     public BedHeightInfo getSounding() {
         return this.sounding;
     }
 
-    public Collection<FlowDepthRow> getRows() {
-        return Collections.unmodifiableCollection(this.rows);
-    }
-
     public double[][] getFlowDepthPoints() {
 
-        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
+        final Collection<FlowDepthRow> rows = getRows();
 
-        for (final FlowDepthRow row : this.rows) {
+        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
+        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
+
+        for (final FlowDepthRow row : rows) {
             xPoints.add(row.getStation());
             yPoints.add(row.getFlowDepth());
         }
@@ -81,85 +56,16 @@
 
     public double[][] getFlowDepthTkhPoints() {
 
-        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
+        final Collection<FlowDepthRow> rows = getRows();
 
-        for (final FlowDepthRow row : this.rows) {
+        final TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
+        final TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
+
+        for (final FlowDepthRow row : rows) {
             xPoints.add(row.getStation());
             yPoints.add(row.getFlowDepthWithTkh());
         }
 
         return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
     }
-
-    public double[][] getTkhUpPoints() {
-        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
-        final List<SoilKind> kinds = new ArrayList<>(this.rows.size());
-
-        for (final FlowDepthRow row : this.rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getTkhUp());
-            kinds.add(row.getTkhKind());
-        }
-
-        return adjustTkhVisualization(xPoints, yPoints, kinds);
-    }
-
-    public double[][] getTkhDownPoints() {
-        final TDoubleArrayList xPoints = new TDoubleArrayList(this.rows.size());
-        final TDoubleArrayList yPoints = new TDoubleArrayList(this.rows.size());
-        final List<SoilKind> kinds = new ArrayList<>(this.rows.size());
-
-        for (final FlowDepthRow row : this.rows) {
-            xPoints.add(row.getStation());
-            yPoints.add(row.getTkhDown());
-            kinds.add(row.getTkhKind());
-        }
-
-        return adjustTkhVisualization(xPoints, yPoints, kinds);
-    }
-
-    /**
-     * 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) {
-
-        final TDoubleArrayList adjustedX = new TDoubleArrayList(xPoints.size());
-        final TDoubleArrayList adjustedY = new TDoubleArrayList(yPoints.size());
-
-        adjustedX.add(xPoints.get(0));
-        adjustedY.add(yPoints.get(0));
-
-        for (int i = 1; i < xPoints.size(); 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 middleX = (x1 + x2) / 2;
-
-                // REMARK: we can't produce a 100% vertical line, as the area-renderer will not work correctly
-                adjustedX.add(middleX - 0.0001);
-                adjustedY.add(y1);
-
-                adjustedX.add(middleX + 0.0001);
-                adjustedY.add(y2);
-            }
-
-            /* always add the real point now */
-            adjustedX.add(xPoints.get(i));
-            adjustedY.add(yPoints.get(i));
-        }
-
-
-        return new double[][] { adjustedX.toNativeArray(), adjustedY.toNativeArray() };
-    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResults.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResults.java	Wed Feb 28 17:27:15 2018 +0100
@@ -20,7 +20,7 @@
 /**
  * @author Gernot Belger
  */
-public final class FlowDepthCalculationResults implements Serializable {
+final class FlowDepthCalculationResults implements Serializable {
     private static final long serialVersionUID = 1L;
 
     private final List<FlowDepthCalculationResult> results = new ArrayList<>();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculator.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,129 @@
+/** 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 java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.dive4elements.river.artifacts.model.WKms;
+import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.SoilKind;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator;
+import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
+import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
+import org.dive4elements.river.utils.DoubleUtil;
+
+/**
+ * @author Gernot Belger
+ */
+final class FlowDepthCalculator {
+
+    private final Collection<FlowDepthRow> rows = new ArrayList<>();
+
+    private final DischargeValuesFinder dischargeProvider;
+
+    private final BedHeightsFinder bedHeight;
+
+    private final TkhCalculator tkhCalculator;
+
+    private final PolynomialSplineFunction wstInterpolator;
+
+    private final RiverInfoProvider riverInfoProvider;
+
+    private final String bedHeightLabel;
+
+    private final String wstLabel;
+
+    public FlowDepthCalculator(final RiverInfoProvider riverInfoProvider, final WKms wstKms,
+            final DischargeValuesFinder dischargeProvider, final BedHeightsFinder bedHeight, final TkhCalculator tkhCalculator) {
+
+        this.riverInfoProvider = riverInfoProvider;
+
+        this.dischargeProvider = dischargeProvider;
+        this.bedHeight = bedHeight;
+        this.tkhCalculator = tkhCalculator;
+
+        this.wstInterpolator = DoubleUtil.getLinearInterpolator(wstKms.allKms(), wstKms.allWs());
+
+        this.bedHeightLabel = bedHeight.getInfo().getDescription();
+        this.wstLabel = wstKms.getName();
+    }
+
+    public FlowDepthCalculationResult execute(final String label, final WstInfo wstInfo, final DoubleRange calcRange) {
+
+        final Collection<Double> stations = this.bedHeight.getStations();
+        for (final Double station : stations) {
+            if (calcRange.containsDouble(station))
+                calculateResultRow(station);
+        }
+
+        return new FlowDepthCalculationResult(label, wstInfo, this.bedHeight.getInfo(), this.tkhCalculator != null, this.rows);
+    }
+
+    private void calculateResultRow(final double station) {
+
+        try {
+            // FIXME: check out of range of waterlevel?
+            final double wst = this.wstInterpolator.value(station);
+
+            final Tkh tkh = calculateTkh(station, wst);
+
+            final double meanBedHeight = tkh.getMeanBedHeight();
+
+            final double flowDepth = wst - meanBedHeight;
+            final double flowDepthTkh = calculateFlowDepthTkh(tkh, wst, meanBedHeight);
+
+            // REMARK: access the location once only during calculation
+            final String location = this.riverInfoProvider.getLocation(station);
+
+            // REMARK: access the gauge once only during calculation
+            final String gaugeLabel = this.riverInfoProvider.findGauge(station);
+
+            this.rows.add(new FlowDepthRow(flowDepth, flowDepthTkh, tkh, this.wstLabel, gaugeLabel, this.bedHeightLabel, location));
+        }
+        catch (final FunctionEvaluationException e) {
+            /* should only happen if out of range */
+            e.printStackTrace();
+            /* simply ignore */
+        }
+    }
+
+    private Tkh calculateTkh(final double station, final double wst) throws FunctionEvaluationException {
+        if (this.tkhCalculator == null) {
+            final double discharge = this.dischargeProvider.getDischarge(station);
+            final double meanBedHeight = this.bedHeight.getMeanBedHeight(station);
+            return new Tkh(station, wst, meanBedHeight, discharge);
+        }
+
+        return this.tkhCalculator.getTkh(station, wst);
+    }
+
+    private double calculateFlowDepthTkh(final Tkh tkh, final double wst, final double meanBedHeight) {
+        final double tkhValue = tkh.getTkh();
+        final SoilKind tkhKind = tkh.getKind();
+
+        if (Double.isNaN(tkhValue) || tkhKind == null)
+            return Double.NaN;
+
+        switch (tkhKind) {
+        case starr:
+            return wst - (meanBedHeight + tkhValue / 100);
+
+        case mobil:
+        default:
+            return wst - (meanBedHeight + tkhValue / 200);
+        }
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthRow.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthRow.java	Wed Feb 28 17:27:15 2018 +0100
@@ -9,66 +9,31 @@
  */
 package org.dive4elements.river.artifacts.sinfo.flowdepth;
 
-import java.io.Serializable;
+import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoResultRow;
+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 implements Serializable {
+final class FlowDepthRow extends AbstractSInfoResultRow {
     private static final long serialVersionUID = 1L;
 
-    private final double station;
-
     private final double flowDepth;
 
     private final double flowDepthWithTkh;
 
-    private final SoilKind tkhKind;
-
-    private final double tkh;
-
-    private final double tkhUp;
-
-    private final double tkhDown;
-
-    private final double waterlevel;
-
-    private final double discharge;
+    private final String soundingLabel;
 
-    private final String waterlevelLabel;
-
-    private final String gauge;
-
-    private final double meanBedHeight;
+    public FlowDepthRow(final double flowDepth, final double flowDepthWithTkh, final Tkh tkh, final String waterlevelLabel,
+            final String gauge, final String soundingLabel, final String location) {
 
-    private final String soundageLabel;
-
-    private final String location;
+        super(tkh, waterlevelLabel, gauge, location);
 
-    public FlowDepthRow(final double station, final double flowDepth, final double flowDepthWithTkh, final SoilKind tkhKind, final double tkh,
-            final double tkhUp, final double tkhDown,
-            final double waterlevel, final double discharge, final String waterlevelLabel, final String gauge, final double meanBedHeight,
-            final String soundageLabel, final String location) {
-        this.station = station;
         this.flowDepth = flowDepth;
         this.flowDepthWithTkh = flowDepthWithTkh;
-        this.tkhKind = tkhKind;
-        this.tkh = tkh;
-        this.tkhUp = tkhUp;
-        this.tkhDown = tkhDown;
-        this.waterlevel = waterlevel;
-        this.discharge = discharge;
-        this.waterlevelLabel = waterlevelLabel;
-        this.gauge = gauge;
-        this.meanBedHeight = meanBedHeight;
-        this.soundageLabel = soundageLabel;
-        this.location = location;
-    }
-
-    public double getStation() {
-        return this.station;
+        this.soundingLabel = soundingLabel;
     }
 
     public double getFlowDepth() {
@@ -79,47 +44,7 @@
         return this.flowDepthWithTkh;
     }
 
-    public SoilKind getTkhKind() {
-        return this.tkhKind;
-    }
-
-    public double getTkh() {
-        return this.tkh;
-    }
-
-    public double getTkhUp() {
-        return this.tkhUp;
-    }
-
-    public double getTkhDown() {
-        return this.tkhDown;
-    }
-
-    public double getWaterlevel() {
-        return this.waterlevel;
-    }
-
-    public double getDischarge() {
-        return this.discharge;
-    }
-
-    public String getWaterlevelLabel() {
-        return this.waterlevelLabel;
-    }
-
-    public String getGauge() {
-        return this.gauge;
-    }
-
-    public double getMeanBedHeight() {
-        return this.meanBedHeight;
-    }
-
     public String getSoundageLabel() {
-        return this.soundageLabel;
-    }
-
-    public String getLocation() {
-        return this.location;
+        return this.soundingLabel;
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowVelocityModelKmValueFinder.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,330 +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 java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.lang.math.DoubleRange;
-import org.apache.log4j.Logger;
-import org.hibernate.SQLQuery;
-import org.hibernate.Session;
-import org.hibernate.type.StandardBasicTypes;
-
-import gnu.trove.TDoubleArrayList;
-
-import org.dive4elements.river.artifacts.math.Linear;
-import org.dive4elements.river.artifacts.math.Utils;
-import org.dive4elements.river.backend.SessionHolder;
-import org.dive4elements.river.model.River;
-
-/**
- * Searchable sorted km array with parallel FlowVelocityKmModelValues array and linear interpolation for km and the model values between the array elements.<br />
- * {@link loadValues} loads all the model values for a given km range of a river.<br />
- * {@link findKmQValues} then searches a km in the values table or the nearest including km interval, resp.
- * The v and tau values for a given discharge are either found directly or also interpolated linearly.<br />
- * 
- * (Created based on a copy of FlowVelocityMeasurementFactory.)
- * 
- * @author Matthias Schäfer
- *
- */
-public class FlowVelocityModelKmValueFinder
-{
-    /***** FIELDS *****/
-    
-    /**
-     * Private log to use here.
-     */
-    private static Logger log = Logger.getLogger(FlowVelocityModelKmValueFinder.class);
-    
-    /**
-     * Query for a range of stations of a river with all their q, main-v and tau values.<br />
-     * (Might be several 10000 rows if many stations and large q range)
-     */
-    private static final String SQL_SELECT_ALL =
-        "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" 
-        + "  FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" 
-        + "    INNER JOIN flow_velocity_model_values fvmv ON fvm.id = fvmv.flow_velocity_model_id" 
-        + "  WHERE (dz.river_id = :river_id) AND (fvmv.station BETWEEN :kmfrom - 0.0001 AND :kmto + 0.0001)" 
-        /* + "  WHERE (dz.river_id = :river_id) AND (fvmv.q BETWEEN :qmin AND :qmax)" */
-        + "  ORDER BY fvmv.station ASC, fvmv.q ASC";
-
-    /**
-     * Query for a river's max km below a limit with all its q, main-v and tau values.
-     */
-    private static final String SQL_SELECT_KMLOWER =
-        "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau"
-        + "  FROM flow_velocity_model_values fvmv"
-        + "    INNER JOIN (SELECT MAX(fvmvi.station) AS kmmax"
-        + "      FROM(discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)"
-        + "        INNER JOIN flow_velocity_model_values fvmvi ON fvm.id = fvmvi.flow_velocity_model_id"
-        + "        WHERE (dz.river_id = :river_id) AND (fvmvi.station < :kmfrom - 0.0001)) finf ON fvmv.station = finf.kmmax" 
-        + "  ORDER BY fvmv.q ASC";
-
-    /**
-     * Query for a river's min km above a limit with all its q, main-v and tau values.
-     */
-    private static final String SQL_SELECT_KMUPPER =
-        "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" 
-        + "  FROM flow_velocity_model_values fvmv"
-        + "    INNER JOIN (SELECT MIN(fvmvi.station) AS kmmin"
-        + "      FROM(discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" 
-        + "        INNER JOIN flow_velocity_model_values fvmvi ON fvm.id = fvmvi.flow_velocity_model_id"
-        + "        WHERE (dz.river_id = :river_id) AND (fvmvi.station > :kmto + 0.0001)) fsup ON fvmv.station = fsup.kmmin" 
-        + "  ORDER BY fvmv.q ASC";
-
-    /**
-     * Query to select all km-q-v-tau of a river that are the q maxima below a q limit
-     */
-    private static final String SQL_SELECT_QLOWER =
-        "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau"
-        + "  FROM flow_velocity_model_values fvmv"
-        + "    INNER JOIN (SELECT fv2.station, MAX(fv2.q) AS q"
-        + "      FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" 
-        + "        INNER JOIN flow_velocity_model_values fv2 ON fvm.id = fv2.flow_velocity_model_id" 
-        + "      WHERE (dz.river_id = :river_id) AND (fv2.q < :qlim) GROUP BY fv2.station) qx"
-        + "    ON (fvmv.station=qx.station) AND (fvmv.q=qx.q)" 
-        + "  ORDER BY fvmv.station ASC";
-        
-    /**
-     * Query to select all km-q-v-tau of a river that are the q minima above a q limit
-     */
-    private static final String SQL_SELECT_QUPPER =
-            "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau"
-            + "  FROM flow_velocity_model_values fvmv"
-            + "    INNER JOIN (SELECT fv2.station, MIN(fv2.q) AS q"
-            + "      FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" 
-            + "        INNER JOIN flow_velocity_model_values fv2 ON fvm.id = fv2.flow_velocity_model_id" 
-            + "      WHERE (dz.river_id = :river_id) AND (fv2.q > :qlim) GROUP BY fv2.station) qx"
-            + "    ON (fvmv.station=qx.station) AND (fvmv.q=qx.q)" 
-            + "  ORDER BY fvmv.station ASC";
-            
-    /**
-     * Kms of the loaded river range
-     */
-    private TDoubleArrayList kms;
-    
-    /**
-     * For each km in kms a list of q-v-tau-tupels
-     */
-    private List<FlowVelocityKmModelValues> values;
-
-    /**
-     * Searched km of the last findKmValue
-     */
-    private double findKm;
-    
-    /**
-     * kms and values index of the interval start found by the last findKmValue
-     */
-    private int leftIndexFound = -1;
-
-    /**
-     * kms and values index of the interval end found by the last findKmValue
-     */
-    private int rightIndexFound = -1;
-
-    /**
-     * Q of the last findKmQValues
-     */
-    private double findQ;
-
-    /***** METHODS *****/
-    
-    /**
-     * Discharge of the last {@link findKmQValue}
-     */
-    public double getFindQ() {
-        return findQ;
-    }
-    
-    /**
-     * Velocity of the last {@link findKmQValues}
-     */
-    public double getFindVmainFound() {
-        if (leftIndexFound < 0)
-            return Double.NaN;
-        else if (leftIndexFound == rightIndexFound)
-            return getLeftValues().getVmainFound();
-        else
-            return Linear.linear(findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getVmainFound(), getRightValues().getVmainFound());
-    }
-    
-    /**
-     * Shear stress tau of the last {@link findKmQValues}
-     */
-    public double getFindTauFound() {
-        if (leftIndexFound < 0)
-            return Double.NaN;
-        else if (leftIndexFound == rightIndexFound)
-            return getLeftValues().getTauFound();
-        else
-            return Linear.linear(findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getTauFound(), getRightValues().getTauFound());
-    }
-    
-    /**
-     * Whether the discharge has been interpolated in the last {@link findKmQValues} 
-     */
-    public boolean getFindIsQInterpolated() {
-        return (getLeftValues() != null) && (getLeftValues().getIsInterpolated() || getRightValues().getIsInterpolated());
-    }
-    
-    /**
-     * Queries a range of a river's kms with all their q-v-tau values.
-     * @return Whether the load has been successful
-     */
-    @SuppressWarnings("unchecked")
-    public boolean loadValues(River river, DoubleRange kmRange, DoubleRange qRange) {
-        // DB session
-        log.debug(String.format("loadValues km %.3f - %.3f / q %.1f - %.1f", kmRange.getMinimumDouble(), kmRange.getMaximumDouble(), qRange.getMinimumDouble(), qRange.getMaximumDouble()));
-        kms = new TDoubleArrayList();
-        values = new ArrayList<FlowVelocityKmModelValues>();
-        boolean isDemoValuesCorrection = river.getName().equalsIgnoreCase("beispielfluss");
-        final Session session = SessionHolder.HOLDER.get();
-        
-        // Select km infimum
-        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_KMLOWER)
-            .addScalar("station", StandardBasicTypes.DOUBLE)
-            .addScalar("q", StandardBasicTypes.DOUBLE)
-            .addScalar("vmain", StandardBasicTypes.DOUBLE)
-            .addScalar("tau", StandardBasicTypes.DOUBLE);
-        sqlQuery.setParameter("river_id", river.getId());
-        sqlQuery.setParameter("kmfrom", kmRange.getMinimumDouble());
-        addKms(sqlQuery.list(), isDemoValuesCorrection);
-
-        // Select km range
-        sqlQuery = session.createSQLQuery(SQL_SELECT_ALL)
-            .addScalar("station", StandardBasicTypes.DOUBLE)
-            .addScalar("q", StandardBasicTypes.DOUBLE)
-            .addScalar("vmain", StandardBasicTypes.DOUBLE)
-            .addScalar("tau", StandardBasicTypes.DOUBLE);
-        sqlQuery.setParameter("river_id", river.getId());
-        sqlQuery.setParameter("kmfrom", kmRange.getMinimumDouble());
-        sqlQuery.setParameter("kmto", kmRange.getMaximumDouble());
-        //sqlQuery.setParameter("qmin", qRange.getMinimumDouble());
-        //sqlQuery.setParameter("qmax", qRange.getMaximumDouble());
-        int kmcount = kms.size();
-        int rowcount = addKms(sqlQuery.list(), isDemoValuesCorrection);
-        kmcount = kms.size() - kmcount;
-        
-        // Select km supremum 
-        sqlQuery = session.createSQLQuery(SQL_SELECT_KMUPPER)
-            .addScalar("station", StandardBasicTypes.DOUBLE)
-            .addScalar("q", StandardBasicTypes.DOUBLE)
-            .addScalar("vmain", StandardBasicTypes.DOUBLE)
-            .addScalar("tau", StandardBasicTypes.DOUBLE);
-        sqlQuery.setParameter("river_id", river.getId());
-        sqlQuery.setParameter("kmto", kmRange.getMaximumDouble());
-        int supcnt = addKms(sqlQuery.list(), isDemoValuesCorrection);
-
-        // Add copy of last km for search of max km value
-        if ((supcnt == 0) && (values.size() >= 1)) {
-            kms.add(kms.getQuick(kms.size()) + 0.0001);
-            values.add(new FlowVelocityKmModelValues(kms.getQuick(kms.size()-1), values.get(values.size()-1)));
-        }
-        
-        // log.debug
-        if (values.size() - 1 >= 0) {
-            log.debug(String.format("loadValues %d: km %.3f - %d values", 0, values.get(0).getKm(), values.get(0).size()));
-            if (values.size() - 1 >= 1) {
-                log.debug(String.format("loadValues %d: km %.3f - %d values", 1, values.get(1).getKm(), values.get(1).size()));
-                if (values.size() - 1 >= 2)
-                    log.debug("loadValues ...");
-                if (values.size() - 2 >= 3)
-                    log.debug(String.format("loadValues %d: km %.3f - %d values", values.size()-2, values.get(values.size()-2).getKm(), values.get(values.size()-2).size()));
-                if (values.size() - 1 >= 3)
-                    log.debug(String.format("loadValues %d: km %.3f - %d values", values.size()-1, values.get(values.size()-1).getKm(), values.get(values.size()-1).size()));
-            }
-        }
-        log.debug(String.format("loadValues %d kms, %d values loaded", kmcount, rowcount));
-        return (kms.size() >= 1);
-    }
-    
-    /**
-     * Adds the km-q-v-tau values of a query result row to the last km of the list, or a new one resp.
-     * @return Number of rows
-     */
-    private int addKms(List<Object[]> rows, boolean isDemoValuesCorrection) {
-        for (Object[] row : rows) {
-            if ((kms.size() == 0) || !Utils.epsilonEquals(kms.get(kms.size()-1), (double) row[0], 0.0001)) {
-                kms.add((double) row[0]);
-                values.add(new FlowVelocityKmModelValues(kms.get(kms.size()-1)));
-            }
-            if (isDemoValuesCorrection)
-                // "Verfremdung" der v-Werte etwas korrigieren (Originalwerte wurden mit Zufallswert zwischen 10 und 20 multipliziert)
-                values.get(values.size()-1).addValues((double) row[1], ((double) row[2]) / 10, (double) row[3]);
-            else
-                values.get(values.size()-1).addValues((double) row[1], (double) row[2], (double) row[3]);
-        }
-        return rows.size();
-    }
-    
-    /**
-     * Searches a km and finds or interpolates the velocity and shear stress values for a discharge<br />
-     * The values may be got via {@link getVmainFound} etc.
-     * @return Whether values have been found
-     */
-    public boolean findKmQValues(double km, double q) {
-        findQ = q;
-        if (!searchKm(km))
-            return false;
-        if (leftIndexFound == rightIndexFound) {
-            // Exact km match
-            final double qfound = getLeftValues().findQ(q);
-            log.debug(String.format("findKmQValues km %.3f q %.0f = %.0f (%d)", km, q, qfound, leftIndexFound));
-            return !Double.isNaN(qfound);
-        }
-        else {
-            final double[] qfound = {getLeftValues().findQ(q), getRightValues().findQ(q)};
-            log.debug(String.format("findKmQValues km %.3f q %.0f = %.0f (%d, %.3f) - %.0f (%d, %.3f)", km, q, qfound[0], leftIndexFound,
-                    getLeftValues().getKm(), qfound[1], rightIndexFound, getRightValues().getKm()));
-            return !Double.isNaN(qfound[0]) && !Double.isNaN(qfound[1]);
-        }
-    }
-    
-    /**
-     * Searches a km
-     * @return Whether the km was within the supported range
-     */
-    private boolean searchKm(double km) {
-        findKm = km;
-        leftIndexFound = -1;
-        rightIndexFound = -1;
-        if ((kms == null) || (kms.size() == 0))
-            return false;
-        int i = kms.binarySearch(km);
-        if (i >= 0) {
-            // Exact km match
-            leftIndexFound = i;
-            rightIndexFound = i;
-            return true;
-        }
-        else {
-            // Out of range or within km interval
-            if (i < 0)
-                i = -i - 1;
-            if ((i <= 0) || (i >= kms.size()))
-                return false;
-            leftIndexFound = i - 1;
-            rightIndexFound = i;
-            return true;
-        }
-    }
-    
-    private FlowVelocityKmModelValues getLeftValues() {
-        return values.get(leftIndexFound);
-    }
-    private FlowVelocityKmModelValues getRightValues() {
-        return values.get(rightIndexFound);
-    }
-
-}
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/SoilKind.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +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;
-
-public enum SoilKind {
-    mobil, starr
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/SoilKindKmValueFinder.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +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 java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.lang.math.DoubleRange;
-import org.apache.commons.math.ArgumentOutsideDomainException;
-import org.apache.log4j.Logger;
-import org.dive4elements.river.model.River;
-
-import gnu.trove.TDoubleArrayList;
-
-/**
- * @author matthias
- *
- */
-public class SoilKindKmValueFinder
-{
-    /**
-     * Private log to use here.
-     */
-    private static Logger log = Logger.getLogger(SoilKindKmValueFinder.class);
-
-    private TDoubleArrayList kms;
-    
-    private List<SoilKind> values;
-    
-    /***** METHODS *****/
-    
-    /**
-     * Searches a km with its soil kind
-     */
-    public SoilKind findSoilKind(double km) throws ArgumentOutsideDomainException {
-        if ((kms == null) || (kms.size() == 0))
-            throw new ArgumentOutsideDomainException(km, Double.NaN, Double.NaN);
-        int i = kms.binarySearch(km);
-        if (i >= 0) {
-            // Exact km match
-            return values.get(i);
-        }
-        else {
-            // Out of range or within km interval
-            if (i < 0)
-                i = -i - 1;
-            if ((i <= 0) || (i >= kms.size()))
-                throw new ArgumentOutsideDomainException(km, Double.NaN, Double.NaN);
-            if (km <= ((kms.get(i-1) + kms.get(i)) / 2))
-                return values.get(i-1);
-            else
-                return values.get(i);
-        }
-    }
-
-    /**
-     * Loads the range of the river's kms with their soil kind.
-     * @return Whether the load has been successful
-     */
-    public boolean loadValues(River river, DoubleRange kmRange) {
-        kms = new TDoubleArrayList();
-        values = new ArrayList<SoilKind>();
-        //FIXME Echte Daten aus der Datenbank abfragen
-        addKmKind(0, SoilKind.starr);
-        addKmKind(15.7, SoilKind.mobil);
-        addKmKind(15.8, SoilKind.mobil);
-        addKmKind(15.9, SoilKind.starr);
-        addKmKind(108.7, SoilKind.mobil);
-        addKmKind(108.8, SoilKind.mobil);
-        addKmKind(108.9, SoilKind.starr);
-        addKmKind(119.1, SoilKind.mobil);
-        addKmKind(119.4, SoilKind.mobil);
-        addKmKind(119.5, SoilKind.starr);
-        addKmKind(128.3, SoilKind.mobil);
-        addKmKind(128.9, SoilKind.mobil);
-        addKmKind(129, SoilKind.starr);
-        addKmKind(133.1, SoilKind.mobil);
-        addKmKind(135.9, SoilKind.mobil);
-        addKmKind(136, SoilKind.starr);
-        addKmKind(136.5, SoilKind.mobil);
-        addKmKind(139.9, SoilKind.mobil);
-        addKmKind(140, SoilKind.starr);
-        addKmKind(140.5, SoilKind.mobil);
-        addKmKind(165, SoilKind.mobil);
-        addKmKind(165.1, SoilKind.starr);
-        addKmKind(165.9, SoilKind.mobil);
-        addKmKind(180.8, SoilKind.mobil);
-        addKmKind(180.9, SoilKind.starr);
-        addKmKind(182, SoilKind.mobil);
-        addKmKind(221.3, SoilKind.mobil);
-        return true;
-    }
-    
-    private void addKmKind(double km, SoilKind kind) {
-        kms.add(km);
-        values.add(kind);
-    }
-}
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkh/TkhState.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
- * Software engineering by Intevation GmbH
- *
- * 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.tkh;
-
-import java.util.List;
-
-import org.dive4elements.artifactdatabase.state.Facet;
-import org.dive4elements.artifacts.CallContext;
-import org.dive4elements.river.artifacts.ChartArtifact;
-import org.dive4elements.river.artifacts.D4EArtifact;
-import org.dive4elements.river.artifacts.WINFOArtifact;
-import org.dive4elements.river.artifacts.model.Calculation;
-import org.dive4elements.river.artifacts.model.CalculationResult;
-import org.dive4elements.river.artifacts.model.EmptyFacet;
-import org.dive4elements.river.artifacts.model.WQKms;
-import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
-import org.dive4elements.river.artifacts.states.DefaultState;
-
-/** State in which a waterlevel has been calculated. */
-public class TkhState extends DefaultState {
-
-    /// ** The log that is used in this state. */
-    // private static Logger log = Logger.getLogger(FlowDepthState.class);
-
-    private static final long serialVersionUID = 1L;
-
-    private static final String I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION = "sinfo.facet.flow_depth.filtered.description";
-
-    private static final String I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION = "sinfo.facet.flow_depth.tkh.filtered.description";
-
-    private static final String I18N_FACET_TKH_DESCRIPTION = "sinfo.facet.tkh.description";
-
-    private static final String SINFO_CHART_FLOW_DEPTH_YAXIS_LABEL = "sinfo.chart.flow_depth.yaxis.label";
-
-    private static final String SINFO_CHART_TKX_YAXIS_LABEL = "sinfo.chart.tkh.yaxis.label";
-
-    /**
-     * From this state can only be continued trivially.
-     */
-    @Override
-    protected String getUIProvider() {
-        return "continue";
-    }
-
-    @Override
-    public Object computeFeed(final D4EArtifact artifact, final String hash, final CallContext context, final List<Facet> facets, final Object old) {
-        // FIXME: why is this necessary?
-        if (artifact instanceof ChartArtifact) {
-            facets.add(new EmptyFacet());
-            return null;
-        }
-
-        return compute((SINFOArtifact) artifact, context, hash, facets, old);
-    }
-
-    @Override
-    public Object computeAdvance(final D4EArtifact artifact, final String hash, final CallContext context, final List<Facet> facets, final Object old) {
-        if (artifact instanceof ChartArtifact) {
-            facets.add(new EmptyFacet());
-            return null;
-        }
-        return compute((SINFOArtifact) artifact, context, hash, facets, old);
-    }
-
-    /**
-     * Compute result or returned object from cache, create facets.
-     *
-     * @param old
-     *            Object that was cached.
-     */
-    private Object compute(final SINFOArtifact sinfo, final CallContext context, final String hash, final List<Facet> facets, final Object old) {
-
-        final CalculationResult res = doCompute(sinfo, context, old);
-
-        if (facets == null)
-            return res;
-
-        // final FlowDepthCalculationResults results = (FlowDepthCalculationResults) res.getData();
-        //
-        // /* add themes for chart, for each result */
-        // final List<FlowDepthCalculationResult> resultList = results.getResults();
-        // for (int index = 0; index < resultList.size(); index++) {
-        //
-        // final FlowDepthCalculationResult result = resultList.get(index);
-        //
-        // /* filtered (zoom dependent mean) flow depth */
-        // final String facetFlowDepthFilteredDescription = Resources.getMsg(context.getMeta(),
-        // I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION,
-        // I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION, result.getLabel());
-        // facets.add(new FlowDepthFacet(index, FlowDepthProcessor.FACET_FLOW_DEPTH_FILTERED, facetFlowDepthFilteredDescription,
-        // SINFO_CHART_FLOW_DEPTH_YAXIS_LABEL, ComputeType.ADVANCE, this.id, hash));
-        //
-        // if (results.isUseTkh()) {
-        // /* filtered (zoom dependent mean) flow depth including tkh */
-        // final String facetFlowDepthTkhFilteredDescription = Resources.getMsg(context.getMeta(),
-        // I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION,
-        // I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION, result.getLabel());
-        // facets.add(new FlowDepthFacet(index, FlowDepthProcessor.FACET_FLOW_DEPTH_TKH_FILTERED,
-        // facetFlowDepthTkhFilteredDescription,
-        // SINFO_CHART_FLOW_DEPTH_YAXIS_LABEL, ComputeType.ADVANCE, this.id, hash));
-        //
-        // // FIXME: add other themes
-        // // - Streckenfavoriten
-        //
-        // // FIXME:
-        // // - Gemittelte Linie der Fließtiefe mitsamt TKH
-        // // - Transportkörperhöhen (oben/unten/schraffur)
-        // final String facetTkhDescription = Resources.getMsg(context.getMeta(), I18N_FACET_TKH_DESCRIPTION,
-        // I18N_FACET_TKH_DESCRIPTION,
-        // result.getLabel());
-        // facets.add(new FlowDepthFacet(index, TkhProcessor.FACET_TKH, facetTkhDescription, SINFO_CHART_TKX_YAXIS_LABEL,
-        // ComputeType.ADVANCE, this.id,
-        // hash));
-        // }
-        //
-        // // FIXME: Datenkorbkonfiguration
-        // }
-        //
-        // if (!resultList.isEmpty()) {
-        // final Facet csv = new DataFacet(FacetTypes.CSV, "CSV data", ComputeType.ADVANCE, hash, this.id);
-        // final Facet pdf = new DataFacet(FacetTypes.PDF, "PDF data", ComputeType.ADVANCE, hash, this.id);
-        //
-        // facets.add(csv);
-        // facets.add(pdf);
-        // }
-        //
-        // final Calculation report = res.getReport();
-        //
-        // if (report.hasProblems()) {
-        // facets.add(new ReportFacet(ComputeType.ADVANCE, hash, this.id));
-        // }
-        //
-        // return res;
-        return null;
-    }
-
-    private CalculationResult doCompute(final SINFOArtifact sinfo, final CallContext context, final Object old) {
-        if (old instanceof CalculationResult)
-            return (CalculationResult) old;
-
-        // res = new FlowDepthCalculation(context).calculate(sinfo);
-
-        final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);
-
-        final CalculationResult waterlevelData = winfo.getWaterlevelData(context);
-        final Calculation winfoProblems = waterlevelData.getReport();
-
-        final WQKms[] kms = (WQKms[]) waterlevelData.getData();
-
-        final Object result = new Object();
-        final Calculation problems = new Calculation();
-
-        return new CalculationResult(result, problems);
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkh/WinfoArtifactWrapper.java	Tue Feb 27 18:06:52 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +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.tkh;
-
-import java.util.Collection;
-
-import org.dive4elements.artifactdatabase.data.DefaultStateData;
-import org.dive4elements.artifactdatabase.data.StateData;
-import org.dive4elements.river.artifacts.D4EArtifact;
-import org.dive4elements.river.artifacts.WINFOArtifact;
-
-/**
- * Ugly wrapper around WINfoArtifact in order to a) not to break serialization of WInfoArtifact b) be able to copy data
- * into it
- *
- * @author Gernot Belger
- *
- */
-class WinfoArtifactWrapper extends WINFOArtifact {
-
-    private static final long serialVersionUID = 1L;
-
-    public WinfoArtifactWrapper(final D4EArtifact dataSource) {
-        final Collection<StateData> allData = dataSource.getAllData();
-        for (final StateData stateData : allData) {
-
-            final DefaultStateData clonedData = new DefaultStateData();
-            clonedData.set(stateData);
-
-            addData(clonedData.getName(), clonedData);
-        }
-
-        addStringData("calculation_mode", "calc.surface.curve");
-    }
-}
\ 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/tkhcalculation/BedQualityD50KmValueFinder.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,260 @@
+/* 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.tkhcalculation;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.apache.commons.math.ArgumentOutsideDomainException;
+import org.apache.commons.math.analysis.interpolation.LinearInterpolator;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.log4j.Logger;
+import org.dive4elements.river.backend.SedDBSessionHolder;
+import org.dive4elements.river.model.River;
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+import org.hibernate.type.StandardBasicTypes;
+
+/**
+ * Searchable sorted km array with parallel bed measurements value array and linear interpolation for km and d50 between
+ * the array elements.<br />
+ * <br />
+ * See comment of SQL command on how the values are filtered and aggregated.
+ *
+ * @author Matthias Schäfer
+ *
+ */
+final class BedQualityD50KmValueFinder {
+
+    /***** INNER CLASSES *****/
+
+    /**
+     * A bed measurements aggregate with its d50 characteristic grain diameter
+     */
+    private static class D50Measurement {
+        private double km;
+
+        public double getKm() {
+            return this.km;
+        }
+
+        // private Date mindate;
+
+        // public Date getMinDate() {
+        // return this.mindate;
+        // }
+
+        // private Date maxdate;
+
+        // public Date getMaxDate() {
+        // return this.maxdate;
+        // }
+
+        private int cnt;
+
+        public int getCnt() {
+            return this.cnt;
+        }
+
+        // private double mindepth;
+
+        // public double getMinDepth() {
+        // return this.mindepth;
+        // }
+
+        // private double maxdepth;
+
+        // public double getMaxDepth() {
+        // return this.maxdepth;
+        // }
+
+        private double d50;
+
+        /**
+         * D50 in m
+         */
+        public double getD50() {
+            return this.d50;
+        }
+
+        // /**
+        // * Parameter constructor
+        // */
+        // public D50Measurement(final double km, final Date mindate, final Date maxdate, final int cnt, final double mindepth,
+        // final double maxdepth,
+        // final double d50mm) {
+        // this.km = km;
+        // this.mindate = mindate;
+        // this.maxdate = maxdate;
+        // this.cnt = cnt;
+        // this.mindepth = mindepth;
+        // this.maxdepth = maxdepth;
+        // this.d50 = d50mm / 1000;
+        // }
+
+        /**
+         * Query result row constructor
+         */
+        public D50Measurement(final Object[] tuple, final String[] aliases) {
+            this.km = 0;
+            // this.mindate = null;
+            // this.maxdate = null;
+            this.cnt = 0;
+            // this.mindepth = Double.NaN;
+            // this.maxdepth = Double.NaN;
+            this.d50 = Double.NaN;
+            for (int i = 0; i < tuple.length; ++i) {
+                if (tuple[i] == null)
+                    continue;
+                switch (aliases[i]) {
+                case "km":
+                    this.km = ((Number) tuple[i]).doubleValue();
+                    break;
+                    // case "mindate":
+                    // this.mindate = (Date) tuple[i];
+                    // break;
+                    // case "maxdate":
+                    // this.maxdate = (Date) tuple[i];
+                    // break;
+                case "cnt":
+                    this.cnt = ((Number) tuple[i]).intValue();
+                    break;
+                    // case "mindepth":
+                    // this.mindepth = ((Number) tuple[i]).doubleValue();
+                    // break;
+                    // case "maxdepth":
+                    // this.maxdepth = ((Number) tuple[i]).doubleValue();
+                    // break;
+                case "d50":
+                    this.d50 = ((Number) tuple[i]).doubleValue() / 1000; // mm to m
+                    break;
+                default:
+                    break;
+                }
+            }
+        }
+    }
+
+    /***** FIELDS *****/
+
+    /**
+     * Private log to use here.
+     */
+    private static Logger log = Logger.getLogger(BedQualityD50KmValueFinder.class);
+
+    /**
+     * Query that aggregates by km for a km range and a time period all sub layer bed measurements with their d50<br />
+     * <br />
+     * A km may have bed measurements for multiple dates, multiple distances from the river bank, and multiple depth layers.
+     * The query filters by km range, time period and layer (sub layer: below bed to max. 50 cm depth).
+     * Those measurements are then grouped by km, and the D50 aggregated as average value.
+     */
+    private static final String SQL_BED_D50_SUBLAYER_MEASUREMENT =
+            "SELECT t.km, MIN(t.datum) AS mindate, MAX(t.datum) AS maxdate, COUNT(*) AS cnt," //
+            + " MIN(p.tiefevon) AS mindepth, MAX(p.tiefebis) AS maxdepth, AVG(a.d50) AS d50" //
+            + " FROM sohltest t INNER JOIN station s ON t.stationid = s.stationid" //
+            + "    INNER JOIN gewaesser g ON s.gewaesserid = g.gewaesserid" //
+            + "    INNER JOIN sohlprobe p ON t.sohltestid = p.sohltestid" //
+            + "    INNER JOIN siebanalyse a ON p.sohlprobeid = a.sohlprobeid" //
+            + " WHERE (g.name = :name) AND (s.km BETWEEN :fromkm - 0.0001 AND :tokm + 0.0001)" //
+            + "    AND (p.tiefevon > 0.0) AND (p.tiefebis <= 0.5)" //
+            + "    AND (t.datum BETWEEN :fromdate AND :todate)" //
+            + " GROUP BY t.km" //
+            + " ORDER BY t.km"; //
+
+    private static final String[] SQL_BED_D50_SELECT_ALIAS = { "km", "mindate", "maxdate", "cnt", "mindepth", "maxdepth", "d50" };
+
+    /**
+     * Real linear interpolator for kms and d50 values
+     */
+    private final PolynomialSplineFunction interpolator;
+
+    /***** METHODS *****/
+
+    private BedQualityD50KmValueFinder(final double[] kms, final double[] values) {
+        this.interpolator = new LinearInterpolator().interpolate(kms, values);
+    }
+
+    /**
+     * Sohlbeschaffenheit (D50 Korndurchmesser aus Seddb)
+     * Abhängig von Peiljahr
+     */
+    public static BedQualityD50KmValueFinder loadBedMeasurements(final River river, final DoubleRange kmRange, final int soundingYear, final int validYears) {
+
+        /* construct valid measurement time range */
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+
+        cal.set(soundingYear - validYears, 0, 1);
+        final Date startTime = cal.getTime();
+
+        cal.set(soundingYear + validYears, 11, 31);
+        final Date endTime = cal.getTime();
+
+        log.debug(String.format("loadValues km %.3f - %.3f %tF - %tF", kmRange.getMinimumDouble(), kmRange.getMaximumDouble(), startTime, endTime));
+        final Session session = SedDBSessionHolder.HOLDER.get();
+        final SQLQuery sqlQuery = session.createSQLQuery(SQL_BED_D50_SUBLAYER_MEASUREMENT).addScalar("km", StandardBasicTypes.DOUBLE)
+                .addScalar("mindate", StandardBasicTypes.DATE).addScalar("maxdate", StandardBasicTypes.DATE).addScalar("cnt", StandardBasicTypes.INTEGER)
+                .addScalar("mindepth", StandardBasicTypes.DOUBLE).addScalar("maxdepth", StandardBasicTypes.DOUBLE).addScalar("d50", StandardBasicTypes.DOUBLE);
+        final String seddbRiver = river.nameForSeddb();
+        sqlQuery.setString("name", seddbRiver);
+        sqlQuery.setDouble("fromkm", kmRange.getMinimumDouble());
+        sqlQuery.setDouble("tokm", kmRange.getMaximumDouble());
+        sqlQuery.setDate("fromdate", startTime);
+        sqlQuery.setDate("todate", endTime);
+
+        final List<Object[]> rows = sqlQuery.list();
+        final double[] kms = new double[rows.size()];
+        final double[] values = new double[rows.size()];
+        D50Measurement measurement;
+        int i = -1;
+        for (final Object[] row : rows) {
+            measurement = new D50Measurement(row, SQL_BED_D50_SELECT_ALIAS);
+            i++;
+            kms[i] = measurement.getKm();
+            values[i] = measurement.getD50();
+            log.debug(String.format("loadValues km %.3f d50(mm) %.1f count %d", kms[i], values[i], measurement.getCnt()));
+        }
+        try {
+            return new BedQualityD50KmValueFinder(kms, values);
+        }
+        catch (final Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Returns the d50 value interpolated according to a km
+     *
+     * @return d50 (mm) of the km, or NaN
+     */
+    public double findD50(final double km) throws ArgumentOutsideDomainException {
+        return this.interpolator.value(km);
+        /*
+         * ohne interpolation:
+         * if ((kms == null) || (kms.size() == 0))
+         * return Double.NaN;
+         * int i = kms.binarySearch(km);
+         * if (i >= 0)
+         * return values.get(i);
+         * i = -i - 1;
+         * if ((i - 1 >= 0) && Utils.epsilonEquals(km, kms.get(i - 1), 0.0001))
+         * return values.get(i - 1);
+         * else if ((i >= 0) && (i <= kms.size() - 1) && Utils.epsilonEquals(km, kms.get(i), 0.0001))
+         * return values.get(i);
+         * else
+         * return Double.NaN;
+         */
+    }
+}
\ 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/tkhcalculation/DischargeValuesFinder.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,61 @@
+/** 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.tkhcalculation;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.dive4elements.river.artifacts.model.QKms;
+import org.dive4elements.river.artifacts.model.WKms;
+import org.dive4elements.river.artifacts.model.WQKms;
+import org.dive4elements.river.utils.DoubleUtil;
+
+/**
+ * @author Gernot Belger
+ */
+public final class DischargeValuesFinder {
+
+    private final UnivariateRealFunction qInterpolator;
+    private final QKms qKms;
+
+    /**
+     * Create an instance from a {@link WKms} object. If the given {@link WKms} is not a {@link WQKms}, a finder that always
+     * returns {@link Double#NaN} is returned.
+     */
+    public static DischargeValuesFinder fromKms(final WKms wstKms) {
+        if (!(wstKms instanceof QKms)) {
+            return new DischargeValuesFinder(null);
+        }
+
+        final QKms qKms = (QKms) wstKms;
+
+        return new DischargeValuesFinder(qKms);
+    }
+
+    public DischargeValuesFinder(final QKms qKms) {
+        this.qKms = qKms;
+        this.qInterpolator = qKms == null ? null : DoubleUtil.getLinearInterpolator(qKms.allKms(), qKms.allQs());
+    }
+
+    /**
+     * If this provider may return valid data at all.
+     */
+    public boolean isValid() {
+        return this.qInterpolator != null;
+    }
+
+    public DoubleRange getRange() {
+        return new DoubleRange(this.qKms.allQs().min(), this.qKms.allQs().max());
+    }
+
+    public double getDischarge(final double station) throws FunctionEvaluationException {
+        return this.qInterpolator.value(station);
+    }
+}
\ 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/tkhcalculation/FlowVelocityModelKmValueFinder.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,337 @@
+/* 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.tkhcalculation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.apache.log4j.Logger;
+import org.dive4elements.river.artifacts.math.Linear;
+import org.dive4elements.river.artifacts.math.Utils;
+import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowVelocityKmModelValues;
+import org.dive4elements.river.backend.SessionHolder;
+import org.dive4elements.river.model.River;
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+import org.hibernate.type.StandardBasicTypes;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * Searchable sorted km array with parallel FlowVelocityKmModelValues array and linear interpolation for km and the
+ * model values between the array elements.<br />
+ * {@link loadValues} loads all the model values for a given km range of a river.<br />
+ * {@link findKmQValues} then searches a km in the values table or the nearest including km interval, resp.
+ * The v and tau values for a given discharge are either found directly or also interpolated linearly.<br />
+ *
+ * (Created based on a copy of FlowVelocityMeasurementFactory.)
+ *
+ * @author Matthias Schäfer
+ */
+final class FlowVelocityModelKmValueFinder {
+    /***** FIELDS *****/
+
+    /**
+     * Private log to use here.
+     */
+    private static Logger log = Logger.getLogger(FlowVelocityModelKmValueFinder.class);
+
+    /**
+     * Query for a range of stations of a river with all their q, main-v and tau values.<br />
+     * (Might be several 10000 rows if many stations and large q range)
+     */
+    private static final String SQL_SELECT_ALL = //
+            "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau"
+            + "  FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)"
+            + "    INNER JOIN flow_velocity_model_values fvmv ON fvm.id = fvmv.flow_velocity_model_id"
+            + "  WHERE (dz.river_id = :river_id) AND (fvmv.station BETWEEN :kmfrom - 0.0001 AND :kmto + 0.0001)"
+            /* + "  WHERE (dz.river_id = :river_id) AND (fvmv.q BETWEEN :qmin AND :qmax)" */
+            + "  ORDER BY fvmv.station ASC, fvmv.q ASC";
+
+    /**
+     * Query for a river's max km below a limit with all its q, main-v and tau values.
+     */
+    private static final String SQL_SELECT_KMLOWER = //
+            "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" + "  FROM flow_velocity_model_values fvmv"
+            + "    INNER JOIN (SELECT MAX(fvmvi.station) AS kmmax"
+            + "      FROM(discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)"
+            + "        INNER JOIN flow_velocity_model_values fvmvi ON fvm.id = fvmvi.flow_velocity_model_id"
+            + "        WHERE (dz.river_id = :river_id) AND (fvmvi.station < :kmfrom - 0.0001)) finf ON fvmv.station = finf.kmmax"
+            + "  ORDER BY fvmv.q ASC";
+
+    /**
+     * Query for a river's min km above a limit with all its q, main-v and tau values.
+     */
+    private static final String SQL_SELECT_KMUPPER = //
+            "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" + "  FROM flow_velocity_model_values fvmv"
+            + "    INNER JOIN (SELECT MIN(fvmvi.station) AS kmmin"
+            + "      FROM(discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)"
+            + "        INNER JOIN flow_velocity_model_values fvmvi ON fvm.id = fvmvi.flow_velocity_model_id"
+            + "        WHERE (dz.river_id = :river_id) AND (fvmvi.station > :kmto + 0.0001)) fsup ON fvmv.station = fsup.kmmin"
+            + "  ORDER BY fvmv.q ASC";
+
+    // /**
+    // * Query to select all km-q-v-tau of a river that are the q maxima below a q limit
+    // */
+    // private static final String SQL_SELECT_QLOWER =
+    // "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau"
+    // + " FROM flow_velocity_model_values fvmv"
+    // + " INNER JOIN (SELECT fv2.station, MAX(fv2.q) AS q"
+    // + " FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)"
+    // + " INNER JOIN flow_velocity_model_values fv2 ON fvm.id = fv2.flow_velocity_model_id"
+    // + " WHERE (dz.river_id = :river_id) AND (fv2.q < :qlim) GROUP BY fv2.station) qx"
+    // + " ON (fvmv.station=qx.station) AND (fvmv.q=qx.q)"
+    // + " ORDER BY fvmv.station ASC";
+    //
+    // /**
+    // * Query to select all km-q-v-tau of a river that are the q minima above a q limit
+    // */
+    // private static final String SQL_SELECT_QUPPER =
+    // "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau"
+    // + " FROM flow_velocity_model_values fvmv"
+    // + " INNER JOIN (SELECT fv2.station, MIN(fv2.q) AS q"
+    // + " FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)"
+    // + " INNER JOIN flow_velocity_model_values fv2 ON fvm.id = fv2.flow_velocity_model_id"
+    // + " WHERE (dz.river_id = :river_id) AND (fv2.q > :qlim) GROUP BY fv2.station) qx"
+    // + " ON (fvmv.station=qx.station) AND (fvmv.q=qx.q)"
+    // + " ORDER BY fvmv.station ASC";
+
+    /**
+     * Kms of the loaded river range
+     */
+    private final TDoubleArrayList kms = new TDoubleArrayList();
+
+    /**
+     * For each km in kms a list of q-v-tau-tupels
+     */
+    private final List<FlowVelocityKmModelValues> values = new ArrayList<>();
+
+    /**
+     * Searched km of the last findKmValue
+     */
+    private double findKm;
+
+    /**
+     * kms and values index of the interval start found by the last findKmValue
+     */
+    private int leftIndexFound = -1;
+
+    /**
+     * kms and values index of the interval end found by the last findKmValue
+     */
+    private int rightIndexFound = -1;
+
+    /**
+     * Q of the last findKmQValues
+     */
+    private double findQ;
+
+    /***** METHODS *****/
+
+    /**
+     * Discharge of the last {@link findKmQValue}
+     */
+    public double getFindQ() {
+        return this.findQ;
+    }
+
+    /**
+     * Velocity of the last {@link findKmQValues}
+     */
+    public double getFindVmainFound() {
+        if (this.leftIndexFound < 0)
+            return Double.NaN;
+        else if (this.leftIndexFound == this.rightIndexFound)
+            return getLeftValues().getVmainFound();
+        else
+            return Linear.linear(this.findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getVmainFound(),
+                    getRightValues().getVmainFound());
+    }
+
+    /**
+     * Shear stress tau of the last {@link findKmQValues}
+     */
+    public double getFindTauFound() {
+        if (this.leftIndexFound < 0)
+            return Double.NaN;
+        else if (this.leftIndexFound == this.rightIndexFound)
+            return getLeftValues().getTauFound();
+        else
+            return Linear.linear(this.findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getTauFound(), getRightValues().getTauFound());
+    }
+
+    /**
+     * Whether the discharge has been interpolated in the last {@link findKmQValues}
+     */
+    public boolean getFindIsQInterpolated() {
+        return (getLeftValues() != null) && (getLeftValues().getIsInterpolated() || getRightValues().getIsInterpolated());
+    }
+
+    /**
+     * Static constructor: queries a range of a river's kms with all their q-v-tau values.
+     *
+     * @return Whether the load has been successful the new instance, <code>null</code> otherwise.
+     */
+    public static FlowVelocityModelKmValueFinder loadValues(final River river, final DoubleRange kmRange, final DoubleRange qRange) {
+        // DB session
+        log.debug(String.format("loadValues km %.3f - %.3f / q %.1f - %.1f", kmRange.getMinimumDouble(), kmRange.getMaximumDouble(), qRange.getMinimumDouble(),
+                qRange.getMaximumDouble()));
+
+        final FlowVelocityModelKmValueFinder instance = new FlowVelocityModelKmValueFinder();
+
+        final TDoubleArrayList kms = instance.kms;
+        final List<FlowVelocityKmModelValues> values = instance.values;
+
+        final boolean isDemoValuesCorrection = river.getName().equalsIgnoreCase("beispielfluss");
+        final Session session = SessionHolder.HOLDER.get();
+
+        // Select km infimum
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_KMLOWER).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("q", StandardBasicTypes.DOUBLE)
+                .addScalar("vmain", StandardBasicTypes.DOUBLE).addScalar("tau", StandardBasicTypes.DOUBLE);
+        sqlQuery.setParameter("river_id", river.getId());
+        sqlQuery.setParameter("kmfrom", kmRange.getMinimumDouble());
+        instance.addKms(sqlQuery.list(), isDemoValuesCorrection);
+
+        // Select km range
+        sqlQuery = session.createSQLQuery(SQL_SELECT_ALL).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("q", StandardBasicTypes.DOUBLE)
+                .addScalar("vmain", StandardBasicTypes.DOUBLE).addScalar("tau", StandardBasicTypes.DOUBLE);
+        sqlQuery.setParameter("river_id", river.getId());
+        sqlQuery.setParameter("kmfrom", kmRange.getMinimumDouble());
+        sqlQuery.setParameter("kmto", kmRange.getMaximumDouble());
+        // sqlQuery.setParameter("qmin", qRange.getMinimumDouble());
+        // sqlQuery.setParameter("qmax", qRange.getMaximumDouble());
+
+        int kmcount = kms.size();
+        final int rowcount = instance.addKms(sqlQuery.list(), isDemoValuesCorrection);
+        kmcount = kms.size() - kmcount;
+
+        // Select km supremum
+        sqlQuery = session.createSQLQuery(SQL_SELECT_KMUPPER).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("q", StandardBasicTypes.DOUBLE)
+                .addScalar("vmain", StandardBasicTypes.DOUBLE).addScalar("tau", StandardBasicTypes.DOUBLE);
+        sqlQuery.setParameter("river_id", river.getId());
+        sqlQuery.setParameter("kmto", kmRange.getMaximumDouble());
+        final int supcnt = instance.addKms(sqlQuery.list(), isDemoValuesCorrection);
+
+        // Add copy of last km for search of max km value
+        if ((supcnt == 0) && (values.size() >= 1)) {
+            kms.add(kms.getQuick(kms.size()) + 0.0001);
+            values.add(new FlowVelocityKmModelValues(kms.getQuick(kms.size() - 1), values.get(values.size() - 1)));
+        }
+
+        // log.debug
+        if (values.size() - 1 >= 0) {
+            log.debug(String.format("loadValues %d: km %.3f - %d values", 0, values.get(0).getKm(), values.get(0).size()));
+
+            if (values.size() - 1 >= 1) {
+                log.debug(String.format("loadValues %d: km %.3f - %d values", 1, values.get(1).getKm(), values.get(1).size()));
+
+                if (values.size() - 1 >= 2)
+                    log.debug("loadValues ...");
+
+                if (values.size() - 2 >= 3)
+                    log.debug(String.format("loadValues %d: km %.3f - %d values", values.size() - 2, values.get(values.size() - 2).getKm(),
+                            values.get(values.size() - 2).size()));
+
+                if (values.size() - 1 >= 3)
+                    log.debug(String.format("loadValues %d: km %.3f - %d values", values.size() - 1, values.get(values.size() - 1).getKm(),
+                            values.get(values.size() - 1).size()));
+            }
+        }
+
+        log.debug(String.format("loadValues %d kms, %d values loaded", kmcount, rowcount));
+
+        if (kms.size() == 0)
+            return null;
+
+        return instance;
+    }
+
+    /**
+     * Adds the km-q-v-tau values of a query result row to the last km of the list, or a new one resp.
+     *
+     * @return Number of rows
+     */
+    private int addKms(final List<Object[]> rows, final boolean isDemoValuesCorrection) {
+        for (final Object[] row : rows) {
+            if ((this.kms.size() == 0) || !Utils.epsilonEquals(this.kms.get(this.kms.size() - 1), (double) row[0], 0.0001)) {
+                this.kms.add((double) row[0]);
+                this.values.add(new FlowVelocityKmModelValues(this.kms.get(this.kms.size() - 1)));
+            }
+            if (isDemoValuesCorrection)
+                // "Verfremdung" der v-Werte etwas korrigieren (Originalwerte wurden mit Zufallswert zwischen 10 und 20 multipliziert)
+                this.values.get(this.values.size() - 1).addValues((double) row[1], ((double) row[2]) / 10, (double) row[3]);
+            else
+                this.values.get(this.values.size() - 1).addValues((double) row[1], (double) row[2], (double) row[3]);
+        }
+        return rows.size();
+    }
+
+    /**
+     * Searches a km and finds or interpolates the velocity and shear stress values for a discharge<br />
+     * The values may be got via {@link getVmainFound} etc.
+     *
+     * @return Whether values have been found
+     */
+    public boolean findKmQValues(final double km, final double q) {
+        this.findQ = q;
+        if (!searchKm(km))
+            return false;
+        if (this.leftIndexFound == this.rightIndexFound) {
+            // Exact km match
+            final double qfound = getLeftValues().findQ(q);
+            log.debug(String.format("findKmQValues km %.3f q %.0f = %.0f (%d)", km, q, qfound, this.leftIndexFound));
+            return !Double.isNaN(qfound);
+        } else {
+            final double[] qfound = { getLeftValues().findQ(q), getRightValues().findQ(q) };
+            log.debug(String.format("findKmQValues km %.3f q %.0f = %.0f (%d, %.3f) - %.0f (%d, %.3f)", km, q, qfound[0], this.leftIndexFound,
+                    getLeftValues().getKm(), qfound[1], this.rightIndexFound, getRightValues().getKm()));
+            return !Double.isNaN(qfound[0]) && !Double.isNaN(qfound[1]);
+        }
+    }
+
+    /**
+     * Searches a km
+     *
+     * @return Whether the km was within the supported range
+     */
+    private boolean searchKm(final double km) {
+        this.findKm = km;
+        this.leftIndexFound = -1;
+        this.rightIndexFound = -1;
+        if ((this.kms == null) || (this.kms.size() == 0))
+            return false;
+        int i = this.kms.binarySearch(km);
+        if (i >= 0) {
+            // Exact km match
+            this.leftIndexFound = i;
+            this.rightIndexFound = i;
+            return true;
+        } else {
+            // Out of range or within km interval
+            if (i < 0)
+                i = -i - 1;
+            if ((i <= 0) || (i >= this.kms.size()))
+                return false;
+            this.leftIndexFound = i - 1;
+            this.rightIndexFound = i;
+            return true;
+        }
+    }
+
+    private FlowVelocityKmModelValues getLeftValues() {
+        return this.values.get(this.leftIndexFound);
+    }
+
+    private FlowVelocityKmModelValues getRightValues() {
+        return this.values.get(this.rightIndexFound);
+    }
+}
\ 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/tkhcalculation/SoilKind.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,14 @@
+/** 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.tkhcalculation;
+
+public enum SoilKind {
+    mobil, starr
+}
\ 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/tkhcalculation/SoilKindKmValueFinder.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,118 @@
+/* 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.tkhcalculation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.apache.commons.math.ArgumentOutsideDomainException;
+import org.dive4elements.river.model.River;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * @author Matthias Schäfer
+ */
+final class SoilKindKmValueFinder {
+    // /**
+    // * Private log to use here.
+    // */
+    // private static Logger log = Logger.getLogger(SoilKindKmValueFinder.class);
+
+    private final TDoubleArrayList kms = new TDoubleArrayList();
+
+    private final List<SoilKind> values = new ArrayList<>();
+
+    /**
+     * Loads the range of the river's kms with their soil kind.
+     *
+     * @return Whether the load has been successful
+     */
+    public static SoilKindKmValueFinder loadValues(final River river, final DoubleRange kmRange) {
+
+        final SoilKindKmValueFinder instance = new SoilKindKmValueFinder();
+
+        // FIXME Echte Daten aus der Datenbank abfragen
+        instance.addKmKind(0, SoilKind.starr);
+        instance.addKmKind(15.7, SoilKind.mobil);
+        instance.addKmKind(15.8, SoilKind.mobil);
+        instance.addKmKind(15.9, SoilKind.starr);
+        instance.addKmKind(108.7, SoilKind.mobil);
+        instance.addKmKind(108.8, SoilKind.mobil);
+        instance.addKmKind(108.9, SoilKind.starr);
+        instance.addKmKind(119.1, SoilKind.mobil);
+        instance.addKmKind(119.4, SoilKind.mobil);
+        instance.addKmKind(119.5, SoilKind.starr);
+        instance.addKmKind(128.3, SoilKind.mobil);
+        instance.addKmKind(128.9, SoilKind.mobil);
+        instance.addKmKind(129, SoilKind.starr);
+        instance.addKmKind(133.1, SoilKind.mobil);
+        instance.addKmKind(135.9, SoilKind.mobil);
+        instance.addKmKind(136, SoilKind.starr);
+        instance.addKmKind(136.5, SoilKind.mobil);
+        instance.addKmKind(139.9, SoilKind.mobil);
+        instance.addKmKind(140, SoilKind.starr);
+        instance.addKmKind(140.5, SoilKind.mobil);
+        instance.addKmKind(165, SoilKind.mobil);
+        instance.addKmKind(165.1, SoilKind.starr);
+        instance.addKmKind(165.9, SoilKind.mobil);
+        instance.addKmKind(180.8, SoilKind.mobil);
+        instance.addKmKind(180.9, SoilKind.starr);
+        instance.addKmKind(182, SoilKind.mobil);
+        instance.addKmKind(221.3, SoilKind.mobil);
+
+        return instance;
+    }
+
+    private SoilKindKmValueFinder() {
+        /* only instantiate me via static constructor */
+    }
+
+    /***** METHODS *****/
+
+    /**
+     * Searches a km with its soil kind
+     */
+    public SoilKind findSoilKind(final double km) throws ArgumentOutsideDomainException {
+        if ((this.kms == null) || (this.kms.size() == 0))
+            throw new ArgumentOutsideDomainException(km, Double.NaN, Double.NaN);
+
+        // TODO: Voraussetzung für die binäre suche ist, dass nach km sortiert ist.
+        // In diesem Fall könnte man ggf. auch gleich eine bessere Datenklasse benutzen, z.B. eine TreeMap<Double, SoilKind>
+        // (also station -> art), und deren funktionen zum finden verwenden:
+        // final double station = 0.0;
+        // final NavigableMap<Double, SoilKind> data = new TreeMap<>();
+        // data.ceilingEntry(station);
+        // data.floorEntry(station);
+
+        int i = this.kms.binarySearch(km);
+        if (i >= 0) {
+            // Exact km match
+            return this.values.get(i);
+        } else {
+            // Out of range or within km interval
+            if (i < 0)
+                i = -i - 1;
+            if ((i <= 0) || (i >= this.kms.size()))
+                throw new ArgumentOutsideDomainException(km, Double.NaN, Double.NaN);
+            if (km <= ((this.kms.get(i - 1) + this.kms.get(i)) / 2))
+                return this.values.get(i - 1);
+            else
+                return this.values.get(i);
+        }
+    }
+
+    private void addKmKind(final double km, final SoilKind kind) {
+        this.kms.add(km);
+        this.values.add(kind);
+    }
+}
\ 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/tkhcalculation/Tkh.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,86 @@
+/** 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.tkhcalculation;
+
+import java.io.Serializable;
+
+/**
+ * Result of a transport bodies height calculation.
+ *
+ * @author Gernot Belger
+ */
+public final class Tkh implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private final double km;
+
+    private final double wst;
+
+    private final double meanBedHeight;
+
+    private final double discharge;
+
+    private final SoilKind kind;
+
+    private final double tkh;
+
+    private final double tkhUp;
+
+    private final double tkhDown;
+
+    public Tkh(final double km, final double wst, final double meanBedHeight, final double discharge) {
+        this(km, wst, meanBedHeight, discharge, null, Double.NaN, Double.NaN, Double.NaN);
+    }
+
+    public Tkh(final double km, final double wst, final double meanBedHeight, final double discharge, final SoilKind kind, final double tkh, final double tkhUp,
+            final double tkhDown) {
+        this.km = km;
+        this.wst = wst;
+        this.meanBedHeight = meanBedHeight;
+        this.discharge = discharge;
+        this.kind = kind;
+        this.tkh = tkh;
+        this.tkhUp = tkhUp;
+        this.tkhDown = tkhDown;
+    }
+
+    public double getStation() {
+        return this.km;
+    }
+
+    public double getTkh() {
+        return this.tkh;
+    }
+
+    public SoilKind getKind() {
+        return this.kind;
+    }
+
+    public double getUp() {
+        return this.tkhUp;
+    }
+
+    public double getDown() {
+        return this.tkhDown;
+    }
+
+    public double getWaterlevel() {
+        return this.wst;
+    }
+
+    public double getDischarge() {
+        return this.discharge;
+    }
+
+    public double getMeanBedHeight() {
+        return this.meanBedHeight;
+    }
+}
\ 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/tkhcalculation/TkhCalculator.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,232 @@
+/** 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.tkhcalculation;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.apache.commons.math.ArgumentOutsideDomainException;
+import org.apache.commons.math.FunctionEvaluationException;
+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.tkhstate.BedHeightsFinder;
+import org.dive4elements.river.model.River;
+
+/**
+ * @author Gernot Belger
+ */
+public final class TkhCalculator {
+
+    private static final int VALID_BED_MEASUREMENT_YEARS = 20;
+
+    private final Calculation problems;
+
+    private final String problemLabel;
+
+    private final CallContext context;
+
+    private final BedQualityD50KmValueFinder bedMeasurementsFinder;
+
+    private final SoilKindKmValueFinder soilKindFinder;
+
+    private final BedHeightsFinder bedHeightsProvider;
+
+    private final DischargeValuesFinder dischargeProvider;
+
+    private final FlowVelocityModelKmValueFinder flowVelocitiesFinder;
+
+    public static TkhCalculator buildTkhCalculator(final boolean useTkh, final CallContext context, final Calculation problems, final String label,
+            final River river, final DoubleRange calcRange, final DischargeValuesFinder dischargeProvider, final BedHeightsFinder bedHeightsProvider) {
+
+        if (!useTkh)
+            return null;
+
+        if (!dischargeProvider.isValid()) {
+            final String message = Resources.getMsg(context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, label);
+            problems.addProblem(message);
+            return null;
+        }
+
+        final Integer soundingYear = bedHeightsProvider.getInfo().getYear();
+        final BedQualityD50KmValueFinder bedMeasurementsFinder = BedQualityD50KmValueFinder.loadBedMeasurements(river, calcRange, soundingYear,
+                VALID_BED_MEASUREMENT_YEARS);
+
+        if (bedMeasurementsFinder == null) {
+            final String message = Resources.getMsg(context.getMeta(), "sinfo_calc_flow_depth.warning.missingSoilKind", null, label);
+            problems.addProblem(message);
+            return null;
+        }
+
+        // FIXME: wie wird ggf. interpoliert? prüfung ob werte vorhanden?
+        final SoilKindKmValueFinder soilKindFinder = SoilKindKmValueFinder.loadValues(river, calcRange);
+        if (soilKindFinder == null) {
+            final String message = Resources.getMsg(context.getMeta(), "sinfo_calc_flow_depth.warning.missingSoilKind", null, label);
+            problems.addProblem(message);
+            return null;
+        }
+
+        final DoubleRange qRange = dischargeProvider.getRange();
+        final FlowVelocityModelKmValueFinder flowVelocitiesFinder = FlowVelocityModelKmValueFinder.loadValues(river, calcRange, qRange);
+        if (flowVelocitiesFinder == null) {
+            final String message = Resources.getMsg(context.getMeta(), "sinfo_calc_flow_depth.warning.missingVelocity", null, label);
+            problems.addProblem(message);
+            return null;
+        }
+
+        return new TkhCalculator(problems, label, context, bedMeasurementsFinder, dischargeProvider, bedHeightsProvider, soilKindFinder, flowVelocitiesFinder);
+    }
+
+    private TkhCalculator(final Calculation problems, final String problemLabel, final CallContext context,
+            final BedQualityD50KmValueFinder bedMeasurementsFinder, final DischargeValuesFinder dischargeProvider, final BedHeightsFinder bedHeightsProvider,
+            final SoilKindKmValueFinder soilKindFinder,
+            final FlowVelocityModelKmValueFinder flowVelocitiesFinder) {
+        this.problems = problems;
+        this.problemLabel = problemLabel;
+        this.context = context;
+        this.bedMeasurementsFinder = bedMeasurementsFinder;
+        this.dischargeProvider = dischargeProvider;
+        this.bedHeightsProvider = bedHeightsProvider;
+        this.soilKindFinder = soilKindFinder;
+        this.flowVelocitiesFinder = flowVelocitiesFinder;
+    }
+
+    private double getDischarge(final double km) {
+
+        try {
+            return this.dischargeProvider.getDischarge(km);
+        }
+        catch (final FunctionEvaluationException e) {
+            // TODO: exceptions nicht komplett schlucken? evtl. mit log.debug(e) ausgeben
+            return Double.NaN;
+        }
+    }
+
+    private SoilKind getSoilKind(final double km) {
+
+        try {
+            return this.soilKindFinder.findSoilKind(km);
+        }
+        catch (final ArgumentOutsideDomainException e) {
+            // FIXME: cumulate problems to one message?
+            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingSoilKind", null, this.problemLabel);
+            this.problems.addProblem(km, message);
+            return null;
+        }
+    }
+
+    private double getBedMeasurement(final double km) {
+
+        try {
+            return this.bedMeasurementsFinder.findD50(km);
+        }
+        catch (final Exception e) {
+            // FIXME: cumulate problems to one message?
+            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingD50", null, this.problemLabel);
+            this.problems.addProblem(km, message);
+
+            return Double.NaN;
+        }
+    }
+
+    public Tkh getTkh(final double km, final double wst) {
+
+        final SoilKind kind = getSoilKind(km);
+
+        final double meanBedHeight = this.bedHeightsProvider.getMeanBedHeight(km);
+
+        final double discharge = getDischarge(km);
+        if (Double.isNaN(discharge)) {
+
+            // final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null,
+            // this.problemLabel);
+            // this.problems.addProblem(km, message);
+
+            // TODO: nochmal gemeinsam überlegen welche probleme wir loggen, an dieser stelle müsste man ggf. die station
+            // mitausgeben
+
+            return new Tkh(km, wst, meanBedHeight, Double.NaN, kind, Double.NaN, Double.NaN, Double.NaN);
+        }
+
+        final double d50 = getBedMeasurement(km);
+        if (Double.isNaN(d50))
+            return new Tkh(km, wst, meanBedHeight, discharge, kind, Double.NaN, Double.NaN, Double.NaN);
+
+        if (!this.flowVelocitiesFinder.findKmQValues(km, discharge)) {
+            // TODO: ggf. station in Fehlermeldung?
+            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, this.problemLabel);
+            this.problems.addProblem(km, message);
+            // FIXME: cumulate problems to one message?
+        }
+
+        final double tkh = calculateTkh(wst - meanBedHeight, this.flowVelocitiesFinder.getFindVmainFound(), d50, this.flowVelocitiesFinder.getFindTauFound());
+        // FIXME: noch mal prüfen, im alten code wurde hier immer auf 0 gesetzt
+        if (Double.isNaN(tkh) || (tkh < 0)) {
+            // TODO: ggf. station in Fehlermeldung?
+
+            // FIXME: Fehlermeldung nicht korrekt, passiert mit Wasserspiegel 'MHQ' und 'QP-1993': alle Daten (auch Abfluss)
+            // vorhanden, aber tkh negativ...
+            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, this.problemLabel);
+            this.problems.addProblem(km, message);
+
+            return new Tkh(km, wst, meanBedHeight, discharge, kind, Double.NaN, Double.NaN, Double.NaN);
+        }
+
+        /*
+         * log.debug(String.format("calculateTkh km %.3f q %.0f w %.2f mbh %.2f vm %.1f tau %.1f d50(mm) %.1f tkh(cm) %.1f",
+         * km, discharge, wst, meanBedHeight, flowVelocitiesFinder.getFindVmainFound(), flowVelocitiesFinder.getFindTauFound(),
+         * d50*1000, tkh));
+         */
+
+        double tkhUp;
+        double tkhDown;
+        switch (kind) {
+        case starr:
+            tkhUp = tkh;
+            tkhDown = 0;
+            break;
+
+        case mobil:
+        default:
+            tkhUp = tkh / 2;
+            tkhDown = -tkh / 2;
+            break;
+        }
+
+        return new Tkh(km, wst, meanBedHeight, discharge, kind, tkh, tkhUp, tkhDown);
+    }
+
+    /**
+     * Calculates a transport body height
+     *
+     * @param h
+     *            flow depth in m
+     * @param vm
+     *            flow velocity in m
+     * @param d50
+     *            grain diameter D50 in m (!)
+     * @param tau
+     *            shear stress in N/m^2
+     * @return transport body height in cm (!)
+     */
+    private double calculateTkh(final double h, final double vm, final double d50, final double tau) {
+        final double PHYS_G = 9.81;
+        final double PHYS_SPECGRAV_S = 2.6;
+        final double PHYS_VELOCCOEFF_N = 6;
+        final double PHYS_FORMCOEFF_ALPHA = 0.7;
+        final double PHYS_VISCOSITY_NUE = 1.3e-6;
+        final double PHYS_GRAIN_DENSITY_RHOS = 2603;
+        final double PHYS_WATER_DENSITY_RHO = 999.97;
+
+        final double froude = vm / Math.sqrt(PHYS_G * h);
+        final double partReynolds = Math.sqrt((PHYS_SPECGRAV_S - 1) * PHYS_G * d50) / PHYS_VISCOSITY_NUE * d50;
+        final double critShields = 0.22 * Math.pow(partReynolds, -0.6) + 0.06 * Math.pow(10, 7.7 * Math.pow(partReynolds, -0.6));
+        final double critTau = critShields * (PHYS_GRAIN_DENSITY_RHOS - PHYS_WATER_DENSITY_RHO) * PHYS_G * d50;
+        return 100 * h * (1 - Math.pow(froude, 2)) / (2 * PHYS_VELOCCOEFF_N * PHYS_FORMCOEFF_ALPHA) * (1 - critTau / tau);
+    }
+}
\ 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/tkhstate/BedHeightsFinder.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,134 @@
+/** 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.dive4elements.river.artifacts.math.Linear;
+import org.dive4elements.river.artifacts.model.Calculation;
+import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
+import org.dive4elements.river.model.BedHeight;
+import org.dive4elements.river.model.BedHeightValue;
+import org.dive4elements.river.model.River;
+
+/**
+ * Provides bed heigts for vcarious calculations.
+ *
+ * @author Gernot Belger
+ */
+public final class BedHeightsFinder {
+
+    private final BedHeightInfo info;
+
+    private final NavigableMap<Double, BedHeightValue> values;
+
+    /**
+     * Create specific bed heights used in tkh-calculation
+     *
+     * @param problems
+     */
+    public static Collection<BedHeightsFinder> createTkhBedHeights(final River river, final Calculation problems, final DoubleRange range) {
+        // FIXME: determine relevant bed-heights by river: read from some configuration file
+        // '3' is already the right one for demo-model == '"DGM-2004_Epoche-2-SOBEK"'
+        final int bedheightId = 3;
+
+        final Collection<BedHeight> bedHeights = Collections.singletonList(BedHeight.getBedHeightById(bedheightId));
+
+        // TODO: check for overlapping ranges... and provide a warning message, else we get problems later
+
+        final List<BedHeightsFinder> result = new ArrayList<>(bedHeights.size());
+
+        for (final BedHeight bedHeight : bedHeights) {
+            result.add(createBedHeights(bedHeight, range));
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a {@link BedHeightsFinder} for a dataset from the database, specified by its id.
+     *
+     * @return <code>null</code> if no bed height with the given id exists.
+     */
+    public static BedHeightsFinder forId(final int id, final DoubleRange range) {
+
+        final BedHeight bedHeight = BedHeight.getBedHeightById(id);
+        if (bedHeight == null)
+            return null;
+
+        return BedHeightsFinder.createBedHeights(bedHeight, range);
+    }
+
+    /**
+     * Create a finder for a given bed height.
+     *
+     * @param range
+     */
+    private static BedHeightsFinder createBedHeights(final BedHeight bedHeight, final DoubleRange range) {
+
+        // FIXME: sort by station, but in what direction?
+        // FIXME: using river.getKmUp()?
+        final NavigableMap<Double, BedHeightValue> values = new TreeMap<>();
+
+        for (final BedHeightValue bedHeightValue : bedHeight.getValues()) {
+            final Double station = bedHeightValue.getStation();
+            if (station != null && range.containsDouble(station)) {
+
+                if (bedHeightValue.getHeight() != null)
+                    values.put(station, bedHeightValue);
+            }
+        }
+
+        final BedHeightInfo info = BedHeightInfo.from(bedHeight);
+
+        return new BedHeightsFinder(info, values);
+    }
+
+    private BedHeightsFinder(final BedHeightInfo info, final NavigableMap<Double, BedHeightValue> values) {
+        this.info = info;
+        this.values = values;
+    }
+
+    public BedHeightInfo getInfo() {
+        return this.info;
+    }
+
+    public Collection<Double> getStations() {
+        return this.values.keySet();
+    }
+
+    public double getMeanBedHeight(final double km) {
+
+        if (this.values.containsKey(km))
+            return this.values.get(km).getHeight();
+
+        final Entry<Double, BedHeightValue> floorEntry = this.values.floorEntry(km);
+        final Entry<Double, BedHeightValue> ceilingEntry = this.values.ceilingEntry(km);
+
+        if (floorEntry == null || ceilingEntry == null)
+            return Double.NaN;
+
+        final double floorKm = floorEntry.getKey();
+        final double floorHeight = floorEntry.getValue().getHeight();
+        final double ceilKm = ceilingEntry.getKey();
+        final double ceilHeight = ceilingEntry.getValue().getHeight();
+
+        // FIXME: check if we always want that...
+
+        return Linear.linear(km, floorKm, ceilKm, floorHeight, ceilHeight);
+    }
+}
\ 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/tkhstate/TkhAccess.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,40 @@
+/* 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.apache.commons.lang.math.DoubleRange;
+import org.dive4elements.river.artifacts.access.RangeAccess;
+import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
+import org.dive4elements.river.artifacts.sinfo.SinfoCalcMode;
+
+/**
+ * Access to the flow depth calculation type specific SInfo artifact data.
+ * REMARK: this class is NOT intended to be hold in the results (or anywhere else), in order to avoid a permanent
+ * reference to the artifact instance.
+ * Hence we do NOT cache any data.
+ *
+ * @author Gernot Belger
+ */
+final class TkhAccess extends RangeAccess {
+    public TkhAccess(final SINFOArtifact artifact) {
+        super(artifact);
+
+        /* assert calculation mode */
+        final SinfoCalcMode calculationMode = artifact.getCalculationMode();
+        assert (calculationMode == SinfoCalcMode.sinfo_calc_transport_bodies_heights);
+    }
+
+    public DoubleRange getRange() {
+        final double from = getFrom();
+        final double to = getTo();
+        return new DoubleRange(from, to);
+    }
+}
\ 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/tkhstate/TkhCalculation.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,152 @@
+/** 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 java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.WINFOArtifact;
+import org.dive4elements.river.artifacts.model.Calculation;
+import org.dive4elements.river.artifacts.model.Calculation.Problem;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.WQKms;
+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.tkhcalculation.DischargeValuesFinder;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator;
+import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
+import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
+import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
+import org.dive4elements.river.artifacts.states.WaterlevelData;
+import org.dive4elements.river.model.River;
+
+/**
+ * @author Gernot Belger
+ */
+final class TkhCalculation {
+
+    private final CallContext context;
+
+    public TkhCalculation(final CallContext context) {
+        this.context = context;
+    }
+
+    public CalculationResult calculate(final SINFOArtifact sinfo) {
+
+        /* access input data */
+        final TkhAccess access = new TkhAccess(sinfo);
+        final River river = access.getRiver();
+        final RiverInfo riverInfo = new RiverInfo(river);
+        final DoubleRange calcRange = access.getRange();
+
+        final Calculation problems = new Calculation();
+
+        /* find relevant bed-heights */
+        final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(river, problems, calcRange);
+
+        /* calculate waterlevels */
+        final WQKms[] kms = calculateWaterlevels(sinfo, problems);
+
+        final RiverInfoProvider infoProvider = RiverInfoProvider.forRange(this.context, river, calcRange);
+
+        final String user = CalculationUtils.findArtifactUser(this.context, sinfo);
+
+        final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name());
+
+        /* for each waterlevel, do a tkh calculation */
+        final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange);
+
+        for (final WQKms wqKms : kms) {
+
+            final TkhCalculationResult result = calculateResult(calcRange, infoProvider, wqKms, bedHeights, problems);
+            if (result != null)
+                // FIXME: must be sorted by station!
+                results.addResult(result);
+        }
+
+        return new CalculationResult(results, problems);
+    }
+
+    private WQKms[] calculateWaterlevels(final SINFOArtifact sinfo, final Calculation problems) {
+
+        /* misuse winfo-artifact to calculate waterlevels in the same way */
+        final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);
+
+        final CalculationResult waterlevelData = winfo.getWaterlevelData(this.context);
+
+        /* copy all problems */
+        final Calculation winfoProblems = waterlevelData.getReport();
+        for (final Problem problem : winfoProblems.getProblems()) {
+            problems.addProblem(problem);
+        }
+
+        return (WQKms[]) waterlevelData.getData();
+    }
+
+    private TkhCalculationResult calculateResult(final DoubleRange calcRange, final RiverInfoProvider riverInfo, final WQKms wkms,
+            final Collection<BedHeightsFinder> bedHeights, final Calculation problems) {
+
+        // FIXME: wo kommt das her? via winfo kein jahr vorhanden, oder doch? aber soll in metadaten ausgegeben werden...
+        final int wspYear = -1;
+        // FIXME: richtig? vgl. WInfo?
+        final boolean showAllGauges = false;
+        final WaterlevelData waterlevel = new WaterlevelData(wkms, wspYear, showAllGauges);
+
+        final RiverInfoProvider riverInfoProvider = riverInfo.forWaterlevel(waterlevel);
+
+        final String label = waterlevel.getName();
+
+        final WstInfo wstInfo = new WstInfo(label, wspYear, riverInfoProvider.getReferenceGauge());
+
+        final Collection<TkhResultRow> rows = new ArrayList<>();
+
+        /*
+         * for each separate bed height dataset we do the calculation and put everything into one result, bed heights must not
+         * overlap accordingly
+         */
+        for (final BedHeightsFinder bedHeightsProvider : bedHeights) {
+
+            final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms);
+
+            /* initialize tkh calculator */
+            final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, this.context, problems, label, riverInfoProvider.getRiver(), calcRange,
+                    dischargeProvider, bedHeightsProvider);
+            if (tkhCalculator == null) {
+                /* just abort, problems have already been updated by buildTkhCalculator() */
+                return null;
+            }
+
+            /* 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.
+            // bei großen abständen?
+
+            final int size = wkms.size();
+            for (int i = 0; i < size; i++) {
+
+                final double station = wkms.getKm(i);
+                final double wst = wkms.getW(i);
+
+                final Tkh tkh = tkhCalculator.getTkh(station, wst);
+
+                final String gaugeLabel = riverInfoProvider.findGauge(station);
+                final String location = riverInfoProvider.getLocation(station);
+
+                rows.add(new TkhResultRow(tkh, label, gaugeLabel, location));
+            }
+        }
+
+        return new TkhCalculationResult(label, wstInfo, true, rows);
+    }
+}
\ 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/tkhstate/TkhCalculationResult.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,29 @@
+/* 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 java.util.Collection;
+
+import org.dive4elements.river.artifacts.sinfo.common.AbstractSInfoCalculationResult;
+import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
+
+/**
+ * Contains the results of a {@link FlowDepthCalculation}.
+ *
+ * @author Gernot Belger
+ */
+final class TkhCalculationResult extends AbstractSInfoCalculationResult<TkhResultRow> {
+
+    private static final long serialVersionUID = 1L;
+
+    public TkhCalculationResult(final String label, final WstInfo wst, final boolean hasTkh, final Collection<TkhResultRow> rows) {
+        super(label, wst, hasTkh, rows);
+    }
+}
\ 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/tkhstate/TkhCalculationResults.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,66 @@
+/** 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 java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang.math.DoubleRange;
+import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
+
+/**
+ * @author Gernot Belger
+ */
+final class TkhCalculationResults implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final List<TkhCalculationResult> results = new ArrayList<>();
+
+    private final String calcModeLabel;
+
+    private final String user;
+
+    private final RiverInfo river;
+
+    private final DoubleRange calcRange;
+
+    public TkhCalculationResults(final String calcModeLabel, final String user, final RiverInfo river, final DoubleRange calcRange) {
+        this.calcModeLabel = calcModeLabel;
+        this.user = user;
+        this.river = river;
+        this.calcRange = calcRange;
+    }
+
+    public String getCalcModeLabel() {
+        return this.calcModeLabel;
+    }
+
+    public String getUser() {
+        return this.user;
+    }
+
+    public RiverInfo getRiver() {
+        return this.river;
+    }
+
+    public DoubleRange getCalcRange() {
+        return this.calcRange;
+    }
+
+    void addResult(final TkhCalculationResult result) {
+        this.results.add(result);
+    }
+
+    public List<TkhCalculationResult> getResults() {
+        return Collections.unmodifiableList(this.results);
+    }
+}
\ 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/tkhstate/TkhResultRow.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,25 @@
+/** 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.AbstractSInfoResultRow;
+import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh;
+
+/**
+ * @author Gernot Belger
+ */
+final class TkhResultRow extends AbstractSInfoResultRow {
+
+    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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhState.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,146 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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 java.util.List;
+
+import org.dive4elements.artifactdatabase.state.Facet;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.ChartArtifact;
+import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.EmptyFacet;
+import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
+import org.dive4elements.river.artifacts.states.DefaultState;
+
+/** State in which a waterlevel has been calculated. */
+public class TkhState extends DefaultState {
+
+    /// ** The log that is used in this state. */
+    // private static Logger log = Logger.getLogger(FlowDepthState.class);
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION = "sinfo.facet.flow_depth.filtered.description";
+
+    private static final String I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION = "sinfo.facet.flow_depth.tkh.filtered.description";
+
+    private static final String I18N_FACET_TKH_DESCRIPTION = "sinfo.facet.tkh.description";
+
+    private static final String SINFO_CHART_FLOW_DEPTH_YAXIS_LABEL = "sinfo.chart.flow_depth.yaxis.label";
+
+    private static final String SINFO_CHART_TKX_YAXIS_LABEL = "sinfo.chart.tkh.yaxis.label";
+
+    /**
+     * From this state can only be continued trivially.
+     */
+    @Override
+    protected String getUIProvider() {
+        return "continue";
+    }
+
+    @Override
+    public Object computeFeed(final D4EArtifact artifact, final String hash, final CallContext context, final List<Facet> facets, final Object old) {
+        // FIXME: why is this necessary?
+        if (artifact instanceof ChartArtifact) {
+            facets.add(new EmptyFacet());
+            return null;
+        }
+
+        return compute((SINFOArtifact) artifact, context, hash, facets, old);
+    }
+
+    @Override
+    public Object computeAdvance(final D4EArtifact artifact, final String hash, final CallContext context, final List<Facet> facets, final Object old) {
+        if (artifact instanceof ChartArtifact) {
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return compute((SINFOArtifact) artifact, context, hash, facets, old);
+    }
+
+    /**
+     * Compute result or returned object from cache, create facets.
+     *
+     * @param old
+     *            Object that was cached.
+     */
+    private Object compute(final SINFOArtifact sinfo, final CallContext context, final String hash, final List<Facet> facets, final Object old) {
+
+        final CalculationResult res = doCompute(sinfo, context, old);
+
+        if (facets == null)
+            return res;
+
+        // final FlowDepthCalculationResults results = (FlowDepthCalculationResults) res.getData();
+        //
+        // /* add themes for chart, for each result */
+        // final List<FlowDepthCalculationResult> resultList = results.getResults();
+        // for (int index = 0; index < resultList.size(); index++) {
+        //
+        // final FlowDepthCalculationResult result = resultList.get(index);
+        //
+        // /* filtered (zoom dependent mean) flow depth */
+        // final String facetFlowDepthFilteredDescription = Resources.getMsg(context.getMeta(),
+        // I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION,
+        // I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION, result.getLabel());
+        // facets.add(new FlowDepthFacet(index, FlowDepthProcessor.FACET_FLOW_DEPTH_FILTERED, facetFlowDepthFilteredDescription,
+        // SINFO_CHART_FLOW_DEPTH_YAXIS_LABEL, ComputeType.ADVANCE, this.id, hash));
+        //
+        // if (results.isUseTkh()) {
+        // /* filtered (zoom dependent mean) flow depth including tkh */
+        // final String facetFlowDepthTkhFilteredDescription = Resources.getMsg(context.getMeta(),
+        // I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION,
+        // I18N_FACET_FLOW_DEPTH_TKH_FILTERED_DESCRIPTION, result.getLabel());
+        // facets.add(new FlowDepthFacet(index, FlowDepthProcessor.FACET_FLOW_DEPTH_TKH_FILTERED,
+        // facetFlowDepthTkhFilteredDescription,
+        // SINFO_CHART_FLOW_DEPTH_YAXIS_LABEL, ComputeType.ADVANCE, this.id, hash));
+        //
+        // // FIXME: add other themes
+        // // - Streckenfavoriten
+        //
+        // // FIXME:
+        // // - Gemittelte Linie der Fließtiefe mitsamt TKH
+        // // - Transportkörperhöhen (oben/unten/schraffur)
+        // final String facetTkhDescription = Resources.getMsg(context.getMeta(), I18N_FACET_TKH_DESCRIPTION,
+        // I18N_FACET_TKH_DESCRIPTION,
+        // result.getLabel());
+        // facets.add(new FlowDepthFacet(index, TkhProcessor.FACET_TKH, facetTkhDescription, SINFO_CHART_TKX_YAXIS_LABEL,
+        // ComputeType.ADVANCE, this.id,
+        // hash));
+        // }
+        //
+        // // FIXME: Datenkorbkonfiguration
+        // }
+        //
+        // if (!resultList.isEmpty()) {
+        // final Facet csv = new DataFacet(FacetTypes.CSV, "CSV data", ComputeType.ADVANCE, hash, this.id);
+        // final Facet pdf = new DataFacet(FacetTypes.PDF, "PDF data", ComputeType.ADVANCE, hash, this.id);
+        //
+        // facets.add(csv);
+        // facets.add(pdf);
+        // }
+        //
+        // final Calculation report = res.getReport();
+        //
+        // if (report.hasProblems()) {
+        // facets.add(new ReportFacet(ComputeType.ADVANCE, hash, this.id));
+        // }
+        //
+        // return res;
+        return null;
+    }
+
+    private CalculationResult doCompute(final SINFOArtifact sinfo, final CallContext context, final Object old) {
+        if (old instanceof CalculationResult)
+            return (CalculationResult) old;
+
+        return new TkhCalculation(context).calculate(sinfo);
+    }
+}
\ 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/tkhstate/WinfoArtifactWrapper.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,42 @@
+/** 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 java.util.Collection;
+
+import org.dive4elements.artifactdatabase.data.DefaultStateData;
+import org.dive4elements.artifactdatabase.data.StateData;
+import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.WINFOArtifact;
+
+/**
+ * Ugly wrapper around WINfoArtifact in order to a) not to break serialization of WInfoArtifact b) be able to copy data
+ * into it
+ *
+ * @author Gernot Belger
+ *
+ */
+class WinfoArtifactWrapper extends WINFOArtifact {
+
+    private static final long serialVersionUID = 1L;
+
+    public WinfoArtifactWrapper(final D4EArtifact dataSource) {
+        final Collection<StateData> allData = dataSource.getAllData();
+        for (final StateData stateData : allData) {
+
+            final DefaultStateData clonedData = new DefaultStateData();
+            clonedData.set(stateData);
+
+            addData(clonedData.getName(), clonedData);
+        }
+
+        addStringData("calculation_mode", "calc.surface.curve");
+    }
+}
\ 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/util/CalculationUtils.java	Wed Feb 28 17:27:15 2018 +0100
@@ -0,0 +1,37 @@
+/** 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.util;
+
+import org.dive4elements.artifacts.Artifact;
+import org.dive4elements.artifacts.ArtifactDatabase;
+import org.dive4elements.artifacts.CallContext;
+
+/**
+ * @author Gernot Belger
+ */
+public final class CalculationUtils {
+
+
+    private CalculationUtils() {
+        throw new UnsupportedOperationException("Helper class");
+    }
+
+    /**
+     * Find the the user of the given artifact, sadly this is not part of the calling context, so instead we determine the
+     * owner oft the artifact
+     *
+     * @param artifact
+     * @param context
+     */
+    public static String findArtifactUser(final CallContext context, final Artifact artifact) {
+        final ArtifactDatabase database = context.getDatabase();
+        return database.findArtifactUser(artifact.identifier());
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/WstInfo.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/WstInfo.java	Wed Feb 28 17:27:15 2018 +0100
@@ -24,10 +24,10 @@
 
     private final String gauge;
 
-    public WstInfo(final String label, final int year, final String gauge) {
+    public WstInfo(final String label, final int year, final String refGauge) {
         this.label = label;
         this.year = year;
-        this.gauge = gauge;
+        this.gauge = refGauge;
     }
 
     public String getLabel() {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelData.java	Tue Feb 27 18:06:52 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelData.java	Wed Feb 28 17:27:15 2018 +0100
@@ -10,8 +10,6 @@
 package org.dive4elements.river.artifacts.states;
 
 import org.dive4elements.river.artifacts.model.WKms;
-import org.dive4elements.river.model.Gauge;
-import org.dive4elements.river.model.River;
 
 /**
  * Represents a waterlevel fetched with the {@link WaterlevelFetcher}.
@@ -68,23 +66,6 @@
         return this.showAllGauges;
     }
 
-    public Gauge findReferenceGauge(final River river) {
-        final double[] wstFromTo = findWstFromTo();
-        return river.determineRefGauge(wstFromTo, true);
-    }
-
-    private double[] findWstFromTo() {
-
-        final double from = this.wkms.getKm(0);
-        final double to = this.wkms.getKm(this.wkms.size() - 1);
-
-        final boolean waterIncreasing = this.wkms.guessWaterIncreasing();
-        if (waterIncreasing)
-            return new double[] { to, from };
-
-        return new double[] { from, to };
-    }
-
     public int getYear() {
         return this.year;
     }

http://dive4elements.wald.intevation.org