view artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.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 fb9430250899
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.tkhstate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.math.DoubleRange;
import org.apache.commons.lang.math.NumberRange;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.WINFOArtifact;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.Calculation.Problem;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.WQKms;
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.tkhcalculation.DischargeValuesFinder;
import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator;
import org.dive4elements.river.artifacts.sinfo.tkhcalculation.WaterlevelValuesFinder;
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.exports.WaterlevelDescriptionBuilder;
import org.dive4elements.river.model.BedHeight;
import org.dive4elements.river.model.River;

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

    private final CallContext context;

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

    public CalculationResult calculate(final SINFOArtifact sinfo) {

        /* access input data */
        final TkhAccess access = new TkhAccess(sinfo);
        final River river = access.getRiver();
        final RiverInfo riverInfo = new RiverInfo(river);
        final DoubleRange calcRange = access.getRange();

        final Calculation problems = new Calculation();

        /* find relevant bed-heights */
        final List<BedHeight> defaultBedHeights = new DefaultBedHeights(river).getBedHeights(problems);
        final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(problems, calcRange, defaultBedHeights);

        /* misuse winfo-artifact to calculate waterlevels in the same way */
        final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);

        /* calculate waterlevels */
        final WQKms[] kms = calculateWaterlevels(winfo, problems);

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

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

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

        final WaterlevelDescriptionBuilder descBuilder = new WaterlevelDescriptionBuilder(winfo, this.context);
        final String descriptionHeader = descBuilder.getColumnHeader();

        /* for each waterlevel, do a tkh calculation */
        final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange, descriptionHeader);

        /* determine calculation steps */
        final Collection<Double> allStations = determineCalculationSteps(bedHeights);

        for (final WQKms wqKms : kms) {
            final TkhCalculationResult result = calculateResult(calcRange, allStations, infoProvider, wqKms, bedHeights, descBuilder, problems);
            results.addResult(result, problems);
        }

        return new CalculationResult(results, problems);
    }

    /**
     * Calculation steps are simply the union of all stations of all involved bed-height datasets
     */
    private Collection<Double> determineCalculationSteps(final Collection<BedHeightsFinder> bedHeights) {

        final Collection<Double> allStations = new TreeSet<>();

        for (final BedHeightsFinder bedHeight : bedHeights) {
            final Collection<Double> stations = bedHeight.getStations();
            allStations.addAll(stations);
        }

        return allStations;
    }

    private WQKms[] calculateWaterlevels(final WINFOArtifact winfo, final Calculation problems) {

        final CalculationResult waterlevelData = winfo.getWaterlevelData(this.context);

        /* copy all problems */
        final Calculation winfoProblems = waterlevelData.getReport();
        final List<Problem> problems2 = winfoProblems.getProblems();
        if (problems2 != null) {
            for (final Problem problem : problems2) {
                problems.addProblem(problem);
            }
        }

        return (WQKms[]) waterlevelData.getData();
    }

    private TkhCalculationResult calculateResult(final DoubleRange calcRange, final Collection<Double> allStations, final RiverInfoProvider riverInfo,
            final WQKms wkms, final Collection<BedHeightsFinder> bedHeights, final WaterlevelDescriptionBuilder descBuilder, final Calculation problems) {

        // We have no wst year as the wst is created by a calculation; we do not need it though
        final int wspYear = -1;
        // Remark: showAllGauges only true for Fixierungsanalyse, false for WInfo, so false here as well
        final boolean showAllGauges = false;
        final WaterlevelData waterlevel = new WaterlevelData(wkms, wspYear, showAllGauges);

        final RiverInfoProvider riverInfoProvider = riverInfo.forWaterlevel(waterlevel);

        // FIXME: check with winfo how the name is generated
        final String wstLabel = waterlevel.getName();

        final WstInfo wstInfo = new WstInfo(wstLabel, wspYear, riverInfoProvider.getReferenceGauge());

        /* build tkh calculators per bedheight */
        final Map<NumberRange, TkhCalculator> calculatorsByRanges = buildCalculators(calcRange, wkms, bedHeights, problems, riverInfoProvider, wstLabel);
        if (calculatorsByRanges.isEmpty()) {
            /* there should already be some problems, so just abort */
            return null;
        }

        final Collection<SInfoResultRow> rows = new ArrayList<>();

        for (final Double stationDbl : allStations) {

            final double station = stationDbl;

            /* find the right calculator (i.e. bed height) depending on station, there should only be one maximal */
            final TkhCalculator tkhCalculator = findCalculator(calculatorsByRanges, station);
            if (tkhCalculator == null)
                continue;

            final SInfoResultRow row = SInfoResultRow.create();

            row.putValue(SInfoResultType.waterlevelLabel, descBuilder.getDesc(wkms));
            row.putValue(SInfoResultType.gaugeLabel, riverInfoProvider.findGauge(station));
            row.putValue(SInfoResultType.location, riverInfoProvider.getLocation(station));

            if (tkhCalculator.calculateTkh(station, row))
                rows.add(row);
        }

        return new TkhCalculationResult(wstLabel, wstInfo, true, rows);
    }

    private TkhCalculator findCalculator(final Map<NumberRange, TkhCalculator> calculators, final double station) {

        // REMAKR: linear search at this point, put we expect the number of bed heights to be very small (1-2 items)
        final Set<Entry<NumberRange, TkhCalculator>> x = calculators.entrySet();
        for (final Entry<NumberRange, TkhCalculator> entry : x) {
            final NumberRange range = entry.getKey();
            // FIXME: check if we need comparison with a tolerance
            if (range.containsDouble(station))
                return entry.getValue();
        }

        return null;
    }

    private Map<NumberRange, TkhCalculator> buildCalculators(final DoubleRange calcRange, final WQKms wkms, final Collection<BedHeightsFinder> bedHeights,
            final Calculation problems, final RiverInfoProvider riverInfoProvider, final String wstLabel) {
        final Map<NumberRange, TkhCalculator> calculatorByRanges = new HashMap<>();
        for (final BedHeightsFinder bedHeightsProvider : bedHeights) {

            final BedHeightInfo info = bedHeightsProvider.getInfo();

            final NumberRange range = new NumberRange(info.getFrom(), info.getTo());

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

            /* initialize tkh calculator */
            final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, problems, wstLabel, riverInfoProvider.getRiver(), calcRange,
                    waterlevelProvider, dischargeProvider, bedHeightsProvider);

            if (tkhCalculator.hasTkh()) {
                /* just ignore invalid ones, problems have already been updated by buildTkhCalculator() */
                calculatorByRanges.put(range, tkhCalculator);
            }
        }

        return calculatorByRanges;
    }
}

http://dive4elements.wald.intevation.org