changeset 9612:f8308db94634

#20 UI, Diagramme
author dnt_bjoernsen <d.tironi@bjoernsen.de>
date Wed, 09 Oct 2019 16:17:16 +0200
parents 8ed6c45136fa
children d889ffe2fb05
files artifacts/doc/conf/artifacts/sinfo.xml artifacts/src/main/java/org/dive4elements/river/artifacts/common/ResultFacet.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FacetCalculator.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculation.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculationResult.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculator.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCurveProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationInfrastructureFacet.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationMainValuesQFacet.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationMainValuesWFacet.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationState.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodHeightProcessor.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/InfrastructureResultFacet.java artifacts/src/main/java/org/dive4elements/river/exports/LegendAggregator.java artifacts/src/main/java/org/dive4elements/river/utils/Formatter.java artifacts/src/main/resources/messages.properties artifacts/src/main/resources/messages_de.properties
diffstat 18 files changed, 600 insertions(+), 341 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/doc/conf/artifacts/sinfo.xml	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/doc/conf/artifacts/sinfo.xml	Wed Oct 09 16:17:16 2019 +0200
@@ -27,9 +27,9 @@
       <data name="ld_to" type="Double" />
 
       <!-- This data will hold the valid stations, needed for the Duration-Chart for the flood duration calculation
-            It is ugly to put it here, but we cannot set the state-data of the current state inside the state calculation (only the data of the previous states is put into the collection description).
-            So this is the only way to safely transport this information to the client.
-       -->    
+        It is ugly to put it here, but we cannot set the state-data of the current state inside the state calculation (only the data of the previous states is put into the collection description).
+        So this is the only way to safely transport this information to the client.
+      -->
       <data name="validStations" type="String" />
     </state>
 
@@ -263,7 +263,7 @@
     </transition>
 
     <state id="state.sinfo.load.year" description="state.sinfo.load.year" state="org.dive4elements.river.artifacts.sinfo.collision.LoadMultipleYearSelectState" helpText="help.state.sinfo.collisions.years">
-      <data name="years" type="String" /> 
+      <data name="years" type="String" />
     </state>
 
     <state id="state.sinfo.load.epoch" description="state.sinfo.load.epoch" state="org.dive4elements.river.artifacts.sinfo.collision.LoadMultipleEpochSelectState" helpText="help.state.sinfo.collisions.epochs">
@@ -315,7 +315,7 @@
       <from state="state.sinfo.calculation_mode" />
       <to state="state.sinfo.distance_only" />
       <condition data="calculation_mode" value="sinfo_calc_flood_duration" operator="equal" />
-    </transition> 
+    </transition>
 
     <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
       <from state="state.sinfo.distance_only" />
@@ -323,16 +323,26 @@
       <condition data="calculation_mode" value="sinfo_calc_flood_duration" operator="equal" />
     </transition>
 
+
     <state id="state.sinfo.riverside" description="state.sinfo.riverside" state="org.dive4elements.river.artifacts.sinfo.flood_duration.RiversideRadioChoice" helpText="help.state.sinfo.riverside">
       <data name="riverside" type="String" />
     </state>
 
+    <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
+      <from state="state.sinfo.riverside" />
+      <to state="state.sinfo.flood_infrastructure" />
+      <condition data="calculation_mode" value="sinfo_calc_flood_duration" operator="equal" />
+    </transition>
+    <state id="state.sinfo.flood_infrastructure" description="state.sinfo.flood_infrastructure" state="org.dive4elements.river.artifacts.sinfo.flood_duration.FloodInfrastructure" helpText="help.state.sinfo.flood_infrastructure">
+      <data name="flood_infrastructure" type="String" />
+    </state>
+
     <state id="state.sinfo.wspl" description="state.sinfo.wspl" state="org.dive4elements.river.artifacts.sinfo.flood_duration.WsplChoice" helpText="help.state.sinfo.flood_duration.wspl">
       <data name="wspl" type="Boolean" />
     </state>
 
     <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
-      <from state="state.sinfo.riverside" />
+      <from state="state.sinfo.flood_infrastructure" />
       <to state="state.sinfo.wspl" />
       <condition data="calculation_mode" value="sinfo_calc_flood_duration" operator="equal" />
     </transition>
@@ -359,35 +369,35 @@
       <outputmodes>
         <outputmode name="sinfo_flood_duration" description="output.sinfo_flood_duration" mime-type="image/png" type="chart">
           <facets>
-            <facet name="sinfo_facet_flood_duration.left" description="flood duration of the heights of the left bank infrastructures (points)"/>
-            <facet name="sinfo_facet_flood_duration.right" description="flood duration of the heights of the right bank infrastructures (points)"/>
-            <facet name="mainvalue.duration" description="flood duration of W main value"/>
+            <facet name="sinfo_facet_flood_duration.left" description="flood duration of the heights of the left bank infrastructures (points)" />
+            <facet name="sinfo_facet_flood_duration.right" description="flood duration of the heights of the right bank infrastructures (points)" />
+            <facet name="mainvalue.duration" description="flood duration of W main value" />
 
             <facet name="sinfo_flood_duration.manualpoints" description="Manual Points" />
           </facets>
         </outputmode>
         <outputmode name="sinfo_flood_height" description="output.sinfo_flood_height" mime-type="image/png" type="chart">
           <facets>
-            <facet name="sinfo_facet_flood_height.left" description="flood heights of the left bank infrastructures (points)"/>
-            <facet name="sinfo_facet_flood_height.right" description="flood heights of the right bank infrastructures (points)"/>
-            <facet name="mainvalue.w" description="W of main value"/>
-            
+            <facet name="sinfo_facet_flood_height.left" description="flood heights of the left bank infrastructures (points)" />
+            <facet name="sinfo_facet_flood_height.right" description="flood heights of the right bank infrastructures (points)" />
+            <facet name="mainvalue.w" description="W of main value" />
+
             <facet name="sinfo_flood_height.manualpoints" description="Manual Points" />
-       
+
           </facets>
         </outputmode>
         <outputmode name="sinfo_floodduration_curve" description="output.sinfo_duration_curve" mime-type="image/png" type="chart">
           <facets>
-            <facet name="duration_curve.w" description="facet.duration_curve.w"/>
-            <facet name="duration_curve.q" description="facet.duration_curve.q"/>
-            <facet name="infrastructure.w.left" description="w of the left bank infrastructure"/>
-            <facet name="infrastructure.w.right" description="w of the right bank infrastructure"/>
-            <facet name="infrastructure.q.left" description="q of the left bank infrastructure"/>
-            <facet name="infrastructure.q.right" description="q of the right bank infrastructure"/>
-            <facet name="sinfo.mainvalues.w" description="W Main Values"/>
-            <facet name="sinfo.mainvalues.q" description="Q Main Values at optional second axis"/>
+            <facet name="duration_curve.w" description="facet.duration_curve.w" />
+            <facet name="duration_curve.q" description="facet.duration_curve.q" />
+            <facet name="infrastructure.w.left" description="w of the left bank infrastructure" />
+            <facet name="infrastructure.w.right" description="w of the right bank infrastructure" />
+            <facet name="infrastructure.q.left" description="q of the left bank infrastructure" />
+            <facet name="infrastructure.q.right" description="q of the right bank infrastructure" />
+            <facet name="sinfo.mainvalues.w" description="W Main Values" />
+            <facet name="sinfo.mainvalues.q" description="Q Main Values at optional second axis" />
 
-            <facet name="sinfo_floodduration_curve.manualpoints" description="Manuelle Punkte"/>
+            <facet name="sinfo_floodduration_curve.manualpoints" description="Manuelle Punkte" />
           </facets>
         </outputmode>
 
@@ -397,8 +407,8 @@
             <facet name="pdf" description="facet.sinfo_flood_duration_export.pdf" />
           </facets>
         </outputmode>
- 
-         <outputmode name="sinfo_floodduration_report" description="output.sinfo_floodduration_report" mime-type="text/xml" type="report">
+
+        <outputmode name="sinfo_floodduration_report" description="output.sinfo_floodduration_report" mime-type="text/xml" type="report">
           <facets>
             <facet name="report" description="facet.sinfo_floodduration_report" />
           </facets>
@@ -428,8 +438,8 @@
       <data name="wq_step" type="Double" />
       <data name="wq_single" type="Double[]" />
     </state>
-    
-     <state id="state.sinfo.flood_duration.wq" description="state.sinfo.flood_duration.wq" state="org.dive4elements.river.artifacts.states.WQSelect" helpText="help.state.sinfo.flood_duration.wq">
+
+    <state id="state.sinfo.flood_duration.wq" description="state.sinfo.flood_duration.wq" state="org.dive4elements.river.artifacts.states.WQSelect" helpText="help.state.sinfo.flood_duration.wq">
       <data name="wq_isq" type="Boolean" />
       <data name="wq_isfree" type="Boolean" />
       <data name="wq_isrange" type="Boolean" />
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/common/ResultFacet.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/common/ResultFacet.java	Wed Oct 09 16:17:16 2019 +0200
@@ -20,7 +20,6 @@
 /**
  * Facet of one of the S-Info curves.
  */
