changeset 9613:f2473dc34535

Nachtrag Pos. 19: Q calculation with historical discharge tables instead of master discharge table
author mschaefer
date Tue, 08 Oct 2019 15:03:24 +0200
parents 6b2496d71936
children ca492336570b
files artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeDischargeValuesFinder.java backend/src/main/java/org/dive4elements/river/model/DischargeTable.java
diffstat 3 files changed, 150 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java	Tue Feb 12 14:08:16 2019 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java	Tue Oct 08 15:03:24 2019 +0200
@@ -11,10 +11,13 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.NavigableSet;
+import java.util.TreeMap;
 import java.util.TreeSet;
 
 import org.apache.commons.lang.math.DoubleRange;
@@ -32,6 +35,8 @@
 import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.backend.utils.DateUtil;
+import org.dive4elements.river.model.DischargeTable;
+import org.dive4elements.river.model.Gauge;
 import org.dive4elements.river.model.MainValueType.MainValueTypeKey;
 import org.dive4elements.river.model.River;
 import org.dive4elements.river.model.sinfo.CollisionAggregateValue;
@@ -43,6 +48,12 @@
 
     private final CallContext context;
 
+    private CollisionAccess access;
+    private River river;
+    private List<DateRange> years;
+    private Calculation problems;
+    private int qfinderProblemCount;
+
     public CollisionCalculation(final CallContext context) {
         this.context = context;
     }
@@ -52,50 +63,49 @@
         final String user = CalculationUtils.findArtifactUser(this.context, sinfo);
 
         // access input data
-        final CollisionAccess access = new CollisionAccess(sinfo);
-        final River river = access.getRiver();
-        final RiverInfo riverInfo = new RiverInfo(river);
-
-        final DoubleRange calcRange = access.getRange();
+        this.access = new CollisionAccess(sinfo);
+        this.river = this.access.getRiver();
+        final RiverInfo riverInfo = new RiverInfo(this.river);
+        final DoubleRange calcRange = this.access.getRange();
 
         // calculate results for each year or epoch
-        final Calculation problems = new Calculation();
-
+        this.problems = new Calculation();
+        this.qfinderProblemCount = 0;
         final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name());
 
-        final CollisionCalculationResults results = new CollisionCalculationResults(calcModeLabel, user, riverInfo, calcRange, access.getYearsHeader());
+        final CollisionCalculationResults results = new CollisionCalculationResults(calcModeLabel, user, riverInfo, calcRange, this.access.getYearsHeader());
 
         final Collection<ResultRow> overViewRows = new ArrayList<>();
 
-        final List<DateRange> years = new ArrayList<>();
+        this.years = new ArrayList<>();
         final NavigableSet<Integer> detailYears = new TreeSet<>();
-        if (access.getYears() != null) {
-            for (final int year : access.getYears()) {
-                calculateOverview(overViewRows, river, access.getLowerKm(), access.getUpperKm(), year, year, false);
-                years.add(new DateRange(DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year)));
+        if (this.access.getYears() != null) {
+            for (final int year : this.access.getYears()) {
+                calculateOverview(overViewRows, year, year, false);
+                this.years.add(new DateRange(DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year)));
                 detailYears.add(Integer.valueOf(year));
             }
         } else {
-            for (final DateRange dr : access.getEpochs()) {
-                calculateOverview(overViewRows, river, access.getLowerKm(), access.getUpperKm(), dr.getFromYear(), dr.getToYear(), true);
-                years.add(dr);
+            for (final DateRange dr : this.access.getEpochs()) {
+                calculateOverview(overViewRows, dr.getFromYear(), dr.getToYear(), true);
+                this.years.add(dr);
                 detailYearsAdd(detailYears, dr);
             }
         }
-        final CollisionCalcOverviewResult overviewResult = new CollisionCalcOverviewResult(access.getYearsHeader(), (access.getYears() == null), years,
-                overViewRows);
-        results.addResult(overviewResult, problems);
+        final CollisionCalcOverviewResult overviewResult = new CollisionCalcOverviewResult(this.access.getYearsHeader(), (this.access.getYears() == null),
+                this.years, overViewRows);
+        results.addResult(overviewResult, this.problems);
 
         // calculate secondary results for each year
-        final Map<String, GaugeDischargeValuesFinder> qFinders = new HashMap<>();
+        final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders = new HashMap<>();
         final Map<String, GaugeMainValueFinder> zoneFinders = new HashMap<>();
         final Collection<ResultRow> detailsRows = new ArrayList<>();
         for (final Integer year : detailYears)
-            calculateDetails(detailsRows, river, access.getLowerKm(), access.getUpperKm(), year, qFinders, zoneFinders, problems);
+            calculateDetails(detailsRows, year, qFinders, zoneFinders);
         final CollisionCalcDetailResult detailResult = new CollisionCalcDetailResult("Details", detailsRows);
-        results.addResult(detailResult, problems);
+        results.addResult(detailResult, this.problems);
 
-        return new CalculationResult(results, problems);
+        return new CalculationResult(results, this.problems);
     }
 
     /**
@@ -110,9 +120,9 @@
      * Calculates the collision counts for a km range of a river and a year or year range (epoch),
      * and adds them to a ResultRow collection
      */
