changeset 8964:45f1ad66560e

Code cleanup concerning calculations: improved error handling; improved interpolation; bed heights are now always used for spatial discretisation
author gernotbelger
date Thu, 29 Mar 2018 15:48:17 +0200
parents b98fbd91f64a
children f89fb9e9abad
files artifacts/src/main/java/org/dive4elements/river/artifacts/BedHeightsArtifact.java artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/BedHeightMinMaxFacet.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowVelocityKmModelValues.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthdev/FlowDepthDevelopmentCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.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/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/tkhcalculation/WaterlevelValuesFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/LinearInterpolator.java artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelData.java artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelFetcher.java artifacts/src/main/resources/messages.properties artifacts/src/main/resources/messages_de.properties backend/src/main/java/org/dive4elements/river/model/BedHeightValueType.java
diffstat 21 files changed, 569 insertions(+), 551 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/BedHeightsArtifact.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/BedHeightsArtifact.java	Thu Mar 29 15:48:17 2018 +0200
@@ -22,11 +22,11 @@
 import org.dive4elements.river.artifacts.model.FacetTypes;
 import org.dive4elements.river.artifacts.model.minfo.BedHeightFacet;
 import org.dive4elements.river.artifacts.model.minfo.BedHeightMinMaxFacet;
-import org.dive4elements.river.artifacts.model.minfo.BedHeightMinMaxFacet.BedHeightValueType;
 import org.dive4elements.river.artifacts.model.minfo.BedHeightSoundingWidthFacet;
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.states.StaticState;
 import org.dive4elements.river.exports.process.BedHeightProcessor;