-// TODO: rename: hat nichts mehr mit sinfo zu tun
 public class ResultFacet extends DataFacet {
 
     private static final long serialVersionUID = 1L;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FacetCalculator.java	Wed Oct 09 16:17:16 2019 +0200
@@ -0,0 +1,184 @@
+/** 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.sinfo.flood_duration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.dive4elements.artifacts.CallContext;
+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.resources.Resources;
+import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
+import org.dive4elements.river.jfree.StickyAxisAnnotation;
+import org.dive4elements.river.jfree.StickyAxisAnnotation.SimpleAxis;
+import org.dive4elements.river.model.Attribute.AttributeKey;
+
+/**
+ * @author Domenico Nardi Tironi
+ *
+ */
+public class FacetCalculator {
+
+    private static final double DELTA_KM = 0.0001;
+
+    private final CallContext m_context;
+
+    public FacetCalculator(final CallContext context) {
+        this.m_context = context;
+    }
+
+    /**
+     * Calculates the data for the W main value lines in the duration curve chart
+     */
+    public List<StickyAxisAnnotation> calcMainValueWAnnotations(final Calculation problems, final double station, final FloodDurationCalculationResult result) {
+
+        final List<ResultRow> stationRows = searchStation(station, result.getAllRows(), AttributeKey.NONE);
+        if (stationRows.isEmpty())
+            return Collections.emptyList();
+
+        final List<StickyAxisAnnotation> annotations = new ArrayList<>();
+        final ResultRow row = stationRows.get(0);
+        final List<DurationWaterlevel> wqds = (List<DurationWaterlevel>) row.getValue(SInfoResultType.customMultiRowColWaterlevel);
+        for (final DurationWaterlevel wqd : wqds) {
+            final String label = !wqd.getBezeichnung().startsWith("W=") ? "W(" + wqd.getBezeichnung() + ")" : wqd.getBezeichnung();
+            final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) wqd.getWaterlevel(), SimpleAxis.Y_AXIS,
+                    FloodDurationCurveGenerator.YAXIS.W.idx);
+            annotation.setHitPoint((float) wqd.getFloodDurDaysPerYear());
+            annotations.add(annotation);
+        }
+        return annotations;
+    }
+
+    /**
+     * Calculates the data for the Q main value lines in the duration curve chart
+     *
+     * @param infrastructure
+     */
+    public List<StickyAxisAnnotation> calcMainValueQAnnotations(final Calculation problems, final double station, final FloodDurationCalculationResult result) {
+
+        final List<ResultRow> stationRows = searchStation(station, result.getAllRows(), AttributeKey.NONE);
+        if (stationRows.isEmpty())
+            return Collections.emptyList();
+
+        final ResultRow row = stationRows.get(0);
+        final List<StickyAxisAnnotation> annotations = new ArrayList<>();
+        final List<DurationWaterlevel> wqds = (List<DurationWaterlevel>) row.getValue(SInfoResultType.customMultiRowColWaterlevel);
+        for (final DurationWaterlevel wqd : wqds) {
+            final String label = wqd.getBezeichnung().startsWith("W=") ? "Q(" + wqd.getBezeichnung() + ")" : wqd.getBezeichnung();
+            final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) wqd.getDischarge(), SimpleAxis.Y_AXIS,
+                    FloodDurationCurveGenerator.YAXIS.Q.idx);
+            annotation.setHitPoint((float) wqd.getFloodDurDaysPerYear());
+            annotations.add(annotation);
+        }
+        return annotations;
+    }
+
+    /**
+     * Find and return the W or Q annotation(s) of a station and a riverside in a previously calculated result
+     *
+     * @param key
+     */
+    public List<StickyAxisAnnotation> calcInfrastructureAnnotations(final Calculation problems, final double station, final boolean isW,
+            final FloodDurationCalculationResult result, final AttributeKey riverside) {
+
+        final List<ResultRow> stationRows = searchStation(station, result.getAllRows(), riverside);
+        if (stationRows.isEmpty())
+            return Collections.emptyList();
+
+        // Same way as in MainValueWFacet and ..QFacet
+        final List<StickyAxisAnnotation> annotations = new ArrayList<>();
+        for (final ResultRow row : stationRows) {
+            if (isW)
+                annotations.add(calcInfrastructureWAnnotation(row));
+            else
+                annotations.add(calcInfrastructureQAnnotation(row));
+        }
+        return annotations;
+    }
+
+    /**
+     * Calculates the Q annotation lines of an infrastructure
+     */
+    private StickyAxisAnnotation calcInfrastructureQAnnotation(final ResultRow row) {
+        final String label = Resources.getMsg(this.m_context.getMeta(), "sinfo.chart.flood_duration.curve.infrastructure",
+                "sinfo.chart.flood_duration.curve.infrastructure",
+                SInfoResultType.infrastructuretype.exportValue(this.m_context, row.getValue(SInfoResultType.infrastructuretype)) + " - "
+                        + SInfoResultType.infrastructurepart.exportValue(this.m_context, row.getValue(SInfoResultType.infrastructurepart)) + " ("
+                        + SInfoResultType.riverside.exportValue(this.m_context, row.getValue(SInfoResultType.riverside)) + ")");
+        final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) row.getDoubleValue(SInfoResultType.floodDischarge), SimpleAxis.Y_AXIS,
+                FloodDurationCurveGenerator.YAXIS.Q.idx);
+        annotation.setHitPoint((float) row.getDoubleValue(SInfoResultType.floodDuration));
+        return annotation;
+    }
+
+    /**
+     * Calculates the W annotation lines of an infrastructure
+     */
+    private StickyAxisAnnotation calcInfrastructureWAnnotation(final ResultRow row) {
+        final String label = Resources.getMsg(this.m_context.getMeta(), "sinfo.chart.flood_duration.curve.infrastructure",
+                "sinfo.chart.flood_duration.curve.infrastructure",
+                SInfoResultType.infrastructuretype.exportValue(this.m_context, row.getValue(SInfoResultType.infrastructuretype)) + " - "
+                        + SInfoResultType.infrastructurepart.exportValue(this.m_context, row.getValue(SInfoResultType.infrastructurepart)) + " ("
+                        + SInfoResultType.riverside.exportValue(this.m_context, row.getValue(SInfoResultType.riverside)) + ")");
+        final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) row.getDoubleValue(SInfoResultType.infrastructureHeight),
+                SimpleAxis.Y_AXIS, FloodDurationCurveGenerator.YAXIS.W.idx);
+        annotation.setHitPoint((float) row.getDoubleValue(SInfoResultType.floodDuration));
+        return annotation;
+    }
+
+    /**
+     * Searches the one or two rows of a station in a result rows collection
+     *
+     * @param m_riverside
+     */
+    private List<ResultRow> searchStation(final double station, final Collection<ResultRow> rows, final AttributeKey riverside) {
+
+        final double searchStation = findSearchStation(rows, station, riverside);
+        if (Double.isNaN(searchStation))
+            return Collections.emptyList();
+
+        final List<ResultRow> found = new ArrayList<>();
+
+        for (final ResultRow row : rows) { // rows are not sorted
+            final String riversideStr = String.valueOf(row.getValue(SInfoResultType.riverside));
+            if (riversideStr.equals("null"))
+                continue;
+
+            if (Math.abs(row.getDoubleValue(GeneralResultType.station) - station) > DELTA_KM)
+                continue;
+
+            if (riverside.equals(AttributeKey.NONE) || riverside.equals(AttributeKey.valueOf(String.valueOf(row.getValue(SInfoResultType.riverside)))))
+                found.add(row);
+        }
+        return found;
+    }
+
+    @Deprecated
+    private double findSearchStation(final Collection<ResultRow> rows, final double station, final AttributeKey riverside) {
+
+        if (!Double.isNaN(station))
+            return station;
+
+        for (final ResultRow row : rows) {
+            final String riversideStr = String.valueOf(row.getValue(SInfoResultType.riverside));
+            if (riversideStr.equals("null"))
+                continue;
+
+            if (riverside.equals(AttributeKey.NONE) || riverside.equals(AttributeKey.valueOf(riversideStr)))
+                return row.getDoubleValue(GeneralResultType.station);
+        }
+
+        return Double.NaN;
+    }
+}
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculation.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculation.java	Wed Oct 09 16:17:16 2019 +0200
@@ -9,7 +9,7 @@
  */
 package org.dive4elements.river.artifacts.sinfo.flood_duration;
 
-import java.util.List;
+import java.util.Set;
 
 import org.apache.commons.lang.math.DoubleRange;
 import org.dive4elements.artifacts.CallContext;
@@ -20,11 +20,10 @@
 import org.dive4elements.river.artifacts.model.river.RiverInfoProvider;
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.artifacts.sinfo.tkhstate.WinfoArtifactWrapper;
 import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
-import org.dive4elements.river.jfree.StickyAxisAnnotation;
-import org.dive4elements.river.model.Attribute.AttributeKey;
 import org.dive4elements.river.model.River;
 
 /**
@@ -72,11 +71,15 @@
     /**
      * Calculates the flood durations of the infrastructures of a km range of a river
      */
