view artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthminmax/FlowDepthMinMaxCalculation.java @ 8980:b194fa64506a

SINFO - show results themes according to spec, either raw data or floating mean values. Some improvements to error handling and handling of empty results.
author gernotbelger
date Thu, 05 Apr 2018 18:30:34 +0200
parents b5600453bb8f
children 0adc6d04de95
line wrap: on
line source
/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
 * Software engineering by
 *  Björnsen Beratende Ingenieure GmbH
 *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
 *
 * 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.sinfo.flowdepthminmax;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.lang.math.DoubleRange;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.WKms;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthUtils;
import org.dive4elements.river.artifacts.sinfo.flowdepthminmax.FlowDepthMinMaxAccess.MinMaxIdPair;
import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder;
import org.dive4elements.river.artifacts.sinfo.tkhcalculation.WaterlevelValuesFinder;
import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
import org.dive4elements.river.artifacts.states.WaterlevelData;
import org.dive4elements.river.artifacts.states.WaterlevelFetcher;
import org.dive4elements.river.model.River;

/**
 * @author Gernot Belger
 */
final class FlowDepthMinMaxCalculation {

    private final CallContext context;

    public FlowDepthMinMaxCalculation(final CallContext context) {
        this.context = context;
    }

    public CalculationResult calculate(final SINFOArtifact sinfo) {

        final String user = CalculationUtils.findArtifactUser(this.context, sinfo);

        /* access input data */
        final FlowDepthMinMaxAccess access = new FlowDepthMinMaxAccess(sinfo);
        final River river = access.getRiver();
        final RiverInfo riverInfo = new RiverInfo(river);

        final Collection<MinMaxIdPair> minMaxPairs = access.getMinMaxPairs();

        final DoubleRange calcRange = access.getRange();

        /* calculate results for each diff pair */
        final Calculation problems = new Calculation();

        final RiverInfoProvider infoProvider = RiverInfoProvider.forRange(this.context, river, calcRange);

        final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name());

        final FlowDepthMinMaxCalculationResults results = new FlowDepthMinMaxCalculationResults(calcModeLabel, user, riverInfo, calcRange);

        for (final MinMaxIdPair minMaxPair : minMaxPairs) {
            final FlowDepthMinMaxCalculationResult result = calculateResult(calcRange, minMaxPair, problems, infoProvider);
            results.addResult(result, problems);
        }

