changeset 8860:28df64078f27

Merge with 0862ea5d66baf60e7eee496d130a35157cc9ec12
author gernotbelger
date Fri, 19 Jan 2018 11:23:42 +0100
parents 7bbfb24e6eec (diff) 0862ea5d66ba (current diff)
children 571e5287dfbb
files artifacts/doc/conf/artifacts/manualpoints.xml artifacts/doc/conf/backend-db.xml artifacts/doc/conf/log4j.properties artifacts/doc/conf/seddb-db.xml artifacts/src/main/java/org/dive4elements/river/artifacts/model/FacetTypes.java artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/CalculationSelectSinfo.java artifacts/src/main/java/org/dive4elements/river/artifacts/states/WDifferencesState.java artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelPairSelectState.java artifacts/src/main/java/org/dive4elements/river/exports/process/FlowVelocityProcessor.java artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYSeries.java gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractUIProvider.java gwt-client/src/main/java/org/dive4elements/river/client/client/ui/CollectionView.java gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java gwt-client/src/main/java/org/dive4elements/river/client/client/ui/ParameterList.java gwt-client/src/main/java/org/dive4elements/river/client/client/ui/minfo/BedHeightsDatacagePanel.java gwt-client/src/main/webapp/WEB-INF/log4j.properties
diffstat 70 files changed, 3321 insertions(+), 637 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/TODO	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/TODO	Fri Jan 19 11:23:42 2018 +0100
@@ -1,1 +1,1 @@
-- Validation of the input values of an incoming feed() call
+- Validation of the input values of an incoming feed() call
\ No newline at end of file
--- a/artifacts/doc/conf/artifacts/manualpoints.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/artifacts/manualpoints.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -35,6 +35,8 @@
             <facet name="sq_relation_e.manualpoints" />
             <facet name="bed_longitudinal_section.manualpoints" />
             <facet name="sq_relation_f.manualpoints" />
+            
+            <!--  brauchen wir .manualpoints für sinfo ergebnisse? -->
           </facets>
         </outputmode>
       </outputmodes>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/doc/conf/artifacts/sinfo.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="sinfo">
+
+    <states>
+        <state id="state.sinfo.river" description="state.sinfo.river" state="org.dive4elements.river.artifacts.states.RiverSelect" helpText="help.state.sinfo.river">
+            <data name="river" type="String" />
+        </state>
+ 
+        <transition transition="org.dive4elements.river.artifacts.transitions.DefaultTransition">
+                <from state="state.sinfo.river"/>
+                <to state="state.sinfo.calculation_mode"/>
+        </transition>
+
+        <state id="state.sinfo.calculation_mode" description="state.sinfo.calculation_mode" state="org.dive4elements.river.artifacts.sinfo.CalculationSelectSinfo" helpText="help.state.sinfo.calculation_mode">
+            <data name="calculation_mode" type="String"/>
+        </state>
+
+        <!-- Fliesstiefen -->
+        <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
+            <from state="state.sinfo.calculation_mode"/>
+            <to state="state.sinfo.distance_only"/>
+            <condition data="calculation_mode" value="sinfo_calc_flow_depth" operator="equal"/>
+        </transition>
+
+        <state id="state.sinfo.distance_only" description="state.sinfo.distance_only" state="org.dive4elements.river.artifacts.states.DistanceOnlySelect" helpText="help.state.sinfo.distance_only">
+            <data name="ld_from" type="Double" />
+            <data name="ld_to"   type="Double" />
+        </state>
+
+        <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
+            <from state="state.sinfo.distance_only"/>
+            <to state="state.sinfo.waterlevel_soundings_select"/>
+            <condition data="calculation_mode" value="sinfo_calc_flow_depth" operator="equal"/>
+        </transition>
+
+        <state id="state.sinfo.waterlevel_soundings_select" description="state.sinfo.waterlevel_soundings_select" state="org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthPairSelectState" helpText="help.state.sinfo.waterlevel_soundings_select">
+            <data name="diffids" type="String" />
+        </state>
+
+        <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
+            <from state="state.sinfo.waterlevel_soundings_select"/>
+            <to state="state.sinfo.use_transport_bodies"/>
+            <condition data="calculation_mode" value="sinfo_calc_flow_depth" operator="equal"/>
+        </transition>
+
+        <!-- FIXME: Nur Anzeigen, wenn Transportkörperhöhen vorliegen! -->
+        <state id="state.sinfo.use_transport_bodies" description="state.sinfo.use_transport_bodies" state="org.dive4elements.river.artifacts.sinfo.flowdepth.UseTransportBodiesChoice" helpText="help.state.sinfo.use_transport_bodies">
+            <data name="use_transport_bodies" type="Boolean" />
+        </state>
+
+        <transition transition="org.dive4elements.river.artifacts.transitions.ValueCompareTransition">
+            <from state="state.sinfo.use_transport_bodies"/>
+            <to state="state.sinfo.flow_depth"/>
+            <condition data="calculation_mode" value="sinfo_calc_flow_depth" operator="equal"/>
+        </transition>
+
+        <state id="state.sinfo.flow_depth" description="state.sinfo.flow_depth" state="org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthState" helpText="help.state.sinfo.flow_depth">
+            <outputmodes>
+                <outputmode name="sinfo_flow_depth" description="output.flow_velocity" mime-type="image/png" type="chart">
+                    <facets>
+                        <!-- REMARK: id's that ends with 'filtered' are handled differently ' -->
+                        <facet name="sinfo_flow_depth.filtered" description="Facet for mean flow depth, filtered by current zoom state"/>
+
+                        <!-- 
+                        <facet name="flow_velocity.totalchannel" description="A facet for total channels"/>
+                        <facet name="flow_velocity.mainchannel" description="A facet for main channels"/>
+                        <facet name="flow_velocity.mainchannel" description="A facet for main channels"/>
+                        <facet name="flow_velocity.tau" description="A facet for tau"/>
+                        <facet name="flow_velocity.totalchannel.filtered" description="A facet for total channels"/>
+                        <facet name="flow_velocity.mainchannel.filtered" description="A facet for main channels"/>
+                        <facet name="flow_velocity.tau.filtered" description="A facet for tau"/>
+                        <facet name="flow_velocity.discharge" description="A facet for discharges"/>
+                        <facet name="flow_velocity.measurement" description="A facet for measured flow velocities"/>
+                        <facet name="longitudinal_section.annotations" description="facet.longitudinal_section.annotations"/>
+                        <facet name="flow_velocity.manualpoints"/>
+                        <facet name="bed_longitudinal_section.diameter.toplayer"/>
+                        <facet name="bed_longitudinal_section.diameter.sublayer"/>
+                        <facet name="bed_longitudinal_section.diameter.bedload"/>
+                         -->
+                    </facets>
+                </outputmode>
+
+                <outputmode name="sinfo_flowdepth_export" description="output.sinfo_flowdepth_export" mime-type="text/plain" type="export">
+                  <facets>
+                    <facet name="csv" description="facet.sinfo_flowdepth_export.csv" />
+                    <facet name="pdf" description="facet.sinfo_flowdepth_export.pdf" />
+                  </facets>
+                </outputmode>
+
+                <outputmode name="sinfo_flowdepth_report" description="output.sinfo_flowdepth_report" mime-type="text/xml" type="report">
+                  <facets>
+                    <facet name="report" description="facet.sinfo_flowdepth_report"/>
+                  </facets>
+                </outputmode>
+
+            </outputmodes>
+        </state>        
+
+    </states>
+
+</artifact>
\ No newline at end of file
--- a/artifacts/doc/conf/backend-db.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/backend-db.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -5,7 +5,7 @@
     <password>d4e</password>
     <dialect>org.hibernate.dialect.PostgreSQLDialect</dialect>
     <driver>org.postgresql.Driver</driver>
-    <url>jdbc:postgresql://localhost:5432/d4e</url>
+    <url>jdbc:postgresql://localhost:63333/d4e</url>
     <validation-query>select 1 from rivers</validation-query>
     <max-wait>30000</max-wait>
 </backend-database>
--- a/artifacts/doc/conf/conf.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/conf.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -22,6 +22,8 @@
     <!ENTITY sqrelation-defaults SYSTEM "generators/sqrelation-diagram-defaults.xml">
     <!ENTITY longitudinal-defaults SYSTEM "generators/longitudinal-diagram-defaults.xml">
     <!ENTITY discharge-defaults SYSTEM "generators/discharge-diagram-defaults.xml">
+
+    <!ENTITY sinfo_artifact SYSTEM "artifacts/sinfo.xml">    
 ]>
 <artifact-database>
     <export-secret>YOUR_SECRET</export-secret>
@@ -168,6 +170,12 @@
             <artifact-factory name="porosity" description="Factory to create an artifact to show porosity values."
                 ttl="3600000"
                 artifact="org.dive4elements.river.artifacts.D4EArtifact">org.dive4elements.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+                
+            <!-- SINFO specific Artifacts -->
+            <artifact-factory name="sinfo" description="Factory to create an artifact to be used in module sinfo."
+                ttl="3600000"
+                artifact="org.dive4elements.river.artifacts.sinfo.SINFOArtifact">org.dive4elements.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+                
         </artifact-factories>
 
         <user-factory name="default" description="Factory to create new users">org.dive4elements.artifactdatabase.DefaultUserFactory</user-factory>
@@ -299,6 +307,8 @@
         &gaugedischargecurve-artifact;
         &sedimentload-artifact;
         &sedimentload-ls-artifact;
+       
+        &sinfo_artifact;
     </artifacts>
 
     &modules;
--- a/artifacts/doc/conf/generators/generators.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/generators/generators.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -58,5 +58,10 @@
     <output-generator names="gauge_discharge_curve_at_export" class="org.dive4elements.river.exports.ATExporter"/>
     <output-generator names="fix_wq_curve_at_export" class="org.dive4elements.river.exports.fixings.FixATExport"/>
     <output-generator names="wsplgen" class="org.dive4elements.river.exports.ShapeExporter"/>
+    
+    <!-- SINFO -->
+    <output-generator names="sinfo_flowdepth_report" class="org.dive4elements.river.exports.ReportGenerator"/>
+    <output-generator names="sinfo_flowdepth_export" class="org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthExporter"/>
+
 </output-generators>
 
--- a/artifacts/doc/conf/generators/longitudinal-diagram-defaults.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/generators/longitudinal-diagram-defaults.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -14,8 +14,8 @@
     <axis name="Velocity"/>
     <axis name="Tau"/>
     <axis name="Q" include-zero="true"/>
-    <domain-axis key="chart.longitudinal.section.xaxis.label" default="Fluss-Km"
-            inverted="org.dive4elements.river.exports.IsKmUpEvaluator()">
+    <axis name="Flowdepth" />
+    <domain-axis key="chart.longitudinal.section.xaxis.label" default="Fluss-Km" inverted="org.dive4elements.river.exports.IsKmUpEvaluator()">
         <arg expr="artifact.river"/>
     </domain-axis>
     <!-- Default longitudinal section Processors -->
@@ -42,4 +42,7 @@
     <processor class="org.dive4elements.river.exports.process.ShearStressProcessor"        axis="Tau"/>
     <processor class="org.dive4elements.river.exports.process.SedimentDensityProcessor"    axis="Density"/>
     <processor class="org.dive4elements.river.exports.process.BedHeightProcessor"          axis="W"/>
-</longitudinal-defaults>
+
+    <!-- S-INFO -->
+    <processor class="org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthProcessor" axis="Flowdepth"/>
+</longitudinal-defaults>
\ No newline at end of file
--- a/artifacts/doc/conf/generators/longitudinal-diagrams.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/generators/longitudinal-diagrams.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -87,5 +87,20 @@
             <arg expr="artifact.river"/>
         </subtitle>
     </output-generator>
-</longitudinal-diagrams>
 
+<!--
+,flow_velocity_chartinfo 
+ -->
+    <output-generator
+        names="sinfo_flow_depth,sinfo_flow_depth_chartinfo"
+        class="org.dive4elements.river.exports.LongitudinalSectionGenerator2"
+        converter="org.dive4elements.river.exports.DiagramAttributes">
+        <title key="sinfo.chart.flow_depth.section.title" default="h-Längsschnitt (DEFAULT)"/>
+        &longitudinal-defaults;
+        <processor class="org.dive4elements.river.exports.process.ManualPointsProcessor"
+            axis="FlowDepth"/>
+        <subtitle key="chart.w_differences.subtitle" default="-">
+            <arg expr="artifact.river"/>
+        </subtitle>
+    </output-generator>
+</longitudinal-diagrams>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/doc/conf/jasper/FIXME.txt	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,6 @@
+Awful:
+- source files together with compiled files in one directory
+- source files part of 'conf', but user will probably never be able to conf them
+- compiled files checked-in into SCM
+- compilation process not part of maven (not even part of project at all)
+- we have different jasper reports for different languages; instead introduce variables and give i10n strings as report variables
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/doc/conf/jasper/templates/sinfo.flowdepth.jrxml	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="flysreport" language="groovy" pageWidth="595" pageHeight="842" columnWidth="515" leftMargin="60" rightMargin="20" topMargin="20" bottomMargin="20">
+	<property name="ireport.zoom" value="1.0"/>
+	<property name="ireport.x" value="0"/>
+	<property name="ireport.y" value="0"/>
+
+	<field name="meta:version" class="java.lang.String"/>
+	<field name="meta:user" class="java.lang.String"/>
+	<field name="meta:date" class="java.lang.String"/>
+	<field name="meta:river" class="java.lang.String"/>
+	<field name="meta:range" class="java.lang.String"/>
+
+	<field name="data:0" class="java.lang.String"/>
+	<field name="data:1" class="java.lang.String"/>
+	<field name="data:2" class="java.lang.String"/>
+	<field name="data:3" class="java.lang.String"/>
+	<field name="data:4" class="java.lang.String"/>
+	<field name="data:5" class="java.lang.String"/>
+<!--	
+	<field name="data:6" class="java.lang.String"/>
+	<field name="data:7" class="java.lang.String"/>
+	<field name="data:8" class="java.lang.String"/>
+	<field name="data:9" class="java.lang.String"/>
+	<field name="data:10" class="java.lang.String"/>
+	<field name="data:11" class="java.lang.String"/>
+-->
+
+	<background>
+		<band splitType="Stretch"/>
+	</background>
+	<title>
+		<band height="182" splitType="Stretch">
+			<staticText>
+				<reportElement x="0" y="1" width="165" height="30"/>
+				<textElement>
+					<font size="18"/>
+				</textElement>
+				<text><![CDATA[Ergebnisausgabe]]></text>
+			</staticText>
+			<!--
+			<textField>
+				<reportElement x="165" y="0" width="350" height="31"/>
+				<textElement>
+					<font size="18"/>
+				</textElement>
+				<textFieldExpression><![CDATA[$F{meta:version}]]></textFieldExpression>
+			</textField>
+			<textField>
+				<reportElement x="165" y="0" width="350" height="31"/>
+				<textElement>
+					<font size="18"/>
+				</textElement>
+				<textFieldExpression><![CDATA[$F{meta:user}]]></textFieldExpression>
+			</textField>
+			<textField>
+				<reportElement x="165" y="0" width="350" height="31"/>
+				<textElement>
+					<font size="18"/>
+				</textElement>
+				<textFieldExpression><![CDATA[$F{river}]]></textFieldExpression>
+			</textField>
+			<textField>
+				<reportElement x="0" y="31" width="515" height="26"/>
+				<textElement>
+					<font size="14"/>
+				</textElement>
+				<textFieldExpression><![CDATA[$F{calculation}]]></textFieldExpression>
+			</textField>
+			-->
+			
+			<staticText>
+				<reportElement x="0" y="70" width="123" height="20"/>
+				<textElement/>
+				<text><![CDATA[FLYS-Version:]]></text>
+			</staticText>
+			<textField>
+				<reportElement x="123" y="70" width="392" height="20"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{meta:version}]]></textFieldExpression>
+			</textField>
+			
+			<staticText>
+				<reportElement x="0" y="90" width="123" height="20"/>
+				<textElement/>
+				<text><![CDATA[Bearbeiter:]]></text>
+			</staticText>
+			<textField>
+				<reportElement x="123" y="90" width="392" height="20"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{meta:user}]]></textFieldExpression>
+			</textField>
+			
+			<staticText>
+				<reportElement x="0" y="110" width="123" height="20"/>
+				<textElement/>
+				<text><![CDATA[Datum der Erstellung:]]></text>
+			</staticText>
+			<textField>
+				<reportElement x="123" y="110" width="392" height="20"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{meta:date}]]></textFieldExpression>
+			</textField>
+			
+			<staticText>
+				<reportElement x="0" y="130" width="123" height="20"/>
+				<textElement/>
+				<text><![CDATA[Gewässer:]]></text>
+			</staticText>
+			<textField>
+				<reportElement x="123" y="130" width="392" height="20"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{meta:river}]]></textFieldExpression>
+			</textField>
+			
+			<staticText>
+				<reportElement x="0" y="150" width="123" height="20"/>
+				<textElement/>
+				<text><![CDATA[Bereich:]]></text>
+			</staticText>
+			<textField>
+				<reportElement x="123" y="150" width="392" height="20"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{meta:range}]]></textFieldExpression>
+			</textField>
+		</band>
+	</title>
+	<columnHeader>
+		<band height="25" splitType="Stretch">
+			<line>
+				<reportElement x="0" y="19" width="515" height="1"/>
+			</line>
+			<staticText>
+				<reportElement x="0" y="0" width="60" height="20"/>
+				<textElement/>
+				<text><![CDATA[Fluss-Km]]></text>
+			</staticText>
+			<staticText>
+				<reportElement x="60" y="0" width="65" height="20"/>
+				<textElement/>
+				<text><![CDATA[W [NN + m]]]></text>
+			</staticText>
+			<staticText>
+				<reportElement x="125" y="0" width="65" height="20"/>
+				<textElement/>
+				<text><![CDATA[Q [m³/s]]]></text>
+			</staticText>
+			<staticText>
+				<reportElement x="190" y="0" width="85" height="20"/>
+				<textElement/>
+				<text><![CDATA[Bezeichnung]]></text>
+			</staticText>
+			<staticText>
+				<reportElement x="275" y="0" width="110" height="20"/>
+				<textElement/>
+				<text><![CDATA[Lage]]></text>
+			</staticText>
+			<staticText>
+				<reportElement x="385" y="0" width="130" height="20"/>
+				<textElement/>
+				<text><![CDATA[Bezugspegel]]></text>
+			</staticText>
+		</band>
+	</columnHeader>
+	<detail>
+		<band height="14" splitType="Stretch">
+			<textField isBlankWhenNull="true">
+				<reportElement x="0" y="0" width="60" height="14"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{data:0}]]></textFieldExpression>
+			</textField>
+			<textField isBlankWhenNull="true">
+				<reportElement x="60" y="0" width="65" height="14"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{data:1}]]></textFieldExpression>
+			</textField>
+			<textField isBlankWhenNull="true">
+				<reportElement x="125" y="0" width="65" height="14"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{data:2}]]></textFieldExpression>
+			</textField>
+			<textField isStretchWithOverflow="true" isBlankWhenNull="false">
+				<reportElement stretchType="RelativeToBandHeight" x="190" y="0" width="85" height="14"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{data:3}]]></textFieldExpression>
+			</textField>
+			<textField isStretchWithOverflow="true" isBlankWhenNull="true">
+				<reportElement stretchType="RelativeToBandHeight" x="275" y="0" width="110" height="14" isPrintWhenDetailOverflows="true"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{data:4}]]></textFieldExpression>
+			</textField>
+			<textField isStretchWithOverflow="true" isBlankWhenNull="true">
+				<reportElement stretchType="RelativeToBandHeight" x="385" y="0" width="130" height="14"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[$F{data:5}]]></textFieldExpression>
+			</textField>
+		</band>
+	</detail>
+	<pageFooter>
+		<band height="29" splitType="Stretch">
+			<textField evaluationTime="Report">
+				<reportElement x="458" y="9" width="57" height="20"/>
+				<textElement/>
+				<textFieldExpression><![CDATA[" / " + $V{PAGE_NUMBER}]]></textFieldExpression>
+			</textField>
+			<textField>
+				<reportElement x="403" y="9" width="55" height="20"/>
+				<textElement textAlignment="Right"/>
+				<textFieldExpression><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression>
+			</textField>
+		</band>
+	</pageFooter>
+	<summary>
+		<band height="42" splitType="Stretch"/>
+	</summary>
+</jasperReport>
--- a/artifacts/doc/conf/log4j.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/log4j.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -4,7 +4,7 @@
 log4j.category.org.hibernate=WARN
 log4j.category.net.sf.ehcache=WARN
 log4j.category.org.eclipse=WARN
