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; gernotbelger@9145: import java.util.Collection; mschaefer@9176: import java.util.HashMap; 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@9202: import org.dive4elements.river.artifacts.model.WQKms; mschaefer@9176: import org.dive4elements.river.artifacts.sinfo.common.GaugeDurationValuesFinder; mschaefer@9202: import org.dive4elements.river.artifacts.sinfo.common.GaugeMainValueFinder; gernotbelger@9145: import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider; gernotbelger@9145: import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType; mschaefer@9176: import org.dive4elements.river.artifacts.sinfo.common.WQBaseTableFinder; mschaefer@9176: import org.dive4elements.river.artifacts.sinfo.flood_duration.RiversideRadioChoice.RiversideChoiceKey; 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@9202: import org.dive4elements.river.model.MainValueType.MainValueTypeKey; 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: gernotbelger@9145: private final Collection rows = new ArrayList<>(); gernotbelger@9145: gernotbelger@9145: private final RiverInfoProvider riverInfoProvider; 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; gernotbelger@9145: } gernotbelger@9145: mschaefer@9176: /** mschaefer@9202: * Calculate the infrastructures flood duration result rows mschaefer@9176: */ mschaefer@9202: public FloodDurationCalculationResult execute(final Calculation problems, final String label, final DoubleRange calcRange, mschaefer@9202: final RiversideChoiceKey riverside, final WINFOArtifact winfo) { gernotbelger@9145: mschaefer@9202: // Find all gauges of the calc range, and create the duration finders mschaefer@9176: final Map durFinders = new HashMap<>(); mschaefer@9202: Gauge firstGauge = null; mschaefer@9176: for (final Gauge gauge : this.riverInfoProvider.getRiver().determineGauges(calcRange.getMinimumDouble(), calcRange.getMaximumDouble())) { mschaefer@9176: durFinders.put(gauge, GaugeDurationValuesFinder.loadValues(gauge, problems)); mschaefer@9202: if (firstGauge == null) mschaefer@9202: firstGauge = gauge; mschaefer@9176: } gernotbelger@9150: mschaefer@9202: // Find all infrastructures within the calc range mschaefer@9202: final AttributeKey bankKey = riverside.getAttributeKey(); mschaefer@9202: final List infras = InfrastructureValue.getValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(), mschaefer@9202: calcRange.getMaximumDouble(), bankKey); gernotbelger@9150: mschaefer@9202: // Merge all stations (range/step, borders of gauge ranges, infrastructures) mschaefer@9202: final Map allStations = new HashMap<>(); mschaefer@9202: final Map secondBank = new HashMap<>(); // any second infrastructure in case of both-banks-option gernotbelger@9215: // FIXME: check, do we really need all stations? compare with tkh... mschaefer@9202: addRangeStations(allStations, winfo); mschaefer@9202: addGaugeLimits(allStations, durFinders.keySet(), calcRange.getMinimumDouble(), calcRange.getMaximumDouble()); mschaefer@9202: addInfrastructures(allStations, secondBank, infras); mschaefer@9202: final double[] stationsSorted = sortStations(allStations.keySet()); gernotbelger@9150: mschaefer@9202: // Calculate W and Q for all stations and the selected discharge states mschaefer@9202: final WQKms[] wqkmsArray = calculateWaterlevels(winfo, stationsSorted, problems); mschaefer@9202: mschaefer@9202: // Determine discharge state labels of the main values mschaefer@9202: final String[] mainValueLabels = findMainValueLabels(wqkmsArray, winfo.getQs(), firstGauge, problems); mschaefer@9202: mschaefer@9202: // Create a finder for Q in the {river}.wst km-w-q table mschaefer@9202: final WQBaseTableFinder wqFinder = WQBaseTableFinder.loadValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(), mschaefer@9202: calcRange.getMaximumDouble(), problems); mschaefer@9202: gernotbelger@9205: final WaterlevelDescriptionBuilder descBuilder = new WaterlevelDescriptionBuilder(winfo, this.context); gernotbelger@9205: mschaefer@9202: // Calculate the durations and create the result rows mschaefer@9202: for (int i = 0; i <= stationsSorted.length - 1; i++) { mschaefer@9202: final Gauge gauge = this.riverInfoProvider.getGauge(stationsSorted[i], true); gernotbelger@9205: final ResultRow row = createRow(descBuilder, stationsSorted[i], gauge, wqkmsArray, durFinders.get(gauge), i); mschaefer@9202: if (allStations.containsKey(stationsSorted[i]) && (allStations.get(stationsSorted[i]) != null)) mschaefer@9202: calculateInfrastructure(row, gauge, allStations.get(stationsSorted[i]), wqFinder, durFinders); mschaefer@9202: this.rows.add(row); mschaefer@9202: if (secondBank.containsKey(stationsSorted[i])) { mschaefer@9202: final ResultRow row2 = ResultRow.create(row); mschaefer@9202: calculateInfrastructure(row2, gauge, secondBank.get(stationsSorted[i]), wqFinder, durFinders); mschaefer@9202: this.rows.add(row2); mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: return new FloodDurationCalculationResult(label, mainValueLabels, this.rows); 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@9202: private void addRangeStations(final Map allStations, final WINFOArtifact winfo) { mschaefer@9202: for (final double station : new ComputationRangeAccess(winfo).getKms()) mschaefer@9202: allStations.put(Double.valueOf(station), null); 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@9202: private void addGaugeLimits(final Map allStations, 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@9202: allStations.put(kmA, null); mschaefer@9202: if (kmB < toKm + 0.0001) mschaefer@9202: allStations.put(kmB, null); mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Adds to a stations map all (first) infrastructures of a station, and the second, if any, to another map mschaefer@9202: */ mschaefer@9202: private void addInfrastructures(final Map allStations, final Map secondBank, mschaefer@9202: final List infrastructures) { mschaefer@9202: for (final InfrastructureValue infrastructure : infrastructures) { mschaefer@9202: final Double station = infrastructure.getStation(); mschaefer@9202: if (!allStations.containsKey(station) || !(allStations.get(station) instanceof InfrastructureValue)) mschaefer@9202: allStations.put(station, infrastructure); mschaefer@9202: else mschaefer@9202: secondBank.put(station, 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@9202: private WQKms[] calculateWaterlevels(final WINFOArtifact winfo, final double[] stations, final Calculation problems) { mschaefer@9202: // REMARK aus TkhCalculation - move to WinfoArtifactWrapper? mschaefer@9202: // TODO das ist ziemlich langsam - durch den WQBaseTableFinder ersetzen? (vorher W-Optionen in Q umrechnen) mschaefer@9202: // (So funktioniert computeWaterlevelData wohl: mschaefer@9202: // Es sucht die Spalte(n) zum Bezugspegel-Q in der W-Q-Tabelle ({river}.wst in Wst etc.) mschaefer@9202: // und interpoliert für diese horizontale Tabellenposition jeweils 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@9202: // bei Vorgabe eines W auf freier Strecke wird wohl vorher noch die .wst-Interpolation eingesetzt. mschaefer@9202: final CalculationResult waterlevelData = winfo.computeWaterlevelData(stations); mschaefer@9202: mschaefer@9202: /* copy all problems */ mschaefer@9202: final Calculation winfoProblems = waterlevelData.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@9202: return (WQKms[]) waterlevelData.getData(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Determines the discharge state labels for the selected Q or W values mschaefer@9202: */ gernotbelger@9205: // FIXME: use WaterlevelDescriptionBuilder instead! mschaefer@9202: private String[] findMainValueLabels(final WQKms[] wqkmsArray, final double[] qs, final Gauge gauge, final Calculation problems) { gernotbelger@9205: mschaefer@9202: final String[] mainValueLabels = new String[wqkmsArray.length]; mschaefer@9202: if (wqkmsArray.length >= 1) { gernotbelger@9215: gernotbelger@9215: // FIXME gernotbelger@9215: // WaterlevelDescriptionBuilder builder = new WaterlevelDescriptionBuilder(artifact, context); gernotbelger@9215: mschaefer@9202: // Labels like Q=123 or W=123 gernotbelger@9215: for (int i = 0; i <= wqkmsArray.length - 1; i++) { gernotbelger@9215: // FIXME gernotbelger@9215: // String label = builder.getDesc(wqkmsArray[i]); gernotbelger@9215: mschaefer@9202: mainValueLabels[i] = wqkmsArray[i].getName(); gernotbelger@9215: } mschaefer@9202: // Replace labels for named main Q values mschaefer@9202: final GaugeMainValueFinder zoneFinder = GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, gauge, problems); mschaefer@9202: if ((zoneFinder != null) && (qs != null)) { mschaefer@9202: for (int i = 0; i <= qs.length - 1; i++) mschaefer@9202: mainValueLabels[i] = zoneFinder.findExactZoneName(qs[i], mainValueLabels[i]); mschaefer@9202: } mschaefer@9202: } mschaefer@9202: return mainValueLabels; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Create a result row for a station and its gauge, and add w-q-values as selected gernotbelger@9205: * gernotbelger@9205: * @param descBuilder mschaefer@9202: */ gernotbelger@9205: private ResultRow createRow(final WaterlevelDescriptionBuilder descBuilder, final Double station, final Gauge gauge, final WQKms[] wqkmsArray, mschaefer@9202: final GaugeDurationValuesFinder durationFinder, final int kmIndex) { gernotbelger@9145: gernotbelger@9145: final ResultRow row = ResultRow.create(); mschaefer@9202: row.putValue(GeneralResultType.station, station); mschaefer@9202: row.putValue(SInfoResultType.infrastructuretype, null); // is replaced later for an infrastructure mschaefer@9202: row.putValue(SInfoResultType.floodDuration, Double.NaN); // is replaced later for an infrastructure gernotbelger@9205: gernotbelger@9205: // row.putValue(SInfoResultType.gaugeLabel, gauge.getName()); gernotbelger@9205: final String gaugeLabel = this.riverInfoProvider.findGauge(station); gernotbelger@9205: row.putValue(SInfoResultType.gaugeLabel, gaugeLabel); gernotbelger@9205: mschaefer@9202: final String location = this.riverInfoProvider.getLocation(station); mschaefer@9202: row.putValue(SInfoResultType.location, location); gernotbelger@9145: gernotbelger@9205: final List waterlevels = new ArrayList<>(wqkmsArray.length); gernotbelger@9205: gernotbelger@9205: for (final WQKms wqKm : wqkmsArray) { gernotbelger@9205: assert (wqKm.getKm(kmIndex) == station.doubleValue()); gernotbelger@9205: gernotbelger@9205: final int overflowDays = (int) Math.round(underflowDaysToOverflowDays(durationFinder.getDuration(wqKm.getQ(kmIndex)))); gernotbelger@9205: gernotbelger@9205: final String waterlevelLabel = descBuilder.getDesc(wqKm); gernotbelger@9205: gernotbelger@9205: final DurationWaterlevel dw = new DurationWaterlevel(wqKm.getW(kmIndex), overflowDays, wqKm.getQ(kmIndex), waterlevelLabel); gernotbelger@9205: waterlevels.add(dw); mschaefer@9202: } gernotbelger@9205: row.putValue(SInfoResultType.customMultiRowColWaterlevel, waterlevels); gernotbelger@9205: mschaefer@9202: return row; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Calculate the result row fields for one infrastructure mschaefer@9202: */ gernotbelger@9205: private void calculateInfrastructure(final ResultRow row, final Gauge gauge, final InfrastructureValue infrastructure, final WQBaseTableFinder wqFinder, gernotbelger@9205: final Map durFinders) { mschaefer@9202: mschaefer@9176: final double q = wqFinder.getDischarge(infrastructure.getStation(), infrastructure.getHeight()); mschaefer@9176: final double qOut = Double.isInfinite(q) ? Double.NaN : q; mschaefer@9202: final double dur = underflowDaysToOverflowDays(durFinders.get(gauge).getDuration(q)); mschaefer@9176: row.putValue(SInfoResultType.riverside, infrastructure.getAttributeKey().getName()); // TODO i18n mschaefer@9202: row.putValue(SInfoResultType.floodDuration, dur); mschaefer@9176: row.putValue(SInfoResultType.floodDischarge, qOut); mschaefer@9176: row.putValue(SInfoResultType.infrastructureHeight, infrastructure.getHeight()); mschaefer@9176: row.putValue(SInfoResultType.infrastructuretype, infrastructure.getInfrastructure().getType().getName()); 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@9202: return 365 - underflowDays; gernotbelger@9145: } gernotbelger@9145: }