# HG changeset patch
# User mschaefer
# Date 1533831604 -7200
# Node ID e4a6679b868f66a1f5c1924d100a7d1958fdaf88
# Parent 2da486c7c05fa024c634ba6d74b946f1bcf6bf40
Implemented first approach of bundu dynamic main value calculation
diff -r 2da486c7c05f -r e4a6679b868f artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java
--- 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 gauge.
- *
- * @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 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 getMainValues(final River river, final Gauge gauge, final Date startTime, final Date endTime) throws MainValuesServiceException {
- // final List mainValues = gauge.getMainValues();
final List 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());
-
- 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 mainValues)
+ throws MainValuesServiceException {
+
+ // Query the gauge's daily Q values
+ final List 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());
+ 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());
+ 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
diff -r 2da486c7c05f -r e4a6679b868f backend/src/main/java/org/dive4elements/river/model/sinfo/DailyDischargeValue.java
--- 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 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();
+ }
}