changeset 8942:11bf13cf0463

Minor changes to tkh calculation. Loading default bed heights form config file.
author gernotbelger
date Fri, 09 Mar 2018 18:47:06 +0100
parents a9950a3a71e5
children 4a6b6a3c279c
files artifacts/doc/conf/sinfo_tkh_bedheights.properties artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/DefaultBedHeights.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/BedHeightInfo.java artifacts/src/main/resources/messages.properties artifacts/src/main/resources/messages_de.properties backend/src/main/java/org/dive4elements/river/model/BedHeight.java
diffstat 8 files changed, 271 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/doc/conf/sinfo_tkh_bedheights.properties	Fri Mar 09 18:47:06 2018 +0100
@@ -0,0 +1,9 @@
+# Konfigurationsdatei f\u00fcr die in der Berechnungsart SINFO-Transportk\u00f6rperh\u00f6hen zu verwendenden Sohlh\u00f6hen.
+# Die Datei ist im Charset 'ISO-8859-1' zu encodieren.
+# Jede Zeile entspricht: <gew\u00e4ssername> = <sohlh\u00f6he1>, <Sohlh\u00f6he2>, ...
+# Alle Daten beziehen sich auf die FLYS Datenbank (d4e Datenbank)
+# der <gew\u00e4ssername> entspricht dem Feld 'name' der Tabelle 'rivers'
+# die Sohlh\u00f6hen dem Feld 'description' aus der Tabelle 'bed_heights'
+# Leerzeichen im Ge\u00e4ssernamen m\u00fcssen mit u0020 kodiert werden.
+Beispielfluss=DGM-2004_Epoche-2-SOBEK  
+Beispiel\u0020fluss=DGM-2004_Epoche-2-SOBEK
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java	Wed Mar 07 17:36:04 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java	Fri Mar 09 18:47:06 2018 +0100
@@ -11,7 +11,6 @@
 
 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;
@@ -19,11 +18,9 @@
 
 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.
@@ -37,23 +34,14 @@
     private final NavigableMap<Double, BedHeightValue> values;
 
     /**
-     * Create specific bed heights used in tkh-calculation
-     *
-     * @param problems
+     * Create bed height finders from a collection of bed heights.
      */
