changeset 9392:e4a6679b868f

Implemented first approach of bundu dynamic main value calculation
author mschaefer
date Thu, 09 Aug 2018 18:20:04 +0200
parents 2da486c7c05f
children 6174daaf5e56
files artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java backend/src/main/java/org/dive4elements/river/model/sinfo/DailyDischargeValue.java
diffstat 2 files changed, 174 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java	Thu Aug 09 17:25:13 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java	Thu Aug 09 18:20:04 2018 +0200
@@ -9,6 +9,7 @@
 package org.dive4elements.river.artifacts.services;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -17,7 +18,9 @@
 import org.dive4elements.artifacts.CallMeta;
 import org.dive4elements.artifacts.GlobalContext;
 import org.dive4elements.artifacts.common.ArtifactNamespaceContext;
+import org.dive4elements.artifacts.common.utils.DateUtils;
 import org.dive4elements.artifacts.common.utils.XMLUtils;
+import org.dive4elements.river.backend.SessionHolder;
 import org.dive4elements.river.model.Gauge;
 import org.dive4elements.river.model.MainValue;
 import org.dive4elements.river.model.MainValueType;
@@ -26,8 +29,14 @@
 import org.dive4elements.river.model.OfficialLine;
 import org.dive4elements.river.model.River;
 import org.dive4elements.river.model.TimeInterval;
+import org.dive4elements.river.model.sinfo.DailyDischargeValue;
+import org.dive4elements.river.model.sinfo.DailyDischargeValue.OrderByField;
+import org.dive4elements.river.utils.DoubleUtil;
+import org.hibernate.Session;
 import org.w3c.dom.Document;
 
+import gnu.trove.TDoubleArrayList;
+
 /**
  * This service returns the main values of a river's gauge based on the start
  * and end point of the river.
@@ -56,6 +65,10 @@
             return buildDocument(river, gauge, mainValues, context);
         }
         catch (final MainValuesServiceException e) {
+            // e.printStackTrace();
+            return error(e.getMessage());
+        }
+        catch (final Exception e) {
             e.printStackTrace();
             return error(e.getMessage());
         }
@@ -106,41 +119,138 @@
     }
 
     /**
-     * This method creates the result document that includes the main values of
-     * the specified <i>gauge</i>.
-     *
-     * @param river
-     *            The river.
-     * @param gauge
-     *            The gauge.
-     * @param endYear
-     * @param startYear
-     *
-     * @return a document that includes the main values of the specified river
-     *         at the specified gauge.
+     * Computes a gauge's main values for a period of time based on its daily discharges stored in the database
      */
