diff artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculator.java @ 9202:b4402594213b

More work on calculations and output for S-Info flood duration workflow (chart types 1 and 2)
author mschaefer
date Mon, 02 Jul 2018 07:33:53 +0200
parents 1614cb14308f
children 3dae6b78e1da
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculator.java	Sun Jul 01 15:29:40 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculator.java	Mon Jul 02 07:33:53 2018 +0200
@@ -12,23 +12,33 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.commons.lang.math.DoubleRange;
 import org.dive4elements.artifacts.CallContext;
+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.Calculation.Problem;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.WQKms;
 import org.dive4elements.river.artifacts.sinfo.common.GaugeDurationValuesFinder;
+import org.dive4elements.river.artifacts.sinfo.common.GaugeMainValueFinder;
 import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.common.WQBaseTableFinder;
 import org.dive4elements.river.artifacts.sinfo.flood_duration.RiversideRadioChoice.RiversideChoiceKey;
-import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.model.Attribute.AttributeKey;
 import org.dive4elements.river.model.Gauge;
+import org.dive4elements.river.model.MainValueType.MainValueTypeKey;
 import org.dive4elements.river.model.sinfo.InfrastructureValue;
 
+import gnu.trove.TDoubleArrayList;
+
 /**
  * Calculation of the result rows of the flood duration of the infrastructures in a river km range
  * and selected main value durations
@@ -50,60 +60,207 @@
     }
 
     /**
-     * Calculate the result rows
-     *
-     * @return a result collection with one result
+     * Calculate the infrastructures flood duration result rows
      */
-    public FloodDurationCalculationResults execute(final Calculation problems, final String label, final String calcModeLabel,
-            final DoubleRange calcRange, final RiversideChoiceKey riverside, final String user) {
+    public FloodDurationCalculationResult execute(final Calculation problems, final String label, final DoubleRange calcRange,
+            final RiversideChoiceKey riverside, final WINFOArtifact winfo) {
 
-        final WQBaseTableFinder wqFinder = WQBaseTableFinder.loadValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(),
-                calcRange.getMaximumDouble(), problems);
+        // Find all gauges of the calc range, and create the duration finders
         final Map<Gauge, GaugeDurationValuesFinder> durFinders = new HashMap<>();
+        Gauge firstGauge = null;
         for (final Gauge gauge : this.riverInfoProvider.getRiver().determineGauges(calcRange.getMinimumDouble(), calcRange.getMaximumDouble())) {
             durFinders.put(gauge, GaugeDurationValuesFinder.loadValues(gauge, problems));
-        }
-        final AttributeKey bankKey = riverside.getAttributeKey();
-        for (final InfrastructureValue infrastructure : InfrastructureValue.getValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(),
-                calcRange.getMaximumDouble(), bankKey)) {
-            calculateResultRow(infrastructure, wqFinder, durFinders);
+            if (firstGauge == null)
+                firstGauge = gauge;
         }
 
-        final FloodDurationCalculationResult result = new FloodDurationCalculationResult(label, this.rows);
+        // Find all infrastructures within the calc range
+        final AttributeKey bankKey = riverside.getAttributeKey();
+        final List<InfrastructureValue> infras = InfrastructureValue.getValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(),
+                calcRange.getMaximumDouble(), bankKey);
 
-        final RiverInfo riverInfo = new RiverInfo(this.riverInfoProvider.getRiver());
+        // Merge all stations (range/step, borders of gauge ranges, infrastructures)
+        final Map<Double, InfrastructureValue> allStations = new HashMap<>();
+        final Map<Double, InfrastructureValue> secondBank = new HashMap<>(); // any second infrastructure in case of both-banks-option
+        addRangeStations(allStations, winfo);
+        addGaugeLimits(allStations, durFinders.keySet(), calcRange.getMinimumDouble(), calcRange.getMaximumDouble());
+        addInfrastructures(allStations, secondBank, infras);
+        final double[] stationsSorted = sortStations(allStations.keySet());
 
-        final FloodDurationCalculationResults results = new FloodDurationCalculationResults(calcModeLabel, user, riverInfo, calcRange);
-        results.addResult(result, problems);
-        return results;
+        // Calculate W and Q for all stations and the selected discharge states
+        final WQKms[] wqkmsArray = calculateWaterlevels(winfo, stationsSorted, problems);
+
+        // Determine discharge state labels of the main values
+        final String[] mainValueLabels = findMainValueLabels(wqkmsArray, winfo.getQs(), firstGauge, problems);
+
+        // Create a finder for Q in the {river}.wst km-w-q table
+        final WQBaseTableFinder wqFinder = WQBaseTableFinder.loadValues(this.riverInfoProvider.getRiver(), calcRange.getMinimumDouble(),
+                calcRange.getMaximumDouble(), problems);
+
+        // Calculate the durations and create the result rows
+        for (int i = 0; i <= stationsSorted.length - 1; i++) {
+            final Gauge gauge = this.riverInfoProvider.getGauge(stationsSorted[i], true);
+            final ResultRow row = createRow(stationsSorted[i], gauge, wqkmsArray, durFinders.get(gauge), i);
+            if (allStations.containsKey(stationsSorted[i]) && (allStations.get(stationsSorted[i]) != null))
+                calculateInfrastructure(row, gauge, allStations.get(stationsSorted[i]), wqFinder, durFinders);
+            this.rows.add(row);
+            if (secondBank.containsKey(stationsSorted[i])) {
+                final ResultRow row2 = ResultRow.create(row);
+                calculateInfrastructure(row2, gauge, secondBank.get(stationsSorted[i]), wqFinder, durFinders);
+                this.rows.add(row2);
+            }
+        }
+
+        return new FloodDurationCalculationResult(label, mainValueLabels, this.rows);
     }
 
     /**
-     * Calculate the result row for one infrastructure
+     * Adds to a stations map all stations corresponding to the active range and step
      */