-    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
-
+    public static Collection<BedHeightsFinder> createTkhBedHeights(final DoubleRange range, final Collection<BedHeight> bedHeights) {
         final List<BedHeightsFinder> result = new ArrayList<>(bedHeights.size());
 
         for (final BedHeight bedHeight : bedHeights) {
-            result.add(createBedHeights(bedHeight, range));
+            final BedHeightsFinder finder = createBedHeights(bedHeight, range);
+            result.add(finder);
         }
 
         return result;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/DefaultBedHeights.java	Fri Mar 09 18:47:06 2018 +0100
@@ -0,0 +1,146 @@
+/** 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.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.NumberRange;
+import org.dive4elements.artifacts.common.utils.Config;
+import org.dive4elements.river.artifacts.model.Calculation;
+import org.dive4elements.river.model.BedHeight;
+import org.dive4elements.river.model.Range;
+import org.dive4elements.river.model.River;
+
+/**
+ * This class knows how to find the default bed heights defined for tkh calculation
+ *
+ * @author Gernot Belger
+ */
+final class DefaultBedHeights {
+    private static final String CONFIG_FILE = "sinfo_tkh_bedheights.properties";
+    private final River river;
+
+    public DefaultBedHeights(final River river) {
+        this.river = river;
+    }
+
+    public List<BedHeight> getBedHeights(final Calculation problems) {
+        final Collection<String> bedHeightNames = loadBedHeightDefaultsForRiver(this.river, problems);
+
+        final List<BedHeight> defaultBedHeights = loadBedHeightsByName(this.river, bedHeightNames, problems);
+
+        final List<BedHeight> validBedHeights = new ArrayList<>(defaultBedHeights.size());
+
+        // REMARK: check for bad ranges because db schema allow for incomplete ranges, and ignore if this is the case
+        for (final BedHeight bedHeight : defaultBedHeights) {
+
+            final Range range = bedHeight.getRange();
+
+            if (range.getA() == null || range.getB() == null)
+                problems.addProblem("sinfo.bedheightsfinder.badrange", bedHeight.getDescription());
+            else
+                validBedHeights.add(bedHeight);
+        }
+
+        /* check for overlapping ranges, N2-search, but we expect only have small numbers of bed heights */
+        final List<BedHeight> result = new ArrayList<>(defaultBedHeights.size());
+
+        for (int i = 0; i < defaultBedHeights.size(); i++) {
+            final BedHeight bedHeight = validBedHeights.get(i);
+
+            final Range range = bedHeight.getRange();
+            final NumberRange bedRange = new NumberRange(range.getA(), range.getB());
+
+            if (overlapsRange(bedRange, validBedHeights, i + 1)) {
+                problems.addProblem("sinfo.bedheightsfinder.overlappingrange", bedHeight.getDescription());
+            } else
+                result.add(bedHeight);
+        }
+
+        return result;
+    }
+
+    private static Collection<String> loadBedHeightDefaultsForRiver(final River river, final Calculation problems) {
+        final File configDir = Config.getConfigDirectory();
+        final File configFile = new File(configDir, CONFIG_FILE);
+
+        final Properties properties = new Properties();
+        try (final InputStreamReader reader = new InputStreamReader(Files.newInputStream(configFile.toPath()), StandardCharsets.ISO_8859_1)) {
+            properties.load(reader);
+
+            final String value = properties.getProperty(river.getName());
+            final String[] split = StringUtils.split(StringUtils.trim(value), ',');
+            if (ArrayUtils.isEmpty(split)) {
+                problems.addProblem("sinfo.bedheightsfinder.configfile.missingriver", CONFIG_FILE, river.getName());
+                return Collections.emptyList();
+            }
+
+            return Arrays.asList(split);
+        }
+        catch (final IOException e) {
+            e.printStackTrace();
+            problems.addProblem("sinfo.bedheightsfinder.configfile.loaderror", CONFIG_FILE, e.getMessage());
+            return Collections.emptyList();
+        }
+    }
+
+    private static List<BedHeight> loadBedHeightsByName(final River shouldBeRiver, final Collection<String> bedHeightNames, final Calculation problems) {
+
+        final List<BedHeight> bedHeights = new ArrayList<>(bedHeightNames.size());
+
+        for (final String name : bedHeightNames) {
+            try {
+                final BedHeight bedHeight = BedHeight.getBedHeightByDescription(name);
+                if (bedHeight == null)
+                    problems.addProblem("sinfo.bedheightsfinder.missingdescription", name);
+                else {
+                    final River river = bedHeight.getRiver();
+                    if (!shouldBeRiver.getId().equals(river.getId()))
+                        problems.addProblem("sinfo.bedheightsfinder.wrongriver", name, shouldBeRiver.getName());
+                    else
+                        bedHeights.add(bedHeight);
+                }
+            }
+            catch (final Exception e) {
+                e.printStackTrace();
+                problems.addProblem("sinfo.bedheightsfinder.missingdescription", name);
+            }
+        }
+
+        return bedHeights;
+    }
+
+    private static boolean overlapsRange(final NumberRange bedRange, final List<BedHeight> result, final int startIndex) {
+
+        for (int i = startIndex; i < result.size(); i++) {
+
+            final BedHeight compareBed = result.get(i);
+            final Range range = compareBed.getRange();
+            final NumberRange compareRange = new NumberRange(range.getA(), range.getB());
+
+            if (compareRange.overlapsRange(bedRange))
+                return true;
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Wed Mar 07 17:36:04 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Fri Mar 09 18:47:06 2018 +0100
@@ -11,9 +11,14 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.commons.lang.math.DoubleRange;
+import org.apache.commons.lang.math.NumberRange;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.WINFOArtifact;
 import org.dive4elements.river.artifacts.model.Calculation;
@@ -26,11 +31,13 @@
 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.BedHeightInfo;
 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.exports.WaterlevelDescriptionBuilder;
+import org.dive4elements.river.model.BedHeight;
 import org.dive4elements.river.model.River;
 
 /**
@@ -55,7 +62,8 @@
         final Calculation problems = new Calculation();
 
         /* find relevant bed-heights */
-        final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(river, problems, calcRange);
+        final List<BedHeight> defaultBedHeights = new DefaultBedHeights(river).getBedHeights(problems);
+        final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(calcRange, defaultBedHeights);
 
         /* misuse winfo-artifact to calculate waterlevels in the same way */
         final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);
@@ -118,46 +126,77 @@
 
         final WstInfo wstInfo = new WstInfo(wstLabel, wspYear, riverInfoProvider.getReferenceGauge());
 
+        /* build tkh calculators per bedheight */
+        final Map<NumberRange, TkhCalculator> calculatorsByRanges = buildCalculators(calcRange, wkms, bedHeights, problems, riverInfoProvider, wstLabel);
+        if (calculatorsByRanges.isEmpty()) {
+            /* there should already be some problems, so just abort */
+            return null;
+        }
+
         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
-         */
+        /* 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);
+
+            /* find the right calculator (i.e. bedheigh) depending on station, there should only be one maximal */
+            final TkhCalculator tkhCalculator = findCalculator(calculatorsByRanges, station);
+            if (tkhCalculator == null)
+                continue;
+
+            final Tkh tkh = tkhCalculator.getTkh(station, wst);
+
+            final String description = descBuilder.getDesc(wkms);
+            final String gaugeLabel = riverInfoProvider.findGauge(station);
+            final String location = riverInfoProvider.getLocation(station);
+
+            rows.add(new TkhResultRow(tkh, description, gaugeLabel, location));
+        }
+
+        return new TkhCalculationResult(wstLabel, wstInfo, true, rows);
+    }
+
+    private TkhCalculator findCalculator(final Map<NumberRange, TkhCalculator> calculators, final double station) {
+
+        // REMAKR: linear search at this point, put we expect the number of bed heights to be very small (1-2 items)
+        final Set<Entry<NumberRange, TkhCalculator>> x = calculators.entrySet();
+        for (final Entry<NumberRange, TkhCalculator> entry : x) {
+            final NumberRange range = entry.getKey();
+            // FIXME: check if we need comparison with a tolerance
+            if (range.containsDouble(station))
+                return entry.getValue();
+        }
+
+        return null;
+    }
+
+    private Map<NumberRange, TkhCalculator> buildCalculators(final DoubleRange calcRange, final WQKms wkms, final Collection<BedHeightsFinder> bedHeights,
+            final Calculation problems, final RiverInfoProvider riverInfoProvider, final String wstLabel) {
+        final Map<NumberRange, TkhCalculator> calculatorByRanges = new HashMap<>();
         for (final BedHeightsFinder bedHeightsProvider : bedHeights) {
 
+            final BedHeightInfo info = bedHeightsProvider.getInfo();
+
+            final NumberRange range = new NumberRange(info.getFrom(), info.getTo());
+
             final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms);
 
             /* initialize tkh calculator */
             final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, this.context, problems, wstLabel, 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?
+                    calcRange, dischargeProvider, bedHeightsProvider);
 
-            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 description = descBuilder.getDesc(wkms);
-                final String gaugeLabel = riverInfoProvider.findGauge(station);
-                final String location = riverInfoProvider.getLocation(station);
-
-                rows.add(new TkhResultRow(tkh, description, gaugeLabel, location));
+            if (tkhCalculator != null) {
+                /* just ignore null ones, problems have already been updated by buildTkhCalculator() */
+                calculatorByRanges.put(range, tkhCalculator);
             }
         }
 
