Mercurial > dive4elements > river
view artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java @ 9396:6ebc9357550c
Fixed: bundu bzws start year plus one
author | mschaefer |
---|---|
date | Mon, 13 Aug 2018 17:15:05 +0200 |
parents | 0255c51283a4 |
children | bc9a45d2b1fa |
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde * Software engineering by Intevation GmbH * * 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.services; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; 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; import org.dive4elements.river.model.MainValueType.MainValueTypeKey; import org.dive4elements.river.model.NamedMainValue; 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. * * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public class DynamicMainValuesService extends AbstractMainValuesService { private static final long serialVersionUID = 1L; private static final String XPATH_START_YEAR = "/art:mainvalues/art:startYear/text()"; private static final String XPATH_END_YEAR = "/art:mainvalues/art:endYear/text()"; @Override public Document doProcess(final Document data, final GlobalContext context, final CallMeta callMeta) { try { final River river = getRequestedRiver(data); final Gauge gauge = getRequestedGauge(data, river); final Date startTime = getRequestedStartYear(data); final Date endTime = getRequestedEndYear(data); final List<MainValue> mainValues = getMainValues(river, gauge, startTime, endTime); 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()); } } private Date getRequestedStartYear(final Document data) throws MainValuesServiceException { final String startStr = XMLUtils.xpathString(data, XPATH_START_YEAR, ArtifactNamespaceContext.INSTANCE); if (startStr == null) throw new MainValuesServiceException("no start year"); try { final int year = Integer.parseInt(startStr); final Calendar cal = Calendar.getInstance(); cal.clear(); cal.set(year, 0, 1); return cal.getTime(); } catch (final NumberFormatException e) { e.printStackTrace(); throw new MainValuesServiceException("invalid start year"); } } private Date getRequestedEndYear(final Document data) throws MainValuesServiceException { final String endStr = XMLUtils.xpathString(data, XPATH_END_YEAR, ArtifactNamespaceContext.INSTANCE); if (endStr == null) throw new MainValuesServiceException("no end year"); try { final int year = Integer.parseInt(endStr); final Calendar cal = Calendar.getInstance(); cal.clear(); cal.set(year, 11, 31); return cal.getTime(); } catch (final NumberFormatException e) { e.printStackTrace(); throw new MainValuesServiceException("invalid end year"); } } /** * 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) throws MainValuesServiceException { final List<MainValue> mainValues = new ArrayList<>(); 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 - obsolete // mhqs.sort(); // final double hq5 = mhqs.get((int) Math.ceil(4 * mhqs.size() / 5)); // mainValues.add(createMainValue(gauge, fetchNamedQMainValue("HQ5", session), hq5, timeperiod)); // Add HSQ-II from the gauge's main values final MainValue hsq2 = fetchHsqII(gauge, session); if (hsq2 != null) mainValues.add(hsq2); // 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) { final double q = getDurationQ(qs, k); mainValues.add(createMainValue(gauge, nmv, q, timeperiod)); if (i == 20) glq20 = q; } } 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 the gauge's HSQ-II from the database, or returns null */ private MainValue fetchHsqII(final Gauge gauge, final Session session) { final NamedMainValue nmv = NamedMainValue.fetchByNameAndType("HSQ-II", MainValueTypeKey.UNKNOWN.getName(), session); if (nmv == null) return null; final List<MainValue> mvs = gauge.getMainValues(); for (final MainValue mv : mvs) { if (mv.getMainValue().getId() == nmv.getId()) return mv; } return null; } /** * 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) { if (i == 0) return qs.getQuick(0); for (int j = i; j <= qs.size() - 1; j++) { if (qs.getQuick(j) > qs.getQuick(j - 1) + 0.001) return qs.getQuick(j); } // Identical values at end of list: increment q on third significant digit final MathContext mc = new MathContext(3, RoundingMode.FLOOR); BigDecimal qplus = BigDecimal.valueOf(qs.getQuick(qs.size() - 1)); qplus = qplus.round(mc).add(BigDecimal.ONE.scaleByPowerOfTen(-qplus.scale())); return qplus.doubleValue(); } }