-    private void calculateOverview(final Collection<ResultRow> rows, final River river, final double fromKm, final double toKm, final int fromYear,
-            final int toYear, final boolean isEpoch) {
-        for (final CollisionAggregateValue aggregate : CollisionAggregateValue.getValuesByKm(river, fromKm, toKm, fromYear, toYear)) {
+    private void calculateOverview(final Collection<ResultRow> rows, final int fromYear, final int toYear, final boolean isEpoch) {
+        for (final CollisionAggregateValue aggregate : CollisionAggregateValue.getValuesByKm(this.river, this.access.getLowerKm(), this.access.getUpperKm(),
+                fromYear, toYear)) {
             rows.add(ResultRow.create().putValue(GeneralResultType.station, aggregate.getStation())
                     .putValue(SInfoResultType.years, yearsToString(isEpoch, fromYear, toYear)).putValue(SInfoResultType.collisionCount, aggregate.getCount()));
         }
@@ -135,15 +145,15 @@
     /**
      * Calculates the collision details for a km range of a river and a year, and adds them to a ResultRow collection
      */
-    private void calculateDetails(final Collection<ResultRow> rows, final River river, final double fromKm, final double toKm, final int year,
-            final Map<String, GaugeDischargeValuesFinder> qFinders, final Map<String, GaugeMainValueFinder> zoneFinders, final Calculation problems) {
+    private void calculateDetails(final Collection<ResultRow> rows, final int year, final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders,
+            final Map<String, GaugeMainValueFinder> zoneFinders) {
 
-        for (final CollisionValue collision : CollisionValue.getValues(river, fromKm, toKm, DateUtil.getStartDateFromYear(year),
-                DateUtil.getEndDateFromYear(year))) {
+        for (final CollisionValue collision : CollisionValue.getValues(this.river, this.access.getLowerKm(), this.access.getUpperKm(),
+                DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year))) {
             final String gaugeName = collision.getGaugeName();
-            final double q = getQ(qFinders, gaugeName, collision.getGaugeW().doubleValue(), river, problems);
+            final double q = getQ(qFinders, gaugeName, collision.getGaugeW().doubleValue(), collision.getEventDate());
             final double qOut = Double.isInfinite(q) ? Double.NaN : q;
-            final String zone = getZone(zoneFinders, gaugeName, q, river, problems);
+            final String zone = getZone(zoneFinders, gaugeName, q);
             rows.add(ResultRow.create().putValue(GeneralResultType.station, collision.getStation())
                     .putValue(GeneralResultType.dateShort, collision.getEventDate()).putValue(SInfoResultType.collisionGaugeW, collision.getGaugeW())
                     .putValue(GeneralResultType.gaugeLabel, gaugeName).putValue(SInfoResultType.dischargeLong, qOut)
@@ -154,27 +164,62 @@
     /**
      * Gets the discharge of a gauge and a W
      */
-    private double getQ(final Map<String, GaugeDischargeValuesFinder> qFinders, final String gaugeName, final double w, final River river,
-            final Calculation problems) {
+    private double getQ(final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders, final String gaugeName, final double w, final Date when) {
         // Find the gauge and load its discharge table, if not already in the map
         final String gnKey = gaugeName.toLowerCase();
         if (!qFinders.containsKey(gnKey))
-            qFinders.put(gnKey, GaugeDischargeValuesFinder.loadValues(river, gaugeName, problems));
+            addQFinders(qFinders, gaugeName);
         // Interpolate W.
-        if (qFinders.get(gnKey) == null)
+        final GaugeDischargeValuesFinder qFinder = getQFinder(qFinders.get(gnKey), when);
+        if (qFinder == null) {
+            this.qfinderProblemCount++;
+            if (this.qfinderProblemCount == 1)
+                this.problems.addProblem("gauge_discharge_table.missing", gaugeName);
             return Double.NaN;
-        return qFinders.get(gnKey).getDischarge(w);
+        }
+        return qFinder.getDischarge(w);
+    }
+
+    /**
+     * Add the discharge finders for a gauge and the active time period
+     */
+    private void addQFinders(final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders, final String gaugeName) {
+        final String gnKey = gaugeName.toLowerCase();
+        final Gauge gauge = this.river.determineGaugeByName(gaugeName);
+        if (gauge == null) {
+            qFinders.put(gnKey, new TreeMap<Date, GaugeDischargeValuesFinder>());
+            return;
+        }
+        final List<DischargeTable> qtables = DischargeTable.fetchHistoricalDischargeTables(gauge, this.years.get(0).getFrom(),
+                this.years.get(this.years.size() - 1).getTo());
+        qFinders.put(gnKey, new TreeMap<Date, GaugeDischargeValuesFinder>());
+        for (final DischargeTable qtable : qtables)
+            qFinders.get(gnKey).put(qtable.getTimeInterval().getStartTime(),
+                    GaugeDischargeValuesFinder.loadValues(qtable, this.river, gaugeName, this.problems));
+    }
+
+    /**
+     * Searches a q values finder map for a date time
+     */
+    private GaugeDischargeValuesFinder getQFinder(final TreeMap<Date, GaugeDischargeValuesFinder> qFinders, final Date when) {
+        if (qFinders.containsKey(when))
+            return qFinders.get(when);
+        final Entry<Date, GaugeDischargeValuesFinder> found = qFinders.floorEntry(when);
+        if (found == null)
+            return null;
+        if ((found.getValue().getEndTime() != null) && found.getValue().getEndTime().before(when))
+            return null;
+        return found.getValue();
     }
 
     /**
      * Gets the main value zone name of a gauge and a Q
      */
-    private String getZone(final Map<String, GaugeMainValueFinder> zoneFinders, final String gaugeName, final double q, final River river,
-            final Calculation problems) {
+    private String getZone(final Map<String, GaugeMainValueFinder> zoneFinders, final String gaugeName, final double q) {
         // Find the gauge and load its main value list, if not already in the map
         final String gnKey = gaugeName.toLowerCase();
         if (!zoneFinders.containsKey(gnKey))
-            zoneFinders.put(gnKey, GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, river, gaugeName, problems, "GLQ"));
+            zoneFinders.put(gnKey, GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, this.river, gaugeName, this.problems, "GLQ"));
         // Build the zone name
         if (zoneFinders.get(gnKey) == null)
             return "";
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeDischargeValuesFinder.java	Tue Feb 12 14:08:16 2019 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeDischargeValuesFinder.java	Tue Oct 08 15:03:24 2019 +0200
@@ -9,6 +9,8 @@
  */
 package org.dive4elements.river.artifacts.sinfo.common;
 
+import java.util.Date;
+
 import org.apache.commons.lang.math.DoubleRange;
 import org.apache.commons.math.FunctionEvaluationException;
 import org.apache.commons.math.analysis.UnivariateRealFunction;
@@ -41,6 +43,10 @@
 
     private final DoubleRange wRange;
 
+    private final Date startTime;
+
+    private final Date endTime;
+
 
     /***** CONSTRUCTORS *****/
 
@@ -48,6 +54,8 @@
         // Load W-Q-values from database
         this.gauge = gauge;
         this.problems = problems;
+        this.startTime = dischargeTable.getTimeInterval().getStartTime();
+        this.endTime = dischargeTable.getTimeInterval().getStopTime();
         final TDoubleArrayList ws = new TDoubleArrayList();
         final TDoubleArrayList qs = new TDoubleArrayList();
         for (final DischargeTableValue v : DischargeTable.fetchValuesSortedByW(dischargeTable)) {
@@ -74,7 +82,7 @@
     /***** METHODS *****/
 
     /**
-     * Loads the the main discharge table of a gauge ({gauge}.at)
+     * Loads the main discharge table of a gauge ({gauge}.at)
      *
      * @return The discharge table values finder of the gauge, or null
      */
@@ -83,7 +91,7 @@
     }
 
     /**
-     * Loads the the main discharge table of a river's gauge ({gauge}.at)
+     * Loads the main discharge table of a river's gauge ({gauge}.at)
      *
      * @return The discharge table values finder of the gauge, or null
      */
@@ -92,8 +100,21 @@
         return loadValues(gauge, gaugeName, problems);
     }
 
+    /**
+     * Loads a discharge table of a river's gauge (*.at)
+     *
+     * @return The discharge table values finder, or null
+     */
+    public static GaugeDischargeValuesFinder loadValues(final DischargeTable table, final River river, final String gaugeName, final Calculation problems) {
+        final Gauge gauge = river.determineGaugeByName(gaugeName);
+        return loadValues(table, gauge, gaugeName, problems);
+    }
+
     private static GaugeDischargeValuesFinder loadValues(final Gauge gauge, final String gaugeName, final Calculation problems) {
-        final DischargeTable table = (gauge != null) ? gauge.fetchMasterDischargeTable() : null;
+        return loadValues((gauge != null) ? gauge.fetchMasterDischargeTable() : null, gauge, gaugeName, problems);
+    }
+
+    private static GaugeDischargeValuesFinder loadValues(final DischargeTable table, final Gauge gauge, final String gaugeName, final Calculation problems) {
         if ((table == null) || (table.getDischargeTableValues().size() == 0)) {
             problems.addProblem("gauge_discharge_table.missing", gaugeName);
             return null;
@@ -132,4 +153,22 @@
             return Double.NaN;
         }
     }
+
+    /**
+     * Start of the discharge table's time interval.
+     *
+     * @return
+     */
+    public Date getStartTime() {
+        return this.startTime;
+    }
+
+    /**
+     * End of the discharge table's time interval.
+     *
+     * @return
+     */
+    public Date getEndTime() {
+        return this.endTime;
+    }
 }
\ No newline at end of file
--- a/backend/src/main/java/org/dive4elements/river/model/DischargeTable.java	Tue Feb 12 14:08:16 2019 +0100
+++ b/backend/src/main/java/org/dive4elements/river/model/DischargeTable.java	Tue Oct 08 15:03:24 2019 +0200
@@ -9,6 +9,7 @@
 package org.dive4elements.river.model;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -223,6 +224,28 @@
     }
 
     /**
+     * Fetches from the database a gauge's historical discharge tables of a time range in chronological order.
+     */
+    public static List<DischargeTable> fetchHistoricalDischargeTables(final Gauge gauge, final Date from, final Date to) {
+
+        final Session session = SessionHolder.HOLDER.get();
+
+        final Query query = session.createQuery("FROM DischargeTable"
+                + " WHERE (kind = 1) AND (gauge.id = :gauge_id)"
+                + "  AND (timeInterval.startTime <= :to)"
+                + "  AND ((timeInterval.stopTime IS NULL) OR (timeInterval.stopTime >= :from))"
+                + " ORDER BY timeInterval.startTime");
+
+        query.setParameter("gauge_id", gauge.getId());
+        query.setParameter("from", from);
+        query.setParameter("to", to);
+        final List<DischargeTable> qtables = new ArrayList<>();
+        for (final Object qtable : query.list())
+            qtables.add((DischargeTable) qtable);
+        return qtables;
+    }
+
+    /**
      * Selects from the database the values of a discharge table sorted by W
      */
     public static List<DischargeTableValue> fetchValuesSortedByW(final DischargeTable dischargeTable) {

http://dive4elements.wald.intevation.org