view artifacts/src/main/java/org/dive4elements/river/artifacts/uinfo/salix/SalixLineCalculation.java @ 9597:5395c6d4ca50

Softwaretests...20181219 7.3: no interpolation of missing bed heights for Uinfo/Salix historical scenario and B&U/Bzws
author mschaefer
date Tue, 05 Feb 2019 15:47:58 +0100
parents b9c87bbff6a4
children
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.Collection;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;

import org.apache.commons.lang.math.DoubleRange;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.access.RangeAccess;
import org.dive4elements.river.artifacts.common.GeneralResultType;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.river.RiverInfoProvider;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsUtils;
import org.dive4elements.river.artifacts.sinfo.tkhstate.DefaultBedHeights;
import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
import org.dive4elements.river.artifacts.uinfo.UINFOArtifact;
import org.dive4elements.river.artifacts.uinfo.salix.SalixLineAccess.ScenarioType;
import org.dive4elements.river.model.BedHeight;
import org.dive4elements.river.model.Gauge;
import org.dive4elements.river.model.River;

/**
 * Calculation of a iota (former salix) longitudinal section, optionally with a delta scenario
 *
 * @author Domenico Nardi Tironi
 * @author Matthias Schäfer
 *
 */
final class SalixLineCalculation {

    private final CallContext context;

    private Calculation problems;

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

    /**
     * Calculates the iota longitudinal section and delta scenario of a uinfo artifact
     */
    public CalculationResult calculate(final UINFOArtifact uinfo) {
        this.problems = new Calculation();

        final String calcModeLabel = Resources.getMsg(this.context.getMeta(), uinfo.getCalculationMode().name());
        final String user = CalculationUtils.findArtifactUser(this.context, uinfo);

        final SalixLineAccess accessSalix = new SalixLineAccess(uinfo);

        final River river = accessSalix.getRiver();
        final RiverInfo riverInfo = new RiverInfo(river);

        final DoubleRange range = accessSalix.getRange();

        final ScenarioType scenarioType = accessSalix.getScenario();

        final Gauge firstUpstreamGauge = river.firstUpstreamGauge();
        final DoubleRange maxRange = new DoubleRange(firstUpstreamGauge.getRange().getA(), river.getKmUp() ? -99999.999 : 99999.999);
        final RiverInfoProvider riverInfoProvider1 = RiverInfoProvider.forRange(this.context, river, maxRange);
        final RiverInfoProvider riverInfoProvider = riverInfoProvider1.forReferenceRange(maxRange, false);

        final SalixLineCalculationResults results = new SalixLineCalculationResults(calcModeLabel, user, riverInfo, range);

        final SalixLineCalculator calculator = new SalixLineCalculator(riverInfoProvider);
        final NavigableMap<Double, List<Double>> rangeScenarios = buildRangeScenarios(accessSalix);

        calculator.execute(this.problems, uinfo, rangeScenarios, scenarioType, buildScenarioLabels(accessSalix), buildPartialRangeString(accessSalix),
                buildAdditionalString(accessSalix), results);

        return new CalculationResult(results, this.problems);
    }

    /**
     * Builds a map of delta-Ws by from-km for the selected scenario
     */
    private NavigableMap<Double, List<Double>> buildRangeScenarios(final SalixLineAccess access) {
        final NavigableMap<Double, List<Double>> rangeScenarios = new TreeMap<>();
        if (access.getScenario() == ScenarioType.REGIONAL)
            fillRangeScenarios(rangeScenarios, access, access.getFromPart().doubleValue(), access.getToPart().doubleValue(),
                    access.getRegionalScenarioIntegers());
        else if (access.getScenario() == ScenarioType.SUPRAREGIONAL)
            fillRangeScenarios(rangeScenarios, access.getSupraRegionalString());
        else if (access.getScenario() == ScenarioType.HISTORICAL)
            fillRangeScenarios(rangeScenarios, access, access.getFromPart().doubleValue(), access.getToPart().doubleValue(), access.getBedHeightId());
        else
            fillRangeScenarios(rangeScenarios, access);

        return rangeScenarios;
    }

