gernotbelger@9145: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@9145: * Software engineering by gernotbelger@9145: * Björnsen Beratende Ingenieure GmbH gernotbelger@9145: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@9145: * gernotbelger@9145: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@9145: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@9145: * documentation coming with Dive4Elements River for details. gernotbelger@9145: */ gernotbelger@9145: package org.dive4elements.river.artifacts.sinfo.flood_duration; gernotbelger@9145: gernotbelger@9145: import java.util.ArrayList; mschaefer@9176: import java.util.HashMap; d@9612: import java.util.HashSet; mschaefer@9202: import java.util.List; mschaefer@9176: import java.util.Map; mschaefer@9202: import java.util.Set; gernotbelger@9145: gernotbelger@9145: import org.apache.commons.lang.math.DoubleRange; gernotbelger@9150: import org.dive4elements.artifacts.CallContext; mschaefer@9202: import org.dive4elements.river.artifacts.WINFOArtifact; mschaefer@9202: import org.dive4elements.river.artifacts.access.ComputationRangeAccess; gernotbelger@9145: import org.dive4elements.river.artifacts.common.GeneralResultType; gernotbelger@9145: import org.dive4elements.river.artifacts.common.ResultRow; gernotbelger@9150: import org.dive4elements.river.artifacts.model.Calculation; mschaefer@9202: import org.dive4elements.river.artifacts.model.Calculation.Problem; mschaefer@9202: import org.dive4elements.river.artifacts.model.CalculationResult; mschaefer@9252: import org.dive4elements.river.artifacts.model.WQDay; mschaefer@9202: import org.dive4elements.river.artifacts.model.WQKms; mschaefer@9269: import org.dive4elements.river.artifacts.model.WstValueTable; mschaefer@9269: import org.dive4elements.river.artifacts.model.WstValueTable.QPosition; mschaefer@9269: import org.dive4elements.river.artifacts.model.WstValueTableFactory; gernotbelger@9499: import org.dive4elements.river.artifacts.model.river.RiverInfoProvider; mschaefer@9176: import org.dive4elements.river.artifacts.sinfo.common.GaugeDurationValuesFinder; gernotbelger@9145: import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType; mschaefer@9625: import org.dive4elements.river.artifacts.sinfo.flood_duration.InfrastructureServerClientXChange.Element; gernotbelger@9205: import org.dive4elements.river.exports.WaterlevelDescriptionBuilder; mschaefer@9176: import org.dive4elements.river.model.Attribute.AttributeKey; mschaefer@9176: import org.dive4elements.river.model.Gauge; mschaefer@9176: import org.dive4elements.river.model.sinfo.InfrastructureValue; gernotbelger@9145: mschaefer@9202: import gnu.trove.TDoubleArrayList; mschaefer@9202: gernotbelger@9145: /** mschaefer@9176: * Calculation of the result rows of the flood duration of the infrastructures in a river km range mschaefer@9176: * and selected main value durations mschaefer@9176: * mschaefer@9176: * @author Matthias Schäfer gernotbelger@9145: */ gernotbelger@9145: final class FloodDurationCalculator { gernotbelger@9145: mschaefer@9259: private final List rows = new ArrayList<>(); gernotbelger@9145: gernotbelger@9145: private final RiverInfoProvider riverInfoProvider; mschaefer@9528: private RiverInfoProvider riverInfoProvider2; mschaefer@9620: private final Map> stationInfras; gernotbelger@9145: gernotbelger@9150: private final CallContext context; gernotbelger@9145: gernotbelger@9150: public FloodDurationCalculator(final CallContext context, final RiverInfoProvider riverInfoProvider) { gernotbelger@9150: this.context = context; gernotbelger@9145: this.riverInfoProvider = riverInfoProvider; mschaefer@9528: this.riverInfoProvider2 = null; mschaefer@9620: this.stationInfras = new HashMap<>(); gernotbelger@9145: } gernotbelger@9145: mschaefer@9176: /** mschaefer@9202: * Calculate the infrastructures flood duration result rows mschaefer@9176: */ mschaefer@9620: public void execute(final Calculation problems, final String label, final DoubleRange calcRange, final AttributeKey riverside, mschaefer@9625: final List infrastructureChoices, final boolean withWspl, final WINFOArtifact winfo, final FloodDurationCalculationResults results) { gernotbelger@9145: mschaefer@9202: // Find all gauges of the calc range, and create the duration finders mschaefer@9176: final Map durFinders = new HashMap<>(); mschaefer@9528: for (final Gauge gauge : this.riverInfoProvider.getGauges()) { mschaefer@9176: durFinders.put(gauge, GaugeDurationValuesFinder.loadValues(gauge, problems)); mschaefer@9176: } gernotbelger@9150: mschaefer@9202: // Find all infrastructures within the calc range mschaefer@9625: final Set choices = new HashSet<>(); mschaefer@9625: for (final Element ifch : infrastructureChoices) mschaefer@9625: choices.add(ifch.getGroupLabel() + "\t" + ifch.getTypeLabel()); mschaefer@9202: final List infras = InfrastructureValue.getValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(), mschaefer@9625: calcRange.getMaximumDouble(), riverside, choices); gernotbelger@9150: mschaefer@9202: // Merge all stations (range/step, borders of gauge ranges, infrastructures) mschaefer@9620: // final Map allStations = new HashMap<>(); mschaefer@9620: this.stationInfras.clear(); mschaefer@9620: // final Map secondBank = new HashMap<>(); // any second infrastructure in case of mschaefer@9620: // both-banks-option mschaefer@9620: addRangeStations(winfo); mschaefer@9620: addGaugeLimits(durFinders.keySet(), calcRange.getMinimumDouble(), calcRange.getMaximumDouble()); mschaefer@9620: addInfrastructures(infras); mschaefer@9620: final double[] stationsSorted = sortStations(this.stationInfras.keySet()); gernotbelger@9150: mschaefer@9286: // Calculate W and Q for all stations and the selected discharge states/waterlevels mschaefer@9398: final WQKms[] wqkmsArray = calculateWsts(winfo, withWspl, stationsSorted, problems); mschaefer@9528: // final WaterlevelData waterlevel = new WaterlevelData(wqkmsArray[0], -1, false, true); mschaefer@9528: // this.riverInfoProvider2 = this.riverInfoProvider.forWaterlevel(waterlevel); mschaefer@9528: this.riverInfoProvider2 = this.riverInfoProvider.forReferenceRange(calcRange, false); mschaefer@9528: // this.riverInfoProvider2.cleanupGaugesAtStart(calcRange); mschaefer@9202: mschaefer@9286: // Determine discharge state labels of the waterlevels mschaefer@9286: updateWstLabels(wqkmsArray, winfo, problems); mschaefer@9202: mschaefer@9286: final Map> gaugeWstDurations = new HashMap<>(); mschaefer@9398: if (withWspl) mschaefer@9398: calcGaugeWstDurations(winfo, new ArrayList<>(durFinders.keySet()), gaugeWstDurations, durFinders); mschaefer@9286: mschaefer@9286: // Load base wst table (river).wst mschaefer@9286: // (should be in cache since already used in calculateWaterlevels (winfo.computeWaterlevelData) mschaefer@9528: final WstValueTable wst = WstValueTableFactory.getTable(this.riverInfoProvider2.getRiver()); mschaefer@9202: mschaefer@9620: final Set infrastructures = new HashSet<>(); d@9612: mschaefer@9286: // Create the result rows, and calculate and add the flood durations etc. mschaefer@9631: ResultRow row; mschaefer@9631: boolean starting; mschaefer@9202: for (int i = 0; i <= stationsSorted.length - 1; i++) { mschaefer@9528: final Gauge gauge = this.riverInfoProvider2.getGauge(stationsSorted[i], true); mschaefer@9631: row = createRow(stationsSorted[i], wqkmsArray, gaugeWstDurations.get(gauge), i); mschaefer@9631: starting = true; mschaefer@9620: if (this.stationInfras.containsKey(stationsSorted[i])) { mschaefer@9620: for (final InfrastructureValue infra : this.stationInfras.get(stationsSorted[i])) { mschaefer@9631: if (!starting) mschaefer@9631: row = createRow(stationsSorted[i], wqkmsArray, gaugeWstDurations.get(gauge), i); mschaefer@9620: calculateInfrastructure(row, gauge, infra, wst, durFinders, infrastructures); mschaefer@9620: this.rows.add(row); mschaefer@9631: starting = false; mschaefer@9620: } mschaefer@9202: } mschaefer@9631: if (starting) mschaefer@9631: this.rows.add(row); mschaefer@9202: } mschaefer@9269: mschaefer@9286: // Get the labels of the selected waterlevels mschaefer@9286: final String[] wstLabels = new String[wqkmsArray.length]; mschaefer@9286: for (int i = 0; i <= wqkmsArray.length - 1; i++) mschaefer@9286: wstLabels[i] = wqkmsArray[i].getName(); mschaefer@9269: d@9612: results.addResult(new FloodDurationCalculationResult(label, wstLabels, this.rows, withWspl, infrastructures), problems); mschaefer@9252: } mschaefer@9202: mschaefer@9257: /** mschaefer@9266: * Calculates the duration curve for a station mschaefer@9286: * (other than the version 3.2.1 W-Info Dauerlinie the wst column positions mschaefer@9286: * are taken from the Q values of the gauge's Q-D-table) mschaefer@9257: */ mschaefer@9266: public WQDay calcWQDays(final Calculation problems, final double station, final WINFOArtifact winfo) { mschaefer@9252: mschaefer@9534: final CalculationResult res = winfo.getDurationCurveData(); mschaefer@9534: final WQDay wqday = (WQDay) res.getData(); mschaefer@9534: if (wqday == null) mschaefer@9534: return null; mschaefer@9534: mschaefer@9534: final int[] odays = new int[wqday.size()]; mschaefer@9534: for (int i = 0; i <= odays.length - 1; i++) mschaefer@9594: odays[i] = 365 - wqday.getDay(i); // TODO Eigentlich 365.25, ist aber mit getDay als int sinnlos mschaefer@9534: return new WQDay(odays, wqday.getWs(), wqday.getQs()); gernotbelger@9145: } gernotbelger@9145: mschaefer@9176: /** mschaefer@9202: * Adds to a stations map all stations corresponding to the active range and step mschaefer@9176: */ mschaefer@9620: private void addRangeStations(final WINFOArtifact winfo) { mschaefer@9202: for (final double station : new ComputationRangeAccess(winfo).getKms()) mschaefer@9620: this.stationInfras.put(Double.valueOf(station), new ArrayList()); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Adds to a stations map all range limits of the gauges within the calc range mschaefer@9202: */ mschaefer@9620: private void addGaugeLimits(final Set gauges, final double fromKm, final double toKm) { mschaefer@9202: for (final Gauge gauge : gauges) { mschaefer@9202: final Double kmA = Double.valueOf(gauge.getRange().getA().doubleValue()); mschaefer@9202: final Double kmB = Double.valueOf(gauge.getRange().getB().doubleValue()); mschaefer@9202: if (kmA > fromKm - 0.0001) mschaefer@9620: this.stationInfras.put(kmA, new ArrayList()); mschaefer@9202: if (kmB < toKm + 0.0001) mschaefer@9620: this.stationInfras.put(kmB, new ArrayList()); mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9620: * Adds all infrastructures of a station to the station map mschaefer@9202: */ mschaefer@9620: private void addInfrastructures(final List infrastructures) { mschaefer@9202: for (final InfrastructureValue infrastructure : infrastructures) { mschaefer@9202: final Double station = infrastructure.getStation(); mschaefer@9620: if (this.stationInfras.containsKey(station)) mschaefer@9620: this.stationInfras.get(station).add(infrastructure); mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Returns a double array with a sorted stations set mschaefer@9202: */ mschaefer@9202: private double[] sortStations(final Set stations) { mschaefer@9202: final TDoubleArrayList sorted = new TDoubleArrayList(); mschaefer@9202: for (final Double station : stations) mschaefer@9202: sorted.add(station.doubleValue()); mschaefer@9202: sorted.sort(); mschaefer@9202: return sorted.toNativeArray(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Calculates an array of w-q-longitudinal sections for all artifact W/Q options mschaefer@9202: */ mschaefer@9398: private WQKms[] calculateWsts(final WINFOArtifact winfo, final boolean withWspl, final double[] stations, final Calculation problems) { mschaefer@9286: // First run may take long, further runs are faster since WstValueTable is in cache then mschaefer@9202: // (So funktioniert computeWaterlevelData wohl: mschaefer@9229: // Es sucht die Spalte(n) zum Bezugspegel-Q in der W-Q-Tabelle ({river}.wst in Wst etc.), mschaefer@9229: // interpoliert die horizontale Tabellenposition (Q) und dann die vertikale Tabellenposition der station; mschaefer@9202: // das ergibt das W einer station für einen Abflusszustand; mschaefer@9202: // bei Vorgabe eines Pegel-W wird vorher anhand der W-Q-Tabelle des Pegels ({gauge}.at in DischargeTable) das Q mschaefer@9202: // interpoliert; mschaefer@9229: // bei Vorgabe eines W auf freier Strecke wird wohl vorher noch die .wst-Interpolation eingesetzt, um das Q zu bekommen. mschaefer@9229: mschaefer@9398: if (!withWspl) mschaefer@9398: return new WQKms[] {}; mschaefer@9398: mschaefer@9286: final CalculationResult wstsData = winfo.computeWaterlevelData(stations); mschaefer@9202: mschaefer@9202: /* copy all problems */ mschaefer@9286: final Calculation winfoProblems = wstsData.getReport(); mschaefer@9202: final List problems2 = winfoProblems.getProblems(); mschaefer@9202: if (problems2 != null) { mschaefer@9202: for (final Problem problem : problems2) { mschaefer@9202: problems.addProblem(problem); mschaefer@9202: } mschaefer@9202: } mschaefer@9286: return (WQKms[]) wstsData.getData(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9259: * Determines the waterlevel/discharge state labels for the selected Q or W values and sets them in the WQKms array mschaefer@9202: */ mschaefer@9286: private void updateWstLabels(final WQKms[] wqkmsArray, final WINFOArtifact winfo, final Calculation problems) { gernotbelger@9205: mschaefer@9229: for (int i = 0; i <= wqkmsArray.length - 1; i++) mschaefer@9259: wqkmsArray[i].setName(buildWQDescription(wqkmsArray[i], winfo)); mschaefer@9259: } mschaefer@9259: mschaefer@9259: /** mschaefer@9286: * Builds the description label of a waterlevel mschaefer@9259: */ mschaefer@9259: private String buildWQDescription(final WQKms wqkms, final WINFOArtifact winfo) { mschaefer@9286: mschaefer@9259: final WaterlevelDescriptionBuilder descBuilder = new WaterlevelDescriptionBuilder(winfo, this.context); mschaefer@9286: // TODO Zwischen numerischem Q-Wert und Dauerzahl-Hauptwert (0..364) unterscheiden mschaefer@9259: final String description = descBuilder.getDesc(wqkms); mschaefer@9259: if (!description.isEmpty() && Character.isDigit(description.charAt(0))) { mschaefer@9259: if (winfo.isQ()) mschaefer@9259: return "Q=" + description; mschaefer@9259: else mschaefer@9259: return "W=" + description; gernotbelger@9312: } else mschaefer@9259: return description; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9286: * Calculates the flood durations of the Qs of the waterlevels/discharge states for a map of gauges mschaefer@9286: */ mschaefer@9286: private void calcGaugeWstDurations(final WINFOArtifact winfo, final List gauges, final Map> gaugeWstDurations, mschaefer@9286: final Map durFinders) { mschaefer@9286: mschaefer@9286: final double[] gaugeKms = new double[gauges.size()]; mschaefer@9286: for (int i = 0; i <= gauges.size() - 1; i++) { mschaefer@9286: gaugeKms[i] = gauges.get(i).getStation().doubleValue(); mschaefer@9286: gaugeWstDurations.put(gauges.get(i), new ArrayList()); mschaefer@9286: } mschaefer@9286: final CalculationResult wstsData = winfo.computeWaterlevelData(gaugeKms); mschaefer@9286: final WQKms[] wsts = (WQKms[]) wstsData.getData(); mschaefer@9286: for (int i = 0; i <= gauges.size() - 1; i++) { mschaefer@9286: final GaugeDurationValuesFinder durFinder = durFinders.get(gauges.get(i)); mschaefer@9286: for (int j = 0; j <= wsts.length - 1; j++) { mschaefer@9286: final double d = durFinder.getDuration(wsts[j].getQ(i)); mschaefer@9286: gaugeWstDurations.get(gauges.get(i)).add(Double.valueOf(underflowDaysToOverflowDays(d))); mschaefer@9286: } mschaefer@9286: } mschaefer@9286: } mschaefer@9286: mschaefer@9286: /** mschaefer@9398: * Create a result row for a station, and add w-q-values as selected mschaefer@9202: */ mschaefer@9398: private ResultRow createRow(final Double station, final WQKms[] wqkmsArray, final List gaugeDurations, final int kmIndex) { gernotbelger@9145: gernotbelger@9145: final ResultRow row = ResultRow.create(); mschaefer@9202: row.putValue(GeneralResultType.station, station); mschaefer@9614: row.putValue(SInfoResultType.infrastructuregroup, null); // is replaced later for an infrastructure type mschaefer@9614: row.putValue(SInfoResultType.infrastructuretype, null); // is replaced later for an infrastructure part mschaefer@9202: row.putValue(SInfoResultType.floodDuration, Double.NaN); // is replaced later for an infrastructure gernotbelger@9205: mschaefer@9528: final String gaugeLabel = this.riverInfoProvider2.findGauge(station); gernotbelger@9318: row.putValue(GeneralResultType.gaugeLabel, gaugeLabel); gernotbelger@9205: mschaefer@9528: final String location = this.riverInfoProvider2.getLocation(station); gernotbelger@9312: row.putValue(GeneralResultType.location, location); gernotbelger@9145: mschaefer@9286: final List wsts = new ArrayList<>(wqkmsArray.length); gernotbelger@9205: mschaefer@9286: for (int i = 0; i <= wqkmsArray.length - 1; i++) { mschaefer@9357: final DurationWaterlevel dw = new DurationWaterlevel(wqkmsArray[i].getW(kmIndex), gaugeDurations.get(i), wqkmsArray[i].getQ(kmIndex), mschaefer@9286: wqkmsArray[i].getName()); mschaefer@9286: wsts.add(dw); mschaefer@9202: } mschaefer@9286: row.putValue(SInfoResultType.customMultiRowColWaterlevel, wsts); gernotbelger@9205: mschaefer@9202: return row; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Calculate the result row fields for one infrastructure mschaefer@9202: */ mschaefer@9269: private void calculateInfrastructure(final ResultRow row, final Gauge gauge, final InfrastructureValue infrastructure, final WstValueTable wst, mschaefer@9620: final Map durFinders, final Set infrastructures) { mschaefer@9202: mschaefer@9269: // Interpolate the infrastructure height in the wst table to get the corresponding Q mschaefer@9269: final Calculation problems = new Calculation(); mschaefer@9269: final double[] qs = wst.findQsForW(infrastructure.getStation().doubleValue(), infrastructure.getHeight().doubleValue(), problems); mschaefer@9269: // TODO Fehlerbehandlung (kein Q gefunden) mschaefer@9269: final double q = (qs.length >= 1) ? qs[0] : Double.NaN; mschaefer@9308: // Set the result row mschaefer@9308: row.putValue(SInfoResultType.riverside, infrastructure.getAttributeKey()); mschaefer@9308: row.putValue(SInfoResultType.floodDischarge, q); mschaefer@9308: row.putValue(SInfoResultType.infrastructureHeight, infrastructure.getHeight()); mschaefer@9614: row.putValue(SInfoResultType.infrastructuregroup, infrastructure.getInfrastructure().getGroup().getName()); mschaefer@9308: row.putValue(SInfoResultType.infrastructuretype, infrastructure.getInfrastructure().getType().getName()); d@9612: mschaefer@9286: // Determine the relative column position of the Q of the infrastructure height mschaefer@9269: final QPosition qPos = wst.getQPosition(infrastructure.getStation().doubleValue(), q); mschaefer@9620: if (qPos != null) { mschaefer@9620: // Get the Q for the found column position for the station of the gauge mschaefer@9620: final double qGauge = wst.getQ(qPos, gauge.getStation().doubleValue()); mschaefer@9620: // Interpolate the Q-D-table of the gauge mschaefer@9620: final double dur = underflowDaysToOverflowDays(durFinders.get(gauge).getDuration(qGauge)); mschaefer@9620: // Set D in the result row mschaefer@9620: row.putValue(SInfoResultType.floodDuration, dur); mschaefer@9620: } mschaefer@9620: final FloodDurationInfrastructureChoice groupType = new FloodDurationInfrastructureChoice(row); mschaefer@9614: infrastructures.add(groupType); mschaefer@9202: } gernotbelger@9145: mschaefer@9202: /** mschaefer@9202: * Translates underflow duration into overflow duration mschaefer@9202: */ mschaefer@9202: private double underflowDaysToOverflowDays(final double underflowDays) { mschaefer@9594: return 365.25 - underflowDays; gernotbelger@9145: } gernotbelger@9145: }