view artifacts/src/main/java/org/dive4elements/river/artifacts/uinfo/salix/SalixLineCalculator.java @ 9321:a978b601a034

Salix: Fixed ArrrayoutOfBoundsException; minor cleanup
author gernotbelger
date Fri, 27 Jul 2018 10:25:09 +0200
parents 72b3270e1568
children b3d3c958a594
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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.GeneralResultType;
import org.dive4elements.river.artifacts.common.ResultRow;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.WstValueTable;
import org.dive4elements.river.artifacts.model.WstValueTable.QPosition;
import org.dive4elements.river.artifacts.model.WstValueTableFactory;
import org.dive4elements.river.artifacts.sinfo.common.GaugeDischargeValuesFinder;
import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
import org.dive4elements.river.artifacts.sinfo.tkhstate.WinfoArtifactWrapper;
import org.dive4elements.river.artifacts.uinfo.UINFOArtifact;
import org.dive4elements.river.artifacts.uinfo.commons.UInfoResultType;
import org.dive4elements.river.artifacts.uinfo.salix.SalixLineAccess.ScenarioType;
import org.dive4elements.river.model.Gauge;
import org.dive4elements.river.model.MainValue;
import org.dive4elements.river.model.MainValueType.MainValueTypeKey;

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

    private final List<ResultRow> rows = new ArrayList<>();

    private final RiverInfoProvider riverInfoProvider;

    private final Map<Gauge, QPosition> gaugeMwPos;
    private final Map<Gauge, QPosition> gaugeMnwPos;
    private final Map<Gauge, QPosition> gaugeMhwPos;

    private Calculation problems;

    private WstValueTable wst;

    public SalixLineCalculator(final RiverInfoProvider riverInfoProvider) {
        this.riverInfoProvider = riverInfoProvider;
        this.gaugeMwPos = new HashMap<>();
        this.gaugeMnwPos = new HashMap<>();
        this.gaugeMhwPos = new HashMap<>();
    }

    /**
     * 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 SalixLineCalculationResults results) {

        this.problems = problems;
        this.wst = WstValueTableFactory.getTable(this.riverInfoProvider.getRiver());

        fetchGaugeMainValuePositions();

        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(station, rangeScenarios));
        }
        if (scenarioType == ScenarioType.REGIONAL)
            results.addResult(new SalixLineCalculationRegionalResult("Salix", scenarioLabels, this.rows), problems);
        else if (scenarioType == ScenarioType.SUPRAREGIONAL)
            results.addResult(new SalixLineCalculationSupraRegionalResult("Salix", this.rows), problems);
        else if (scenarioType == ScenarioType.HISTORICAL)
            results.addResult(new SalixLineCalculationHistoricalResult("Salix", this.rows), problems);
        else
            results.addResult(new SalixLineCalculationNoScenarioResult("Salix", this.rows), problems);
    }

    /**
     * Fetch MW, MNW and MHW of all gauges and determine the wst QPosition for each one
     */
    private void fetchGaugeMainValuePositions() {
        this.gaugeMwPos.clear();
        this.gaugeMnwPos.clear();
        this.gaugeMhwPos.clear();
        for (final Gauge gauge : this.riverInfoProvider.getGauges()) {
            this.gaugeMwPos.put(gauge, null);
            this.gaugeMnwPos.put(gauge, null);
            this.gaugeMhwPos.put(gauge, null);
            final GaugeDischargeValuesFinder finder = GaugeDischargeValuesFinder.loadValues(gauge, this.problems);
            if (finder == null)
                continue;
            final double gaugeKm = gauge.getStation().doubleValue();
            for (final MainValue mv : MainValue.getValuesOfGaugeAndType(gauge, MainValueTypeKey.W)) {
                if (mv.getMainValue().getName().equalsIgnoreCase("mw"))
                    this.gaugeMwPos.put(gauge, this.wst.getQPosition(gaugeKm, finder.getDischarge(mv.getValue().doubleValue())));
                else if (mv.getMainValue().getName().equalsIgnoreCase("mnw"))
                    this.gaugeMnwPos.put(gauge, this.wst.getQPosition(gaugeKm, finder.getDischarge(mv.getValue().doubleValue())));
                else if (mv.getMainValue().getName().equalsIgnoreCase("mhw"))
                    this.gaugeMhwPos.put(gauge, this.wst.getQPosition(gaugeKm, finder.getDischarge(mv.getValue().doubleValue())));
            }
        }
    }

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

        final ResultRow row = ResultRow.create();
        // Find station's gauge
        final Gauge gauge = this.riverInfoProvider.getGauge(station, true);
        row.putValue(GeneralResultType.station, station);
        // 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));
        row.putValue(SInfoResultType.waterlevel, mnw);
        row.putValue(SInfoResultType.waterlevel1, mw);
        row.putValue(SInfoResultType.waterlevel2, mhw);
        // Calc salix-line and mw-mnw
        row.putValue(UInfoResultType.salixline, calcSalix(mhw, mw));
        row.putValue(UInfoResultType.salix_delta_mw, calcMwmnw(mw, mnw));
        // Calc scenario values (always all scenario types set, Result variant extracts the fields needed)
        final List<SalixScenario> scenarios = new ArrayList<>();
        final double[] deltaws = getDeltaWs(station, rangeScenarios);
        for (int i = 0; i <= deltaws.length - 1; i++) {
            if (Math.abs(deltaws[i]) < 0.0001) {
                row.putValue(UInfoResultType.salix_line_scenario, Double.NaN);
                row.putValue(UInfoResultType.salix_line_scenario_dwspl, 0); // TODO NaN when changed from int to double
                /* always need to add a member, so the exporter will produce empty columns */
                scenarios.add(null);
            } else {
                final double salix = calcSalix(mhw, mw + deltaws[i]);
                row.putValue(UInfoResultType.salix_line_scenario, salix);
                row.putValue(UInfoResultType.salix_line_scenario_dwspl, (int) (deltaws[i] * 100));
                scenarios.add(new SalixScenario((int) (deltaws[i] * 100), salix));
            }
        }
        row.putValue(UInfoResultType.customMultiRowColSalixRegionalValue_Dwspl, scenarios);
        return row;
    }

    /**
     * Interpolates the W for a station with a fixed (virtual) wst column position
     */
    private double interpolateW(final double station, final QPosition qPosition) {
        if (qPosition != null)
            return this.wst.interpolateW(station, qPosition, this.problems);

        return Double.NaN;
    }

    /**
     * Calculates the salix value
     */
    private double calcSalix(final double mhw, final double mw) {
        return mhw - 2.31 - mw;
    }

    /**
     * Calculates the inverse MW-MNW difference
     */
    private double calcMwmnw(final double mw, final double mnw) {
        return mnw - mw;
    }

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

        final double[] deltaws = new double[stationScenarios.getValue().size()];
        for (int i = 0; i <= stationScenarios.getValue().size() - 1; i++)
            deltaws[i] = stationScenarios.getValue().get(i);
        return deltaws;
    }
}

http://dive4elements.wald.intevation.org