+import org.dive4elements.river.model.BedHeightValueType;
 import org.w3c.dom.Document;
 
 public class BedHeightsArtifact extends AbstractStaticStateArtifact implements FacetTypes {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/BedHeightMinMaxFacet.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/BedHeightMinMaxFacet.java	Thu Mar 29 15:48:17 2018 +0200
@@ -19,6 +19,7 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.model.BedHeight;
 import org.dive4elements.river.model.BedHeightValue;
+import org.dive4elements.river.model.BedHeightValueType;
 
 import gnu.trove.TDoubleArrayList;
 
@@ -32,29 +33,6 @@
 
     private static final long serialVersionUID = 1L;
 
-    public static enum BedHeightValueType {
-        min {
-            @Override
-            public Double getValue(final BedHeightValue bedheightValue) {
-                return bedheightValue.getMinHeight();
-            }
-        },
-        max {
-            @Override
-            public Double getValue(final BedHeightValue bedheightValue) {
-                return bedheightValue.getMaxHeight();
-            }
-        },
-        value {
-            @Override
-            public Double getValue(final BedHeightValue bedheightValue) {
-                return bedheightValue.getHeight();
-            }
-        };
-
-        public abstract Double getValue(final BedHeightValue bedheightValue);
-    }
-
     private final BedHeightValueType valueType;
 
     public BedHeightMinMaxFacet(final String name, final String description, final BedHeightValueType valueType) {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.java	Thu Mar 29 15:48:17 2018 +0200
@@ -90,7 +90,7 @@
             return null;
 
         /* REMARK: fetch ALL wst kms, because we want to determine the original reference gauge */
-        final WaterlevelData waterlevel = new WaterlevelFetcher().findWaterlevel(this.context, wstId, Double.NaN, Double.NaN, problems);
+        final WaterlevelData waterlevel = new WaterlevelFetcher().findWaterlevel(this.context, wstId, calcRange, problems);
         if (waterlevel == null)
             return null;
 
@@ -101,8 +101,6 @@
         final String label = String.format("%s - %s", wspLabel, soundingLabel);
 
         FlowDepthUtils.checkYearDifference(label, waterlevel.getYear(), 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 RiverInfoProvider riverInfoProvider = infoProvider.forWaterlevel(waterlevel);
@@ -110,35 +108,14 @@
         final int wspYear = waterlevel.getYear();
         final WstInfo wstInfo = new WstInfo(wspLabel, wspYear, riverInfoProvider.getReferenceGauge());
 
-        final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(wstKms);
+        final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(problems, wstKms);
         final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wstKms);
 
         final River river = riverInfoProvider.getRiver();
-        final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(useTkh, this.context, problems, label, river, calcRange, waterlevelProvider,
-                dischargeProvider,
-                bedHeight);
+        final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(useTkh, problems, label, river, calcRange, waterlevelProvider,
+                dischargeProvider, bedHeight);
 
         final FlowDepthCalculator calculator = new FlowDepthCalculator(riverInfoProvider, wspLabel, bedHeight, tkhCalculator);
         return calculator.execute(label, wstInfo, calcRange);
     }
-
-    /* Checks if the discretisation of the waterlevel exceeds 1000m */
-    private void checkWaterlevelDiscretisation(final WKms wstKms, final DoubleRange calcRange, final Calculation problems) {
-
-        final int size = wstKms.size();
-        for (int i = 0; i < size - 2; i++) {
-            final double kmPrev = wstKms.getKm(i);
-            final double kmNext = wstKms.getKm(i + 1);
-
-            /* only check if we are within the calculation range */
-            if (calcRange.overlapsRange(new DoubleRange(kmPrev, kmNext))) {
-                if (Math.abs(kmPrev - kmNext) > 1) {
-                    final String label = wstKms.getName();
-
-                    final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.waterlevel_discretisation", null, label);
-                    problems.addProblem(kmPrev, message);
-                }
-            }
-        }
-    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowVelocityKmModelValues.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowVelocityKmModelValues.java	Thu Mar 29 15:48:17 2018 +0200
@@ -17,170 +17,173 @@
 
 /**
  * Sorted arrays of a station's q, v, and tau model values, running in parallel
+ *
  * @author Matthias Schäfer
  *
  */
 public class FlowVelocityKmModelValues {
 
     /***** FIELDS *****/
-    
+
     /**
      * Km
      */
-    private double km;
-    
+    private final double km;
+
     /**
      * The station's discharge model values, sorted in ascending order
      */
-    private TDoubleArrayList qs;
-    
+    private final TDoubleArrayList qs;
+
     /**
      * The station's main section velocity for the q values
      */
-    private TDoubleArrayList vmains;
-    
+    private final TDoubleArrayList vmains;
+
     /**
      * The station's shear stress (tau) values for the q values
      */
-    private TDoubleArrayList taus;
+    private final TDoubleArrayList taus;
 
     /**
      * Discharge found by the last findQ
      */
     private double findQ;
-    
+
     /**
      * Velocity found by the last {@link findQ}
      */
     private double vmainFound;
-    
+
     /**
      * Shear stress found by the last {@link findQ}
      */
     private double tauFound;
-    
+
     /**
      * Whether qFound has been interpolated
      */
     private boolean isInterpolated;
-    
+
     /**
      * Real linear interpolator for q and v values
      */
     private PolynomialSplineFunction vInterpolator;
-    
+
     /**
      * Real linear interpolator for q and tau values
      */
     private PolynomialSplineFunction tauInterpolator;
-    
-    
+
     /***** CONSTRUCTORS *****/
-    
+
     /**
      * Constructor with km parameter
      */
-    public FlowVelocityKmModelValues(double km) {
+    public FlowVelocityKmModelValues(final double km) {
         this.km = km;
-        qs = new TDoubleArrayList();
-        vmains = new TDoubleArrayList();
-        taus = new TDoubleArrayList();
-        vInterpolator = null;
-        tauInterpolator = null;
+        this.qs = new TDoubleArrayList();
+        this.vmains = new TDoubleArrayList();
+        this.taus = new TDoubleArrayList();
+        this.vInterpolator = null;
+        this.tauInterpolator = null;
     }
 
     /**
      * Copy constructor with new km
      */
-    public FlowVelocityKmModelValues(double km, FlowVelocityKmModelValues src) {
+    public FlowVelocityKmModelValues(final double km, final FlowVelocityKmModelValues src) {
         this(km);
-        src.copyTo(qs, vmains, taus);
+        src.copyTo(this.qs, this.vmains, this.taus);
     }
-    
+
     /***** METHODS *****/
-    
+
     /**
      * Number of the q-v-tau tuples
      */
     public int size() {
-        if (qs != null)
-            return qs.size();
-        else
-            return 0;
+        return this.qs.size();
     }
-    
+
     /**
      * Km
      */
     public double getKm() {
-        return km;
+        return this.km;
     }
-    
+
     /**
      * Adds all q-v-tau to another set of arrays
      */
-    void copyTo(TDoubleArrayList dstqs, TDoubleArrayList dstvmains, TDoubleArrayList dsttaus) {
-        for (int i = 0; i <= qs.size(); i++) {
-            dstqs.add(qs.getQuick(i));
-            dstvmains.add(vmains.getQuick(i));
-            dsttaus.add(taus.getQuick(i));
+    void copyTo(final TDoubleArrayList dstqs, final TDoubleArrayList dstvmains, final TDoubleArrayList dsttaus) {
+        for (int i = 0; i <= this.qs.size(); i++) {
+            dstqs.add(this.qs.getQuick(i));
+            dstvmains.add(this.vmains.getQuick(i));
+            dsttaus.add(this.taus.getQuick(i));
         }
     }
-    
+
     /**
      * Discharge found by the last {@link findQ}
+     *
      * @return
      */
     public double getFindQ() {
-        return findQ;
+        return this.findQ;
     }
-    
+
     /**
      * Velocity found by the last {@link findQ}
      */
     public double getVmainFound() {
-        return vmainFound;
+        return this.vmainFound;
     }
-    
+
     /**
      * Shear stress found by the last {@link findQ}
      */
     public double getTauFound() {
-        return tauFound;
+        return this.tauFound;
     }
-    
+
     /**
-     * Whether qFound has been interpolated 
+     * Whether qFound has been interpolated
      */
     public boolean getIsInterpolated() {
-        return isInterpolated;
+        return this.isInterpolated;
     }
-    
+
     /**
-     * Adds a q-v-tau value triple. 
+     * Adds a q-v-tau value triple.
      */
-    public void addValues(double q, double vmain, double tau) {
-        qs.add(q);
-        vmains.add(vmain);
-        taus.add(tau);
+    public void addValues(final double q, final double vmain, final double tau) {
+        this.qs.add(q);
+        this.vmains.add(vmain);
+        this.taus.add(tau);
     }
-    
+
     /**
      * Searches a discharge value and returns it or the interpolated value
+     *
      * @return Found or interpolated discharge, or NaN otherwise
      */
-    public double findQ(double q) {
-        if (vInterpolator == null) {
-            vInterpolator = new LinearInterpolator().interpolate(qs.toNativeArray(), vmains.toNativeArray());
-            tauInterpolator = new LinearInterpolator().interpolate(qs.toNativeArray(), taus.toNativeArray());
+    public double findQ(final double q) {
+        if (this.vInterpolator == null) {
+            this.vInterpolator = new LinearInterpolator().interpolate(this.qs.toNativeArray(), this.vmains.toNativeArray());
+            this.tauInterpolator = new LinearInterpolator().interpolate(this.qs.toNativeArray(), this.taus.toNativeArray());
         }
-        findQ = q;
+
+        this.findQ = q;
+
         try {
-            vmainFound = vInterpolator.value(q);
-            tauFound = tauInterpolator.value(q);
-        } catch (Exception e) {
-            q = Double.NaN;
+            this.vmainFound = this.vInterpolator.value(q);
+            this.tauFound = this.tauInterpolator.value(q);
+            return q;
         }
-        return q;
+        catch (final Exception e) {
+            e.printStackTrace();
+            return Double.NaN;
+        }
     }
-}
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthdev/FlowDepthDevelopmentCalculation.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthdev/FlowDepthDevelopmentCalculation.java	Thu Mar 29 15:48:17 2018 +0200
@@ -11,6 +11,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.TreeSet;
 
 import org.apache.commons.lang.math.DoubleRange;
 import org.dive4elements.artifacts.CallContext;
@@ -79,11 +80,11 @@
             final WstSoundingIdPair histPair, final Calculation problems, final RiverInfoProvider infoProvider) {
 
         /* access real input data from database */
-        final WaterlevelData currentWaterlevel = loadWaterlevel(currentPair, problems);
+        final WaterlevelData currentWaterlevel = loadWaterlevel(currentPair, calcRange, problems);
         if (currentWaterlevel == null)
             return null;
 
-        final WaterlevelData historicalWaterlevel = loadWaterlevel(histPair, problems);
+        final WaterlevelData historicalWaterlevel = loadWaterlevel(histPair, calcRange, problems);
         if (historicalWaterlevel == null)
             return null;
 
@@ -112,9 +113,6 @@
         FlowDepthUtils.checkYearDifference("", currentWstYear, currentSoundingYear, problems);
         FlowDepthUtils.checkYearDifference("", historicalWstYear, historicalSoundingYear, 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 RiverInfoProvider currentRiverInfoProvider = infoProvider.forWaterlevel(currentWaterlevel);
         final RiverInfoProvider histRiverInfoProvider = infoProvider.forWaterlevel(historicalWaterlevel);
@@ -123,35 +121,27 @@
         final WstInfo historicalWstInfo = new WstInfo(historicalWaterlevel.getName(), historicalWstYear, histRiverInfoProvider.getReferenceGauge());
 
         final WKms currentWkms = currentWaterlevel.getWkms();
-        final WaterlevelValuesFinder currentWstProvider = WaterlevelValuesFinder.fromKms(currentWkms);
-        // final DischargeValuesFinder currentDischargeProvider = DischargeValuesFinder.fromKms(currentWkms);
+        final WaterlevelValuesFinder currentWstProvider = WaterlevelValuesFinder.fromKms(problems, currentWkms);
 
         final WKms historicalWkms = historicalWaterlevel.getWkms();
-        final WaterlevelValuesFinder historicalWstProvider = WaterlevelValuesFinder.fromKms(historicalWkms);
-        // final DischargeValuesFinder historicalDischargeProvider = DischargeValuesFinder.fromKms(historicalWkms);
+        final WaterlevelValuesFinder historicalWstProvider = WaterlevelValuesFinder.fromKms(problems, historicalWkms);
 
         final int currentMeanYear = (currentWstYear + currentSoundingYear) / 2;
         final int historcialMeanYear = (historicalWstYear + historicalSoundingYear) / 2;
 
-        // final String waterlevelLabel = waterlevel.getName();
-        // final String soundingLabel = buildSoundingLabel(minBedHeight, maxBedHeight);
-
         final double diffYear = currentMeanYear - historcialMeanYear;
 
         /* real calculation loop */
         final Collection<SInfoResultRow> rows = new ArrayList<>();
 
-        // FIXME: determine what is the spatial discretisation that we will use...
-        final double[] allKms = currentWkms.allKms().toNativeArray();
-        for (final double station : allKms) {
+        final Collection<Double> stations = determineCalculationSteps(currentSounding, historicalSounding);
+        for (final double station : stations) {
             if (calcRange.containsDouble(station)) {
 
                 final double currentWst = currentWstProvider.getWaterlevel(station);
-                // final double currentDischarge = currentDischargeProvider.getDischarge(station);
                 final double currentBedHeight = currentSounding.getMeanBedHeight(station);
 
                 final double historicalWst = historicalWstProvider.getWaterlevel(station);
-                // final double historicalDischarge = historicalDischargeProvider.getDischarge(station);
                 final double historicalBedHeight = historicalSounding.getMeanBedHeight(station);
 
                 final double diffWst = (currentWst - historicalWst) * 100;
@@ -186,6 +176,19 @@
                 rows);
     }
 
+    /**
+     * Calculation steps are simply the union of all stations of all involved bed-height datasets
+     */
+    private Collection<Double> determineCalculationSteps(final BedHeightsFinder currentSounding, final BedHeightsFinder historicalSounding) {
+
+        final Collection<Double> allStations = new TreeSet<>();
+
+        allStations.addAll(currentSounding.getStations());
+        allStations.addAll(historicalSounding.getStations());
+
+        return allStations;
+    }
+
     private String buildLabel(final WaterlevelData currentWaterlevel, final BedHeightInfo currentSounding, final WaterlevelData historicalWaterlevel,
             final BedHeightInfo historicalSounding) {
 
@@ -201,9 +204,9 @@
     }
 
     /* REMARK: fetch ALL wst kms, because we need to determine the original reference gauge */
-    private WaterlevelData loadWaterlevel(final WstSoundingIdPair pair, final Calculation problems) {
+    private WaterlevelData loadWaterlevel(final WstSoundingIdPair pair, final DoubleRange calcRange, final Calculation problems) {
         final String wstId = pair.getWstId();
-        return new WaterlevelFetcher().findWaterlevel(this.context, wstId, Double.NaN, Double.NaN, problems);
+        return new WaterlevelFetcher().findWaterlevel(this.context, wstId, calcRange, problems);
     }
 
     private BedHeightsFinder loadBedHeight(final WstSoundingIdPair pair, final DoubleRange calcRange, final Calculation problems) {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.java	Thu Mar 29 15:48:17 2018 +0200
@@ -87,6 +87,9 @@
 
         /* access real input data from database */
         final String wstId = minMaxPair.getWstId();
+
+        // FIXME: bfg überzeugen dass man immer nur pärchen auswählen kann --> min/max id ist gleich!
+
         final String minSoundingId = minMaxPair.getMinSoundingId();
         final String maxSoundingId = minMaxPair.getMaxSoundingId();
 
@@ -96,7 +99,7 @@
             return null;
 
         /* REMARK: fetch ALL wst kms, because we want to determine the original reference gauge */
-        final WaterlevelData waterlevel = new WaterlevelFetcher().findWaterlevel(this.context, wstId, Double.NaN, Double.NaN, problems);
+        final WaterlevelData waterlevel = new WaterlevelFetcher().findWaterlevel(this.context, wstId, calcRange, problems);
         if (waterlevel == null)
             return null;
 
@@ -106,9 +109,6 @@
 
         final int soundingYear = checkSoundingYear(minBedHeight, maxBedHeight, problems);
         FlowDepthUtils.checkYearDifference(label, waterlevel.getYear(), soundingYear, problems);
-        // FIXME
-        // 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 RiverInfoProvider riverInfoProvider = infoProvider.forWaterlevel(waterlevel);
@@ -116,7 +116,7 @@
         final int wspYear = waterlevel.getYear();
         final WstInfo wstInfo = new WstInfo(waterlevel.getName(), wspYear, riverInfoProvider.getReferenceGauge());
 
-        final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(wstKms);
+        final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(problems, wstKms);
         final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wstKms);
 
         final String waterlevelLabel = waterlevel.getName();
@@ -125,9 +125,9 @@
         /* real calculation loop */
         final Collection<SInfoResultRow> rows = new ArrayList<>();
 
-        // FIXME: determine what is the spatial discretisation that we will use...
-        final double[] allKms = wstKms.allKms().toNativeArray();
-        for (final double station : allKms) {
+        // FIXME: we use the stations of one of the bed heights atm, because we probably will later use only data from one bed heights datasets!
+        final Collection<Double> stations = minBedHeight == null ? maxBedHeight.getStations() : minBedHeight.getStations();
+        for (final double station : stations) {
             if (calcRange.containsDouble(station)) {
 
                 final double wst = waterlevelProvider.getWaterlevel(station);
@@ -140,6 +140,7 @@
                 final double maxFlowDepth = wst - maxBedHeightValue;
 
                 // FIXME: unclear what is meant here...
+                // FIXME: this will simply the bed height of 'the' bed height if we reduce this to simply using one sounding dataset
                 final double meanBedHeight = Double.NaN;
 
                 // REMARK: access the location once only during calculation
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/BedQualityD50KmValueFinder.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/BedQualityD50KmValueFinder.java	Thu Mar 29 15:48:17 2018 +0200
@@ -20,6 +20,7 @@
 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.Calculation;
 import org.dive4elements.river.backend.SedDBSessionHolder;
 import org.dive4elements.river.model.River;
 import org.hibernate.SQLQuery;
@@ -51,39 +52,42 @@
      * <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).<br />
-     * 
-     * If PostgreSQL would support a median aggregate function like Oracle does, the aggregation could be placed into this query.
+     *
+     * If PostgreSQL would support a median aggregate function like Oracle does, the aggregation could be placed into this
+     * query.
      */
-    private static final String SQL_BED_D50_SUBLAYER_MEASUREMENT =
-    "SELECT t.km, t.datum, p.tiefevon, p.tiefebis, a.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)"
-        + " ORDER BY t.km ASC, a.d50 ASC";
+    private static final String SQL_BED_D50_SUBLAYER_MEASUREMENT = "SELECT t.km, t.datum, p.tiefevon, p.tiefebis, a.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)" + " ORDER BY t.km ASC, a.d50 ASC";
+
+    private Calculation problems;
 
     /**
      * Real linear interpolator for kms and d50 values (m)
      */
     private final PolynomialSplineFunction interpolator;
 
-
     /***** CONSTRUCTORS *****/
-    
-    private BedQualityD50KmValueFinder(final double[] kms, final double[] values) {
+
+    private BedQualityD50KmValueFinder(final Calculation problems, final double[] kms, final double[] values) {
+        this.problems = problems;
+
+        // FIXME: check: max distance prüfen? dann D4E-LinearInterpolator verwenden
         this.interpolator = new LinearInterpolator().interpolate(kms, values);
     }
 
     /***** METHODS *****/
-    
+
     /**
      * Sohlbeschaffenheit (D50 Korndurchmesser aus Seddb)
      * Abhängig von Peiljahr
+     *
+     * @param problems
      */
-    public static BedQualityD50KmValueFinder loadBedMeasurements(final River river, final DoubleRange kmRange, final int soundingYear, final int validYears) {
+    public static BedQualityD50KmValueFinder loadBedMeasurements(final Calculation problems, final River river, final DoubleRange kmRange,
+            final int soundingYear, final int validYears) {
 
         /* construct valid measurement time range */
         final Calendar cal = Calendar.getInstance();
@@ -98,8 +102,8 @@
         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("datum", StandardBasicTypes.DATE).addScalar("tiefevon", StandardBasicTypes.DOUBLE)
-                .addScalar("tiefebis", StandardBasicTypes.DOUBLE).addScalar("d50", StandardBasicTypes.DOUBLE);
+                .addScalar("datum", StandardBasicTypes.DATE).addScalar("tiefevon", StandardBasicTypes.DOUBLE).addScalar("tiefebis", StandardBasicTypes.DOUBLE)
+                .addScalar("d50", StandardBasicTypes.DOUBLE);
         final String seddbRiver = river.nameForSeddb();
         sqlQuery.setString("name", seddbRiver);
         sqlQuery.setDouble("fromkm", kmRange.getMinimumDouble());
@@ -111,21 +115,29 @@
         final TDoubleArrayList kms = new TDoubleArrayList();
         final TDoubleArrayList values = new TDoubleArrayList();
         final TDoubleArrayList kmd50s = new TDoubleArrayList();
+
         for (int i = 0; i <= rows.size() - 1; i++) {
             kmd50s.add((double) rows.get(i)[4]);
-            if (((i == rows.size() - 1) || !Utils.epsilonEquals((double) rows.get(i)[0], (double) rows.get(i+1)[0], 0.0001))) {
-                int k = kmd50s.size() / 2;
-                values.add(((k + k < kmd50s.size()) ? kmd50s.get(k) : (kmd50s.get(k-1) + kmd50s.get(k)) / 2) / 1000);
+            if (((i == rows.size() - 1) || !Utils.epsilonEquals((double) rows.get(i)[0], (double) rows.get(i + 1)[0], 0.0001))) {
+                final int k = kmd50s.size() / 2;
+                values.add(((k + k < kmd50s.size()) ? kmd50s.get(k) : (kmd50s.get(k - 1) + kmd50s.get(k)) / 2) / 1000);
                 kms.add((double) rows.get(i)[0]);
-                log.debug(String.format("loadValues km %.3f d50(mm) %.1f count %d", kms.get(kms.size()-1), values.get(values.size()-1), kmd50s.size()));
+                log.debug(String.format("loadValues km %.3f d50(mm) %.1f count %d", kms.get(kms.size() - 1), values.get(values.size() - 1), kmd50s.size()));
                 kmd50s.clear();
+            }
         }
+
+        if (kms.size() < 2 || values.size() < 2) {
+            problems.addProblem("bedqualityd50kmvaluefinder.empty", soundingYear);
+            return null;
         }
+
         try {
-            return new BedQualityD50KmValueFinder(kms.toNativeArray(), values.toNativeArray());
+            return new BedQualityD50KmValueFinder(problems, kms.toNativeArray(), values.toNativeArray());
         }
         catch (final Exception e) {
             e.printStackTrace();
+            problems.addProblem("bedqualityd50kmvaluefinder.error", e.getLocalizedMessage());
             return null;
         }
     }
@@ -135,22 +147,20 @@
      *
      * @return d50 (m) 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;
-         */
+    public double findD50(final double km) {
+        try {
+            return this.interpolator.value(km);
+        }
+        catch (final ArgumentOutsideDomainException e) {
+            e.printStackTrace();
+
+            if (this.problems != null) {
+                this.problems.addProblem(km, "bedqualityd50kmvaluefinder.missing");
+                // Report only once
+                this.problems = null;
+            }
+
+            return Double.NaN;
+        }
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/DischargeValuesFinder.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/DischargeValuesFinder.java	Thu Mar 29 15:48:17 2018 +0200
@@ -77,6 +77,8 @@
     public double getDischarge(final double station) {
 
         try {
+            // FIXME: check: ich dachte wir interpolieren den abfluss nicht linear?
+
             // IMPORTANT: we first try to retrieve the exact value if it is present, to avoid rounding changes due to interpolation.
             // This is important because in the WaterlevelExporter code, these values are double-compared (with '==' ...) in order
             // to find the corresponding main-value.
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/FlowVelocityModelKmValueFinder.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/FlowVelocityModelKmValueFinder.java	Thu Mar 29 15:48:17 2018 +0200
@@ -17,6 +17,7 @@
 import org.apache.log4j.Logger;
 import org.dive4elements.river.artifacts.math.Linear;
 import org.dive4elements.river.artifacts.math.Utils;
+import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowVelocityKmModelValues;
 import org.dive4elements.river.backend.SessionHolder;
 import org.dive4elements.river.model.River;
@@ -37,6 +38,7 @@
  *
  * @author Matthias Schäfer
  */
+// TODO: noch mal prüfen, ob wir eine interpolationsschranke brauchen (max. km-abstand)
 final class FlowVelocityModelKmValueFinder {
     /***** FIELDS *****/
 
@@ -115,6 +117,8 @@
      */
     private final List<FlowVelocityKmModelValues> values = new ArrayList<>();
 
+    private Calculation problems;
+
     /**
      * Searched km of the last findKmValue
      */
@@ -135,6 +139,10 @@
      */
     private double findQ;
 
+    public FlowVelocityModelKmValueFinder(final Calculation problems) {
+        this.problems = problems;
+    }
+
     /***** METHODS *****/
 
     /**
@@ -163,10 +171,11 @@
     public double getFindTauFound() {
         if (this.leftIndexFound < 0)
             return Double.NaN;
-        else if (this.leftIndexFound == this.rightIndexFound)
+
+        if (this.leftIndexFound == this.rightIndexFound)
             return getLeftValues().getTauFound();
-        else
-            return Linear.linear(this.findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getTauFound(), getRightValues().getTauFound());
+
+        return Linear.linear(this.findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getTauFound(), getRightValues().getTauFound());
     }
 
     /**
@@ -179,14 +188,17 @@
     /**
      * Static constructor: queries a range of a river's kms with all their q-v-tau values.
      *
+     * @param problems
+     *
      * @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) {
+    public static FlowVelocityModelKmValueFinder loadValues(final Calculation problems, 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 FlowVelocityModelKmValueFinder instance = new FlowVelocityModelKmValueFinder(problems);
 
         final TDoubleArrayList kms = instance.kms;
         final List<FlowVelocityKmModelValues> values = instance.values;
@@ -249,8 +261,10 @@
 
         log.debug(String.format("loadValues %d kms, %d values loaded", kmcount, rowcount));
 
-        if (kms.size() == 0)
+        if (kms.size() == 0) {
+            problems.addProblem("flowvelocitymodelkmvaluefinder.empty");
             return null;
+        }
 
         return instance;
     }
@@ -283,19 +297,35 @@
      */
     public boolean findKmQValues(final double km, final double q) {
         this.findQ = q;
+
+        final boolean found = doFindKmQValues(km, q);
+
+        if (this.problems != null) {
+
+            this.problems.addProblem(km, "flowvelocitymodelkmvaluefinder.missing");
+
+            // report only once
+            this.problems = null;
+        }
+
+        return found;
+    }
+
+    private boolean doFindKmQValues(final double km, final double 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]);
         }
+
+        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]);
     }
 
     /**
@@ -307,24 +337,23 @@
         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;
         }
+
+        // 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() {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/SoilKindKmValueFinder.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/SoilKindKmValueFinder.java	Thu Mar 29 15:48:17 2018 +0200
@@ -16,14 +16,13 @@
 import java.util.TreeMap;
 
 import org.apache.commons.lang.math.DoubleRange;
-import org.apache.commons.math.ArgumentOutsideDomainException;
+import org.dive4elements.river.artifacts.model.Calculation;
 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;
 
-
 /**
  * @author Matthias Schäfer
  */
@@ -38,21 +37,17 @@
      */
     private static final String SQL_BED_MOBILITY = "SELECT bmv.station, bmv.moving"
             + " FROM bed_mobility bm INNER JOIN bed_mobility_values bmv ON bm.id = bmv.bed_mobility_id"
-            + " WHERE (bm.river_id=:river_id) AND (bmv.station BETWEEN (:fromkm-0.0001) AND (:tokm+0.0001))"
-            + " ORDER BY bmv.station ASC";
+            + " WHERE (bm.river_id=:river_id) AND (bmv.station BETWEEN (:fromkm-0.0001) AND (:tokm+0.0001))" + " ORDER BY bmv.station ASC";
 
-    private final NavigableMap<Double, SoilKind> kmMobility;
+    private final NavigableMap<Double, SoilKind> kmMobility = new TreeMap<>();
 
+    private Calculation problems;
 
     /***** CONSTRUCTORS *****/
 
-    private SoilKindKmValueFinder() {
-        /* only instantiate me via static constructor */
-        this.kmMobility = null;
-    }
+    private SoilKindKmValueFinder(final Calculation problems, final List<Object[]> queryRows) {
+        this.problems = problems;
 
-    private SoilKindKmValueFinder(final List<Object[]> queryRows) {
-        this.kmMobility = new TreeMap<>();
         for (int i = 0; i <= queryRows.size() - 1; i++) {
             this.kmMobility.put(Double.valueOf((double) queryRows.get(i)[0]), (((int) queryRows.get(i)[1]) == 1) ? SoilKind.mobil : SoilKind.starr);
         }
@@ -65,18 +60,19 @@
      *
      * @return Whether the load has been successful
      */
-    public static SoilKindKmValueFinder loadValues(final River river, final DoubleRange kmRange) {
+    public static SoilKindKmValueFinder loadValues(final Calculation problems, final River river, final DoubleRange kmRange) {
         final Session session = SessionHolder.HOLDER.get();
-        final SQLQuery sqlQuery = session.createSQLQuery(SQL_BED_MOBILITY).addScalar("station", StandardBasicTypes.DOUBLE)
-                .addScalar("moving", StandardBasicTypes.INTEGER);
+        final SQLQuery sqlQuery = session.createSQLQuery(SQL_BED_MOBILITY).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("moving",
+                StandardBasicTypes.INTEGER);
         sqlQuery.setInteger("river_id", river.getId().intValue());
         sqlQuery.setDouble("fromkm", kmRange.getMinimumDouble());
         sqlQuery.setDouble("tokm", kmRange.getMaximumDouble());
         final List<Object[]> rows = sqlQuery.list();
         if (rows.size() >= 1)
-            return new SoilKindKmValueFinder(rows);
-        else
-            return null;
+            return new SoilKindKmValueFinder(problems, rows);
+
+        problems.addProblem("soilkindkmvaluefinder.empty");
+        return null;
     }
 
     /***** METHODS *****/
@@ -84,26 +80,41 @@
     /**
      * Searches a km with its soil kind
      */
-    public SoilKind findSoilKind(final double km) throws ArgumentOutsideDomainException {
-        if ((this.kmMobility == null) || this.kmMobility.isEmpty())
-            throw new ArgumentOutsideDomainException(km, Double.NaN, Double.NaN);
-        else if (this.kmMobility.containsKey(Double.valueOf(km)))
+    public SoilKind findSoilKind(final double km) {
+
+        if (this.kmMobility.containsKey(Double.valueOf(km)))
             return this.kmMobility.get(Double.valueOf(km));
-        else {
-            // FIXME: Assert minimum distance between neighbouring stations and candidate km?
-            final Entry<Double, SoilKind> streamUp = this.kmMobility.floorEntry(Double.valueOf(km));
-            if (streamUp == null)
-                throw new ArgumentOutsideDomainException(km, Double.NaN, Double.NaN);
-            else {
-                // Return the soil kind of the neighbouring station with the shorter distance to the candidate.
-                final Entry<Double, SoilKind> streamDown = this.kmMobility.ceilingEntry(Double.valueOf(km));
-                if (streamDown == null)
-                    return streamUp.getValue();
-                else if ((streamUp.getKey().doubleValue() + streamDown.getKey().doubleValue()) / 2 <= km)
-                    return streamUp.getValue();
-                else
-                    return streamDown.getValue();
-            }
+
+        final Entry<Double, SoilKind> streamUp = this.kmMobility.floorEntry(Double.valueOf(km));
+        if (streamUp == null)
+        {
+            reportProblem(km);
+            return null;
         }
+
+        // FIXME: Assert minimum distance between neighbouring stations and candidate km?
+
+        // Return the soil kind of the neighbouring station with the shorter distance to the candidate.
+        final Entry<Double, SoilKind> streamDown = this.kmMobility.ceilingEntry(Double.valueOf(km));
+        if (streamDown == null)
+            return streamUp.getValue();
+
+        final double streamUpValue = streamUp.getKey().doubleValue();
+        final double streamDownValue = streamDown.getKey().doubleValue();
+
+        if ((streamUpValue + streamDownValue) / 2 <= km)
+            return streamUp.getValue();
+
+        return streamDown.getValue();
+    }
+
+    private void reportProblem(final double km) {
+        if (this.problems == null)
+            return;
+
+        this.problems.addProblem(km, "soilkindkmvaluefinder.missing");
+
+        // report problem only once
+        this.problems = null;
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/Tkh.java	Wed Mar 28 17:04:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +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.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 flowDepth;
-
-    private final double flowDepthTkh;
-
-    private final double discharge;
-
-    private final SoilKind kind;
-
-    private final double tkh;
-
-    private final double tkhUp;
-
-    private final double tkhDown;
-
-    private final double velocity;
-
-    private final double d50;
-
-    private final double tau;
-
-    public Tkh(final double km, final double wst, final double meanBedHeight, final double flowDepth, final double discharge) {
-        this(km, wst, meanBedHeight, flowDepth, discharge, null);
-    }
-
-    public Tkh(final double km, final double wst, final double meanBedHeight, final double flowDepth, final double discharge, final SoilKind kind) {
-        this(km, wst, meanBedHeight, flowDepth, Double.NaN, discharge, kind, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN);
-    }
-
-    public Tkh(final double km, final double wst, final double meanBedHeight, final double flowDepth, final double flowDepthTkh, final double discharge,
-            final SoilKind kind, final double tkh, final double tkhUp, final double tkhDown, final double velocity, final double d50, final double tau) {
-        this.km = km;
-        this.wst = wst;
-        this.meanBedHeight = meanBedHeight;
-        this.flowDepth = flowDepth;
-        this.flowDepthTkh = flowDepthTkh;
-        this.discharge = discharge;
-        this.kind = kind;
-        this.tkh = tkh;
-        this.tkhUp = tkhUp;
-        this.tkhDown = tkhDown;
-        this.velocity = velocity;
-        this.d50 = d50;
-        this.tau = tau;
-    }
-
-    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;
-    }
-
-    public double getFlowDepth() {
-        return this.flowDepth;
-    }
-
-    public double getFlowDepthTkh() {
-        return this.flowDepthTkh;
-    }
-
-    public double getVelocity() {
-        return this.velocity;
-    }
-
-    public double getD50() {
-        return this.d50;
-    }
-
-    public double getTau() {
-        return this.tau;
-    }
-}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java	Thu Mar 29 15:48:17 2018 +0200
@@ -10,10 +10,7 @@
 package org.dive4elements.river.artifacts.sinfo.tkhcalculation;
 
 import org.apache.commons.lang.math.DoubleRange;
-import org.apache.commons.math.ArgumentOutsideDomainException;
-import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.model.Calculation;
-import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
@@ -26,12 +23,6 @@
 
     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;
@@ -44,56 +35,42 @@
 
     private final FlowVelocityModelKmValueFinder flowVelocitiesFinder;
 
-    public static TkhCalculator buildTkhCalculator(final boolean useTkh, final CallContext context, final Calculation problems, final String label,
+    public static TkhCalculator buildTkhCalculator(final boolean useTkh, final Calculation problems, final String label,
             final River river, final DoubleRange calcRange, final WaterlevelValuesFinder waterlevelProvider, final DischargeValuesFinder dischargeProvider,
             final BedHeightsFinder bedHeightsProvider) {
 
         if (!useTkh)
-            return new TkhCalculator(problems, label, context, null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
+            return new TkhCalculator(null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
 
         if (!dischargeProvider.isValid()) {
-            final String message = Resources.getMsg(context.getMeta(), "sinfo_calc_flow_depth.warning.missingQ", null, label);
-            problems.addProblem(message);
-            return new TkhCalculator(problems, label, context, null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
+            problems.addProblem("sinfo_calc_flow_depth.warning.missingQ", label);
+            return new TkhCalculator(null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
         }
 
+        /* access bed quality data */
         final int soundingYear = bedHeightsProvider.getInfo().getYear();
-        final BedQualityD50KmValueFinder bedMeasurementsFinder = BedQualityD50KmValueFinder.loadBedMeasurements(river, calcRange, soundingYear,
+        final BedQualityD50KmValueFinder bedMeasurementsFinder = BedQualityD50KmValueFinder.loadBedMeasurements(problems, river, calcRange, soundingYear,
                 VALID_BED_MEASUREMENT_YEARS);
+        if (bedMeasurementsFinder == null)
+            return new TkhCalculator(null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
 
-        if (bedMeasurementsFinder == null) {
-            final String message = Resources.getMsg(context.getMeta(), "sinfo_calc_flow_depth.warning.missingD50", null, label);
-            problems.addProblem(message);
-            return new TkhCalculator(problems, label, context, null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, 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 new TkhCalculator(problems, label, context, null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
-        }
+        /* access bed soil kind data */
+        final SoilKindKmValueFinder soilKindFinder = SoilKindKmValueFinder.loadValues(problems, river, calcRange);
+        if (soilKindFinder == null)
+            return new TkhCalculator(null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, 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 new TkhCalculator(problems, label, context, null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
-        }
+        final FlowVelocityModelKmValueFinder flowVelocitiesFinder = FlowVelocityModelKmValueFinder.loadValues(problems, river, calcRange, qRange);
+        if (flowVelocitiesFinder == null)
+            return new TkhCalculator(null, waterlevelProvider, dischargeProvider, bedHeightsProvider, null, null);
 
-        return new TkhCalculator(problems, label, context, bedMeasurementsFinder, waterlevelProvider, dischargeProvider, bedHeightsProvider, soilKindFinder,
+        return new TkhCalculator(bedMeasurementsFinder, waterlevelProvider, dischargeProvider, bedHeightsProvider, soilKindFinder,
                 flowVelocitiesFinder);
     }
 
-    private TkhCalculator(final Calculation problems, final String problemLabel, final CallContext context,
-            final BedQualityD50KmValueFinder bedMeasurementsFinder, final WaterlevelValuesFinder waterlevelProvider,
+    private TkhCalculator(final BedQualityD50KmValueFinder bedMeasurementsFinder, final WaterlevelValuesFinder waterlevelProvider,
             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.waterlevelProvider = waterlevelProvider;
         this.dischargeProvider = dischargeProvider;
@@ -121,32 +98,14 @@
 
     private SoilKind getSoilKind(final double km) {
 
-        try {
-            if (this.soilKindFinder == null)
-                return null;
+        if (this.soilKindFinder == null)
+            return null;
 
-            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;
-        }
+        return this.soilKindFinder.findSoilKind(km);
     }
 
     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;
-        }
+        return this.bedMeasurementsFinder.findD50(km);
     }
 
     public void calculateTkh(final double km, final SInfoResultRow row) {
@@ -178,13 +137,8 @@
             return;
         row.putValue(SInfoResultType.d50, d50);
 
-        if (!this.flowVelocitiesFinder.findKmQValues(km, discharge)) {
-            // TODO: ggf. station in Fehlermeldung?
-            final String message = Resources.getMsg(this.context.getMeta(), "sinfo_calc_flow_depth.warning.missingVelocity", null, this.problemLabel);
-            this.problems.addProblem(km, message);
-            // FIXME: cumulate problems to one message?
+        if (!this.flowVelocitiesFinder.findKmQValues(km, discharge))
             return;
-        }
 
         final double velocity = this.flowVelocitiesFinder.getFindVmainFound();
         row.putValue(SInfoResultType.velocity, velocity);
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/WaterlevelValuesFinder.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/WaterlevelValuesFinder.java	Thu Mar 29 15:48:17 2018 +0200
@@ -9,10 +9,9 @@
  */
 package org.dive4elements.river.artifacts.sinfo.tkhcalculation;
 
-import org.apache.commons.math.ArgumentOutsideDomainException;
-import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.WKms;
-import org.dive4elements.river.utils.DoubleUtil;
+import org.dive4elements.river.artifacts.sinfo.util.LinearInterpolator;
 
 /**
  * Abstraction for access to waterlevels by station.
@@ -21,23 +20,19 @@
  */
 public class WaterlevelValuesFinder {
 
-    public static WaterlevelValuesFinder fromKms(final WKms wkms) {
-        return new WaterlevelValuesFinder(wkms);
+    private static final double MAX_DSTANCE_KM = 1.0;
+
+    public static WaterlevelValuesFinder fromKms(final Calculation problems, final WKms wkms) {
+        return new WaterlevelValuesFinder(problems, wkms);
     }
 
-    private final PolynomialSplineFunction wstInterpolator;
+    private final LinearInterpolator wstInterpolator;
 
-    private WaterlevelValuesFinder(final WKms wkms) {
-        this.wstInterpolator = DoubleUtil.getLinearInterpolator(wkms.allKms(), wkms.allWs());
+    private WaterlevelValuesFinder(final Calculation problems, final WKms wkms) {
+        this.wstInterpolator = LinearInterpolator.create(problems, wkms.allKms(), wkms.allWs(), MAX_DSTANCE_KM);
     }
 
     public double getWaterlevel(final double km) {
-        try {
-            return this.wstInterpolator.value(km);
-        }
-        catch (final ArgumentOutsideDomainException e) {
-            e.printStackTrace();
-            return Double.NaN;
-        }
+        return this.wstInterpolator.value(km);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java	Thu Mar 29 15:48:17 2018 +0200
@@ -24,33 +24,33 @@
 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.BedHeightValueType;
 import org.dive4elements.river.utils.RiverUtils;
 
 /**
- * Provides bed heigts for vcarious calculations.
+ * Provides bed heights for various calculations.
  *
  * @author Gernot Belger
  */
 public final class BedHeightsFinder {
 
+    private static double MAX_DISTANCE_KM = 1;
+
     private final BedHeightInfo info;
 
     private final NavigableMap<Double, BedHeightValue> values;
 
-    private double meanBedHeight;
-
-    private double minBedHeight;
-
-    private double maxBedHeight;
+    private Calculation problems;
 
     /**
      * Create bed height finders from a collection of bed heights.
      */
-    public static Collection<BedHeightsFinder> createTkhBedHeights(final DoubleRange range, final Collection<BedHeight> bedHeights) {
+    public static Collection<BedHeightsFinder> createTkhBedHeights(final Calculation problems, final DoubleRange range,
+            final Collection<BedHeight> bedHeights) {
         final List<BedHeightsFinder> result = new ArrayList<>(bedHeights.size());
 
         for (final BedHeight bedHeight : bedHeights) {
-            final BedHeightsFinder finder = createBedHeights(bedHeight, range);
+            final BedHeightsFinder finder = createBedHeights(problems, bedHeight, range);
             result.add(finder);
         }
 
@@ -83,13 +83,18 @@
         // BedHeightFactory uses its own (direct) way of accessing the data, with its own implemented data classes.
         // return BedHeightFactory.getHeight(bedheightType, bedheightId, from, to);
 
-        final BedHeightsFinder bedHeight = bedheightId == null ? null : BedHeightsFinder.forId(bedheightId, calcRange);
-        if (bedHeight != null)
-            return bedHeight;
+        final BedHeightsFinder bedHeight = bedheightId == null ? null : BedHeightsFinder.forId(problems, bedheightId, calcRange);
+        if (bedHeight == null) {
+            problems.addProblem("sinfo.bedheightsfinder.notfound", soundingId);
+            return null;
+        }
 
-        // FIXME: 10n
-        problems.addProblem("Failed to access sounding with id '{0}'", soundingId);
-        return null;
+        if (bedHeight.isEmpty()) {
+            problems.addProblem("sinfo.bedheightsfinder.empty");
+            return null;
+        }
+
+        return bedHeight;
     }
 
     /**
@@ -97,21 +102,20 @@
      *
      * @return <code>null</code> if no bed height with the given id exists.
      */
-    private static BedHeightsFinder forId(final int id, final DoubleRange range) {
+    private static BedHeightsFinder forId(final Calculation problems, final int id, final DoubleRange range) {
 
         final BedHeight bedHeight = BedHeight.getBedHeightById(id);
         if (bedHeight == null)
             return null;
 
-        return BedHeightsFinder.createBedHeights(bedHeight, range);
+        return BedHeightsFinder.createBedHeights(problems, bedHeight, range);
     }
 
     /**
      * Create a finder for a given bed height.
      *
-     * @param range
      */
-    private static BedHeightsFinder createBedHeights(final BedHeight bedHeight, final DoubleRange range) {
+    private static BedHeightsFinder createBedHeights(final Calculation problems, final BedHeight bedHeight, final DoubleRange range) {
 
         // FIXME: sort by station, but in what direction?
         // FIXME: using river.getKmUp()?
@@ -128,12 +132,17 @@
 
         final BedHeightInfo info = BedHeightInfo.from(bedHeight);
 
-        return new BedHeightsFinder(info, values);
+        return new BedHeightsFinder(problems, info, values);
     }
 
-    private BedHeightsFinder(final BedHeightInfo info, final NavigableMap<Double, BedHeightValue> values) {
+    private BedHeightsFinder(final Calculation problems, final BedHeightInfo info, final NavigableMap<Double, BedHeightValue> values) {
         this.info = info;
         this.values = values;
+        this.problems = problems;
+    }
+
+    public boolean isEmpty() {
+        return this.values.isEmpty();
     }
 
     public BedHeightInfo getInfo() {
@@ -145,53 +154,46 @@
     }
 
     public double getMeanBedHeight(final double km) {
-        getBedHeights(km);
-        return this.meanBedHeight;
+        return interpolateBedHeights(km, BedHeightValueType.value);
     }
 
     public double getMinBedHeight(final double km) {
-        getBedHeights(km);
-        return this.minBedHeight;
+        return interpolateBedHeights(km, BedHeightValueType.min);
     }
 
     public double getMaxBedHeight(final double km) {
-        getBedHeights(km);
-        return this.maxBedHeight;
+        return interpolateBedHeights(km, BedHeightValueType.max);
     }
 
-    private boolean getBedHeights(final double km) {
-        if (this.values.containsKey(km)) {
-            this.meanBedHeight = (this.values.get(km).getHeight() != null) ? this.values.get(km).getHeight().doubleValue() : Double.NaN;
-            this.minBedHeight = (this.values.get(km).getMinHeight() != null) ? this.values.get(km).getMinHeight().doubleValue() : Double.NaN;
-            this.maxBedHeight = (this.values.get(km).getMaxHeight() != null) ? this.values.get(km).getMaxHeight().doubleValue() : Double.NaN;
-            return true;
+    private double interpolateBedHeights(final double km, final BedHeightValueType type) {
+        if (this.values.containsKey(km))
+        {
+            final Double value = type.getValue(this.values.get(km));
+            return value == null ? Double.NaN : value.doubleValue();
         }
 
         final Entry<Double, BedHeightValue> floorEntry = this.values.floorEntry(km);
         final Entry<Double, BedHeightValue> ceilingEntry = this.values.ceilingEntry(km);
 
-        if (floorEntry == null || ceilingEntry == null) {
-            this.meanBedHeight = Double.NaN;
-            this.minBedHeight = Double.NaN;
-            this.maxBedHeight = Double.NaN;
-            return false;
-        }
+        if (floorEntry == null || ceilingEntry == null)
+            return Double.NaN;
 
         final double floorKm = floorEntry.getKey().doubleValue();
         final double ceilKm = ceilingEntry.getKey().doubleValue();
 
-        // FIXME: check if we always want that...
+        /* report once if the interpolation distance exceeds 1000m */
+        if (Math.abs(floorKm - ceilKm) > MAX_DISTANCE_KM && this.problems != null) {
+            this.problems.addProblem(km, "linearInterpolator.maxdistance", MAX_DISTANCE_KM * 1000);
+            this.problems = null;
+            return Double.NaN;
+        }
 
-        this.meanBedHeight = interpolate(km, floorKm, ceilKm, floorEntry.getValue().getHeight(), ceilingEntry.getValue().getHeight());
-        this.minBedHeight = interpolate(km, floorKm, ceilKm, floorEntry.getValue().getMinHeight(), ceilingEntry.getValue().getMinHeight());
-        this.maxBedHeight = interpolate(km, floorKm, ceilKm, floorEntry.getValue().getMaxHeight(), ceilingEntry.getValue().getMaxHeight());
-        return true;
-    }
+        final Double floorHeight = type.getValue(floorEntry.getValue());
+        final Double ceilingHeight = type.getValue(ceilingEntry.getValue());
 
-    private double interpolate(final double km, final double floorKm, final double ceilKm, final Double floorHeight, final Double ceilHeight) {
-        if ((floorHeight != null) && (ceilHeight != null))
-            return Linear.linear(km, floorKm, ceilKm, floorHeight, ceilHeight);
-        else
+        if (floorHeight == null || ceilingHeight == null)
             return Double.NaN;
+
+        return Linear.linear(km, floorKm, ceilKm, floorHeight, ceilingHeight);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Thu Mar 29 15:48:17 2018 +0200
@@ -16,6 +16,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.apache.commons.lang.math.DoubleRange;
 import org.apache.commons.lang.math.NumberRange;
@@ -65,7 +66,7 @@
 
         /* find relevant bed-heights */
         final List<BedHeight> defaultBedHeights = new DefaultBedHeights(river).getBedHeights(problems);
-        final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(calcRange, defaultBedHeights);
+        final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(problems, calcRange, defaultBedHeights);
 
         /* misuse winfo-artifact to calculate waterlevels in the same way */
         final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);
@@ -85,17 +86,33 @@
         /* for each waterlevel, do a tkh calculation */
         final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange, descriptionHeader);
 
-        for (final WQKms wqKms : kms) {
+        /* determine calculation steps */
+        final Collection<Double> allStations = determineCalculationSteps(bedHeights);
 
-            final TkhCalculationResult result = calculateResult(calcRange, infoProvider, wqKms, bedHeights, descBuilder, problems);
+        for (final WQKms wqKms : kms) {
+            final TkhCalculationResult result = calculateResult(calcRange, allStations, infoProvider, wqKms, bedHeights, descBuilder, problems);
             if (result != null)
-                // FIXME: must be sorted by station!
                 results.addResult(result);
         }
 
         return new CalculationResult(results, problems);
     }
 
+    /**
+     * Calculation steps are simply the union of all stations of all involved bed-height datasets
+     */
+    private Collection<Double> determineCalculationSteps(final Collection<BedHeightsFinder> bedHeights) {
+
+        final Collection<Double> allStations = new TreeSet<>();
+
+        for (final BedHeightsFinder bedHeight : bedHeights) {
+            final Collection<Double> stations = bedHeight.getStations();
+            allStations.addAll(stations);
+        }
+
+        return allStations;
+    }
+
     private WQKms[] calculateWaterlevels(final WINFOArtifact winfo, final Calculation problems) {
 
         final CalculationResult waterlevelData = winfo.getWaterlevelData(this.context);
@@ -112,12 +129,12 @@
         return (WQKms[]) waterlevelData.getData();
     }
 
-    private TkhCalculationResult calculateResult(final DoubleRange calcRange, final RiverInfoProvider riverInfo, final WQKms wkms,
-            final Collection<BedHeightsFinder> bedHeights, final WaterlevelDescriptionBuilder descBuilder, final Calculation problems) {
+    private TkhCalculationResult calculateResult(final DoubleRange calcRange, final Collection<Double> allStations, final RiverInfoProvider riverInfo,
+            final WQKms wkms, final Collection<BedHeightsFinder> bedHeights, final WaterlevelDescriptionBuilder descBuilder, final Calculation problems) {
 
-        // FIXME: wo kommt das her? via winfo kein jahr vorhanden, oder doch? aber soll in metadaten ausgegeben werden...
+        // We have no wst year as the wst is created by a calculation; we do not need it though
         final int wspYear = -1;
-        // FIXME: richtig? vgl. WInfo?
+        // Remark: showAllGauges only true for Fixierungsanalyse, false for WInfo, so false here as well
         final boolean showAllGauges = false;
         final WaterlevelData waterlevel = new WaterlevelData(wkms, wspYear, showAllGauges);
 
@@ -137,14 +154,9 @@
 
         final Collection<SInfoResultRow> rows = new ArrayList<>();
 
-        /* using wst-kms as basis, because we know that they are generated wst's with a fixed km-step */
-        // FIXME: das führt dazu, das aktuell die Sohlhöhen beliebig linear interpolierrt werden. ist das immer richtig? z.b.
-        // bei großen abständen?
+        for (final Double stationDbl : allStations) {
 
-        final int size = wkms.size();
-        for (int i = 0; i < size; i++) {
-
-            final double station = wkms.getKm(i);
+            final double station = stationDbl;
 
             /* find the right calculator (i.e. bed height) depending on station, there should only be one maximal */
             final TkhCalculator tkhCalculator = findCalculator(calculatorsByRanges, station);
@@ -188,15 +200,15 @@
 
             final NumberRange range = new NumberRange(info.getFrom(), info.getTo());
 
-            final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(wkms);
+            final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(problems, wkms);
             final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms);
 
             /* initialize tkh calculator */
-            final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, this.context, problems, wstLabel, riverInfoProvider.getRiver(),
-                    calcRange, waterlevelProvider, dischargeProvider, bedHeightsProvider);
+            final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, problems, wstLabel, riverInfoProvider.getRiver(), calcRange,
+                    waterlevelProvider, dischargeProvider, bedHeightsProvider);
 
-            if (tkhCalculator != null) {
-                /* just ignore null ones, problems have already been updated by buildTkhCalculator() */
+            if (tkhCalculator.hasTkh()) {
+                /* just ignore invalid ones, problems have already been updated by buildTkhCalculator() */
                 calculatorByRanges.put(range, tkhCalculator);
             }
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/LinearInterpolator.java	Thu Mar 29 15:48:17 2018 +0200
@@ -0,0 +1,77 @@
+/** 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 java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import org.dive4elements.river.artifacts.math.Linear;
+import org.dive4elements.river.artifacts.model.Calculation;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * Helper for interpolating values from a piecewise linear function defined by discrete values.
+ *
+ * @author Gernot Belger
+ */
+public final class LinearInterpolator {
+
+    private final NavigableMap<Double, Double> data;
+    private final double maxDistance;
+
+    private Calculation problems;
+
+    public static LinearInterpolator create(final Calculation problems, final TDoubleArrayList xs, final TDoubleArrayList ys, final double maxDistance) {
+        if (xs.size() != ys.size())
+            throw new IllegalArgumentException("Array sizes must be equal");
+
+        if (xs.size() < 2)
+            throw new IllegalArgumentException("Array must have at least 2 values");
+
+        final NavigableMap<Double, Double> data = new TreeMap<>();
+
+        for (int i = 0; i < xs.size(); i++) {
+            final double x = xs.getQuick(i);
+            final double y = ys.getQuick(i);
+            data.put(x, y);
+        }
+
+        return new LinearInterpolator(problems, data, maxDistance);
+    }
+
+    private LinearInterpolator(final Calculation problems, final NavigableMap<Double, Double> data, final double maxDistance) {
+        this.problems = problems;
+        this.data = data;
+        this.maxDistance = maxDistance;
+    }
+
+    public double value(final double value) {
+
+        final Entry<Double, Double> floorEntry = this.data.floorEntry(value);
+        final Entry<Double, Double> ceilingEntry = this.data.ceilingEntry(value);
+
+        if (floorEntry == null || ceilingEntry == null)
+            return Double.NaN;
+
+        final double floorKey = floorEntry.getKey();
+        final double floorValue = floorEntry.getValue();
+        final double ceilingKey = ceilingEntry.getKey();
+        final double ceilingValue = ceilingEntry.getValue();
+
+        if (Math.abs(floorKey - ceilingKey) > this.maxDistance && this.problems != null) {
+            this.problems.addProblem(value, "linearInterpolator.maxdistance", this.maxDistance * 1000);
+            this.problems = null;
+        }
+
+        return Linear.linear(value, floorKey, ceilingKey, floorValue, ceilingValue);
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelData.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelData.java	Thu Mar 29 15:48:17 2018 +0200
@@ -9,8 +9,11 @@
  */
 package org.dive4elements.river.artifacts.states;
 
+import org.apache.commons.lang.math.DoubleRange;
 import org.dive4elements.river.artifacts.model.WKms;
 
+import gnu.trove.TDoubleArrayList;
+
 /**
  * Represents a waterlevel fetched with the {@link WaterlevelFetcher}.
  *
@@ -69,4 +72,22 @@
     public int getYear() {
         return this.year;
     }
-}
+
+    public boolean covers(final DoubleRange simulationRange) {
+
+        final TDoubleArrayList allKms = this.wkms.allKms();
+
+        if (allKms.isEmpty())
+            return false;
+
+        final double min = allKms.min();
+        if (min > simulationRange.getMaximumDouble())
+            return false;
+
+        final double max = allKms.max();
+        if (max < simulationRange.getMinimumDouble())
+            return false;
+
+        return true;
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelFetcher.java	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelFetcher.java	Thu Mar 29 15:48:17 2018 +0200
@@ -13,6 +13,7 @@
 import java.util.Date;
 import java.util.List;
 
+import org.apache.commons.lang.math.DoubleRange;
 import org.apache.log4j.Logger;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.D4EArtifact;
@@ -44,8 +45,12 @@
 public class WaterlevelFetcher {
     private static Logger log = Logger.getLogger(WaterlevelFetcher.class);
 
-    public WaterlevelData findWaterlevel(final CallContext context, final String mingle, final double from,
-            final double to, final Calculation problems) {
+    /**
+     * @param simulationRange
+     *            This range is used to check if the found waterlevel covers it. It is NOT used to reduce the fetched data,
+     *            because in case of waterlevels we always need to full set in order to determine the relevant gauge.
+     */
+    public WaterlevelData findWaterlevel(final CallContext context, final String mingle, final DoubleRange simulationRange, final Calculation problems) {
 
         final String[] def = mingle.split(";");
         final String uuid = def[0];
@@ -53,21 +58,25 @@
         final int idx = Integer.parseInt(def[2]);
         final String name = def[3];
         final D4EArtifact d4eArtifact = RiverUtils.getArtifact(uuid, context);
+        if (d4eArtifact == null)
+            return null;
 
-        final WaterlevelData data = fetchWaterlevelFromArtifact(context, d4eArtifact, idx, from, to);
-        if (data != null)
-            return data.withName(name);
+        final WaterlevelData data = fetchWaterlevelFromArtifact(context, d4eArtifact, idx, Double.NaN, Double.NaN);
+        if (data == null) {
+            problems.addProblem("waterlevelfetcher.missing", mingle);
+            return null;
+        }
 
-        problems.addProblem("waterlevelfetcher.missing'", mingle);
-        return null;
+        if (!data.covers(simulationRange)) {
+            problems.addProblem("waterlevelfetcher.empty", data.getName());
+            return null;
+        }
+
+        return data.withName(name);
     }
 
-    private WaterlevelData fetchWaterlevelFromArtifact(final CallContext context, final D4EArtifact d4eArtifact,
-            final int idx, final double from, final double to) {
-        if (d4eArtifact == null) {
-            log.warn("One of the artifacts (1) for diff calculation " + "could not be loaded");
-            return null;
-        }
+    private WaterlevelData fetchWaterlevelFromArtifact(final CallContext context, final D4EArtifact d4eArtifact, final int idx, final double from,
+            final double to) {
 
         if (d4eArtifact instanceof StaticWKmsArtifact) {
             return fetchStaticWKmsArtifactWaterlevel((StaticWKmsArtifact) d4eArtifact, idx, from, to);
@@ -83,7 +92,7 @@
         if (d4eArtifact instanceof FixationArtifact)
             return fetchFixationArtifactWaterlevel(context, (FixationArtifact) d4eArtifact, idx, from, to);
 
-        log.warn(String.format("Get Waterlevel from %s not implemented! )", d4eArtifact.getClass().getSimpleName()));
+        log.warn(String.format("Get Waterlevel from %s not implemented!", d4eArtifact.getClass().getSimpleName()));
         return null;
     }
 
@@ -91,15 +100,13 @@
     // this logic back to the corresponding artifacts. However this will most certainly break existing
     // artifact-serialization
 
-    private WaterlevelData fetchStaticWKmsArtifactWaterlevel(final StaticWKmsArtifact staticWKms, final int idx,
-            final double from, final double to) {
+    private WaterlevelData fetchStaticWKmsArtifactWaterlevel(final StaticWKmsArtifact staticWKms, final int idx, final double from, final double to) {
 
         log.debug("WDifferencesState obtain data from StaticWKms");
 
         final WKms wkms = staticWKms.getWKms(idx, from, to);
 
-        if (wkms != null)
-        {
+        if (wkms != null) {
             final int year = fetchStaticWKmsYear(staticWKms);
             return new WaterlevelData(wkms, year);
         }
@@ -108,15 +115,13 @@
         return null;
     }
 
-    private WaterlevelData fetchStaticWQKmsArtifactWaterlevel(final StaticWQKmsArtifact staticWKms, final double from,
-            final double to) {
+    private WaterlevelData fetchStaticWQKmsArtifactWaterlevel(final StaticWQKmsArtifact staticWKms, final double from, final double to) {
 
         log.debug("WDifferencesState obtain data from StaticWQKms");
 
         final WQKms wkms = staticWKms.getWQKms(from, to);
 
-        if (wkms != null)
-        {
+        if (wkms != null) {
             final int year = fetchStaticWKmsYear(staticWKms);
             return new WaterlevelData(wkms, year);
         }
@@ -125,8 +130,8 @@
         return null;
     }
 
-    private WaterlevelData fetchWINFOArtifactWaterlevel(final CallContext context, final WINFOArtifact flys,
-            final int idx, final double from, final double to) {
+    private WaterlevelData fetchWINFOArtifactWaterlevel(final CallContext context, final WINFOArtifact flys, final int idx, final double from,
+            final double to) {
         log.debug("Get WKms from WINFOArtifact");
 
         final WKms[] wkms = (WKms[]) flys.getWaterlevelData(context).getData();
@@ -146,8 +151,8 @@
         return new WaterlevelData(wkms[idx], year).filterByRange(from, to);
     }
 
-    private WaterlevelData fetchFixationArtifactWaterlevel(final CallContext context,
-            final FixationArtifact fixation, final int idx, final double from, final double to) {
+    private WaterlevelData fetchFixationArtifactWaterlevel(final CallContext context, final FixationArtifact fixation, final int idx, final double from,
+            final double to) {
 
         log.debug("Get WKms from FixationArtifact.");
 
--- a/artifacts/src/main/resources/messages.properties	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/resources/messages.properties	Thu Mar 29 15:48:17 2018 +0200
@@ -775,9 +775,6 @@
 sinfo_calc_flow_depth.warning.missingQ = {0}: no discharge available, calculation of transport body height not possible
 sinfo_calc_flow_depth.warning.waterlevel_discretisation  = Wasserspiegel {0}: r\u00e4umliche Aufl\u00f6sung betr\u00e4gt mehr als 1000m
 sinfo_calc_flow_depth.warning.year_difference = {0}: Sie verwenden als Differenzenpaar eine Wasserspiegellage aus dem Jahr {1} und eine Peilung aus dem Jahr {2}. Dies kann zu unplausiblen Werten f\u00fchren.
-sinfo_calc_flow_depth.warning.missingSoilKind = {0}: no soil kind available, calculation of transport body height not possible
-sinfo_calc_flow_depth.warning.missingD50 = {0}: no D50 available, calculation of transport body height not possible
-sinfo_calc_flow_depth.warning.missingVelocity = {0}: no flow velocities available, calculation of transport body height not possible
 
 sinfo.bedheightsfinder.badrange = Invalid range for bed heights {0}.
 sinfo.bedheightsfinder.overlappingrange = Range of bed height {0} overlaps with other ranges.
@@ -785,6 +782,8 @@
 sinfo.bedheightsfinder.wrongriver = Bed heights {0} does not belong to river {1}
 sinfo.bedheightsfinder.configfile.missingriver = River not defined in config file '{0}': {1}
 sinfo.bedheightsfinder.configfile.loaderror = Failed to load config file '{0}': {1}
+sinfo.bedheightsfinder.notfound = Failed to access sounding with id '{0}'
+sinfo.bedheightsfinder.empty = The bed heights do not contain any values for the selected calculation range
 
 sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung
 sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
@@ -935,6 +934,19 @@
 sinfo.export.flow_depth_minmax.csv.header.max = Maximale Flie\u00dftiefe
 
 waterlevelfetcher.missing = Failed to access waterlevel with id '{0}'
+waterlevelfetcher.empty = The water level {0} does not contain any values for the selected calculation range
+
+bedqualityd50kmvaluefinder.error = Failed to access D50 data, calculation of transport body height not possible: {0}
+bedqualityd50kmvaluefinder.empty = The bed quality (D50) does not contain any values for the selected calculation range and sounding year {0}
+bedqualityd50kmvaluefinder.missing = Bed quality (D50) not available
+
+soilkindkmvaluefinder.empty = no soil kind available for the selected calculation range, calculation of transport body height not possible
+soilkindkmvaluefinder.missing = no soil kind available
+
+flowvelocitymodelkmvaluefinder.empty = no flow velocities available for the selected calculation range, calculation of transport body height not possible
+flowvelocitymodelkmvaluefinder.missing = no flow velocities available, calculation of transport body height not possible
+
+linearInterpolator.maxdistance = spatial discretisation exceeds {0}m, interpolation does not take place
 
 sinfo.export.csv.meta.header.sounding.current = ##METADATEN PEILUNG aktuell
 sinfo.export.csv.meta.header.sounding.historical = ##METADATEN PEILUNG historisch
--- a/artifacts/src/main/resources/messages_de.properties	Wed Mar 28 17:04:20 2018 +0200
+++ b/artifacts/src/main/resources/messages_de.properties	Thu Mar 29 15:48:17 2018 +0200
@@ -775,9 +775,6 @@
 sinfo_calc_flow_depth.warning.missingQ = {0}: keine Abflussdaten vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
 sinfo_calc_flow_depth.warning.waterlevel_discretisation  = Wasserspiegel {0}: r\u00e4umliche Aufl\u00f6sung betr\u00e4gt mehr als 1000m
 sinfo_calc_flow_depth.warning.year_difference = {0}: Sie verwenden als Differenzenpaar eine Wasserspiegellage aus dem Jahr {1} und eine Peilung aus dem Jahr {2}. Dies kann zu unplausiblen Werten f\u00fchren.
-sinfo_calc_flow_depth.warning.missingSoilKind = {0}: keine Sohlart vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
-sinfo_calc_flow_depth.warning.missingD50 = {0}: kein D50 vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
-sinfo_calc_flow_depth.warning.missingVelocity = {0}: keine Flie\u00dfgeschwindigkeiten vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
 
 sinfo.bedheightsfinder.badrange = Ung\u00fcltige -range- f\u00fcr Sohlh\u00f6hen {0}.
 sinfo.bedheightsfinder.overlappingrange = -Range- der Sohlh\u00f6hen {0} \u00fcberlappt andere Sohlh\u00f6hen.
@@ -785,6 +782,8 @@
 sinfo.bedheightsfinder.wrongriver = Sohlh\u00f6he {0} geh\u00f6rt nicht zum Gew\u00e4sser {1}
 sinfo.bedheightsfinder.configfile.missingriver = Gew\u00e4sser  {1} ist in Konfigurationsdatei {0} nicht definiert.
 sinfo.bedheightsfinder.configfile.loaderror = Fehler beim Laden der Konfigurationsdatei '{0}': {1}
+sinfo.bedheightsfinder.notfound = Keine Sohlh\u00f6he mit id '{0}' vorhanden
+sinfo.bedheightsfinder.empty = Die Sohlh\u00f6hen enthalten keine Werte f\u00fcr die gew\u00e4hlte Berechnungsstrecke 
 
 sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung
 sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
@@ -935,6 +934,19 @@
 sinfo.export.flow_depth_minmax.csv.header.max = Maximale Flie\u00dftiefe
 
 waterlevelfetcher.missing = Fehler beim Zugriff auf Wasserspiegel mit id '{0}'
+waterlevelfetcher.empty = Der Wasserspiegel '{0}' enth\u00e4lt keine Werte f\u00fcr die gew\u00e4hlte Berechnungsstrecke
+
+bedqualityd50kmvaluefinder.error = Fehler beim Zugriff auf die D50 Daten, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich: {0}
+bedqualityd50kmvaluefinder.empty = F\u00fcr das Jahr {0} liegen keine D50-Korndurchmesser f\u00fcr die gew\u00e4hlte Berechnungsstrecke vor
+bedqualityd50kmvaluefinder.missing = Keine D50-Korndurchmesser vorhanden
+
+soilkindkmvaluefinder.empty = Keine Sohlart f\u00fcr die gew\u00e4hlte Berechnungsstrecke vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
+soilkindkmvaluefinder.missing = keine Sohlart vorhanden
+
+flowvelocitymodelkmvaluefinder.empty = Keine Flie\u00dfgeschwindigkeiten f\u00fcr die gew\u00e4hlte Berechnungsstrecke vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
+flowvelocitymodelkmvaluefinder.missing = Keine Flie\u00dfgeschwindigkeiten vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich
+
+linearInterpolator.maxdistance = R\u00e4umliche Aufl\u00f6sung gr\u00f6\u00dfer als {0}m, es findet keine Interpolation statt
 
 sinfo.export.csv.meta.header.sounding.current = ##METADATEN PEILUNG aktuell
 sinfo.export.csv.meta.header.sounding.historical = ##METADATEN PEILUNG historisch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/backend/src/main/java/org/dive4elements/river/model/BedHeightValueType.java	Thu Mar 29 15:48:17 2018 +0200
@@ -0,0 +1,39 @@
+/** 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.model;
+
+/**
+ * Enumerations that allows to access min/max/mean value of {@link BedHeight} in the same way.
+ *
+ * @author Gernot Belger
+ *
+ */
+public enum BedHeightValueType {
+    min {
+        @Override
+        public Double getValue(final BedHeightValue bedheightValue) {
+            return bedheightValue.getMinHeight();
+        }
+    },
+    max {
+        @Override
+        public Double getValue(final BedHeightValue bedheightValue) {
+            return bedheightValue.getMaxHeight();
+        }
+    },
+    value {
+        @Override
+        public Double getValue(final BedHeightValue bedheightValue) {
+            return bedheightValue.getHeight();
+        }
+    };
+
+    public abstract Double getValue(final BedHeightValue bedheightValue);
+}
\ No newline at end of file

http://dive4elements.wald.intevation.org