view artifacts/src/main/java/org/dive4elements/river/artifacts/uinfo/salix/SalixLineCalculator.java @ 9499:853f2dafc16e

VegetationZones in CrossSectionsDiagram
author gernotbelger
date Thu, 27 Sep 2018 18:06:26 +0200
parents e44c1a8b0c54
children 76c0665888a3
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.uinfo.salix;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.NavigableMap;

import org.dive4elements.river.artifacts.WINFOArtifact;
import org.dive4elements.river.artifacts.access.ComputationRangeAccess;
import org.dive4elements.river.artifacts.common.AbstractResultType;
import org.dive4elements.river.artifacts.common.GeneralResultType;
import org.dive4elements.river.artifacts.common.ResultRow;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.river.MainWstValuesCalculator;
import org.dive4elements.river.artifacts.model.river.RiverInfoProvider;
import org.dive4elements.river.artifacts.sinfo.tkhstate.WinfoArtifactWrapper;
import org.dive4elements.river.artifacts.uinfo.UINFOArtifact;
import org.dive4elements.river.artifacts.uinfo.common.UInfoResultType;
import org.dive4elements.river.artifacts.uinfo.salix.SalixLineAccess.ScenarioType;
import org.dive4elements.river.artifacts.uinfo.vegetationzones.VegetationZoneServerClientXChange;
import org.dive4elements.river.utils.Formatter;

/**
 * Calculation of the result rows of the u-info salix line calc mode
 *
 * @author Matthias Schäfer
 */
final class SalixLineCalculator {

    private static final String MAIN_VALUE_MNQ = "mnq";

    private static final String MAIN_VALUE_MQ = "mq";

    private static final String MAIN_VALUE_MHQ = "mhq";

    private static final String MAIN_VALUE_HQ5 = "hq5";

    private static final BigDecimal SALIX_DISTANCE = new BigDecimal("2.31");
    private final List<ResultRow> rows = new ArrayList<>();

    private final RiverInfoProvider riverInfoProvider;

    public SalixLineCalculator(final RiverInfoProvider riverInfoProvider) {
        this.riverInfoProvider = riverInfoProvider;
    }

    /**
     * Calculate the salix line result rows
     */
    public void execute(final Calculation problems, final UINFOArtifact uinfo, final NavigableMap<Double, List<Double>> rangeScenarios,
            final ScenarioType scenarioType, final String[] scenarioLabels, final String rangeString, final String additionalString,
            final SalixLineCalculationResults results) {

        final MainWstValuesCalculator mainWstValues = fetchGaugeMainValuePositions2(problems);

        final WINFOArtifact winfo = new WinfoArtifactWrapper(uinfo);
        winfo.addStringData("ld_mode", "distance");
        winfo.addStringData("ld_step", "100");
        for (final double station : new ComputationRangeAccess(winfo).getKms()) {
            this.rows.add(createRow(mainWstValues, station, rangeScenarios));
        }
        if (scenarioType == ScenarioType.REGIONAL)
            results.addResult(new SalixLineCalculationRegionalResult("Salix-regional", scenarioLabels, rangeString, additionalString, this.rows), problems);
        else if (scenarioType == ScenarioType.SUPRAREGIONAL)
            results.addResult(new SalixLineCalculationSupraRegionalResult("Salix-supra", scenarioLabels, rangeString, additionalString, this.rows), problems);
        else if (scenarioType == ScenarioType.HISTORICAL)
            results.addResult(new SalixLineCalculationHistoricalResult("Salix-hist", scenarioLabels, rangeString, additionalString, this.rows), problems);
        else
            results.addResult(new SalixLineCalculationResult("Salix-simple", this.rows), problems);
    }

    private MainWstValuesCalculator fetchGaugeMainValuePositions2(final Calculation problems) {
        final MainWstValuesCalculator mainWstValues = MainWstValuesCalculator.forRiverInfo(this.riverInfoProvider, MAIN_VALUE_MQ, MAIN_VALUE_MNQ,
                MAIN_VALUE_MHQ, MAIN_VALUE_HQ5);

        if (!mainWstValues.hasPosition(MAIN_VALUE_MQ))
            problems.addProblem("uinfo_salix_calc.warning.missing_mq");
        else {
            if (!mainWstValues.hasPosition(MAIN_VALUE_MHQ))
                problems.addProblem("uinfo_salix_calc.warning.missing_mhq");
            if (!mainWstValues.hasPosition(MAIN_VALUE_MNQ))
                problems.addProblem("uinfo_salix_calc.warning.missing_mnq");
        }

        return mainWstValues;
    }