        return new CalculationResult(results, problems);
    }

    /**
     * Calculates one W-MSH differences pair.
     *
     * @param infoProvider
     */
    private FlowDepthMinMaxCalculationResult calculateResult(final DoubleRange calcRange, final MinMaxIdPair minMaxPair, final Calculation problems,
            final RiverInfoProvider infoProvider) {

        /* access real input data from database */
        final String wstId = minMaxPair.getWstId();

        // FIXME: bfg überzeugen dass man immer nur pärchen auswählen kann --> min/max id ist gleich!

        final String minSoundingId = minMaxPair.getMinSoundingId();
        final String maxSoundingId = minMaxPair.getMaxSoundingId();

        final BedHeightsFinder minBedHeight = minSoundingId == null ? null : BedHeightsFinder.forId(this.context, minSoundingId, calcRange, problems);
        final BedHeightsFinder maxBedHeight = maxSoundingId == null ? null : BedHeightsFinder.forId(this.context, maxSoundingId, calcRange, problems);
        if (minBedHeight == null && maxBedHeight == null)
            return null;

        /* REMARK: fetch ALL wst kms, because we want to determine the original reference gauge */
        final WaterlevelData waterlevel = new WaterlevelFetcher().findWaterlevel(this.context, wstId, calcRange, problems);
        if (waterlevel == null)
            return null;

        final String label = createLabel(waterlevel, minBedHeight, maxBedHeight);

        final WKms wstKms = waterlevel.getWkms();

        final int soundingYear = checkSoundingYear(minBedHeight, maxBedHeight, problems);
        FlowDepthUtils.checkYearDifference(label, waterlevel.getYear(), soundingYear, problems);

        /* re-determine the reference gauge, in the same way as the WaterlevelArtifact would do it */
        final RiverInfoProvider riverInfoProvider = infoProvider.forWaterlevel(waterlevel);

        final int wspYear = waterlevel.getYear();
        final WstInfo wstInfo = new WstInfo(waterlevel.getName(), wspYear, riverInfoProvider.getReferenceGauge());

        final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(problems, wstKms);
        final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wstKms);

        final String waterlevelLabel = waterlevel.getName();
        final String soundingLabel = buildSoundingLabel(minBedHeight, maxBedHeight);

        /* real calculation loop */
        final Collection<SInfoResultRow> rows = new ArrayList<>();

        // FIXME: we use the stations of one of the bed heights atm, because we probably will later use only data from one bed heights datasets!
        final Collection<Double> stations = minBedHeight == null ? maxBedHeight.getStations() : minBedHeight.getStations();
        for (final double station : stations) {
            if (calcRange.containsDouble(station)) {

                final double wst = waterlevelProvider.getWaterlevel(station);
                final double discharge = dischargeProvider.getDischarge(station);

                final double minBedHeightValue = minBedHeight == null ? Double.NaN : minBedHeight.getMeanBedHeight(station);
                final double maxBedHeightValue = maxBedHeight == null ? Double.NaN : maxBedHeight.getMeanBedHeight(station);

                final double minFlowDepth = wst - minBedHeightValue;
                final double maxFlowDepth = wst - maxBedHeightValue;

                // FIXME: unclear what is meant here...
                // FIXME: this will simply the bed height of 'the' bed height if we reduce this to simply using one sounding dataset
                final double meanBedHeight = Double.NaN;

                // REMARK: access the location once only during calculation
                final String location = riverInfoProvider.getLocation(station);

                // REMARK: access the gauge once only during calculation
                final String gaugeLabel = riverInfoProvider.findGauge(station);

                /* ignore invalid lines */
                if (Double.isNaN(wst) || Double.isNaN(minBedHeightValue) || Double.isNaN(maxBedHeightValue))
                    continue;

                final SInfoResultRow row = SInfoResultRow.create().//
                        putValue(SInfoResultType.station, station). //
                        putValue(SInfoResultType.flowdepthmin, minFlowDepth). //
                        putValue(SInfoResultType.flowdepthmax, maxFlowDepth). //
                        putValue(SInfoResultType.waterlevel, wst). //
                        putValue(SInfoResultType.discharge, discharge). //
                        putValue(SInfoResultType.waterlevelLabel, waterlevelLabel). //
                        putValue(SInfoResultType.gaugeLabel, gaugeLabel). //
                        putValue(SInfoResultType.meanBedHeight, meanBedHeight). //
                        putValue(SInfoResultType.soundingLabel, soundingLabel). //
                        putValue(SInfoResultType.location, location);
                rows.add(row);
            }
        }

        final BedHeightInfo minBedHeightInfo = minBedHeight == null ? null : minBedHeight.getInfo();
        final BedHeightInfo maxBedHeightInfo = maxBedHeight == null ? null : maxBedHeight.getInfo();

        return new FlowDepthMinMaxCalculationResult(label, wstInfo, minBedHeightInfo, maxBedHeightInfo, rows);
    }

    private String buildSoundingLabel(final BedHeightsFinder minBedHeight, final BedHeightsFinder maxBedHeight) {

        if (minBedHeight == null)
            return maxBedHeight.getInfo().getDescription();

        if (maxBedHeight == null)
            return minBedHeight.getInfo().getDescription();

        return String.format("%s / %s", minBedHeight.getInfo().getDescription(), maxBedHeight.getInfo().getDescription());
    }

    private String createLabel(final WaterlevelData waterlevel, final BedHeightsFinder minBedHeight, final BedHeightsFinder maxBedHeight) {

        final StringBuilder buffer = new StringBuilder(waterlevel.getName());

        if (minBedHeight != null)
            buffer.append(" - "). //
            append(minBedHeight.getInfo().getDescription());

        if (maxBedHeight != null)
            buffer.append(" - "). //
            append(maxBedHeight.getInfo().getDescription());

        return buffer.toString();
    }

    private int checkSoundingYear(final BedHeightsFinder minBedHeight, final BedHeightsFinder maxBedHeight, final Calculation problems) {

        if (maxBedHeight == null)
            return minBedHeight.getInfo().getYear();

        if (minBedHeight == null)
            return maxBedHeight.getInfo().getYear();

        final int minYear = minBedHeight.getInfo().getYear();
        final int maxYear = minBedHeight.getInfo().getYear();

        if (minYear != maxYear)
            problems.addProblem("sinfo.flowdepthminmaxcalculation.soundingyear.different");

        return minYear;
    }
}

http://dive4elements.wald.intevation.org