-    private void calculateResult(final String label, final DoubleRange calcRange, final RiverInfoProvider riverInfoProvider,
-            final FloodDurationAccess access, final Calculation problems, final WINFOArtifact winfo, final FloodDurationCalculationResults results) {
+    private void calculateResult(final String label, final DoubleRange calcRange, final RiverInfoProvider riverInfoProvider, final FloodDurationAccess access,
+            final Calculation problems, final WINFOArtifact winfo, final FloodDurationCalculationResults results) {
 
         final FloodDurationCalculator calculator = new FloodDurationCalculator(this.context, riverInfoProvider);
-        calculator.execute(problems, label, calcRange, access.getRiverside(), access.getIsWspl(), winfo, results);
+
+        // FIXME: fetch from acces; maybe we need database for that... whatever
+        final Set<Infrastructure> infrastructureKeys = null;
+
+        calculator.execute(problems, label, calcRange, access.getRiverside(), infrastructureKeys, access.getIsWspl(), winfo, results);
     }
 
     /**
@@ -100,44 +103,9 @@
         if (!Double.isNaN(station)) {
             winfo.addStringData("ld_locations", Double.toString(station));
             return calculator.calcWQDays(problems, station, winfo);
-        }
-        else {
+        } else {
             winfo.addStringData("ld_locations", Double.toString(calcRange.getMinimumDouble()));
             return calculator.calcWQDays(problems, calcRange.getMinimumDouble(), winfo);
         }
     }
-
-    /**
-     * Calculates the annotations of the infrastructure(s) of a station for a flood duration calculation
-     */
-    public List<StickyAxisAnnotation> calcInfrastructureAnnotations(final double station, final AttributeKey riverside, final boolean isW,
-            final FloodDurationCalculationResult result) {
-
-        final Calculation problems = new Calculation();
-
-        final FloodDurationCalculator calculator = new FloodDurationCalculator(this.context, null);
-        return calculator.calcInfrastructureAnnotations(problems, station, riverside, isW, result);
-    }
-
-    /**
-     * Calculates the annotations of the W main values of a station
-     */
-    public List<StickyAxisAnnotation> calcMainValueWAnnotations(final double station, final FloodDurationCalculationResult result) {
-
-        final Calculation problems = new Calculation();
-
-        final FloodDurationCalculator calculator = new FloodDurationCalculator(this.context, null);
-        return calculator.calcMainValueWAnnotations(problems, station, result);
-    }
-
-    /**
-     * Calculates the annotations of the Q main values of a station
-     */
-    public List<StickyAxisAnnotation> calcMainValueQAnnotations(final double station, final FloodDurationCalculationResult result) {
-
-        final Calculation problems = new Calculation();
-
-        final FloodDurationCalculator calculator = new FloodDurationCalculator(this.context, null);
-        return calculator.calcMainValueQAnnotations(problems, station, result);
-    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculationResult.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculationResult.java	Wed Oct 09 16:17:16 2019 +0200
@@ -9,13 +9,19 @@
  */
 package org.dive4elements.river.artifacts.sinfo.flood_duration;
 
+import java.io.Serializable;
 import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.commons.collections.Predicate;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.common.AbstractCalculationExportableResult;
 import org.dive4elements.river.artifacts.common.AbstractExportContext;
 import org.dive4elements.river.artifacts.common.ExportContextCSV;
@@ -24,6 +30,7 @@
 import org.dive4elements.river.artifacts.common.IResultType;
 import org.dive4elements.river.artifacts.common.MetaAndTableJRDataSource;
 import org.dive4elements.river.artifacts.common.ResultRow;
+import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.model.Attribute.AttributeKey;
@@ -37,19 +44,19 @@
  */
 public final class FloodDurationCalculationResult extends AbstractCalculationExportableResult {
 
-    private final static class RiversidePredicate implements Predicate {
+    private final static class InfrastructurePredicate implements Predicate {
 
-        private final AttributeKey riverside;
+        private final Infrastructure m_infrastructure;
 
-        public RiversidePredicate(final AttributeKey riverside) {
-            this.riverside = riverside;
+        public InfrastructurePredicate(final Infrastructure infrastructure) {
+            this.m_infrastructure = infrastructure;
         }
 
         @Override
         public boolean evaluate(final Object object) {
             final ResultRow row = (ResultRow) object;
-
-            return row.getValue(SInfoResultType.riverside) == this.riverside;
+            final Infrastructure test = new Infrastructure(row);
+            return this.m_infrastructure.equals(test);
         }
     }
 
@@ -63,6 +70,73 @@
         }
     }
 
+    public static final class Infrastructure implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        private final String m_type;
+
+        private final String m_part;
+
+        private final AttributeKey m_riverside;
+
+        private static final String FACET_FLOOD_DURATION_DESCRIPTION = "sinfo_facet_flood_duration";
+
+        private static final String FACET_ABSOLUTE_HEIGHT = "sinfo.flood_duration.absolute.height";
+
+        public Infrastructure(final ResultRow row) {
+            this.m_type = String.valueOf(row.getValue(SInfoResultType.infrastructuretype));
+            this.m_part = String.valueOf(row.getValue(SInfoResultType.infrastructurepart));
+            final String riversideStr = String.valueOf(row.getValue(SInfoResultType.riverside));
+            this.m_riverside = riversideStr.equals("null") ? AttributeKey.NONE : AttributeKey.valueOf(riversideStr);
+        }
+
+        public AttributeKey getRiverside() {
+            return this.m_riverside;
+        }
+
+        @Override
+        public int hashCode() {
+            return new HashCodeBuilder() //
+                    .append(this.m_type)//
+                    .append(this.m_part)//
+                    .append(this.m_riverside)//
+                    .toHashCode();
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+
+            if (obj == null)
+                return false;
+            if (obj == this)
+                return true;
+            if (obj.getClass() != getClass())
+                return false;
+
+            final Infrastructure other = (Infrastructure) obj;
+            return new EqualsBuilder() //
+                    .append(this.m_type, other.m_type) //
+                    .append(this.m_part, other.m_part) //
+                    .append(this.m_riverside, other.m_riverside) //
+                    .isEquals();
+        }
+
+        public String getFloodHeightLabel(final CallContext context) {
+            return Resources.getMsg(context.getMeta(), FACET_ABSOLUTE_HEIGHT, FACET_ABSOLUTE_HEIGHT) + " " + this.m_type + " - " + this.m_part + " ("
+                    + getLocalizedRiverside(context) + ")";
+        }
+
+        public String getFloodDurationLabel(final CallContext context) {
+            return Resources.getMsg(context.getMeta(), FACET_FLOOD_DURATION_DESCRIPTION, FACET_FLOOD_DURATION_DESCRIPTION) + " " + this.m_type + " - "
+                    + this.m_part + " (" + getLocalizedRiverside(context) + ")";
+        }
+
+        private String getLocalizedRiverside(final CallContext callContext) {
+            return SInfoResultType.localizeRiverside(callContext, this.m_riverside);
+        }
+    }
+
     private static final long serialVersionUID = 1L;
 
     private final boolean isUseWspl;
@@ -71,6 +145,8 @@
 
     private final int maxWaterlevelPdf = 3;
 
+    private final Set<Infrastructure> m_infastructures;
+
     public interface ValueGetter {
         double getValue(DurationWaterlevel waterlevel);
     }
@@ -79,10 +155,16 @@
         pdf, csv
     }
 
-    public FloodDurationCalculationResult(final String label, final String[] mainvalueLabels, final Collection<ResultRow> rows, final boolean isUseWspl) {
+    public FloodDurationCalculationResult(final String label, final String[] mainvalueLabels, final Collection<ResultRow> rows, final boolean isUseWspl,
+            final Set<Infrastructure> infrastructures) {
         super(label, rows);
         this.waterlevelLabels = mainvalueLabels;
         this.isUseWspl = isUseWspl;
+        this.m_infastructures = infrastructures;
+    }
+
+    public Set<Infrastructure> getInfastructureMap() {
+        return this.m_infastructures;
     }
 
     /**
@@ -96,7 +178,7 @@
 
         final List<ResultRow> infrasOnlyRows = new ArrayList<>();
         for (final ResultRow row : rows) {
-            if (row.getValue(SInfoResultType.infrastructuretype) != null)
+            if (row.getValue(SInfoResultType.infrastructuretype) != null && row.getValue(SInfoResultType.infrastructurepart) != null)
                 infrasOnlyRows.add(row);
         }
         return Collections.unmodifiableCollection(infrasOnlyRows);
@@ -130,9 +212,12 @@
         lines.add(exportContextCSV.formatRowValue(row, GeneralResultType.station));
         lines.add(exportContextCSV.formatRowValue(row, SInfoResultType.riverside));
         lines.add(exportContextCSV.formatRowValue(row, SInfoResultType.floodDuration));
+
         lines.add(exportContextCSV.formatRowValue(row, SInfoResultType.floodDischarge));
+
         lines.add(exportContextCSV.formatRowValue(row, SInfoResultType.infrastructureHeight));
         lines.add(exportContextCSV.formatRowValue(row, SInfoResultType.infrastructuretype));
+        lines.add(exportContextCSV.formatRowValue(row, SInfoResultType.infrastructurepart));
 
         final List<DurationWaterlevel> waterlevelList = (List<DurationWaterlevel>) row.getValue(SInfoResultType.customMultiRowColWaterlevel);
 
@@ -178,6 +263,7 @@
         header.add(exportContextCSV.msgUnitCSV(SInfoResultType.floodDischarge, SInfoResultType.floodDischarge.getUnit()));
         header.add(exportContextCSV.msgUnitCSV(SInfoResultType.infrastructureHeight, river.getWstUnit()));
         header.add(exportContextCSV.formatCsvHeader(SInfoResultType.infrastructuretype));
+        header.add(exportContextCSV.formatCsvHeader(SInfoResultType.infrastructurepart));
 
         // add dynamic headers
         final int waterlevelCount = // results.
@@ -227,6 +313,7 @@
         exportContextPDF.addJRMetadata(source, "inundationduration_q_header", SInfoResultType.floodDischarge);
         exportContextPDF.addJRMetadata(source, "infrastructure_height_header", SInfoResultType.infrastructureHeight);
         exportContextPDF.addJRMetadata(source, "infrastructure_type_header", SInfoResultType.infrastructuretype);
+        exportContextPDF.addJRMetadata(source, "infrastructure_part_header", SInfoResultType.infrastructurepart);
 
         for (int i = 1; i <= this.getWaterlevelCount(); i++) {
 
@@ -262,8 +349,9 @@
     /**
      * Gets the longitudinal section of a result value type for one river side
      */
-    public final double[][] getInfrastructurePoints(final IResultType type, final AttributeKey riverside) {
-        return getPoints(GeneralResultType.station, type, new RiversidePredicate(riverside));
+    public final double[][] getInfrastructurePoints(final IResultType type, final Infrastructure infrastructure) {
+
+        return getPoints(GeneralResultType.station, type, new InfrastructurePredicate(infrastructure));
     }
 
     /**
@@ -295,4 +383,9 @@
     public boolean isUseWspl() {
         return this.isUseWspl;
     }
+
+    public Collection<Entry<String, String>> getUniqueInfrastruktureTypes() {
+        // TODO Auto-generated method stub
+        return null;
+    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculator.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCalculator.java	Wed Oct 09 16:17:16 2019 +0200
@@ -10,8 +10,8 @@
 package org.dive4elements.river.artifacts.sinfo.flood_duration;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -31,13 +31,11 @@
 import org.dive4elements.river.artifacts.model.WstValueTable.QPosition;
 import org.dive4elements.river.artifacts.model.WstValueTableFactory;
 import org.dive4elements.river.artifacts.model.river.RiverInfoProvider;
-import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.common.GaugeDurationValuesFinder;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.artifacts.sinfo.flood_duration.RiversideRadioChoice.RiversideChoiceKey;
 import org.dive4elements.river.exports.WaterlevelDescriptionBuilder;
-import org.dive4elements.river.jfree.StickyAxisAnnotation;
-import org.dive4elements.river.jfree.StickyAxisAnnotation.SimpleAxis;
 import org.dive4elements.river.model.Attribute.AttributeKey;
 import org.dive4elements.river.model.Gauge;
 import org.dive4elements.river.model.sinfo.InfrastructureValue;
@@ -68,8 +66,13 @@
     /**
      * Calculate the infrastructures flood duration result rows
      */
-    public void execute(final Calculation problems, final String label, final DoubleRange calcRange, final RiversideChoiceKey riverside, final boolean withWspl,
-            final WINFOArtifact winfo, final FloodDurationCalculationResults results) {
+    public void execute(final Calculation problems, final String label, final DoubleRange calcRange, final RiversideChoiceKey riverside,
+            final Set<Infrastructure> infrastruktureKey, final boolean withWspl, final WINFOArtifact winfo, final FloodDurationCalculationResults results) {
+
+        // FIXME Schäfer:
+        // filter by infrastructureKey
+        // FIXME: Tironi: api gscheit benennen;
+        // Lösung finden für: Infrastructure enthält riverside, wird hier aber nicht benötigt (weil schon im choice)
 
         // Find all gauges of the calc range, and create the duration finders
         final Map<Gauge, GaugeDurationValuesFinder> durFinders = new HashMap<>();
@@ -108,18 +111,22 @@
         // (should be in cache since already used in calculateWaterlevels (winfo.computeWaterlevelData)
         final WstValueTable wst = WstValueTableFactory.getTable(this.riverInfoProvider2.getRiver());
 
+        final Set<FloodDurationCalculationResult.Infrastructure> infrastructures = new HashSet<>();
+
         // Create the result rows, and calculate and add the flood durations etc.
         for (int i = 0; i <= stationsSorted.length - 1; i++) {
             final Gauge gauge = this.riverInfoProvider2.getGauge(stationsSorted[i], true);
             final ResultRow row = createRow(stationsSorted[i], wqkmsArray, gaugeWstDurations.get(gauge), i);
             if (allStations.containsKey(stationsSorted[i]) && (allStations.get(stationsSorted[i]) != null))
-                calculateInfrastructure(row, gauge, allStations.get(stationsSorted[i]), wst, durFinders);
+                calculateInfrastructure(row, gauge, allStations.get(stationsSorted[i]), wst, durFinders, infrastructures);
+
             this.rows.add(row);
             if (secondBank.containsKey(stationsSorted[i])) {
                 final ResultRow row2 = ResultRow.create(row);
-                calculateInfrastructure(row2, gauge, secondBank.get(stationsSorted[i]), wst, durFinders);
+                calculateInfrastructure(row2, gauge, secondBank.get(stationsSorted[i]), wst, durFinders, infrastructures);
                 this.rows.add(row2);
             }
+
         }
 
         // Get the labels of the selected waterlevels
@@ -127,7 +134,7 @@
         for (int i = 0; i <= wqkmsArray.length - 1; i++)
             wstLabels[i] = wqkmsArray[i].getName();
 
-        results.addResult(new FloodDurationCalculationResult(label, wstLabels, this.rows, withWspl), problems);
+        results.addResult(new FloodDurationCalculationResult(label, wstLabels, this.rows, withWspl, infrastructures), problems);
     }
 
     /**
@@ -149,140 +156,6 @@
     }
 
     /**
-     * Calculates the data for the Q main value lines in the duration curve chart
-     */
-    public List<StickyAxisAnnotation> calcMainValueQAnnotations(final Calculation problems, final double station, final FloodDurationCalculationResult result) {
-
-        // Search the station in the previously calculated result rows and terminate if no infrastructure row found
-        double station1 = station;
-        if (Double.isNaN(station)) {
-            for (final ResultRow row : result.getAllRows()) {
-                station1 = row.getDoubleValue(GeneralResultType.station);
-                break;
-            }
-        }
-        final List<ResultRow> stationRows = searchStation(station1, AttributeKey.NONE, result.getAllRows());
-        if (stationRows.isEmpty()) {
-            return new ArrayList<>();
-        }
-        final ResultRow row = stationRows.get(0);
-        final List<StickyAxisAnnotation> annotations = new ArrayList<>();
-        final List<DurationWaterlevel> wqds = (List<DurationWaterlevel>) row.getValue(SInfoResultType.customMultiRowColWaterlevel);
-        for (final DurationWaterlevel wqd : wqds) {
-            final String label = wqd.getBezeichnung().startsWith("W=") ? "Q(" + wqd.getBezeichnung() + ")" : wqd.getBezeichnung();
-            final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) wqd.getDischarge(), SimpleAxis.Y_AXIS,
-                    FloodDurationCurveGenerator.YAXIS.Q.idx);
-            annotation.setHitPoint((float) wqd.getFloodDurDaysPerYear());
-            annotations.add(annotation);
-        }
-        return annotations;
-    }
-
-    /**
-     * Calculates the data for the W main value lines in the duration curve chart
-     */
-    public List<StickyAxisAnnotation> calcMainValueWAnnotations(final Calculation problems, final double station, final FloodDurationCalculationResult result) {
-
-        // Search the station in the previously calculated result rows and terminate if no infrastructure row found
-        double station1 = station;
-        if (Double.isNaN(station)) {
-            for (final ResultRow row : result.getAllRows()) {
-                station1 = row.getDoubleValue(GeneralResultType.station);
-                break;
-            }
-        }
-        final List<ResultRow> stationRows = searchStation(station1, AttributeKey.NONE, result.getAllRows());
-        if (stationRows.isEmpty()) {
-            return new ArrayList<>();
-        }
-        final List<StickyAxisAnnotation> annotations = new ArrayList<>();
-        final ResultRow row = stationRows.get(0);
-        final List<DurationWaterlevel> wqds = (List<DurationWaterlevel>) row.getValue(SInfoResultType.customMultiRowColWaterlevel);
-        for (final DurationWaterlevel wqd : wqds) {
-            final String label = !wqd.getBezeichnung().startsWith("W=") ? "W(" + wqd.getBezeichnung() + ")" : wqd.getBezeichnung();
-            final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) wqd.getWaterlevel(), SimpleAxis.Y_AXIS,
-                    FloodDurationCurveGenerator.YAXIS.W.idx);
-            annotation.setHitPoint((float) wqd.getFloodDurDaysPerYear());
-            annotations.add(annotation);
-        }
-        return annotations;
-    }
-
-    /**
-     * Find and return the W or Q annotation(s) of a station and a riverside in a previously calculated result
-     */
-    public List<StickyAxisAnnotation> calcInfrastructureAnnotations(final Calculation problems, final double station, final AttributeKey riverside,
-            final boolean isW, final FloodDurationCalculationResult result) {
-
-        // Search the station in the previously calculated result rows and terminate if no infrastructure row found
-        double station1 = station;
-        if (Double.isNaN(station)) {
-            for (final ResultRow row : result.getRows()) {
-                if (row.getValue(SInfoResultType.riverside) == riverside) {
-                    station1 = row.getDoubleValue(GeneralResultType.station);
-                    break;
-                }
-            }
-        }
-        final List<ResultRow> stationRows = searchStation(station1, riverside, result.getRows());
-        if (stationRows.isEmpty() || (stationRows.get(0).getValue(SInfoResultType.infrastructuretype) == null)) {
-            return new ArrayList<>();
-        }
-        // Same way as in MainValueWFacet and ..QFacet
-        final List<StickyAxisAnnotation> annotations = new ArrayList<>();
-        for (final ResultRow row : stationRows) {
-            if (isW)
-                annotations.add(calcInfrastructureWAnnotation(row));
-            else
-                annotations.add(calcInfrastructureQAnnotation(row));
-        }
-        return annotations;
-    }
-
-    /**
-     * Searches the one or two rows of a station in a result rows collection
-     */
-    private List<ResultRow> searchStation(final double station, final AttributeKey riverside, final Collection<ResultRow> rows) {
-        final List<ResultRow> found = new ArrayList<>();
-        for (final ResultRow row : rows) {
-            if (row.getDoubleValue(GeneralResultType.station) > station + 0.0001)
-                break;
-            else if ((row.getDoubleValue(GeneralResultType.station) > station - 0.0001)
-                    && ((riverside == AttributeKey.NONE) || (row.getValue(SInfoResultType.riverside) == riverside)))
-                found.add(row);
-        }
-        return found;
-    }
-
-    /**
-     * Calculates the Q annotation lines of an infrastructure
-     */
-    private StickyAxisAnnotation calcInfrastructureQAnnotation(final ResultRow row) {
-        final String label = Resources.getMsg(this.context.getMeta(), "sinfo.chart.flood_duration.curve.infrastructure",
-                "sinfo.chart.flood_duration.curve.infrastructure",
-                SInfoResultType.infrastructuretype.exportValue(this.context, row.getValue(SInfoResultType.infrastructuretype)) + ", "
-                        + SInfoResultType.riverside.exportValue(this.context, row.getValue(SInfoResultType.riverside)));
-        final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) row.getDoubleValue(SInfoResultType.floodDischarge), SimpleAxis.Y_AXIS,
-                FloodDurationCurveGenerator.YAXIS.Q.idx);
-        annotation.setHitPoint((float) row.getDoubleValue(SInfoResultType.floodDuration));
-        return annotation;
-    }
-
-    /**
-     * Calculates the W annotation lines of an infrastructure
-     */
-    private StickyAxisAnnotation calcInfrastructureWAnnotation(final ResultRow row) {
-        final String label = Resources.getMsg(this.context.getMeta(), "sinfo.chart.flood_duration.curve.infrastructure",
-                "sinfo.chart.flood_duration.curve.infrastructure",
-                SInfoResultType.infrastructuretype.exportValue(this.context, row.getValue(SInfoResultType.infrastructuretype)) + ", "
-                        + SInfoResultType.riverside.exportValue(this.context, row.getValue(SInfoResultType.riverside)));
-        final StickyAxisAnnotation annotation = new StickyAxisAnnotation(label, (float) row.getDoubleValue(SInfoResultType.infrastructureHeight),
-                SimpleAxis.Y_AXIS, FloodDurationCurveGenerator.YAXIS.W.idx);
-        annotation.setHitPoint((float) row.getDoubleValue(SInfoResultType.floodDuration));
-        return annotation;
-    }
-
-    /**
      * Adds to a stations map all stations corresponding to the active range and step
      */
     private void addRangeStations(final Map<Double, InfrastructureValue> allStations, final WINFOArtifact winfo) {
@@ -413,7 +286,8 @@
 
         final ResultRow row = ResultRow.create();
         row.putValue(GeneralResultType.station, station);
-        row.putValue(SInfoResultType.infrastructuretype, null); // is replaced later for an infrastructure
+        row.putValue(SInfoResultType.infrastructuretype, null); // is replaced later for an infrastructure type
+        row.putValue(SInfoResultType.infrastructurepart, null); // is replaced later for an infrastructure part
         row.putValue(SInfoResultType.floodDuration, Double.NaN); // is replaced later for an infrastructure
 
         final String gaugeLabel = this.riverInfoProvider2.findGauge(station);
@@ -436,9 +310,11 @@
 
     /**
      * Calculate the result row fields for one infrastructure
+     *
+     * @param map
      */
     private void calculateInfrastructure(final ResultRow row, final Gauge gauge, final InfrastructureValue infrastructure, final WstValueTable wst,
-            final Map<Gauge, GaugeDurationValuesFinder> durFinders) {
+            final Map<Gauge, GaugeDurationValuesFinder> durFinders, final Set<Infrastructure> infrastructures) {
 
         // Interpolate the infrastructure height in the wst table to get the corresponding Q
         final Calculation problems = new Calculation();
@@ -450,6 +326,12 @@
         row.putValue(SInfoResultType.floodDischarge, q);
         row.putValue(SInfoResultType.infrastructureHeight, infrastructure.getHeight());
         row.putValue(SInfoResultType.infrastructuretype, infrastructure.getInfrastructure().getType().getName());
+
+        int dochNichtRandom = (int) (q / 20 + 1);
+        if (dochNichtRandom > 10)
+            dochNichtRandom = 4;
+
+        row.putValue(SInfoResultType.infrastructurepart, "TEST_" + dochNichtRandom);
         // Determine the relative column position of the Q of the infrastructure height
         final QPosition qPos = wst.getQPosition(infrastructure.getStation().doubleValue(), q);
         if (qPos == null)
@@ -460,6 +342,9 @@
         final double dur = underflowDaysToOverflowDays(durFinders.get(gauge).getDuration(qGauge));
         // Set D in the result row
         row.putValue(SInfoResultType.floodDuration, dur);
+
+        final FloodDurationCalculationResult.Infrastructure typePart = new FloodDurationCalculationResult.Infrastructure(row);
+        infrastructures.add(typePart);
     }
 
     /**
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCurveProcessor.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationCurveProcessor.java	Wed Oct 09 16:17:16 2019 +0200
@@ -17,6 +17,8 @@
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.common.AbstractCalculationResult;
 import org.dive4elements.river.artifacts.model.FacetTypes;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.exports.DiagramGenerator;
 import org.dive4elements.river.exports.DurationCurveGenerator;
 import org.dive4elements.river.exports.process.DefaultProcessor;
@@ -83,25 +85,53 @@
     }
 
     public static Facet createMainValuesQFacet(final CallContext context, final String hash, final String id, final AbstractCalculationResult result,
-            final int facetIndex, final int resultIndex, final String description) {
+            final int facetIndex, final int resultIndex, final String description, final Infrastructure infrastructure) {
 
-        return new FloodDurationMainValuesQFacet(FACET_FLOOD_DURATION_MAINVALUES_Q, description);
+        return new FloodDurationMainValuesQFacet(FACET_FLOOD_DURATION_MAINVALUES_Q, description, facetIndex, infrastructure);
     }
 
     public static Facet createInfrastructureFacet(final CallContext context, final String hash, final String id, final AbstractCalculationResult result,
-            final int facetIndex, final int resultIndex, final String description, final AttributeKey riverside, final boolean isW) {
+            final int facetIndex, final int resultIndex, final boolean isW, final AttributeKey riverside) {
 
-        if (riverside == AttributeKey.LEFT) {
+        final String description = getLabel(context, isW, riverside);
+        final String facetName = getFacetName(riverside, isW);
+
+        return new FloodDurationInfrastructureFacet(facetName, isW, resultIndex, riverside, facetIndex, description);
+    }
+
+    private static String getFacetName(final AttributeKey riverside, final boolean isW) {
+        switch (riverside) {
+        case LEFT:
             if (isW)
-                return new FloodDurationInfrastructureFacet(FACET_FLOOD_DURATION_INFRASTRUCTURE_W_LEFT, description, riverside, isW);
+                return FACET_FLOOD_DURATION_INFRASTRUCTURE_W_LEFT;
             else
-                return new FloodDurationInfrastructureFacet(FACET_FLOOD_DURATION_INFRASTRUCTURE_Q_LEFT, description, riverside, isW);
+                return FACET_FLOOD_DURATION_INFRASTRUCTURE_Q_LEFT;
+
+        case RIGHT:
+            if (isW)
+                return FACET_FLOOD_DURATION_INFRASTRUCTURE_W_RIGHT;
+            else
+                return FACET_FLOOD_DURATION_INFRASTRUCTURE_Q_RIGHT;
+        default:
+            throw new IllegalStateException();
         }
-        else {
+    }
+
+    private static String getLabel(final CallContext context, final boolean isW, final AttributeKey riverside) {
+        switch (riverside) {
+        case LEFT:
             if (isW)
-                return new FloodDurationInfrastructureFacet(FACET_FLOOD_DURATION_INFRASTRUCTURE_W_RIGHT, description, riverside, isW);
+                return Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.w.left.description");
             else
-                return new FloodDurationInfrastructureFacet(FACET_FLOOD_DURATION_INFRASTRUCTURE_Q_RIGHT, description, riverside, isW);
+                return Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.q.left.description");
+        case RIGHT:
+            if (isW)
+                return Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.w.right.description");
+            else
+                return Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.q.right.description");
+
+        default:
+            throw new IllegalStateException();
         }
     }
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationInfrastructureFacet.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationInfrastructureFacet.java	Wed Oct 09 16:17:16 2019 +0200
@@ -17,6 +17,7 @@
 import org.dive4elements.artifacts.Artifact;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.CalculationResult;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 import org.dive4elements.river.exports.fixings.FixChartGenerator;
@@ -24,28 +25,32 @@
 import org.dive4elements.river.jfree.StickyAxisAnnotation;
 import org.dive4elements.river.model.Attribute.AttributeKey;
 
-
 /**
  * Facet to show W and Q annotation lines of an infrastructure height.
  */
 public class FloodDurationInfrastructureFacet extends DefaultFacet {
 
+    private static final long serialVersionUID = 1L;
+
     /** Own log. */
     private static Logger log = Logger.getLogger(FloodDurationInfrastructureFacet.class);
 
-    private final AttributeKey riverBankKey;
-
     private final boolean isW;
 
-    public FloodDurationInfrastructureFacet(final String name, final String description, final AttributeKey riverside, final boolean isW) {
+    private final int m_resultIndex;
+
+    private final AttributeKey m_riverside;
+
+    public FloodDurationInfrastructureFacet(final String name, final boolean isW, final int resultIndex, final AttributeKey attributeKey, final int facetIndex,
+            final String description) {
         this.description = description;
         this.name = name;
-        this.index = 0;
-        this.riverBankKey = riverside;
+        this.index = facetIndex;
+        this.m_riverside = attributeKey;
         this.isW = isW;
+        this.m_resultIndex = resultIndex;
     }
 
-
     /**
      * Returns the data this facet requires.
      */
@@ -60,24 +65,28 @@
 
         final FloodDurationCalculationResults data = (FloodDurationCalculationResults) res.getData();
 
+        final FloodDurationCalculationResult result = data.getResults().get(this.m_resultIndex);
+
         final double currentKm = FixChartGenerator.getCurrentKm(context);
 
-        final List<StickyAxisAnnotation> annotations = new FloodDurationCalculation(context).calcInfrastructureAnnotations(currentKm,
-                this.riverBankKey, this.isW, data.getResults().get(this.index));
+        final Calculation problems = new Calculation();
+
+        final FacetCalculator calculator = new FacetCalculator(context);
+        final List<StickyAxisAnnotation> annotations = calculator.calcInfrastructureAnnotations(problems, currentKm, this.isW, result, this.m_riverside);
 
         return new RiverAnnotation(this.description, annotations);
     }
 
-
     /**
      * Create a deep copy of this Facet.
+     *
      * @return a deep copy.
      */
     @Override
     public FloodDurationInfrastructureFacet deepCopy() {
-        final FloodDurationInfrastructureFacet copy = new FloodDurationInfrastructureFacet(this.name, this.description, this.riverBankKey, this.isW);
+        final FloodDurationInfrastructureFacet copy = new FloodDurationInfrastructureFacet(this.name, this.isW, this.m_resultIndex, this.m_riverside,
+                this.index, this.description);
         copy.set(this);
         return copy;
     }
 }
-
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationMainValuesQFacet.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationMainValuesQFacet.java	Wed Oct 09 16:17:16 2019 +0200
@@ -17,29 +17,34 @@
 import org.dive4elements.artifacts.Artifact;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 import org.dive4elements.river.exports.fixings.FixChartGenerator;
 import org.dive4elements.river.jfree.RiverAnnotation;
 import org.dive4elements.river.jfree.StickyAxisAnnotation;
 
-
 /**
  * Facet to show Main Q Values.
  */
 public class FloodDurationMainValuesQFacet extends DefaultFacet {
 
+    private static final long serialVersionUID = 1L;
+
     /** Own log. */
     private static Logger log = Logger.getLogger(FloodDurationMainValuesQFacet.class);
 
+    private final Infrastructure m_infrastructure;
+
     /** Trivial Constructor. */
-    public FloodDurationMainValuesQFacet(final String name, final String description) {
+    public FloodDurationMainValuesQFacet(final String name, final String description, final int facetIndex, final Infrastructure infrastructure) {
         this.description = description;
         this.name = name;
-        this.index = 1;
+        this.index = facetIndex;
+        this.m_infrastructure = infrastructure;
     }
 
-
     /**
      * Returns the data this facet requires.
      */
@@ -56,21 +61,25 @@
 
         final double currentKm = FixChartGenerator.getCurrentKm(context);
 
-        final List<StickyAxisAnnotation> annotations = new FloodDurationCalculation(context).calcMainValueQAnnotations(currentKm,
-                data.getResults().get(0));
+        final FloodDurationCalculationResult result = data.getResults().get(0);
+
+        final Calculation problems = new Calculation();
+
+        final FacetCalculator calculator = new FacetCalculator(context);
+        final List<StickyAxisAnnotation> annotations = calculator.calcMainValueQAnnotations(problems, currentKm, result);
 
         return new RiverAnnotation(this.description, annotations);
     }
 
-
     /**
      * Create a deep copy of this Facet.
+     *
      * @return a deep copy.
      */
     @Override
     public FloodDurationMainValuesQFacet deepCopy() {
-        final FloodDurationMainValuesQFacet copy = new FloodDurationMainValuesQFacet(this.name, this.description);
+        final FloodDurationMainValuesQFacet copy = new FloodDurationMainValuesQFacet(this.name, this.description, this.index, this.m_infrastructure);
         copy.set(this);
         return copy;
     }
-}
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationMainValuesWFacet.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationMainValuesWFacet.java	Wed Oct 09 16:17:16 2019 +0200
@@ -17,6 +17,7 @@
 import org.dive4elements.artifacts.Artifact;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.CalculationResult;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 import org.dive4elements.river.exports.fixings.FixChartGenerator;
@@ -28,6 +29,8 @@
  */
 public class FloodDurationMainValuesWFacet extends DefaultFacet {
 
+    private static final long serialVersionUID = 1L;
+
     /** Own log. */
     private static Logger log = Logger.getLogger(FloodDurationMainValuesWFacet.class);
 
@@ -52,14 +55,20 @@
 
         final double currentKm = FixChartGenerator.getCurrentKm(context);
 
-        final List<StickyAxisAnnotation> annotations = new FloodDurationCalculation(context).calcMainValueWAnnotations(currentKm,
-                data.getResults().get(0));
+        final FloodDurationCalculationResult result = data.getResults().get(0);
+
+        final Calculation problems = new Calculation();
+
+        final FacetCalculator calculator = new FacetCalculator(context);
+
+        final List<StickyAxisAnnotation> annotations = calculator.calcMainValueWAnnotations(problems, currentKm, result);
 
         return new RiverAnnotation(this.description, annotations);
     }
 
     /**
      * Create a deep copy of this Facet.
+     *
      * @return a deep copy.
      */
     @Override
@@ -68,4 +77,4 @@
         copy.set(this);
         return copy;
     }