    /**
     * Fills a map of delta-Ws with only one 0-delta for the whole calc range (no scenario)
     */
    private void fillRangeScenarios(final NavigableMap<Double, List<Double>> rangeScenarios, final RangeAccess calcRange) {
        final List<Double> nulls = new ArrayList<>();
        nulls.add(null);
        rangeScenarios.put(Double.valueOf(calcRange.getLowerKm() - 0.0001), nulls);
    }

    /**
     * Fills a map of delta-Ws by km-range from the regional scenario input data
     */
    private void fillRangeScenarios(final NavigableMap<Double, List<Double>> rangeScenarios, final RangeAccess calcRange, final double partFrom,
            final double partTo, final int[] deltaWs) {
        final List<Double> nulls = new ArrayList<>();
        final List<Double> dwsm = new ArrayList<>();
        for (int i = 0; i <= deltaWs.length - 1; i++) {
            nulls.add(null);
            dwsm.add(deltaWs[i] / 100.0);
        }
        rangeScenarios.put(Double.valueOf(calcRange.getLowerKm() - 0.0001), nulls);
        rangeScenarios.put(Double.valueOf(partFrom - 0.0001), dwsm);
        rangeScenarios.put(Double.valueOf(partTo + 0.0001), nulls);
    }

    /**
     * Fills a map of delta-Ws by km-range from the supraregional scenario input data
     * (the zones input by the user cover the calc range completely)
     */
    private void fillRangeScenarios(final NavigableMap<Double, List<Double>> rangeScenarios, final String zones) {
        final List<SalixZone> parts = SalixZone.parse(zones);
        for (final SalixZone part : parts) {
            final List<Double> dwsm = new ArrayList<>();
            if (part.getDwsplValue() == 0)
                dwsm.add(null);
            else
                dwsm.add(part.getDwsplValue() / 100.0);
            rangeScenarios.put(Double.valueOf(part.getFromKm().doubleValue() - 0.0001), dwsm);
        }
    }

    /**
     * Fetches historical and reference bed levels and fills a map of delta-MSHs for all fetched stations in the calc range
     */
    private void fillRangeScenarios(final NavigableMap<Double, List<Double>> rangeScenarios, final RangeAccess calcRange, final double partFrom,
            final double partTo, final int historicalBedHeightId) {

        // Find relevant default bed-heights
        final River river = calcRange.getRiver();
        final List<BedHeight> defaultBedHeights = new DefaultBedHeights(river).getBedHeights(this.problems);
        if (defaultBedHeights.isEmpty())
            return;
        final DoubleRange scenarioRange = new DoubleRange(partFrom, partTo);
        final Collection<BedHeightsFinder> allFinders = BedHeightsFinder.createScenarioBedHeights(this.problems, scenarioRange, defaultBedHeights);
        final Collection<BedHeightsFinder> currentFinders = new ArrayList<>(allFinders);

        // Add historical bed-heights
        final BedHeightsFinder historicalFinder = BedHeightsFinder.forId(this.problems, historicalBedHeightId, scenarioRange, false);
        allFinders.add(historicalFinder);
        final Collection<Double> stations = BedHeightsUtils.extractStationCollection(allFinders, true);
        final List<Double> nulls = new ArrayList<>();
        nulls.add(null);
        rangeScenarios.put(Double.valueOf(calcRange.getLowerKm() - 0.0001), nulls);
        for (final Double station : stations) {
            rangeScenarios.put(station, new ArrayList<Double>());
            final double delta = bedHeightDifference(station, currentFinders, historicalFinder);
            if (Double.isNaN(delta)) {
                rangeScenarios.get(station).add(null);
                if (!this.problems.hasProblems()) {
                    final String msg = Resources.getMsg(this.context.getMeta(), "uinfo_salix_calc.warning.missing_bedheights");
                    this.problems.addProblem(station, msg);
                }
            } else
                rangeScenarios.get(station).add(Double.valueOf(delta));
        }
        rangeScenarios.put(Double.valueOf(partTo + 0.0001), nulls);
    }

