# 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(); + } }