-log4j.category.org.restlet=INFO
+log4j.category.org.restlet=WARN
 
 
 ########## APPENDER SETTINGS
@@ -12,10 +12,7 @@
 log4j.appender.FLYS.layout.ConversionPattern=%d{HH:mm:ss} [%t] %-5p %c{1} - %m%n
 
 
-log4j.appender.FLYS=org.apache.log4j.RollingFileAppender
-log4j.appender.FLYS.File=/var/log/d4e-river/d4e-server.log
-log4j.appender.FLYS.MaxFileSize=5000KB
-log4j.appender.FLYS.MaxBackupIndex=1
+log4j.appender.FLYS=org.apache.log4j.ConsoleAppender
 
 log4j.logger.org.dive4elements.artifactdatabase.rest.Standalone=INFO, START
 log4j.appender.START=org.apache.log4j.ConsoleAppender
--- a/artifacts/doc/conf/meta-data.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/meta-data.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -242,6 +242,19 @@
                   <dc:when test="$out = 'floodmap'">
                     <dc:call-macro name="flood-map-complete"/>
                   </dc:when>
+
+                  <dc:comment> S-INFO </dc:comment>
+                  <dc:when test="$out = 'sinfo_flowdepth_minfo_heights'">
+                    <dc:call-macro name="bed-heights-single"/>
+                  </dc:when>
+                  <dc:when test="$out = 'sinfo_flowdepth_waterlevels'">
+                    <!-- FIXME: check requirement what to show here... -->
+                    <dc:call-macro name="basedata_0"/>
+                    <dc:call-macro name="basedata_1_additionals"/>
+                    <dc:call-macro name="basedata_2_fixations"/>
+                    <dc:call-macro name="basedata_4_heightmarks-points"/>
+                    <dc:call-macro name="basedata_5_flood-protections"/>
+                  </dc:when>
                 </dc:choose>
               </dc:iterate>
             </dc:otherwise>
--- a/artifacts/doc/conf/modules.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/modules.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -3,6 +3,9 @@
   <module name="winfo" selected="true">
     <river name="Beispielfluss"/>
   </module>
+  <module name="sinfo">
+    <river name="Beispielfluss"/>
+  </module>
   <module name="minfo">
     <river name="Beispielfluss"/>
   </module>
@@ -15,5 +18,4 @@
   <module name="fixanalysis">
     <river name="Beispielfluss"/>
   </module>
-</modules>
-
+</modules>
\ No newline at end of file
--- a/artifacts/doc/conf/seddb-db.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/seddb-db.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -5,7 +5,7 @@
     <password>seddb</password>
     <dialect>org.hibernate.dialect.PostgreSQLDialect</dialect>
     <driver>org.postgresql.Driver</driver>
-    <url>jdbc:postgresql://localhost:5432/seddb</url>
+    <url>jdbc:postgresql://localhost:63333/seddb</url>
     <validation-query>select 1 from gewaesser</validation-query>
     <max-wait>30000</max-wait>
     <!--
--- a/artifacts/doc/conf/themes.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/themes.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -404,6 +404,8 @@
 
         <!-- Mappings for porosity from backend-DB -->
         <mapping from="porosity" to="Porosity" />
+
+        <!--  Mappings for S-INFO -->
+        <mapping from="sinfo_flow_depth.filtered" to="SInfoFlowDepth" />
     </mappings>
-
-</themes>
+</themes>
\ No newline at end of file
--- a/artifacts/doc/conf/themes/default.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/themes/default.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -2872,4 +2872,16 @@
             <field name="linecolor" type="color" default="175, 175, 175" />
         </fields>
     </theme>
+
+    <!-- S-INFO Flow-Depth Themes -->
+    <theme name="SInfoFlowDepth">
+        <inherits>
+            <inherit from="LongitudinalSection" />
+        </inherits>
+        <fields>
+            <field name="linecolor" type="Color" display="Linienfarbe"
+                default="0, 0, 255" />
+        </fields>
+    </theme>
+    
 </themegroup>
--- a/artifacts/doc/conf/themes/second.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/doc/conf/themes/second.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -2869,4 +2869,15 @@
             <field name="linecolor" type="color" default="175, 175, 175" />
         </fields>
     </theme>