    /**
     * Gets the difference of a historical bed level against a current one for a station
     */
    private double bedHeightDifference(final double station, final Collection<BedHeightsFinder> currentFinders, final BedHeightsFinder historicalFinder) {
        double currentMSH = Double.NaN;
        for (final BedHeightsFinder bhf : currentFinders) {
            currentMSH = bhf.getMeanBedHeight(station);
            if (!Double.isNaN(currentMSH))
                break;
        }
        if (Double.isNaN(currentMSH))
            return Double.NaN;
        final double historicalMSH = historicalFinder.getMeanBedHeight(station);
        if (Double.isNaN(historicalMSH))
            return Double.NaN;
        return (historicalMSH - currentMSH);
    }

    /**
     * Builds the list of delta-w labels for the scenario type
     */
    private String[] buildScenarioLabels(final SalixLineAccess access) {
        final List<String> labels = new ArrayList<>();
        if (access.getScenario() == ScenarioType.REGIONAL) {
            final int[] deltaws = access.getRegionalScenarioIntegers();
            for (int i = 0; i <= deltaws.length - 1; i++)
                if (deltaws[i] != 0)
                    labels.add(Integer.toString(deltaws[i]) + " cm");
        } else if (access.getScenario() == ScenarioType.SUPRAREGIONAL)
            labels.add(Resources.getMsg(this.context.getMeta(), "uinfo_salix_scenario_supraregional"));
        else if (access.getScenario() == ScenarioType.HISTORICAL)
            labels.add(Resources.getMsg(this.context.getMeta(), "uinfo_salix_scenario_historical"));
        return labels.toArray(new String[labels.size()]);
    }

    /**
     * Builds the km range string for the scenario type
     */
    private String buildPartialRangeString(final SalixLineAccess access) {
        if ((access.getScenario() == ScenarioType.REGIONAL) || (access.getScenario() == ScenarioType.HISTORICAL)) {
            return String.format("%s - %s", GeneralResultType.station.exportValue(this.context, access.getFromPart().doubleValue()),
                    GeneralResultType.station.exportValue(this.context, access.getToPart().doubleValue()));
        }
        if (access.getScenario() == ScenarioType.SUPRAREGIONAL) {
            String ranges = "";
            String sep = "";
            final List<SalixZone> parts = SalixZone.parse(access.getSupraRegionalString());
            for (final SalixZone part : parts) {
                if (part.getDwsplValue() != 0) {
                    ranges = ranges + sep + String.format("%s - %s", GeneralResultType.station.exportValue(this.context, part.getFromKm().doubleValue()),
                            GeneralResultType.station.exportValue(this.context, part.getToKm().doubleValue()));
                    sep = ", ";
                }
            }
            return ranges;
        }
        return "";
    }

    /**
     * Builds the delta w or time string for the scenario type
     */
    private String buildAdditionalString(final SalixLineAccess access) {
        if (access.getScenario() == ScenarioType.REGIONAL) {
            String deltas = "";
            String sep = "";
            for (final int d : access.getRegionalScenarioIntegers()) {
                deltas = deltas + sep + Integer.toString(d);
                sep = ", ";
            }
            return deltas;
        }
        if (access.getScenario() == ScenarioType.HISTORICAL) {
            return String.valueOf(access.getYearEpoch());
        }
        if (access.getScenario() == ScenarioType.SUPRAREGIONAL) {
            String deltas = "";
            String sep = "";
            final List<SalixZone> parts = SalixZone.parse(access.getSupraRegionalString());
            for (final SalixZone part : parts) {
                if (part.getDwsplValue() != 0) {
                    deltas = deltas + sep + Integer.toString(part.getDwsplValue());
                    sep = ", ";
                }
            }
            return deltas;
        }
        return "";
    }
}

http://dive4elements.wald.intevation.org