gernotbelger@8996: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@8996: * Software engineering by gernotbelger@8996: * Björnsen Beratende Ingenieure GmbH gernotbelger@8996: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@8996: * gernotbelger@8996: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@8996: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@8996: * documentation coming with Dive4Elements River for details. gernotbelger@8996: */ gernotbelger@8996: package org.dive4elements.river.artifacts.uinfo.salix; gernotbelger@8996: mschaefer@9309: import java.util.ArrayList; mschaefer@9394: import java.util.Collection; mschaefer@9309: import java.util.List; mschaefer@9309: import java.util.NavigableMap; mschaefer@9309: import java.util.TreeMap; gernotbelger@8996: gernotbelger@9321: import org.apache.commons.lang.math.DoubleRange; gernotbelger@8996: import org.dive4elements.artifacts.CallContext; mschaefer@9309: import org.dive4elements.river.artifacts.access.RangeAccess; mschaefer@9361: import org.dive4elements.river.artifacts.common.GeneralResultType; gernotbelger@8996: import org.dive4elements.river.artifacts.model.Calculation; gernotbelger@8996: import org.dive4elements.river.artifacts.model.CalculationResult; gernotbelger@9499: import org.dive4elements.river.artifacts.model.river.RiverInfoProvider; gernotbelger@8996: import org.dive4elements.river.artifacts.resources.Resources; mschaefer@9394: import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder; mschaefer@9394: import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsUtils; mschaefer@9394: import org.dive4elements.river.artifacts.sinfo.tkhstate.DefaultBedHeights; gernotbelger@8996: import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; gernotbelger@8996: import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; gernotbelger@8996: import org.dive4elements.river.artifacts.uinfo.UINFOArtifact; mschaefer@9309: import org.dive4elements.river.artifacts.uinfo.salix.SalixLineAccess.ScenarioType; mschaefer@9394: import org.dive4elements.river.model.BedHeight; mschaefer@9529: import org.dive4elements.river.model.Gauge; gernotbelger@8996: import org.dive4elements.river.model.River; gernotbelger@8996: gernotbelger@8996: /** mschaefer@9394: * Calculation of a iota (former salix) longitudinal section, optionally with a delta scenario mschaefer@9394: * gernotbelger@8996: * @author Domenico Nardi Tironi mschaefer@9394: * @author Matthias Schäfer gernotbelger@8996: * gernotbelger@8996: */ gernotbelger@8996: final class SalixLineCalculation { gernotbelger@8996: gernotbelger@8996: private final CallContext context; gernotbelger@8996: mschaefer@9394: private Calculation problems; mschaefer@9394: gernotbelger@8996: public SalixLineCalculation(final CallContext context) { gernotbelger@8996: this.context = context; gernotbelger@8996: } gernotbelger@8996: mschaefer@9394: /** mschaefer@9394: * Calculates the iota longitudinal section and delta scenario of a uinfo artifact mschaefer@9394: */ gernotbelger@8996: public CalculationResult calculate(final UINFOArtifact uinfo) { mschaefer@9394: this.problems = new Calculation(); gernotbelger@8996: gernotbelger@8996: final String calcModeLabel = Resources.getMsg(this.context.getMeta(), uinfo.getCalculationMode().name()); gernotbelger@8996: final String user = CalculationUtils.findArtifactUser(this.context, uinfo); gernotbelger@8996: gernotbelger@9070: final SalixLineAccess accessSalix = new SalixLineAccess(uinfo); gernotbelger@9070: gernotbelger@9070: final River river = accessSalix.getRiver(); gernotbelger@8996: final RiverInfo riverInfo = new RiverInfo(river); gernotbelger@8996: gernotbelger@9321: final DoubleRange range = accessSalix.getRange(); gernotbelger@9068: gernotbelger@9321: final ScenarioType scenarioType = accessSalix.getScenario(); gernotbelger@9070: mschaefer@9529: final Gauge firstUpstreamGauge = river.firstUpstreamGauge(); mschaefer@9529: final DoubleRange maxRange = new DoubleRange(firstUpstreamGauge.getRange().getA(), river.getKmUp() ? -99999.999 : 99999.999); mschaefer@9529: final RiverInfoProvider riverInfoProvider1 = RiverInfoProvider.forRange(this.context, river, maxRange); mschaefer@9529: final RiverInfoProvider riverInfoProvider = riverInfoProvider1.forReferenceRange(maxRange, false); gernotbelger@8996: gernotbelger@9321: final SalixLineCalculationResults results = new SalixLineCalculationResults(calcModeLabel, user, riverInfo, range); gernotbelger@9321: gernotbelger@9321: final SalixLineCalculator calculator = new SalixLineCalculator(riverInfoProvider); mschaefer@9309: final NavigableMap> rangeScenarios = buildRangeScenarios(accessSalix); mschaefer@9309: mschaefer@9394: calculator.execute(this.problems, uinfo, rangeScenarios, scenarioType, buildScenarioLabels(accessSalix), buildPartialRangeString(accessSalix), mschaefer@9361: buildAdditionalString(accessSalix), results); gernotbelger@9243: mschaefer@9394: return new CalculationResult(results, this.problems); gernotbelger@8996: } gernotbelger@9243: mschaefer@9309: /** mschaefer@9394: * Builds a map of delta-Ws by from-km for the selected scenario mschaefer@9309: */ mschaefer@9309: private NavigableMap> buildRangeScenarios(final SalixLineAccess access) { mschaefer@9309: final NavigableMap> rangeScenarios = new TreeMap<>(); mschaefer@9309: if (access.getScenario() == ScenarioType.REGIONAL) mschaefer@9309: fillRangeScenarios(rangeScenarios, access, access.getFromPart().doubleValue(), access.getToPart().doubleValue(), mschaefer@9309: access.getRegionalScenarioIntegers()); mschaefer@9309: else if (access.getScenario() == ScenarioType.SUPRAREGIONAL) mschaefer@9309: fillRangeScenarios(rangeScenarios, access.getSupraRegionalString()); mschaefer@9394: else if (access.getScenario() == ScenarioType.HISTORICAL) mschaefer@9394: fillRangeScenarios(rangeScenarios, access, access.getFromPart().doubleValue(), access.getToPart().doubleValue(), access.getBedHeightId()); mschaefer@9309: else mschaefer@9309: fillRangeScenarios(rangeScenarios, access); gernotbelger@9321: mschaefer@9309: return rangeScenarios; mschaefer@9309: } mschaefer@9309: mschaefer@9309: /** mschaefer@9394: * Fills a map of delta-Ws with only one 0-delta for the whole calc range (no scenario) mschaefer@9309: */ mschaefer@9309: private void fillRangeScenarios(final NavigableMap> rangeScenarios, final RangeAccess calcRange) { mschaefer@9309: final List nulls = new ArrayList<>(); mschaefer@9394: nulls.add(null); mschaefer@9309: rangeScenarios.put(Double.valueOf(calcRange.getLowerKm() - 0.0001), nulls); mschaefer@9309: } mschaefer@9309: mschaefer@9309: /** mschaefer@9394: * Fills a map of delta-Ws by km-range from the regional scenario input data mschaefer@9309: */ gernotbelger@9499: private void fillRangeScenarios(final NavigableMap> rangeScenarios, final RangeAccess calcRange, final double partFrom, gernotbelger@9499: final double partTo, final int[] deltaWs) { mschaefer@9309: final List nulls = new ArrayList<>(); mschaefer@9309: final List dwsm = new ArrayList<>(); mschaefer@9309: for (int i = 0; i <= deltaWs.length - 1; i++) { mschaefer@9394: nulls.add(null); mschaefer@9309: dwsm.add(deltaWs[i] / 100.0); mschaefer@9309: } mschaefer@9309: rangeScenarios.put(Double.valueOf(calcRange.getLowerKm() - 0.0001), nulls); mschaefer@9309: rangeScenarios.put(Double.valueOf(partFrom - 0.0001), dwsm); mschaefer@9309: rangeScenarios.put(Double.valueOf(partTo + 0.0001), nulls); mschaefer@9309: } mschaefer@9309: mschaefer@9309: /** mschaefer@9394: * Fills a map of delta-Ws by km-range from the supraregional scenario input data mschaefer@9309: * (the zones input by the user cover the calc range completely) mschaefer@9309: */ mschaefer@9309: private void fillRangeScenarios(final NavigableMap> rangeScenarios, final String zones) { mschaefer@9309: final List parts = SalixZone.parse(zones); mschaefer@9309: for (final SalixZone part : parts) { mschaefer@9309: final List dwsm = new ArrayList<>(); mschaefer@9394: if (part.getDwsplValue() == 0) mschaefer@9394: dwsm.add(null); mschaefer@9394: else mschaefer@9394: dwsm.add(part.getDwsplValue() / 100.0); mschaefer@9309: rangeScenarios.put(Double.valueOf(part.getFromKm().doubleValue() - 0.0001), dwsm); mschaefer@9309: } mschaefer@9309: } mschaefer@9309: mschaefer@9316: /** gernotbelger@9573: * Fetches historical and reference bed levels and fills a map of delta-MSHs for all fetched stations in the calc range mschaefer@9394: */ gernotbelger@9499: private void fillRangeScenarios(final NavigableMap> rangeScenarios, final RangeAccess calcRange, final double partFrom, gernotbelger@9499: final double partTo, final int historicalBedHeightId) { mschaefer@9394: mschaefer@9394: // Find relevant default bed-heights mschaefer@9394: final River river = calcRange.getRiver(); mschaefer@9394: final List defaultBedHeights = new DefaultBedHeights(river).getBedHeights(this.problems); mschaefer@9394: if (defaultBedHeights.isEmpty()) mschaefer@9394: return; mschaefer@9394: final DoubleRange scenarioRange = new DoubleRange(partFrom, partTo); mschaefer@9394: final Collection allFinders = BedHeightsFinder.createTkhBedHeights(this.problems, scenarioRange, defaultBedHeights); mschaefer@9394: final Collection currentFinders = new ArrayList<>(allFinders); mschaefer@9394: mschaefer@9394: // Add historical bed-heights mschaefer@9394: final BedHeightsFinder historicalFinder = BedHeightsFinder.forId(this.problems, historicalBedHeightId, scenarioRange); mschaefer@9394: allFinders.add(historicalFinder); mschaefer@9394: final Collection stations = BedHeightsUtils.extractStationCollection(allFinders, true); mschaefer@9394: final List nulls = new ArrayList<>(); mschaefer@9394: nulls.add(null); mschaefer@9394: rangeScenarios.put(Double.valueOf(calcRange.getLowerKm() - 0.0001), nulls); mschaefer@9394: for (final Double station : stations) { mschaefer@9394: rangeScenarios.put(station, new ArrayList()); mschaefer@9421: final double delta = bedHeightDifference(station, currentFinders, historicalFinder); mschaefer@9421: if (Double.isNaN(delta)) { mschaefer@9421: rangeScenarios.get(station).add(null); mschaefer@9421: if (!this.problems.hasProblems()) { mschaefer@9421: final String msg = Resources.getMsg(this.context.getMeta(), "uinfo_salix_calc.warning.missing_bedheights"); mschaefer@9421: this.problems.addProblem(station, msg); mschaefer@9421: } gernotbelger@9499: } else mschaefer@9421: rangeScenarios.get(station).add(Double.valueOf(delta)); mschaefer@9394: } mschaefer@9394: rangeScenarios.put(Double.valueOf(partTo + 0.0001), nulls); mschaefer@9394: } mschaefer@9394: mschaefer@9394: /** gernotbelger@9573: * Gets the difference of a historical bed level against a current one for a station mschaefer@9394: */ mschaefer@9394: private double bedHeightDifference(final double station, final Collection currentFinders, final BedHeightsFinder historicalFinder) { mschaefer@9394: double currentMSH = Double.NaN; mschaefer@9394: for (final BedHeightsFinder bhf : currentFinders) { mschaefer@9394: currentMSH = bhf.getMeanBedHeight(station); mschaefer@9394: if (!Double.isNaN(currentMSH)) mschaefer@9394: break; mschaefer@9394: } mschaefer@9421: if (Double.isNaN(currentMSH)) mschaefer@9421: return Double.NaN; mschaefer@9421: final double historicalMSH = historicalFinder.getMeanBedHeight(station); mschaefer@9421: if (Double.isNaN(historicalMSH)) mschaefer@9421: return Double.NaN; mschaefer@9504: return (historicalMSH - currentMSH); mschaefer@9394: } mschaefer@9394: mschaefer@9394: /** mschaefer@9394: * Builds the list of delta-w labels for the scenario type mschaefer@9316: */ mschaefer@9316: private String[] buildScenarioLabels(final SalixLineAccess access) { mschaefer@9316: final List labels = new ArrayList<>(); mschaefer@9316: if (access.getScenario() == ScenarioType.REGIONAL) { mschaefer@9316: final int[] deltaws = access.getRegionalScenarioIntegers(); mschaefer@9316: for (int i = 0; i <= deltaws.length - 1; i++) mschaefer@9316: if (deltaws[i] != 0) mschaefer@9316: labels.add(Integer.toString(deltaws[i]) + " cm"); gernotbelger@9391: } else if (access.getScenario() == ScenarioType.SUPRAREGIONAL) mschaefer@9361: labels.add(Resources.getMsg(this.context.getMeta(), "uinfo_salix_scenario_supraregional")); mschaefer@9361: else if (access.getScenario() == ScenarioType.HISTORICAL) mschaefer@9361: labels.add(Resources.getMsg(this.context.getMeta(), "uinfo_salix_scenario_historical")); mschaefer@9316: return labels.toArray(new String[labels.size()]); gernotbelger@9243: } mschaefer@9361: mschaefer@9361: /** mschaefer@9394: * Builds the km range string for the scenario type mschaefer@9361: */ mschaefer@9361: private String buildPartialRangeString(final SalixLineAccess access) { mschaefer@9361: if ((access.getScenario() == ScenarioType.REGIONAL) || (access.getScenario() == ScenarioType.HISTORICAL)) { mschaefer@9361: return String.format("%s - %s", GeneralResultType.station.exportValue(this.context, access.getFromPart().doubleValue()), mschaefer@9361: GeneralResultType.station.exportValue(this.context, access.getToPart().doubleValue())); mschaefer@9361: } mschaefer@9361: if (access.getScenario() == ScenarioType.SUPRAREGIONAL) { mschaefer@9361: String ranges = ""; mschaefer@9361: String sep = ""; mschaefer@9361: final List parts = SalixZone.parse(access.getSupraRegionalString()); mschaefer@9361: for (final SalixZone part : parts) { mschaefer@9361: if (part.getDwsplValue() != 0) { mschaefer@9361: ranges = ranges + sep + String.format("%s - %s", GeneralResultType.station.exportValue(this.context, part.getFromKm().doubleValue()), mschaefer@9361: GeneralResultType.station.exportValue(this.context, part.getToKm().doubleValue())); mschaefer@9361: sep = ", "; mschaefer@9361: } mschaefer@9361: } mschaefer@9361: return ranges; mschaefer@9361: } mschaefer@9361: return ""; mschaefer@9361: } mschaefer@9361: mschaefer@9361: /** mschaefer@9394: * Builds the delta w or time string for the scenario type mschaefer@9361: */ mschaefer@9361: private String buildAdditionalString(final SalixLineAccess access) { mschaefer@9361: if (access.getScenario() == ScenarioType.REGIONAL) { mschaefer@9361: String deltas = ""; mschaefer@9361: String sep = ""; mschaefer@9361: for (final int d : access.getRegionalScenarioIntegers()) { mschaefer@9361: deltas = deltas + sep + Integer.toString(d); mschaefer@9361: sep = ", "; mschaefer@9361: } mschaefer@9361: return deltas; mschaefer@9361: } mschaefer@9361: if (access.getScenario() == ScenarioType.HISTORICAL) { gernotbelger@9391: return String.valueOf(access.getYearEpoch()); mschaefer@9361: } mschaefer@9361: if (access.getScenario() == ScenarioType.SUPRAREGIONAL) { mschaefer@9361: String deltas = ""; mschaefer@9361: String sep = ""; mschaefer@9361: final List parts = SalixZone.parse(access.getSupraRegionalString()); mschaefer@9361: for (final SalixZone part : parts) { mschaefer@9361: if (part.getDwsplValue() != 0) { mschaefer@9361: deltas = deltas + sep + Integer.toString(part.getDwsplValue()); mschaefer@9361: sep = ", "; mschaefer@9361: } mschaefer@9361: } mschaefer@9361: return deltas; mschaefer@9361: } mschaefer@9361: return ""; mschaefer@9361: } gernotbelger@8996: }