-</themegroup>
+    
+    <!-- S-INFO Flow-Depth Themes -->
+    <theme name="SInfoFlowDepth">
+        <inherits>
+            <inherit from="LongitudinalSection" />
+        </inherits>
+        <fields>
+            <field name="linecolor" type="Color" display="Linienfarbe"
+                default="0, 0, 255" />
+        </fields>
+    </theme>
+</themegroup>
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/DataFacet.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/DataFacet.java	Fri Jan 19 11:23:42 2018 +0100
@@ -104,12 +104,14 @@
      */
     @Override
     public Facet deepCopy() {
+    	// FIXME: why not use the full constructor instead? would also fix the next problem
         DataFacet copy = new DataFacet();
+        // FIXME: usage of internal knowledge of parent class...
+        // Either the set method should be correctly overwritten, or implement a correct copy-constructor!
         copy.set(this);
         copy.type    = type;
         copy.hash    = hash;
         copy.stateId = stateId;
         return copy;
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/FacetTypes.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/FacetTypes.java	Fri Jan 19 11:23:42 2018 +0100
@@ -189,6 +189,8 @@
         SQF("sq_relation_f"),
         HD("historical_discharge"),
         HDWQ("historical_discharge_wq");
+        // FIXME: do we need this? and why?
+    	// SFD("sinfo_flow_depth");
 
         private String chartTypeString;
 
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/resources/Resources.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/resources/Resources.java	Fri Jan 19 11:23:42 2018 +0100
@@ -124,18 +124,18 @@
      * @return a translated string.
      */
     public static String getMsg(
-            CallMeta meta,
-            String   key,
-            String   def,
-            Object[] args)
+    		CallMeta meta,
+    		String   key,
+    		String   def,
+    		Object... args)
     {
-        String template = getMsg(meta, key, (String)null);
-
-        if (template == null) {
-            return def;
-        }
-
-        return format(meta, template, args);
+    	String template = getMsg(meta, key, (String)null);
+    	
+    	if (template == null) {
+    		return def;
+    	}
+    	
+    	return format(meta, template, args);
     }
 
     public static String format(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/CalculationSelectSinfo.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,78 @@
+/* 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.log4j.Logger;
+import org.dive4elements.artifacts.Artifact;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.artifacts.common.utils.XMLUtils;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.states.DefaultState;
+import org.w3c.dom.Element;
+
+/**
+ * @author Gernot Belger
+ */
+public class CalculationSelectSinfo extends DefaultState {
+
+	private static final long serialVersionUID = 1L;
+
+	/** The log that is used in this class. */
+    private static Logger log = Logger.getLogger(CalculationSelectSinfo.class);
+
+    public CalculationSelectSinfo() {
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        final CallMeta meta   = context.getMeta();
+        
+        final Collection<Element> calcs = new ArrayList<>(SinfoCalcMode.values().length);
+
+        for (final SinfoCalcMode calcMode : SinfoCalcMode.values()) {
+        	final String calc = calcMode.name();
+        	
+        	final String label = Resources.getMsg(meta, calc, calc);
+        	
+        	final Element element = createItem(
+        			cr, new String[] {
+        					label,
+        					calc
+        			});
+        	calcs.add(element);
+		}
+        
+        return calcs.toArray(new Element[calcs.size()]);
+    }
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        log.debug("CalculationSelect.validate");
+
+        final SINFOArtifact sinfo = (SINFOArtifact) artifact;
+        /* throws an exception if calculation mode is invalid */
+        sinfo.getCalculationMode();
+        return true;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/SINFOArtifact.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,73 @@
+/* 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.dive4elements.river.artifacts.D4EArtifact;
+
+/**
+ * The default MINFO artifact.
+ *
+ * @author Gernot Belger
+ */
+public class SINFOArtifact
+extends      D4EArtifact
+{
+    /** Error message that is thrown if no mode has been chosen. */
+    private static final String ERROR_NO_CALCULATION_MODE =
+        "error_feed_no_calculation_mode";
+
+    /** Error message that is thrown if an invalid calculation mode has been
+     * chosen. */
+    private static  final String ERROR_INVALID_CALCULATION_MODE =
+        "error_feed_invalid_calculation_mode";
+
+	
+    /** The name of the artifact. */
+    private static final String ARTIFACT_NAME = "sinfo";
+
+    private static final String FIELD_FIVER = "river";
+
+    private static final String FIELD_MODE = "calculation_mode";
+    
+    /**
+     * Default constructor, because it's serializable.
+     */
+    public SINFOArtifact() {
+    }
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    @Override
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+    
+    public SinfoCalcMode getCalculationMode() {
+
+        final String    calc = getDataAsString(FIELD_MODE);
+        if (calc == null) {
+        	throw new IllegalArgumentException(ERROR_NO_CALCULATION_MODE);
+        }
+
+        try {
+			return SinfoCalcMode.valueOf(StringUtils.trimToEmpty(calc).toLowerCase());
+		} catch (Exception e) {
+			throw new IllegalArgumentException(ERROR_INVALID_CALCULATION_MODE, e);
+		}
+	}
+    
+    public String getRiver() {
+    	return getDataAsString(FIELD_FIVER);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/SinfoCalcMode.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,20 @@
+/* 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;
+
+public enum SinfoCalcMode{
+	sinfo_calc_flow_depth,
+	sinfo_calc_flow_depth_development,
+    sinfo_calc_flow_depth_minmax,
+    sinfo_calc_grounding,
+    sinfo_calc_transport_bodies_heights,
+    sinfo_calc_infrastructures_inundation_duration
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthAccess.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,88 @@
+/* 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.flowdepth;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.dive4elements.river.artifacts.access.RangeAccess;
+import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
+import org.dive4elements.river.artifacts.sinfo.SinfoCalcMode;
+import org.dive4elements.river.backend.utils.StringUtil;
+
+/**
+ * Access to the flow depth calculation type specific SInfo artifact data.
+ * REMARK: this class is NOT intended to be hold in the results (or anywhere else), in order to avoid a permanent reference to the artifact instance. 
+ * Hence we do NOT cache any data.
+ * 
+ * @author Gernot Belger
+ */
+public class FlowDepthAccess
+extends      RangeAccess
+{
+	public static class DifferencesPair
+	{
+		private final String wstId;
+		private final String soundingId;
+
+		public DifferencesPair( final String wstId, final String soundingId ) {
+			this.wstId = wstId;
+			this.soundingId = soundingId;
+		}
+		
+		public String getWstId() {
+			return this.wstId;
+		}
+
+		public String getSoundingId() {
+			return this.soundingId;
+		}
+	}
+
+	private static final String FIELD_USE_TKH = "use_transport_bodies"; //$NON-NLS-1$
+
+	public FlowDepthAccess(final SINFOArtifact artifact) {
+		super(artifact);
+
+		/* assert calculation mode */
+		final SinfoCalcMode calculationMode = artifact.getCalculationMode();
+		assert(calculationMode == SinfoCalcMode.sinfo_calc_flow_depth);
+	}
+
+	public boolean isUseTransportBodies() {
+		final Boolean useTkh = artifact.getDataAsBoolean( FIELD_USE_TKH );
+		return useTkh == null ? false : useTkh;
+	}
+
+	public Collection<DifferencesPair> getDifferencePairs() {
+
+		final Collection<DifferencesPair> diffPairs = new ArrayList<>();
+
+		 final String diffids = super.getString("diffids");
+		 if( diffids == null )
+		 {
+			 // Should never happen as this is handled by the ui
+			 return Collections.emptyList();
+		 }
+
+		 // FIXME: this way of parsing the datacage-ids is repeated all over flys!
+		 final String datas[] = diffids.split("#");
+		 for(int i = 0; i < datas.length; i+=2) {
+			 final String leftId = StringUtil.unbracket( datas[i] );
+			 final String rightId = StringUtil.unbracket( datas[i+1] );
+
+			 diffPairs.add(new DifferencesPair(leftId, rightId));
+		 }
+
+		return Collections.unmodifiableCollection(diffPairs);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculation.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,152 @@
+/* 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.flowdepth;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.BedHeightsArtifact;
+import org.dive4elements.river.artifacts.model.Calculation;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.LocationProvider;
+import org.dive4elements.river.artifacts.model.WKms;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
+import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthAccess.DifferencesPair;
+import org.dive4elements.river.artifacts.states.WDifferencesState;
+import org.dive4elements.river.model.BedHeight;
+import org.dive4elements.river.model.Gauge;
+import org.dive4elements.river.model.River;
+import org.dive4elements.river.utils.GaugeIndex;
+import org.dive4elements.river.utils.RiverUtils;
+
+class FlowDepthCalculation {
+
+    private static final String CSV_NOT_IN_GAUGE_RANGE = "export.waterlevel.csv.not.in.gauge.range";
+
+	private CallContext context;
+
+	public FlowDepthCalculation( final CallContext context ) {
+		this.context = context;
+	}
+	
+    public CalculationResult calculate(final SINFOArtifact sinfo) {
+    	
+    	/* access input data */
+    	final FlowDepthAccess access = new FlowDepthAccess(sinfo);
+    	final River river = access.getRiver();
+    	
+    	final Collection<DifferencesPair> diffPairs = access.getDifferencePairs();
+    	
+		final double from = access.getFrom();
+		final double to = access.getTo();
+
+    	final boolean useTkh = access.isUseTransportBodies();
+
+    	/* calculate results for each diff pair */
+    	final Calculation problems = new Calculation();
+
+    	final List<Gauge> gauges = river.determineGauges(from, to);
+    	final GaugeIndex gaugeIndex = new GaugeIndex(gauges);
+
+    	final FlowDepthCalculationResults results = new FlowDepthCalculationResults(river, from, to, useTkh);
+
+    	for (final DifferencesPair diffPair : diffPairs) {
+    		final FlowDepthCalculationResult result = calculateResult( river, from, to, diffPair, problems, gaugeIndex );
+    		if( result != null )
+    			results.addResult(result);
+		}
+    	
+		return new CalculationResult(results,problems);
+    }
+
+	private FlowDepthCalculationResult calculateResult(final River river, final double from, final double to, final DifferencesPair diffPair, final Calculation problems, final GaugeIndex gaugeIndex) {
+
+		/* access real input data from database */
+    	final String soundingId = diffPair.getSoundingId();
+    	final String wstId = diffPair.getWstId();
+
+    	final BedHeight bedHeight = loadBedHeight( soundingId, from, to );
+		final WKms wstKms = new WDifferencesState().getWKms(wstId, context, from, to);
+		if( bedHeight == null || wstKms == null )
+			return null;
+
+    	final FlowDepthCalculationResult resultData = new FlowDepthCalculationResult(wstKms.getName(), bedHeight.getDescription());
+
+        final String notinrange = Resources.getMsg(context.getMeta(), CSV_NOT_IN_GAUGE_RANGE, CSV_NOT_IN_GAUGE_RANGE);
+
+    	// TODO: unklarheiten
+    	// 'idealerweise alle 100m' was heisst das? kann doch nur durch datenverfügbarkeit bestimmt werden
+    	// wie mit unterschiedlichen Ranges umgehen? Schnitt bilden? Fehlermeldung? ...?
+    	// wie interpolieren? wst interpolieren? peilung interpolieren?
+
+		// FIXME: für die Berechnung der TKH sind weitere 'in FLYS vorliegende' Daten notwendig.
+		// aktuell unklar ob das durch andere Barten berechnete Werte oder Basisdaten sind
+		// TODO: check Vergleiche BArt 'Transportkörperhöhen'
+        
+    	// TODO: Berechnung der Transportkörperhöhen
+    	// - woher kommen die zusätzlichen eingangsdaten? sind das fixe daten pro gewässer? --> falls ja, warum nicht einmal berechnen und in db ablegen?
+
+    	final String bedHeightLabel = bedHeight.getDescription();
+    	final String wstLabel = wstKms.getName();
+
+		for (int i = 0; i < wstKms.size(); i++) {
+
+			final double km = wstKms.getKm(i);
+    		final double wst = wstKms.getW(i);
+    		// FIXME: interpolate from bedheights?
+    		final double meanBedHeight = 79.32;
+
+    		final double flowDepth = wst - meanBedHeight;
+    		
+    		final double tkh = 0;
+    		final double flowDepthTkh = flowDepth - tkh;
+    		
+    		// FIXME: discharge not available for all wst? or any?
+    		final double discharge = 0.0;
+
+    		// REMARK: access the location once only during calculation 
+    		final String location = LocationProvider.getLocation(river.getName(), km);
+    		
+    		// REMARK: access the gauge once only during calculation 
+    		final Gauge gauge = gaugeIndex.findGauge(km);
+    		final String gaugeLabel = gauge == null ? notinrange : gauge.getName();
+    		
+			resultData.addRow( km, flowDepth, flowDepthTkh, tkh, wst, discharge, wstLabel, gaugeLabel, meanBedHeight, bedHeightLabel, location );
+		}
+		
+		return resultData;
+	}
+
+	private BedHeight loadBedHeight(final String soundingId, final double from, final double to) {
+		
+		// FIXME: absolutely unbelievable....
+		// The way how bed-heights (and other data too) is accessed is different for nearly ever calculation-type throughout flys.
+		// The knowledge on how to parse the datacage-ids is spread thorugh the complete code-base...
+
+		// We use here the way on how bed-heights are accessed by the BedDifferenceAccess/BedDifferenceCalculation, but this is plain random
+	    final String[] parts = soundingId.split(";");
+
+	    final BedHeightsArtifact artifact = (BedHeightsArtifact) RiverUtils.getArtifact(parts[0], context);
+
+	    final Integer bedheightId = artifact.getDataAsInteger("height_id");
+	    // FIXME: this only works with type 'single'; unclear on how to distinguish from epoch data (or whatever the other type means)
+	    // Luckily, the requirement is to only access 'single' data here.
+	    // final String bedheightType = artifact.getDataAsString("type");
+	    
+	    // FIXME: BedDifferences uses this, but we also need the metadata of the BedHeight
+	    // FIXME: second absolutely awful thing: BedHeight is a hibernate binding class, accessing the database via hibernate stuff
+	    // BedHeightFactory uses its own (direct) way of accessing the data, with its own implemented data classes.
+	    //return BedHeightFactory.getHeight(bedheightType, bedheightId, from, to);
+	     
+	    return BedHeight.getBedHeightById(bedheightId);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResult.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,68 @@
+/* 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.flowdepth;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * Contains the results of a {@link FlowDepthCalculation}.
+ *
+ * @author Gernot Belger
+ */
+class FlowDepthCalculationResult
+implements Serializable {
+	
+	private static final long serialVersionUID = 1L;
+
+	private final Collection<FlowDepthRow> rows = new ArrayList<>();
+
+	private final String wstLabel;
+	
+	private final String soundingLabel;
+
+	public FlowDepthCalculationResult(final String wstLabel, final String soundingLabel) {
+		this.wstLabel = wstLabel;
+		this.soundingLabel = soundingLabel;
+	}
+
+	public void addRow(double station, double flowDepth, double flowDepthWithTkh, double tkh, double waterlevel, double discharge, String waterlevelLabel, String gauge, double meanBedHeight, String sondageLabel, String location) {
+		rows.add(new FlowDepthRow(station, flowDepth, flowDepthWithTkh, tkh, waterlevel, discharge, waterlevelLabel, gauge, meanBedHeight, sondageLabel, location));
+	}
+	
+	public String getWstLabel() {
+		return this.wstLabel;
+	}
+
+	public String getSoundingLabel() {
+		return this.soundingLabel;
+	}
+
+	public Collection<FlowDepthRow> getRows() {
+		return  Collections.unmodifiableCollection( rows );
+	}
+	
+    public double[][] getFlowDepthPoints() {
+
+    	TDoubleArrayList xPoints = new TDoubleArrayList(rows.size());
+    	TDoubleArrayList yPoints = new TDoubleArrayList(rows.size());
+    	
+        for (FlowDepthRow row : rows) {
+        	xPoints.add(row.getStation());
+			yPoints.add(row.getFlowDepth());
+		}
+        
+        return new double[][] { xPoints.toNativeArray(), yPoints.toNativeArray() };
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthCalculationResults.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,63 @@
+/** 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.flowdepth;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.dive4elements.river.model.River;
+
+/**
+ * @author Gernot Belger
+ *
+ */
+public class FlowDepthCalculationResults{
+	private final List<FlowDepthCalculationResult> results = new ArrayList<>();
+
+	private final River river;
+	
+	private final double from;
+	
+	private final double to;
+	
+	private final boolean useTkh;
+
+	public FlowDepthCalculationResults(final River river, final double from, final double to, final boolean useTkh) {
+		this.river = river;
+		this.from = from;
+		this.to = to;
+		this.useTkh = useTkh;
+	}
+	
+	public River getRiver() {
+		return this.river;
+	}
+
+	public double getFrom() {
+		return this.from;
+	}
+
+	public double getTo() {
+		return this.to;
+	}
+
+	public boolean isUseTkh() {
+		return this.useTkh;
+	}
+	
+	void addResult(final FlowDepthCalculationResult result) {
+		results.add(result);
+	}
+	
+	public List<FlowDepthCalculationResult> getResults() {
+		return Collections.unmodifiableList(results);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,449 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.flowdepth;
+
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.artifacts.common.utils.Config;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.artifacts.sinfo.util.MetaAndTableJRDataSource;
+import org.dive4elements.river.exports.AbstractExporter;
+import org.dive4elements.river.model.River;
+import org.dive4elements.river.utils.Formatter;
+
+import au.com.bytecode.opencsv.CSVWriter;
+import net.sf.jasperreports.engine.JRDataSource;
+import net.sf.jasperreports.engine.JRException;
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+
+/**
+ * Generates different output formats (csv, pdf) of data that resulted from a flow depths computation.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ * @author Gernot Belger
+ */
+// REMARK: must be public because its registered in generators.xml
+public class FlowDepthExporter extends AbstractExporter {
+
+    /** The log used in this exporter.*/
+    private static Logger log = Logger.getLogger(FlowDepthExporter.class);
+
+    private static final String CSV_KM_HEADER = "sinfo.export.flow_depth.csv.header.km";
+    private static final String CSV_FLOWDEPTH_HEADER = "sinfo.export.flow_depth.csv.header.flowdepth";
+    private static final String CSV_FLOWDEPTHTKH_HEADER = "sinfo.export.flow_depth.csv.header.flowdepthTkh";
+    private static final String CSV_TKH_HEADER = "sinfo.export.flow_depth.csv.header.tkh";
+    private static final String CSV_WATERLEVEL_HEADER = "sinfo.export.flow_depth.csv.header.waterlevel";
+    private static final String CSV_DISCHARGE_HEADER = "sinfo.export.flow_depth.csv.header.discharge";
+    private static final String CSV_LABEL_HEADER = "sinfo.export.flow_depth.csv.header.label";
+    private static final String CSV_GAUGE_HEADER = "sinfo.export.flow_depth.csv.header.gauge";
+    private static final String CSV_MEAN_BED_HEIGHT_HEADER = "sinfo.export.flow_depth.csv.header.mean_bed_height";
+    private static final String CSV_SOUNDING_HEADER = "sinfo.export.flow_depth.csv.header.sounding";
+    private static final String CSV_LOCATION_HEADER = "sinfo.export.flow_depth.csv.header.location";
+    
+    private static final String CSV_META_HEADER_RESULT =
+        "sinfo.export.flow_depth.csv.meta.header.result";
+
+    private static final String CSV_META_VERSION =
+    		"sinfo.export.flow_depth.csv.meta.version";
+    
+    private static final String CSV_META_USER =
+    		"sinfo.export.flow_depth.csv.meta.user";
+    
+    private static final String CSV_META_CREATION =
+        "sinfo.export.flow_depth.csv.meta.creation";
+
+    private static final String CSV_META_RIVER =
+        "sinfo.export.flow_depth.csv.meta.river";
+
+    private static final String CSV_META_HEADER_SOUNDING =
+    		"sinfo.export.flow_depth.csv.meta.header.sounding";
+
+    private static final String CSV_META_HEADER_WATERLEVEL =
+    		"sinfo.export.flow_depth.csv.meta.header.waterlevel";
+    
+    private static final String JASPER_FILE     = "/jasper/sinfo.flowdepth.jasper"; //$NON-NLS-1$
+
+    /** The storage that contains the current calculation result.*/
+    private FlowDepthCalculationResults data = null;
+
+	private NumberFormat meanBedHeightFormatter;
+
+	private NumberFormat tkhFormatter;
+
+	private NumberFormat flowDepthFormatter;
+
+    private NumberFormat getMeanBedHeightFormatter() {
+    	if( meanBedHeightFormatter == null )
+    		// FIXME: check if this is right
+    		meanBedHeightFormatter = Formatter.getMiddleBedHeightHeight(context);
+		return meanBedHeightFormatter;
+	}
+
+	private NumberFormat getTkhFormatter() {
+    	if( tkhFormatter == null )
+    		// FIXME: check if this is right, probably not, we need one digit
+    		tkhFormatter = Formatter.getWaterlevelW(context);
+		return tkhFormatter;
+	}
+
+	private NumberFormat getFlowDepthFormatter() {
+    	if( flowDepthFormatter == null )
+    		// FIXME: check if this is right
+    		flowDepthFormatter = Formatter.getMeterFormat(context);
+		return flowDepthFormatter;
+	}    
+    
+    @Override
+    protected void addData(Object d) {
+    	/* reset */
+    	data = null;
+
+        if (d instanceof CalculationResult) {
+
+        	final Object dat = ((CalculationResult)d).getData();
+        	if( dat != null )
+        		data = (FlowDepthCalculationResults)dat;
+        }
+    }
+
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        log.info("FlowDepthExporter.writeCSVData");
+
+        /* fetch calculation results */
+        final FlowDepthCalculationResults results = data;
+
+        /* write as csv */
+
+//        boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
+//        boolean isQ     = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
+//        RiverUtils.WQ_INPUT input
+//            = RiverUtils.getWQInputMode((D4EArtifact)master);
+
+        final boolean useTkh = results.isUseTkh();
+
+        writeCSVMeta(writer, results);
+        writeCSVHeader(writer, useTkh);
+
+        for (final FlowDepthCalculationResult result : results.getResults()) {
+        	writeCSVFlowDepthResult(writer, result, useTkh);
+		}
+    }
+
+    private void writeCSVFlowDepthResult(final CSVWriter writer, final FlowDepthCalculationResult result, final boolean useTkh) {
+        final Collection<FlowDepthRow> rows = result.getRows();
+        for (final FlowDepthRow flowDepthRow : rows) {
+        	writeCSVFlowDepthRow(writer, flowDepthRow, useTkh);
+		}
+	}
+
+	private void writeCSVMeta(final CSVWriter writer, final FlowDepthCalculationResults results) {
+        log.info("FlowDepthExporter.writeCSVMeta");
+
+        // Workflow zur Berechnung der Fließtiefe.pdf
+        // "##ERGEBNISAUSGABE - Name des Gewässers - Fließtiefe"
+        final River river = results.getRiver();
+		writeCSVMeataEntry(writer, CSV_META_HEADER_RESULT, river.getName() );
+
+		// "# FLYS-Version: "
+        // FIXME
+        final String flysVersion = "unbekannt";
+        writeCSVMeataEntry(writer, CSV_META_VERSION, flysVersion );
+
+		// "# Bearbeiter: "
+        // FIXME
+        final String user = "unbekannt";
+        writeCSVMeataEntry(writer, CSV_META_USER, user );
+
+        // "# Datum der Erstellung: "
+        final Locale locale = Resources.getLocale(context.getMeta());
+        final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+        writeCSVMeataEntry(writer, CSV_META_CREATION, df.format(new Date()) );
+
+        // "# Gewässer: "
+        writeCSVMeataEntry(writer, CSV_META_RIVER, river.getName() );
+
+        // "# Höhensystem des Flusses: "
+        
+        // FIXME
+
+        // "# Ort/Bereich (km): "
+        // FIXME
+        // TODO: unklar, es wird nur ein Bereich eingegeben
+//        RangeAccess rangeAccess = new RangeAccess(flys);
+//        double[] kms = rangeAccess.getKmRange();
+//        writer.writeNext(new String[] {
+//        		Resources.getMsg(
+//        				meta,
+//        				CSV_META_RANGE,
+//        				CSV_META_RANGE,
+//        				new Object[] { kms[0], kms[kms.length-1] })
+//        });
+
+        // "##METADATEN PEILUNG"
+        writeCSVMeataEntry(writer, CSV_META_HEADER_SOUNDING );
+
+//        "# Jahr der Peilung: "
+        // FIXME
+//        "# Aufnahmeart: "
+        // FIXME
+//        "# Lagesystem: "
+        // FIXME
+//        "# Höhensystem: "
+        // FIXME
+//        "# ursprüngliches Höhensystem: "
+        // FIXME
+//        "##METADATEN WASSERSPIEGELLAGE"
+        writeCSVMeataEntry(writer, CSV_META_HEADER_WATERLEVEL );
+//        "# Bezeichnung der Wasserspiegellage: "
+        // FIXME
+//        "# Höhensystem der Wasserspiegellage: "
+        // FIXME
+//        "# Auswerter: "
+        // FIXME
+//        "# Bezugspegel: "
+        // FIXME
+//        "# Jahr/Zeitraum der Wasserspiegellage: "
+        // FIXME
+
+        // "# W/Pegel [cm]: " (nur bei Eingabe des Wasserstands am Pegel)
+        // TODO: unklar, es wird kein W eingegeben
+
+        // "# Q (m³/s): " (nur bei Eingabe des Durchflusses)        
+        // TODO: unklar, es wird kein Q eingegeben
+
+//        writer.writeNext(new String[] {
+//            Resources.getMsg(
+//                meta,
+//                CSV_META_GAUGE,
+//                CSV_META_GAUGE,
+//                new Object[] { RiverUtils.getGaugename(flys) })
+//        });
+
+        writer.writeNext(new String[] { "" });
+    }
+
+
+    private void writeCSVMeataEntry(CSVWriter writer, String message, Object... messageArgs) {
+
+        CallMeta meta = context.getMeta();
+
+        writer.writeNext(new String[] {
+                Resources.getMsg(
+                    meta,
+                    message,
+                    message,
+                    messageArgs)
+            });		
+    }
+    
+	/**
+     * Write the header, with different headings depending on whether at a
+     * gauge or at a location.
+	 * @param useTkh 
+     */
+    private void writeCSVHeader(
+        final CSVWriter writer,
+        final boolean useTkh
+    ) {
+        log.info("FlowDepthExporter.writeCSVHeader");
+
+        final Collection<String> header = new ArrayList<>(11);
+        
+        header.add(msg(CSV_KM_HEADER,CSV_KM_HEADER));
+        header.add(msg(CSV_FLOWDEPTH_HEADER));
+        if( useTkh )
+        {
+        	header.add(msg(CSV_FLOWDEPTHTKH_HEADER));
+        	header.add(msg(CSV_TKH_HEADER));
+        }
+        header.add(msg(CSV_WATERLEVEL_HEADER));
+        header.add(msg(CSV_DISCHARGE_HEADER));
+        header.add(msg(CSV_LABEL_HEADER));
+        header.add(msg(CSV_GAUGE_HEADER));
+        header.add(msg(CSV_MEAN_BED_HEIGHT_HEADER));
+        header.add(msg(CSV_SOUNDING_HEADER));
+        header.add(msg(CSV_LOCATION_HEADER));
+
+       writer.writeNext(header.toArray(new String[header.size()]));
+    }
+
+    /**
+     * Format a row of a flow depth result into an array of string, both used by csv and pdf
+     * @param useTkh 
+     */
+    private String[] formatFlowDepthRow(
+    		final FlowDepthRow row, 
+    		boolean useTkh ) {
+
+    	final Collection<String> lines = new ArrayList<>(11);
+    	
+    	// Fluss-km
+    	lines.add( getKmFormatter().format( row.getStation() ) );
+    	
+    	// Fließtiefe [m]
+    	lines.add( getFlowDepthFormatter().format( row.getFlowDepth() ) );
+    	
+    	if( useTkh )
+    	{
+    		// Fließtiefe mit TKH [m]
+    		lines.add( getFlowDepthFormatter().format( row.getFlowDepthWithTkh() ) );
+    		
+    		// TKH [cm]
+    		lines.add( getTkhFormatter().format( row.getTkh() ) );
+    	}
+    	
+    	// Wasserstand [NN + m]
+    	lines.add( getWFormatter().format( row.getWaterlevel() ) );
+    	
+    	// Q [m³/s]
+    	lines.add( getQFormatter().format( row.getDischarge() ) );
+    	
+    	// Bezeichnung
+    	lines.add( row.getWaterlevelLabel() );
+    	
+    	// Bezugspegel
+    	lines.add( row.getGauge() );
+    	
+    	// Mittlere Sohlhöhe [NN + m]
+    	lines.add( getMeanBedHeightFormatter().format( row.getMeanBedHeight( ) ) );
+    	
+    	// Peilung/Epoche
+    	lines.add( row.getSoundageLabel() );
+
+    	// Lage
+    	lines.add( row.getLocation() );
+    	
+    	return lines.toArray(new String[lines.size()]);
+    }
+    /**
+     * Write "rows" of csv data from wqkms with writer.
+     * @param useTkh 
+     */
+    private void writeCSVFlowDepthRow(
+        final CSVWriter writer,
+        final FlowDepthRow row, 
+        final boolean useTkh
+    ) {
+        log.debug("FlowDepthExporter.writeCSVFlowDepthRow");
+
+        final String[] formattedRow = formatFlowDepthRow(row, useTkh);
+        writer.writeNext( formattedRow );
+    }
+
+	@Override
+    protected void writePDF(OutputStream outStream) {
+        log.debug("write PDF");
+        
+        final JRDataSource source = createJRData();
+
+        final String confPath = Config.getConfigDirectory().toString();
+
+        // FIXME: distinguish between with and without tkh: we need two jasper reports! 
+
+        final Map<String,Object> parameters = new HashMap<>();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            final JasperPrint print = JasperFillManager.fillReport(
+                confPath + JASPER_FILE,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, outStream);
+        }
+        catch(JRException je) {
+            log.warn("Error generating PDF Report!", je);
+        }
+    }
+
+    private JRDataSource createJRData() {
+    	
+        /* fetch calculation results */
+        final FlowDepthCalculationResults results = data;
+    	
+    	final MetaAndTableJRDataSource source = new MetaAndTableJRDataSource();
+    	
+        addJRMetaData(source, results);
+
+        final boolean useTkh = results.isUseTkh();
+        
+        for (final FlowDepthCalculationResult result : results.getResults()) {
+			addJRTableData(source, result, useTkh);
+		}
+
+        return source;
+    }
+
+	private void addJRMetaData(final MetaAndTableJRDataSource source, FlowDepthCalculationResults results) {
+
+		// Workflow zur Berechnung der Fließtiefe.pdf
+        // "##ERGEBNISAUSGABE - Name des Gewässers - Fließtiefe"
+        // writeCSVMeataEntry(writer, CSV_META_HEADER_RESULT, inputData.getRiver() );
+        
+        // FIXME
+        final String flysVersion = "unbekannt";
+        // CSV_META_VERSION
+        source.addMetaData("version", flysVersion);
+
+        // FIXME
+        String user = "unbekannt";
+        // CSV_META_USER
+        source.addMetaData("user", user);
+        
+        final Locale locale = Resources.getLocale(context.getMeta());
+        final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+        source.addMetaData("date", df.format(new Date()));
+
+        // CSV_META_RIVER
+        source.addMetaData("river", results.getRiver().getName());
+
+        // FIXME
+        source.addMetaData("range", "FIXME");
+        // "# Ort/Bereich (km): "
+        // FIXME
+        // TODO: unklar, es wird nur ein Bereich eingegeben
+//        RangeAccess rangeAccess = new RangeAccess(flys);
+//        double[] kms = rangeAccess.getKmRange();
+//        writer.writeNext(new String[] {
+//        		Resources.getMsg(
+//        				meta,
+//        				CSV_META_RANGE,
+//        				CSV_META_RANGE,
+//        				new Object[] { kms[0], kms[kms.length-1] })
+//        });
+
+//        RangeAccess rangeAccess = new RangeAccess(flys);
+//        double[] kms = rangeAccess.getKmRange();
+//        source.addMetaData("range",
+//                kmf.format(kms[0]) + " - " + kmf.format(kms[kms.length-1]));
+    }
+
+    private void addJRTableData(final MetaAndTableJRDataSource source, final FlowDepthCalculationResult result, final boolean useTkh) {
+    	
+    	final Collection<FlowDepthRow> rows = result.getRows();
+    	
+    	for (final FlowDepthRow row : rows) {
+    		
+    		final String[] formattedRow = formatFlowDepthRow(row, useTkh);
+    		source.addData(formattedRow);
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthFilterFacet.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,98 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.flowdepth;
+
+import org.apache.log4j.Logger;
+import org.dive4elements.artifactdatabase.state.Facet;
+import org.dive4elements.artifacts.Artifact;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.DataFacet;
+import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
+
+/**
+ * Facet of a FlowDepth curve.
+ */
+public class FlowDepthFilterFacet extends DataFacet {
+
+    private static Logger log = Logger.getLogger(FlowDepthFilterFacet.class);
+
+    public FlowDepthFilterFacet() {
+        // required for clone operation deepCopy()
+    }
+
+    public FlowDepthFilterFacet(
+        int         idx,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash
+    ) {
+        super(idx, name, description, type, hash, stateId);
+        this.metaData.put("X", "sinfo.chart.flow_depth.xaxis.label");
+        this.metaData.put("Y", "sinfo.chart.flow_depth.yaxis.label");
+    }
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        log.debug("Get data for flow velocity at index: " + index);
+
+        final D4EArtifact flys = (D4EArtifact) artifact;
+
+        final CalculationResult res = (CalculationResult)
+            flys.compute(context, hash, stateId, type, false);
+
+        final FlowDepthCalculationResults data = (FlowDepthCalculationResults) res.getData();
+        
+        final FlowDepthCalculationResult result = data.getResults().get(index);
+
+        // FIXME: variable mean computation depending on current scale
+//      Double start = (Double)context.getContextValue("startkm");
+//      Double end = (Double)context.getContextValue("endkm");
+//        if(start != null && end != null) {
+//            RiverContext fc = (RiverContext)context.globalContext();
+//            ZoomScale scales = (ZoomScale)fc.get("zoomscale");
+//            RiverAccess access = new RiverAccess((D4EArtifact)artifact);
+//            String river = access.getRiverName();
+//
+//            double radius = scales.getRadius(river, start, end);
+//            FlowVelocityData oldData = data[index];
+//            FlowVelocityData newData = new FlowVelocityData();
+//            double[][] q = oldData.getQPoints();
+//            double[][] totalV = MovingAverage.weighted(oldData.getTotalChannelPoints(), radius);
+//            double[][] mainV = MovingAverage.weighted(oldData.getMainChannelPoints(), radius);
+//            double[][] tau = MovingAverage.weighted(oldData.getTauPoints(), radius);
+//            for(int j = 0; j < q[0].length; j++) {
+//                newData.addKM(q[0][j]);
+//                newData.addQ(q[1][j]);
+//                newData.addTauMain(tau[1][j]);
+//                newData.addVMain(mainV[1][j]);
+//                newData.addVTotal(totalV[1][j]);
+//            }
+//            return newData;
+//        }
+
+      return result;
+    }
+
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        FlowDepthFilterFacet copy = new FlowDepthFilterFacet();
+        // FIXME: why does DataFacet does not override set? Bad access to variables of parent!
+        copy.set(this);
+        copy.type    = type;
+        copy.hash    = hash;
+        copy.stateId = stateId;
+        return copy;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthPairSelectState.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,27 @@
+/** 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.flowdepth;
+
+import org.dive4elements.river.artifacts.states.WaterlevelPairSelectState;
+
+/**
+ * @author Gernot Belger
+ *
+ */
+public class FlowDepthPairSelectState
+// FIXME: very ugly; but probably we will break the serialization of WaterlevelPairSelectState if we introduce an abstraction 
+extends WaterlevelPairSelectState {
+
+    /** Specify to display a datacage_twin_panel. */
+    @Override
+    protected String getUIProvider() {
+        return "sinfo_flowdepth_twin_panel";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthProcessor.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,105 @@
+/* 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.flowdepth;
+
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.exports.DiagramGenerator;
+import org.dive4elements.river.exports.StyledSeriesBuilder;
+import org.dive4elements.river.exports.process.DefaultProcessor;
+import org.dive4elements.river.jfree.StyledXYSeries;
+import org.dive4elements.river.themes.ThemeDocument;
+
+public final class FlowDepthProcessor extends DefaultProcessor {
+
+	/* Theme name, usually defined in 'FacetTypes', but that is soooo bad dependencies... */
+    static String FACET_FLOW_DEPTH_FILTERED = "sinfo_flow_depth.filtered";
+
+    private final static Logger log = Logger.getLogger(FlowDepthProcessor.class);
+
+    private static final String I18N_AXIS_LABEL = "sinfo.chart.flow_depth.section.yaxis.label";
+    private String yAxisLabel;
+
+    @Override
+    public void doOut(
+            final DiagramGenerator generator,
+            final ArtifactAndFacet bundle,
+            final ThemeDocument    theme,
+            final boolean          visible) {
+
+        final CallContext context = generator.getCallContext();
+        final Map<String, String> metaData = bundle.getFacet().getMetaData();
+        
+        final StyledXYSeries series = new StyledXYSeries(bundle.getFacetDescription(), theme);
+        series.putMetaData(metaData, bundle.getArtifact(), context);
+
+        yAxisLabel = metaData.get("Y");
+
+        final String facetName = bundle.getFacetName();
+        final FlowDepthCalculationResult data = (FlowDepthCalculationResult) bundle.getData(context);
+        if (data == null) {
+            // Check has been here before so we keep it for security reasons
+            // this should never happen though.
+            log.error("Data is null for facet: " + facetName);
+            return;
+        }
+        
+        final double [][] points = generatePoints(data);
+
+        StyledSeriesBuilder.addPoints(series, points, true);
+        generator.addAxisSeries(series, axisName, visible);
+    }
+
+    private static double[][] generatePoints(final FlowDepthCalculationResult data) {
+
+    	// FIXME
+    	return data.getFlowDepthPoints();
+
+//        if (facetName.equals(FacetTypes.FLOW_VELOCITY_TOTALCHANNEL) ||
+//                facetName.equals(FacetTypes.FLOW_VELOCITY_TOTALCHANNEL_FILTERED)) {
+//            FlowVelocityData fData = (FlowVelocityData) data;
+//            points = fData.getTotalChannelPoints();
+//        } else if (facetName.equals(FacetTypes.FLOW_VELOCITY_MAINCHANNEL) ||
+//                facetName.equals(FacetTypes.FLOW_VELOCITY_MAINCHANNEL_FILTERED)) {
+//            FlowVelocityData fData = (FlowVelocityData) data;
+//            points = fData.getMainChannelPoints(); // I hate facets!
+//        } else if (facetName.equals(FacetTypes.FLOW_VELOCITY_MEASUREMENT)) {
+//            FastFlowVelocityMeasurementValue fData =
+//                (FastFlowVelocityMeasurementValue) data;
+//            points = new double[][] {{fData.getStation()},{fData.getV()}};
+//        } else {
+//            log.error("Unknown facet name: " + facetName);
+//            return;
+//        }
+	}
+
+	@Override
+    public boolean canHandle(String facettype) {
+        return FACET_FLOW_DEPTH_FILTERED.equals(facettype);
+    }
+
+    @Override
+    public String getAxisLabel(DiagramGenerator generator) {
+        if (yAxisLabel != null && !yAxisLabel.isEmpty()) {
+        	// REMARK/UNINTENDED: yAxisLabel may also be a resolved message (side-effect of StyledXYSeries#putMetadata),
+        	// and cannot be resolved, so we need to give the resolved value as default
+        	// In other implementations (i.e. FlowVelocityProcessor), an explicit (German) default label is given here, probably
+        	// the English version will also show German (CHECK)
+            return generator.msg(yAxisLabel, yAxisLabel);
+        }
+        return generator.msg(
+                I18N_AXIS_LABEL,
+                "MISSING");
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthRow.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,90 @@
+/* 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.flowdepth;
+
+import java.io.Serializable;
+
+/**
+ * Part of {@link FlowDepthCalculationResult} which represents one calculated row of flow depth data.
+ * 
+ * @author Gernot Belger
+ */
+final class FlowDepthRow
+implements Serializable {
+	private final double station;
+	private final double flowDepth;
+	private final double flowDepthWithTkh;
+	private final double tkh;
+	private final double waterlevel;
+	private final double discharge;
+	private final String waterlevelLabel;
+	private final String gauge;
+	private final double meanBedHeight;
+	private final String soundageLabel;
+	private final String location;
+
+	public FlowDepthRow( double station, double flowDepth, double flowDepthWithTkh, double tkh, double waterlevel, double discharge, String waterlevelLabel, String gauge, double meanBedHeight, String soundageLabel, String location ) {
+		this.station = station;
+		this.flowDepth = flowDepth;
+		this.flowDepthWithTkh = flowDepthWithTkh;
+		this.tkh = tkh;
+		this.waterlevel = waterlevel;
+		this.discharge = discharge;
+		this.waterlevelLabel = waterlevelLabel;
+		this.gauge = gauge;
+		this.meanBedHeight = meanBedHeight;
+		this.soundageLabel = soundageLabel;
+		this.location = location;
+	}
+
+	public double getStation() {
+		return station;
+	}
+
+	public double getFlowDepth() {
+		return flowDepth;
+	}
+
+	public double getFlowDepthWithTkh() {
+		return flowDepthWithTkh;
+	}
+	
+	public double getTkh() {
+		return tkh;
+	}
+
+	public double getWaterlevel() {
+		return waterlevel;
+	}
+
+	public double getDischarge() {
+		return discharge;
+	}
+
+	public String getWaterlevelLabel() {
+		return waterlevelLabel;
+	}
+
+	public String getGauge() {
+		return gauge;
+	}
+
+	public double getMeanBedHeight() {
+		return meanBedHeight;
+	}
+
+	public String getSoundageLabel() {
+		return soundageLabel;
+	}
+
+	public String getLocation() {
+		return location;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthState.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,140 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.flowdepth;
+
+import java.util.List;
+
+import org.dive4elements.artifactdatabase.state.Facet;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.ChartArtifact;
+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.model.DataFacet;
+import org.dive4elements.river.artifacts.model.EmptyFacet;
+import org.dive4elements.river.artifacts.model.FacetTypes;
+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.states.DefaultState;
+
+/** State in which a waterlevel has been calculated. */
+public class FlowDepthState
+extends      DefaultState
+{
+    /// ** The log that is used in this state. */
+    // private static Logger log = Logger.getLogger(FlowDepthState.class);
+
+    private static final String I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION = "sinfo.facet.flow_depth.filtered.description";
+
+    /**
+     * From this state can only be continued trivially.
+     */
+    @Override
+    protected String getUIProvider() {
+        return "continue";
+    }
+
+    @Override
+    public Object computeFeed(
+        final D4EArtifact artifact,
+        final String       hash,
+        final CallContext  context,
+        final List<Facet>  facets,
+        final Object       old
+    ) {
+    	// FIXME: why is this necessary?
+        if (artifact instanceof ChartArtifact) {
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return compute((SINFOArtifact) artifact, context, hash, facets, old);
+    }
+
+    @Override
+    public Object computeAdvance(
+    	final D4EArtifact artifact,
+    	final String       hash,
+    	final CallContext  context,
+    	final List<Facet>  facets,
+    	final Object       old
+    ) {
+        if (artifact instanceof ChartArtifact) {
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return compute((SINFOArtifact) artifact, context, hash, facets, old);
+    }
+    
+    /**
+     * Compute result or returned object from cache, create facets.
+     * @param old Object that was cached.
+     */
+    private Object compute(
+    	final SINFOArtifact sinfo,
+        final CallContext   context,
+        final String        hash,
+        final List<Facet>   facets,
+        final Object        old
+    ) {
+        final CalculationResult res;
+		if (old instanceof CalculationResult)
+			res = (CalculationResult) old;
+		else
+			res = new FlowDepthCalculation(context).calculate(sinfo);
+
+        if (facets == null) {
+            return res;
+        }
+
+        final FlowDepthCalculationResults results =  (FlowDepthCalculationResults) res.getData();
+
+        /* add themes for chart, for each result */
+        final List<FlowDepthCalculationResult> resultList = results.getResults();
+        for (int index = 0; index < resultList.size(); index++) {
+
+			final FlowDepthCalculationResult result = resultList.get(index);
+			/* compute theme label */
+			final String wspLabel = result.getWstLabel();
+			final String soundingLabel = result.getSoundingLabel();
+			final String inputLabel = String.format("%s - %s", wspLabel, soundingLabel);
+			
+			/* filtered (zoom dependent mean) flow depth */
+			final String facetFlowDepthFilteredDescription = Resources.getMsg( context.getMeta(), I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION, I18N_FACET_FLOW_DEPTH_FILTERED_DESCRIPTION, inputLabel );
+			facets.add(new FlowDepthFilterFacet(
+			        index,
+			        FlowDepthProcessor.FACET_FLOW_DEPTH_FILTERED,
+			        facetFlowDepthFilteredDescription,
+			        ComputeType.ADVANCE,
+			        id,
+			        hash
+			    ));
+			
+			// FIXME: add other themes
+		}
+
+        if (!resultList.isEmpty() ) {
+            Facet csv = new DataFacet(
+                FacetTypes.CSV, "CSV data", ComputeType.ADVANCE, hash, id);
+            Facet pdf = new DataFacet(
+            		FacetTypes.PDF, "PDF data", ComputeType.ADVANCE, hash, id);
+
+            facets.add(csv);
+            facets.add(pdf);
+        }
+
+        final Calculation report = res.getReport();
+
+        if (report.hasProblems()) {
+            facets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
+        }
+
+        return res;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/UseTransportBodiesChoice.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,21 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.flowdepth;
+
+import org.dive4elements.river.artifacts.states.BooleanChoiceState;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class UseTransportBodiesChoice extends BooleanChoiceState {
+
+    public UseTransportBodiesChoice() {
+    	super( "useTransportBodies.option", "useTransportBodies.active", "useTransportBodies.inactive" );
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/MetaAndTableJRDataSource.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,64 @@
+/* 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.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.jasperreports.engine.JRDataSource;
+import net.sf.jasperreports.engine.JRException;
+import net.sf.jasperreports.engine.JRField;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public final class MetaAndTableJRDataSource implements JRDataSource
+{
+    private List<String[]> data = new ArrayList<>();
+
+    private Map<String, String> metaData = new HashMap<>();
+
+    private int index = -1;
+
+    public void addData(final String[] data) {
+        this.data.add(data);
+    }
+
+    public void addMetaData(final String key, final String value) {
+        this.metaData.put(key, value);
+    }
+
+    @Override
+	public boolean next() throws JRException
+    {
+        index++;
+
+        return index < data.size();
+    }
+
+    @Override
+	public Object getFieldValue(final JRField field) throws JRException
+    {
+        final String fieldName = field.getName();
+        
+        if( fieldName.startsWith("meta:"))
+        	return metaData.get(fieldName.substring("meta:".length()));
+
+        if( fieldName.startsWith("data:"))
+        {
+        	int column = Integer.valueOf(fieldName.substring("data:".length()));
+        	return data.get(index)[column];
+        }
+        
+        return null;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/BooleanChoiceState.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,100 @@
+/* Copyright (C) 2011, 2012, 2017 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.states;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import org.dive4elements.artifacts.Artifact;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.artifacts.CallMeta;
+
+import org.dive4elements.artifacts.common.utils.XMLUtils;
+
+import org.dive4elements.artifactdatabase.ProtocolUtils;
+
+import org.dive4elements.river.artifacts.resources.Resources;
+
+/**
+ * Generic state for a boolean choice. Only difference between real implementations are the human readable labels.
+ * 
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ * @author Gernot Belger
+ */
+public abstract class BooleanChoiceState extends DefaultState {
+
+	private String optionMsg;
+	private String activeMsg;
+	private String inactiveMsg;
+
+    public BooleanChoiceState( String optionMsg, String activeMsg, String inactiveMsg ) {
+		this.optionMsg = optionMsg;
+		this.activeMsg = activeMsg;
+		this.inactiveMsg = inactiveMsg;
+	}
+    
+    private static final Logger log =
+        Logger.getLogger(BooleanChoiceState.class);
+
+    @Override
+    protected String getUIProvider() {
+        return "boolean_panel";
+    }
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        CallMeta meta = context.getMeta();
+
+        Element option = createItem(
+            cr,
+            new String[] { Resources.getMsg(meta, optionMsg, optionMsg), "true" });
+
+        return new Element[] { option };
+    }
+
+
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        log.debug("GET LABEL FOR '" + name + "' / '" + value + "'");
+        if (value != null && value.equals("true")) {
+            return Resources.getMsg(cc.getMeta(), activeMsg, activeMsg);
+        }
+        else {
+            return Resources.getMsg(cc.getMeta(), inactiveMsg, inactiveMsg);
+        }
+    }
+
+
+    protected Element createItem(XMLUtils.ElementCreator cr, Object obj) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        String[] arr = (String[]) obj;
+
+        label.setTextContent(arr[0]);
+        value.setTextContent(arr[1]);
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/FloodplainChoice.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/FloodplainChoice.java	Fri Jan 19 11:23:42 2018 +0100
@@ -26,6 +26,7 @@
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  */
+// FIXME: inherit from BooleanChoiceState instead to remove duplicate code; BUT: this will probably break artifact serialization
 public class FloodplainChoice extends DefaultState {
 
     public static final String OPTION   = "floodplain.option";
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WDifferencesState.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WDifferencesState.java	Fri Jan 19 11:23:42 2018 +0100
@@ -109,6 +109,7 @@
     /**
      * Access the data (wkms) of an artifact, coded in mingle.
      */
+    // FIXME: meanwhile used by several places outside this context; refactor into separate helper class to access waterlevels
     public WKms getWKms(
         String mingle,
         CallContext context,
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelPairSelectState.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelPairSelectState.java	Fri Jan 19 11:23:42 2018 +0100
@@ -48,7 +48,7 @@
     /** Specify to display a datacage_twin_panel. */
     @Override
     protected String getUIProvider() {
-        return "datacage_twin_panel";
+        return "waterlevel_twin_panel";
     }
 
 
--- a/artifacts/src/main/java/org/dive4elements/river/exports/process/FlowVelocityProcessor.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/process/FlowVelocityProcessor.java	Fri Jan 19 11:23:42 2018 +0100
@@ -94,6 +94,9 @@
     @Override
     public String getAxisLabel(DiagramGenerator generator) {
         if (yAxisLabel != null && !yAxisLabel.isEmpty()) {
+        	// FIXME/UNINTENDED: yAxisLabel is probably a resolved message (side-effect of StyledXYSeries#putMetadata),
+        	// and cannot be resolved again.
+        	// An explicit (German) default label is therefore given here, probably the English version will also show German (CHECK)
             return generator.msg(yAxisLabel, I18N_AXIS_LABEL_DEFAULT);
         }
         return generator.msg(
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYSeries.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYSeries.java	Fri Jan 19 11:23:42 2018 +0100
@@ -146,6 +146,7 @@
 
 
     @Override
+    // FIXME: bad! method with undocumented side-effects; given metadata will be changed inline
     public void putMetaData(Map<String, String> metaData,
         Artifact artifact,
         CallContext context) {
@@ -155,6 +156,7 @@
         String unit = "";
         if (river != null) {
             rivername = river.getName();
+            // FIXME: this will always return the wst unit, regardless if the series is a water level or not!
             unit      = river.getWstUnit().getName();
         }
         if (metaData.containsKey("X")) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/utils/GaugeIndex.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,52 @@
+/** 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.utils;
+
+import java.util.List;
+
+import org.dive4elements.river.model.Gauge;
+
+/**
+ * Allows performant access to gauges by station.
+ * @author Gernot Belger
+ */
+public class GaugeIndex {
+	private List<Gauge> gauges;
+	
+	private Gauge lastGauge = null;
+
+	public GaugeIndex( List<Gauge> gauges) {
+		this.gauges = gauges;
+	}
+	
+	public Gauge findGauge(double km) {
+
+		// REMARK: this is code copied from WaterlevelExporter, which is honestly not very fast/good.
+		// Instead we need to index by range with an RTree and directly acccess the right gauge.
+		
+		if( lastGauge != null && lastGauge.getRange().contains(km) )
+			return lastGauge;
+		
+		final Gauge gauge = findGauge(km, gauges);
+
+        lastGauge = gauge;
+            
+        return gauge;
+	}
+	
+    private static Gauge findGauge(double km, List<Gauge> gauges) {
+        for (Gauge gauge: gauges) {
+            if (gauge.getRange().contains(km)) {
+                return gauge;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/resources/messages.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/resources/messages.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -761,3 +761,65 @@
 help.state.fix.vollmer.preprocessing=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.preprocessing
 help.state.fix.vollmer.qs=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.qs
 help.state.fix.vollmer.compute=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.compute
+
+state.sinfo.river = River
+state.sinfo.calculation_mode=Calculation Mode
+
+sinfo_calc_flow_depth=Flie\u00dftiefen
+sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung
+sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
+sinfo_calc_grounding=Grundber\u00fchrungen
+sinfo_calc_transport_bodies_heights=Transportk\u00f6rperh\u00f6hen
+sinfo_calc_infrastructures_inundation_duration=\u00dcberflutungsdauern Infrastrukturen BWaStr
+
+help.state.sinfo=${help.url}/OnlineHilfe/SINFO
+help.state.sinfo.river=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.river
+help.state.sinfo.calculation_mode=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.calculation_mode
+
+state.sinfo.distance_only = Range selection
+help.state.sinfo.distance_only=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.distance_only
+
+state.sinfo.waterlevel_soundings_select= Chosen Differences
+help.state.sinfo.waterlevel_soundings_select=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.waterlevel_soundings_select
+
+state.sinfo.use_transport_bodies=Transportk\u00f6rperh\u00f6hen
+help.state.sinfo.use_transport_bodies=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.use_transport_bodies
+
+useTransportBodies.option = Transportk\u00f6rperh\u00f6hen miteinbeziehen?
+useTransportBodies.active = Activ
+useTransportBodies.inactive = Inactiv
+
+sinfo.export.flow_depth.csv.meta.header.result = ## Calculation Output - {0} - Flie\u00dftiefe - FLYS 3
+sinfo.export.flow_depth.csv.meta.version = # FLYS-Version: {0}
+sinfo.export.flow_depth.csv.meta.user = # Bearbeiter: {0}
+sinfo.export.flow_depth.csv.meta.creation = # Time of creation: {0}
+sinfo.export.flow_depth.csv.meta.river = # River: {0}
+sinfo.export.flow_depth.csv.meta.header.sounding = ##METADATEN PEILUNG
+sinfo.export.flow_depth.csv.meta.header.waterlevel = ##METADATEN WASSERSPIEGELLAGE
+
+#export.export.flow_depth.csv.meta.range = # Location/Range (km): {0} - {1}
+#export.export.flow_depth.csv.meta.gauge = # Gauge: {0}
+#export.export.flow_depth.csv.meta.q = # Q (m\u00b3/s): {0}
+#export.export.flow_depth.csv.meta.w = # W (NN + m): {0} - {1}
+
+sinfo.export.flow_depth.csv.header.km = Fluss-km
+sinfo.export.flow_depth.csv.header.flowdepth = Flie\u00dftiefe [m]
+sinfo.export.flow_depth.csv.header.flowdepthTkh = Flie\u00dftiefe mit TKH [m]
+sinfo.export.flow_depth.csv.header.tkh = Flie\u00dftiefe mit TKH [cm]
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.waterlevel = Wasserstand [NN+m]
+sinfo.export.flow_depth.csv.header.discharge = Q [m\u00b3/s]
+sinfo.export.flow_depth.csv.header.label = Bezeichnung
+sinfo.export.flow_depth.csv.header.gauge = Bezugspegel
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.mean_bed_height = Mittlere Sohlh\u00f6he [NN+m]
+sinfo.export.flow_depth.csv.header.sounding = Peilung/Epoche
+sinfo.export.flow_depth.csv.header.location = Lage
+
+sinfo.chart.flow_depth.section.title=h-L\u00e4ngsschnitt
+
+sinfo.chart.flow_depth.xaxis.label = {0}-km
+sinfo.chart.flow_depth.yaxis.label = Flie\u00dftiefe [m]
+
+sinfo.chart.flow_depth.section.yaxis.label=Flie\u00dftiefe h [m]
+sinfo.facet.flow_depth.filtered.description = Flie\u00dftiefe ({0}) 
--- a/artifacts/src/main/resources/messages_de.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/resources/messages_de.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -36,10 +36,10 @@
 state.fix.analysis.referenceperiod = Bezugszeitraum
 state.fix.analysis.analysisperiods = Analysezeitr\u00e4ume
 state.fix.analysis.function = Ausgleichsfunktion
-state.fix.analysis.preprocessing = Ausrei\u00DFer
+state.fix.analysis.preprocessing = Ausrei\u00dfer
 state.fix.preprocess=Ausrei\u00dfertest durchf\u00fchren
 state.fix.vollmer.function= Ausgleichsfunktion
-state.fix.vollmer.preprocessing = Ausrei\u00DFer
+state.fix.vollmer.preprocessing = Ausrei\u00dfer
 state.fix.vollmer.qs = Eingabe f\u00fcr W/Q Daten
 
 state.minfo.river = Gew\u00e4sser
@@ -286,7 +286,7 @@
 facet.sedimentload.calc.bed_load = Geschiebefracht (Berechnung FLYS) - {0} [{1}]
 facet.sedimentload.calc.bed_load_susp_sand = bettbildende Fracht (Berechnung FLYS) - {0} [{1}]
 
-minfo.sedimentload.no.data = Keine Sedimentfracht-Daten verfügbar
+minfo.sedimentload.no.data = Keine Sedimentfracht-Daten verf\u00fcgbar
 sedimentload.missing.fraction.coarse = Fehlende Fraktion Grober Kies/Steine - {0}
 sedimentload.missing.fraction.fine_middle = Fehlende Fraktion Fein/Mittlerer Kies - {0}
 sedimentload.missing.fraction.sand = Fehlende Fraktion Sand - {0}
@@ -730,6 +730,7 @@
 static.sq.station = Messstelle
 
 module.winfo = W-INFO
+module.sinfo = S-INFO
 module.minfo = M-INFO
 module.fixanalysis = Fixierungsanalyse
 module.new_map = Neue Karte
@@ -766,3 +767,65 @@
 help.state.fix.vollmer.preprocessing=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.preprocessing
 help.state.fix.vollmer.qs=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.qs
 help.state.fix.vollmer.compute=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.compute
+
+state.sinfo.river = Gew\u00e4sser
+state.sinfo.calculation_mode=Berechnungsart
+
+sinfo_calc_flow_depth=Flie\u00dftiefen
+sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung
+sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
+sinfo_calc_grounding=Grundber\u00fchrungen
+sinfo_calc_transport_bodies_heights=Transportk\u00f6rperh\u00f6hen
+sinfo_calc_infrastructures_inundation_duration=\u00dcberflutungsdauern Infrastrukturen BWaStr
+
+help.state.sinfo=${help.url}/OnlineHilfe/SINFO
+help.state.sinfo.river=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.river
+help.state.sinfo.calculation_mode=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.calculation_mode
+
+state.sinfo.distance_only = Wahl der Berechnungsstrecke
+help.state.sinfo.distance_only=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.distance_only
+
+state.sinfo.waterlevel_soundings_select= Ausgew\u00e4hlte Differenzen
+help.state.sinfo.waterlevel_soundings_select=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.waterlevel_soundings_select
+
+state.sinfo.use_transport_bodies=Transportk\u00f6rperh\u00f6hen
+help.state.sinfo.use_transport_bodies=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.use_transport_bodies
+
+useTransportBodies.option = Transportk\u00f6rperh\u00f6hen miteinbeziehen?
+useTransportBodies.active = Aktiv
+useTransportBodies.inactive = Inaktiv
+
+sinfo.export.flow_depth.csv.meta.header.result = ## Ergebnisausgabe - {0} - Flie\u00dftiefe - FLYS 3
+sinfo.export.flow_depth.csv.meta.version = # FLYS-Version: {0}
+sinfo.export.flow_depth.csv.meta.user = # Bearbeiter: {0}
+sinfo.export.flow_depth.csv.meta.creation = # Datum der Erstellung: {0}
+sinfo.export.flow_depth.csv.meta.river = # Gew\u00e4sser: {0}
+sinfo.export.flow_depth.csv.meta.header.sounding = ##METADATEN PEILUNG
+sinfo.export.flow_depth.csv.meta.header.waterlevel = ##METADATEN WASSERSPIEGELLAGE
+
+#export.export.flow_depth.csv.meta.range = # Location/Range (km): {0} - {1}
+#export.export.flow_depth.csv.meta.gauge = # Gauge: {0}
+#export.export.flow_depth.csv.meta.q = # Q (m\u00b3/s): {0}
+#export.export.flow_depth.csv.meta.w = # W (NN + m): {0} - {1}
+
+sinfo.export.flow_depth.csv.header.km = Fluss-km
+sinfo.export.flow_depth.csv.header.flowdepth = Flie\u00dftiefe [m]
+sinfo.export.flow_depth.csv.header.flowdepthTkh = Flie\u00dftiefe mit TKH [m]
+sinfo.export.flow_depth.csv.header.tkh = Flie\u00dftiefe mit TKH [cm]
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.waterlevel = Wasserstand [NN+m]
+sinfo.export.flow_depth.csv.header.discharge = Q [m\u00b3/s]
+sinfo.export.flow_depth.csv.header.label = Bezeichnung
+sinfo.export.flow_depth.csv.header.gauge = Bezugspegel
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.mean_bed_height = Mittlere Sohlh\u00f6he [NN+m]
+sinfo.export.flow_depth.csv.header.sounding = Peilung/Epoche
+sinfo.export.flow_depth.csv.header.location = Lage
+
+sinfo.chart.flow_depth.section.title=h-L\u00e4ngsschnitt
+
+sinfo.chart.flow_depth.xaxis.label = {0}-km
+sinfo.chart.flow_depth.yaxis.label = Flie\u00dftiefe [m]
+
+sinfo.chart.flow_depth.section.yaxis.label=Flow Depth h [m]
+sinfo.facet.flow_depth.filtered.description = Flie\u00dftiefe ({0}) 
\ No newline at end of file
--- a/artifacts/src/main/resources/messages_de_DE.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/resources/messages_de_DE.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -36,10 +36,10 @@
 state.fix.analysis.referenceperiod = Bezugszeitraum
 state.fix.analysis.analysisperiods = Analysezeitr\u00e4ume
 state.fix.analysis.function = Ausgleichsfunktion
-state.fix.analysis.preprocessing = Ausrei\u00DFer
+state.fix.analysis.preprocessing = Ausrei\u00dfer
 state.fix.preprocess=Ausrei\u00dfertest durchf\u00fchren
 state.fix.vollmer.function= Ausgleichsfunktion
-state.fix.vollmer.preprocessing = Ausrei\u00DFer
+state.fix.vollmer.preprocessing = Ausrei\u00dfer
 state.fix.vollmer.qs = Eingabe f\u00fcr W/Q Daten
 
 state.minfo.river = Gew\u00e4sser
@@ -283,7 +283,7 @@
 facet.sedimentload.calc.bed_load = Geschiebefracht (Berechnung FLYS) - {0} [{1}]
 facet.sedimentload.calc.bed_load_susp_sand = bettbildende Fracht (Berechnung FLYS) - {0} [{1}]
 
-minfo.sedimentload.no.data = Keine Sedimentfracht-Daten verfügbar
+minfo.sedimentload.no.data = Keine Sedimentfracht-Daten verf\u00fcgbar
 sedimentload.missing.fraction.coarse = Fehlende Fraktion Grober Kies/Steine - {0}
 sedimentload.missing.fraction.fine_middle = Fehlende Fraktion Fein/Mittlerer Kies - {0}
 sedimentload.missing.fraction.sand = Fehlende Fraktion Sand - {0}
@@ -763,3 +763,65 @@
 help.state.fix.vollmer.preprocessing=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.preprocessing
 help.state.fix.vollmer.qs=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.qs
 help.state.fix.vollmer.compute=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.compute
+
+state.sinfo.river = Gew\u00e4sser
+state.sinfo.calculation_mode=Berechnungsart
+
+sinfo_calc_flow_depth=Flie\u00dftiefen
+sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung
+sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
+sinfo_calc_grounding=Grundber\u00fchrungen
+sinfo_calc_transport_bodies_heights=Transportk\u00f6rperh\u00f6hen
+sinfo_calc_infrastructures_inundation_duration=\u00dcberflutungsdauern Infrastrukturen BWaStr
+
+help.state.sinfo=${help.url}/OnlineHilfe/SINFO
+help.state.sinfo.river=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.river
+help.state.sinfo.calculation_mode=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.calculation_mode
+
+state.sinfo.distance_only = Wahl der Berechnungsstrecke
+help.state.sinfo.distance_only=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.distance_only
+
+state.sinfo.waterlevel_soundings_select= Ausgew\u00e4hlte Differenzen
+help.state.sinfo.waterlevel_soundings_select=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.waterlevel_soundings_select
+
+state.sinfo.use_transport_bodies=Transportk\u00f6rperh\u00f6hen
+help.state.sinfo.use_transport_bodies=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.use_transport_bodies
+
+useTransportBodies.option = Transportk\u00f6rperh\u00f6hen miteinbeziehen?
+useTransportBodies.active = Aktiv
+useTransportBodies.inactive = Inaktiv
+
+sinfo.export.flow_depth.csv.meta.header.result = ## Ergebnisausgabe - {0} - Flie\u00dftiefe - FLYS 3
+sinfo.export.flow_depth.csv.meta.version = # FLYS-Version: {0}
+sinfo.export.flow_depth.csv.meta.user = # Bearbeiter: {0}
+sinfo.export.flow_depth.csv.meta.creation = # Datum der Erstellung: {0}
+sinfo.export.flow_depth.csv.meta.river = # Gew\u00e4sser: {0}
+sinfo.export.flow_depth.csv.meta.header.sounding = ##METADATEN PEILUNG
+sinfo.export.flow_depth.csv.meta.header.waterlevel = ##METADATEN WASSERSPIEGELLAGE
+
+#export.export.flow_depth.csv.meta.range = # Location/Range (km): {0} - {1}
+#export.export.flow_depth.csv.meta.gauge = # Gauge: {0}
+#export.export.flow_depth.csv.meta.q = # Q (m\u00b3/s): {0}
+#export.export.flow_depth.csv.meta.w = # W (NN + m): {0} - {1}
+
+sinfo.export.flow_depth.csv.header.km = Fluss-km
+sinfo.export.flow_depth.csv.header.flowdepth = Flie\u00dftiefe [m]
+sinfo.export.flow_depth.csv.header.flowdepthTkh = Flie\u00dftiefe mit TKH [m]
+sinfo.export.flow_depth.csv.header.tkh = Flie\u00dftiefe mit TKH [cm]
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.waterlevel = Wasserstand [NN+m]
+sinfo.export.flow_depth.csv.header.discharge = Q [m\u00b3/s]
+sinfo.export.flow_depth.csv.header.label = Bezeichnung
+sinfo.export.flow_depth.csv.header.gauge = Bezugspegel
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.mean_bed_height = Mittlere Sohlh\u00f6he [NN+m]
+sinfo.export.flow_depth.csv.header.sounding = Peilung/Epoche
+sinfo.export.flow_depth.csv.header.location = Lage
+
+sinfo.chart.flow_depth.section.title=h-L\u00e4ngsschnitt
+
+sinfo.chart.flow_depth.xaxis.label = {0}-km
+sinfo.chart.flow_depth.yaxis.label = Flie\u00dftiefe [m]
+
+sinfo.chart.flow_depth.section.yaxis.label=Flow Depth h [m]
+sinfo.facet.flow_depth.filtered.description = Flie\u00dftiefe ({0}) 
\ No newline at end of file
--- a/artifacts/src/main/resources/messages_en.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/artifacts/src/main/resources/messages_en.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -762,3 +762,65 @@
 help.state.fix.vollmer.preprocessing=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.preprocessing
 help.state.fix.vollmer.qs=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.qs
 help.state.fix.vollmer.compute=${help.url}/OnlineHilfe/Fixierungsanalyse#help.state.fix.vollmer.compute
+
+state.sinfo.river = River
+state.sinfo.calculation_mode=Calculation Mode
+
+sinfo_calc_flow_depth=Flie\u00dftiefen
+sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung
+sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe
+sinfo_calc_grounding=Grundber\u00fchrungen
+sinfo_calc_transport_bodies_heights=Transportk\u00f6rperh\u00f6hen
+sinfo_calc_infrastructures_inundation_duration=\u00dcberflutungsdauern Infrastrukturen BWaStr
+
+help.state.sinfo=${help.url}/OnlineHilfe/SINFO
+help.state.sinfo.river=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.river
+help.state.sinfo.calculation_mode=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.calculation_mode
+
+state.sinfo.distance_only = Range selection
+help.state.sinfo.distance_only=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.distance_only
+
+state.sinfo.waterlevel_soundings_select= Chosen Differences
+help.state.sinfo.waterlevel_soundings_select=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.waterlevel_soundings_select
+
+state.sinfo.use_transport_bodies=Transportk\u00f6rperh\u00f6hen
+help.state.sinfo.use_transport_bodies=${help.url}/OnlineHilfe/SINFO#help.state.sinfo.use_transport_bodies
+
+useTransportBodies.option = Transportk\u00f6rperh\u00f6hen miteinbeziehen?
+useTransportBodies.active = Activ
+useTransportBodies.inactive = Inactiv
+
+sinfo.export.flow_depth.csv.meta.header.result = ## Calculation Output - {0} - Flie\u00dftiefe - FLYS 3
+sinfo.export.flow_depth.csv.meta.version = # FLYS-Version: {0}
+sinfo.export.flow_depth.csv.meta.user = # Bearbeiter: {0}
+sinfo.export.flow_depth.csv.meta.creation = # Time of creation: {0}
+sinfo.export.flow_depth.csv.meta.river = # River: {0}
+sinfo.export.flow_depth.csv.meta.header.sounding = ##METADATEN PEILUNG
+sinfo.export.flow_depth.csv.meta.header.waterlevel = ##METADATEN WASSERSPIEGELLAGE
+
+#export.export.flow_depth.csv.meta.range = # Location/Range (km): {0} - {1}
+#export.export.flow_depth.csv.meta.gauge = # Gauge: {0}
+#export.export.flow_depth.csv.meta.q = # Q (m\u00b3/s): {0}
+#export.export.flow_depth.csv.meta.w = # W (NN + m): {0} - {1}
+
+sinfo.export.flow_depth.csv.header.km = Fluss-km
+sinfo.export.flow_depth.csv.header.flowdepth = Flie\u00dftiefe [m]
+sinfo.export.flow_depth.csv.header.flowdepthTkh = Flie\u00dftiefe mit TKH [m]
+sinfo.export.flow_depth.csv.header.tkh = Flie\u00dftiefe mit TKH [cm]
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.waterlevel = Wasserstand [NN+m]
+sinfo.export.flow_depth.csv.header.discharge = Q [m\u00b3/s]
+sinfo.export.flow_depth.csv.header.label = Bezeichnung
+sinfo.export.flow_depth.csv.header.gauge = Bezugspegel
+# FIXME: H\u00f6henbezugssystem?
+sinfo.export.flow_depth.csv.header.mean_bed_height = Mittlere Sohlh\u00f6he [NN+m]
+sinfo.export.flow_depth.csv.header.sounding = Peilung/Epoche
+sinfo.export.flow_depth.csv.header.location = Lage
+
+sinfo.chart.flow_depth.section.title=h-L\u00e4ngsschnitt
+
+sinfo.chart.flow_depth.xaxis.label = {0}-km
+sinfo.chart.flow_depth.yaxis.label = Flie\u00dftiefe [m]
+
+sinfo.chart.flow_depth.section.yaxis.label=Flie\u00dftiefe h [m]
+sinfo.facet.flow_depth.filtered.description = Flie\u00dftiefe ({0}) 
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.java	Fri Jan 19 11:23:42 2018 +0100
@@ -1421,5 +1421,15 @@
     String error_no_sedimentloadinfo_found();
 
     String error_no_sedimentloadinfo_data();
+
+    String sinfo();
+    
+    String sinfo_flowdepth_export();
+
+    String sinfo_flowdepth_report();
+
+    String sinfo_flow_depth();
+
+	String sinfo_flowdepth_twinpanel_no_pair_selected();
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -756,3 +756,9 @@
 upper_time = to
 
 no_data_for_year = No data available for: $1
+
+sinfo = S-INFO
+sinfo_flowdepth_export = Flie\u00dftiefen Export
+sinfo_flowdepth_report = Flie\u00dftiefen Bericht
+sinfo_flow_depth = Flie\u00dftiefen
+sinfo_flowdepth_twinpanel_no_pair_selected = Error - at least one input pair must be selected
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_de.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_de.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -748,3 +748,9 @@
 upper_time = bis
 
 no_data_for_year = F\u00fcr das Jahr $1 liegen keine Daten vor.
+
+sinfo = S-INFO
+sinfo_flowdepth_export = Flie\u00dftiefen Export
+sinfo_flowdepth_report = Flie\u00dftiefen Bericht
+sinfo_flow_depth = Flie\u00dftiefen
+sinfo_flowdepth_twinpanel_no_pair_selected = Fehler - kein Paar zur Differenzenbildung gew\u00e4hlt.
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_en.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_en.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -785,3 +785,9 @@
 upper_time = to
 
 no_data_for_year = No data available for: $1
+
+sinfo = S-INFO
+sinfo_flowdepth_export = Flie\u00dftiefen Export
+sinfo_flowdepth_report = Flie\u00dftiefen Bericht
+sinfo_flow_depth = Flie\u00dftiefen
+sinfo_flowdepth_twinpanel_no_pair_selected = Error - at least one input pair must be selected
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractPairRecommendationPanel.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,508 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.client.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import com.smartgwt.client.data.Record;
+import com.smartgwt.client.types.ListGridFieldType;
+import com.smartgwt.client.widgets.Canvas;
+import com.smartgwt.client.widgets.events.ClickEvent;
+import com.smartgwt.client.widgets.grid.ListGrid;
+import com.smartgwt.client.widgets.grid.ListGridField;
+import com.smartgwt.client.widgets.grid.ListGridRecord;
+import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
+import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
+import com.smartgwt.client.widgets.layout.VLayout;
+
+import org.dive4elements.river.client.client.Config;
+import org.dive4elements.river.client.client.FLYSConstants;
+import org.dive4elements.river.client.client.event.StepForwardEvent;
+import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync;
+import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync;
+import org.dive4elements.river.client.shared.model.Artifact;
+import org.dive4elements.river.client.shared.model.Collection;
+import org.dive4elements.river.client.shared.model.Data;
+import org.dive4elements.river.client.shared.model.DataItem;
+import org.dive4elements.river.client.shared.model.DataList;
+import org.dive4elements.river.client.shared.model.DefaultData;
+import org.dive4elements.river.client.shared.model.DefaultDataItem;
+import org.dive4elements.river.client.shared.model.Recommendation;
+import org.dive4elements.river.client.shared.model.Recommendation.Facet;
+import org.dive4elements.river.client.shared.model.Recommendation.Filter;
+import org.dive4elements.river.client.shared.model.User;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+// TODO Probably better to branch off AbstractUIProvider.
+// TODO Merge with other datacage-widget impls.
+/**
+ * Panel containing a Grid and a "next" button. The Grid is fed by a
+ * DatacagePairWidget which is put in the input-helper area.
+ */
+public abstract class AbstractPairRecommendationPanel
+extends      TextProvider {
+
+	/**
+	 * Allows for abstraction on how to handle/serialize the recommendation and the used factories.
+	 * @author Gernot Belger
+	 *
+	 */
+	public static interface IRecommendationInfo	{
+
+		String getFactory();
+
+		/**
+		 * Separate factory for the 'createDataString' method, because in the case of waterlevels. See HOTFIX/FIXME there.
+		 */
+		String getDataStringFactory();
+
+	    /**
+	     * Set factory of recommendation such that the correct artifacts will
+	     * be cloned for difference calculations.
+	     */
+	    void adjustRecommendation(Recommendation recommendation);
+	}
+	
+	public static interface IValidator
+	{
+		List<String> validate(ListGrid differencesList, FLYSConstants msgProvider);
+	}
+
+    private static final long serialVersionUID = 8906629596491827857L;
+
+    // FIXME: why? we hide the field of the super class with exactly the same thing...
+    private static FLYSConstants MSG_PROVIDER = GWT.create(FLYSConstants.class);
+
+    private String dataName;
+
+    private User user;
+
+    /** ListGrid that displays user-selected pairs to build differences with. */
+    private ListGrid differencesList;
+
+    /**
+     * List to track previously selected but now removed pairs. (Needed to
+     * be able to identify artifacts that can be removed from the collection.
+     */
+    private List<RecommendationPairRecord> removedPairs =
+        new ArrayList<RecommendationPairRecord>();
+
+    /** Service handle to clone and add artifacts to collection. */
+    private LoadArtifactServiceAsync loadArtifactService = GWT.create(
+            org.dive4elements.river.client.client.services.LoadArtifactService.class);
+
+    /** Service to remove artifacts from collection. */
+    private RemoveArtifactServiceAsync removeArtifactService = GWT.create(
+            org.dive4elements.river.client.client.services.RemoveArtifactService.class);
+
+	private IRecommendationInfo leftInfo;
+
+	private IRecommendationInfo rightInfo;
+
+	private IValidator validator;
+
+	/**
+	 * @param Validates the content of this form when the user clicks 'apply'
+	 * @param leftInfo Delegate for handling the left part of the recommendation-pair
+	 * @param rightInfo Delegate for handling the right part of the recommendation-pair
+	 */
+    public AbstractPairRecommendationPanel(final User user, IValidator validator, final IRecommendationInfo leftInfo, final IRecommendationInfo rightInfo ) {
+        this.user = user;
+		this.validator = validator;
+		this.leftInfo = leftInfo;
+		this.rightInfo = rightInfo;
+    }
+    
+    // FIXME: better than copy/pasting the MSG field into every sub-class but not really nice yet.
+    protected final static FLYSConstants msg() {
+		return MSG_PROVIDER;
+	}
+
+    /**
+     * Remove first occurrence of "[" and "]" (if both do occur).
+     * @param value String to be stripped of [] (might be null).
+     * @return input string but with [ and ] removed, or input string if no
+     *         brackets were found.
+     * @see StringUtil.unbracket
+     */
+    // FIXME: check if this is the same as STringUItils#unbracket
+    private static final String unbracket(String value) {
+        // null- guard.
+        if (value == null) return value;
+
+        int start = value.indexOf("[");
+        int end   = value.indexOf("]");
+
+        if (start < 0 || end < 0) {
+            return value;
+        }
+
+        return value.substring(start + 1, end);
+    }
+
+    /**
+     * Create a recommendation from a string representation of it.
+     * @param from string in format as shown above.
+     * @param leftInfo2 
+     * @return recommendation from input string.
+     */
+    private Recommendation createRecommendationFromString(final String from, final IRecommendationInfo info) {
+        // TODO Construct "real" filter.
+        String[] parts = unbracket(from).split(";");
+        Recommendation.Filter filter = new Recommendation.Filter();
+        Recommendation.Facet  facet  = new Recommendation.Facet(
+                parts[1],
+                parts[2]);
+
+        List<Recommendation.Facet> facets = new ArrayList<Recommendation.Facet>();
+        facets.add(facet);
+        filter.add("longitudinal_section", facets);
+        
+        final String factory = info.getFactory(  );
+
+        final  Recommendation r = new Recommendation(factory, parts[0], this.artifact.getUuid(), filter);
+        r.setDisplayName(parts[3]);
+        return r;
+    }
+
+
+    /**
+     * Add RecomendationPairRecords from input String to the ListGrid.
+     */
+    private void populateGridFromString(String from){
+        // Split this string.
+        // Create according recommendations and display strings.
+        String[] recs = from.split("#");
+        if (recs.length % 2 != 0) return;
+        for (int i = 0; i < recs.length; i+=2) {
+            Recommendation minuend =
+                createRecommendationFromString(recs[i+0], leftInfo);
+            Recommendation subtrahend =
+                createRecommendationFromString(recs[i+1], rightInfo);
+
+            RecommendationPairRecord pr = new RecommendationPairRecord(
+                minuend, subtrahend);
+            // This Recommendation Pair comes from the data string and was thus
+            // already cloned.
+            pr.setIsAlreadyLoaded(true);
+            this.differencesList.addData(pr);
+        }
+    }
+
+    /**
+     * Creates the graphical representation and interaction widgets for the data.
+     * @param dataList the data.
+     * @return graphical representation and interaction widgets for data.
+     */
+    @Override
+    public final Canvas create(DataList dataList) {
+    	
+        final Canvas widget = createWidget();
+    	
+    	final Canvas canvas = createChooserWidgets(widget, dataList, user, differencesList);
+    	
+        populateGrid(dataList);
+        
+        return canvas;
+    }
+
+    /**
+     * Creates the individual parts of the input-helper area ('Eingabeunterstützung') for choosing the content of this widget.
+     */
+    protected abstract Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User auser, final ListGrid diffList);
+
+	private void populateGrid(DataList dataList) {
+        Data data     = dataList.get(0);
+        this.dataName = data.getLabel();
+        for (int i = 0; i < dataList.size(); i++) {
+            if (dataList.get(i) != null && dataList.get(i).getItems() != null) {
+                if (dataList.get(i).getItems() != null) {
+                    populateGridFromString(
+                        dataList.get(i).getItems()[0].getStringValue());
+                }
+            }
+        }
+    }
+
+    @Override
+    public final List<String> validate() {
+    	return validator.validate(differencesList, MSG_PROVIDER);
+    }
+
+    /**
+     * Creates layout with grid that displays selection inside.
+     */
+    protected final Canvas createWidget() {
+        VLayout layout  = new VLayout();
+        differencesList = new ListGrid();
+
+        differencesList.setCanEdit(false);
+        differencesList.setCanSort(false);
+        differencesList.setShowHeaderContextMenu(false);
+        differencesList.setHeight(150);
+        differencesList.setShowAllRecords(true);
+
+        ListGridField nameField    = new ListGridField("first",  "Minuend");
+        ListGridField capitalField = new ListGridField("second", "Subtrahend");
+        // Track removed rows, therefore more or less reimplement
+        // setCanRecomeRecords.
+        final ListGridField removeField  =
+            new ListGridField("_removeRecord", "Remove Record"){{
+                setType(ListGridFieldType.ICON);
+                setIcon(GWT.getHostPageBaseURL() + msg().removeFeature());
+                setCanEdit(false);
+                setCanFilter(false);
+                setCanSort(false);
+                setCanGroupBy(false);
+                setCanFreeze(false);
+                setWidth(25);
+        }};
+
+        differencesList.setFields(new ListGridField[] {nameField,
+           capitalField, removeField});
+
+        differencesList.addRecordClickHandler(new RecordClickHandler() {
+                @Override
+                public void onRecordClick(final RecordClickEvent event) {
+                    // Just handle remove-clicks
+                    if(!event.getField().getName().equals(removeField.getName())) {
+                        return;
+                    }
+                    trackRemoved(event.getRecord());
+                    event.getViewer().removeData(event.getRecord());
+                }
+            });
+        layout.addMember(differencesList);
+
+        return layout;
+    }
+
+
+    /**
+     * Add record to list of removed records.
+     */
+    protected final void trackRemoved(Record r) {
+        RecommendationPairRecord pr = (RecommendationPairRecord) r;
+        this.removedPairs.add(pr);
+    }
+
+    /**
+     * Validates data, does nothing if invalid, otherwise clones new selected
+     * waterlevels and add them to collection, forward the artifact.
+     */
+    @Override
+    public void onClick(ClickEvent e) {
+        GWT.log("AbstractPairRecommendationPanel.onClick");
+
+        List<String> errors = validate();
+        if (errors != null && !errors.isEmpty()) {
+            showErrors(errors);
+            return;
+        }
+
+        Config config = Config.getInstance();
+        String locale = config.getLocale();
+
+        ListGridRecord[] records = differencesList.getRecords();
+
+        List<Recommendation> ar  = new ArrayList<Recommendation>();
+        List<Recommendation> all = new ArrayList<Recommendation>();
+
+        for (ListGridRecord record : records) {
+            RecommendationPairRecord r =
+                (RecommendationPairRecord) record;
+            // Do not add "old" recommendations.
+            if (!r.isAlreadyLoaded()) {
+                // Check whether one of those is a dike or similar.
+                // TODO differentiate and merge: new clones, new, old.
+                Recommendation firstR = r.getFirst();
+                leftInfo.adjustRecommendation(firstR);
+
+                Recommendation secondR = r.getSecond();
+                rightInfo.adjustRecommendation(secondR);
+                ar.add(firstR);
+                ar.add(secondR);
+            }
+            else {
+                all.add(r.getFirst());
+                all.add(r.getSecond());
+            }
+        }
+
+        final Recommendation[] toClone = ar.toArray(new Recommendation[ar.size()]);
+        final Recommendation[] toUse   = all.toArray(new Recommendation[all.size()]);
+
+        // Find out whether "old" artifacts have to be removed.
+        List<String> artifactIdsToRemove = new ArrayList<String>();
+        for (RecommendationPairRecord rp: this.removedPairs) {
+            Recommendation first  = rp.getFirst();
+            Recommendation second = rp.getSecond();
+
+            for (Recommendation recommendation: toUse) {
+                if (first != null && first.getIDs().equals(recommendation.getIDs())) {
+                    first = null;
+                }
+                if (second != null && second.getIDs().equals(recommendation.getIDs())) {
+                    second = null;
+                }
+
+                if (first == null && second == null) {
+                    break;
+                }
+            }
+            if (first != null) {
+                artifactIdsToRemove.add(first.getIDs());
+            }
+            if (second != null) {
+                artifactIdsToRemove.add(second.getIDs());
+            }
+        }
+
+        // Remove old artifacts, if any. Do this asychronously without much
+        // feedback.
+        for(final String uuid: artifactIdsToRemove) {
+            removeArtifactService.remove(this.collection,
+                uuid,
+                locale,
+                new AsyncCallback<Collection>() {
+                    @Override
+                    public void onFailure(Throwable caught) {
+                        GWT.log("RemoveArtifact (" + uuid + ") failed.");
+                    }
+                    @Override
+                    public void onSuccess(Collection coll) {
+                        GWT.log("RemoveArtifact succeeded");
+                    }
+                });
+        }
+
+        // Clone new ones (and spawn statics), go forward.
+        parameterList.lockUI();
+        loadArtifactService.loadMany(
+            this.collection,
+            toClone,
+            //"staticwkms" and "waterlevel"
+            null,
+            locale,
+            new AsyncCallback<Artifact[]>() {
+                @Override
+                public void onFailure(Throwable caught) {
+                	caught.printStackTrace();
+                    GWT.log("Failure of cloning with factories!");
+                    parameterList.unlockUI();
+                }
+                @Override
+                public void onSuccess(Artifact[] artifacts) {
+                    GWT.log("Successfully cloned " + toClone.length +
+                        " with factories.");
+
+                    fireStepForwardEvent(new StepForwardEvent(
+                        getData(toClone, artifacts, toUse)));
+                    parameterList.unlockUI();
+                }
+            });
+    }
+    
+    /**
+     * Create Data and DataItem from selection (a long string with identifiers
+     * to construct diff-pairs).
+     *
+     * @param newRecommendations "new" recommendations (did not survive a
+     *        backjump).
+     * @param newArtifacts artifacts cloned from newRecommendations.
+     * @param oldRecommendations old recommendations that survived a backjump.
+     *
+     * @return dataitem with a long string with identifiers to construct
+     *         diff-pairs.
+     */
+    protected final Data[] getData(
+            Recommendation[] newRecommendations,
+            Artifact[] newArtifacts,
+            Recommendation[] oldRecommendations)
+    {
+        // Construct string with info about selections.
+        String dataItemString = "";
+        for (int i = 0; i < newRecommendations.length; i++) {
+            Recommendation r = newRecommendations[i];
+            Artifact newArtifact = newArtifacts[i];
+            String uuid = newArtifact.getUuid();
+            r.setMasterArtifact(uuid);
+
+            if (i>0) 
+            	dataItemString += "#";
+
+            // REMARK: ugly, but we know that the recommandations comes in left/right pairs.
+            final IRecommendationInfo info = i % 2 == 0 ? leftInfo : rightInfo;
+            
+            dataItemString += createDataString(uuid, r, info);
+        }
+
+        for (int i = 0; i < oldRecommendations.length; i++) {
+            Recommendation r = oldRecommendations[i];
+            String uuid = r.getIDs();
+            
+            if (dataItemString.length() > 0) 
+            	dataItemString += "#";
+
+            // REMARK: ugly, but we know that the recommandations comes in left/right pairs.
+            final IRecommendationInfo info = i % 2 == 0 ? leftInfo : rightInfo;
+
+            dataItemString += createDataString(uuid, r, info);
+        }
+
+        // TODO some hassle could be resolved by using multiple DataItems
+        // (e.g. one per pair).
+        DataItem item = new DefaultDataItem(dataName, dataName, dataItemString);
+        return new Data[] { new DefaultData(
+            dataName, null, null, new DataItem[] {item}) };
+    }
+
+    /**
+     * Creates part of the String that encodes minuend or subtrahend.
+     * @param recommendation Recommendation to wrap in string.
+     * @param info Provides the factory to encode.
+     */
+    protected static final String createDataString(final String artifactUuid, final Recommendation recommendation, final IRecommendationInfo info) {
+    	final String factory = info.getDataStringFactory();
+        
+    	Filter filter = recommendation.getFilter();
+		Facet  f      = null;
+		
+		if(filter != null) {
+		    Map<String, List<Facet>>               outs = filter.getOuts();
+		    Set<Map.Entry<String, List<Facet>>> entries = outs.entrySet();
+		
+		    for (Map.Entry<String, List<Facet>> entry: entries) {
+		        List<Facet> fs = entry.getValue();
+		
+		        f = fs.get(0);
+		        if (f != null) {
+		            break;
+		        }
+		    }
+		
+		    return "[" + artifactUuid + ";"
+		        + f.getName()
+		        + ";"
+		        + f.getIndex()
+		        + ";"
+		        + recommendation.getDisplayName() + "]";
+		}
+		
+		return "["
+		    + artifactUuid
+		    + ";" + factory + ";0;"
+		    + recommendation.getDisplayName() + "]";
+    }
+}
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractUIProvider.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/AbstractUIProvider.java	Fri Jan 19 11:23:42 2018 +0100
@@ -309,12 +309,14 @@
         return null;
     }
 
-
+    /**
+     * Validates the selection.
+     * @return List of internationalized errror messages (if any).
+     */
     public List<String> validate() {
         return new ArrayList<String>(); // FIXME: What's this?
     }
 
-
     /** Create simple DefaultData with single DataItem inside. */
     public static DefaultData createDataArray(String name, String value) {
         DataItem item = new DefaultDataItem(
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/CollectionView.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/CollectionView.java	Fri Jan 19 11:23:42 2018 +0100
@@ -199,6 +199,9 @@
             this.parameterList = new ParameterList(
                 flys,
                 this,
+                // FIXME: literally every information about the artifact is transported from the server side
+                // but... the international name is resolved client-side.... Instead also transport the description of the artifact and use it!
+                // FIXME: the same holds for a very few other international strings (e.g. names of facets used in Tabs)
                 messages.getString(artifact.getName()),
                 artifact);
         }
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacagePairWidget.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacagePairWidget.java	Fri Jan 19 11:23:42 2018 +0100
@@ -49,12 +49,14 @@
      *
      * @param artifact Artifact to query datacage with.
      * @param user     User to query datacage with.
-     * @param outs     outs to query datacage with.
+     * @param leftOuts     outs to query the left datacage with.
+     * @param rightOuts     outs to query the right datacage with.
      * @param grid     Grid into which to insert selection of pairs.
      */
     public DatacagePairWidget(Artifact artifact,
          User user,
-         String outs,
+         String leftOuts,
+         String rightOuts,
          ListGrid grid) {
         this.grid = grid;
 
@@ -62,13 +64,13 @@
         firstDatacageWidget  = new DatacageWidget(
             artifact,
             user,
-            outs,
+            leftOuts,
             "load-system:true",
             false);
         secondDatacageWidget = new DatacageWidget(
             artifact,
             user,
-            outs,
+            rightOuts,
             "load-system:true",
             false);
         firstDatacageWidget.setIsMutliSelectable(false);
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageTwinPanel.java	Fri Jan 19 11:23:42 2018 +0100
@@ -8,531 +8,56 @@
 
 package org.dive4elements.river.client.client.ui;
 
+import org.dive4elements.river.client.shared.model.DataList;
+import org.dive4elements.river.client.shared.model.User;
+
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-import com.smartgwt.client.data.Record;
-import com.smartgwt.client.types.ListGridFieldType;
 import com.smartgwt.client.widgets.Canvas;
-import com.smartgwt.client.widgets.events.ClickEvent;
 import com.smartgwt.client.widgets.grid.ListGrid;
-import com.smartgwt.client.widgets.grid.ListGridField;
-import com.smartgwt.client.widgets.grid.ListGridRecord;
-import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
-import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
 import com.smartgwt.client.widgets.layout.HLayout;
 import com.smartgwt.client.widgets.layout.VLayout;
 
-import org.dive4elements.river.client.client.Config;
-import org.dive4elements.river.client.client.FLYSConstants;
-import org.dive4elements.river.client.client.event.StepForwardEvent;
-import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync;
-import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync;
-import org.dive4elements.river.client.shared.model.Artifact;
-import org.dive4elements.river.client.shared.model.Collection;
-import org.dive4elements.river.client.shared.model.Data;
-import org.dive4elements.river.client.shared.model.DataItem;
-import org.dive4elements.river.client.shared.model.DataList;
-import org.dive4elements.river.client.shared.model.DefaultData;
-import org.dive4elements.river.client.shared.model.DefaultDataItem;
-import org.dive4elements.river.client.shared.model.Recommendation;
-import org.dive4elements.river.client.shared.model.Recommendation.Facet;
-import org.dive4elements.river.client.shared.model.Recommendation.Filter;
-import org.dive4elements.river.client.shared.model.User;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-// TODO Probably better to branch off AbstractUIProvider.
-// TODO Merge with other datacage-widget impls.
 /**
- * Panel containing a Grid and a "next" button. The Grid is fed by a
+ * A {@link AbstractPairRecommendationPanel} that uses a 'TwinDatacage' in the help-input area.
  * DatacagePairWidget which is put in the input-helper area.
  */
-public class DatacageTwinPanel
-extends      TextProvider {
-
-    private static final long serialVersionUID = 8906629596491827857L;
-
-    protected static FLYSConstants MSG = GWT.create(FLYSConstants.class);
-
-    protected String dataName;
-
-    protected User user;
-
-    /** ListGrid that displays user-selected pairs to build differences with. */
-    protected ListGrid differencesList;
-
-    /**
-     * List to track previously selected but now removed pairs. (Needed to
-     * be able to identify artifacts that can be removed from the collection.
-     */
-    protected List<RecommendationPairRecord> removedPairs =
-        new ArrayList<RecommendationPairRecord>();
-
-    /** Service handle to clone and add artifacts to collection. */
-    LoadArtifactServiceAsync loadArtifactService = GWT.create(
-        org.dive4elements.river.client.client.services
-        .LoadArtifactService.class);
-
-    /** Service to remove artifacts from collection. */
-    RemoveArtifactServiceAsync removeArtifactService = GWT.create(
-        org.dive4elements.river.client.client.services
-        .RemoveArtifactService.class);
-
-
-    public DatacageTwinPanel(User user) {
-        super();
-        this.user = user;
-    }
-
-
-    /**
-     * Remove first occurrence of "[" and "]" (if both do occur).
-     * @param value String to be stripped of [] (might be null).
-     * @return input string but with [ and ] removed, or input string if no
-     *         brackets were found.
-     * @see StringUtil.unbracket
-     */
-    public static final String unbracket(String value) {
-        // null- guard.
-        if (value == null) return value;
-
-        int start = value.indexOf("[");
-        int end   = value.indexOf("]");
-
-        if (start < 0 || end < 0) {
-            return value;
-        }
-
-        value = value.substring(start + 1, end);
+public abstract class DatacageTwinPanel
+extends      AbstractPairRecommendationPanel {
 
-        return value;
-    }
-
-
-    /**
-     * Create a recommendation from a string representation of it.
-     * @param from string in format as shown above.
-     * @return recommendation from input string.
-     */
-    public Recommendation createRecommendationFromString(
-        String from,
-        String factory
-    ) {
-        // TODO Construct "real" filter.
-        String[] parts = unbracket(from).split(";");
-        Recommendation.Filter filter = new Recommendation.Filter();
-        Recommendation.Facet  facet  = new Recommendation.Facet(
-                parts[1],
-                parts[2]);
-
-        List<Recommendation.Facet> facets = new ArrayList<Recommendation.Facet>
-            ();
-        facets.add(facet);
-        filter.add("longitudinal_section", facets);
-        Recommendation r = new Recommendation(factory, parts[0],
-            this.artifact.getUuid(), filter);
-        r.setDisplayName(parts[3]);
-        return r;
-    }
-
+	private IDatacageTwinPanelInfo leftInfo;
+	private IDatacageTwinPanelInfo rightInfo;
 
-    /**
-     * Add RecomendationPairRecords from input String to the ListGrid.
-     */
-    public void populateGridFromString(String from, String factory){
-        // Split this string.
-        // Create according recommendations and display strings.
-        String[] recs = from.split("#");
-        if (recs.length % 2 != 0) return;
-        for (int i = 0; i < recs.length; i+=2) {
-            Recommendation minuend =
-                createRecommendationFromString(recs[i+0], factory);
-            Recommendation subtrahend =
-                createRecommendationFromString(recs[i+1], factory);
+	public static interface IDatacageTwinPanelInfo extends IRecommendationInfo
+	{
+		String getOuts();
+	}
+	
+	public DatacageTwinPanel(final User user, IValidator validator, final IDatacageTwinPanelInfo leftInfo, final IDatacageTwinPanelInfo rightInfo ) {
+		super(user, validator, leftInfo, rightInfo);
 
-            RecommendationPairRecord pr = new RecommendationPairRecord(
-                minuend, subtrahend);
-            // This Recommendation Pair comes from the data string and was thus
-            // already cloned.
-            pr.setIsAlreadyLoaded(true);
-            this.differencesList.addData(pr);
-        }
-    }
-
-
-    /**
-     * Creates the graphical representation and interaction widgets
-     * for the data.
-     * @param dataList the data.
-     * @return graphical representation and interaction widgets for data.
-     */
+		this.leftInfo = leftInfo;
+		this.rightInfo = rightInfo;
+	}
+	
     @Override
-    public Canvas create(DataList dataList) {
+    protected final Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User user, final ListGrid differencesList) {
         GWT.log("createData()");
 
-        Canvas widget = createWidget();
         Canvas submit = getNextButton();
 
         VLayout layout       = new VLayout();
         HLayout helperLayout = new HLayout();
-        helperLayout.addMember(new DatacagePairWidget(this.artifact,
-            user, "winfo_diff_twin_panel", differencesList));
+        
+        final String leftOuts = leftInfo.getOuts();
+        final String rightOuts = rightInfo.getOuts();
+        
+        helperLayout.addMember(new DatacagePairWidget(this.artifact, user, leftOuts, rightOuts, differencesList));
 
         layout.addMember(widget);
         layout.addMember(submit);
         layout.setMembersMargin(10);
         this.helperContainer.addMember(helperLayout);
 
-        populateGrid(dataList, "waterlevel");
-        return layout;
-    }
-
-    protected void populateGrid(DataList dataList, String factory) {
-        Data data     = dataList.get(0);
-        this.dataName = data.getLabel();
-        for (int i = 0; i < dataList.size(); i++) {
-            if (dataList.get(i) != null
-                && dataList.get(i).getItems() != null
-            ) {
-                if (dataList.get(i).getItems() != null) {
-                    populateGridFromString(
-                        dataList.get(i).getItems()[0].getStringValue(),
-                        factory);
-                }
-            }
-        }
-    }
-
-
-    /**
-     * Validates the selection.
-     * @return List of internationalized errror messages (if any).
-     */
-    @Override
-    public List<String> validate() {
-        List<String> errors = new ArrayList<String>();
-        if (differencesList.getRecords().length == 0) {
-            errors.add(MSG.error_no_waterlevel_pair_selected());
-        }
-        // Check whether minuend and subtrahend are equal.
-        for (ListGridRecord record: differencesList.getRecords()) {
-            RecommendationPairRecord r = (RecommendationPairRecord) record;
-            if (r.getFirst().equals(r.getSecond())) {
-                errors.add(MSG.error_same_waterlevels_in_pair());
-            }
-        }
-
-        return errors;
-    }
-
-
-    /**
-     * Creates layout with grid that displays selection inside.
-     */
-    public Canvas createWidget() {
-        VLayout layout  = new VLayout();
-        differencesList = new ListGrid();
-
-        differencesList.setCanEdit(false);
-        differencesList.setCanSort(false);
-        differencesList.setShowHeaderContextMenu(false);
-        differencesList.setHeight(150);
-        differencesList.setShowAllRecords(true);
-
-        ListGridField nameField    = new ListGridField("first",  "Minuend");
-        ListGridField capitalField = new ListGridField("second", "Subtrahend");
-        // Track removed rows, therefore more or less reimplement
-        // setCanRecomeRecords.
-        final ListGridField removeField  =
-            new ListGridField("_removeRecord", "Remove Record"){{
-                setType(ListGridFieldType.ICON);
-                setIcon(GWT.getHostPageBaseURL() + MSG.removeFeature());
-                setCanEdit(false);
-                setCanFilter(false);
-                setCanSort(false);
-                setCanGroupBy(false);
-                setCanFreeze(false);
-                setWidth(25);
-        }};
-
-        differencesList.setFields(new ListGridField[] {nameField,
-           capitalField, removeField});
-
-        differencesList.addRecordClickHandler(new RecordClickHandler() {
-                @Override
-                public void onRecordClick(final RecordClickEvent event) {
-                    // Just handle remove-clicks
-                    if(!event.getField().getName()
-                        .equals(removeField.getName())
-                    ) {
-                        return;
-                    }
-                    trackRemoved(event.getRecord());
-                    event.getViewer().removeData(event.getRecord());
-                }
-            });
-        layout.addMember(differencesList);
-
         return layout;
     }
-
-
-    /**
-     * Add record to list of removed records.
-     */
-    public void trackRemoved(Record r) {
-        RecommendationPairRecord pr = (RecommendationPairRecord) r;
-        this.removedPairs.add(pr);
-    }
-
-    /**
-     * Set factory of recommendation such that the correct artifacts will
-     * be cloned for difference calculations.
-     */
-    public void adjustRecommendation(Recommendation recommendation) {
-        // XXX: THIS IS AN EVIL HACK TO MAKE W-DIFFERENCES WORK AGAIN!
-        // TODO: Throw all this code away and do it with server side
-        // recommendations!
-        recommendation.setTargetOut("w_differences");
-
-        if (recommendation.getIDs() != null) {
-            GWT.log("Setting staticwkms factory for rec with ID "
-                + recommendation.getIDs());
-            recommendation.setFactory("staticwkms");
-        }
-        /*
-        // So far, we do not need to rewrite the factory anymore,
-        // except for staticwkms; probably other cases will pop up later.
-        else if (recommendation.getFactory().equals("winfo")) {
-            GWT.log("Setting waterlevel factory for a winfo rec.");
-            recommendation.setFactory("waterlevel");
-        }
-        */
-        else {
-           GWT.log("Leave rec. id " + recommendation.getIDs() + ", factory "
-               + recommendation.getFactory() + " untouched.");
-        }
-    }
-
-    /**
-     * Validates data, does nothing if invalid, otherwise clones new selected
-     * waterlevels and add them to collection, forward the artifact.
-     */
-    @Override
-    public void onClick(ClickEvent e) {
-        GWT.log("DatacageTwinPanel.onClick");
-
-        List<String> errors = validate();
-        if (errors != null && !errors.isEmpty()) {
-            showErrors(errors);
-            return;
-        }
-
-        Config config = Config.getInstance();
-        String locale = config.getLocale();
-
-        ListGridRecord[] records = differencesList.getRecords();
-
-        List<Recommendation> ar  = new ArrayList<Recommendation>();
-        List<Recommendation> all = new ArrayList<Recommendation>();
-
-        for (ListGridRecord record : records) {
-            RecommendationPairRecord r =
-                (RecommendationPairRecord) record;
-            // Do not add "old" recommendations.
-            if (!r.isAlreadyLoaded()) {
-                // Check whether one of those is a dike or similar.
-                // TODO differentiate and merge: new clones, new, old.
-                Recommendation firstR = r.getFirst();
-                adjustRecommendation(firstR);
-
-                Recommendation secondR = r.getSecond();
-                adjustRecommendation(secondR);
-                ar.add(firstR);
-                ar.add(secondR);
-            }
-            else {
-                all.add(r.getFirst());
-                all.add(r.getSecond());
-            }
-        }
-
-        final Recommendation[] toClone = ar.toArray(
-            new Recommendation[ar.size()]);
-        final Recommendation[] toUse   = all.toArray(
-            new Recommendation[all.size()]);
-
-        // Find out whether "old" artifacts have to be removed.
-        List<String> artifactIdsToRemove = new ArrayList<String>();
-        for (RecommendationPairRecord rp: this.removedPairs) {
-            Recommendation first  = rp.getFirst();
-            Recommendation second = rp.getSecond();
-
-            for (Recommendation recommendation: toUse) {
-                if (first != null
-                    && first.getIDs().equals(recommendation.getIDs())
-                ) {
-                    first = null;
-                }
-                if (second != null
-                    && second.getIDs().equals(recommendation.getIDs())
-                ) {
-                    second = null;
-                }
-
-                if (first == null && second == null) {
-                    break;
-                }
-            }
-            if (first != null) {
-                artifactIdsToRemove.add(first.getIDs());
-            }
-            if (second != null) {
-                artifactIdsToRemove.add(second.getIDs());
-            }
-        }
-
-        // Remove old artifacts, if any. Do this asychronously without much
-        // feedback.
-        for(final String uuid: artifactIdsToRemove) {
-            removeArtifactService.remove(this.collection,
-                uuid,
-                locale,
-                new AsyncCallback<Collection>() {
-                    @Override
-                    public void onFailure(Throwable caught) {
-                        GWT.log("RemoveArtifact (" + uuid + ") failed.");
-                    }
-                    @Override
-                    public void onSuccess(Collection collection) {
-                        GWT.log("RemoveArtifact succeeded");
-                    }
-                });
-        }
-
-        // Clone new ones (and spawn statics), go forward.
-        parameterList.lockUI();
-        loadArtifactService.loadMany(
-            this.collection,
-            toClone,
-            //"staticwkms" and "waterlevel"
-            null,
-            locale,
-            new AsyncCallback<Artifact[]>() {
-                @Override
-                public void onFailure(Throwable caught) {
-                    GWT.log("Failure of cloning with factories!");
-                    parameterList.unlockUI();
-                }
-                @Override
-                public void onSuccess(Artifact[] artifacts) {
-                    GWT.log("Successfully cloned " + toClone.length +
-                        " with factories.");
-
-                    fireStepForwardEvent(new StepForwardEvent(
-                        getData(toClone, artifacts, toUse)));
-                    parameterList.unlockUI();
-                }
-            });
-    }
-
-
-    /**
-     * Create Data and DataItem from selection (a long string with identifiers
-     * to construct diff-pairs).
-     *
-     * @param newRecommendations "new" recommendations (did not survive a
-     *        backjump).
-     * @param newArtifacts artifacts cloned from newRecommendations.
-     * @param oldRecommendations old recommendations that survived a backjump.
-     *
-     * @return dataitem with a long string with identifiers to construct
-     *         diff-pairs.
-     */
-    protected Data[] getData(
-            Recommendation[] newRecommendations,
-            Artifact[] newArtifacts,
-            Recommendation[] oldRecommendations)
-    {
-        // Construct string with info about selections.
-        String dataItemString = "";
-        for (int i = 0; i < newRecommendations.length; i++) {
-            Recommendation r = newRecommendations[i];
-            Artifact newArtifact = newArtifacts[i];
-            String uuid = newArtifact.getUuid();
-            r.setMasterArtifact(uuid);
-            if (i>0) dataItemString += "#";
-
-            dataItemString += createDataString(uuid, r);
-        }
-
-        for (int i = 0; i < oldRecommendations.length; i++) {
-            Recommendation r = oldRecommendations[i];
-            String uuid = r.getIDs();
-            if (dataItemString.length() > 0) dataItemString += "#";
-
-            dataItemString += createDataString(uuid, r);
-        }
-
-        // TODO some hassle could be resolved by using multiple DataItems
-        // (e.g. one per pair).
-        DataItem item = new DefaultDataItem(dataName, dataName, dataItemString);
-        return new Data[] { new DefaultData(
-            dataName, null, null, new DataItem[] {item}) };
-    }
-
-
-    protected String createDataString(
-        String artifact,
-        Recommendation recommendation
-    ) {
-        return createDataString(artifact, recommendation, "staticwkms");
-    }
-
-    /**
-     * Creates part of the String that encodes minuend or subtrahend.
-     * @param artifact Artifacts UUID.
-     * @param recommendation Recommendation to wrap in string.
-     * @param factory The factory to encode.
-     */
-    protected String createDataString(
-        String artifact,
-        Recommendation recommendation,
-        String factory)
-    {
-        Filter filter = recommendation.getFilter();
-        Facet  f      = null;
-
-        if(filter != null) {
-            Map<String, List<Facet>>               outs = filter.getOuts();
-            Set<Map.Entry<String, List<Facet>>> entries = outs.entrySet();
-
-            for (Map.Entry<String, List<Facet>> entry: entries) {
-                List<Facet> fs = entry.getValue();
-
-                f = fs.get(0);
-                if (f != null) {
-                    break;
-                }
-            }
-
-            return "[" + artifact + ";"
-                + f.getName()
-                + ";"
-                + f.getIndex()
-                + ";"
-                + recommendation.getDisplayName() + "]";
-        }
-        else {
-            return "["
-                + artifact
-                + ";" + factory + ";0;"
-                + recommendation.getDisplayName() + "]";
-        }
-    }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DefaultDatacageTwinPanelInfo.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,47 @@
+/** 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.client.client.ui;
+
+import org.dive4elements.river.client.client.ui.DatacageTwinPanel.IDatacageTwinPanelInfo;
+import org.dive4elements.river.client.shared.model.Recommendation;
+
+/**
+ * @author Gernot Belger
+ */
+public final class DefaultDatacageTwinPanelInfo implements IDatacageTwinPanelInfo {
+
+	private String factory;
+	private String outs;
+
+	public DefaultDatacageTwinPanelInfo(final String factory, final String outs) {
+		this.factory = factory;
+		this.outs = outs;
+	}
+	
+	@Override
+	public String getFactory() {
+		return factory;
+	}
+	
+	@Override
+	public String getDataStringFactory() {
+		return factory;
+	}
+
+    @Override
+	public void adjustRecommendation(Recommendation recommendation) {
+        recommendation.setFactory(factory);
+    }
+
+	@Override
+	public String getOuts() {
+		return outs;
+	}
+}
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/ParameterList.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/ParameterList.java	Fri Jan 19 11:23:42 2018 +0100
@@ -65,6 +65,7 @@
 import org.dive4elements.river.client.shared.model.OutputMode;
 import org.dive4elements.river.client.shared.model.ReportMode;
 import org.dive4elements.river.client.shared.model.River;
+import org.dive4elements.river.client.shared.model.SINFOArtifact;
 import org.dive4elements.river.client.shared.model.WINFOArtifact;
 
 import java.util.ArrayList;
@@ -771,8 +772,11 @@
                 setCurrentData(null, null);
             }
         }
+
+        // FIXME: we got a whole artifact framework to separate ui and backend stuff, but in the end.... we have switches over specific datatypes here...
         if (art instanceof WINFOArtifact
-                || art instanceof FixAnalysisArtifact) {
+            || art instanceof SINFOArtifact
+        	|| art instanceof FixAnalysisArtifact) {
             createGaugePanel();
             renderInfo(desc.getRiver(), desc.getOldData());
         }
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/UIProviderFactory.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/UIProviderFactory.java	Fri Jan 19 11:23:42 2018 +0100
@@ -22,6 +22,7 @@
 import org.dive4elements.river.client.client.ui.minfo.SedLoadEpochPanel;
 import org.dive4elements.river.client.client.ui.minfo.SedLoadPeriodPanel;
 import org.dive4elements.river.client.client.ui.minfo.SedLoadSQTiPanel;
+import org.dive4elements.river.client.client.ui.sinfo.FlowDepthTwinPanel;
 import org.dive4elements.river.client.client.ui.sq.SQPeriodPanel;
 import org.dive4elements.river.client.shared.model.User;
 
@@ -86,8 +87,8 @@
         else if (uiProvider.equals("dgm_datacage_panel")) {
             return new DemDatacagePanel(user);
         }
-        else if (uiProvider.equals("datacage_twin_panel")) {
-            return new DatacageTwinPanel(user);
+        else if (uiProvider.equals("waterlevel_twin_panel")) {
+            return new WaterlevelTwinPanel(user);
         }
         else if (uiProvider.equals("auto_integer")) {
             return new AutoIntegerPanel();
@@ -194,10 +195,12 @@
         else if (uiProvider.equals("static_sqrelation")) {
             return new StaticDataPanel();
         }
-        else {
-            //GWT.log("Picked default provider.");
-            return new SelectProvider();
-        }
+        
+        if ("sinfo_flowdepth_twin_panel".equals(uiProvider))
+        	return new FlowDepthTwinPanel(user);
+
+        //GWT.log("Picked default provider.");
+        return new SelectProvider();
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelRecommandationInfo.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,69 @@
+/** 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.client.client.ui;
+
+import org.dive4elements.river.client.client.ui.DatacageTwinPanel.IDatacageTwinPanelInfo;
+import org.dive4elements.river.client.shared.model.Recommendation;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * @author Gernot Belger
+ */
+public final class WaterlevelRecommandationInfo implements IDatacageTwinPanelInfo {
+
+	private String outs;
+
+	public WaterlevelRecommandationInfo(String outs ) {
+		this.outs = outs;
+	}
+	
+	@Override
+	public String getFactory() {
+		// FIXME: why are the factory here and the one used in createDataString different?
+		// Probably also because of the 'throw all this code away comment'
+		return "waterlevel";
+	}
+	
+	@Override
+	public String getDataStringFactory() {
+		return "staticwkms";
+	}
+	
+    @Override
+	public void adjustRecommendation(Recommendation recommendation) {
+        // XXX: THIS IS AN EVIL HACK TO MAKE W-DIFFERENCES WORK AGAIN!
+        // TODO: Throw all this code away and do it with server side recommendations!
+        recommendation.setTargetOut("w_differences");
+
+        if (recommendation.getIDs() != null) {
+            GWT.log("Setting staticwkms factory for rec with ID "
+                + recommendation.getIDs());
+            recommendation.setFactory("staticwkms");
+        }
+        /*
+        // So far, we do not need to rewrite the factory anymore,
+        // except for staticwkms; probably other cases will pop up later.
+        else if (recommendation.getFactory().equals("winfo")) {
+            GWT.log("Setting waterlevel factory for a winfo rec.");
+            recommendation.setFactory("waterlevel");
+        }
+        */
+        else {
+           GWT.log("Leave rec. id " + recommendation.getIDs() + ", factory "
+               + recommendation.getFactory() + " untouched.");
+        }
+    }
+
+	@Override
+	public String getOuts() {
+		return outs;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelTwinPanel.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,25 @@
+/** 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.client.client.ui;
+
+import org.dive4elements.river.client.shared.model.User;
+
+/**
+ * A DatacageTwinPanel implementation for W-INFO Differences: choose two waterlevels
+ * 
+ * @author Gernot Belger
+ */
+public class WaterlevelTwinPanel
+extends DatacageTwinPanel {
+
+	public WaterlevelTwinPanel(final User user) {
+		super(user, new WaterlevelTwinPanelValidator(), new WaterlevelRecommandationInfo("winfo_diff_twin_panel"), new WaterlevelRecommandationInfo("winfo_diff_twin_panel") );
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/WaterlevelTwinPanelValidator.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,48 @@
+/** 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.client.client.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dive4elements.river.client.client.FLYSConstants;
+import org.dive4elements.river.client.client.ui.AbstractPairRecommendationPanel.IValidator;
+
+import com.smartgwt.client.widgets.grid.ListGrid;
+import com.smartgwt.client.widgets.grid.ListGridRecord;
+
+/**
+ * Contains the old code from the validate-method of the DatacageTwinPanel.
+ *  
+ * @author Gernot Belger
+ */
+public final class WaterlevelTwinPanelValidator implements IValidator {
+
+	@Override
+	public List<String> validate(final ListGrid differencesList, final FLYSConstants msgProvider) {
+		
+        final List<String> errors = new ArrayList<String>();
+        if (differencesList.getRecords().length == 0) {
+        	// FIXME: waterlevel dependent! This will lead to a bad error message in English, for M-Info/Bed-Differences calculation
+            errors.add(msgProvider.error_no_waterlevel_pair_selected());
+        }
+        // Check whether minuend and subtrahend are equal.
+        for (ListGridRecord record: differencesList.getRecords()) {
+            RecommendationPairRecord r = (RecommendationPairRecord) record;
+            if (r.getFirst().equals(r.getSecond())) {
+            	// FIXME: this is still waterlevel specific!
+            	// TODO: delegate validation to specific implementations
+                errors.add(msgProvider.error_same_waterlevels_in_pair());
+            }
+        }
+
+        return errors;
+	}
+}
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/minfo/BedHeightsDatacagePanel.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/minfo/BedHeightsDatacagePanel.java	Fri Jan 19 11:23:42 2018 +0100
@@ -8,85 +8,51 @@
 
 package org.dive4elements.river.client.client.ui.minfo;
 
+import java.util.List;
+
+import org.dive4elements.river.client.client.ui.AbstractPairRecommendationPanel;
+import org.dive4elements.river.client.client.ui.DatacageWidget;
+import org.dive4elements.river.client.client.ui.DefaultDatacageTwinPanelInfo;
+import org.dive4elements.river.client.client.ui.RecommendationPairRecord;
+import org.dive4elements.river.client.client.ui.WaterlevelTwinPanelValidator;
+import org.dive4elements.river.client.shared.model.DataList;
+import org.dive4elements.river.client.shared.model.ToLoad;
+import org.dive4elements.river.client.shared.model.User;
+
 import com.google.gwt.core.client.GWT;
-
 import com.smartgwt.client.util.SC;
 import com.smartgwt.client.widgets.Button;
 import com.smartgwt.client.widgets.Canvas;
-
 import com.smartgwt.client.widgets.events.ClickEvent;
 import com.smartgwt.client.widgets.events.ClickHandler;
-
+import com.smartgwt.client.widgets.grid.ListGrid;
 import com.smartgwt.client.widgets.layout.VLayout;
 import com.smartgwt.client.widgets.tree.TreeNode;
 
-import org.dive4elements.river.client.client.FLYSConstants;
-
-import org.dive4elements.river.client.client.services.LoadArtifactServiceAsync;
-import org.dive4elements.river.client.client.services.RemoveArtifactServiceAsync;
-
-import org.dive4elements.river.client.client.ui.DatacageTwinPanel;
-import org.dive4elements.river.client.client.ui.DatacageWidget;
-import org.dive4elements.river.client.client.ui.RecommendationPairRecord;
-
-import org.dive4elements.river.client.shared.model.DataList;
-import org.dive4elements.river.client.shared.model.ToLoad;
-
-import org.dive4elements.river.client.shared.model.Recommendation;
-import org.dive4elements.river.client.shared.model.User;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// TODO Probably better to branch off AbstractUIProvider.
 public class BedHeightsDatacagePanel
-extends      DatacageTwinPanel {
-
-    protected static FLYSConstants MSG = GWT.create(FLYSConstants.class);
-
-    /**
-     * List to track previously selected but now removed pairs. (Needed to
-     * be able to identify artifacts that can be removed from the collection.
-     */
-    protected List<RecommendationPairRecord> removedPairs =
-        new ArrayList<RecommendationPairRecord>();
-
-    /** Service handle to clone and add artifacts to collection. */
-    LoadArtifactServiceAsync loadArtifactService = GWT.create(
-        org.dive4elements.river.client.client.services
-        .LoadArtifactService.class);
-
-    /** Service to remove artifacts from collection. */
-    RemoveArtifactServiceAsync removeArtifactService = GWT.create(
-        org.dive4elements.river.client.client.services
-        .RemoveArtifactService.class);
-
-    protected DatacageWidget datacage;
+extends      AbstractPairRecommendationPanel {
 
     public BedHeightsDatacagePanel(User user) {
-        super(user);
+    	// FIXME: This will lead to a bad error message in English (i.e. contains something about waterlevels), for M-Info/Bed-Differences calculation
+    	// BUT: this is the behavior of 3.2.1 (because of sloppy derivation), so we do not change it now
+        super(user, new WaterlevelTwinPanelValidator(), new DefaultDatacageTwinPanelInfo("bedheight", null), new DefaultDatacageTwinPanelInfo("bedheight", null) );
     }
 
-    /**
-     * Creates graphical representation and interaction widgets for the data.
-     * @param dataList the data.
-     * @return graphical representation and interaction widgets for data.
-     */
     @Override
-    public Canvas create(DataList dataList) {
+    protected Canvas createChooserWidgets(final Canvas widget, final DataList dataList, final User user, final ListGrid differencesList) {
         GWT.log("createData()");
 
-        Canvas widget = createWidget();
         Canvas submit = getNextButton();
-        datacage = new DatacageWidget(
+
+        final DatacageWidget datacage = new DatacageWidget(
             this.artifact, user, "minfo_diff_panel", "load-system:true", false);
 
-        Button plusBtn = new Button(MSG.datacage_add_pair());
+        Button plusBtn = new Button(msg().datacage_add_pair());
         plusBtn.setAutoFit(true);
         plusBtn.addClickHandler(new ClickHandler() {
             @Override
             public void onClick(ClickEvent event) {
-                plusClicked();
+                plusClicked(datacage, differencesList);
             }
         });
 
@@ -100,32 +66,19 @@
         layout.setMembersMargin(10);
         this.helperContainer.addMember(helperLayout);
 
-        populateGrid(dataList, "bedheight");
-
         return layout;
     }
 
-    public void adjustRecommendation(Recommendation recommendation) {
-        recommendation.setFactory("bedheight");
-    }
-
-    @Override
-    protected String createDataString(
-        String artifact,
-        Recommendation recommendation
-    ) {
-        return createDataString(artifact, recommendation, "bedheight");
-    }
-
     /**
      * Callback for add-button.
      * Fires to load for every selected element and handler.
+     * @param differencesList 
      */
-    public void plusClicked() {
+    protected final static void plusClicked( final DatacageWidget datacage, ListGrid differencesList ) {
         List<TreeNode> selection = datacage.getPlainSelection();
 
         if (selection == null || selection.isEmpty()) {
-            SC.say(MSG.warning());
+            SC.say(msg().warning());
             return;
         }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/sinfo/FlowDepthTwinPanel.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,25 @@
+/** 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.client.client.ui.sinfo;
+
+import org.dive4elements.river.client.client.ui.DatacageTwinPanel;
+import org.dive4elements.river.client.client.ui.DefaultDatacageTwinPanelInfo;
+import org.dive4elements.river.client.client.ui.WaterlevelRecommandationInfo;
+import org.dive4elements.river.client.shared.model.User;
+
+/**
+ * @author Gernot Belger
+ */
+public class FlowDepthTwinPanel 
+extends DatacageTwinPanel {
+	public FlowDepthTwinPanel(final User user) {
+		super(user, new FlowDepthTwinPanelValidator(), new WaterlevelRecommandationInfo("sinfo_flowdepth_waterlevels"), new DefaultDatacageTwinPanelInfo("bedheight", "sinfo_flowdepth_minfo_heights") );
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/sinfo/FlowDepthTwinPanelValidator.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,39 @@
+/** 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.client.client.ui.sinfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dive4elements.river.client.client.FLYSConstants;
+import org.dive4elements.river.client.client.ui.AbstractPairRecommendationPanel.IValidator;
+
+import com.smartgwt.client.widgets.grid.ListGrid;
+import com.smartgwt.client.widgets.grid.ListGridRecord;
+
+/**
+ * Contains the old code from the validate-method of the DatacageTwinPanel.
+ *  
+ * @author Gernot Belger
+ */
+final class FlowDepthTwinPanelValidator implements IValidator {
+
+	@Override
+	public List<String> validate(final ListGrid differencesList, final FLYSConstants msgProvider) {
+
+        final List<String> errors = new ArrayList<String>();
+        if (differencesList.getRecords().length == 0) {
+        	// FIXME: waterlevel dependent! This will lead to a bad error message in English, for M-Info/Bed-Differences calculation
+            errors.add(msgProvider.sinfo_flowdepth_twinpanel_no_pair_selected());
+        }
+
+        return errors;
+	}
+}
\ No newline at end of file
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/FLYSArtifactCreator.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/FLYSArtifactCreator.java	Fri Jan 19 11:23:42 2018 +0100
@@ -31,6 +31,7 @@
 import org.dive4elements.river.client.shared.model.FixAnalysisArtifact;
 import org.dive4elements.river.client.shared.model.GaugeDischargeCurveArtifact;
 import org.dive4elements.river.client.shared.model.MapArtifact;
+import org.dive4elements.river.client.shared.model.SINFOArtifact;
 import org.dive4elements.river.client.shared.model.MINFOArtifact;
 import org.dive4elements.river.client.shared.model.StaticSQRelationArtifact;
 import org.dive4elements.river.client.shared.model.WINFOArtifact;
@@ -134,35 +135,47 @@
 
         name = name.trim();
 
-        if (name.length() > 0 && name.equals("winfo")) {
+        // FIXME: why do we have a super sophisticated artifact-framework if, in the end, module dependent stuff is still switched manually....
+        if (name.equals("winfo")) {
             log.debug("+++++ NEW WINFO ARTIFACT.");
             return new WINFOArtifact(uuid, hash, background, msg);
         }
-        else if (name.length() > 0 && name.equals("new_map")) {
+
+        if (name.equals("new_map")) {
             log.debug("+++++ NEW MAP ARTIFACT.");
             return new MapArtifact(uuid, hash, background, msg);
         }
-        else if (name.length() > 0 && name.equals("new_chart")) {
+        
+        if (name.equals("new_chart")) {
             log.debug("+++++ NEW CHART ARTIFACT.");
             return new ChartArtifact(uuid, hash, background, msg);
         }
-        else if (name.length() > 0 && name.equals("minfo")) {
+        
+        if (name.equals("minfo")) {
             log.debug("+++++ NEW MINFO ARTIFACT.");
             return new MINFOArtifact(uuid, hash, background, msg);
         }
-        else if (name.length() > 0 && name.equals("fixanalysis")) {
+        
+        if (name.equals("fixanalysis")) {
             log.debug("+++++ NEW FIXANALYSIS ARTIFACT.");
             return new FixAnalysisArtifact(uuid, hash, background, msg);
         }
-        else if (name.length() > 0 && name.equals("gaugedischargecurve")) {
+        
+        if (name.equals("gaugedischargecurve")) {
             log.debug("+++++ NEW GAUGEDISCHARGECURVE ARTIFACT.");
             return new GaugeDischargeCurveArtifact(uuid, hash, background, msg);
         }
-        else if (name.length() > 0 && name.equals("staticsqrelation")) {
+        
+        if (name.equals("staticsqrelation")) {
             log.debug("+++++ STATICSQRELATION ARTIFACT.");
             return new StaticSQRelationArtifact(uuid, hash, background, msg);
         }
 
+        if (name.equals("sinfo")) {
+            log.debug("+++++ NEW SINFO ARTIFACT.");
+            return new SINFOArtifact(uuid, hash, background, msg);
+        }
+
         return new DefaultArtifact(uuid, hash, background, msg);
     }
 
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/UserClient.java	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/UserClient.java	Fri Jan 19 11:23:42 2018 +0100
@@ -84,6 +84,8 @@
         account.setAttribute("name", user.getAccount());
 
         //TODO create roles
+        // FIXME: not creating the roles will write an broken xmldocument (only header) into the artifacts db 
+        // which in turn will result in an exception (which is handled)
         artuser.appendChild(account);
         action.appendChild(type);
         action.appendChild(artuser);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/SINFOArtifact.java	Fri Jan 19 11:23:42 2018 +0100
@@ -0,0 +1,46 @@
+/* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * 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.client.shared.model;
+
+import java.util.List;
+
+
+/**
+ * The SINFO implementation of an Artifact.
+ *
+ * @author Gernot Belger
+ */
+public class SINFOArtifact extends DefaultArtifact {
+
+    /** The name of this artifact: 'sinfo'.*/
+    private static final String NAME = "sinfo";
+
+    /** Necessary for serialization */
+    public SINFOArtifact() {
+    }
+
+//    public  SINFOArtifact(String uuid, String hash) {
+//        super(uuid, hash);
+//    }
+
+    public SINFOArtifact(
+        String                   uuid,
+        String                   hash,
+        boolean                  inBackground,
+        List<CalculationMessage> messages
+    ) {
+        super(uuid, hash, inBackground, messages);
+    }
+
+
+    public String getName() {
+        return NAME;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/webapp/WEB-INF/features.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/webapp/WEB-INF/features.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ftr:features xmlns:ftr="http://www.intevation.de/2012/flys/features">
     <ftr:role name="d4e_demo_all">
+        <ftr:feature>module:sinfo</ftr:feature>
         <ftr:feature>module:winfo</ftr:feature>
         <ftr:feature>module:minfo</ftr:feature>
         <ftr:feature>module:new_map</ftr:feature>
--- a/gwt-client/src/main/webapp/WEB-INF/log4j.properties	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/webapp/WEB-INF/log4j.properties	Fri Jan 19 11:23:42 2018 +0100
@@ -1,18 +1,15 @@
 log4j.rootLogger=DEBUG, FLYS
 
 ########## INTERNAL PACKAGES
-log4j.category.de.intevation.flys.client.server=DEBUG
-
+log4j.category.org.dive4elements.river.client.server=DEBUG
 
 ########## EXTERNAL PACKAGES
 log4j.category.org.apache.http=ERROR
-log4j.category.de.intevation.artifacts.httpclient=WARN
+log4j.category.org.dive4elements.artifacts.httpclient=WARN
 
 ########## APPENDER SETTINGS
 log4j.appender.FLYS.layout=org.apache.log4j.PatternLayout
 log4j.appender.FLYS.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
 
-log4j.appender.FLYS=org.apache.log4j.RollingFileAppender
-log4j.appender.FLYS.File=/var/log/d4e-river/d4e-client.log
-log4j.appender.FLYS.MaxFileSize=5000KB
-log4j.appender.FLYS.MaxBackupIndex=3
+log4j.appender.FLYS=org.apache.log4j.ConsoleAppender
+log4j.appender.FLYS.Target = System.out
--- a/gwt-client/src/main/webapp/WEB-INF/web.xml	Thu Jan 18 20:54:03 2018 +0100
+++ b/gwt-client/src/main/webapp/WEB-INF/web.xml	Fri Jan 19 11:23:42 2018 +0100
@@ -85,6 +85,8 @@
     <url-pattern>/flys/user</url-pattern>
   </servlet-mapping>
 
+  <!-- FIXME: mixing the order of elements here i.e. (servlet - servlet-mapping - servlet) results in errors when validating against the official DTD -->
+
   <servlet>
     <servlet-name>server-info</servlet-name>
     <servlet-class>org.dive4elements.river.client.server.ServerInfoServiceImpl</servlet-class>

http://dive4elements.wald.intevation.org