-    protected List<MainValue> getMainValues(final River river, final Gauge gauge, final Date startTime, final Date endTime) {
-
-        // TODO: compute our own main values from the discharge timeseries.
+    protected List<MainValue> getMainValues(final River river, final Gauge gauge, final Date startTime, final Date endTime) throws MainValuesServiceException {
 
-        // final List<MainValue> mainValues = gauge.getMainValues();
         final List<MainValue> mainValues = new ArrayList<>();
-
-        final MainValue myMain = new MainValue();
-
-        // TODO: fetch real NamedMainValue from database: GLQ20, MNQ, MQ, MHQ, HQ5 + Dauerzahlen
-        final NamedMainValue mainValue = new NamedMainValue("Testname", new MainValueType(MainValueTypeKey.Q.getName()));
-        mainValue.setOfficialLines(new ArrayList<OfficialLine>());
-
-        myMain.setMainValue(mainValue);
-        // FIXME: compute value
-        myMain.setValue(new BigDecimal("1234.567"));
-
-        final TimeInterval timeInterval = new TimeInterval(startTime, endTime);
-        myMain.setTimeInterval(timeInterval);
-
-        mainValues.add(myMain);
-
+        computeMainDischargeValues(gauge, startTime, endTime, mainValues);
         return mainValues;
     }
+
+    /**
+     * Computes mnq, mq, mhq, hq5 and q(d=0..364) and adds them to a MainValue list
+     */
+    private void computeMainDischargeValues(final Gauge gauge, final Date startTime, final Date endTime, final List<MainValue> mainValues)
+            throws MainValuesServiceException {
+
+        // Query the gauge's daily Q values
+        final List<DailyDischargeValue> qdvs = DailyDischargeValue.getValues(gauge, startTime, endTime, OrderByField.DAY);
+        if (qdvs.isEmpty())
+            throw new MainValuesServiceException("no daily discharge values for gauge " + gauge.getName() + " in the requested time period");
+        // return; // TODO Fehlermeldung
+
+        // Build yearly aggregates
+        final TDoubleArrayList mnqs = new TDoubleArrayList();
+        final TDoubleArrayList mqs = new TDoubleArrayList();
+        int mqcnt = 0;
+        final TDoubleArrayList mhqs = new TDoubleArrayList();
+        for (int i = 0, j = 0; i <= qdvs.size() - 1; i++) {
+            final DailyDischargeValue qdv = qdvs.get(i);
+            if ((i >= 1) && isSameQYear(qdv.getDay(), qdvs.get(i - 1).getDay())) {
+                // Continue aggregating the values of the current year
+                mnqs.set(j, Math.min(mnqs.get(j), qdv.getDischarge().doubleValue()));
+                mhqs.set(j, Math.max(mhqs.get(j), qdv.getDischarge().doubleValue()));
+                mqs.set(j, mqs.get(j) + qdv.getDischarge().doubleValue());
+                mqcnt++;
+                if (i == qdvs.size() - 1)
+                    mqs.set(j, mqs.get(j) / mqcnt);
+            }
+            else {
+                // Complete mq aggregation
+                if (mqcnt >= 1) {
+                    mqs.set(j, mqs.get(j) / mqcnt);
+                    j++;
+                }
+                // Start next year
+                mnqs.add(qdv.getDischarge().doubleValue());
+                mhqs.add(qdv.getDischarge().doubleValue());
+                mqs.add(qdv.getDischarge().doubleValue());
+                mqcnt = 1;
+            }
+        }
+
+        // Compute arithmetic means of the yearly values
+        final Session session = SessionHolder.HOLDER.get();
+        final TimeInterval timeperiod = new TimeInterval(startTime, endTime);
+        final double mnq = DoubleUtil.sum(mnqs.toNativeArray()) / mnqs.size();
+        mainValues.add(createMainValue(gauge, fetchNamedQMainValue("MNQ", session), mnq, timeperiod));
+        final double mq = DoubleUtil.sum(mqs.toNativeArray()) / mqs.size();
+        mainValues.add(createMainValue(gauge, fetchNamedQMainValue("MQ", session), mq, timeperiod));
+        final double mhq = DoubleUtil.sum(mhqs.toNativeArray()) / mhqs.size();
+        mainValues.add(createMainValue(gauge, fetchNamedQMainValue("MHQ", session), mhq, timeperiod));
+
+        // Compute hq5
+        mhqs.sort();
+        final double hq5 = mhqs.get((int) Math.ceil(4 * mhqs.size() / 5));
+        mainValues.add(createMainValue(gauge, fetchNamedQMainValue("HQ5", session), hq5, timeperiod));
+
+        // Query the gauge's daily Q values and build a list sorted by ascending Q
+        final TDoubleArrayList qs = new TDoubleArrayList();
+        for (final DailyDischargeValue qdv : qdvs)
+            qs.add(qdv.getDischarge().doubleValue());
+        qs.sort();
+
+        // Step through the sorted Q list and get the duration discharges
+        final int yearCnt = DateUtils.getYearFromDate(endTime) - DateUtils.getYearFromDate(startTime) + 1;
+        double glq20 = Double.NaN;
+        for (int i = 0, k = 0; (i <= 364) && (k <= qs.size() - 1); i++, k += yearCnt) {
+            final NamedMainValue nmv = fetchNamedQMainValue(i, session, mainValues.get(0).getMainValue().getType());
+            if (nmv != null)
+                mainValues.add(createMainValue(gauge, nmv, getDurationQ(qs, k), timeperiod));
+            if (i == 20)
+                glq20 = qs.get(k);
+        }
+        mainValues.add(createMainValue(gauge, fetchNamedQMainValue("GlQ", session), glq20, timeperiod));
+    }
+
+    /**
+     * Checks year equality of two dates (calendar year)
+     */
+    private boolean isSameQYear(final Date a, final Date b) {
+        final Calendar ca = Calendar.getInstance();
+        ca.setTime(a);
+        final Calendar cb = Calendar.getInstance();
+        cb.setTime(b);
+        return (ca.get(Calendar.YEAR) == cb.get(Calendar.YEAR));
+    }
+
+    /**
+     * Fetches a named main Q value from the database, if existing
+     */
+    private NamedMainValue fetchNamedQMainValue(final String name, final Session session) {
+        final NamedMainValue nmv = NamedMainValue.fetchByNameAndType(name, MainValueTypeKey.Q.getName(), session);
+        if (nmv != null)
+            nmv.setOfficialLines(new ArrayList<OfficialLine>());
+        return nmv;
+    }
+
+    /**
+     * Fetches a named main Q(duration) value from the database, if existing
+     */
+    private NamedMainValue fetchNamedQMainValue(final int days, final Session session, final MainValueType qType) {
+        final NamedMainValue nmv = NamedMainValue.fetchByNameAndType(Integer.toString(days), MainValueTypeKey.DURATION.getName(), session);
+        // final NamedMainValue nmv = new NamedMainValue(Integer.toString(days), qType);
+        if (nmv != null)
+            nmv.setOfficialLines(new ArrayList<OfficialLine>());
+        return nmv;
+    }
+
+    /**
+     * Creates a main value for a main value name
+     */
+    private MainValue createMainValue(final Gauge gauge, final NamedMainValue nmv, final double value, final TimeInterval timeperiod) {
+        return new MainValue(gauge, nmv, BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_EVEN), timeperiod); // TODO Scale per Formatter-Konstante o.รค.
+    }
+
+    /**
+     * Gets the q from a list at a list position, or the next larger q if the immediate successors are equal
+     */
+    private double getDurationQ(final TDoubleArrayList qs, final int i) {
+        for (int j = i; j + 1 <= qs.size() - 1; j++)
+            if (qs.get(j + 1) > qs.get(j) + 0.001)
+                return qs.get(j);
+        // TODO Increment q on third significant digit
+        return qs.get(qs.size() - 1);
+    }
 }
