view artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java @ 9415:9744ce3c3853

Rework of fixanalysis computation and dWt and WQ facets. Got rid of strange remapping and bitshifting code by explicitely saving the column information and using it in the facets. The facets also put the valid station range into their xml-metadata
author gernotbelger
date Thu, 16 Aug 2018 16:27:53 +0200
parents bc9a45d2b1fa
children 4cccbd32b680
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.common.utils.DateUtils;
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 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 AbstractDynamicMainValuesService {

    private static final long serialVersionUID = 1L;

    /**
     * Computes a gauge's main values for a period of time based on its daily discharges stored in the database
     */
    @Override
    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;

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

http://dive4elements.wald.intevation.org