view artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java @ 8948:a4f1ac81f26d

Work on SINFO-FlowDepthMinMax. Also rework of result row stuff, in order to reduce abstraction, using result type concept
author gernotbelger
date Wed, 14 Mar 2018 14:10:32 +0100
parents 5d5d482da3e9
children 183f42641ab6
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 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(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);

        for (final WQKms wqKms : kms) {

            final TkhCalculationResult result = calculateResult(calcRange, infoProvider, wqKms, bedHeights, descBuilder, problems);
            if (result != null)
                // FIXME: must be sorted by station!
                results.addResult(result);
        }

        return new CalculationResult(results, problems);
    }

    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 RiverInfoProvider riverInfo, final WQKms wkms,
            final Collection<BedHeightsFinder> bedHeights, final WaterlevelDescriptionBuilder descBuilder, final Calculation problems) {

        // FIXME: wo kommt das her? via winfo kein jahr vorhanden, oder doch? aber soll in metadaten ausgegeben werden...
        final int wspYear = -1;
        // FIXME: richtig? vgl. WInfo?
        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<>();

        /* using wst-kms as basis, because we know that they are generated wst's with a fixed km-step */
        // FIXME: das führt dazu, das aktuell die Sohlhöhen beliebig linear interpolierrt werden. ist das immer richtig? z.b.
        // bei großen abständen?

        final int size = wkms.size();
        for (int i = 0; i < size; i++) {

            final double station = wkms.getKm(i);

            /* find the right calculator (i.e. bedheigh) 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));

            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(wkms);
            final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms);

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

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

        return calculatorByRanges;
    }
}

http://dive4elements.wald.intevation.org