-    private void calculateResultRow(final InfrastructureValue infrastructure, final WQBaseTableFinder wqFinder,
-            final Map<Gauge, GaugeDurationValuesFinder> durFinders) {
+    private void addRangeStations(final Map<Double, InfrastructureValue> allStations, final WINFOArtifact winfo) {
+        for (final double station : new ComputationRangeAccess(winfo).getKms())
+            allStations.put(Double.valueOf(station), null);
+    }
+
+    /**
+     * Adds to a stations map all range limits of the gauges within the calc range
+     */
+    private void addGaugeLimits(final Map<Double, InfrastructureValue> allStations, final Set<Gauge> gauges, final double fromKm, final double toKm) {
+        for (final Gauge gauge : gauges) {
+            final Double kmA = Double.valueOf(gauge.getRange().getA().doubleValue());
+            final Double kmB = Double.valueOf(gauge.getRange().getB().doubleValue());
+            if (kmA > fromKm - 0.0001)
+                allStations.put(kmA, null);
+            if (kmB < toKm + 0.0001)
+                allStations.put(kmB, null);
+        }
+    }
+
+    /**
+     * Adds to a stations map all (first) infrastructures of a station, and the second, if any, to another map
+     */
+    private void addInfrastructures(final Map<Double, InfrastructureValue> allStations, final Map<Double, InfrastructureValue> secondBank,
+            final List<InfrastructureValue> infrastructures) {
+        for (final InfrastructureValue infrastructure : infrastructures) {
+            final Double station = infrastructure.getStation();
+            if (!allStations.containsKey(station) || !(allStations.get(station) instanceof InfrastructureValue))
+                allStations.put(station, infrastructure);
+            else
+                secondBank.put(station, infrastructure);
+        }
+    }
+
+    /**
+     * Returns a double array with a sorted stations set
+     */
+    private double[] sortStations(final Set<Double> stations) {
+        final TDoubleArrayList sorted = new TDoubleArrayList();
+        for (final Double station : stations)
+            sorted.add(station.doubleValue());
+        sorted.sort();
+        return sorted.toNativeArray();
+    }
+
+    /**
+     * Calculates an array of w-q-longitudinal sections for all artifact W/Q options
+     */
+    private WQKms[] calculateWaterlevels(final WINFOArtifact winfo, final double[] stations, final Calculation problems) {
+        // REMARK aus TkhCalculation - move to WinfoArtifactWrapper?
+        // TODO das ist ziemlich langsam - durch den WQBaseTableFinder ersetzen? (vorher W-Optionen in Q umrechnen)
+        // (So funktioniert computeWaterlevelData wohl:
+        // Es sucht die Spalte(n) zum Bezugspegel-Q in der W-Q-Tabelle ({river}.wst in Wst etc.)
+        // und interpoliert für diese horizontale Tabellenposition jeweils die vertikale Tabellenposition der station;
+        // das ergibt das W einer station für einen Abflusszustand;
+        // bei Vorgabe eines Pegel-W wird vorher anhand der W-Q-Tabelle des Pegels ({gauge}.at in DischargeTable) das Q
+        // interpoliert;
+        // bei Vorgabe eines W auf freier Strecke wird wohl vorher noch die .wst-Interpolation eingesetzt.
+        final CalculationResult waterlevelData = winfo.computeWaterlevelData(stations);
+
+        /* copy all problems */
+        final Calculation winfoProblems = waterlevelData.getReport();
+        final List<Problem> problems2 = winfoProblems.getProblems();
+        if (problems2 != null) {
+            for (final Problem problem : problems2) {
+                problems.addProblem(problem);
+            }
+        }
+        return (WQKms[]) waterlevelData.getData();
+    }
+
+    /**
+     * Determines the discharge state labels for the selected Q or W values
+     */
+    private String[] findMainValueLabels(final WQKms[] wqkmsArray, final double[] qs, final Gauge gauge, final Calculation problems) {
+        final String[] mainValueLabels = new String[wqkmsArray.length];
+        if (wqkmsArray.length >= 1) {
+            // Labels like Q=123 or W=123
+            for (int i = 0; i <= wqkmsArray.length - 1; i++)
+                mainValueLabels[i] = wqkmsArray[i].getName();
+            // Replace labels for named main Q values
+            final GaugeMainValueFinder zoneFinder = GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, gauge, problems);
+            if ((zoneFinder != null) && (qs != null)) {
+                for (int i = 0; i <= qs.length - 1; i++)
+                    mainValueLabels[i] = zoneFinder.findExactZoneName(qs[i], mainValueLabels[i]);
+            }
+        }
+        return mainValueLabels;
+    }
+
+    /**
+     * Create a result row for a station and its gauge, and add w-q-values as selected
+     */
+    private ResultRow createRow(final Double station, final Gauge gauge, final WQKms[] wqkmsArray,
+            final GaugeDurationValuesFinder durationFinder, final int kmIndex) {
 
         final ResultRow row = ResultRow.create();
+        row.putValue(GeneralResultType.station, station);
+        row.putValue(SInfoResultType.infrastructuretype, null); // is replaced later for an infrastructure
+        row.putValue(SInfoResultType.floodDuration, Double.NaN); // is replaced later for an infrastructure
+        row.putValue(SInfoResultType.gaugeLabel, gauge.getName());
+        final String location = this.riverInfoProvider.getLocation(station);
+        row.putValue(SInfoResultType.location, location);
 
-        final Gauge gauge = this.riverInfoProvider.getGauge(infrastructure.getStation(), true);
+        if (wqkmsArray.length >= 1) {
+            assert (wqkmsArray[0].getKm(kmIndex) == station.doubleValue());
+            row.putValue(SInfoResultType.waterlevel1, wqkmsArray[0].getW(kmIndex));
+            row.putValue(SInfoResultType.discharge1, wqkmsArray[0].getQ(kmIndex));
+            row.putValue(SInfoResultType.mainValue1Duration, underflowDaysToOverflowDays(durationFinder.getDuration(wqkmsArray[0].getQ(kmIndex))));
+            if (wqkmsArray.length >= 2) {
+                assert (wqkmsArray[1].getKm(kmIndex) == station.doubleValue());
+                row.putValue(SInfoResultType.waterlevel2, wqkmsArray[1].getW(kmIndex));
+                row.putValue(SInfoResultType.discharge2, wqkmsArray[1].getQ(kmIndex));
+                row.putValue(SInfoResultType.mainValue2Duration, underflowDaysToOverflowDays(durationFinder.getDuration(wqkmsArray[1].getQ(kmIndex))));
+                if (wqkmsArray.length >= 3) {
+                    assert (wqkmsArray[2].getKm(kmIndex) == station.doubleValue());
+                    row.putValue(SInfoResultType.waterlevel3, wqkmsArray[2].getW(kmIndex));
+                    row.putValue(SInfoResultType.discharge3, wqkmsArray[2].getQ(kmIndex));
+                    row.putValue(SInfoResultType.mainValue3Duration, underflowDaysToOverflowDays(durationFinder.getDuration(wqkmsArray[2].getQ(kmIndex))));
+                }
+            }
+        }
+        return row;
+    }
+
+    /**
+     * Calculate the result row fields for one infrastructure
+     */
+    private void calculateInfrastructure(final ResultRow row, final Gauge gauge, final InfrastructureValue infrastructure,
+            final WQBaseTableFinder wqFinder, final Map<Gauge, GaugeDurationValuesFinder> durFinders) {
+
         final double q = wqFinder.getDischarge(infrastructure.getStation(), infrastructure.getHeight());
         final double qOut = Double.isInfinite(q) ? Double.NaN : q;
-        // REMARK: access the location once only during calculation
-        final String location = this.riverInfoProvider.getLocation(infrastructure.getStation());
-        row.putValue(GeneralResultType.station, infrastructure.getStation());
+        final double dur = underflowDaysToOverflowDays(durFinders.get(gauge).getDuration(q));
         row.putValue(SInfoResultType.riverside, infrastructure.getAttributeKey().getName()); // TODO i18n
-        row.putValue(SInfoResultType.floodDuration, 365 - durFinders.get(gauge).getDuration(q));
+        row.putValue(SInfoResultType.floodDuration, dur);
         row.putValue(SInfoResultType.floodDischarge, qOut);
         row.putValue(SInfoResultType.infrastructureHeight, infrastructure.getHeight());
         row.putValue(SInfoResultType.infrastructuretype, infrastructure.getInfrastructure().getType().getName());
-
-        // TODO Berechne W, Überflutungsdauer, Q und Bezeichnung von bis zu drei WSPL
-        // row.putValue(SInfoResultType.customMultiRowColWaterlevel, rowWsps);
+    }
 
-        row.putValue(SInfoResultType.gaugeLabel, gauge.getName());
-        row.putValue(SInfoResultType.location, location);
-
-        this.rows.add(row);
+    /**
+     * Translates underflow duration into overflow duration
+     */
+    private double underflowDaysToOverflowDays(final double underflowDays) {
+        return 365 - underflowDays;
     }
 }
\ No newline at end of file

http://dive4elements.wald.intevation.org