-        return new TkhCalculationResult(wstLabel, wstInfo, true, rows);
+        return calculatorByRanges;
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/BedHeightInfo.java	Wed Mar 07 17:36:04 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/BedHeightInfo.java	Fri Mar 09 18:47:06 2018 +0100
@@ -10,6 +10,7 @@
 package org.dive4elements.river.artifacts.sinfo.util;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 import org.dive4elements.river.model.BedHeight;
 
@@ -36,6 +37,9 @@
 
     private final String oldElevationModelUnit;
 
+    private final BigDecimal from;
+
+    private final BigDecimal to;
 
     public static BedHeightInfo from(final BedHeight bedHeight) {
         return new BedHeightInfo(bedHeight);
@@ -49,6 +53,9 @@
         this.locationSystem = bedHeight.getLocationSystem().getName();
         this.curElevationModelUnit = bedHeight.getCurElevationModel().getUnit().getName();
         this.oldElevationModelUnit = bedHeight.getCurElevationModel().getUnit().getName();
+
+        this.from = bedHeight.getRange().getA();
+        this.to = bedHeight.getRange().getB();
     }
 
     public Integer getYear() {
@@ -78,4 +85,12 @@
     public String getOldElevationModelUnit() {
         return this.oldElevationModelUnit;
     }
-}
+
+    public BigDecimal getFrom() {
+        return this.from;
+    }
+
+    public BigDecimal getTo() {
+        return this.to;
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/resources/messages.properties	Wed Mar 07 17:36:04 2018 +0100
+++ b/artifacts/src/main/resources/messages.properties	Fri Mar 09 18:47:06 2018 +0100
@@ -779,6 +779,13 @@
 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.
+sinfo.bedheightsfinder.missingdescription = No bed heights found with description = {0}
+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_calc_flow_depth_development=Flie\u00dftiefenentwicklung
 sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
 sinfo_calc_grounding=Grundber\u00fchrungen
--- a/artifacts/src/main/resources/messages_de.properties	Wed Mar 07 17:36:04 2018 +0100
+++ b/artifacts/src/main/resources/messages_de.properties	Fri Mar 09 18:47:06 2018 +0100
@@ -779,6 +779,13 @@
 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.
+sinfo.bedheightsfinder.missingdescription = Sohlh\u00f6he mit -description- {0} nicht vorhanden
+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_calc_flow_depth_development=Flie\u00dftiefenentwicklung
 sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
 sinfo_calc_grounding=Grundber\u00fchrungen
--- a/backend/src/main/java/org/dive4elements/river/model/BedHeight.java	Wed Mar 07 17:36:04 2018 +0100
+++ b/backend/src/main/java/org/dive4elements/river/model/BedHeight.java	Fri Mar 09 18:47:06 2018 +0100
@@ -262,5 +262,17 @@
 
         return singles != null ? singles.get(0) : null;
     }
+
+    public static BedHeight getBedHeightByDescription(final String description) {
+        
+        final Session session = SessionHolder.HOLDER.get();
+        
+        final Query query = session.createQuery("from BedHeight where description=:description");
+        query.setParameter("description", description);
+        
+        final List<BedHeight> singles = query.list();
+        
+        return singles != null ? singles.get(0) : null;        
+    }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
\ No newline at end of file

http://dive4elements.wald.intevation.org