    /**
     * Create a result row for a station and its gauge, and add w-q-values as selected
     *
     * @param mainWstValues
     */
    private ResultRow createRow(final MainWstValuesCalculator mainWstValues, final double station, final NavigableMap<Double, List<Double>> rangeScenarios) {

        final ResultRow row = ResultRow.create();
        row.putValue(GeneralResultType.station, station);
        // Find station's gauge (obsolete version which calculates gauge-wise)
        // final Gauge gauge = this.riverInfoProvider.getGauge(station, true);
        // Interpolate mnw, mw, and mhw
        // final double mnw = interpolateW(station, this.gaugeMnwPos.get(gauge));
        // final double mw = interpolateW(station, this.gaugeMwPos.get(gauge));
        // final double mhw = interpolateW(station, this.gaugeMhwPos.get(gauge));
        final double mnw = mainWstValues.interpolateW(station, MAIN_VALUE_MNQ);
        final double mw = mainWstValues.interpolateW(station, MAIN_VALUE_MQ);
        final double mhw = mainWstValues.interpolateW(station, MAIN_VALUE_MHQ);
        final double hw5 = mainWstValues.interpolateW(station, MAIN_VALUE_HQ5);
        row.putValue(UInfoResultType.waterlevelMNW, mnw);
        row.putValue(UInfoResultType.waterlevelMW, mw);
        row.putValue(UInfoResultType.waterlevelMHW, mhw);
        row.putValue(UInfoResultType.waterlevelMH5, hw5);

        // Calc salix-line and mw-mnw
        row.putValue(UInfoResultType.salixline, calcSalix(mhw, mw, 0.0));
        row.putValue(UInfoResultType.salix_mw_mnw, calcMwmnw(mw, mnw));
        final double salixw = Formatter.roundW(mhw).subtract(SALIX_DISTANCE).doubleValue();
        row.putValue(UInfoResultType.salixw, salixw);
        // Calc scenario values (always all scenario types set, Result variant extracts the fields needed)
        final List<SalixScenario> scenarios = new ArrayList<>();
        final List<Double> deltaws = getDeltaWs(station, rangeScenarios);
        for (final Double deltaw : deltaws) {
            if (deltaw != null) {
                final double salix = calcSalix(mhw, mw, deltaw.doubleValue());
                final double scen = calcSalix(mhw, 0.0, deltaw.doubleValue());
                scenarios.add(new SalixScenario((int) (deltaw * 100), salix, scen));
            } else {
                scenarios.add(null);
            }
        }
        row.putValue(UInfoResultType.customMultiRowColSalixScenarios, scenarios);
        row.putValue(GeneralResultType.gaugeLabel, this.riverInfoProvider.findGauge(station));
        return row;
    }

    /**
     * Calculates the salix value
     */
    private double calcSalix(final double mhw, final double mw, final double deltamw) {
        if (Double.isNaN(mw) || Double.isInfinite(mw) || Double.isNaN(mhw) || Double.isInfinite(mhw))
            return mhw - mw; // preserving NaN or Infinity
        return Formatter.roundW(mhw).subtract(SALIX_DISTANCE).subtract(Formatter.roundW(mw).add(Formatter.roundW(deltamw))).doubleValue();
    }

    /**
     * Calculates the inverse MW-MNW difference
     */
    private double calcMwmnw(final double mw, final double mnw) {
        if (Double.isNaN(mw) || Double.isInfinite(mw) || Double.isNaN(mnw) || Double.isInfinite(mnw))
            return mnw - mw; // preserving NaN or Inifinity
        return Formatter.roundW(mnw).subtract(Formatter.roundW(mw)).doubleValue();
    }

    /**
     * Gets the station-specific list of delta-ws of the active scenario, at least with one null item in any case
     */
    private List<Double> getDeltaWs(final double station, final NavigableMap<Double, List<Double>> rangeScenarios) {
        final Entry<Double, List<Double>> stationScenarios = rangeScenarios.floorEntry(station);
        if (stationScenarios != null) {
            return stationScenarios.getValue();
        }
        final List<Double> noScen = new ArrayList<>();
        noScen.add(null);
        return noScen;
    }

    /**
     * Find and return a height (iota, w main value) of a station in a previously calculated result
     */
    public double fetchStationHeight(final Calculation problems, final double station, final AbstractResultType resultType,
            final SalixLineCalculationResult result) {

        // Search the station in the previously calculated result rows
        final ResultRow stationRow = searchStation(station, result.getRows());
        if (stationRow != null)
            return stationRow.getDoubleValue(resultType);
        return Double.NaN;
    }

    /**
     * Searches the row of a station in a result rows collection
     */
    private ResultRow searchStation(final double station, final Collection<ResultRow> rows) {
        for (final ResultRow row : rows) {
            if (row.getDoubleValue(GeneralResultType.station) > station + 0.0001)
                return row;
        }
        return null;
    }

    /**
     * Computes the height of a vegetation zone limit for a station and a previously computed salix line result
     */
    public double computeVegetationZoneHeight(final Calculation problems, final double station, final int vegetationZoneType,
            final SalixLineCalculationResult result) {

        // Search the station in the previously calculated result rows
        final ResultRow stationRow = searchStation(station, result.getRows());
        if (stationRow == null)
            return Double.NaN;

        // Compute height from overflow duration days
        final List<VegetationZoneServerClientXChange> vzs = VegetationZoneServerClientXChange.getStandardList(null, null); // TODO river, context
        if ((vegetationZoneType >= 1) && (vegetationZoneType <= vzs.size())) {
            final int uefd = vzs.get(vegetationZoneType - 1).getMin_day_overflow();
            // Üfd = -70,559 ∗ ln((DGM - MW) + 0,5) + 80,711
            final double f1 = -70.559;
            final double f2 = -88.711;
            final double mw = stationRow.getDoubleValue(UInfoResultType.waterlevelMW);
            final double dgm = Math.exp((uefd - f2) / f1) + mw - 0.5;
            return dgm;
        }
        return Double.NaN;
    }
}

http://dive4elements.wald.intevation.org