-}
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationProcessor.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationProcessor.java	Wed Oct 09 16:17:16 2019 +0200
@@ -18,9 +18,9 @@
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.common.AbstractCalculationResult;
 import org.dive4elements.river.artifacts.common.AbstractProcessor;
-import org.dive4elements.river.artifacts.common.ResultFacet;
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.ValueGetter;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 import org.dive4elements.river.exports.DiagramGenerator;
@@ -39,8 +39,6 @@
 
     private static final String FACET_FLOOD_DURATION_RIGHT = "sinfo_facet_flood_duration.right";
 
-    private static final String FACET_FLOOD_DURATION_DESCRIPTION = "sinfo_facet_flood_duration.description";
-
     private static final String FACET_MAIN_VALUE_DURATION = "mainvalue.duration";
 
     private static final String FACET_MAIN_VALUE_DURATION_DESCRIPTION = "mainvalue.duration.description";
@@ -60,18 +58,11 @@
     }
 
     public static Facet createFloodDurationFacet(final CallContext context, final String hash, final String id, final AbstractCalculationResult result,
-            final int facetIndex, final int resultIndex) {
+            final int facetIndex, final int resultIndex, final Infrastructure infrastructure) {
 
-        if (facetIndex == 0) {
-            final String description = Resources.getMsg(context.getMeta(), FACET_FLOOD_DURATION_DESCRIPTION, FACET_FLOOD_DURATION_DESCRIPTION,
-                    SInfoResultType.localizeRiverside(context, AttributeKey.LEFT));
-            return new ResultFacet(facetIndex, resultIndex, FACET_FLOOD_DURATION_LEFT, description, I18N_AXIS_LABEL, ComputeType.ADVANCE, id, hash);
-        }
-        else {
-            final String description = Resources.getMsg(context.getMeta(), FACET_FLOOD_DURATION_DESCRIPTION, FACET_FLOOD_DURATION_DESCRIPTION,
-                    SInfoResultType.localizeRiverside(context, AttributeKey.RIGHT));
-            return new ResultFacet(facetIndex, resultIndex, FACET_FLOOD_DURATION_RIGHT, description, I18N_AXIS_LABEL, ComputeType.ADVANCE, id, hash);
-        }
+        final String description = infrastructure.getFloodDurationLabel(context);
+        final String facetName = infrastructure.getRiverside() == AttributeKey.LEFT ? FACET_FLOOD_DURATION_LEFT : FACET_FLOOD_DURATION_RIGHT;
+        return new InfrastructureResultFacet(facetIndex, resultIndex, facetName, description, I18N_AXIS_LABEL, id, hash, infrastructure);
     }
 
     public static Facet createMainValueDurationFacet(final CallContext context, final String hash, final String id, final FloodDurationCalculationResult result,
@@ -89,12 +80,8 @@
 
         final String facetName = bundle.getFacetName();
 
-        if (FACET_FLOOD_DURATION_LEFT.contentEquals(facetName)) {
-            return buildInfrastructureSeries(generator, bundle, theme, visible, AttributeKey.LEFT);
-        }
-
-        if (FACET_FLOOD_DURATION_RIGHT.contentEquals(facetName))
-            return buildInfrastructureSeries(generator, bundle, theme, visible, AttributeKey.RIGHT);
+        if (FACET_FLOOD_DURATION_LEFT.contentEquals(facetName) || FACET_FLOOD_DURATION_RIGHT.contentEquals(facetName))
+            return buildInfrastructureSeries(generator, bundle, theme, visible);
 
         if (FACET_MAIN_VALUE_DURATION.contentEquals(facetName)) {
 
@@ -119,12 +106,15 @@
         throw new UnsupportedOperationException(error);
     }
 
-    private String buildInfrastructureSeries(final DiagramGenerator generator, final ArtifactAndFacet bundle, final ThemeDocument theme, final boolean visible,
-            final AttributeKey riverside) {
+    private String buildInfrastructureSeries(final DiagramGenerator generator, final ArtifactAndFacet bundle, final ThemeDocument theme,
+            final boolean visible) {
+
+        final InfrastructureResultFacet infFacet = (InfrastructureResultFacet) bundle.getFacet();
+        final Infrastructure infrastructure = infFacet.getInfrastructure();
 
         final FloodDurationCalculationResult data = (FloodDurationCalculationResult) getResult(generator, bundle);
 
-        final double[][] points = data.getInfrastructurePoints(SInfoResultType.floodDuration, riverside);
+        final double[][] points = data.getInfrastructurePoints(SInfoResultType.floodDuration, infrastructure);
 
         return buildSeriesForPoints(points, generator, bundle, theme, visible, null);
     }
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationState.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodDurationState.java	Wed Oct 09 16:17:16 2019 +0200
@@ -10,7 +10,9 @@
 
 package org.dive4elements.river.artifacts.sinfo.flood_duration;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 import org.dive4elements.artifactdatabase.state.Facet;
@@ -25,7 +27,7 @@
 import org.dive4elements.river.artifacts.model.ReportFacet;
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
-import org.dive4elements.river.artifacts.sinfo.flood_duration.RiversideRadioChoice.RiversideChoiceKey;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.artifacts.states.DefaultState;
 import org.dive4elements.river.model.Attribute.AttributeKey;
 
@@ -80,6 +82,7 @@
 
         int resultIndex = 0;
         int themeCount = 0;
+
         for (final FloodDurationCalculationResult result : resultList) {
 
             if (resultIndex == 0) {
@@ -92,51 +95,45 @@
             }
 
             final FloodDurationAccess access = new FloodDurationAccess(sinfo);
-            if ((access.getRiverside() == RiversideChoiceKey.LEFT) || (access.getRiverside() == RiversideChoiceKey.BOTH))
-                facets.add(FloodDurationProcessor.createFloodDurationFacet(context, hash, this.id, result, 0, resultIndex));
-            if ((access.getRiverside() == RiversideChoiceKey.RIGHT) || (access.getRiverside() == RiversideChoiceKey.BOTH))
-                facets.add(FloodDurationProcessor.createFloodDurationFacet(context, hash, this.id, result, 1, resultIndex));
 
             final int waterlevelCount = result.getWaterlevelCount();
 
-            if ((access.getRiverside() == RiversideChoiceKey.LEFT) || (access.getRiverside() == RiversideChoiceKey.BOTH))
-                facets.add(FloodHeightProcessor.createFloodHeightFacet(context, hash, this.id, result, 0, resultIndex));
-            if ((access.getRiverside() == RiversideChoiceKey.RIGHT) || (access.getRiverside() == RiversideChoiceKey.BOTH))
-                facets.add(FloodHeightProcessor.createFloodHeightFacet(context, hash, this.id, result, 1, resultIndex));
-
             for (int j = 0; j < waterlevelCount; j++) {
 
                 // final String waterlevelLabel = result.getMainValueLabel(j);
                 // FIXME: use label as label for theme
 
                 // final int facetIndex, final int resultIndex, final int dataIndex
-                facets.add(FloodDurationProcessor.createMainValueDurationFacet(context, hash, this.id, result, themeCount, resultIndex, j));
-                facets.add(FloodHeightProcessor.createMainValueHeightFacet(context, hash, this.id, result, themeCount, resultIndex, j));
+                facets.add(FloodDurationProcessor.createMainValueDurationFacet(context, hash, this.id, result, themeCount++, resultIndex, j));
+                facets.add(FloodHeightProcessor.createMainValueHeightFacet(context, hash, this.id, result, themeCount++, resultIndex, j));
 
-                themeCount++;
             }
 
             final String nameW = Resources.getMsg(context.getMeta(), "sinfo.chart.flood_duration.curve.w");
             final String nameQ = Resources.getMsg(context.getMeta(), "sinfo.chart.flood_duration.curve.q");
-            facets.add(FloodDurationCurveProcessor.createFloodDurationWCurveFacet(context, hash, this.id, result, 0, resultIndex, nameW));
-            facets.add(FloodDurationCurveProcessor.createFloodDurationQCurveFacet(context, hash, this.id, result, 1, resultIndex, nameQ));
+            facets.add(FloodDurationCurveProcessor.createFloodDurationWCurveFacet(context, hash, this.id, result, themeCount++, resultIndex, nameW));
+            facets.add(FloodDurationCurveProcessor.createFloodDurationQCurveFacet(context, hash, this.id, result, themeCount++, resultIndex, nameQ));
             if (waterlevelCount >= 1) {
-                facets.add(FloodDurationCurveProcessor.createMainValuesWFacet(context, hash, this.id, result, 0, resultIndex,
+                facets.add(FloodDurationCurveProcessor.createMainValuesWFacet(context, hash, this.id, result, themeCount++, resultIndex,
                         Resources.getMsg(context.getMeta(), "sinfo.chart.flood_duration.curve.mainw")));
-                facets.add(FloodDurationCurveProcessor.createMainValuesQFacet(context, hash, this.id, result, 1, resultIndex,
-                        Resources.getMsg(context.getMeta(), "sinfo.chart.flood_duration.curve.mainq")));
+                facets.add(FloodDurationCurveProcessor.createMainValuesQFacet(context, hash, this.id, result, themeCount++, resultIndex,
+                        Resources.getMsg(context.getMeta(), "sinfo.chart.flood_duration.curve.mainq"), null));
             }
-            if ((access.getRiverside() == RiversideChoiceKey.LEFT) || (access.getRiverside() == RiversideChoiceKey.BOTH)) {
-                facets.add(FloodDurationCurveProcessor.createInfrastructureFacet(context, hash, this.id, result, 0, resultIndex,
-                        Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.w.left.description"), AttributeKey.LEFT, true));
-                facets.add(FloodDurationCurveProcessor.createInfrastructureFacet(context, hash, this.id, result, 0, resultIndex,
-                        Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.q.left.description"), AttributeKey.LEFT, false));
+
+            final AttributeKey choice = access.getRiverside().getAttributeKey();
+
+            for (final AttributeKey riversideC : getRiversides(choice)) {
+                facets.add(FloodDurationCurveProcessor.createInfrastructureFacet(context, hash, this.id, result, themeCount++, resultIndex, true, riversideC));
+
+                facets.add(FloodDurationCurveProcessor.createInfrastructureFacet(context, hash, this.id, result, themeCount++, resultIndex, false, riversideC));
             }
-            if ((access.getRiverside() == RiversideChoiceKey.RIGHT) || (access.getRiverside() == RiversideChoiceKey.BOTH)) {
-                facets.add(FloodDurationCurveProcessor.createInfrastructureFacet(context, hash, this.id, result, 0, resultIndex,
-                        Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.w.right.description"), AttributeKey.RIGHT, true));
-                facets.add(FloodDurationCurveProcessor.createInfrastructureFacet(context, hash, this.id, result, 0, resultIndex,
-                        Resources.getMsg(context.getMeta(), "sinfo_facet_flood_duration_curve.infra.q.right.description"), AttributeKey.RIGHT, false));
+
+            final Set<Infrastructure> infrastructures = result.getInfastructureMap();
+            for (final Infrastructure entry : infrastructures) {
+
+                facets.add(FloodDurationProcessor.createFloodDurationFacet(context, hash, this.id, result, themeCount++, resultIndex, entry));
+
+                facets.add(FloodHeightProcessor.createFloodHeightFacet(context, hash, this.id, result, themeCount++, resultIndex, entry));
             }
 
             facets.add(new DataFacet(FacetTypes.CSV, "CSV data", ComputeType.ADVANCE, hash, this.id));
@@ -152,6 +149,20 @@
         return res;
     }
 
+    private Set<AttributeKey> getRiversides(final AttributeKey choice) {
+        final Set<AttributeKey> usedRiversides = new HashSet<>();
+        if (choice.equals(AttributeKey.LEFT))
+            usedRiversides.add(AttributeKey.LEFT);
+        else if (choice.equals(AttributeKey.RIGHT))
+            usedRiversides.add(AttributeKey.RIGHT);
+
+        else {
+            usedRiversides.add(AttributeKey.RIGHT);
+            usedRiversides.add(AttributeKey.LEFT);
+        }
+        return usedRiversides;
+    }
+
     private CalculationResult doCompute(final SINFOArtifact sinfo, final CallContext context, final Object old) {
         if (old instanceof CalculationResult)
             return (CalculationResult) old;
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodHeightProcessor.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/FloodHeightProcessor.java	Wed Oct 09 16:17:16 2019 +0200
@@ -18,9 +18,9 @@
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.common.AbstractCalculationResult;
 import org.dive4elements.river.artifacts.common.AbstractProcessor;
-import org.dive4elements.river.artifacts.common.ResultFacet;
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
 import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.ValueGetter;
 import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
 import org.dive4elements.river.exports.DiagramGenerator;
@@ -39,12 +39,10 @@
 
     private static final String FACET_FLOOD_HEIGHT_RIGHT = "sinfo_facet_flood_height.right";
 
-    private static final String FACET_FLOOD_HEIGHT_DESCRIPTION = "sinfo_facet_flood_height.description";
+    private static final String FACET_MAIN_VALUE_HEIGHT_DESCRIPTION = "mainvalue.w.description";
 
     private static final String FACET_MAIN_VALUE_HEIGHT = "mainvalue.w";
 
-    private static final String FACET_MAIN_VALUE_HEIGHT_DESCRIPTION = "mainvalue.w.description";
-
     private static final String I18N_AXIS_LABEL = "sinfo.chart.flood_duration.height.section.yaxis.label";
 
     private static final Set<String> HANDLED_FACET_TYPES = new HashSet<>();
@@ -60,18 +58,11 @@
     }
 
     public static Facet createFloodHeightFacet(final CallContext context, final String hash, final String id, final AbstractCalculationResult result,
-            final int facetIndex, final int resultIndex) {
+            final int facetIndex, final int resultIndex, final Infrastructure infrastructure) {
 
-        if (facetIndex == 0) {
-            final String description = Resources.getMsg(context.getMeta(), FACET_FLOOD_HEIGHT_DESCRIPTION, FACET_FLOOD_HEIGHT_DESCRIPTION,
-                    SInfoResultType.localizeRiverside(context, AttributeKey.LEFT));
-            return new ResultFacet(facetIndex, resultIndex, FACET_FLOOD_HEIGHT_LEFT, description, I18N_AXIS_LABEL, ComputeType.ADVANCE, id, hash);
-        }
-        else {
-            final String description = Resources.getMsg(context.getMeta(), FACET_FLOOD_HEIGHT_DESCRIPTION, FACET_FLOOD_HEIGHT_DESCRIPTION,
-                    SInfoResultType.localizeRiverside(context, AttributeKey.RIGHT));
-            return new ResultFacet(facetIndex, resultIndex, FACET_FLOOD_HEIGHT_RIGHT, description, I18N_AXIS_LABEL, ComputeType.ADVANCE, id, hash);
-        }
+        final String facetName = infrastructure.getRiverside().equals(AttributeKey.LEFT) ? FACET_FLOOD_HEIGHT_LEFT : FACET_FLOOD_HEIGHT_RIGHT;
+        final String description = infrastructure.getFloodHeightLabel(context);
+        return new InfrastructureResultFacet(facetIndex, resultIndex, facetName, description, I18N_AXIS_LABEL, id, hash, infrastructure);
     }
 
     public static Facet createMainValueHeightFacet(final CallContext context, final String hash, final String id, final FloodDurationCalculationResult result,
@@ -87,12 +78,8 @@
 
         final String facetName = bundle.getFacetName();
 
-        if (FACET_FLOOD_HEIGHT_LEFT.contentEquals(facetName)) {
-            return buildInfrastructureSeries(generator, bundle, theme, visible, AttributeKey.LEFT);
-        }
-
-        if (FACET_FLOOD_HEIGHT_RIGHT.contentEquals(facetName))
-            return buildInfrastructureSeries(generator, bundle, theme, visible, AttributeKey.RIGHT);
+        if (FACET_FLOOD_HEIGHT_LEFT.contentEquals(facetName) || FACET_FLOOD_HEIGHT_RIGHT.contentEquals(facetName))
+            return buildInfrastructureSeries(generator, bundle, theme, visible);
 
         if (FACET_MAIN_VALUE_HEIGHT.contentEquals(facetName)) {
 
@@ -117,12 +104,15 @@
         throw new UnsupportedOperationException(error);
     }
 
-    private String buildInfrastructureSeries(final DiagramGenerator generator, final ArtifactAndFacet bundle, final ThemeDocument theme, final boolean visible,
-            final AttributeKey riverside) {
+    private String buildInfrastructureSeries(final DiagramGenerator generator, final ArtifactAndFacet bundle, final ThemeDocument theme,
+            final boolean visible) {
+
+        final InfrastructureResultFacet infFacet = (InfrastructureResultFacet) bundle.getFacet();
+        final Infrastructure infrastructure = infFacet.getInfrastructure();
 
         final FloodDurationCalculationResult data = (FloodDurationCalculationResult) getResult(generator, bundle);
 
-        final double[][] points = data.getInfrastructurePoints(SInfoResultType.infrastructureHeight, riverside);
+        final double[][] points = data.getInfrastructurePoints(SInfoResultType.infrastructureHeight, infrastructure);
 
         return buildSeriesForPoints(points, generator, bundle, theme, visible, null);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flood_duration/InfrastructureResultFacet.java	Wed Oct 09 16:17:16 2019 +0200
@@ -0,0 +1,35 @@
+/** 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.sinfo.flood_duration;
+
+import org.dive4elements.river.artifacts.common.ResultFacet;
+import org.dive4elements.river.artifacts.sinfo.flood_duration.FloodDurationCalculationResult.Infrastructure;
+import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
+
+/**
+ * @author Domenico Nardi Tironi
+ *
+ */
+public class InfrastructureResultFacet extends ResultFacet {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Infrastructure m_infrastructure;
+
+    public InfrastructureResultFacet(final int facetIndex, final int resultIndex, final String name, final String description, final String axisLabel,
+            final String id, final String hash, final Infrastructure infrastructure) {
+        super(facetIndex, resultIndex, name, description, axisLabel, ComputeType.ADVANCE, id, hash);
+        this.m_infrastructure = infrastructure;
+    }
+
+    public Infrastructure getInfrastructure() {
+        return this.m_infrastructure;
+    }
+}
--- a/artifacts/src/main/java/org/dive4elements/river/exports/LegendAggregator.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/LegendAggregator.java	Wed Oct 09 16:17:16 2019 +0200
@@ -17,7 +17,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -144,8 +143,7 @@
      */
     private List<LegendItem> findDistinctItems(final List<LegendItem> items) {
 
-        // HACKY: we hash by unique shape, because we know that the used shapes are cashed in a static cache.
-        final Map<Shape, LegendItem> shapeMap = new IdentityHashMap<>();
+        final Map<Shape, LegendItem> shapeMap = new HashMap<>();
 
         for (final LegendItem item : items) {
 
--- a/artifacts/src/main/java/org/dive4elements/river/utils/Formatter.java	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/utils/Formatter.java	Wed Oct 09 16:17:16 2019 +0200
@@ -101,6 +101,15 @@
     public static final int FLOWDEPTH_MAX_DIGITS = 2;
 
     /**
+     * Creates a NumberFormatter to format numbers with 3 significant figures.
+     *
+     * not used as real string output, only used to 'trim' numbers
+     *
+     */
+
+    public static final NumberFormat SIGINIFICANT_FORMATTER_3 = new DecimalFormat("##.#E0");
+
+    /**
      * Creates a localized NumberFormatter with given range of decimal digits.
      *
      * @param m
--- a/artifacts/src/main/resources/messages.properties	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/resources/messages.properties	Wed Oct 09 16:17:16 2019 +0200
@@ -338,7 +338,12 @@
 facet.flow_velocity.totalchannel.raw = v Totalchannel at {0} (raw)
 facet.flow_velocity.tauchannel.raw = Bottom Shear Stress Mainchannel at {0} (raw)
 facet.flow_velocity.velocity = V {0}
-facet.flow_velocity.waterlevel = Waterlevel {0}
+facet.flow_velocity.waterlevel = W {0}
+facet.flow_depth.waterlevel = W {0}
+facet.flow_depth.bedheight = Mean Bedheight {0}
+facet.flow_depth.bedheight.min = Min. Bedheight ({0})
+facet.flow_depth.bedheight.max = Max. Bedheight ({0})
+facet.waterlevel = W ({0})
 facet.bedheight_middle.single = Bed Level {0,number,####}
 facet.bedheight_middle.epoch = Bed Level Epoch {0,number,####} - {1,number,####}
 facet.bedquality.bed.porosity.toplayer = Porosity ({0,date} - {1,date}) ({2})
@@ -967,13 +972,16 @@
 common.export.csv.header.location = Location
 
 sinfo.export.flood_duration.csv.header.riverside = Bank
+sinfo.export.flood_duration.pdf.header.riverside = Bank
 sinfo.export.flood_duration.csv.header.infrastructure.height = Infrastructure Height
 sinfo.export.flood_duration.csv.header.duration = Flooding Duration [d/a]
 sinfo.export.flood_duration.csv.header.discharge = Flooding Duration Discharge Q
-sinfo.export.flood_duration.csv.header.infrastructure_type = Infrastructure Type
+sinfo.export.flood_duration.csv.header.infrastructure_type = Infrastructure
+sinfo.export.flood_duration.csv.header.infrastructure_part = Type/Designation
+sinfo.export.flood_duration.pdf.header.infrastructure_part = Type/ Designa-tion
 sinfo.export.flood_duration.pdf.header.duration = Flooding Duration Discharge [d/a]
 sinfo.export.flood_duration.pdf.header.discharge = Flooding Duration Discharge Q [m\u00b3/s]
-sinfo.export.flood_duration.pdf.header.infrastructure_type = Infrastruc-ture Type
+sinfo.export.flood_duration.pdf.header.infrastructure_type = Infrastruc-ture
 sinfo.export.flood_duration.pdf.header.infrastructure.height =  Infrastruc-ture Height
 
 sinfo.chart.flow_depth.section.title=h-Longitudinal Section
@@ -1034,9 +1042,11 @@
 
 sinfo_facet_waterlevel_difference.filtered = \u0394WL [cm]
 sinfo.facet.waterlevel_difference.filtered.description = \u0394WL ({0})
+sinfo.facet.waterlevel_difference.raw.description = \u0394WL ({0}) (raw)
 
 sinfo_facet_bedheight_difference.filtered = \u0394WL [cm]
 sinfo.facet.bedheight_difference.filtered.description = \u0394MBL ({0})
+sinfo.facet.bedheight_difference.raw.description = \u0394MBL ({0}) (raw)
 
 sinfo_facet_flow_depth_current.filtered = h-current [m]
 sinfo.facet.flow_depth_current.filtered.description =  h-current ({0})
@@ -1054,6 +1064,11 @@
 sinfo.flood_duration.header.w_index = Waterlevel {0} [{1}]
 sinfo.flood_duration.header.pdf.w_index = Waterlevel {0} 
 sinfo.flood_duration.header.q_index =  Q{0} [m\u00b3/s]
+sinfo.flood_duration.absolute.height = Absolute Height
+sinfo.flood_duration.absolute.height.left = Absolute Height Infrastructures BWaStr (left)
+sinfo.flood_duration.absolute.height.right = Absolute Height Infrastructures BWaStr (right)
+sinfo.flood_duration.infrastructures.left = Flooding Durations Infrastructures BWaStr (left)
+sinfo.flood_duration.infrastructures.right = Flooding Durations Infrastructures BWaStr (right)
 
 sinfo.export.flow_depth_minmax.csv.header.min = Minimum Flow Depth
 sinfo.export.flow_depth_minmax.csv.header.max = Maximum Flow Depth
--- a/artifacts/src/main/resources/messages_de.properties	Wed Oct 09 15:58:46 2019 +0200
+++ b/artifacts/src/main/resources/messages_de.properties	Wed Oct 09 16:17:16 2019 +0200
@@ -338,7 +338,12 @@
 facet.flow_velocity.tauchannel.raw = Sohlschubspannung Hauptgerinne bei {0} (Rohdaten)
 facet.flow_velocity.discharge = Abfluss bei {0}
 facet.flow_velocity.velocity = V ({0})
-facet.flow_velocity.waterlevel = W ({0})
+facet.flow_depth.waterlevel = W ({0})
+facet.flow_depth.bedheight = Mittl. Sohlh\u00f6he ({0})
+facet.flow_depth.bedheight.min = Min. Sohlh\u00f6he ({0})
+facet.flow_depth.bedheight.max = Max. Sohlh\u00f6he ({0})
+facet.flow_depth.discharge = Q ({0})
+facet.waterlevel = W ({0})
 facet.bedheight_middle.single = Sohlh\u00f6he {0,number,####}
 facet.bedheight_middle.epoch = Sohlh\u00f6he Epoche {0,number,####} - {1,number,####}
 facet.bedquality.bed.porosity.toplayer = Porosit\u00e4t ({0,date} - {1,date}) ({2})
@@ -967,10 +972,13 @@
 common.export.csv.header.location = Lage
 
 sinfo.export.flood_duration.csv.header.riverside = Uferseite
+sinfo.export.flood_duration.pdf.header.riverside = Ufer-seite
 sinfo.export.flood_duration.csv.header.infrastructure.height = H\u00f6he der Infrastruktur
 sinfo.export.flood_duration.csv.header.duration = \u00dcberflutungsdauer [d/a]
 sinfo.export.flood_duration.csv.header.discharge = \u00dcberflutungsdauerabfluss Q
-sinfo.export.flood_duration.csv.header.infrastructure_type = Infrastrukturtyp
+sinfo.export.flood_duration.csv.header.infrastructure_type = Infrastruktur
+sinfo.export.flood_duration.csv.header.infrastructure_part = Typ/Bezeichnung
+sinfo.export.flood_duration.pdf.header.infrastructure_part = Typ/ Bezeich-nung
 sinfo.export.flood_duration.pdf.header.duration = \u00dcber-flutungs-dauer [d/a]
 sinfo.export.flood_duration.pdf.header.discharge = \u00dcber-flutungs-dauer-abfluss Q [m\u00b3/s]
 sinfo.export.flood_duration.pdf.header.infrastructure_type = Infra-struktur-typ
@@ -1034,9 +1042,11 @@
 
 sinfo_facet_waterlevel_difference.filtered = \u0394WSPL [cm]
 sinfo.facet.waterlevel_difference.filtered.description = \u0394WSPL ({0})
+sinfo.facet.waterlevel_difference.raw.description = \u0394WSPL ({0}) (Rohdaten)
 
 sinfo_facet_bedheight_difference.filtered = \u0394WSPL [cm]
 sinfo.facet.bedheight_difference.filtered.description = \u0394MSH ({0})
+sinfo.facet.bedheight_difference.raw.description = \u0394MSH ({0}) (Rohdaten)
 
 sinfo_facet_flow_depth_current.filtered = h-aktuell [m]
 sinfo.facet.flow_depth_current.filtered.description =  h-aktuell ({0})
@@ -1054,6 +1064,11 @@
 sinfo.flood_duration.header.w_index = Wasserstand/Wasserspiegellage{0} [{1}]
 sinfo.flood_duration.header.pdf.w_index = Wasser-stand/ Wasser-spiegel-lage{0} 
 sinfo.flood_duration.header.q_index =  Q{0} [m\u00b3/s]
+sinfo.flood_duration.absolute.height = Absolute H\u00f6he
+sinfo.flood_duration.absolute.height.left = Absolute H\u00f6he Infrastrukturen BWaStr (links)
+sinfo.flood_duration.absolute.height.right = Absolute H\u00f6he Infrastrukturen BWaStr (rechts)
+sinfo.flood_duration.infrastructures.left = \u00dcberflutungsdauern Infrastrukturen BWaStr (links)
+sinfo.flood_duration.infrastructures.right = \u00dcberflutungsdauern Infrastrukturen BWaStr (rechts)
 
 sinfo.export.flow_depth_minmax.csv.header.min = Minimale Flie\u00dftiefe
 sinfo.export.flow_depth_minmax.csv.header.max = Maximale Flie\u00dftiefe

http://dive4elements.wald.intevation.org