\ No newline at end of file
--- a/backend/src/main/java/org/dive4elements/river/model/sinfo/DailyDischargeValue.java	Thu Aug 09 17:25:13 2018 +0200
+++ b/backend/src/main/java/org/dive4elements/river/model/sinfo/DailyDischargeValue.java	Thu Aug 09 18:20:04 2018 +0200
@@ -12,6 +12,7 @@
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -23,6 +24,11 @@
 import javax.persistence.SequenceGenerator;
 import javax.persistence.Table;
 
+import org.dive4elements.river.backend.SessionHolder;
+import org.dive4elements.river.model.Gauge;
+import org.hibernate.Query;
+import org.hibernate.Session;
+
 
 /**
  * Hibernate binding for the DB table daily_discharge_values
@@ -34,6 +40,16 @@
 @Table(name = "daily_discharge_values")
 public class DailyDischargeValue implements Serializable {
 
+    /***** TYPES *****/
+
+    /**
+     * Field to use in a query's order-by clause
+     *
+     */
+    public enum OrderByField {
+        DAY, DISCHARGE;
+    }
+
     /***** FIELDS *****/
 
     private static final long serialVersionUID = -6192738825193230784L;
@@ -107,4 +123,20 @@
     public void setDay(final Date day) {
         this.day = day;
     }
+
+    /**
+     * Selects from the database the daily discharge values of a gauge and a date range
+     */
+    public static List<DailyDischargeValue> getValues(final Gauge gauge, final Date startDate, final Date endDate, final OrderByField orderBy) {
+        final Session session = SessionHolder.HOLDER.get();
+        final String orderField = (orderBy == OrderByField.DISCHARGE) ? "discharge" : "day";
+        final Query query = session.createQuery("SELECT v"
+                + " FROM DailyDischargeValue AS v JOIN v.dailyDischarge AS s"
+                + " WHERE (s.gauge.id=:gaugeid) AND (v.day BETWEEN :startDate AND :endDate)"
+                + " ORDER BY " + orderField);
+        query.setParameter("gaugeid", gauge.getId());
+        query.setParameter("startDate", startDate);
+        query.setParameter("endDate", endDate);
+        return query.list();
+    }
 }

http://dive4elements.wald.intevation.org