changeset 2793:6310b1582f2d

merged flys-artifacts/2.7
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:30 +0200
parents ada02bbd3b7f (current diff) fe987587ebc9 (diff)
children f0a67bc0e777
files
diffstat 437 files changed, 88708 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/ChangeLog	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,15594 @@
+2012-05-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java:
+	  Fit from Q to W not from W to Q!
+	  Delta w/t's are now in cm.
+	  Slimmed down fitting error report.
+
+	* src/main/java/de/intevation/flys/exports/DeltaWtExporter.java:
+	  Changed default column header of delta W to cm.
+	  
+2012-05-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Made Delta W/t calculation work.
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java:
+	  Added toString() method.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumn.java:
+	  Fixed problem accessing Ws.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Parameters.java:
+	  Fixed silly 'name.equals(name)' bug. This mainly prevented delta W/t
+	  computation from working.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWTsKM.java:
+	  More deug code.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java:
+	  More debug code. Simplified access to data a bit.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Allow CSV exporter to throw IOExceptions.
+
+	* src/main/java/de/intevation/flys/exports/DeltaWtExporter.java:
+	  More debug output. Flush CSV after writing.
+
+2012-05-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java:
+	  Units of step is in meter and not km. Added more debug output.
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation/FixationCompute.java:
+	  Stupidly forgot to add facet to return the delta w/t as CSV.
+
+2012-05-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java:
+	  There was a cast to WINFOArtifact where a cast to FLYSArtifact
+	  is sufficient. This prevented generating error reports that
+	  does not inherit from WINFOArtifact like FixationArtifact.
+
+2012-05-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/fixanalysis.xml,
+	  doc/conf/conf.xml: Added CSV and error report generator/facets.
+
+2012-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java:
+	  CrossSectionFactory.isNewest() will return true if the CrossSection is
+	  the only CrossSection found in the database. No IndexOutOfBounds is
+	  thrown.
+
+2012-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* Tagged module as '2.7'.
+
+2012-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Don't break chart creation if no hyks are existing.
+
+2012-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWTsKM.java,
+	  src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java,
+	  src/main/java/de/intevation/flys/artifacts/model/fixings/FixResult.java,
+	  src/main/java/de/intevation/flys/artifacts/ExternalWMSArtifact.java:
+	  Removed superfluous imports.
+
+2012-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWTsKM.java:
+	  Added getters to access the inner data structures.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: Added formatting
+	  support for delta w/t exports of fix analysis.
+
+	* src/main/java/de/intevation/flys/exports/DeltaWtExporter.java:
+	  New. CSV exporter for delta w/ts of fix analysis.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Reordered imports.
+
+2012-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FitResult.java:
+	  Removed. Obsolete.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixResult.java:
+	  New. Stores the result of the fixing analysis.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java:
+	  Return the right structure (FixResult) from the calculation.
+
+2012-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Parameters.java:
+	  Fixed stupid index bug.
+
+2012-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation/FixationCompute.java:
+	  Call the computing.
+
+2012-05-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Use the correct Artifacts for wms fixpoints and
+	  floodmaps (they were interchanged).
+
+2012-05-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixResults.java:
+	  New. Stores all results of a fixing analysis.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWTsKM.java:
+	  New. Km indexed container for Delta W/t diagrams.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java:
+	  Create FixResults and DeltaWTsKM now to store the results.
+	
+2012-05-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java:
+	  Replaced another silly "@attribute" XPATH with direct getAttribute()
+	  call.
+	
+2012-05-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Draw line of mainvalues to duration curve hit point to ground.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Also work for q-value case on second y-axis, refactored.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java:
+	  Doc and fix.Doc and fix.
+
+2012-05-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Also draw lines to curve from q-mainvalues in dur. curve
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java:
+	  In analogy to the W-case, calculate where line from main value hits
+	  a wqday-curve from DurationCurve.
+
+2012-05-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Draw line of w-mainvalue to duration curve hit point to ground.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  If StickyAxisAnnotation has a hit point set, also add line annotation
+	  from the crossing point to the x-axis.
+
+2012-05-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Draw line of w-mainvalue to duration curve, baby version.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Added field "hitPoint", when a line to a point should be drawn
+	  - leveraging this annotation into the second dimension.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java:
+	  Calculate where mainvalue hits a duration curve.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  If StickyAxisAnnotation has a hit point set, add line annotation
+	  to hit (the duration) curve.
+
+2012-05-25  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Added a warning to not use virtual themes for facets.
+
+2012-05-25  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Added concrete themes for maps.
+
+2012-05-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Moved parameter of addAnnotations out, renamed to addVisibleAnnotation.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Adjusted.
+
+2012-05-25  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Added concrete themes for areas.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Load mainvalues in reference curves.
+
+	* doc/conf/artifacts/winfo.xml: Add mainvalues to compatibility list
+	  of reference curves; fix typo.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Render mainvalues as annotations.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Render static wkms data.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Cosmetic.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java,
+	  src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java:
+          (flipStickyAxis): Allow easy change of sticky axes.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java
+	  (getWaterlevelData): Exception for discharge case.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java
+	  (getLocations): Exception for discharge case.
+
+2012-05-24  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 667.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Added data to jfreechart data source.
+
+2012-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/services/ThemeListingService.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/HYKArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/AreaArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/GaugeDischargeArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java,
+	  src/main/java/de/intevation/flys/themes/ThemeGroup.java: Removed
+	  needless imports.
+
+2012-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Reproject
+	  geometries into the coordinate reference system defined in the
+	  configuration.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Include other "thematic" heights for
+	  reference curve relative points dc conf.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RelativePointFacet.java:
+	  Added TODO.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/relativepoint.xml,
+	  doc/conf/conf.xml: Remove relativepoint artifact remainders, so far
+	  all cases are covered by facet.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Handle reference curve case, slightly refactored.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Handle points in doOut.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Expose data via blackboard.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Serve fixations
+	  with relative points for reference curves.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java
+	  (getReferenceStartKm, getReferenceEndKms): Made public.
+
+2012-05-24	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Add relative point to compatibility
+	  list of reference curve out.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Remove second facet from
+	  StaticWKmsArtifact from compatibility list of duration curves.
+
+	* doc/conf/themes.xml: Add basic theme for relativepoint.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Serve floodprotections and heightmarks
+	  with relative points for duration curves.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Handle relative points in doOut.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Serve fixations with relative points
+	  for duration curves.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/ExternalWMSArtifact.java:
+	  Use superclass static field and method.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RelativePointFacet.java:
+	  Handle duration curve case. Do a linear interpolation of the day.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DurationCurveFacet.java:
+	  Be a DataProvider, provide the duration curve and its km.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  (getWAtKmLin): Linear interpolating version of getWAtKm.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RelativePointFacet.java:
+	  Add (dummy-) implementation of a relative point facet.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  Use superclass infrastructure. Spawn RelativePointFacet.
+
+	* doc/conf/winfo.xml: Add relativepoint facet to compatibility list
+	  of duration curve out.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Add RELATIVE_POINT Facet Type.
+
+2012-05-23  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/fitting/Function.java:
+	  Allow instantiation of a function with concrete parameters.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Parameters.java:
+	  Enable indexed access to parameter names.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FitResult.java:
+	  New. Bundles the results of the fitting.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWT.java:
+	  New. Stores results of Delta W/t calcs.
+
+	* src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java:
+	  Calculate Delta W/t s, too.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Make component filters ('and', 'or') more easy to construct.
+
+2012-05-23  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java,
+	  src/main/java/de/intevation/flys/themes/ThemeFactory.java:
+	  Improved startup performance.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java
+	  (getDatacageIDValue): New, extracted from various duplicates.
+
+	* src/main/java/de/intevation/flys/artifacts/HYKArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/AreaArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/GaugeDischargeArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java:
+	  Use getDatacageIDValue() from superclass or static context.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java:
+	  Define XPath to access "id" value of element coming from datacage-document.
+
+	* src/main/java/de/intevation/flys/artifacts/HYKArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/AreaArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/GaugeDischargeArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java:
+	  Use xpath defined in superclass.
+
+2012-05-23	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java:
+	  Whitespace-Cosmetics.
+
+2012-05-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/relativepoint.xml: New artifact-state-config for upcoming
+	  artifact.
+
+	* doc/conf/conf.xml: Register artifact factory and artifact for
+	  upcoming artifact.
+
+2012-05-23  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/ThemeGroup.java:
+	  New. A theme group takes a set of themes configured in the themes.xml.
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java:
+	  Read the theme groups configured in the themes.xml.
+	  Improved reading the xml file. Thanks to Sascha for the patch.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Get a theme using the new parameter themegroup.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Create theme groups while configuring themes.
+
+	* src/main/java/de/intevation/flys/artifacts/services/ThemeListingService.java:
+	  New. This service returns a list with all configured theme groups, each
+	  containing exactly one theme filtered by name.
+	  The theme name has to be provided as parameter.
+
+	* doc/conf/conf.xml:
+	  Added new service to config.
+
+	* doc/conf/themes.xml:
+	  Introduced theme groups and added new group.
+
+2012-05-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Repair CS-DC-Conf, allow loading
+	  waterlevels, add fixations and heightmarks.
+
+2012-05-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Cosmetics.
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Allow loading of fixations in cross-sections.
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/GaugeDischargeState.java:
+	  More debugging of discharge curve issue.
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/GaugeDischargeArtifact.java
+	  (getDischargeCurveData): Moved to more specific class.
+
+	* src/main/java/de/intevation/flys/artifacts/model/GaugeDischargeFacet.java:
+	  Cosmetics, be louder in miscondition.
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Allow loading of fixations and thmatic
+	  heights in longitudinal sections, make discharge-curve-misbehavior
+	  testable with trunk.
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Render discharge outs (was own out once).
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Allow cross-section stuff in
+	  dischargelongitudinal state.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java:
+	  Add CrossSectionWaterLineFacets, will trigger the cross section output.
+
+2012-05-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FixationArtifact.java:
+	  Added vim line.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Removed empty line.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Removed invalid/not helpful comments.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java
+	  (outputExists): Doc.
+
+2012-05-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation/ReferencePeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/AnalysisPeriods.java:
+	  Adjusted ui providers to make parameter names unique in client.
+
+2012-05-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Fixed short evaluation problem.
+
+	* src/main/java/de/intevation/flys/artifacts/FixationArtifactAccess.java:
+	  Added more debuf output.
+
+2012-05-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FixationArtifactAccess.java:
+	  New. Access the state data of fixings artifacts.
+
+2012-05-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/fixanalysis.xml: Make "start" and "end" parameters
+	  unique in fixings artifact states.
+
+2012-05-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/winfo.xml: Extend compatibility lists.
+
+	* doc/conf/conf.xml: Add gaugedischarge factory and artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/GaugeDischargeFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/GaugeDischargeArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/GaugeDischargeState.java:
+	  New, State, Facet and Artifact to access discharge curves of gauges.
+
+	* doc/conf/gaugedischarge.xml: New trivial state model for gaugedis.
+
+2012-05-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Allow loading of prev. reference curves.
+
+2012-05-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: (yet unused) sceleton for ref-curve dc conf.
+
+2012-05-21  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings for fix analysis.
+
+2012-05-21  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation/EventSelect.java:
+	  Implemented static data for state.
+
+	* doc/conf/artifacts/fixanalysis.xml:
+	  Changed transition in 'volmer' states.
+
+
+2012-05-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java:
+	  Include km in names of duration curve facets.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Adjusted i18n for
+	  duration curve facet names (now include km).
+
+2012-05-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Allow loading of previous duration curves and
+	  fixations in duration curves.
+
+2012-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/SoundingsSelect.java:
+	  Take the current km range into account in getOptions().
+
+2012-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java,
+	  doc/conf/artifacts/minfo.xml: Adapted facets for middle bed height. We
+	  now distinguish between epochs and singles.
+
+	* doc/conf/conf.xml: Registered new output generator for middle bed height.
+
+	* doc/conf/themes.xml: Added new themes for middle bed height epoch and
+	  single curves.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightCalculation.java:
+	  Take the km range into account.
+
+	* src/main/java/de/intevation/flys/artifacts/states/MiddleBedHeight.java:
+	  Create facets for single and epochs.
+
+	* src/main/java/de/intevation/flys/exports/MiddleBedHeightInfoGenerator.java,
+	* src/main/java/de/intevation/flys/exports/MiddleBedHeightGenerator.java:
+	  Chart and info generator for middle bed height charts.
+
+2012-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightData.java:
+	  Added new methods that create names for singles and epochs.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: Added number
+	  formatters used to export middle bed heights values.
+
+	* src/main/java/de/intevation/flys/exports/MiddleBedHeightExporter.java:
+	  New exporter for middle bed heights values.
+
+	* doc/conf/conf.xml: Registered new output generator for middle bed height
+	  exports.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: New i18n strings for middle
+	  bed height facets and csv export.
+
+2012-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightData.java:
+	  New model class for storing data for middle bed height exports (chart,
+	  data exports).
+
+	* src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightCalculation.java:
+	  New Calculation that generates new MiddleBedHeightData.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightFacet.java:
+	  New Facet which is used for middle bed height charts.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  new facet type for middle bed height curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/MiddleBedHeight.java:
+	  Calculate middle bed height data using MiddleBedHeightCalculation and
+	  create new facets for charts and csv export.
+
+	* src/main/java/de/intevation/flys/artifacts/MINFOArtifact.java: Added new
+	  methods that return the IDs of selected single bed heights and epoch bed
+	  heights.
+
+2012-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/SoundingsSelect.java:
+	  Implemented getOptions() and getLabelFor().
+
+2012-05-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Stabilized legend item aggregation.
+
+2012-05-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Extensive - although mostly trivial - doc adds.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/minfo.xml: Added states and transitions for MINFO
+	  middle bed height calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/states/MultiStringArrayState.java:
+	  New. Nearly a copy of MultiIntArrayState but uses String values.
+
+	* src/main/java/de/intevation/flys/artifacts/states/SoundingsSelect.java:
+	  New subclass of MultiStringArrayState. It will be used for bed heights
+	  selection. We need to do handle the values as string, because singles
+	  and epochs are mixed, so the DB ID is not enough as parameter value.
+
+	* src/main/java/de/intevation/flys/artifacts/states/MiddleBedHeight.java:
+	  New. This state will trigger the calculation for middle bed height in
+	  MINFO module.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/MultiIntArrayState.java:
+	  Do not add a <art:choices> element to the dynamic DESCRIBE but add the
+	  <art:item> directly to <art:select> which allows using default mechanism
+	  in FLYS client.
+
+2012-05-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Replaced event name by its description, which combines the
+	  description of the fixing (= WST file description) and the name
+	  of the column.
+	
+	* contrib/fixoverview2html.xsl: Adjusted.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/minfo.xml: Added a chart output and relevant facets to
+	  flow velocity state.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  new facet types for mainchannel, totalchannel and tau of flow velocity
+	  calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FlowVelocityFacet.java:
+	  New Facet that returns FlowVelocityData in getData().
+
+	* src/main/java/de/intevation/flys/artifacts/model/FlowVelocityData.java:
+	  Added new methods that return xy points for mainchannel, totalchannel and
+	  tau.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FlowVelocityState.java:
+	  Create new mainchannel, totalchannel and tau Facets for each
+	  FlowVelocityData instance retrieved by FlowVelocityCalculation.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Commented
+	  legend aggregation out.
+
+	* src/main/java/de/intevation/flys/exports/FlowVelocityInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/FlowVelocityGenerator.java: New
+	  chart generator for flow velocity data and its info generator.
+
+	* doc/conf/conf.xml: Registered the chart and chart info generator for flow
+	  velocity data.
+
+	* doc/conf/themes.xml: Added new themes for mainchannel, totalchannel and
+	  tau of flow velocity calculation.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for flow
+	  velocity facets.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/MultiIntArrayState.java:
+	  Add a 'label' attribute to each <data> element in the static DESCRIBE.
+	  In addition to that, the <data> element created in this class can have
+	  multiple <item> elements which consist of a 'value' and a 'label'
+	  attribute. This 'label' attribute is taken from getLabelFor() which is
+	  abstract and needs to be implemented by subclasses.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeState.java:
+	  Implemented the abstract method getLabelFor().
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties: New i18n strings for MINFO
+	  parameter names.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: New i18n strings for MINFO
+	  states.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/FlowVelocityExporter.java:
+	  Append the name of a location for the km row which is provided by
+	  FLYSUtils.getLocationDescription(km);
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for flow
+	  velocity CSV export headers.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/minfo.xml: Added new state and csv export for MINFO flow
+	  velocity calculation.
+
+	* doc/conf/conf.xml: Registered a new CSV exporter for flow velocity
+	  values.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FlowVelocityCalculation.java:
+	  New calculation for MINFO flow velocity. Actually, this is no
+	  calculation but a bundling of data.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FlowVelocityData.java:
+	  New model class that is used to store flow velocity relevant data.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FlowVelocityState.java:
+	  New state that triggers the FlowVelocityCalculation and creates new
+	  facets. Currently, only a CSV Facet is created.
+
+	* src/main/java/de/intevation/flys/artifacts/MINFOArtifact.java: Added
+	  methods to retrieve the IDs of main channels and total channels for the
+	  flow velocity calculation.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: New formatters
+	  that are used in CSV exporter for flow velocity.
+
+	* src/main/java/de/intevation/flys/exports/FlowVelocityExporter.java: New
+	  concrete subclasses of AbstractExporter that currently implements the
+	  path to export FlowVelocityData as CSV.
+
+2012-05-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/HYKArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Removed getDataAsDouble() which is now implemented in FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented getDataAsDouble(). This method will return a Double based on
+	  a given parameter name.
+
+2012-05-15  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/artifacts/fixanalysis.xml:
+	  Changed data type for state 'eventselect'.
+
+2012-05-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSQPSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSHydrBoundaryArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSFixpointsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSFloodmapsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FixationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSHwsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSCatchmentArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/QSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/ReferencePeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationVolmerCompute.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/PreprocessingSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationPeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FunctionSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/GaugeRange.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/LocationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/AnalysisPeriods.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationCompute.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSHydrBoundaryPolyArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSBuildingsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSGaugeLocationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSLineArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSFloodplainArtifact.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Removed superfluous imports.
+
+2012-05-15  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/artifacts/fixanalysis.xml:
+	  Adjusted state data fields.
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation/ReferencePeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/PreprocessingSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FunctionSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/AnalysisPeriods.java:
+	  Implemented states.
+
+2012-05-15  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/fitting/Function.java:
+	  Fixed constructor. Descriptions are now available.
+
+2012-05-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/minfo.xml: Added new transistions and states for
+	  MINFO flow velocity calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/states/MultiIntArrayState.java:
+	  New abstract states that might be used to append a list of available
+	  integer values to the DESCRIBE document.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeState.java:
+	  Concrete subclass of MultiIntArrayState which implements the abstract
+	  method getOptions().
+
+2012-05-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Parameters.java:
+	  New. Model for storing fitting results in cache.
+	  
+2012-05-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java:
+	  Removed the computeFeed() method that created Facets for discharge
+	  curves to make this state ready to use in MINFO calculations.
+
+2012-05-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/minfo.xml: New Artifact configuration for module
+	  MINFO.
+
+	* src/main/java/de/intevation/flys/artifacts/MINFOArtifact.java: New
+	  Artifact for module INFO.
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelectMinfo.java:
+	  New. The state is used for choosing the calculation in module MINFO.
+
+	* doc/conf/conf.xml: Registered the MINFO Artifact.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: New strings for states and
+	  calculations.
+
+2012-05-13  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Removed unused code. 
+
+2012-05-13  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Doc.
+
+2012-05-13  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Allow duration_curve.q loading via datacage.
+
+2012-05-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Allow hyk loading also when in non-
+	  recommendation mode.
+
+2012-05-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Improved middle height calculations for waterlevels.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java
+	 (area): Improved or even corrected area calculation.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Corrected middle height calculated, have a very verbose label for
+	  the time being.
+
+2012-05-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Somewhat improve area calculation during line computation.
+	Still faulty.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java
+	 (fillWater): Piecewise calculate area in more cases.
+
+2012-05-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Tackle water-through-mountain issue that exists since r4099.
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java 
+	  (addPoints): Add parameter that decides whether or not to skip NaNs.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Set new parameter to StyledSeriesBuilder.addPoints calls.
+	  For waterlines it is important that NaNs stay included!
+
+2012-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSGaugeLocationArtifact.java:
+	  New Artifact for storing geometries for gauges.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  a new facet type for WMSGaugeLocationArtifact 'floodmap.gaugelocation'.
+
+	* doc/conf/artifacts/winfo.xml,
+	  doc/conf/artifacts/map.xml: Added 'floodmap.gaugelocation' facet to the
+	  list of valid facets for map and floodmap output.
+
+	* doc/conf/conf.xml: Added a factory for creating
+	  WMSGaugeLocationArtifacts.
+
+	* doc/conf/themes.xml: Added a theme for 'floodmap.gaugelocation' facets.
+
+	* doc/conf/meta-data.xml: Added a datacage section for gauge locations to
+	  the floodmap and map section.
+
+2012-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  new facet types 'floodmap.hydr_boundaries' and
+	  'floodmap.hydr_boundaries_poly'
+
+	* doc/conf/artifacts/winfo.xml,
+	  doc/conf/artifacts/map.xml: Registered 'floodmap.hydr_boundaries' and
+	  'floodmap.hydr_boundaries_poly' for floodmap and map output types.
+
+	* doc/conf/conf.xml:
+
+	* doc/conf/themes.xml: Added themes for 'floodmap.hydr_boundaries' and
+	  'floodmap.hydr_boundaries_poly'.
+
+	* doc/conf/meta-data.xml: Improved datacage configuration specific to
+	  floodmaps and maps.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSHydrBoundaryArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSHydrBoundaryPolyArtifact.java:
+	  New artifacts to save information about hydrological boundaries.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSQPSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSFixpointsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSFloodmapsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSHwsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSCatchmentArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSBuildingsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSLineArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSFloodplainArtifact.java:
+	  Moved the methods getRiverId() and getTitle() into WMSDBArtifact.
+
+2012-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/wsplgen_layer.vm,
+	  doc/conf/mapserver/shapefile_layer.vm: Added a 'PROJECTION' section.
+
+2012-05-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Minor refactoring, doc.
+
+2012-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/layer.vm: Added a 'PROJECTION' section.
+
+2012-05-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Minimal doc, TODO.
+
+2012-05-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix issue620: synchron navigation at impossible kms.
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java
+	  (searchCrossSectionLine): Do not return a CrossSectionLine if there
+	  is none found within 1km.
+
+2012-05-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java:
+	  Minimal cosmetics.
+
+2012-05-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Preparations for fix issue620: Synchron navigation on non-existing km.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java
+	  (getData): Return empty lines if no cross section found.
+
+2012-05-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Display mittlere hoehe (which is not yet calculated).
+
+	* doc/conf/themes.xml: Added theme prop to display middlere hoehe or not.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  (parseShowMiddleHeight): New, parse the new theme prop.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  (fillWater, ListWithArea): Return new type wich also contains area.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Respect new theme prop and adjust label if set.
+
+2012-05-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Beginning of calculation of Mittlere Hoehe: area calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  Adjusted to LineData constructor.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java
+	  (area): New, calculate area of a polygon.
+	  Added todos where area calculation code paths are missing.
+
+2012-05-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Moved the hws layers out to an own node.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSCatchmentArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java: Moved the
+	  'name' property from WMSCatchmentArtifact to WMSDBArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSHwsArtifact.java: Create
+	  more specific filter and extent that takes the 'name' into account.
+
+2012-05-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Moved the catchments out to an own node.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSCatchmentArtifact.java:
+	  Parse the name of required catchments fetch name specific catchments
+	  only.
+
+2012-05-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/map.xml: Added the 'floodmap.floodmaps' facet to the
+	  list of valid facets of the 'map' output.
+
+	* doc/conf/themes.xml: Renamed the 'Line' theme used for floodmaps to
+	  'FloodmapLines'.
+
+2012-05-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/db_layer.vm: Added a PROJECTION section which is
+	  filled by the LayerInfo object.
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java: Added
+	  a 'srid' property.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Set the
+	  'srid' property of all LayerInfo objects.
+
+2012-05-07  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/artifacts/fixanalysis.xml:
+	  Changed state data and added new state.
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation/QSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/EventSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationPeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/GaugeRange.java:
+	  Modified states and added new state for concrete event selection.
+
+2012-05-04	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes: Add text-bg properties to  Area style.
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java
+	  (TextStyle.apply): Apply text bg theme-properties to renderer.
+
+	* src/main/java/de/intevation/flys/jfree/StyledAreaSeriesCollection.java:
+	  Cosmetics.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Added field to store labels background color, calculate arithmetic
+	  middles of polygons, to put label there (improves situation slightly).
+
+2012-05-03	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java
+	  (parseShowArea): parse show area field of theme.
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java
+	  (TextStyle.apply): Be applicable to StableXYDifferenceRenderers.
+
+	* src/main/java/de/intevation/flys/jfree/StyledAreaSeriesCollection.java:
+	  Apply setting from theme to renderer.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Simplified area calculation (always calculate). Added getters and
+	  setters for text for label style, respect most of that.
+
+2012-05-03	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes: Let Area style not inherit from colorlines (as labels
+	  work slightly different. Enable showarea property. Comment dupe Lines.
+
+2012-05-03	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java
+	  (doArea): Add Lines.LineData datatype to areable data types.
+
+2012-05-03	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Add two new options for waterlevel label in cs.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  Parse new theme properties.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Adjust label according to chosen properties.
+
+2012-05-03	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java
+	  (parseShowLineLabelBG): parse show line label bg field of theme.
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Respect show line label bg setting of theme.
+
+	* src/main/java/de/intevation/flys/jfree/XYStyle.java:
+	  Apply setting from theme to renderer.
+
+2012-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a 'helpText' field to each state.
+	  The 'helpText' field represents in this case a message in the i18n
+	  resources which should be a link to a online help page.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Append the 'helpText' to the DESCRIBE document.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added links to help text in
+	  the FLYS wiki.
+
+2012-05-03	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Use the colors specified in theme document for linelabel.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  New methods to find colors specified for linelabels.
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Store and use background and foreground color for linelabels.
+
+	* src/main/java/de/intevation/flys/jfree/XYStyle.java:
+	  Communicate colors of linelabels to renderer.
+
+2012-05-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Actually use the font specified in theme document for linelabel.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  Fixed paths for linelabel-related fields in theme doc.
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Set font for linelabel.
+
+2012-05-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: No default background show for linelabels.
+
+2012-05-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	  * src/main/java/de/intevation/flys/artifacts/states/WaterlevelInfoState.java:
+	    Cosmetic, added annotation.
+
+2012-05-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Parse font for linelabels, do not use it yet.
+
+	* doc/conf/themes.xml: Add more theme properties related to linelabels.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java
+	  (parseTextSize, parseLineLabelSize): New and slightly refactored.
+	  (parseLineLabelFont): New.
+	  (parseTextStyle, parseLineLabelStyle): New and slightly refactored.
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java
+	  (setLineLabelFont, getLineLabelFont): New, accessors to
+	                                        linelabelfont field.
+
+	* src/main/java/de/intevation/flys/jfree/XYStyle.java
+	  (applyLineLabelFont): New.
+
+2012-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Make it compile again after signature change in backend.
+
+2012-04-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Doc.
+
+2012-04-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Added local class LineData and return it to also include
+	  Length of lines.
+	
+	* src/main/java/de/intevation/flys/artifacts/WaterLineArtifact.java:
+	  Changed signature of interfaces getWaterLines.
+	
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  (getWaterLines): adjusted signature.
+	
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Adjusted to return type of Lines.createWaterLines.
+	
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Include length in label (stub).
+
+	* src/main/java/de/intevation/flys/jfree/HasLabel.java:
+	  New interface.
+	
+	* src/main/java/de/intevation/flys/jfree/StyledXYSeries.java:
+	  Implement new HasLabel interface.
+	
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Take into account that StyledXYSeries are HasLabel.
+
+2012-04-26  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Initial transition configuration and artifact/state stubs for fixation
+	analysis.
+
+	* doc/conf/artifacts/fixanalysis.xml:
+	  New. Initial transition configuration.
+
+	* doc/conf/conf.xml:
+	  Added artifact and factory entry to config.
+
+	* src/main/java/de/intevation/flys/artifacts/FixationArtifact.java:
+	  New. Stub with initial implementation.
+
+	* src/main/java/de/intevation/flys/artifacts/states/fixation,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/QSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/ReferencePeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationVolmerCompute.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/PreprocessingSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationPeriod.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FunctionSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/GaugeRange.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/LocationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/AnalysisPeriods.java,
+	  src/main/java/de/intevation/flys/artifacts/states/fixation/FixationCompute.java:
+	  New. Stubs for fixation states.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings.
+
+2012-04-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Add method to calculate length of water lines.
+
+2012-04-26	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Most of issue454: Label waterlevels.
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java
+	  (drawSecondaryPass): Use correct label for lines.
+
+2012-04-26	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java
+	  (showSeriesMinimunX, showLineLabel): Renamed field.
+	  (isShowLineLabel, setShowLineLabel): Added getters and setters for
+	                                       showLineLabel field.
+	  (drawSecondaryPass): Respect showLineLabel field, draw static text
+	                       as placeholder for real label (stub).
+
+	* doc/conf/themes.xml: Define theme prop 'showlinelabel' for Lines.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Parse theme
+	                                                         theme prop.
+
+	* src/main/java/de/intevation/flys/jfree/XYStyle.java: Apply showline
+	                                                       theme prop.
+
+2012-04-26	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+          Cosmetics, doc.
+
+2012-04-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/Outlier.java:
+	  Gah! Checked in out dated version.
+
+	* src/main/java/de/intevation/flys/artifacts/services/FileUploadService.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java:
+	  Removed superfluous imports.
+
+2012-04-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/Outlier.java:
+	  New. Simple Grubb's outlier test. Needs testing. ;-)
+
+2012-04-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java
+	  (isMinimumX): Implemented minimum finding for x in analogy to the ones for
+	  y values. Stub to label dataset curves in plot.
+
+2012-04-25	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Cosmetics, added doc, TODO and an OPTIMIZE.
+
+2012-04-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FileUploadService.java:
+	  Make it cross platform.
+
+2012-04-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FileUploadService.java:
+	  Make it compilable.
+
+2012-04-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 657.
+
+	* src/main/java/de/intevation/flys/artifacts/services/FileUploadService.java:
+	  New. Service to upload a file as base64 string embedded in xml structure.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Extract shape files from zip archive and move them to artifact directory.
+	  Add the user shapefile as parameter for wsplgen calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java:
+	  Remove the artifact directory on step back.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java,
+	  src/main/java/de/intevation/flys/exports/MapGenerator.java:
+	  Create layer to display the uploaded shapefile.
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java:
+	  Create a facet to output the user defined layer.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added type for user shape file.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java:
+	  Resized array for '-lin' parameter.
+
+	* doc/conf/artifacts/winfo.xml:
+	  Added facet to diplay user defined shapefile.
+
+	* doc/conf/conf.xml:
+	  Added file upload service.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n string.
+
+2012-04-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 560.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENLayerFacet.java:
+	  New. Facet for WSPLGEN layers.
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java:
+	  Create a new WSPLGEN facet when creating uesk layers.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java,
+	  src/main/java/de/intevation/flys/exports/MapGenerator.java:
+	  Use WSPLGENLayerFacet to create uesk layer.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  Switch back to false as return value in isQueryable().
+
+2012-04-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 560.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java:
+	  Generate WSPLGEN-Layer with its own template and set the layertitle to
+	  an i18n string.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java:
+	  Call createUeskLayer with a CallContext.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  Set queryable to true.
+
+	* doc/conf/mapserver/wsplgen_layer.vm:
+	  New. Template for WSPLGEN-Layer.
+
+2012-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Removed superfluous imports.
+
+2012-04-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Added mapping for manual points in historical discharge chart.
+
+2012-04-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Fixed duplicated legend entry for manual points.
+
+2012-04-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 494.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Create manual points with text annotations in time charts.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Add manual points to the chart.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added historical discharge to chart types.
+
+	* doc/conf/artifacts/winfo.xml,
+	  doc/conf/artifacts/manualpoints.xml:
+	  Added manual points facet to output modes.
+
+2012-04-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/fixoverview2html.xsl: Render optional check boxes.
+	  Use different font. TODO: Move to client,
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Ignore hours and seconds.
+
+2012-04-13  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 528, 637
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWQQJRDataSource.java:
+	  Added fields for new columns.
+	
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Added columns for 'W at gauge' and fixed column header.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added filenames for new pdf export templates.
+
+2012-04-13  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/jasper/reference_gauge_end.jasper,
+	  doc/conf/jasper/reference_gauge.jasper,
+	  doc/conf/jasper/reference_gauge_start_end.jasper:
+	  Small fixes in pdf templates.
+
+2012-04-12  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 528, 637.
+
+	* doc/conf/jasper/reference_gauge.jasper,
+	  doc/conf/jasper/reference_en_gauge.jasper:
+	  Modified templates.
+
+	* doc/conf/jasper/reference_gauge_end.jasper,
+	  doc/conf/jasper/reference_gauge_start_end.jasper,
+	  doc/conf/jasper/reference_en_gauge_end.jasper,
+	  doc/conf/jasper/reference_en_gauge_start_end.jasper:
+	  New. Templates for reference curve pdf export.
+
+2012-04-12  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 528, 637.
+
+	* doc/conf/jasper/reference.jasper,
+	  doc/conf/jasper/reference_en.jasper:
+	  Fixed column header.
+
+	* doc/conf/jasper/reference_gauge.jasper,
+	  doc/conf/jasper/reference_en_gauge.jasper:
+	  New.
+
+2012-04-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/check-i18n-properties.py: Checked in unsaved version. :-/
+
+2012-04-11  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 656.
+
+	* src/main/resources/messages_en.properties:
+	  Added missing i18n strings.
+
+2012-04-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/check-i18n-properties.py: New. Script to check inconsistencies
+	of i18n properties files: Detects duplicates and keys not defined in other
+	properties files. Usage:
+
+	$ find -name messages\*.properties | \
+	  xargs contrib/check-i18n-properties.py
+
+2012-04-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Generate error message when more than one Q is found for given
+	  Q in "W auf freier Strecke".
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  Added feature to adopt problems from other reports.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n for the error message.
+
+2012-04-11  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Use factor to calculate steps in range input mode.
+
+2012-04-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Increased number of fixings columns a bit.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumnFactory.java:
+	  Corrected misspelled cache name. Added bebug output.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverviewFactory.java:
+	  Added debug output.
+
+2012-04-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsKMChartService.java:
+	  Small fixes: The service works as expected but there seems 
+	  to be a caching issue.
+
+2012-04-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsKMChartService.java:
+	  Generate chart and deliver the image as the response of the service.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumnFactory.java:
+	  Return null when looking for a fixing data column that does not exist.
+
+2012-04-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumnFactory.java:
+	  Load ws and qs from database. Cache the fixings columns.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumn.java:
+	  Removed column id and added q values.
+
+	* doc/conf/cache.xml: Added cache for the columns of the fixings.
+
+2012-04-05  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Fix for 'W free' validation and theme names.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Append the current Q values to theme name if the previous theme has
+	  the same name.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Use the start km to get min/max W values in distance mode and use the
+	  first km to get min/max W values if in location mode.
+
+2012-04-05  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 499.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Added validation for 'W free' using WstValueTable min/max values.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Added getter for WstValueTable.
+
+2012-04-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Defined a theme for WSPLGEN layers.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java:
+	  Made style creation more robust against nullpointers and added an
+	  Expression class.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Added a static
+	  method createWSPLGENStyle() and some helpers to create a WSPLGEN style.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java,
+	  src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Methods
+	  for creating the WSPLGEN layer now take a style document.
+
+2012-04-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsKMChartService.java:
+	  Extract parameters from input xml and fetch fixings filters and overview
+	  to find the columns to be viewed.
+	  TODO: Generate chart.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumn.java: Added
+	  boolean result to indicate if value comes from table or is interpolated.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsColumnFactory.java:
+	  New. Factory to fetch fixings column data from cache or database. Stub by now.
+
+2012-04-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsKMChartService.java:
+	  New. Service to generate a chart of fixings for given river and km applied
+	  some filters.
+
+	  Usage:
+	     $ curl http://localhost:8181/service/fixings-km-chart \
+	       -d @fix-overview.xml > chart.png
+
+	  The input XML is nearly the same as for the overview service.
+	  Besides the river, filters and range it accepts elements
+	  <km value="..."/>                  for passing the river km, 
+	  <extent width="..." height="..."/> for the extent of the output chart,
+	  <mime type="..."/>                 for type of result. Currently ignored.
+
+	  ATM only a static fixed sized PNG image is delivered.
+
+	* doc/conf/conf.xml: Added service to list of services.
+
+2012-04-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Made bullet proof for null filters and ranges.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsFilterBuilder.java:
+	  Fixed bug which leeds to always generating a null range filter.
+
+2012-04-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Squashed performance bug introduced in rev4070, which slowed down
+	  CSV export about 245 times!
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java:
+	  Added debug output about savings when joining neighbored nodes.
+
+2012-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java:
+	  Removed the code for filter building.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsFilterBuilder.java:
+	  New. Now contains the code for filter building. This useful because other
+	  services like the "Fixierungs-Rohdaten am KM" service need this filtering, too.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Refactored the code to obtain a filtered list of fixings columns without
+	  the XML serialization, too. The "Fixierungs-Rohdaten am KM" service
+	  does not need the overview data but the ids of the fixing columns to be
+	  displayed.
+
+2012-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/CacheInvalidationService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/FLYSService.java:
+	  Derive from XMLService now.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Removed superfluous import.
+
+2012-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue460 (W-Differenzen: Frühere Berechnungen aus den Datenkorb werden doppelt geladen)
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	  code to find out all Outputs of this Artifacts. A method getOutputs()
+	  had been implemented some time ago that does exactly this work.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Heavy
+	  bugfix! Adding the current state id to the list of previous state ids
+	  for temporary usage was no good idea! Now, the list of previous state
+	  ids only modifified in advance(). This solves the problem of having
+	  duplicated Outputs in the Artifact's DESCRIBE document.
+
+2012-03-29  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue366 (W-INFO / Abflusskurve, Diagramm: Umbenennen von Themen und Anzeige im Diagramm)
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Use description from ArtifactAndFacet objects for JFreeChart Series
+	  creation.
+
+2012-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue362 (W-Info / Abflusskurve, Diagramm)
+
+	* doc/conf/themes.xml: Adjusted text color of w and q main values for
+	  computed discharge curves.
+
+2012-03-23	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	  flys/issue639: Bezugsliniendiagramm,Achsen-Intervall-Skalierung vereinheitlichen
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Render only axis ticks in meters.
+
+2012-03-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Let hyks have a text orientation in themes.
+
+2012-03-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue491, if area fill between curve and axis, draw not only
+	to zero.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java
+	  (doArea): Add an artificial dataset to set lower bounds for area.
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java
+	  (addPoints): New convenience method to be used in future.
+	  (createGroundAtInfinity, createCeilingAtInfinity): Create artificial
+	  datasets for better areas.
+
+2012-03-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 506.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Create the date from long values instead of the year.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java:
+	  Added method to transform a String into an array of long values.
+
+	* doc/conf/artifacts/winfo.xml:
+	  changed data type from intrange to longrange.
+
+2012-03-20  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue544 (Historische ATs: Eingabeunterstützung W/Q)
+
+	* src/main/java/de/intevation/flys/artifacts/services/GaugeInfoService.java:
+	  New service that returns information for gauges based on a specified
+	  river.
+
+	* doc/conf/conf.xml: Registered the GaugeInfoService.
+
+2012-03-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/DischargeInfoService.java:
+	  Skip DischargeTables that have no time interval set or that are marked
+	  as master DischargeTable.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Skip
+	  DischargeTables that have no time interval set.
+
+2012-03-20  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue146 (In WINFO Diagramm Abflusskurve raus, wenn nicht gewünscht)
+
+	* doc/conf/artifacts/winfo.xml: Do not create outputs for discharge curves
+	  at gauge any longer.
+
+2012-03-20  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue252 (W-INFO: Wasserspiegellagenberechnung / Mitführung der Jährlichkeiten in der Diagramm-/Ergbnisausgabe)
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Added
+	  the numeric value of a selected named main value to the WST column name.
+
+2012-03-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/db_layer.vm: Set the FILTER expression into double
+	  quotes (") instead of single quotes (') which allows single quotes in
+	  expressions.
+
+	* doc/conf/meta-data.xml: Moved the floodmaps from kilometrage to its
+	  parent folder.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSFloodmapsArtifact.java:
+	  Fixed broken filter expression and set geometry type (MapServer does not
+	  know a 'MULTIPOLYGON').
+
+2012-03-16  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Remove unnecessary outputs from attributes instead of copying outs to
+	  new attribute element.
+
+2012-03-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* Tagged module as 'pre2.7-2012-03-16'.
+
+2012-03-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Removed useless imports.
+
+2012-03-16  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue523 (W-INFO / Bezugslinienverfahren / Linieneinstellungen / Min+Max anzeigen)
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Improved the look of the minimum and maximum shape of a series. They are
+	  now rectangles with a darker fill color as the line color is.
+
+2012-03-15  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue522 (W-INFO / Bezugslinienverfahren / Schriftgröße der Achsenbeschriftung)
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Label and
+	  tick label fonts for axes will now always be the same.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Removed the createYAxis() method which is already implemented in an
+	  upper class. There, the method also takes the ChartSettings into account.
+
+2012-03-15  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 547.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveExporter.java:
+	  Use date format with MEDIUM style.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java:
+	  Added date format with MEDIUM style.
+
+2012-03-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartHelper.java: Added a new
+	  method that returns the Bounds for a given XYDataset. Based on the
+	  concrete type of the XYDataset, the call is dispatched to a more specific
+	  method.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Removed the whole "range" stuff and some methods that are implemented in
+	  upper classes.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Removed the
+	  setXRange() and setYRange() methods. In our own code, we will use Bounds
+	  instead of JFreeChart's Range instances to save range/bounds information.
+	  This is necessary to save information which is not not from type double
+	  (which is the case in Timeseries charts).
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Replaced
+	  usage of Range with Bounds in the whole class.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Adapted the method signature of zoom() and zoomX().
+
+2012-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Copied the adjustAxes() method from XYChartGenerator which enables axes
+	  label font sizes.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Removed unnecessary axes creation method which is implemented in upper
+	  classes.
+
+2012-03-14  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 640.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Remove all unnecessary output settings from attributes.
+
+2012-03-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial for flys/issue358, rough stub for legenditem aggregation.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Added rough stub for legend aggregation.
+
+2012-03-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Fixed broken XML syntax.
+
+2012-03-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Modified the datacage structure of 'floodmaps'.
+
+2012-03-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSFloodmapsArtifact.java: New.
+	  This Artifacts allows loading 'floodmaps'.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added a
+	  new Facet type "floodmap.floodmaps" which are created by
+	  WMSFloodmapsArtifact.
+
+	* doc/conf/artifacts/winfo.xml: Registered the "floodmap.floodmaps" facet
+	  for "floodmap" output.
+
+	* doc/conf/conf.xml: Registered the new WMSFloodmapsArtifact.
+
+	* doc/conf/themes.xml: Added a theme for the "floodmap.floodmaps" facet.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n string for
+	  "floodmap.floodmaps" title.
+
+2012-03-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Improved datacage configuration to serve
+	  floodmaps.
+
+2012-03-05  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue566 (ÜSK: Formalien in der Parametrisierung)
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodplainChoice.java:
+	  Override getLabelFor() method to return better I18N strings for selected
+	  floodplain option.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added options
+	  for floodplain (active/inactive) and adapted a german I18N string.
+
+2012-03-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue 528 for csv.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Fixed column order, reworked todos.
+
+2012-03-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Fixed column order.
+
+2012-03-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Stub to export the "W (cm)" column conditionally on Gauge presence.
+
+2012-03-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StepCSVWriter.java:
+	  New utility to handle csv rows of more "dynamical" length.
+
+2012-03-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StepCSVWriter.java:
+	  New utility to handle csv rows of more "dynamical" length.
+
+2012-03-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue501 (manual points have two legend entries).
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Respect wish to not have a legend entry.
+
+2012-03-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue636 (cross-section: name of waterlevel facets).
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Corrected name for waterlevel facets.
+	  
+2012-03-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial fix flys/issue615 (datacage: events for fixations).
+
+	* doc/conf/meta-data.xml: Adjusted to make loading single events
+	  possible.
+
+2012-03-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial Dontcrash on flys/issue647 (NaNs from WstValueTable interpolation).
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java:
+	  Do not add data pairs where a NaN is present.
+
+2012-03-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Cosmetics, doc.
+
+2012-02-27  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Added functions to be used for fitting in 
+	the "Fixierungsanalyse" and "Extremwertermittlung".
+
+	* src/main/java/de/intevation/flys/artifacts/math/fitting/Function.java: New.
+	  Abstract base class for functions to fit. Provides the name of the function,
+	  a short description, the names of the parameters and an initial parameter guess
+	  for the fit processe. Sub classes have to overwite the function evaluation and
+	  the partial derivative of the function in respect to the parameters.
+
+	  TODO: Add a meachnism for the inverse function (needed for AT export) and
+	  the first derivative (needed for the respective diagram).
+
+	* src/main/java/de/intevation/flys/artifacts/math/fitting/FunctionFactory.java:
+	  New. Factory to fetch a function by its name.
+
+	 * src/main/java/de/intevation/flys/artifacts/math/fitting/Exp.java: New.
+	   exp: W(Q) = m * a^Q + b
+
+	 * src/main/java/de/intevation/flys/artifacts/math/fitting/Quad.java: New.
+	   quad: W(Q) = n*Q^2 + m*Q + b
+
+	 * src/main/java/de/intevation/flys/artifacts/math/fitting/Linear.java: New.
+	   linear: W(Q) = m*Q + b
+
+	 * src/main/java/de/intevation/flys/artifacts/math/fitting/LogLinear.java: New.
+	   log-linear: W(Q) = a*ln(m*Q + b)
+
+	 * src/main/java/de/intevation/flys/artifacts/math/fitting/Log.java: New
+	   log: W(Q) = m*ln(Q + b)
+
+	 * src/main/java/de/intevation/flys/artifacts/math/fitting/Pow.java: New.
+	   pow: W(Q) = a*Q^c + d
+
+	   !!! This power function is new in the pool of functions to be fit. !!!
+	   See my mail "Manuelle Punkte in der Fixierungsanalyse" 2011-10-27 for details.
+	   The function exp-new found in the old FLYS function pool is omitted 
+	   because it is worthless and was maybe never used.
+
+	* src/main/java/de/intevation/flys/artifacts/math/fitting/App.java: New.
+	  Small test driver to check if the fitting is working. The points to
+	  fit are read from stdin the function to fit is determined by the
+	  system property 'function'. Example usage:
+
+	    $ mvn -e \
+	    -Dfunction=linear \
+	    -Dexec.mainClass=de.intevation.flys.artifacts.math.fitting.App exec:java <<EOF
+	    357.390696917 7546.72096163
+	    61.4291036312 1334.54835721
+	    799.962128234 16836.7698076
+	    126.52761023 2703.69789985
+	    900.448553398 18955.0578748
+	    EOF
+
+2012-02-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages_de.properties:
+	  Added ommitted translations.
+
+2012-02-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	fix flys/issue638.
+	
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java
+	  (doArea): Ract to WKms as upper/lower area data again.
+
+2012-02-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial fix flys/issue637.
+	
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java,
+	  src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Modified translations.
+
+2012-02-21  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	 * src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	   - Calculate the extent of the river by the extents of the fixings.
+	   Reason: The river Elbe has only has fixings up to km 351. The other half
+	   of the river is empty which results in a sparse looking overview.
+	   - Add the name of the river as name attribute to resulting <river> element.
+
+	 * contrib/fixoverview2html.xsl: New. Demo XSL transformation to turn
+	   the output of the overview service directly into HTML. Used in the
+	   client a variant of this script could be used for producing the content
+	   of the assistance widget. Usage:
+
+	   $ curl http://localhost:8181/service/fixings-overview \
+	     -d @fix-overview.xml |                              \
+	     xsltproc contrib/fixoverview2html.xsl -             \
+	     > fixings-overview.html
+
+2012-02-20  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java:
+	  Made the filter mechanisms available via the incoming XML document.
+
+	  1 - range: Simply add a <range from="..." to="..."/> element next to the
+	      <river> element. Optional. Defaults to full extent.
+
+	  2 - filters: Add a <filter> element next to the <river> element.
+	      Optional. Defaults to accepting all.
+	      The <filter> element can contain the following elements:
+
+	        <column cid="..."/>: Creates an IdFilter.
+	        <date when="..."/>: Creates a DateFilter.
+	        <date-range from="..." to="..."/>: Creates a DateRangeFilter.
+	        <sector-range from="..." to="..."/>: Create a SectorRangeFilter.
+	        <not>...</not>: Creates a NotFilter. The nested element is negated.
+	        <and>...</and>: Create an AndFilter: The nested elements are 'and'ed.
+	        <or>...</or>: Create an OrFilter: The nested elements are 'or'ed.
+
+	  These filters need testing!
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Made date format a public constant.
+
+2012-02-20  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Added a filter mechanism for generating output.
+
+	  There are two kinds of filters:
+
+	  1 - A range. Defaults to [-Double.MAX_VALUE, Double.MAX_VALUE]
+	      The resulting sectors are cut to this range. Sectors outside
+	      this range are omitted.
+
+	  2 - column filters: Given a fixing column they can decide to keep
+	      or not to keep it. Following filters exist:
+
+	      - IdFilter: Keep column if column id matches.
+	      - DateFilter: Keep column if column date matches.
+	      - DateRangeFilter: Keep column if column date is in range.
+	      - SectorRangeFilter: Keep colum if one of its q sectors is in given range.
+	      - NotFilter: Negates another nested filter.
+	      - AndFilter: Keep column if all nested filters accept column.
+	      - OrFilter: Keep column if at least one of the nested filters accepts it.
+
+2012-02-20  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  Refactored to build the Q sectors once at construction time and not
+	  every time output is generated.
+	  This will ease the job of applying filters to the Overview.
+
+2012-02-19  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	  Service to generate the data needed to build the "Fixerungen pro Fluss Uebersicht"
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java:
+	  New. A service that serves an XML overview version of the fixings of a given river.
+
+	  The input documents look like this:
+
+	    <?xml version="1.0" encoding="UTF-8"?>
+	    <fixings>
+	      <river name="Saar"/>
+	    </fixings>
+
+	  The output documents looks like this:
+
+	  <?xml version="1.0" encoding="UTF-8"?>
+	  <fixings>
+	    <river from="0.0" rid="1" to="93.0"/>
+	    <events>
+	      ...
+	      <event cid="85" date="29.01.1995 00:00" name="Fixierungen/0-93_1988-2001.wst">
+	        <sector class="0" from="82.7" to="92.85"/>
+	      </event>
+	      <event cid="86" date="13.11.1996 00:00" name="Fixierungen/0-93_1988-2001.wst">
+	        <sector class="2" from="0.0" to="50.599999"/>
+	        <sector class="1" from="50.6" to="65.89"/>
+	      </event>
+	      ...
+	    </events>
+	  </fixings>
+
+	  The <river> element contains the extent of the whole river
+	  and its db id.
+	  The <events> sections contains serveral <event> elements.
+	  They have a meassure date, db id, a name and a number of <sector>s.
+	  These sectors are q classified from/to ranges. Classes are:
+
+	  0: smaller (MNQ+MQ)/2
+	  1: (MNQ+MQ)/2 to (MQ+MHQ)/2
+	  2: (MQ+MHQ)/2 to HQ5
+	  3: greater HQ5
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  New. Cacheable instance of the overview.
+	  TODO: Apply a filter mechanism when generating the output.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverviewFactory.java:
+	  New. Fetched the fixing overviews from cache if configured.
+
+	* doc/conf/conf.xml: Added the service.
+	* doc/conf/cache.xml: Configured the cache for the fixing overviews.
+
+2012-02-17  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(getMinMaxQ):
+	  Argh! Forget to check in the range method.
+
+2012-02-17  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(getMinMaxQ):
+	  Symmetrical to getMinMaxW() add two methods to find min/max Q for a given
+	  km or a range. The range method is maybe a bit slow.
+
+2012-02-17  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(getMinMaxW):
+	  Added signature to give w extend for a given km range.
+
+2012-02-17  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(getMinMaxW):
+	  Added method to find the w extent for a given km. Useful
+	  for validating "W auf freier Strecke".
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java:
+	  Removed superfluous import.
+
+2012-02-17  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/utils/FLYSUtils.java,
+	  doc/conf/artifacts/winfo.xml:
+	  Renamed state data fields: wq_mode      -> wq_isq
+	                             wq_free      -> wq_isfree
+	                             wq_selection -> wq_isrange
+
+2012-02-17  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Write the description instead of the Q value to CSV file.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  Added parameter to overriden method.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java:
+	  Get Qs without range.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Adjusted i18n string for CSV metadata.
+
+2012-02-17  Ingo Weinzierl <ingo@intevation.de>
+
+	Part II/III flys/issue497 (Diagrammeigenschaften in Zeitseriendiagrammen)
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Apply legend specific settings (show/hide).
+
+2012-02-17  Ingo Weinzierl <ingo@intevation.de>
+
+	Part I/III flys/issue497 (Diagrammeigenschaften in Zeitseriendiagrammen)
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Moved
+	  adjustPlot() from XYChartGenerator to ChartGenerator which enables the
+	  TimeseriesChartGenerator to call this method as well.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Call adjustPlot() in generateChart() to apply the settings specific to
+	  gridlines in the timeseries plot.
+
+2012-02-16  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Refactored the wq data fields.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Changed the wq data fields to boolean values.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java:
+	  Changed the wq data fields to boolean values and added method that
+	  returns the km input mode.
+
+2012-02-16  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Fixed W->Q conversion for "W fuer ungleichwertige Abflusslaengsschnitte."
+
+2012-02-16  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue509 (Dauerlinie: Q-Achse sollte bei 0 beginnen)
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Set the lower bounds of the Q axis to 0 to avoid displaying negative
+	  discharge values.
+
+2012-02-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WKms.java:
+	  (guessWaterIncreasing): Removed from interface.
+
+2012-02-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue150 (exposure of gravitational anomalies)
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WKms.java:
+	  (guessWaterIncreasing): In analogy to W->WQ->WQKms defined and
+				  implemented.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  (needInvertAxis): Changed Parameter type to WKms (was WQKms).
+	  (doW): Use in this case weaker wkms cast to find out whether axis
+		 has to be inverted.
+
+2012-02-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java: Fixed
+	  SQL statement to fetch hyk formations for rivers with 'km_up' = 0.
+
+2012-02-14  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Another partial fix for flys/issue499: Do the W to Q conversions
+	needed for "W am Pegel" correctly.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Repaired getQsForW(): The mapping from W to Q is not unique! There
+	  could be more then one Q having the the same W.
+	  Ws are not strictly monoton/sorted so doing a binary search on this
+	  is just a fail. We now scan them linearly.
+
+	  XXX: The whole class is mess. The scaling stuff is a stupid
+	  and there is no caching.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Fetch the master discharge table for converting Ws to Qs. Handle
+	  the case that there are more Qs for a given W.
+	  
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Adjusted to new semantic.
+
+2012-02-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial Fix flys/issue500: text-orientation for texts.
+
+	* doc/conf/themes.xml: Change default text orientation for text
+	  annotations.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Minor cosmetics.
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java:
+	  Parse and apply text rotation field.
+
+2012-02-13  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Partial fix for flys/issue499 (Wasserspiegellagen: Berechnung für W frei und Pegel kaputt)
+	There are still validation issue in the client.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Fixed state evaluation for "W auf freier Strecke"/"W am Pegel".
+	  There is still an issue in the client with the input validation
+	  which prevents entering the right W values for 
+	  "W auf freier Strecke". :-/
+
+2012-02-13  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Make it compilable again.
+
+2012-02-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial Fix flys/issue500: text-backgrounds for manual points.
+
+	* doc/conf/themes.xml: Added text-related style items to manual
+	  point themes.
+
+2012-02-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial Fix flys/issue500
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java:
+	  Parse whether to show text background. Apply to textannotation.
+
+2012-02-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue490
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Pass artifact and facet for doAnnotation.
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java:
+	  Use AddScaleFunction instead of LinearFunction.
+
+	* src/main/java/de/intevation/flys/artifacts/math/AddScaleFunction.java:
+	  New. f(x) = m*(x+b). I know its aequivalent to m*x + m*b but it
+	  makes more clear that the datum [m] (PNP) is substracted before the
+	  scaling to cm is done.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Bugfix: take
+	  care on empty ArtifactAndFacet objects in doAnnotations().
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java:
+	  Inherits from ReferenceCurveGenerator now which really simplifies it a lot.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Added to methods to be overwritten by sub classes.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Adjusted i8n.
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java:
+	  Indexed access to getInCm.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Simplified. Do not provide any data to blackboard. Only side effect the
+	  call context.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java:
+	  Added convinience method to i18n string with defaulting to key name.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Label the axis according the scale types of the data.
+	  TODO: Let the NormalizedReferenceCurveGenerator be a sub class of
+	  this.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: i18n for the reference curve axes.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveExporter.java:
+	  Fixed wrong double array dimension for PDF exports and write correct
+	  differences into PDF.
+
+2012-02-10	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java:
+	  Adjust function to deliver data provider ('blackboard') keys.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Change the way we exploit the blackboard-mechanism. TODOs added.
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Argh! Finally found the stupid bug in the W~W relation:
+	  Used iQ1 index on W2(Q2).
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Improved error messsages even more.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Adjusted messages.
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Forget to put new created WWAxisType onto blackboard.
+
+2012-02-10  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java:
+	  Adjust offset of linear function by same factor as the values.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java: Write
+	  correct min and max values for date axes into the info document.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Enabled zooming for timeseries charts.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/Bounds.java,
+	  src/main/java/de/intevation/flys/jfree/TimeBounds.java,
+	  src/main/java/de/intevation/flys/jfree/DoubleBounds.java: Added a method
+	  applyBounds(ValueAxis, int) that might be used to adapt the range of the
+	  axis to the bounds adding a space to the left and right.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Defined
+	  new abstract methods for setting and getting Bounds. Modified and
+	  renamed getValueAxisRange(). This method is now called
+	  getValueAxisRangeFromRequest() and returns no longer a Range object but
+	  a String array that consists of the raw string values speicified in the
+	  request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented the missing method getDomainAxisRange(). This method returns
+	  a Range object based on the String array returned from
+	  getValueAxisRangeFromRequest().
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/TimeBounds.java: Added new
+	  methods getLowerAsDate() and getUpperAsDate(). The toString() method
+	  will now return a string that contains a human readable date string.
+
+	* src/main/java/de/intevation/flys/jfree/DoubleBounds.java: Made 'lower'
+	  always be smaller than 'upper' in the default constructor.
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Give more precise message when an error occurs in W~W relation.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Improved error messages.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Modified
+	  and renamend getDomainAxisRange(). This method is now called
+	  getDomainAxisRangeFromRequest() and returns no longer a Range object but
+	  a String array that consists of the raw string values specified in the
+	  request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented the missing method getDomainAxisRange(). This method returns
+	  a Range object based on the String array returned from
+	  getDomainAxisRangeFromRequest().
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartHelper.java: Added a
+	  helper function to determine the min and max bounds (x and y) for
+	  TimeSeriesCollections.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/TimeBounds.java,
+	  src/main/java/de/intevation/flys/jfree/DoubleBounds.java: Removed
+	  useless imports.
+
+2012-02-10  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	  Add accessors to relative heights.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Add relative heights (to gauge) to csv export.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* pom.xml: Added dependency to GeoTools swing module.
+
+	* src/main/java/de/intevation/flys/artifacts/map/PrintMap.java: Standalone
+	  demo application to print WMS layers to file.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/TimeBounds.java,
+	  src/main/java/de/intevation/flys/jfree/DoubleBounds.java,
+	  src/main/java/de/intevation/flys/jfree/Bounds.java: New model classes that
+	  should be used for adapting axes ranges later.
+
+2012-02-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java:
+	  Generate a valid intial i18n name.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Pass call context to calculation.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue485 (Themen können umgenannt werden, dies wird nicht in der Legende reflektiert)
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Use ArtifactAndFacet.setFacetDescription() to set the description for
+	  themes which are stored in the Collection's XML attribute.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Some refactoring of doXXXOut() methods. We pass the ArtifactAndFacet
+	  objects of doOut() into those concrete doOut() methods which allows us to
+	  generate series names for chart curves.
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Replaced dashes by underscores in entities
+	  to ease translation.
+
+2012-02-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Fixed problem with including Y-Zeros.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Removed superfluous imports.
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Allow points in "new chart"s.
+
+	* doc/conf/artifacts/chart.xml: Added manualpoint facets to
+	  compatibility lists.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/Style.java: New. A Style object
+	  needs to implements a method applyTheme() which takes a
+	  XYLineAndShapeRenderer.
+
+	* src/main/java/de/intevation/flys/jfree/XYStyle.java: New. This subclass of
+	  Style modifies the XYLineAndShapeRenderer specified in applyTheme().
+
+	* src/main/java/de/intevation/flys/jfree/StyledSeries.java: New. This
+	  interface defines two methods getStyle() and setStyle() to adjust and
+	  retrieve the Style of a series.
+
+	* src/main/java/de/intevation/flys/jfree/StyledTimeSeries.java: New.
+	  Implementation of StyledSeries that subclasses JFreeChart's TimeSeries.
+
+	* src/main/java/de/intevation/flys/jfree/StyledXYSeries.java: Removed the
+	  code for adapting a XYLineAndShapeRenderer. Instead of implementing this
+	  stuff in StyledXYSeries itself, this instance now implements StyledSeries
+	  and stores an instance of XYStyle.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Create new instances of StyledTimeSeries now which enables styling in this
+	  chart.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Enabled
+	  styling of series for all series that implement StyledSeries.
+
+2012-02-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(relateWs):
+	  Be more conservative about arrays and indices in "Bezugslinienverfahren".
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Allow areas in new chart/longitudinal_section.
+
+	* doc/conf/artifacts/chart.xml: Added area facet to compatibility
+	  list.
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue490. Allow CollisionFree..Annotations to collide with
+	non-collision-free annotations.
+
+	* src/main/java/de/intevation/flys/jfree/CollisionFreeXYTextAnnotationEntity.java:
+	  New, to take advantage of cheap instanceof operator.
+
+	* src/main/java/de/intevation/flys/jfree/CollisionFreeXYTextAnnotation.java:
+	  Only avoid collisions with members of same family (i.e. allow
+	  collision with annotations of other types).
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added themes for historical discharge curves and
+	  the differences to a reference curve.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Some
+	  bugfixes and improvements:
+	  1) take only historical discharge tables into account for calculation
+	  2) use the correct values for computing differences
+	  3) moved timerange preparation into an own method
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/EnterMultipleLocationsState.java:
+	  Fix call to StringUtils.join .
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n string for waterlevels (from datacage).
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/EnterMultipleLocationsState.java:
+	  Locale-format multiple double values also.
+
+2012-02-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Registered a new facet for difference curves
+	  to the historical discharge output.
+
+	* src/main/java/de/intevation/flys/artifacts/model/HistoricalWQTimerange.java:
+	  New model that is able to store a further double value "diff" besides a W,
+	  Q and a Timerange.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Create
+	  new HistoricalWQTimerange instances with difference between the reference
+	  discharge table and the historical discharge tables.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: New type
+	  for historical differences.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQTimerange.java: Cast
+	  Timerange array in getTimeranges().
+
+	* src/main/java/de/intevation/flys/artifacts/model/HistoricalDischargeDifferenceFacet.java:
+	  New Facet for historical discharge difference curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java:
+	  Create new Facets for the difference curves if the processed data are from
+	  type HistoricalWQTimerange.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Added doOut() path for difference curves.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveExporter.java:
+	  Increased the size of a double array to be able to get results from
+	  HistoricalWQTimerange.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translation for warning
+	  if no reference discharge table was found for the specified gauge.
+
+2012-02-09  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Added PDF export to reference curve calculation.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Added PDF export.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java,
+	  doc/conf/artifacts/winfo.xml:
+	  Add facet for PDF export to state.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWQQJRDataSource.java:
+	  New. Datasource for JasperReports containing reference curve data.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings for JasperReports template file and calculation mode.
+
+	* doc/conf/jasper/reference.jasper,
+	  doc/conf/jasper/reference_en.jasper:
+	  New. Templates for reference curve PDF export.
+
+2012-02-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java(getReferenceEndKms):
+	  Prevent duplicates in end kms for reference curve calculation.
+
+2012-02-09  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  doc/conf/artifacts/winfo.xml: Renamed data item names to allow
+	  translations.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Give (Normalized)ReferenceCurveFacets are more appropriate
+	  description (to be seen in client).
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Prepare Reference curves to eat multiple locations as 'Ziel'.
+
+	* src/main/java/de/intevation/flys/artifacts/states/EnterLocationState.java:
+	  Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/states/EnterMultipleLocationsState.java:
+	  New. State to provoke multi_location_panel.
+
+	* doc/conf/artifacts/winfo.xml: Made end state of ref curves a (new)
+	  EnterMultipleLocationState.
+
+2012-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue483 (Historische Abflusstafeln ohne Ergebnis)
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Add
+	  a problem for the calculation report if no discharge tables were found
+	  for the given timerange.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translation for error
+	  messages created by Calculation6.
+
+2012-02-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java:
+	  Format kms in error reports at least with one digit after
+	  the decimal separator.
+
+2012-02-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Fix flys/issue468 (Fehler beim Projekt-Duplizieren)
+
+	* src/main/java/de/intevation/flys/exports/ReportGenerator.java:
+	  Always create a new XML document when calling report generation.
+	  XXX: There is a bug somewhere which calls error reporting twice
+	  when cloning a collection. Speculation: It has something to do with
+	  the settings stuff.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Remove superfluous imports.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENCalculation.java:
+	  Added missing Override annotation.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Handle Manual Point Facets, added TODO.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java:
+	  Subclass BlackboardDataFacet to repair areas over cross sections.
+
+2012-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Let text
+	  and line annotations placed at x or y axes always be in foreground.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue474 (manual points in reference curves).
+
+	* doc/conf/artifacts/winfo.xml,
+	  doc/conf/artifacts/manualpoints.xml:
+	  Add manualpoints to lists just so that really everybody knows.
+
+	* doc/conf/themes.xml: Add styles for manual points in ref curves.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Add ref curve chart types.
+
+	* src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Handle manual point facets.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial Fix flys/issue474.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translation for
+	  normalized_reference_curve.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue475.
+
+	* doc/conf/themes.xml: Register theme for reference_curve_normalized.
+
+2012-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Add a "/" to the
+	  URL of the user wms if the URL configured in conf.xml doesn't end with
+	  a "/".
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue472.
+
+	* doc/conf/themes.xml: Added theme for discharge_curve manualpoints.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added discharge_curve as chart type.
+
+2012-02-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Fix flys/issue484 ((i18n: Ungleichwertige... Report))
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Fixed key spelling.
+	
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue478 (Manuelle Punkte: Stil nicht änderbar).
+	
+	* doc/conf/themes: Map correct theme to manualpoint themes.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial fix flys/issue472 .
+	
+	* doc/conf/artifacts/winfo.xml: Add manual points to compatibility
+	  list of cross_sections.
+
+	* doc/conf/artifacts/manualpoints.xml: Add manualpoints to list of
+	  generated facets.
+
+2012-02-08  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Commented styling option for show area until
+	  area calculation works.
+
+2012-02-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  i18n the km of the error report.
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  Fixed potential race conditions.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: Added
+	  formatter for error report kms.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  Removed superfluous imports.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Commented out some imports. They are used be out commented code.
+
+2012-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/LocationProvider.java:
+	  Use the correct km key for an unspecific km for fetching the whole river
+	  annotations.
+
+2012-02-07	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Added missing config of chart info generator
+	  of normalized reference curve.
+
+2012-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Fixed wrong chart info generator name for historical
+	  discharge curves.
+
+2012-02-07  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Added PDF export for historical discharge curve calculations.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveExporter.java:
+	  Implemented PDF export for calculation results.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQTJRDataSource.java:
+	  New. Datasource for JasperReports.
+
+	* doc/conf/artifacts/winfo.xml:
+	  Added facet for PDF export to historical discharge curve output mode.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings for historical discharge curve export.
+
+	* doc/conf/jasper/historical-discharge.jasper,
+	  doc/conf/jasper/historical-discharge_en.jasper:
+	  New. Templates for JasperReports PDF creation.
+
+2012-02-07  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Handle WWQQs, extended CSV export of reference curves.
+
+2012-02-07  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Recactored, moved Formatter accessors to base class AbstractExporter.
+
+2012-02-07  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Let reference curve calculation result in specific WWQQ, extended CSV
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java
+	  (getWW,getWWQQ): Renamed and changed result type.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Result in WWQQs, not in WWs.
+
+2012-02-07  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Log error as warning.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearFunction.java:
+	  New. Linear function f(x) = m*x + b.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java:
+	  Do the normalizing. Cascading functions is not required.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveInfoGenerator.java:
+	  Added missing chart info generator for the normalized reference curves.
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties: Fix i18n.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java:
+	  Added optional boolean flag to indicated if the data should be 'normalized'.
+	  TODO: Build the cascading normalising function call chain.
+
+	* src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Fetch WWAxisTypes from call context to do the right scaling.
+
+2012-02-06  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Added service for discharge infos at a specific gauge.
+
+	* doc/conf/conf.xml: Added service factory entry.
+
+	* src/main/java/de/intevation/flys/artifacts/services/DischargeInfoService.java:
+	  New. Returns description, start year and end year of discharges at a
+	  specific gauge.
+
+	* src/main/java/de/intevation/flys/artifacts/states/GaugeTimerangeState.java:
+	  Return a more specific ui provider string.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Registered generator for normalized reference curves.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added "reference_curve_normalized".
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Added facets for normalized reference curves.
+
+	* src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java:
+	  Generator for normalized reference curves.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added config for "reduzierte Bezugslinie".
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added error report.
+
+2012-02-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java: Set
+	  proper bounds for date axis elements; tell the transformation matrix which
+	  type it is (number/date).
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Include location description string in csv output of referencecurve-
+	  exporter.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java: Added constructor call.
+	* src/main/java/de/intevation/flys/artifacts/model/WWQQ.java: New.
+	  Stores the Qs for the Ws, too.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java: Create
+	  WWQQs instead of WWs.
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Added further i18n stubs and slightly more data.
+
+2012-02-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(relateWs):
+	  Return the Qs of the Ws, too.
+	  
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java:
+	  Adjusted to cope with the return Qs. TODO: Create WQWQ or WWQQ dataset
+	  for storing the result.
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Added further i18n stubs.
+	  Also added a second Y axis (experimental, to ease axis labeling etc.).
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Register new export output and csv
+	  facet for reference curves.
+
+	* doc/conf/conf.xml: Register Exporter for reference curve.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java:
+	  Primitive exporter, bland copy of another one.
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Enabled some i18n of labels.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Basic i18n for reference
+	  curves added.
+
+2012-02-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Registered the HistoricalDischargeCurveInfoGenerator.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveInfoGenerator.java:
+	  New. This info generator returns information of a
+	  HistoricalDischargeCurveGenerator.
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java: Add an
+	  attribute "axistype" to axes nodex based on the JFreeChart Axis type.
+	  Number axes get a "number", date axes a "date".
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Defined an
+	  abstract method getRangesForAxis().
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java: Awaits
+	  now an instance of ChartGenerator instead of XYChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Implemented the getRangesForAxis() method (copied from XYChartGenerator).
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added
+	  @Override annotation to getRangesForAxis().
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesExporter.java:
+	  Cosmetics.
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Add CSV facet (not yet evaluated).
+
+2012-02-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveExporter.java:
+	  New. This OutGenerator exports historical discharge curves to CSV and PDF.
+	  Currently, only the CSV export is implemented!
+
+	* doc/conf/artifacts/winfo.xml: Registered a new Output CSV for historical
+	  discharge curves.
+
+	* doc/conf/conf.xml: Registered the HistoricalDischargeCurveExporter.
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java:
+	  Generate new Facets for CSV and PDF  output.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: Added new functions
+	  that return number formatters for historical discharge curves and a date
+	  formatter for short dates.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added strings for CSV header of
+	  historical discharge curve export.
+
+2012-02-06  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Adjusted to name change in River ('hibernate fix').
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Adapted the data type of W/Q input values
+	  for historical discharge curve calculation (intarray -> doublearray).
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeState.java:
+	  Adapted the data type of W/Q input values.
+
+2012-02-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First steps to serve the data in cm if we are are gauges only.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Fetch map gauge station -> gauge datum from backend for reference curve.
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearFilterFunction.java:
+	  New. Linear transforms another function.
+
+	* src/main/java/de/intevation/flys/artifacts/math/FilterFunction.java:
+	  New. Allows cascading functions.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java:
+	  New. Classifier what kind of axis types are needed.
+
+	* src/main/java/de/intevation/flys/artifacts/model/NormalizedReferenceCurveFacet.java:
+	  Removed. Is now obsolete.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	  Stores gauge station kms for start and end km if they are any.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Should generate a WWAxisTypes classifier via side effect to the 
+	  call context.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java:
+	  Figure out for each start/end km if its a gauge station and store this
+	  information in the result WWs.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Uses the WWAxisTypes classifier now to generate the right data.
+	  TODO: Use the blackboard object.
+
+2012-02-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/EnterLocationState.java:
+	  Removed superfluous imports.
+
+2012-02-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Set number of interpolation steps for relation curve to 200.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Do not sort the XYSeries.
+
+2012-02-03  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveInputState.java:
+	  Removed. Was stub.
+
+2012-02-03  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Improved Reference Curves.
+
+	* doc/conf/artifacts/winfo.xml: Add start/end input step for reference
+	  curves.
+
+	* doc/conf/themes.xml: Add theme for reference curve.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Disable start/end point-hack for reference curve. Go with 'live'
+	  values.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  i18n.
+
+	* src/main/java/de/intevation/flys/artifacts/states/EnterLocationState.java:
+	  New. State to enter location.
+
+	* src/main/java/de/intevation/flys/artifacts/states/InputDoubleState.java:
+	  New.
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Take WW name to display in themepanel.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translations.
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Use
+	  the current date for discharge tables which doesn't have a stop time set.
+	  A warning message is written to log.
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java:
+	  Generate report facet before leaving the computeXXX() method when no
+	  calculation results has been computed.
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a function
+	  that returns the name of a reference gauge (for historical discharge
+	  curves).
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Create title, subtitle and axes labels for charts of this type.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added title, subtitle and axes
+	  labels for historical discharge curves.
+
+2012-02-03  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Fix.
+
+2012-02-03  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translations for
+	  states (will be shown in client).
+
+2012-02-03  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Ja!vadoc.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Declare getYAxisWalker as abstract to avoid weird issue with
+	  overriden methods of this name.
+
+2012-02-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java:
+	  Use the WW.ApplyFunctionIterator to fetch the data.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java:
+	  Removed superfluous imports.
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Registered a ReportFacet for historical
+	  discharge curve report.
+
+	* doc/conf/conf.xml: Registered a ReportGenerator for historical discharge
+	  curves.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Add
+	  problems if we were not able to find a Q for a given W.
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java:
+	  Generate a ReportFacet if there were problems during calculcation.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added error messages for the
+	  case that we were not able to find a Q for a given W while calculating
+	  historical discharge curve data.
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Improved adding new data for historical discharge curves: skip NaN
+	  values; use JFreeChart's Day TimePeriod instead of Second.
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java: Refactoring:
+	  moved addDatasets(), applyThemes() and some other methods into
+	  ChartGenerator; enhanced the AxisDataset interface.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Implemented necessary abstract methods and improved internal AxisDataset
+	  class (added new methods).
+
+2012-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartHelper.java: Added a
+	  function that expands a given range. Take care on NaN in getRanges().
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Prevent of NullPointerExceptions in isMinimumShapeVisible() and
+	  isMaximumShapeVisible().
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Create new AxisDatasets and new TimeSeriesCollections for each Facet.
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQTimerange.java: Added a
+	  method getTimerange(idx) that returns the Timerange at a given index.
+
+	* src/main/java/de/intevation/flys/artifacts/model/HistoricalDischargeFacet.java:
+	  Return the correct WQTimerange object in getData().
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java: More
+	  refactoring: the AxisDataset is defined by an interface in ChartGenerator
+	  now. Each subclass of ChartGenerator should implement its own AxisDataset.
+	  This allows us to provide multiple XYDataset types in different charts.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Implemented some necessary methods (abstract definitions of parent class)
+	  and create new empty timeseries charts.
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartHelper.java: New helper
+	  class for working with charts.
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Removed useless import.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java: More
+	  refactoring: moved more base code from XYChartGenerator to its parent
+	  class ChartGenerator.
+
+2012-02-02  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  Refactored. Use supers static field.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Refactored. Use static field.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java,
+	  src/main/java/de/intevation/flys/utils/FLYSUtils.java
+	  (getMinMax, getRiverMinMax): Refactored. Moved to FLYSUtils.
+	
+	* src/main/java/de/intevation/flys/artifacts/states/MinMaxState.java:
+	  Add so-called javadoc.
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java: Moved
+	  further base code from XYChartGenerator into its ChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Override generateChart() instead of generate() which is now implemented
+	  in ChartGenerator.
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  New. This ChartGenerator should be used for timeseries charts. Currently,
+	  this class is a stub only! WORK IS IN PROGRESS!
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  Subclasses TimeseriesChartGenerator now instead of XYChartGenerator,
+	  because historical discharge curve charts will have a time x axis set.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java: Moved some
+	  basic stuff from XYChartGenerator into ChartGenerator.
+
+2012-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/TimeseriesStepChart.java:
+	  Added standalone demo app for evaluating different timeseries charts.
+
+2012-02-02  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 466: CSV export for chart themes.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Export a CSV file if the requested format is 'csv'.
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  Generate the CSV file containing X-Y-data of all activated themes.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Moved code to load values of a discharge table into an own static
+	  function to be able to reuse it in Calculation6.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java:
+	  Implemented the findValueForW().
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fake some reference curve input values to have working prototype
+	to work on.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Syntax-stunt fake reference inputs.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java
+	  (getW1): Accessor for first w at index (for the raw ww).
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java
+	  (doReferenceOut): Plot the raw W-over-W.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Create time
+	  millis for timerange used in Calculation6.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: Added
+	  the first step of the calculation: fetch relevant discharge tables based
+	  on the given input parameters. The calculated values are not the right
+	  ones! Finding Ws and Qs in discharge tables is still not implemented! WORK
+	  IN PROGRESS!
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java:
+	  Create facets for each WQTimerange object calculated by Calculation6.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Timerange.java: Added a
+	  new constructor that takes Date objects for start and end time.
+
+2012-02-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/W.java:
+	  Forget to create ws data in constructor.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Replaced Reference Curve input state
+	  with LocationSelect-State for debugging purposes. Add output to
+	  reference.curve- state.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml: Register ReferenceCurve*Generators.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  Cosmetics.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Added pro forma facet. 
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  Added comment.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added reference curve facet type.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Fetch
+	  necessary parameters for 'historical discharge curve' calculation and call
+	  Calculation6 with those parameters.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java:
+	  Modified default constructor and calculate() signatures and added a
+	  parameter check which is evaluated before the calculation starts.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReferenceCurveInfoGenerator.java:
+	  Added outline of a new chart-generator.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Timerange.java: New.
+	  This class might be used to store timeranges. Start and end time are
+	  stores as long (milliseconds since january 1, 1970).
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQTimerange.java:
+	  New. This class should be used to save Ws, Qs and timeranges.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added helper
+	  methods to extract int[] and double[] from string.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Registered new facet type
+	  'historical_discharge.historicalq' to the 'historical_discharge' output.
+
+	* doc/conf/conf.xml: Registered new HistoricalDischargeCurveGenerator for
+	  output type 'historical_discharge'.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  new facet type for HISTORICAL_DISCHARGE_Q.
+
+	* src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java:
+	  New. This ChartGenerator is used to generate new charts for historical
+	  discharge curves. WORK IN PROGRESS!
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation6.java: New.
+	  This class should 'compute' the historical discharge curves. WORK IN
+	  PROGRESS!
+
+	* src/main/java/de/intevation/flys/artifacts/model/HistoricalDischargeFacet.java:
+	  New. This Facet is used during the calculation of historical discharge
+	  curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java,
+	  src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added stubs
+	  to trigger the calculation of data used for generating historical
+	  discharge curve charts.
+
+2012-02-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Removed needless imports.
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages_de.properties,
+	  src/main/resources/messages_de_DE.properties:
+	  Fixed "W bei ..m" -> "W fuer ...n" .
+
+2012-02-01  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Avoid NPE, add warning to log to not forget about it.
+
+2012-01-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  Fixed problems with unformatted numbers in error message.
+
+2012-01-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeState.java:
+	  Override getLabelFor() to return the I18N string for the selected
+	  evaluation mode.
+
+2012-01-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Modified types of parameters in the
+	  'historical discharge curves' transitions.
+
+2012-01-30  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Fix transition type (Stupid Bug 1.0 reverse).
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceGaugeState.java:
+	  Override getLabelFor() method defined in DefaultState to return the name
+	  of a Gauge based on the official number which is stored in the
+	  parameterization.
+
+2012-01-30  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Allow manual points in more diagram types.
+
+	* doc/conf/artifacts/winfo.xml: Add respective facet types to
+	  compatibility lists of output modes.
+
+	* doc/conf/artifacts/manualpoints.xml: Mention facet types.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added ChartType enum to ease later iteration.
+	  (IS.MANUALPOINTS): Added helper.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManualPointsFacet.java:
+	  Help artifact figuring out which data to provide by passing facet
+	  name.
+
+	* src/main/java/de/intevation/flys/artifacts/ManualPointsArtifact.java:
+	  Respond to getPointData with correct data item, depending on facet
+	  name.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ManualPointsSingleState.java:
+	  Iterate over chart types. Add facets if corresponding data is found.
+	
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Handle manual point facets.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Clone
+	  StateData model objects to save parameters in this Artifact - create
+	  new StateData instances only if no StateData model was found for a
+	  parameter. This solves the problem of loosing the type of a StateData.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Implemented a getLabelFor() method that is used to format a parameter
+	  value.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	  Adapted method signature of transform().
+
+	* src/main/java/de/intevation/flys/artifacts/states/MinMaxState.java:
+	  Override getLabelFor() method to create a well formatted label for min/max
+	  values.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StateFactory.java:
+	  Bugfix: use correct order of parameters in DefaultStateData constructor.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Fixed a stupid bug. Use
+	  ValueCompareTransition instead of DefaultTransition to evaluate values.
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/MinMaxState.java:
+	  Write default values for min and max items into the DESCRIBE document.
+
+2012-01-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	  Added field 'startKm' and 'endKm' to make it easier to associate
+	  gauge to km to return Ws in cm if km is at a gauge.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java:
+	  Construct the WWs with startKm and endKm. This is necessary because
+	  if an error occurs in calcuting an in between km in a list of end kms
+	  the index does not correspond to the input value any more. 
+
+2012-01-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added transitions for "historical discharge
+	  curves" calculation.
+
+2012-01-30  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  (doPoints): Skip 'inactive' points.
+
+2012-01-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	  Removed the createNormalized() method because it did not
+	  solve all problems with the transformation of the W~W data we have.
+
+	  - We need to shift the values to be based in (0, 0).
+	  - We need to linear transform the values if they are at gauges.
+	  - Copying the data is memory expensive.
+
+	  Therefore there is now a new inner class ApplyFunctionIterator
+	  that is able to transform the values in an iterator like manner.
+	  The transform functions default to the identity.
+
+	* src/main/java/de/intevation/flys/artifacts/math/Sub.java: New.
+	  Simple function that subtract a scalar from the parameter.
+
+	* src/main/java/de/intevation/flys/artifacts/model/W.java: Added
+	  method minWs() to calulate the minimal W of the data.
+
+	* src/main/java/de/intevation/flys/artifacts/model/NormalizedReferenceCurveFacet.java:
+	  getData() returns a WW.ApplyFunctionIterator with identity transform.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  getData() returns a WW.ApplyFunctionIterator with a base shift to (0, 0).
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	  Added method createNormalized() to shift the data to start in (0, 0).
+
+	* src/main/java/de/intevation/flys/artifacts/model/NormalizedReferenceCurveFacet.java:
+	  Returns the 'normalized' data of the reference curve.
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/NormalizedReferenceCurveFacet.java:
+	  New. Facet for the normalized W~W relation.
+	  TODO: Implement the on-the-fly creation logic.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java:
+	  New. Facet for the W~W relation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java: Cosmetics.
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java:
+	  Added stub loop for creating facets.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Cosmetics.
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Call the 'Bezugslinienverfahren'.
+	
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  I18N for the 'Bezugslinienverfahren' error messages.
+
+2012-01-27  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Better theme for manual points.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Theme text part of manual points. Offset it a bit.
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation5.java:
+	  Fixed stupid bug.
+
+2012-01-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceGaugeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/GaugeTimerangeState.java:
+	  New. States used in the 'historical discharge curve' calculation.
+
+2012-01-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/MinMaxState.java: New
+	  state that eases adding min/max value pairs to describe document.
+
+	* src/main/java/de/intevation/flys/artifacts/states/IntRangeState.java:
+	  New. Subclasses MinMaxState and allows adding min/max integer value pairs.
+
+2012-01-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java: Moved
+	  code to add items to a data node for dynamich UI into an own method.
+
+2012-01-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKmsFacet.java: Removed
+	  useless import.
+
+2012-01-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added strings which are used n
+	  the calculation "historical discharge curve".
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	 * src/main/java/de/intevation/flys/artifacts/model/Calculation5.java:
+	   New. Calcutation to be created from WINFOArtifact
+	   for the 'Bezugslinienverfahren'.
+	
+	 * src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	   Added TODO about optimization of the 'Bezugslinienverfahren'.
+	   Added number of default samples of the W~W relation.
+
+	 * src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	   Added convinience constructor to store the calculation results.
+
+	 * ChangeLog: Removed false TODO.
+
+2012-01-27  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesExporter.java:
+	  Read jasper template file from resources to support i18n reports.
+
+	* doc/conf/jasper/wdifferences.jasper,
+	  doc/conf/jasper/duration.jasper,
+	  doc/conf/jasper/computed-discharge.jasper,
+	  doc/conf/jasper/waterlevel.jasper:
+	  Updated report template. Resized left margin.
+
+	* doc/conf/jasper/computed-discharge_en.jasper,
+	  doc/conf/jasper/duration_en.jasper,
+	  doc/conf/jasper/wdifferences_en.jasper,
+	  doc/conf/jasper/waterlevel_en.jasper:
+	  New. Templates for english reports.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings for template files and modes.
+
+2012-01-27  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Prevent second Legend Entry for points (prior was one for the
+		  TextAnnotations and one for the points).
+
+2012-01-27  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Blindly render Text of manual points if it does not collide with
+	other text.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Handle TextAnnotations, create some for manual points.
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java:
+	  Added setter for TextAnnotations, documentation.
+
+2012-01-27  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+
+2012-01-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Refactored class hierachy to integrate model for W~W:
+
+	NamedObjImpl
+	\-- W
+	    +-- WW
+	    \-- WQ
+	        +-- WQDay
+	        \-- WQKms
+	            \-- WQCKms
+
+	* src/main/java/de/intevation/flys/artifacts/model/W.java:
+	  New. Base class
+
+	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
+	  New. Model for W~W
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQDay.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQ.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQCKms.java:
+	  Adjusted to follow the new class hierarchy.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Removed superfluous imports.
+
+2012-01-27  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties,
+	  src/main/java/de/intevation/flys/artifacts/states/ManualPointsSingleState.java:
+	  i18n for ManualPointsFacets.
+
+2012-01-27  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 138: PDF output for calculations.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesExporter.java:
+	  Create the PDF data source and generate the PDF report output.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Add the PDF facet to state. Add export facets only once.
+
+	* doc/conf/artifacts/winfo.xml:
+	  Added PDF facet to state.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsJRDataSource.java:
+	  Added new field type 'differences'.
+
+	* doc/conf/jasper/wdifferences.jasper:
+	  New. Template for w-differences PDF export.
+
+2012-01-27  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java:
+	  New method that creates and returns the chosen differences of w-difference
+	  calculation as string.
+
+2012-01-27  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 138: PDF output for calculations.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  Create the PDF data source and generate the PDF report output.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  doc/conf/artifacts/winfo.xml:
+	  Added PDF facet to state.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsJRDataSource.java:
+	  Added new field type 'day'.
+
+	* doc/conf/jasper/duration.jasper:
+	  New. Template for duration curve PDF report.
+
+2012-01-26  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 138: PDF output for calculations.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Create the PDF datasource and generate the PDF report output.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  doc/conf/artifacts/winfo.xml:
+	  Added PDF facet to state.
+
+	* doc/conf/jasper/computed-discharge.jasper:
+	  New. Template for computed discharge PDF report.
+
+2012-01-26  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 138: PDF output for calculations.
+
+	* pom.xml: New Dependency to net.sf.jasperreports/jasperreports and
+	  org.codehaus.groovy/groovy-all.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Added methods for PDF generation.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Create the PDF data source and generate the PDF report output.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsJRDataSource.java:
+	  New. Data source for jasper reports containing the waterlevel calculation
+	  result.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Added facet for PDF export to state.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added PDF facet type.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesExporter.java:
+	  Added stub for inherited abstract method.
+
+	* doc/conf/artifacts/winfo.xml:
+	  Added PDF facet to outputmode 'export'.
+
+	* doc/conf/jasper,
+	  doc/conf/jasper/waterlevel.jasper:
+	  New. Folder for jasperreports templates and first template for
+	  waterlevel report.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n string for calculation mode.
+
+2012-01-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Renderer 'manual' points in LongitudinalSectionGenerator.
+
+	* pom.xml: New dependency to org.json/json.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  (doOut): Handle manual points.
+	  (doPoints): Add point- series.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added MANUALPOINTS Facet Type.
+
+	* doc/conf/themes.xml: Added Default Theme for Manual Points.
+
+	* doc/conf/artifacts/winfo.xml: Added manual point facet to
+	  compatibility list of longitudinal section diagram.
+	
+2012-01-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManualPointsFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/ManualPointsArtifact.java:
+	  Removed obsolete imports.
+
+2012-01-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add initial infrastructure for user-provided data ('manual points').
+
+	* doc/conf/artifacts/manualpoints.xml: Trivial state model for new
+					       Artifact.
+
+	* doc/conf/conf.xml: Registered manualpoint artifact and factory.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManualPointsFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ManualPointsSingleState.java,
+	  src/main/java/de/intevation/flys/artifacts/ManualPointsArtifact.java:
+	  New. Basic infrastructure for ManualPoints.
+
+2012-01-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Removed superfluous condition checks.
+
+2012-01-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Cosmetics, documentation.
+
+2012-01-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Add states/transitions stubs for 
+	"Bezugsllinenverfahren."
+
+	* src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveInputState.java:
+	  Stubs for the two new states.
+
+2012-01-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/visualize-transitions.xsl: Fixed output of the conditions 
+	  of the transitions. Now you can see the circumstances
+	  when a branch is taken.
+
+2012-01-25  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 461.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Override buildChartSection() to disable the chart subtitle entry in
+	  ChartSettings.
+
+2012-01-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	I18N for WINFO calculations messages.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  Allow optional arguments in error messages to support meaningful
+	  formatting containing e.g. numbers.
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  Added a convenience getMsg() method.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation2.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation3.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Adjusted the code to use the I18N symbols now.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added the I18N strings.
+
+2012-01-23	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Handle the case in "W auf freier Strecke" where more than
+	  one Q is related to a W. TODO: Generate user report.
+
+2012-01-23	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Added code for the simple case of "Bezugslinienverfahren" ("W auf freier Strecke").
+	  TODO: Handle case if the model returns more than one Q for a given W.
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Micro optimizations.
+
+2012-01-23  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue452 (Annotations at second y-axis).
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new type (duration_curve.mainvalues.q).
+
+	* doc/conf/themes.xml: Added theme mapping for new facet type.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Create another Facet.
+
+	* doc/conf/artifacts/winfo.xml: Added new facet type to compatibility
+	  list.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Handle new Facet.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Added field to be able to remember which axis to stick to.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java:
+	  Tell Annotations to stick to Q axis in the special duration_curve
+	  environment.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (AxisDataset): Added plotAxisIndex to now have a
+			 two-way-association.
+	  Tell own axisDatasets to which 'jfreechart'-axis they are assigned.
+	  Evaluate which axis the StickyAxisAnnotations should be sticked to,
+	  and calculate text and line positions accordingly.
+	  Removed junk.
+
+2012-01-23  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix compilation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java:
+	  Use StickyAxisAnnotations instead of XYTextAnnotations.
+
+2012-01-23  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Some unification of Annotation handling.
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java:
+	  Add a third annotation type (now have text, hyk and sticky).
+
+	* src/main/java/de/intevation/flys/jfree/CollisionFreeXYTextAnnotation.java:
+	  Use double instead of float values, removed commented code.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Reduced to keep information about placements etc only. Not a
+	  Annotation (in JFreeChart sense) anymore.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Use StickyAxisAnnotations instead of XYTextAnnotations.
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java:
+	  Added new LineStyle class.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Call addAnnotationsToRenderer later, where positioning information
+	  is already known.
+	  (addAnnotationsToRenderer): Deal with the three Annotation types.
+				      Instead of StickyAnnotations, add
+				      CollisionFreeXYText- and
+				      XYLineAnnotations.
+	  (Area): Helper class.
+
+2012-01-23  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Some unification of Annotation handling.
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+
+2012-01-23  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue395 (SVG/PDF-Export: Längsschitt enthält keine
+		Streckenfavoriten.)
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  Fix pdf/svg rendering of annotatios, by adding a
+	  ChartRenderingInfo-object to the chart.draw call.
+
+2012-01-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Added new key and getter for a short subtitle. The short subtitle is
+	  used if no range exists.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n string for shor subtitle.
+
+2012-01-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/ChartArtifact.java:
+	  Removed state validation. Validation prevents drawing the diffenrences
+	  diagramm and all states except of WDifferencesState always return
+	  true.
+
+2012-01-20  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/CollisionFreeXYTextAnnotation.java:
+	  New. Candidate to replace StickyAxisAnnotation eventually.
+
+2012-01-20  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java:
+	  Added null-guards to functions parameters.
+
+2012-01-20  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java:
+	  Respect flowing direction of river when finding valid
+	  hykformations.
+
+2012-01-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Add an empty facet to this state if the artifact is a Chartartifact.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Do not add the range to subtitle if no range exists.
+
+2012-01-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	'New chart' for cross sections.
+
+	* doc/conf/artifacts/chart.xml: Updated the states and transitions for
+	  new charts.
+
+	* doc/conf/meta-data.xml: Added cross sections to datacage.
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  Changed cast from WINFOArtifact to FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/ChartArtifact.java:
+	  Changed artifact data strings for chart_type.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Changed i18n string for cross sections and logitudinal sections.
+
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add basic (text) styling for HYKs.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Minor cleanup, pass HYK theme on.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Apply TextStyle to TextAnnotation part for HYK zones.
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java:
+	  (TextStyle): New class to bundle text-styling info.
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java:
+	  Refactored constructors to ease setting Style in the newly
+	  created usage scenario.
+
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticHYKState.java:
+	  Cleanup. Mark spot where State-based caching could happen.
+	  
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFacet.java:
+	  Cosmetics.
+
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/cache.xml: Added Cache for HYKS.
+
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java:
+	  Made HYKFactory.Zone Serializable.
+
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/HYKArtifact.java:
+	  Cosmetics.
+
+2012-01-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added style definition for hyks.
+
+	* doc/conf/themes.xml: Added hyk style (text).
+
+2012-01-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Removed cache "service-distanceinfo".
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java:
+	  Now uses the annotations from the LocationProvider and the
+	  XML documents are no longer cached.
+
+2012-01-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Removed "annotations" cache.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Removed annotation caching.
+
+	* src/main/java/de/intevation/flys/artifacts/model/LocationProvider.java:
+	  Changed internal data structure to FastAnnotations which are also cached.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java:
+	  Use FastAnnotations now which are fetched via the LocationProvider.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Added
+	  time measurement for writing the CVS output.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticHYKState.java:
+	  Removed superfluous import.
+
+2012-01-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Respect visibility flag of HYK facet.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Log malcondition, make colored hyk-box a bit smaller.
+
+2012-01-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DataFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java:
+	  Cosmetics, docs, improved logging.
+
+2012-01-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Recommend hyks for current river if hit by
+	  a crosssection-out.
+
+2012-01-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Somewhat improved HYK handling and rendering.
+
+	* src/main/java/de/intevation/flys/artifacts/HYKArtifact.java:
+	  Store ids in more sensibly named data item and expose it.
+	  Spawn all facets in inactive state.
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java:
+	  Make HYK name accessible, do not query hyks by river, but by hyk-id,
+	  made query more real-world.
+	
+	* src/main/java/de/intevation/flys/artifacts/states/StaticHYKState.java:
+	  Use hyks name as facet name, pass hyk-id when asking for hyks.
+
+2012-01-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Somewhat improved HYK handling and rendering.
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java:
+	  Added ability to store 'zones', next to text annotations.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (getLowestYValue, getUppestYValue, colorForHYKZone),
+	  (addBoxAnnotations): New. Create box annotations from zones.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  (doHYK): New, handle hyk facets by adding zones to annotation
+		   container.
+
+2012-01-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Preparing improved HYK handling.
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java:
+	  (Zone.getTo): New accesor.
+	  TODOs and documentation added.
+
+2012-01-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	'New Chart' for w-differences curve.
+
+	* doc/conf/artifacts/chart.xml: Added empty facet to output mode.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Add an empty facet if the artifact is a ChartArtifact.
+
+2012-01-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	'New Chart' for discharge longitudinal section curve.
+
+	* doc/conf/artifacts/chart.xml: Added transition for location input.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  Changed cast from WINFOArtifact to FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java:
+	  Add an empty facet if the artifact is a ChartArtifact.
+
+2012-01-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	'New Chart' for duration curve.
+
+	* doc/conf/artifacts/chart.xml: Added state and transition for location input.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Changed cast from WINFOArtifact to FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java:
+	  Add an empty facet if the artifact is a ChartArtifact.
+
+2012-01-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added accidentally ommitted HYKFacet, fixing build.
+
+	* src/main/java/de/intevation/flys/artifacts/model/HYKFacet.java:
+	  New, stubby facet for hyk infrastructure.
+
+2012-01-18  Raimund Renkert <raimund.renkert@intevation.de>
+
+	'New Chart' for computed discharge curve.
+
+	* doc/conf/artifacts/chart.xml: Fixed transitions and added state for km input.
+
+	* src/main/java/de/intevation/flys/artifacts/model/EmptyFacet.java:
+	  New. Added an empty facet to avoid deleting an empty output.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java:
+	  Changed some return values if the artifact is a ChartArtifact.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Changed casts from WINFOArtifact to FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java:
+	  Add an empty facet if the artifact is a ChartArtifact.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Set a default axis range if the range is null.
+
+2012-01-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added stub hyk infrastructure.
+
+	* doc/conf/artifacts/hyk.xml: Added trivial transition configuration
+	  for hyk artifacts.
+
+	* doc/conf/conf.xml: Register HYK artifact and its transition conf.
+
+	* src/main/java/de/intevation/flys/artifacts/HYKArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/states/StaticHYKState.java:
+	  Added static 'hyk' artifact, facet and state.
+
+2012-01-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Add 'hyk' facet type.
+
+2012-01-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add hyk facet to compatibility list of winfo.xml .
+
+	* doc/conf/artifacts/winfo.xml: Add hyk facets to compatibility list.
+
+2012-01-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method getDataAsLong that returns a Long representation of a data item or
+	  null if no such data item is existing or it cannot be parsed as Long.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a function
+	  that returns the selected reference gauge (specified by 'reference_gauge'
+	  parameter) of a FLYSArtifact.
+
+2012-01-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/LocationProvider.java:
+	  Cache a TreeMap<km, annotation string> of the annotation values of whole 
+	  rivers. This is _much_ more efficient than firing an HQL/SQL statement for 
+	  each km and caching these results.
+
+2012-01-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionLine.java:
+	  Deleted. It is in the backend now.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionChunk.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionLineFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WaterLineArtifact.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Adjusted to use the fast cross section line from backend now.
+
+	* doc/conf/cache.xml: Store cross section lines cache on disk.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticWQKmsState.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DataFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java: Removed
+	  superfluous imports.
+
+2012-01-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix assignment of stateId/hash in WaterlevelFacet.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java:
+	  More verbose logging, fix hash/stateid assignment.
+
+	* src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java:
+	  (deepCopy): implemented.
+
+2012-01-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	"Ported" StaticWQKmsArtifact to use compute-type caching mechanism.
+
+	* doc/conf/artifacts/staticwqkms.xml: Declare trivial info.
+
+	* doc/conf/conf.xml: Register staticwqkms artifact and factory.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKmsFacet.java:
+	  Sublacss DataFacet.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticWQKmsState.java:
+	  New. State.
+	
+	* src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java:
+	  Refactored. Most computation and stuff now done in State and Facet.
+
+2012-01-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Minor cosmetics.
+
+2012-01-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Refactoring.
+
+	* src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java:
+	  Added new constructor.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java:
+	  Refactored, subclass BlackboardDataFacet.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java:
+	  Refactored, subclass DataFacet.
+
+	* src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java:
+	  Adjusted.
+
+2012-01-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java:
+	  Cosmetics, added new constructor.
+
+2012-01-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	  Use improved caching for cross section data. 
+	  !!! This commit needs heavy testing !!!
+
+	* src/main/java/de/intevation/flys/artifacts/services/CrossSectionKMService.java:
+	  Refactored to provide the map to determine the nearest cross section line
+	  neighbors for a given km to the public. This is a bit hackish because
+	  it hhould have its own factory which is then used by this service and 
+	  other parts of the code.
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  searchCrossSectionLine() now returns a FastCrossSectionLine. To find this
+	  line efficiently it uses the map of the CrossSectionKMService which
+	  already provides a nearest neighbor searching mechanism for cross
+	  section lines. The FastCrossSectionLine is fetched with the
+	  FastCrossSectionLineFactory which uses a cache as described in the
+	  previous commit.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java
+	  src/main/java/de/intevation/flys/artifacts/WaterLineArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Uses
+	  the FastCrossSectionLine instead of CrossSectionLines now.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionLine.java:
+	  Added method fetchCrossSectionProfile() to access the point data in
+	  the profile generating compatible way.
+
+	* src/main/java/de/intevation/flys/artifacts/ChartArtifact.java: Removed
+	  superfluous import.
+
+2012-01-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Added cache for fast section lines chunks.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionChunk.java:
+	  New. Chunks of FastCrossSectionLines. Stores cross section lines for ranges
+	  of one KM.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionLineFactory.java:
+	  New. Access fast cross section lines. It uses the cache 'fast-cross-section-lines'
+	  configured in cache.xml to store chunks of cross section lines.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionLine.java:
+	  New. Stores the important data (points) of a cross section line.
+
+2012-01-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Added a new calculation option for historical discharge curves.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translations for
+	  historical discharge curves.
+
+2012-01-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added certain facets to to
+	  compatibility matrices.
+
+2012-01-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/annotation.xml: Fixed accidental change.
+
+2012-01-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/annotation.xml: Cosmetics.
+
+2012-01-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Enabled old floodmap calculations for output
+	  type 'map'.
+
+2012-01-13  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Initial implementation for module 'new chart'.
+
+	* src/main/java/de/intevation/flys/artifacts/ChartArtifact.java:
+	  New. Artifact for module 'new chart'.
+
+	* doc/conf/artifacts/chart.xml:
+	  New. Artifact description for the new artifact.
+
+	* doc/conf/conf.xml:
+	  Added new artifact to config.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings for 'new chart' chart types.
+
+2011-01-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Cosmetics.
+
+2011-01-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Avoid NPE when copying data from one artifact to another.
+
+2011-01-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (expandRange): Prevent collapse due to negative values.
+
+2012-01-12  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/artifacts/map.xml,
+	  doc/conf/conf.xml,
+	  src/main/java/de/intevation/flys/artifacts/MapArtifact.java:
+	  Changed the MapArtifact name to 'new_map'.
+
+2011-01-12	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Added code to calculate the area of the generated polygons.
+	  TODO: Render the generated area sum to the plot.
+
+2011-01-12	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	 * src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	   Replaced System.err.println()s with proper logging.
+
+	  * src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	    src/main/java/de/intevation/flys/artifacts/states/WaterlevelInfoState.java,
+	    src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	    superfluous imports.
+
+2011-01-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	flys/issue450 (i18n: Datenkorb: longitudinal-section)
+	
+	* doc/conf/meta-data.xml: Replaced dash by underscore in
+	  longitudinal-section.
+
+2011-01-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Towards areas on other than the first axes.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java:
+	  Changed signature on which to provide data via the blackboard.
+	  Include facets name to allow unique identification.
+	  
+	* src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java:
+	  Store name of one of the facets involved in area creation.
+	  (AreaFacet.Data): New class to hold result data.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  New methods to find out whether we have a general Q or W type of
+	  facet.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  (doArea): Use new Result-Data object instead of Object[], resolve
+		    which axis to put area to.
+	  (axisIdxForFacet): New.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Subclass LongitudinalSectionGenerator.
+
+2011-01-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelInfoState.java:
+	  Do not intefere with CrossSections.
+
+2011-01-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics, cleanups.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  (getCrossSections, getCrossSectionNames): Removed.
+
+2011-01-11	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	flys/issue439 (Streckenfavoriten lassen sich nicht über den Datenkorb
+		hinzuladen)
+
+	* doc/conf/meta-data.xml: Made Annotations available via datacage
+	  in longitudinal section, cosmetics.
+
+2012-01-11  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/conf.xml:
+	  Added output generator for output type 'map'.
+
+	* doc/conf/artifacts/map.xml:
+	  Changed output name to 'map'.
+
+	* doc/conf/meta-data.xml:
+	  Added new output type 'map'.
+
+2011-01-11	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue446 (Dauerline/Abflusskurve am Pegel: Eigenschaften vom
+	Hauptwertthema nicht aufrufbar)
+
+	* doc/conf/themes.xml: Add default styles for mainvalues.q/w and other
+	  themes.
+
+2012-01-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java:
+	  Removed XPath injection security hole. A serious one because it allowed
+	  inspecting the conf.xml file ... with all the db passwords.
+
+2012-01-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Removed superfluous imports.
+
+2012-01-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Added the definition of old calculation results
+	  for floodmaps.
+
+2012-01-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java: Added
+	  methods getLayers() and removeLayer().
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method getFacets() which returns a list of Facets supported by this
+	  Artifact. In addition, the FLYSArtifact is now more verbose while
+	  filtering Facets for Outputs.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Implemented initialize() which now copies the shapefile directory of the
+	  model Artifact and modifies its Facets (adapts the shapepath which is the
+	  uuid of the Artifact).
+
+2012-01-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java,
+	  src/main/java/de/intevation/flys/collections/OutputParser.java: Made
+	  them more verbose for better debugging.
+
+2012-01-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Initialize() now also calls State.initialize() for each State.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Override initialize() which is defined in AbstractState. NOTE: Currently,
+	  this method has no code.
+
+2012-01-07	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/CacheInvalidationService.java:
+	  New. The service to invalidate the caches. (Argh! Forgot to 'svn add')
+
+2012-01-06  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue298 (Karte: Automatischer Zoom auf Berechnungsergebnisse)
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java: Added a
+	  debug statement that prints out the WSPLGEN extent.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: Create new
+	  Envelope instances for initial and max extent if they are not existing.
+	  Some debug statements have now been removed.
+
+2011-01-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add cache for 'static' wqkms (e.g. BaseData) to default cache conf.
+	
+	* doc/conf/cache.xml: Add Cache Config for static wqkms.
+
+	* src/main/java/de/intevation/flys/artifacts/model/StaticWQKmsCacheKey.java:
+	  Adjust typo in cache name, minor cosmetic.
+
+2011-01-06  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MapArtifact.java:
+	  The map artifact has two states now and overrides describe() to
+	  generate UI sections in the describe response.
+	  The map state overrides computeAdvance() instead of computeFeed().
+
+	* src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java:
+	  Changed the WMSDBArtifact in WMSDBState to FLYSArtifact and adjusted
+	  constructors in state classes.
+
+	* doc/conf/artifacts/map.xml:
+	  Added initial state and transition.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added i18n strings for new state.
+
+2011-01-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial fix for flys/issue437 (Querprofil: Zugeladene Basisdaten
+	ignorieren W/Q-heit, falsche Namen).
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  Set name also for first column (zero based).
+
+2012-01-06  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue196 (i18n/l10n: Zahlenformate einheitlich)
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java: New method
+	  getRawValue() that extracts the raw double value from 'name' parameter.
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: New method
+	  getRawFormatter() that returns a NumberFormat instance for the current
+	  locale.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Localized the W/Q labels in CSV exports.
+
+2011-01-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	flys/issue442 (i18n: Datenkorb: flood-protections)
+
+	* doc/conf/meta-data.xml: Replaced dash in flood-protection by
+	  underscore to allow translation.
+
+2011-01-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	flys/issue438 (i18n: Datenkorb: additionals)
+
+	* doc/conf/meta-data.xml: Fix typo.
+
+2011-01-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/CacheInvalidationService.java:
+	  New. Uses the CacheFactory to invalidate caches by their names.
+	  This can be used by external ETL processes like the AFT/DIPS importer
+	  to prevent cache inconsistencies.
+
+	  Input documents look like this:
+
+	    <caches>
+	      <cache name="my-cache"/>
+	      ...
+	      <cache name="your-cache"/>
+	    </caches>
+
+	  Output documents look like this:
+
+	    <caches>
+	      <cache name="my-cache">All elements removed.</cache>
+	      ...
+	      <cache name="your-cache">Error: Cache not found.</cache>
+	    </caches>
+
+	 * doc/conf/conf.xml: Added the cache invalidation service.
+
+	* src/main/java/de/intevation/flys/artifacts/services/FLYSService.java:
+	  Added Override annotation.
+
+2011-01-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	flys/issue432 (Diagrammeigenschaften/Flächenrenderer:
+	Legenden-schriftgröße für Flächenthemen wird ignoriert)
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Respect legend item size when generating legend items for
+	  area dataseries.
+
+2011-12-09  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Implemented new module "new map".
+
+	* src/main/java/de/intevation/flys/artifacts/MapArtifact.java:
+	  New. Artifact for the new module, extends RiverAxisArtifact and
+	  contains the MapState class.
+
+	* src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java:
+	  Added empty constructor to embedded state classes to allow instantiation
+	  of MapState that extends the RiverAxisState.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java:
+	  Do not generate an initialExtent element if the initial extent is
+	  empty.
+
+	* doc/conf/artifacts/map.xml:
+	  New. Describes the state for the new module.
+
+	* doc/conf/conf.xml:
+	  Added new config file.
+
+2012-01-05  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue444 (error_no_export_found bei "W bei ungleichwertigem Abfluss")
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: If no boolean
+	  value "wq_free" exists in getWQMode(), false is the default.
+
+2011-01-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	flys/issue443 (Querprofile: Hibernate LazyInitializationException) 
+
+	* doc/conf/cache.xml: Disable cache for cross-sections.
+
+2012-01-05  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue447 (W-Differenzen: Fehler beim Hinzufügen einer Parameterisierung)
+
+	* src/main/java/de/intevation/flys/collections/CollectionAttribute.java:
+	  Added a new method cleanEmptyOutputs() which removes Outputs that have
+	  no Facets set.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: Call
+	  CollectionAttribute.cleanEmptyOutputs() at the end of write() to remove
+	  empty Outputs that have been added during the merge process.
+
+2012-01-05  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue440 (Karte: WSPLGEN Berechnungen für Mosel schlagen fehl)
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Modified the
+	  getRiverBoundary() method because the signature of
+	  RiverAxis.getRiverAxis() has changed. This method will now take each
+	  geometry into account, that is retrieved by
+	  RiverAxis.getRiverBoundary().
+
+	* src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java:
+	  Use GeometryUtils.getRiverBoundary() to determine the boundary of a
+	  river.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Create the riveraxis shapefile with all geometries returned by
+	  RiverAxis.getRiverAxis().
+
+2012-01-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Repaired
+	  broken items in legend. The new renderer requires the current plot. This
+	  is set now.
+
+2012-01-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added a new field 'pointsize' to the virtual theme
+	  'HiddenColorLines'.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Added methods to
+	  parse the new field of 'HiddenColorLines'.
+
+	* src/main/java/de/intevation/flys/jfree/StyledXYSeries.java: Apply the
+	  pointsize defined in themes.
+
+2012-01-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Use correct keys for buffering in getMinimum() and getMaximum().
+
+2012-01-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java,
+	  src/main/java/de/intevation/flys/exports/StyledAreaSeriesCollection.java,
+	  src/main/java/de/intevation/flys/exports/EnhancedLineAndShapeRenderer.java:
+	  Moved to de.intevation.flys.jfree.
+
+	* src/main/java/de/intevation/flys/jfree/StyledXYSeries.java,
+	  src/main/java/de/intevation/flys/jfree/StyledAreaSeriesCollection.java,
+	  src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java:
+	  Moved from de.intevation.flys.exports.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Added necessary imports.
+
+2012-01-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/EnhancedLineAndShapeRenderer.java:
+	  Buffered the computation results of getMinimum() and getMaximum().
+
+2012-01-04  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue393 (Themenstileditor: Minimum anzeigen / Beschriftung anzeigen)
+
+	* src/main/java/de/intevation/flys/exports/EnhancedLineAndShapeRenderer.java:
+	  New renderer that overrides JFreeChart's XYLineAndShapeRenderer. This
+	  renderer brings the option to explicitly display the minimum and/or
+	  maximum or a series as shape. Currently, there are no options to adjust
+	  the style of those shapes.
+
+	* doc/conf/themes.xml: Added a new virtual theme 'MinMaxPoints'. All line
+	  theme inherit from that theme now.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Added methods to
+	  parse the fields 'showminimum' and 'showmaximum' of 'MinMaxPoints' theme.
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java: Added
+	  methods to apply the fields of the new Theme 'MinMaxPoints'.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Modified
+	  the getRenderer() method which now always returns a new instance of
+	  EnhancedLineAndShapeRenderer.
+
+2011-01-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Allow styling of outline of areas.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Allow styling of outline of areas.
+	
+	* src/main/java/de/intevation/flys/exports/StyledAreaSeriesCollection.java:
+	  Parse outline style for areas, apply it to renderer.
+
+2012-01-03  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue104 (W-INFO: Wasserspiegellagenberechnung / Strecke)
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: Call
+	  the new flys-backend method Wst.determineMinMaxQFree() to determine the
+	  min/max Qs at a given kilometer.
+
+2011-01-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Reflect name chnage of longitudinal sections
+	  area artifacts and include ColorLine style for area styles.
+
+2012-01-02  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue370 (WINFO: Berechnungsausgabe W/Pegel [cm] fehlt bei Wasserspiegellage und W am Pegel)
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: New method that
+	  extracts the double value of a WQ object's name.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Adapted
+	  the header of CSV exports and the content of the "W at gauge" column.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  Adapted method signatures that have been changed in WaterlevelExporter.
+
+2012-01-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added strings used in the CSV
+	  export.
+
+2012-01-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Check
+	  if the location of a CSV row is in range of the reference gauge. Write
+	  "outside reference gauge" into CSV in such cases.
+
+2012-01-02  Ingo Weinzierl <ingo@intevation.de>
+
+	PART II of flys/issue125 (W-INFO: Wasserspiegellagenberechnung / tabellarische Berechnungsausgabe)
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Add the
+	  named main value of a Q and the name of the gauge used for the calculation
+	  if the WQ mode is "W at gauge" or "Q at gauge".
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  Adapted the method signatures that have been modified in
+	  WaterlevelExporter.
+
+2012-01-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledAreaSeriesCollection.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AxisSection.java,
+	  src/main/java/de/intevation/flys/exports/ExportSection.java,
+	  src/main/java/de/intevation/flys/exports/LegendSection.java,
+	  src/main/java/de/intevation/flys/exports/ChartSection.java: Removed
+	  unused imports.
+
+2012-01-02  Ingo Weinzierl <ingo@intevation.de>
+
+	PART I of flys/issue125 (W-INFO: Wasserspiegellagenberechnung / tabellarische Berechnungsausgabe)
+
+	* doc/conf/cache.xml: Registered a new Cache for the LocationProvider.
+
+	* src/main/java/de/intevation/flys/artifacts/model/LocationProvider.java:
+	  New. This class is able to return the description of a location based on a
+	  river and kilometer parameter. The LocationProvider stores single
+	  locations into a Cache if one is configured for this class.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Added a method that returns a single Annotation for a specific kilometer and
+	  river.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a method
+	  getLocationDescription() that might be used to determine the description
+	  of a specified kilometer for a given river.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Make use
+	  of FLYSUtils.getLocationDescription() to add a new column that contains
+	  the location description.
+
+2011-12-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: Write
+	  min/max values for free Qs into Artifact's DESCRIBE document.
+
+2011-12-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: Fixed
+	  broken order to determine the step width of Qs and Ws.
+
+2011-12-28  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue104 (W-INFO: Wasserspiegellagenberechnung / Strecke)
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Validate user defined free Q values.
+
+2011-12-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Compute better step width based on a maximal number of steps = 30.
+	  Results with digits are rounded up. E.g.:
+	    Q range = 9.6 - 1750
+	    Step width = 58.01
+	    Rounded result = 60
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/AxisSection.java,
+	  src/main/java/de/intevation/flys/exports/LegendSection.java,
+	  src/main/java/de/intevation/flys/exports/ChartSection.java: Subclasses
+	  TypeSection to be able to use convinience methods for string, integer,
+	  double and boolean values.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: The
+	  getSize() method now returns null if no width and height is specified in
+	  the request document or if width/height <= 0. It no longer returns the
+	  result of getDefaultSize().
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Set the
+	  size of a chart export to the size specified in the ChartSettings if
+	  there are no valid values in the request document.
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java: Set the
+	  chart size to ChartGenerator.getDefaultSize() if no valid values are
+	  returned by ChartGenerator.getSize(). This has been done autoamtically
+	  before.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/TypeSection.java: New. This
+	  Section defines some convinience methods to add/set string, integer,
+	  double and boolean values.
+
+	* src/main/java/de/intevation/flys/exports/ExportSection.java: New.
+	  Subclasses TypeSection. The ExportSection currently offers attributes
+	  'width' and 'height'.
+
+	* src/main/java/de/intevation/flys/exports/ChartSettings.java: Added
+	  getter/setter methods to support an ExportSection.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Create an
+	  ExportSection while initial ChartSettings creation.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added and
+	make use of a new method createLegendLabelFont() to create unified Fonts for
+	LegendItems. This method considers the user defined size for LegendItems.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented adjustAxes(). This method now sets the label Font of the X
+	  axis. Its size is determined by getXAxisLabelFontSize().
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/AxisSection.java: Added method
+	  getFontSize() to retrieve the font size for an axis.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Modified createYAxis(int): call super.createYAxis(int) and adjust
+	  necessary settings - no Axis creation takes place here.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added
+	  getXAxisLabelFontSize() and getYAxisLabelFontSize(int) to retrieve the
+	  user defined font size for an axis. The getYAxisLabelFontSize() is used in
+	  createYAxis(int) to set the font size for axes labels.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Override getYAxisWalker().
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Create new
+	  instances of IdentifiableNumberAxis in createYAxis(int) default
+	  implementation.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/AxisSection.java: Added
+	  getLabel() to retrieve the axis label.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Override getDefaultXAxisLabel() and getDefaultYAxisLabel() defined in
+	  XYChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Implement
+	  getXAxisLabel() and getYAxisLabel(int). Both methods search for an axis
+	  label defined in the ChartSettings first. If no label is specified or if
+	  no ChartSettings is set, getDefaultXAxisLabel() or
+	  getDefaultYAxisLabel(int) is called to retrieve the initial/default axis
+	  label.
+
+2011-12-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartSettings.java: Modified the
+	  signature of addAxisSection(). This method now accepts AxisSections only.
+	  In addition, there is a new method getAxisSection(String) that returns an
+	  AxisSection specified by its identifier.
+
+	* src/main/java/de/intevation/flys/exports/AxisSection.java: Added new
+	  methods getIdentifier(), isFixed(), getUpperRange() and getLowerRange() to
+	  retrieve the attributes supported by this Section.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Make use
+	  of axes ranges specified in ChartSettings if an axis is fixed.
+
+2011-12-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/IdentifiableNumberAxis.java: New.
+	  Subclasses JFreeChart's NumberAxis and offers a getId() method which
+	  returns an identifiable key.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Implements
+	  a createNumberAxis() method that should be used by all subclasses to
+	  create new axes. This method returns an instance of IdentifiableNumberAxis
+	  which is required for zooming.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Create new NumberAxis instances by using XYChartGenerator.createNumberAxis().
+
+2011-12-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented the methods getChartTitle() and getChartSubtitle(). Both
+	  methods try to get the required information from ChartSettings. If no
+	  ChartSettings is set for this OutGenerator, these methods will call
+	  getDefaultChartTitle() and getDefaultChartSubtitle().
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Renamed getChartTitle() and getChartSubtitle() to
+	  getDefaultChartTitle() and getDefaultChartSubtitle(). In addition, the
+	  methods addSubtitles() became more robust - these OutGenerators add
+	  subtitles only if the subtitle is not empty.
+
+2011-12-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: Added a
+	  setSettings(Settings) method.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Call OutGenerator.setSettings() before calling doOut() for each Facet.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Implemented
+	  setSettings() and added convinience methods to access chart specific
+	  settings.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/exports/ATExporter.java,
+	  src/main/java/de/intevation/flys/exports/ReportGenerator.java: Implemented
+	  setSettings().
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Make use
+	  of the attributes specified in the Settings: the title, subtitle,
+	  displayGrid and displayLegend settings are functional now.
+
+2011-12-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartSettings.java,
+	  src/main/java/de/intevation/flys/exports/LegendSection.java,
+	  src/main/java/de/intevation/flys/exports/ChartSection.java: Use more
+	  concrete classes than Settings and Section in these classes to avoid a lot
+	  of castings.
+
+2011-12-22  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue242 (W-INFO: Fehlende Header in Datenexporten)
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added functions
+	  getQs(), getWs(), getGauge(), getGaugename() and getRivername() that all
+	  take a parameter FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Moved the
+	  implementation of getGauge() to FLYSUtils. The getGauge() in this class
+	  just calls and returns FLYSUtils.getGauge().
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Write a
+	  header into a CSV export containing meta information about this export.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added strings used in the CSV
+	  export as header.
+
+2011-12-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  (getKm): Removed, not called anymore.
+
+2011-12-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Include km of cross-section-master in diagrams subtitle (fetched
+	  from 'blackboard'.
+
+2011-12-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java:
+	  Cosmetics, docs.
+
+2011-12-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java
+	  (getInitialFacetActivity): Only newest CrossSection is initially
+				     active.
+
+2011-12-22	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java
+	  (isNewest): New, query whether a CrossSection is the newest for its
+		      river, doc.
+
+2011-12-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  convinience method to retrieve the value of an data object stored at
+	  FLYSArtifact as Boolean value.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a method that
+	  returns the named value of a given double value. This method returns only
+	  the named value, if the WQ mode is "Q at gauge" and if the value fits to a
+	  named value. In addition to this method, there is a new method to retrieve
+	  the selected WQ mode as 'WQ_MODE' enum.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Removed prepareData() and its call. The data preparation had a bad side
+	  effect: the modifications are "persisted" into cache, which has again bad
+	  side effects.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Removed
+	  prepareData(). The label creation for columns in the WST export will now
+	  take place in addWSTColumn(). With help of the master Artifact (I forgot
+	  this Artifact in my last commit) we are able to replace Q values with
+	  their named main value.
+
+2011-12-21	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  Cover 'locations' case for initial km of cross section artifacts.
+
+2011-12-21	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  (initialize): Eat ld_from from master artifact.
+	  (setup): Set cross_section.km to either masters km or the lowest
+		   defined cross-section line, whatever is bigger.
+
+2011-12-21  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue252 (W-INFO: Wasserspiegellagenberechnung / Mitführung der Jährlichkeiten in der Diagramm-/Ergbnisausgabe)
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a function
+	  stripNamedMainValue(). The result of this function is a named main value's
+	  base name without declaration of a year.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: Added a
+	  method pepareData() that is called in doOut() before the data supported by
+	  the current Facet is added using addData().
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Override
+	  the prepareData() method to reset the name of WQKms objects. The Qs in a
+	  waterlevel export should be the Q value or the named main value if the
+	  value fits to a named main value.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Survive cases where the first dataset has an area-renderer assigned.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/AreaArtifact.java:
+	  Store additional parameter (whether or not to fill everything in
+	  between two curves.)
+
+	* src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java:
+	  Deliver additional info from artifact.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Evaluate new parameter.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Add transparency setting to area theme style.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Helper to
+	  access transparency setting in theme.
+	
+	* src/main/java/de/intevation/flys/exports/StyledAreaSeriesCollection.java:
+	  Respect transparency setting.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFacet.java:
+	  Subclass BlackboardDataFacet to provide data for area calculation.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Cast data to WKms instead of WQKms.
+
+2011-12-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Use a ';' as seperator between Qs and Ws.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java
+	  (doOut, doArea): Added handling for areafacets. Code yet mostly
+			   copied from CrossSectionGenerator.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  Refactoring, subclass BlackboardDataFacet, remove duplicate code.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* doc/conf/artifacts/winfo.xml,
+	  src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Renamed facet for consistency reasons.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Make Waterlevelfacet deliver data via blackbord.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java:
+	  Subclass BlackboardDataFacet.
+
+2011-12-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Create all formatted string labels for Ws and Qs in this class, because
+	  this class is the only instance that knows that there are double values
+	  to format.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Introduced new Facet that will deliver whatever getData returns via
+	blackbord under key which is defined by convention. Attention, the
+	data is not cached if handled this way.
+
+	* src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java:
+	  New facet, will be useful for easing implementation of facets that
+	  can contribute to area-computations.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/VisibleAttribute.java:
+	  Removed obsolete imports.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Removed obsolete imports.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/AreaArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/AreaCreationState.java,
+	  src/main/java/de/intevation/flys/exports/StyledAreaSeriesCollection.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Removed obsolete imports.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	With StableXYDifferenceRenderer, create legend items in rectangular
+	form, to discern "line" from "area" in legend.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java
+	  (legendLine, legendShape): Renamed.
+	  (getLegendItem): Create LegendItem with fill, use PositivePaint for
+			   that.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added further 'area' infrastructure.
+
+	* src/main/java/de/intevation/flys/exports/StyledAreaSeriesCollection.java:
+	  New, "area dataset".
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java
+	  (doOut): Use helper to decide if facet is an 'area' facet.
+	  (doArea): Construct StyledAreaSeriesCollection instead of two
+		    dataseries.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (AxisDataset.isArea): Distinguish area datasets with instanceof.
+	  (AxisDataset.addArea): New. Replaces addAreaDataset.
+	  (addAreaSeries): Simplified with new custom SeriesCollection.
+	  (applyTheme): Register and style StableXYDifferenceRenderer for
+			StyledAreaSeriesCollections.
+	  Added various TODOs and debug output to stabilize development.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added facets to compatibility
+	  matrices.
+
+	* doc/conf/themes.xml: Added Area theme defaults.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Lay ground for having areas in longitudinal section diagrams, too.
+	This is done by different naming of the facets.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java:
+	  Make the name dynamic.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added further facet types, helper.
+
+	* src/main/java/de/intevation/flys/artifacts/AreaArtifact.java:
+	  Store name for facets in data item, restrict access to some fields.
+
+	* src/main/java/de/intevation/flys/artifacts/states/AreaCreationState.java:
+	  Use AreaArtifacts data item to use name for facets.
+
+2011-12-20	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java,
+	  src/main/java/de/intevation/flys/exports/StyledXYSeries.java:
+	  Doc.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Whitespace.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Doc and
+	  whitespace.
+
+2011-12-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java
+	  (parseBoolean): New, extracted, updated callers.
+	  (parseFillColorField, parseShowBorder): New, for area styles.
+
+2011-12-19  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue202 (W-INFo: Wasserspiegellagenberechnung / Vorbelegung Strecke)
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a system property which is used to determine, if the DESCRIBE
+	  document of an Artifact should include default values (values, that have
+	  been inserted by the user some time ago) or not. The default case is,
+	  that the DESCRIBE does NOT include default values. To enable default
+	  values, set "flys.use.default.values" to "true".
+
+2011-12-19  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue419 (Themen-Name "Q(null)" bei W bei ungl. A.)
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java:
+	  Use correct variable to create Facet names.
+
+2011-12-19  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue380 (W-INFO / Überschwemmungskarte, falsches DGM)
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  convinience method that returns a parameter of FLYSArtifact as Integer.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java: Override
+	  validate() to determine, if the DGM selected by the user is valid for the
+	  current calculation range and river.
+
+2011-12-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Roll-back accidentally committed changes.
+
+2011-12-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java
+	  (doOut): Hide 'invisible' (deleted) themes from Outgenerators.
+
+2011-12-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added area.name data item and access to areaartifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java:
+	  Rephrased debug output, do survive case where only one curve is
+	  given for area calculation (this is the "above" or "under" case).
+
+	* src/main/java/de/intevation/flys/artifacts/AreaArtifact.java
+	  (getAreaName): Access "area.name" data item.
+
+	* src/main/java/de/intevation/flys/artifacts/states/AreaCreationState.java:
+	  Respect area.name data of artifact when reproducing facets.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml: Fix, accidentally added wrong factory in last
+	                     commit.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml: Register area artifact factory.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added area artifacts to cross-section
+	  compatibility matrix.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add area dataseries when facet delivering one.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java
+	  (doOut): Handle area facets.
+	  (doArea): Register areas for area facets.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add simple area registerig functions.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (AxisDataset.addAreaDataset): New, add an area dataset.
+	  (AxisDataset.isArea): New.
+	  (addAreaSeries): New. Add Area Dataset.
+	  (applyThemes): Pass info if we have an area, to set different
+			 renderer.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticState.java:
+	  Added convenience function and easy sybclassing.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Register AREA ("area") facet type.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  Register blackboard key uuid+index and respond with data to it, as
+	  assumed by the areaartifact and facet.
+
+2011-12-16	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added partial area-infrastructure.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/AreaArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/AreaCreationState.java:
+	  New artifact, facet and state for area rendering.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Defined an
+	  interface YAxisWalker that allows to walk over each Y axis definition in
+	  subclasses. This walker can be retrieved using the new getYAxisWalker()
+	  method. The AxisSections are built in this class now.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Implemented the YAxisWalker interface and the getYAxisWalker() method.
+	  Removed the code to build AxisSections.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Implemented getYAxisLabel(int pos) and getYAxisWalker().
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Implemented the getYAxisWalker() method.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Some optimizations during out() operation - the CollectionAttribute is
+	  parsed a single time now (*i guess*). This code really needs some
+	  refactoring!
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added an INFO message that displays the duration time for the out()
+	  operation.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Calls super.buildAxisSections().
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented the method buildAxisSections(). The result list will contain
+	  an AxisSection for the X axis.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: Bugfix:
+	  Add new Outputs to the current CollectionAttribute if no old one is
+	  existing.
+
+2011-12-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  Directly fetch key/value pairs when writing a collection attribute.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartSettings.java: Added new
+	  functions that allow parsing a ChartSettings object from DOM Node.
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java: Parse
+	  the Settings of each Output.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/CollectionAttribute.java:
+	  Added a method to set a new Settings object for a specific Output and a
+	  method to clear the list of Facets of a specific Output.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: The
+	  AttributeWriter no longer creates new CollectionAttributes - it only
+	  modifies the old CollectionAttribute. At first, it clears the Facets of
+	  all Outputs. Finally, the merged Facets are added to the Outputs.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added the CollectionAttribute to the AttributeWriters constructor.
+
+2011-12-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DoubleAttribute.java: New. An
+	  Attribute that stores double values.
+
+	* src/main/java/de/intevation/flys/exports/AxisSection.java: Added methods
+	  to set values for 'fixation', 'font-size', 'lower' and 'upper'.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Set new attributes mentioned above for each axis' AxisSection.
+
+2011-12-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/AxisSection.java: Added methods
+	  to set the axis label and id.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Improved
+	  the ChartSettings that will now contain a set of AxisSections. The new
+	  buildAxisSections() method in this class is not implemented and needs to
+	  be implemented by subclasses.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Override buildAxisSections() of XYChartGenerator to create an AxisSection 
+	  for each axis that is able to be displayed in this sort of chart. In
+	  addition, there is a new method getYAxisLabel(int) that returns the label
+	  for a specific Y axis.
+
+2011-12-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/IntegerAttribute.java: New.
+	  Concrete subclass of a DefaultAttribute for storing integer values.
+
+	* src/main/java/de/intevation/flys/exports/LegendSection.java: New. A
+	  concrete Section subclass to store legend specific attributes.
+
+	* src/main/java/de/intevation/flys/exports/BooleanAttribute.java,
+	  src/main/java/de/intevation/flys/exports/StringAttribute.java: Removed
+	  needless import of org.w3c.dom.Attr.
+
+	* src/main/java/de/intevation/flys/exports/ChartSettings.java: ChartSettings
+	  is able to store a Section for legends now.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added
+	  methods to retrieve the font size of legends and if the legend should be
+	  visible or not. In addition, the ChartSettings returned by this instance
+	  will now contain a LegendSection as well.
+
+2011-12-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Introduced
+	  methods getChartSubtitle() and isGridVisible(). getChartSubtitle() returns
+	  in this implementation null. Concrete subclasses should override this
+	  mehtod if they require subtitles in charts. isGridVisible() determines if
+	  the grid in the chart should be visible or not. This method return always
+	  true in this implementation.
+	  In addition, the Settings object returned by getSettings() will now have a
+	  ChartSection set properly.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Override getChartSubtitle().
+
+2011-12-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Prepare the OutGenerator (process each of the Output's Facets) during the
+	  describe() operation to be able to return an initial Settings object.
+
+2011-12-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/BooleanAttribute.java,
+	  src/main/java/de/intevation/flys/exports/VisibleAttribute.java,
+	  src/main/java/de/intevation/flys/exports/StringAttribute.java: Fixed wrong
+	  usage of DOM operations.
+
+2011-12-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/BooleanAttribute.java,
+	  src/main/java/de/intevation/flys/exports/VisibleAttribute.java,
+	  src/main/java/de/intevation/flys/exports/StringAttribute.java: New.
+	  Concrete subclasses of a DefaultAttribute.
+
+	* src/main/java/de/intevation/flys/exports/ChartSettings.java,
+	  src/main/java/de/intevation/flys/exports/AxisSection.java,
+	  src/main/java/de/intevation/flys/exports/ChartSection.java:
+	  Implementations for chart settings. WORK IN PROGRESS!
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Override
+	  the getSettings() method. The implementation here returns a ChartSettings
+	  instance.
+
+	* src/main/java/de/intevation/flys/exports/EmptySettings.java: Modified the
+	  node name of the settings ("art:settings" -> "settings").
+
+2011-12-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  Cosmetics, doc.
+
+2011-12-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  Remove needless imports.
+
+2011-12-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix/Guard certain misconditions.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Do
+	  not crash when given null-range.
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java:
+	  Do not crash when given malformed array.
+
+2011-12-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java,
+	  ChangeLog: Whitespace cosmetic.
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  Annotation cosmetic.
+
+	* src/main/java/de/intevation/flys/artifacts/ExternalWMSArtifact.java:
+	  Convenience cosmetic.
+
+	* src/main/java/de/intevation/flys/utils/DataUtil.java: 
+	  vim-magicosmetic.
+
+2011-12-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Implement new WaterLineArtifact where needed so far.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Declare that we implement WaterLineArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  (getWaterLines): Implement to fulfil new WaterLineArtifact-
+			   interface-impl. Also generate new Facet.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  Do not depend on WINFOArtifacts, but on WaterLineArtifacts instead.
+
+2011-12-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added interface WaterLineArtifact to be implemented by artifacts
+	that know how to create a water line "against" a cross section.
+
+	* src/main/java/de/intevation/flys/artifacts/WaterLineArtifact.java:
+	  New, straight-forward interface.
+
+2011-12-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/EmptySettings.java: An
+	  implementation of the Settings interface defined in the artifact-database
+	  module. This implementation accepts no Section objects at all and creates
+	  an empty "settings" Node in its toXML() operation.
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: Defined a new
+	  method getSettings() that returns a Settings instance.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReportGenerator.java,
+	  src/main/java/de/intevation/flys/exports/MapGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/exports/ATExporter.java,
+	  src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java:
+	  Implemented the getSettings() operation. All OutGenerators will currently
+	  return an instance of EmptySettings.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Before the DESCRIBE document is created, we gonna evaluate each Output
+	  defined in the Collection's attribute document, if it has a Settings
+	  object set. If this is not the case, the relevant OutGenerator is called
+	  to retrieve a new instance of Settings.
+
+	* src/main/java/de/intevation/flys/collections/CollectionAttribute.java:
+	  Append the Settings of Outputs to the Output nodes in the XML
+	  representation.
+
+2011-12-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Recommend cross-sections.
+
+	* doc/conf/meta-data.xml: When having a cross-section out, recommend
+	  respective artifacts.
+
+2011-12-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Register factory for CrossSectionArtifacts.
+
+	* doc/conf/conf.xml: Register CrossSectionArtifact-Factory.
+
+2011-12-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/CollectionAttribute.java:
+	  New. This class will store the information provided in the Collection's
+	  attribute (which is a DOM document).
+
+	* src/main/java/de/intevation/flys/collections/CollectionDescriptionHelper.java:
+	  Store an instance of CollectionAttribute and append its XML representation
+	  to the DESCRIBE document.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: Removed
+	  all DOM operations from this writer. Its new task is to create a new
+	  CollectionAttribute object which represents a merged version of the old
+	  CollectionAttribute and the information provided by the Collection's child
+	  Artifacts.
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java: The
+	  result of AttributeParser's parse() operation is a CollectionAttribute
+	  object now. The methods getOuts() and getFacets() are as of now proxy
+	  methods that call the relevant methods of CollectionAttribute.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Some structural changes in the process to build the attribute Document of
+	  the Collection's DESCRIBE. We will no longer work with Document during
+	  this process but with instances of CollectionAttribute.
+
+2011-12-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Server-side of newer Cross-Section diagram construction architecture.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java
+	  (searchCrossSectionKmLine, getCrossSectionSnapKm),
+	  (getCrossSectionData): Removed, most functionality contained in
+				 CrossSectionArtifact.
+	  (getWaterLines): Now get CrossSectionLines to calculate water line.
+	 
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  Get a CrossSectionLine from blackboard.
+	
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Hard TODO, commented out function needed for subtitle to allow
+	  compilation.
+	
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java:
+	  Added Empty-Dataset- guard.
+
+2011-12-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Cosmetics.
+
+2011-12-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/CrossSectionKMService.java,
+	  src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  Removed superfluous imports.
+
+2011-12-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/CollectionDescriptionHelper.java:
+	  New. This class helps generating the DESCRIBE document of a collection.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Moved some of the code to create the DESCRIBE document out to
+	  CollectionDescriptionHelper.
+
+2011-12-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Resolve todo about wrongly named cross sections.
+	Open StaticState to allow that facets survive a compute.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticState.java
+	  (computeAdvance, computeFeed, computeInit): Override to call
+						      staticCompute.
+	  (staticCompute): New. Do nothing but be able to be overridden.
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java
+	  (setup): Fetch facets name from db (resolves todo).
+	  (getCurrentState): override staticstates staticcompute to let
+			     facets survive a compute.
+
+2011-12-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java,
+	  src/main/java/de/intevation/flys/wsplgen/WSPLGENCallable.java: Renamed
+	  JobExecutor to WSPLGENCallable (because it is a Callable now). In addition
+	  to the call() method which starts the WSPLGEN process, this Callable
+	  offers a cancelWSPLGEN() method to destroy a running WSPLGEN process.
+
+	* src/main/java/de/intevation/flys/wsplgen/WSPLGENFuture.java: A FutureTask
+	  that overrides cancel(boolean). Before this instance call
+	  super.cancel(boolean), it executes WSPLGENCallable.cancelWSPLGEN() to kill
+	  a running WSPLGEN process.
+
+	* src/main/java/de/intevation/flys/wsplgen/Scheduler.java: The Scheduler is
+	  no longer a Runnable. It makes now use of a ScheduledThreadPoolExecutor to
+	  schedule the incoming WSPLGENJobs. The ScheduledThreadPoolExecutor has a
+	  fixed number of worker threads that process the jobs. The number is 1 per
+	  default; it can be modified using a System property "wsplgen.max.threads".
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java: Added
+	  a string constant SCHEDULER.
+
+	* src/main/java/de/intevation/flys/wsplgen/SchedulerSetup.java: A
+	  LifetimeListener that currently implements the systemUp() method to create
+	  an instance of Scheduler. After its creation, the Scheduler is put into
+	  the GlobalContext using FLYSContext.SCHEDULER as key.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Fetch the Scheduler from GlobalContext.
+
+	* doc/conf/conf.xml: Registered SchedulerSetup as LifetimeListener.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java:
+	  (describe): Add data items to StaticFLYSArtifacts describe-doc.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticState.java:
+	  (addDefaultChartOutput): Convenienve function to add a chart-output.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java:
+	  Play nice with CrossSectionArtifact. Employ blackboard.
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  Spawn a CrossSectionFacet, handle various data.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Do not produce CrossSectionFacets anymore, these now "belong" to
+	  CrossSectionArtifacts.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/cache.xml: Added cross_sections cache.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java:
+	  (getCrossSection, getCrossSectionUncached): New, access specific
+	  CrossSection, employ caching.
+
+2011-12-09	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java:
+	  New artifact to handle cross-section access.
+
+2011-12-09  Raimund Renkert <raimund.renkert@intevation.de>
+
+	Issue 413.
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java:
+	  Apply point size from theme attribute linewidth.
+
+2011-12-08	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java,
+	  src/main/java/de/intevation/flys/exports/OutGenerator.java,
+	  src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Cosmetics.
+
+2011-12-08	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/CrossSectionKMService.java:
+	  Documentation added, let a value be its own neighbour (distance 0).
+
+2011-12-08	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Added "Bezugslinie" to list of calculation alternatives.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties: Added I18N.
+
+2011-12-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Further flys/issue420 fix (No Discharge Curves for Mosel).
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (includeYRange, mergeRanges): Moved NaN-guard to lowest level.
+	  (combineXRanges): Also NaN guard the X Axis extent.
+
+2011-12-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Partial fix for flys/issue420 (Berechnete Abflusskurve: Kein Diagramm für
+	Mosel). Protect axis extent calculation from empty or invalid
+	datasets.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (includeRange, includeYRange): Renamed, updated callers.
+	  (includeYRange): Protect from merging extent with NaNs.
+	  (debugDatasets): Be more verbose on the datasets.
+	  (zoom): Doc.
+
+2011-12-06	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue423 (Diagramm: Hauptwerte bei Abflusskurve am Pegel
+	werden an Y-Achse nicht angezeigt) - show not "raw" (vs interpolated)
+	values at Gauge.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java:
+	  Add parameterization to let facet know whether to fetch data at
+	  Gauges or at Artifacts position.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Let the MainValueFacets know whether to ask for interpolated
+	  MainValues; (do not interpolate for Gauges Main Values).
+	  (getMainValuesQ, getMainValuesW): Added parameter to control
+					    interpolation.
+
+2011-12-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Modified
+	  prefix constants for Mapserver layers and renamed constants (which have
+	  been postfixes before).
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java: Adjusted
+	  usage of Mapserver constants to the changes described above.
+
+2011-12-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Bugfix: Evaluate the correct parameter whether to set the floodplain or
+	  not. In addition, the scenario parameter used by WSPLGEN is now set
+	  correctly.
+
+>>>>>>> .r3356
+2011-12-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Allow longitudinal_section.q facets in wdiff states output.
+
+	* doc/conf/artifacts/winfo.xml: Added longitudinal_section.q facets
+	  to w-diff states out compatibility- matrix.
+
+2011-12-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Refactored Longitudinal*/WDiff-*Generator hierarchy and change axis
+	ordering, resolved label-i18n TODO.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Merge, avoid duplicate code, fix axis ordering in w-diff diagram,
+	  label in ls-diagramm.
+
+2011-12-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added missing state titles.
+
+2011-12-05	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Do not include zero on first axis.
+
+2011-12-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  (buildArtifactNode): Include artifacts state data in description
+	  document of collection.
+
+2011-11-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReportGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/exports/ATExporter.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/OutGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed superfluous imports.
+
+2011-11-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed import to make it compileable again.
+
+2011-11-30  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Introduce pre-rendering inter-facet communication phase ('blackboard
+	pass').
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java
+	  (doBlackboardPass): New. Before actually calling doOut, bundle
+			      ArtifactAndFacets and let them register
+			      themselfes as DataProvider in CallContext if they
+			      want ("announce on blackboard").
+
+2011-11-30  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java
+	  (doOut): Adjusted signature.
+
+2011-11-30  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Changed doOut signature to use ArtifactAndFacet, which will be
+	side effect of upcoming "blackboard" feature.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java
+	  (getRangesForDataset, getRangesForAxis): Renamed, removed TODO.
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java
+	  (createAxis): Update call to XYChartGenerator.getRangesForAxis,
+	  cosmetics.
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java
+	  (doOut): Changed Signature to accet ArifactAndFacet instead of
+		   Artifact and Facet.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReportGenerator.java,
+	  src/main/java/de/intevation/flys/exports/MapGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/exports/ATExporter.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java
+	  (doOut): Adjusted to new signature.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  (adjustAxes): Removed, we do not need a (manual) second axis.
+
+2011-11-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Fix compilation, use features of XYChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Set default behaviour such that 0 is not included in ranges.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Set behaviour of axis such that 0 is not (automagically) included.
+
+2011-11-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java:
+	  Fix wrong documentation.
+
+2011-11-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix LongitudinalSections multi-axes plotting behavior.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Removed obsolete functions, use better working multi-axis
+	  magic by XYChartGenerator.
+
+2011-11-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Overhaul dataset/axis/renderer housekeeping in Mother of all
+	ChartGenerators.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Refactored, keep axis/rendering relevant information in objects
+	  of new class AxisDataset. Removed some obsolete code while adding
+	  documentation.
+
+2011-11-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(relateWs):
+	  Added the implementation of the 'Bezugslinienverfahren'. Should
+	  be complete but needs testing!
+	  TODO: Setup a Calculation and integrate it into WINFO.
+
+2011-11-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Refactored the code for the "berechnete Abflusskurve" to enable
+	  the "Bezugslinienverfahren" to use the same code paths. It also
+	  removes a good deal of already existing code duplication.
+
+2011-11-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(findQsForW):
+	  Added method findQsForW(w, km) to retrieve the Qs that correspond
+	  for the given w and km.
+
+	  This is to be called when doing a "W auf freier Strecke" calculation
+	  to find out the Qs belonging to the user given W.
+	
+	* src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Removed superfluous imports.
+
+2011-11-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix bug when adding Q data in LongitudinalSectionGenerator.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Add data from Q -Facet as Q over Km points.
+
+2011-11-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added handling of empty plots.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (createAxes, removeEmptyRangeAxes): Survive empty datasets map, create
+	  primary axis.
+	  (recoverEmptyPlot): New.
+
+2011-11-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Let first visible axis be always on the left.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (createAxes): When creating axes, keep track of which is the first
+			one. Set its location to "left".
+
+2011-11-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Use multiple axis in relevant generators.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  (createYAxis): Implemented.
+	  Define and use YAXIS enum for axes.
+
+2011-11-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix theming and legend items for plot with multiaxis feature.
+	Decouple renderer index from dataset index.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  (applyThemes): Do not get renderer based on dataset/axis-index but
+			 count.
+
+2011-11-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	In XYChartGenerators allow more than two datasets.
+	Assign axis to indices of datasets, do not show axis if corresponding
+	dataset is set to be not visible.
+	Do proper axis-setting in LongitudinalSectionGenerator only (other
+	will follow). Based on a patch by Sascha Teichmann.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Keep relation between index and dataset, once its added. Compute
+	  ranges per index. Allow subclasses to override createAxes to specify
+	  internationalized labels etc.
+	
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java:
+	  Add datasets to first index.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Implement createYAxis to create correct first, second and third
+	  axis. Added enum to easy identification of axis. Stripped down
+	  adjustAxis which was used to create second axis.
+	  Add datasets at correct indices.
+
+2011-11-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/StackFrames.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/FunctionResolver.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Cosmetics, docs.
+
+2011-11-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Cosmetics, docs.
+
+2011-11-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableCacheKey.java:
+	  Cosmetics, docs.
+
+2011-11-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Resolved TODO about caching certain WstValueTables.
+
+2011-11-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/ExternalWMSArtifact.java: New.
+	  This Artifact is used to allow users adding external WMS layers to their
+	  floodmaps. An ExternalWMSArtifact stores an URL of a WMS, the name and the
+	  title of the WMS layer. The internal State extends WMSBackgroundState.
+
+	* doc/conf/conf.xml: Registered the ExternalWMSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added a
+	  new type "floodmap.externalwms" which is used by the ExternalWMSArtifact.
+
+	* doc/conf/artifacts/winfo.xml: Allowed the "floodmap.externalwms" facet for
+	  floodmaps.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java:
+	  Some refactoring to allow easier subclassing.
+
+2011-11-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fixed flys/411.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Use different queries to avoid costy joins.
+
+2011-11-22  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Use different queries to avoid costy joins. Patch by Sascha
+	  Teichmann, minor typo-fix.
+
+2011-11-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added other.wkms.interpol facet
+	  to compatibility matrix for computed discharge curves.
+
+2011-11-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Resolve cosmetic todo, use importData-convenience method.
+
+2011-11-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Handle STATIC_WKMS_INTERPOL and WQ/Points as Annotations.
+
+2011-11-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Give Facet different name when its a flood*, so that it can be
+	  understood to be e.g. a flood-protection further down the processing
+	  line.
+
+2011-11-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Prevent ArrayIndexOutOfBounds, log method entry.
+
+2011-11-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new Facet Type: Interpolated W/Km values.
+
+2011-11-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java,
+	  src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Avoid
+	  WMS layer names that begin with digits. This would lead to invalid
+	  WMSGetFeatureInfo responses, where the name of a layer is the name of a
+	  XML node.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Added and use *_wq macros for interpolated
+	  w/q data (currently used in computed discharge curve only).
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Handle STATIC_WQ_ANNOTATION type facets, build and add annotations
+	  for these.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Set Facet type (name) based on static datas name (special case
+	  everything starting with "height").
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Extended compatibility matrices.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new STATIC_WQ_ANNOTATIONS Facet Type.
+
+2011-11-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  Added a method isQueryable() that determines if a layer is queryable via
+	  WMS GetFeatureInfo request. This method returns false as default.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSDBLayerFacet.java:
+	  Override isQueryable(). All WMSDBLayerFacets are queryable via WMS
+	  GetFeatureInfo request.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Added functionality to artifact to use single column wst
+	  interpolators.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Added methods to create WstValueTables (interpolators) for specific
+	  columns of wsts.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Added Point Style for other.wq data.
+
+2011-11-16	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Do not re-evaluate constant size() in for-loops.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Get ld_locations not locations data item to determine km.
+	  (getDataAsDouble): New helper function to get data item as double.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Recommend fixations for computed discharge
+	  curve outs, minor refactoring of dc conf.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml: Added wqinterpol factory to produce
+	WQKmsInterpolArtifacts.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Use StyledSeriesBuilder to add WQ data from WQKms to Series.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Handle interpolated WQ data.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java:
+	  Adjusted to similar implementations. Added TODO about merging with
+	  these similar implementations.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Removed logging noise, find better suited rows for interpolation.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Cache WstValueTables that were fetched by wst_id.
+
+2011-11-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  (getWstName): Fix and use SQL statement.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java:
+	  Added new Facet and Artifact to access W over Q data.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  (importData): New function to copy data from one artifact to
+			another.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java
+	  (getTable): New methods to get WstValueTable for given wst_id.
+	  Prepolate Arrays with NaNs.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  (getWKmsName): Fix definition.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  (getWKmsName): New function. Get name (description) of a WST.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new "other.wq"/STATIC_WQ Facet-Type, e.g. for fixations in
+	  discharge curves.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Picky cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedFacetAdapter.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java:
+	  Cosmetics, docs.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added translations for Mosel, Elbe and Saar.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added translations
+	  for Mosel, Elbe, Saar.
+
+2011-11-15  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added new interpolation mechanism to WstValueTable to interpolate
+	given columns only.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java
+	  (linearW): New, interpolate a given columns w's between given rows.
+	  (interpolateWQColumnwise): New, interpolate between rows ws at a
+				     given column and km.
+
+2011-11-14  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Minor doc added.
+
+2011-11-13	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQKmsFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java,
+	  src/main/java/de/intevation/flys/themes/ThemeFactory.java:
+	  Removed dead ';' from empty bodies.
+
+2011-11-13	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  s/reset/resetQuick/s in TDoubleArrayLists.
+
+2011-11-13	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Added system properties 'waterlevel' and 'km'. Useful to
+	  init the UI with a given waterlevel and drawing the cross-sections
+	  at the given km.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Spliting by NaNs definition holes _should_ work now. Needs 
+	  some more testing.
+	  TODOs: 
+	  - Use log4j instead of println for logging.
+	  - Subclass XYDifferenceRenderer instead of replacing it totally.
+
+2011-11-12	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Draw water, too.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  First code to split datasets by NaNs. WIP.
+
+2011-11-12	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Draw ground with StableXYDifferenceRenderer.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Removed XYDatasetToZeroMapper stuff. Not needed any longer
+	  because we use rendereres on dataset basis now.
+
+	* src/main/java/de/intevation/flys/jfree/XYDatasetToZeroMapper.java:
+	  Removed.Not longer needed.
+
+2011-11-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Include computed discharge curves in dc
+	  conf.
+
+2011-11-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Somewhat unify user-part of dc config.
+
+2011-11-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Modified some floodmap styles and added a
+	  backgroundcolor attribute to polygon themes.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java:
+	  Added support for backgroundcolor.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Parse
+	  backgroundcolor from theme document. If a value is given, the
+	  backgroundcolor is set on the Mapserver style.
+
+2011-11-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java:
+	  Set correct Mapserver attribute name to adjust the width of a line.
+
+2011-11-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added 'textcolor' and 'textsize' attributes to the
+	  existing 'Kms' theme.
+
+	* doc/conf/mapserver/fontset.txt: Defined a 'DefaultFont' that is used as
+	  default font for Mapserver labels.
+
+	* doc/conf/mapserver/db_layer.vm: Add a Mapserver LABELITEM if a value is
+	  provided by LayerInfo object.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java: Added a
+	  getLabelItem() method that returns null as default.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java: Override
+	  getLabelItem() to return "km" which is the database field that contains
+	  the kilometer information.
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WMSDBLayerFacet.java:
+	  Added an attribute labelItem with appropriate getter/setter methods.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java:
+	  Splitted up the internal class Clazz. Now, there are two new inner
+	  classes Style and Label that fulfill the appropriate Mapfile sections of
+	  Mapserver.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Also Read font
+	  attributes and add new Clazz Label for the Mapserver layer.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Set the
+	  "labelItem" attribute on the LayerInfo object used to fill DB layer
+	  templates.
+
+2011-11-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Re-use macros to include more data to
+	  datacage in more situations.
+
+2011-11-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  Removed needless imports.
+
+2011-11-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSFixpointsArtifact.java: New
+	  WMSDBArtifact that creates facets for "fixpoints" relation.
+
+	* doc/conf/artifacts/winfo.xml: Registered "floodmap.fixpoints" as valid
+	  "floodmap" facet.
+
+	* doc/conf/conf.xml: Registered the new WMSFixpointsArtifact.
+
+	* doc/conf/themes.xml: Added a theme for "floodmap.fixpoints".
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  Facet type "floodmap.fixpoints".
+
+	* doc/conf/meta-data.xml: Made "floodmap.fixpoints" available via datacage.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added titles for
+	  "floodmap.fixpoints" facets.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Refactored and (re)use macros.
+
+2011-11-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSBuildingsArtifact.java: New
+	  WMSDBArtifact that creates facets for "buildings" relation.
+
+	* doc/conf/artifacts/winfo.xml: Registered "floodmap.buildings" as valid
+	  "floodmap" facet.
+
+	* doc/conf/conf.xml: Registered the new WMSBuildingsArtifact.
+
+	* doc/conf/themes.xml: Added a theme for "floodmap.buildings".
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  Facet type "floodmap.buildings".
+
+	* doc/conf/meta-data.xml: Made "floodmap.buildings" available via datacage.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added titles for
+	  "floodmap.buildings" facets.
+
+2011-11-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSLineArtifact.java: New
+	  WMSDBArtifact that creates facets for "lines" relation.
+
+	* doc/conf/artifacts/winfo.xml: Registered "floodmap.lines" as valid
+	  "floodmap" facet.
+
+	* doc/conf/conf.xml: Registered the new WMSLineArtifact.
+
+	* doc/conf/themes.xml: Added a theme for "floodmap.lines".
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  Facet type "floodmap.lines".
+
+	* doc/conf/meta-data.xml: Made "floodmap.lines" available via datacage.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added titles for facets.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Adjusted DC configuration to
+	  also allow heightmarks and base data in discharge longitudinal
+	  sections. Minor refac.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Adjusted DC configuration to let old
+	  calculations be available for discharge longitudinal sections.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java:
+	  Handle other WQKm and WKm Facets.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java
+	  (IS): New inner class with static method to allow queries whether
+		a type belongs to a however-defined "group".
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml: Reverted accidental commit.
+
+	* doc/conf/meta-data.xml: Extracted annotations-macro, add recommendation
+	  for discharge longitudinal sections.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml: Reverted accidental commit.
+
+	* doc/conf/meta-data.xml: Extracted annotations-macro, add recommendation
+	  for discharge longitudinal sections.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Made discharge longitudinal section out compatible with annotations.
+
+	* doc/conf/artifacts/winfo.xml: Added facets to compatibility list
+	  for discharge longitudinal section outs.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Extracted Annotations-Macro in dc conf, recommend Annotations for
+	discharge longitudinal sections. (Note correction two commits later).
+
+	* doc/conf/conf.xml: Extracted annotations-macro, add recommendation
+	  for discharge longitudinal sections.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Handle Annotations in DischargeLongitudinalSection diagrams.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java:
+	  Call doAnnotations for LONGITUDINAL_ANNOTATION facets. 
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Resolve code duplicate.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java
+	  (doAnnotationsOut): Removed duplicate code.
+	  Theoretically handle WQKMS data.
+	
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Adjusted call to doAnnotationOut.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java
+	  (doAnnotations): Doc from LongitudinalSectionGenerator.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Include exception when logging issue with spline creation.
+
+2011-11-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java:
+	  When during XPath evaluation an exception is thrown, log the
+	  expression that caused the trouble.
+
+2011-11-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Use separate XYDataset for each curve. This is needed because 
+	  "Raum/Flaeche" needs specialized renderers, which are not compatible
+	  with the standard renderers.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java:
+	  Removed superfluous import.
+
+2011-11-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSFloodplainArtifact.java: New
+	  Artifact that is used to create WMS layers for floodplains in maps.
+
+	* doc/conf/conf.xml: Registered the new WMSFloodplainArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  a new type 'floodmap.floodplain'.
+
+	* doc/conf/artifacts/winfo.xml: Registered the 'floodmap.floodplain' facet
+	  for floodmaps.
+
+	* doc/conf/themes.xml: Added a theme for 'floodmap.floodplain' facets.
+
+	* doc/conf/meta-data.xml: Added configuration for 'floodplain'.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added facet titles for
+	  'floodmap.floodplain' facets.
+
+2011-11-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSCatchmentArtifact.java: New
+	  Artifact that is used to create WMS layers for catchments in maps.
+
+	* doc/conf/conf.xml: Registered the new WMSCatchmentArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  a new type 'floodmap.catchment'.
+
+	* doc/conf/artifacts/winfo.xml: Registered the 'floodmap.catchment' facet
+	  for floodmaps.
+
+	* doc/conf/themes.xml: Added a theme for 'floodmap.catchment' facets.
+
+	* doc/conf/meta-data.xml: Added configuration for 'catchments'.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added facet titles for
+	  'floodmap.catchment' facets.
+
+2011-11-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSHwsArtifact.java: New
+	  Artifact that is used to create WMS layers for flood protected works.
+
+	* doc/conf/conf.xml: Registered the new WMSHwsArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  a new type 'floodmap.hws'.
+
+	* doc/conf/artifacts/winfo.xml: Registered the 'floodmap.hws' facet for
+	  floodmaps.
+
+	* doc/conf/themes.xml: Added a theme for 'floodmap.hws' facets.
+
+	* doc/conf/meta-data.xml: Added configuration for 'hws'.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added facet titles for
+	  'floodmap.hws' facets.
+
+2011-11-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/dbconnection.include: Removed. DB connections are
+	  supported by LayerInfo objects now. So, we do not need to configure it any
+	  more.
+
+	* doc/conf/mapserver/db_layer.vm: The database connection is provided by
+	  LayerInfo objects. The "INCLUDE dbconnection.include" has been replaced.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java: The inner
+	  State class got two new methods that provide information about db
+	  connection string and connection type.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSDBLayerFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java: Both
+	  classes support getter/setter for connection and connectionType.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Fill
+	  LayerInfo objects used to create DB layers with the connection and
+	  connection type provided by WMSDBLayerFacet.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Allow height marks with points style in w-differences diagrams.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Handle HEIGHTMARKS_POINTS facets.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Made Q Duration curve initially inactive.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java
+	  (getInitialFacetActivity): Return 0 for DURATION_Q facets. Minor
+				     cosmetics.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Recommend mainvalues for Duration Curve Diagrams.
+
+	* doc/conf/meta-data.xml: Recommend mainvalues for duration curve
+	  diagrams. refactored into macro.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Handle MainValue Facets in Duration Curve Diagrams.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Handle MainValues.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Adjusted to call doAnnotations.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Move do*Annotation* (like mainvalue) in XYChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java
+	  (doAnnotations): New, moved from DischargeCurveGenerator.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java
+	  (doMainValueAnnotations): Moved to superclass.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Removed duplicate code.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java
+	  (): Minor cosmetics, added stability.
+	  (getInitialFacetActivity): Let facets be inactive in duration curve
+				     diagrams.
+
+2011-11-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Allow other.wqkms facets in many
+	  outputs, mainvalues in duration curves.
+
+2011-11-09  Ingo Weinzierl <ingo@intevation.de>
+
+	 * doc/conf/mapserver/dbconnection.include: Adapted connection params for
+	   using an oracle database.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSQPSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java:
+	  Added Oracle support for Mapserver's DATA attribute. Oracle doesn't allow
+	  a "USING UNIQUE id" string in this attribute which is required by Postgis.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Modified the geometry type of "talaue.shp" from MultiPolygon to Polygon.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a function
+	  which returns true, if the backend uses an Oracle db instance. Otherwise,
+	  it returns false.
+
+2011-11-08  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix flys/issue406 (Themestyle-editor: themes for "other.wkms" and
+	"other.wqkms" missing)
+
+	* doc/conf/themes.xml: Fixed typos in WKms and WQKms theme names.
+
+2011-11-08  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix flys/issue405 (Datacage: Recommendations get loaded twice).
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java
+	  (setup): Do not try/catch exception.
+	  (spawn_state): Generate just one "general" output.
+
+2011-11-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  Added a log.warn() which prints out an exception - previously it was
+	  just skipped.
+
+2011-11-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Removed orphaned datacage configuration which is
+	  no longer loadable.
+
+2011-11-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added an "post-describe" hook which is necessary to
+	  load recommendations for "floodmaps".
+
+2011-11-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java
+	  (loadRows, loadColumns, loadQRanges): Refactored in preparation to
+	  ability to create WstValueTables for given wst_id and
+	  column_pos (interpolation for static data).
+
+2011-11-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  Minor, picky cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKmsFactory.java:
+	  Removed junk.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Added documentation.
+
+2011-11-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Be more specific in what to catch.
+	
+2011-11-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Added the CrossSectionTracks to the "floodmap"
+	  datacage configuration.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added a
+	  FacetType "floodmap.qps".
+
+	* doc/conf/conf.xml: Defined an ArtifactFactory for the "wmspqsartifact"
+	  string. The factory will create new instances of WMSQPSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSQPSArtifact.java: New. This
+	  Artifact is used to create "floodmap.qps" facets. It has an internal fixed
+	  State WMSQPSState.
+
+	* doc/conf/artifacts/winfo.xml: Added the "floodmap.qps" layer to the
+	  "floodmap" output.
+
+	* doc/conf/themes.xml: Added a theme for "floodmap.qps" facets.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added strings for the QPS WMS
+	  layer used in floodmaps.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Re-enable mainvalue-recommendations for computed discharge curves,
+	as the NPE should be gone.
+
+	* doc/conf/meta-data.xml: Uncomment mainvalue recommendations.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	When querying metadata/datacage, use only output-names of outputs that
+	actually exists (in the sense of having facets).
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java:
+	  Get output names from artifact, not from state.
+
+2011-11-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Removed DEMs from floodmap configuration,
+	  because we are not able to draw DEMs into maps.
+
+2011-11-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java:
+	  Subclasses WMSDBArtifact now and defines an inner class RiverAxisState
+	  which subclasses WMSDBState.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Removed. The RiverAxisState is an inner class of RiverAxisArtifact now
+	  which subclasses WMSDBState.
+
+	* doc/conf/artifacts/riveraxis.xml: Removed, because the RiverAxisArtifact
+	  has a fixed static State only.
+
+	* doc/conf/conf.xml: Removed riveraxis.xml definition.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	For a Flys-Collection, add outputt to attributes-part of describe
+	document only if they contain facets.  -> Prevent empty output nodes
+	in flys-collections outputs.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  (writeFacets): Added return type to indicate whether any facet was
+	  written. Decide whether to add an output-node depending on this
+	  return value.
+
+2011-11-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Create
+	  line and polygon layers for barriers only if they are really existing.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Disable mainvalue-recommendations for discharge curves as they trigger
+	a yet-to-be understood NPE.
+
+	* doc/conf/meta-data.xml: Comment mainvalue-recommendations for
+	  discharge curves.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added new matching condition for theme-mappings: the name of
+	the output.
+
+	* src/main/java/de/intevation/flys/themes/ThemeMapping.java:
+	  Added output field and function to match it against a given
+	  output name.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Createing ThemeMapping with output attribute from configuration.
+	  
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java:
+	  (getTheme(FLYSContext, string)): Removed, never called.
+	  (getTheme): Added outputName argument, match it.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Pass outputs name until it can be matched against mapping.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Added default themes for other.w(q)kms.
+
+2011-11-04  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Cosmetics, reduce logging noise.
+
+2011-11-03  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added access to static W_Q_Kms - data in much the same way then static
+	WKms.
+
+	* src/main/java/de/intevation/flys/artifacts/model/StaticWQKmsCacheKey.java:
+	  Cache Key for static wqkms data.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/WQKmsFacet.java:
+	  Facet for WQKms.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/WQKmsFactory.java:
+	  Factory to access WQKms.
+	
+	* src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java:
+	  Artifact that provides 'static' WQKms.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added STATIC_WQKMS type.
+
+2011-11-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/XYDatasetToZeroMapper.java:
+	  New. Maps series to zero to be compatible with XYDifferenceRenderer.
+	  It returns an iterator over XYDatasets to enable splitting by NaNs,
+	  which still needs to be implemented.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Uses a XYDatasetToZeroMapper now. Fixed package name.
+
+2011-11-03  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added new matching options for theme-mappings to allow more
+	overspecification of defaults (e.g. now name,description-pattern and
+	master-artifacts attributes are matched). The first full match from
+	the configuration file is done.
+	New matching option in the masterAttr- field of a mapping are
+	super-basic until new use-cases come up.
+	Concrete new themes are point-styles of Ws when locations where chosen
+	to calculate.
+
+	* doc/conf/themes.xml: Added newly defined Themes.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Put master-artifact in flys-context.
+
+	* src/main/java/de/intevation/flys/themes/ThemeMapping.java:
+	  Accept masterAttr in constructor.
+	  (masterAttrMatches): New, check masterAttr-condition against
+	  artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  When creating ThemeMappings, pass in masterAttr.
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java:
+	  (getTheme): Evaluate masterAttr-condition, always return first full
+	  match.
+
+2011-11-03  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java:
+	  Added ARTIFACT key, documentation.
+
+2011-11-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  New. At the moment a pure copy of JFreeChart's XYDifferenceRenderer.
+	  Needs to be refactored to cope with its limitations:
+
+	  - Series numbers need to be zero based. We have more than two series
+	    in our diagrams.
+
+	  - Cannot handle definition holes indicated by NaNs. We have these
+	    cases e.g. more than one "Fliessbereich".
+
+2011-11-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Reenabled dumping data to disk.
+
+2011-11-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Removed superfluous import.
+
+2011-11-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java:
+	  Use new pair in stack of results and connections because they are
+	  always used in pairs. Maintaining two separate stacks is not
+	  needed any longer.
+
+2011-11-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+	
+	* src/main/java/de/intevation/flys/utils/Pair.java: New. A generic pair.
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Rewritten to useful as test bed for "Raum/Flaeche" operations.
+
+2011-11-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java: Added
+	  setter methods for all parameters and removed the constructors. There is
+	  just an empty constructor - all parameters need to be set via setter
+	  methods.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Modified the
+	  creation of LayerInfo objects.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DBLayerInfo.java:
+	  Removed, because the internal structure and constructors of LayerInfo have
+	  changed, so that we gonna use LayerInfo for all layers now.
+
+	* doc/conf/themes.xml: Modified the color definitions of
+	  'floodmap.riveraxis' and 'floodmap.kms' themes and added a 'symbol' field
+	  to 'floodmap.kms'.
+
+	* doc/conf/mapserver/symbols.sym,
+	  doc/conf/mapserver/fontset.txt: New. Required by Mapserver.
+
+	* doc/conf/mapserver/db_layer.vm: Added an 'EXTENT' field that is filled
+	  using LayerInfo.getExtent().
+
+	* doc/conf/mapserver/mapfile.vm: Modified FONTSET directory and added a
+	  SYMBOLSET.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java: Added an
+	  abstract method getGeometryType().
+
+	* src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java: Override
+	  getGeometryType() of WMSDBArtifact. This Artifact provides "POINT"s.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Create new WMSDBLayerFacets with geometry type "LINE".
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSDBLayerFacet.java:
+	  Added a 'geometryType' attribute and getter/setter methods. This attribute
+	  determines the type of geometry provided by this database wms layer. Types
+	  could be "POLYGON", "POINT", "LINE" and so on.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java:
+	  Added a 'symbol' attribute to the inner class Clazz.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Added a function to
+	  parse the symbol field of a theme. This symbol is used for
+	  MapserverStyle.Clazz.
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Apply point theme to heightmarks when imported in longitudinal
+	section diagram.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new FacetType HEIGHTMARK_POINTS.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFacet.java:
+	  Allow name to be given in constructor.
+	
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  If heightmarks were loaded, give respective name in WKmsFacet
+	  generation.
+
+	* doc/conf/themes.xml: Added virtual "Points" and concrete
+	  heightmark_points - theme.
+	
+	* doc/conf/artifacts/winfo.xml: Made longitudinal_section output
+	  compatible with heightmarks_points.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Render heightmarks like other wkms.
+	
+	* doc/conf/meta-data.xml: Changed heightmark ids such that it can be
+	  identified in StaticWKmsFacet .
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java:
+	  Documentation added.
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java:
+	  Fix bug where (wrong) active-attribute was set, doc.
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Refactoring to allow mainvalues in both discharge and computed
+	discharge curve diagrams.
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java
+	(addPointsQW): New helper function.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Moved doMainValueQAnnotations, doMainValueWAnnotations from child-
+	  to parent-class, extracetd doDischargeOut. Use
+	  StyledSeriesBuilder.addPointsQW .
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Removed logger/debugging noise.
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	1) Pass outputs name to artifacts getInitialFacetActivity().
+	2) Do not allow "gaps" in positions of facets in outputs in attributes
+	   of collection (prevent e.g. positions 1,3,5; will become 1,2,3
+	   instead).
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  Pass outputname to artifacts getInitialFacetActivity(), prevent
+	  gaps in facets positions in outputs (1,3,10 become 1,2,3).
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	1) Give Artifacts information about the out when they have to decide
+	whether a given facet is initially in/active by adding parameter
+	to getInitialFacetActivity(+outputName).
+	2) Generate separate Set of MainValueFacets for discharge curves.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  (getInitialFacetActivity): Adjusted, new parameter outputName.
+	  Added MAINVALUES_{Q,W} definition.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java:
+	  Accept name in constructor.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Create second pair of MainValuesFacets, give distinguishable names.
+
+2011-11-02	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added compatibility of (computed)discharge-curve diagrams with
+	mainvalues.
+
+	* doc/conf/artifacts/winfo.xml: Added mainvalues to compatibility list
+	  of (computed) discharge curve outputs. These facet-definitionss can
+	  differ in names because we can can have up to 4 mainvalue facets in
+	  one state.
+
+2011-11-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java: Determine
+	  the extent of such WMS layer based on the list of RiverAxisKm objects
+	  returned by the backend.
+
+2011-11-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Added a datacage node that represents a
+	  kilometer WMS layer.
+
+	* doc/conf/conf.xml: Added a new Artifact WMSKmArtifact.
+
+	* doc/conf/themes.xml: Added a style for 'floodmap.kms' facets.
+
+	* doc/conf/artifacts/winfo.xml: Defined 'floodmap.kms' as valid floodmap
+	  facet.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  a 'floodmap.kms' facet.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a method to
+	  determine the srid of a river based on its name.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java: New. This
+	  Artifact should act as base Artifact for WMS layers that represent data
+	  from database datastore.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java: New. This
+	  Artifact is used to generate facets for kilometer WMS layers.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added default descriptions
+	  for 'floodmap.kms' facets.
+
+2011-11-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix incompilability.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelInfoState.java:
+	  Adjust to changed CrossSectionFacet.
+
+2011-11-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Minor preparations to handle multiple cross sections in one
+	diagram/artifact, faking certain aspects (e.g. ability to display
+	multiple cross sections, but let these fetch the exactly same data
+	for now).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Parameterize but fake access to cross-section (always take first
+	  one).
+	  (getCrossSectionName,getCrossSectionNames): Renamed, access names
+	  of all cross-sections, so that at least facets with different names
+	  are created (they will still deliver the same data).
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java:
+	  Allow indexing.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Index created cross-sections.
+
+2011-11-01	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Show multiple water lines and facets in cross-section diagram if
+	multiple waterlevel values had been entered.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java
+	  (appendBackgroundActivity): Made static.
+	  (getWaterLines): Add 'idx' argument to specify index of queried
+			   waterlevel.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  Add index.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Add one Facet for each of the computed waterlevels.
+
+2011-10-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/VectorUtils.java:
+	  Added code to calculate intersection points.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java:
+	  Added polygons for trivial cases. WIP
+
+2011-10-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java:
+	  Made it compilable again.
+
+2011-10-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/VectorUtils.java:
+	  Made X() and Y() access macros public.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java:
+	  More code. WIP.
+
+2011-10-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java:
+	  Handle start points when building polygons. Work in progress.
+
+2011-10-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/VectorUtils.java:
+	  New. Vector operations on Point2D.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java:
+	  Moved vector operations to VectorUtils.
+
+2011-10-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  Do not call size() in for loop again and again.
+
+2011-10-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java:
+	  Interim check in. Work in progress.
+
+2011-10-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java: New.
+	  Polygon class to help creating "Raum/Flaeche" renderers with gaps in
+	  their definitions. WORK IN PROGRESS!
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added a default theme for the riveraxis used in the
+	  floodmap.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java:
+	  New. This class is used by ThemeUtil to create a style which is
+	  compatible for Mapserver-
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java: Added a method to
+	  retrieve a Mapserver compatible style (as string) based on a given
+	  Document (that comes from CollectionItem's attribute).
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java:
+	  Implemented the setStyle() and getStyle() methods.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Added a
+	  new parameter 'style' to createDatabaseLayer(). This parameter is set on
+	  LayerInfo.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: Create
+	  Mapserver compatible styles and call createDatabaseLayer() with this
+	  style.
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Defined "floodmap.riveraxis" and
+	  "floodmap.wmsbackground" as compatible layers for the floodmap output.
+
+	* doc/conf/mapserver/db_layer.vm: New. This layer template is used for
+	  Mapserver layers with database datastore.
+
+	* doc/conf/mapserver/dbconnection.include: New. The database configuration
+	  used in the db_layer template.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a method
+	  getUserWMSUrl() that returns the URL to the user specific WMS server.
+	  This method requires a UUID of an Artifact to identify the owner of the
+	  Artifact.
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java: Use
+	  FLYSUtils.getUserWMSUrl() to create the URL to the user WMS for
+	  WMSLayerFacets creation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSDBLayerFacet.java:
+	  New. Subclasses WMSLayerFacet to save data and filter parameters used
+	  for database storage in Mapfiles.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DBLayerInfo.java: New.
+	  Subclasses LayerInfo to save database relevant parameters.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Creates new WMSDBLayerFacets, so that the riveraxis layer data is
+	  fetched from database.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Added a
+	  public method that allows creating layers (type LINE) based on
+	  WMSDBLayerFacets.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: Enabled
+	  support for Facets other than "floodmap.wsplgen" and "floodmap.barriers".
+	  Those other Facets are supposed to be WMSDBLayerFacets.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Refactored, added StyledSeriesBuilder to unify adding points to
+	XYSeries.
+
+	* src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java:
+	  New class to help with adding points to XYSeries.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Use StyledSeriesBuilder to add points to series.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added DC-conf, such that static data can be loaded from w-difference
+	diagrams datacage.
+
+	* doc/conf/meta-data.xml: Minor "refactoring" (definition of two
+	  macros, allow certain static data to be loaded via datacage to
+	  w-difference diagrams, too.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Load and display annotations in w-differences, minor polishing.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Use FacetType 'instead' of string. Allow Annotations in
+	  WDifferences- diagram.
+
+	* doc/conf/meta-data.xml: Recommend annotations in w-differences case.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: 
+	  Survive case where a given output doesnt exist in compatibility
+	  matrix.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticState.java:
+	  Added simpler constructor.
+	
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Adjusted construction of StaticStates.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Allow Annotations in longitudinal and
+	  w-differences diagrams.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Use artifacts configuration (e.g. winfo.xml) to define which facets
+	can be used in which output. Hide no-matches.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added use of "compatibility matrix". Only include facets in in
+	  collections description document that are marked compatible in the
+	  masterartifacts configuration (e.g. winfo.xml).
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java
+	  (mergeAttributes, getMasterArtifact): Extraced, updated caller.
+	  Cosmetics to reduce indentation one step.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java
+	  (getStateHistoryIds): New, return list of current and all previous
+	  state ids.
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Trigger the re-creation of FLYS mapfile if endOfLife() of this state is
+	  called.
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/mapfile.vm: The "layers" injected by VelocityEngine is
+	  now used to include layers. A single string in this list represents the
+	  path to a file which contains a LAYER section for Mapserver' Mapfile.
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java: Removed the
+	  update() call of MapfileGenerator. Mapfiles are generated by MapGenerator
+	  only which requires a FLYSArtifactCollection.doOut()!
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: Call update()
+	  of MapfileGenerator to trigger the re-creation of mapfile(s).
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Collect all
+	  LAYER snippets from filesystem and inject the filepath for each snippet
+	  into the Mapfile template.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Cosmetics, doc.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Let OutputParser and AttributeParser collect all facets on the way.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java,
+	  src/main/java/de/intevation/flys/collections/AttributeParser.java:
+	  Collect all facets while iterating over Outputs and Attributes,
+	  documentation added.
+
+2011-10-28	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Extracted getFlysContext from FLYSArtifacts into FLYSUtils.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java
+	  (getFlysContext): Added, extracted from FLYSArtifact.
+	
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  (getFlysContext): Moved to FLYSUtils, updated callers.
+	
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Update callers to getFlysContext.
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: Bugfix:
+	  Catch IOException - flys-artifacts compiles again.
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Defined "floodmap.barriers" as valid facet
+	  for floodmaps.
+
+	* doc/conf/mapserver/shapefile_layer.vm: New. Currently a copy of
+	  layer.vm. This template will evolve to a special mapserver layer
+	  template with a shapefile data source.
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java: Fixed broken
+	  facet name of barriers.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Added
+	  public methods for creating wsplgen and barriers layer files for
+	  mapserver.
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: Use
+	  MapfileGenerator to create new layer files for wsplgen and barriers.
+
+2011-10-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Added new
+	  method gerRiverBoundary() which returns an Envelope object (which
+	  represents the bounding box of a Geometry) of a riveraxis specified by its
+	  rivername.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java: Use
+	  Geometry.getRiverBoundary() to determine the max extent of a river.
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java: Use
+	  JTS Envelope to save the bounding boxes of WMS layers.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java:
+	  Adapted the code to apply the changes in GeometryUtils (use Envelope to
+	  determine the max extent of the river axis).
+
+	* src/main/java/de/intevation/flys/exports/MapGenerator.java: New (work in
+	  progress). This Generator will currently return a map configuration in XML
+	  which consists of parameters required by OpenLayers to create a map.
+
+	* doc/conf/conf.xml: Registered the new MapGenerator.
+
+2011-10-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Do not recommend historical data to load
+	  when having computational discharge curves.
+
+2011-10-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Reduce noise, given "error" message was more of "debug" nature.
+
+2011-10-27	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Restore mapping of state id to facets (essentially revert, revision
+	3083 and 3088).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  (getInitialFacetActivity): Be more explicit on which facets to
+	  introduce inactivated.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Minor refactoring, declare a string final static.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Restore association from state id to facets.
+
+2011-10-26	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java,
+	  src/main/java/de/intevation/flys/collections/OutputParser.java:
+	  Cosmetics, documentation.
+
+2011-10-26	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Prepare rendering of "other/static wkms" (functional) and
+	  w-differences (not yet fully functional).
+
+2011-10-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Cosmetics.
+
+2011-10-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java:
+	  Do not store facets in a map from stateId to list of facets, but in
+	  a pure list instead.
+
+2011-10-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Adjusted to newer semantics,
+	  minor cleanups.
+
+2011-10-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Resolve association of facets to states in artifacts. This eases
+	merging of outputs and facets and inhibition of unwanted outputs
+	substiantially (at the price of slightly more expensive merging).
+	Also, the semantics of artifacts configuration files (e.g. winfo.xml)
+	is changed (facet elements within an output elements are used for
+	merging).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Do not store facets in a map from stateId to list of facets, but in
+	  a pure list instead.
+
+2011-10-26  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Added dc configuration for some data
+	  that can be loaded from longitudinal section diagrams.
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Cosmetics, docs.
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Let Qs in Longitudinal Diagram be inactive, initally.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  (getInitialFacetActivity): Do not let Facets ending with a 'q'
+	  enter in active state.
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Fix broken datacage config.
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Configured to include correct id to
+	  clone artifact that produces w-diff.
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Revert correct behaviour of Annotations (the small axis tick shall
+	  always be drawn). Minor refactoring.
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Added configuration to include differences
+	  in datacage when longitudinal sections are shown (yet not
+	  functional).
+
+2011-10-25  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml: Cosmetics.
+
+2011-10-24  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* doc/conf/themes.xml: Added theme for w_differences facets.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java
+	  (createSecondAxisRange): Survive parameter-nullness for now.
+	  Added documentation.
+
+2011-10-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/ThemeAccess.java:
+	  New. Caching wrapper around an XML document theme. It uses ThemeUtil
+	  to access the values and stores them in instance variable.
+	  Background: ThemeUtil use XPath a lot which is expensive.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Use ThemeAccess to style the annotations.
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java:
+	  Removed some XPath strings. They are in ThemeUtil.
+
+2011-10-21  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  Added methods to parse further attributes.
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java:
+	  Apply the theme attributes and use ThemeUtils to get the attribute values.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Removed the spamy debug output.
+
+2011-10-21	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	To obtain the size of a diagram it is rendered twice. The
+	second time the generated image is omitted so the concrete
+	rendered image is not needed. To save CPU cycles in this pass 
+	the image is rendered to to /dev/null Graphics2D object.
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java:
+	  Added boolean system property "info.rendering.nop.graphics" (default: false).
+	  With this property set the info rendering is done via a new
+	  NOPGraphics2D opbject which does not render the image.
+
+	* src/main/java/de/intevation/flys/java2d/NOPGraphics2D.java:
+	  New. Implements java.awt.Graphics2D trivial empty methods.
+	  This prevents rendering.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Commented out spamy debug output
+
+2011-10-21  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Draw the text background and use orientation attribute.
+
+2011-10-21  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Renamed 'textbackground' to 'backgroundcolor' to have 'color' in the
+	  attribute name.
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  Process text attributes correctly.
+
+2011-10-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/CrossSectionKMService.java:
+	  New. Service to lookup the Nth nearest neighbors for a set of given
+	  cross section ids and kms.
+	
+	* doc/conf/conf.xml: Registered service.
+
+	* doc/conf/cache.xml: Cache config.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  Removed superfluous imports.
+
+2011-10-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  Added methods to extract further attributes from theme.
+
+2011-10-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added main value themes for longitudinal section
+	  charts.
+
+	* src/main/java/de/intevation/flys/themes/ThemeMapping.java: New. This
+	  class stores the name of a facet, the related theme and a pattern
+	  string.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Read the pattern string and store a list of ThemeMapping objects in the
+	  FLYSContext.
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: Modified
+	  getTheme() which now takes the FLYSContext, the name of a facet and an
+	  optional pattern string. Now, we can have specialized Themes for each
+	  chart type. E.g. the facet "longitudinal_section.w" maps the default
+	  Theme for W lines in longitudinal section charts. If the optional
+	  pattern string matches the pattern ".*(HQ1000)(\D.*)*", the ThemeFactory
+	  will return the Theme "LongitudinalSectionW_HQ1000".
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Use the description of a facet as pattern string to get the relevant
+	  Theme from ThemeFactory.
+
+2011-10-20  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Added new theme attributes.
+
+2011-10-19  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix WDifference plots where masterartifact has no range set.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  (addSubtitles): Overridden, the master artifact has no
+	  range.
+
+2011-10-19  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix masterartifact in collections in cases where the original
+	masterartifacts facets do not come first in certain list. Query
+	'backend'/db instead.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Set 'real' master artifact, defined to be oldest belonging to this
+	  collection.
+
+2011-10-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  Modified the method signature of createStaticData() which now also
+	  requires a FLYSArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Override createStaticData() to create titles for Qs manually - we want
+	  to display the named main values if existing for the selected Qs.
+
+2011-10-19  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  ComputedDischargeCurveW and ComputedDischargeCurveQ inherit attributes from
+	  theme 'Text'.
+
+2011-10-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Fix for flys/issue316
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java: Added
+	  constructor to not sort the data.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Use the not sorting constructor of StyledXYSeries.
+
+2011-10-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  moved the code to create WSP W and Q facet names to FLYSUtils.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: New functions for
+	  creating WSP W and Q facet names and for querying a named main value
+	  based on a given gauge and value. The names of W and Q facets will now
+	  depend on the selected Q and Q mode: if the mode is "q at gauge" and a
+	  named value is found for the given value, the facet's name contains the
+	  named value instead of the value itself.
+
+2011-10-19  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix datacage configuration to let client load ZUS and flood
+	protections.
+
+	* doc/conf/meta-data.xml: Use 'ids' instead of 'id' to help client.
+
+2011-10-19  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  Added methods to parse text attributes from theme document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java,
+	  src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Apply a theme to axis annotations.
+
+2011-10-19	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Create (data) Label from data string (client will be adjusted to send
+	the name).
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  Create label from input data string, documentation added, junk
+	  removed.
+
+2011-10-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java(zoom):
+	  Do not crash if no axis is given.
+
+2011-10-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added
+	  getFormat() which extracts the format string from XML request document.
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java: Adapted
+	  method signatures of exportImage(), exportSVG() and exportPDF(). All
+	  methods now take a CallContext object which stores extra chart export
+	  parameters.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Enabled PDF and SVG chart exports based on the "format" string given in
+	  the XML request document.
+
+2011-10-18  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Simplify rendereing W(Q)Kms in WDifferencesCurveGenerator.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  (doWOut, doWOut): Simplified.
+
+2011-10-18  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Render zus and flood-protections in WDifferences-diagrams.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Added basic respect of "other.wkms"- facets.
+
+
+2011-10-18  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Adjusted Datacage-Configuration to fetch "extra longitudinal...."
+	(.zus) - waterlevels in certain case (in system-part).
+
+	* doc/conf/meta-data.xml: Adjusted to present extra-kms with
+	  staticwkms factory in certain case.
+
+2011-10-17  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/themes.xml:
+	  Added new virtual theme for text with the attributes 'font', 'textcolor'
+	  and 'textsize'.
+
+2011-10-18  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Give StaticWKmsArtifacts proper names, and pre-deselect them.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  (getInitialFacetActivity): Overridden to let facets enter plot
+	  inactively.
+	  (setup): Give Facets the name of the Wst.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  Refactored to expose getWKmsName separately.
+
+2011-10-18  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Adjusted Datacage-Configuration to fetch flood-protections when
+	waterlevels are requested (in system-part).
+
+	* doc/conf/meta-data.xml: Adjusted to present flood-protections with
+	  staticwkms factory in certain case.
+
+2011-10-18  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Register staticwkms factory.
+
+	* doc/conf/conf.xml: Register staticwkms factory to spawn StaticWKms-
+	  Artifacts.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fetch name of static WKms.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  Removed dependence on "kind", but fetch name for created WKms.
+	
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  Remove dependence on Kind.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Store parameterization in data, not in Artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  Resolve col_pos and wst_id field, use data instead.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add convenience-method to add defaultdata (string).
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  (addStringData): Add Default (String) Data .
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add a new Artifact and Facet (StaticWKmsArtifact, WKmsFacet) to
+	access WKms obtainable with the WKmsFactory.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java:
+	  New, artifact with single state to get WKms from WKmsFactory.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFacet.java:
+	  New Facet to display W over km.
+	
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new type name.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add WKMsFactory to access 'static' wst-data.
+
+	* src/main/java/de/intevation/flys/artifacts/model/StaticWKmsCacheKey.java:
+	  Cache Key for the static WKms data.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java:
+	  New, creates WKms from wst-id and column. Does not yet fetch the
+	  name.
+
+	* doc/conf/cache.xml: Added cache configuration for static wkms data.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics, docs.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java:
+	  Cosmetics, docs.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java:
+	  Added constructor that takes name, docs.
+
+2011-10-18	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix issue that Annotations do not come with theme/style.
+	
+	* doc/conf/themes.xml: Added "Annotations" default style.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Set style of annotations, minor cosmetics.
+
+2011-10-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Made inner class ThemeList static.
+	  s/new Integer(small)/Integer.valueOf(small)/
+
+2011-10-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  The inner class ThemeList makes now use of ManagedDomFacet to read the
+	  attributes of Facets saved in the Collection's attribute.
+
+2011-10-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	flys/issue314: Variables in datacage are now case insensitive.
+	(H2 returns meta data variables uppercase)
+
+	* doc/conf/meta-data.xml:
+	  Made a statement more precise. Added some debug output.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/CompiledStatement.java:
+	  Use uppercase variable names.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/StackFrames.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/FunctionResolver.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/ResultData.java:
+	  Variables are now treated as uppercase.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java:
+	  Input variables are now treated uppercase.
+
+2011-10-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedFacetAdapter.java:
+	  Prepared Facets to support a "visible" attribute.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java: Adapted
+	  the constructor call of ManagedFacetAdapter.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  The inner class ThemeList now supports the "visible" attribute of
+	  ManagedFacets.
+
+2011-10-17  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java:
+	  Added method to apply line type.
+
+	* doc/conf/themes.xml:
+	  Changed initial default value for line type.
+
+2011-10-17  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue226 (W-INFO: Dauerlinienberechung /Abbbildung x-Achse)
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Set the upper bound of these charts to 364.
+
+2011-10-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java:
+	  Added a function that returns a Gauge based on its name.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java:
+	  Create proper descriptions for facets.
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java: Added a
+	  setter for labels.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Use the string returned by Facet.getDescription() as series names. The
+	  ThemePanel and the Legend will always display the same titles for curves
+	  now.
+
+
+2011-10-17	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix flys/issue363 (W-INFO/ Abflusskurve, Extremwert-Rendering).
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  (parseLineWidth): New. Get line width from Document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  When adding annotations, parse line width from theme, set it.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  When painting, set Paint and stroke early enough.
+
+2011-10-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Put the
+	  config directory into the Velocity context. It is available as
+	  '$CONFIGDIR' in templates.
+
+	* doc/conf/mapserver/mapfile.vm: Set the debug file to
+	  "$CONFIGDIR/flys-user-wms.log" and added a LEGEND section.
+
+	* doc/conf/mapserver/wsplgen_class.vm: Adapted the class names. Those
+	  names are displayed in the image served by GetLegendGraphic.
+
+2011-10-14	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Add possibility of programmatic configuration of initial "activity"
+	state (active or inactive) of (Managed)Facets by introducing
+	FLYSArtifact.getInitialFacetActivity. This method shall be overriden
+	by subclasses where Facets are wanted to come to live inactive.
+	Artifacts will be asked only once how the MangedFacet should come to live,
+	namely when AttributeWriter finds a genuinely new Facet.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java
+	  (getInitialFacetActivity):
+	  New function to let Artifact decide whether a ManagedFacet shall
+	  initially be set to active or inactive.
+
+	 * src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	   Accept database in constructor. For genuinely new Facets, spawn its
+	   mother artifact and ask whether the (Managed)Facet shall be active
+	   or inactive (initially).
+
+	 * src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java
+	   (buildOutAttributes): Pass database to AttributeWrite (which needs it
+	   to spawn artifacts), rename items parameter to reflect content.
+
+2011-10-13	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics, removed obsolete imports.
+
+	* src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Removed obsolete imports.
+
+2011-10-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Added a further attribute "mark.selected" to the barriers feature type.
+	  This attribute is used in the client, where we are not able to remove it
+	  properly. Reading the GeoJSON string without this attribute is no longer
+	  possible... strange!
+
+2011-10-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added i18n for label of wdiff "pair select" states data.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n for
+	  state.winfo.waterlevel_pair_select .
+
+2011-10-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Addressed "Wrong labels" [6] part of flys/issue371 (W-INFO / Differenzen:
+	Anmerkungen zur Umsetzung) .
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesExporter.java:
+	  Adjusted variable names to avoid conflict in subclasses, adjusted
+	  default value for i18n string.
+
+2011-10-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Cosmetics, docs.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Cosmetics: Space after full stop in commments, slightly improved
+	  documentation, added one debug message.
+
+2011-10-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix remainder of flys/issue304 (Erweiterte Funktionen W-Differenzen) .
+
+	* doc/conf/artifacts/winfo.xml: Removed obsolete data of
+	  WDifferencesState.
+
+	* src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java:
+	  Re-enable facet-filtering, but adjust filters before that happens
+	  (former longitudinal_section output is now w_differences output).
+
+2011-10-11	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Added wdiff-chart translations.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n for wdiff.
+
+2011-10-11	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Expose translateable Strings as constants.
+
+2011-10-11	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	Fix most labels in w-differences charts.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Remove most static final i18n-variables in favor of direct String
+	  usage or usage of methods. By this, allow easier adoption of labels
+	  in subclasses.
+
+2011-10-11  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue383 (Zweite Y-Achse wird beim Zoomen/Verschieben nicht angepasst.)
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Call adjustAxes() before applying zoom settings with autoZoom(). We need
+	  to add new y-axes first before we adjust their ranges.
+
+2011-10-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  s/new Integer(small)/Integer.valueOf(small)/
+
+2011-10-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Do not fire change events for each and every data point added.
+
+2011-10-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix WDifferences with more than one Pair (crashed due to incorrect index for
+	facets.)
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Fix wrong index for difference facets (allows for more than one
+	  difference facet per artifact without crashes).
+
+2011-10-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Use slightly modified doWOut-implementation from
+	  LongitudinalSectionGenerator (here need to add Ws to different axis).
+
+2011-10-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  Fix import/reference.
+
+2011-10-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Let WaterlevelPairSelectState include diffids-data (if any) to enable
+	future work on repopulation of Grid in GUI when jumping back
+	(reparameterization).
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  (createItems): Override to include old data.
+
+2011-10-10  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue150 (Diagramm: Anzeige von W bergauf)
+	flys/issue345 (W-INFO / Wasserspiegellagenberechnung, Diagrammausgabe)
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Corrected the determination to invert the x axis, so that waterlines
+	  will start with their highest value at the left and end with their
+	  lowest values at the right.
+
+2011-10-10  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java:
+	  Cosmetics, added an (@Override) annotation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CalculationResult.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Cosmetics, added documentation.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Cosmetics, whitespace after full stops in comments, added
+	  (@Override) annotation.
+
+2011-10-10  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue220 (Diagramm: Achsenbeschriftungen an verschiedenen Achsen müssen gleich aussehen)
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Adjusted the label font of the 2nd y-axis - now, both axes labels look
+	  equal.
+
+2011-10-10  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue189 (WINFO/Dauerlinie: Sortierung der Berechnungsausgabe nach Dauerzahlen aufsteigend)
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQDay.java: Added a
+	  method that determines if the items (days) in this object are increasing
+	  or not.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  Changed the order of the CSV export - the highest day is at the top of
+	  the export; the lowest day is at the bottom.
+
+2011-10-07	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Worked on flys/issue150 (Diagramm: Anzeige von W bergauf).
+	Still does not work in all cases.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java (generateChart()):
+	  Reordered calls to ensure that the inversion of the x axis is
+	  not eliminated by other chart generation steps as a side effect.
+
+	 * src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	   Assuming that JFreeChart is inverting axis automatically if the
+	   KMs are reversed ordered only do invert only in some situations.
+
+	   Do not invert axis for Q (@Ingo: This is wrong! We must do this
+	   if we are only displaying the Qs and the Ws are deactived).
+
+2011-10-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Take care on empty ranges while preparing ranges for single points.
+
+2011-10-07  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue114 (W-INFO: Wasserspiegellagenberechnung / Ort (Spezialfall: Generierung eines Diagramms bei punkthafter Berechnung))
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Expand
+	  ranges for x and y axes if there is just a single point in a series -
+	  JFreeChart requires a range where lower <> upper.
+
+2011-10-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RangeState.java,
+	  src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Add whitespace after full stop in comments, minor doc improvements.
+
+2011-10-07  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue353 (W-INFO / Wasserspiegellagenberechnung, Diagramm)
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted the process of collecting outs for Artifacts/Facets. We will now
+	  call OutGenerator.doOut() for each Artifact and Facet - never mind if
+	  the facet is activated (visible) or not. The OutGenerator should decide
+	  on its own whtat to do with facets which are "marked" as _not_ visible.
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ReportGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/exports/ATExporter.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java:
+	  Adapted the signature of OutGenerator.doOut(). There will be a new
+	  boolean parameter "visible" that determines if the facet specified in
+	  this method is visible for this output or not.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Now,
+	  that we walk over every Artifact/Facet, we are able to collect min/max
+	  data for all axes. We store these information and use them to set the
+	  ranges of x and y axes. The result of this: a chart can have proper axes
+	  set without any data in it.
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java: Use
+	  min/max ranges stored while calling doOut() for each Artifact/Facet
+	  instead of fetching those information from chart's Datasets (which could
+	  be null).
+
+2011-10-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: Changed the max
+	  number of digits for AT exports from 0 to 2.
+
+2011-10-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java: Get rid
+	  of buggy first line code.
+
+2011-10-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	fixed flys/issue201
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Replace >= with > in km index lookup because last km was not found.
+
+2011-10-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	fixed flys/issue177
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Revert rev2245. Code works fine now! :-)
+
+2011-10-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java: Stores a
+	  list of XYTextAnnotations instead of FLYS specific Annotations. This
+	  makes this class suitable for other annotation types as well.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java:
+	  Both facets' getData() will now return an instance of FLYSAnnotation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java:
+	  Create XYTextAnnotations used to instantiate an object of
+	  FLYSAnnotation.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Removed the code to add annotations to the plot. This task is general
+	  enough to move this code to parent class.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Instances of this class are now able to store - besides first and second
+	  axes datasets - a list of annotations. This annotations are added to the
+	  plot after the datasets have been added. To support LegendItems for
+	  those annotions, it was necessary to create a the LegendItemCollection
+	  by ourself. This work is done while applying the themes for each series
+	  in the chart.
+
+2011-10-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Removed code duplication of guessWaterIncreasing()
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKms.java(allKms, allWs):
+	  Added methods to fetch all kms and all ws.
+
+	* src/main/java/de/intevation/flys/utils/DataUtil.java: Generalized to 
+	  get WKms as arguments.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQ.java: Implements
+	  the extended WKms interface.
+
+	 * src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	   Uses the generalized DataUtil.guessWaterIncreasing().
+
+2011-10-05  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue347 (W-INFO / Wasserspiegellagenberechnung, Längsschnittdiagramm)
+	flys/issue303 (Keine Streckenfavoriten, wenn nur Q im Längsschnittdiagram ausgewählt)
+	flys/issue353 (W-INFO / Wasserspiegellagenberechnung, Diagramm)
+
+	* src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java: New. A
+	  wrapper for Annotations which allows us to provide a description for a
+	  set of annotations.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java:
+	  The getData() will now return an instance of FLYSAnnotation that wraps
+	  the Annotations returned by the AnnotationArtifact. The lebel of
+	  FLYSAnnotation is the description of this Facet.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Modified the way to add Annotations. We will no longer create an empty
+	  series to support a LegendItem for a set of Annotations, but we will add
+	  a LegendItem manually to the LegendItemCollection of the plot. In
+	  addition, we are now able to display annotations if one of the two
+	  y-axes are missing. If there are no y-axes existing, we are not able to
+	  display annotations yet.
+
+2011-10-05	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Lifted the wrong point. Now all backjump corrections look fine. :-)
+
+2011-10-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Fixed bugs and make use of a cache for annotations now.
+
+2011-10-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Worked on flys/issue31
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Simplified the code a lot. Needs testing. Maybe flys/issue31 is gone
+
+2011-10-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Fetch the WstUnit value from river - the Wst itself no longer supports a
+	  Unit iself.
+
+2011-10-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java,
+	  src/main/java/de/intevation/flys/utils/DoubleUtil.java: Moved some generic
+	  double array code to DoubleUtil.
+
+2011-10-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/DoubleUtil.java (interpolateSorted):
+	  Added code to linear interpolate double values in a sorted array.
+	  Keys and values are given as double arrays. Keys need to be sorted.
+
+2011-10-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  Removed superfluous imports.
+
+2011-10-04  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue330 (Dauerlinie kann nicht berechnet werden)
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation3.java:
+	  Add a problem if no data was found for duration curves.
+
+2011-10-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/Theme.java,
+	  src/main/java/de/intevation/flys/themes/DefaultTheme.java: Added
+	  getter/setter methods to provide a facet (string) and index (int). Both
+	  values are written as attribute to the Theme's XML representation.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Set the "facet" and "index" values of themes.
+
+2011-10-04  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue346 (W-INFO / Anzeige der Höheninformation)
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Modified x and y axis title. The x axis title is now "RIVERNAME-km"; the
+	  y axis title is now "W[WST_UNIT]" where WST_UNIT depends on the unit of
+	  the WST.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Modified i18n expressions for
+	  x and y axis of longitudinal section charts.
+
+2011-09-30  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue351 (W-INFO / Wasserspiegellagenberechnungen)
+
+	* src/main/java/de/intevation/flys/utils/DoubleUtil.java: Modified the
+	  explode() function that returns a list of values specified by min, max
+	  and an interval. If the last value, determined by the interval, is
+	  bigger than the max value, it is not included in the result list.
+
+2011-09-30  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	flys/issue334 (Querprofil-Diagramm: Ausgabe dieses Diagrammtyps
+	möglich, obwohl WSP Berechnung keine Ergebnisse liefert)
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  (compute): Add Facets regarding CrossSections only if data available.
+
+2011-09-29  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue176 (Diagramm: Benennung eines Abflusses bei gewählter Höhe am Pegel)
+	flys/issue349 (W-INFO / Wasserspiegellagenberechnung, Längsschnittdiagramm)
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Create titles for W and Q waterlevel facets with proper fractions.
+
+2011-09-30  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  (createStaticData, getLabels): Create proper labels for differences.
+
+2011-09-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	flys/issue244 (WINFO: Export von AT-Dateien im ersten Teil unterschiedlich)
+	flys/issue332 (W-INFO / Berechnung Abflusskurve, Export, FLYS 2.5)
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java: Due to a rounding
+	  issue the w's of the first line underun the minimal w of the curve at times.
+	  An extra test was introduced to suppress the output of the q's of the wrong w's.
+
+2011-09-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	Use StringUtil.wWrap , fix wrong loop, minor refac and cosmetics.
+
+2011-09-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	Extracted/use StringUtil.wWrap .
+
+2011-09-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java:
+	  Log the gauge which has been determined.
+
+2011-09-28  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue332 (W-INFO / Berechnung Abflusskurve, Export, FLYS 2.5)
+
+	* src/main/java/de/intevation/flys/exports/ATExporter.java: Store the
+	  master Artifact which is set via setMasterArtifact(). This is required
+	  for meta information used while preparing the header row of AT exports.
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java: Print a header
+	  row into the AT export for being compatible with desktop FLYS.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added header row for AT
+	  export files.
+
+2011-09-28  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue328 (W-INFO / ÜSK: Auswahl der Wasserspiegellage / Auswahlunterstützung)
+
+	* doc/conf/meta-data.xml: Added an out 'waterlevels' that might be used to
+	  fetch user specific waterlevels (same as longitudinal sections, but
+	  without Q facet).
+	  In addition, the system specific datacage stuff is now fetched, when:
+	  a) no user-id is given
+	  b) a user-id is given and there is a parameter 'load-system'
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	  If the label of the WQKms object specified by the waterlevel selection
+	  begins with a "Q", the label is wrapped into a "W()", e.g. "W(Q=1200)".
+
+2011-09-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Made
+	  feed() able to remove existing data items from Artifact's data pool.
+	  Therefore, the value for the item which should be removed needs to an
+	  empty string.
+
+2011-09-28  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Extracted StringUtil.unbracket from WaterlevelSelectState.strip.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	  Extract and use StringUtil.unbracket, minor doc.
+
+2011-09-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Changed access of getCurrentState() from protected to public.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java: We
+	  use the configured Outputs instead of the actuel Outputs of an Artifact
+	  to make recommendations, now. This has the bad side effect of giving
+	  recommendations for Outputs that we might _NOT_ be able to produce. But
+	  otherwise, we would not be able to give recommendations for states with
+	  long calculation times that start background threads for calculation (as
+	  WSPLGEN caluclations does).
+
+2011-09-27  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Enable "auto-scaling" axis for waterlevels in WDifference-plots.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  (createSecondaryAxis, zoomY): Refactored to allow modification in
+	  siblings.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  (createSecondaryAxisRange): Override to achieve expected behavior.
+	  Also adjusted label.
+
+2011-09-27  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	In W-Difference Calculation, respect indices of selected facets.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  (computeAdvance): Respect index of selected facets.
+
+2011-09-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	flys/issue317: (Querprofil-Diagramm: Referenzen auf CrossSectionApp entfernen)
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Removed dependency to demo app.
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Refactored to use logic from the models.
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Moved some logic from the demo app to this model.
+
+2011-09-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/wsplgen/FacetCreator.java: New. Code
+	  from FloodMapState moved to its own class with the intent, to use it in
+	  classes different from FloodMapState.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Removed the inner class FacetCreator.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java: Stores
+	  an instance of FacetCreator.
+	  NOTE: Maybe we should move the WSPLGEN parameters into an own class
+	  which might be serializable.
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java: Use the
+	  FacetCreator instance stored in the WSPLGENJob to create a new WSPLGEN
+	  facet if the calculation was successfully (without errors). Finally, the
+	  facets of FacetCreator are added to the Facet list of the FLYSArtifacts.
+
+2011-09-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Put Artifact in background mode first before starting WSPLGEN, otherwise
+	  a very fast errors (call CallContext.afterBackground() before Artifact is
+	  in Background mode) might lead to an inconsistent state.
+
+	* src/main/java/de/intevation/flys/wsplgen/ProblemObserver.java: Repaired
+	  broken error num parsing.
+
+2011-09-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/wsplgen/ProblemObserver.java: Track
+	  critical errors as well (improved regular expression for errors).
+
+2011-09-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* ChangeLog: Fixed whitespace usage.
+
+2011-09-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/RiverService.java,
+	  src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Removed superfluous imports.
+
+2011-09-27  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue68 (Diagramm: Werte an der Y-Achse benötigen i18n)
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added a
+	  method to retrieve the current/preferred locale specified by CallMeta.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Introduced two methods localizeDomainAxis() and localizeRangeAxis().
+	  Both methods of this class override the NumberFormat used to format axes
+	  numbers. Those methods are called by localizeAxes() - which has private
+	  access - for each domain and range axis of the current XYPlot.
+
+2011-09-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Number of cached annotations was much
+	  to low.
+
+2011-09-27  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Improved w-differences diagram generation where multiple differences
+	can be shown.
+
+	* src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java:
+	  New facet type.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Employ new DifferenceCurveFacet, return CalculationResult that can
+	  store more than one WKms.
+
+2011-09-27  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue320 (ÃœSK:Mapserver hat Probleme beim Shapefilepath mit "../" im Pfad)
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Use
+	  File.getCanonicalPath() to substitute "../" in shapefile directories.
+
+2011-09-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  The extent of the WSPLGEN result layer is now specified by the extent of
+	  the CrossSectionTracks that matches the start and end kilometer of the
+	  WSPLGEN calculation.
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: New function
+	  that creates the OpenLayers bounding box based on two Geometries.
+
+2011-09-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Improved w-differences diagram generation with included "absolute"
+	values.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Fixed one (of two) incorrect labels.
+	  (doWaterlevelOut): Survive non-found gauge, fetch kilometer and w
+	  instead of values for w and q.
+
+2011-09-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Add positional-conflict-awareness when merging Facets for a
+	FLYSArtifactCollection. First come first serve.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  (writeFacets): First, sort incoming facets into 2 groups:
+	  "genuinely new" and "already there", then for each new check
+	  whether the position is already taken. If so, push "up" (position++)
+	  until no conflict exists anymore.
+
+2011-09-26  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue296 (Karte: Bezeichnungen verbessern)
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  I18N of the WSPLGEN and barriers facets (WMS layers).
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added strings for the wsplgen
+	  and barriers WMS layers.
+
+2011-09-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix (revert) access to position in ManagedDomFacet.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java:
+	  (getPosition): Do not add prefix when querying position ("pos")
+	  attribute, add a logger for faster future debugging.
+
+2011-09-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Squash positional conflict-bug.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  (mergeFacets): Removed, replaced in parts by pickFacet.
+	  (pickFacet): New, return facet to be added to document.
+	  Documentation added.
+
+2011-09-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Squash a bug about wrongly named "art:pos" attribute in ManagedDomFacet (was
+	"pos"). Added documentation from commit message.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java:
+	  Added documentation (commit message with minor adjustments).
+	  (getPosition, setPosition): Include PREFIX in attribute name.
+
+2011-09-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Refactoring, doc.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  (mergeFacets): Removed, replaced in parts by pickFacet.
+	  (pickFacet): New, return facet to be added to document.
+	  Documentation added.
+
+2011-09-23  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics, docs.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  Documentation added.
+
+2011-09-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Subclasses the FLYSService now - this should improve the database
+	  connection handling.
+
+2011-09-22  Bjoern Schilberg <bjoern.schilberg@intevation.de>
+
+	* doc/mapserver/mosel-mapfile.map:
+	  Full blown mosel wms mapfile.
+
+2011-09-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/FLYSService.java:
+	  New. A subclass of DefaultService which is used in FLYS to init and
+	  shutdown database connections.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Centralized the initialization and shutdown of database connections.
+	  These services now subclass FLYSService which handles the database stuff.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java: The
+	  current database connection is not closed here - this is done in a
+	  Service or in the CallContext.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java: It's
+	  not necessary to create new database connections here. We already have
+	  an existing connection which is initialized by CallContext.
+
+2011-09-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DistanceOnlySelect.java:
+	  Override validate() of parent classes to suppress "step" validation
+	  which is not present in this state.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java: Added
+	  new method validateBounds() which really just validates a boundary
+	  without "step" parameter.
+
+2011-09-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added WaterlevelArtifact to accompany WINFOArtifacts in WDifferencesPlots.
+
+	* src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java:
+	  New WaterlevelArtifact.
+
+	* doc/conf/conf.xml:
+	  Added configuration for WaterlevelArtifact configuration (path to state-xml)
+	  waterlevel-factory.
+
+	* doc/conf/artifacts/waterlevel.xml:
+	  New, trivial state description for Waterlevelartifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelInfoState.java:
+	  New, only state for WaterlevelArtifact.
+
+2011-09-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics, docs.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java,
+	  src/main/java/de/intevation/flys/collections/AttributeWriter.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Cosmetics, documentation.
+
+2011-09-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added WaterlevelOut-processing ability to WDifferencesCurveGenerator.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Process LONGITUDINAL_W facets.
+
+2011-09-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Improved WDifferenceState in preparation to be able to deal with multiple
+	pairs for differences.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Prepare multiple-pairs-case.
+
+2011-09-21  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue315 (Überschwemmungsfläche: String bei Streckenauswahl)
+
+	* doc/conf/artifacts/winfo.xml: Added a new state for floodmap's range
+	  input. This state will accept a km range only, there is no step width.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DistanceOnlySelect.java:
+	  New. The state which is used to enter a km range with step width.
+
+2011-09-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics, improved debug output, doc.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Improved debug output.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java:
+	  Added documentation.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java,
+	  src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Removed commented code.
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java:
+	  Whitespace cosmetics.
+
+2011-09-21  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Avoid NullPointerException when drawing XYChart without data.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Guard calls to dataset to avoid NullPointerException.
+
+2011-09-21  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue325 (FLYS Client: Auswahl des DGM zeigt numerischen Wert an)
+
+	* src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java: Write
+	  a better label for the selected DEM into the static DESCRIBE. Use the
+	  name of the DEM file as label instead of the database id.
+
+2011-09-16  Bjoern Schilberg <bjoern.schilberg@intevation.de>
+
+	* doc/mapserver/fontset.txt:
+	  Added initial font set for km_annotation layer.
+	* doc/mapserver/symbols/symbols.sym:
+	  Added square symbol for km and fixpoint layer.
+	* doc/mapserver/saar-mapfile.map:
+	  Adjust styling in the flys karte-archiv way.
+
+2011-09-19  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.5
+
+	* Changes: Prepared changes for release.
+
+2011-09-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Removed
+	  needless imports.
+
+2011-09-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Improved datacage configuration for DEMs.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Query DGMs by the given ID in the FLYS data pool - not by given range
+	  values.
+
+2011-09-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Added a section for DEMs to the floodmap
+	  section.
+
+2011-09-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix build.
+
+	* src/main/java/de/intevation/flys/artifacts/state/WDifferencesState.java:
+	  Fix build.
+
+2011-09-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Take a given WaterlevelPair-String, load artifact and plot the diff.
+
+	* src/main/java/de/intevation/flys/artifacts/state/WDifferencesState.java:
+	  Load correct artifacts.
+
+2011-09-16  Bjoern Schilberg <bjoern.schilberg@intevation.de>
+
+	* doc/mapserver/*: Added inital mapserver configuration files.
+
+2011-09-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data.xml: Moved the system specific configuration into a
+	  macro to the top of the configuration document. Call this macro at the
+	  end of the user specific part and in the part that should contain the
+	  system specific stuff only!
+
+2011-09-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Defined createItem() here and adapted some method signatures, because I
+	  need the CallContext deeper in code than expected.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	  Write the name of the selected waterlevel into the static DESCRIBE.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java:
+	  Adapted method signatures that have been changed in DefaultState.
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Removed method createItem() which is now defined in the upper class
+	  DefaultState.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added string for selected
+	  waterlevel that is displayed in static UI.
+
+2011-09-16  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added state label for
+	  waterlevel selection.
+
+2011-09-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	  Validate incoming data string and strip brackets.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Fetch waterlevel data from external Artifact if existing. If no external
+	  Artifact is specified that provides waterlevel data, we gonna try to
+	  fetch it from the current Artifact.
+
+2011-09-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  Removed needless import which caused compile errors.
+
+2011-09-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a transform() method. This method should be used to transform
+	  input data in form of a string into a better data structure. This state
+	  provides a simple implementation which just returns a StateData object
+	  that contains exactly the input string.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Transform
+	  input strings using DefaultState.transform() before they are added to
+	  its data pool.
+
+2011-09-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Made collection solid for the case if it has no Artifacts.
+
+2011-09-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Fetch Artifact
+	  from ArtifactDatabase properly. Write better error logs if that process
+	  fails.
+
+2011-09-14  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Extend WDifferences branch to have calculations in dedicated, new state.
+
+	* doc/conf/artifacts/winfo.xml: Added new State and Transition in
+	  WDifferences-branch.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java:
+	  New state.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Specify to not take input, prepare getting other facets.
+
+2011-09-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java: This
+	  state now desires the UIProvider 'dem_datacage_panel'.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java:
+	  New. This state is used to define the desired UIProvider
+	  'wsp_datacage_panel'.
+
+	* doc/conf/artifacts/winfo.xml: Added a new way to start a WSPLGEN
+	  calculation when choosing the calculation type 'floodmap'.
+
+2011-09-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: Merge
+	  facets only if their name AND their owner artifact are equal.
+
+2011-09-14  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Partial Fix flys/issue304 (3) (Baseline).
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Show Baseline in WDifferencesPlot.
+
+2011-09-14  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix flys/issue310 (cross-section theme).
+
+	* doc/conf/themes.xml: CrossSection-Themes: profile thin, water blue.
+
+2011-09-14  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix flys/issue310 (cross-section theme).
+
+	* doc/conf/themes.xml: CrossSection-Themes: profile thin, water blue.
+
+2011-09-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Add an quick and simple error to the report if an error occured while
+	  WSPLGENJob creation.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added error messages for
+	  WSPLGEN job creation errors.
+
+2011-09-13  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix crash when drawing StickyAxisAnnotation when no range was given.
+	User-directed issues remain ( flys/issue303 ).
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java
+	  (draw):
+	  Guard access to domainAxis, rangeAxis and the corresponding ranges.
+	  Warn and exit if any was null.
+
+2011-09-13  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue290 (Karte: Eingabe von Differenzen zw. WSP und Gelände findet
+	keine Ausprägung in der Karte)
+
+	* doc/conf/mapserver/wsplgen_class.vm: Added styles for DIFF attribute.
+
+	* doc/conf/mapserver/mapfile.vm: Set debug default to '5'.
+
+2011-09-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Add a first WSPLGEN status message (notifies the user about a
+	  queued job) after the job has been added to the Scheduler.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added english and german
+	  status message text.
+
+2011-09-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Write
+	  shapefiles only if there are features for it existing.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  A WSPLGEN facet is only created, if the calculation was successfully
+	  added to the Scheduler. A barrier facet is only created if the WSPLGEN
+	  calculation has been added to the scheduler AND if there are digitized
+	  geometries existing.
+
+2011-09-12  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Copied and slightly modified implementation of guessWaterIncreasing from
+	WQKms to (new) DataUtils. Accidentally commited usage in last commit, to
+	correct orientation of diagram (invert x axis).
+
+	* src/main/java/de/intevation/flys/utils/DataUtils.java:
+	  New file with guessWaterIncreasing implementation from WQKms,
+	  slightly adjusted.
+
+2011-09-12  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java:
+	  Cosmetics.
+
+2011-09-12  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added CSV Export for W-Differences.
+
+	* doc/conf/conf.xml: Added Exporter.
+	
+	* doc/conf/artifacts/winfo.xml: Removed transition over distances
+	  state, added export outputmode and facet.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesExporter.java:
+	  New, CSV-Exporter for WDifferences.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Add export facet.
+
+2011-09-12  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java:
+	  Cosmetics, docs.
+	
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  Cosmetics.
+
+2011-09-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Sourced the code to append a concrete data item to the static DESCRIBE
+	  part out to an own method (appendStaticData()).
+
+	* src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java:
+	  Override appendStaticData() to suppress the GeoJSON string to be
+	  included in the static DESCRIBE.
+
+2011-09-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java,
+	  src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Removed needless imports.
+
+2011-09-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CalculationMessage.java:
+	  New. A subclass of Message (in the Artifacts system). It stores a
+	  string message and a progress (in form of step x/y).
+
+	* src/main/java/de/intevation/flys/wsplgen/JobObserver.java: The observer
+	  now writes background messages into the artifact system using the
+	  CallContext.addBackgroundMessage(). We use instances of
+	  CalculationMessage here, that makes the WINFOArtifact able to put
+	  progress information into the Artifact's DESCRIBE as well.
+	  
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Write
+	  status message and progress information into the DESCRIBE if the
+	  Artifact has started a background process.
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix flys/issue280 .
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Do not draw boxes around annotations.
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix flys/issue279 .
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Fix behaviour in various thinkable malconditions.
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Made one case of cross section fetching more robust.
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix Facet name and Legend of W-Differences, also insert state to specify
+	distance.
+
+	* doc/conf/artifacts/winfo.xml: Add additional transitions to walk over
+	  distances state.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  Set facets description.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  Take facets description as legend.
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added configuration to kick-in w-differences branch of winfo.
+
+	* doc/conf/conf.xml: Register new (w-differences) OutputGenerators.
+
+	* doc/conf/artifacts/winfo.xml: Register new state and transitions.
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Added new (w-differences) calculation mode.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveInfoGenerator.java:
+	  New file, implementing naive approach to display w-differences
+	  (accidentially omitted in last commit).
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added very stubby WDifferences State/OutGenerator for WINFOArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added w_differences facet type.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java:
+	  New file, implements naive approach to calculate w-differences (of
+	  calculations identified by hardcoded uuids!) and register respective facet.
+
+	* src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java:
+	  New file, implementing naive approach to display w-differences.
+
+2011-09-09  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Implement getArtifact(uuid,context) in FLYSUtils.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java (getArtifact):
+	  Added implementation, partially resolving a TODO. Added logger instance.
+
+2011-09-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENCalculation.java:
+	  New. This sublcass of Calculation saves warnings and errors that occur
+	  while WSPLGEN is running.
+	  Note, that the interface of this class doesn't exactly apply the interface
+	  of Calculation. Maybe, we should generalize this interface!
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java: Stores
+	  an instance of WSPLGENCalculation now. We use this instance to save
+	  warnings and errors.
+
+	* src/main/java/de/intevation/flys/wsplgen/ProblemObserver.java: Use the
+	  WSPLGENCalculation to save errors and warnings.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENReportFacet.java:
+	  New. This facet is used for WSPLGEN reports. It stores an instance of
+	  WSPLGENCalculation which saves ERRORS and WARNINGS that occur while
+	  WSPLGEN execution.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Create a WSPLGENReportFacet for WSPLGEN reports.
+
+	* doc/conf/conf.xml: Added an OutputGenerator 'report' for WSPLGEN
+	  reports.
+
+	* doc/conf/artifacts/winfo.xml: Added an output 'report' for WSPLGEN
+	  reports.
+
+2011-09-08  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/math/WKmsOperation.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Cosmetics, docs.
+
+2011-09-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/wsplgen/JobObserver.java: Small
+	  adjustments that makes it easier to subclass this observer.
+
+	* src/main/java/de/intevation/flys/wsplgen/ProblemObserver.java: New. A
+	  sublcass of JobObserver which analyses the WSPLGEN output for errors and
+	  warnings.
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java: Use
+	  JobObserver and ProblemObserver to track the whole WSPLGEN output and
+	  print number of errors/warnings to log output.
+
+2011-09-08  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added translation of w_differences.
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages.properties:
+	  Added "w_differnces" translation.
+
+2011-09-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Appended
+	  a new attribute 'background-processing' to the DESCRIBE of this
+	  Artifact. Its value is 'true' if this Artifact has started a background
+	  thread which has not finished yet - otherwise it is 'false.
+
+2011-09-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java,
+	  src/main/java/de/intevation/flys/utils/GeometryUtils.java: Moved the
+	  code to determine the extent of a river based on its axis to
+	  GeometryUtils.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Determine the extent of the selected river and set the WMSLayerFacet's
+	  extent attribute.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added Themeing support for CrossSection Diagrams.
+
+	* doc/conf/themes.xml:
+	  Added new themes and mappings, slightly modified longitudinalsection
+	  theme.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	In CrossSection Diagram fix subtitle to display the km of which the data is
+	actually displayed (maybe contrasting users wish).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  (getCrossSectionSnapKm): New method to fetch the actual km of crosssection.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Add correct km to charts subtitle.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix various display-issues like i18n in cross-section diagram.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Set description of facets to expected values.
+
+	* src/main/java/de/intevation/flys/artifacts/exports/CrossSectionGenerator.java:
+	  Fix i18n of chart title. Set subtitle to expected value, pass facets
+	  description to StyledSeries to see expected legend.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics, resolved refactoring todo.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java
+	  (compute, computeAdvance, computeFeed): Refactored, extracted method,
+	  resolving duplicate code and TODO.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix waterline "calculation" at given km. Chosen approach is
+	"head-through-wall".
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Some refactoring to do the same calculation twice easier and be able
+	  to "cross" waterline against correct profile data.
+
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix setting of kilometer for profile (not yet waterlevel) of cross section
+	diagram.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  (getCrossSectionData): Respect corss_section.km data; do naive linear
+	  search for profile data for this km.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java:
+	  Declare a ComputeType.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Implement computeFeed.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Minor cosmetics.
+
+2011-09-07  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DataFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/state/DefaultState.java:
+	  Cosmetics, docs.
+
+2011-09-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/barrier_polygons_class.vm,
+	  doc/conf/mapserver/barrier_lines_class.vm,
+	  doc/conf/mapserver/wsplgen_class.vm: Default Mapserver styles for
+	  barriers and WSPLGEN results. Those styles are only used as long as we
+	  don't have map specific themes (as already used in charts).
+
+	* doc/conf/mapserver/layer.vm: Implements a fallback mechanism for styling
+	  barrier lines/polygons and WSPLGEN results.
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java: Added a
+	  getStyle() method that currently returns "null". This method needs to be
+	  implemented when map themes are introduced.
+
+2011-09-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java: Renamed
+	  some attributes to make their job in the mapfile more obvious.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  new facet type for barriers.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java: Add
+	  a facet for the barriers layer.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: Parse
+	  barriers (lines and polygons) and create two new layers for each type -
+	  those layers are grouped.
+
+	* doc/conf/mapserver/layer.vm: Renamed attribute based on changes in
+	  LayerInfo and added support for Group-Layers.
+
+2011-09-06  Raimund Renkert <raimund.renkert@intevation.de>
+
+	* doc/conf/conf.xml:
+	  Added driver to database configuration for use with postgresql.
+
+2011-09-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/mapfile.vm: Removed FONTSET attribute and set quotes
+	  for SHAPEPATH.
+
+2011-09-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENFacet.java:
+	  Removed. We gonna use the WMSLayerFacet until now.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Add a WMSLayerFacet after we triggered the Scheduler to start a WSPLGEN
+	  calculation.
+
+2011-09-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/mapserver/layer.vm,
+	  doc/conf/mapserver/mapfile.vm: Small bugfixes and style improvements.
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java: Trigger the
+	  MapfileGenerator after a WSPLGEN job has finished regardless if it has
+	  been finished successfully or not.
+
+2011-09-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* pom.xml: Added Apache Velocity 1.7 for templating support.
+
+	* doc/conf/conf.xml: Added config options for mapserver/template relevant
+	  stuff.
+
+	* doc/conf/mapserver/mapfile.vm,
+	  doc/conf/mapserver/layer.vm: New. A default mapfile template and a
+	  template used for layers.
+
+	* src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java: New.
+	  This class is used while reading WMS layer relevant information from
+	  filesystem.
+
+	* src/main/java/de/intevation/flys/utils/MapfileGenerator.java: New. This
+	  thread is used for creating mapfiles for Mapserver. The MapfileGenerator
+	  runs in daemon mode (own thread) and creates mapfiles based on WMS
+	  layer relevant information read from filesystem.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java,
+	  src/main/java/de/intevation/flys/utils/FLYSUtils.java: Moved shapefile
+	  specific XPath expressions from FloodMapState to FLYSUtils which is a
+	  better place to use it in other classes (as MapfileGenerator).
+
+2011-09-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java:
+	  Bugfixed broken attribute assignment.
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java: Call
+	  CallContext.afterBackground() after a WSPLGEN job has finished to remove
+	  the background lock from Artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Call CallContext.afterCall(BACKGROUND) to lock the Artifact for
+	  background processing.
+
+2011-09-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/wsplgen/JobExecutor.java: New. This
+	  class is used to start WSPLGEN for a specific WSPLGENJob. The System
+	  property "wsplgen.bin.path" tells the JobExecutor where the WSPLGEN
+	  binary is placed (which means in general, the property points to the
+	  'wsplgen.exe').
+
+	* src/main/java/de/intevation/flys/wsplgen/Scheduler.java: New. This
+	  scheduler currently allows to start just a single WSPLGEN Thread. All
+	  WSPLGEN calculations should be started using Scheduler.addJob().
+
+	* src/main/java/de/intevation/flys/wsplgen/JobObserver.java: New. This
+	  thread reads log messages from WSPLGEN and listens for specific
+	  messages. It should be used to update status messages of the WSPLGEN
+	  calculation that is currently running.
+	  There is a System property that tells the JobObserver to log all WSPLGEN
+	  output to log4j: enable WSPLGEN output with "-Dwsplgen.log.output=true".
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java: Added
+	  the FLYSArtifact, the current working directory and the CallContext.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Use the Scheduler to start new WSPLGEN calculations.
+
+2011-09-02  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Add CrossSectionInfoGenerator.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionInfoGenerator.java:
+	  New, trivial implementation of CrossSectionInfoGenerator.
+
+	* doc/conf/conf.xml:
+	  Register CrossSectionInfoGenerator.
+
+2011-09-01  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cleanups of CrossSection*.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  New methods to retrieve name of utilized CrossSection.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Cleanup, get rid of copied unused method, documentation and more sensible
+	  translations.
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages.properties:
+	  Added cross_section* translations, also cleanups.
+
+2011-09-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Set the "typ" attribute of lines and polygons in barrier shapefiles.
+
+2011-09-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Set the Z values of line and polygon barrier geometries. Both barrier
+	  shapefiles will contain 3D geometries now.
+
+2011-09-01  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Get real data to display in CrossSection (although ignorant of
+	parameterization), making use of the showcase code of the CrossSectionApp-
+	Standalone application.
+
+	* doc/conf/artifacts/winfo.xml: Add new facet (~waterline) to state/out.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  New methods to get relevant data. So far just takes the first value of
+	  everything and assuming a waterlevel at 130m.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java:
+	  New Facet responsible of water level in cross section.
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Made some functionality publicly and statically available.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java:
+	  Update call.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added new
+	  Facet type.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Added new Facet to out.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Respect new facet and facets data.
+
+2011-09-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Close an open
+	  shapefile transaction and catch exceptions which are thrown while
+	  shapefile creation here. If there occured an error, this functions
+	  returns FALSE, otherwise TRUE.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Removed exception handling while shapefile creation - this is done in
+	  GeometryUtils now.
+
+2011-09-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Renamed the file for WSPLGEN required waterlevels to "waterlevels.wst".
+	  WSPLGEN did not work with the former "waterlevels.txt" file.
+
+2011-09-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: New static
+	  function that builds new SimpleFeatureTypes with additional attributes.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Write attributes "ELEVATION" and "KILOMETER" into the crosssection
+	  tracks shapefiles.
+
+2011-08-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a method
+	  stub that should return a FLYSArtifact based on a given UUID.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Write the selected WST file for WSPLGEN. Note, that this is the WST file
+	  of the current WINFO artifact. Furthermore, there is currently no way
+	  for the user to select a column from WST file, so we currently use the
+	  column that is written to WST file at first.
+
+2011-08-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Changed
+	  the parameter order of a compute(...) method. This makes me able to call
+	  this without a hash value.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java:
+	  Adapted the parameter order of the compute() call (see above).
+
+2011-08-31  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	More bones to CrossSection sceleton.
+
+	* src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java:
+	  New, yet trivial implementations of Factory and Facet for CrossSections.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Add new CrossSection- (instead of Default-)Facet.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Adjusted to use (touch) new Factory and Facet.
+
+2011-08-31  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Cosmetics.
+
+2011-08-31  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java,
+	  src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  Removed obselete imports.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Removed obselete imports, whitespaces.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Whitespaces, docs.
+
+2011-08-31  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Fix translations of Main Values Facets.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Do acrobatics to keep state transient but get translated title.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesQFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesWFacet.java:
+	  Changed constructor to get description (which is then already be translated)
+	  dynamically, adjust deepCopy.
+
+	* src/main/java/de/intevation/flys/states/StaticState.java:
+	  Adjust constructor accordingly.
+
+2011-08-31  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Add sceleton for CrossSection outs.
+
+	* doc/conf/artifacts/winfo.xml:
+	  Added new output mode to respective state of winfo artifact configuration.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Add new Dummy-Facet in state.
+
+	* doc/conf/conf.xml:
+	  Registered new OutputGenerator.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new CROSS_SECTION type.
+
+	* src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java:
+	  New, stubby skeleton for an CrossSectionGenerator.
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages.properties:
+	  Added cross_section translation, also cleanups (e.g. main values).
+
+2011-08-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java: Marked
+	  required parameters with a comment.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Export floodplains (german 'Talaue') to shapefile and write its file
+	  path into the WSPLGEN job.
+
+2011-08-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Search for a DGM that fits to the current river and km range and write
+	  its file path into the WSPLGEN job.
+
+2011-08-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Improved
+	  exception handling: exceptions are catched in GeometryUtils now.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Write river axis and crosssections to shapefiles and save shapefile
+	  pathes in WSPLGENJob.
+
+2011-08-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Use the coorect SRID for reading GeoJSON and writing line/polygon
+	  shapefiles.
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: Use a
+	  concrete coordinate system while feature type creation.
+
+2011-08-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* pom.xml: Added GeoTools 2.7.2 dependencies for Shapefile, GeoJSON and
+	  EPSG support.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java: Store
+	  'LIN' parameter in a list now. A WSPLGEN parameter might contain many
+	  LINs.
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: New functions
+	  to create FeatureTypes and to write shapefiles.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Write user specified barriers into a shapefile placed in the artifact
+	  directory.
+
+2011-08-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: Added a function
+	  that extracts the SRID defined in the global configuration file for a
+	  given river.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Removed the code that extracts the river SRID - use FLYSUtils instead.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Use FLYSUtils.getRiver instead of WINFOArtifact.getRiver.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java:
+	  Use FLYSUtils.getRiver instead of WINFOArtifact.getRiver.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Use FLYSUtils.getRiver instead of WINFOArtifact.getRiver.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Use FLYSUtils.getRiver instead of WINFOArtifact.getRiver.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Commit accidentally omitted result of refactoring (WINFO/FLYSUtils/getRiver).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Removed implementations of getRiver, update calls to use FLYSUtils.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Interpolate Q main values, generate interpolated W main values on the fly from
+	Q main values.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  (getGaugeDatum): removed, obsolete
+	  (getLocation): new, gets location
+	  Use WstValueTable to look up interpolated Qs of MainValues. In absence of
+	  the same functionality for Ws, generate W Main Values from Q Main Values.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Use new helper class FLYSUtils, minor refactorization.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Use new functionality of helper class, convenience of FLYSArtifact.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Refactored to use new Helper class FLYSUtils, moved getRiver-functionality
+	in there.
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java (getRiver):
+	  New function to retrieve river of an artifact, slightly modified from
+	  WINFOArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Removed implementations of getRiver, update calls to use FLYSUtils.
+
+2011-08-29  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Minor cosmetics.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Resolved a TODO.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Added some documentation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  Minor cosmetic.
+
+2011-08-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data.xml: Made it Oracle compatible.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/CompiledStatement.java:
+	  If running in debug mode log executed statements.
+	  Helps debugging Oracle connections.
+
+2011-08-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Set some WSPLGENJob parameters which are stored at FLYSArtifact.
+
+2011-08-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/FLYSUtils.java: New. This helper
+	  class should provide some basic FLYS stuff. Currently, there are functions
+	  that return the km range/location.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	  the methods that return the km range/locations. This is implemented in
+	  FLYSUtils now.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java: Adapted
+	  the WINFO method calls to retrieve the km range/locations - call
+	  FLYSUtils now.
+
+2011-08-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New method
+	  to destroy a single state.
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Cosmetic, remove debug output and comments, minor style adjustments.
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added limited themeing-support for MainValues.
+
+	* doc/conf/themes.xml:
+	  Added Q/W-MainValues themes.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Added limited theming support, add legend entry for main values.
+
+2011-08-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added a configuration node that points to the directory
+	  where shapefiles should be stored in.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java: New.
+	  This class is used to save/write the parameter for a WSPLGEN calculation.
+	  WSPLGEN's *.par files are written using the toFile() method.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  A directory for all WSPLGEN stuff is created in computeAdvance() - those
+	  directory and all its contained files are removed in endOfLife().
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/ThemeUtil.java:
+	  New Util to work with theme-related stuff.
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Use NamedDoubles instead of MainValues, try to adjust scale of Ws.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Use NamedDoubles instead of MainValues, generalize annotation handling, to
+	  allow easier reusability and themeing.
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Added convenience constructor, exemplary switch on bordered text.
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/meta-data.xml:
+	  Recommend MainValues for Computed discharge curves.
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	New NamedDouble class which implements a <String,double>-pair.
+
+	* src/main/java/de/intevation/flys/artifacts/model/NamedDouble.java:
+	  New, implementation of a double with a string or vice versa.
+
+2011-08-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Call
+	  State.endOfLife() for all States when endOfLife() of the Artifact is
+	  called.
+
+2011-08-26  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Also plot "W"-MainValues (on vertical axis), take correct parameters, but
+	do not convert to correct scale (cm vs NN+m).
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Naive attempt at allowing the vertical axis to be sticked at.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Store Q and W MainValues separately, add them to plot as annotations.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Serve the MainValues, parameterized on river and location, Q and W.
+	  Removed Facet-implementation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Updated Facet Types.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java:
+	  src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java:
+	  New, trivial facets, extracted from MainValuesArtifact.
+
+2011-08-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Call
+	  State.endOfLife() of each State that is no longer in the queue of the
+	  artifact when this artifact steps back to a previous state.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Plot MainValues delivered by MainValuesFacet in much the same ways than
+	  Annotations in LongitudinalSection plots.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Prepare further differentiation between Annotations that stick to X or Y-
+	  Axis, copied some positioning logic into StickyAxisAnnotation
+	  implementation.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Minor cosmetics.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added MainValue-Fetching-Capabilities to MainValuesArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java
+	  (initialize, getMinValues):
+	  Let MainValuesArtifact return "real" MainValues, although ignorant of all
+	  parameterization.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java
+	  (MainValueFacet):
+	  Improved and straightened implementation, added code-Annotations and Todos.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java
+	  (MainValueFacet):
+	  Improved and straightened implementation, added code-Annotations and Todos.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java
+	  (getState):
+	  Resolved multiple creation of state (yet not very clean).
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Cosmetics, docs.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Cosmetics.
+
+2011-08-25  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added new (MainValues) Facet-Type.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Added a method that returns a River object based on its database id.
+
+	* src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java: The
+	  data that is required for this artifact is fetched from database instead
+	  from a Master-Artifact. The creation of static artifacts should use
+	  database ids instead of cloning a Master-Artifact.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Removed needless imports.
+
+2011-08-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added configuration for factory and rule for MainValueArtifacts.
+
+	* doc/conf.xml:
+	  Add a mainvalue factory to serve MainValueArtifacts.
+
+	* doc/conf/meta-data.xml:
+	  Recomment mainvalue artifact when computed_discharge_curve can be put out.
+
+2011-08-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Let MainValuesArtifact have a state, outputmode and facet.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StaticState.java:
+	  New, a non-abstract DefaultState.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Progressed with implementation, use StaticState to hook output modes and
+	  facet in; use (Static)FLYSArtifact implementation.
+
+2011-08-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Smaller cosmetics.
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Cosmetics in comments.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Added @Override annotations.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java:
+	  Use brackets to improve readability of ternary operator.
+
+2011-08-24  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data.xml: Use "ids" in user specific part, too.
+
+2011-08-24  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data.xml: s/db-ids/ids/g to unify system and user specific
+	  loading.
+
+2011-08-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Extracted access to state and states in order to have fewer places to
+	  modify when going for single/trivial state- artifacts.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java:
+	  Append the river's srid to the WMSLayerFacet.
+
+2011-08-24  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Minor fixes, ressurect Facet implementation as inner class.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Moved the input of barriers one state
+	  earlier where the scenario is selected as well.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java:
+	  This state now desires the "map_digitize" UI provider and returns both
+	  items "scenario" and "uesk.barriers" in the dynamic describe part.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Removed the UI provider and the computeFeed() which is no longer needed,
+	  because there is no more user input in this state.
+
+2011-08-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Repaired broken xpath expressions (the config changed in one of the last
+	  commits) and make use of the variable support in xpath expressions to
+	  replace the rivername.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java:
+	  Save the rivername while initializing this artifact. This is now
+	  necessary, because each river can have its own background wms
+	  configured.
+
+2011-08-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Repaired broken river-wms initialization.
+
+2011-08-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DataFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DurationCurveFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/ManagedFacetAdapter.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WSPLGENFacet.java:
+	  Made facets cloneable with the right type.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Removed superfluous imports.
+
+2011-08-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Declare the first artifact of an output as master artifact (artifacts
+	  are ordered by their creation time).
+
+2011-08-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  Removed facet interface.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Make artifacts cloneable. TODO: Override deepCopy() in subclassed
+	  states and facets.
+
+2011-08-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added the MapInfoService and adapted the floodmap
+	  configuration. Now, each river can have its own background wms layer
+	  defined.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java:
+	  New. This service returns some basic information used to create maps for
+	  a specific river. The name of the desired river needs to be defined at
+	  "/mapinfo/river/text()".
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Adapted an xpath expression that points to the srid of a river in the
+	  floodmap configuration (which changed).
+
+2011-08-22  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Build out/facet filter from XML document passed at creation time.
+
+2011-08-22  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Added some code to filter outs/facets by an optional positive list.
+	  This is needed to only expose parts of the facets. This
+	  is needed for artifacts which are loaded into a collection.
+	  TODO: create the filter from the XML document passed at creation
+	  time.
+
+2011-08-22  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Moved all
+	  WINFO specific code to WINFOArtifact. FLYSArtifact is now only revolving 
+	  about state affairs.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Re-inserted
+	  the specific stuff here.
+	  
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java:
+	  Adjusted the casts.
+
+2011-08-22  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data.xml: Filter by outs in user template part.
+
+2011-08-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Defined an input value for the GeoJSON
+	  string to save user defined barriers in the map.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  Added a computeFeed() method. In addition, this state now prefers the
+	  "noinput" UI provider.
+
+2011-08-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Each river requires a SRID definition. This
+	  definition is used to transform the river's geometries into the desired
+	  projection.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  This facets are able to save a SRID.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  The extent that is written into the WMSLayerFacet is determined by the
+	  boundary of the geometry.
+
+	* src/main/java/de/intevation/flys/utils/GeometryUtils.java: New. A
+	  utility class that provides helper functions for geometries. Currently,
+	  one function is defined, that creates a boundary string for OpenLayers.
+
+2011-08-19  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ProfileDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RangeState.java:
+	  Removed superfluous imports.
+
+2011-08-19  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Removed CallContext from state validation. It is not needed and hindered
+	  the extraction of all out of an artifact if you don't have a
+	  call context (like initial scan of datacage database).
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Extract all outs now.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/FloodplainChoice.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ProfileDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RangeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Adjusted calls.
+
+2011-08-19  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Added lacalization of "Streckenfavoriten".
+
+2011-08-19  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Picky white-space cosmetics.
+
+2011-08-19  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added stub implementation of new MainValuesArtifact.
+
+	* src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java:
+	  New. Stub implementation for new MainValuesArtifact.
+
+2011-08-19  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fixed flys/issue262
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java:
+	  Unified user and system tempate.
+	  Looks for "/artifact-database/metadata/template/text()"
+	  in conf.xml. Defaults to "${artifacts.config.dir}/meta-data.xml". If user id is
+	  given its the default connection for contexts.
+
+	* doc/conf/conf.xml: Adjusted
+	* doc/conf/meta-data-system.xml, doc/conf/meta-data-user.xml: Deleted.
+	* doc/conf/meta-data.xml: Unified version of user and system template.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/App.java:
+	  Adjusted the test program.
+
+2011-08-18  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fixed flys/issue260
+
+	* doc/conf/meta-data-user.xml: Uses master_artifacts view now.
+
+2011-08-18  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Added view master_artifacts to select
+	  the master artifacts of the collections more easily.
+	  To upgrade existing database:
+
+	    CREATE VIEW master_artifacts AS
+	        SELECT a2.id             AS id,
+	               a2.gid            AS gid,
+	               a2.state          AS state,
+	               a2.creation       AS creation,
+	               ci2.collection_id AS collection_id
+	        FROM   collection_items ci2 
+	               JOIN artifacts a2 
+	                 ON ci2.artifact_id = a2.id 
+	               JOIN (SELECT ci.collection_id AS c_id, 
+	                            MIN(a.creation)  AS oldest_a 
+	                     FROM   collection_items ci 
+	                            JOIN artifacts a 
+	                              ON ci.artifact_id = a.id 
+	                     GROUP  BY ci.collection_id) o 
+	                 ON o.c_id = ci2.collection_id 
+	        WHERE  a2.creation = o.oldest_a;
+
+	 TODO: Use the view in the templates.
+
+2011-08-18  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data-user.xml: Removed state filter because it was broken.
+	  Simplified by joining two contexts.
+
+2011-08-18  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  Fix issues with lines of annotation when zoomed (wrong scale used).
+
+2011-08-18  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added legend for annotations to LongitudinalSectionDiagram.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Added a pseudo-dataseries/collection to employ existing infrastructure for
+	  displaying localized label for Annotations (yet unthemed).
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Made label-string available for localization.
+
+2011-08-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Moved StickyAxisAnnotation into new package de.intevation.flys.jfree .
+
+	* src/main/java/de/intevation/flys/exports/StickyAxisAnnotation.java:
+	  Deleted/moved to src/main/java/de/intevation/flys/jfree/ .
+
+	* src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java:
+	  New/moved from src/main/java/de/intevation/flys/export/ , adjusted
+	  package statement, made class public.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Added import statement for de.intevation.flys.jfree.StickyAxisAnnotation .
+
+2011-08-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Extracted and renamed CustomAnnotation to StickyAxisAnnotation. Also removed
+	needless imports.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/StickyAxisAnnotation.java:
+	  Extracted class implementation CustomAnnotation and renamed to
+	  StickyAxisAnnotation.
+
+2011-08-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Improved CustomAnnotations and rendering thereof, now including an
+	"axis mark" (little line at axis), also prepared possibility to put
+	annotations on Y-axis.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Improved CustomAnnotation to include marks on the axis and better spacing
+	  from it.
+
+2011-08-17  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Implemented proof-of-concept collision-detection when drawing
+	CustomAnnotations (text only).
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  Pass a fresh ChartRenderingInfo-Object to createBufferedImage, such that
+	  information can be collected while rendering.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Exploit the fact that XYTextAnnotation already registers drawn shape in the
+	  ChartRenderingInfo if it exists and either an URL or tooltip is set.
+	  Before drawing, calculate own shape and compare against already registered
+	  shapes.
+
+2011-08-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Implemented (yet dummy) custom Annotation class.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Added implementation of yet dummy CustomAnnotation class.
+
+2011-08-16  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Slightly improved rendering of annotations.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Slightly improved rendering of annotations. Still no valid collision
+	  detection. Annotations are drawn every 2 km; first come first serve.
+
+2011-08-15  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue191
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java(longestIncreasingWRangeIndices):
+	  Added a method to find the longest index range with increasing w values.
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java: Export the longest
+	  range of monotone increasing w values instead of the first one.
+	  TODO: The first line of the export is still broken.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Removed superfluous import.
+
+2011-08-12  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java:
+	  Resolved two TODOs: get Annotations of selected River, get "point"
+	  Annotations only.
+
+2011-08-12  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added functionality to query range and point-annotations only to
+	AnnotationFactory.
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java
+	  (getPointAnnotations, getAnnotationsBreadth):
+	  New functions to query breadth and point-only annotations.
+
+2011-08-11  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java:
+	  Added a development mode for recommendations. Enabled with
+	  stetting system property 'flys.datacage.recommendations.development' to true.
+	  When set the XML template are re-read if the timestamps of the
+	  files have changed so you do not have to restart the server again and again.
+
+	* doc/conf/meta-data-user.xml: Sort collections by creation time in descending order.
+
+2011-08-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Rather picky cosmetics only.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQKms.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  End comments on a full stop, separate from closing '*/' by whitespace,
+	  adjusted javadoc comments.
+
+2011-08-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Made Annotations visible in LongitudinalSection diagrams.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Register new Facet Type, let LongitudinalSectionGenerator include
+	  Annotations in diagram (yet unfiltered and independent of river).
+
+2011-08-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml,
+	  doc/conf/artifacts/annotation.xml,
+	  doc/conf/meta-data-system.xml:
+	  Added configuration for AnnotationArtifacts.
+
+2011-08-11  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	Added demo- implementation of a AnnotationArtifact and its Facet.
+
+	* src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/states/AnnotationRiverState.java:
+	  New. Initial version of an AnnotationArtifact and its State and Facet.
+
+2011-08-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Extended schema for artifacts and collections
+	  to have creation times, too.
+
+	  To update existing databases:
+
+	    ALTER TABLE artifacts ADD COLUMN creation TIMESTAMP NOT NULL DEFAULT current_timestamp;
+	    ALTER TABLE collections ADD COLUMN creation TIMESTAMP NOT NULL DEFAULT current_timestamp;
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Store creation times for artifacts and collections, too.
+
+	* src/main/resources/datacage-sql/org-h2-driver.properties,
+	  src/main/resources/datacage-sql/org-postgresql-driver.properties:
+	  Adjusted SQL statements.
+
+2011-08-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Make it compilable again (BackendListener interface changed).
+
+2011-08-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data-user.xml: Added grouping element around w/q of each
+	  longitudinal section artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/CompiledStatement.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating/StackFrames.java:
+	  Added some debugging capabilities.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/ResultData.java:
+	  Added isEmpty() method.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java:
+	  Now it is possible to nest <dc:elements> into other elements in the <dc:context>
+	  body. This is useful and needed for grouping and repeating results.
+
+2011-08-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java:
+	  User connection was cached, system was not. Lead to incorrect results.
+
+2011-08-09  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data-user.xml: Added <old_calculations> element
+	  around old calculations.
+
+2011-08-09  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java:
+	  Cosmetic: Replaced usage of legacy java.util.Stack with java.util.Deque.
+
+2011-08-04  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data-user.xml: Use 'CAST(x AS uuid)' instead of 'x::uuid'
+	  to be more compatible.
+
+2011-08-04  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java:
+	  If given an artifact place its identifier into parameters passed to template.
+	  Fixed swapped user/system connections if using the user template.
+
+	* doc/conf/meta-data-user.xml: Recommend w/q facet from old calculations
+	  if an artifact was given that represents a longitudinal section
+	  "Laengsschnitt".
+
+	  TODO  I: The template uses PostgreSQL specific UUID casts.
+	  TODO II: We need to find a way only to recommend the master artifacts.
+
+2011-08-04  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java:
+	  Added a <dc:comment> tag to place comments in the meta data templates.
+	  <!-- ... --> comments are copied through.
+
+	* doc/conf/meta-data-user.xml: Added a simple test.
+
+2011-08-04  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Be a bit more tolerant about empty strings for UUIDs of artifact and user.
+
+2011-08-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Bring user specific meta data service to life.
+
+	* doc/conf/conf.xml: There are now two templates to configure:
+	  The system template (only the data from the backend) and the
+	  user template (the datcage db and the backend db),
+
+	* doc/conf/meta-data-template.xml: Deleted.
+	* doc/conf/meta-data-user.xml: New. The user specific template. TODO: Write it!
+	* doc/conf/meta-data-system.xml: New. The system template.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/NoneUserSpecific.java:
+	  Deleted.
+	* src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java:
+	  New. The logic to fill the templates.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java:
+	  Adjusted to follow the new call signatures.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Refactored. Removed the old code and only leave the new service. Following
+	  XPaths are evaluated on the incomming document:
+
+	  "/art:meta/art:artifact-id/@value" The UUID of the artifact. Optional.
+	                                     Used to fill the template enviroment.
+	  "/art:meta/art:user-id/@value"     The UUID of the user. Optional.
+	                                     If given the user specific template is filled.
+	  "/art:meta/art:outs/@value"        The list of outs used to recommend for the
+	                                     various outputs.
+	  "/art:meta/art:parameters/@value"  A list of key/value pairs to inject more
+	                                     filters to the templating.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/App.java:
+	  Change to follow the new recommendations semantics.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java:
+	  Added symbolic constants to distinguish "user" and "system" db connections.
+
+2011-08-03  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* doc/conf/conf.xml:
+	  Cosmetics, let comments start with a capital and end on a full stop,
+	  removed incorrect comment.
+
+2011-08-03  Felix Wolfsteller <felix.wolfsteller@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Cosmetics, let comments start with a capital and end on a full stop.
+
+2011-08-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java(extractOutputNames):
+	  Fixed potential NPE.
+
+2011-08-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Added support for more than one db connection in datacage templating.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java:
+	  Now you can pass a list of named db connections to the build process.
+	  The purpose is to mix more then one database (e.g. the backend db and
+	  the user specific one).
+
+	  To use this feature you can add an "connection" attribute
+	  to <dc:context> with the name of the connection to use.
+	  If no connection name is given the last used is used again.
+	  Initially the first connection in the given list is used. 
+	  If the context is left the connection that was active before 
+	  will be active again in a stacking manner.
+
+	  When creating NamedConnection objects you can set a boolean flag
+	  if the results coming from the connection should be cached. This
+	  is useful e.g. for the user specific database which runs in-memory
+	  so caching would introduce some superfluous overhead.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/templating/CompiledStatement.java:
+	  When executing the queries explicitly pass if caching should be used.
+
+2011-08-02  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Forgot to fetch dialect dependent SQL statement for deleting
+	  artifacts by uuid.
+
+2011-08-02  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Set the name of the collections at initial scan, too.
+
+2011-08-02  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Fixed wrong SQL references.
+
+2011-08-02  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Fixed two NPEs.
+
+2011-08-02  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java,
+	  src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Forwarded kill collections and artifacts events to datacage.
+	
+	* src/main/resources/datacage-sql/org-h2-driver.properties,
+	  src/main/resources/datacage-sql/org-postgresql-driver.properties:
+	  Added statement to delete artifact by uuid.
+
+2011-08-02  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java:
+	  Made it compilable again. The signature of BackendListener has changed.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Completed the backend listener stuff.
+
+	  TODO  I: Added some cleanup for orphaned artifacts.
+	  TODO II: Figure out a way to delete collections/artifacts
+	           which are delete from backend without the 
+			   backend API.
+
+	* src/main/resources/datacage-sql/org-h2-driver.properties,
+	  src/main/resources/datacage-sql/org-postgresql-driver.properties:
+	  Added needed statements.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Update collection names on change. Remove artifacts from collections.
+
+	* src/main/resources/datacage-sql/org-h2-driver.properties,
+	  src/main/resources/datacage-sql/org-postgresql-driver.properties:
+	  Added needed statements.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java:
+	  Call datacage with the global context. This is needed to access the state engine.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java: Changed
+	  signatures to take the global context, too. Create artifacts via backend listener
+	  interface.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java:
+	  Fixed recursion bug.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java:
+	  Added debug output.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Added backend listener for datacage.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java:
+	  New. Proxies backend listener calls to datacage.
+	 
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Implements backend listener. TODO: Update the datacage database
+	  according the change calls.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta,
+	  src/main/java/de/intevation/flys/artifacts/datacage/templating:
+	  Moved/renamed package to better fit the common semantics.
+	  DataCage.java is now call NoneUserSpecific.java to reflect the
+	  fact that it is the template for the user independent db
+	  analysis.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java,
+	  src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Ajusted imports and calls.
+
+2011-08-01  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: For documentation purposes added a out-commented 
+	  section with the default configuration of the datacage.
+
+2011-07-31  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Delete the artifacts at before initial scan, too. They are independent
+	  from users. Fixed problem when writing state data content.
+
+	* src/main/resources/datacage-sql/org-h2-driver.properties: Added
+	  statement to delete the artifacts at initial scan, too.
+
+	* src/main/resources/datacage-sql/org-postgresql-driver.properties:
+	  New. PostgreSQL version of the statements. The database scheme
+	  is the same as H2. Very useful for debugging.
+
+2011-07-31  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Added kludge for the types of artifact data. They seem to be null
+	  in some circumstances. Needs to be debugged!
+
+2011-07-31  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DBConfig.java:
+	  Fixed default connection URL to use a namend in-mermory database.
+
+2011-07-31  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Fixed constraint.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Added some debug output.
+
+2011-07-29  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Removed artifact_id from facet because there
+	  is a link via out_id -> outs.id: artifact_id -> artifacts to
+	  find the corresponding artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties: Store
+	  facets of outs into datacage db at initial scan.
+
+2011-07-29  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties:
+	  Store outs of artifacts into datacage db at initial scan.
+	  TODO: store facets.
+
+2011-07-29  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Added 'type' column in artifacts data.
+	  Maybe useful for filtering.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties:
+	  Store artifact data into db at initial scan.
+	  TODO: store outs and facets.
+
+2011-07-29  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties:
+	  Add artifacts into datacage db at initial scan.
+	  TODO: Store data, outs and facets.
+
+2011-07-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Fixed spelling in sequence name.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties: Simply add
+	  collection item at initial scan if artifact was stored before.
+	  TODO: Store new artifacts.
+
+2011-07-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties: Add
+	  collections at initial scan.
+
+2011-07-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties: Add users
+	  at initial scan.
+
+2011-07-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Using sequences for id generation now
+	  to make schema more compatible.
+
+2011-07-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java,
+	  src/main/resources/datacage-sql/org-h2-driver.properties: Clear database
+	  before initial scan.
+
+2011-07-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DBConnection.java:
+	  Deleted. This stuff comes from the artifact database now.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DBConfig.java:
+	  New. The db config of the datacage database.
+
+	* src/main/resources/datacage-sql/org-h2-driver.properties: New.
+	  The SQL statements needed for the datacage.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  Make use of the db config.
+
+2011-07-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java:
+	  New. A artifact database lifetime listener to build the initial
+	  index of the artifacts in database.
+
+	* doc/conf/conf.xml: Added the datacage to the list of lifetime
+	  listeners.
+
+2011-07-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Added an explicit table for the outs
+	  of an artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Make the current outs of an artifact accessible only with
+	  the global context.
+	
+2011-07-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: Added ON DELETE CASCADE constraints.
+	  Added state in facet.
+
+2011-07-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/datacage.sql: New. H2 Schema for the datacage database.
+	  Uses special features like IDENTITY (autoincrement) typed columns.
+
+	* src/main/java/de/intevation/flys/artifacts/datacage/DBConnection.java:
+	  Pooled connection.
+
+	* pom.xml: Added dependencies to H2 and Apache DBCP.
+
+2011-07-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/test/java/de/intevation/flys/artifacts/AppTest.java,
+	  src/main/java/de/intevation/flys/artifacts/App.java: Removed.
+	  This the stupid "Hello, World!" app initially created by the
+	  maven archetype. It was never used.
+
+2011-07-25  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Argh! Usage the DOM was not thread safe (discovered with ab).
+
+2011-07-25  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/meta-data-template.xml: '$recommended' lead to XPath
+	  errors. Using "dc:contains($parameters, 'recommended')" helps.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java,
+	  src/main/java/de/intevation/flys/artifacts/services/meta/FunctionResolver.java:
+	  Added some debugging.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/DataCage.java:
+	  Added parameters as 'parameters' to parameters. Usefull to check
+	  for containment of variables.
+
+2011-07-25  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Added 'if log.isDebugEnabled() {}'.
+
+2011-07-25  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Made Artifact UUID optional and accept extra parameters, too. This enables
+	  the service to be used without an arttifact and test all filters.
+
+	  <art:meta xmlns:art="http://www.intevation.de/2009/artifacts">
+	     <art:outs value="computed_discharge_curve,floodmap"/>
+	     <art:parameters value="river:Elbe"/>
+	     <art:filters value="recommended"/>
+	  </art:meta>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/FunctionResolver.java:
+	  'contain' accept Maps and collection, too.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/DataCage.java:
+	  Made artifact option (= null) in recommendations.
+
+2011-07-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/CompiledStatement.java:
+	  Moved connection specific into inner class. The enables the reuse of the
+	  compiled statement.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java: The
+	  SQL statements are now only compiled once at creation time of the builder.
+	  Each connection now reuses them.
+
+2011-07-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Added configuration for static datacage db access.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/App.java: Using
+	  caches seems to need an explicit System.exit().
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/CompiledStatement.java:
+	  Added support for caching the SQL statements and there results.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java: Some
+	  clean up. Reordered code for performance. Strip SQL statements more
+	  aggressively.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/ResultData.java:
+	  Made it Serializable.
+
+	* src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java:
+	  Introduced system property 'flys.artifacts.cache.config.file' to make
+	  the caching configurable without pulling up the whole stack.
+
+2011-07-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Prepared the FLYSArtifactCollection to support the storage of already
+	  loaded recommendations in its attribute document.
+
+2011-07-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Adjusted to use the DataCage recommendations. The incomming document
+	  can pass the artifacts UUID by '/art:outs/@value' the outs as a
+	  comma separated list in '/art:outs/@value' and optional a set of
+	  filters comma separated in '/art:filters/@value'.
+
+	  If UUID and OUTS are not given the old service is used. This
+	  should be removed as soon as the client uses the new service.
+
+2011-07-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/CompiledStatement.java:
+	  Allow '-' in variable names.
+
+2011-07-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data-template.xml: Now, the river-id is really added to
+	  the factory node of the wmsbackground layer.
+
+2011-07-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data-template.xml: Added the river-id to the factory node
+	  of the riveraxis and wmsbackground layer.
+
+2011-07-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Adjusted to implement changed Service interface.
+
+2011-07-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Refactored
+	  the method that retrieves the Outputs for the Artifact. Now, we are able
+	  to query the Outputs for the current state, and all outputs separately.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java: The
+	  recommendations provided by this monitor will take the Outputs of the
+	  current state only into account.
+
+2011-07-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/meta-data-template.xml: Added conditions for each output type.
+	  Splitted the "floodmap" output into two parts: a recommended one and a
+	  complete one. The recommended part will only build the document tree for
+	  the recommended artifacts; the complete part will build the whole document
+	  tree that is available for a floodmap.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java:
+	  This Hook now uses the DataCage to generate the recommended artifacts.
+	  The output-defaults configurtion is needless now.
+
+	* doc/conf/output-defaults.xml: Removed. The configuration of recommended
+	  artifacts takes place in meta-data-template.xml.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/DataCage.java:
+	  Bugfix: the DataCage didn't start working if its builder was NOT null,
+	  but it shouldn't start if the builder IS null.
+
+2011-07-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Now it is possible to directly pass a Node as a root to the builder.
+	  The owning document if fetch by Node.getOwnerDocument(). This is
+	  useful if you want to generate the recommendation directly into
+	  an already existing document under a given node.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/DataCage.java:
+	  Changed the signature of recommend() to accept a node where to
+	  append the recommendations.
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Added a macro mechanism:
+
+	    <dc:macro name="keine-fuenf">
+	      <dc:text>'5' ist nicht in der Liste der Outs.</dc:text>
+	    </dc:macro>
+	    <dc:call-macro name="keine-fuenf"/>
+	    <dc:call-macro name="keine-fuenf"/>
+	    <dc:call-macro name="keine-fuenf"/>
+
+	  Macros can be defined everywhere in the template
+	  with 'macro'. There bodies can contain all valid elements
+	  including other 'macro's and 'call-macro's. They are
+	  called with their 'name' with 'call-macro'. The control flow
+	  is continued inside the body of the called macro and 
+	  will continue right after the calling 'call-macro' when
+	  the macro body is finished.
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Added a new 'if' construct similiar to XSLT:
+
+	    <dc:if test="not(dc:contains($outs, '5'))">
+	        <dc:text>'5' ist nicht in der Liste der Outs.</dc:text>
+	    </dc:if>
+
+	  The control flow is continued inside the 'if' if the 'test' attribute
+	  as an XPath expression on an empty document evalutes to true.
+	  Else the inside is skipped. There is no 'else'. Use 'choose'/'otherwise'
+	  if you need this.
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/DataCage.java:
+	  Added a recommend() method to generate recommendations for
+	  a given artifact, outs and extra parameters.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added
+	  method to extract all data at once.
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/FunctionResolver.java:
+	  New. Custom XPath function provider. Provides
+	  'dc:contains(Object [] haystack, Object needle)' by now. Should be
+	  useful to check containments in 'out' lists later.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Register the FunctionResolver to the evaluated XPaths.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/App.java:
+	  Added code to parse
+	  "param:a,b,c" to "param" -> new String [] { "a", "b", "c" } to
+	  help testing the 'dc:contains' XPath function.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added wms configurations for Saar, Mosel and Elbe.
+	  Each river supported by FLYS requires such a WMS configuration. A WMS
+	  should contain layers for the river axis, buildings, kilometer labels
+	  and maybe a background layer as well.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java:
+	  Added key that is used to store a map of WMS URLs - for each river a
+	  WMS URL.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Parse the river WMS from global configuration.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Create WMSLayerFacets with URLs based on the river and the river wms
+	  configuration stored in the FLYSContext.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java:
+	  Adapted the initialize() signature and the method call of computeInit()
+	  which requires a FLYSContext to retrieve the river WMS configurations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added the context object parameter to the computeInit() method.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Call
+	  initialize() with the context object - which is a FLYSContext or a
+	  CallContext.
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/App.java:
+	  Check if builder was created properly before using it.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/StackFrames.java:
+	  Implements now variable provider for XPath expressions.
+	
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Added new choose/when/otherwise construct similiar to XSLT
+
+	      <dc:choose>
+	         <dc:when test="$river = 'Mosel'">
+	             <dc:text>Es ist die Mosel.</dc:text>
+	         </dc:when>
+	         <dc:when test="$river = 'Saar'">
+	             <dc:text>Es ist die Saar.</dc:text>
+	         </dc:when>
+	         <dc:otherwise>
+	            <dc:text>Es ist weder Mosel noch Saar.</dc:text>
+	         </dc:otherwise>
+	      </dc:choose>
+
+	  A 'choose' block can contain a list of 'when's and an optional
+	  'otherwise'. For each 'when' the test attribute is evaluated
+	  as an XPath expression on an empty document. The result of
+	  the evaluation is taken as a boolean value. If its value is
+	  true the control flow is continued inside the corresponding
+	  'when' and the other choose elements are not tested.
+	  If the value is values the testing continues with the next
+	  'when'. If no 'test' expression is evaluated to true, the
+	  control flow continues inside the 'otherwise'. If no 'otherwise'
+	  is given nothing happens at all.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added a config section for floodmaps. Currently, the
+	  background layer's url and layername is defined here.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java:
+	  Read the background layer configuration from conf.xml. Those values are
+	  used to create the WMSLayerFacet.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java:
+	  The background layer facet will no longer have an extent set.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java:
+	  Set the extent of the created WMSLayerFacets and i18n its descriptions.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added I18N strings for the
+	  background an river axis layer.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  This facet type has a method to set the extent of a WMS layer. The
+	  extent is written to the facets XML node in toXML() as well.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Adapted
+	  the signature of setup() which requires a CallMeta parameter now.
+
+	* src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java: Use
+	  the CallMeta object retrieved in setup() to call initialize(). It is now
+	  able to i18n things.
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/App.java: New.
+	  Standalone app to debug the datacage template. To use in a maven environment:
+
+	  -Dmeta.data.template=PATH_TO_META_DATA-TEMPLATE.XML \
+	  -Dmeta.data.parameters=river:Mosel \
+	  -Dmeta.data.output=OUTPUT.XML \
+	  -Dflys.backend.user=DB_USER \
+	  -Dflys.backend.password=DB_PASSWORD \
+	  -Dflys.backend.url=DB_CONNECTION_URL \
+	  -Dexec.mainClass=de.intevation.flys.artifacts.services.meta.App
+
+2011-07-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/DataCage.java:
+	  New. First step to factor out the "Datenkorb" logic into a service independent
+	  singleton.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Uses the "Datenkorb" singleton now.
+
+2011-07-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java:
+	  Removed "index" property, because it is already existing in parent
+	  class.
+
+2011-07-19  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Improved error handling.
+
+2011-07-19  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/resources/metadata/template.xml: Deleted.
+
+	* doc/conf/meta-data-template.xml: New. Was template.xml
+
+	* doc/conf/conf.xml: Made meta data template configurable.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Load template from configuration not from resources.
+
+2011-07-19  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/resources/metadata/template.xml: s/[a-z]+-id/db-id/
+	  Make database ids identifiable with unique name "db-id".
+
+2011-07-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java:
+	  Override the toXML() method. Subclasses can now write their own XML
+	  representation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java:
+	  New. This ManagedFacet uses an Element (DOM) to store the information
+	  about a facet. The intent of this facet type is to represent a facet
+	  stored in an Collection attribute. Different facets can have different
+	  attributes that we need to parse, but the only thing ManagedFacets need
+	  to do, is to adjust the attributes "active" and "position". So, those
+	  values are set directly on the Element, the other attributes aren't
+	  touched.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacetAdapter.java:
+	  New. This facet is a wrapper for another facet. This subclass of a
+	  ManagedFacet overrides the toXML() method. The XML representation is
+	  defined by the inner facet that is stored as member variable. The
+	  ManagedFacet specific attributes "artifact", "facet", "pos" and "active"
+	  are added manually.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: Uses
+	  the toXML() method to write a facet node into the attribute document.
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java: Uses
+	  the ManagedDomFacet to save the information of a facet which is
+	  contained in the attribute part of a Collection's DESCRIBE document.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java: Uses the
+	  ManagedFacetAdapter to save a facet, because we want to keep the
+	  specific facet to be able to write its specific XML representation into
+	  the Collection's DESCRIBE document.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted the XPath of facets stored in the attribute part of the
+	  DESCRIBE.
+
+2011-07-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method that returns the Outputs for the Artifact.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapeted the call of OutputParser.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java:
+	  Simplified the code to read the Outputs of Artifacts. This parser will
+	  now longer parse the DESCRIBE documents of the Artifacts, but query the
+	  Outputs via FLYSArtifact.getOutputs() directly.
+
+2011-07-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java:
+	  Adapted the function call of ProtocolUtils.appendOutputModes().
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  Override toXML() to add the URL and layernames to the XML representation
+	  of this facet.
+
+2011-07-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/wmsbackground.xml,
+	  doc/conf/artifacts/riveraxis.xml: New configurations for an Artifact
+	  that is used as background layer in floodmaps, and an artifact that is
+	  used as layer showing the river axis in a floodmap.
+
+	* doc/conf/output-defaults.xml: New file to configure default artifacts
+	  for specific output states. E.g. the floodmap state recommends a
+	  background layer and a layer displaying the river axis. In suche case,
+	  the floodmap state recommends two artifacts for the two layers.
+
+	* doc/conf/conf.xml: Added new artifacts/artifact-factories and a Hook to
+	  monitor artifacts (-> CollectionMonitor.java).
+
+	* src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java:
+	  New. This facet is used to represent a layer in a map. So, this facet
+	  stores information about a WMS URL and the layer names provided by this
+	  WMS.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  facet types for the wmsbackground and riveraxis.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java,
+	  src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java:
+	  New. An artifact and its default state. The intent of these classes is
+	  to generate WMSLayerFacets which represent background layers in maps.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverAxisState.java,
+	  src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java: New.
+	  An artifact and its default state. The intent of these classes is to
+	  generate WMSLayerFacets which represent layers that display a river
+	  axis.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added the INIT ComputeType.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  case for the INIT ComputeType while computing data.
+
+2011-07-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a method computeInit() which is called to initialize data/facets
+	  after an artifact has been created.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Repaired
+	  broken XPath.
+
+2011-07-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java:
+	  Removed the code to generate new artifacts. Instead of creating new
+	  artifacts automatically, we decided to suggest creating new artifacts
+	  from specific types. Therefore, the DESCRIBE document of the artifacts
+	  is extended with a node that contains recommended artifact types.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  FLYSArtifacts might be setup with the identifier of an other
+	  FLYSArtifact. Subclasses are able to override a method called
+	  initialize(Artifact, GlobalContext). This might be helpful to extract
+	  required values or clone artifacts.
+
+2011-07-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Adapted the signature of createArtifactContext() - it returns an
+	  instance of GlobalContext now.
+
+2011-07-14  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Debugged the water fill algorithm. Added a lot of logging.
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Added a text field to give a water level to fill in.
+
+2011-07-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Moved code
+	  to create the output modes based on the given facets to FLYSArtifact. In
+	  addition, FLYSArtifact got a new method that returns a specific input
+	  value as string.
+
+	* src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java: New.
+	  This hook monitors the "post-feed" and "post-advance". If the monitored
+	  Artifact's state has configured recommended artifacts, this hook will
+	  create new Artifacts.
+
+	  TODO: We have to add the UUIDs of the new Artifacts to the DESCRIBE
+	  document of the artifact to let the client know, that there are new
+	  recommended Artifacts.
+
+	* src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java: New.
+	  This Artifact is the base class for Artifacts, that represent static
+	  data. E.g. this could be a decoration theme in a chart or a background
+	  layer in the map.
+
+	* src/main/java/de/intevation/flys/artifacts/states/OutputState.java: New.
+	  This state might be used as base class for states, that doesn't require
+	  any user input, but only provide static Facets added by a computeFeed()
+	  operation. So, subclasses need to implement computeFeed() only.
+
+2011-07-13  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  New. Standalone Swing-App to test cross sections from database without the
+	  hassles of our complete software stack. Runnable from a maven environment:
+
+	  $ mvn -e \
+	    -Dflys.backend.user=DB_USER \
+		-Dflys.backend.password=DB_PASSWD \
+		-Dflys.backend.url=DB_CONNECTION_URL \
+		-Dexec.mainClass=de.intevation.flys.artifacts.charts.CrossSectionApp \
+		exec:java
+
+	  You can set the river to be used with the system property 'river'.
+	  Defaults to 'Mosel'.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENFacet.java:
+	  Removed superfluous imports.
+
+2011-07-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: The FloodMapState has a new Outputmode
+	  called "floodmap" now.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WSPLGENFacet.java: New.
+	  This facet is used to generate WSPLGEN results.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Added
+	  the WSPLGENFacet.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java:
+	  This state will now generate WSPLGENFacets.
+
+2011-07-12  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java:
+	  Fixed corner case.
+
+2011-07-11  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/geom/Lines.java: New.
+	  fillWater() generates a list of wet lines for a given profile and a
+	  given water level.
+
+2011-07-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  Outputs created while generating the DESCRIBE document will now have the
+	  'type' member set.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java: Read the
+	  'type' member from DESCRIBE document.
+
+2011-07-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Removed a typo.
+
+2011-07-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java:
+	  DataFacet are now able to store the ID of the state which has created
+	  this Facet.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Initialize DataFacets with the ID of this state. This is necessary to
+	  renew the waterlevel data if it is no longer existing in the cache.
+
+2011-07-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java: A
+	  report facet can now store the state's id and the artifact's hash value
+	  when it has been created.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Initialize the ReportFacet and WaterlevelFacet with state id and hash
+	  information. This has been necessary to retrieve reports and waterlevels
+	  in states after this one - in states that we need to enter for floodmap
+	  parameterization.
+
+2011-07-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelGroundDifferences.java:
+	  Changed the desired UI provider.
+
+2011-07-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added the option to continue the
+	  waterlevel parameterization with the intent to create flood maps.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  This state is no longer a final state. The user has the option to
+	  continue with the parameterization for flood maps based on the current
+	  waterlevel. Therefore, this states desires the "continue" UI provider.
+	  Clients should recognice this to just step to the next state or display
+	  a button that lets the user step to the next state manually.
+
+	* src/main/java/de/intevation/flys/artifacts/states/FloodplainChoice.java,
+	  src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ProfileDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelGroundDifferences.java:
+	  New. These states are used to parameterize a further calculation type:
+	  flood map.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Required strings for the
+	  flood maps states.
+
+2011-07-04  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix(?) for flys/issue114
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java:
+	  Make guessing a bit more robust.
+
+2011-07-03  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Added the math needed to calculate "W-Differenzen" in "Laengsschnitten".
+	Needs testing!
+
+	* src/main/java/de/intevation/flys/artifacts/model/NamedObject.java:
+	  Made it an interface to be usable in more than one inheritance chain.
+
+	* src/main/java/de/intevation/flys/artifacts/model/NamedObjectImpl.java:
+	  Implements the NamedObject interface and is the new base class of
+	  WQ and WKmsImpl.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKms.java:
+	  New. Interface to associate kms with ws.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+	  Changed the base class to NamedObjectImpl. Renamed getKms(int)
+	  to getKm(int) to make clear it return a single scalar value
+	  and fullfil the WKms interface.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java:
+	  New. Implements the WKms interface. Intended to be a lightweight
+	  datastore for "zusaetzliche Laengsschnitte" and as results
+	  of the WKmsOperations.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java:
+	  Changed base class to NamedObjectImpl.
+
+	* src/main/java/de/intevation/flys/artifacts/math/WKmsOperation.java:
+	  New. Operations on WKms data.
+	  Currently only the SUBTRACTION operation is implemented. This
+	  one is needed to calculate the "W-Differenzen". The operation
+	  is insensitive about the km directions of the datasets. Missing
+	  values are interpolated linear.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Adjusted to satisfy the signature change of WQKMs.
+
+2011-07-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/StyledXYSeries.java: New. This
+	  XYSeries stores the style information that should be used to render this
+	  series. These information are stored as raw XML documents. A public
+	  method can be used to apply those style information to a
+	  XYLineAndShapeRenderer.
+
+	  Note: The only two attributes currently supported by StyledXYSeries
+	  items are "linesize" and "linecolor".
+
+	* doc/conf/themes.xml: Added some more basic themes for the four
+	  calculation methods.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: This
+	  generator now tries to apply themes for all series contained in the
+	  chart. If a series is no instance of StyledXYSeries, the default
+	  renderer is used.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Removed the code that had been introduced to adapt renderers statically.
+	  Now, each of these concrete ChartGenerators instantiates StyledXYSeries
+	  items to put the curves into the chart. Those items contain style
+	  information now!
+
+2011-07-01  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue135 (Diagramm: Trotz abgeschalteter Themen bleiben Beschriftungen bestehen)
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: The way
+	  to store datasets has changed. Until this revision, the concrete
+	  generators managed their own datasets. E.g. the
+	  DischargeLongitudinalSectionGenerator had three datasets: w, q and
+	  corrected w. Now, there are just two datasets, managed by this base
+	  generator - one dataset for the first Y axis and one dataset for the
+	  second Y axis. This makes it easier to remove axes, that have no data to
+	  be displayed. All concrete chart generators have to add their XYSeries
+	  using two methods: addFirstAxisSeries() and addSecondAxisSeries().
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Those concrete chart generators no longer manage datasets themself but
+	  they use the two methods described above, to plot the data to the first
+	  or second Y axis.
+
+2011-07-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java:
+	  Added the option to use a filter to reduce the number of items returned
+	  by this service.
+
+2011-06-30  Ingo Weinzierl <ingo@intevation.de>
+
+	flys/issue159 (WINFO: Radiobutton - Ortsauswahl bei "W für ungleichwertigen Abflusslängsschnitt" entfernen)
+
+	* doc/conf/artifacts/winfo.xml: Changed the kilometer range input for
+	  calculation 4. This calculation type requires a kilometer range. So,
+	  after choosing the calculation 4, the transition model leads to a state
+	  that just allows the input of a kilometer range with no option to
+	  enter locations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java:
+	  New. A base state for the kilometer selection for calculations. The
+	  target of this state is to provide facets for the duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DistanceSelect.java:
+	  New. This state is used to enter a kilometer range. The difference to
+	  the LocationDistanceSelect state is, that there is no option to enter
+	  locations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java:
+	  Improved this state to be the base state for calculation ranges.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: This
+	  state no longer inherits from RangeState which now is used as base
+	  state for kilometer ranges.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added new i18n strings for
+	  the DistanceSelect state.
+
+2011-06-28  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* pom.xml: Downgraded Trove to 1.1-beta-5, because the new
+	  later ones are removed from the maven repos.
+
+	  The functionality we need is in 1.1 so this downgrade should
+	  cause no problems.
+
+	  Would be nice if we would support the maintainers of trove to 
+	  bring there new versions back into the main maven repos.
+
+2011-06-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* pom.xml: Repaired the JBoss repository which place has changed.
+
+2011-06-28  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.4
+
+2011-06-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/WstWriter.java:
+	  Append the corrected W column (if existing) to the WST export.
+
+2011-06-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java:
+	  Guessing increaing w is not based on direct neighbors any more.
+	  The second to be compared with is choosen by random of
+	  the values before the first one. This makes the guessing
+	  more robust against 'plateaus' of equal w values.
+
+2011-06-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java: Array
+	  for constructing the spline was too large leading to non-increasing
+	  values.
+
+2011-06-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue150
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Invert the x axis if its guessed that water is increasing.
+
+2011-06-27  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java(guessWaterIncreasing):
+	  Added a method to guess based on a given factor of the size (default 0.05)
+	  if the water levels are increasing. Needed to determine in which direction
+	  the water level curve should be orientated. Based on random to avoid
+	  running over large dataset each time a diagram is generated.
+
+2011-06-27  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue177 (WINFO: Abflusskurven am Pegel verursachen ein Hängen des Servers)
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Commented out code to generate time ranges for series names. There
+	  seems to be a problem while loading the discharge tables of a gauge or
+	  while determining the start and/or end time of such discharge tables.
+
+2011-06-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/**/*.java: Removed trailing whitespace.
+
+2011-06-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Removed dead code.
+
+2011-06-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue173
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Fixed the way the gauge was found for a given interval.
+	  The old way does not work because it was just tested if
+	  the station point was inside the segments which is not
+	  necessarily true. The obvious solution to simply check
+	  the overlapping intervals does not work either because
+	  the gauge ranges touch each other and so more than
+	  one gauge are returned in these cases. The River.maxOverlap()
+	  is now used to find the gauge with the max overlapping
+	  range.
+
+2011-06-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue147
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation1.java:
+	  Removed the 'kmUp' flag. It was an left over from former
+	  WSP calculations (w/o ref km) leading to wrong results now.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Don't pass the kmUp flag to the calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Added debug output to see the value of 'wq_free'.
+
+2011-06-26  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue86
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  The reference gauge for calculations "am Pegel" was determined
+	  wrong.
+	  
+2011-06-25  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Conversion w->q was broken. This should fix a number of issues
+	  around "W am Pegel" calculations.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Issue an error report if a w->q conversion fails.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  The gauge to convert w->q with its discharge table was determined wrong.
+
+2011-06-24  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue174 (Diagramm: Q-Linie wird bei initialem Laden des Diagramms
+	  anders dargestellt als bei Ansicht auf gesamten Wertebereich)
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  The chart will now have upper margins again.
+
+2011-06-24  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue172 (Diagramm: Ursprung der Diagramme bei Dauerzahlen)
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Charts of this type will have the lower X value set to "0".
+
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java:
+	  Results are now in cm. Made it more robust against corner cases.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Removed superfluous import.
+
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml, doc/conf/conf.xml:
+	  Added facet config for AT exports.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added 'at' facet type.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java:
+	  Generate AT facets.
+	
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ATExporter.java: New.
+	  Exporter for AT facets. Needs testing.
+
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ATWriter.java: New.
+	  Writer for AT files. New code because our data model differs
+	  from Desktop-FLYS. Needs testing.
+
+2011-06-23  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue157 (Diagramm: Ursprung berechnete Abflusskurve)
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Moved the method that adjusts the X-axis to include the "0" value from
+	  ComputedDischargeCurveGenerator to DischargeCurveGenerator. Now, both
+	  charts will include the "0" on the X-axis.
+
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQDay.java:
+	  Based on WQ now to make it exportable as AT.
+
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQ.java:
+	  New. Base class for WQKms.
+
+	  TODO 1: make it a base class for WQDay, too,
+	  TODO 2: Generate instances of WQ instead of WQKms in "Abflusskurven"
+	          calculations. This will save memory.
+
+	  AT exporter will get instances of this class as data model
+
+	* src/main/java/de/intevation/flys/artifacts/model/NamedObject.java:
+	  Add default constructor to ease inheritance.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+	  Based on WQ now. Removed code allready defined in base class.
+
+2011-06-23  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Add TODOs for error reports
+	  of "Abflusskurven am Pegel". Maybe we don't need them?
+
+	* doc/conf/conf.xml: Configure report for each calculated output type.
+
+2011-06-22  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue164 (Berechnung 4: Umgekehrtes Berechnungsintervall führt zur)
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Create the items with range information for W and Q which allows the
+	  client to validate the user inserted values.
+
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Arguments in filling datastructure were flip. Now
+	  the "Abflusskurve am Pegel" looks correct again.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Removed superfluous imports.
+
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Improved situtation on rendering "Abflusskurve am Pegel". Not
+	fully working, yet.
+
+	* doc/conf/artifacts/winfo.xml: Generate facets for the location path, too.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: Misspelled
+	  the facet which prevented the facets from being to the outputs.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java:
+	  Extended to store the hash and the state id of the producing artifact/state
+	  else it results in NPEs because the data is calculated on later (wrong) states.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Store the state id and the hash in the facet, too.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Add two
+	  method to do calculations for a state the artifact is currently not in
+	  and fetching the current state id.
+
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Generate facets for "Abflusskurven am Pegel"
+	  Not working by now. :-/
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java: New facet type
+	  for "Abflusskurven am Pegel"
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Do calculation
+	  in the artifact not in the output generator.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Generate the new facets.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Fetch data from facet.
+
+2011-06-22  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue161 (Diagramm: Q-Achse in W-Längsschnitten immer bei Q=0)
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  The Q axis (which is the second y axis) initially contains the 0 value.
+	  After a zoom action has taken place, this behaviour is no longer
+	  supported.
+
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Draw correction curve again.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java:
+	  Use correction curve to map plot to axes. Not doing so prevented
+	  the correction curve from being drawn!
+	  Smaller code cleanups and simplifications.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQCKms.java:
+	  Added methods to directly access the components w, q and c
+	  at a given index.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java:
+	  Attribute access via DOM instead of XPath.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Code simplification.
+
+2011-06-21  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue157 (Diagramm: Ursprung berechnete Abflusskurve)
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Splitted
+	  up zooming for x and y axes to be able to override specific axis
+	  zoom behaviour.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  The lower bound of the x axis (which is the Q axis in such chart) is
+	  always 0.
+
+2011-06-21  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue90 (Diagramm: Trennung derDiagrammfläche und Achsenaufheben)
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Added a margin between chart data and chart axes.
+
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue158
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  Checks for right class now.
+	
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Fix for flys/issue154
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Checks for right class now.
+
+2011-06-21  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Only generate 'outs' if they have facets.
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  'boolean ? true : false' <=> 'boolean'
+
+2011-06-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added the time-to-live to the DESCRIBE document.
+
+2011-06-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Configured states to generate report facets.
+
+2011-06-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java:
+	  Return the report.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java:
+	  Generate report facets if there are problems with the calculations.
+	  TODO: Adjust winfo.xml to configure the facets.
+
+2011-06-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ReportGenerator.java:
+	  New. Generator for calculation reports.
+
+	* doc/conf/conf.xml: Added ReportGenerator.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  Added type 'report'.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java:
+	  Specialized facet for serving reports. TODO: Added them to the
+	  calculation states.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  Looped through CallMeta for i18n purposes. TODO: do i18n
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Removed superfluous import.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Added some override annotations.
+
+2011-06-20  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/CalculationResult.java:
+	  New. Used to transport the data and the error report.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation1.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation2.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation3.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation4.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DurationCurveFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation.java,
+	  src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  Use the CalculationResult now.
+
+2011-06-18  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation1.java:
+	  Allow an explicit reference km to enable calculation "am Pegel".
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  If a calculation "am Pegel" is done, take start km of the
+	  calculation range and find the gauge in which range it is located.
+	  Take the station of the gauge as the reference km. If no gauge
+	  is found the calcualtion falls back to calculation "auf freier Strecke".
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Replaced another inefficient attribute extraction via XPath
+	  with direct DOM access.
+
+2011-06-18  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Removed dead code.
+
+2011-06-18  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java
+	  src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DurationCurveFacet.java,
+	  src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Only generate facets when needed.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java:
+	  Removed more XPath for simply accessing attributes of an element.
+
+	* doc/conf/cache.xml: 200 elements in memory for "computed.values" are enough,
+	  LRU as eviction strategy is sufficent.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java,
+	  src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Use
+	  more 'standard' Java naming conventions.
+
+2011-06-17  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Merged in the facet-slt branch to bring in the 'facet' feature.
+
+	* doc/conf/artifacts/winfo.xml: Fixed some facets.
+
+	* doc/conf/cache.xml: Added a "computed.values" cache to store the
+	  results of the WINFO calculations.
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java:
+	  Add support for index per facet to make them unique and identifiable.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java:
+	  New. Inteface to be used to access the facet names of the configuration.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DataFacet.java: New.
+	  A facet to be used to have raw access to the computed data of an artifact.
+	  Useful to export things like CSV and WST.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java: New.
+	  Specialized facet to access the water level data stored in WQKms arrays.
+
+	* src/main/java/de/intevation/flys/artifacts/model/DurationCurveFacet.java: New.
+	  Specialized facet to access the duration data stored in WQDay data structures.
+	  
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Facets are
+	  now generated dynamically from the current available ones stored with
+	  the artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java: Added
+	  methods computeAdvance() and computeFeed() called if artifact is fed or
+	  adance. This overwritten in subclasses to do the state depending calculations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java: These
+	  states overwrites the computeAdvance() and computeFeed() methods to do
+	  the corresponding WINFO calculations.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Calls 
+	  computeAdvance() and computeFeed() if artifact is fed or advanced. Centralized
+	  the caching mechanism.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java,
+	  src/main/java/de/intevation/flys/collections/AttributeParser.java,
+	  src/main/java/de/intevation/flys/collections/OutputParser.java,
+	  src/main/java/de/intevation/flys/collections/AttributeWriter.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adjusted the code to cope with the indices of the facets. Used
+	  DOM to access the attributes instead of XPath. Removed smaller bugs
+	  concerning position generation.
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/OutGenerator.java:
+	  Forwarded facet references.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Uses facets to fetch data and generate output now.
+
+2011-06-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a new parameter "wq_free" that
+	  determines the mode of calculation 1. If it is "false" (default),
+	  the calculation should be bound to a gauge.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method to retrieve the information about the "wq_free" parameter.
+
+2011-06-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a facet for corrected W in
+	  computation 4.
+
+2011-06-14  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java:
+	  Got rid of namespace in result document.
+
+2011-06-14  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue77 (Diagramm: Beschriftung der Kurven bei Dauerlinien)
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Give the curves in the chart names.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added titles for duration
+	  chart curves.
+
+2011-06-14  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java:
+	  Write top 'Oberkante' and bottom 'Unterkante' to out going XML
+	  if they exist.
+
+2011-06-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Write the min/max W/Q ranges as art:range elements into the DESCRIBE.
+
+2011-06-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  This state that is used to retrieve locations will now write the
+	  kilometer range of the selected river into the DESCRIBE document.
+
+2011-06-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation1.java:
+	  New. Factored out version of "Wasserspiegellage" calculation.
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+
+	  Removed some dead code.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQCKms.java:
+	  Added Override annotation and used quick access method.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Looped through error reporting use by interpolate.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Used factored out version of calculation 1. Removed dead code.
+
+2011-06-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation2.java:
+	  New. Factored out version of "Abflusskurve".
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Loop errors through w/q at km interpolation.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use factored out version of calculation 2.
+
+2011-06-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation3.java:
+	  New. Factored out version of "Dauerzahlen".
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Loop errors through for q->w interpolations.
+	  
+	* src/main/java/de/intevation/flys/artifacts/model/WQDay.java:
+	  Added constructor to directly create with calculated results.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  Added method to return the number of problems.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use factored out version of calculation 3.
+
+2011-06-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQCKms.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WQDay.java:
+	  Added methods to remove NaN values.
+
+2011-06-10  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation.java:
+	  New. Base class for calculations. Used to collect problems occuring
+	  during calculation.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Extends Calculation now. Looped through the problem reports to
+	  base class.
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Looped through the problem reports.
+
+2011-06-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java:
+	  Append the min/max range and a transformation matrix for each axis.
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java:
+	  Instantiate the InfoGeneratorHelper with a XYChartGenerator instance.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Changed the zoom operation. The zoom values defined in the chart request
+	  document are no longer absolute values for a specific axis. Those values
+	  represent percental values for the start and end point of x and y axes.
+	  E.g. a chart has three axes with the following ranges:
+	    - x axis  :  0 - 10
+	    - y axis 1: 20 - 40
+	    - y axis 2: 40 - 90
+	    - zoom values for x: 0.1 - 0.9 (10% - 90%)
+	    - zoom values for y: 0.2 - 0.8 (20% - 80%)
+	  The produced chart will have the following ranges:
+	    - x axis  :  1 - 9
+		- y axis 1: 24 - 36
+		  y axis 2: 50 - 80
+
+2011-06-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  Map datasets to axes correctly.
+
+2011-06-08  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Determine the gauges by their station positions. This hopfully
+	  fixes the problem with wrong assigned gauges and invalid segments.
+
+2011-06-08  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Segment.java,
+	  src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Added more debug output.
+
+2011-06-08  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue103 PART 1 (WINFO: Wasserspiegellagenberechnung / Layout-Inkonsistenz)
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Selected values are formatted with the current locale. The static part
+	  of the DESCRIBE document will now contain i18n formatted numbers.
+
+2011-06-08  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue93 (WINFO: Benennung der Berechnungsart korrigieren)
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties: Changed the name of
+	  calculation 4.
+
+2011-06-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Made the range determination more robust against NaN values.
+
+2011-06-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  The second y axis is set to position "1". It was set to "2" before, but
+	  in that case, there was no position "1".
+
+2011-06-08  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  s@m³/s@m\\u00b3/s@
+
+2011-06-08  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java:
+	  Only generate an axis element if a axis really exists.
+
+2011-06-07  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Simpified array swapping.
+
+2011-06-07  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Use java.util.List instead of java.util.Vector
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Removed superfluous imports.
+
+2011-06-07  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  createItem() is not abstract any longer to avoid code repetitionin sub classes.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Removed duplicated code, inserted default constructors and Override annotations.
+
+2011-06-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java:
+	  Append axes range information to the info document.
+
+2011-06-07  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  If feed() fails do not store invalid values in database.
+
+2011-06-06  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java:
+	  Removed asymmetrical "- 1" from width calculation.
+
+2011-06-05  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  Fixed problem when more than one value per segment are given.
+
+2011-06-05  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	Refactored version of "Berechnung 4"
+
+	* src/main/java/de/intevation/flys/artifacts/model/Segment.java:
+	  Added instance fields for a reference point (= location of gauge)
+	  and backup of values (needed for naming).
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQCKms.java:
+	  Added a constructor to be created from a WQKms. This is helpful
+	  if a WQKms is replaced by a back jump correction.
+
+	* src/main/java/de/intevation/flys/artifacts/model/Calculation4.java:
+	  New. Outfactored version of "W bei ungleichmaessigen Abflusslaengsschnitt".
+	  Much cleaner now and it should have a better handling of the corner
+	  cases.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Removed the linear interpolation stuff. It is now in Linear. Removed
+	  the LinearRemap interpolation method because it is not needed any
+	  longer. Added a method to interpolate a given km with a given
+	  function.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Removed the old calc 4 and used the new one.
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearRemap.java:
+	  Deleted. Not needed any longer.
+
+	* src/main/java/de/intevation/flys/artifacts/math/Function.java:
+	  New. Interface for a uni-variate real function.
+
+	* src/main/java/de/intevation/flys/artifacts/math/Identity.java:
+	  New. Implements Function with f(x) = x
+
+	* src/main/java/de/intevation/flys/artifacts/math/Linear.java:
+	  New. Implements Function with f(x) = m*x + b
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Factored some stuff out to DoubleUtil. Removed some dead code.
+	  Does some rounding correct.
+
+	* src/main/java/de/intevation/flys/utils/DoubleUtil.java: New.
+	  Centralized utils surrounding common double operations.
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java:
+	  Removed superfluous imports.
+
+2011-06-03  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue90(Diagramm: Trennung der Diagrammfläche und Achsen aufheben)
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Determine the ranges of x and y axes. If no zoom ranges are given, we
+	  will determine the min and max xy values in the dataset manually,
+	  because JFreeCharts adds a margin to the left and right of the data
+	  area automatically..
+
+2011-06-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Moved
+	  the chart creation into an own public method. This lets the
+	  ChartInfoGenerator create charts without duplicating code.
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java:
+	  Limited the possible class for generators to XYChartGenerator. This
+	  enables the ChartInfoGenerator class to do the whole chart creation
+	  stuff itself without outsourcing the code to concrete subclasses.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveInfoGenerator.java:
+	  Removed the code to generate charts - this is done in ChartInfoGenerator
+	  now.
+
+2011-06-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added new
+	  methods to extract the x and y ranges from request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added a
+	  method that zooms the chart to the specified x and y ranges.
+
+2011-06-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Set collection ttl to 6 hours.
+
+2011-06-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/Segment.java:
+	  New. Parse segments only once.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Expose new parser to artifacts. TODO: Use it.
+
+2011-06-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Changed interpolation methods to interpolate to an arbitrary position
+	  in a given double result array as a preparation for segment independent
+	  calculation.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed superfluous import.
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Fixed cause for crashing: Run back too far in some siutations.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Refactored range code a bit. Needs more work.
+
+2011-06-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java:
+	  Interchange the lower and upper x value of the chart if the x-axis is
+	  inverted before the matrix values are computed. Now, the matrix is able
+	  to work with charts that have an inverted x-axis.
+
+2011-06-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java: New. A
+	  chart info generator generates a document that contains meta information
+	  for a specific chart. Concrete instances of this abstract class need to
+	  instantiate concrete ChartGenerators and dispatch nearly all methods of
+	  an OutGenerator (init(), doOut(), setMaster()) to this instance. The
+	  generate() method is implemented in the ChartInfoGenerator itself. It
+	  creates a chart with help of the ChartGenerator instance and builds a
+	  document that contains meta information of this chart.
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java: New.
+	  This helper is used to create the chart info document. At the moment,
+	  the only information that is included in this document is a
+	  transformation matrix to transform image coordinates into chart
+	  coordinates.
+
+	  NOTE: The transformation matrix creation needs some work to support
+	  charts with inverted X axis.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveInfoGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveInfoGenerator.java:
+	  Concrete instances of ChartInfoGenerator that create the chart info for
+	  the currently supported chart types.
+
+	* doc/conf/conf.xml: Registered new OutGenerators.
+
+2011-05-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added support for the 'type' paramter of the collection's out()
+	  operation.
+
+2011-05-31	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Use an explicit reference km for interpolation now.
+
+2011-05-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/cache.xml: Introduced cache to store the distance info per river.
+	  This boosts performance in following ab setup from  3.61 to 39.91 requests/secs.
+
+	  $ ab -c 20 -n 1000 -p distances.xml http://127.0.0.1:8181/service/distanceinfo
+
+	  $ cat distances.xml
+	    <?xml version="1.0" encoding="UTF-8"?>
+	    <art:river xmlns:art="http://www.intevation.de/2009/artifacts">Elbe</art:river>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java: Added
+	  an iterator result to avoid construction expensive interim lists.
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java: Use
+	  the cache if configured.
+
+2011-05-30	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	flys/issue82
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Only successful interpolations are named.
+
+2011-05-27	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Make it work independent of river flow direction.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Fixed bug in ordering segments
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Re-enabled
+	  calculation of the back jump correction. Fixed more flow direction issues.
+	  
+2011-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed call of XMLDebug class which is not in the version control.
+
+2011-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Moved the code part that skips themes right after the part that sets the
+	  master artifact for the OutGenerator. We need this master artifact to
+	  display empty charts - master artifact is used to create titles and
+	  axes.
+
+2011-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Deactivated themes are not put into the chart.
+
+2011-05-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java:
+	  Repaired broken XPath expressions to find the output modes in an
+	  attribute document of a collection.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: This
+	  writer will now create a document that has a root node art:attribute.
+	  Before these changes, the document's root node was art:outputs which is
+	  part of the attribute document but not the right root node.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted some XPath expressions and corrected the the process to create
+	  attribute documents.
+
+2011-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearRemap.java:
+	  Made it work independent of from/to order.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Added method to extract the ranges correctly from data.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use the correct ranges. Comment out backjump detection temporarily.
+
+2011-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Generate fields for w/q input depend on flow direction.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Moved km up question out of loop.
+2011-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Take the flow direction into account.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Prevent NPE.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java:
+	  Allow to be 'from' greater than 'to' in ranges.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Negate
+	  step if 'from' is greater than 'to'.
+
+2011-05-26	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Round exploded values to a precision of 1e-6.
+
+2011-05-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Qs are now stored in ranges for each column.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory2.java:
+	  Deleted.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Was WstValueTableFactory2.
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java: Fixed
+	  node linking bug. Removed dead code.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Q values are now stored in range trees by each column. The qs of the rows
+	  are removed and the calculations are adjusted. Removed dead code.
+
+2011-05-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Moved cache name to WstValueTableCacheKey. Do not cache null references.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableCacheKey.java:
+	  Moved cache name into this class.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Store QRangeTree for each column of value table. TODO: Use them!
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory2.java:
+	  Intended as a replacement for WstValueTableFactory, but is work in progress.
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java: Fixed
+	  index errors and added methods to dump as graphviz graph.
+
+2011-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Improved the validation of WQ values.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Use the
+	  correct input data object to determine the selected WQ mode (range or
+	  single input).
+
+2011-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-40 (part I/II)
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Write default values (values already selected by the user before) of the
+	  input data items into DESCRIBE.
+
+2011-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Removed needless imports.
+
+2011-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-85 (part III/III)
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Added a static function that returns the kilometer values (double[])
+	  from locations input (whitespace separated double values).
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Adapted
+	  the getKms() method. It will now return a computed array of kilometers
+	  if we had inserted a range, or it will return the inserted kilometers if
+	  we had inserted locations.
+
+2011-05-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Moved cache key to separate class.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableCacheKey.java:
+	  New. The new cache key class.
+
+2011-05-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java:
+	  Model to store the q values of a WST column efficiently. First
+	  building block not to store the q values directly aside the
+	  w values.
+
+2011-05-24  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-85 (part I/III)
+
+	* doc/conf/artifacts/winfo.xml: Added two further field 'ld_mode' and
+	  'ld_locations' to the range/locations state to track the selected mode
+	  and locations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Added methods to validate the user inserted locations.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method to determine of a range or locations have been inserted.
+
+2011-05-23  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-62 (part II/II)
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Dump the
+	  artifacts state/data in DEBUG mode in describe().
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  States will no longer store data. The only reason for states storing
+	  StateData is to know about the necessary data for this state. If a State
+	  needs to access the user input for a specific StateData object, it needs
+	  to query the FLYSArtifact which stores the data.
+
+2011-05-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method to dump the artifacts state(s)/data.
+
+2011-05-23	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	flys/issue84
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstFactory.java:
+	  Forget to select wst kind.
+	  
+2011-05-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	flys/issue81
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  weights for kms were swapped. 
+
+2011-05-20  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 2.3.1
+
+2011-05-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* Changes: Prepared changes for the upcoming release.
+
+2011-05-20  Hans Plum <hans@intevation.de>
+
+	* NEWS:
+	Hint to Release 2.3.1. For further information look into module
+	flys-client/NEWS
+
+2011-05-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java (getExplodedValues):
+	  Increment kms array size by one to take the end of range, too.
+
+2011-05-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  Sort by rows (should not be necessary).
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Use unsharp km lookup (epsilon = 0.0001). This fixes the problem
+	  that some kms were not found.
+
+2011-05-19  Ingo Weinzierl <ingo@intevation.de>
+
+	  flys/issue66
+
+	* src/main/resources/messages_en.properties: Fixed broken template.
+
+2011-05-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Set the name of the computed discharge curve objects.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  The curves of this chart will now have names that consist of the word
+	  'Discharge Curve', the river name and the kilometer that has been used
+	  for the computation.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  computed discharge curves.
+
+2011-05-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Added convenience method isQ() to determine if we are
+	  doing Q calculations.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Reintroduced titles for the "W for unausgeglichene Abfluesse".
+
+2011-05-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  The curves will now have names that consist of the gauge name and its
+	  valid time range.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  discharge curves.
+
+2011-05-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Removed dead code.
+
+2011-05-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use the correct method to generate kms.
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearRemap.java:
+	  Added some logging to test the map in debug mode.
+
+2011-05-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Made getExplodedValues static.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use new logic to calculate "W für ungleichwertige Abfluesse".
+	  Not working, yet.
+
+	* ChangeLog: Fixed former entry.
+
+2011-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Registered the WST export for discharge
+	  longitudinal sections.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Set the
+	  names of the discharge longitudinal section computation results.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java:
+	  The W/Q curves in the chart will now have names.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  The WstWriter is filled with column names in an own method. So, we are
+	  able to override this process in subclasses.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  Adapted the column names for the WST export.
+
+2011-05-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	Work on flys/issue69
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use new logic to calculate "Wasserstand/Wasserspiegellage".
+	  Compared to desktop FLYS are the results are structurally right 
+	  but a bit off in the positions after the decimal points.
+	  Maybe a result of the interpolation? Need to debug this.
+
+2011-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  W and Q curves will now have names based on the defined W or Q values
+	  for the waterlevel computation.
+
+2011-05-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/NamedObject.java:
+	  New. This object is used to give objects a name.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Inherit
+	  from NamedObject now. Because we need to display names for those objects
+	  in different places.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The WQKms
+	  objects returned by a waterlevel computation will now have names.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Insert the column names for the WSTs into the WstWriter.
+
+	* src/main/java/de/intevation/flys/exports/WstWriter.java: The column
+	  names are written into the head of the WSTs now.
+
+2011-05-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstLine.java: New. This
+	  class is used to store the W/Q values of a specific kilometer of a WST.
+
+	* src/main/java/de/intevation/flys/exports/WstWriter.java: New. A writer
+	  that creates WSTs.
+
+	  TODO: The header of the WSTs is not finished. The Q descriptions are
+	  missing.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java:
+	  Enabled WST exports.
+
+	* doc/conf/artifacts/winfo.xml: Registered the WST export for waterlevels.
+
+2011-05-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/utils/Formatter.java: New. This class
+	  supports functions to retrieve formatters for specific types of data
+	  used in FLYS.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Removed the formatter declaration - the whole formatter stuff is done in
+	  Formatter now.
+
+2011-05-17  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-72
+
+	* src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java:
+	  Repaired broken XPath expressions to extract start and end kilometer.
+
+2011-05-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Removed the Hibernate loading stuff.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java:
+	  New. The Hibernate loading.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Use the WstValueTableFactory for loading now.
+
+2011-05-17  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 0.1 aka Version 2.3.0
+
+2011-05-16  Hans Plum <hans@intevation.de>
+
+	* NEWS:
+	New. Giving some user specific perspective to new functionality and
+	changes. This file references releases dates only; details can be find
+	in the client module at flys-client.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First step to calculate "W fuer ungleichwertige Abfluesse" correctly.
+	flys/issue55
+
+	* src/main/java/de/intevation/flys/artifacts/math/LinearRemap.java:
+	  New. Remaps "gleichwertige" Q values to the corresponding
+	  "ungleichwertige" Q values depending on km.
+	  
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Remap the Q values "ungleichwertig" depending on the 
+	  "gleichwertige" ones.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	First step to fix flys/issue69
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  New code path to implement the calculation of "Wasserstand/Wasspiegellage"
+	  correctly. TODO 1: Use new path in UI. TODO 2: Remove unused old code.
+
+2011-05-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* Changes: Prepared Changes for the upcoming release 2.3 - see Changes
+	  file to get to know about the changes of the version numbers.
+
+2011-05-13  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-37
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java: This
+	  state would be happy if there is a UI provider called "river_panel".
+
+2011-05-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added new
+	  methods that return the requested chart size as integer array [width,
+	  height]. The requested size is read from the incomding request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: The size
+	  of a chart is no longer static. The requested size is fetched using
+	  ChartGenerator.getSize().
+
+2011-05-11  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-52
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  The X-Axis of such a chart is inverted, if the head of the river is not
+	  at kilometer 0. This type of charts always have the head of the river at
+	  the left side.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Make incoming XML symmetric to DistanceInfoService.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-47
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Adjusted temporarily the color of the W, Q and corrected W curves to
+	  distinguish each other.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Adjusted the plot of xy charts - the gridlines are displayed now.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	  ISSUE-53
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  Added a method that returns the preferred locale based on the available
+	  locales of the server and the desired locales of the request (CallMeta).
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: Added a
+	  method that creates a number formatter with minimum and maximum digits.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Formatted the number values of the CSV exports.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RangeWithValues.java:
+	  New. A data structure that enables us to save a data triple: a range
+	  that consist of lower and upper double value and a set of values that
+	  belong to this range.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Implemented the validation of W/Q values.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: New
+	  method to retrieve i18n messages based on keys.
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java,
+	  src/main/java/de/intevation/flys/exports/WaterlevelExporter.java,
+	  src/main/java/de/intevation/flys/exports/DurationCurveExporter.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Added headers for CSV exports.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added new i18n strings for
+	  CSV headers.
+
+2011-05-10	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Fetches river name from incoming XML document. If no river is given all 
+	  infos about all rivers are listed.
+
+	* src/main/resources/metadata/template.xml: Templates honors the 'river'
+	  parameter.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Extended to pass parameters to the templating. Added support for
+	  type conversion.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/StackFrames.java:
+	  Take parameters as an initial stack frame.
+	  
+	* src/main/java/de/intevation/flys/artifacts/services/meta/TypeConverter.java:
+	  New. Converts types off stacked variables.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Added a
+	  method to add subtitles to charts. The implementation in this class does
+	  not add any subtitle. Concrete subclasses may override this method to
+	  add some.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Add subtitles to charts.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n templates for
+	  compound messages (chart subtitles).
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  Added new methods to retrieve translated compound messages.
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: Added a
+	  method to set the master artifact that should be used for some special
+	  operations.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/AbstractExporter.java:
+	  Implement the setMasterArtifact() method of the interface.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Call OutGenerator.setMasterArtifact().
+
+	  NOTE: The determination of the master artifact needs to be implemented!
+
+2011-05-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Removed
+	  hard coded dev code that defined a WQ mode.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  Stripped ugly extra whitespace from output introduced by
+	  templating.
+
+2011-05-09	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/resources/metadata/template.xml: Added forgotten
+	  columns of fixation WSTs.
+
+2011-05-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Added a
+	  method to retrieve i18n strings.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Enabled i18n support for chart title and axes labels.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  chart types above.
+
+2011-05-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java:
+	  New. This OutGenerator exports the data of a discharge longitudinal
+	  section computation.
+
+	* doc/conf/conf.xml: Added the DischargeLongitudinalSectionExporter.
+
+	* doc/conf/artifacts/winfo.xml: Added the exporter with CSV facet to the
+	  discharge_longitudinal_section state.
+
+2011-05-07	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+	  Added code to make back jump correction work with both
+	  potential flow directions.
+
+2011-05-06	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/resources/metadata/template.xml:
+	  Added data cage configuration for 'Längsschnitt'.
+
+2011-05-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added the missing Q facet for discharge
+	  longitudinal sections.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQCKms.java: New. A
+	  derived dataset to store W/Q values with corrected Ws for a kilometer
+	  range.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Some new
+	  methods and a new constructor to initialize this data object with a
+	  predefined set of values.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New
+	  methods to retrieve the W/Q values for the 'discharge longitudinal
+	  section' computation.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: New
+	  methods to retrieve and compute data used for the 'discharge
+	  longitudinal section' computation.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java:
+	  Bugfix: just write the ranges of gauges into the DESCRIBE if the
+	  'wq_values' data item is required.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n messages used in
+	  the DESCRIBE of the WQAdapted state.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Changed the title of the y-axis (now 'W [NN+m]').
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Improved the transition model to reach the
+	  output state for creating 'discharge longitudinal section' charts.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added a
+	  method that returns all gauges of the selected river based on a the
+	  given kilometer range.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java: New.
+	  This state creates a set of elements for the DESCRIBE that consist of a
+	  tuple of kilometer values. The number of elements depend on the number
+	  of gauges intersected by the given kilometer range.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java:
+	  New. This state is the output state that is reached after the 'discharge
+	  longitudinal section' computation has been chosen.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Default
+	  step width between two kilometers added - if no step width is given,
+	  this default width is used.
+
+2011-05-05  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Fixed a potential NullPointerException if there is just a single
+	  kilometer given to create a longitudinal section.
+
+2011-05-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added http://repository.jboss.org/maven2 repo
+	  to fix flys/issue30
+
+2011-05-04	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  To make diagram generation possible ws are now generated from qs
+	  because they are many ws having different qs.
+
+2011-05-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  Fetch the WQ data from WINFO artifact and write those values into the
+	  CSV export.
+
+2011-05-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  methods to compute and retrieve the data for discharge curves (computed).
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  Fetch the computed discharge curve data from WINFOArtifact and add the
+	  values into the JFreeChart dataset.
+
+2011-05-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added new transitions and states to enable
+	  the WINFO artifact for computing discharge curves.
+
+	* doc/conf/conf.xml: Added OutGenerators that generate computed discharge
+	  curves and exports for its data.
+
+	* src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java:
+	  New. This state is reached if the user chose the computed discharge
+	  curve.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java:
+	  New. This is only a stub implementation at the moment. This
+	  OutGenerator should create computed discharge curves later. It extends
+	  the DischargeCurveGenerator which should do the same stuff for discharge
+	  curves for gauges.
+
+	* src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java:
+	  New. This is only a stub implementation at the moment. This OutGenerator
+	  should create the exports of the discharge curve computation.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a new output mode for the duration
+	  curve state (CSV export).
+
+	* doc/conf/conf.xml: Added a new OutGenerator to export duration curve
+	  computations.
+
+	* src/main/java/de/intevation/flys/exports/AbstractExporter.java: New.
+	  This abstract OutGenerator represents the base class for exporting
+	  computed data. Currently, the CSV export is supported.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: Moved
+	  the most code to export to CSV into the AbstractExporter.
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveExporter.java:
+	  New. This exporter exports the computed data of a duration computation.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Modified a wrong debug statement which would confuse the user.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added a new output mode for the waterlevel
+	  state (CSV export).
+
+	* doc/conf/conf.xml: Added a new OutGenerator to export waterlevels.
+
+	* src/main/java/de/intevation/flys/exports/WaterlevelExporter.java: New.
+	  This OutGenerator exports the data of a waterlevel computation. Note:
+	  It is necessary to specify the desired facet (e.g.
+	  'waterlevel_export.csv').
+
+	* pom.xml: Added a dependency to OpenCSV.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Introduced an "export" output target. If the target is "export", a
+	  facet (read from the incoming xml document) is a necessary parameter
+	  that determines which facets are written to the output.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_en.properties: Bugfix: replaced german
+	  string (copy & paste mistake).
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added new i18n strings for
+	  for the location selection.
+
+2011-05-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java:
+	  New. An OutGenerator for creating duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQDay.java: New. A
+	  model class to store necessary data for creating W and Q facets of a
+	  duration curve. This model stores W, Q and Days.
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesFactory.java:
+	  Added a function to retrieve tuples of (day, q) based on a given gauge -
+	  these tuples are necessary for creating duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  methods to compute and retrieve the data necessary for creating duration
+	  curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  Bugfix: improved the access to the location array (avoid
+	  NullPointerException).
+
+	* doc/conf/conf.xml: Registered the new OutGenerator for duration curves.
+
+2011-05-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/visualize-transitions.xsl: State quoting was done wrong.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Enhanced the transition model to reach the
+	  final state for creating duration curves.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java:
+	  New. This state should be reached to just insert an array of locations.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java:
+	  New. This state is reached if the duration curve calculation is
+	  selected.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Adjusted
+	  getDistance() so that it takes care on inserted locations - not just
+	  inserted ranges.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java:
+	  Flush/persist caches at program exists.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Commented out too eloquent debug output.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Uses the methods of FLYSArtifact to retrieve the necessary information
+	  rivername and selected distance.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Added interpolateW method to take reference to result ws array
+	  as an argument to avoid expensive array allocations in km iterating
+	  loops.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+	  Added a constructor to create backing trove datastructure
+	  with the right capacity.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Move allocation of result ws out of km loop.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added two further data items in the WQ
+	  selection state. Those items are necessary to store the information
+	  about the selected mode (range/single selection) and  the values of the
+	  single selection.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java: If
+	  there is no value for a data item, this item is not written into the
+	  static DESCRIBE part.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java,
+	  src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Renamed the method to validate upper and lower values.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: This
+	  state can now handle values inserted in the single selection. Therefore,
+	  new validate methods has been added.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: The
+	  methods getWs() and getWs() take care on the values inserted in the
+	  single insert mode of the client which enables the user to insert single
+	  W and Q.
+
+2011-05-02	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Repaired getQForW() by calculating indices on right dimension.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New
+	  methods for retrieving selected W values (getWs()).
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  method for retrieving waterlevel data takes care on selected Ws, now.
+	  The selected Ws are transformed using the DischargeTables.getQForW()
+	  into Q values.
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Removed an unused parameter 'result' of getQForW().
+
+2011-05-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: Removed
+	  the getDataset() method and replaced it with a addDataset() method.
+	  On this way, concrete subclasses of this OutGenerator can have multiple
+	  datasets (e.g. different datasets for W and Q). This abstract method is
+	  called after the chart generation is finished.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Both classes implement the addDataset() method. The
+	  LongitudinalSectionGenerator has already multiple datasets for W and Q.
+	  Both are added to the chart - both have an own range axis.
+
+2011-05-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Use the cache for the wst value table if configured.
+
+	* doc/conf/cache.xml: Choose a more precise name for the 
+	  wst value table cache.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to Ehcache. Apache 2.0 license.
+
+	* doc/conf/conf.xml: Added configuration of ehcache.
+
+	* doc/conf/cache.xml: New. Cache configurations.
+
+	* src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java:
+	  New. Factory to access caches.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Forgot to add.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/Builder.java:
+	  New. Given a database connection and a XML template it generates
+	  an output with meta data about the database.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/CompiledStatement.java:
+	  New. Holds prepared statements optimized to be run in the stack of
+	  contextes.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/StackFrames.java:
+	  New. Model to hold a hierarchical scope of variables.
+
+	* src/main/java/de/intevation/flys/artifacts/services/meta/ResultData.java:
+	  New. Stores data set fetched from a sql select to be iterated in
+	  a context.
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java,
+	  src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed superfluous imports.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Added meta data service.
+
+	* src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java:
+	  Stub for the meta data service.
+
+	* src/main/resources/metadata/template.xml: Initial template for
+	  the meta data service.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Bugfix: Now, curves for Q values are drawn into a longitudinal section
+	  chart as well. Therefore, it was necessary to change the datastructure
+	  of the inner class ThemeList that stores all themes included in a chart
+	  in an ordered list (stored in a java.util.Vector now).
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java: New. An
+	  abstract base class for ChartGenerators that create XY charts.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Sourced the generate() method out to the XYChartGenerator.
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  Implemented the methods to add W and Q facets to the chart.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Improved
+	  the calculation of the step with for ranges.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Added a
+	  method that returns the number of elements stored in the data pool.
+
+2011-04-29	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java:
+	  Added a get() method which takes destination array as an
+	  argument.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: Changed the
+	  data structure to store w, q and kms values from List<Double> to
+	  TDoubleArrayList which stores native double values instead of big
+	  Double values.
+
+	* pom.xml: Added the GNU Trove dependency.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: Added the
+	  name a the requested facet to doOut(). Concrete generators should just
+	  create output for this facet now.
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java,
+	  src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java,
+	  src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  Adapted the method signature of doOut().
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted the method call of OutGenerator.doOut().
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: Added mappings for the facets
+	  longitudinal.section.w and longitudinal.section.q.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Added some
+	  methods to retrieve necessary information for computing the data of a
+	  waterlevel.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  methods to compute and retrieve the data of a waterlevel computation.
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WQKms.java: New. This
+	  model class represents a pool of data triples that consist of W, Q and
+	  Kms information. This class might be used to compute data for creating
+	  longitudinal section curves (which are based on those W, Q and Kms
+	  values).
+
+2011-04-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Bugfix: Removed endless loop and a bug while iterating over Hibernate
+	  results.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java:
+	  New. An OutGenerator that creates longitudinal section curves.
+
+	  NOTE: This is just the stub - the out creation needs to be implemented!
+
+	* doc/conf/conf.xml: Added the LongitudinalSectionGenerator.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Bugfix: Added missing <output-generators> section.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WaterlevelState.java: New.
+	  This state should be reached if the 'calc.surface.curve' calculation
+	  method has been chosen.
+
+	* doc/conf/artifacts/winfo.xml: Modified a transition and added the
+	  WaterlevelState. This state is reached if the 'calc.surface.curve'
+	  calculation method has been chosen. It currently has 1 output - a
+	  longitudinal section that is not implemented yet!
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/ValueCompareTransition.java:
+	  New. This transition is valid if the a data object of the current
+	  artifact equals/notequals a configured value in the transition model.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Bugfixes:
+	  - Adapted the parameters of the isStateReachable() call - added the
+	    artifact and the current state.
+	  - Append the outputs of a current state if the state is filled with
+	    valid data.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java:
+	  Added the missing init() method that has been introduced in the
+	  interface some commits ealier.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java:
+	  Call init() after a Transition has been created.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java:
+	  Bugfix: Introduced a <art:outputs> node in the attribute document of a
+	  Collection that contains further <art:output> nodes - instead of having
+	  multiple <art:output> nodes at toplevel of the document.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Adapted the XPath to retrieve the outputs in the attribute document of a
+	  Collection.
+
+2011-04-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* contrib/visualize-transitions.xsl: Added to create a 
+	  Graphviz digraph out of the config.xml. Usage:
+
+	  $ xsltproc --stringparam base-dir ../doc/conf/ \
+	    contrib/visualize-transitions.xsl \
+	    doc/conf/conf.xml > transitions.dot
+
+	  $ dot -Tsvg -o transitions.svg transitions.dot
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  If no data has been inserted so far, an IllegalArgumentException is
+	  thrown.
+
+2011-04-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java::
+	  A lot of new methods to retrieve the theme of a facet - used while
+	  creating the output of a facet/artifact. If a facet has no theme yet, it
+	  is initialized.
+
+2011-04-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java,
+	  src/main/java/de/intevation/flys/collections/OutputParser.java: Removed
+	  useless imports.
+
+2011-04-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: New. An
+	  abstract OutGenerator that might be used to create chart output. Some
+	  basic things that are equal in all charts should be done here!
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  This OutGenerator no longer implements the OutGenerator directly, but it
+	  extends the ChartGenerator now.
+
+2011-04-27  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Restructured the out() operation of a collection: Now, we collect a list
+	  of artifacts/facets and use this for the OutGenerator. Its doOut()
+	  method gets the attribute of an artifact - the position and the active
+	  state is managed by the Collection itself.
+
+2011-04-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java: New.
+	  a specialized facet that stores information about its position and its
+	  state (active/inactive) in an output of a collection.
+
+	* src/main/java/de/intevation/flys/collections/AttributeParser.java: New.
+	  This parser takes the attributes (XML) of a collection and extracts the
+	  contained outputs with its facets. The result is a Map<String, Output>.
+
+	* src/main/java/de/intevation/flys/collections/OutputParser.java: New.
+	  This parser is used to query the artifact's DESCRIBE and to extract the
+	  supported outputs. The result is a Map<String, Output>.
+
+	* src/main/java/de/intevation/flys/collections/AttributeWriter.java: New.
+	  This writer merges the outputs contained in an attribute of a collection
+	  with the outputs of a collection's artifacts.
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  The attributes of a collection are written into its DESCRIBE document
+	  now. The OutputParser and AttributeParser are used to read the supported
+	  attributes by the collection and its artifacts - the AttributeWriter is
+	  used to merge both attributes and create a final attribute document.
+
+2011-04-26  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java,
+	  src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  The facet-2-theme mappings are initialized at startup and stored in the
+	  FLYSContext.
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: Added a
+	  function that retrieves a theme from FLYSContext based on its name.
+
+2011-04-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Corrected a silly c&p mistake.
+
+2011-04-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java:
+
+	  Implementation of the "Ruecksprungkorrektur" to be done in
+	  "W fuer angepassten Abflusslaengschnitt".
+
+	  All tests show the expected results. In some corner cases the
+	  algorithm described in the "Anwenderhandbuch" chapter 3.3.4.3 "Korrektur"
+	  has some definition shortcomings:
+
+	  a - What should happend when you cannot find point 2 because
+	      you cannot step back one quarter from point 3 because there
+	      is no data there any more (river too short in this direction)?
+	      The implemented algorithm raises point 3' only to an
+	      according factor. E.g. If you can step back the whole quarter
+	      distance the elevation is the full quarter. If you can
+	      step back only the half of the quarter the elevation is
+	      only an eighth.
+
+	  b - If the water heights between point 2 and 3 are constant then
+	      the algorithm will produce a spline interpolation that
+	      lowers those values. Is this intended?
+
+	  For real data the back jumps are expected to be more in the middle
+	  of the distance ranges so the corner cases are maybe not so
+	  important.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Removed superfluous import.
+
+2011-04-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: Removed debug
+	  code that has been commited by accident.
+
+2011-04-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/themes.xml: New. A first small theme configuration.
+
+	* doc/conf/conf.xml: Added a link to the theme configuration.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java:
+	  Defined a key that is used to store a themes map in the FLYSContext.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  The theme configuration is read at startup and the themes are stores in
+	  the FLYSContext.
+
+2011-04-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/themes/Theme.java,
+	  src/main/java/de/intevation/flys/themes/DefaultTheme.java:
+	  New. The interface and its default implementation that represents themes
+	  used to style charts and maps.
+
+	* src/main/java/de/intevation/flys/themes/ThemeField.java,
+	  src/main/java/de/intevation/flys/themes/DefaultThemeField.java:
+	  New. The interface and its default implementation that represents fields
+	  in themes. A theme might be "Lines" and one of its field might be
+	  "Color" or "Size".
+
+	* src/main/java/de/intevation/flys/themes/ThemeFactory.java: A factory
+	  that creates new themes based on a theme configuration.
+
+2011-04-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Implementation of "Abflusskurve/Abflusstafel" calculation.
+
+	  Added method interpolateWQ() which takes an km and results in a
+	  tuple of two double arrays containing the w/q values interpolated
+	  between the surrounding w/q values of the table.
+	  w values are interpolated linear, q values with a cubic spline.
+
+	  Drawing w over q gives you the discharge table at the given km.
+
+	  !!! This code needs testing !!!
+
+2011-04-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to Apache Commons Math 2.2 (Apache License 2.0)
+
+2011-04-20	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Fix problem when sorting by q (copied w instead of q).
+
+2011-04-20  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Fixed broken HQL statement.
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  add a method interpolateW() which takes an array of
+	  q values and returns an equal sized array of w values.
+	  This is essentially the "Wasserstand/Wasserspiegellagen" calculation
+	  of desktop FLYS.
+
+	  If you want to do a calculation with given w values you have
+	  to convert the w values with DischargeTables.getQForW() first.
+
+	  !!! This code needs heavy testing !!!
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  We need a getQForW() method and not getWForQ() because when
+	  doing a "Wasserstand/Wasserspiegellagen" calculation with given
+	  w values these values need to be translated to q values with
+	  the master discharge table.
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Sorting of q values was done wrong.
+
+2011-04-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Ordered the list of annotations returned by this factory based on its
+	  range.
+
+2011-04-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java:
+	  New. This service returns an XML document that includes the main values
+	  of a gauge based on a river name, a start and an end point.
+
+	* doc/conf/conf.xml: Registered the MainValuesService.
+
+2011-04-19  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/MainValuesFactory.java:
+	  A factory that provides methods to return MainValues.
+
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Added static method getWForQ() to interpolate a w value for
+	  a given q value based on a given discharge table.
+	
+2011-04-19	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Added convenience constructors/methods to ease the access to the master
+	  discharge table of a gauge.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Moved the query complexity into view 'wst_value_table' and
+	  used this instead.
+
+2011-04-18	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Fetches w/q value tables from the backend. TODO: Move this
+	  to the backend and use a view.
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Changed
+	  the error key that is thrown if no input data was found so that the key
+	  is usable for GWT's i18n mechanism.
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  The inserted river is validated now (overrides validate() of
+	  DefaultState).
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  The inserted calculation method is validated now (overrides validate()
+	  of DefaultState).
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  The input data of feed() are validated using the DefaultStates
+	  validate() method.
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  New method validate() that needs to be overidden by concrete subclasses.
+	  It should return true, if the data of the State is fine, otherwise it
+	  should raise an exception.
+
+	  NOTE: The exceptions are not translated in the server but in the client!
+
+	* src/main/java/de/intevation/flys/artifacts/states/RangeState.java: New.
+	  This abstract class exists to provide some methods for handling ranges.
+	  Currently, there is a method that validates a given range based on
+	  min/max values.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Implemented input data validation for ranges.
+
+	  NOTE: The input validation of concrete values has not been implemented
+	  yet!
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Removed debug code that has been commited by accident :-/
+
+2011-04-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: The
+	  getGauge() method returns the first gauge based on the given start and
+	  end point of the river.
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  Fixed potential bugs: if no gauge could be determined, the default
+	  values for W and Q are the minimum and maximum double values.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java: Fills
+	  the DESCRIBE with default values for W and Q.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstFactory.java:
+	  New. Returns Wst object - based on a river.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Now
+	  provides some methods that return some basic objects inserted while
+	  parameterization: River, Gauge and so on.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Bugfix: Repaired broken imports of the SessionHolder.
+
+2011-04-15  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/SessionHolder.java:
+	  Moved this class to flys-backend.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Adapted imports of the SessionHolder.
+
+2011-04-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* trunk/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java,
+	  trunk/src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Acquire/release sessions in services to avoid db connection leaks.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Changed some method signatures - added a reference to the owner
+	  Artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/WQSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Modified method signatures based on the changes in DefaultState.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  Added default values to the dynamic part of DESCRIBE.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Added a function that returns a River object based on a given river
+	  name.
+
+2011-04-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java,
+	  src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  Static methods are using the SessionHolder, too.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Added the setup() method that has been added to the interface
+	  description in the last commits.
+
+	* doc/conf/conf.xml: Registered the SessionCallContextListener as
+	  CallContext.Listener.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/SessionHolder.java:
+	  Bugfix: Call correct method to retrieve an instance of
+	  SessionFactoryProvider.
+
+2011-04-14	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/SessionHolder.java:
+	  New. Contains thread local session holder for hibernate sessions.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Uses session from SessionHolder.
+
+	* src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Interacts with SessionHolder now.
+	
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  Removed superfluous import.
+	
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java:
+	  Implementation of a CallContext.Listener to open/close Hibernate
+	  Sessions for each request.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Some modifications related to the last commit - modification of the
+	  describe() signature of a State.
+
+2011-04-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/GaugeSelect.java:
+	  Removed. We do not need a state to select a gauge - the selection takes
+	  place by choosing a start and an end point.
+
+2011-04-13  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Bugfix: added missing label node to root node and a namespace to the
+	  data node.
+
+2011-04-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  The name of the artifact 'winfo' is written into the DESCRIBE document
+	  now. We need this to have a proper way to distinguish between different
+	  artifacts in the UI.
+
+2011-04-12  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Bugfix:
+	  States are filled with data before they describe themself.
+
+2011-04-11  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java:
+	  New. A factory that returns the annotations of a specific river.
+
+	* src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java:
+	  New. This service provides a document that contains information about
+	  distances of a river.
+
+	* doc/conf/conf.xml: Registered the DistanceInfoService.
+
+2011-04-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  This collection overrides the out() operation now. The incoming request
+	  document is read and the related OutGenerator is used to create the
+	  output.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	  the code to create discharge curves. It has moved to the
+	  DischargeCurveGenerator which now does this work.
+
+2011-04-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java: The
+	  generate() method throws an IOException now.
+
+	* src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java:
+	  New. This OutGenerator creates discharge curves.
+
+2011-04-06  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: Changed
+	  the visibility of the getData() method. It's now public, because the
+	  OutGenerator needs an artifact's data.
+
+2011-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/**/*.java: Removed trailing whitespace.
+
+2011-04-03	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Make project compilable again by
+	  commenting out a not existing XMLDebug reference.
+
+2011-03-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Added code to parse the configured OutGenerators and to save them (in a
+	  map) in the FLYSContext.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java:
+	  Added a key that is used to save the OutGenerators Map in the context.
+
+2011-03-31  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/OutGenerator.java:
+	  New. This interface is used to generator different types of output.
+	  ArtifactCollections will make use of this interface to create a
+	  collected output of all its artifacts.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Removed the Backend from FLYSArtifactCollection - used ArtifactDatabase
+	  operations instead.
+
+	* TODO: Removed 'remove Backend reference' TODO.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Enhanced the configuration of the
+	  discharge curve output mode. This output now provides three facets - W,
+	  Q and the curve itself.
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	Tagged RELEASE 0.1
+
+2011-03-30  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Renamed an output mode in the WINFO
+	  artifact configuration.
+
+2011-03-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_de.properties: Changed a german string.
+
+2011-03-29  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Modified the available calculation modes and its order in the DESCRIBE
+	  document.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Adapted the names of
+	  calculation modes.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added the hash code of an artifact to the artifact part of the
+	  collection's DESCRIBE document.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Changed
+	  the background color of discharge curves to white.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Cache the scale, too. Otherwise two calls to getValues() with
+	  different arguments will result in the same output.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Introduced a 'scale' parameter in the getValues() method.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Filled
+	  the out() operation with code that draws a discharge table of one or
+	  more gauges specified by the given range in entered in a previous state.
+
+2011-03-28  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartExportHelper.java:
+	  New. A helper class to exports charts.
+
+	* pom.xml: Added dependencies to iText, Batik and JFreeChart.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/PegelGltParser.java:
+	  Fixed swap of operands.
+
+2011-03-28	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java:
+	  New. Load gauges for a river and filter them for given
+	  ranges.
+
+2011-03-25	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  Store data in a double [][] instead of interleaved double []
+	  to be compatible with org.jfree.data.xy.DefaultXYDataset.
+
+2011-03-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* TODO: Removed 'i18n' and 'step-back' TODOs and added an issue to remove
+	  the Backend reference from FLYSArtifactCollection.
+
+2011-03-25  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  Added missing artifact namespace of an attribute in the DESCRIBE
+	  document.
+
+2011-03-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java:
+	  New. This ArtifactCollection overrides the DefaultArtifactCollection to
+	  implement FLYS specific describe() and out() operations.
+
+	* doc/conf/conf.xml: Use the FLYSArtifactCollection instead of the
+	  DefaultArtifactCollection for this application.
+
+2011-03-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java:
+	  New. Fetches values of discharge tables in form of packed
+	  w/q double arrays for given gauges.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Removed needless import.
+
+2011-03-24  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_de_DE.properties: Added a german resource
+	  bundle to avoid exceptions in the flys artifacts. Sometimes, the
+	  Resources class is not able to find a 'de_DE' bundle and throws an
+	  exception. This is really strange, because it should use the 'de' bundle
+	  in that case, but it doesn't.
+
+2011-03-23  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Added
+	  some code to append the output modes of previous states to the DESCRIBE
+	  document.
+
+	  TODO: Determine if the current state is already filled with data and
+	  append its output modes as well!
+
+2011-03-22  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Enhanced the location_distance state with
+	  an output mode 'discharge_table'.
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Write human readable strings as label attribute into the DESCRIBE
+	  output. Those labels are used to be displayed in the GUI.
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  Added some further calculation types.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  calculation types
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented the step-back part of the advance() operation.
+
+2011-03-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/resources/messages_de.properties: Fixed a german umlaut.
+
+2011-03-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Added new states for entering a
+	  location/distance and w/q.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: New string for the
+	  location/distance and w/q input states.
+
+2011-03-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/WQSelect.java:
+	  New. A state for the W/Q input of the WINFO parameterization.
+
+2011-03-18  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Append an attribute 'uiprovider' to the dynamic UI node.
+
+	* src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java:
+	  New. A state for the location/distance selection of the WINFO
+	  parameterization.
+
+2011-03-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a new method getUIProvider() that might be overriden by concreted
+	  subclasses that should be rendered with a specific UIProvider.
+
+2011-03-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  The static UI part is created by the previous states now. This makes it
+	  possible to group the data objects (which is necessary to group the
+	  objects in the ui).
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Added a describeStatic() method that creates a node that contains the
+	  data of that state.
+
+2011-03-17  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  Removed TODO: the document contains the rivers provided by the backend
+	  now.
+
+2011-03-17	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* pom.xml: Added dependency to flys backend.
+
+	* src/main/java/de/intevation/flys/artifacts/model/River.java:
+	  Removed. We are using the backend model now.
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java:
+	  Fetches the rivers from the backend.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Import fixes.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* doc/conf/conf.xml: Added section for database backend configuration.
+
+2011-03-15	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  Fixed build error coming from different import of XMLUtils.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  Appended the missing label node that contains the human readable name of
+	  the data item.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java,
+	  src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  I18N of strings for the DESCRIBE document.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: I18N strings for the
+	  calculcation mode state.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Modified the winfo states.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Adapted
+	  the artifact regarding the changes of the last commit. The states
+	  describe() method creates the dynamic UI node - the artifact needs to
+	  apply this node.
+
+	* src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java:
+	  New. The state for choosing the calculation mode.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Added i18n strings for the
+	  calculation mode state.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/DefaultState.java:
+	  New. This is the base state for the FLYS application. It provides a
+	  method that creates the dynamic ui node for the DESCRIBE.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/GaugeSelect.java: Both
+	  classes extend the abstract base class DefaultState.
+
+2011-03-14  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/resources/Resources.java:
+	  New. This class retrieves the i18n strings from a ResourceBundle.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties: Resource files for german and
+	  english translation.
+
+2011-03-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Appended
+	  the data that have been inserted in former states into the static node
+	  of the DESCRIBE.
+
+2011-03-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: The
+	  operations feed() and advance() return the description of the artifact
+	  using the describe() operation. This avoids additional server round trips
+	  in the client - the clients gets to know about the new state of the
+	  artifact immediately.
+
+2011-03-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented a part (step forward) of the advance operation.
+
+	* TODO: Implement Step-Back in advance operation.
+
+2011-03-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java:
+	  Implemented the abstract method getName(). It returns the constant
+	  'winfo' string.
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java:
+	  Implemented the feed action. The data of an incoming feed() operation is
+	  stored in StateData objects that are saved in a map in the artifact.
+
+	  NOTE: There is no input validation and no i18n of error messages (see
+	  TODO).
+
+2011-03-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* TODO: This file contains some open points that need to be done.
+
+2011-03-09  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java: New. This
+	  artifact serves as the default artifact for the FLYS application.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: This
+	  artifact now inherits from FLYSArtifact. Furthermore, there is one big
+	  change: we don't store the State objects itself in the artifact, but
+	  just the identifier of those. This makes the artifact smaller and more
+	  compatible agains previous versions of the software.
+
+2011-03-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/services/RiverService.java:
+	  New. This service will retrieve a list of provided rivers.
+
+	* doc/conf/conf.xml: Added a configuration for the RiverService.
+
+2011-03-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: Added a section user-factory and collection-factory in
+	  the factories part of the configuration.
+
+2011-03-01  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Replaced the 'special' attribute from DESCRIBE with a 'uiprovider'
+	  attribute.
+
+2011-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  RiverSelect state is called to create the UI part of the describe
+	  document.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java:
+	  Implemented the dynamic UI part of describe(). The static part is not
+	  inserted into the describe document at the moment. We need a reference to
+	  the previous states for this.
+
+2011-02-08  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java,
+	  src/main/java/de/intevation/flys/artifacts/model/River.java: New. A model
+	  class that represents a river and its factory to create concrete river
+	  instances.
+	  NOTE: Currently, this is just a mockup. The factory just returns two
+	  static rivers "Mosel" and "Saar" without a connection to a backend.
+
+2011-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/StateFactory.java: The
+	  input data of a state is initialized with empty StateData objects after
+	  the State has been created.
+
+	* doc/conf/artifacts/winfo.xml: Renamed the input data nodes of the states
+	  which now fits better to the class name of the implementation.
+
+2011-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: The
+	  describe document returned by this artifact now contains the current state
+	  and the reachable states.
+
+2011-02-07  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  The transitions are put into the TransitionEngine with the ID of the state
+	  - not longer with the artifact name. On this way, we are able to fetch
+	  just the transitions for a specific state, instead of all the transitions
+	  of an artifact.
+
+2011-02-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Describe()
+	  returns the artifact's uuid and hash value. The whole implementation of
+	  describe() is still outstanding.
+
+2011-02-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/artifacts/winfo.xml: Removed useless config stuff.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: Removed
+	  useless methods, and improved the init process - the first state is set as
+	  the current state for this artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java,
+	  src/main/java/de/intevation/flys/artifacts/states/GaugeSelect.java: New.
+	  The states are used in the first two steps of the WINFOArtifact.
+	  Currently, they just implement stubs of the necessary methods setup() and
+	  describe().
+
+2011-02-04  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/State.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/TransitionEngine.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/Transition.java:
+	  Removed. These classes are placed in the artifact-database now.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java:
+	  Adapted imports of Transition.
+
+	* src/main/java/de/intevation/flys/artifacts/states/StateFactory.java: New.
+	  This factory should be used to create concrete State objects.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java: Added
+	  a constant key to store the StateEngine in the context.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  New method that initializes the states at application start.
+
+2011-02-03  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/states/State.java: New. The
+	  interface description of a state.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/Transition.java,
+	  src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java:
+	  New. The interface description and a default implementation of a
+	  transition.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/TransitionEngine.java:
+	  New. The TransitionEngine stores all transitions for each artifact and
+	  should be used to determine, if an artifact can advance from one state to
+	  another.
+
+	* src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java:
+	  New. Transitions should be created by using this class.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java: New.
+	  The Flys context. It currently defines keys to store important components
+	  in the context.
+
+	* src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java:
+	  New. The context factory initializes the basic components of the
+	  application. Currently, the TransitionEngine is created and all artifacts
+	  with its transitions are read from the global configuration and stored in
+	  the FLYSContext.
+
+	* pom.xml: Added a dependency to the 'artifacts-common' package.
+
+	* doc/conf/artifacts/winfo.xml: Corrected the classname of the
+	  DefaultTransition.
+
+	* doc/conf/conf.xml: Added FLYSContextFactory as context-factory.
+
+2011-02-02  Ingo Weinzierl <ingo@intevation.de>
+
+	* doc/conf/conf.xml: An initial configuration file for the FLYS artifact
+	  server.
+
+	* doc/conf/artifacts/winfo.xml: An initial transition configuration of an
+	  WINFO artifact.
+
+	* src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java: A stub of
+	  an artifact for a WINFO parameterization.
+
+	* pom.xml: Set the source code version to 1.5.
+
+2011-02-01	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/**, pom.xml: Added initial maven project.
+	* ChangeLog: new.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/Changes	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,310 @@
+2011-09-19  RELEASE 2.5
+
+    NEW:
+
+        * Introduced the concept of a "datacage": the datacage is a service that
+          provides a list of chart themes that fit to a given chart type. The
+          service accepts a user uuid, the uuid of a master Artifact, the name
+          of the output type and a set of further string parameters. In general,
+          we distinguish between user-specific datacage and system-specific
+          datacage:
+          The user-specific datacage returns themes provided by old
+          calculations computed by the user.
+          The system-specific datacage returns themes that might be generated
+          using the data stored in the flys-backend.
+
+        * Introduced a database used by the datacage that stores information
+          about Artifacts, their outputs and their facets. This database
+          requires synchronization with the Artifact database. The intent of
+          this database is to have a fast access to data stored in Artifacts.
+
+        * Introduced a mechanism to clone existing Artifacts (with or without
+          restrictions). For cloning an Artifact, it is necessary to specify the
+          UUID of a "model Artifact". The clone will be based on that model by
+          extracting all required data from it.
+
+        * Introduced "recommendations": recommendations are themes in charts or
+          maps (Artifacts/Facets) that should be added automatically to an
+          existing chart/map. They are provided by the datacage.
+
+        * Introduced new Artifact types to provide further chart themes:
+          - Annotations
+          - Mainvalues
+
+        * Introduced new Facet type WMSLayerFacet. This type stores all required
+          information (server url, layer names, layer extent, layer srid) to
+          display a WMS layer in a WMS client.
+
+        * Introduced a new chart output "Querprofildiagramm". This output
+          calculates a waterlevel and displays it as single line together with
+          one or more cross section lines ("Querprofilspuren").
+
+        * Introduced a new chart output "W-Differenzen". This output calculates
+          waterlevel differences based on at least two waterlevels (a
+          "W-Differenzen" output can consist of more than a single waterlevel
+          difference calculation). Each waterlevel difference calculation
+          results in three chart themes: two W curves and a W-Differences curve.
+
+        * Introduced a new output "Ãœberschwemmungskarte". The visualization of
+          this output is a map. One of the map's layers is a WMS layer that
+          displays the calculation result of WSPLGEN (external C++ tool).
+
+        * Improved the rendering process of chart themes: the styles configured
+          for themes are now used.
+
+        * Improved the DistanceInfoService: it supports filters to filter the
+          type of items returned by this service (reduces the number of returned
+          items of course).
+
+        * Introduced a CSV export for "W-Differenzen".
+
+        * Downgraded GNU Trove to 1.1-beta-5 (later versions have been removed
+          from maven repositories).
+
+
+    FIXES:
+
+        * flys/issue135 (Diagramm: Trotz abgeschalteter Themen bleiben Beschriftungen bestehen)
+
+        * flys/issue159 (WINFO: Radiobutton - Ortsauswahl bei "W für ungleichwertigen Abflusslängsschnitt" entfernen)
+
+        * flys/issue160 (WINFO: Auswahltabelle Orte bei Modus Strecke nach Step-Back)
+
+        * flys/issue176 (Diagramm: Benennung eines Abflusses bei gewählter Höhe am Pegel)
+
+        * flys/issue180 (WINFO: Zeilen der Tabelle können nach der Markierung in die Zwischenablage kopiert werden.)
+
+        * flys/issue181 (Erstes Thema in der Themenliste wählt sich automatisch wieder an)
+
+        * flys/issue191 (AT-Export: Längsten monoton steigenden Bereich exportieren.)
+
+        * flys/issue219 (W-INFO: Abflusskurvenberechnung / keine Themen im Diagramm)
+
+        * flys/issue254 (Datenkorb: Klonen von Artefakten in anderen Collections + Facettenfilter zum Ausblenden)
+
+        * flys/issue256 (Datenkorb: XXX Issue festhalten)
+
+        * flys/issue258 (Datenkorb: Outs statt States führen)
+
+        * flys/issue259 (Daten aus Datenkorb in Diagramm einladen)
+
+        * flys/issue260 (Datenkorb: Masterartefakt in View aufführen)
+
+        * flys/issue262 (Datenkorb: Vereinigung der beiden Konfigurations-Templates)
+
+        * flys/issue279 (WINFO: Elbe Wasserspiegellage - Index Out of Bounds)
+
+        * flys/issue280 (BoundingBoxen von Streckenfavoriten und Haupt- und Extremwerten unsichtbar machen)
+
+        * flys/issue281 (Karte: Auswahl der berechnten Wasserspiegellage über Inline-Datenkorb)
+
+        * flys/issue282 (Karte: Abstand interpolierte Profile - Default wert)
+
+        * flys/issue290 (Karte: Eingabe von Differenzen zw. WSP und Gelände findet keine Ausprägung in der Karte)
+
+        * flys/issue303 (Keine Streckenfavoriten, wenn nur Q im Längsschnittdiagram ausgewählt)
+
+        * flys/issue309 (Querprofil: Manuelle Eingabe funktioniert nicht nach Return (nur nach Tab))
+
+        * flys/issue310 (Querprofil: Farben der Themen)
+
+        * flys/issue311 (Querprofil: i18n)
+
+
+
+2011-06-27  RELEASE 2.4
+
+    NEW:
+
+        * Finalized the Facet concept: output modes and the output generation
+          are now based on facets. Facets are created dynamically by an Artifact
+          based on the results of a calculation.
+
+        * Introduced a report mechanism that gives feedback of calculation
+          problems.
+
+        * Introduced output modes that generate XML document with calculation
+          report information.
+
+        * Introduced deactivated themes in charts: such themes are not rendered.
+
+        * Introduced a cache to store distance info per river.
+
+        * Introduced output modes that generate XML documents which contain meta
+          information of charts as axes ranges, data ranges and a transformation
+          matrix that allows to transform image coordinates into chart
+          coordinates.
+
+        * Added support for zoom values in Chart output modes.
+
+        * Added support for min/max values in DESCRIBE documents.
+
+        * Added "Oberkante" and "Unterkante" columns to distance info service.
+
+        * Added a new export mode to save data in AT format.
+
+        * Improved performance while storing/loading Q values of WST columns.
+
+        * Improved the WQ values validation for calculation 1 & 4.
+
+        * Improved calculations to work independent of "from"/"to" order of
+          kilometer ranges.
+
+        * Improved the input of WQ values for calculation 1. We distinguish
+          between a selected Q at a given gauge or a selected Q that doesn't
+          base on a given gauge.
+
+        * Allow "from" to be greater than "to" in kilometer ranges.
+
+        * Write default values of the user into the Artifact's DESCRIBE
+          document (flys/issue40).
+
+
+    FIXES:
+
+        * flys/issue62 Artifacts no longer share their data with each other.
+
+        * flys/issue77 Added titles for themes in duration curve charts.
+
+        * flys/issue81
+
+        * flys/issue82 Fixed NPE after a calculation has taken place.
+
+        * flys/issue84
+
+        * flys/issue85 Fixed location input for calculation 1 & 4.
+
+        * flys/issue86 Fixed Q determination based on a given W.
+
+        * flys/issue90 Removed space between chart axes and chart area.
+
+        * flys/issue93 Renamed calculation 4.
+
+        * flys/issue103 Append values selected by the user in the correct format
+          to the DESCRIBE document of Artifacts (uses i18n).
+
+        * flys/issue147
+
+        * flys/issue150 Invert the X axis correctly for charts of type
+          calculation 1 & 4.
+
+        * flys/issue154 Repaired computed discharge curve that broke after the
+          facets had been finalized.
+
+        * flys/issue157 Discharge curve charts (computed an static) will now
+          have a lower x value set to "1".
+
+        * flys/issue161 Longitudinal section chart's second Y axis will
+          initially start at Q=0.
+
+        * flys/issue164 Improved input validation for WQ input of calculation 4.
+
+        * flys/issue172 Duration curve charts will now have a lower x value set
+          to "0".
+
+        * flys/issue173 Fixed broken gauge determination in calculation 4.
+
+        * flys/issue174 Repaired broken upper margin between chart data and
+          chart border in longitudinal section charts.
+
+        * Added missing "Corrected W" facet for results of calculation 4.
+
+        * Map datasets in duration curve charts to the correct axes.
+
+        * Fixed broken XPath to detect output modes in an attribute document of
+          a Collection.
+
+        * Feed operation will no longer save data if the validation of the given
+          values failed.
+
+
+
+2011-05-19  RELEASE 2.3.1
+
+    NEW:
+
+        * New export format for waterlevels: WST.
+
+        * Added descriptions for the curves of the following charts:
+          - discharge curves (dt. 'Abflusskurven am Pegel')
+          - computed discharge curves (dt. 'berechnete Abflusskurven')
+          - longitudinal section curves (dt. 'Längsschnitt')
+          - discharge longitudinal section curves (dt. 'Abflusslängsschnitt')
+
+        * Number formatting is done in a central place/class.
+
+    FIXES:
+
+        * flys/issue47 (Diagramm: Farbliche Unterscheidung von Abfluß und Wasserstand)
+
+        * flys/issue52 (WINFO: W-Längsschnitt - Wasser jeweils von links nach rechts laufen lassen)
+
+        * flys/issue53 (WINFO/Berechnungsausgabe: Kilometerierung und Wasserstände werden zum Teil mit vielen Nachkommastellen angezeigt)
+
+        * flys/issue66: (i18n: Untertitel bei Längsschnitten - Bereich der Strecke enthält "double")
+
+        * flys/issue67 (WINFO: Längsschnitt - Wasser fließt bergauf)
+
+        * flys/issue72: (WINFO: Q/W/D-Info liefert selten eine Antwort)
+
+
+
+2011-05-13  RELEASE 2.3
+
+    NEW:
+
+        * Initial release of the artifacts for FLYS. Currently there is a single
+          WINFO artifact for the following computations:
+          - waterlevels
+          - discharge curves
+          - duration curves
+          - discharge longitudinal section curves
+
+        * Configuration is placed in doc/conf/conf.xml
+
+        * WINFO Artifact specific configuration is placed in
+          doc/conf/artifacts/winfo.xml
+
+        * So called 'OutGenerators' produce different types of output.
+          Currently, the flys-artifacts are able to produce charts and exports.
+          Each output type has to be configured in conf.xml.
+
+        * New chart types:
+          - discharge curves (dt. 'Abflusskurven am Pegel')
+          - computed discharge curves (dt. 'Abflusskurve')
+          - longitudinal section curves (dt. 'Längsschnitte')
+          - duration curves (dt. 'Dauerlinie')
+          - discharge longitudinal section curve (dt. 'W bei
+            ungleichmäßigem Abflusslängsschnitt')
+
+        * New exports:
+          - csv of waterlevels
+          - csv of duration curves
+          - csv of computed discharge curves
+          - csv of discharge longitudinal section
+
+        * New services that provides:
+          - supported rivers
+          - main values of a gauge
+          - range information of a river
+          - meta information of a river
+
+        * Caching of computation relevant values
+
+        * Initial model to support chart specific themes (theme.xml)
+
+
+    LIMITATIONS:
+
+        * Charts are not rendered using the themes in theme.xml
+
+
+    !!!
+
+    The version number of this release depends on an existing desktop variant of
+    this software that is in version 2.1.
+
+    !!!
+
+
+2011-03-30  RELEASE 0.1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/NEWS	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,13 @@
+NEWS 
+
+for enduser-specific information, c.f. the NEWS -file in the module flys-client
+
+2011-05-19  Release V 2.3.1
+    * Bugfixing Release for WINFO
+
+2011-05-16  Release V 2.3.0
+    * Enhanced functionality for WINFO, diagram and Data-Manager (Datenkorb)
+
+2011-03-27  Release V 2.2
+
+This file starts with V. 2.2. Earlier versions are based on a Desktop-version until V 2.1.3.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/TODO	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1 @@
+- Validation of the input values of an incoming feed() call
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/contrib/check-i18n-properties.py	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+import sys
+import re
+
+SPLIT_RE = re.compile(r"^\s*([^=]+)=\s*(.*)\s*")
+
+def load_properties_file(filename):
+    props = {}
+    with open(filename, "r") as f:
+        while True:
+            line = f.readline()
+            if not line: break
+            m = SPLIT_RE.match(line)
+            if not m: continue
+            k = m.group(1).strip()
+            v = m.group(2).strip()
+            if k in props:
+                print >> sys.stderr, "'%s' found more than once in '%s'." % (
+                    k, filename)
+            else:
+                props[k] = v
+    return props
+
+def main():
+
+    props = [(arg, load_properties_file(arg)) for arg in sys.argv[1:]]
+
+    l = len(props)
+
+    for i in range(0, l):
+        a = props[i][1]
+        for j in range(i+1, l):
+            b = props[j][1]
+            for k in a.iterkeys():
+                if k not in b:
+                    print >> sys.stderr, "'%s' found in '%s' but not in '%s'." % (
+                        k, props[i][0], props[j][0])
+            for k in b.iterkeys():
+                if k not in a:
+                    print >> sys.stderr, "'%s' found in '%s' but not in '%s'." % (
+                        k, props[j][0], props[i][0])
+        
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/contrib/fixoverview2html.xsl	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet
+  version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+  <xsl:output method="xml" encoding="UTF-8"
+    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
+    doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" indent="yes"/>
+
+  <xsl:param name="project-uuid">de3f3307-3429-4ff9-8f43-3fb2fcf21b27</xsl:param>
+  <xsl:param name="render-checkboxes" select="true()"/>
+
+  <xsl:template name="percent">
+    <xsl:param name="sector"/>
+    <xsl:variable name="rfrom" select="number(/fixings/river/@from)"/>
+    <xsl:variable name="rto" select="number(/fixings/river/@to)"/>
+    <xsl:variable name="rlength" select="$rto - $rfrom"/>
+
+    <xsl:variable name="sfrom" select="number($sector/@from)"/>
+    <xsl:variable name="sto" select="number($sector/@to)"/>
+    <xsl:variable name="slength" select="$sto - $sfrom"/>
+    <xsl:value-of select="100.0 * ($slength div $rlength)"/>
+  </xsl:template>
+
+  <xsl:template name="emit-gap">
+    <xsl:param name="sector"/>
+    <xsl:variable name="preds" select="$sector/preceding-sibling::sector"/>
+    <xsl:variable name="num-preds" select="count($preds)"/>
+    <xsl:variable name="glength">
+      <xsl:choose>
+        <xsl:when test="$num-preds &lt; 1">
+          <xsl:variable name="rfrom" select="number(/fixings/river/@from)"/>
+          <xsl:value-of select="number($sector/@from) - $rfrom"/>
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:variable name="bend" select="number($preds[last()]/@to)"/>
+          <xsl:value-of select="number($sector/@from) - $bend"/>
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:variable>
+    <xsl:choose>
+      <xsl:when test="number($glength) &lt; 0.05 "/>
+      <xsl:otherwise>
+        <div>
+          <xsl:attribute name="style">
+            <xsl:text>width:</xsl:text>
+              <xsl:variable name="rfrom" select="number(/fixings/river/@from)"/>
+              <xsl:variable name="rto" select="number(/fixings/river/@to)"/>
+              <xsl:variable name="rlength" select="$rto - $rfrom"/>
+              <xsl:value-of select="100.0 * ($glength div $rlength)"/>
+            <xsl:text>%;float:left</xsl:text>
+          </xsl:attribute>
+          <xsl:text disable-output-escaping="yes"><![CDATA[&nbsp;]]></xsl:text>
+        </div>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match="sector" mode="sectors">
+    <xsl:call-template name="emit-gap">
+      <xsl:with-param name="sector" select="."/>
+    </xsl:call-template>
+    <div>
+      <xsl:attribute name="style">
+        <xsl:text>background:</xsl:text>
+        <xsl:choose>
+          <xsl:when test="@class = '0'">green</xsl:when>
+          <xsl:when test="@class = '1'">blue</xsl:when>
+          <xsl:when test="@class = '2'">magenta</xsl:when>
+          <xsl:when test="@class = '3'">red</xsl:when>
+          <xsl:otherwise>black</xsl:otherwise>
+        </xsl:choose>
+        <xsl:text>;width:</xsl:text>
+        <xsl:call-template name="percent">
+          <xsl:with-param name="sector" select="."/>
+        </xsl:call-template>
+        <xsl:text>%</xsl:text>
+        <xsl:text>;float:left</xsl:text>
+      </xsl:attribute>
+      <xsl:attribute name="title">
+        <xsl:text>km </xsl:text>
+        <xsl:value-of select="@from"/>
+        <xsl:text> - </xsl:text>
+        <xsl:value-of select="@to"/>
+      </xsl:attribute>
+      <xsl:text disable-output-escaping="yes"><![CDATA[&nbsp;]]></xsl:text>
+    </div>
+  </xsl:template>
+
+  <xsl:template match="event">
+    <tr>
+      <xsl:if test="$render-checkboxes">
+        <td>
+          <input type="checkbox" name="{$project-uuid}:{@cid}"/>
+        </td>
+      </xsl:if>
+      <td>
+        <xsl:apply-templates mode="sectors"/>
+      </td>
+      <td align="center">
+        <xsl:attribute name="title"><xsl:value-of select="@description"/></xsl:attribute>
+        <xsl:value-of select="@date"/>
+      </td>
+    </tr>
+  </xsl:template>
+
+  <xsl:template match="events">
+    <table width="95%" border="1" cellspacing="0" cellpadding="0" style="font-size: 11pt;font-family:Arial;Verdana,sans-serif">
+      <colgroup>
+        <xsl:if test="$render-checkboxes">
+            <col width="1em"/>
+        </xsl:if>
+        <col width="*"/>
+        <col width="11em"/>
+      </colgroup>
+      <xsl:apply-templates/>
+    </table>
+  </xsl:template>
+
+  <xsl:template match="/">
+    <html>
+      <head>
+        <title>Fixierungen:</title>
+      </head>
+      <body>
+        <xsl:apply-templates/>
+      </body>
+    </html>
+  </xsl:template>
+
+  <xsl:template match="text()"/>
+  <xsl:template match="text()" mode="sectors"/>
+
+</xsl:stylesheet>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/contrib/visualize-transitions.xsl	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2010 by Intevation GmbH
+
+ This program is free software under the LGPL (>=v2.1)
+ Read the file LGPL.txt coming with the software for details
+ or visit http://www.gnu.org/licenses/ if it does not exist.
+
+ Author: Sascha L. Teichmann (sascha.teichmann@intevation.de)
+-->
+
+<xsl:stylesheet 
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    version="1.0">
+
+    <xsl:output method="text" encoding="UTF-8"/>
+
+    <xsl:param name="base-dir">.</xsl:param>
+
+    <xsl:template match="/">
+        <xsl:text>digraph transition_model {&#xa;</xsl:text>
+        <xsl:apply-templates />
+        <xsl:text>}&#xa;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="artifact">
+        <xsl:choose>
+            <xsl:when test="@xlink:href != ''">
+                <!-- handle external artifacts -->
+                <xsl:variable name="path">
+                    <xsl:call-template name="string-replace-all">
+                    <xsl:with-param name="text" select="@xlink:href" />
+                    <xsl:with-param name="replace">${artifacts.config.dir}</xsl:with-param>
+                    <xsl:with-param name="by" select="$base-dir" />
+                    </xsl:call-template>
+                </xsl:variable>
+                <xsl:for-each select="document($path)">
+                    <xsl:apply-templates select="/artifact"/>
+                </xsl:for-each>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- handle internal artifacts -->
+                <xsl:text>subgraph </xsl:text><xsl:value-of select="@name"/>
+                <xsl:text> {&#xa;</xsl:text>
+                <xsl:text>    label = "Artefakt: </xsl:text>
+                <xsl:value-of select="@name"/>
+                <xsl:text>";&#xa;</xsl:text>
+                <xsl:apply-templates mode="inside-artifact" select="./states/state"/>
+                <xsl:apply-templates mode="inside-artifact" select="./states/transition"/>
+                <xsl:text>}&#xa;</xsl:text>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template match="state" mode="inside-artifact">
+        <xsl:text>    "</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text disable-output-escaping="yes"
+        >" [ shape = "record" label=&lt;&lt;table border="0" cellborder="0" cellpadding="3"&gt;
+        &lt;tr&gt;&lt;td align="center" colspan="2" bgcolor="black"&gt;&lt;font color="white"&gt;</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;</xsl:text>
+            <xsl:apply-templates mode="inside-artifact" />
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/table&gt;&gt;]</xsl:text>
+        <xsl:text>;&#xa;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="data" mode="inside-artifact">
+        <xsl:text disable-output-escaping="yes"
+        >&lt;tr&gt;&lt;td align="right"&gt;</xsl:text>
+        <xsl:value-of select="@name"/>
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/td&gt;&lt;td align="left"&gt;</xsl:text>
+        <xsl:value-of select="@type"/>
+        <xsl:text disable-output-escaping="yes"
+        >&lt;/td&gt;&lt;/tr&gt;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="transition" mode="inside-artifact">
+        <xsl:text>    "</xsl:text>
+        <xsl:value-of select="from/@state"/>
+        <xsl:text disable-output-escaping="yes">" -&gt; "</xsl:text>
+        <xsl:value-of select="to/@state"/>
+        <xsl:text>"</xsl:text>
+        <xsl:apply-templates mode="inside-artifact"/>
+        <xsl:text>;&#xa;</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="condition" mode="inside-artifact">
+        <xsl:text> [ label="</xsl:text>
+        <xsl:value-of select="@data"/>
+        <xsl:text> </xsl:text>
+        <xsl:call-template name="readable-operator">
+            <xsl:with-param name="operator" select="@operator"/>
+        </xsl:call-template>
+        <xsl:text> </xsl:text>
+        <xsl:value-of select="@value"/>
+        <xsl:text>" ]</xsl:text>
+    </xsl:template>
+
+    <xsl:template match="text()" mode="inside-artifact"/>
+    <xsl:template match="text()"/>
+
+    <xsl:template name="readable-operator">
+        <xsl:param name="operator" />
+        <xsl:choose>
+            <xsl:when test='$operator = "equal"'>=</xsl:when>
+            <xsl:when test='$operator = "notequal"'>!=</xsl:when>
+            <xsl:otherwise><xsl:value-of select="$operator"/></xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+     <xsl:template name="string-replace-all">
+        <xsl:param name="text" />
+        <xsl:param name="replace" />
+        <xsl:param name="by" />
+        <xsl:choose>
+          <xsl:when test="contains($text, $replace)">
+            <xsl:value-of select="substring-before($text,$replace)" />
+            <xsl:value-of select="$by" />
+            <xsl:call-template name="string-replace-all">
+              <xsl:with-param name="text"
+                  select="substring-after($text,$replace)" />
+              <xsl:with-param name="replace" select="$replace" />
+              <xsl:with-param name="by" select="$by" />
+            </xsl:call-template>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:value-of select="$text" />
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:template>
+    
+</xsl:stylesheet>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/annotation.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="annotation">
+  <states>
+    <state id="state.annotation.river"
+               description="state.annotation.river"
+               state="de.intevation.flys.artifacts.states.AnnotationRiverState">
+      <outputmodes>
+        <outputmode name="longitudinal_section" description="output.longitudinal_section" mime-type="image/png" type="chart">
+          <facets>
+            <facet name="longitudinal_section.annotations" description="facet.longitudinal_section.annotations" />
+          </facets>
+        </outputmode>
+      </outputmodes>
+    </state>
+  </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/chart.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="new_chart">
+    <states>
+         <state id="state.chart.river" description="state.winfo.river" state="de.intevation.flys.artifacts.states.RiverSelect">
+            <data name="river" type="String" />
+         </state>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+             <from state="state.chart.river"/>
+             <to state="state.chart.type"/>
+         </transition>
+
+         <state id="state.chart.type" description="state.chart.type" state="de.intevation.flys.artifacts.ChartArtifact$ChartState">
+            <data name="chart_type" type="String" />
+         </state>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+             <from state="state.chart.type"/>
+             <to state="state.chart.w_differences"/>
+             <condition data="chart_type" value="chart.new.w_differences" operator="equal"/>
+         </transition>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+             <from state="state.chart.type"/>
+             <to state="state.chart.km"/>
+             <condition data="chart_type" value="chart.new.durationcurve" operator="equal"/>
+         </transition>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+             <from state="state.chart.type"/>
+             <to state="state.chart.km"/>
+             <condition data="chart_type" value="chart.new.computeddischargecurve" operator="equal"/>
+         </transition>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+             <from state="state.chart.type"/>
+             <to state="state.chart.crosssection"/>
+             <condition data="chart_type" value="chart.new.crosssection" operator="equal"/>
+         </transition>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+             <from state="state.chart.type"/>
+             <to state="state.chart.longitudinal_section"/>
+             <condition data="chart_type" value="chart.new.longitudinal_section" operator="equal"/>
+         </transition>
+
+         <state id="state.chart.km" description="state.chart.km" state="de.intevation.flys.artifacts.states.LocationSelect">
+             <data name="ld_locations" type="Double[]"/>
+         </state>
+
+         <state id="state.chart.w_differences" description="state.chart.w_differences" state="de.intevation.flys.artifacts.states.WDifferencesState">
+            <outputmodes>
+                <outputmode name="w_differences" description="output.w_differences" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="empty.facet" description="Empty"/>
+                        <facet name="longitudinal_section.q" description="facet.longitudinal_section.q"/>
+                        <facet name="longitudinal_section.w" description="facet.longitudinal_section.w"/>
+                        <facet name="w_differences" description="facet.w_differences"/>
+                        <facet name="other.wkms" description="facet.other.wkms"/>
+                        <facet name="other.wqkms" description="facet.other.wqkms"/>
+                        <facet name="heightmarks_points" description="facet.other.wkms.heightmarks_points"/>
+                        <facet name="longitudinal_section.annotations" description="facet.longitudinal_section.annotations"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.chart.longitudinal_section" description="state.chart.logitudinal_section" state="de.intevation.flys.artifacts.states.DischargeLongitudinalSection">
+            <outputmodes>
+                <outputmode name="longitudinal_section" description="output.discharge_longitudinal_section" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="empty.facet" decription= "Empty"/>
+                        <facet name="discharge_longitudinal_section.w"/>
+                        <facet name="discharge_longitudinal_section.q"/>
+                        <facet name="discharge_longitudinal_section.c"/>
+                        <facet name="other.wqkms"/>
+                        <facet name="other.wkms"/>
+                        <facet name="heightmarks_points"/>
+                        <facet name="longitudinal_section.area"        description="an area"/>
+                        <facet name="longitudinal_section.annotations"/>
+                        <facet name="longitudinal_section.w"/>
+                        <facet name="longitudinal_section.manualpoints"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.chart.crosssection" description="state.chart.crosssection" state="de.intevation.flys.artifacts.states.WaterlevelState">
+            <outputmodes>
+                <outputmode name="cross_section" description="output.cross_section" mime-type="image/png" type="chart">
+                  <facets>
+                    <facet name="empty.facet" description="Empty"/>
+                    <facet name="cross_section_water_line" description="facet.cross_section_water_line"/>
+                    <facet name="cross_section"            description="facet.cross_section"/>
+                    <facet name="area"                     description="an area"/>
+                    <facet name="cross_section.area"         description="an area"/>
+                    <facet name="cross_section.manualpoints" description="points"/>
+                    <facet name="hyk"                      description="hyks"/>
+                  </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.chart.km"/>
+            <to state="state.chart.computeddischargecurve"/>
+            <condition data="chart_type" value="chart.new.computeddischargecurve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.chart.km"/>
+            <to state="state.chart.durationcurve"/>
+            <condition data="chart_type" value="chart.new.durationcurve" operator="equal"/>
+        </transition>
+
+        <state id="state.chart.computeddischargecurve" description="state.chart.computeddischargecurve" state="de.intevation.flys.artifacts.states.ComputedDischargeCurveState">
+            <outputmodes>
+                <outputmode name="computed_discharge_curve" description="output.computed_discharge_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="empty.facet" description="Empty"/>
+                        <facet name="computed_discharge_curve.manualpoints"/>
+                        <facet name="computed_discharge_curve.q" description="facet.computed_discharge_curve.q"/>
+                        <facet name="computed_discharge_curve.mainvalues.q" description="facet.computed_discharge_curve.mainvalues.q"/>
+                        <facet name="computed_discharge_curve.mainvalues.w" description="facet.computed_discharge_curve.mainvalues.w"/>
+                        <facet name="other.wqkms" description="facet.other.wqkms"/>
+                        <facet name="other.wq" description="Point-like data like fixations"/>
+                        <facet name="other.wq" description="Point-like data like fixations"/>
+                        <facet name="other.wkms.interpol" description="Height over km, like flood protections."/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.chart.durationcurve" description="state.chart.durationcurve" state="de.intevation.flys.artifacts.states.DurationCurveState">
+            <outputmodes>
+                <outputmode name="duration_curve" description="output.duration_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="empty.facet" description="Empty"/>
+                        <facet name="duration_curve.manualpoints"/>
+                        <facet name="duration_curve.w" description="facet.duration_curve.w"/>
+                        <facet name="duration_curve.q" description="facet.duration_curve.q"/>
+                        <facet name="computed_discharge_curve.mainvalues.q" description="Q Main Values"/>
+                        <facet name="computed_discharge_curve.mainvalues.w" description="W Main Values"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/fixanalysis.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="fixanalysis">
+    <states>
+        <state id="state.fix.river" description="state.fix.river"
+            state="de.intevation.flys.artifacts.states.RiverSelect">
+            <data name="river" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.river"/>
+            <to state="state.fix.calculation.mode"/>
+        </transition>
+
+        <state id="state.fix.calculation.mode"
+            description="state.fix.calcuation.mode"
+            state="de.intevation.flys.artifacts.states.fixation.FixationSelect">
+            <data name="calculation.mode" type="String"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.calculation.mode"/>
+            <to state="state.fix.location"/>
+        </transition>
+
+        <state id="state.fix.location" description="state.fix.location"
+            state="de.intevation.flys.artifacts.states.fixation.LocationSelect">
+            <data name="from" type="Double"/>
+            <data name="to" type="Double"/>
+            <data name="step" type="Double"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.location"/>
+            <to state="state.fix.period"/>
+        </transition>
+
+        <state id="state.fix.period" description="state.fix.period"
+            state="de.intevation.flys.artifacts.states.fixation.FixationPeriod">
+            <data name="start" type="Long"/>
+            <data name="end" type="Long"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.period"/>
+            <to state="state.fix.gaugerange"/>
+        </transition>
+
+        <state id="state.fix.gaugerange" description="state.fix.gaugerange"
+            state="de.intevation.flys.artifacts.states.fixation.GaugeRange">
+            <data name="q1" type="Integer"/>
+            <data name="q2" type="Integer"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.gaugerange"/>
+            <to state="state.fix.eventselect"/>
+        </transition>
+
+        <state id="state.fix.eventselect" description="state.fix.eventselect"
+            state="de.intevation.flys.artifacts.states.fixation.EventSelect">
+            <data name="events" type="intarray"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.fix.eventselect"/>
+            <to state="state.fix.analysis.referenceperiod"/>
+            <condition data="calculation.mode" value="calculation.analysis"
+                operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.fix.eventselect"/>
+            <to state="state.fix.volmer.function"/>
+            <condition data="calculation.mode" value="calculation.volmer"
+                operator="equal"/>
+        </transition>
+
+        <state id="state.fix.analysis.referenceperiod"
+            description="state.fix.analysis.referenceperiod"
+            state="de.intevation.flys.artifacts.states.fixation.ReferencePeriod">
+            <data name="ref_start" type="Long"/>
+            <data name="ref_end" type="Long"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.analysis.referenceperiod"/>
+            <to state="state.fix.analysis.analysisperiods"/>
+        </transition>
+
+        <state id="state.fix.analysis.analysisperiods"
+            description="state.fix.analysis.analysisperiods"
+            state="de.intevation.flys.artifacts.states.fixation.AnalysisPeriods">
+            <!--Temporary data fields. These have to be changed to take n
+                periods-->
+            <data name="ana_start" type="Long"/>
+            <data name="ana_end" type="Long"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.analysis.analysisperiods"/>
+            <to state="state.fix.analysis.function"/>
+        </transition>
+
+        <state id="state.fix.analysis.function"
+            description="state.fix.analysis.function"
+            state="de.intevation.flys.artifacts.states.fixation.FunctionSelect">
+            <data name="function" type="String"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.analysis.function"/>
+            <to state="state.fix.analysis.preprocessing"/>
+        </transition>
+
+        <state id="state.fix.analysis.preprocessing"
+            description="state.fix.analysis.preprocessing"
+            state="de.intevation.flys.artifacts.states.fixation.PreprocessingSelect">
+            <data name="preprocessing" type="Boolean"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.analysis.preprocessing"/>
+            <to state="state.fix.analysis.compute"/>
+        </transition>
+
+        <state id="state.fix.analysis.compute"
+            description="state.fix.analysis.compute"
+            state="de.intevation.flys.artifacts.states.fixation.FixationCompute">
+            <outputmodes>
+                <outputmode name="fix_deltawt_export" description="output.fix_deltawt_export" mine-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="face.fix_deltawt.csv" />
+                    </facets>
+                </outputmode>
+                <outputmode name="fix_report" description="output.fix_report.report" mime-type="text/plain" type="report">
+                    <facets>
+                        <facet name="report" description="facet.fix.report" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.fix.volmer.function"
+            description="state.fix.volmer.function"
+            state="de.intevation.flys.artifacts.states.fixation.FunctionSelect">
+            <data name="function" type="String"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.volmer.function"/>
+            <to state="state.fix.volmer.preprocessing"/>
+        </transition>
+
+        <state id="state.fix.volmer.preprocessing"
+            description="state.fix.volmer.preprocessing"
+            state="de.intevation.flys.artifacts.states.fixation.PreprocessingSelect">
+            <data name="preprocessing" type="Boolean"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.volmer.preprocessing"/>
+            <to state="state.fix.volmer.qs"/>
+        </transition>
+
+        <state id="state.fix.volmer.qs" description="state.fix.volmer.qs"
+            state="de.intevation.flys.artifacts.states.fixation.QSelect">
+            <data name="qs" type="Double[]"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.fix.volmer.qs"/>
+            <to state="state.fix.volmer.compute"/>
+        </transition>
+
+        <state id="state.fix.volmer.compute"
+            description="state.fix.volmer.compute"
+            state="de.intevation.flys.artifacts.states.fixation.FixationVolmerCompute">
+            <outputmodes>
+                <outputmode>
+                    <facets>
+                        <facet/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/gaugedischarge.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="gaugedischarge">
+  <states>
+    <state id="state.gaugedischarge.init"
+               description="state.gaugedischarge.init"
+               state="de.intevation.flys.artifacts.states.GaugeDischargeState">
+      <outputmodes>
+        <outputmode name="computed_discharge_curve" description="computed_discharge_curve" mime-type="image/png" type="chart">
+          <facets>
+            <facet name="discharge_curve.curve" description="facet.discharge_curve.curve" />
+          </facets>
+        </outputmode>
+      </outputmodes>
+    </state>
+  </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/hyk.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="hyk">
+  <states>
+    <state id="state.hyk.static"
+               description="state.hyk.static"
+               state="de.intevation.flys.artifacts.states.StaticHYKState">
+      <outputmodes>
+        <outputmode name="cross_section" description="output.cross_section" mime-type="image/png" type="chart">
+          <facets>
+            <facet name="hyk" description="hyk data" />
+          </facets>
+        </outputmode>
+      </outputmodes>
+    </state>
+  </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/manualpoints.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="manualpoints">
+  <states>
+    <state id="state.manualpoints.static"
+               description="state.manualpoints.static"
+               state="de.intevation.flys.artifacts.states.ManualPointsSingleState">
+      <outputmodes>
+        <outputmode name="longitudinal_section" description="output.longitudinal_section" mime-type="image/png" type="chart">
+          <facets>
+            <facet name="longitudinal_section.manualpoints" description="Points provided by user." />
+            <facet name="discharge_longitudinal_section.manualpoints" description="Points provided by user." />
+            <facet name="discharge_curve.manualpoints" description="Points provided by user." />
+            <facet name="w_differences.manualpoints" description="Points provided by user." />
+            <facet name="duration_curve.manualpoints" description="Points provided by user." />
+            <facet name="computed_discharge_curve.manualpoints" description="Points provided by user." />
+            <facet name="cross_section.manualpoints" description="Points provided by user." />
+            <facet name="reference_curve.manualpoints" description="User-provided points *yawn*"/>
+            <facet name="reference_curve_normalized.manualpoints" description="points"/>
+            <facet name="historical_discharge.manualpoints" description="Points provided by user." />
+          </facets>
+        </outputmode>
+      </outputmodes>
+    </state>
+  </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/map.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="new_map">
+    <states>
+         <state id="state.map.river" description="state.winfo.river" state="de.intevation.flys.artifacts.states.RiverSelect">
+            <data name="river" type="String" />
+         </state>
+
+         <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+             <from state="state.map.river"/>
+             <to state="state.map.new"/>
+         </transition>
+
+         <state id="state.map.new" description="state.map.new" state="de.intevation.flys.artifacts.MapArtifact$MapState">
+            <outputmodes>
+                <outputmode name="map" description="output.uesk.map.description" type="map">
+                    <facets>
+                        <facet name="floodmap.wsplgen"/>
+                        <facet name="floodmap.barriers"/>
+                        <facet name="floodmap.riveraxis"/>
+                        <facet name="floodmap.wmsbackground"/>
+                        <facet name="floodmap.kms"/>
+                        <facet name="floodmap.qps"/>
+                        <facet name="floodmap.hws"/>
+                        <facet name="floodmap.hydr_boundaries"/>
+                        <facet name="floodmap.hydr_boundaries_poly"/>
+                        <facet name="floodmap.catchment"/>
+                        <facet name="floodmap.floodplain"/>
+                        <facet name="floodmap.lines"/>
+                        <facet name="floodmap.buildings"/>
+                        <facet name="floodmap.fixpoints"/>
+                        <facet name="floodmap.floodmaps"/>
+                        <facet name="floodmap.gauge_location"/>
+                        <facet name="floodmap.externalwms"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/minfo.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="minfo">
+
+    <states>
+        <state id="state.minfo.river" description="state.minfo.river" state="de.intevation.flys.artifacts.states.RiverSelect" helpText="help.index">
+            <data name="river" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+                <from state="state.minfo.river"/>
+                <to state="state.minfo.calculation_mode"/>
+        </transition>
+
+        <state id="state.minfo.calculation_mode" description="state.minfo.calculation_mode" state="de.intevation.flys.artifacts.states.CalculationSelectMinfo" helpText="help.minfo">
+            <data name="calculation_mode" type="String"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.minfo.calculation_mode"/>
+            <to state="state.minfo.distance_only"/>
+            <condition data="calculation_mode" value="calc.flow.velocity" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.minfo.calculation_mode"/>
+            <to state="state.minfo.distance_only"/>
+            <condition data="calculation_mode" value="calc.bed.middle" operator="equal"/>
+        </transition>
+
+        <state id="state.minfo.distance_only" description="state.minfo.distance_only" state="de.intevation.flys.artifacts.states.DistanceOnlySelect" helpText="help.minfo.distance">
+            <data name="ld_from" type="Double" />
+            <data name="ld_to"   type="Double" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.minfo.distance_only"/>
+            <to state="state.minfo.dischargestate"/>
+            <condition data="calculation_mode" value="calc.flow.velocity" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.minfo.distance_only"/>
+            <to state="state.minfo.soundings"/>
+            <condition data="calculation_mode" value="calc.bed.middle" operator="equal"/>
+        </transition>
+
+        <state id="state.minfo.dischargestate" description="state.minfo.dischargestate" state="de.intevation.flys.artifacts.states.DischargeState" helpText="help.minfo.dischargestate">
+            <data name="total.channel" type="intoptions"/>
+            <data name="main.channel"  type="intoptions"/>
+        </state>
+
+        <state id="state.minfo.soundings" description="state.minfo.soundingsstate" state="de.intevation.flys.artifacts.states.SoundingsSelect" helpText="help.minfo.soundingsstate">
+            <data name="soundings" type="options"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.minfo.dischargestate"/>
+            <to state="state.minfo.flow_velocity"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.minfo.soundings"/>
+            <to state="state.minfo.bedheight_middle"/>
+        </transition>
+
+        <state id="state.minfo.flow_velocity" description="state.minfo.flow_velocity" state="de.intevation.flys.artifacts.states.FlowVelocityState" helpText="help.minfo.flowvelocity">
+            <outputmodes>
+                <outputmode name="flow_velocity" description="output.flow_velocity" mime-type="image/png" type="chart">
+                    <facets>
+                        <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.tau" description="A facet for tau"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="flow_velocity_export" description="output.flow_velocity_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.flow_velocity_export.csv" />
+                        <!--
+                        <facet name="pdf" description="facet.historical_discharge.pdf" />
+                        -->
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.minfo.bedheight_middle" description="state.minfo.bedheight_middle" state="de.intevation.flys.artifacts.states.MiddleBedHeight" helpText="help.minfo.bedheight_middle">
+            <outputmodes>
+                <outputmode name="bedheight_middle" description="output.bedheight_middle" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="bedheight_middle.single" description="A facet for total channels"/>
+                        <facet name="bedheight_middle.epoch" description="A facet for total channels"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="bedheight_middle_export" description="output.bedheight_middle_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.bedheight_middle_export.csv" />
+                        <!--
+                        <facet name="pdf" description="facet.historical_discharge.pdf" />
+                        -->
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+    </states>
+
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/staticwqkms.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="staticwqkms">
+  <states>
+    <state id="state.staticwqkms.static"
+               description="state.staticwqkms.state"
+               state="de.intevation.flys.artifacts.states.StaticWQKmsState">
+      <outputmodes>
+        <outputmode name="longitudinal_section" description="output.longitudinal_section" mime-type="image/png" type="chart">
+          <facets>
+            <facet name="other.wqkms.w" description="W-type data" />
+            <facet name="other.wqkms.q" description="Q-type data" />
+          </facets>
+        </outputmode>
+      </outputmodes>
+    </state>
+  </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/waterlevel.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="waterlevel">
+    <states>
+        <state id="state.waterlevel.done"
+               description="state.waterlevel.done"
+               state="de.intevation.flys.artifacts.states.WaterlevelInfoState">
+           <outputmodes>
+             <outputmode name="w_differences" description="output.w_differences" mime-type="image/png" type="chart">
+                  <facets>
+                     <facet name="longitudinal_section.w" description="facet.longitudinal_section.w" />
+                  </facets>
+              </outputmode>
+           </outputmodes>
+        </state>
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/winfo.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,557 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="winfo">
+    <states>
+
+        <state id="state.winfo.river" description="state.winfo.river" state="de.intevation.flys.artifacts.states.RiverSelect" helpText="help.index">
+            <data name="river" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <from state="state.winfo.river"/>
+            <to state="state.winfo.calculation_mode"/>
+        </transition>
+
+        <state id="state.winfo.calculation_mode" description="state.winfo.calculation_mode" state="de.intevation.flys.artifacts.states.CalculationSelect" helpText="help.winfo">
+            <data name="calculation_mode" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location_distance"/>
+            <condition data="calculation_mode" value="calc.surface.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.distance_only"/>
+            <condition data="calculation_mode" value="calc.flood.map" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location"/>
+            <condition data="calculation_mode" value="calc.discharge.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.location"/>
+            <condition data="calculation_mode" value="calc.duration.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.distance"/>
+            <condition data="calculation_mode" value="calc.discharge.longitudinal.section" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.waterlevel_pair_select"/>
+            <condition data="calculation_mode" value="calc.w.differences" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.historicalq.reference_gauge"/>
+            <condition data="calculation_mode" value="calc.historical.discharge.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.waterlevel_pair_select"/>
+            <to state="state.winfo.w_differences"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.calculation_mode"/>
+            <to state="state.winfo.reference.curve.input.start"/>
+            <condition data="calculation_mode" value="calc.reference.curve" operator="equal"/>
+        </transition>
+
+        <state id="state.winfo.location" description="state.winfo.location" state="de.intevation.flys.artifacts.states.LocationSelect" helpText="help.winfo.duration.locations">
+            <data name="ld_locations" type="Double[]" />
+
+            <!--
+            <outputmodes>
+                <outputmode name="discharge_curve" description="output.discharge_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="discharge_curve.curve" description="facet.discharge_curve.curve"/>
+                        <facet name="mainvalues.q" description="facet.computed_discharge_curve.mainvalues.q"/>
+                        <facet name="discharge_curve.manualpoints" description="Manuelle Punkte"/>
+                        <facet name="mainvalues.w" description="facet.computed_discharge_curve.mainvalues.w"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+            -->
+        </state>
+
+        <state id="state.winfo.distance_only" description="state.winfo.distance_only" state="de.intevation.flys.artifacts.states.DistanceOnlySelect" helpText="help.winfo.uesk.distance">
+            <data name="ld_from" type="Double" />
+            <data name="ld_to"   type="Double" />
+        </state>
+
+        <state id="state.winfo.distance" description="state.winfo.distance" state="de.intevation.flys.artifacts.states.DistanceSelect" helpText="help.winfo.discharge.longitudinal.distance">
+            <data name="ld_from" type="Double" />
+            <data name="ld_to"   type="Double" />
+            <data name="ld_step" type="Double" />
+
+            <!--
+            <outputmodes>
+                <outputmode name="discharge_curve" description="output.discharge_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="discharge_curve.curve" description="facet.discharge_curve.curve"/>
+                        <facet name="mainvalues.q" description="facet.computed_discharge_curve.mainvalues.q"/>
+                        <facet name="discharge_curve.manualpoints" description="Manuelle Punkte"/>
+                        <facet name="mainvalues.w" description="facet.computed_discharge_curve.mainvalues.w"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+            -->
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.distance_only"/>
+            <to state="state.winfo.uesk.wsp"/>
+        </transition>
+
+        <state id="state.winfo.location_distance" description="state.winfo.location_distance" state="de.intevation.flys.artifacts.states.LocationDistanceSelect" helpText="help.winfo.wsp.location_distance">
+            <data name="ld_mode" type="String" />
+            <data name="ld_locations" type="Double[]" />
+            <data name="ld_from" type="Double" />
+            <data name="ld_to" type="Double" />
+            <data name="ld_step" type="Double" />
+
+            <!--
+            <outputmodes>
+                <outputmode name="discharge_curve" description="output.discharge_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="discharge_curve.curve" description="facet.discharge_curve.curve"/>
+                        <facet name="mainvalues.q" description="facet.computed_discharge_curve.mainvalues.q"/>
+                        <facet name="discharge_curve.manualpoints" description="Manuelle Punkte"/>
+                        <facet name="mainvalues.w" description="facet.computed_discharge_curve.mainvalues.w"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+            -->
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.distance"/>
+            <to state="state.winfo.wq_adapted"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.location_distance"/>
+            <to state="state.winfo.wq"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.location"/>
+            <to state="state.winfo.durationcurve"/>
+            <condition data="calculation_mode" value="calc.duration.curve" operator="equal"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.ValueCompareTransition">
+            <from state="state.winfo.location"/>
+            <to state="state.winfo.computeddischargecurve"/>
+            <condition data="calculation_mode" value="calc.discharge.curve" operator="equal"/>
+        </transition>
+
+        <state id="state.winfo.wq" description="state.winfo.wq" state="de.intevation.flys.artifacts.states.WQSelect" helpText="help.winfo.wsp.wq">
+            <data name="wq_isq" type="Boolean" />
+            <data name="wq_isfree" type="Boolean" />
+            <data name="wq_isrange" type="Boolean" />
+            <data name="wq_from" type="Double" />
+            <data name="wq_to" type="Double" />
+            <data name="wq_step" type="Double" />
+            <data name="wq_single" type="Double[]" />
+        </state>
+
+        <state id="state.winfo.wq_adapted" description="state.winfo.wq_adapted" state="de.intevation.flys.artifacts.states.WQAdapted" helpText="help.winfo.discharge.longitudinal.wq">
+            <!-- TODO Add data objects -->
+            <data name="wq_isq"   type="String" />
+            <data name="wq_values" type="WQTriple" />
+        </state>
+
+        <state id="state.winfo.waterlevel_pair_select" description="state.winfo.waterlevel_pair_select" state="de.intevation.flys.artifacts.states.WaterlevelPairSelectState" helpText="help.winfo.diff.diffs">
+            <data name="diffids" type="String" />
+        </state>
+
+        <state id="state.winfo.reference.curve.input.start" description="state.winfo.reference.curve.input (start)" state="de.intevation.flys.artifacts.states.EnterLocationState" helpText="help.winfo.reference.curve.start">
+            <data name="reference_startpoint" type="Double[]"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.reference.curve.input.start"/>
+            <to   state="state.winfo.reference.curve.input.end"/>
+        </transition>
+
+        <state id="state.winfo.reference.curve.input.end" description="state.winfo.reference.curve.input (end)" state="de.intevation.flys.artifacts.states.EnterMultipleLocationsState" helpText="help.winfo.reference.curve.end">
+            <data name="reference_endpoint" type="Double[]"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.reference.curve.input.end"/>
+            <to state="state.winfo.reference.curve"/>
+        </transition>
+
+        <state id="state.winfo.reference.curve" description="state.winfo.reference.curve" state="de.intevation.flys.artifacts.states.ReferenceCurveState" helpText="">
+            <outputmodes>
+                <outputmode name="reference_curve_normalized" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="reference_curve_normalized" description="A normalized reference curve!"/>
+                        <facet name="reference_curve_normalized.manualpoints" description="points"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="reference_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="reference_curve" description="A ... reference curve!"/>
+                        <facet name="reference_curve.manualpoints" description="points"/>
+                        <facet name="relativepoint"    description="Points at curve"/>
+                        <facet name="mainvalues.w" description="facet.computed_discharge_curve.mainvalues.w"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="reference_curve_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.reference_curve_export.csv" />
+                        <facet name="pdf" description="facet.reference_curve_export.pdf" />
+                    </facets>
+                </outputmode>
+                <outputmode name="reference_curve_report" description="output.reference_curve_report" mime-type="text/xml" type="report">
+                    <facets>
+                        <facet name="report" description="facet.reference_curve_export.report" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.w_differences" description="state.winfo.w_differences" state="de.intevation.flys.artifacts.states.WDifferencesState" helpText="https://flys-intern.intevation.de/Flys-3.0">
+
+            <outputmodes>
+                <outputmode name="w_differences" description="output.w_differences" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="longitudinal_section.q" description="facet.longitudinal_section.q"/>
+                        <facet name="longitudinal_section.w" description="facet.longitudinal_section.w"/>
+                        <facet name="w_differences" description="facet.w_differences"/>
+                        <facet name="other.wkms" description="facet.other.wkms"/>
+                        <facet name="other.wqkms" description="facet.other.wqkms"/>
+                        <facet name="heightmarks_points" description="facet.other.wkms.heightmarks_points"/>
+                        <facet name="w_differences.manualpoints" description="Manuelle Punkte"/>
+                        <facet name="longitudinal_section.annotations" description="facet.longitudinal_section.annotations"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="w_differences_export" description="output.w_differences.export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.w_differences.csv" />
+                        <facet name="pdf" description="facet.w_differences.pdf" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.durationcurve" description="state.winfo.durationcurve" state="de.intevation.flys.artifacts.states.DurationCurveState" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <outputmodes>
+                <outputmode name="duration_curve" description="output.duration_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="duration_curve.w" description="facet.duration_curve.w"/>
+                        <facet name="duration_curve.q" description="facet.duration_curve.q"/>
+                        <facet name="other.wq"         description="Point-like data like fixations"/>
+                        <facet name="duration_curve.mainvalues.q" description="Q Main Values at optional second axis"/>
+                        <facet name="computed_discharge_curve.mainvalues.w" description="W Main Values"/>
+                        <facet name="duration_curve.manualpoints" description="Manuelle Punkte"/>
+                        <facet name="relativepoint"    description="Points at curve"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="durationcurve_export" description="output.durationcurve_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.durationcurve_export.csv" />
+                        <facet name="pdf" description="facet.durationcurve_export.pdf" />
+                    </facets>
+                </outputmode>
+                <outputmode name="durationcurve_report" description="output.durationcurve_report" mime-type="text/xml" type="report">
+                    <facets>
+                        <facet name="report" description="facet.durationcurve_export.report" />
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.computeddischargecurve" description="state.winfo.computeddischargecurve" state="de.intevation.flys.artifacts.states.ComputedDischargeCurveState" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <outputmodes>
+                <outputmode name="computed_discharge_curve" description="output.computed_discharge_curve" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="computed_discharge_curve.q" description="facet.computed_discharge_curve.q"/>
+                        <facet name="computed_discharge_curve.mainvalues.q" description="facet.computed_discharge_curve.mainvalues.q"/>
+                        <facet name="computed_discharge_curve.mainvalues.w" description="facet.computed_discharge_curve.mainvalues.w"/>
+                        <facet name="discharge_curve.curve" description="facet.discharge_curve.curve"/>
+                        <facet name="heightmarks_points" description="facet.other.wqkms"/>
+                        <facet name="other.wqkms" description="facet.other.wqkms"/>
+                        <facet name="other.wq"    description="Point-like data like fixations"/>
+                        <facet name="other.wkms"  description="Point-like data like fixations"/>
+                        <facet name="other.wkms.interpol" description="Height over km, like flood protections."/>
+                        <facet name="computed_discharge_curve.manualpoints" description="Manuelle Punkte"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="computed_dischargecurve_export" description="output.computed_dischargecurve_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.computed_dischargecurve_export.csv" />
+                        <facet name="pdf" description="facet.computed_dischargecurve_export.pdf" />
+                    </facets>
+                </outputmode>
+                <outputmode name="computed_dischargecurve_report" description="output.computed_dischargecurve_report" mime-type="text/xml" type="report">
+                    <facets>
+                        <facet name="report" description="facet.computed_dischargecurve_export.report"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="computed_dischargecurve_at_export" description="output.computed_dischargecurve_at_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="at" description="facet.computed_dischargecurve_export.at"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.wq"/>
+            <to state="state.winfo.waterlevel"/>
+        </transition>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.wq_adapted"/>
+            <to state="state.winfo.discharge_longitudinal_section"/>
+        </transition>
+
+        <state id="state.winfo.waterlevel" description="state.winfo.waterlevel" state="de.intevation.flys.artifacts.states.WaterlevelState" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <outputmodes>
+                <outputmode name="longitudinal_section" description="output.longitudinal_section" mime-type="image/png" type="chart">
+                  <facets>
+                    <facet name="longitudinal_section.w" description="facet.longitudinal_section.w"/>
+                    <facet name="longitudinal_section.q" description="facet.longitudinal_section.q"/>
+                    <facet name="w_differences"          description="facet.w_differences"/>
+                    <facet name="other.wqkms.w"          description="W-Type of data" />
+                    <facet name="other.wq"               description="WQ-Type of data" />
+                    <facet name="other.wqkms.q"          description="Q-Type of data" />
+                    <facet name="other.wkms"             description="facet.other.wkms"/>
+                    <facet name="longitudinal_section.manualpoints" description="Manuelle Punkte"/>
+                    <facet name="other.wqkms"            description="facet.other.wqkms"/>
+                    <facet name="heightmarks_points"     description="facet.other.wkms.heightmarks_points"/>
+                    <facet name="longitudinal_section.area"        description="an area"/>
+                    <facet name="longitudinal_section.annotations" description="facet.longitudinal_section.annotations"/>
+                  </facets>
+                </outputmode>
+                <outputmode name="waterlevel_export" description="output.waterlevel_export" mime-type="text/plain" type="export">
+                  <facets>
+                    <facet name="csv" description="facet.waterlevel_export.csv" />
+                    <facet name="wst" description="facet.waterlevel_export.wst" />
+                    <facet name="pdf" description="facet.waterlevel_export.pdf" />
+                  </facets>
+                </outputmode>
+                <outputmode name="waterlevel_report" description="output.waterlevel_report" mime-type="text/xml" type="report">
+                  <facets>
+                    <facet name="report" description="facet.waterlevel_export.report"/>
+                  </facets>
+                </outputmode>
+                <outputmode name="cross_section" description="output.cross_section" mime-type="image/png" type="chart">
+                  <facets>
+                    <facet name="cross_section_water_line"   description="facet.cross_section_water_line"/>
+                    <facet name="cross_section"              description="facet.cross_section"/>
+                    <facet name="area"                       description="an area"/>
+                    <facet name="cross_section.area"         description="an area"/>
+                    <facet name="cross_section.manualpoints" description="points"/>
+                    <facet name="hyk"                        description="hyks"/>
+                  </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <state id="state.winfo.discharge_longitudinal_section" description="state.winfo.discharge_longitudinal_section" state="de.intevation.flys.artifacts.states.DischargeLongitudinalSection" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <outputmodes>
+                <outputmode name="discharge_longitudinal_section" description="output.discharge_longitudinal_section" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="discharge_longitudinal_section.w"/>
+                        <facet name="discharge_longitudinal_section.q"/>
+                        <facet name="discharge_longitudinal_section.c"/>
+                        <facet name="discharge_longitudinal_section.manualpoints" description="Manuelle Punkte"/>
+                        <facet name="other.wqkms.w"/>
+                        <facet name="other.wq"/>
+                        <facet name="other.wqkms.q"/>
+                        <facet name="other.wqkms"/>
+                        <facet name="other.wkms"/>
+                        <facet name="heightmarks_points"/>
+                        <facet name="longitudinal_section.annotations"/>
+                        <facet name="longitudinal_section.w"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="discharge_longitudinal_section_export" description="output.discharge_longitudinal_section_export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.discharge_longitudinal_section_export.csv" />
+                        <facet name="wst" description="facet.discharge_longitudinal_section_export.wst" />
+                    </facets>
+                </outputmode>
+                <outputmode name="discharge_longitudinal_section_report" description="output.discharge_longitudinal_section_report" mime-type="text/xml" type="report">
+                    <facets>
+                        <facet name="report" description="facet.discharge_longitudinal_section_export.report"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="cross_section" description="output.cross_section" mime-type="image/png" type="chart">
+                  <facets>
+                    <facet name="cross_section_water_line" description="facet.cross_section_water_line"/>
+                    <facet name="cross_section"            description="facet.cross_section"/>
+                    <facet name="area"                     description="an area"/>
+                    <facet name="cross_section.area"                     description="an area"/>
+                    <facet name="cross_section.manualpoints" description="points"/>
+                    <facet name="hyk"                      description="hyks"/>
+                  </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.waterlevel"/>
+            <to state="state.winfo.uesk.dgm"/>
+        </transition>
+
+        <state id="state.winfo.uesk.wsp" description="state.winfo.uesk.wsp" state="de.intevation.flys.artifacts.states.WaterlevelSelectState" helpText="help.winfo.uesk.wsp">
+            <data name="wsp" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.uesk.wsp"/>
+            <to state="state.winfo.uesk.dgm"/>
+        </transition>
+
+        <state id="state.winfo.uesk.dgm" description="state.winfo.uesk.dgm" state="de.intevation.flys.artifacts.states.DGMSelect" helpText="help.winfo.uesk.dem">
+            <data name="dgm" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.uesk.dgm"/>
+            <to state="state.winfo.uesk.profiles"/>
+        </transition>
+
+        <state id="state.winfo.uesk.profiles" description="state.winfo.uesk.profiles" state="de.intevation.flys.artifacts.states.ProfileDistanceSelect" helpText="help.winfo.uesk.profiles">
+            <data name="profile_distance" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.uesk.profiles"/>
+            <to state="state.winfo.uesk.floodplain"/>
+        </transition>
+
+        <state id="state.winfo.uesk.floodplain" description="state.winfo.uesk.floodplain.description" state="de.intevation.flys.artifacts.states.FloodplainChoice" helpText="help.winfo.uesk.floodplain">
+            <data name="use_floodplain" type="Boolean" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.uesk.floodplain"/>
+            <to state="state.winfo.uesk.differences"/>
+        </transition>
+
+        <state id="state.winfo.uesk.differences" description="state.winfo.uesk.differences" state="de.intevation.flys.artifacts.states.WaterlevelGroundDifferences" helpText="help.winfo.uesk.differences">
+            <data name="diff_from" type="Double" />
+            <data name="diff_to"   type="Double" />
+            <data name="diff_diff" type="Double" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.uesk.differences"/>
+            <to state="state.winfo.uesk.scenario"/>
+        </transition>
+
+        <state id="state.winfo.uesk.scenario" description="state.winfo.uesk.scenario" state="de.intevation.flys.artifacts.states.ScenarioSelect" helpText="help.winfo.uesk.scenario">
+            <data name="scenario" type="String" />
+            <data name="uesk.barriers" type="String" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.uesk.scenario"/>
+            <to state="state.winfo.uesk.uesk"/>
+        </transition>
+
+        <state id="state.winfo.uesk.uesk" description="state.winfo.uesk.uesk" state="de.intevation.flys.artifacts.states.FloodMapState" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <outputmodes>
+                <outputmode name="floodmap" description="output.uesk.map.description" type="map">
+                    <facets>
+                        <facet name="floodmap.wsplgen"/>
+                        <facet name="floodmap.barriers"/>
+                        <facet name="floodmap.usershape"/>
+                        <facet name="floodmap.riveraxis"/>
+                        <facet name="floodmap.wmsbackground"/>
+                        <facet name="floodmap.kms"/>
+                        <facet name="floodmap.qps"/>
+                        <facet name="floodmap.hws"/>
+                        <facet name="floodmap.hydr_boundaries"/>
+                        <facet name="floodmap.hydr_boundaries_poly"/>
+                        <facet name="floodmap.catchment"/>
+                        <facet name="floodmap.floodplain"/>
+                        <facet name="floodmap.lines"/>
+                        <facet name="floodmap.buildings"/>
+                        <facet name="floodmap.fixpoints"/>
+                        <facet name="floodmap.floodmaps"/>
+                        <facet name="floodmap.gauge_location"/>
+                        <facet name="floodmap.externalwms"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="wsplgen_report" description="output.wsplgen_report" mime-type="text/xml" type="report">
+                    <facets>
+                        <facet name="report" description="facet.wsplgen_export.report"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+
+        <!-- path for historical discharge curves -->
+        <state id="state.winfo.historicalq.reference_gauge" description="state.winfo.historicalq.reference_gauge" state="de.intevation.flys.artifacts.states.ReferenceGaugeState" helpText="help.winfo.historical.discharge.reference_gauge">
+            <data name="reference_gauge" type="Integer" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.historicalq.reference_gauge"/>
+            <to state="state.winfo.historicalq.timerange"/>
+        </transition>
+
+        <state id="state.winfo.historicalq.timerange" description="state.winfo.historicalq.timerange" state="de.intevation.flys.artifacts.states.GaugeTimerangeState" helpText="help.winfo.historical.discharge.timerange">
+            <data name="year_range" type="longrange"/>
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.historicalq.timerange"/>
+            <to state="state.winfo.historicalq.mode"/>
+        </transition>
+
+        <state id="state.winfo.historicalq.mode" description="state.winfo.historicalq.mode" state="de.intevation.flys.artifacts.states.HistoricalDischargeState" helpText="help.winfo.historical.discharge.mode">
+            <data name="historical_mode" type="intoptions" />
+            <data name="historical_values" type="doublearray" />
+        </state>
+
+        <transition transition="de.intevation.flys.artifacts.transitions.DefaultTransition">
+            <from state="state.winfo.historicalq.mode"/>
+            <to state="state.winfo.historicalq.compute"/>
+        </transition>
+
+        <state id="state.winfo.historicalq.compute" description="state.winfo.historicalq.compute" state="de.intevation.flys.artifacts.states.HistoricalDischargeComputeState" helpText="https://flys-intern.intevation.de/Flys-3.0">
+            <outputmodes>
+                <outputmode name="historical_discharge" description="output.historical_discharge.description" mime-type="image/png" type="chart">
+                    <facets>
+                        <facet name="historical_discharge.historicalq"/>
+                        <facet name="historical_discharge.historicalq.diff"/>
+                        <facet name="historical_discharge.manualpoints"/>
+                    </facets>
+                </outputmode>
+                <outputmode name="historical_discharge_export" description="output.historical_discharge.export" mime-type="text/plain" type="export">
+                    <facets>
+                        <facet name="csv" description="facet.historical_discharge.csv" />
+                        <facet name="pdf" description="facet.historical_discharge.pdf" />
+                    </facets>
+                </outputmode>
+                <outputmode name="historical_discharge_report" description="output.historical_discharge_report" mime-type="text/xml" type="report">
+                    <facets>
+                        <facet name="report" description="facet.historical_discharge.report"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/artifacts/wmsbackground.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<artifact name="wmsbackground">
+    <states>
+        <state id="state.wmsbackground.layer"
+               description="state.wmsbackground.layer.description"
+               state="de.intevation.flys.artifacts.states.WMSBackgroundState">
+            <outputmodes>
+                <outputmode name="floodmap" description="output.uesk.map.description" type="map">
+                    <facets>
+                        <facet name="floodmap.wmsbackground"/>
+                    </facets>
+                </outputmode>
+            </outputmodes>
+        </state>
+    </states>
+</artifact>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/cache.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ehcache>
+
+    <diskStore path="java.io.tmpdir"/>
+
+    <defaultCache
+            maxElementsInMemory="1000"
+            eternal="false"
+            timeToIdleSeconds="120"
+            timeToLiveSeconds="3600"
+            overflowToDisk="true"
+            maxElementsOnDisk="100000"
+            diskPersistent="false"
+            diskExpiryThreadIntervalSeconds="120"
+            memoryStoreEvictionPolicy="LRU"
+            />
+
+    <!-- This one is used for the WST value tables -->
+
+    <cache name="wst-value-table"
+           maxElementsInMemory="20"
+           maxElementsOnDisk="100"
+           eternal="false"
+           timeToIdleSeconds="360"
+           overflowToDisk="true"
+           timeToLiveSeconds="14400"
+           diskPersistent="true"
+           memoryStoreEvictionPolicy="LRU"
+           />
+
+    <cache name="location-provider"
+           maxElementsInMemory="20"
+           eternal="false"
+           diskPersistent="true"
+           overflowToDisk="true"
+           timeToIdleSeconds="360"
+           timeToLiveSeconds="86400"
+           memoryStoreEvictionPolicy="LFU"
+           />
+
+    <!-- This one is used to cache the computed values.-->
+    <cache name="computed.values"
+           maxElementsInMemory="200"
+           eternal="false"
+           timeToLiveSeconds="172800"
+           overflowToDisk="true"
+           diskPersistent="true"
+           memoryStoreEvictionPolicy="LRU"
+           />
+
+    <!-- This one is used to cache the non-computed wst-values.-->
+    <cache name="wst-value-table-static"
+           maxElementsInMemory="200"
+           eternal="false"
+           timeToLiveSeconds="172800"
+           overflowToDisk="true"
+           diskPersistent="true"
+           memoryStoreEvictionPolicy="LRU"
+           />
+
+    <!-- This one is used to cache the non-computed wst-values.-->
+    <cache name="wst-wq-value-table-static"
+           maxElementsInMemory="200"
+           eternal="false"
+           timeToLiveSeconds="172800"
+           overflowToDisk="true"
+           diskPersistent="true"
+           memoryStoreEvictionPolicy="LRU"
+           />
+
+    <!-- This one is used for the SQL statements used by the static datacage -->
+    <cache name="datacage.db"
+           maxElementsInMemory="2000"
+           eternal="false"
+           timeToLiveSeconds="7200"
+           memoryStoreEvictionPolicy="LFU"
+           />
+
+    <!-- This one is used for the cross section next neighbor lookup -->
+    <cache name="cross-section-kms"
+           maxElementsInMemory="50"
+           eternal="false"
+           timeToLiveSeconds="7200"
+           memoryStoreEvictionPolicy="LRU"
+           />
+
+    <!-- This one is used to load chunks of the cross section lines  -->
+    <cache name="fast-cross-section-lines"
+           maxElementsInMemory="500"
+           eternal="false"
+           timeToLiveSeconds="7200"
+           memoryStoreEvictionPolicy="LRU"
+           overflowToDisk="true"
+           diskPersistent="true"
+           />
+
+    <!-- This one is used to hold the fixings overviews. -->
+    <cache name="fixings-overviews"
+           maxElementsInMemory="15"
+           eternal="false"
+           timeToLiveSeconds="7200"
+           memoryStoreEvictionPolicy="LFU"
+           overflowToDisk="true"
+           diskPersistent="true"
+           />
+
+    <!-- This one is used to hold the data columns of fixings. -->
+    <cache name="fixings-columns"
+           maxElementsInMemory="70"
+           eternal="false"
+           timeToLiveSeconds="7200"
+           memoryStoreEvictionPolicy="LFU"
+           overflowToDisk="true"
+           diskPersistent="true"
+           />
+
+    <!-- This one is used to load single HYK Zones  -->
+    <cache name="hykache"
+           maxElementsInMemory="500"
+           eternal="false"
+           timeToLiveSeconds="14400"
+           memoryStoreEvictionPolicy="LRU"
+           overflowToDisk="true"
+           diskPersistent="true"
+           />
+
+    <!-- This one is used for the cross section lookup 
+         Because of lazy fetching and relatively big amount of data, disabled
+         cache for now.
+    <cache name="cross_sections"
+           maxElementsInMemory="50"
+           eternal="false"
+           timeToLiveSeconds="86400"
+           memoryStoreEvictionPolicy="LRU"
+           />
+    -->
+</ehcache>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/conf.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,344 @@
+<artifact-database>
+    <export-secret>YOUR_SECRET</export-secret>
+    <factories>
+        <context-factory>de.intevation.flys.artifacts.context.FLYSContextFactory</context-factory>
+
+        <collection-factory
+            name="DefaultArtifactCollectionFactory"
+            description="The default artifact collection factory"
+            ttl="21600000"
+            artifact-collection="de.intevation.flys.collections.FLYSArtifactCollection">de.intevation.artifactdatabase.DefaultArtifactCollectionFactory</collection-factory>
+
+        <artifact-factories>
+            <!-- All Artifactfactories which are available in this Database. -->
+            <artifact-factory name="winfo" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WINFOArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="riveraxis" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.RiverAxisArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="hyk" description="Factory to create an artifact to be used for hyks"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.HYKArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="new_map" description="Factory to create an artifact to be used for new map"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.MapArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmskmfactory" description="Factory to create an artifact that generates WMS facets for KMs."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSKmArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsqpsfactory" description="Factory to create an artifact that generates WMS facets for CrossSectionTracks."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSQPSArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmshwsfactory" description="Factory to create an artifact that generates WMS facets for CrossSectionTracks."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSHwsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmshydrboundariesfactory" description="Factory to create an artifact that generates WMS facets for CrossSectionTracks."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSHydrBoundaryArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmshydrboundariespolyfactory" description="Factory to create an artifact that generates WMS facets for CrossSectionTracks."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSHydrBoundaryPolyArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmscatchmentfactory" description="Factory to create an artifact that generates WMS facets for CrossSectionTracks."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSCatchmentArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsfloodplainfactory" description="Factory to create an artifact that generates WMS facets for CrossSectionTracks."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSFloodplainArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmslinefactory" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSLineArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsbuildingsfactory" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSBuildingsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsfixpointsfactory" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSFixpointsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsfloodmapsfactory" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSFloodmapsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsgaugelocationfactory" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSGaugeLocationArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wmsbackground" description="Factory to create an artifact to be used in WINFO"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WMSBackgroundArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="externalwmsfactory" description="Factory to create an artifact to be used in Floodmaps to display external WMS layers"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.ExternalWMSArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="annotations" description="Factory to create an artifact to access Annotations for Points at rivers"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.AnnotationArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="crosssections" description="Factory to create an artifact to access cross sections"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.CrossSectionArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="waterlevel" description="Factory to create an artifact to access waterlevel data"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WaterlevelArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="mainvalue" description="Factory to create an artifact to access Main Values for discharge curve diagrams"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.MainValuesArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="staticwkms" description="Factory to create an artifact to access 'other' WKms data"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.StaticWKmsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="area" description="Factory to create an artifact to draw (wkms) area data"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.AreaArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="staticwqkms" description="Factory to create an artifact to access 'other' WKms data"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.StaticWQKmsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="wqinterpol" description="Factory to create an artifact to access 'other' WQ (at km) data"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.WQKmsInterpolArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="new_chart" description="Factory to create an artifact to be used for new chart"
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.ChartArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="manualpoints" description="Factory to create an artifact for storing and editing points added by the user."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.ManualPointsArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="fixanalysis" description="Factory to create an artifact to be used in fixation analysis."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.FixationArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+            <artifact-factory name="gaugedischarge" description="Factory to create an artifact to host historical qs."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.GaugeDischargeArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+
+            <!-- MINFO specific Artifacts -->
+            <artifact-factory name="minfo" description="Factory to create an artifact to be used in module minfo."
+                ttl="3600000"
+                artifact="de.intevation.flys.artifacts.MINFOArtifact">de.intevation.artifactdatabase.DefaultArtifactFactory</artifact-factory>
+        </artifact-factories>
+
+        <user-factory name="default" description="Factory to create new users">de.intevation.artifactdatabase.DefaultUserFactory</user-factory>
+
+        <service-factories>
+            <service-factory
+                name="rivers"
+                service="de.intevation.flys.artifacts.services.RiverService"
+                description="This service returns a list of provided rivers by the artifact server.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="distanceinfo"
+                service="de.intevation.flys.artifacts.services.DistanceInfoService"
+                description="Returns a list of distances supported by a specific river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="mainvalues"
+                service="de.intevation.flys.artifacts.services.MainValuesService"
+                description="Returns the main values of a river's gauge based on a start and end point of the river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="metadata"
+                service="de.intevation.flys.artifacts.services.MetaDataService"
+                description="The service provides some introspection into the database content.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="mapinfo"
+                service="de.intevation.flys.artifacts.services.MapInfoService"
+                description="The service provides some basic information to create a WMS for a specific river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="cross-section-km"
+                service="de.intevation.flys.artifacts.services.CrossSectionKMService"
+                description="The service provides the N next neighbored kms and ids of cross section lines for given cross section id, km and N.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="cache-invalidation"
+                service="de.intevation.flys.artifacts.services.CacheInvalidationService"
+                description="The service invalidates caches.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="dischargeinfo"
+                service="de.intevation.flys.artifacts.services.DischargeInfoService"
+                description="Returns description, start year and end year of discharges at a specific gauge.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="fixings-overview"
+                service="de.intevation.flys.artifacts.services.FixingsOverviewService"
+                description="Returns an overview of the fixings of a given river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="fixings-km-chart"
+                service="de.intevation.flys.artifacts.services.FixingsKMChartService"
+                description="Returns a chart of fixings of given river and km.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="gaugeinfo"
+                service="de.intevation.flys.artifacts.services.GaugeInfoService"
+                description="Returns an overview of the fixings of a given river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="fileupload"
+                service="de.intevation.flys.artifacts.services.FileUploadService"
+                description="Takes base64 coded files embedded in XML.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="themelisting"
+                service="de.intevation.flys.artifacts.services.ThemeListingService"
+                description="Returns a list of Themes filtered by theme name.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+        </service-factories>
+
+    </factories>
+
+    <lifetime-listeners>
+        <listener>de.intevation.flys.artifacts.datacage.Datacage</listener>
+        <listener>de.intevation.flys.wsplgen.SchedulerSetup</listener>
+    </lifetime-listeners>
+
+    <backend-listeners>
+        <listener>de.intevation.flys.artifacts.datacage.DatacageBackendListener</listener>
+    </backend-listeners>
+
+    <callcontext-listener
+        name="SessionCallContextListener"
+        description="A CallContext.Listener to open and close Hibernatesessions">de.intevation.flys.artifacts.context.SessionCallContextListener</callcontext-listener>
+
+    <artifacts>
+        <artifact name="manualpoints" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/manualpoints.xml" />
+        <artifact name="winfo" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/winfo.xml" />
+        <artifact name="minfo" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/minfo.xml" />
+        <artifact name="waterlevel" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/waterlevel.xml" />
+        <artifact name="annotation" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/annotation.xml" />
+        <artifact name="hyk" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/hyk.xml" />
+        <artifact name="wmsbackground" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/wmsbackground.xml" />
+        <artifact name="new_map" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/map.xml" />
+        <artifact name="new_chart" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/chart.xml" />
+        <artifact name="staticwqkms" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/staticwqkms.xml" />
+        <artifact name="fixanalysis" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/fixanalysis.xml" />
+        <artifact name="gaugedischarge" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${artifacts.config.dir}/artifacts/gaugedischarge.xml" />
+    </artifacts>
+
+
+    <hooks>
+        <hook
+            class="de.intevation.flys.artifacts.CollectionMonitor"
+            applies="post-feed,post-advance,post-describe"
+            xmlns:xlink="http://www.w3.org/1999/xlink"
+            xlink:href="${artifacts.config.dir}/output-defaults.xml">
+        </hook>
+    </hooks>
+
+    <output-generators>
+        <output-generator name="discharge_curve">de.intevation.flys.exports.DischargeCurveGenerator</output-generator>
+        <output-generator name="discharge_curve_chartinfo">de.intevation.flys.exports.DischargeCurveInfoGenerator</output-generator>
+        <output-generator name="cross_section">de.intevation.flys.exports.CrossSectionGenerator</output-generator>
+        <output-generator name="cross_section_chartinfo">de.intevation.flys.exports.CrossSectionInfoGenerator</output-generator>
+        <output-generator name="computed_discharge_curve">de.intevation.flys.exports.ComputedDischargeCurveGenerator</output-generator>
+        <output-generator name="computed_discharge_curve_chartinfo">de.intevation.flys.exports.ComputedDischargeCurveInfoGenerator</output-generator>
+        <output-generator name="longitudinal_section">de.intevation.flys.exports.LongitudinalSectionGenerator</output-generator>
+        <output-generator name="longitudinal_section_chartinfo">de.intevation.flys.exports.LongitudinalSectionInfoGenerator</output-generator>
+        <output-generator name="duration_curve">de.intevation.flys.exports.DurationCurveGenerator</output-generator>
+        <output-generator name="duration_curve_chartinfo">de.intevation.flys.exports.DurationCurveInfoGenerator</output-generator>
+        <output-generator name="discharge_longitudinal_section">de.intevation.flys.exports.DischargeLongitudinalSectionGenerator</output-generator>
+        <output-generator name="discharge_longitudinal_section_chartinfo">de.intevation.flys.exports.DischargeLongitudinalSectionInfoGenerator</output-generator>
+        <output-generator name="waterlevel_export">de.intevation.flys.exports.WaterlevelExporter</output-generator>
+        <output-generator name="durationcurve_export">de.intevation.flys.exports.DurationCurveExporter</output-generator>
+        <output-generator name="computed_dischargecurve_export">de.intevation.flys.exports.ComputedDischargeCurveExporter</output-generator>
+        <output-generator name="discharge_longitudinal_section_export">de.intevation.flys.exports.DischargeLongitudinalSectionExporter</output-generator>
+        <output-generator name="w_differences">de.intevation.flys.exports.WDifferencesCurveGenerator</output-generator>
+        <output-generator name="w_differences_chartinfo">de.intevation.flys.exports.WDifferencesCurveInfoGenerator</output-generator>
+        <output-generator name="w_differences_export">de.intevation.flys.exports.WDifferencesExporter</output-generator>
+        <output-generator name="floodmap">de.intevation.flys.exports.MapGenerator</output-generator>
+        <output-generator name="map">de.intevation.flys.exports.MapGenerator</output-generator>
+        <output-generator name="reference_curve">de.intevation.flys.exports.ReferenceCurveGenerator</output-generator>
+        <output-generator name="reference_curve_normalized">de.intevation.flys.exports.NormalizedReferenceCurveGenerator</output-generator>
+        <output-generator name="reference_curve_normalized_chartinfo">de.intevation.flys.exports.NormalizedReferenceCurveInfoGenerator</output-generator>
+        <output-generator name="reference_curve_chartinfo">de.intevation.flys.exports.ReferenceCurveInfoGenerator</output-generator>
+        <output-generator name="reference_curve_export">de.intevation.flys.exports.ReferenceCurveExporter</output-generator>
+        <output-generator name="historical_discharge">de.intevation.flys.exports.HistoricalDischargeCurveGenerator</output-generator>
+        <output-generator name="historical_discharge_chartinfo">de.intevation.flys.exports.HistoricalDischargeCurveInfoGenerator</output-generator>
+        <output-generator name="historical_discharge_export">de.intevation.flys.exports.HistoricalDischargeCurveExporter</output-generator>
+        <output-generator name="flow_velocity">de.intevation.flys.exports.FlowVelocityGenerator</output-generator>
+        <output-generator name="flow_velocity_chartinfo">de.intevation.flys.exports.FlowVelocityInfoGenerator</output-generator>
+        <output-generator name="flow_velocity_export">de.intevation.flys.exports.FlowVelocityExporter</output-generator>
+        <output-generator name="bedheight_middle">de.intevation.flys.exports.MiddleBedHeightGenerator</output-generator>
+        <output-generator name="bedheight_middle_chartinfo">de.intevation.flys.exports.MiddleBedHeightInfoGenerator</output-generator>
+        <output-generator name="bedheight_middle_export">de.intevation.flys.exports.MiddleBedHeightExporter</output-generator>
+        <output-generator name="fix_deltawt_export">de.intevation.flys.exports.DeltaWtExporter</output-generator>
+        <!-- Error report generators. -->
+        <output-generator name="discharge_longitudinal_section_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="waterlevel_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="computed_dischargecurve_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="durationcurve_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="wsplgen_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="historical_discharge_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="reference_curve_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <output-generator name="fix_report">de.intevation.flys.exports.ReportGenerator</output-generator>
+        <!-- AT exporter. -->
+        <output-generator name="computed_dischargecurve_at_export">de.intevation.flys.exports.ATExporter</output-generator>
+    </output-generators>
+
+    <!-- Path to the template file of the meta data. -->
+    <metadata>
+        <template>${artifacts.config.dir}/meta-data.xml</template>
+    </metadata>
+
+    <!-- The floodmap configuration for each supported river. Each element
+         requires a srid, wms and background-wms. Those information are used for
+         creating a Map view.-->
+    <floodmap>
+        <shapefile-path value="${artifacts.config.dir}/../shapefiles"/>
+        <mapserver>
+            <server path="http://flys-devel.intevation.de/cgi-bin/"/>
+            <mapfile path="${artifacts.config.dir}/../flys.map"/>
+            <templates path="${artifacts.config.dir}/mapserver/"/>
+            <map-template path="mapfile.vm"/>
+        </mapserver>
+
+        <velocity>
+            <logfile path="${artifacts.config.dir}/../velocity_log.log"/>
+        </velocity>
+
+        <river name="Saar">
+            <srid value="31466"/>
+            <river-wms url="http://flys-devel.intevation.de/cgi-bin/saar-wms"/>
+            <background-wms url="http://vmap0.tiles.osgeo.org/wms/vmap0" layers="basic"/>
+        </river>
+        <river name="Mosel">
+            <srid value="31466"/>
+            <river-wms url="http://flys-devel.intevation.de/cgi-bin/mosel-wms"/>
+            <background-wms url="http://vmap0.tiles.osgeo.org/wms/vmap0" layers="basic"/>
+        </river>
+        <river name="Elbe">
+            <srid value="31466"/>
+            <river-wms url="http://flys-devel.intevation.de/cgi-bin/elbe-wms"/>
+            <background-wms url="http://vmap0.tiles.osgeo.org/wms/vmap0" layers="basic"/>
+        </river>
+    </floodmap>
+
+    <rest-server>
+        <!--  The port which the ArtifactDatabase (ArtifactServer) will bind to. -->
+        <port>8181</port>
+        <listen>localhost</listen>
+    </rest-server>
+    <!-- Garbage collection of outdated artifacts. -->
+    <cleaner>
+        <sleep-time>60000</sleep-time>
+    </cleaner>
+
+    <cache>
+        <config-file>${artifacts.config.dir}/cache.xml</config-file>
+    </cache>
+
+    <!-- This is the default configuration of the datacage db:
+    <datacage>
+        <user/>
+        <password/>
+        <driver>org.h2.Driver</driver>
+        <url>jdbc:h2:mem:datacage;INIT=RUNSCRIPT FROM '${artifacts.config.dir}/datacage.sql'</url>
+    </datacage>
+    -->
+
+    <database>
+        <!-- This Section configures the Settings for connecting to the 
+             Artifact-Database instance. e.g. SQLite -->
+        <user>SA</user>
+        <password></password>
+        <!-- For use with a postgresql database use the appropriate driver-->
+        <!--driver>org.postgresql.Driver</driver-->
+        <url>jdbc:h2:${artifacts.config.dir}/../artifactdb/artifacts.db</url>
+    </database>
+    <!-- This is the default backend db configuration. -->
+    <!--
+    <backend-database>
+        <user>flys</user>
+        <password>flys</password>
+        <dialect>org.hibernate.dialect.PostgreSQLDialect</dialect>
+        <driver>org.postgresql.Driver</driver>
+        <url>jdbc:postgresql://localhost:5432/flys</url>
+    </backend-database>
+    -->
+
+    <flys>
+        <themes>
+            <configuration>${artifacts.config.dir}/themes.xml</configuration>
+        </themes>
+    </flys>
+</artifact-database>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/datacage.sql	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,104 @@
+BEGIN;
+
+CREATE SEQUENCE USERS_ID_SEQ;
+
+CREATE TABLE users (
+    id  INT  PRIMARY KEY NOT NULL,
+    gid UUID             NOT NULL UNIQUE
+);
+
+CREATE SEQUENCE COLLECTIONS_ID_SEQ;
+
+CREATE TABLE collections (
+    id       INT  PRIMARY KEY NOT NULL,
+    gid      UUID             NOT NULL UNIQUE,
+    user_id  INT              NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+    name     VARCHAR(256)     NOT NULL,
+    creation TIMESTAMP        NOT NULL
+);
+
+CREATE SEQUENCE ARTIFACTS_ID_SEQ;
+
+CREATE TABLE artifacts (
+    id       INT  PRIMARY KEY NOT NULL,
+    gid      UUID             NOT NULL UNIQUE,
+    state    VARCHAR(256)     NOT NULL,
+    creation TIMESTAMP        NOT NULL
+);
+
+CREATE SEQUENCE COLLECTION_ITEMS_ID_SEQ;
+
+CREATE TABLE collection_items (
+    id            INT PRIMARY KEY NOT NULL,
+    collection_id INT             NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
+    artifact_id   INT             NOT NULL REFERENCES artifacts(id)   ON DELETE CASCADE
+);
+
+CREATE SEQUENCE ARTIFACT_DATA_ID_SEQ;
+
+CREATE TABLE artifact_data (
+    id          INT PRIMARY KEY NOT NULL,
+    artifact_id INT             NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
+    kind        VARCHAR(256)    NOT NULL,
+    k           VARCHAR(256)    NOT NULL,
+    v           VARCHAR(256),   -- Maybe too short
+    UNIQUE (artifact_id, k)
+);
+
+CREATE SEQUENCE OUTS_ID_SEQ;
+
+CREATE TABLE outs (
+    id          INT PRIMARY KEY NOT NULL,
+    artifact_id INT             NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
+    name        VARCHAR(256)    NOT NULL,
+    description VARCHAR(256),
+    out_type    VARCHAR(256)
+);
+
+CREATE SEQUENCE FACETS_ID_SEQ;
+
+CREATE TABLE facets (
+    id          INT PRIMARY KEY NOT NULL,
+    out_id      INT             NOT NULL REFERENCES outs(id) ON DELETE CASCADE,
+    name        VARCHAR(256)    NOT NULL,
+    num         INT             NOT NULL,
+    state       VARCHAR(256)    NOT NULL,
+    description VARCHAR(256),
+    UNIQUE (out_id, num, name)
+);
+
+CREATE VIEW master_artifacts AS
+    SELECT a2.id             AS id,
+           a2.gid            AS gid,
+           a2.state          AS state,
+           a2.creation       AS creation,
+           ci2.collection_id AS collection_id
+    FROM   collection_items ci2 
+           JOIN artifacts a2 
+             ON ci2.artifact_id = a2.id 
+           JOIN (SELECT ci.collection_id AS c_id, 
+                        MIN(a.creation)  AS oldest_a 
+                 FROM   collection_items ci 
+                        JOIN artifacts a 
+                          ON ci.artifact_id = a.id 
+                 GROUP  BY ci.collection_id) o 
+             ON o.c_id = ci2.collection_id 
+    WHERE  a2.creation = o.oldest_a;
+
+-- DROP VIEW master_artifacts;
+-- DROP SEQUENCE USERS_ID_SEQ;
+-- DROP SEQUENCE COLLECTIONS_ID_SEQ;
+-- DROP SEQUENCE ARTIFACTS_ID_SEQ;
+-- DROP SEQUENCE COLLECTION_ITEMS_ID_SEQ;
+-- DROP SEQUENCE ARTIFACT_DATA_ID_SEQ;
+-- DROP SEQUENCE OUTS_ID_SEQ;
+-- DROP SEQUENCE FACETS_ID_SEQ;
+-- DROP TABLE facets;
+-- DROP TABLE outs;
+-- DROP TABLE artifact_data;
+-- DROP TABLE collection_items;
+-- DROP TABLE collections;
+-- DROP TABLE artifacts;
+-- DROP TABLE users;
+
+COMMIT;
Binary file flys-artifacts/doc/conf/jasper/computed-discharge.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/computed-discharge_en.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/duration.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/duration_en.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/historical-discharge.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/historical-discharge_en.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_en.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_en_gauge.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_en_gauge_end.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_en_gauge_start_end.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_gauge.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_gauge_end.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/reference_gauge_start_end.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/waterlevel.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/waterlevel_en.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/wdifferences.jasper has changed
Binary file flys-artifacts/doc/conf/jasper/wdifferences_en.jasper has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/barrier_lines_class.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,32 @@
+CLASS
+    NAME "Damm"
+    EXPRESSION ("[TYP]"="Damm")
+    STYLE
+        SIZE 5
+        OUTLINECOLOR "#008000"
+    END
+END
+CLASS
+    NAME "Rohr 1"
+    EXPRESSION ("[TYP]"="Rohr 1")
+    STYLE
+        SIZE 5
+        OUTLINECOLOR "#800080"
+    END
+END
+CLASS
+    NAME "Rohr 2"
+    EXPRESSION ("[TYP]"="Rohr 2")
+    STYLE
+        SIZE 5
+        OUTLINECOLOR "#808080"
+    END
+END
+CLASS
+    NAME "Graben"
+    EXPRESSION ("[TYP]"="Graben")
+    STYLE
+        SIZE 5
+        OUTLINECOLOR "#800000"
+    END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/barrier_polygons_class.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,7 @@
+CLASS
+    NAME "POLYGON_BARRIERS"
+    STYLE
+        SIZE 5
+        OUTLINECOLOR "#FF8000"
+    END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/db_layer.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,58 @@
+LAYER
+    NAME "$LAYER.getName()"
+    TYPE $LAYER.getType()
+
+    CONNECTIONTYPE $LAYER.getConnectionType()
+    CONNECTION "$LAYER.getConnection()"
+
+    DATA    "$LAYER.getData()"
+    FILTER  "$LAYER.getFilter()"
+    EXTENT  $LAYER.getExtent()
+
+    STATUS    ON
+    TEMPLATE  map.html
+    TOLERANCE 10
+    DUMP      TRUE
+    #if( $LAYER.getGroup() )
+        GROUP "$LAYER.getGroup()"
+    #end
+
+    #if ( $LAYER.getLabelItem() )
+        LABELITEM $LAYER.getLabelItem()
+    #end
+
+    PROJECTION
+        "init=epsg:$LAYER.getSrid()"
+    END
+
+    METADATA
+        "wms_title" "$LAYER.getTitle()"
+        "gml_include_items" "all"
+        #if ( $LAYER.getGroupTitle() )
+            "wms_group_title" "$LAYER.getGroupTitle()"
+        #end
+    END
+
+    #if ( $LAYER.getStyle() )
+        $LAYER.getStyle()
+    #else
+        CLASS
+            NAME ""
+            STYLE
+                SIZE 5
+                OUTLINECOLOR "#000000"
+            END
+            #if ( $LAYER.getLabelItem() )
+                LABEL
+                    ANGLE auto
+                    SIZE 10
+                    COLOR "#000000"
+                    TYPE truetype
+                    FONT LiberationSans-Italic
+                    POSITION ur
+                    OFFSET 2 2
+                END
+            #end
+        END
+    #end
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/fontset.txt	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,2 @@
+FreeSans /usr/share/fonts/truetype/freefont/FreeSans.ttf
+DefaultFont /usr/share/fonts/truetype/freefont/FreeSans.ttf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/layer.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,38 @@
+LAYER
+    NAME      "$LAYER.getName()"
+    TYPE      $LAYER.getType()
+    DATA      "$LAYER.getDirectory()/$LAYER.getData()"
+    STATUS    ON
+    TEMPLATE  map.html
+    TOLERANCE 10
+    DUMP      TRUE
+    #if( $LAYER.getGroup() )
+        GROUP "$LAYER.getGroup()"
+    #end
+
+    METADATA
+        "wms_title" "$LAYER.getTitle()"
+        "gml_include_items" "all"
+        #if ( $LAYER.getGroupTitle() )
+            "wms_group_title" "$LAYER.getGroupTitle()"
+        #end
+    END
+
+    PROJECTION
+        "init=epsg:$LAYER.getSrid()"
+    END
+
+    #if ( !$LAYER.getStyle() )
+        #if ( $LAYER.getGroupTitle() )
+            #if ( $LAYER.getType() == "POLYGON" )
+                #include("barrier_polygons_class.vm")
+            #else
+                #include("barrier_lines_class.vm")
+            #end
+        #else
+            #include("wsplgen_class.vm")
+        #end
+    #else
+        $LAYER.getStyle()
+    #end
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/mapfile.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,60 @@
+MAP
+    NAME "FLYS-Map"
+    STATUS ON
+    SIZE 600 400
+    MAXSIZE 4000
+    EXTENT -90 -180 90 180
+    UNITS DD
+    SHAPEPATH "$SHAPEFILEPATH"
+    FONTSET "$CONFIGDIR/mapserver/fontset.txt"
+    SYMBOLSET "$CONFIGDIR/mapserver/symbols.sym"
+    IMAGECOLOR 255 255 255
+    PROJECTION
+        "init=epsg:31466"
+    END
+
+    DEBUG 5
+    CONFIG "MS_ERRORFILE" "/tmp/flys-user-wms.log"
+
+    WEB
+      METADATA
+        "wms_title"             "FLYS Web Map Service"
+        "wms_onlineresource"    "$MAPSERVERURL"
+        "wms_accessconstraints" "none"
+        "wms_fees"              "none"
+        "wms_addresstype"       "postal"
+        "wms_address"           "Any Street"
+        "wms_city"              "Any City"
+        "wms_stateorprovince"   "Any state"
+        "wms_postcode"          "My Postalcode"
+        "wms_country"           "Any Country"
+        "wms_contactperson"     "Any Person"
+        "wms_contactorganization" "Any Orga"
+        "wms_contactelectronicmailaddress" "any-email@example.com"
+        "wms_contactvoicetelephone" "Any's telephone number"
+        "wms_srs" "EPSG:4326 EPSG:31466 EPSG:31467"
+        "wms_feature_info_mime_type" "text/html"
+        "ows_enable_request"   "*"
+      END
+    END
+
+    LEGEND
+        KEYSIZE 20 20
+        STATUS ON
+        TRANSPARENT ON
+
+        LABEL
+            COLOR 150 150 150
+            OUTLINECOLOR 255 255 255
+            TYPE truetype
+            FONT "FreeSans"
+            SIZE 12
+            POSITION AUTO
+        END
+    END
+
+    ## Don't change the following lines.
+    #foreach ($LAYER in $LAYERS)
+        include "$LAYER"
+    #end
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/shapefile_layer.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,38 @@
+LAYER
+    NAME      "$LAYER.getName()"
+    TYPE      $LAYER.getType()
+    DATA      "$LAYER.getDirectory()/$LAYER.getData()"
+    STATUS    ON
+    TEMPLATE  map.html
+    TOLERANCE 10
+    DUMP      TRUE
+    #if( $LAYER.getGroup() )
+        GROUP "$LAYER.getGroup()"
+    #end
+
+    PROJECTION
+        "init=epsg:$LAYER.getSrid()"
+    END
+
+    METADATA
+        "wms_title" "$LAYER.getTitle()"
+        "gml_include_items" "all"
+        #if ( $LAYER.getGroupTitle() )
+            "wms_group_title" "$LAYER.getGroupTitle()"
+        #end
+    END
+
+    #if ( !$LAYER.getStyle() )
+        #if ( $LAYER.getGroupTitle() )
+            #if ( $LAYER.getType() == "POLYGON" )
+                #include("barrier_polygons_class.vm")
+            #else
+                #include("barrier_lines_class.vm")
+            #end
+        #else
+            #include("wsplgen_class.vm")
+        #end
+    #else
+        $LAYER.getStyle()
+    #end
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/symbols.sym	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,22 @@
+SYMBOLSET
+SYMBOL
+  NAME 'point'
+  TYPE ELLIPSE
+  POINTS
+    1 1
+  END
+  FILLED TRUE
+END
+SYMBOL
+  NAME "square"
+  TYPE VECTOR
+  POINTS
+    0 0
+    0 1
+    1 1
+    1 0
+    0 0
+  END
+  FILLED TRUE
+END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/wsplgen_class.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,44 @@
+CLASS
+    NAME "0.0 <= DIFF < 1"
+    EXPRESSION ([DIFF] < 1)
+    STYLE
+        SIZE 5
+        COLOR "#B2C9D7"
+    END
+END
+
+CLASS
+    NAME "1.0 <= DIFF < 2"
+    EXPRESSION ([DIFF] >= 1 AND [DIFF] < 2)
+    STYLE
+        SIZE 5
+        COLOR "#6F93AA"
+    END
+END
+
+CLASS
+    NAME "2.0 <= DIFF < 3"
+    EXPRESSION ([DIFF] >= 2 AND [DIFF] < 3)
+    STYLE
+        SIZE 5
+        COLOR "#426F8B"
+    END
+END
+
+CLASS
+    NAME "3.0 <= DIFF < 4"
+    EXPRESSION ([DIFF] >= 3 AND [DIFF] < 4)
+    STYLE
+        SIZE 5
+        COLOR "#214F6C"
+    END
+END
+
+CLASS
+    NAME "Sonstiges"
+    EXPRESSION ([DIFF] >= 4)
+    STYLE
+        SIZE 5
+        COLOR "#021B2A"
+    END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/mapserver/wsplgen_layer.vm	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,40 @@
+LAYER
+    NAME      "$LAYER.getName()"
+    TYPE      $LAYER.getType()
+    DATA      "$LAYER.getDirectory()/$LAYER.getData()"
+    STATUS    ON
+    TEMPLATE  map.html
+    TOLERANCE 10
+    DUMP      TRUE
+    #if( $LAYER.getGroup() )
+        GROUP "$LAYER.getGroup()"
+    #end
+
+    PROJECTION
+        "init=epsg:$LAYER.getSrid()"
+    END
+
+    METADATA
+        "wms_title" "$LAYER.getTitle()"
+        "gml_include_items" "all"
+        "ows_enable_request" "GetFeatureInfo"
+        "wms_feature_info_mime_type" "gml"
+        #if ( $LAYER.getGroupTitle() )
+            "wms_group_title" "$LAYER.getGroupTitle()"
+        #end
+    END
+
+    #if ( !$LAYER.getStyle() )
+        #if ( $LAYER.getGroupTitle() )
+            #if ( $LAYER.getType() == "POLYGON" )
+                #include("barrier_polygons_class.vm")
+            #else
+                #include("barrier_lines_class.vm")
+            #end
+        #else
+            #include("wsplgen_class.vm")
+        #end
+    #else
+        $LAYER.getStyle()
+    #end
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/meta-data.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1489 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<dc:template xmlns:dc="http://www.intevation.org/2011/Datacage">
+<datacage>
+  <dc:macro name="load-system">
+    <dc:context connection="system">
+      <dc:statement>
+        SELECT id AS river_id, name as river_name FROM rivers
+        WHERE lower(name) LIKE lower(${river})
+      </dc:statement>
+      <dc:elements>
+        <dc:comment>
+          Base-data macros (mostly data imported from wst-files)
+        </dc:comment>
+        <dc:macro name="basedata_0">
+            <dc:comment comment=" BASEDATA ---------------------------"/>
+            <basedata>
+              <dc:context>
+                <dc:statement>
+                  SELECT id          AS prot_id,
+                         description AS prot_description
+                  FROM wsts WHERE kind = 0 AND river_id = ${river_id}
+                </dc:statement>
+                <dc:elements>
+                  <basedata>
+                    <dc:attribute name="name" value="${prot_description}"/>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id       AS prot_column_id,
+                               name     AS prot_column_name,
+                               position AS prot_rel_pos
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="ids" value="base_data-wstv-${prot_rel_pos}-${prot_id}"/>
+                          <dc:attribute name="factory" value="staticwkms"/>
+                        </column>
+                      </dc:elements>
+                    </dc:context>
+                  </basedata>
+                </dc:elements>
+              </dc:context>
+            </basedata>
+        </dc:macro>
+        <dc:macro name="basedata_0_wq">
+            <dc:comment comment=" BASEDATA ---------------------------"/>
+            <basedata>
+              <dc:context>
+                <dc:statement>
+                  SELECT id          AS prot_id,
+                         description AS prot_description
+                  FROM wsts WHERE kind = 0 AND river_id = ${river_id}
+                </dc:statement>
+                <dc:elements>
+                  <basedata>
+                    <dc:attribute name="name" value="${prot_description}"/>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id       AS prot_column_id,
+                               name     AS prot_column_name,
+                               position AS prot_rel_pos
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="ids" value="base_data-wstv-${prot_rel_pos}-${prot_id}"/>
+                          <dc:attribute name="factory" value="wqinterpol"/>
+                        </column>
+                      </dc:elements>
+                    </dc:context>
+                  </basedata>
+                </dc:elements>
+              </dc:context>
+            </basedata>
+        </dc:macro>
+
+        <dc:macro name="basedata_1_additionals">
+            <dc:comment comment=".ZUS -------------------------------"/>
+            <additionals>
+              <dc:context>
+                <dc:statement>
+                  SELECT id          AS prot_id,
+                         description AS prot_description
+                  FROM wsts WHERE kind = 1 AND river_id = ${river_id}
+                </dc:statement>
+                <dc:elements>
+                  <additional>
+                    <dc:attribute name="name" value="${prot_description}"/>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id       AS prot_column_id,
+                               name     AS prot_column_name,
+                               position AS prot_rel_pos
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="ids" value="additionals-wstv-${prot_rel_pos}-${prot_id}"/>
+                          <dc:attribute name="factory" value="staticwkms"/>
+                        </column>
+                      </dc:elements>
+                    </dc:context>
+                  </additional>
+                </dc:elements>
+              </dc:context>
+            </additionals>
+        </dc:macro>
+
+        <dc:macro name="basedata_1_additionals-relative_point">
+            <dc:comment comment=".ZUS -------------------------------"/>
+            <additionals>
+              <dc:context>
+                <dc:statement>
+                  SELECT id          AS prot_id,
+                         description AS prot_description
+                  FROM wsts WHERE kind = 1 AND river_id = ${river_id}
+                </dc:statement>
+                <dc:elements>
+                  <relativepoint>
+                    <dc:attribute name="name" value="${prot_description}"/>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id       AS prot_column_id,
+                               name     AS prot_column_name,
+                               position AS prot_rel_pos
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="ids" value="additionals-wstv-${prot_rel_pos}-${prot_id}"/>
+                          <dc:attribute name="factory" value="staticwkms"/>
+                        </column>
+                      </dc:elements>
+                    </dc:context>
+                  </relativepoint>
+                </dc:elements>
+              </dc:context>
+            </additionals>
+        </dc:macro>
+
+        <dc:macro name="basedata_2_fixations_wst">
+          <fixations>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 2 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <fixation>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <!--dc:attribute name="ids" value="fixations-wstv-A-${prot_id}"/-->
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="fixations-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="wqinterpol"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </fixation>
+              </dc:elements>
+            </dc:context>
+          </fixations>
+        </dc:macro>
+
+        <dc:macro name="basedata_2_fixations_wqkms">
+          <fixations>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 2 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <fixation>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="fixations-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="wqinterpol"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </fixation>
+              </dc:elements>
+            </dc:context>
+          </fixations>
+        </dc:macro>
+
+        <dc:macro name="basedata_2_fixations">
+          <fixations>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 2 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <fixation>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="fixations-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="staticwkms"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </fixation>
+              </dc:elements>
+            </dc:context>
+          </fixations>
+        </dc:macro>
+
+        <dc:macro name="basedata_2_fixations_relative_point">
+          <fixations>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 2 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <relativepoint>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="fixations-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="staticwkms"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </relativepoint>
+              </dc:elements>
+            </dc:context>
+          </fixations>
+        </dc:macro>
+
+        <dc:macro name="basedata_4_heightmarks-points-relative_points">
+          <heightmarks>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 4 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <relativepoint>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="heightmarks_points-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="staticwkms"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </relativepoint>
+              </dc:elements>
+            </dc:context>
+          </heightmarks>
+        </dc:macro>
+
+        <dc:macro name="basedata_4_heightmarks-points">
+          <heightmarks>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 4 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <heightmark>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="heightmarks_points-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="staticwkms"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </heightmark>
+              </dc:elements>
+            </dc:context>
+          </heightmarks>
+        </dc:macro>
+
+        <dc:macro name="basedata_4_heightmarks-wq">
+          <heightmarks>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 4 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <heightmark>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id       AS prot_column_id,
+                             name     AS prot_column_name,
+                             position AS prot_rel_pos
+                      FROM wst_columns WHERE wst_id = ${prot_id}
+                      ORDER by position
+                    </dc:statement>
+                    <dc:elements>
+                      <column>
+                        <dc:attribute name="name" value="${prot_column_name}"/>
+                        <dc:attribute name="ids" value="heightmarks_annotations-wstv-${prot_rel_pos}-${prot_id}"/>
+                        <dc:attribute name="factory" value="wqinterpol"/>
+                      </column>
+                    </dc:elements>
+                  </dc:context>
+                </heightmark>
+              </dc:elements>
+            </dc:context>
+          </heightmarks>
+        </dc:macro>
+
+        <dc:macro name="basedata_5_flood-protections_relative_points">
+          <flood_protections>
+            <dc:attribute name="id" value="flood-protections-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 5 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <relativepoint>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:attribute name="db-id" value="${prot_id}"/>
+                  <dc:attribute name="factory" value="staticwkms"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id       AS prot_column_id,
+                               name     AS prot_column_name,
+                               position AS prot_rel_pos
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="ids" value="flood_protection-wstv-${prot_rel_pos}-${prot_id}"/>
+                          <dc:attribute name="factory" value="staticwkms"/>
+                        </column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </relativepoint>
+              </dc:elements>
+            </dc:context>
+          </flood_protections>
+        </dc:macro>
+
+        <dc:macro name="basedata_5_flood-protections">
+          <flood_protections>
+            <dc:attribute name="id" value="flood-protections-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM wsts WHERE kind = 5 AND river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <flood_protection>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:attribute name="db-id" value="${prot_id}"/>
+                  <dc:attribute name="factory" value="staticwkms"/>
+                  <columns>
+                    <dc:context>
+                      <dc:statement>
+                        SELECT id       AS prot_column_id,
+                               name     AS prot_column_name,
+                               position AS prot_rel_pos
+                        FROM wst_columns WHERE wst_id = ${prot_id}
+                        ORDER by position
+                      </dc:statement>
+                      <dc:elements>
+                        <column>
+                          <dc:attribute name="name" value="${prot_column_name}"/>
+                          <dc:attribute name="ids" value="flood_protection-wstv-${prot_rel_pos}-${prot_id}"/>
+                          <dc:attribute name="factory" value="staticwkms"/>
+                        </column>
+                      </dc:elements>
+                    </dc:context>
+                  </columns>
+                </flood_protection>
+              </dc:elements>
+            </dc:context>
+          </flood_protections>
+        </dc:macro>
+
+        <dc:macro name="mainvalues">
+          <mainvalue>
+            <dc:attribute name="factory" value="mainvalue"/>
+            <dc:attribute name="ids"     value="${river_id}"/>
+          </mainvalue>
+        </dc:macro>
+
+        <dc:macro name="annotations">
+          <annotation>
+            <dc:attribute name="factory" value="annotations"/>
+            <dc:attribute name="ids"     value="${river_id}"/>
+          </annotation>
+        </dc:macro>
+
+        <dc:macro name="cross_sections">
+          <cross_sections>
+            <dc:attribute name="id" value="flood-protections-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS prot_id,
+                       description AS prot_description
+                FROM cross_sections WHERE river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <cross_section>
+                  <dc:attribute name="name" value="${prot_description}"/>
+                  <dc:attribute name="ids" value="${prot_id}"/>
+                  <dc:attribute name="factory" value="crosssections"/>
+                </cross_section>
+              </dc:elements>
+            </dc:context>
+          </cross_sections>
+        </dc:macro>
+
+        <dc:macro name="hyks">
+          <hyks>
+            <dc:attribute name="id" value="hyk-${river_id}"/>
+            <dc:context>
+              <dc:statement>
+                SELECT id          AS hyk_id,
+                       description AS hyk_description
+                FROM hyks WHERE river_id = ${river_id}
+              </dc:statement>
+              <dc:elements>
+                <hyk>
+                  <dc:attribute name="name" value="${hyk_description}"/>
+                  <dc:attribute name="ids" value="${hyk_id}"/>
+                  <dc:attribute name="factory" value="hyk"/>
+                </hyk>
+              </dc:elements>
+            </dc:context>
+          </hyks>
+        </dc:macro>
+
+        <dc:comment>
+
+        + River-Node
+
+        </dc:comment>
+
+        <river>
+          <dc:attribute name="name" value="${river_name}"/>
+
+          <dc:choose>
+            <dc:when test="dc:contains($parameters, 'recommended')">
+               <dc:comment>
+                  Recommendations (client shall load immediately).
+               </dc:comment>
+               <dc:if test="dc:contains($artifact-outs, 'w_differences') or (dc:contains($artifact-outs, 'discharge_longitudinal_section'))">
+                 <dc:call-macro name="annotations"/>
+               </dc:if>
+               <dc:if test="dc:contains($artifact-outs, 'cross_section')">
+                 <dc:call-macro name="cross_sections"/>
+                 <dc:call-macro name="hyks"/>
+               </dc:if>
+               <dc:if test="dc:contains($artifact-outs, 'reference_curve')">
+                 <dc:call-macro name="annotations"/>
+                 <dc:call-macro name="mainvalues"/>
+               </dc:if>
+            </dc:when>
+            <dc:otherwise>
+              <dc:comment>
+                 Non - Recommendations.
+              </dc:comment>
+              <dc:if test="dc:contains($artifact-outs, 'cross_section')">
+                 <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="cross_sections"/>
+                 <dc:call-macro name="hyks"/>
+              </dc:if>
+              <dc:if test="dc:contains($artifact-outs, 'discharge_longitudinal_section')">
+                 <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:if>
+              <dc:if test="dc:contains($artifact-outs, 'computed_discharge_curve')">
+                 <dc:call-macro name="basedata_0_wq"/>
+                 <dc:call-macro name="basedata_4_heightmarks-wq"/>
+              </dc:if>
+              <dc:if test="dc:contains($artifact-outs, 'longitudinal_section') or (dc:contains($artifact-outs, 'w_differences'))">
+                 <dc:call-macro name="basedata_0"/>
+                 <dc:call-macro name="basedata_1_additionals"/>
+                 <dc:comment comment=" FIXATIONS ---------------------------"/>
+                 <dc:call-macro name="basedata_2_fixations"/>
+                 <dc:comment comment=" HOEHENMARKEN ---------------------------"/>
+                 <dc:call-macro name="basedata_4_heightmarks-points"/>
+                 <dc:call-macro name="annotations"/>
+              </dc:if>
+               <dc:if test="dc:contains($artifact-outs, 'reference_curve')">
+                 <dc:call-macro name="annotations"/>
+                 <!--dc:call-macro name="basedata_0"/-->
+                 <dc:call-macro name="basedata_1_additionals-relative_point"/>
+                 <dc:comment comment=" FIXATIONS ---------------------------"/>
+                 <dc:call-macro name="basedata_2_fixations_relative_point"/>
+                 <dc:comment comment=" HOEHENMARKEN ---------------------------"/>
+                 <dc:call-macro name="basedata_4_heightmarks-points-relative_points"/>
+                 <dc:call-macro name="basedata_5_flood-protections_relative_points"/>
+               </dc:if>
+             <dc:comment comment="--- non-recommendations---"/>
+            </dc:otherwise>
+          </dc:choose>
+
+
+          <dc:if test="dc:contains($artifact-outs, 'waterlevels')">
+
+            <!-- base data -->
+            <dc:call-macro name="basedata_0"/>
+
+            <!-- extra-longitudinal-sections -->
+            <dc:call-macro name="basedata_1_additionals"/>
+
+            <!-- fixations -->
+            <dc:call-macro name="basedata_2_fixations"/>
+
+            <!-- flood water marks-->
+            <dc:call-macro name="basedata_4_heightmarks-points"/>
+
+            <!-- flood protection -->
+            <dc:call-macro name="basedata_5_flood-protections"/>
+
+          </dc:if>
+          <dc:if test="dc:contains($artifact-outs, 'computed_discharge_curve') and (dc:contains($parameters, 'recommended'))">
+                <!--dc:call-macro name="basedata_2_fixations_wst"/-->
+          </dc:if>
+
+          <dc:if test="dc:contains($artifact-outs, 'computed_discharge_curve') and not (dc:contains($parameters, 'recommended'))">
+              <discharge_table_nn>
+                <discharge_table_gauge>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id   AS gauge_id,
+                             name AS gauge_name
+                      FROM gauges WHERE river_id = ${river_id}
+                    </dc:statement>
+                    <dc:elements>
+                      <gauge>
+                        <dc:attribute name="name" value="${gauge_name}"/>
+                        <dc:attribute name="db-id" value="${gauge_id}"/>
+                        <dc:context>
+                          <dc:statement>
+                            SELECT description   AS gauge_desc, 
+                                   d.id          AS discharge_id,
+                                   ti.start_time AS g_start, 
+                                   ti.stop_time  AS g_stop
+                            FROM discharge_tables d JOIN time_intervals ti 
+                            ON d.time_interval_id = ti.id
+                            WHERE d.gauge_id = ${gauge_id} AND d.kind = 1
+                          </dc:statement>
+                          <dc:elements>
+                            <historical>
+                              <dc:attribute name="name" value="${gauge_desc}"/>
+                              <dc:attribute name="factory" value="gaugedischarge"/>
+                              <dc:attribute name="from" value="${g_start}"/>
+                              <dc:attribute name="to" value="${g_stop}"/>
+                              <dc:attribute name="ids" value="${discharge_id}-${g_start}-${g_stop}"/>
+                            </historical>
+                          </dc:elements>
+                        </dc:context>
+                      </gauge>
+                    </dc:elements>
+                  </dc:context>
+                </discharge_table_gauge>
+
+              </discharge_table_nn>
+
+            <dc:call-macro name="basedata_2_fixations_wst"/>
+
+            <dc:call-macro name="basedata_5_flood-protections"/>
+
+            <!-- former waterlevels -->
+            <dc:call-macro name="basedata_0"/>
+
+            <dc:call-macro name="basedata_1_additionals"/>
+
+            <!-- former flood-water-marks -->
+            <dc:call-macro name="basedata_4_heightmarks-points"/>
+          </dc:if>
+          <dc:if test="dc:contains($artifact-outs, 'computed_discharge_curve')"><!-- or (dc:contains($artifact-outs, 'discharge_curve'))"-->
+              <!-- && parameter contains recommended/ation -->
+              <computed_discharge_curve>
+                <dc:call-macro name="mainvalues"/>
+              </computed_discharge_curve>
+              <dc:call-macro name="basedata_2_fixations_wst"/>
+          </dc:if>
+
+          <dc:if test="dc:contains($artifact-outs, 'duration_curve')">
+            <dc:choose>
+                <dc:when test="dc:contains($parameters, 'recommended')">
+                  <dc:call-macro name="mainvalues"/>
+                </dc:when>
+                <dc:otherwise>
+                  <dc:call-macro name="mainvalues"/>
+                  <dc:call-macro name="basedata_2_fixations_relative_point"/>
+                  <dc:call-macro name="basedata_4_heightmarks-points-relative_points"/>
+                  <dc:call-macro name="basedata_5_flood-protections_relative_points"/>
+                </dc:otherwise>
+            </dc:choose>
+          </dc:if>
+
+          <dc:if test="dc:contains($artifact-outs, 'longitudinal_section')">
+              <longitudinal_section>
+                <dc:call-macro name="annotations"/>
+              </longitudinal_section>
+          </dc:if>
+          <dc:if test="dc:contains($artifact-outs, 'map')">
+              <map>
+              <dc:choose>
+                  <dc:when test="dc:contains($parameters, 'recommended')">
+                  </dc:when>
+                  <dc:otherwise>
+                      <dc:call-macro name="flood-map-complete"/>
+                  </dc:otherwise>
+              </dc:choose>
+              </map>
+          </dc:if>
+          <dc:if test="dc:contains($artifact-outs, 'floodmap')">
+              <floodmap>
+              <dc:choose>
+                  <dc:when test="dc:contains($parameters, 'recommended')">
+                    <dc:call-macro name="flood-map-recommended"/>
+                  </dc:when>
+                  <dc:when test="dc:contains($parameters, 'dem')">
+                    <dc:call-macro name="flood-map-dem"/>
+                  </dc:when>
+                  <dc:otherwise>
+                    <dc:call-macro name="flood-map-complete"/>
+                  </dc:otherwise>
+              </dc:choose>
+              </floodmap>
+              <dc:macro name="flood-map-recommended">
+                <dc:comment>
+                   FIXME: Following two macros look identical to me.
+                </dc:comment>
+                  <kilometrage>
+                      <riveraxis>
+                          <dc:attribute name="factory" value="riveraxis"/>
+                          <dc:attribute name="ids" value="${river_id}"/>
+                      </riveraxis>
+                  </kilometrage>
+                  <rastermap>
+                      <background>
+                          <dc:attribute name="factory" value="wmsbackground"/>
+                          <dc:attribute name="ids" value="${river_id}"/>
+                      </background>
+                  </rastermap>
+              </dc:macro>
+              <dc:macro name="flood-map-dem">
+                <dems>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT id    AS dem_id,
+                             lower AS dem_lower,
+                             upper AS dem_upper
+                      FROM dem WHERE river_id = ${river_id}
+                    </dc:statement>
+                    <dc:elements>
+                        <dem>
+                          <dc:attribute name="factory" value="demfactory"/>
+                          <dc:attribute name="ids" value="${dem_id}"/>
+                          <dc:attribute name="description" value="${dem_lower}-${dem_upper}"/>
+                        </dem>
+                    </dc:elements>
+                  </dc:context>
+                </dems>
+              </dc:macro>
+              <dc:macro name="flood-map-km">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists
+                    FROM river_axes_km WHERE river_id = ${river_id}
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <km>
+                        <dc:attribute name="factory" value="wmskmfactory"/>
+                        <dc:attribute name="ids" value="${river_id}"/>
+                      </km>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-qps">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists
+                    FROM cross_section_tracks WHERE river_id = ${river_id}
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <qps>
+                        <dc:attribute name="factory" value="wmsqpsfactory"/>
+                        <dc:attribute name="ids" value="${river_id}"/>
+                      </qps>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-hws">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists, name as name
+                    FROM hws WHERE river_id = ${river_id} GROUP BY name
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <hws>
+                        <dc:attribute name="factory" value="wmshwsfactory"/>
+                        <dc:attribute name="ids" value="${river_id};${name}"/>
+                        <dc:attribute name="name" value="${name}"/>
+                      </hws>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-hydr-boundaries">
+                <hydr_boundaries_lines>
+                  <dc:call-macro name="flood-map-hydr-boundaries-lines"/>
+                </hydr_boundaries_lines>
+                <hydr_boundaries_polygons>
+                  <dc:call-macro name="flood-map-hydr-boundaries-poly"/>
+                </hydr_boundaries_polygons>
+              </dc:macro>
+              <dc:macro name="flood-map-hydr-boundaries-lines">
+                <bfg>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT count(*) as km_exists, name as name
+                      FROM hydr_boundaries WHERE river_id = ${river_id} AND kind = 1 GROUP BY name
+                    </dc:statement>
+                     <dc:elements>
+                      <dc:if test="$km_exists>0">
+                        <hydrboundary>
+                          <dc:attribute name="factory" value="wmshydrboundariesfactory"/>
+                          <dc:attribute name="ids" value="${river_id};${name}"/>
+                          <dc:attribute name="name" value="${name}"/>
+                        </hydrboundary>
+                      </dc:if>
+                    </dc:elements>
+                  </dc:context>
+                </bfg>
+                <land>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT count(*) as km_exists, name as name
+                      FROM hydr_boundaries WHERE river_id = ${river_id} AND kind = 2 GROUP BY name
+                    </dc:statement>
+                     <dc:elements>
+                      <dc:if test="$km_exists>0">
+                        <hydrboundary>
+                          <dc:attribute name="factory" value="wmshydrboundariesfactory"/>
+                          <dc:attribute name="ids" value="${river_id};${name}"/>
+                          <dc:attribute name="name" value="${name}"/>
+                        </hydrboundary>
+                      </dc:if>
+                    </dc:elements>
+                  </dc:context>
+                </land>
+              </dc:macro>
+              <dc:macro name="flood-map-hydr-boundaries-poly">
+                <bfg>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT count(*) as km_exists, name as name
+                      FROM hydr_boundaries_poly WHERE river_id = ${river_id} AND kind = 1 GROUP BY name
+                    </dc:statement>
+                     <dc:elements>
+                      <dc:if test="$km_exists>0">
+                        <hws>
+                          <dc:attribute name="factory" value="wmshydrboundariespolyfactory"/>
+                          <dc:attribute name="ids" value="${river_id};${name}"/>
+                          <dc:attribute name="name" value="${name}"/>
+                        </hws>
+                      </dc:if>
+                    </dc:elements>
+                  </dc:context>
+                </bfg>
+                <land>
+                  <dc:context>
+                    <dc:statement>
+                      SELECT count(*) as km_exists, name as name
+                      FROM hydr_boundaries_poly WHERE river_id = ${river_id} AND kind = 2 GROUP BY name
+                    </dc:statement>
+                     <dc:elements>
+                      <dc:if test="$km_exists>0">
+                        <hws>
+                          <dc:attribute name="factory" value="wmshydrboundariespolyfactory"/>
+                          <dc:attribute name="ids" value="${river_id};${name}"/>
+                          <dc:attribute name="name" value="${name}"/>
+                        </hws>
+                      </dc:if>
+                    </dc:elements>
+                  </dc:context>
+                </land>
+              </dc:macro>
+              <dc:macro name="flood-map-catchments">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists, name as name
+                    FROM catchment WHERE river_id = ${river_id} GROUP BY name
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <catchment>
+                        <dc:attribute name="factory" value="wmscatchmentfactory"/>
+                        <dc:attribute name="ids" value="${river_id};${name}"/>
+                        <dc:attribute name="name" value="${name}"/>
+                      </catchment>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-floodplain">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists
+                    FROM floodplain WHERE river_id = ${river_id}
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <floodplain>
+                        <dc:attribute name="factory" value="wmsfloodplainfactory"/>
+                        <dc:attribute name="ids" value="${river_id}"/>
+                      </floodplain>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-lines">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists, name as name
+                    FROM lines WHERE river_id = ${river_id} GROUP BY name
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <line>
+                        <dc:attribute name="factory" value="wmslinefactory"/>
+                        <dc:attribute name="ids" value="${river_id};${name}"/>
+                        <dc:attribute name="name" value="${name}"/>
+                      </line>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-buildings">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists, name as name
+                    FROM buildings WHERE river_id = ${river_id} GROUP BY name
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <building>
+                        <dc:attribute name="factory" value="wmsbuildingsfactory"/>
+                        <dc:attribute name="ids" value="${river_id};${name}"/>
+                        <dc:attribute name="name" value="${name}"/>
+                      </building>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-fixpoints">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists, name as name
+                    FROM fixpoints WHERE river_id = ${river_id} GROUP BY name
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <fixpoint>
+                        <dc:attribute name="factory" value="wmsfixpointsfactory"/>
+                        <dc:attribute name="ids" value="${river_id};${name}"/>
+                        <dc:attribute name="name"  value="${name}"/>
+                      </fixpoint>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-gaugelocations">
+                <dc:context>
+                  <dc:statement>
+                    SELECT count(*) as km_exists, name as name
+                    FROM gauge_location WHERE river_id = ${river_id} GROUP BY name
+                  </dc:statement>
+                   <dc:elements>
+                    <dc:if test="$km_exists>0">
+                      <gaugelocation>
+                        <dc:attribute name="factory" value="wmsgaugelocationfactory"/>
+                        <dc:attribute name="ids" value="${river_id};${name}"/>
+                        <dc:attribute name="name"  value="${name}"/>
+                      </gaugelocation>
+                    </dc:if>
+                  </dc:elements>
+                </dc:context>
+              </dc:macro>
+              <dc:macro name="flood-map-uesk">
+                <uesk>
+                  <calculations>
+                    <current>
+                      <bfg>
+                        <dc:context>
+                          <dc:statement>
+                              SELECT count(*) as uesg_exist, name as name
+                              FROM floodmaps
+                              WHERE river_id = ${river_id} AND kind = 111
+                              GROUP BY name, kind
+                          </dc:statement>
+                          <dc:elements>
+                            <dc:if test="$uesg_exist>0">
+                              <floodmaps>
+                                <dc:attribute name="factory" value="wmsfloodmapsfactory"/>
+                                <dc:attribute name="ids" value="${river_id};${name}"/>
+                                <dc:attribute name="name" value="${name}"/>
+                              </floodmaps>
+                            </dc:if>
+                          </dc:elements>
+                        </dc:context>
+                      </bfg>
+                      <land>
+                        <dc:context>
+                          <dc:statement>
+                              SELECT count(*) as uesg_exist, name as name
+                              FROM floodmaps
+                              WHERE river_id = ${river_id} AND kind = 112
+                              GROUP BY name, kind
+                          </dc:statement>
+                          <dc:elements>
+                            <dc:if test="$uesg_exist>0">
+                              <floodmaps>
+                                <dc:attribute name="factory" value="wmsfloodmapsfactory"/>
+                                <dc:attribute name="ids" value="${river_id};${name}"/>
+                                <dc:attribute name="name" value="${name}"/>
+                              </floodmaps>
+                            </dc:if>
+                          </dc:elements>
+                        </dc:context>
+                      </land>
+                    </current>
+                    <potentiel>
+                      <bfg>
+                        <dc:context>
+                          <dc:statement>
+                              SELECT count(*) as uesg_exist, name as name
+                              FROM floodmaps
+                              WHERE river_id = ${river_id} AND kind = 121
+                              GROUP BY name, kind
+                          </dc:statement>
+                          <dc:elements>
+                            <dc:if test="$uesg_exist>0">
+                              <floodmaps>
+                                <dc:attribute name="factory" value="wmsfloodmapsfactory"/>
+                                <dc:attribute name="ids" value="${river_id};${name}"/>
+                                <dc:attribute name="name" value="${name}"/>
+                              </floodmaps>
+                            </dc:if>
+                          </dc:elements>
+                        </dc:context>
+                      </bfg>
+                      <land>
+                        <dc:context>
+                          <dc:statement>
+                              SELECT count(*) as uesg_exist, name as name
+                              FROM floodmaps
+                              WHERE river_id = ${river_id} AND kind = 122
+                              GROUP BY name, kind
+                          </dc:statement>
+                          <dc:elements>
+                            <dc:if test="$uesg_exist>0">
+                              <floodmaps>
+                                <dc:attribute name="factory" value="wmsfloodmapsfactory"/>
+                                <dc:attribute name="ids" value="${river_id};${name}"/>
+                                <dc:attribute name="name" value="${name}"/>
+                              </floodmaps>
+                            </dc:if>
+                          </dc:elements>
+                        </dc:context>
+                      </land>
+                    </potentiel>
+                  </calculations>
+                </uesk>
+              </dc:macro>
+              <dc:macro name="flood-map-complete">
+                  <buildings>
+                    <dc:call-macro name="flood-map-buildings"/>
+                  </buildings>
+                  <catchments>
+                    <dc:call-macro name="flood-map-catchments"/>
+                  </catchments>
+                  <fixpoints>
+                    <dc:call-macro name="flood-map-fixpoints"/>
+                  </fixpoints>
+                  <hws>
+                    <dc:call-macro name="flood-map-hws"/>
+                  </hws>
+                  <hydrboundaries>
+                    <dc:call-macro name="flood-map-hydr-boundaries"/>
+                    <dc:call-macro name="flood-map-floodplain"/>
+                  </hydrboundaries>
+                  <kilometrage>
+                      <riveraxis>
+                          <dc:attribute name="factory" value="riveraxis"/>
+                          <dc:attribute name="ids" value="${river_id}"/>
+                      </riveraxis>
+                    <dc:call-macro name="flood-map-km"/>
+                    <dc:call-macro name="flood-map-qps"/>
+                  </kilometrage>
+                  <lines>
+                    <dc:call-macro name="flood-map-lines"/>
+                  </lines>
+                  <dc:call-macro name="flood-map-uesk"/>
+                  <gaugelocations>
+                    <dc:call-macro name="flood-map-gaugelocations"/>
+                  </gaugelocations>
+                  <rastermap>
+                      <background>
+                          <dc:attribute name="factory" value="wmsbackground"/>
+                          <dc:attribute name="ids" value="${river_id}"/>
+                      </background>
+                  </rastermap>
+              </dc:macro>
+          </dc:if>
+        </river>
+      </dc:elements>
+    </dc:context>
+  </dc:macro>
+
+  <dc:choose>
+    <dc:comment>
+      User specific part
+      ------------------
+    </dc:comment>
+    <dc:when test="dc:contains($parameters, 'user-id')">
+
+
+     <old_calculations>
+      <dc:context connection="user">
+        <dc:comment>
+           Get the user and collection-id.
+        </dc:comment>
+        <dc:statement>
+            SELECT u.id AS user_id, c.id AS collection_id, c.name as collection_name
+            FROM collections c JOIN users u ON c.user_id = u.id
+            WHERE u.gid = CAST(${user-id} AS uuid)
+            ORDER BY c.creation DESC
+        </dc:statement>
+
+
+        <dc:comment>
+          SHOW W-DIFFERENCES
+        </dc:comment>
+
+        <dc:if test="dc:contains($artifact-outs, 'longitudinal_section') or (dc:contains($artifact-outs, 'w_differences') or (dc:contains($artifact-outs, 'discharge_longitudinal_section')))">
+          <differences>
+            <dc:elements>
+                <dc:context>
+                  <dc:statement>
+                    SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                    FROM   master_artifacts m
+                    WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                    AND EXISTS (
+                        SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                  </dc:statement>
+                  <dc:elements>
+                      <dc:context>
+                        <dc:statement>
+                          SELECT a.gid as aid, f.id AS fid, f.name AS facet_name, f.num AS facet_num, f.description as facet_description
+                          FROM outs as o, facets as f, artifacts as a
+                          WHERE f.name = 'w_differences' and f.out_id = o.id and o.artifact_id = ${a_id} and a.id = ${a_id}
+                        </dc:statement>
+                        <dc:elements>
+                          <dc:element name="${facet_name}">
+                            <dc:attribute name="description" value="${facet_description}"/>
+                            <dc:attribute name="factory"     value="winfo"/>
+                            <dc:attribute name="artifact-id" value="${aid}"/>
+                            <dc:attribute name="ids"         value="${aid}"/>
+                            <dc:attribute name="out"         value="w_differences"/>
+                          </dc:element>
+                        </dc:elements>
+                      </dc:context>
+                  </dc:elements>
+                </dc:context>
+            </dc:elements>
+          </differences>
+        </dc:if>
+
+        <dc:comment>
+          SHOW REFERENCE CURVE
+        </dc:comment>
+
+        <dc:if test="dc:contains($artifact-outs, 'reference_curve')">
+          <reference_curves>
+            <dc:elements>
+                <dc:context>
+                  <dc:statement>
+                    SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                    FROM   master_artifacts m
+                    WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                    AND EXISTS (
+                        SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                  </dc:statement>
+                  <dc:elements>
+                      <dc:context>
+                        <dc:statement>
+                          SELECT a.gid as aid, f.id AS fid, f.name AS facet_name, f.num AS facet_num, f.description as facet_description
+                          FROM outs as o, facets as f, artifacts as a
+                          WHERE f.name = 'reference_curve' and f.out_id = o.id and o.artifact_id = ${a_id} and a.id = ${a_id}
+                        </dc:statement>
+                        <dc:elements>
+                          <dc:element name="${facet_name}">
+                            <dc:attribute name="description" value="${facet_description}"/>
+                            <dc:attribute name="factory"     value="winfo"/>
+                            <dc:attribute name="artifact-id" value="${aid}"/>
+                            <dc:attribute name="ids"         value="${aid}"/>
+                            <dc:attribute name="out"         value="reference_curve"/>
+                          </dc:element>
+                        </dc:elements>
+                      </dc:context>
+                  </dc:elements>
+                </dc:context>
+            </dc:elements>
+          </reference_curves>
+        </dc:if>
+
+        <dc:comment>
+          SHOW COMPUTED DISCHARGE CURVES
+        </dc:comment>
+
+        <dc:if test="dc:contains($artifact-outs, 'computed_discharge_curve')">
+          <computed_discharge_curves>
+            <dc:elements>
+                <dc:context>
+                  <dc:statement>
+                    SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                    FROM   master_artifacts m
+                    WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                    AND EXISTS (
+                        SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                  </dc:statement>
+                  <dc:elements>
+                      <dc:context>
+                        <dc:statement>
+                          SELECT a.gid as aid, f.id AS fid, f.name AS facet_name, f.num AS facet_num, f.description as facet_description
+                          FROM outs as o, facets as f, artifacts as a
+                          WHERE f.name = 'computed_discharge_curve.q' and f.out_id = o.id and o.artifact_id = ${a_id} and a.id = ${a_id}
+                        </dc:statement>
+                        <dc:elements>
+                          <dc:element name="${facet_name}">
+                            <dc:attribute name="description" value="${facet_description}"/>
+                            <dc:attribute name="factory"     value="winfo"/>
+                            <dc:attribute name="artifact-id" value="${aid}"/>
+                            <dc:attribute name="ids"         value="${aid}"/>
+                            <dc:attribute name="out"         value="computed_discharge_curve"/>
+                          </dc:element>
+                        </dc:elements>
+                      </dc:context>
+                  </dc:elements>
+                </dc:context>
+            </dc:elements>
+          </computed_discharge_curves>
+        </dc:if>
+        
+        <dc:comment>
+          CROSS SECTION
+        </dc:comment>
+
+        <dc:if test="dc:contains($artifact-outs, 'cross_section')">
+          <waterlevels>
+            <dc:elements>
+              <dc:context>
+                 <dc:statement>
+                   SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                   FROM   master_artifacts m
+                   WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                   AND EXISTS (
+                       SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                 </dc:statement>
+                 <dc:elements>
+                   <dc:context>
+                     <dc:statement>
+                       SELECT id AS out_id
+                       FROM outs
+                       WHERE artifact_id = ${a_id} AND name = 'cross_section'
+                     </dc:statement>
+                     <dc:elements>
+                       <dc:context>
+                         <dc:statement>
+                           SELECT name AS facet_name, num as facet_num, description AS facet_description
+                           FROM facets
+                           WHERE out_id = ${out_id}
+                           ORDER BY num ASC, name DESC
+                         </dc:statement>
+                         <longitudinal_section_columns>
+                             <dc:attribute name="description" value="${river} ${a_creation}"/>
+                             <dc:elements>
+                               <dc:element name="${facet_name}">
+                                 <dc:attribute name="description" value="${facet_description}"/>
+                                 <dc:attribute name="ids" value="${facet_num}"/>
+                                 <dc:attribute name="factory" value="winfo"/>
+                                 <dc:attribute name="artifact-id" value="${a_gid}"/>
+                                 <dc:attribute name="out" value="cross_section"/>
+                               </dc:element>
+                             </dc:elements>
+                         </longitudinal_section_columns>
+                       </dc:context>
+                     </dc:elements>
+                   </dc:context>
+                 </dc:elements>
+               </dc:context>
+             </dc:elements>
+          </waterlevels>
+        </dc:if>
+
+
+        <dc:if test="dc:contains($artifact-outs, 'longitudinal_section') or (dc:contains($artifact-outs, 'discharge_longitudinal_section') or (dc:contains($artifact-outs, 'w_differences')))">
+          <waterlevels>
+            <dc:elements>
+              <dc:context>
+                 <dc:statement>
+                   SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                   FROM   master_artifacts m
+                   WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                   AND EXISTS (
+                       SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                 </dc:statement>
+                 <dc:elements>
+                   <dc:context>
+                     <dc:statement>
+                       SELECT id AS out_id
+                       FROM outs
+                       WHERE artifact_id = ${a_id} AND name = 'longitudinal_section'
+                     </dc:statement>
+                     <dc:elements>
+                       <dc:context>
+                         <dc:statement>
+                           SELECT name AS facet_name, num as facet_num, description AS facet_description
+                           FROM facets
+                           WHERE out_id = ${out_id}
+                           ORDER BY num ASC, name DESC
+                         </dc:statement>
+                         <longitudinal_section_columns>
+                             <dc:attribute name="description" value="${river} ${a_creation}"/>
+                             <dc:elements>
+                               <dc:element name="${facet_name}">
+                                 <dc:attribute name="description" value="${facet_description}"/>
+                                 <dc:attribute name="ids" value="${facet_num}"/>
+                                 <dc:attribute name="factory" value="winfo"/>
+                                 <dc:attribute name="artifact-id" value="${a_gid}"/>
+                                 <dc:attribute name="out" value="longitudinal_section"/>
+                               </dc:element>
+                             </dc:elements>
+                         </longitudinal_section_columns>
+                       </dc:context>
+                     </dc:elements>
+                   </dc:context>
+                 </dc:elements>
+               </dc:context>
+             </dc:elements>
+          </waterlevels>
+        </dc:if>
+
+        <dc:if test="dc:contains($artifact-outs, 'duration_curve')">
+          <computed_discharge_curves>
+            <dc:elements>
+                <dc:context>
+                  <dc:statement>
+                    SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                    FROM   master_artifacts m
+                    WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                    AND EXISTS (
+                        SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                  </dc:statement>
+                  <dc:elements>
+                      <dc:context>
+                        <dc:statement>
+                          SELECT a.gid as aid, f.id AS fid, f.name AS facet_name, f.num AS facet_num, f.description as facet_description
+                          FROM outs as o, facets as f, artifacts as a
+                          WHERE (f.name = 'duration_curve.q' or f.name = 'duration_curve.w') and f.out_id = o.id and o.artifact_id = ${a_id} and a.id = ${a_id}
+                        </dc:statement>
+                        <dc:elements>
+                          <dc:element name="${facet_name}">
+                            <dc:attribute name="description" value="${facet_description}"/>
+                            <dc:attribute name="factory"     value="winfo"/>
+                            <dc:attribute name="artifact-id" value="${aid}"/>
+                            <dc:attribute name="ids"         value="${aid}"/>
+                            <dc:attribute name="out"         value="duration_curve"/>
+                          </dc:element>
+                        </dc:elements>
+                      </dc:context>
+                  </dc:elements>
+                </dc:context>
+            </dc:elements>
+          </computed_discharge_curves>
+        </dc:if>
+        <dc:comment>
+           WATERLEVELS - ONLY SHOW Ws
+        </dc:comment>
+
+        <dc:if test="dc:contains($artifact-outs, 'waterlevels')">
+          <waterlevels>
+            <dc:elements>
+              <dc:context>
+                 <dc:statement>
+                   SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                   FROM   master_artifacts m
+                   WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                   AND EXISTS (
+                       SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                 </dc:statement>
+                 <dc:elements>
+
+                   <dc:context>
+                   <dc:statement>
+                     SELECT id AS out_id
+                     FROM outs
+                     WHERE artifact_id = ${a_id} AND name = 'longitudinal_section'
+                   </dc:statement>
+                   <dc:elements>
+                     <dc:context>
+                       <dc:statement>
+                         SELECT name AS facet_name, num as facet_num, description AS facet_description
+                         FROM facets
+                         WHERE out_id = ${out_id} and name = 'longitudinal_section.w'
+                         ORDER BY num ASC, name DESC
+                       </dc:statement>
+                       <waterlevels>
+                         <dc:attribute name="description" value="${river} ${a_creation} ${collection_name}"/>
+                         <dc:elements>
+                           <dc:element name="${facet_name}">
+                             <dc:attribute name="description" value="${facet_description}"/>
+                             <dc:attribute name="ids"         value="${facet_num}"/>
+                             <dc:attribute name="factory"     value="winfo"/>
+                             <dc:attribute name="artifact-id" value="${a_gid}"/>
+                             <dc:attribute name="out"         value="longitudinal_section"/>
+                           </dc:element>
+                         </dc:elements>
+                       </waterlevels>
+                     </dc:context>
+                   </dc:elements>
+                  </dc:context>
+                 </dc:elements>
+                </dc:context>
+             </dc:elements>
+          </waterlevels>
+        </dc:if>
+
+        <dc:comment>
+          SHOW FLOODMAPS
+        </dc:comment>
+
+        <dc:if test="dc:contains($artifact-outs, 'floodmap') or dc:contains($artifact-outs, 'map')">
+          <floodmap>
+            <dc:elements>
+                <dc:context>
+                  <dc:statement>
+                    SELECT m.id AS a_id, m.state AS a_state, m.gid AS a_gid, m.creation AS a_creation
+                    FROM   master_artifacts m
+                    WHERE  m.collection_id = ${collection_id} AND m.gid &lt;&gt; CAST(${artifact-id} AS uuid)
+                    AND EXISTS (
+                        SELECT id FROM artifact_data ad WHERE ad.artifact_id = m.id AND k = 'river' AND v = ${river})
+                  </dc:statement>
+                  <dc:elements>
+                      <dc:context>
+                        <dc:statement>
+                          SELECT a.gid as aid, f.id AS fid, f.name AS facet_name, f.num AS facet_num, f.description as facet_description
+                          FROM outs as o, facets as f, artifacts as a
+                          WHERE f.name = 'floodmap.wsplgen' and f.out_id = o.id and o.artifact_id = ${a_id} and a.id = ${a_id}
+                        </dc:statement>
+                        <dc:elements>
+                          <dc:element name="${facet_name}">
+                            <dc:attribute name="description" value="${facet_description}"/>
+                            <dc:attribute name="factory"     value="winfo"/>
+                            <dc:attribute name="artifact-id" value="${aid}"/>
+                            <dc:attribute name="ids"         value="${aid}"/>
+                            <dc:attribute name="out"         value="floodmap"/>
+                          </dc:element>
+                        </dc:elements>
+                      </dc:context>
+                  </dc:elements>
+                </dc:context>
+            </dc:elements>
+          </floodmap>
+        </dc:if>
+
+      </dc:context>
+      </old_calculations>
+
+
+      <dc:comment>
+        Include System specific part when 'load-system' is in parameters.
+        -----------------------------------------------------------------
+      </dc:comment>
+      <dc:choose>
+        <dc:when test="dc:contains($parameters,'load-system')">
+          <dc:call-macro name="load-system"/>
+        </dc:when>
+      </dc:choose>
+    </dc:when>
+
+
+    <dc:comment>
+      Include System specific part only if no user ID is given.
+      ---------------------------------------------------------
+    </dc:comment>
+    <dc:otherwise>
+        <dc:call-macro name="load-system"/>
+    </dc:otherwise>
+  </dc:choose>
+</datacage>
+</dc:template>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/conf/themes.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,2123 @@
+<themes>
+
+    <!-- Concrete themes are following now! -->
+    <themegroup name="default">
+        <theme name="DischargeCurve">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+
+        <!-- concrete theme for historical discharge curves -->
+        <theme name="HistoricalDischargeCurveQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="HistoricalDischargeCurveQDiff">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+
+        <!--
+            Discharge Longitudinal Section
+        -->
+        <theme name="LongitudinalSectionW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ2">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ5">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ10">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ20">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ25">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 51, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ50">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ100">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ200">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ500">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1000">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQRZ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HSQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="253, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MHQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 255, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MNQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_NQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 204, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQExtrem">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionPoints">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="showlines"  type="boolean" display="Linie anzeigen"  default="false"/>
+                <field name="showpoints" type="boolean" display="Punkte anzeigen" default="true"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ2_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ5_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ10_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ20_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ25_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 51, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ50_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 153, 153"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ100_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ200_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ500_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1000_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQRZ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HSQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="253, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MHQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 255, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MNQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_NQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 204, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQExtrem_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <!--
+          Longitudinal Section Q's
+        -->
+
+        <theme name="LongitudinalSectionQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ1">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ2">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ5">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ10">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ20">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ25">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 51, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ50">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 153, 153"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ100">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ200">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ500">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ1000">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQRZ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HSQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="253, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_MHQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 255, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_MNQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_MQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_NQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 204, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQExtrem">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+
+        <!--
+            Computed Discharge Curves
+        -->
+        <theme name="ComputedDischargeCurve">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="ComputedDischargeCurveQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Farbe" default="200, 0, 15"/>
+                <field name="textcolor" type="Color" display="Farbe" default="200, 0, 15"/>
+            </fields>
+        </theme>
+
+        <theme name="ComputedDischargeCurveW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Farbe" default="0, 215, 0"/>
+                <field name="textcolor" type="Color" display="Farbe" default="0, 215, 0"/>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="true"/>
+            </fields>
+        </theme>
+
+        <!--
+            Cross Sections
+        -->
+        <theme name="CrossSection">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,0,0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="CrossSectionWaterLine">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,0,153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+                <field name="showwidth" type="boolean" display="Breite anzeigen" default="false"/>
+                <field name="showlevel" type="boolean" display="Wasserstand anzeigen" default="false"/>
+                <field name="showmiddleheight" type="boolean" display="Wasserstand anzeigen" default="false"/>
+            </fields>
+        </theme>
+
+        <theme name="Hyk">
+            <inherits>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="true"/>
+            </fields>
+        </theme>
+
+        <!--
+            Relative Points
+        -->
+        <theme name="RelativePoint">
+            <inherits>
+              <inherit from="LongitudinalSectionPoints"/></inherits>
+            <!--fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields-->
+        </theme>
+
+        <!--
+            Duration Curves
+        -->
+        <theme name="DurationCurveW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,51,204"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="DurationCurveQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,204,0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <!-- Differences -->
+        <theme name="Differences">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <!-- Reference Curves -->
+        <theme name="ReferenceCurveNormalized">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+           <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <theme name="ReferenceCurve">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+           <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <!-- General -->
+        <theme name="WKms">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+           <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <theme name="WQKms">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <theme name="WQPoints">
+          <inherits><inherit from="Points"/></inherits>
+          <fields>
+            <field name="showlines"  type="boolean" display="Linie anzeigen"  default="false"/>
+            <field name="showpoints" type="boolean" display="Punkte anzeigen" default="true"/>
+            <field name="linecolor" type="Color" display="Linienfarbe" default="204, 0, 0"/>
+          </fields>
+        </theme>
+
+        <!--
+            Discharge Longitudinal Section
+        -->
+        <theme name="DischargeLongitudinalSectionW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+
+        <theme name="DischargeLongitudinalSectionC">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0 , 0"/>
+            </fields>
+        </theme>
+
+        <theme name="DischargeLongitudinalSectionQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+
+        <!-- Annotations -->
+        <theme name="Annotations">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="Text"/>
+                <inherit from="AnnotationText"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <!-- Manual Points -->
+        <theme name="ManualPoints">
+            <inherits>
+                <inherit from="Points"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="250, 0, 0"/>
+                <field name="font"      type="Font"  display="Schriftart"      default="arial"/>
+                <field name="textcolor" type="Color" display="Schriftfarbe"    default="0, 0, 0"/>
+                <field name="textsize"  type="int"   display="Schriftgröße" default="10"/>
+                <field name="textstyle" type="Style" display="Schriftstil"     default="standard"/>
+                <field name="pointsize"  type="int"     display="Punktdicke"           default="3"     hints="h"/>
+                <field name="backgroundcolor"  type="Color"   display="Texthintergrund"      default="255, 255, 255"/>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="true"/>
+                <field name="showbackground"   type="boolean" display="Hintergrund anzeigen" default="false"/>
+            </fields>
+        </theme>
+
+        <!-- Height Marks -->
+        <theme name="heightmarks_points">
+            <inherits>
+                <inherit from="Points"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <!-- Areas -->
+        <theme name="Area">
+            <inherits>
+                <inherit from="Areas"/>
+            </inherits>
+        </theme>
+
+        <!-- Map -->
+        <theme name="Floodmaps">
+            <inherits>
+                <inherit from="Floodmap"/>
+            </inherits>
+        </theme>
+
+        <theme name="Floodplains">
+            <inherits>
+                <inherit from="Floodplain"/>
+            </inherits>
+        </theme>
+
+        <theme name="WSPLGEN">
+            <inherits>
+                <inherit from="WSPLGENS"/>
+            </inherits>
+        </theme>
+
+        <theme name="RiverAxis">
+            <inherits>
+                <inherit from="RiverAxes"/>
+            </inherits>
+        </theme>
+
+        <theme name="Kms">
+            <inherits>
+                <inherit from="Km"/>
+            </inherits>
+        </theme>
+
+
+        <theme name="Fixpoints">
+            <inherits>
+                <inherit from="Fixpoint"/>
+            </inherits>
+        </theme>
+
+
+        <theme name="GaugeLocation">
+            <inherits>
+                <inherit from="GaugeLocations"/>
+            </inherits>
+        </theme>
+
+
+        <theme name="Qps">
+            <inherits>
+                <inherit from="Qp"/>
+            </inherits>
+        </theme>
+
+        <theme name="Hws">
+            <inherits>
+                <inherit from="Hw"/>
+            </inherits>
+        </theme>
+
+        <theme name="Catchment">
+            <inherits>
+                <inherit from="Catchments"/>
+            </inherits>
+        </theme>
+
+        <theme name="FloodmapLines">
+            <inherits>
+                <inherit from="FloodmapLine"/>
+            </inherits>
+        </theme>
+
+        <theme name="Buildings">
+            <inherits>
+                <inherit from="Building"/>
+            </inherits>
+        </theme>
+
+        <theme name="HydrBoundariesLines">
+            <inherits>
+                <inherit from="HydrBoundariesLine"/>
+            </inherits>
+        </theme>
+
+        <theme name="HydrBoundariesPolys">
+            <inherits>
+                <inherit from="HydrBoundariesPoly"/>
+            </inherits>
+        </theme>
+
+    </themegroup>
+
+    <themegroup name="default-2">
+        <theme name="DischargeCurve">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+
+        <!-- concrete theme for historical discharge curves -->
+        <theme name="HistoricalDischargeCurveQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="HistoricalDischargeCurveQDiff">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 0, 204"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+
+        <!--
+            Discharge Longitudinal Section
+        -->
+        <theme name="LongitudinalSectionW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 0, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 102, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ2">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 102, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ5">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ10">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ20">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ25">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 102, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ50">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 153"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ100">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ200">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ500">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 102, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1000">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQRZ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HSQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="253, 153, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MHQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 255, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MNQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_NQ">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQExtrem">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionPoints">
+            <inherits>
+                <inherit from="LongitudinalSectionW"/>
+            </inherits>
+            <fields>
+                <field name="showlines"  type="boolean" display="Linie anzeigen"  default="false"/>
+                <field name="showpoints" type="boolean" display="Punkte anzeigen" default="true"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ2_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 1"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ5_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ10_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ20_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ25_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 51, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ50_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 153, 153"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ100_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ200_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ500_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQ1000_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQRZ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HSQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="253, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MHQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 255, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MNQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_MQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_NQ_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 204, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionW_HQExtrem_Points">
+            <inherits><inherit from="LongitudinalSectionPoints"/></inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <!--
+          Longitudinal Section Q's
+        -->
+
+        <theme name="LongitudinalSectionQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ1">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ2">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ5">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 153, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ10">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ20">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ25">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 51, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ50">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 153, 153"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ100">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 51"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ200">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ500">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQ1000">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQRZ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HSQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="253, 153, 0"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_MHQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="102, 255, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_MNQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 255, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_MQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 51, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_NQ">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="153, 204, 255"/>
+            </fields>
+        </theme>
+
+        <theme name="LongitudinalSectionQ_HQExtrem">
+            <inherits>
+                <inherit from="LongitudinalSectionQ"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+
+        <!--
+            Computed Discharge Curves
+        -->
+        <theme name="ComputedDischargeCurve">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="ComputedDischargeCurveQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Farbe" default="200, 0, 15"/>
+                <field name="textcolor" type="Color" display="Farbe" default="200, 0, 15"/>
+            </fields>
+        </theme>
+
+        <theme name="ComputedDischargeCurveW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Farbe" default="0, 215, 0"/>
+                <field name="textcolor" type="Color" display="Farbe" default="0, 215, 0"/>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="true"/>
+            </fields>
+        </theme>
+
+        <!--
+            Cross Sections
+        -->
+        <theme name="CrossSection">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,0,0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="CrossSectionWaterLine">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,0,153"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+                <field name="showwidth" type="boolean" display="Breite anzeigen" default="false"/>
+                <field name="showlevel" type="boolean" display="Wasserstand anzeigen" default="false"/>
+                <field name="showmiddleheight" type="boolean" display="Wasserstand anzeigen" default="false"/>
+            </fields>
+        </theme>
+
+        <theme name="Hyk">
+            <inherits>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="true"/>
+            </fields>
+        </theme>
+
+        <!--
+            Duration Curves
+        -->
+        <theme name="DurationCurveW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,51,204"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="DurationCurveQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0,204,0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="2" hints="h"/>
+            </fields>
+        </theme>
+
+        <!-- Differences -->
+        <theme name="Differences">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <!-- Reference Curves -->
+        <theme name="ReferenceCurveNormalized">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+           <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <theme name="ReferenceCurve">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+           <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <!-- General -->
+        <theme name="WKms">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+           <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <theme name="WQKms">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+             <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+           </fields>
+        </theme>
+
+        <theme name="WQPoints">
+          <inherits><inherit from="Points"/></inherits>
+          <fields>
+            <field name="showlines"  type="boolean" display="Linie anzeigen"  default="false"/>
+            <field name="showpoints" type="boolean" display="Punkte anzeigen" default="true"/>
+            <field name="linecolor" type="Color" display="Linienfarbe" default="204, 0, 0"/>
+          </fields>
+        </theme>
+
+        <!--
+            Discharge Longitudinal Section
+        -->
+        <theme name="DischargeLongitudinalSectionW">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+
+        <theme name="DischargeLongitudinalSectionC">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0 , 0"/>
+            </fields>
+        </theme>
+
+        <theme name="DischargeLongitudinalSectionQ">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+
+        <!-- Annotations -->
+        <theme name="Annotations">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="Text"/>
+                <inherit from="AnnotationText"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <!-- Manual Points -->
+        <theme name="ManualPoints">
+            <inherits>
+                <inherit from="Points"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="250, 0, 0"/>
+                <field name="font"      type="Font"  display="Schriftart"      default="arial"/>
+                <field name="textcolor" type="Color" display="Schriftfarbe"    default="0, 0, 0"/>
+                <field name="textsize"  type="int"   display="Schriftgröße" default="10"/>
+                <field name="textstyle" type="Style" display="Schriftstil"     default="standard"/>
+                <field name="pointsize"  type="int"     display="Punktdicke"           default="3"     hints="h"/>
+                <field name="backgroundcolor"  type="Color"   display="Texthintergrund"      default="255, 255, 255"/>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="true"/>
+                <field name="showbackground"   type="boolean" display="Hintergrund anzeigen" default="false"/>
+            </fields>
+        </theme>
+
+        <!-- Height Marks -->
+        <theme name="heightmarks_points">
+            <inherits>
+                <inherit from="Points"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+            </fields>
+        </theme>
+
+        <!-- Areas -->
+        <theme name="Area">
+            <inherits>
+                <inherit from="Areas"/>
+            </inherits>
+        </theme>
+
+        <!-- Map -->
+        <theme name="Floodmaps">
+            <inherits>
+                <inherit from="Floodmap"/>
+            </inherits>
+        </theme>
+
+        <theme name="Floodplains">
+            <inherits>
+                <inherit from="Floodplain"/>
+            </inherits>
+        </theme>
+
+        <theme name="WSPLGEN">
+            <inherits>
+                <inherit from="WSPLGENS"/>
+            </inherits>
+        </theme>
+
+        <theme name="RiverAxis">
+            <inherits>
+                <inherit from="RiverAxes"/>
+            </inherits>
+        </theme>
+
+        <theme name="Kms">
+            <inherits>
+                <inherit from="Km"/>
+            </inherits>
+        </theme>
+
+
+        <theme name="Fixpoints">
+            <inherits>
+                <inherit from="Fixpoint"/>
+            </inherits>
+        </theme>
+
+        <theme name="GaugeLocation">
+            <inherits>
+                <inherit from="GaugeLocations"/>
+            </inherits>
+        </theme>
+
+        <theme name="Qps">
+            <inherits>
+                <inherit from="Qp"/>
+            </inherits>
+        </theme>
+
+        <theme name="Hws">
+            <inherits>
+                <inherit from="Hw"/>
+            </inherits>
+        </theme>
+
+        <theme name="Catchment">
+            <inherits>
+                <inherit from="Catchments"/>
+            </inherits>
+        </theme>
+
+        <theme name="FloodmapLines">
+            <inherits>
+                <inherit from="FloodmapLine"/>
+            </inherits>
+        </theme>
+
+        <theme name="Buildings">
+            <inherits>
+                <inherit from="Building"/>
+            </inherits>
+        </theme>
+
+        <theme name="HydrBoundariesLines">
+            <inherits>
+                <inherit from="HydrBoundariesLine"/>
+            </inherits>
+        </theme>
+
+        <theme name="HydrBoundariesPolys">
+            <inherits>
+                <inherit from="HydrBoundariesPoly"/>
+            </inherits>
+        </theme>
+
+    </themegroup>
+
+    <!-- Virtual themes are following now!
+         DO NOT USE VIRTUAL THEMES FOR FACETS!
+         Please implement concrete themes! -->
+    <themegroup name="virtual">
+        <theme name="Lines" type="virtual">
+            <fields>
+                <field name="showlines" type="boolean" display="Linie anzeigen" default="true"/>
+                <field name="linesize"  type="int"     display="Liniendicke"    default="1"/>
+                <field name="linetype"  type="Dash"    display="Linienart"      default="10"/>
+                <field name="showlinelabel" type="boolean" display="Beschriftung anzeigen"       default="false" hints="h"/>
+                <field name="linelabelfont"      type="Font"  display="Beschriftung: Schriftart" default="arial"/>
+                <field name="linelabelcolor" type="Color" display="Beschriftung: Schriftfarbe" default="0, 0, 0"/>
+                <field name="linelabelsize"  type="int"   display="Beschriftung: Schriftgröße" default="10"/>
+                <field name="linelabelstyle" type="Style" display="Beschriftung: Schriftstil"  default="standard"/>
+                <field name="linelabelbgcolor" type="Color" display="Beschriftung: Hintergrundfarbe"    default="0, 0, 0"/>
+                <field name="linelabelshowbg" type="boolean" display="Beschriftung: Hintergrund füllen" default="false"/>
+                <!--orientation? -->
+            </fields>
+        </theme>
+
+        <theme name="Points" type="virtual">
+            <fields>
+                <field name="showlines"  type="boolean" display="Linie anzeigen"  default="false"/>
+                <field name="linesize"   type="int"     display="Liniendicke"     default="1"/>
+                <field name="linetype"   type="Dash"    display="Linienart"       default="10"/>
+                <field name="showpoints" type="boolean" display="Punkte anzeigen" default="true"/>
+            </fields>
+        </theme>
+
+        <theme name="ColorLines" type="virtual">
+            <inherits>
+                <inherit from="Lines"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="Color.BLACK"/>
+            </fields>
+        </theme>
+
+        <theme name="HiddenColorLines" type="virtual">
+            <inherits>
+                <inherit from="ColorLines"/>
+            </inherits>
+            <fields>
+                <field name="showlines"  type="boolean" display="Linie anzeigen"       default="true"        hints="h"/>
+                <field name="linesize"   type="int"     display="Liniendicke"          default="1"           hints="h"/>
+                <field name="linetype"   type="Dash"    display="Linienart"            default="10"          hints="h"/>
+                <field name="showpoints" type="boolean" display="Datenpunkte anzeigen" default="false" hints="h"/>
+                <field name="pointsize"  type="int"     display="Punktdicke"           default="5"     hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="MinMaxPoints" type="virtual">
+            <fields>
+                <field name="showminimum" type="boolean" display="Minimum anzeigen"    default="false" hints="h"/>
+                <field name="showmaximum" type="boolean" display="Minimum anzeigen"    default="false" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="Text" type="virtual">
+            <fields>
+                <field name="font"      type="Font"  display="Schriftart"      default="arial"/>
+                <field name="textcolor" type="Color" display="Schriftfarbe"    default="0, 0, 0"/>
+                <field name="textsize"  type="int"   display="Schriftgröße" default="10"/>
+                <field name="textstyle" type="Style" display="Schriftstil"     default="standard"/>
+            </fields>
+        </theme>
+
+        <theme name="AnnotationText" type="virtual">
+            <fields>
+                <field name="backgroundcolor"  type="Color"   display="Texthintergrund"      default="255, 255, 255"/>
+                <field name="textorientation"  type="boolean" display="Textausrichtung"      default="false"/>
+                <field name="showbackground"   type="boolean" display="Hintergrund anzeigen" default="false"/>
+            </fields>
+        </theme>
+
+        <!-- Area relevant theme(s) -->
+        <theme name="Areas">
+            <inherits>
+                <inherit from="Text"/>
+            </inherits>
+            <fields>
+                <field name="backgroundcolor"  type="Color"   display="Texthintergrund"      default="255, 255, 255"/>
+                <field name="showbackground"   type="boolean" display="Hintergrund anzeigen" default="false"/>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="Color.BLACK"/>
+                <field name="showlines" type="boolean" display="Linie anzeigen" default="true"/>
+                <field name="linesize"  type="int"     display="Liniendicke"    default="1"/>
+                <field name="linetype"  type="Dash"    display="Linienart"      default="10"/>
+                <field name="fillcolor" type="Color" display="Fuellfarbe" default="0, 100, 0"/>
+                <field name="showarea" type="boolean" display="Flaeche beschriften" default="false"/>
+                <field name="showborder" type="boolean" display="Flaechebegrenzungslinie anzeigen" default="false"/>
+                <field name="transparent" type="boolean" display="Transparenz" default="false"/>
+            </fields>
+        </theme>
+
+
+        <!-- MAP relevant themes -->
+        <theme name="WSPLGENS">
+            <fields>
+                <field name="wsplgen_cat1" type="Color" display="Hintergrund" default="178, 201, 215"/>
+                <field name="wsplgen_cat2" type="Color" display="Hintergrund" default="111, 147, 170"/>
+                <field name="wsplgen_cat3" type="Color" display="Hintergrund" default="66, 111, 139"/>
+                <field name="wsplgen_cat4" type="Color" display="Hintergrund" default="33, 79, 108"/>
+                <field name="wsplgen_cat5" type="Color" display="Hintergrund" default="2, 27, 42"/>
+            </fields>
+        </theme>
+
+        <theme name="RiverAxes">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 205"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="3" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="Km">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="5" hints="h"/>
+                <field name="textcolor" type="Color" display="Schriftfarbe"    default="0, 0, 0"/>
+                <field name="textsize"  type="int"   display="Schriftgröße" default="10"/>
+                <field name="symbol"    type="Symbol" display="Symbol"     default="square"/>
+            </fields>
+        </theme>
+
+        <theme name="Fixpoint">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="3" hints="h"/>
+                <field name="symbol"    type="Symbol" display="Symbol"     default="point"/>
+            </fields>
+        </theme>
+
+        <theme name="Floodmap">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+                <field name="backgroundcolor" type="Color" display="Hintergrund" default="140, 200, 130"/>
+            </fields>
+        </theme>
+
+        <theme name="GaugeLocations">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+                <field name="symbol"    type="Symbol" display="Symbol"     default="point"/>
+            </fields>
+        </theme>
+
+        <theme name="Qp">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 255"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="3" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="Hw">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="3" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="Catchments">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+                <field name="backgroundcolor" type="Color" display="Hintergrund" default="140, 200, 130"/>
+            </fields>
+        </theme>
+
+        <theme name="Floodplain">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="1" hints="h"/>
+                <field name="backgroundcolor" type="Color" display="Hintergrund" default="140, 200, 130"/>
+            </fields>
+        </theme>
+
+        <theme name="FloodmapLine">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="140, 200, 130"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="3" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="Building">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="5" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="HydrBoundariesLine">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="5" hints="h"/>
+            </fields>
+        </theme>
+
+        <theme name="HydrBoundariesPoly">
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="255, 0, 0"/>
+                <field name="linesize"  type="int"   display="Liniendicke" default="5" hints="h"/>
+                <field name="backgroundcolor" type="Color" display="Hintergrund" default="140, 200, 130"/>
+            </fields>
+        </theme>
+
+
+        <!--
+             FLOW VELOCITY
+        -->
+        <theme name="FlowVelocityVMainChannel">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="FlowVelocityVTotalChannel">
+            <inherits>
+                <inherit from="FlowVelocityVMainChannel"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+
+        <theme name="FlowVelocityTau">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 204, 204"/>
+            </fields>
+        </theme>
+
+
+        <!--
+             MIDDLE BED HEIGHT
+        -->
+        <theme name="MiddleBedHeightSingle">
+            <inherits>
+                <inherit from="HiddenColorLines"/>
+                <inherit from="MinMaxPoints"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="204, 204, 204"/>
+            </fields>
+        </theme>
+
+        <theme name="MiddleBedHeightEpoch">
+            <inherits>
+                <inherit from="FlowVelocityVMainChannel"/>
+            </inherits>
+            <fields>
+                <field name="linecolor" type="Color" display="Linienfarbe" default="0, 0, 102"/>
+            </fields>
+        </theme>
+    </themegroup>
+
+    <!--
+      Mappings are following now. A mapping maps between a name of a facet
+      and a theme.
+      Always the first matching mapping is taken, so consider putting most
+      specific mappings on top of the list.
+    -->
+    <mappings>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ1)(\D.*)*" to="LongitudinalSectionW_HQ1_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ2)(\D.*)*" to="LongitudinalSectionW_HQ2_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ5)(\D.*)*" to="LongitudinalSectionW_HQ5_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ10)(\D.*)*" to="LongitudinalSectionW_HQ10_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ20)(\D.*)*" to="LongitudinalSectionW_HQ20_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ25)(\D.*)*" to="LongitudinalSectionW_HQ25_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ50)(\D.*)*" to="LongitudinalSectionW_HQ50_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ100)(\D.*)*" to="LongitudinalSectionW_HQ100_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ200)(\D.*)*" to="LongitudinalSectionW_HQ200_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ500)(\D.*)*" to="LongitudinalSectionW_HQ500_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQ1000)(\D.*)*" to="LongitudinalSectionW_HQ1000_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQExtrem)(\D.*)*" to="LongitudinalSectionW_HQExtrem_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HQRZ)(\D.*)*" to="LongitudinalSectionW_HQRZ_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(HSQ)(\D.*)*" to="LongitudinalSectionW_HSQ_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(MHQ)(\D.*)*" to="LongitudinalSectionW_MHQ_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(MNQ)(\D.*)*" to="LongitudinalSectionW_MNQ_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(MQ)(\D.*)*" to="LongitudinalSectionW_MQ_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" pattern=".*(NQ)(\D.*)*" to="LongitudinalSectionW_NQ_Points"/>
+        <mapping from="longitudinal_section.w" masterAttr="ld_mode==location" to="LongitudinalSectionPoints"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ1)(\D.*)*" to="LongitudinalSectionW_HQ1"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ2)(\D.*)*" to="LongitudinalSectionW_HQ2"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ5)(\D.*)*" to="LongitudinalSectionW_HQ5"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ10)(\D.*)*" to="LongitudinalSectionW_HQ10"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ20)(\D.*)*" to="LongitudinalSectionW_HQ20"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ25)(\D.*)*" to="LongitudinalSectionW_HQ25"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ50)(\D.*)*" to="LongitudinalSectionW_HQ50"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ100)(\D.*)*" to="LongitudinalSectionW_HQ100"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ200)(\D.*)*" to="LongitudinalSectionW_HQ200"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ500)(\D.*)*" to="LongitudinalSectionW_HQ500"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQ1000)(\D.*)*" to="LongitudinalSectionW_HQ1000"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQExtrem)(\D.*)*" to="LongitudinalSectionW_HQExtrem"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HQRZ)(\D.*)*" to="LongitudinalSectionW_HQRZ"/>
+        <mapping from="longitudinal_section.w" pattern=".*(HSQ)(\D.*)*" to="LongitudinalSectionW_HSQ"/>
+        <mapping from="longitudinal_section.w" pattern=".*(MHQ)(\D.*)*" to="LongitudinalSectionW_MHQ"/>
+        <mapping from="longitudinal_section.w" pattern=".*(MNQ)(\D.*)*" to="LongitudinalSectionW_MNQ"/>
+        <mapping from="longitudinal_section.w" pattern=".*(MQ)(\D.*)*" to="LongitudinalSectionW_MQ"/>
+        <mapping from="longitudinal_section.w" pattern=".*(NQ)(\D.*)*" to="LongitudinalSectionW_NQ"/>
+        <mapping from="longitudinal_section.w" to="LongitudinalSectionW"/>
+
+        <mapping from="longitudinal_section.q" pattern="(HQ1)(\D.*)*" to="LongitudinalSectionQ_HQ1"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ2)(\D.*)*" to="LongitudinalSectionQ_HQ2"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ5)(\D.*)*" to="LongitudinalSectionQ_HQ5"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ10)(\D.*)*" to="LongitudinalSectionQ_HQ10"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ20)(\D.*)*" to="LongitudinalSectionQ_HQ20"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ25)(\D.*)*" to="LongitudinalSectionQ_HQ25"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ50)(\D.*)*" to="LongitudinalSectionQ_HQ50"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ100)(\D.*)*" to="LongitudinalSectionQ_HQ100"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ200)(\D.*)*" to="LongitudinalSectionQ_HQ200"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ500)(\D.*)*" to="LongitudinalSectionQ_HQ500"/>
+        <mapping from="longitudinal_section.q" pattern="(HQ1000)(\D.*)*" to="LongitudinalSectionQ_HQ1000"/>
+        <mapping from="longitudinal_section.q" pattern="(HQExtrem)(\D.*)*" to="LongitudinalSectionQ_HQExtrem"/>
+        <mapping from="longitudinal_section.q" pattern="(HQRZ)(\D.*)*" to="LongitudinalSectionQ_HQRZ"/>
+        <mapping from="longitudinal_section.q" pattern="(HSQ)(\D.*)*" to="LongitudinalSectionQ_HSQ"/>
+        <mapping from="longitudinal_section.q" pattern="(MHQ)(\D.*)*" to="LongitudinalSectionQ_MHQ"/>
+        <mapping from="longitudinal_section.q" pattern="(MNQ)(\D.*)*" to="LongitudinalSectionQ_MNQ"/>
+        <mapping from="longitudinal_section.q" pattern="(MQ)(\D.*)*" to="LongitudinalSectionQ_MQ"/>
+        <mapping from="longitudinal_section.q" pattern="(NQ)(\D.*)*" to="LongitudinalSectionQ_NQ"/>
+        <mapping from="longitudinal_section.q" to="LongitudinalSectionQ"/>
+
+        <mapping from="discharge_curve.curve" to="DischargeCurve"/>
+        <mapping from="historical_discharge.historicalq" to="HistoricalDischargeCurveQ"/>
+        <mapping from="historical_discharge.historicalq.diff" to="HistoricalDischargeCurveQDiff"/>
+        <mapping from="cross_section" to="CrossSection"/>
+        <mapping from="cross_section_water_line" to="CrossSectionWaterLine"/>
+        <mapping from="computed_discharge_curve.q" to="ComputedDischargeCurve"/>
+        <mapping from="duration_curve.w" to="DurationCurveW"/>
+        <mapping from="duration_curve.q" to="DurationCurveQ"/>
+        <mapping from="discharge_longitudinal_section.w" to="DischargeLongitudinalSectionW"/>
+        <mapping from="discharge_longitudinal_section.c" to="DischargeLongitudinalSectionC"/>
+        <mapping from="discharge_longitudinal_section.q" to="DischargeLongitudinalSectionQ"/>
+        <mapping from="computed_discharge_curve.mainvalues.q" to="ComputedDischargeCurveQ"/>
+        <mapping from="computed_discharge_curve.mainvalues.w" to="ComputedDischargeCurveW"/>
+        <mapping from="duration_curve.mainvalues.q" to="ComputedDischargeCurveQ"/>
+        <mapping from="mainvalues.q" to="ComputedDischargeCurveQ"/>
+        <mapping from="mainvalues.w" to="ComputedDischargeCurveW"/>
+        <mapping from="longitudinal_section.annotations" to="Annotations"/>
+        <mapping from="w_differences" to="Differences"/>
+        <mapping from="floodmap.wsplgen" to="WSPLGEN"/>
+        <mapping from="floodmap.riveraxis" to="RiverAxis"/>
+        <mapping from="floodmap.kms" to="Kms"/>
+        <mapping from="floodmap.qps" to="Qps"/>
+        <mapping from="floodmap.hws" to="Hws"/>
+        <mapping from="floodmap.hydr_boundaries" to="HydrBoundariesLines"/>
+        <mapping from="floodmap.hydr_boundaries_poly" to="HydrBoundariesPolys"/>
+        <mapping from="floodmap.catchment" to="Catchment"/>
+        <mapping from="floodmap.floodplain" to="Floodplains"/>
+        <mapping from="floodmap.lines" to="FloodmapLines"/>
+        <mapping from="floodmap.buildings" to="Buildings"/>
+        <mapping from="floodmap.fixpoints" to="Fixpoints"/>
+        <mapping from="floodmap.floodmaps" to="Floodmaps"/>
+        <mapping from="floodmap.gauge_location" to="GaugeLocation"/>
+        <mapping from="other.wq" to="WQPoints"/>
+        <mapping from="other.wkms" to="WKms"/>
+        <mapping from="other.wqkms" to="WQKms"/>
+        <mapping from="other.wqkms.w" to="WQKms"/>
+        <mapping from="other.wqkms.q" to="WQKms"/>
+        <mapping from="heightmarks_points" to="heightmarks_points"/>
+        <mapping from="area" to="Area"/>
+        <mapping from="cross_section.area" to="Area"/>
+        <mapping from="hyk" to="Hyk"/>
+        <mapping from="longitudinal_section.area" to="Area"/>
+        <mapping from="longitudinal_section.manualpoints" to="ManualPoints"/>
+        <mapping from="cross_section.manualpoints" to="ManualPoints"/>
+        <mapping from="computed_discharge_curve.manualpoints" to="ManualPoints"/>
+        <mapping from="duration_curve.manualpoints" to="ManualPoints"/>
+        <mapping from="wdifferences.manualpoints" to="ManualPoints"/>
+        <mapping from="discharge_longitudinal_section.manualpoints" to="ManualPoints"/>
+        <mapping from="discharge_curve.manualpoints" to="ManualPoints"/>
+        <mapping from="reference_curve.manualpoints" to="ManualPoints"/>
+        <mapping from="reference_curve_normalized.manualpoints" to="ManualPoints"/>
+        <mapping from="historical_discharge.manualpoints" to="ManualPoints"/>
+        <mapping from="manualpoints" to="ManualPoints"/>
+        <mapping from="reference_curve" to="ReferenceCurve"/>
+        <mapping from="reference_curve_normalized" to="ReferenceCurveNormalized"/>
+        <mapping from="flow_velocity.totalchannel" to="FlowVelocityVTotalChannel"/>
+        <mapping from="flow_velocity.mainchannel" to="FlowVelocityVMainChannel"/>
+        <mapping from="flow_velocity.tau" to="FlowVelocityTau"/>
+        <mapping from="bedheight_middle.single" to="MiddleBedHeightSingle"/>
+        <mapping from="bedheight_middle.epoch" to="MiddleBedHeightEpoch"/>
+        <mapping from="relativepoint" to="RelativePoint"/>
+
+    </mappings>
+</themes>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/dbconnection.include	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,2 @@
+CONNECTIONTYPE postgis
+CONNECTION "dbname='flys3' host=127.0.0.1 port=5432 user='flys' password='flys' sslmode=disable"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/elbe-mapfile.map	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,73 @@
+MAP
+    NAME "Elbe"
+    STATUS ON
+    SIZE 600 400
+    MAXSIZE 4000
+    EXTENT 3590790.616790 5648985.748203 3868521.325854 5922665.254118
+    UNITS DD
+    #FONTSET "fontset.txt"
+    IMAGECOLOR 255 255 255
+    PROJECTION
+        "init=epsg:31466"
+    END
+
+
+    OUTPUTFORMAT
+        NAME agg
+        DRIVER AGG/PNG
+        IMAGEMODE RGB
+    END
+
+    CONFIG "MS_ERRORFILE" "logs/flys-elbe-wms.log"
+    DEBUG 5
+
+    WEB
+      METADATA
+        "wms_title"             "FLYS-3.0 WMS (ELBE)"
+        "wms_onlineresource"    "http://czech-republic.atlas.intevation.de/cgi-bin/elbe-wms"
+        "wms_accessconstraints" "none"
+        "wms_fees"              "none"
+        "wms_addresstype"       "postal"
+        "wms_address"           "Any Street"
+        "wms_city"              "Any City"
+        "wms_stateorprovince"   "Any state"
+        "wms_postcode"          "My Postalcode"
+        "wms_country"           "Any Country"
+        "wms_contactperson"     "Any Person"
+        "wms_contactorganization" "Any Orga"
+        "wms_contactelectronicmailaddress" "any-email@example.com"
+        "wms_contactvoicetelephone" "Any's telephone number"
+        "wms_srs" "EPSG:31466 EPSG:4326"
+        "wms_feature_info_mime_type" "text/html"
+        "ows_enable_request"   "*"
+      END
+    END
+
+    LAYER
+        NAME riveraxis
+        EXTENT 3590790.616790 5648985.748203 3868521.325854 5922665.254118
+        DEBUG 5
+  
+        METADATA
+            "wms_title" "River Axis"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "dbconnection.include"
+	DATA 'geom FROM "river_axes" USING UNIQUE id USING srid=31466'
+        FILTER "river_id='6'"
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "riveraxis"
+            STYLE
+                SIZE 5
+                COLOR "#000000"
+            END
+        END
+    END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/fontset.txt	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,5 @@
+# LiberationsSans weist eine metrische Identitaet zu Arial auf und ist unter
+# einer freien Lizenz (modifizierte GPL 2) verfuegbar.
+# Quelle: http://de.wikipedia.org/wiki/Arial
+
+LiberationSans-Italic /usr/share/fonts/truetype/LiberationSans-Italic.ttf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/mosel-mapfile.map	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,301 @@
+MAP
+    NAME "Mosel"
+    STATUS ON
+    SIZE 600 400
+    MAXSIZE 4000
+    EXTENT 2457864.326387 5297459.306295 2634771.191263 5586961.449130
+    UNITS DD
+    FONTSET "fontset.txt"
+    SYMBOLSET './symbols/symbols.sym'
+    IMAGECOLOR 255 255 255
+    PROJECTION
+        "init=epsg:31466"
+    END
+
+
+    OUTPUTFORMAT
+        NAME agg
+        DRIVER AGG/PNG
+        IMAGEMODE RGB
+    END
+
+    CONFIG "MS_ERRORFILE" "logs/flys-mosel-wms.log"
+    DEBUG 5
+
+    WEB
+      METADATA
+        "wms_title"             "FLYS-3.0 WMS (SAAR)"
+        "wms_onlineresource"    "http://czech-republic.atlas.intevation.de/cgi-bin/saar-wms"
+        "wms_accessconstraints" "none"
+        "wms_fees"              "none"
+        "wms_addresstype"       "postal"
+        "wms_address"           "Any Street"
+        "wms_city"              "Any City"
+        "wms_stateorprovince"   "Any state"
+        "wms_postcode"          "My Postalcode"
+        "wms_country"           "Any Country"
+        "wms_contactperson"     "Any Person"
+        "wms_contactorganization" "Any Orga"
+        "wms_contactelectronicmailaddress" "any-email@example.com"
+        "wms_contactvoicetelephone" "Any's telephone number"
+        "wms_srs" "EPSG:31466 EPSG:4326"
+        "wms_feature_info_mime_type" "text/html"
+        "ows_enable_request"   "*"
+      END
+    END
+
+    LAYER
+        NAME catchment 
+	EXTENT 2457864.326387 5297459.306295 2634771.191263 5586961.449130
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "catchment"
+        END
+
+        TYPE POLYGON
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM catchment USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "catchment"
+            STYLE
+                COLOR "#000080"
+                OUTLINECOLOR "#000000"
+            END
+        END
+    END
+    LAYER
+        NAME km 
+	GROUP km
+	EXTENT 2525910.000000 5481666.000000 2614362.250000 5582403.000000
+        DEBUG 0
+	DUMP TRUE
+  
+        METADATA
+            "wms_title" "km"
+        END
+
+        TYPE POINT 
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM river_axes_km USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "km"
+  	    STYLE
+             COLOR "#ff0000"
+  	     SYMBOL 'square'
+  	     SIZE 5
+  	   END
+        END
+    END
+
+    LAYER
+	NAME km_annotation 
+	GROUP km
+	EXTENT 2525910.000000 5481666.000000 2614362.250000 5582403.000000
+        DEBUG 5
+	DUMP TRUE
+  
+        METADATA
+            "wms_title" "km_annotation"
+        END
+  	
+	TYPE ANNOTATION 
+  	STATUS ON 
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM river_axes_km USING SRID 31466"
+        FILTER 'river_id=2'
+  	LABELITEM km
+
+        MAXSCALE 25000 
+
+  	CLASS 
+  	  LABEL 
+  	    ANGLE auto 
+  	    SIZE 10
+  	    COLOR "#000000" 
+  	    TYPE truetype 
+  	    FONT LiberationSans-Italic 
+	    POSITION ur
+	    OFFSET 2 2
+  	  END
+  	END 
+    END
+
+    LAYER
+        NAME buildings 
+	Extent 2526389.654387 5492305.031511 2612653.500000 5582427.500000
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "buildings (Bauwerke/Wehre)"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM buildings USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "buildings"
+            STYLE
+        	COLOR "#ff2222"
+            END
+        END
+    END
+    LAYER
+        NAME fixpoints 
+	EXTENT 2525789.640000 5481448.110000 2614324.201153 5582705.474713
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "fixpoints (Geodaesie/Festpunkte)"
+        END
+
+        TYPE POINT 
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM fixpoints USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "fixpoints"
+  	    STYLE
+             COLOR "#ffff00"
+  	     SYMBOL 'square'
+  	     SIZE 6
+  	   END
+        END
+    END
+    LAYER
+        NAME riveraxis
+	EXTENT 2525866.883066 5480800.000000 2614283.382914 5582578.641600
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "River Axes"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM river_axes USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "riveraxes"
+            STYLE
+       		 COLOR "#0000ff"
+            END
+        END
+    END
+    
+    LAYER
+        NAME qps
+	EXTENT 2525526.058176 5481412.836939 2614739.057487 5582746.998120
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "QPS (CrossSectionTracks)"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM cross_section_tracks USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+	MAXSCALEDENOM 100000
+
+        CLASS
+            NAME "qps"
+            STYLE
+        	COLOR "#0000ff"
+            END
+        END
+    END
+
+    LAYER
+        NAME hws 
+	EXTENT 2531846.270698 5501745.060309 2580199.261246 5535383.855597
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "HWS"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM hws USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "hws"
+            STYLE
+        	COLOR "#ff2222" 
+            END
+        END
+    END
+    LAYER
+        NAME floodplain 
+        EXTENT 2583046.060000 5556912.223213 2614325.275661 5582743.883699
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "floodplain (Hydr. Grenzen/Talaue)"
+        END
+
+        TYPE POLYGON
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM floodplain USING SRID 31466"
+        FILTER 'river_id=2'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "floodplain"
+            STYLE
+                COLOR "#800080"
+		OUTLINECOLOR "#000080"
+            END
+        END
+    END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/oracle_dbconnection.include	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,2 @@
+CONNECTIONTYPE oraclespatial
+CONNECTION "flys3/flys3@localhost"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/saar-mapfile.map	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,301 @@
+MAP
+    NAME "Saar"
+    STATUS ON
+    SIZE 600 400
+    MAXSIZE 4000
+    EXTENT 2539488.036000 5450928.892000 2575486.407000 5507352.839000
+    UNITS DD
+    FONTSET "fontset.txt"
+    SYMBOLSET './symbols/symbols.sym'
+    IMAGECOLOR 255 255 255
+    PROJECTION
+        "init=epsg:31466"
+    END
+
+
+    OUTPUTFORMAT
+        NAME agg
+        DRIVER AGG/PNG
+        IMAGEMODE RGB
+    END
+
+    CONFIG "MS_ERRORFILE" "logs/flys-saar-wms.log"
+    DEBUG 0
+
+    WEB
+      METADATA
+        "wms_title"             "FLYS-3.0 WMS (SAAR)"
+        "wms_onlineresource"    "http://czech-republic.atlas.intevation.de/cgi-bin/saar-wms"
+        "wms_accessconstraints" "none"
+        "wms_fees"              "none"
+        "wms_addresstype"       "postal"
+        "wms_address"           "Any Street"
+        "wms_city"              "Any City"
+        "wms_stateorprovince"   "Any state"
+        "wms_postcode"          "My Postalcode"
+        "wms_country"           "Any Country"
+        "wms_contactperson"     "Any Person"
+        "wms_contactorganization" "Any Orga"
+        "wms_contactelectronicmailaddress" "any-email@example.com"
+        "wms_contactvoicetelephone" "Any's telephone number"
+        "wms_srs" "EPSG:31466 EPSG:4326"
+        "wms_feature_info_mime_type" "text/html"
+        "ows_enable_request"   "*"
+      END
+    END
+
+    LAYER
+        NAME catchment 
+        EXTENT 2520667.897954 5376316.575645 2634771.191263 5508288.005707
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "catchment"
+        END
+
+        TYPE POLYGON
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM catchment USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "catchment"
+            STYLE
+                COLOR "#000080"
+                OUTLINECOLOR "#000000"
+            END
+        END
+    END
+    LAYER
+        NAME km 
+	GROUP km
+	EXTENT 2539489.068000 5450953.000500 2575482.527500 5507278.634500
+        DEBUG 0
+	DUMP TRUE
+  
+        METADATA
+            "wms_title" "km"
+        END
+
+        TYPE POINT 
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM river_axes_km USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "km"
+  	    STYLE
+             COLOR "#ff0000"
+  	     SYMBOL 'square'
+  	     SIZE 5
+  	   END
+        END
+    END
+
+    LAYER
+	NAME km_annotation 
+	GROUP km
+	EXTENT 2539489.068000 5450953.000500 2575482.527500 5507278.634500
+        DEBUG 5
+	DUMP TRUE
+  
+        METADATA
+            "wms_title" "km_annotation"
+        END
+  	
+	TYPE ANNOTATION 
+  	STATUS ON 
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM river_axes_km USING SRID 31466"
+        FILTER 'river_id=1'
+  	LABELITEM km
+
+        MAXSCALE 25000 
+
+  	CLASS 
+  	  LABEL 
+  	    ANGLE auto 
+  	    SIZE 10
+  	    COLOR "#000000" 
+  	    TYPE truetype 
+  	    FONT LiberationSans-Italic 
+	    POSITION ur
+	    OFFSET 2 2
+  	  END
+  	END 
+    END
+
+    LAYER
+        NAME buildings 
+        EXTENT 2540544.253718 5456266.217464 2567747.834199 5502557.982120
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "buildings (Bauwerke/Wehre)"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM buildings USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "buildings"
+            STYLE
+        	COLOR "#ff2222"
+            END
+        END
+    END
+    LAYER
+        NAME fixpoints 
+        EXTENT 2539388.036000 5450896.688000 2575586.296000 5507370.606000
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "fixpoints (Geodaesie/Festpunkte)"
+        END
+
+        TYPE POINT 
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM fixpoints USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "fixpoints"
+  	    STYLE
+             COLOR "#ffff00"
+  	     SYMBOL 'square'
+  	     SIZE 6
+  	   END
+        END
+    END
+    LAYER
+        NAME riveraxes
+        EXTENT 2539488.036000 5450928.892000 2575486.407000 5507352.839000
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "River Axes"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM river_axes USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "riveraxes"
+            STYLE
+       		 COLOR "#0000ff"
+            END
+        END
+    END
+    
+    LAYER
+        NAME qps
+        EXTENT 2539289.724000 5450852.896743 2576589.878311 5507289.656000
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "QPS (CrossSectionTracks)"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM cross_section_tracks USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+	MAXSCALEDENOM 100000
+
+        CLASS
+            NAME "qps"
+            STYLE
+        	COLOR "#0000ff"
+            END
+        END
+    END
+
+    LAYER
+        NAME hws 
+	EXTENT 2539778.101933 5456638.161347 2567463.841704 5500605.745332
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "HWS"
+        END
+
+        TYPE LINE
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM hws USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "hws"
+            STYLE
+        	COLOR "#ff2222" 
+            END
+        END
+    END
+    LAYER
+        NAME floodplain 
+        EXTENT 2539343.776823 5451397.340027 2576021.009478 5507230.640000
+        DEBUG 0
+  
+        METADATA
+            "wms_title" "floodplain (Hydr. Grenzen/Talaue)"
+        END
+
+        TYPE POLYGON
+        STATUS ON
+        INCLUDE "oracle_dbconnection.include"
+	DATA "GEOM FROM floodplain USING SRID 31466"
+        FILTER 'river_id=1'
+
+        PROJECTION
+            "init=epsg:31466"
+        END
+
+        CLASS
+            NAME "floodplain"
+            STYLE
+                COLOR "#800080"
+		OUTLINECOLOR "#000080"
+            END
+        END
+    END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/doc/mapserver/symbols/symbols.sym	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,22 @@
+SYMBOLSET
+SYMBOL
+  NAME 'point'
+  TYPE ELLIPSE
+  POINTS
+    1 1
+  END
+  FILLED TRUE
+END
+SYMBOL
+  NAME "square"
+  TYPE VECTOR
+  POINTS
+    0 0
+    0 1
+    1 1
+    1 0
+    0 0
+  END
+  FILLED TRUE
+END
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/pom.xml	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,161 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>de.intevation.flys.artifacts</groupId>
+  <artifactId>flys-artifacts</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>flys-artifacts</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>2.0.2</version>
+          <configuration>
+              <source>1.5</source>
+              <target>1.5</target>
+          </configuration>
+      </plugin>
+     </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.sf.ehcache</groupId>
+      <artifactId>ehcache-core</artifactId>
+      <version>2.4.2</version>
+    </dependency>
+    <dependency>
+      <groupId>jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+      <version>1.0.13</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>trove</groupId>
+      <artifactId>trove</artifactId>
+      <version>1.1-beta-5</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.opencsv</groupId>
+      <artifactId>opencsv</artifactId>
+      <version>2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>de.intevation.bsh.artifact-database</groupId>
+      <artifactId>artifact-database</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>de.intevation.artifacts.common</groupId>
+      <artifactId>artifacts-common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>de.intevation.flys</groupId>
+      <artifactId>flys-backend</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+      <version>1.0.13</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.xmlgraphics</groupId>
+      <artifactId>batik-dom</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.xmlgraphics</groupId>
+      <artifactId>batik-svggen</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>com.lowagie</groupId>
+      <artifactId>itext</artifactId>
+      <version>2.1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math</artifactId>
+      <version>2.2</version>
+    </dependency>
+    <dependency>
+      <groupId>com.h2database</groupId>
+      <artifactId>h2</artifactId>
+      <version>1.3.158</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-dbcp</groupId>
+      <artifactId>commons-dbcp</artifactId>
+      <version>1.2.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.geotools</groupId>
+      <artifactId>gt-shapefile</artifactId>
+      <version>2.7.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.geotools</groupId>
+      <artifactId>gt-epsg-wkt</artifactId>
+      <version>2.7.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.geotools</groupId>
+      <artifactId>gt-geojson</artifactId>
+      <version>2.7.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.geotools</groupId>
+      <artifactId>gt-swing</artifactId>
+      <version>2.7.2</version>
+    </dependency>
+    <dependency>   
+      <groupId>org.json</groupId>   
+      <artifactId>json</artifactId>   
+      <version>20090211</version>   
+    </dependency>
+    <dependency>
+      <groupId>org.apache.velocity</groupId>
+      <artifactId>velocity</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jasperreports</groupId>
+      <artifactId>jasperreports</artifactId>
+      <version>4.5.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.groovy</groupId>
+      <artifactId>groovy-all</artifactId>
+      <version>1.6.0</version>
+    </dependency>
+  </dependencies>
+  <repositories>
+    <repository>
+        <id>jboss-repo2</id>
+        <name>JBoss repo2</name>
+        <url>http://repository.jboss.org/nexus/content/groups/public/</url>
+    </repository>
+    <repository>
+      <id>gt2.repo</id>
+      <name>GeoTools2 Repository including JTS</name>
+      <url>http://download.osgeo.org/webdav/geotools</url>
+    </repository>
+  </repositories>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/AnnotationArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,213 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+/**
+ * Artifact to access names of Points Of Interest along a segment of a river.
+ */
+public class AnnotationArtifact extends StaticFLYSArtifact {
+
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(AnnotationArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "annotation";
+
+    /** Get river, setup Facets. */
+    @Override
+    protected void initialize(Artifact artifact, Object context,
+            CallMeta meta) {
+        logger.debug("AnnotationArtifact.initialize, id: "
+            + artifact.identifier());
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+        importData(flys, "river");
+
+        List<Facet> fs = new ArrayList<Facet>();
+
+        // TODO Add CallMeta (duplicate TODO in RiverAxisArtifact.java).
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, meta, fs);
+
+        if (!fs.isEmpty()) {
+            logger.debug("Facets to add in AnnotationsArtifact.initialize .");
+            facets.put(getCurrentStateId(), fs);
+        }
+        else {
+            logger.debug("No facets to add in AnnotationsArtifact.initialize .");
+        }
+    }
+
+
+    public double[] getDistance() {
+        /** TODO In initialize(), access maximal range of river (via
+         * AnnotationFactory) instead of overriding getDistance, 
+         * important for diagram generation. */
+        return new double[] {0f, 1000f};
+    }
+
+
+    /**
+     * Create the description of this AnnotationArtifact-instance.
+     *
+     * @param data Some data.
+     * @param context The CallContext.
+     *
+     * @return the description of this artifact.
+     */
+    @Override
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        if (logger.isDebugEnabled()) {
+            dumpArtifact();
+        }
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context);
+
+        root.appendChild(name);
+        root.appendChild(outs);
+
+        return description;
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    /**
+     * Append outputmode elements to given document.
+     *
+     * @param doc Document to add outputmodes to.
+     * @param outs Element to add outputmode elements to.
+     * @param context The given CallContext (mostly for internationalization).
+     */
+    //@Override
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append output modes for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            List<Output> list = state.getOutputs();
+            if (list == null || list.size() == 0) {
+                logger.debug("-> No output modes for this state.");
+                continue;
+            }
+
+            List<Facet> fs = facets.get(stateId);
+            if (fs == null || fs.size() == 0) {
+                logger.debug("No facets found.");
+                continue;
+            }
+
+            logger.debug("Found " + fs.size() + " facets in previous states.");
+
+            List<Output> generated = generateOutputs(list, fs);
+
+            ProtocolUtils.appendOutputModes(doc, outs, generated);
+        }
+
+        try {
+            DefaultState cur = (DefaultState) getCurrentState(context);
+            if (cur.validate(this)) {
+                List<Output> list = cur.getOutputs();
+                if (list != null && list.size() > 0) {
+                    logger.debug(
+                        "Append output modes for state: " + cur.getID());
+
+                    List<Facet> fs = facets.get(cur.getID());
+                    if (fs != null && fs.size() > 0) {
+                        List<Output> generated = generateOutputs(list, fs);
+
+                        logger.debug("Found " + fs.size() + " current facets.");
+                        if (!generated.isEmpty()) {
+                            ProtocolUtils.appendOutputModes(
+                                doc, outs, generated);
+                        }
+                        else{
+                            logger.debug("Cannot append output to generated document.");
+                        }
+                    }
+                    else {
+                        logger.debug("No facets found for the current state.");
+                    }
+                }
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            // state is not valid, so we do not append its outputs.
+        }
+    } 
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/AreaArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,180 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.model.AreaFacet;
+
+
+import de.intevation.flys.artifacts.states.AreaCreationState;
+import de.intevation.flys.artifacts.states.StaticState;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+
+/**
+ * Artifact describing the area between two WKms.
+ */
+public class AreaArtifact extends StaticFLYSArtifact {
+
+    /** Name of Artifact. */
+    public static final String AREA_ARTIFACT_NAME = "area_artifact";
+
+    /** Dataitem: Facet name. Facets with this name will be created (important
+     * to not have the area calculated in e.g. a CrossSection to be shown in
+     * LongitudinalSection.  */
+    protected static final String FACET_NAME = "area.facet";
+
+    /** Name of state. */
+    public static final String STATIC_STATE_NAME = "state.area_artifact";
+
+    /** data item name to access upper curve. */
+    protected static final String AREA_CURVE_OVER = "area.curve_over";
+
+    /** data item name to access lower curve. */
+    protected static final String AREA_CURVE_UNDER = "area.curve_under";
+
+    /** data item name to access whether or not paint over and under. */
+    protected static final String AREA_BETWEEN = "area.between";
+
+    /** Name of state. */
+    protected static final String AREA_NAME = "area.name";
+
+    /** Own logger. */
+    private static final Logger logger =
+        Logger.getLogger(AreaArtifact.class);
+
+
+    /** Return given name. */
+    @Override
+    public String getName() {
+        return AREA_ARTIFACT_NAME;
+    }
+
+
+    /** Store ids, create an AreaFacet. */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.info("AreaArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+
+        // TODO yet unused.
+        String ids = getDatacageIDValue(data);
+
+        // TODO this facet will be remodeled during next feed.
+        List<Facet> fs = new ArrayList<Facet>();
+        fs.add(new AreaFacet(0, "", "TODO: I am an AreaFacet"));
+
+        AreaCreationState state = (AreaCreationState) getCurrentState(context);
+
+        if (!fs.isEmpty()) {
+            facets.put(getCurrentStateId(), fs);
+        }
+    }
+
+    // TODO Data is not cached in this way.
+
+    /** Do not copy data from daddyfact. */
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object   context,
+        CallMeta callMeta)
+    {
+        // do nothing
+    }
+
+
+    /**
+     * Get name of facets to create.
+     */
+    public String getFacetName() {
+        return getDataAsString(FACET_NAME);
+    }
+
+
+    /**
+     * Get dataprovider key for the 'lower' curve (we got that information fed
+     * from the client and store it as data).
+     */
+    public String getLowerDPKey() {
+        return getDataAsString(AREA_CURVE_UNDER);
+    }
+
+
+    /**
+     * True if the whole area between the two curves shall be filled.
+     */
+    public boolean getPaintBetween() {
+        String val = getDataAsString(AREA_BETWEEN);
+
+        return val != null && val.equals("true");
+    }
+
+
+    /**
+     * Get dataprovider key for the 'upper' curve (we got that information fed
+     * from the client and store it as data).
+     */
+    public String getUpperDPKey() {
+        return getDataAsString(AREA_CURVE_OVER);
+    }
+
+
+    /** Return data item that is used to configure name of area. */
+    public String getAreaName() {
+        return getDataAsString(AREA_NAME);
+    }
+
+
+    /**
+     * Create and return a new AreaCreationState with charting output.
+     */
+    @Override
+    public State getCurrentState(Object cc) {
+        final List<Facet> fs = facets.get(getCurrentStateId());
+
+        AreaCreationState state = new AreaCreationState();
+
+        StaticState.addDefaultChartOutput(state, "cross_section", fs);
+
+        return state;
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getCurrentState(context));
+
+        return states;
+    }
+
+
+    /** Trivia. */
+    protected State getState(Object context, String stateID) {
+        return getCurrentState(null);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/ChartArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,321 @@
+package de.intevation.flys.artifacts;
+
+import org.apache.log4j.Logger;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+public class ChartArtifact extends FLYSArtifact {
+
+    private static final Logger logger =
+        Logger.getLogger(ChartArtifact.class);
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callmeta,
+        Document        data)
+    {
+        logger.debug("ChartArtifact.setup");
+        this.identifier = identifier;
+        name = "new_chart";
+
+        List<State> states = getStates(context);
+
+        setCurrentState(states.get(0));
+    }
+
+
+    @Override
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        if (logger.isDebugEnabled()) {
+            dumpArtifact();
+        }
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        List<State> reachable = transitionEngine.getReachableStates(
+            this, getCurrentState(context), stateEngine);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+        ProtocolUtils.appendReachableStates(creator, root, reachable);
+
+        appendBackgroundActivity(creator, root, context);
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element ui = ProtocolUtils.createArtNode(
+            creator, "ui", null, null);
+
+        Element staticUI  = ProtocolUtils.createArtNode(
+            creator, "static", null, null);
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context, identifier());
+
+        appendStaticUI(description, staticUI, context, identifier());
+
+        Element dynamic = current.describe(
+            this,
+            description,
+            root,
+            context,
+            identifier());
+
+        if (dynamic != null) {
+            ui.appendChild(dynamic);
+        }
+
+        ui.appendChild(staticUI);
+
+        root.appendChild(name);
+        root.appendChild(ui);
+        root.appendChild(outs);
+
+        return description;
+
+    }
+
+
+    protected static void appendBackgroundActivity(
+        ElementCreator cr,
+        Element        root,
+        CallContext    context
+    ) {
+        Element inBackground = cr.create("background-processing");
+        root.appendChild(inBackground);
+
+        cr.addAttr(
+            inBackground,
+            "value",
+            String.valueOf(context.isInBackground()),
+            true);
+    }
+
+
+    /**
+     * Append output mode nodes to a document.
+     */
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context,
+        String      uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append output modes for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            List<Output> list = state.getOutputs();
+            if (list == null || list.size() == 0) {
+                logger.debug("-> No output modes for this state.");
+                continue;
+            }
+
+            List<Facet> fs = facets.get(stateId);
+
+            if (fs == null || fs.size() == 0) {
+                logger.debug("No facets for previous state found.");
+                continue;
+            }
+
+            logger.debug("Found " + fs.size() + " facets in previous states.");
+
+            List<Output> generated = generateOutputs(list, fs);
+
+            ProtocolUtils.appendOutputModes(doc, outs, generated);
+        }
+
+        try {
+            DefaultState cur = (DefaultState) getCurrentState(context);
+            List<Output> list = cur.getOutputs();
+            if (list != null && list.size() > 0) {
+                logger.debug(
+                    "Append output modes for current state: " + cur.getID());
+
+                List<Facet> fs = facets.get(cur.getID());
+
+                if (fs != null && fs.size() > 0) {
+                    List<Output> generated = generateOutputs(list, fs);
+
+                    logger.debug("Found " + fs.size() + " current facets.");
+                    if (!generated.isEmpty()) {
+                        ProtocolUtils.appendOutputModes(
+                            doc, outs, generated);
+                    }
+                }
+                else {
+                    logger.debug("No facets found for the current state.");
+                }
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            // state is not valid, so we do not append its outputs.
+        }
+    }
+
+
+    /**
+     * This method appends the static data - that has already been inserted by
+     * the user - to the static node of the DESCRIBE document.
+     *
+     * @param doc The document.
+     * @param ui The root node.
+     * @param context The CallContext.
+     * @param uuid The identifier of the artifact.
+     */
+    protected void appendStaticUI(
+        Document    doc,
+        Node        ui,
+        CallContext context,
+        String uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        logger.debug("previous states: " + stateIds);
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append static data for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
+        }
+    }
+
+
+    public static class ChartState extends DefaultState {
+
+        public static final String FIELD_MODE = "chart_type";
+
+        public static final String DURATION_CURVE =
+            "chart.new.durationcurve";
+
+        public static final String COMPUTED_DISCHARGE_CURVE =
+            "chart.new.computeddischargecurve";
+
+        public static final String DISCHARGE_LONGITUDINAL_CURVE =
+            "chart.new.longitudinal_section";
+
+        public static final String W_DIFFERENCES =
+            "chart.new.w_differences";
+
+        public static final String WATERLEVEL =
+            "chart.new.crosssection";
+
+        public static final String[] CHARTS = {
+            COMPUTED_DISCHARGE_CURVE,
+            DURATION_CURVE,
+            DISCHARGE_LONGITUDINAL_CURVE,
+            W_DIFFERENCES,
+            WATERLEVEL };
+
+
+
+        @Override
+        public Object computeAdvance(
+            FLYSArtifact artifact,
+            String       hash,
+            CallContext  context,
+            List<Facet>  facets,
+            Object       old)
+        {
+            logger.debug("ChartState.computeAdvance");
+
+
+            return null;
+        }
+
+
+        @Override
+        protected Element[] createItems(
+            XMLUtils.ElementCreator cr,
+            Artifact    artifact,
+            String      name,
+            CallContext context)
+        {
+            CallMeta meta   = context.getMeta();
+            Element[] charts = new Element[CHARTS.length];
+
+            int i = 0;
+
+            for (String chart: CHARTS) {
+                charts[i++] = createItem(
+                    cr, new String[] {
+                        Resources.getMsg(meta, chart, chart),
+                        chart
+                    });
+            }
+
+            return charts;
+        }
+
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/CollectionMonitor.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,104 @@
+package de.intevation.flys.artifacts;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.Hook;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.state.Output;
+
+import de.intevation.flys.artifacts.datacage.Recommendations;
+import de.intevation.flys.artifacts.ChartArtifact;
+
+public class CollectionMonitor implements Hook {
+
+    public static final String XPATH_RESULT = "/art:result";
+
+
+    private static final Logger logger =
+        Logger.getLogger(CollectionMonitor.class);
+
+
+    @Override
+    public void setup(Node cfg) {
+    }
+
+
+    @Override
+    public void execute(Artifact artifact, CallContext context, Document doc) {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        Element result = (Element) XMLUtils.xpath(
+            doc,
+            XPATH_RESULT,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        ElementCreator creator = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element recommended = creator.create("recommended-artifacts");
+        result.appendChild(recommended);
+
+        String[] outs              = extractOutputNames(flys, context);
+        Map<String, Object> params = getNoneUserSpecificParameters(flys, context);
+
+        Recommendations rec = Recommendations.getInstance();
+        rec.recommend(flys, null, outs, params, recommended);
+    }
+
+
+    /**
+     * Get outputnames from current state (only the ones for which
+     * facets exist).
+     */
+    public static String[] extractOutputNames(
+        FLYSArtifact flys,
+        CallContext  context)
+    {
+        if (flys instanceof ChartArtifact) {
+            return new String[0];
+        }
+
+        List<Output>  outs = flys.getCurrentOutputs(context);
+
+        int num = outs == null ? 0 : outs.size();
+
+        String[] names = new String[num];
+
+        for (int i = 0; i < num; i++) {
+            names[i] = outs.get(i).getName();
+        }
+
+        return names;
+    }
+
+
+    protected Map<String, Object> getNoneUserSpecificParameters(
+        FLYSArtifact flys,
+        CallContext  context)
+    {
+        Map<String, Object> params = new HashMap<String, Object>(1);
+        params.put("recommended", "true");
+
+        return params;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/CrossSectionArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,297 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NavigableMap;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.model.CrossSectionFacet;
+import de.intevation.flys.artifacts.model.FastCrossSectionLineFactory;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+
+import de.intevation.flys.model.CrossSection;
+import de.intevation.flys.model.CrossSectionLine;
+import de.intevation.flys.artifacts.model.CrossSectionFactory;
+
+import de.intevation.flys.artifacts.states.StaticState;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import de.intevation.flys.artifacts.services.CrossSectionKMService;
+
+
+/**
+ * Artifact describing a cross-section.
+ */
+public class CrossSectionArtifact extends StaticFLYSArtifact {
+
+    /** Name of Artifact. */
+    public static final String CS_ARTIFACT_NAME = "cross_section";
+
+    /** Name of state. */
+    public static final String STATIC_STATE_NAME = "state.cross_section";
+
+    /** Name of data item keeping the position. */
+    public static final String DATA_KM = "cross_section.km";
+
+    /** Name of data item keeping the database id of this c.s.. */
+    public static final String DATA_DBID = "cross_section.dbid";
+
+    /** Name of data item flagging whether we think that we are master. */
+    public static final String DATA_IS_MASTER = "cross_section.master?";
+
+    /** Name of data item flagging whether we are the newest. */
+    public static final String DATA_IS_NEWEST = "cross_section.newest?";
+
+    /** Own logger. */
+    private static final Logger logger =
+        Logger.getLogger(CrossSectionArtifact.class);
+
+
+    /** Return given name. */
+    @Override
+    public String getName() {
+        return CS_ARTIFACT_NAME;
+    }
+
+
+    /** Store ids, create a CrossSectionFacet. */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.info("CrossSectionArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+
+        String ids = getDatacageIDValue(data);
+
+        if (ids != null && ids.length() > 0) {
+            addStringData(DATA_DBID, ids);
+            logger.debug("CrossSectionArtifacts db-id: " + ids);
+        }
+        else {
+            throw new IllegalArgumentException("No attribute 'ids' found!");
+        }
+
+        List<Facet> fs = new ArrayList<Facet>();
+        CrossSection cs = CrossSectionFactory.getCrossSection(
+            Integer.valueOf(ids));
+
+        List<CrossSectionLine> csls = cs.getLines();
+        if (!csls.isEmpty()) {
+            CrossSectionLine csl = csls.get(0);
+            // Find min-km of cross sections,
+            // then set DATA_KM to min(DATA_KM, minCross).
+            double masterKm = Double.valueOf(getDataAsString(DATA_KM));
+            if (masterKm < csl.getKm().doubleValue()) {
+                addStringData(DATA_KM, csl.getKm().toString());
+            }
+        }
+        fs.add(new CrossSectionFacet(0, cs.getDescription()));
+
+        // Find out if we are newest.
+        boolean isNewest = CrossSectionFactory.isNewest(cs);
+        String newString = (isNewest) ? "1" : "0";
+        addStringData(DATA_IS_NEWEST, newString);
+        addStringData(DATA_IS_MASTER, newString);
+
+        StaticState state = (StaticState) getCurrentState(context);
+
+        if (!fs.isEmpty()) {
+            facets.put(getCurrentStateId(), fs);
+        }
+    }
+
+
+    /** Copy km where master-artifact "starts". */
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object   context,
+        CallMeta callMeta)
+    {
+        FLYSArtifact winfo = (FLYSArtifact) artifact;
+        double[] range = FLYSUtils.getKmRange(winfo);
+        double min = 0.0f;
+        if (range != null && range.length > 0) {
+            min = range[0];
+        }
+        this.addStringData(DATA_KM, Double.toString(min));
+    }
+
+
+    /**
+     * Create and return a new StaticState with charting output.
+     */
+    @Override
+    public State getCurrentState(Object cc) {
+        final List<Facet> fs = facets.get(getCurrentStateId());
+
+        StaticState state = new StaticState(STATIC_STATE_NAME) {
+            @Override
+            public Object staticCompute(List<Facet> facets) {
+                if (facets != null) {
+                    facets.addAll(fs);
+                }
+                return null;
+            }
+        };
+
+        state.addDefaultChartOutput("cross_section", fs);
+
+        return state;
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getCurrentState(context));
+
+        return states;
+    }
+
+    // TODO all data access needs proper caching.
+
+    /**
+     * Get a DataItem casted to int (0 if fails).
+     */
+    public int getDataAsIntNull(String dataName) {
+        String val = getDataAsString(dataName);
+        try {
+            return Integer.valueOf(val);
+        }
+        catch (NumberFormatException e) {
+            logger.warn("Could not get data " + dataName + " as int", e);
+            return 0;
+        }
+    }
+
+
+    /** Returns database-id of cross-section (from data). */
+    protected int getDBID() {
+        return getDataAsIntNull(DATA_DBID);
+    }
+
+
+    /**
+     * Return position (km) from data, 0 if not found.
+     */
+    protected double getKm() {
+        String val = getDataAsString(DATA_KM);
+        try {
+            return Double.valueOf(val);
+        }
+        catch (NumberFormatException e) {
+            logger.warn("Could not get data " + DATA_KM + " as double", e);
+            return 0;
+        }
+    }
+
+
+    /** Returns true if artifact is set to be a "master" (other facets will
+     * refer to this). */
+    public boolean isMaster() {
+        return !getDataAsString(DATA_IS_MASTER).equals("0");
+    }
+
+
+    /**
+     * Get points of Profile of cross section at given kilometer.
+     *
+     * @return an array holding coordinates of points of profile (
+     *         in the form {{x1, x2} {y1, y2}} ).
+     */
+    public double [][] getCrossSectionData() {
+        logger.info("getCrossSectionData() for cross_section.km "
+            + getDataAsString(DATA_KM));
+        FastCrossSectionLine line = searchCrossSectionLine();
+
+        return line != null
+               ? line.fetchCrossSectionProfile()
+               : null;
+    }
+
+
+    /**
+     * Get CrossSectionLine spatially closest to what is specified in the data
+     * "cross_section.km", null if considered too far.
+     *
+     * @return CrossSectionLine closest to "cross_section.km", might be null
+     *         if considered too far.
+     */
+    public FastCrossSectionLine searchCrossSectionLine() {
+        double TOO_FAR = 1d;
+        CrossSection crossSection = CrossSectionFactory
+            .getCrossSection(getDBID());
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("dbid " + getDBID() + " : " + crossSection);
+        }
+
+        NavigableMap<Double, Integer> kms = CrossSectionKMService
+            .getKms(crossSection.getId());
+
+        Double wishKM = getKm();
+
+        Double floor = kms.floorKey(wishKM);
+        Double ceil  = kms.ceilingKey(wishKM);
+
+        double floorD = floor != null
+            ? Math.abs(floor - wishKM)
+            : Double.MAX_VALUE;
+
+        double ceilD = ceil != null
+            ? Math.abs(ceil - wishKM)
+            : Double.MAX_VALUE;
+
+        double km = floorD < ceilD ? floor : ceil;
+
+        // If we are too far from the wished km, return null.
+        if (Math.abs(km - wishKM) > TOO_FAR) {
+            return null;
+        }
+
+        return FastCrossSectionLineFactory
+            .getCrossSectionLine(crossSection, km);
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param outputName Ignored.
+     * @param facetName Ignored.
+     * @param index     Ignored.
+     * @return 0 if not active
+     */
+    @Override
+    public int getInitialFacetActivity(String outputName, String facetName, int index) {
+        return (getDataAsString(DATA_IS_NEWEST) != null
+            && getDataAsString(DATA_IS_NEWEST).equals("1")) ? 1 : 0;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/ExternalWMSArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,165 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.artifacts.states.WMSBackgroundState;
+
+
+public class ExternalWMSArtifact extends StaticFLYSArtifact {
+
+    public static final String NAME = "external_wms";
+
+    private static final Logger logger =
+        Logger.getLogger(ExternalWMSArtifact.class);
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.info("ExternalWMSArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+
+        String ids = getDatacageIDValue(data);
+
+        if (ids != null && ids.length() > 0) {
+            addStringData("ids", ids);
+        }
+        else {
+            throw new IllegalArgumentException("No attribute 'ids' found!");
+        }
+
+        List<Facet> fs = new ArrayList<Facet>();
+
+        WMSBackgroundState s = (WMSBackgroundState) getCurrentState(context);
+        s.computeInit(this, hash(), context, callMeta, fs);
+
+        if (!fs.isEmpty()) {
+            facets.put(getCurrentStateId(), fs);
+        }
+    }
+
+
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object   context,
+        CallMeta callMeta)
+    {
+        // do nothing
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new ExternalWMSState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getCurrentState(context));
+
+        return states;
+    }
+
+
+    public static class ExternalWMSState extends WMSBackgroundState {
+
+        protected ExternalWMSArtifact artifact;
+
+        protected String ids;
+
+
+        public ExternalWMSState(ExternalWMSArtifact artifact) {
+            super();
+            this.artifact = artifact;
+        }
+
+        protected String getIds() {
+            if (ids == null || ids.length() == 0) {
+                ids = artifact.getDataAsString("ids");
+            }
+
+            return ids;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_EXTERNAL_WMS;
+        }
+
+        @Override
+        protected String getSrid() {
+            return "";
+        }
+
+        @Override
+        protected String getUrl() {
+            String   ids   = getIds();
+            String[] parts = ids.split(";");
+
+            return parts[0];
+        }
+
+        @Override
+        protected String getLayer() {
+            String   ids   = getIds();
+            String[] parts = ids.split(";");
+
+            return parts[1];
+        }
+
+        @Override
+        protected String getTitle(CallMeta meta) {
+            String   ids   = getIds();
+            String[] parts = ids.split(";");
+
+            return parts[2];
+        }
+    } // end of class ExternalWMSState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1306 @@
+package de.intevation.flys.artifacts;
+
+import de.intevation.artifactdatabase.ArtifactDatabaseImpl;
+import de.intevation.artifactdatabase.DefaultArtifact;
+
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.xml.xpath.XPathConstants;
+
+import net.sf.ehcache.Cache;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * The default FLYS artifact with convenience added.
+ * (Subclass to get fully functional artifacts).
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class FLYSArtifact extends DefaultArtifact {
+
+    /** The logger that is used in this artifact. */
+    private static Logger logger = Logger.getLogger(FLYSArtifact.class);
+
+    public static final String COMPUTING_CACHE = "computed.values";
+
+    /** The XPath that points to the input data elements of the FEED document. */
+    public static final String XPATH_FEED_INPUT =
+        "/art:action/art:data/art:input";
+
+    /** The XPath that points to the name of the target state of ADVANCE. */
+    public static final String XPATH_ADVANCE_TARGET =
+        "/art:action/art:target/@art:name";
+
+    public static final String XPATH_MODEL_ARTIFACT =
+        "/art:action/art:template/@uuid";
+
+    public static final String XPATH_FILTER =
+        "/art:action/art:filter/art:out";
+
+    /** The constant string that shows that an operation was successful. */
+    public static final String OPERATION_SUCCESSFUL = "SUCCESS";
+
+    /** The constant string that shows that an operation failed. */
+    public static final String OPERATION_FAILED = "FAILURE";
+
+    /** The identifier of the current state. */
+    protected String currentStateId;
+
+    /** The identifiers of previous states on a stack. */
+    protected List<String> previousStateIds;
+
+    /** The name of the artifact. */
+    protected String name;
+
+    /** The data that have been inserted into this artifact. */
+    protected Map<String, StateData> data;
+
+    /** Mapping of state names to created facets. */
+    protected Map<String, List<Facet>> facets;
+
+    /**
+     * Used to generates "view" on the facets (hides facets not matching the
+     * filter in output of collection);  out -&gt; facets.
+     */
+    protected Map<String, List<Facet>> filterFacets;
+
+
+    /**
+     * The default constructor that creates an empty FLYSArtifact.
+     */
+    public FLYSArtifact() {
+        data             = new TreeMap<String, StateData>();
+        previousStateIds = new ArrayList<String>();
+        facets           = new HashMap<String, List<Facet>>();
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return name;
+    }
+
+
+    /**
+     * Initialize the artifact and insert new data if <code>data</code> contains
+     * information necessary for this artifact.
+     *
+     * @param identifier The UUID.
+     * @param factory The factory that is used to create this artifact.
+     * @param context The CallContext.
+     * @param data Some optional data.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("Setup this artifact with the uuid: " + identifier);
+
+        super.setup(identifier, factory, context, callMeta, data);
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        List<State> states = getStates(context);
+
+        String name = getName();
+        logger.debug("Set initial state for artifact '" + name + "'");
+
+        if (states == null) {
+            logger.error("No states found from which an initial "
+                + "state could be picked.");
+        }
+        setCurrentState(states.get(0));
+
+        String model = XMLUtils.xpathString(
+            data,
+            XPATH_MODEL_ARTIFACT,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (model != null && model.length() > 0) {
+            ArtifactDatabase db = (ArtifactDatabase) flysContext.get(
+                ArtifactDatabaseImpl.GLOBAL_CONTEXT_KEY);
+
+            try {
+                initialize(db.getRawArtifact(model), context, callMeta);
+            }
+            catch (ArtifactDatabaseException adbe) {
+                logger.error(adbe, adbe);
+            }
+        }
+
+        filterFacets = buildFilterFacets(data);
+    }
+
+
+    /** Get copy of previous state ids as Strings in list. */
+    protected List<String> clonePreviousStateIds() {
+        return new ArrayList<String>(previousStateIds);
+    }
+
+
+    /**
+     * Copies data item from other artifact to this artifact.
+     *
+     * @param other Artifact from which to get data.
+     * @param name  Name of data.
+     */
+    protected void importData(FLYSArtifact other, final String name) {
+        if (other == null) {
+            logger.error("No other art. to import data " + name + " from.");
+            return;
+        }
+
+        StateData sd = other.getData(name);
+
+        if (sd == null) {
+            logger.warn("Other artifact has no data " + name + ".");
+            return;
+        }
+
+        this.addData(name, sd);
+    }
+
+
+    protected Map<String, StateData> cloneData() {
+        Map<String, StateData> copy = new TreeMap<String, StateData>();
+
+        for (Map.Entry<String, StateData> entry: data.entrySet()) {
+            copy.put(entry.getKey(), entry.getValue().deepCopy());
+        }
+
+        return copy;
+    }
+
+    /**
+     * Return a copy of the facet mapping.
+     * @return Mapping of state-ids to facets.
+     */
+    protected Map<String, List<Facet>> cloneFacets() {
+        Map copy = new HashMap<String, List<Facet>>();
+
+        for (Map.Entry<String, List<Facet>> entry: facets.entrySet()) {
+            List<Facet> facets      = entry.getValue();
+            List<Facet> facetCopies = new ArrayList<Facet>(facets.size());
+            for (Facet facet: facets) {
+                facetCopies.add(facet.deepCopy());
+            }
+            copy.put(entry.getKey(), facetCopies);
+        }
+
+        return copy;
+    }
+
+
+    /**
+     * (called from setup).
+     * @param artifact master-artifact (if any, otherwise initialize is not called).
+     */
+    protected void initialize(
+        Artifact artifact,
+        Object   context,
+        CallMeta callMeta)
+    {
+        if (!(artifact instanceof FLYSArtifact)) {
+            return;
+        }
+
+        FLYSArtifact flys = (FLYSArtifact)artifact;
+
+        currentStateId   = flys.currentStateId;
+        previousStateIds = flys.clonePreviousStateIds();
+        name             = flys.name;
+        data             = flys.cloneData();
+        facets           = flys.cloneFacets();
+        // Do not clone filter facets!
+
+        ArrayList<String> stateIds     = (ArrayList<String>) getPreviousStateIds();
+        ArrayList<String> toInitialize = (ArrayList<String>) stateIds.clone();
+
+        toInitialize.add(getCurrentStateId());
+
+        for (String stateId: toInitialize) {
+            State state = getState(context, stateId);
+
+            if (state != null) {
+                state.initialize(artifact, this, context, callMeta);
+            }
+        }
+    }
+
+
+    /**
+     * Builds filter facets from document.
+     * @see filterFacets
+     */
+    protected Map<String, List<Facet>> buildFilterFacets(Document document) {
+
+        NodeList nodes = (NodeList)XMLUtils.xpath(
+            document,
+            XPATH_FILTER,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (nodes == null || nodes.getLength() == 0) {
+            return null;
+        }
+
+        Map<String, List<Facet>> result = new HashMap<String, List<Facet>>();
+
+        for (int i = 0, N = nodes.getLength(); i < N; ++i) {
+            Element element = (Element)nodes.item(i);
+            String oName = element.getAttribute("name");
+            if (oName.length() == 0) {
+                continue;
+            }
+
+            List<Facet> facets = new ArrayList<Facet>();
+
+            NodeList facetNodes = element.getElementsByTagNameNS(
+                ArtifactNamespaceContext.NAMESPACE_URI,
+                "facet");
+
+            for (int j = 0, M = facetNodes.getLength(); j < M; ++j) {
+                Element facetElement = (Element)facetNodes.item(j);
+
+                String fName = facetElement.getAttribute("name");
+
+                int index;
+                try {
+                    index = Integer.parseInt(facetElement.getAttribute("index"));
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn(nfe);
+                    index = 0;
+                }
+                facets.add(new DefaultFacet(index, fName, ""));
+            }
+
+            if (!facets.isEmpty()) {
+                result.put(oName, facets);
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * Insert new data included in <code>input</code> into the current state.
+     *
+     * @param target XML document that contains new data.
+     * @param context The CallContext.
+     *
+     * @return a document that contains a SUCCESS or FAILURE message.
+     */
+    @Override
+    public Document feed(Document target, CallContext context) {
+        logger.info("FLYSArtifact.feed()");
+
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element result = creator.create("result");
+        doc.appendChild(result);
+
+        try {
+            saveData(target, XPATH_FEED_INPUT, context);
+
+            compute(context, ComputeType.FEED, true);
+
+            return describe(target, context);
+        }
+        catch (IllegalArgumentException iae) {
+            // do not store state if validation fails.
+            context.afterCall(CallContext.NOTHING);
+            creator.addAttr(result, "type", OPERATION_FAILED, true);
+
+            result.setTextContent(iae.getMessage());
+        }
+
+        return doc;
+    }
+
+
+    /**
+     * This method handles request for changing the current state of an
+     * artifact. It is possible to step forward or backward.
+     *
+     * @param target The incoming ADVANCE document.
+     * @param context The CallContext.
+     *
+     * @return a document that contains a SUCCESS or FAILURE message.
+     */
+    public Document advance(Document target, CallContext context) {
+        Document doc = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element result = ec.create("result");
+
+        String currentStateId = getCurrentStateId();
+        String targetState    = XMLUtils.xpathString(
+            target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE);
+
+        logger.info("FLYSArtifact.advance() to '" + targetState + "'");
+
+        if (!currentStateId.equals(targetState)
+            && isStateReachable(targetState, context))
+        {
+            logger.info("Advance: Step forward");
+
+            List<String> prev = getPreviousStateIds();
+            prev.add(currentStateId);
+
+            setCurrentStateId(targetState);
+
+            logger.debug("Compute data for state: " + targetState);
+            compute(context, ComputeType.ADVANCE, true);
+
+            return describe(target, context);
+        }
+        else if (isPreviousState(targetState, context)) {
+            logger.info("Advance: Step back to");
+
+            List<String> prevs   = getPreviousStateIds();
+            int targetIdx        = prevs.indexOf(targetState);
+            int start            = prevs.size() - 1;
+
+            destroyStates(prevs, context);
+
+            for (int i = start; i >= targetIdx; i--) {
+                String prev = prevs.get(i);
+                logger.debug("Remove state id '" + prev + "'");
+
+                prevs.remove(prev);
+                facets.remove(prev);
+            }
+
+            destroyState(getCurrentStateId(), context);
+            setCurrentStateId(targetState);
+
+            return describe(target, context);
+        }
+
+        logger.warn("Advance: Cannot advance to '" + targetState + "'");
+        ec.addAttr(result, "type", OPERATION_FAILED, true);
+
+        doc.appendChild(result);
+
+        return doc;
+    }
+
+
+    /**
+     * Returns the identifier of the current state.
+     *
+     * @return the identifier of the current state.
+     */
+    public String getCurrentStateId() {
+        return currentStateId;
+    }
+
+
+    /**
+     * Sets the identifier of the current state.
+     *
+     * @param id the identifier of a state.
+     */
+    protected void setCurrentStateId(String id) {
+        currentStateId = id;
+    }
+
+
+    /**
+     * Set the current state of this artifact. <b>NOTE</b>We don't store the
+     * State object itself - which is not necessary - but its identifier. So
+     * this method will just call the setCurrentStateId() method with the
+     * identifier of <i>state</i>.
+     *
+     * @param state The new current state.
+     */
+    protected void setCurrentState(State state) {
+        setCurrentStateId(state.getID());
+    }
+
+
+    /**
+     * Returns the current state of the artifact.
+     *
+     * @return the current State of the artifact.
+     */
+    public State getCurrentState(Object context) {
+        return getState(context, getCurrentStateId());
+    }
+
+
+    /**
+     * Get list of existant states for this Artifact.
+     * @param context Contex to get StateEngine from.
+     * @return list of states.
+     */
+    protected List<State> getStates(Object context) {
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+        return engine.getStates(getName());
+    }
+
+
+    /**
+     * Get state with given ID.
+     * @param context Context to get StateEngine from.
+     * @param stateID ID of state to get.
+     * @return state with given ID.
+     */
+    protected State getState(Object context, String stateID) {
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+        return engine.getState(stateID);
+    }
+
+
+    /**
+     * Returns the vector of previous state identifiers.
+     *
+     * @return the vector of previous state identifiers.
+     */
+    protected List<String> getPreviousStateIds() {
+        return previousStateIds;
+    }
+
+
+    /**
+     * Get all previous and the current state id.
+     * @return #getPreviousStateIds() + #getCurrentStateId()
+     */
+    public List<String> getStateHistoryIds() {
+        ArrayList<String> prevIds = (ArrayList) getPreviousStateIds();
+        ArrayList<String> allIds  = (ArrayList) prevIds.clone();
+
+        allIds.add(getCurrentStateId());
+        return allIds;
+    }
+
+
+    /**
+     * Adds a new StateData item to the data pool of this artifact.
+     *
+     * @param name the name of the data object.
+     * @param data the data object itself.
+     */
+    protected void addData(String name, StateData data) {
+        this.data.put(name, data);
+    }
+
+
+    protected StateData removeData(String name) {
+        return this.data.remove(name);
+    }
+
+
+    /**
+     * This method returns a specific StateData object that is stored in the
+     * data pool of this artifact.
+     *
+     * @param name The name of the data object.
+     *
+     * @return the StateData object if existing, otherwise null.
+     */
+    public StateData getData(String name) {
+        return data.get(name);
+    }
+
+
+    /** Return named data item, null if not found. */
+    public String getDataAsString(String name) {
+        StateData data = getData(name);
+        return data != null ? (String) data.getValue() : null;
+    }
+
+
+    /**
+     * This method returns the value of a StateData object stored in the data
+     * pool of this Artifact as Integer.
+     *
+     * @param name The name of the StateData object.
+     *
+     * @return an Integer representing the value of the data object or null if
+     * no object was found for <i>name</i>.
+     *
+     * @throws NumberFormatException if the value of the data object could not
+     * be transformed into an Integer.
+     */
+    public Integer getDataAsInteger(String name)
+    throws NumberFormatException
+    {
+        String value = getDataAsString(name);
+
+        if (value != null && value.length() > 0) {
+            return Integer.parseInt(value);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * This method returns the value of a StateData object stored in the data
+     * pool of this Artifact as Double.
+     *
+     * @param name The name of the StateData object.
+     *
+     * @return an Double representing the value of the data object or null if
+     * no object was found for <i>name</i>.
+     *
+     * @throws NumberFormatException if the value of the data object could not
+     * be transformed into a Double.
+     */
+    public Double getDataAsDouble(String name)
+    throws NumberFormatException
+    {
+        String value = getDataAsString(name);
+
+        if (value != null && value.length() > 0) {
+            return Double.parseDouble(value);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * This method returns the value of a StateData object stored in the data
+     * pool of this Artifact as Long.
+     *
+     * @param name The name of the StateData object.
+     *
+     * @return a Long representing the value of the data object or null if
+     * no object was found for <i>name</i>.
+     *
+     * @throws NumberFormatException if the value of the data object could not
+     * be transformed into a Long.
+     */
+    public Long getDataAsLong(String name)
+    throws NumberFormatException
+    {
+        String value = getDataAsString(name);
+
+        if (value != null && value.length() > 0) {
+            return Long.parseLong(value);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * This method returns the value of a StateData object stored in the data
+     * pool of this Artifact is Boolean using Boolean.valueOf().
+     *
+     * @param name The name of the StateData object.
+     *
+     * @return a Boolean representing the value of the data object or null if no
+     * such object is existing.
+     */
+    public Boolean getDataAsBoolean(String name) {
+        String value = getDataAsString(name);
+
+        if (value == null || value.length() == 0) {
+            return null;
+        }
+
+        return Boolean.valueOf(value);
+    }
+
+
+    /**
+     * Add StateData containing a given string.
+     * @param name Name of the data object.
+     * @param value String to store.
+     */
+    public void addStringData(String name, String value) {
+        addData(name, new DefaultStateData(name, null, null, value));
+    }
+
+
+    public Collection<StateData> getAllData() {
+        return data.values();
+    }
+
+
+    public List<Facet> getFacets() {
+        List<Facet> all = new ArrayList<Facet>();
+
+        Set<Map.Entry<String, List<Facet>>> entries = facets.entrySet();
+        for (Map.Entry<String, List<Facet>> entry: entries) {
+            List<Facet> fs = entry.getValue();
+            for (Facet f: fs) {
+                all.add(f);
+            }
+        }
+
+        return all;
+    }
+
+
+    /**
+     * Get facet as stored internally, with equalling name and index than given
+     * facet.
+     * @param facet that defines index and name of facet searched.
+     * @return facet instance or null if not found.
+     */
+    public Facet getNativeFacet(Facet facet) {
+        String name  = facet.getName();
+        int    index = facet.getIndex();
+
+        for (Map.Entry<String, List<Facet>> facetList: facets.entrySet()) {
+            for (Facet f: facetList.getValue()) {
+                if (f.getIndex() == index && f.getName().equals(name)) {
+                    return f;
+                }
+            }
+        }
+
+        logger.warn("Could not find facet: " + name + " at " + index);
+        return null;
+    }
+
+
+    /**
+     * This method stores the data that is contained in the FEED document.
+     *
+     * @param feed The FEED document.
+     * @param xpath The XPath that points to the data nodes.
+     */
+    public void saveData(Document feed, String xpath, CallContext context)
+    throws IllegalArgumentException
+    {
+        if (feed == null || xpath == null || xpath.length() == 0) {
+            throw new IllegalArgumentException("error_feed_no_data");
+        }
+
+        NodeList nodes = (NodeList) XMLUtils.xpath(
+            feed,
+            xpath,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (nodes == null || nodes.getLength() == 0) {
+            throw new IllegalArgumentException("error_feed_no_data");
+        }
+
+        int count = nodes.getLength();
+        logger.debug("Try to save " + count + " data items.");
+
+        String uri = ArtifactNamespaceContext.NAMESPACE_URI;
+
+        DefaultState current = (DefaultState) getCurrentState(context);
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (int i = 0; i < count; i++) {
+            Element node = (Element)nodes.item(i);
+
+            String name  = node.getAttributeNS(uri, "name");
+            String value = node.getAttributeNS(uri, "value");
+
+            if (name.length() > 0 && value.length() > 0) {
+                logger.debug("Save data item for '" + name + "' : " + value);
+
+                StateData model = engine.getStateData(getName(), name);
+
+                StateData sd = model != null
+                    ? model.deepCopy()
+                    : new DefaultStateData(name, null, null, value);
+
+                addData(
+                    name, current.transform(this, context, sd, name, value));
+            }
+            else if (name.length() > 0 && value.length() == 0) {
+                if (removeData(name) != null) {
+                    logger.debug("Removed data '" + name + "' successfully.");
+                }
+            }
+        }
+
+        current.validate(this);
+    }
+
+
+    /**
+     * Determines if the state with the identifier <i>stateId</i> is reachable
+     * from the current state. The determination itself takes place in the
+     * TransitionEngine.
+     *
+     * @param stateId The identifier of a state.
+     * @param context The context object.
+     *
+     * @return true, if the state specified by <i>stateId</i> is reacahble,
+     * otherwise false.
+     */
+    protected boolean isStateReachable(String stateId, Object context) {
+        logger.debug("Determine if the state '" + stateId + "' is reachable.");
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        State currentState  = getCurrentState(context);
+        StateEngine sEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine tEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        return tEngine.isStateReachable(this, stateId, currentState, sEngine);
+    }
+
+
+    /**
+     * Determines if the state with the identifier <i>stateId</i> is a previous
+     * state of the current state.
+     *
+     * @param stateId The target state identifier.
+     * @param context The context object.
+     */
+    protected boolean isPreviousState(String stateId, Object context) {
+        logger.debug("Determine if the state '" + stateId + "' is old.");
+
+        List<String> prevs = getPreviousStateIds();
+        if (prevs.contains(stateId)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Computes the hash code of the entered values.
+     *
+     * @return a hash code.
+     */
+    @Override
+    public String hash() {
+        Set<Map.Entry<String, StateData>> entries = data.entrySet();
+
+        long hash  = 0L;
+        int  shift = 3;
+
+        for (Map.Entry<String, StateData> entry: entries) {
+            String key   = entry.getKey();
+            Object value = entry.getValue().getValue();
+
+            hash ^= ((long)key.hashCode() << shift)
+                 |  ((long)value.hashCode() << (shift + 3));
+            shift += 2;
+        }
+
+        return getCurrentStateId() + hash;
+    }
+
+
+    /**
+     * Return List of outputs, where combinations of outputname and filtername
+     * that match content in filterFacets is left out.
+     * @return filtered Outputlist.
+     */
+    protected List<Output> filterOutputs(List<Output> outs) {
+        if (filterFacets == null || filterFacets.isEmpty()) {
+            logger.debug("No filter for Outputs.");
+            return outs;
+        }
+
+        logger.debug("Filter Facets with " + filterFacets.size() + " filters.");
+
+        List<Output> filtered = new ArrayList<Output>();
+
+        for (Output out: outs) {
+            String outName = out.getName();
+
+            logger.debug("  filter Facets for Output: " + outName);
+
+            List<Facet> fFacets = filterFacets.get(outName);
+            if (fFacets != null) {
+                logger.debug("" + fFacets.size() + " filters for: " + outName);
+
+                if (logger.isDebugEnabled()) {
+                    for (Facet tmp: fFacets) {
+                        logger.debug("   filter = '" + tmp.getName() + "'");
+                    }
+                }
+
+                List<Facet> resultFacets = new ArrayList<Facet>();
+
+                for (Facet facet: out.getFacets()) {
+                    for (Facet fFacet: fFacets) {
+                        if (facet.getIndex() == fFacet.getIndex()
+                        &&  facet.getName().equals(fFacet.getName())) {
+                            resultFacets.add(facet);
+                            break;
+                        }
+                    }
+                }
+
+                logger.debug("Facets after filtering = " + resultFacets.size());
+
+                if (!resultFacets.isEmpty()) {
+                    DefaultOutput nout = new DefaultOutput(
+                        out.getName(),
+                        out.getDescription(),
+                        out.getMimeType(),
+                        resultFacets);
+                    filtered.add(nout);
+                }
+            }
+        }
+
+        logger.debug("All Facets after filtering = " + filtered.size());
+
+        return filtered;
+    }
+
+
+    /**
+     * Get all outputs that the Artifact can do in this state (which includes
+     * all previous states).
+     *
+     * @return list of outputs
+     */
+    public List<Output> getOutputs(Object context) {
+        logger.debug("##### Get Outputs for: " + identifier() + " #####");
+
+        dumpArtifact();
+
+        List<String> stateIds  = getPreviousStateIds();
+        List<Output> generated = new ArrayList<Output>();
+
+        for (String stateId: stateIds) {
+            DefaultState state = (DefaultState) getState(context, stateId);
+            generated.addAll(getOutputForState(state));
+        }
+
+        generated.addAll(getCurrentOutputs(context));
+
+        return filterOutputs(generated);
+    }
+
+
+    /**
+     * Get output(s) for current state.
+     * @return list of outputs for current state.
+     */
+    public List<Output> getCurrentOutputs(Object context) {
+        DefaultState cur = (DefaultState) getCurrentState(context);
+
+        try {
+            if (cur.validate(this)) {
+                return getOutputForState(cur);
+            }
+        }
+        catch (IllegalArgumentException iae) { }
+
+        return new ArrayList<Output>();
+    }
+
+
+    /**
+     * Get output(s) for a specific state.
+     * @param state State of interest
+     * @return list of output(s) for given state.
+     */
+    protected List<Output> getOutputForState(DefaultState state) {
+        logger.debug("Find Outputs for State: " + state.getID());
+
+        List<Output> list = state.getOutputs();
+        if (list == null || list.size() == 0) {
+            logger.debug("-> No output modes for this state.");
+            return new ArrayList<Output>();
+        }
+
+        String stateId = state.getID();
+
+        List<Facet> fs = facets.get(stateId);
+
+        if (fs == null || fs.size() == 0) {
+            logger.debug("No facets found.");
+            return new ArrayList<Output>();
+        }
+
+        List<Output> gen = generateOutputs(list, fs);
+
+        logger.debug("State '" + stateId + "' has " + gen.size() + " outs");
+
+        return gen;
+    }
+
+
+    /**
+     * Generate a list of outputs with facets from fs if type is found in list
+     * of output.
+     *
+     * @param list List of outputs
+     * @param fs List of facets
+     */
+    protected List<Output> generateOutputs(List<Output> list, List<Facet> fs) {
+        List<Output> generated = new ArrayList<Output>();
+
+        boolean debug = logger.isDebugEnabled();
+
+        for (Output out: list) {
+            Output o = new DefaultOutput(
+                out.getName(),
+                out.getDescription(),
+                out.getMimeType(),
+                out.getType());
+
+            Set<String> outTypes = new HashSet<String>();
+
+            for (Facet f: out.getFacets()) {
+                if (outTypes.add(f.getName()) && debug) {
+                    logger.debug("configured facet " + f);
+                }
+            }
+
+            boolean facetAdded = false;
+            for (Facet f: fs) {
+                String type = f.getName();
+
+                if (outTypes.contains(type)) {
+                    if (debug) {
+                        logger.debug("Add facet " + f);
+                    }
+                    facetAdded = true;
+                    o.addFacet(f);
+                }
+            }
+
+            if (facetAdded) {
+                generated.add(o);
+            }
+        }
+
+        return generated;
+    }
+
+
+    /**
+     * Dispatches the computation request to compute(CallContext context, String
+     * hash) with the current hash value of the artifact which is provided by
+     * hash().
+     *
+     * @param context The CallContext.
+     */
+    public Object compute(
+        CallContext context,
+        ComputeType type,
+        boolean     generateFacets
+    ) {
+        return compute(context, hash(), type, generateFacets);
+    }
+
+
+    /**
+     * Dispatches computation requests to the current state which needs to
+     * implement a createComputeCallback(String hash, FLYSArtifact artifact)
+     * method.
+     *
+     * @param context The CallContext.
+     * @param hash The hash value which is used to fetch computed data from
+     * cache.
+     *
+     * @return the computed data.
+     */
+    public Object compute(
+        CallContext context,
+        String      hash,
+        ComputeType type,
+        boolean     generateFacets
+    ) {
+        DefaultState current = (DefaultState) getCurrentState(context);
+        return compute(context, hash, current, type, generateFacets);
+    }
+
+
+    /**
+     * Like compute, but identify State by it id (string).
+     */
+    public Object compute(
+        CallContext context,
+        String      hash,
+        String      stateID,
+        ComputeType type,
+        boolean     generateFacets
+    ) {
+        DefaultState current =
+            (stateID == null)
+            ? (DefaultState)getCurrentState(context)
+            : (DefaultState)getState(context, stateID);
+
+        if (hash == null) {
+            hash = hash();
+        }
+
+        return compute(context, hash, current, type, generateFacets);
+    }
+
+
+    /**
+     * Let current state compute and register facets.
+     *
+     * @param key key of state
+     * @param state state
+     * @param type Type of compute
+     * @param generateFacets Whether new facets shall be generated.
+     */
+    public Object compute(
+        CallContext   context,
+        String        key,
+        DefaultState  state,
+        ComputeType   type,
+        boolean       generateFacets
+    ) {
+        String stateID = state.getID();
+
+        List<Facet> fs = (generateFacets) ? new ArrayList<Facet>() : null;
+
+        try {
+            Cache cache = CacheFactory.getCache(COMPUTING_CACHE);
+
+            Object old = null;
+
+            if (cache != null) {
+                net.sf.ehcache.Element element = cache.get(key);
+                if (element != null) {
+                    logger.debug("Got computation result from cache.");
+                    old = element.getValue();
+                }
+            }
+
+            Object res;
+            switch (type) {
+                case FEED:
+                    res = state.computeFeed(this, key, context, fs, old);
+                    break;
+                case ADVANCE:
+                    res = state.computeAdvance(this, key, context, fs, old);
+                    break;
+                case INIT:
+                    res = state.computeInit(this, key, context, context.getMeta(), fs);
+                default:
+                    res = null;
+            }
+
+            if (cache != null && old != res && res != null) {
+                logger.debug("Store computation result to cache.");
+                net.sf.ehcache.Element element =
+                    new net.sf.ehcache.Element(key, res);
+                cache.put(element);
+            }
+
+            return res;
+        }
+        finally {
+            if (generateFacets) {
+                if (fs.isEmpty()) {
+                    facets.remove(stateID);
+                }
+                else {
+                    facets.put(stateID, fs);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Method to dump the artifacts state/data.
+     */
+    protected void dumpArtifact() {
+        if (logger.isDebugEnabled()) {
+            logger.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++");
+            // Include uuid, type, name
+
+            logger.debug("------ DUMP DATA ------");
+            Collection<StateData> allData = data.values();
+
+            for (StateData d: allData) {
+                String name  = d.getName();
+                String value = (String) d.getValue();
+
+                logger.debug("- " + name + ": " + value);
+            }
+
+            logger.debug("------ DUMP PREVIOUS STATES ------");
+            List<String> stateIds = getPreviousStateIds();
+
+            for (String id: stateIds) {
+                logger.debug("- State: " + id);
+            }
+
+            logger.debug("CURRENT STATE: " + getCurrentStateId());
+
+            debugFacets();
+            dumpFilterFacets();
+
+            logger.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++");
+        }
+    }
+
+
+    protected void debugFacets() {
+        logger.debug("######### FACETS #########");
+        Set<Map.Entry<String, List<Facet>>> entries = facets.entrySet();
+
+        for (Map.Entry<String, List<Facet>> entry: entries) {
+            String out = entry.getKey();
+            List<Facet> fs = entry.getValue();
+            for (Facet f: fs) {
+                logger.debug("  # " + out + " : " + f.getName());
+            }
+        }
+
+        logger.debug("######## FACETS END ########");
+    }
+
+
+    protected void dumpFilterFacets() {
+        logger.debug("######## FILTER FACETS ########");
+
+        if (filterFacets == null || filterFacets.isEmpty()) {
+            logger.debug("No Filter Facets defined.");
+            return;
+        }
+
+        Set<Map.Entry<String, List<Facet>>> entries = filterFacets.entrySet();
+        for (Map.Entry<String, List<Facet>> entry: entries) {
+            String      out     = entry.getKey();
+            List<Facet> filters = entry.getValue();
+
+            logger.debug("There are " + filters.size() + " filters for: " +out);
+
+            for (Facet filter: filters) {
+                logger.debug("  filter: " + filter.getName());
+            }
+        }
+
+        logger.debug("######## FILTER FACETS END ########");
+    }
+
+
+    protected void destroyState(String id, Object context) {
+        State s = getState(context, id);
+        s.endOfLife(this, context);
+    }
+
+
+    /**
+     * Calls endOfLife() for each state in the list <i>ids</i>.
+     *
+     * @param ids The State IDs that should be destroyed.
+     * @param context The FLYSContext.
+     */
+    protected void destroyStates(List<String> ids, Object context) {
+        for (int i = 0, num = ids.size(); i < num; i++) {
+            destroyState(ids.get(i), context);
+        }
+    }
+
+
+    /**
+     * Destroy the states.
+     */
+    @Override
+    public void endOfLife(Object context) {
+        logger.info("FLYSArtifact.endOfLife: " + identifier());
+
+        ArrayList<String> ids       = (ArrayList<String>) getPreviousStateIds();
+        ArrayList<String> toDestroy = (ArrayList<String>) ids.clone();
+
+        toDestroy.add(getCurrentStateId());
+
+        destroyStates(toDestroy, context);
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName  name of the facet.
+     * @param outputName name of the output.
+     * @param index      index of the facet.
+     *
+     * @return 1 if wished to be initally active, 0 if not. FLYSArtifact
+     *         defaults to "1".
+     */
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 1;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FixationArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,176 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.states.DefaultState;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+/**
+ * The default fixation analysis artifact.
+ *
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FixationArtifact
+extends      FLYSArtifact
+implements   FacetTypes {
+
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(FixationArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "fixanalysis";
+
+    /**
+     * The default constructor.
+     */
+    public FixationArtifact() {
+    }
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        if (logger.isDebugEnabled()) {
+            dumpArtifact();
+        }
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        List<State> reachable = transitionEngine.getReachableStates(
+            this, getCurrentState(context), stateEngine);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+        ProtocolUtils.appendReachableStates(creator, root, reachable);
+
+        Element ui = ProtocolUtils.createArtNode(
+            creator, "ui", null, null);
+
+        Element staticUI  = ProtocolUtils.createArtNode(
+            creator, "static", null, null);
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context, identifier());
+
+        appendStaticUI(description, staticUI, context, identifier());
+
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element dynamic = current.describe(
+            this,
+            description,
+            root,
+            context,
+            identifier());
+
+        if (dynamic != null) {
+            ui.appendChild(dynamic);
+        }
+
+        ui.appendChild(staticUI);
+
+        root.appendChild(name);
+        root.appendChild(ui);
+        root.appendChild(outs);
+
+        return description;
+    }
+
+
+    /**
+     * Append output mode nodes to a document.
+     */
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context,
+        String      uuid)
+    {
+        List<Output> generated = getOutputs(context);
+        logger.debug("This Artifact has " + generated.size() + " Outputs.");
+
+        ProtocolUtils.appendOutputModes(doc, outs, generated);
+    }
+
+
+    /**
+     * This method appends the static data - that has already been inserted by
+     * the user - to the static node of the DESCRIBE document.
+     *
+     * @param doc The document.
+     * @param ui The root node.
+     * @param context The CallContext.
+     * @param uuid The identifier of the artifact.
+     */
+    protected void appendStaticUI(
+        Document    doc,
+        Node        ui,
+        CallContext context,
+        String uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append static data for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
+        }
+    }
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FixationArtifactAccess.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,384 @@
+package de.intevation.flys.artifacts;
+
+import java.util.Arrays;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import gnu.trove.TDoubleArrayList;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import org.apache.log4j.Logger;
+
+public class FixationArtifactAccess
+{
+    private static Logger log = Logger.getLogger(FixationArtifactAccess.class);
+
+    protected FLYSArtifact artifact;
+
+    protected String river;
+
+    protected String calculationMode;
+
+    protected Double from;
+    protected Double to;
+    protected Double step;
+
+    protected Long start;
+    protected Long end;
+
+    protected Integer qSectorStart;
+    protected Integer qSectorEnd;
+
+    protected Long referenceStart;
+    protected Long referenceEnd;
+
+    protected long [][] analysisPeriods;
+
+    protected int [] events;
+
+    protected Boolean preprocessing;
+
+    protected String  function;
+
+    protected double [] qs;
+
+    public FixationArtifactAccess() {
+    }
+
+    public FixationArtifactAccess(FLYSArtifact artifact) {
+        this.artifact = artifact;
+    }
+
+    public FLYSArtifact getArtifact() {
+        return artifact;
+    }
+
+    public String getRiver() {
+        if (river == null) {
+            StateData sd = artifact.getData("river");
+            if (sd == null) {
+                log.warn("missing 'river' value");
+                return null;
+            }
+            river = (String)sd.getValue();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("river: '" + river + "'");
+        }
+        return river;
+    }
+
+    public String getCalculationMode() {
+        if (calculationMode == null) {
+            StateData sd = artifact.getData("calculation.mode");
+            if (sd == null) {
+                log.warn("missing 'calculation.mode' value");
+                return null;
+            }
+            calculationMode = (String)sd.getValue();
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("calculationMode: '" + calculationMode + "'");
+        }
+        return calculationMode;
+    }
+
+    public Double getFrom() {
+
+        if (from == null) {
+            StateData sd = artifact.getData("from");
+            if (sd == null) {
+                log.warn("missing 'from' value");
+                return null;
+            }
+            try {
+                from = Double.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("from '" + sd.getValue() + "' is not numeric.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("from: '" + from + "'");
+        }
+
+        return from;
+    }
+
+    public Double getTo() {
+
+        if (to == null) {
+            StateData sd = artifact.getData("to");
+            if (sd == null) {
+                log.warn("missing 'to' value");
+                return null;
+            }
+            try {
+                to = Double.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("to '" + sd.getValue() + "' is not numeric.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("to: '" + to + "'");
+        }
+
+        return to;
+    }
+
+    public Double getStep() {
+
+        if (step == null) {
+            StateData sd = artifact.getData("step");
+            if (sd == null) {
+                log.warn("missing 'step' value");
+                return null;
+            }
+            try {
+                step = Double.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("step '" + sd.getValue() + "' is not numeric.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("step: '" + step + "'");
+        }
+
+        return step;
+    }
+
+    public Long getStart() {
+
+        if (start == null) {
+            StateData sd = artifact.getData("start");
+            if (sd == null) {
+                log.warn("missing 'start' value");
+                return null;
+            }
+            try {
+                start = Long.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("start '" + sd.getValue() + "' is not an integer.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("start: '" + start + "'");
+        }
+
+        return start;
+    }
+
+    public Long getEnd() {
+
+        if (end == null) {
+            StateData sd = artifact.getData("end");
+            if (sd == null) {
+                log.warn("missing 'end' value");
+                return null;
+            }
+            try {
+                end = Long.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("end '" + sd.getValue() + "' is not an integer.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("end: '" + end + "'");
+        }
+
+        return end;
+    }
+
+    public Integer getQSectorStart() {
+
+        if (qSectorStart == null) {
+            StateData sd = artifact.getData("q1");
+            if (sd == null) {
+                log.warn("missing 'q1' value");
+                return null;
+            }
+            try {
+                qSectorStart = Integer.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("q1 '" + sd.getValue() + "' is not an integer.");
+            }
+        }
+
+        return qSectorStart;
+    }
+
+    public Integer getQSectorEnd() {
+
+        if (qSectorEnd == null) {
+            StateData sd = artifact.getData("q2");
+            if (sd == null) {
+                log.warn("missing 'q2' value");
+                return null;
+            }
+            try {
+                qSectorEnd = Integer.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("q2 '" + sd.getValue() + "' is not an integer.");
+            }
+        }
+
+        return qSectorEnd;
+    }
+
+    public int [] getEvents() {
+        if (events == null) {
+            StateData sd = artifact.getData("events");
+            if (sd == null) {
+                log.warn("missing 'events' value");
+                return null;
+            }
+            events = FLYSUtils.intArrayFromString((String)sd.getValue());
+        }
+        return events;
+    }
+
+    public Long getReferenceStart() {
+        if (referenceStart == null) {
+            StateData sd = artifact.getData("ref_start");
+            if (sd == null) {
+                log.warn("missing 'ref_start' value");
+                return null;
+            }
+            try {
+                referenceStart = Long.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("ref_start '"
+                    + sd.getValue() + "' is not an integer.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("referenceStart: '" + referenceStart + "'");
+        }
+
+        return referenceStart;
+    }
+
+    public Long getReferenceEnd() {
+        if (referenceEnd == null) {
+            StateData sd = artifact.getData("ref_end");
+            if (sd == null) {
+                log.warn("missing 'ref_end' value");
+                return null;
+            }
+            try {
+                referenceEnd = Long.valueOf((String)sd.getValue());
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("ref_end '"
+                    + sd.getValue() + "' is not an integer.");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("referenceEnd: '" + referenceEnd + "'");
+        }
+
+        return referenceEnd;
+    }
+
+    public long [][] getAnalysisPeriods() {
+        if (analysisPeriods == null) {
+            /** TODO: Use real arrays here! */
+            StateData sdStart = artifact.getData("ana_start");
+            StateData sdEnd   = artifact.getData("ana_end");
+
+            if (sdStart == null || sdEnd == null) {
+                log.warn("missing 'ana_start' or 'ana_end'");
+                return null;
+            }
+
+            try {
+                analysisPeriods = new long [][] {
+                    { Long.parseLong((String)sdStart.getValue()),
+                      Long.parseLong((String)sdEnd.getValue()) }
+                };
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("'ana_start' or 'ana_end' is not an integer.");
+                return null;
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            for (int i = 0; i < analysisPeriods.length; ++i) {
+                long [] ap = analysisPeriods[i];
+                log.debug("analysis period " + ap[0] + " - " + ap[1]);
+            }
+        }
+
+        return analysisPeriods;
+    }
+
+    public Boolean getPreprocessing() {
+        if (preprocessing == null) {
+            StateData sd = artifact.getData("preprocessing");
+            if (sd == null) {
+                log.warn("missing 'preprocessing'");
+                return null;
+            }
+            preprocessing = Boolean.valueOf((String)sd.getValue());
+        }
+        return preprocessing;
+    }
+
+    public String getFunction() {
+        if (function == null) {
+            StateData sd = artifact.getData("function");
+            if (sd == null) {
+                log.warn("missing 'function'");
+                return null;
+            }
+            function = (String)sd.getValue();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("function: " + function);
+        }
+        return function;
+    }
+
+    public double [] getQs() {
+        if (qs == null) {
+            StateData sd = artifact.getData("qs");
+            if (sd == null) {
+                log.warn("missing 'qs'");
+                return null;
+            }
+            String [] parts = ((String)sd.getValue()).split("[\\s;]");
+            TDoubleArrayList list = new TDoubleArrayList(parts.length);
+            for (String part: parts) {
+                try {
+                    list.add(Double.parseDouble(part));
+                }
+                catch (NumberFormatException nfe) {
+                    log.warn("'" + part + "' is not numeric.");
+                }
+            }
+            qs = list.toNativeArray();
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("qs: " + Arrays.toString(qs));
+        }
+        return qs;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/GaugeDischargeArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,199 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+
+import de.intevation.flys.artifacts.StaticFLYSArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+import de.intevation.flys.artifacts.model.Calculation;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+import de.intevation.flys.artifacts.model.DischargeTables;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * Artifact to store user-added points.
+ */
+public class GaugeDischargeArtifact
+extends      WINFOArtifact
+implements   FacetTypes
+{
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(GaugeDischargeArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "gaugedischarge";
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public GaugeDischargeArtifact() {
+        logger.debug("GaugeDischargeArtifact.HistoricalQArtifact()");
+    }
+
+
+    /**
+     * Gets called from factory, to set things up.
+     * Especially, when loaded via datacage mechanisms, provide the
+     * data document.
+     * @param data filled with stuff from dc, if any.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("GaugeDischargeArtifact.setup");
+        super.setup(identifier, factory, context, callMeta, data);
+        String ids = StaticFLYSArtifact.getDatacageIDValue(data);
+        logger.debug("id for gaugedischarge: " + ids);
+    }
+
+
+    /** Return the name of this artifact. */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    /** Access state data storing the jsonstring with points. 
+    public String getPointsData(String facetName) {
+        return getDataAsString(facetName + ".data");
+    }*/
+
+
+    /**
+     * Setup state and facet, copy from master artifact.
+     */
+    @Override
+    protected void initialize(Artifact art, Object context, CallMeta meta) {
+        logger.debug("GaugeDischargeArtifact.initialize");
+        List<Facet> fs = new ArrayList<Facet>();
+        FLYSArtifact artifact = (FLYSArtifact) art;
+
+        // Get the location(s)
+        //importData(artifact, "ld_mode");
+        //importData(artifact, "ld_from");
+        //importData(artifact, "ld_to");
+        //importData(artifact, "ld_locations");
+        addStringData("ld_from", "0");
+        addStringData("ld_to", "1000");
+        addStringData("ld_mode", "distance");
+
+
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, meta, fs);
+        if (!fs.isEmpty()) { 
+            logger.debug("Facets to add in GaugeDischargeArtifact.initialize. ("
+                + state.getID() + ")."); 
+            facets.put(getCurrentStateId(), fs); 
+        } 
+        else { 
+            logger.debug("No facets to add in GaugeDischargeArtifact.initialize ("
+                + state.getID() + ").");
+        }
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     * @return 0 if not active
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 1;
+    }
+
+
+    /**
+     * Returns the data that is used to create discharge curves.
+     *
+     */
+    public CalculationResult getDischargeCurveData() {
+
+        River river = FLYSUtils.getRiver(this);
+        if (river == null) {
+            return error(new WQKms[0], "no.river.selected");
+        }
+
+        double [] distance = FLYSUtils.getKmRange(this);
+        logger.debug("getDischargeCurveData: get range");
+
+        if (distance == null) {
+            return error(new WQKms[0], "no.range.found");
+        }
+
+        List<Gauge> gauges = river.determineGauges(distance[0], distance[1]);
+        logger.debug("getDischargeCurveData: get gauges");
+
+        if (gauges.isEmpty()) {
+            return error(new WQKms[0], "no.gauge.selected");
+        }
+
+        String [] names = new String[gauges.size()];
+
+        for (int i = 0; i < names.length; ++i) {
+            names[i] = gauges.get(i).getName();
+            logger.debug("getDischargeCurveData: name " + names[i]);
+        }
+
+        DischargeTables dt = new DischargeTables(river.getName(), names);
+
+        Map<String, double [][]> map = dt.getValues(100d);
+
+        ArrayList<WQKms> res = new ArrayList<WQKms>();
+
+        for (Gauge gauge: gauges) {
+            String name = gauge.getName();
+            double [][] values = map.get(name);
+            if (values == null) {
+                continue;
+            }
+            double [] kms = new double[values[0].length];
+            Arrays.fill(kms, gauge.getStation().doubleValue());
+            res.add(new WQKms(kms, values[0], values[1], name));
+            logger.debug("getDischargeCurveData: add one res gauge");
+        }
+
+        return new CalculationResult(
+            res.toArray(new WQKms[res.size()]),
+            new Calculation());
+    }
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/HYKArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,139 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+
+/**
+ * Artifact to get hydr zones (HYKs).
+ */
+public class HYKArtifact extends StaticFLYSArtifact {
+
+    /** Name of Artifact. */
+    public static final String HYK_ARTIFACT_NAME = "hyk";
+
+    /** Name of data item keeping the hyk id to load formations from. */
+    public static final String HYK_ID = "hyk_artifact.data.id";
+
+    /** Name of data item keeping the km of cs master. */
+    public static final String HYK_KM = "hyk_artifact.data.km";
+
+    /** Own logger. */
+    private static final Logger logger =
+        Logger.getLogger(HYKArtifact.class);
+
+
+    /** Return given name. */
+    @Override
+    public String getName() {
+        return HYK_ARTIFACT_NAME;
+    }
+
+
+    /** Store ids, do super.setup. */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.info("HYKArtifact.setup");
+
+        String ids = getDatacageIDValue(data);
+
+        logger.info("HYKArtifact.setup: id is " + ids);
+
+        addStringData(HYK_ID, ids);
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    /** Set km as Data. */
+    public void setKm(double km) {
+        addStringData(HYK_KM, Double.toString(km));
+    }
+
+
+    /** Get km from state data. */
+    public double getKm() {
+        Double km = getDataAsDouble(HYK_KM);
+        if (km == null) {
+            // XXX returning 0 is to be compatible to older versions that had an
+            // own method getDataAsDouble that returned 0 if parsing the
+            // parameter failed.
+            return 0;
+        }
+        else {
+            return km;
+        }
+    }
+
+
+    /** Get hyk-id from state data. */
+    public int getHykId() {
+        return getDataAsInteger(HYK_ID);
+    }
+
+
+    /** Do not copy data from daddyfact. */
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object   context,
+        CallMeta callMeta)
+    {
+        logger.debug("HYKArtifact.initialize");
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        importData(winfo, "river");
+
+        List<Facet> fs = new ArrayList<Facet>();
+
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, callMeta, fs);
+        if (!fs.isEmpty()) { 
+            logger.debug("Facets to add in HYKArtifact.initialize ."); 
+            facets.put(getCurrentStateId(), fs); 
+        } 
+        else { 
+            logger.debug("No facets to add in HYKArtifact.initialize ("
+                + state.getID() + ").");
+        }
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     *
+     * @return Always 0. Hyk Data will enter plots inactive.
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 0;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/MINFOArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,333 @@
+package de.intevation.flys.artifacts;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.Message;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+import de.intevation.flys.artifacts.model.CalculationMessage;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.states.SoundingsSelect;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import gnu.trove.TIntArrayList;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * The default MINFO artifact.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MINFOArtifact
+extends      FLYSArtifact
+implements   FacetTypes {
+
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(MINFOArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "minfo";
+
+    /** XPath */
+    public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
+
+
+    /**
+     * The default constructor.
+     */
+    public MINFOArtifact() {
+    }
+
+
+    /**
+     * This method returns a description of this artifact.
+     *
+     * @param data Some data.
+     * @param context The CallContext.
+     *
+     * @return the description of this artifact.
+     */
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        List<State> reachable = transitionEngine.getReachableStates(
+            this, getCurrentState(context), stateEngine);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+        ProtocolUtils.appendReachableStates(creator, root, reachable);
+
+        appendBackgroundActivity(creator, root, context);
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element ui = ProtocolUtils.createArtNode(
+            creator, "ui", null, null);
+
+        Element staticUI  = ProtocolUtils.createArtNode(
+            creator, "static", null, null);
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context, identifier());
+
+        appendStaticUI(description, staticUI, context, identifier());
+
+        Element dynamic = current.describe(
+            this,
+            description,
+            root,
+            context,
+            identifier());
+
+        if (dynamic != null) {
+            ui.appendChild(dynamic);
+        }
+
+        ui.appendChild(staticUI);
+
+        root.appendChild(name);
+        root.appendChild(ui);
+        root.appendChild(outs);
+
+        return description;
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    protected static void appendBackgroundActivity(
+        ElementCreator cr,
+        Element        root,
+        CallContext    context
+    ) {
+        Element inBackground = cr.create("background-processing");
+        root.appendChild(inBackground);
+
+        cr.addAttr(
+            inBackground,
+            "value",
+            String.valueOf(context.isInBackground()),
+            true);
+
+        LinkedList<Message> messages = context.getBackgroundMessages();
+
+        if (messages == null) {
+            return;
+        }
+
+        CalculationMessage  message  = (CalculationMessage) messages.getLast();
+        cr.addAttr(
+            inBackground,
+            "steps",
+            String.valueOf(message.getSteps()),
+            true);
+
+        cr.addAttr(
+            inBackground,
+            "currentStep",
+            String.valueOf(message.getCurrentStep()),
+            true);
+
+        inBackground.setTextContent(message.getMessage());
+    }
+
+
+    /**
+     * Append output mode nodes to a document.
+     */
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context,
+        String      uuid)
+    {
+        List<Output> generated = getOutputs(context);
+        logger.debug("This Artifact has " + generated.size() + " Outputs.");
+
+        ProtocolUtils.appendOutputModes(doc, outs, generated);
+    }
+
+
+    /**
+     * This method appends the static data - that has already been inserted by
+     * the user - to the static node of the DESCRIBE document.
+     *
+     * @param doc The document.
+     * @param ui The root node.
+     * @param context The CallContext.
+     * @param uuid The identifier of the artifact.
+     */
+    protected void appendStaticUI(
+        Document    doc,
+        Node        ui,
+        CallContext context,
+        String uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append static data for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
+        }
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     * @return 0 if not active
+     */
+    @Override
+    public int getInitialFacetActivity(String outputName, String facetName, int index) {
+        logger.warn("MINFOArtifact.getInitialFacetActivity: not implemented!");
+        return 1;
+    }
+
+
+    public int[] getMainChannels() {
+        String data = getDataAsString("main.channel");
+
+        if (data == null) {
+            logger.warn("No 'main.channel' parameter specified!");
+            return null;
+        }
+
+        return FLYSUtils.intArrayFromString(data);
+    }
+
+
+    public int[] getTotalChannels() {
+        String data = getDataAsString("total.channel");
+
+        if (data == null) {
+            logger.warn("No 'total.channel' parameter specified!");
+            return null;
+        }
+
+        return FLYSUtils.intArrayFromString(data);
+    }
+
+
+    public int[] getBedHeightSingleIDs() {
+        String data = getDataAsString("soundings");
+
+        if (data == null) {
+            logger.warn("No 'soundings' parameter specified!");
+            return null;
+        }
+
+        String[] parts = data.split(";");
+
+        TIntArrayList ids = new TIntArrayList();
+
+        for (String part: parts) {
+            if (part.indexOf(SoundingsSelect.PREFIX_SINGLE) >= 0) {
+                String tmp = part.replace(SoundingsSelect.PREFIX_SINGLE, "");
+
+                try {
+                    ids.add(Integer.parseInt(tmp));
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn("Cannot parse int from string: '" + tmp + "'");
+                }
+            }
+        }
+
+        return ids.toNativeArray();
+    }
+
+
+    public int[] getBedHeightEpochIDs() {
+        String data = getDataAsString("soundings");
+
+        if (data == null) {
+            logger.warn("No 'soundings' parameter specified!");
+            return null;
+        }
+
+        String[] parts = data.split(";");
+
+        TIntArrayList ids = new TIntArrayList();
+
+        for (String part: parts) {
+            if (part.indexOf(SoundingsSelect.PREFIX_EPOCH) >= 0) {
+                String tmp = part.replace(SoundingsSelect.PREFIX_EPOCH, "");
+
+                try {
+                    ids.add(Integer.parseInt(tmp));
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn("Cannot parse int from string: '" + tmp + "'");
+                }
+            }
+        }
+
+        return ids.toNativeArray();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/MainValuesArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,349 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.Calculation;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.MainValuesQFacet;
+import de.intevation.flys.artifacts.model.MainValuesWFacet;
+import de.intevation.flys.artifacts.model.NamedDouble;
+import de.intevation.flys.artifacts.model.WstValueTable;
+import de.intevation.flys.artifacts.model.WstValueTableFactory;
+
+import de.intevation.flys.artifacts.states.StaticState;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * Artifact to access names of Points Of Interest along a segment of a river.
+ * This artifact neglects (Static)FLYSArtifacts capabilities of interaction
+ * with the StateEngine by overriding the getState*-methods.
+ */
+public class MainValuesArtifact
+extends      StaticFLYSArtifact
+implements   FacetTypes
+{
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(MainValuesArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "mainvalue";
+
+    /** The name of the static state for this artifact. */
+    public static final String STATIC_STATE_NAME = "state.mainvalue.static";
+
+    /** One and only state to be in. */
+    protected transient State state = null;
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public MainValuesArtifact() {
+        logger.debug("MainValuesArtifact.MainValuesartifact()");
+    }
+
+
+    /**
+     * Gets called from factory, to set things up.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("MainValuesArtifact.setup");
+        state = new StaticState(STATIC_STATE_NAME);
+
+        Facet qfacet0 = new MainValuesQFacet(
+            DURATION_MAINVALUES_Q,
+            Resources.getMsg(
+                callMeta,
+                "facet.discharge_curves.mainvalues.q",
+                "facet.discharge_curves.mainvalues.q"),
+            false);
+        Facet qfacet1 = new MainValuesQFacet(
+            COMPUTED_DISCHARGE_MAINVALUES_Q,
+            Resources.getMsg(
+                callMeta,
+                "facet.discharge_curves.mainvalues.q",
+                "facet.discharge_curves.mainvalues.q"),
+            false);
+        Facet qfacet2 = new MainValuesQFacet(
+            MAINVALUES_Q,
+            Resources.getMsg(
+                callMeta,
+                "facet.discharge_curves.mainvalues.q",
+                "facet.discharge_curves.mainvalues.q"),
+            true);
+        Facet wfacet1 = new MainValuesWFacet(
+            COMPUTED_DISCHARGE_MAINVALUES_W,
+            Resources.getMsg(
+                callMeta,
+                "facet.discharge_curves.mainvalues.w",
+                "facet.discharge_curves.mainvalues.w"),
+            false);
+        Facet wfacet2 = new MainValuesWFacet(
+            MAINVALUES_W,
+            Resources.getMsg(
+                callMeta,
+                "facet.discharge_curves.mainvalues.w",
+                "facet.discharge_curves.mainvalues.w"),
+            true);
+
+        List<Facet> fs = new ArrayList<Facet>();
+        fs.add(qfacet0);
+        fs.add(qfacet1);
+        fs.add(qfacet2);
+        fs.add(wfacet1);
+        fs.add(wfacet2);
+
+        facets.put(state.getID(), fs);
+        spawnState();
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    /**
+     * Create "the" state.
+     */
+    protected State spawnState() {
+        state = new StaticState(STATIC_STATE_NAME);
+        List<Facet> fs = (List<Facet>) facets.get(STATIC_STATE_NAME);
+
+        DefaultOutput mainValuesOutput = new DefaultOutput(
+            "computed_discharge_curve",
+            "output.computed_discharge_curve", "image/png",
+            fs,
+            "chart");
+
+        state.getOutputs().add(mainValuesOutput);
+        return state;
+    }
+
+
+    @Override
+    protected void initialize(Artifact artifact, Object context, CallMeta meta) {
+        logger.debug("MainValuesArtifact.initialize");
+        FLYSArtifact winfo = (FLYSArtifact) artifact;
+        double [] locations = FLYSUtils.getLocations(winfo);
+        if (locations != null) {
+            double location = locations[0];
+            addData("location", new DefaultStateData("location", null, null,
+                    String.valueOf(location)));
+        }
+        else {
+            logger.warn("No location for mainvalues given.");
+        }
+        importData(winfo, "river");
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getState());
+        return states;
+    }
+
+
+    /**
+     * Get the "current" state.
+     * @param cc ignored.
+     * @return the "current" state.
+     */
+    @Override
+    public State getCurrentState(Object cc) {
+        return getState();
+    }
+
+
+    /**
+     * Get the only possible state.
+     * @return the state.
+     */
+    protected State getState() {
+        return getState(null, null);
+    }
+
+
+    /**
+     * Get the state.
+     * @param context ignored.
+     * @param stateID ignored.
+     * @return the state.
+     */
+    @Override
+    protected State getState(Object context, String stateID) {
+        if (state != null)
+            return state;
+        else
+            return spawnState();
+    }
+
+
+    /**
+     * Access the Gauge that the mainvalues are taken from.
+     * @return Gauge that main values are taken from or null in case of
+     *         invalid parameterization.
+     */
+    protected Gauge getGauge() {
+        River river = FLYSUtils.getRiver(this);
+
+        // TODO use helper to get location as double
+        String locationStr = getDataAsString("location");
+
+        if (river == null || locationStr == null) {
+            return null;
+        }
+
+        double location = Double.parseDouble(locationStr);
+
+        return river.determineGaugeByPosition(location);
+    }
+
+
+    /**
+     * Get current location.
+     * @return the location.
+     */
+    public double getLocation() {
+        double location = Double.parseDouble(getDataAsString("location"));
+        return location;
+    }
+
+
+    /**
+     * Get a list of "Q" main values.
+     * @return list of Q main values.
+     */
+    public List<NamedDouble> getMainValuesQ(boolean atGauge) {
+        List<NamedDouble> filteredList = new ArrayList<NamedDouble>();
+        Gauge gauge = getGauge();
+        WstValueTable interpolator = WstValueTableFactory.getTable(FLYSUtils.getRiver(this));
+        Calculation c = new Calculation();
+        double w_out[] = {0.0f};
+        double q_out[] = {0.0f};
+        double kms[] = {getLocation()};
+        double gaugeStation = gauge.getStation().doubleValue();
+        if (gauge != null) {
+            List<MainValue> orig = gauge.getMainValues();
+            for (MainValue mv : orig) {
+                if (mv.getMainValue().getType().getName().equals("Q")) {
+                    if (atGauge) {
+                        q_out[0] = mv.getValue().doubleValue();
+                    }
+                    else {
+                        interpolator.interpolate(mv.getValue().doubleValue(),
+                            gaugeStation, kms, w_out, q_out, c);
+                    }
+                    filteredList.add(new NamedDouble(
+                                mv.getMainValue().getName(),
+                                q_out[0]
+                                ));
+                }
+            }
+        }
+        return filteredList;
+    }
+
+
+    /**
+     * Get a list of "W" main values.
+     * @param atGauge if true, do not interpolate
+     * @return list of W main values.
+     */
+    public List<NamedDouble> getMainValuesW(boolean atGauge) {
+        List<NamedDouble> filteredList = new ArrayList<NamedDouble>();
+        Gauge gauge = getGauge();
+        WstValueTable interpolator = WstValueTableFactory.getTable(FLYSUtils.getRiver(this));
+        Calculation c = new Calculation();
+
+        double gaugeStation = gauge.getStation().doubleValue();
+        double w_out[] = {0.0f};
+        double q_out[] = {0.0f};
+        double kms[] = {getLocation()};
+        if (gauge != null) {
+            List<MainValue> orig = gauge.getMainValues();
+            for (MainValue mv : orig) {
+                if (atGauge) {
+                    if (mv.getMainValue().getType().getName().equals("W")) {
+                        filteredList.add(new NamedDouble(mv.getMainValue().getName(),
+                                mv.getValue().doubleValue()));
+                    
+                    }
+                } else
+                // We cannot interpolate the W values, so derive them
+                // from given Q values.
+                if (mv.getMainValue().getType().getName().equals("Q")) {
+                    interpolator.interpolate(mv.getValue().doubleValue(),
+                            gaugeStation, kms, w_out, q_out, c);
+                    filteredList.add(new NamedDouble(
+                                "W(" + mv.getMainValue().getName() +")",
+                                w_out[0]
+                                ));
+                }
+            }
+        }
+        return filteredList;
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     * @return 0 if not active
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        logger.debug("MainValuesArtifact.active?: "
+           + outputName
+           + "/"
+           + facetName);
+
+        if (outputName.equals("computed_discharge_curve")
+            || outputName.equals("duration_curve")) {
+            return 0;
+        }
+        else {
+            return 1;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/ManualPointsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,110 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+
+/**
+ * Artifact to store user-added points.
+ */
+public class ManualPointsArtifact
+extends      StaticFLYSArtifact
+implements   FacetTypes
+{
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(ManualPointsArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "manualpoints";
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public ManualPointsArtifact() {
+        logger.debug("ManualPointsArtifact.ManualPointsArtifact()");
+    }
+
+
+    /**
+     * Gets called from factory, to set things up.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("ManualPointsArtifact.setup");
+        super.setup(identifier, factory, context, callMeta, data);
+        initialize(null, context, callMeta);
+    }
+
+
+    /** Return the name of this artifact. */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    /** Access state data storing the jsonstring with points. */
+    public String getPointsData(String facetName) {
+        return getDataAsString(facetName + ".data");
+    }
+
+
+    /** Setup state and facet. */
+    @Override
+    protected void initialize(Artifact artifact, Object context, CallMeta meta) {
+        logger.debug("ManualPointsArtifact.initialize");
+        List<Facet> fs = new ArrayList<Facet>();
+
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, meta, fs);
+        if (!fs.isEmpty()) { 
+            logger.debug("Facets to add in ManualPointsArtifact.initialize ."); 
+            facets.put(getCurrentStateId(), fs); 
+        } 
+        else { 
+            logger.debug("No facets to add in ManualPointsArtifact.initialize ("
+                + state.getID() + ").");
+        }
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     * @return 0 if not active
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 1;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/MapArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,306 @@
+package de.intevation.flys.artifacts;
+
+import org.apache.log4j.Logger;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.utils.FLYSUtils;
+
+import de.intevation.flys.artifacts.RiverAxisArtifact.RiverAxisState;
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.model.WMSDBLayerFacet;
+
+
+public class MapArtifact extends FLYSArtifact {
+
+    private static final Logger logger =
+        Logger.getLogger(MapArtifact.class);
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callmeta,
+        Document        data)
+    {
+        logger.debug("MapArtifact.setup");
+        this.identifier = identifier;
+        name = "new_map";
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        List<State> states = getStates(context);
+
+        setCurrentState(states.get(0));
+    }
+
+
+    @Override
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        if (logger.isDebugEnabled()) {
+            dumpArtifact();
+        }
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        List<State> reachable = transitionEngine.getReachableStates(
+            this, getCurrentState(context), stateEngine);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+        ProtocolUtils.appendReachableStates(creator, root, reachable);
+
+        appendBackgroundActivity(creator, root, context);
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element ui = ProtocolUtils.createArtNode(
+            creator, "ui", null, null);
+
+        Element staticUI  = ProtocolUtils.createArtNode(
+            creator, "static", null, null);
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context, identifier());
+
+        appendStaticUI(description, staticUI, context, identifier());
+
+        Element dynamic = current.describe(
+            this,
+            description,
+            root,
+            context,
+            identifier());
+
+        if (dynamic != null) {
+            ui.appendChild(dynamic);
+        }
+
+        ui.appendChild(staticUI);
+
+        root.appendChild(name);
+        root.appendChild(ui);
+        root.appendChild(outs);
+
+        return description;
+
+    }
+
+
+    protected static void appendBackgroundActivity(
+        ElementCreator cr,
+        Element        root,
+        CallContext    context
+    ) {
+        Element inBackground = cr.create("background-processing");
+        root.appendChild(inBackground);
+
+        cr.addAttr(
+            inBackground,
+            "value",
+            String.valueOf(context.isInBackground()),
+            true);
+    }
+
+
+    /**
+     * Append output mode nodes to a document.
+     */
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context,
+        String      uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append output modes for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            List<Output> list = state.getOutputs();
+            if (list == null || list.size() == 0) {
+                logger.debug("-> No output modes for this state.");
+                continue;
+            }
+
+            List<Facet> fs = facets.get(stateId);
+
+            if (fs == null || fs.size() == 0) {
+                logger.debug("No facets for previous state found.");
+                continue;
+            }
+
+            logger.debug("Found " + fs.size() + " facets in previous states.");
+
+            List<Output> generated = generateOutputs(list, fs);
+
+            ProtocolUtils.appendOutputModes(doc, outs, generated);
+        }
+
+        try {
+            DefaultState cur = (DefaultState) getCurrentState(context);
+            if (cur.validate(this)) {
+                List<Output> list = cur.getOutputs();
+                if (list != null && list.size() > 0) {
+                    logger.debug(
+                        "Append output modes for current state: " + cur.getID());
+
+                    List<Facet> fs = facets.get(cur.getID());
+
+                    if (fs != null && fs.size() > 0) {
+                        List<Output> generated = generateOutputs(list, fs);
+
+                        logger.debug("Found " + fs.size() + " current facets.");
+                        if (!generated.isEmpty()) {
+                            ProtocolUtils.appendOutputModes(
+                                doc, outs, generated);
+                        }
+                    }
+                    else {
+                        logger.debug("No facets found for the current state.");
+                    }
+                }
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            // state is not valid, so we do not append its outputs.
+        }
+    }
+
+
+    /**
+     * This method appends the static data - that has already been inserted by
+     * the user - to the static node of the DESCRIBE document.
+     *
+     * @param doc The document.
+     * @param ui The root node.
+     * @param context The CallContext.
+     * @param uuid The identifier of the artifact.
+     */
+    protected void appendStaticUI(
+        Document    doc,
+        Node        ui,
+        CallContext context,
+        String uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        logger.debug("previous states: " + stateIds);
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append static data for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
+        }
+    }
+
+
+    public static class MapState extends RiverAxisState {
+
+        @Override
+        public Object computeAdvance(
+            FLYSArtifact artifact,
+            String       hash,
+            CallContext  context,
+            List<Facet>  facets,
+            Object       old)
+        {
+            logger.debug("MapState.computeAdvance");
+
+            this.artifact = artifact;
+
+            String type = getFacetType();
+
+            WMSDBLayerFacet facet = new WMSDBLayerFacet(
+                0,
+                type,
+                getTitle(context.getMeta()),
+                ComputeType.ADVANCE,
+                getID(), hash,
+                getUrl());
+
+            String name = type + "-" + artifact.identifier();
+
+            facet.addLayer(name);
+            facet.setExtent(getExtent());
+            facet.setSrid(getSrid());
+            facet.setData(getDataString());
+            facet.setFilter(getFilter());
+            facet.setGeometryType(getGeometryType());
+            facet.setConnection(getConnection());
+            facet.setConnectionType(getConnectionType());
+            facet.setLabelItem(getLabelItem());
+
+            facets.add(facet);
+
+            return null;
+        }
+
+        @Override
+        public int getRiverId() {
+            River r = FLYSUtils.getRiver(artifact);
+            int riverId = r.getId();
+
+            return riverId;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/RiverAxisArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,150 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.GeometryUtils;
+
+
+public class RiverAxisArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "riveraxis";
+
+
+    private static final Logger logger =
+        Logger.getLogger(RiverAxisArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("RiverAxisArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new RiverAxisState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class RiverAxisState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(RiverAxisState.class);
+
+        protected Geometry geom;
+        protected int      riverId;
+
+        public RiverAxisState(){}
+
+        public RiverAxisState(FLYSArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String ids = artifact.getDataAsString("ids");
+
+                try {
+                    riverId = Integer.valueOf(ids);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + ids + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_RIVERAXIS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return GeometryUtils.getRiverBoundary(river.getName());
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId());
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM river_axes USING SRID " + srid;
+            }
+            else {
+                return "geom FROM river_axes USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "LINE";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/StaticFLYSArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,122 @@
+package de.intevation.flys.artifacts;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.data.StateData;
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+/**
+ * A basic FLYSArtifact.
+ */
+public abstract class StaticFLYSArtifact extends FLYSArtifact {
+
+    private static final Logger logger =
+        Logger.getLogger(StaticFLYSArtifact.class);
+
+    /** Path to 'ids' (data) in doc that comes from datacage. */
+    public static final String XPATH_IDS = "/art:action/art:ids/@value";
+
+    /**
+     * Create description document which includes outputmodes.
+     * @param data ignored.
+     */
+    @Override
+    public Document describe(Document data, CallContext cc) {
+        logger.debug("Describe artifact: " + identifier());
+
+        Document desc = XMLUtils.newDocument();
+
+        ElementCreator creator = new ElementCreator(
+            desc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        desc.appendChild(root);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        root.appendChild(createOutputModes(cc, desc, creator));
+
+        // Add the data to an anonymous state.
+        Collection<StateData> datas = this.data.values();
+        if (datas.size() > 0) {
+            Element ui = creator.create("ui");
+            Element staticE = creator.create("static");
+            Element state = creator.create("state");
+            ui.appendChild(staticE);
+            staticE.appendChild(state);
+            root.appendChild(ui);
+    
+            for (StateData dataItem : datas) {
+                Element itemelent = creator.create("data");
+                creator.addAttr(itemelent, "name", dataItem.getName(), true);
+                creator.addAttr(itemelent, "type", dataItem.getType(), true);
+                state.appendChild(itemelent);
+                Element valuement = creator.create("item");
+                creator.addAttr(valuement, "label", dataItem.getDescription(), true);
+                creator.addAttr(valuement, "value", dataItem.getValue().toString(), true);
+                itemelent.appendChild(valuement);
+            }
+        }
+
+        return desc;
+    }
+
+
+    /**
+     * Return the value of id element in Datacage data document.
+     * @param data Document as passed by datacage.
+     * @return the id element value of data document.
+     */
+    public static String getDatacageIDValue(Document data) {
+        return XMLUtils.xpathString(data, XPATH_IDS,
+            ArtifactNamespaceContext.INSTANCE);
+    }
+
+
+    protected Element createOutputModes(
+        CallContext    cc,
+        Document       doc,
+        ElementCreator creator)
+    {
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+
+        State state       = getCurrentState(cc);
+        List<Output> list = state.getOutputs();
+
+        if (list != null && list.size() > 0) {
+            List<Facet> fs = facets.get(state.getID());
+            if (fs != null && fs.size() > 0) {
+                List<Output> generated = generateOutputs(list, fs);
+
+                logger.debug("Found " + fs.size() + " current facets.");
+                if (!generated.isEmpty()) {
+                    ProtocolUtils.appendOutputModes(
+                        doc, outs, generated);
+                }
+            }
+            else {
+                logger.debug("No facets found for the current state.");
+            }
+        }
+
+        return outs;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/StaticWKmsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,331 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import java.awt.geom.Point2D;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.flys.artifacts.math.Linear;
+
+import de.intevation.flys.artifacts.model.CrossSectionWaterLineFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RelativePointFacet;
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WKmsFacet;
+import de.intevation.flys.artifacts.model.WKmsFactory;
+
+import de.intevation.flys.artifacts.states.StaticState;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.geom.Lines;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+
+/**
+ * Artifact to access additional "waterlevel"-type of data, like the height
+ * of protective measures (dikes).
+ *
+ * This artifact neglects (Static)FLYSArtifacts capabilities of interaction
+ * with the StateEngine by overriding the getState*-methods.
+ */
+public class StaticWKmsArtifact
+extends      StaticFLYSArtifact
+implements   FacetTypes, WaterLineArtifact
+{
+    /** The logger for this class. */
+    private static Logger logger =
+        Logger.getLogger(StaticWKmsArtifact.class);
+
+    public static final String STATIC_STATE_NAME =
+        "state.additional_wkms.static";
+
+    /** One and only state to be in. */
+    protected transient State state = null;
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public StaticWKmsArtifact() {
+        logger.debug("StaticWKmsArtifact.StaticWKmsArtifact");
+    }
+
+
+    /**
+     * Gets called from factory, to set things up.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("StaticWKmsArtifact.setup");
+
+        state = new StaticState(STATIC_STATE_NAME);
+
+        List<Facet> fs = new ArrayList<Facet>();
+        logger.debug(XMLUtils.toString(data));
+        String code = getDatacageIDValue(data);
+
+        // TODO Go for JSON, one day.
+        //ex.: flood_protection-wstv-114-12
+        if (code != null) {
+            String [] parts = code.split("-");
+
+            if (parts.length >= 4) {
+                int col = -1;
+                int wst = Integer.valueOf(parts[3]);
+
+                if (!parts[2].equals("A")) {
+                    col = Integer.valueOf(parts[2]);
+                }
+
+                addStringData("col_pos", parts[2]);
+                addStringData("wst_id",  parts[3]);
+
+                String wkmsName;
+                if (col >= 0) {
+                    wkmsName = WKmsFactory.getWKmsName(col, wst);
+                }
+                else {
+                    wkmsName = WKmsFactory.getWKmsName(wst);
+                }
+
+                String name;
+                if (parts[0].equals(HEIGHTMARKS_POINTS)) {
+                    name = HEIGHTMARKS_POINTS;
+                }
+                else {
+                    name = STATIC_WKMS;
+                }
+
+                String facetDescription = Resources.getMsg(
+                    callMeta, wkmsName, wkmsName);
+                Facet wKmsFacet = new WKmsFacet(
+                    name,
+                    facetDescription);
+                Facet csFacet = new CrossSectionWaterLineFacet(0,
+                    facetDescription);
+                Facet rpFacet = new RelativePointFacet(facetDescription);
+
+                fs.add(wKmsFacet);
+                fs.add(csFacet);
+                fs.add(rpFacet);
+                facets.put(state.getID(), fs);
+            }
+        }
+
+        spawnState();
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    /**
+     * Initialize the static state with output.
+     * @return static state
+     */
+    protected State spawnState() {
+        state = new StaticState(STATIC_STATE_NAME);
+        List<Facet> fs = facets.get(STATIC_STATE_NAME);
+        DefaultOutput output = new DefaultOutput(
+            "general",
+            "general", "image/png",
+            fs,
+            "chart");
+
+        state.getOutputs().add(output);
+        return state;
+    }
+
+
+    /**
+     * Called via setup.
+     *
+     * @param artifact The master-artifact.
+     */
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object context,
+        CallMeta meta)
+    {
+        logger.debug("StaticWKmsArtifact.initialize");
+        FLYSArtifact winfo = (FLYSArtifact) artifact;
+        // TODO: The river is of no interest, so far.
+        addData("river", winfo.getData("river"));
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getState());
+        return states;
+    }
+
+
+    /**
+     * Get the "current" state (there is but one).
+     * @param cc ignored.
+     * @return the "current" (only possible) state.
+     */
+    @Override
+    public State getCurrentState(Object cc) {
+        return getState();
+    }
+
+
+    /**
+     * Get the only possible state.
+     * @return the state.
+     */
+    protected State getState() {
+        return getState(null, null);
+    }
+
+
+    /**
+     * Get the state.
+     * @param context ignored.
+     * @param stateID ignored.
+     * @return the state.
+     */
+    @Override
+    protected State getState(Object context, String stateID) {
+        return (state != null)
+            ? state
+            : spawnState();
+    }
+
+
+    /**
+     * Get WKms from factory.
+     * @param TODO idx param is not needed
+     * @return WKms according to parameterization (can be null);
+     */
+    public WKms getWKms(int idx) {
+        logger.debug("StaticWKmsArtifact.getWKms");
+
+        return WKmsFactory.getWKms(
+            Integer.valueOf(getDataAsString("col_pos")),
+            Integer.valueOf(getDataAsString("wst_id")));
+    }
+
+
+    /**
+     * Returns W at Km of WKms, linearly interpolated.
+     * Returns -1 if not found.
+     */
+    public static double getWAtKmLin(WKms wkms, double km) {
+        // Uninformed search.
+        int size = wkms.size();
+        int idx = 0;
+        boolean kmIncreasing = (wkms.getKm(0) < wkms.getKm(wkms.size()-1))
+            ? true : false;
+        if (kmIncreasing) {
+            while (idx < size && wkms.getKm(idx) < km) {
+                idx++;
+            }
+        }
+        else {
+            idx = wkms.size() -1;
+            while (idx > 0 && wkms.getKm(idx) > km) {
+                idx--;
+            }
+        }
+
+        if (idx == size -1 || idx == 0) {
+            return -1;
+        }
+
+        // Do linear interpolation
+        int mod = kmIncreasing ? -1 : +1;
+        return Linear.linear(km, wkms.getKm(idx+mod), wkms.getKm(idx), wkms.getW(idx+mod), wkms.getW(idx));
+    }
+
+
+    /**
+     * Returns W at Km of WKms, searching linearly.
+     * Returns -1 if not found.
+     */
+    public static double getWAtKm(WKms wkms, double km) {
+        // Uninformed search.
+        int size = wkms.size();
+        for (int i = 0; i < size; i++) {
+            if (wkms.getKm(i) == km) {
+                return wkms.getW(i);
+            }
+        }
+
+        return -1;
+    }
+
+
+    /**
+     * Get points of line describing the surface of water at cross section.
+     *
+     * @return an array holding coordinates of points of surface of water (
+     *         in the form {{x1, x2} {y1, y2}} ).
+     */
+    @Override
+    public Lines.LineData getWaterLines(int idx, FastCrossSectionLine csl) {
+        logger.debug("getWaterLines(" + idx + ")");
+
+        List<Point2D> points = csl.getPoints();
+
+        WKms wkms = getWKms(0);
+
+        double km = csl.getKm();
+
+        // Find W at km.
+        double wAtKm = getWAtKm(wkms, km);
+        if (wAtKm == -1) {
+            logger.warn("Waterlevel at km " + km + " unknown.");
+            return new Lines.LineData(new double[][] {{}}, 0d, 0d);
+        }
+
+        return Lines.createWaterLines(points, wAtKm);
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     *
+     * @return Always 0. Static Data will enter plots inactive.
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 0;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/StaticWQKmsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,167 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WKmsFactory;
+import de.intevation.flys.artifacts.model.WQKmsFactory;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+/**
+ * Artifact to access additional "waterlevel/discharge"-type of data, like
+ * fixation measurements.
+ *
+ * This artifact neglects (Static)FLYSArtifacts capabilities of interaction
+ * with the StateEngine by overriding the getState*-methods.
+ */
+public class StaticWQKmsArtifact
+extends      StaticFLYSArtifact
+implements   FacetTypes
+{
+    /** The logger for this class. */
+    private static Logger logger =
+        Logger.getLogger(StaticWQKmsArtifact.class);
+
+    public static final String STATIC_STATE_NAME =
+        "state.additional_wqkms.static";
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public StaticWQKmsArtifact() {
+        logger.debug("StaticWQKmsArtifact.StaticWQKmsArtifact");
+    }
+
+
+    /**
+     * Gets called from factory, to set things up.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("StaticWQKmsArtifact.setup");
+
+        // Store the 'ids' (from datacage).
+        logger.debug("StaticWQKmsArtiact.setup" + XMLUtils.toString(data));
+
+        String code = getDatacageIDValue(data);
+        addStringData("ids", code);
+        if (code != null) {
+            String [] parts = code.split("-");
+
+            if (parts.length >= 4) {
+                int col = Integer.valueOf(parts[2]);
+                int wst = Integer.valueOf(parts[3]);
+
+                addStringData("col_pos", parts[2]);
+                addStringData("wst_id",  parts[3]);
+            }
+        }
+
+        // Do this AFTER we have set the col_pos etc.
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    /**
+     * Called via setup.
+     *
+     * @param artifact The master-artifact.
+     */
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object context,
+        CallMeta meta)
+    {
+        logger.debug("StaticWQKmsArtifact.initialize");
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        // TODO: The river is of no interest, so far., also use importData
+        importData(winfo, "river");
+
+        List<Facet> fs = new ArrayList<Facet>();
+
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, meta, fs);
+        if (!fs.isEmpty()) { 
+            logger.debug("Facets to add in StaticWQKmsArtifact.initialize ."); 
+            facets.put(getCurrentStateId(), fs); 
+        } 
+        else { 
+            logger.debug("No facets to add in StaticWQKmsArtifact.initialize ("
+                + state.getID() + ").");
+        }
+    }
+
+
+    /**
+     * Get WQKms from factory.
+     * @return WQKms according to parameterization (can be null);
+     */
+    public WQKms getWQKms() {
+        logger.debug("StaticWQKmsArtifact.getWQKms");
+
+        int col = Integer.valueOf(getDataAsString("col_pos"));
+        int wst = Integer.valueOf(getDataAsString("wst_id"));
+
+        /** TODO do not run twice against db to do this. */
+        String wkmsName = WKmsFactory.getWKmsName(col, wst);
+
+        WQKms res = WQKmsFactory.getWQKms(
+            Integer.valueOf(getDataAsString("col_pos")),
+            Integer.valueOf(getDataAsString("wst_id")));
+        res.setName(wkmsName);
+        return res;
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     *
+     * @return Always 0. Static Data will enter plots inactive.
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 0;
+    }
+
+
+    /** Return specific name. */
+    public String getName() {
+        return "staticwqkms";
+    }
+
+    // TODO implement deepCopy.
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1338 @@
+package de.intevation.flys.artifacts;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.Message;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+import de.intevation.flys.artifacts.model.Calculation1;
+import de.intevation.flys.artifacts.model.Calculation2;
+import de.intevation.flys.artifacts.model.Calculation3;
+import de.intevation.flys.artifacts.model.Calculation4;
+import de.intevation.flys.artifacts.model.Calculation5;
+import de.intevation.flys.artifacts.model.Calculation6;
+import de.intevation.flys.artifacts.model.Calculation;
+import de.intevation.flys.artifacts.model.CalculationMessage;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DischargeTables;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.MainValuesFactory;
+import de.intevation.flys.artifacts.model.Segment;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WW;
+import de.intevation.flys.artifacts.model.WstValueTable;
+import de.intevation.flys.artifacts.model.WstValueTableFactory;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.states.LocationDistanceSelect;
+
+import de.intevation.flys.geom.Lines;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.DischargeTable;
+
+import de.intevation.flys.utils.DoubleUtil;
+import de.intevation.flys.utils.FLYSUtils;
+
+import gnu.trove.TDoubleArrayList;
+
+import java.awt.geom.Point2D;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * The default WINFO artifact.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WINFOArtifact
+extends      FLYSArtifact
+implements   FacetTypes, WaterLineArtifact {
+
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(WINFOArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "winfo";
+
+    /** XPath */
+    public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
+
+    /** The default number of steps between the start end end of a selected Q
+     * range. */
+    public static final int DEFAULT_Q_STEPS = 30;
+
+    /** The default step width between the start end end kilometer. */
+    public static final double DEFAULT_KM_STEPS = 0.1;
+
+
+    /**
+     * The default constructor.
+     */
+    public WINFOArtifact() {
+    }
+
+
+    /**
+     * This method returns a description of this artifact.
+     *
+     * @param data Some data.
+     * @param context The CallContext.
+     *
+     * @return the description of this artifact.
+     */
+    public Document describe(Document data, CallContext context) {
+        logger.debug("Describe: the current state is: " + getCurrentStateId());
+
+        if (logger.isDebugEnabled()) {
+            dumpArtifact();
+        }
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+
+        StateEngine stateEngine = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
+            FLYSContext.TRANSITION_ENGINE_KEY);
+
+        List<State> reachable = transitionEngine.getReachableStates(
+            this, getCurrentState(context), stateEngine);
+
+        Document description            = XMLUtils.newDocument();
+        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
+            description,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ProtocolUtils.createRootNode(creator);
+        description.appendChild(root);
+
+        State current = getCurrentState(context);
+
+        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
+        ProtocolUtils.appendState(creator, root, current);
+        ProtocolUtils.appendReachableStates(creator, root, reachable);
+
+        appendBackgroundActivity(creator, root, context);
+
+        Element name = ProtocolUtils.createArtNode(
+            creator, "name",
+            new String[] { "value" },
+            new String[] { getName() });
+
+        Element ui = ProtocolUtils.createArtNode(
+            creator, "ui", null, null);
+
+        Element staticUI  = ProtocolUtils.createArtNode(
+            creator, "static", null, null);
+
+        Element outs = ProtocolUtils.createArtNode(
+            creator, "outputmodes", null, null);
+        appendOutputModes(description, outs, context, identifier());
+
+        appendStaticUI(description, staticUI, context, identifier());
+
+        Element dynamic = current.describe(
+            this,
+            description,
+            root,
+            context,
+            identifier());
+
+        if (dynamic != null) {
+            ui.appendChild(dynamic);
+        }
+
+        ui.appendChild(staticUI);
+
+        root.appendChild(name);
+        root.appendChild(ui);
+        root.appendChild(outs);
+
+        return description;
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    protected static void appendBackgroundActivity(
+        ElementCreator cr,
+        Element        root,
+        CallContext    context
+    ) {
+        Element inBackground = cr.create("background-processing");
+        root.appendChild(inBackground);
+
+        cr.addAttr(
+            inBackground,
+            "value",
+            String.valueOf(context.isInBackground()),
+            true);
+
+        LinkedList<Message> messages = context.getBackgroundMessages();
+
+        if (messages == null) {
+            return;
+        }
+
+        CalculationMessage  message  = (CalculationMessage) messages.getLast();
+        cr.addAttr(
+            inBackground,
+            "steps",
+            String.valueOf(message.getSteps()),
+            true);
+
+        cr.addAttr(
+            inBackground,
+            "currentStep",
+            String.valueOf(message.getCurrentStep()),
+            true);
+
+        inBackground.setTextContent(message.getMessage());
+    }
+
+
+    /**
+     * Append output mode nodes to a document.
+     */
+    protected void appendOutputModes(
+        Document    doc,
+        Element     outs,
+        CallContext context,
+        String      uuid)
+    {
+        List<Output> generated = getOutputs(context);
+        logger.debug("This Artifact has " + generated.size() + " Outputs.");
+
+        ProtocolUtils.appendOutputModes(doc, outs, generated);
+    }
+
+
+    /**
+     * This method appends the static data - that has already been inserted by
+     * the user - to the static node of the DESCRIBE document.
+     *
+     * @param doc The document.
+     * @param ui The root node.
+     * @param context The CallContext.
+     * @param uuid The identifier of the artifact.
+     */
+    protected void appendStaticUI(
+        Document    doc,
+        Node        ui,
+        CallContext context,
+        String uuid)
+    {
+        List<String> stateIds = getPreviousStateIds();
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine      = (StateEngine) flysContext.get(
+            FLYSContext.STATE_ENGINE_KEY);
+
+        for (String stateId: stateIds) {
+            logger.debug("Append static data for state: " + stateId);
+            DefaultState state = (DefaultState) engine.getState(stateId);
+
+            ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
+        }
+    }
+
+    protected static boolean reportGeneratedWs(
+        Calculation report,
+        double []   ws
+    ) {
+        if (ws == null || ws.length < 2) {
+            return false;
+        }
+
+        double  lastW = ws[0];
+        boolean alreadyReported = false;
+
+        for (int i = 1; i < ws.length; ++i) {
+            if (Math.abs(lastW - ws[i]) < 1e-5) {
+                if (!alreadyReported) {
+                    alreadyReported = true;
+                    report.addProblem("more.than.one.q.for.w", ws[i]);
+                }
+            }
+            else {
+                alreadyReported = false;
+            }
+            lastW = ws[i];
+        }
+
+        return true;
+    }
+
+
+    //
+    // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
+    //
+
+    /**
+     * Returns the data that is computed by a waterlevel computation.
+     *
+     * @return an array of data triples that consist of W, Q and Kms.
+     */
+    public CalculationResult getWaterlevelData()
+    {
+        logger.debug("WINFOArtifact.getWaterlevelData");
+
+        if (getDataAsString("calculation_mode").equals("calc.discharge.longitudinal.section")) {
+            return getDischargeLongitudinalSectionData();
+        }
+
+        River river = FLYSUtils.getRiver(this);
+        if (river == null) {
+            return error(new WQKms[0], "no.river.selected");
+        }
+
+        double[] kms = getKms();
+        if (kms == null) {
+            return error(new WQKms[0], "no.kms.selected");
+        }
+
+        double[] qs   = getQs();
+        double[] ws   = null;
+        boolean  qSel = true;
+
+        Calculation report = new Calculation();
+
+        if (qs == null) {
+            logger.debug("Determine Q values based on a set of W values.");
+            qSel = false;
+            ws   = getWs();
+            double [][] qws = getQsForWs(ws);
+            if (qws == null || qws.length == 0) {
+                return error(new WQKms[0], "converting.ws.to.qs.failed");
+            }
+            qs = qws[0];
+
+            if (reportGeneratedWs(report, qws[1])) { 
+                ws = qws[1];
+            }
+        }
+
+        WstValueTable wst = WstValueTableFactory.getTable(river);
+        if (wst == null) {
+            return error(new WQKms[0], "no.wst.for.selected.river");
+        }
+
+
+        double [] range = FLYSUtils.getKmRange(this);
+        if (range == null) {
+            return error(new WQKms[0], "no.range.found");
+        }
+
+        double refKm;
+
+        if (isFreeQ() || isFreeW()) {
+            refKm = range[0];
+            logger.debug("'free' calculation (km " + refKm + ")");
+        }
+        else {
+            Gauge gauge = river.determineGaugeByPosition(range[0]);
+            if (gauge == null) {
+                return error(
+                    new WQKms[0], "no.gauge.found.for.km");
+            }
+
+            refKm = gauge.getStation().doubleValue();
+
+            logger.debug(
+                "reference gauge: " + gauge.getName() + " (km " + refKm + ")");
+        }
+
+        return computeWaterlevelData(kms, qs, ws, wst, refKm, report);
+    }
+
+
+    /**
+     * Computes the data of a waterlevel computation based on the interpolation
+     * in WstValueTable.
+     *
+     * @param kms The kilometer values.
+     * @param qa The discharge values.
+     * @param wst The WstValueTable used for the interpolation.
+     *
+     * @return an array of data triples that consist of W, Q and Kms.
+     */
+    public static CalculationResult computeWaterlevelData(
+        double []     kms,
+        double []     qs,
+        double []     ws,
+        WstValueTable wst,
+        double        refKm,
+        Calculation   report
+    ) {
+        logger.info("WINFOArtifact.computeWaterlevelData");
+
+        Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm);
+
+        if (report != null) {
+            calc1.addProblems(report);
+        }
+
+        return calc1.calculate(wst);
+    }
+
+
+    /**
+     * Returns the data that is computed by a duration curve computation.
+     *
+     * @return the data computed by a duration curve computation.
+     */
+    public CalculationResult getDurationCurveData() {
+        logger.debug("WINFOArtifact.getDurationCurveData");
+
+        River r = FLYSUtils.getRiver(this);
+
+        if (r == null) {
+            return error(null, "no.river.selected");
+        }
+
+        Gauge g = getGauge();
+
+        if (g == null) {
+           return error(null, "no.gauge.selected");
+        }
+
+        double[] locations = FLYSUtils.getLocations(this);
+
+        if (locations == null) {
+            return error(null, "no.locations.selected");
+        }
+
+        WstValueTable wst = WstValueTableFactory.getTable(r);
+        if (wst == null) {
+            return error(null, "no.wst.for.river");
+        }
+
+        return computeDurationCurveData(g, wst, locations[0]);
+    }
+
+
+    /**
+     * Computes the data used to create duration curves.
+     *
+     * @param gauge The selected gauge.
+     * @param location The selected location.
+     *
+     * @return the computed data.
+     */
+    public static CalculationResult computeDurationCurveData(
+        Gauge         gauge,
+        WstValueTable wst,
+        double        location)
+    {
+        logger.info("WINFOArtifact.computeDurationCurveData");
+
+        Object[] obj = MainValuesFactory.getDurationCurveData(gauge);
+
+        int[]    days = (int[]) obj[0];
+        double[] qs   = (double[]) obj[1];
+
+        Calculation3 calculation = new Calculation3(location, days, qs);
+
+        return calculation.calculate(wst);
+    }
+
+
+    /**
+     * Returns the data that is computed by a discharge curve computation.
+     *
+     * @return the data computed by a discharge curve computation.
+     */
+    public CalculationResult getComputedDischargeCurveData()
+    throws NullPointerException
+    {
+        logger.debug("WINFOArtifact.getComputedDischargeCurveData");
+
+        River r = FLYSUtils.getRiver(this);
+
+        if (r == null) {
+            return error(new WQKms[0], "no.river.selected");
+        }
+
+        double[] locations = FLYSUtils.getLocations(this);
+
+        if (locations == null) {
+            return error(new WQKms[0], "no.locations.selected");
+        }
+
+        WstValueTable wst = WstValueTableFactory.getTable(r);
+        if (wst == null) {
+            return error(new WQKms[0], "no.wst.for.river");
+        }
+
+        return computeDischargeCurveData(wst, locations[0]);
+    }
+
+
+    /**
+     * Computes the data used to create computed discharge curves.
+     *
+     * @param wst The WstValueTable that is used for the interpolation.
+     * @param location The location where the computation should be based on.
+     *
+     * @return an object that contains tuples of W/Q values at the specified
+     * location.
+     */
+    public static CalculationResult computeDischargeCurveData(
+        WstValueTable wst,
+        double location)
+    {
+        logger.info("WINFOArtifact.computeDischargeCurveData");
+
+        Calculation2 calculation = new Calculation2(location);
+
+        return calculation.calculate(wst);
+    }
+
+    protected static final CalculationResult error(Object data, String msg) {
+        return new CalculationResult(data, new Calculation(msg));
+    }
+
+
+    /**
+     * Returns the data computed by the discharge longitudinal section
+     * computation.
+     *
+     * @return an array of WQKms object - one object for each given Q value.
+     */
+    public CalculationResult getDischargeLongitudinalSectionData() {
+
+        logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");
+
+        River river = FLYSUtils.getRiver(this);
+        if (river == null) {
+            logger.debug("No river selected.");
+            return error(new WQKms[0], "no.river.selected");
+        }
+
+        WstValueTable table = WstValueTableFactory.getTable(river);
+        if (table == null) {
+            logger.debug("No wst found for selected river.");
+            return error(new WQKms[0], "no.wst.for.river");
+        }
+
+        List<Segment> segments = getSegments();
+
+        if (segments == null) {
+            logger.debug("Cannot create segments.");
+            return error(new WQKms[0], "cannot.create.segments");
+        }
+
+        double [] range = getFromToStep();
+
+        if (range == null) {
+            logger.debug("Cannot figure out range.");
+            return error(new WQKms[0], "no.range.found");
+        }
+
+        Calculation4 calc4 = new Calculation4(segments, river, isQ());
+
+        return calc4.calculate(table, range[0], range[1], range[2]);
+    }
+
+
+    /**
+     * Returns the data that is computed by a reference curve computation.
+     *
+     * @return the data computed by a reference curve computation.
+     */
+    public CalculationResult getReferenceCurveData(CallContext context) {
+
+        Double startKm = getReferenceStartKm();
+
+        if (startKm == null) {
+            return error(new WW[0], "no.reference.start.km");
+        }
+
+        double [] endKms = getReferenceEndKms();
+
+        if (endKms == null || endKms.length == 0) {
+            return error(new WW[0], "no.reference.end.kms");
+        }
+
+        Calculation5 calc5 = new Calculation5(startKm, endKms);
+
+        River r = FLYSUtils.getRiver(this);
+        if (r == null) {
+            return error(new WW[0], "no.river.found");
+        }
+
+        WstValueTable wst = WstValueTableFactory.getTable(r);
+        if (wst == null) {
+            return error(new WW[0], "no.wst.for.river");
+        }
+
+        Map<Double, Double> kms2gaugeDatums = r.queryGaugeDatumsKMs();
+
+        return calc5.calculate(wst, kms2gaugeDatums, context);
+    }
+
+
+    /** Get reference (start) km. */
+    public Double getReferenceStartKm() {
+        StateData sd = getData("reference_startpoint");
+
+        if (sd == null) {
+            logger.warn("no reference start given.");
+            return null;
+        }
+
+        logger.debug("Reference start km given: " + sd.getValue());
+
+        String input = (String) sd.getValue();
+
+        if (input == null || (input = input.trim()).length() == 0) {
+            logger.warn("reference start string is empty.");
+            return null;
+        }
+
+        try {
+            return Double.valueOf(input);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("reference start string is not numeric.");
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Get end kms for reference curve (null if none).
+     */
+    public double [] getReferenceEndKms() {
+        StateData sd = getData("reference_endpoint");
+
+        if (sd == null) {
+            logger.warn("no reference end given.");
+            return null;
+        }
+        else {
+            logger.debug("Reference end km : " + sd.getValue());
+        }
+
+        String input = (String) sd.getValue();
+
+        if (input == null || (input = input.trim()).length() == 0) {
+            logger.warn("reference end string is empty.");
+            return null;
+        }
+
+        TDoubleArrayList endKms = new TDoubleArrayList();
+
+        for (String part: input.split("\\s+")) {
+            try {
+                double km = Double.parseDouble(part);
+                if (!endKms.contains(km)) {
+                    endKms.add(km);
+                }
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("reference end string is not numeric.");
+            }
+        }
+
+        return endKms.toNativeArray();
+    }
+
+
+    public CalculationResult getHistoricalDischargeData() {
+        Gauge  gauge        = FLYSUtils.getReferenceGauge(this);
+        String rawTimerange = getDataAsString("year_range");
+        String rawValues    = getDataAsString("historical_values");
+        int    mode         = getDataAsInteger("historical_mode");
+
+        long[]   timerange = FLYSUtils.longArrayFromString(rawTimerange);
+        double[] values    = FLYSUtils.doubleArrayFromString(rawValues);
+
+        Calendar start = new GregorianCalendar();
+        start.setTimeInMillis(timerange[0]);
+        Calendar end   = new GregorianCalendar();
+        end.setTimeInMillis(timerange[1]);
+
+        Calculation6 calc = new Calculation6(
+            mode,
+            new long[] { start.getTimeInMillis(), end.getTimeInMillis() },
+            values);
+
+        return calc.calculate(gauge);
+    }
+
+
+    public List<Segment> getSegments() {
+        StateData wqValues = getData("wq_values");
+        if (wqValues == null) {
+            logger.warn("no wq_values given");
+            return Collections.emptyList();
+        }
+        String input = (String)wqValues.getValue();
+        if (input == null || (input = input.trim()).length() == 0) {
+            logger.warn("wq_values are empty");
+            return Collections.emptyList();
+        }
+        return Segment.parseSegments(input);
+    }
+
+
+    /**
+     * Get points of line describing the surface of water at cross section.
+     *
+     * @return an array holding coordinates of points of surface of water (
+     *         in the form {{x1, x2} {y1, y2}} ).
+     */
+    @Override
+    public Lines.LineData getWaterLines(int idx, FastCrossSectionLine csl) {
+        logger.debug("getWaterLines(" + idx + ")");
+
+        List<Point2D> points = csl.getPoints();
+
+        // Need W at km
+        WQKms [] wqkms = (WQKms[]) getWaterlevelData().getData();
+        if (wqkms.length == 0) {
+            logger.error("No WQKms found.");
+            return Lines.createWaterLines(points, 0.0f);
+        }
+
+        if (wqkms.length < idx) {
+            logger.error("getWaterLines() requested index ("
+                         + idx + " not found.");
+        }
+
+        // Find W at km, linear naive approach.
+        WQKms triple = wqkms[idx];
+
+        // Find index of km.
+        double wishKM = csl.getKm();
+        int old_idx = 0;
+
+        if (triple.size() == 0) {
+            logger.warn("Calculation of waterline is empty.");
+            return Lines.createWaterLines(points, 0.0f);
+        }
+
+        // Linear seach in WQKms for closest km.
+        double old_dist_wish = Math.abs(wishKM - triple.getKm(0));
+        double last_w = triple.getW(0);
+
+        for (int i = 0, T = triple.size(); i < T; i++) {
+            double diff = Math.abs(wishKM - triple.getKm(i));
+            if (diff > old_dist_wish) {
+                break;
+            }
+            last_w = triple.getW(i);
+            old_dist_wish = diff;
+        }
+        return Lines.createWaterLines(points, last_w);
+    }
+
+
+    /**
+     * Returns the Qs for a number of Ws. This method makes use of
+     * DischargeTables.getQForW().
+     *
+     * @param ws An array of W values.
+     *
+     * @return an array of Q values.
+     */
+    public double [][] getQsForWs(double[] ws) {
+
+        if (ws == null) {
+            logger.error("getQsForWs: ws == null");
+            return null;
+        }
+
+        boolean debug = logger.isDebugEnabled();
+
+        if (debug) {
+            logger.debug("FLYSArtifact.getQsForWs");
+        }
+
+        River r = FLYSUtils.getRiver(this);
+        if (r == null) {
+            logger.warn("no river found");
+            return null;
+        }
+
+        double [] range = FLYSUtils.getKmRange(this);
+        if (range == null) {
+            logger.warn("no ranges found");
+            return null;
+        }
+
+        if (isFreeW()) {
+            logger.debug("Bezugslinienverfahren I: W auf freier Strecke");
+            // The simple case of the "Bezugslinienverfahren"
+            // "W auf freier Strecke".
+            WstValueTable wst = WstValueTableFactory.getTable(r);
+            if (wst == null) {
+                logger.warn("no wst value table found");
+                return null;
+            }
+            double km = range[0];
+
+            TDoubleArrayList outQs = new TDoubleArrayList(ws.length);
+            TDoubleArrayList outWs = new TDoubleArrayList(ws.length);
+
+            boolean generatedWs = false;
+
+            for (int i = 0; i < ws.length; ++i) {
+                double w = ws[i];
+                if (debug) {
+                    logger.debug("getQsForWs: lookup Q for W: " + w);
+                }
+                // There could bemore than one Q per W.
+                double [] qs = wst.findQsForW(km, w);
+                for (int j = 0; j < qs.length; ++j) {
+                    outWs.add(ws[i]);
+                    outQs.add(qs[j]);
+                }
+                generatedWs |= qs.length != 1;
+            }
+
+            if (debug) {
+                logger.debug("getQsForWs: number of Qs: " + outQs.size());
+            }
+
+            return new double [][] { 
+                outQs.toNativeArray(), 
+                generatedWs ? outWs.toNativeArray() : null };
+        }
+
+        if (debug) {
+            logger.debug("range: " + Arrays.toString(range));
+        }
+
+        Gauge g = r.determineGaugeByPosition(range[0]);
+        if (g == null) {
+            logger.warn("no gauge found for km: " + range[0]);
+            return null;
+        }
+
+        if (debug) {
+            logger.debug("convert w->q with gauge '" + g.getName() + "'");
+        }
+
+        DischargeTable dt = g.fetchMasterDischargeTable();
+
+        if (dt == null) {
+            logger.warn("No master discharge table found for gauge '"
+                + g.getName() + "'");
+            return null;
+        }
+
+        double [][] values = DischargeTables.loadDischargeTableValues(dt, 1);
+
+        TDoubleArrayList wsOut = new TDoubleArrayList(ws.length);
+        TDoubleArrayList qsOut = new TDoubleArrayList(ws.length);
+
+        boolean generatedWs = false;
+
+        for (int i = 0; i < ws.length; i++) {
+            if (Double.isNaN(ws[i])) {
+                logger.warn("W is NaN: ignored");
+                continue;
+            }
+            double w = ws[i] / 100d;
+            double [] qs = DischargeTables.getQsForW(values, w);
+
+            if (qs.length == 0) {
+                logger.warn("No Qs found for W = " + ws[i]);
+            }
+            else {
+                for (double q: qs) {
+                    wsOut.add(ws[i]);
+                    qsOut.add(q * 100d);
+                }
+            }
+            generatedWs |= qs.length != 1;
+        }
+
+        return new double [][] {
+            qsOut.toNativeArray(),
+            generatedWs ? wsOut.toNativeArray() : null
+        };
+    }
+
+
+    /**
+     * Determines the selected mode of distance/range input.
+     *
+     * @return true, if the range mode is selected otherwise false.
+     */
+    public boolean isRange() {
+        StateData mode = getData("ld_mode");
+
+        if (mode == null) {
+            logger.warn("No mode location/range chosen. Defaults to range.");
+            return true;
+        }
+
+        String value = (String) mode.getValue();
+
+        return value.equals("distance");
+    }
+
+
+    /**
+     * Returns the selected distance based on a given range (from, to).
+     *
+     * @param dFrom The StateData that contains the lower value.
+     * @param dTo The StateData that contains the upper value.
+     *
+     * @return the selected distance.
+     */
+    protected double[] getDistanceByRange(StateData dFrom, StateData dTo) {
+        double from = Double.parseDouble((String) dFrom.getValue());
+        double to   = Double.parseDouble((String) dTo.getValue());
+
+        return new double[] { from, to };
+    }
+
+
+    /**
+     * Returns the selected Kms.
+     *
+     * @param distance An 2dim array with [lower, upper] values.
+     *
+     * @return the selected Kms.
+     */
+    public double[] getKms(double[] distance) {
+        StateData dStep = getData("ld_step");
+
+        if (dStep == null) {
+            logger.warn("No step width given. Cannot compute Kms.");
+            return null;
+        }
+
+        double step = Double.parseDouble((String) dStep.getValue());
+
+        // transform step from 'm' into 'km'
+        step = step / 1000;
+
+        if (step == 0d) {
+            step = DEFAULT_KM_STEPS;
+        }
+
+        return DoubleUtil.explode(distance[0], distance[1], step);
+    }
+
+
+    /**
+     * Returns the selected Kms.
+     *
+     * @return the selected kms.
+     */
+    public double[] getKms() {
+        if (isRange()) {
+            double[] distance = FLYSUtils.getKmRange(this);
+            return getKms(distance);
+
+        }
+        else {
+            return LocationDistanceSelect.getLocations(this);
+        }
+    }
+
+
+    public double [] getFromToStep() {
+        if (!isRange()) {
+            return null;
+        }
+        double [] fromTo = FLYSUtils.getKmRange(this);
+
+        if (fromTo == null) {
+            return null;
+        }
+
+        StateData dStep = getData("ld_step");
+        if (dStep == null) {
+            return null;
+        }
+
+        double [] result = new double[3];
+        result[0] = fromTo[0];
+        result[1] = fromTo[1];
+
+        try {
+            String step = (String)dStep.getValue();
+            result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d);
+        }
+        catch (NumberFormatException nfe) {
+            return null;
+        }
+
+        return result;
+    }
+
+
+    /**
+     * Returns the gauge based on the current distance and river.
+     *
+     * @return the gauge.
+     */
+    public Gauge getGauge() {
+        return FLYSUtils.getGauge(this);
+    }
+
+
+    /**
+     * Returns the gauges that match the selected kilometer range.
+     *
+     * @return the gauges based on the selected kilometer range.
+     */
+    public List<Gauge> getGauges() {
+
+        River river = FLYSUtils.getRiver(this);
+        if (river == null) {
+            return null;
+        }
+
+        double [] dist = FLYSUtils.getKmRange(this);
+        if (dist == null) {
+            return null;
+        }
+
+        return river.determineGauges(dist[0], dist[1]);
+    }
+
+
+    /**
+     * This method returns the Q values.
+     *
+     * @return the selected Q values or null, if no Q values are selected.
+     */
+    public double[] getQs() {
+        StateData dMode      = getData("wq_isq");
+        StateData dSelection = getData("wq_isrange");
+
+        boolean isRange = dSelection != null
+            ? Boolean.valueOf((String)dSelection.getValue())
+            : false;
+
+        if (isQ()) {
+            if (!isRange) {
+                return getSingleWQValues();
+            }
+            else {
+                return getWQTriple();
+            }
+        }
+        else {
+            logger.warn("You try to get Qs, but W has been inserted.");
+            return null;
+        }
+    }
+
+
+    public boolean isQ() {
+        StateData mode = getData("wq_isq");
+        String value = (mode != null) ? (String) mode.getValue() : null;
+        return value != null ? Boolean.valueOf(value) : false;
+    }
+
+    public boolean isW() {
+        StateData mode = getData("wq_isq");
+        String value = (mode != null) ? (String) mode.getValue() : null;
+        return value != null ? !Boolean.valueOf(value) : false;
+    }
+
+    public boolean isFreeW() {
+        if(!isW()) {
+            return false;
+        }
+        StateData mode = getData("wq_isfree");
+        String value =  (mode != null) ? (String) mode.getValue() : null;
+
+        return value != null ? Boolean.valueOf(value) : false;
+    }
+
+
+    /**
+     * Returns true, if the parameter is set to compute data on a free range.
+     * Otherwise it returns false, which tells the calculation that it is bound
+     * to a gauge.
+     *
+     * @return true, if the calculation should compute on a free range otherwise
+     * false and the calculation is bound to a gauge.
+     */
+    public boolean isFreeQ() {
+        if(!isQ()) {
+            return false;
+        }
+        StateData mode  = getData("wq_isfree");
+        String    value = (mode != null) ? (String) mode.getValue() : null;
+
+        logger.debug("isFreeQ: " + value);
+
+        return value != null && Boolean.valueOf(value);
+    }
+
+
+    /**
+     * Returns the Q values based on a specified kilometer range.
+     *
+     * @param range A 2dim array with lower and upper kilometer range.
+     *
+     * @return an array of Q values.
+     */
+    public double[] getQs(double[] range) {
+        StateData dMode   = getData("wq_isq");
+
+        if (isQ()) {
+            return getWQForDist(range);
+        }
+
+        logger.warn("You try to get Qs, but Ws has been inserted.");
+        return null;
+    }
+
+
+    /**
+     * Returns the W values based on a specified kilometer range.
+     *
+     * @param range A 2dim array with lower and upper kilometer range.
+     *
+     * @return an array of W values.
+     */
+    public double[] getWs(double[] range) {
+        if (isW()) {
+            return getWQForDist(range);
+        }
+
+        logger.warn("You try to get Ws, but Qs has been inserted.");
+        return null;
+    }
+
+
+    /**
+     * This method returns the W values.
+     *
+     * @return the selected W values or null, if no W values are selected.
+     */
+    public double[] getWs() {
+        StateData dSingle = getData("wq_single");
+
+        if (isW()) {
+            if (dSingle != null) {
+                return getSingleWQValues();
+            }
+            else {
+                return getWQTriple();
+            }
+        }
+        else {
+            logger.warn("You try to get Ws, but Q has been inserted.");
+            return null;
+        }
+    }
+
+    /**
+     * This method returns the given W or Q values for a specific range
+     * (inserted in the WQ input panel for discharge longitudinal sections).
+     *
+     * @param dist A 2dim array with lower und upper kilometer values.
+     *
+     * @return an array of W or Q values.
+     */
+    protected double[] getWQForDist(double[] dist) {
+        logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
+        StateData data = getData("wq_values");
+
+        if (data == null) {
+            logger.warn("Missing wq values!");
+            return null;
+        }
+
+        String dataString = (String) data.getValue();
+        String[]   ranges = dataString.split(":");
+
+        for (String range: ranges) {
+            String[] parts = range.split(";");
+
+            double lower = Double.parseDouble(parts[0]);
+            double upper = Double.parseDouble(parts[1]);
+
+            if (lower <= dist[0] && upper >= dist[1]) {
+                String[] values = parts[2].split(",");
+
+                int      num = values.length;
+                double[] res = new double[num];
+
+                for (int i = 0; i < num; i++) {
+                    try {
+                        res[i] = Double.parseDouble(values[i]);
+                    }
+                    catch (NumberFormatException nfe) {
+                        logger.warn(nfe, nfe);
+                    }
+                }
+
+                return res;
+            }
+        }
+
+        logger.warn("Specified range for WQ not found!");
+
+        return null;
+    }
+
+
+    /**
+     * This method returns an array of inserted WQ triples that consist of from,
+     * to and the step width.
+     *
+     * @return an array of from, to and step width.
+     */
+    protected double[] getWQTriple() {
+        StateData dFrom = getData("wq_from");
+        StateData dTo   = getData("wq_to");
+
+        if (dFrom == null || dTo == null) {
+            logger.warn("Missing start or end value for range.");
+            return null;
+        }
+
+        double from = Double.parseDouble((String) dFrom.getValue());
+        double to   = Double.parseDouble((String) dTo.getValue());
+
+        StateData dStep = getData("wq_step");
+
+        if (dStep == null) {
+            logger.warn("No step width given. Cannot compute Qs.");
+            return null;
+        }
+
+        double step  = Double.parseDouble((String) dStep.getValue());
+
+        // if no width is given, the DEFAULT_Q_STEPS is used to compute the step
+        // width. Maybe, we should round the value to a number of digits.
+        if (step == 0d) {
+            double diff = to - from;
+            step = diff / DEFAULT_Q_STEPS;
+        }
+
+        return DoubleUtil.explode(from, to, step);
+    }
+
+
+    /**
+     * Returns an array of inserted WQ double values stored as whitespace
+     * separated list.
+     *
+     * @return an array of W or Q values.
+     */
+    protected double[] getSingleWQValues() {
+        StateData dSingle = getData("wq_single");
+
+        if (dSingle == null) {
+            logger.warn("Cannot determine single WQ values. No data given.");
+            return null;
+        }
+
+        String   tmp       = (String) dSingle.getValue();
+        String[] strValues = tmp.split(" ");
+
+        TDoubleArrayList values = new TDoubleArrayList();
+
+        for (String strValue: strValues) {
+            try {
+                values.add(Double.parseDouble(strValue));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        values.sort();
+
+        return values.toNativeArray();
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     * @return 0 if not active
+     */
+    @Override
+    public int getInitialFacetActivity(String outputName, String facetName, int index) {
+        String [] inactives = new String[] {
+                                            LONGITUDINAL_Q,
+                                            DURATION_Q
+                                           };
+
+        logger.debug("WINFOArtifact.active?: "
+            + outputName
+            + "/"
+            + facetName);
+
+        if (facetName.equals(COMPUTED_DISCHARGE_MAINVALUES_Q) ||
+             facetName.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
+             && outputName.equals("computed_discharge_curve"))
+            {
+                return 0;
+            }
+        return Arrays.asList(inactives).contains(facetName)
+               ? 0
+               : 1;
+    }
+
+
+    /**
+     * Returns the WstValueTable.
+     */
+    public WstValueTable getWstValueTable() {
+        River r = FLYSUtils.getRiver(this);
+        return WstValueTableFactory.getTable(r);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSBackgroundArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,48 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+public class WMSBackgroundArtifact extends StaticFLYSArtifact {
+
+    public static final String NAME = "wmsbackground";
+
+    private static final Logger logger =
+        Logger.getLogger(WMSBackgroundArtifact.class);
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    protected void initialize(Artifact artifact, Object context, CallMeta meta) {
+        logger.debug("Initialize internal state with: "+ artifact.identifier());
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+        addData("river", flys.getData("river"));
+
+        List<Facet> fs = new ArrayList<Facet>();
+
+        // TODO Add CallMeta
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, meta, fs);
+
+        if (!fs.isEmpty()) {
+            facets.put(getCurrentStateId(), fs);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSBuildingsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,147 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Building;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSBuildingsArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "buildings";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSBuildingsArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSBuildingsArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new BuildingsState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class BuildingsState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(BuildingsState.class);
+
+        protected int riverId;
+
+        public BuildingsState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_BUILDINGS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<Building> buildings =
+                Building.getBuildings(getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (Building b: buildings) {
+                Envelope env = b.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId()) +
+                " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM buildings USING SRID " + srid;
+            }
+            else {
+                return "geom FROM buildings USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "LINE";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSCatchmentArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,163 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.Catchment;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSCatchmentArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "catchment";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSCatchmentArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSCatchmentArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new CatchmentState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class CatchmentState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(CatchmentState.class);
+
+        protected int riverId;
+
+        public CatchmentState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String   ids   = artifact.getDataAsString("ids");
+                String[] parts = ids.split(";");
+
+                try {
+                    riverId = Integer.valueOf(parts[0]);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + ids + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_CATCHMENT;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<Catchment> catchments =
+                Catchment.getCatchments(getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (Catchment c: catchments) {
+                Envelope env = c.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId())
+                + " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM catchment USING SRID " + srid;
+            }
+            else {
+                return "geom FROM catchment USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POLYGON";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSDBArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,302 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.impl.SessionFactoryImpl;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+
+import de.intevation.flys.backend.SessionFactoryProvider;
+
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.model.WMSDBLayerFacet;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public abstract class WMSDBArtifact extends StaticFLYSArtifact {
+
+    private static final Logger logger = Logger.getLogger(WMSDBArtifact.class);
+
+    public static final Pattern DB_URL_PATTERN =
+        Pattern.compile("(.*)\\/\\/(.*):([0-9]+)\\/([a-zA-Z]+)");
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSDBArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+
+        String ids = getDatacageIDValue(data);
+
+        if (ids != null && ids.length() > 0) {
+            addData("ids", new DefaultStateData("ids", null, null, ids));
+        }
+        else {
+            throw new IllegalArgumentException("No attribute 'ids' found!");
+        }
+
+        List<Facet> fs = new ArrayList<Facet>();
+
+        WMSDBState state = (WMSDBState) getCurrentState(context);
+        state.computeInit(this, hash(), context, callMeta, fs);
+
+        if (!fs.isEmpty()) {
+            facets.put(getCurrentStateId(), fs);
+        }
+    }
+
+
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object   context,
+        CallMeta callMeta)
+    {
+        // do nothing
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getCurrentState(context));
+
+        return states;
+    }
+
+
+
+    public static abstract class WMSDBState extends DefaultState {
+        private static final Logger logger = Logger.getLogger(WMSDBState.class);
+
+        protected FLYSArtifact artifact;
+
+        protected String name;
+        protected int    riverId;
+
+
+        public WMSDBState() {}
+
+        public WMSDBState(FLYSArtifact artifact) {
+            this.artifact = artifact;
+            this.name     = null;
+            this.riverId  = 0;
+        }
+
+        @Override
+        public Object computeInit(
+            FLYSArtifact artifact,
+            String       hash,
+            Object       context,
+            CallMeta     meta,
+            List<Facet>  facets
+        ) {
+            logger.debug("WMSDBState.computeInit");
+
+            String type = getFacetType();
+
+            WMSDBLayerFacet facet = new WMSDBLayerFacet(
+                0,
+                type,
+                getTitle(meta),
+                ComputeType.INIT,
+                getID(), hash,
+                getUrl());
+
+            String name = type + "-" + artifact.identifier();
+
+            facet.addLayer(name);
+            facet.setExtent(getExtent());
+            facet.setSrid(getSrid());
+            facet.setData(getDataString());
+            facet.setFilter(getFilter());
+            facet.setGeometryType(getGeometryType());
+            facet.setConnection(getConnection());
+            facet.setConnectionType(getConnectionType());
+            facet.setLabelItem(getLabelItem());
+
+            facets.add(facet);
+
+            return null;
+        }
+
+        /**
+         * This method returns a connection string for databases used by
+         * Mapserver's Mapfile.
+         *
+         * @return A connection string for Mapserver.
+         */
+        protected String getConnection() {
+            SessionFactoryImpl sf = (SessionFactoryImpl)
+                SessionFactoryProvider.getSessionFactory();
+
+            String user = SessionFactoryProvider.getUser(sf);
+            String pass = SessionFactoryProvider.getPass(sf);
+            String url  = SessionFactoryProvider.getURL(sf);
+
+            logger.debug("Parse connection url: " + url);
+
+            Matcher m = DB_URL_PATTERN.matcher(url);
+            if (!m.matches()) {
+                logger.warn("Could not parse Connection string.");
+                return null;
+            }
+
+            logger.debug("Groups for connection string: " + m.groupCount());
+            int groups = m.groupCount();
+
+            for (int i = 0; i <= m.groupCount(); i++) {
+                logger.debug("Group " + i + ": " + m.group(i));
+            }
+
+            String connection = null;
+
+            if (FLYSUtils.isUsingOracle()) {
+                if (groups < 3) {
+                    logger.warn("Could only partially parse connection string.");
+                    return null;
+                }
+
+                String host = m.group(2);
+                String port = m.group(3);
+
+                connection = user + "/" + pass + "@" + host;
+            }
+            else {
+                if (groups < 4) {
+                    logger.warn("Could only partially parse connection string.");
+                    return null;
+                }
+
+                String host = m.group(2);
+                String port = m.group(3);
+                String db   = m.group(4);
+
+                StringBuilder sb = new StringBuilder();
+                sb.append("dbname=" + db);
+                sb.append("host='" + host + "'");
+                sb.append("port=" + port);
+                sb.append("password='" + pass + "'");
+                sb.append("sslmode=disable");
+
+                connection = sb.toString();
+            }
+
+            logger.debug("Created connection: '" + connection + "'");
+
+            return connection;
+        }
+
+        protected String getConnectionType() {
+            return FLYSUtils.isUsingOracle() ? "oraclespatial" : "postgis";
+        }
+
+        protected String getLabelItem() {
+            return null;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String   ids   = artifact.getDataAsString("ids");
+                String[] parts = ids.split(";");
+
+                try {
+                    riverId = Integer.valueOf(parts[0]);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + parts[0] + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        /**
+         * Returns the name of the WMS layer. This method extracts the name
+         * from 'ids' data string. It is expected, that the 'ids' string is
+         * seperated by ';' and that the name is placed at index 1.
+         *
+         * @return the name of the WMS layer.
+         */
+        public String getName() {
+            if (name == null) {
+                String ids = artifact.getDataAsString("ids");
+
+                String parts[] = ids != null ? ids.split(";") : null;
+
+                if (parts != null && parts.length >= 2) {
+                    name = parts[1];
+                }
+            }
+
+            return name;
+        }
+
+
+        /**
+         * Returns the name of the layer (returned by getName()) or the layer
+         * type if the name is empty. The layer type is created by an i18n
+         * string of getFacetType().
+         *
+         * @param meta A CallMeta used for i18n.
+         *
+         * @return the name of the layer or its type if name is empty.
+         */
+        protected String getTitle(CallMeta meta) {
+            String name = getName();
+
+            return name != null && name.length() > 0
+                ? name
+                : Resources.getMsg(
+                    meta,
+                    getFacetType(),
+                    getFacetType());
+        }
+
+
+        protected abstract String getFacetType();
+
+        protected abstract String getUrl();
+
+        protected abstract String getSrid();
+
+        protected abstract Envelope getExtent();
+
+        protected abstract String getFilter();
+
+        protected abstract String getDataString();
+
+        protected abstract String getGeometryType();
+    } // end of WMSDBState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSFixpointsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,145 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Fixpoint;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSFixpointsArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "fixpoints";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSFixpointsArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSFixpointsArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new FixpointsState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class FixpointsState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(FixpointsState.class);
+
+        protected int riverId;
+
+        public FixpointsState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_FIXPOINTS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<Fixpoint> fixpoints = Fixpoint.getFixpoints(getRiverId());
+
+            Envelope max = null;
+
+            for (Fixpoint f: fixpoints) {
+                Envelope env = f.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId());
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM fixpoints USING SRID " + srid;
+            }
+            else {
+                return "geom FROM fixpoints USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POINT";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSFloodmapsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,147 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Floodmaps;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSFloodmapsArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "floodmaps";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSFloodmapsArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSFloodmapsArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new FloodmapsState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class FloodmapsState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(FloodmapsState.class);
+
+        protected int    riverId;
+
+        public FloodmapsState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId      = 0;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_FLOODMAPS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<Floodmaps> floodmaps =
+                Floodmaps.getFloodmaps(getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (Floodmaps f: floodmaps) {
+                Envelope env = f.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId())
+                + " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM floodmaps USING SRID " + srid;
+            }
+            else {
+                return "geom FROM floodmaps USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POLYGON";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSFloodplainArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,151 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.Floodplain;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSFloodplainArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "floodplain";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSFloodplainArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSFloodplainArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new FloodplainState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class FloodplainState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(FloodplainState.class);
+
+        protected int riverId;
+
+        public FloodplainState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String ids = artifact.getDataAsString("ids");
+
+                try {
+                    riverId = Integer.valueOf(ids);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + ids + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        protected River getRiver() {
+            return RiverFactory.getRiver(getRiverId());
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_FLOODPLAIN;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = getRiver();
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            River      river = getRiver();
+            Floodplain plain = Floodplain.getFloodplain(river.getName());
+            return plain.getGeom().getEnvelopeInternal();
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId());
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM floodplain USING SRID " + srid;
+            }
+            else {
+                return "geom FROM floodplain USING UNIQUE id USING SRID " +srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POLYGON";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSGaugeLocationArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,150 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.GaugeLocation;
+
+import de.intevation.flys.artifacts.WMSDBArtifact.WMSDBState;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSGaugeLocationArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "wmsgaugelocation";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSGaugeLocationArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSGaugeLocationArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new WMSGaugeLocationState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+
+    public static class WMSGaugeLocationState extends WMSDBState implements FacetTypes {
+
+        private static final Logger logger =
+            Logger.getLogger(WMSGaugeLocationState.class);
+
+        protected Geometry geom;
+
+        public WMSGaugeLocationState(WMSDBArtifact artifact) {
+            super(artifact);
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_GAUGE_LOCATION;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<GaugeLocation> gauges =
+                GaugeLocation.getGaugeLocations(getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (GaugeLocation gauge: gauges) {
+                Envelope env = gauge.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId()) +
+                " and name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM gauge_location USING SRID " + srid;
+            }
+            else {
+                return "geom FROM gauge_location " +
+                       "USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POINT";
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSHwsArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,162 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Hws;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSHwsArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "hws";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSHwsArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSHwsArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new HwsState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class HwsState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(HwsState.class);
+
+        protected int riverId;
+
+        public HwsState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String   ids   = artifact.getDataAsString("ids");
+                String[] parts = ids.split(";");
+
+                try {
+                    riverId = Integer.valueOf(parts[0]);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + parts[0] + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_HWS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<Hws> hws = Hws.getHws(getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (Hws h: hws) {
+                Envelope env = h.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId())
+                + " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM hws USING SRID " + srid;
+            }
+            else {
+                return "geom FROM hws USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "LINE";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSHydrBoundaryArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,147 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.HydrBoundary;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSHydrBoundaryArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "hydr_boundary";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSHydrBoundaryArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSHydrBoundaryArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new HydrBoundaryState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class HydrBoundaryState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(HydrBoundaryState.class);
+
+        protected int riverId;
+
+        public HydrBoundaryState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_HYDR_BOUNDARY;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<HydrBoundary> boundaries = HydrBoundary.getHydrBoundaries(
+                getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (HydrBoundary b: boundaries) {
+                Envelope env = b.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId())
+                + " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM hydr_boundaries USING SRID " + srid;
+            }
+            else {
+                return "geom FROM hydr_boundaries USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "LINE";
+        }
+    } // end of HydrBoundaryState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSHydrBoundaryPolyArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,147 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.HydrBoundaryPoly;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSHydrBoundaryPolyArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "hydr_boundary_poly";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSHydrBoundaryPolyArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSHydrBoundaryArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new HydrBoundaryPolyState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class HydrBoundaryPolyState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(HydrBoundaryPolyState.class);
+
+        protected int riverId;
+
+        public HydrBoundaryPolyState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_HYDR_BOUNDARY_POLY;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<HydrBoundaryPoly> boundaries = HydrBoundaryPoly.getHydrBoundaries(
+                getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (HydrBoundaryPoly b: boundaries) {
+                Envelope env = b.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId())
+                + " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM hydr_boundaries USING SRID " + srid;
+            }
+            else {
+                return "geom FROM hydr_boundaries USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POLYGON";
+        }
+    } // end of HydrBoundaryState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSKmArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,168 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.RiverAxisKm;
+
+import de.intevation.flys.artifacts.WMSDBArtifact.WMSDBState;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSKmArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "wmskm";
+
+
+    private static final Logger logger = Logger.getLogger(WMSKmArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSKmArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new WMSKmState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+
+    public static class WMSKmState extends WMSDBState implements FacetTypes {
+
+        private static final Logger logger = Logger.getLogger(WMSKmState.class);
+
+        protected Geometry geom;
+        protected int      riverId;
+
+        public WMSKmState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String ids = artifact.getDataAsString("ids");
+
+                try {
+                    riverId = Integer.valueOf(ids);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + ids + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_KMS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<RiverAxisKm> kms = RiverAxisKm.getRiverAxisKms(getRiverId());
+
+            Envelope max = null;
+
+            for (RiverAxisKm km: kms) {
+                Envelope env = km.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId());
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM river_axes_km USING SRID " + srid;
+            }
+            else {
+                return "geom FROM river_axes_km " +
+                       "USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getLabelItem() {
+            return "km";
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "POINT";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSLineArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,162 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Line;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSLineArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "lines";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSLineArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSLineArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new LineState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class LineState extends WMSDBState implements FacetTypes
+    {
+        private static final Logger logger =
+            Logger.getLogger(LineState.class);
+
+        protected int riverId;
+
+        public LineState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String   ids   = artifact.getDataAsString("ids");
+                String[] parts = ids.split(";");
+
+                try {
+                    riverId = Integer.valueOf(parts[0]);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + parts[0] + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_LINES;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            List<Line> lines = Line.getLines(getRiverId(), getName());
+
+            Envelope max = null;
+
+            for (Line l: lines) {
+                Envelope env = l.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId()) +
+                " AND name='" + getName() + "'";
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM lines USING SRID " + srid;
+            }
+            else {
+                return "geom FROM lines USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "LINE";
+        }
+    } // end of WMSKmState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WMSQPSArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,164 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.CrossSectionTrack;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class WMSQPSArtifact extends WMSDBArtifact {
+
+    public static final String NAME = "qps";
+
+
+    private static final Logger logger =
+        Logger.getLogger(WMSQPSArtifact.class);
+
+
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WMSQPSArtifact.setup");
+
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+
+    @Override
+    public State getCurrentState(Object cc) {
+        State s = new WMSQPSState(this);
+
+        List<Facet> fs = facets.get(getCurrentStateId());
+
+        DefaultOutput o = new DefaultOutput(
+            "floodmap",
+            "floodmap",
+            "image/png",
+            fs,
+            "map");
+
+        s.getOutputs().add(o);
+
+        return s;
+    }
+
+
+    public static class WMSQPSState extends WMSDBState implements FacetTypes {
+
+        private static final Logger logger =
+            Logger.getLogger(WMSQPSState.class);
+
+        protected int riverId;
+
+        public WMSQPSState(WMSDBArtifact artifact) {
+            super(artifact);
+            riverId = 0;
+        }
+
+        public int getRiverId() {
+            if (riverId == 0) {
+                String ids = artifact.getDataAsString("ids");
+
+                try {
+                    riverId = Integer.valueOf(ids);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.error("Cannot parse river id from '" + ids + "'");
+                }
+            }
+
+            return riverId;
+        }
+
+        @Override
+        protected String getFacetType() {
+            return FLOODMAP_QPS;
+        }
+
+        @Override
+        protected String getUrl() {
+            return FLYSUtils.getUserWMSUrl(artifact.identifier());
+        }
+
+        @Override
+        protected String getSrid() {
+            River river = RiverFactory.getRiver(getRiverId());
+            return FLYSUtils.getRiverSrid(river.getName());
+        }
+
+        @Override
+        protected Envelope getExtent() {
+            River river = RiverFactory.getRiver(getRiverId());
+
+            List<CrossSectionTrack> qps =
+                CrossSectionTrack.getCrossSectionTrack(river.getName());
+
+            Envelope max = null;
+
+            for (CrossSectionTrack qp: qps) {
+                Envelope env = qp.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                    continue;
+                }
+
+                max.expandToInclude(env);
+            }
+
+            return max;
+        }
+
+        @Override
+        protected String getFilter() {
+            return "river_id=" + String.valueOf(getRiverId());
+        }
+
+        @Override
+        protected String getDataString() {
+            String srid = getSrid();
+
+            if (FLYSUtils.isUsingOracle()) {
+                return "geom FROM cross_section_tracks USING SRID " + srid;
+            }
+            else {
+                return "geom FROM cross_section_tracks " +
+                       "USING UNIQUE id USING SRID " + srid;
+            }
+        }
+
+        @Override
+        protected String getGeometryType() {
+            return "LINE";
+        }
+    } // end of WMSQPSState
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WQKmsInterpolArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,287 @@
+package de.intevation.flys.artifacts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WQFacet;
+import de.intevation.flys.artifacts.model.WKmsFactory;
+import de.intevation.flys.artifacts.model.WQKmsFactory;
+import de.intevation.flys.artifacts.model.WstValueTable;
+import de.intevation.flys.artifacts.model.WstValueTableFactory;
+
+import de.intevation.flys.artifacts.states.StaticState;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * Artifact to access additional "waterlevel/discharge"-type of data, like
+ * fixation measurements, but doing so with costy interpolation.
+ *
+ * This artifact neglects (Static)FLYSArtifacts capabilities of interaction
+ * with the StateEngine by overriding the getState*-methods.
+ */
+public class WQKmsInterpolArtifact
+extends      StaticFLYSArtifact
+implements   FacetTypes
+{
+    /** The logger for this class. */
+    private static Logger logger =
+        Logger.getLogger(WQKmsInterpolArtifact.class);
+
+    public static final String STATIC_STATE_NAME =
+        "state.additional_wqkms.interpol.static";
+
+    /** One and only state to be in. */
+    protected transient State state = null;
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public WQKmsInterpolArtifact() {
+        logger.debug("WQKmsInterpolArtifact.WQKmsInterpolArtifact");
+    }
+
+
+    /**
+     * Gets called from factory, to set things up.
+     */
+    @Override
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        logger.debug("WQKmsInterpolArtifact.setup");
+
+        state = new StaticState(STATIC_STATE_NAME);
+
+        List<Facet> fs = new ArrayList<Facet>();
+        String code = getDatacageIDValue(data);
+
+        // TODO Go for JSON, one day.
+        //ex.: flood_protection-wstv-114-12
+        if (code != null) {
+            String [] parts = code.split("-");
+
+            if (parts.length >= 4) {
+                int wst = Integer.valueOf(parts[3]);
+                int col = -1;
+                String colpos = parts[2];
+                // Are we interested in a single column or in all columns?
+                if (colpos.equals("A")) {
+                    ; // Take all.
+                }
+                else {
+                    col = Integer.valueOf(colpos);
+                    addStringData("col_pos", parts[2]);
+                }
+                addStringData("wst_id",  parts[3]);
+                String wkmsName = (col > 0)
+                                ? WKmsFactory.getWKmsName(col, wst)
+                                : WKmsFactory.getWKmsName(wst);
+                String name;
+                if (parts[0].startsWith("height")){
+                    name = STATIC_WQ_ANNOTATIONS;
+                }
+                else if (parts[0].startsWith("flood")) {
+                    name = STATIC_WKMS_INTERPOL;
+                }
+                else {
+                    name = STATIC_WQ;
+                }
+
+                Facet facet = new WQFacet(name,
+                    Resources.getMsg(
+                        callMeta,
+                        wkmsName,
+                        wkmsName));
+                fs.add(facet);
+                facets.put(state.getID(), fs);
+            }
+        }
+
+        spawnState();
+        super.setup(identifier, factory, context, callMeta, data);
+    }
+
+
+    /**
+     * Initialize the static state with output.
+     * @return static state
+     */
+    protected State spawnState() {
+        state = new StaticState(STATIC_STATE_NAME);
+        List<Facet> fs = facets.get(STATIC_STATE_NAME);
+        DefaultOutput output = new DefaultOutput(
+            "general",
+            "general",
+            "image/png",
+            fs,
+            "chart");
+
+        state.getOutputs().add(output);
+
+        return state;
+    }
+
+
+    /**
+     * Called via setup.
+     *
+     * @param artifact The master-artifact.
+     */
+    @Override
+    protected void initialize(
+        Artifact artifact,
+        Object context,
+        CallMeta meta)
+    {
+        logger.debug("WQKmsInterpolArtifact.initialize");
+        FLYSArtifact winfo = (FLYSArtifact) artifact;
+        importData(winfo, "river");
+        importData(winfo, "ld_locations");
+    }
+
+
+    /**
+     * Get a list containing the one and only State.
+     * @param  context ignored.
+     * @return list with one and only state.
+     */
+    @Override
+    protected List<State> getStates(Object context) {
+        ArrayList<State> states = new ArrayList<State>();
+        states.add(getState());
+        return states;
+    }
+
+
+    /**
+     * Get WQ at a given km.
+     */
+    public double [][] getWQAtKm(double km) {
+
+        WstValueTable interpolator = null;
+        // Get WstValueTable
+        if (getDataAsString("col_pos") != null) {
+            interpolator = WstValueTableFactory.getWstColumnTable(
+                getDataAsInt("wst_id"), getDataAsInt("col_pos"));
+        }
+        else {
+            interpolator = WstValueTableFactory.getTable(
+                getDataAsInt("wst_id"));
+        }
+
+        Double tmp = getDataAsDouble("ld_locations");
+        double [][] vs = interpolator.interpolateWQColumnwise(
+            tmp != null ? tmp : 0);
+
+        for (int x = 0; x < vs[1].length; x++) {
+            logger.debug("getWQAtKm: Q/W " + vs[0][x] + " / " + vs[1][x]);
+        }
+
+        return vs;
+    }
+
+
+    /**
+     * Get a DataItem casted to int (0 if fails).
+     */
+    public int getDataAsInt(String dataName) {
+        String val = getDataAsString(dataName);
+        try {
+            return Integer.valueOf(val);
+        }
+        catch (NumberFormatException e) {
+            logger.warn("Could not get data " + dataName + " as int", e);
+            return 0;
+        }
+    }
+
+
+    /**
+     * Get the "current" state (there is but one).
+     * @param cc ignored.
+     * @return the "current" (only possible) state.
+     */
+    @Override
+    public State getCurrentState(Object cc) {
+        return getState();
+    }
+
+
+    /**
+     * Get the only possible state.
+     * @return the state.
+     */
+    protected State getState() {
+        return getState(null, null);
+    }
+
+
+    /**
+     * Get the state.
+     * @param context ignored.
+     * @param stateID ignored.
+     * @return the state.
+     */
+    @Override
+    protected State getState(Object context, String stateID) {
+        return (state != null)
+            ? state
+            : spawnState();
+    }
+
+
+    /**
+     * Get WQKms from factory.
+     * @param TODO idx param is not needed
+     * @return WQKms according to parameterization (can be null);
+     */
+    public WQKms getWQKms(int idx) {
+        logger.debug("WQKmsInterpolArtifact.getWQKms");
+        logger.warn("Stub, getWQKms not yet implemented.");
+
+        return WQKmsFactory.getWQKms(
+            Integer.valueOf(getDataAsString("col_pos")),
+            Integer.valueOf(getDataAsString("wst_id")));
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI). This will be checked one time
+     * when the facet enters a collections describe document.
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     *
+     * @return Always 0. Static Data will enter plots inactive.
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 0;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WaterLineArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,15 @@
+package de.intevation.flys.artifacts;
+
+import de.intevation.flys.geom.Lines;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+/**
+ * Interface, Artifact can create WaterLines (Water against Cross-Profile).
+ */
+public interface WaterLineArtifact {
+
+    /** Get points that define a line of a (water)facet against a cross-
+     * section. */
+    public Lines.LineData getWaterLines(int facetIdx, FastCrossSectionLine csl);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WaterlevelArtifact.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,119 @@
+package de.intevation.flys.artifacts;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+
+/**
+ * Clone of an WINFOArtifact to expose exactly one waterlevel only.
+ * All Facets of the "longitudinal_section" output will be added to the
+ * "w_differences" output and filterFacets adjusted accordingly.
+ *
+ * @TODO Straighten inheritance-line (waterlevel-WINFO or vice versa).
+ */
+public class WaterlevelArtifact extends WINFOArtifact {
+
+    /** The logger for this class. */
+    private static Logger logger = Logger.getLogger(WaterlevelArtifact.class);
+
+    /** The name of the artifact. */
+    public static final String ARTIFACT_NAME = "waterlevel";
+
+
+    /**
+     * The default constructor.
+     */
+    public WaterlevelArtifact() {
+    }
+
+
+    /**
+     * Setup and restate longitudinal_section filterfacets to apply to the
+     * w_differences output, too.
+     */
+    public void setup(
+        String          identifier,
+        ArtifactFactory factory,
+        Object          context,
+        CallMeta        callMeta,
+        Document        data)
+    {
+        super.setup(identifier, factory, context, callMeta, data);
+        if(filterFacets != null) {
+            filterFacets.put(
+                "w_differences",
+                filterFacets.get("longitudinal_section"));
+        }
+    }
+
+
+    /**
+     * Clone important stuff of an WINFOArtifact.
+     * @param artifact the WINFOArtifact to clone stuff from.
+     */
+    protected void initialize(
+        Artifact artifact,
+        Object context,
+        CallMeta meta)
+    {
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        this.data = winfo.cloneData();
+	logger.debug("Cloned data of winfo artifact.");
+        // Statically add Facets.
+        List<Facet> fs = new ArrayList<Facet>();
+        DefaultState state = (DefaultState) getCurrentState(context);
+        state.computeInit(this, hash(), context, meta, fs);
+        if (!fs.isEmpty()) { 
+            logger.debug("Facets to add in WaterlevelArtifact.initialize ."); 
+            facets.put(getCurrentStateId(), fs); 
+        } 
+        else { 
+            logger.debug("No facets to add in WaterlevelArtifact.initialize ("
+                + state.getID() + ").");
+        }
+    }
+
+
+    /**
+     * Returns the name of the concrete artifact.
+     *
+     * @return the name of the concrete artifact.
+     */
+    public String getName() {
+        return ARTIFACT_NAME;
+    }
+
+
+    /**
+     * Determines Facets initial disposition regarding activity (think of
+     * selection in Client ThemeList GUI).
+     * WaterlevelArtifact Facets should come to live "inactive" (always
+     * return 0).
+     *
+     * @param facetName name of the facet.
+     * @param index     index of the facet.
+     *
+     * @return Always 0 to have Facets initial predisposition to "inactive".
+     */
+    @Override
+    public int getInitialFacetActivity(
+        String outputName,
+        String facetName,
+        int index)
+    {
+        return 0;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/cache/CacheFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,79 @@
+package de.intevation.flys.artifacts.cache;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+
+import org.apache.log4j.Logger;
+
+public final class CacheFactory
+{
+    private static Logger log = Logger.getLogger(CacheFactory.class);
+
+    public static final String CACHE_CONFIG_FILE_PROPERTY =
+        "flys.artifacts.cache.config.file";
+
+    public static final String XPATH_CACHE_CONFIG_FILE =
+        "/artifact-database/cache/config-file/text()";
+
+    private CacheFactory() {
+    }
+
+    private static boolean      initialized;
+
+    private static CacheManager cacheManager;
+
+    public static final Cache getCache() {
+        return getCache(Cache.DEFAULT_CACHE_NAME);
+    }
+
+    public static final String getConfigFile() {
+        String configFile = System.getProperty(CACHE_CONFIG_FILE_PROPERTY);
+
+        if (configFile != null) {
+            return configFile;
+        }
+
+        configFile = Config.getStringXPath(XPATH_CACHE_CONFIG_FILE);
+
+        if (configFile != null) {
+            configFile = Config.replaceConfigDir(configFile);
+        }
+
+        return configFile;
+    }
+
+    public static final synchronized Cache getCache(String cacheName) {
+        if (!initialized) {
+            initialized = true; // try only once
+            String configFile = getConfigFile();
+            if (configFile != null) {
+                try {
+                    cacheManager = CacheManager.create(configFile);
+                    //System.setProperty(
+                    //  "net.sf.ehcache.enableShutdownHook", "true");
+                    Runtime.getRuntime().addShutdownHook(new Thread() {
+                        public void run() {
+                            log.info("shutting down caches");
+                            for (String name: cacheManager.getCacheNames()) {
+                                log.info("\tflushing '" + name + "'");
+                                cacheManager.getCache(name).flush();
+                            }
+                            cacheManager.shutdown();
+                        }
+                    });
+                }
+                catch (CacheException ce) {
+                    log.error("cannot configure cache", ce);
+                }
+            }
+        }
+
+        return cacheManager != null
+            ? cacheManager.getCache(cacheName)
+            : null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,701 @@
+package de.intevation.flys.artifacts.charts;
+
+import de.intevation.flys.backend.SessionFactoryProvider;
+
+import de.intevation.flys.geom.Lines;
+
+import de.intevation.flys.model.CrossSection;
+import de.intevation.flys.model.CrossSectionLine;
+import de.intevation.flys.model.CrossSectionPoint;
+
+import de.intevation.flys.utils.Pair;
+
+import de.intevation.flys.jfree.StableXYDifferenceRenderer;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import java.awt.Color;
+import java.awt.Paint;
+import java.awt.TexturePaint;
+
+import java.awt.image.BufferedImage;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+import javax.swing.table.AbstractTableModel;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+
+import org.jfree.chart.axis.NumberAxis;
+
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+
+import org.jfree.data.xy.DefaultXYDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+
+import org.jfree.ui.ApplicationFrame;
+import org.jfree.ui.RefineryUtilities;
+
+/**
+ * Standalone tech-demo.
+ */
+public class CrossSectionApp
+extends      ApplicationFrame
+{
+    public static final String RIVER = System.getProperty("river", "Saar");
+
+    public static final String WATER_LEVEL = System.getProperty("waterlevel");
+
+    public static final String KM = System.getProperty("km");
+
+    public static final double EPSILON = 1e-4;
+
+    protected Session session;
+
+    protected JComboBox crossSectionLinesCB;
+    protected JTextField waterlevelTF;
+
+    protected ChartPanel chartPanel;
+
+    protected Double lastWaterLevel;
+
+    protected List<CrossSection> crossSections;
+    protected boolean [] drawCrossSection;
+    protected boolean [] drawWaterLevel;
+    protected boolean [] drawGround;
+    protected boolean [] drawFill;
+
+    protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines;
+
+    protected static final Paint TRANSPARENT = createTransparentPaint();
+
+    public class CrossSectionTableModel extends AbstractTableModel {
+
+        @Override
+        public String getColumnName(int col) {
+            switch (col) {
+                case 0: return "Peilungsname";
+                case 1: return "Peilung";
+                case 2: return "Wasserstand";
+                case 3: return "Boden";
+                case 4: return "Wasser";
+            }
+            return "";
+        }
+
+        @Override
+        public int getColumnCount() {
+            return 5; 
+        }
+
+        @Override
+        public int getRowCount() {
+            return crossSections != null ? crossSections.size() : 0;
+        }
+
+        @Override
+        public Object getValueAt(int row, int col) {
+            if (crossSections == null) return null;
+            switch (col) {
+                case 0: return crossSections.get(row).getDescription();
+                case 1: return drawCrossSection[row];
+                case 2: return drawWaterLevel[row];
+                case 3: return drawGround[row];
+                case 4: return drawFill[row];
+            }
+            return null;
+        }
+
+        @Override
+        public void setValueAt(Object value, int row, int col) {
+            switch (col) {
+                case 1: 
+                    if (change(drawCrossSection, row, (Boolean)value)) {
+                        fireTableCellUpdated(row, col);
+                    }
+                    break;
+                case 2:
+                    if (change(drawWaterLevel, row, (Boolean)value)) {
+                        fireTableCellUpdated(row, col);
+                    }
+                    break;
+                case 3:
+                    if (change(drawGround, row, (Boolean)value)) {
+                        fireTableCellUpdated(row, col);
+                    }
+                    break;
+                case 4:
+                    if (change(drawFill, row, (Boolean)value)) {
+                        fireTableCellUpdated(row, col);
+                    }
+                    break;
+            }
+        }
+
+        @Override
+        public Class<?> getColumnClass(int columnIndex) {
+            switch (columnIndex) {
+                case 0: return String.class;
+                case 1:
+                case 2:
+                case 3: 
+                case 4: return Boolean.class;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isCellEditable(
+            int rowIndex,
+            int columnIndex
+        ) {
+            return columnIndex >= 1 && columnIndex <= 4;
+        }
+    } // class CrossSectionTableModel
+
+    private static boolean change(
+        boolean [] values,
+        int        index, 
+        boolean    value
+    ) {
+        if (values[index] != value) {
+            values[index] = value;
+            return true;
+        }
+        return false;
+    }
+
+    public static class CrossSectionLineItem {
+
+        Double km;
+        List<Pair<CrossSection, CrossSectionLine>> lines;
+
+        public CrossSectionLineItem(
+            Double km, 
+            List<Pair<CrossSection, CrossSectionLine>> lines
+        ) {
+            this.km    = km;
+            this.lines = lines;
+        }
+
+        public String toString() {
+            return String.valueOf(km);
+        }
+    } // CrossSectionLineItem
+
+    public CrossSectionApp(String title) {
+        super(title);
+
+        session = SessionFactoryProvider
+            .createSessionFactory()
+            .openSession();
+
+        JPanel content = createContent();
+        content.setPreferredSize(new Dimension(800, 480));
+        setContentPane(content);
+
+    }
+
+    public List<CrossSection> crossSections(String river) {
+        Query query = session.createQuery(
+            "from CrossSection where river.name = :river");
+        query.setParameter("river", river);
+        return query.list();
+    }
+
+    protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>>
+        loadAllLines(List<CrossSection> crossSections) {
+        Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines =
+            new TreeMap<Double, List<Pair<CrossSection, CrossSectionLine>>>();
+        for (CrossSection cs: crossSections) {
+            List<CrossSectionLine> lines = cs.getLines();
+            for (CrossSectionLine csl: lines) {
+                Double km = Math.round(csl.getKm().doubleValue() * 1000d)/1000d;
+                List<Pair<CrossSection, CrossSectionLine>> ls
+                    = km2lines.get(km);
+                if (ls == null) {
+                    ls = new ArrayList<Pair<CrossSection, CrossSectionLine>>(2);
+                    km2lines.put(km, ls);
+                }
+                ls.add(new Pair<CrossSection, CrossSectionLine>(cs, csl));
+            }
+        }
+        return km2lines;
+    }
+
+    public JPanel createContent() {
+        JPanel panel = new JPanel(new BorderLayout());
+
+
+        JPanel nav = new JPanel(new FlowLayout());
+
+        crossSections = crossSections(RIVER);
+        km2lines = loadAllLines(crossSections);
+
+        int CS = crossSections.size();
+        Arrays.fill(drawCrossSection = new boolean[CS], true);
+        drawWaterLevel = new boolean[CS];
+        drawGround     = new boolean[CS];
+        drawFill       = new boolean[CS];
+
+        Object [] clis = createCrossSectionLineItems(km2lines);
+
+        DefaultComboBoxModel dcbm = new DefaultComboBoxModel(clis);
+
+        crossSectionLinesCB = new JComboBox(dcbm);
+
+        if (KM != null) {
+            try {
+                double km = Double.parseDouble(KM);
+
+                CrossSectionLineItem found = null;
+
+                for (Object o: clis) {
+                    CrossSectionLineItem csli = (CrossSectionLineItem)o;
+                    if (Math.abs(csli.km - km) < EPSILON) {
+                        found = csli;
+                        break;
+                    }
+                }
+
+                if (found != null) {
+                    crossSectionLinesCB.setSelectedItem(found);
+                }
+            }
+            catch (NumberFormatException nfe) {
+                System.err.println("km is not a number: "
+                    + nfe.getMessage());
+            }
+        }
+
+        nav.add(crossSectionLinesCB);
+
+        crossSectionLinesCB.addItemListener(new ItemListener() {
+            @Override
+            public void itemStateChanged(ItemEvent ie) {
+                if (ie.getStateChange() == ItemEvent.SELECTED) {
+                    updateChart();
+                }
+            }
+        });
+
+        waterlevelTF = new JTextField(5);
+
+        if (WATER_LEVEL != null) {
+            try {
+                waterlevelTF.setText(
+                    (lastWaterLevel = Double.valueOf(WATER_LEVEL)).toString());
+            }
+            catch (NumberFormatException nfe) {
+                System.err.println("Water level not a number: " +
+                    nfe.getMessage());
+            }
+        }
+
+        waterlevelTF.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                waterLevelChanged();
+            }
+        });
+
+        nav.add(waterlevelTF);
+
+        JButton dump = new JButton("dump");
+
+        dump.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent ae) {
+                dumpData();
+            }
+        });
+
+        nav.add(dump);
+
+
+        chartPanel = createChartPanel();
+
+        panel.add(chartPanel, BorderLayout.CENTER);
+
+
+        CrossSectionTableModel cstm = new CrossSectionTableModel();
+
+        cstm.addTableModelListener(new TableModelListener() {
+            @Override
+            public void tableChanged(TableModelEvent e) {
+                updateChart();
+            }
+        });
+
+        JTable crossTable = new JTable(cstm);
+
+        JPanel west = new JPanel(new BorderLayout());
+        JScrollPane scrollPane = new JScrollPane(crossTable);
+        west.add(scrollPane);
+
+        west.add(nav, BorderLayout.SOUTH);
+
+        panel.add(west, BorderLayout.WEST);
+
+        return panel;
+    }
+
+    protected void waterLevelChanged() {
+        String value = waterlevelTF.getText();
+        try {
+            lastWaterLevel = Double.valueOf(value);
+        }
+        catch (NumberFormatException nfe) {
+            waterlevelTF.setText(
+                lastWaterLevel != null ? lastWaterLevel.toString() : "");
+            return;
+        }
+        updateChart();
+    }
+
+    protected void updateChart() {
+
+        CrossSectionLineItem csli =
+            (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();
+
+        JFreeChart chart = createChart();
+
+        chartPanel.setChart(chart);
+    }
+
+    protected ChartPanel createChartPanel() {
+        CrossSectionLineItem csli =
+            (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();
+
+        JFreeChart chart = createChart();
+
+        return new ChartPanel(chart);
+    }
+
+    protected void dumpData() {
+
+        CrossSectionLineItem csli =
+            (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();
+
+        if (csli == null) {
+            return;
+        }
+
+
+        double km = Math.round(csli.km.doubleValue() * 1000d)/1000d;
+
+        String kmS = String.valueOf(km).replace(".", "-");
+
+        int i = 1;
+        File file = new File("cross-section-" + kmS + ".txt");
+        while (file.exists()) {
+            file = new File("cross-section-" + kmS + "[" + (i++) + "].txt");
+        }
+
+        System.err.println("dump points to file '" + file + "'");
+
+        PrintWriter out = null;
+
+        MathContext mc = new MathContext(3);
+
+        try {
+            out =
+                new PrintWriter(
+                new FileWriter(file));
+
+            for (Pair<CrossSection, CrossSectionLine> pair: csli.lines) {
+                out.println("# " + pair.getA().getDescription());
+                for (CrossSectionPoint point: pair.getB().getPoints()) {
+                    out.println(
+                        new BigDecimal(point.getX()).round(mc) + " " +
+                        new BigDecimal(point.getY()).round(mc));
+                }
+            }
+
+            out.flush();
+        }
+        catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+        finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    public void generateWaterLevels(
+        List<Point2D>                         points,
+        List<Pair<XYDataset, XYItemRenderer>> datasets
+    ) {
+        if (points == null || points.isEmpty() || lastWaterLevel == null) {
+            return;
+        }
+
+        double [][] data = Lines.createWaterLines(points, lastWaterLevel).points;
+        XYSeries series =
+            new XYSeries(String.valueOf(lastWaterLevel), false);
+
+        double [] x = data[0];
+        double [] y = data[1];
+        for (int i = 0; i < x.length; ++i) {
+            series.add(x[i], y[i], false);
+        }
+
+        datasets.add(new Pair<XYDataset, XYItemRenderer>(
+            new XYSeriesCollection(series), null));
+    }
+
+    public void generateFill(
+        List<Point2D>                         points,
+        String                                legend,
+        List<Pair<XYDataset, XYItemRenderer>> datasets
+    ) {
+        if (points == null || points.isEmpty() || lastWaterLevel == null) {
+            return;
+        }
+
+        double [][] data   = Lines.createWaterLines(points, lastWaterLevel).points;
+        double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);
+
+        DefaultXYDataset dataset = new DefaultXYDataset();
+
+        dataset.addSeries(legend + "-Linie", values);
+        dataset.addSeries(legend + "-Fl\u00e4che", data);
+
+        datasets.add(new Pair<XYDataset, XYItemRenderer>(
+            dataset,
+            new StableXYDifferenceRenderer(
+                TRANSPARENT, Color.blue, false)));
+    }
+
+    public void generateProfile(
+        List<Point2D>                         points,
+        String                                legend,
+        List<Pair<XYDataset, XYItemRenderer>> datasets
+    ) {
+        if (points == null || points.isEmpty()) {
+            return;
+        }
+
+        double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);
+
+        XYSeries series = new XYSeries(legend, false);
+
+        double [] x = values[0];
+        double [] y = values[1];
+        for (int i = 0; i < x.length; ++i) {
+            series.add(x[i], y[i], false);
+        }
+
+        datasets.add(new Pair<XYDataset, XYItemRenderer>(
+            new XYSeriesCollection(series), null));;
+    }
+
+
+    /**
+     * @param legend the legend entry.
+     */
+    public void generateGround(
+        List<Point2D>                         points,
+        String                                legend,
+        List<Pair<XYDataset, XYItemRenderer>> datasets
+    ) {
+        if (points == null || points.isEmpty()) {
+            return;
+        }
+
+        double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);
+
+        DefaultXYDataset dataset = new DefaultXYDataset();
+
+        XYSeries series = new XYSeries(legend, false);
+
+        dataset.addSeries(legend, values);
+
+        StableXYDifferenceRenderer renderer =
+            new StableXYDifferenceRenderer();
+
+        datasets.add(new Pair<XYDataset, XYItemRenderer>(
+            dataset, renderer));
+    }
+
+    public List<Pair<XYDataset, XYItemRenderer>> generateDatasets() {
+
+        List<Pair<XYDataset, XYItemRenderer>> datasets =
+            new ArrayList<Pair<XYDataset, XYItemRenderer>>();
+
+        CrossSectionLineItem csli =
+            (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();
+
+        for (int i = 0; i < drawCrossSection.length; ++i) {
+            List<Point2D> points = null;
+            CrossSection cs = crossSections.get(i);
+
+            if (drawGround[i]) {
+                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
+                    if (csl.getA() == cs) {
+                        if (points == null) {
+                            points = csl.getB().fetchCrossSectionLinesPoints();
+                        }
+                        generateGround(
+                            points,
+                            cs.getDescription() + "/Boden",
+                            datasets);
+                        break;
+                    }
+                }
+            }
+
+            if (drawFill[i]) {
+                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
+                    if (csl.getA() == cs) {
+                        if (points == null) {
+                            points = csl.getB().fetchCrossSectionLinesPoints();
+                        }
+
+                        generateFill(
+                            points, cs.getDescription(), datasets);
+                        break;
+                    }
+                }
+            }
+
+            if (drawCrossSection[i]) {
+                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
+                    if (csl.getA() == cs) {
+                        if (points == null) {
+                            points = csl.getB().fetchCrossSectionLinesPoints();
+                        }
+
+                        generateProfile(
+                            points, cs.getDescription(), datasets);
+                        break;
+                    }
+                }
+            }
+
+            if (drawWaterLevel[i]) {
+                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
+                    if (csl.getA() == cs) {
+                        if (points == null) {
+                            points = csl.getB().fetchCrossSectionLinesPoints();
+                        }
+                        generateWaterLevels(points, datasets);
+                        break;
+                    }
+                }
+            }
+
+        }
+
+        return datasets;
+    }
+
+    protected Object [] createCrossSectionLineItems(
+        Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines
+    ) {
+        Object [] result = new Object[km2lines.size()];
+        int i = 0;
+        for (Map.Entry<Double, List<Pair<CrossSection, CrossSectionLine>>> entry:
+            km2lines.entrySet()) {
+            result[i++] = new CrossSectionLineItem(
+                entry.getKey(),
+                entry.getValue());
+        }
+        return result;
+    }
+
+
+    public JFreeChart createChart() {
+        JFreeChart chart = ChartFactory.createXYLineChart(
+            null,
+            "Abstand [m]",
+            "H\u00f6he [m]",
+            null,
+            PlotOrientation.VERTICAL,
+            true,
+            true,
+            false);
+
+        List<Pair<XYDataset, XYItemRenderer>> datasets =
+            generateDatasets();
+
+        XYPlot plot = chart.getXYPlot();
+
+        for (int i = 0, N = datasets.size(); i < N; ++i) {
+            Pair<XYDataset, XYItemRenderer> p = datasets.get(i);
+            plot.setDataset(i, p.getA());
+            plot.mapDatasetToRangeAxis(i, 0);
+            XYItemRenderer renderer = p.getB();
+            if (renderer != null) {
+                plot.setRenderer(i, renderer);
+            }
+        }
+
+        NumberAxis yAxis = (NumberAxis)plot.getRangeAxis();
+        yAxis.setAutoRangeIncludesZero(false);
+
+        ChartUtilities.applyCurrentTheme(chart);
+        return chart;
+    }
+
+    protected static Paint createTransparentPaint() {
+        BufferedImage texture = new BufferedImage(
+            1, 1, BufferedImage.TYPE_4BYTE_ABGR);
+
+        return new TexturePaint(
+            texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
+    }
+
+    public static void main(String [] args) {
+        CrossSectionApp csa = new CrossSectionApp("Querprofile");
+        csa.pack();
+        RefineryUtilities.centerFrameOnScreen(csa);
+        csa.setVisible(true);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/charts/TimeseriesStepChart.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,217 @@
+package de.intevation.flys.artifacts.charts;
+
+import java.awt.Dimension;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.ui.ApplicationFrame;
+
+import org.jfree.data.time.SimpleTimePeriod;
+import org.jfree.data.time.Second;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.data.time.TimePeriodValues;
+import org.jfree.data.time.TimePeriodValuesCollection;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+
+
+public class TimeseriesStepChart extends ApplicationFrame {
+
+    private static final Logger logger =
+        Logger.getLogger(TimeseriesStepChart.class);
+
+
+    public static final String LOG4J_PROPERTIES = "FLYS_CLIENT_LOG4J_PROPERIES";
+
+    public static final int USER_CHART_TYPE =
+        Integer.getInteger("chart.type", -1);
+
+    public static final int CHART_TYPE_TIMEPERIOD = 1;
+    public static final int CHART_TYPE_TIMESERIES = 2;
+
+
+    public static void main(String[] args) {
+        configureLogging();
+
+        logger.info("Start TimeseriesStepChart demo.");
+
+        if (USER_CHART_TYPE <= 0) {
+            logger.error("Unknown chart type: " + USER_CHART_TYPE);
+        }
+
+        logger.debug("Create Chart from type: " + USER_CHART_TYPE);
+
+        TimeseriesStepChart tsc = new TimeseriesStepChart();
+        tsc.build(USER_CHART_TYPE);
+
+        logger.info("End of TimeseriesStepChart demo.");
+    }
+
+
+    public static void configureLogging() {
+        String log4jProperties = System.getenv(LOG4J_PROPERTIES);
+
+        if (log4jProperties != null && log4jProperties.length() > 0) {
+            PropertyConfigurator.configure(log4jProperties);
+            logger.info("Log4J logging initialized.");
+        }
+        else {
+            System.out.println("Error while setting up Log4J configuration.");
+        }
+
+        System.out.println("LoggingConfigurator.init finished");
+    }
+
+
+    public TimeseriesStepChart() {
+        super("TimeseriesStepChart");
+    }
+
+
+    public void build(int type) {
+        ChartPanel chartPanel = createChartPanel(type);
+        chartPanel.setPreferredSize(new Dimension(500, 300));
+
+        setContentPane(chartPanel);
+
+        pack();
+        setVisible(true);
+    }
+
+
+    protected ChartPanel createChartPanel(int type) {
+        JFreeChart chart = createJFreeChart(type);
+        ChartPanel panel = new ChartPanel(chart);
+        return panel;
+    }
+
+
+    protected JFreeChart createJFreeChart(int type) {
+        if (type == CHART_TYPE_TIMEPERIOD) {
+            return createTimePeriodValuesChart();
+        }
+        else if (type == CHART_TYPE_TIMESERIES) {
+            return createTimeSeriesChart();
+        }
+
+        logger.error("Unknown chart type: " + type);
+
+        return null;
+    }
+
+
+    public static Date createDate(int year, int month, int day) {
+        GregorianCalendar cal = new GregorianCalendar(year, month, day);
+        return cal.getTime();
+    }
+
+
+
+    //
+    // XXX FOLLOWING METHODS ARE USED FOR "TimePeriodValues" CHART TYPE
+    //
+    protected JFreeChart createTimePeriodValuesChart() {
+        return ChartFactory.createTimeSeriesChart(
+            "TimePeriodValues Chart",
+            "Time Axis",
+            "Y Axis",
+            createTimePeriodValuesDataset(),
+            true,
+            false,
+            false);
+    }
+
+
+    protected TimePeriodValuesCollection createTimePeriodValuesDataset() {
+        TimePeriodValuesCollection dataset = new TimePeriodValuesCollection();
+        TimePeriodValues series = createTimePeriodValues();
+
+        dataset.addSeries(series);
+
+        return dataset;
+    }
+
+
+    protected TimePeriodValues createTimePeriodValues() {
+        TimePeriodValues series1 = new TimePeriodValues("Series 1");
+
+        Date start1 = createDate(2000, 0, 1);
+        Date end1   = createDate(2000, 11, 31);
+
+        logger.debug("START DATE 1 = " + start1);
+        logger.debug("END   DATE 1 = " + end1);
+
+        SimpleTimePeriod period1 = new SimpleTimePeriod(start1, end1);
+
+        Date start2 = createDate(2001, 0, 1);
+        Date end2   = createDate(2001, 11, 31);
+        SimpleTimePeriod period2 = new SimpleTimePeriod(start2, end2);
+
+        Date start3 = createDate(2002, 0, 1);
+        Date end3   = createDate(2002, 11, 31);
+        SimpleTimePeriod period3 = new SimpleTimePeriod(start3, end3);
+
+        series1.add(period1, 100);
+        series1.add(period2, 200);
+        series1.add(period3, 150);
+
+        return series1;
+    }
+
+
+    //
+    // XXX FOLLOWING METHODS ARE USED FOR "TimePeriodValues" CHART TYPE
+    //
+    protected JFreeChart createTimeSeriesChart() {
+        return ChartFactory.createTimeSeriesChart(
+            "TimeSeriesCollection Chart",
+            "Time Axis",
+            "Y Axis",
+            createTimeSeriesCollectionDataset(),
+            true,
+            false,
+            false);
+    }
+
+
+    protected TimeSeriesCollection createTimeSeriesCollectionDataset() {
+        TimeSeriesCollection dataset = new TimeSeriesCollection();
+        TimeSeries[]         series  = createTimeSeries();
+
+        for (TimeSeries tmp: series) {
+            dataset.addSeries(tmp);
+        }
+
+        return dataset;
+    }
+
+
+    protected TimeSeries[] createTimeSeries() {
+        TimeSeries series1 = new TimeSeries("Series 1");
+        series1.add(new Second(0, 0, 0, 1, 1, 2000), 100);
+        series1.add(new Second(59, 59, 23, 31, 12, 2000), 100);
+
+        series1.add(new Second(0, 0, 0, 1, 1, 2001), 200);
+        series1.add(new Second(59, 59, 23, 31, 12, 2001), 200);
+
+        series1.add(new Second(0, 0, 0, 1, 1, 2002), 150);
+        series1.add(new Second(59, 59, 23, 31, 12, 2002), 150);
+
+        TimeSeries series2 = new TimeSeries("Series 2");
+        series2.add(new Second(0, 0, 0, 1, 1, 2000), 10);
+        series2.add(new Second(59, 59, 23, 31, 12, 2000), 10);
+
+        series2.add(new Second(0, 0, 0, 1, 1, 2001), 20);
+        series2.add(new Second(59, 59, 23, 31, 12, 2001), 20);
+
+        series2.add(new Second(0, 0, 0, 1, 1, 2002), 15);
+        series2.add(new Second(59, 59, 23, 31, 12, 2002), 15);
+
+        return new TimeSeries[] { series1, series2 };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/context/FLYSContext.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,69 @@
+package de.intevation.flys.artifacts.context;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.DefaultArtifactContext;
+
+
+/**
+ * This class is used to store application wide information in a global context.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FLYSContext extends DefaultArtifactContext {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(FLYSContext.class);
+
+    /** The key that is used to store the StateEngine in the context. */
+    public static final String ARTIFACT_KEY =
+        "artifact";
+
+    /** The key that is used to store the TransitionEngine in the context. */
+    public static final String TRANSITION_ENGINE_KEY =
+        "artifact.transition.engine";
+
+    /** The key that is used to store the StateEngine in the context. */
+    public static final String STATE_ENGINE_KEY =
+        "artifact.state.engine";
+
+    /** The key that is used to store the Map of OutGenerator classes in the
+     * context. */
+    public static final String OUTGENERATORS_KEY =
+        "flys.export.outgenerators";
+
+    /** The key that is used to store the map of themes in the context. */
+    public static final String THEMES =
+        "flys.themes.map";
+
+    /** The key that is used to store a map of theme mappings in the context. */
+    public static final String THEME_MAPPING =
+        "flys.themes.mapping.map";
+
+    /** The key that is used to store a map of WMS urls for each river. */
+    public static final String RIVER_WMS =
+        "flys.floodmap.river.wms";
+
+    /** The key that is used to store an instance of Scheduler in the context.*/
+    public static final String SCHEDULER =
+        "flys.wsplgen.scheduler";
+
+
+    /**
+     * The default constructor.
+     */
+    public FLYSContext() {
+        super();
+    }
+
+
+    /**
+     * A constructor with a config document.
+     */
+    public FLYSContext(Document config) {
+        super(config);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/context/FLYSContextFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,422 @@
+package de.intevation.flys.artifacts.context;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.ArtifactContextFactory;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.state.StateEngine;
+import de.intevation.artifactdatabase.transition.Transition;
+import de.intevation.artifactdatabase.transition.TransitionEngine;
+
+import de.intevation.flys.artifacts.states.StateFactory;
+import de.intevation.flys.artifacts.transitions.TransitionFactory;
+import de.intevation.flys.themes.Theme;
+import de.intevation.flys.themes.ThemeGroup;
+import de.intevation.flys.themes.ThemeFactory;
+import de.intevation.flys.themes.ThemeMapping;
+
+
+/**
+ * The ArtifactContextFactory is used to initialize basic components and put
+ * them into the global context of the application.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FLYSContextFactory implements ArtifactContextFactory {
+
+    /** The logger that is used in this class. */
+    private static Logger logger = Logger.getLogger(FLYSContextFactory.class);
+
+    /** The XPath to the artifacts configured in the configuration. */
+    public static final String XPATH_ARTIFACTS =
+        "/artifact-database/artifacts/artifact";
+
+    /** The XPath to the name of the artifact. */
+    public static final String XPATH_ARTIFACT_NAME = "/artifact/@name";
+
+    /** The XPath to the xlink ref in an artifact configuration. */
+    public static final String XPATH_XLINK = "xlink:href";
+
+    /** The XPath to the transitions configured in the artifact config. */
+    public static final String XPATH_TRANSITIONS =
+        "/artifact/states/transition";
+
+    /** The XPath to the states configured in the artifact config. */
+    public static final String XPATH_STATES =
+        "/artifact/states/state";
+
+    public static final String XPATH_OUTPUT_GENERATORS =
+        "/artifact-database/output-generators/output-generator";
+
+    public static final String XPATH_THEME_CONFIG =
+        "/artifact-database/flys/themes/configuration/text()";
+
+    public static final String XPATH_THEMES =
+        "theme";
+
+    public static final String XPATH_THEME_GROUPS =
+        "/themes/themegroup";
+
+    public static final String XPATH_THEME_MAPPINGS =
+        "/themes/mappings/mapping";
+
+    public static final String XPATH_RIVER_WMS =
+        "/artifact-database/floodmap/river";
+
+    /**
+     * Creates a new FLYSArtifactContext object and initialize all
+     * components required by the application.
+     *
+     * @param config The artifact server configuration.
+     * @return a FLYSArtifactContext.
+     */
+    public GlobalContext createArtifactContext(Document config) {
+        FLYSContext context = new FLYSContext(config);
+
+        configureTransitions(config, context);
+        configureStates(config, context);
+        configureOutGenerators(config, context);
+        configureThemes(config, context);
+        configureThemesMappings(config, context);
+        configureRiverWMS(config, context);
+
+        return context;
+    }
+
+
+    /**
+     * This method initializes the transition configuration.
+     *
+     * @param config the config document.
+     * @param context the FLYSContext.
+     */
+    protected void configureTransitions(Document config, FLYSContext context) {
+        TransitionEngine engine = new TransitionEngine();
+
+        Document[] artifacts = getArtifactConfigurations(config);
+        logger.info("Found " + artifacts.length + " artifacts in the config.");
+
+        for (Document doc: artifacts) {
+            List<Transition> transitions = new ArrayList<Transition>();
+
+            String artName = (String) XMLUtils.xpath(
+                doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
+
+            NodeList list = (NodeList) XMLUtils.xpath(
+                doc, XPATH_TRANSITIONS, XPathConstants.NODESET);
+
+            if (list == null) {
+                logger.warn("The artifact " + artName +
+                    " has no transitions configured.");
+                continue;
+            }
+
+            int trans = list.getLength();
+
+            logger.info(
+                "Artifact '" + artName + "' has " + trans + " transitions.");
+
+            for (int i = 0; i < trans; i++) {
+                Transition t = TransitionFactory.createTransition(list.item(i));
+                String     s = t.getFrom();
+                engine.addTransition(s, t);
+            }
+        }
+
+        context.put(FLYSContext.TRANSITION_ENGINE_KEY, engine);
+    }
+
+
+    /**
+     * This method returns all artifact documents defined in
+     * <code>config</code>. <br>NOTE: The artifact configurations need to be
+     * stored in own files referenced by an xlink.
+     *
+     * @param config The global configuration.
+     *
+     * @return an array of Artifact configurations.
+     */
+    protected Document[] getArtifactConfigurations(Document config) {
+        NodeList artifacts = (NodeList) XMLUtils.xpath(
+            config, XPATH_ARTIFACTS, XPathConstants.NODESET);
+
+        int count = artifacts.getLength();
+
+        Document[] artifactDocs = new Document[count];
+
+        for (int i = 0; i < count; i++) {
+            Element tmp = (Element) artifacts.item(i);
+
+            String xlink = tmp.getAttribute(XPATH_XLINK);
+            xlink        = Config.replaceConfigDir(xlink);
+
+            File artifactFile = new File(xlink);
+            artifactDocs[i]   = XMLUtils.parseDocument(artifactFile);
+        }
+
+        return artifactDocs;
+    }
+
+
+    /**
+     * This method initializes the transition configuration.
+     *
+     * @param config the config document.
+     * @param context the FLYSContext.
+     */
+    protected void configureStates(Document config, FLYSContext context) {
+        StateEngine engine = new StateEngine();
+
+        Document[] artifacts = getArtifactConfigurations(config);
+        logger.info("Found " + artifacts.length + " artifacts in the config.");
+
+        for (Document doc: artifacts) {
+            List<State> states = new ArrayList<State>();
+
+            String artName = (String) XMLUtils.xpath(
+                doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
+
+            NodeList stateList = (NodeList) XMLUtils.xpath(
+                doc, XPATH_STATES, XPathConstants.NODESET);
+
+            if (stateList == null) {
+                logger.warn("The artifact " + artName +
+                    " has no states configured.");
+                continue;
+            }
+
+            int count = stateList.getLength();
+
+            logger.info(
+                "Artifact '" + artName + "' has " + count + " states.");
+
+            for (int i = 0; i < count; i++) {
+                states.add(StateFactory.createState(
+                    stateList.item(i)));
+            }
+
+            engine.addStates(artName, states);
+        }
+
+        context.put(FLYSContext.STATE_ENGINE_KEY, engine);
+    }
+
+
+    /**
+     * This method intializes the provided output generators.
+     *
+     * @param config the config document.
+     * @param context the FLYSContext.
+     */
+    protected void configureOutGenerators(Document config, FLYSContext context){
+        Map<String, Class> generators = new HashMap<String, Class>();
+
+        NodeList outGenerators = (NodeList) XMLUtils.xpath(
+            config,
+            XPATH_OUTPUT_GENERATORS,
+            XPathConstants.NODESET);
+
+        int num = outGenerators == null ? 0 : outGenerators.getLength();
+
+        if (num == 0) {
+            logger.warn("No output generators configured in this application.");
+            return;
+        }
+
+        logger.info("Found " + num + " configured output generators.");
+
+        int idx = 0;
+
+        for (int i = 0; i < num; i++) {
+            Node item = outGenerators.item(i);
+
+            String name = (String) XMLUtils.xpath(
+                item, "@name", XPathConstants.STRING);
+
+            String clazz = (String) XMLUtils.xpath(
+                item, "text()", XPathConstants.STRING);
+
+            if (name == null || clazz == null) {
+                continue;
+            }
+
+            try {
+                generators.put(name, Class.forName(clazz));
+
+                idx++;
+            }
+            catch (ClassNotFoundException cnfe) {
+                logger.warn(cnfe, cnfe);
+            }
+        }
+
+        logger.info("Successfully loaded " + idx + " output generators.");
+        context.put(FLYSContext.OUTGENERATORS_KEY, generators);
+    }
+
+
+    /**
+     * This methods reads the configured themes and puts them into the
+     * FLYSContext.
+     *
+     * @param config The global configuration.
+     * @param context The FLYSContext.
+     */
+    protected void configureThemes(Document config, FLYSContext context) {
+        logger.debug("FLYSContextFactory.configureThemes");
+
+        Document cfg = getThemeConfig(config);
+
+        NodeList themeGroups = (NodeList) XMLUtils.xpath(
+            cfg, XPATH_THEME_GROUPS, XPathConstants.NODESET);
+
+        int groupNum = themeGroups != null ? themeGroups.getLength() : 0;
+
+        if (groupNum == 0) {
+            logger.warn("There are no theme groups configured!");
+        }
+
+        List<ThemeGroup> groups = new ArrayList<ThemeGroup>();
+
+        for (int g = 0; g < groupNum; g++) {
+            Element themeGroup = (Element) themeGroups.item(g);
+            NodeList themes = (NodeList) XMLUtils.xpath(
+                themeGroup, XPATH_THEMES, XPathConstants.NODESET);
+
+            int num = themes != null ? themes.getLength() : 0;
+
+            if (num == 0) {
+                logger.warn("There are no themes configured!");
+                return;
+            }
+
+            logger.debug("Found " + num + " configured theme groups.");
+
+            Map<String, Theme> theThemes = new HashMap<String, Theme>();
+
+            for (int i = 0; i < num; i++) {
+                Node theme = themes.item(i);
+
+                Theme theTheme = ThemeFactory.createTheme(cfg, theme);
+
+                if (theme != null) {
+                    theThemes.put(theTheme.getName(), theTheme);
+                }
+            }
+            String gName = themeGroup.getAttribute("name");
+            groups.add(new ThemeGroup(gName, theThemes));
+            logger.debug("Initialized " + theThemes.size() + "/" + num + " themes");
+        }
+        context.put(FLYSContext.THEMES, groups);
+    }
+
+    /**
+     * This method is used to retrieve the theme configuration document.
+     *
+     * @param config The global configuration.
+     *
+     * @return the theme configuration.
+     */
+    protected Document getThemeConfig(Document config) {
+        String themeConfig = (String) XMLUtils.xpath(
+            config,
+            XPATH_THEME_CONFIG,
+            XPathConstants.STRING);
+
+        themeConfig = Config.replaceConfigDir(themeConfig);
+
+        logger.debug("Parse theme cfg: " + themeConfig);
+
+        return XMLUtils.parseDocument(new File(themeConfig));
+    }
+
+
+    protected void configureThemesMappings(Document cfg, FLYSContext context) {
+        logger.debug("FLYSContextFactory.configureThemesMappings");
+
+        Document config = getThemeConfig(cfg);
+
+        NodeList mappings = (NodeList) XMLUtils.xpath(
+            config, XPATH_THEME_MAPPINGS, XPathConstants.NODESET);
+
+        int num = mappings != null ? mappings.getLength() : 0;
+
+        if (num == 0) {
+            logger.warn("No theme <--> facet mappins found!");
+            return;
+        }
+
+        Map<String, List<ThemeMapping>> mapping =
+            new HashMap<String, List<ThemeMapping>>();
+
+        for (int i = 0; i < num; i++) {
+            Element node = (Element)mappings.item(i);
+
+            String from              = node.getAttribute("from");
+            String to                = node.getAttribute("to");
+            String pattern           = node.getAttribute("pattern");
+            String masterAttrPattern = node.getAttribute("masterAttr");
+            String outputPattern     = node.getAttribute("output");
+
+            if (from.length() > 0 && to.length() > 0) {
+                List<ThemeMapping> tm = mapping.get(from);
+
+                if (tm == null) {
+                    tm = new ArrayList<ThemeMapping>();
+                    mapping.put(from, tm);
+                }
+
+                tm.add(new ThemeMapping(
+                    from, to, pattern, masterAttrPattern, outputPattern));
+            }
+        }
+
+        logger.debug("Found " + mapping.size() + " theme mappings.");
+
+        context.put(FLYSContext.THEME_MAPPING, mapping);
+    }
+
+
+    protected void configureRiverWMS(Document cfg, FLYSContext context) {
+        Map<String, String> riverWMS = new HashMap<String, String>();
+
+        NodeList rivers = (NodeList) XMLUtils.xpath(
+            cfg, XPATH_RIVER_WMS, XPathConstants.NODESET);
+
+        int num = rivers != null ? rivers.getLength() : 0;
+
+        for (int i = 0; i < num; i++) {
+            Element e = (Element) rivers.item(i);
+
+            String river = e.getAttribute("name");
+            String url   = XMLUtils.xpathString(e, "river-wms/@url", null);
+
+            if (river != null && url != null) {
+                riverWMS.put(river, url);
+            }
+        }
+
+        logger.debug("Found " + riverWMS.size() + " river WMS.");
+
+        context.put(FLYSContext.RIVER_WMS, riverWMS);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/context/SessionCallContextListener.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,68 @@
+package de.intevation.flys.artifacts.context;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import org.hibernate.Session;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallContext.Listener;
+
+
+/**
+ * This CallContextListener is used to initialize a ThreadLocal variable in
+ * each CallContext (for each request) that holds Sessions.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class SessionCallContextListener implements Listener {
+
+    public static final String SESSION_KEY = "context.session";
+
+    /** The logger that is used in this class.*/
+    private static Logger logger =
+        Logger.getLogger(SessionCallContextListener.class);
+
+
+    public SessionCallContextListener() {
+    }
+
+
+    public void setup(Document config, Node listenerConfig) {
+        // nothing to do here
+    }
+
+
+    /**
+     * Initializes a ThreadLocal variable that is used to hold sessions.
+     *
+     * @param context The CallContext.
+     */
+    public void init(CallContext context) {
+        logger.debug("SessionCallContextListener.init");
+
+        Session session = SessionHolder.acquire();
+
+        context.putContextValue(SESSION_KEY, session);
+    }
+
+
+    /**
+     * Closes open sessions of the ThreadLocal variable opened in init().
+     *
+     * @param context The CallContext.
+     */
+    public void close(CallContext context) {
+        logger.debug("SessionCallContextListener.close");
+
+        Session session = (Session)context.getContextValue(SESSION_KEY);
+        session.close();
+
+        SessionHolder.release();
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/DBConfig.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,108 @@
+package de.intevation.flys.artifacts.datacage;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.artifactdatabase.db.SQL;
+import de.intevation.artifactdatabase.db.DBConnection;
+
+import org.apache.log4j.Logger;
+
+public class DBConfig
+{ 
+    private static Logger logger = Logger.getLogger(DBConfig.class);
+
+     /**
+     * XPath to access the database driver within the global configuration.
+     */
+    public static final String DB_DRIVER =
+        "/artifact-database/datacage/driver/text()";
+    /**
+     * XPath to access the database URL within the global configuration.
+     */
+    public static final String DB_URL =
+        "/artifact-database/datacage/url/text()";
+    /**
+     * XPath to access the database use within the global configuration.
+     */
+    public static final String DB_USER =
+        "/artifact-database/datacage/user/text()";
+    /**
+     * XPath to access the database password within the global configuration.
+     */
+    public static final String DB_PASSWORD =
+        "/artifact-database/datacage/password/text()";
+
+    /**
+     * The default database driver: H2
+     */
+    public static final String DEFAULT_DRIVER =
+        "org.h2.Driver";
+
+    /**
+     * The default database user: ""
+     */
+    public static final String DEFAULT_USER = "";
+
+    /**
+     * The default database password: ""
+     */
+    public static final String DEFAULT_PASSWORD = "";
+
+
+    public static final String DEFAULT_URL =
+        "jdbc:h2:mem:datacage;INIT=RUNSCRIPT FROM '${artifacts.config.dir}/datacage.sql'";
+
+    public static final String RESOURCE_PATH = "/datacage-sql";
+
+    private static DBConfig instance;
+
+    protected DBConnection dbConnection;
+    protected SQL          sql;
+
+    public DBConfig() {
+    }
+
+    public DBConfig(DBConnection dbConnection, SQL sql) {
+        this.dbConnection = dbConnection;
+        this.sql          = sql;
+    }
+
+    public static synchronized DBConfig getInstance() {
+        if (instance == null) {
+            instance = createInstance();
+        }
+        return instance;
+    }
+
+    protected static DBConfig createInstance() {
+        String driver = Config.getStringXPath(
+            DB_DRIVER, DEFAULT_DRIVER);
+
+        String url = Config.getStringXPath(
+            DB_URL, DEFAULT_URL);
+
+        url = Config.replaceConfigDir(url);
+
+        String user = Config.getStringXPath(
+            DB_USER, DEFAULT_USER);
+
+        String password = Config.getStringXPath(
+            DB_PASSWORD, DEFAULT_PASSWORD);
+
+        DBConnection dbConnection = new DBConnection(
+            driver, url, user, password);
+
+        SQL sql = new SQL(DBConfig.class, RESOURCE_PATH, driver);
+
+        return new DBConfig(dbConnection, sql);
+    }
+
+    public DBConnection getDBConnection() {
+        return dbConnection;
+    }
+
+    public SQL getSQL() {
+        return sql;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/Datacage.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1074 @@
+package de.intevation.flys.artifacts.datacage;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Date;
+
+import java.sql.SQLException;
+import java.sql.PreparedStatement;
+import java.sql.Types;
+import java.sql.Timestamp;
+
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.ArtifactCollection;
+import de.intevation.artifacts.User;
+
+import de.intevation.artifactdatabase.db.SQL;
+import de.intevation.artifactdatabase.db.SQLExecutor;
+
+import de.intevation.artifactdatabase.LifetimeListener;
+import de.intevation.artifactdatabase.Backend;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.artifacts.common.utils.LRUCache;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+public class Datacage
+implements   LifetimeListener
+{
+    private static Logger log = Logger.getLogger(Datacage.class);
+
+    public static final String DATACAGE_KEY =
+        "global.datacage.instance";
+
+    public static final String ARTEFACT_DATABASE_KEY =
+        "global.artifact.database";
+
+    private String SQL_DELETE_ALL_USERS      = "delete.all.users";
+    private String SQL_DELETE_ALL_ARTIFACTS  = "delete.all.artifacts";
+    private String SQL_USER_ID_NEXTVAL       = "user.id.nextval";
+    private String SQL_USER_BY_GID           = "user.by.gid";
+    private String SQL_INSERT_USER           = "insert.user";
+    private String SQL_COLLECTION_BY_GID     = "collection.by.gid";
+    private String SQL_COLLECTION_ID_NEXTVAL = "collection.id.nextval";
+    private String SQL_INSERT_COLLECTION     = "insert.collection";
+    private String SQL_ARTIFACT_BY_GID       = "artifact.by.gid";
+    private String SQL_COLLECTION_ITEM_ID_NEXTVAL =
+        "collection.item.id.nextval";
+    private String SQL_INSERT_COLLECTION_ITEM = "insert.collection.item";
+    private String SQL_ARTIFACT_ID_NEXTVAL    = "artifact.id.nextval";
+    private String SQL_INSERT_ARTIFACT        = "insert.artifact";
+    private String SQL_ARTIFACT_DATA_ID_NEXTVAL = "artifact.data.id.nextval";
+    private String SQL_INSERT_ARTIFACT_DATA   = "insert.artifact.data";
+    private String SQL_OUT_ID_NEXTVALUE       = "out.id.nextval";
+    private String SQL_INSERT_OUT             = "insert.out";
+    private String SQL_FACET_ID_NEXTVAL       = "facet.id.nextval";
+    private String SQL_INSERT_FACET           = "insert.facet";
+    private String SQL_UPDATE_COLLECTION_NAME = "update.collection.name";
+    private String SQL_DELETE_ARTIFACT_FROM_COLLECTION =
+        "delete.artifact.from.collection";
+    private String SQL_DELETE_COLLECTION_BY_GID =
+        "delete.collection.by.gid";
+    private String SQL_DELETE_USER_BY_GID = "delete.user.by.gid";
+    private String SQL_DELETE_ARTIFACT_DATA_BY_ARTIFACT_ID =
+        "delete.artifact.data.by.artifact.id";
+    private String SQL_DELETE_OUTS_BY_ARTIFACT_ID =
+        "delete.outs.by.artifact.id";
+    private String SQL_DELETE_FACETS_BY_ARTIFACT_ID =
+        "delete.facets.by.artifact.id";
+    private String SQL_DELETE_ARTIFACT_BY_GID =
+        "delete.artifact.by.gid";
+
+    protected SQLExecutor sqlExecutor;
+
+    public class InitialScan 
+    implements   ArtifactDatabase.ArtifactLoadedCallback
+    {
+        protected LRUCache<String, Integer> users;
+        protected LRUCache<String, Integer> collections;
+        protected LRUCache<String, Integer> artifacts;
+
+        protected GlobalContext context;
+
+        public InitialScan() {
+            users       = new LRUCache<String, Integer>();
+            collections = new LRUCache<String, Integer>();
+            artifacts   = new LRUCache<String, Integer>();
+        }
+
+        public InitialScan(GlobalContext context) {
+            this();
+            this.context = context;
+        }
+
+        @Override
+        public void artifactLoaded(
+            String   userId,
+            String   collectionId,
+            String   collectionName,
+            Date     collectionCreated,
+            String   artifactId,
+            Date     artifactCreated,
+            Artifact artifact
+        ) {
+            if (!(artifact instanceof FLYSArtifact)) {
+                log.warn("ignoring none FLYS artifacts");
+                return;
+            }
+
+            FLYSArtifact flysArtifact = (FLYSArtifact)artifact;
+
+            Integer uId = getUserId(userId);
+            Integer cId = getCollectionId(
+                collectionId, uId, collectionName, collectionCreated);
+
+            storeArtifact(artifactId, cId, flysArtifact, artifactCreated);
+        }
+
+        protected Integer getId(
+            LRUCache<String, Integer> cache,
+            final String              idString,
+            final String              selectById
+        ) {
+            Integer id = cache.get(idString);
+            if (id != null) {
+                return id;
+            }
+
+            final Integer [] res = new Integer[1];
+
+            SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+                @Override
+                public boolean doIt() throws SQLException {
+                    prepareStatement(selectById);
+                    stmnt.setString(1, idString);
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        return false;
+                    }
+                    res[0] = result.getInt(1);
+                    return true;
+                }
+            };
+
+            if (exec.runRead()) {
+                cache.put(idString, res[0]);
+                return res[0];
+            }
+
+            return null;
+        }
+
+        protected void storeArtifact(
+            final String       artifactId,
+            Integer            collectionId,
+            final FLYSArtifact artifact,
+            final Date         artifactCreated
+        ) {
+            Integer aId = getId(artifacts, artifactId, SQL_ARTIFACT_BY_GID);
+
+            if (aId != null) {
+                // We've already stored it. Just create the collection item.
+                storeCollectionItem(collectionId, aId);
+                return;
+            }
+            // We need to write it to database
+
+            final Integer [] res = new Integer[1];
+
+            SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+                @Override
+                public boolean doIt() throws SQLException {
+                    prepareStatement(SQL_ARTIFACT_ID_NEXTVAL);
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        return false;
+                    }
+                    res[0] = result.getInt(1);
+                    reset();
+                    prepareStatement(SQL_INSERT_ARTIFACT);
+                    stmnt.setInt   (1, res[0]);
+                    stmnt.setString(2, artifactId);
+                    stmnt.setString(3, artifact.getCurrentStateId());
+                    Timestamp timestamp = new Timestamp(artifactCreated != null
+                        ? artifactCreated.getTime()
+                        : System.currentTimeMillis());
+                    stmnt.setTimestamp(4, timestamp);
+                    stmnt.execute();
+                    conn.commit();
+                    return true;
+                }
+            };
+
+            if (!exec.runWrite()) {
+                log.error("storing of artifact failed.");
+                return;
+            }
+
+            artifacts.put(artifactId, aId = res[0]);
+
+            storeCollectionItem(collectionId, aId);
+
+            storeData(aId, artifact);
+
+            storeOuts(aId, artifact, context);
+        }
+
+
+        protected void storeCollectionItem(
+            final Integer collectionId,
+            final Integer artifactId
+        ) {
+            SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+                @Override
+                public boolean doIt() throws SQLException {
+                    prepareStatement(SQL_COLLECTION_ITEM_ID_NEXTVAL);
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        return false;
+                    }
+                    int ciId = result.getInt(1);
+                    reset();
+                    prepareStatement(SQL_INSERT_COLLECTION_ITEM);
+                    stmnt.setInt(1, ciId);
+                    stmnt.setInt(2, collectionId);
+                    stmnt.setInt(3, artifactId);
+                    stmnt.execute();
+                    conn.commit();
+                    return true;
+                }
+            };
+
+            if (!exec.runWrite()) {
+                log.error("storing of collection item failed.");
+            }
+        }
+
+        protected Integer getCollectionId(
+            final String  collectionId,
+            final Integer ownerId,
+            final String  collectionName,
+            final Date    collectionCreated
+        ) {
+            Integer c = getId(collections, collectionId, SQL_COLLECTION_BY_GID);
+
+            if (c != null) {
+                return c;
+            }
+
+            final Integer [] res = new Integer[1];
+
+            SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+                @Override
+                public boolean doIt() throws SQLException {
+                    prepareStatement(SQL_COLLECTION_ID_NEXTVAL);
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        return false;
+                    }
+                    res[0] = result.getInt(1);
+                    reset();
+                    prepareStatement(SQL_INSERT_COLLECTION);
+                    stmnt.setInt   (1, res[0]);
+                    stmnt.setString(2, collectionId);
+                    stmnt.setInt   (3, ownerId);
+                    setString(stmnt, 4, collectionName);
+                    Timestamp timestamp = new Timestamp(collectionCreated != null
+                        ? collectionCreated.getTime()
+                        : System.currentTimeMillis());
+                    stmnt.setTimestamp(5, timestamp);
+                    stmnt.execute();
+                    conn.commit();
+                    return true;
+                }
+            };
+
+            if (exec.runWrite()) {
+                collections.put(collectionId, res[0]);
+                return res[0];
+            }
+
+            return null;
+        }
+
+        protected Integer getUserId(final String userId) {
+
+            Integer u = getId(users, userId, SQL_USER_BY_GID);
+
+            if (u != null) {
+                return u;
+            }
+
+            final Integer [] res = new Integer[1];
+
+            SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+                @Override
+                public boolean doIt() throws SQLException {
+                    prepareStatement(SQL_USER_ID_NEXTVAL);
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        return false;
+                    }
+                    res[0] = result.getInt(1);
+                    reset();
+                    prepareStatement(SQL_INSERT_USER);
+                    stmnt.setInt   (1, res[0]);
+                    stmnt.setString(2, userId);
+                    stmnt.execute();
+                    conn.commit();
+                    return true;
+                }
+            };
+
+            if (exec.runWrite()) {
+                users.put(userId, res[0]);
+                return res[0];
+            }
+
+            return null;
+        }
+
+        public boolean scan(ArtifactDatabase adb) {
+            log.debug("scan");
+            try {
+                adb.loadAllArtifacts(this);
+            }
+            catch (ArtifactDatabaseException ade) {
+                log.error(ade);
+                return false;
+            }
+            return true;
+        }
+    } // class InitialScan
+
+
+    public Datacage() {
+    }
+
+    @Override
+    public void setup(Document document) {
+        log.debug("setup");
+        DBConfig config = DBConfig.getInstance();
+        setupSQL(config.getSQL());
+        sqlExecutor = new SQLExecutor(config.getDBConnection());
+    }
+
+    protected void setupSQL(SQL sql) {
+        SQL_DELETE_ALL_USERS      = sql.get(SQL_DELETE_ALL_USERS);
+        SQL_DELETE_ALL_ARTIFACTS  = sql.get(SQL_DELETE_ALL_ARTIFACTS);
+        SQL_USER_ID_NEXTVAL       = sql.get(SQL_USER_ID_NEXTVAL);
+        SQL_USER_BY_GID           = sql.get(SQL_USER_BY_GID);
+        SQL_INSERT_USER           = sql.get(SQL_INSERT_USER);
+        SQL_COLLECTION_BY_GID     = sql.get(SQL_COLLECTION_BY_GID);
+        SQL_COLLECTION_ID_NEXTVAL = sql.get(SQL_COLLECTION_ID_NEXTVAL);
+        SQL_INSERT_COLLECTION     = sql.get(SQL_INSERT_COLLECTION);
+        SQL_ARTIFACT_BY_GID       = sql.get(SQL_ARTIFACT_BY_GID);
+        SQL_COLLECTION_ITEM_ID_NEXTVAL =
+            sql.get(SQL_COLLECTION_ITEM_ID_NEXTVAL);
+        SQL_INSERT_COLLECTION_ITEM =
+            sql.get(SQL_INSERT_COLLECTION_ITEM);
+        SQL_ARTIFACT_ID_NEXTVAL = sql.get(SQL_ARTIFACT_ID_NEXTVAL);
+        SQL_INSERT_ARTIFACT     = sql.get(SQL_INSERT_ARTIFACT);
+        SQL_ARTIFACT_DATA_ID_NEXTVAL = sql.get(SQL_ARTIFACT_DATA_ID_NEXTVAL);
+        SQL_INSERT_ARTIFACT_DATA = sql.get(SQL_INSERT_ARTIFACT_DATA);
+        SQL_OUT_ID_NEXTVALUE     = sql.get(SQL_OUT_ID_NEXTVALUE);
+        SQL_INSERT_OUT           = sql.get(SQL_INSERT_OUT);
+        SQL_FACET_ID_NEXTVAL     = sql.get(SQL_FACET_ID_NEXTVAL);
+        SQL_INSERT_FACET         = sql.get(SQL_INSERT_FACET);
+        SQL_UPDATE_COLLECTION_NAME = sql.get(SQL_UPDATE_COLLECTION_NAME);
+        SQL_DELETE_ARTIFACT_FROM_COLLECTION =
+            sql.get(SQL_DELETE_ARTIFACT_FROM_COLLECTION);
+        SQL_DELETE_COLLECTION_BY_GID = sql.get(SQL_DELETE_COLLECTION_BY_GID);
+        SQL_DELETE_USER_BY_GID       = sql.get(SQL_DELETE_USER_BY_GID);
+        SQL_DELETE_ARTIFACT_DATA_BY_ARTIFACT_ID =
+            sql.get(SQL_DELETE_ARTIFACT_DATA_BY_ARTIFACT_ID);
+        SQL_DELETE_OUTS_BY_ARTIFACT_ID =
+            sql.get(SQL_DELETE_OUTS_BY_ARTIFACT_ID);
+        SQL_DELETE_FACETS_BY_ARTIFACT_ID =
+            sql.get(SQL_DELETE_FACETS_BY_ARTIFACT_ID);
+        SQL_DELETE_ARTIFACT_BY_GID =
+            sql.get(SQL_DELETE_ARTIFACT_BY_GID);
+    }
+
+    protected static final int numFacets(List<Output> outs) {
+        int sum = 0;
+        for (Output out: outs) {
+            sum += out.getFacets().size();
+        }
+        return sum;
+    }
+
+    protected static final void setString(
+        PreparedStatement stmnt, 
+        int               index,
+        Object            value
+    ) 
+    throws SQLException
+    {
+        if (value == null) {
+            stmnt.setNull(index, Types.VARCHAR);
+        }
+        else {
+            stmnt.setString(index, value.toString());
+        }
+    }
+
+    @Override
+    public void systemUp(GlobalContext context) {
+        log.debug("systemUp entered");
+        initialScan(context);
+        context.put(DATACAGE_KEY, this);
+        log.debug("systemUp leaved");
+    }
+
+    protected void initialScan(GlobalContext context) {
+        log.debug("initialScan");
+
+        Object adbObject = context.get(ARTEFACT_DATABASE_KEY);
+
+        if (!(adbObject instanceof ArtifactDatabase)) {
+            log.error("missing artefact database. Cannot scan");
+            return;
+        }
+
+        ArtifactDatabase adb = (ArtifactDatabase)adbObject;
+
+        if (!cleanDatabase()) {
+            log.error("cleaning database failed");
+            return;
+        }
+
+        InitialScan is = new InitialScan(context);
+
+        if (!is.scan(adb)) {
+            log.error("initial scan failed");
+            return;
+        }
+
+    }
+
+    protected boolean cleanDatabase() {
+
+        log.debug("cleanDatabase");
+
+        boolean success = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_DELETE_ALL_USERS);
+                stmnt.execute();
+                prepareStatement(SQL_DELETE_ALL_ARTIFACTS);
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        }.runWrite();
+
+        log.debug("after runWrite(): " + success);
+
+        return success;
+    }
+
+
+    @Override
+    public void systemDown(GlobalContext context) {
+        log.debug("systemDown");
+    }
+
+    public void setup(GlobalContext globalContext) {
+        log.debug("setup");
+    }
+
+    public void createdArtifact(
+        Artifact      artifact, 
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("createdArtifact");
+
+        if (artifact == null) {
+            log.warn("artifact to create is null");
+            return;
+        }
+
+        if (!(artifact instanceof FLYSArtifact)) {
+            log.warn("need FLYSArtifact here (have " + artifact.getClass() + ")");
+            return;
+        }
+
+        final FLYSArtifact flys = (FLYSArtifact)artifact;
+
+        final int [] res = new int[1];
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_ARTIFACT_ID_NEXTVAL);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    log.error("id generation for artifact failed");
+                    return false;
+                }
+                res[0] = result.getInt(1);
+                reset();
+                prepareStatement(SQL_INSERT_ARTIFACT);
+                stmnt.setInt      (1, res[0]);
+                stmnt.setString   (2, flys.identifier());
+                stmnt.setString   (3, flys.getCurrentStateId());
+                stmnt.setTimestamp(4,
+                    new Timestamp(System.currentTimeMillis()));
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("storing of artifact failed.");
+            return;
+        }
+
+        storeData(res[0], flys);
+        storeOuts(res[0], flys, context);
+    }
+
+    public void storedArtifact(
+        Artifact      artifact,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("storedArtifact");
+        if (!(artifact instanceof FLYSArtifact)) {
+            log.warn("need FLYSArtifact here but have a " + artifact.getClass());
+            return;
+        }
+
+        final FLYSArtifact flys = (FLYSArtifact)artifact;
+
+        final Integer [] res = new Integer[1];
+
+        // check first if artifact already exists
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_ARTIFACT_BY_GID);
+                stmnt.setString(1, flys.identifier());
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    // new artifact
+                    return true;
+                }
+                res[0] = result.getInt(1);
+                return true;
+            }
+        };
+
+        if (!exec.runRead()) {
+            log.error("querying artifact failed");
+            return;
+        }
+
+        if (res[0] == null) { // new artifact
+            createdArtifact(artifact, backend, context);
+            return;
+        }
+
+        // artifact already exists -> delete old data
+        exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_DELETE_ARTIFACT_DATA_BY_ARTIFACT_ID);
+                stmnt.setInt(1, res[0]);
+                stmnt.execute();
+                prepareStatement(SQL_DELETE_FACETS_BY_ARTIFACT_ID);
+                stmnt.setInt(1, res[0]);
+                stmnt.execute();
+                prepareStatement(SQL_DELETE_OUTS_BY_ARTIFACT_ID);
+                stmnt.setInt(1, res[0]);
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("deleting old artifact data failed");
+            return;
+        }
+
+        // write new data
+        storeData(res[0], flys);
+        storeOuts(res[0], flys, context);
+    }
+
+    public void createdUser(
+        final User    user,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("createdUser");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_USER_ID_NEXTVAL);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    log.error("id generation for user failed");
+                    return false;
+                }
+                int uId = result.getInt(1);
+                reset();
+                prepareStatement(SQL_INSERT_USER);
+                stmnt.setInt(1, uId);
+                stmnt.setString(2, user.identifier());
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("create user failed");
+        }
+    }
+
+    public void deletedUser(
+        final String  identifier,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("deletedUser");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_DELETE_USER_BY_GID);
+                stmnt.setString(1, identifier);
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("delete user failed");
+        }
+    }
+
+    public void createdCollection(
+        final ArtifactCollection collection,
+        Backend                  backend,
+        GlobalContext            context
+    ) {
+        log.debug("createdCollection");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                String userId = collection.getUser().identifier();
+                prepareStatement(SQL_USER_BY_GID);
+                stmnt.setString(1, userId);
+                result = stmnt.executeQuery();
+                int uId;
+                if (result.next()) {
+                    uId = result.getInt(1);
+                    reset();
+                }
+                else {
+                    // need to create user first
+                    reset();
+                    prepareStatement(SQL_USER_ID_NEXTVAL);
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        log.error("id generation for user failed");
+                        return false;
+                    }
+                    uId = result.getInt(1);
+                    reset();
+                    prepareStatement(SQL_INSERT_USER);
+                    stmnt.setInt(1, uId);
+                    stmnt.setString(2, userId);
+                    stmnt.execute();
+                    conn.commit();
+                    reset();
+                }
+
+                prepareStatement(SQL_COLLECTION_ID_NEXTVAL);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    log.error("id generation for collection failed");
+                    return false;
+                }
+                int cId = result.getInt(1);
+                reset();
+
+                String identifier = collection.identifier();
+                String name       = collection.getName();
+
+                prepareStatement(SQL_INSERT_COLLECTION);
+                stmnt.setInt(1, cId);
+                stmnt.setString(2, identifier);
+                stmnt.setInt(3, uId);
+                setString(stmnt, 4, name);
+                stmnt.setTimestamp(5,
+                    new Timestamp(System.currentTimeMillis()));
+                stmnt.execute();
+
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("create collection failed");
+        }
+    }
+
+    public void deletedCollection(
+        final String  identifier,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("deletedCollection");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_DELETE_COLLECTION_BY_GID);
+                stmnt.setString(1, identifier);
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("delete collection failed");
+        }
+    }
+
+    public void changedCollectionAttribute(
+        String        identifier,
+        Document      document,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("changedCollectionAttribute");
+    }
+
+    public void changedCollectionItemAttribute(
+        String        collectionId,
+        String        artifactId,
+        Document      document,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("changedCollectionItemAttribute");
+    }
+
+    public void addedArtifactToCollection(
+        final String  artifactId,
+        final String  collectionId,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("addedArtifactToCollection");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_ARTIFACT_BY_GID);
+                stmnt.setString(1, artifactId);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    return false;
+                }
+                int aId = result.getInt(1);
+                reset();
+
+                prepareStatement(SQL_COLLECTION_BY_GID);
+                stmnt.setString(1, collectionId);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    return false;
+                }
+                int cId = result.getInt(1);
+                reset();
+
+                prepareStatement(SQL_COLLECTION_ITEM_ID_NEXTVAL);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    return false;
+                }
+                int ciId = result.getInt(1);
+                reset();
+
+                prepareStatement(SQL_INSERT_COLLECTION_ITEM);
+                stmnt.setInt(1, ciId);
+                stmnt.setInt(2, cId);
+                stmnt.setInt(3, aId);
+                stmnt.execute();
+
+                conn.commit();
+                return true;
+            }
+        };
+        if (!exec.runWrite()) {
+            log.error("added artifact to collection failed");
+        }
+    }
+
+    public void removedArtifactFromCollection(
+        final String  artifactId,
+        final String  collectionId,
+        Backend       backend,
+        GlobalContext context
+    ) {
+        log.debug("removedArtifactFromCollection");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_ARTIFACT_BY_GID);
+                stmnt.setString(1, artifactId);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    return false;
+                }
+                int aId = result.getInt(1);
+                reset();
+                prepareStatement(SQL_COLLECTION_BY_GID);
+                stmnt.setString(1, collectionId);
+                result = stmnt.executeQuery();
+                if (!result.next()) {
+                    return false;
+                }
+                int cId = result.getInt(1);
+                reset();
+                prepareStatement(SQL_DELETE_ARTIFACT_FROM_COLLECTION);
+                stmnt.setInt(1, cId);
+                stmnt.setInt(2, aId);
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+        if (!exec.runWrite()) {
+            log.error("removing artifact from collection failed");
+        }
+    }
+
+    public void setCollectionName(
+        final String  collectionId,
+        final String  name,
+        GlobalContext context
+    ) {
+        log.debug("setCollectionName");
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_UPDATE_COLLECTION_NAME);
+                stmnt.setString(1, name);
+                stmnt.setString(2, collectionId);
+                stmnt.execute();
+                conn.commit();
+                return true;
+            }
+        };
+        if (!exec.runWrite()) {
+            log.error("changing name failed");
+        }
+    }
+
+    protected void storeData(
+        final int     artifactId,
+        FLYSArtifact  artifact
+    ) {
+        final Collection<StateData> data = artifact.getAllData();
+
+        if (data.isEmpty()) {
+            return;
+        }
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                int [] ids = new int[data.size()];
+                prepareStatement(SQL_ARTIFACT_DATA_ID_NEXTVAL);
+
+                for (int i = 0; i < ids.length; ++i) {
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        log.error("generating id for artifact data failed");
+                        return false;
+                    }
+                    ids[i] = result.getInt(1);
+                    result.close(); result = null;
+                }
+                reset();
+                prepareStatement(SQL_INSERT_ARTIFACT_DATA);
+
+                int i = 0;
+                for (StateData sd: data) {
+                    int id = ids[i++];
+                    stmnt.setInt(1, id);
+                    stmnt.setInt(2, artifactId);
+                    // XXX: Where come the nulls from?
+                    String type = sd.getType();
+                    if (type == null) type = "String";
+                    stmnt.setString(3, type);
+                    stmnt.setString(4, sd.getName());
+                    setString(stmnt, 5, sd.getValue());
+                    stmnt.execute();
+                }
+
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("storing artifact data failed");
+        }
+    }
+
+    protected void storeOuts(
+        final int          artifactId,
+        final FLYSArtifact artifact,
+        GlobalContext      context
+    ) {
+        final List<Output> outs = artifact.getOutputs(context);
+
+        if (outs.isEmpty()) {
+            return;
+        }
+
+        final int [] outIds = new int[outs.size()];
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_OUT_ID_NEXTVALUE);
+                for (int i = 0; i < outIds.length; ++i) {
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        log.error("generation of out ids failed");
+                        return false;
+                    }
+                    outIds[i] = result.getInt(1);
+                    result.close(); result = null;
+                }
+                reset();
+                prepareStatement(SQL_INSERT_OUT);
+                for (int i = 0; i < outIds.length; ++i) {
+                    Output out = outs.get(i);
+                    stmnt.setInt(1, outIds[i]);
+                    stmnt.setInt(2, artifactId);
+                    stmnt.setString(3, out.getName());
+                    setString(stmnt, 4, out.getDescription());
+                    setString(stmnt, 5, out.getType());
+                    stmnt.execute();
+                }
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("storing artifact outs failed");
+            return;
+        }
+
+        final int FACETS = numFacets(outs);
+
+        if (FACETS == 0) {
+            return;
+        }
+
+        exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                int [] facetIds = new int[FACETS];
+                prepareStatement(SQL_FACET_ID_NEXTVAL);
+                for (int i = 0; i < facetIds.length; ++i) {
+                    result = stmnt.executeQuery();
+                    if (!result.next()) {
+                        log.error("generation of facet ids failed");
+                        return false;
+                    }
+                    facetIds[i] = result.getInt(1);
+                    result.close(); result = null;
+                }
+                reset();
+                prepareStatement(SQL_INSERT_FACET);
+                int index = 0;
+                for (int i = 0, N = outs.size(); i < N; ++i) {
+                    Output out = outs.get(i);
+                    int outId = outIds[i];
+                    for (Facet facet: out.getFacets()) {
+                        stmnt.setInt(1, facetIds[index]);
+                        stmnt.setInt(2, outId);
+                        stmnt.setString(3, facet.getName());
+                        stmnt.setInt(4, facet.getIndex());
+                        stmnt.setString(5, "XXX"); // TODO: handle states
+                        setString(stmnt, 6, facet.getDescription());
+                        stmnt.execute();
+                        ++index;
+                    }
+                }
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("storing facets failed");
+        }
+    }
+
+    public void killedCollections(
+        final List<String> identifiers,
+        GlobalContext      context
+    ) {
+        log.debug("killedCollections");
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_DELETE_COLLECTION_BY_GID);
+                for (String identifier: identifiers) {
+                    stmnt.setString(1, identifier);
+                    stmnt.execute();
+                }
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("killing collections failed");
+        }
+    }
+
+    public void killedArtifacts(
+        final List<String> identifiers,
+        GlobalContext      context
+    ) {
+        log.debug("killedArtifacts");
+
+        SQLExecutor.Instance exec = sqlExecutor.new Instance() {
+            @Override
+            public boolean doIt() throws SQLException {
+                prepareStatement(SQL_DELETE_ARTIFACT_BY_GID);
+                for (String identifier: identifiers) {
+                    stmnt.setString(1, identifier);
+                    stmnt.execute();
+                }
+                conn.commit();
+                return true;
+            }
+        };
+
+        if (!exec.runWrite()) {
+            log.error("killing artifacts failed");
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/DatacageBackendListener.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,190 @@
+package de.intevation.flys.artifacts.datacage;
+
+import java.util.List;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactCollection;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.User;
+
+import de.intevation.artifactdatabase.BackendListener;
+import de.intevation.artifactdatabase.Backend;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+public class DatacageBackendListener
+implements   BackendListener
+{
+    private static Logger log =
+        Logger.getLogger(DatacageBackendListener.class);
+
+    protected GlobalContext context;
+
+    public DatacageBackendListener() {
+        log.debug("new DatacageBackendListener");
+    }
+
+    protected Datacage getDatacage() {
+        Object listener = context.get(Datacage.DATACAGE_KEY);
+        return listener instanceof Datacage
+            ? (Datacage)listener
+            : null;
+    }
+
+    @Override
+    public void setup(GlobalContext context) {
+        log.debug("setup");
+        this.context = context;
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.setup(context);
+        }
+    }
+
+    @Override
+    public void createdArtifact(Artifact artifact, Backend backend) {
+        log.debug("createdArtifact");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.createdArtifact(artifact, backend, context);
+        }
+    }
+
+    @Override
+    public void storedArtifact(Artifact artifact, Backend backend) {
+        log.debug("storedArtifact");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.storedArtifact(artifact, backend, context);
+        }
+    }
+
+    @Override
+    public void createdUser(User user, Backend backend) {
+        log.debug("createdUser");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.createdUser(user, backend, context);
+        }
+    }
+
+    @Override
+    public void deletedUser(String identifier, Backend backend) {
+        log.debug("deletedUser");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.deletedUser(identifier, backend, context);
+        }
+    }
+
+    @Override
+    public void createdCollection(
+        ArtifactCollection collection,
+        Backend            backend
+    ) {
+        log.debug("createdCollection");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.createdCollection(collection, backend, context);
+        }
+    }
+
+    @Override
+    public void deletedCollection(String identifier, Backend backend) {
+        log.debug("deletedCollection");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.deletedCollection(identifier, backend, context);
+        }
+    }
+
+    @Override
+    public void changedCollectionAttribute(
+        String   identifier,
+        Document document,
+        Backend  backend
+    ) {
+        log.debug("changedCollectionAttribute");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.changedCollectionAttribute(
+                identifier, document, backend, context);
+        }
+    }
+
+    @Override
+    public void changedCollectionItemAttribute(
+        String   collectionId,
+        String   artifactId,
+        Document document,
+        Backend  backend
+    ) {
+        log.debug("changedCollectionItemAttribute");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.changedCollectionItemAttribute(
+                collectionId, artifactId, document, backend, context);
+        }
+    }
+
+    @Override
+    public void addedArtifactToCollection(
+        String  artifactId,
+        String  collectionId,
+        Backend backend
+    ) {
+        log.debug("addedArtifactToCollection");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.addedArtifactToCollection(
+                artifactId, collectionId, backend, context);
+        }
+    }
+
+    @Override
+    public void removedArtifactFromCollection(
+        String  artifactId,
+        String  collectionId,
+        Backend backend
+    ) {
+        log.debug("removedArtifactFromCollection");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.removedArtifactFromCollection(
+                artifactId, collectionId, backend, context);
+        }
+    }
+
+    @Override
+    public void setCollectionName(
+        String collectionId,
+        String name
+    ) {
+        log.debug("setCollectionName");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.setCollectionName(collectionId, name, context);
+        }
+    }
+
+    @Override
+    public void killedCollections(List<String> identifiers, Backend backend) {
+        log.debug("killedCollections");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.killedCollections(identifiers, context);
+        }
+    }
+
+    @Override
+    public void killedArtifacts(List<String> identifiers, Backend backend) {
+        log.debug("killedArtifacts");
+        Datacage l = getDatacage();
+        if (l != null) {
+            l.killedArtifacts(identifiers, context);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/Recommendations.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,291 @@
+package de.intevation.flys.artifacts.datacage;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.File;
+
+import java.io.FileInputStream;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import org.hibernate.Session;
+
+import org.hibernate.jdbc.Work;
+
+import de.intevation.artifacts.common.utils.Config;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.StringUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.datacage.templating.Builder;
+
+public class Recommendations
+{
+    private static Logger log = Logger.getLogger(Recommendations.class);
+
+    private static final boolean DEVELOPMENT_MODE =
+        Boolean.getBoolean("flys.datacage.recommendations.development");
+
+    public static final String XPATH_TEMPLATE =
+        "/artifact-database/metadata/template/text()";
+
+    public static final String DEFAULT_TEMPLATE_PATH =
+        "${artifacts.config.dir}/meta-data.xml";
+
+    private static Recommendations INSTANCE;
+
+    public static class BuilderProvider
+    {
+        protected Builder builder;
+
+        public BuilderProvider() {
+        }
+
+        public BuilderProvider(Builder builder) {
+            this.builder = builder;
+        }
+
+        public Builder getBuilder() {
+            return builder;
+        }
+    } // class BuilderProvider
+
+    public static class FileBuilderProvider
+    extends             BuilderProvider
+    {
+        protected File file;
+        protected long lastModified;
+
+        public FileBuilderProvider() {
+        }
+
+        public FileBuilderProvider(File file) {
+            this.file    = file;
+            lastModified = Long.MIN_VALUE;
+        }
+
+        @Override
+        public synchronized Builder getBuilder() {
+            long modified = file.lastModified();
+            if (modified > lastModified) {
+                lastModified = modified;
+                try {
+                    Document template = loadTemplate(file);
+                    builder = new Builder(template);
+                }
+                catch (IOException ioe) {
+                    log.error(ioe);
+                }
+            }
+            return builder;
+        }
+
+        public BuilderProvider toStaticProvider() {
+            return new BuilderProvider(builder);
+        }
+    } // class BuilderProvider
+
+    protected BuilderProvider builderProvider;
+
+    public Recommendations() {
+    }
+
+    public Recommendations(BuilderProvider builderProvider) {
+        this.builderProvider = builderProvider;
+    }
+
+    public Builder getBuilder() {
+        return builderProvider.getBuilder();
+    }
+
+    protected static void artifactToParameters(
+        FLYSArtifact        artifact, 
+        Map<String, Object> parameters
+    ) {
+        parameters.put("CURRENT-STATE-ID", artifact.getCurrentStateId());
+        parameters.put("ARTIFACT-ID",      artifact.identifier());
+
+        for (StateData sd: artifact.getAllData()) {
+            Object value = sd.getValue();
+            if (value == null) {
+                continue;
+            }
+            String key = sd.getName().replace('.', '-').toUpperCase();
+            parameters.put(key, value);
+        }
+    }
+
+    public static void convertKeysToUpperCase(
+        Map<String, Object> src,
+        Map<String, Object> dst
+    ) {
+        for (Map.Entry<String, Object> entry: src.entrySet()) {
+            dst.put(entry.getKey().toUpperCase(), entry.getValue());
+        }
+    }
+
+    public void  recommend(
+        FLYSArtifact        artifact,
+        String              userId,
+        String []           outs,
+        Map<String, Object> extraParameters,
+        Node                result
+    ) {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+
+        if (extraParameters != null) {
+            convertKeysToUpperCase(extraParameters, parameters);
+        }
+
+        if (userId != null) {
+            parameters.put("USER-ID", userId);
+        }
+
+        if (artifact != null) {
+            artifactToParameters(artifact, parameters);
+        }
+
+        parameters.put("ARTIFACT-OUTS", StringUtils.toUpperCase(outs));
+
+        parameters.put("PARAMETERS", parameters);
+
+        recommend(parameters, userId, result);
+    }
+
+    public void recommend(
+        Map<String, Object> parameters,
+        String              userId,
+        Node                result
+    ) {
+        recommend(parameters, userId, result, SessionHolder.HOLDER.get());
+    }
+
+    public void recommend(
+        final Map<String, Object> parameters,
+        final String              userId,
+        final Node                result,
+        Session                   session
+    ) {
+        session.doWork(new Work() {
+            @Override
+            public void execute(Connection systemConnection)
+            throws SQLException
+            {
+                List<Builder.NamedConnection> connections =
+                    new ArrayList<Builder.NamedConnection>(2);
+
+                Connection userConnection = userId != null
+                    ? DBConfig
+                        .getInstance()
+                        .getDBConnection()
+                        .getDataSource()
+                        .getConnection()
+                    : null;
+
+                try {
+                    if (userConnection != null) {
+                        connections.add(new Builder.NamedConnection(
+                            Builder.CONNECTION_USER, userConnection, false));
+                    }
+
+                    connections.add(new Builder.NamedConnection(
+                        Builder.CONNECTION_SYSTEM, systemConnection, true));
+
+                    getBuilder().build(connections, result, parameters);
+                }
+                finally {
+                    if (userConnection != null) {
+                        userConnection.close();
+                    }
+                }
+            }
+        });
+    }
+
+    public static synchronized Recommendations getInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = createRecommendations();
+        }
+        return INSTANCE;
+    }
+
+    protected static Document loadTemplate(File file) throws IOException {
+        InputStream in = null;
+
+        try {
+            in = new FileInputStream(file);
+
+            Document template = XMLUtils.parseDocument(in);
+
+            if (template == null) {
+                throw new IOException("cannot load template");
+            }
+            return template;
+        }
+        finally {
+            if (in != null) {
+                try {
+                    in.close();
+                }
+                catch (IOException ioe) {
+                    log.error(ioe);
+                }
+            }
+        }
+    }
+
+    public static Recommendations createRecommendations(File file) {
+        log.debug("Recommendations.createBuilder");
+
+        if (!file.isFile() || !file.canRead()) {
+            log.error("Cannot open template file '" + file + "'");
+            return null;
+        }
+
+        FileBuilderProvider fbp = new FileBuilderProvider(file);
+
+        if (fbp.getBuilder() == null) {
+            log.error("failed loading builder");
+            return null;
+        }
+
+        BuilderProvider bp = DEVELOPMENT_MODE
+            ? fbp
+            : fbp.toStaticProvider();
+
+        return new Recommendations(bp);
+    }
+
+    protected static Recommendations createRecommendations() {
+        log.debug("Recommendations.createRecommendations");
+
+        String path = Config.getStringXPath(XPATH_TEMPLATE);
+
+        if (path == null) {
+            path = DEFAULT_TEMPLATE_PATH;
+        }
+
+        path = Config.replaceConfigDir(path);
+
+        log.info("Meta data template: " + path);
+
+        return createRecommendations(new File(path));
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/App.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,109 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+
+import de.intevation.flys.backend.SessionFactoryProvider;
+
+import org.hibernate.Session;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.datacage.Recommendations;
+
+public class App
+{
+    private static Logger log = Logger.getLogger(App.class);
+
+    public static final String template =
+        System.getProperty("meta.data.template", "meta-data.xml");
+
+    public static final String userId =
+        System.getProperty("user.id");
+
+    public static final String PARAMETERS =
+        System.getProperty("meta.data.parameters", "");
+
+    public static final String OUTPUT =
+        System.getProperty("meta.data.output");
+
+    public static Map<String, Object> getParameters() {
+        HashMap<String, Object> map = new HashMap<String, Object>();
+        String [] parts = PARAMETERS.split("\\s*;\\s*");
+        for (String part: parts) {
+            String [] kv = part.split("\\s*:\\s*");
+            if (kv.length < 2 || (kv[0] = kv[0].trim()).length() == 0) {
+                continue;
+            }
+            String [] values = kv[1].split("\\s*,\\s*");
+            map.put(kv[0], values.length == 1 ? values[0] : values);
+        }
+        return map;
+    }
+
+    public static void main(String [] args) {
+
+        Recommendations rec = Recommendations.createRecommendations(
+            new File(template));
+
+        if (rec == null) {
+            System.err.println("No recommendations created");
+            return;
+        }
+
+        final Document result = XMLUtils.newDocument();
+
+        final Map<String, Object> parameters = getParameters();
+
+        Session session = SessionFactoryProvider
+            .createSessionFactory()
+            .openSession();
+
+        try {
+            rec.recommend(parameters, userId, result, session);
+        }
+        finally {
+            session.close();
+        }
+
+        OutputStream out;
+
+        if (OUTPUT == null) {
+            out = System.out;
+        }
+        else {
+            try {
+                out = new FileOutputStream(OUTPUT);
+            }
+            catch (IOException ioe) {
+                log.error(ioe);
+                return;
+            }
+        }
+
+        try {
+            XMLUtils.toStream(result, out);
+        }
+        finally {
+            if (OUTPUT != null) {
+                try {
+                    out.close();
+                }
+                catch (IOException ioe) {
+                    log.error(ioe);
+                }
+            }
+        }
+        System.exit(0);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/Builder.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,617 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Deque;
+import java.util.ArrayDeque;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathConstants;
+
+import java.sql.SQLException;
+import java.sql.Connection;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.utils.Pair;
+
+import org.apache.log4j.Logger;
+
+public class Builder
+{
+    private static Logger log = Logger.getLogger(Builder.class);
+
+    public static final String CONNECTION_USER   = "user";
+    public static final String CONNECTION_SYSTEM = "system";
+    public static final String DEFAULT_CONNECTION_NAME = CONNECTION_SYSTEM;
+
+    public static final Pattern STRIP_LINE_INDENT =
+        Pattern.compile("\\s*\\r?\\n\\s*");
+
+    public static final String DC_NAMESPACE_URI =
+        "http://www.intevation.org/2011/Datacage";
+
+    private static final Document EVAL_DOCUMENT =
+        XMLUtils.newDocument();
+
+    private static final XPathFactory XPATH_FACTORY =
+        XPathFactory.newInstance();
+
+    protected Document template;
+
+    protected Map<String, CompiledStatement> compiledStatements;
+
+    public static class NamedConnection {
+
+        protected String     name;
+        protected Connection connection;
+        protected boolean    cached;
+
+        public NamedConnection() {
+        }
+
+        public NamedConnection(
+            String     name, 
+            Connection connection
+        ) {
+            this(name, connection, true);
+        }
+
+        public NamedConnection(
+            String     name, 
+            Connection connection,
+            boolean    cached
+        ) {
+            this.name       = name;
+            this.connection = connection;
+            this.cached     = cached;
+        }
+    } // class NamedConnection
+
+
+    public class BuildHelper
+    {
+        protected Node                                     output;
+        protected Document                                 owner;
+        protected StackFrames                              frames;
+        protected List<NamedConnection>                    connections;
+        protected Map<String, CompiledStatement.Instance>  statements;
+        protected Deque<Pair<NamedConnection, ResultData>> connectionsStack;
+
+        public BuildHelper(
+            Node                  output,
+            List<NamedConnection> connections,
+            Map<String, Object>   parameters
+        ) {
+            if (connections.isEmpty()) {
+                throw new IllegalArgumentException("no connections given.");
+            }
+
+            this.connections = connections;
+            connectionsStack =
+                new ArrayDeque<Pair<NamedConnection, ResultData>>();
+            this.output      = output;
+            frames           = new StackFrames(parameters);
+            owner            = getOwnerDocument(output);
+            statements =
+                new HashMap<String, CompiledStatement.Instance>();
+        }
+
+        public void build() throws SQLException {
+            try {
+                synchronized (template) {
+                    for (Node current: rootsToList()) {
+                        build(output, current);
+                    }
+                }
+            }
+            finally {
+                closeStatements();
+            }
+        }
+
+        protected void closeStatements() {
+            for (CompiledStatement.Instance csi: statements.values()) {
+                csi.close();
+            }
+            statements.clear();
+        }
+
+        /**
+         * Handle a \<context\> node.
+         */
+        protected void context(Node parent, Element current)
+        throws SQLException
+        {
+            log.debug("dc:context");
+
+            NodeList subs = current.getChildNodes();
+            int S = subs.getLength();
+
+            // Check only direct children.
+            Node stmntNode = null;
+            for (int i = 0; i < S; ++i) {
+                Node node = subs.item(i);
+                String ns;
+                if (node.getNodeType() == Node.ELEMENT_NODE
+                && node.getLocalName().equals("statement")
+                && (ns = node.getNamespaceURI()) != null
+                && ns.equals(DC_NAMESPACE_URI)) {
+                    stmntNode = node;
+                    break;
+                }
+            }
+
+            if (stmntNode == null) {
+                log.warn("dc:context: cannot find statement");
+                return;
+            }
+
+            String stmntText = stmntNode.getTextContent();
+
+            String con = current.getAttribute("connection");
+
+            String key = con + "-" + stmntText;
+
+            CompiledStatement.Instance csi = statements.get(key);
+
+            if (csi == null) {
+                CompiledStatement cs = compiledStatements.get(stmntText);
+                csi = cs.new Instance();
+                statements.put(key, csi);
+            }
+
+            NamedConnection connection = connectionsStack.isEmpty()
+                ? connections.get(0)
+                : connectionsStack.peek().getA();
+
+            if (con.length() > 0) {
+                for (NamedConnection nc: connections) {
+                    if (con.equals(nc.name)) {
+                        connection = nc;
+                        break;
+                    }
+                }
+            }
+
+            ResultData rd = csi.execute(
+                connection.connection,
+                frames,
+                connection.cached);
+
+            // only descent if there are results
+            if (!rd.isEmpty()) {
+                connectionsStack.push(new Pair(connection, rd));
+                try {
+                    for (int i = 0; i < S; ++i) {
+                        build(parent, subs.item(i));
+                    }
+                }
+                finally {
+                    connectionsStack.pop();
+                }
+            }
+        }
+
+        /**
+         * Kind of foreach over results of a statement within a context.
+         */
+        protected void elements(Node parent, Element current) 
+        throws SQLException
+        {
+            log.debug("dc:elements");
+
+            if (connectionsStack.isEmpty()) {
+                log.warn("dc:elements without having results");
+                return;
+            }
+
+            NodeList subs = current.getChildNodes();
+            int S = subs.getLength();
+
+            if (S == 0) {
+                log.debug("dc:elements has no children");
+                return;
+            }
+
+            ResultData rd = connectionsStack.peek().getB();
+
+            String [] columns = rd.getColumnLabels();
+
+            //if (log.isDebugEnabled()) {
+            //    log.debug("pushing vars: "
+            //        + java.util.Arrays.toString(columns));
+            //}
+
+            for (Object [] row: rd.getRows()) {
+                frames.enter();
+                try {
+                    frames.put(columns, row);
+                    //if (log.isDebugEnabled()) {
+                    //    log.debug("current vars: " + frames.dump());
+                    //}
+                    for (int i = 0; i < S; ++i) {
+                        build(parent, subs.item(i));
+                    }
+                }
+                finally {
+                    frames.leave();
+                }
+            }
+        }
+
+        /**
+         * Create element.
+         */
+        protected void element(Node parent, Element current)
+        throws SQLException
+        {
+            String attr = expand(current.getAttribute("name"));
+
+            if (log.isDebugEnabled()) {
+                log.debug("dc:element -> '" + attr + "'");
+            }
+
+            if (attr.length() == 0) {
+                log.warn("no name attribute found");
+                return;
+            }
+
+            Element element = owner.createElement(attr);
+
+            NodeList children = current.getChildNodes();
+            for (int i = 0, N = children.getLength(); i < N; ++i) {
+                build(element, children.item(i));
+            }
+
+            parent.appendChild(element);
+        }
+
+        protected void text(Node parent, Element current)
+        throws SQLException
+        {
+            log.debug("dc:text");
+            String value = expand(current.getTextContent());
+            parent.appendChild(owner.createTextNode(value));
+        }
+
+        /**
+         * Add attribute to an element
+         * @see element
+         */
+        protected void attribute(Node parent, Element current) {
+
+            if (parent.getNodeType() != Node.ELEMENT_NODE) {
+                log.warn("need element here");
+                return;
+            }
+
+            String name  = expand(current.getAttribute("name"));
+            String value = expand(current.getAttribute("value"));
+
+            Element element = (Element)parent;
+
+            element.setAttribute(name, value);
+        }
+
+        protected void callMacro(Node parent, Element current)
+        throws SQLException
+        {
+            String name = current.getAttribute("name");
+
+            if (name.length() == 0) {
+                log.warn("missing 'name' attribute in 'call-macro'");
+                return;
+            }
+
+            NodeList macros = template.getElementsByTagNameNS(
+                DC_NAMESPACE_URI, "macro");
+
+            for (int i = 0, N = macros.getLength(); i < N; ++i) {
+                Element macro = (Element) macros.item(i);
+                if (name.equals(macro.getAttribute("name"))) {
+                    NodeList subs = macro.getChildNodes();
+                    for (int j = 0, M = subs.getLength(); j < M; ++j) {
+                        build(parent, subs.item(j));
+                    }
+                    return;
+                }
+            }
+
+            log.warn("no macro '" + name + "' found.");
+        }
+
+        protected void ifClause(Node parent, Element current)
+        throws SQLException
+        {
+            String test = current.getAttribute("test");
+
+            if (test.length() == 0) {
+                log.warn("missing 'test' attribute in 'if'");
+                return;
+            }
+
+            Boolean result = evaluateXPath(test);
+
+            if (result != null && result.booleanValue()) {
+                NodeList subs = current.getChildNodes();
+                for (int i = 0, N = subs.getLength(); i < N; ++i) {
+                    build(parent, subs.item(i));
+                }
+            }
+        }
+
+        protected void choose(Node parent, Element current)
+        throws SQLException
+        {
+            Node branch = null;
+
+            NodeList children = current.getChildNodes();
+            for (int i = 0, N = children.getLength(); i < N; ++i) {
+                Node child = children.item(i);
+                String ns = child.getNamespaceURI();
+                if (ns == null
+                || !ns.equals(DC_NAMESPACE_URI)
+                || child.getNodeType() != Node.ELEMENT_NODE
+                ) {
+                    continue;
+                }
+                String name = child.getLocalName();
+                if ("when".equals(name)) {
+                    Element when = (Element)child;
+                    String test = when.getAttribute("test");
+                    if (test.length() == 0) {
+                        log.warn("no 'test' attribute found for when");
+                        continue;
+                    }
+
+                    Boolean result = evaluateXPath(test);
+                    if (result != null && result.booleanValue()) {
+                        branch = child;
+                        break;
+                    }
+
+                    continue;
+                }
+                else if ("otherwise".equals(name)) {
+                    branch = child;
+                    // No break here.
+                }
+            }
+
+            if (branch != null) {
+                NodeList subs = branch.getChildNodes();
+                for (int i = 0, N = subs.getLength(); i < N; ++i) {
+                    build(parent, subs.item(i));
+                }
+            }
+        }
+
+        protected Boolean evaluateXPath(String expr) {
+
+            if (log.isDebugEnabled()) {
+                log.debug("evaluate: '" + expr + "'");
+            }
+
+            try {
+                XPath xpath = XPATH_FACTORY.newXPath();
+                xpath.setXPathVariableResolver(frames);
+                xpath.setXPathFunctionResolver(FunctionResolver.FUNCTIONS);
+                Object result = xpath.evaluate(
+                    expr, EVAL_DOCUMENT, XPathConstants.BOOLEAN);
+
+                return result instanceof Boolean
+                    ? (Boolean)result
+                    : null;
+            }
+            catch (XPathExpressionException xfce) {
+                log.error("expression: " + expr, xfce);
+            }
+            return null;
+        }
+
+        protected void convert(Node parent, Element current) {
+
+            String variable = expand(current.getAttribute("var"));
+            String type     = expand(current.getAttribute("type"));
+
+            Object [] result = new Object[1];
+
+            if (frames.getStore(variable, result)) {
+                Object object = TypeConverter.convert(result[0], type);
+                frames.put(variable.toUpperCase(), object);
+            }
+        }
+
+        protected String expand(String s) {
+            Matcher m = CompiledStatement.VAR.matcher(s);
+
+            Object [] result = new Object[1];
+
+            StringBuffer sb = new StringBuffer();
+            while (m.find()) {
+                String key = m.group(1);
+                result[0] = null;
+                if (frames.getStore(key, result)) {
+                    m.appendReplacement(
+                        sb, result[0] != null ? result[0].toString() : "");
+                }
+                else {
+                    m.appendReplacement(sb, "\\${" + key + "}");
+                }
+            }
+            m.appendTail(sb);
+            return sb.toString();
+        }
+
+        protected void build(Node parent, Node current)
+        throws SQLException
+        {
+            String ns = current.getNamespaceURI();
+            if (ns != null && ns.equals(DC_NAMESPACE_URI)) {
+                if (current.getNodeType() != Node.ELEMENT_NODE) {
+                    log.warn("need elements here");
+                }
+                else {
+                    String localName = current.getLocalName();
+                    if ("attribute".equals(localName)) {
+                        attribute(parent, (Element)current);
+                    }
+                    else if ("context".equals(localName)) {
+                        context(parent, (Element)current);
+                    }
+                    else if ("if".equals(localName)) {
+                        ifClause(parent, (Element)current);
+                    }
+                    else if ("choose".equals(localName)) {
+                        choose(parent, (Element)current);
+                    }
+                    else if ("call-macro".equals(localName)) {
+                        callMacro(parent, (Element)current);
+                    }
+                    else if ("macro".equals(localName)) {
+                        // Simply ignore the definition.
+                    }
+                    else if ("element".equals(localName)) {
+                        element(parent, (Element)current);
+                    }
+                    else if ("elements".equals(localName)) {
+                        elements(parent, (Element)current);
+                    }
+                    else if ("text".equals(localName)) {
+                        text(parent, (Element)current);
+                    }
+                    else if ("comment".equals(localName)
+                         ||  "statement".equals(localName)) {
+                        // ignore comments and statements in output
+                    }
+                    else if ("convert".equals(localName)) {
+                        convert(parent, (Element)current);
+                    }
+                    else {
+                        log.warn("unknown '" + localName + "' -> ignore");
+                    }
+                }
+                return;
+            }
+
+            if (current.getNodeType() == Node.TEXT_NODE) {
+                String txt = current.getNodeValue();
+                if (txt != null && txt.trim().length() == 0) {
+                    return;
+                }
+            }
+
+            Node copy = owner.importNode(current, false);
+
+            NodeList children = current.getChildNodes();
+            for (int i = 0, N = children.getLength(); i < N; ++i) {
+                build(copy, children.item(i));
+            }
+            parent.appendChild(copy);
+        }
+    } // class BuildHelper
+
+
+    public Builder() {
+        compiledStatements = new HashMap<String, CompiledStatement>();
+    }
+
+    public Builder(Document template) {
+        this();
+        this.template = template;
+        compileStatements();
+    }
+
+    protected void compileStatements() {
+
+        NodeList nodes = template.getElementsByTagNameNS(
+            DC_NAMESPACE_URI, "statement");
+
+        for (int i = 0, N = nodes.getLength(); i < N; ++i) {
+            Element stmntElement = (Element)nodes.item(i);
+            String stmnt = trimStatement(stmntElement.getTextContent());
+            if (stmnt == null || stmnt.length() == 0) {
+                throw new IllegalArgumentException("found empty statement");
+            }
+            CompiledStatement cs = new CompiledStatement(stmnt);
+            // For faster lookup store a shortend string into the template.
+            stmnt = "s" + i;
+            stmntElement.setTextContent(stmnt);
+            compiledStatements.put(stmnt, cs);
+        }
+    }
+
+    protected List<Node> rootsToList() {
+
+        NodeList roots = template.getElementsByTagNameNS(
+            DC_NAMESPACE_URI, "template");
+
+        List<Node> elements = new ArrayList<Node>();
+
+        for (int i = 0, N = roots.getLength(); i < N; ++i) {
+            NodeList rootChildren = roots.item(i).getChildNodes();
+            for (int j = 0, M = rootChildren.getLength(); j < M; ++j) {
+                Node child = rootChildren.item(j);
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    elements.add(child);
+                }
+            }
+        }
+
+        return elements;
+    }
+
+    protected static final String trimStatement(String stmnt) {
+        if (stmnt == null) return null;
+        //XXX: Maybe a bit to radical for multiline strings?
+        return STRIP_LINE_INDENT.matcher(stmnt.trim()).replaceAll(" ");
+    }
+
+    protected static Document getOwnerDocument(Node node) {
+        Document document = node.getOwnerDocument();
+        return document != null ? document : (Document)node;
+    }
+
+    private static final List<NamedConnection> wrap(Connection connection) {
+        List<NamedConnection> list = new ArrayList<NamedConnection>(1);
+        list.add(new NamedConnection(DEFAULT_CONNECTION_NAME, connection));
+        return list;
+    }
+
+    public void build(
+        Connection          connection,
+        Node                output,
+        Map<String, Object> parameters
+    )
+    throws SQLException
+    {
+        build(wrap(connection), output, parameters);
+    }
+
+    public void build(
+        List<NamedConnection> connections,
+        Node                  output,
+        Map<String, Object>   parameters
+    )
+    throws SQLException
+    {
+        BuildHelper helper = new BuildHelper(output, connections, parameters);
+
+        helper.build();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/CompiledStatement.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,213 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.ArrayList;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import org.apache.log4j.Logger;
+
+public class CompiledStatement
+{
+    private static Logger log = Logger.getLogger(CompiledStatement.class);
+
+    public static final String DATACAGE_DB_CACHE =
+        "datacage.db";
+
+    public static final Pattern VAR =
+        Pattern.compile("\\$\\{([a-zA-Z0-9_-]+)\\}");
+
+    protected String original;
+    protected String statement;
+
+    protected Map<String, List<Integer>> positions;
+
+    protected int numVars;
+
+    public class Instance {
+
+        protected PreparedStatement preparedStatement;
+
+        public Instance() {
+        }
+
+        protected ResultData executeCached(
+            Cache       cache,
+            Connection  connection,
+            StackFrames frames
+        )
+        throws SQLException
+        {
+            log.debug("executeCached");
+            Object [] values = new Object[numVars];
+
+            StringBuilder sb = new StringBuilder(original);
+
+            for (Map.Entry<String, List<Integer>> entry: positions.entrySet()) {
+                String key   = entry.getKey();
+                Object value = frames.get(key);
+                sb.append(';').append(key).append(':').append(value);
+                for (Integer index: entry.getValue()) {
+                    values[index] = value;
+                }
+            }
+
+            // XXX: Maybe too many collisions?
+            // String key = original + Arrays.hashCode(values);
+            String key = sb.toString();
+
+            Element element = cache.get(key);
+
+            if (element != null) {
+                return (ResultData)element.getValue();
+            }
+
+            if (preparedStatement == null) {
+                preparedStatement = connection.prepareStatement(statement);
+            }
+
+            for (int i = 0; i < values.length; ++i) {
+                preparedStatement.setObject(i+1, values[i]);
+            }
+
+            ResultData data;
+
+            if (log.isDebugEnabled()) {
+                log.debug("executing: " + statement);
+            }
+
+            ResultSet result = preparedStatement.executeQuery();
+            try {
+                data = new ResultData(preparedStatement.getMetaData())
+                    .addAll(result);
+            }
+            finally {
+                result.close();
+            }
+
+            element = new Element(key, data);
+            cache.put(element);
+
+            return data;
+        }
+
+        protected ResultData executeUncached(
+            Connection  connection,
+            StackFrames frames
+        ) 
+        throws SQLException
+        {
+            log.debug("executeUncached");
+            if (preparedStatement == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("preparing statement: " + statement);
+                }
+                preparedStatement = connection.prepareStatement(statement);
+            }
+
+            for (Map.Entry<String, List<Integer>> entry: positions.entrySet()) {
+                Object value = frames.get(entry.getKey());
+                for (Integer index: entry.getValue()) {
+                    preparedStatement.setObject(index+1, value);
+                }
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("executing: " + statement);
+            }
+
+            ResultSet result = preparedStatement.executeQuery();
+            try {
+                return new ResultData(preparedStatement.getMetaData())
+                    .addAll(result);
+            }
+            finally {
+                result.close();
+            }
+        }
+
+        public ResultData execute(
+            Connection  connection,
+            StackFrames frames,
+            boolean     cached
+        )
+        throws SQLException
+        {
+            if (!cached) {
+                return executeUncached(connection, frames);
+            }
+
+            Cache cache = CacheFactory.getCache(DATACAGE_DB_CACHE);
+
+            return cache != null
+                ? executeCached(cache, connection, frames)
+                : executeUncached(connection, frames);
+        }
+
+        public void close() {
+            if (preparedStatement != null) {
+                try {
+                    preparedStatement.close();
+                }
+                catch (SQLException sqle) {
+                }
+                preparedStatement = null;
+            }
+        }
+    } // class Instance
+
+    public CompiledStatement() {
+    }
+
+    public CompiledStatement(String original) {
+        this.original = original;
+        // TreeMap to ensure order
+        positions = new TreeMap<String, List<Integer>>();
+        compile();
+    }
+
+    protected void compile() {
+
+        StringBuffer sb = new StringBuffer();
+
+        Matcher m = VAR.matcher(original);
+
+        int index = 0;
+
+        while (m.find()) {
+            String key = m.group(1).toUpperCase();
+            List<Integer> indices = positions.get(key);
+            if (indices == null) {
+                indices = new ArrayList<Integer>();
+                positions.put(key, indices);
+            }
+            indices.add(index);
+            m.appendReplacement(sb, "?");
+            ++index;
+        }
+
+        m.appendTail(sb);
+
+        numVars = index;
+
+        statement = sb.toString();
+    }
+
+    public String getStatement() {
+        return statement;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/FunctionResolver.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,109 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+import java.util.List;
+import java.util.Collection;
+import java.util.Map;
+import java.util.ArrayList;
+
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathFunction;
+import javax.xml.xpath.XPathFunctionException;
+
+import javax.xml.namespace.QName;
+
+import org.apache.log4j.Logger;
+
+public class FunctionResolver
+implements   XPathFunctionResolver
+{
+    private static Logger log = Logger.getLogger(FunctionResolver.class);
+
+    public static final String FUNCTION_NAMESPACE_URI = "dc";
+
+    public static final class Entry {
+
+        String        name;
+        XPathFunction function;
+        int           arity;
+
+        public Entry() {
+        }
+
+        public Entry(String name, XPathFunction function, int arity) {
+            this.name     = name;
+            this.function = function;
+            this.arity    = arity;
+        }
+    } // class Entry
+
+    public static final FunctionResolver FUNCTIONS = new FunctionResolver();
+
+    static {
+        /** Implementation of case-ignoring dc:contains. */
+        FUNCTIONS.addFunction("contains", 2, new XPathFunction() {
+            @Override
+            public Object evaluate(List args) throws XPathFunctionException {
+                Object haystack = args.get(0);
+                Object needle   = args.get(1);
+
+                if (needle instanceof String) {
+                    needle = ((String)needle).toUpperCase();
+                }
+
+                try {
+                    if (haystack instanceof Collection) {
+                        return Boolean.valueOf(
+                            ((Collection)haystack).contains(needle));
+                    }
+
+                    if (haystack instanceof Map) {
+                        return Boolean.valueOf(
+                            ((Map)haystack).containsKey(needle));
+                    }
+
+                    if (haystack instanceof Object []) {
+                        for (Object straw: (Object [])haystack) {
+                            if (straw.equals(needle)) {
+                                return Boolean.TRUE;
+                            }
+                        }
+                    }
+
+                    return Boolean.FALSE;
+                }
+                catch (Exception e) {
+                    log.error(e);
+                    throw new XPathFunctionException(e);
+                }
+            }
+        });
+    }
+
+    protected List<Entry> functions;
+
+    public FunctionResolver() {
+        functions = new ArrayList<Entry>();
+    }
+
+    public void addFunction(String name, int arity, XPathFunction function) {
+        functions.add(new Entry(name, function, arity));
+    }
+
+    @Override
+    public XPathFunction resolveFunction(QName functionName, int arity) {
+
+        if (!functionName.getNamespaceURI().equals(FUNCTION_NAMESPACE_URI)) {
+            return null;
+        }
+
+        String name = functionName.getLocalPart();
+        for (Entry entry: functions) {
+            if (entry.arity == arity && entry.name.equals(name)) {
+                return entry.function;
+            }
+        }
+
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/ResultData.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,77 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+import java.io.Serializable;
+
+import java.sql.ResultSetMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+public class ResultData
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(ResultData.class);
+
+    protected String [] columns;
+
+    protected List<Object []> rows;
+
+    public ResultData() {
+        rows = new ArrayList<Object []>();
+    }
+
+    public ResultData(ResultSetMetaData meta)
+    throws SQLException
+    {
+        this();
+
+        boolean debug = log.isDebugEnabled();
+
+        int N = meta.getColumnCount();
+
+        columns = new String[N];
+
+        if (debug) {
+            log.debug("ResultSet column names:");
+        }
+
+        for (int i = 1; i <= N; ++i) {
+            columns[i-1] = meta.getColumnLabel(i).toUpperCase();
+            if (debug) {
+                log.debug("    " + i + ": " + columns[i-1]);
+            }
+        }
+    }
+
+    public String [] getColumnLabels() {
+        return columns;
+    }
+
+    public ResultData addAll(ResultSet result) throws SQLException {
+        while (result.next()) {
+            add(result);
+        }
+        return this;
+    }
+
+    public void add(ResultSet result) throws SQLException {
+        Object [] row = new Object[columns.length];
+        for (int i = 0; i < columns.length; ++i) {
+            row[i] = result.getObject(i+1);
+        }
+        rows.add(row);
+    }
+
+    public List<Object []> getRows() {
+        return rows;
+    }
+
+    public boolean isEmpty() {
+        return rows.isEmpty();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/StackFrames.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,133 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.xpath.XPathVariableResolver;
+
+import javax.xml.namespace.QName;
+
+import org.apache.log4j.Logger;
+
+public class StackFrames
+implements   XPathVariableResolver
+{
+    private static Logger log = Logger.getLogger(StackFrames.class);
+
+    protected List<Map<String, Object>> frames;
+
+    public StackFrames() {
+        frames = new ArrayList<Map<String, Object>>();
+    }
+
+    public StackFrames(Map<String, Object> initialFrame) {
+        this();
+        if (initialFrame != null) {
+            frames.add(new HashMap<String, Object>(initialFrame));
+        }
+    }
+
+    public void enter() {
+        frames.add(new HashMap<String, Object>());
+    }
+
+    public void leave() {
+        frames.remove(frames.size()-1);
+    }
+
+    public void put(String key, Object value) {
+        int N = frames.size();
+        if (N > 0) {
+            frames.get(N-1).put(key, value);
+        }
+    }
+
+    public void put(String [] keys, Object [] values) {
+        Map<String, Object> top = frames.get(frames.size()-1);
+        for (int i = 0; i < keys.length; ++i) {
+            top.put(keys[i], values[i]);
+        }
+    }
+
+    public boolean containsKey(String key) {
+        key = key.toUpperCase();
+        for (int i = frames.size()-1; i >= 0; --i) {
+            if (frames.get(i).containsKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get element (variable) key.
+     * Returns null if not found.
+     * @param key name to resolve
+     * @return resolution, null if not found.
+     */
+    public Object get(String key) {
+        return get(key, null);
+    }
+
+    public boolean getStore(String key, Object [] result) {
+
+        key = key.toUpperCase();
+
+        for (int i = frames.size()-1; i >= 0; --i) {
+            Map<String, Object> frame = frames.get(i);
+            if (frame.containsKey(key)) {
+                result[0] = frame.get(key);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public Object get(String key, Object def) {
+
+        key = key.toUpperCase();
+
+        for (int i = frames.size()-1; i >= 0; --i) {
+            Map<String, Object> frame = frames.get(i);
+            if (frame.containsKey(key)) {
+                return frame.get(key);
+            }
+        }
+
+        return def;
+    }
+
+    @Override
+    public Object resolveVariable(QName variableName) {
+        if (log.isDebugEnabled()) {
+            log.debug("resolve var: " + variableName);
+        }
+        return get(variableName.getLocalPart());
+    }
+
+    public String dump() {
+        StringBuilder sb = new StringBuilder("[");
+        Set<String> already = new HashSet<String>();
+
+        boolean first = true;
+
+        for (int i = frames.size()-1; i >= 0; --i) {
+            Map<String, Object> frame = frames.get(i);
+            for (Map.Entry<String, Object> entry: frame.entrySet()) {
+                if (already.add(entry.getKey())) {
+                    if (first) { first = false;   }
+                    else       { sb.append(", "); }
+                    sb.append('\'').append(entry.getKey())
+                      .append("'='").append(entry.getValue()).append('\'');
+                }
+            }
+        }
+        return sb.append(']').toString();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/datacage/templating/TypeConverter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,31 @@
+package de.intevation.flys.artifacts.datacage.templating;
+
+public class TypeConverter
+{
+    private TypeConverter() {
+    }
+
+    public static Object convert(Object object, String type) {
+
+        if (type == null) {
+            return object;
+        }
+
+        if ("Integer".equals(type)) {
+            return Integer.valueOf(object.toString());
+        }
+
+        if ("Double".equals(type)) {
+            return Double.valueOf(object.toString());
+        }
+
+        if ("String".equals(type)) {
+            return object.toString();
+        }
+
+        // TODO: Add more types
+
+        return object;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/geom/Lines.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,403 @@
+package de.intevation.flys.geom;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Iterator;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.Line2D;
+
+import de.intevation.flys.artifacts.math.Linear;
+
+import org.apache.log4j.Logger;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * Utility to create lines (intersect water with cross-section etc).
+ */
+public class Lines
+{
+    private static Logger log = Logger.getLogger(Lines.class);
+
+    public static final double EPSILON = 1e-4;
+
+    public static enum Mode { UNDEF, WET, DRY };
+
+
+    /** Never instantiate Lines, use static functions instead. */
+    protected Lines() {
+    }
+
+
+    /**
+     * Calculate area of polygon with four vertices.
+     * @return area of polygon with four vertices.
+     */
+    public static double area(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {
+        double[] x = new double[] {p1.getX(), p2.getX(), p3.getX(), p4.getX(), p1.getX() };
+        double[] y = new double[] {p1.getY(), p2.getY(), p3.getY(), p4.getY(), p1.getY() };
+        double area = 0d;
+        for (int i=0; i <4; i++) {
+            area += (x[i] * y[i+1]) - (x[i+1] * y[i]);
+        }
+        return Math.abs(area * 0.5d);
+    }
+
+
+    /**
+     * Calculate the 'length' of the given lines.
+     * @param lines lines of which to calculate length.
+     */
+    public static double length(List<Line2D> lines) {
+        double sum = 0d;
+        for (Line2D line: lines) {
+            double xDiff = line.getX1() - line.getX2();
+            double yDiff = line.getY1() - line.getY2();
+            sum += Math.sqrt(xDiff*xDiff + yDiff*yDiff);
+        }
+        return sum;
+    }
+
+
+    /** List of lines and a double-precision area. */
+    private static class ListWithArea {
+        public List<Line2D> lines;
+        public double area;
+        public ListWithArea(List<Line2D> lines, double area) {
+            this.lines = lines;
+            this.area = area;
+        }
+    }
+
+
+    /**
+     * For a cross section given as points and a waterlevel (in meters),
+     * create a set of lines that represent the water surface, assuming it
+     * is distributed horizontally equally.
+     * @param points the points describing the river bed.
+     * @param waterLevel the height of the horizontal water line.
+     * @return A list of Lines representing the water surface and the
+     *         calculated area between water surface and river bed.
+     */
+    public static ListWithArea fillWater(List<Point2D> points, double waterLevel) {
+
+        boolean debug = log.isDebugEnabled();
+
+        if (debug) {
+            log.debug("fillWater");
+            log.debug("----------------------------");
+        }
+
+        List<Line2D> result = new ArrayList();
+
+        int N = points.size();
+
+        if (N == 0) {
+            return new ListWithArea(result, 0d);
+        }
+
+        if (N == 1) {
+            Point2D p = points.get(0);
+            // Only generate point if over profile
+            if (waterLevel > p.getY()) {
+                result.add(new Line2D.Double(
+                    p.getX(), waterLevel,
+                    p.getX(), waterLevel));
+            }
+            // TODO continue calculating area.
+            return new ListWithArea(result, 0d);
+        }
+
+        double minX =  Double.MAX_VALUE;
+        double minY =  Double.MAX_VALUE;
+        double maxX = -Double.MAX_VALUE;
+        double maxY = -Double.MAX_VALUE;
+
+        // To ensure for sequences of equals x's that
+        // the original index order is preserved.
+        for (Point2D p: points) {
+            double x = p.getX(), y = p.getY();
+            if (x < minX) minX = x;
+            if (x > maxX) maxX = x;
+            if (y < minY) minY = y;
+            if (y > maxY) maxY = y;
+        }
+
+        if (minY > waterLevel) { // profile completely over water level
+            log.debug("complete over water");
+            return new ListWithArea(result, 0d);
+        }
+
+        if (waterLevel > maxY) { // water floods profile
+            log.debug("complete under water");
+            result.add(new Line2D.Double(minX, waterLevel, maxX, waterLevel));
+            return new ListWithArea(result, 0d);
+        }
+
+        // Water is sometimes above, sometimes under profile.
+        Mode mode = Mode.UNDEF;
+
+        double startX = minX;
+
+        double area = 0d;
+        // Walking along the profile.
+        for (int i = 1; i < N; ++i) {
+            Point2D p1 = points.get(i-1);
+            Point2D p2 = points.get(i);
+
+            if (p1.getY() < waterLevel && p2.getY() < waterLevel) {
+                // completely under water
+                if (debug) {
+                    log.debug("under water: " + p1 + " " + p2);
+                }
+                if (mode != Mode.WET) {
+                    startX = p1.getX();
+                    mode = Mode.WET;
+                }
+                area += area(p1, p2,
+                    new Point2D.Double(p2.getX(), waterLevel),
+                    new Point2D.Double(p1.getX(), waterLevel));
+                continue;
+            }
+
+            // TODO trigger area calculation
+            if (p1.getY() > waterLevel && p2.getY() > waterLevel) {
+                if (debug) {
+                    log.debug("over water: " + p1 + " " + p2);
+                }
+                // completely over water
+                if (mode == Mode.WET) {
+                    log.debug("over/wet");
+                    result.add(new Line2D.Double(
+                        startX, waterLevel,
+                        p1.getX(), waterLevel));
+                }
+                mode = Mode.DRY;
+                continue;
+            }
+
+            // TODO trigger area calculation
+            if (Math.abs(p1.getX() - p2.getX()) < EPSILON) {
+                // vertical line
+                switch (mode) {
+                    case WET:
+                        log.debug("vertical/wet");
+                        mode = Mode.DRY;
+                        result.add(new Line2D.Double(
+                            startX, waterLevel,
+                            p1.getX(), waterLevel));
+                        break;
+                    case DRY:
+                        log.debug("vertical/dry");
+                        mode = Mode.WET;
+                        startX = p2.getX();
+                        break;
+                    default: // UNDEF
+                        log.debug("vertical/undef");
+                        if (p2.getY() < waterLevel) {
+                            mode = Mode.WET;
+                            startX = p2.getX();
+                        }
+                        else {
+                            mode = Mode.DRY;
+                        }
+                }
+                continue;
+            }
+
+            // check if waterlevel directly hits the vertices;
+
+            boolean p1W = Math.abs(waterLevel - p1.getY()) < EPSILON;
+            boolean p2W = Math.abs(waterLevel - p2.getY()) < EPSILON;
+
+            // TODO trigger area calculation
+            if (p1W || p2W) {
+                if (debug) {
+                    log.debug("water hits vertex: " + p1 + " " + p2 + " " + mode);
+                }
+                if (p1W && p2W) { // parallel to water -> dry
+                    log.debug("water hits both vertices");
+                    if (mode == Mode.WET) {
+                        result.add(new Line2D.Double(
+                            startX, waterLevel,
+                            p1.getX(), waterLevel));
+                    }
+                    mode = Mode.DRY;
+                }
+                else if (p1W) { // p1 == waterlevel
+                    log.debug("water hits first vertex");
+                    if (p2.getY() > waterLevel) { // --> dry
+                        if (mode == Mode.WET) {
+                            result.add(new Line2D.Double(
+                                startX, waterLevel,
+                                p1.getX(), waterLevel));
+                        }
+                        mode = Mode.DRY;
+                    }
+                    else { // --> wet
+                        if (mode != Mode.WET) {
+                            startX = p1.getX();
+                            mode = Mode.WET;
+                        }
+                        area += area(p1, p2,
+                            new Point2D.Double(p2.getX(), waterLevel),
+                            new Point2D.Double(p2.getX(), waterLevel));
+                    }
+                }
+                else { // p2 == waterlevel
+                    log.debug("water hits second vertex");
+                    if (p1.getY() > waterLevel) { // --> wet
+                        if (mode != Mode.WET) {
+                            startX = p2.getX();
+                            mode = Mode.WET;
+                        }
+                    }
+                    else { // --> dry
+                        if (mode == Mode.WET) {
+                            result.add(new Line2D.Double(
+                                startX, waterLevel,
+                                p2.getX(), waterLevel));
+                        }
+                        mode = Mode.DRY;
+                        area += area(p1, p2,
+                            new Point2D.Double(p1.getX(), waterLevel),
+                            new Point2D.Double(p1.getX(), waterLevel));
+                    }
+                }
+                if (debug) {
+                    log.debug("mode is now: " + mode);
+                }
+                continue;
+            }
+
+            // TODO trigger area calculation
+            // intersection case
+            double x = Linear.linear(
+                waterLevel,
+                p1.getY(), p2.getY(),
+                p1.getX(), p2.getX());
+
+            if (debug) {
+                log.debug("intersection p1:" + p1);
+                log.debug("intersection p2:" + p2);
+                log.debug("intersection at x: " + x);
+            }
+
+            // Add area of that part of intersection that is 'wet'.
+            if (p1.getY() > waterLevel) {
+                area += area(new Point2D.Double(x, waterLevel),
+                             p2,
+                             new Point2D.Double(p2.getX(), waterLevel),
+                             new Point2D.Double(x, waterLevel));
+            }
+            else {
+                area += area(new Point2D.Double(x, waterLevel),
+                             p1,
+                             new Point2D.Double(p1.getX(), waterLevel),
+                             new Point2D.Double(x, waterLevel));
+            }
+
+            switch (mode) {
+                case WET:
+                    log.debug("intersect/wet");
+                    mode = Mode.DRY;
+                    result.add(new Line2D.Double(
+                        startX, waterLevel,
+                        x, waterLevel));
+                    break;
+
+                case DRY:
+                    log.debug("intersect/dry");
+                    mode   = Mode.WET;
+                    startX = x;
+                    break;
+
+                default: // UNDEF
+                    log.debug("intersect/undef");
+                    if (p2.getY() > waterLevel) {
+                        log.debug("intersect/undef/over");
+                        mode = Mode.DRY;
+                        result.add(new Line2D.Double(
+                            p1.getX(), waterLevel,
+                            x, waterLevel));
+                    }
+                    else {
+                        mode = Mode.WET;
+                        startX = x;
+                    }
+            } // switch mode
+        } // for all points p[i] and p[i-1]
+
+        if (mode == Mode.WET) {
+            result.add(new Line2D.Double(
+                startX, waterLevel,
+                maxX, waterLevel));
+        }
+
+        return new ListWithArea(result, area);
+    }
+
+
+    /**
+     * Class holding points that form lines and the calculated length.
+     */
+    public static class LineData {
+        public double [][] points;
+        public double width;
+        public double area;
+        public LineData(double[][] points, double width, double area) {
+            this.points = points;
+            this.width = width;
+            this.area = area;
+        }
+    }
+
+
+    /** Return length of a single line. */
+    public static double lineLength(Line2D line) {
+        double xDiff = line.getX1() - line.getX2();
+        double yDiff = line.getY1() - line.getY2();
+        return Math.sqrt(xDiff*xDiff + yDiff*yDiff);
+    }
+
+
+    /**
+     * @param points the riverbed.
+     */
+    public static LineData createWaterLines(
+        List<Point2D> points,
+        double        waterlevel
+    ) {
+        ListWithArea listAndArea = fillWater(points, waterlevel);
+        List<Line2D> lines = listAndArea.lines;
+
+        TDoubleArrayList lxs = new TDoubleArrayList();
+        TDoubleArrayList lys = new TDoubleArrayList();
+        double linesLength = 0.0f;
+
+        for (Iterator<Line2D> iter = lines.iterator(); iter.hasNext();) {
+            Line2D  line = iter.next();
+            Point2D p1   = line.getP1();
+            Point2D p2   = line.getP2();
+            lxs.add(p1.getX());
+            lys.add(p1.getY());
+            lxs.add(p2.getX());
+            lys.add(p2.getY());
+
+            // Length calculation.
+            linesLength += lineLength(line);
+
+            if (iter.hasNext()) {
+                lxs.add(Double.NaN);
+                lys.add(Double.NaN);
+            }
+        }
+
+        return new LineData(
+            new double [][] { lxs.toNativeArray(), lys.toNativeArray() },
+            linesLength, listAndArea.area
+            );
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,430 @@
+package de.intevation.flys.geom;
+
+import java.io.Serializable;
+
+import java.awt.Shape;
+
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Collections;
+
+import de.intevation.flys.artifacts.math.Linear;
+
+import static de.intevation.flys.geom.VectorUtils.X;
+import static de.intevation.flys.geom.VectorUtils.Y;
+import static de.intevation.flys.geom.VectorUtils.EPSILON;
+
+public class Polygon2D
+implements   Serializable
+{
+    public static final Comparator<Point2D> POINT_X_CMP =
+        new Comparator<Point2D>() {
+            public int compare(Point2D a, Point2D b) {
+                double d = X(a) - X(b);
+                if (d < 0d) return -1;
+                return d > 0d ? + 1 : 0;
+            }
+        };
+
+    public static final Comparator<Point2D []> FIRST_POINT_X =
+        new Comparator<Point2D []>() {
+            public int compare(Point2D [] a, Point2D [] b) {
+                if (a.length == 0) return -1; // should not happen.
+                if (b.length == 0) return +1; // should not happen.
+                double d = X(a[0]) - Y(b[0]);
+                if (d < 0d) return -1;
+                return d > 0d ? + 1 : 0;
+            }
+        };
+
+    protected List<Point2D> points;
+
+    public Polygon2D() {
+        points = new ArrayList<Point2D>();
+    }
+
+    public Polygon2D(List<Point2D> points) {
+        this.points = points;
+    }
+
+    public void add(double x, double y) {
+        points.add(new Point2D.Double(x, y));
+    }
+
+    protected static boolean addCheck(Point2D p, List<Point2D> points) {
+        switch (points.size()) {
+            case 0:
+                points.add(p);
+                return true;
+            case 1:
+                if (VectorUtils.epsilonEquals(points.get(0), p)) {
+                    return false;
+                }
+                points.add(p);
+                return true;
+            default:
+                int L = points.size()-1;
+                Point2D last = points.get(L);
+                if (VectorUtils.epsilonEquals(last, p)) {
+                    return false;
+                }
+                Point2D before = points.get(L-1);
+                if (VectorUtils.collinear(before, last, p)) {
+                    points.set(L, p);
+                }
+                else {
+                    points.add(p);
+                }
+                return true;
+        }
+    }
+
+    public boolean addCheck(Point2D p) {
+        return addCheck(p, points);
+    }
+
+    public void addReversed(List<Point2D> other) {
+        for (int i = other.size()-1; i >= 0; --i) {
+            addCheck(other.get(i));
+        }
+    }
+
+    public double area() {
+        double area = 0d;
+
+		for (int i = 0, N = points.size(); i < N; ++i) {
+			int j = (i + 1) % N;
+            Point2D pi = points.get(i);
+            Point2D pj = points.get(j);
+			area += X(pi)*Y(pj);
+			area -= X(pj)*Y(pi);
+		}
+
+        return 0.5d*area;
+    }
+
+    public Shape toShape() {
+        Path2D.Double path = new Path2D.Double();
+
+        int N = points.size();
+
+        if (N > 0) {
+            Point2D p = points.get(0);
+            path.moveTo(X(p), Y(p));
+            for (int i = 1; i < N; ++i) {
+                p = points.get(i);
+                path.lineTo(X(p), Y(p));
+            }
+            path.closePath();
+        }
+
+        return path;
+    }
+
+    protected static List<Point2D []> splitByNaNs(
+        double [] xs,
+        double [] ys
+    ) {
+        List<Point2D []> parts = new ArrayList<Point2D []>();
+
+        List<Point2D> tmp = new ArrayList<Point2D>(xs.length);
+
+        for (int i = 0; i < xs.length; ++i) {
+            double x = xs[i];
+            double y = ys[i];
+
+            if (Double.isNaN(x) || Double.isNaN(y)) {
+                if (!tmp.isEmpty()) {
+                    Point2D [] part = new Point2D[tmp.size()];
+                    parts.add(tmp.toArray(part));
+                    tmp.clear();
+                }
+            }
+            else {
+                tmp.add(new Point2D.Double(x, y));
+            }
+        }
+
+        if (!tmp.isEmpty()) {
+            Point2D [] part = new Point2D[tmp.size()];
+            parts.add(tmp.toArray(part));
+        }
+
+        return parts;
+    }
+
+    protected static boolean removeNoneIntersecting(
+        List<Point2D []> As,
+        List<Point2D []> Bs
+    ) {
+        int B = Bs.size()-1;
+        OUTER: for (int i = 0; i < As.size();) {
+            Point2D [] a = As.get(i);
+            int lo = 0, hi = B;
+            while (lo <= hi) {
+                int mid = (lo + hi) >> 1;
+                Point2D [] b = Bs.get(mid);
+                     if (X(a[0]) > X(b[b.length-1])) lo = mid+1;
+                else if (X(a[a.length-1]) < X(b[0])) hi = mid-1;
+                else {
+                    // found: keep
+                    ++i;
+                    continue OUTER;
+                }
+            }
+            // not found: remove
+            As.remove(i);
+        }
+
+        return As.isEmpty();
+    }
+
+    protected static void buildPolygons(
+        Point2D []      as,
+        Point2D []      bs,
+        Point2D [][]    rest,
+        List<Polygon2D> positives,
+        List<Polygon2D> negatives
+    ) {
+        List<Point2D> apoints = new ArrayList<Point2D>();
+        List<Point2D> bpoints = new ArrayList<Point2D>();
+
+        double ax = X(as[0]);
+        double bx = X(bs[0]);
+
+        int ai = 1;
+        int bi = 1;
+
+        boolean intersect = false;
+
+        if (ax == bx) {
+            apoints.add(as[0]);
+            bpoints.add(bs[0]);
+        }
+        else if (ax > bx) {
+            apoints.add(as[0]);
+            double bx1;
+            while ((bx1 = X(bs[bi])) < ax) ++bi;
+            if (bx1 == ax) {
+                bpoints.add(bs[bi]);
+            }
+            else { // need to calculate start b point.
+                intersect = true;
+                double by1 = Linear.linear(
+                    ax,
+                    X(bs[bi-1]), bx1,
+                    Y(bs[bi-1]), Y(bs[bi]));
+
+                bpoints.add(new Point2D.Double(ax, by1));
+            }
+        }
+        else { // bx > ax: Symmetric case
+            bpoints.add(bs[0]);
+            double ax1;
+            while ((ax1 = X(as[ai])) < bx) ++ai;
+            if (ax1 == bx) {
+                apoints.add(as[ai]);
+            }
+            else { // need to calculate start b point.
+                intersect = true;
+                double ay1 = Linear.linear(
+                    bx,
+                    X(as[ai-1]), ax1,
+                    Y(as[ai-1]), Y(as[ai]));
+
+                apoints.add(new Point2D.Double(bx, ay1));
+            }
+        }
+
+        // now we have a point in each list, decide if neg/pos.
+        boolean neg = Y(bpoints.get(0)) > Y(apoints.get(0));
+
+        // Continue with inner points
+
+        Line line = new Line();
+
+        while (ai < as.length && bi < bs.length) {
+            double xan = X(as[ai]);
+            double xbn = X(bs[bi]);
+            if (xan == xbn) { 
+                double yan = Y(as[ai]);
+                double ybn = Y(bs[ai]);
+
+                if (neg) {
+                    if (yan > ybn) { // intersection
+                        Point2D ip = VectorUtils.intersection(
+                            apoints.get(apoints.size()-1), as[ai],
+                            bpoints.get(bpoints.size()-1), bs[bi]);
+                        addCheck(ip, apoints);
+                        addCheck(ip, bpoints);
+                        Polygon2D p = new Polygon2D(
+                            new ArrayList<Point2D>(apoints));
+                        p.addReversed(bpoints);
+                        negatives.add(p);
+                        apoints.clear();
+                        bpoints.clear();
+                        apoints.add(ip);
+                        bpoints.add(ip);
+                        neg = !neg;
+                    }
+                    else { // no intersection
+                        addCheck(as[ai], apoints);
+                        addCheck(bs[bi], bpoints);
+                    }
+                }
+                else { // not neg
+                    if (ybn > yan) { // intersection
+                        Point2D ip = VectorUtils.intersection(
+                            apoints.get(apoints.size()-1), as[ai],
+                            bpoints.get(bpoints.size()-1), bs[bi]);
+                        addCheck(ip, apoints);
+                        addCheck(ip, bpoints);
+                        Polygon2D p = new Polygon2D(
+                            new ArrayList<Point2D>(apoints));
+                        p.addReversed(bpoints);
+                        positives.add(p);
+                        apoints.clear();
+                        bpoints.clear();
+                        apoints.add(ip);
+                        bpoints.add(ip);
+                        neg = !neg;
+                    }
+                    else { // no intersection
+                        addCheck(as[ai], apoints);
+                        addCheck(bs[bi], bpoints);
+                    }
+                }
+                ++ai;
+                ++bi;
+            }
+            else if (xan > xbn) {
+                line.set(apoints.get(apoints.size()-1), as[ai]);
+                double dir = neg ? -1d: 1d; // XXX: correct sign?
+                while  (bi < bs.length 
+                    && X(bs[bi]) < xan
+                    && line.eval(bs[bi])*dir > EPSILON) 
+                    ++bi;
+                if (bi == bs.length) {
+                    // b run out of points
+                    // calculate Y(last_a, as[ai]) for X(bs[bi-1])
+                    double ay1 = Linear.linear(
+                        X(bs[bi-1]),
+                        X(apoints.get(apoints.size()-1)), X(as[ai]),
+                        Y(apoints.get(apoints.size()-1)), Y(as[ai]));
+                    addCheck(new Point2D.Double(X(bs[bi-1]), ay1), apoints);
+                    addCheck(bs[bi-1], bpoints);
+                    Polygon2D p = new Polygon2D(
+                        new ArrayList<Point2D>(apoints));
+                    p.addReversed(bpoints);
+                    apoints.clear();
+                    bpoints.clear();
+                    (neg ? negatives : positives).add(p);
+                    break;
+                }
+                else {
+                    // TODO: intersect line and/or X(bs[bi]) >= xan?
+                }
+            }
+            else { // xbn > xan
+                line.set(bpoints.get(bpoints.size()-1), bs[bi]);
+                // TODO: continue symmetric
+            }
+        }
+
+        // TODO: Continue with closing segment
+    }
+
+    public static final class Line {
+
+        private double a;
+        private double b;
+        private double c;
+
+        public Line() {
+        }
+
+        public Line(Point2D p1, Point2D p2) {
+            set(p1, p2);
+        }
+
+        public void set(Point2D p1, Point2D p2) {
+            Point2D p3 = 
+                VectorUtils.normalize(
+                VectorUtils.sub(p1, p2));
+
+            Point2D n = VectorUtils.ortho(p3);
+
+            a = X(n);
+            b = Y(n);
+
+            // a*x + b*y + c = 0
+            // c = -a*x -b*y
+
+            c = -a*X(p1) - b*Y(p1);
+        }
+
+        public double eval(Point2D p) {
+            return a*X(p) + b*Y(p) + c;
+        }
+    }
+
+    public static void createPolygons(
+        double [] xAs, double [] yAs,
+        double [] xBs, double [] yBs,
+        List<Polygon2D> positives,
+        List<Polygon2D> negatives
+    ) {
+        if (xAs.length == 0 || xBs.length == 0) {
+            return;
+        }
+
+        List<Point2D []> splAs = splitByNaNs(xAs, yAs);
+        List<Point2D []> splBs = splitByNaNs(xBs, yBs);
+
+        // They feeded us with NaNs only.
+        if (splAs.isEmpty() || splBs.isEmpty()) {
+            return;
+        }
+
+        // Sort each part by x to ensure that the first
+        // is the smallest.
+        for (Point2D [] splA: splAs) {
+            Arrays.sort(splA, POINT_X_CMP);
+        }
+
+        for (Point2D [] splB: splBs) {
+            Arrays.sort(splB, POINT_X_CMP);
+        }
+
+        // Now sort all parts by there first elements.
+        // Should be good enough to find overlapping regions.
+        Collections.sort(splAs, FIRST_POINT_X);
+        Collections.sort(splBs, FIRST_POINT_X);
+
+        // Check if the two series intersect at all.
+        // If no then there will be no area between them.
+
+        Point2D [] p1 = splAs.get(0);
+        Point2D [] p2 = splBs.get(splBs.size()-1);
+
+        // Sort out the ranges that are not intersecting
+        // the ranges in the other series.
+        // We are going to merge them anyway
+        // so this is not strictly required. 
+        // Keep it to recude cases.
+        if (removeNoneIntersecting(splAs, splBs)
+        ||  removeNoneIntersecting(splBs, splAs)
+        ) {
+            // They do not intersect at all.
+            return;
+        }
+
+        // TODO: Intersect/split the two series parts.
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/geom/VectorUtils.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,149 @@
+package de.intevation.flys.geom;
+
+import java.awt.geom.Point2D;
+
+public final class VectorUtils
+{
+    public static final double EPSILON = 1e-4;
+
+    private VectorUtils() {
+    }
+
+    public static final double X(Point2D p) {
+        return p.getX();
+    }
+
+    public static final double Y(Point2D p) {
+        return p.getY();
+    }
+
+    public static final Point2D sub(Point2D a, Point2D b) {
+        return new Point2D.Double(X(a)-X(b), Y(a)-Y(b));
+    }
+
+    public static final double dot(Point2D a, Point2D b) {
+        return X(a)*X(b) + Y(a)*Y(b);
+    }
+
+    public static final Point2D add(Point2D a, Point2D b) {
+        return new Point2D.Double(X(a)+X(b), Y(a)+Y(b));
+    }
+
+    public static final Point2D negate(Point2D a) {
+        return new Point2D.Double(-X(a), -Y(a));
+    }
+
+    public static final Point2D ortho(Point2D a) {
+        return new Point2D.Double(-Y(a), X(a));
+    }
+
+    public static final Point2D scale(Point2D a, double s) {
+        return new Point2D.Double(s*X(a), s*Y(a));
+    }
+
+    public static final double lengthSq(Point2D a) {
+        double x = X(a);
+        double y = Y(a);
+        return x*x + y*y;
+    }
+
+    public static final double length(Point2D a) {
+        return Math.sqrt(lengthSq(a));
+    }
+
+    public static final Point2D normalize(Point2D a) {
+        double length = length(a);
+        return length != 0d
+            ? scale(a, 1d/length) 
+            : new Point2D.Double(X(a), Y(a));
+    }
+
+    public static final double L1(Point2D a, Point2D b) {
+        return Math.abs(X(a)-X(b)) + Math.abs(Y(a)-Y(b));
+    }
+
+    public static final boolean collinear(Point2D a, Point2D b, Point2D c) {
+        double x1 = X(a);
+        double y1 = Y(a);
+        double x2 = X(b);
+        double y2 = Y(b);
+        double x3 = X(c);
+        double y3 = Y(c);
+
+        return Math.abs((x2-x1)*(y3-y1)-(y2-y1)*(x3-x1)) < EPSILON;
+    }
+
+    public static boolean epsilonEquals(Point2D a, Point2D b) {
+        return Math.abs(X(a)-X(b)) < EPSILON 
+            && Math.abs(Y(a)-Y(b)) < EPSILON;
+    }
+
+    public static final Point2D intersection(
+        Point2D p1, Point2D p2,
+        Point2D p3, Point2D p4
+    ) {
+        double x1 = X(p1);
+        double y1 = Y(p1);
+        double x2 = X(p2);
+        double y2 = Y(p2);
+        double x3 = X(p3);
+        double y3 = Y(p3);
+        double x4 = X(p4);
+        double y4 = Y(p4);
+
+        // Compute a1, b1, c1, where line joining points 1 and 2
+        // is "a1 x + b1 y + c1 = 0".
+        double a1 = y2 - y1;
+        double b1 = x1 - x2;
+        double c1 = x2*y1 - x1*y2;
+
+        // Compute r3 and r4.
+        double r3 = a1*x3 + b1*y3 + c1;
+        double r4 = a1*x4 + b1*y4 + c1;
+
+        if (r3 != 0d && r4 != 0d && r3*r4 >= 0) {
+            return null;
+        }
+
+        // Compute a2, b2, c2
+        double a2 = y4 - y3;
+        double b2 = x3 - x4;
+        double c2 = (x4 * y3) - (x3 * y4);
+
+        // Compute r1 and r2
+        double r1 = a2*x1 + b2*y1 + c2;
+        double r2 = a2*x2 + b2*y2 + c2;
+
+        if (r1 != 0d && r2 != 0d && r1*r2 >= 0) {
+            return null;
+        }
+
+        // Line segments intersect: compute intersection point.
+        double denom = a1*b2 - a2*b1;
+
+        if (denom == 0d) { // collinear
+            return null;
+        }
+
+        double offset = Math.abs(denom)/2d;
+
+        // The denom/2 is to get rounding instead of truncating. It
+        // is added or subtracted to the numerator, depending upon the
+        // sign of the numerator.
+        double num = b1*c2 - b2*c1;
+
+        double x = num < 0d
+            ? (num - offset)/denom
+            : (num + offset)/denom;
+
+        num = a2*c1 - a1*c2;
+
+        double y = num < 0d
+            ? (num - offset)/denom
+            : (num + offset)/denom;
+
+        return new Point2D.Double(x, y);
+    }
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/map/PrintMap.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,130 @@
+package de.intevation.flys.artifacts.map;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import java.util.List;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import javax.imageio.ImageIO;
+
+
+import org.geotools.data.ows.Layer;
+import org.geotools.data.ows.WMSCapabilities;
+import org.geotools.data.wms.WebMapServer;
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.map.MapContext;
+import org.geotools.map.WMSMapLayer;
+import org.geotools.ows.ServiceException;
+import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.renderer.GTRenderer;
+
+
+public class PrintMap {
+
+    public static final String DEFAULT_WMS = "http://map1.naturschutz.rlp.de/service_lanis/mod_wms/wms_getmap.php?mapfile=group_gdide&REQUEST=GetCapabilities&SERVICE=WMS";
+    public static final String DEFAULT_OUTFILE = "~/map.jpeg";
+
+    public static final String MAPSERVER = System.getProperty("wms", DEFAULT_WMS);
+    public static final String MAP_IMAGE = System.getProperty("outfile", DEFAULT_OUTFILE);
+
+
+    public static void main(String[] args) {
+        System.out.println("-> start PrintMap");
+        System.out.println("  -> Print layers of WMS: " + MAPSERVER);
+
+        try {
+            WebMapServer    server = getMapserver();
+            WMSMapLayer[] wmsLayer = getWMSLayers(server);
+
+            MapContext mapcontent = new MapContext( wmsLayer );
+            mapcontent.setTitle(" NEW MAP CONTENT TITLE ");
+
+            printMap(mapcontent);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("-> finished PrintMap");
+    }
+
+
+    public static void printMap(MapContext map) throws Exception {
+        int imageWidth = 600;
+
+        GTRenderer renderer = new StreamingRenderer();
+        renderer.setContext(map);
+
+        Rectangle imageBounds = null;
+        ReferencedEnvelope mapBounds = null;
+
+        try {
+            mapBounds = map.getLayerBounds();
+            double heightToWidth = mapBounds.getSpan(1) / mapBounds.getSpan(0);
+            imageBounds = new Rectangle(
+                0, 0,
+                imageWidth,
+                (int) Math.round(imageWidth * heightToWidth));
+
+        }
+        catch (Exception e) {
+            // failed to access map layers
+            throw new RuntimeException(e);
+        }
+
+        BufferedImage image = new BufferedImage(imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D gr = image.createGraphics();
+        gr.setPaint(Color.WHITE);
+        gr.fill(imageBounds);
+
+        try {
+            renderer.paint(gr, imageBounds, mapBounds);
+            File fileToSave = new File(MAP_IMAGE);
+            ImageIO.write(image, "jpeg", fileToSave);
+        }
+        catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    public static WebMapServer getMapserver() throws MalformedURLException, IOException, ServiceException {
+        return new WebMapServer(getServerUrl());
+    }
+
+
+    public static URL getServerUrl() throws MalformedURLException {
+        return new URL(MAPSERVER);
+    }
+
+
+    public static WMSMapLayer[] getWMSLayers(WebMapServer server) {
+        if (server == null) {
+            System.out.println("WebMapServer == null");
+            throw new RuntimeException("WebMapServer == null");
+        }
+
+        WMSCapabilities capabilities = server.getCapabilities();
+
+        List<Layer> layers = capabilities.getLayerList();
+        WMSMapLayer[] wmslayers = new WMSMapLayer[layers.size()];
+
+        for (int i = 0; i < layers.size(); i++) {
+            Layer l = layers.get(i);
+
+            System.out.println("   -> add layer: " + l);
+            wmslayers[i] = new WMSMapLayer(server, l);
+        }
+
+        return wmslayers;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/AddScaleFunction.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,19 @@
+package de.intevation.flys.artifacts.math;
+
+public class AddScaleFunction
+implements   Function
+{
+    protected double b;
+    protected double m;
+
+    public AddScaleFunction(double b, double m) {
+        this.b = b;
+        this.m = m;
+    }
+
+    @Override
+    public double value(double x) {
+        return (x + b)*m;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,217 @@
+package de.intevation.flys.artifacts.math;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
+
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+
+import org.apache.commons.math.ArgumentOutsideDomainException;
+
+import org.apache.commons.math.exception.MathIllegalArgumentException;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.model.Calculation;
+
+import de.intevation.flys.utils.DoubleUtil;
+
+public class BackJumpCorrector
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(BackJumpCorrector.class);
+
+    protected ArrayList<Double> backjumps;
+
+    protected double [] corrected;
+
+    public BackJumpCorrector() {
+        backjumps = new ArrayList<Double>();
+    }
+
+    public boolean hasBackJumps() {
+        return !backjumps.isEmpty();
+    }
+
+    public List<Double> getBackJumps() {
+        return backjumps;
+    }
+
+    public double [] getCorrected() {
+        return corrected;
+    }
+
+    public boolean doCorrection(
+        double []   km,
+        double []   ws,
+        Calculation errors
+    ) {
+        boolean wsUp = DoubleUtil.isIncreasing(ws);
+
+        if (wsUp) {
+            km = DoubleUtil.swapClone(km);
+            ws = DoubleUtil.swapClone(ws);
+        }
+
+        boolean kmUp = DoubleUtil.isIncreasing(km);
+
+        if (!kmUp) {
+            km = DoubleUtil.sumDiffs(km);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("BackJumpCorrector.doCorrection ------- enter");
+            log.debug("  km increasing: " + DoubleUtil.isIncreasing(km));
+            log.debug("  ws increasing: " + DoubleUtil.isIncreasing(ws));
+            log.debug("BackJumpCorrector.doCorrection ------- leave");
+        }
+
+        boolean hasBackJumps = doCorrectionClean(km, ws, errors);
+
+        if (hasBackJumps && wsUp) {
+            // mirror back
+            DoubleUtil.swap(corrected);
+        }
+
+        return hasBackJumps;
+    }
+
+    protected boolean doCorrectionClean(
+        double []   km,
+        double []   ws,
+        Calculation errors
+    ) {
+        int N = km.length;
+
+        if (N != ws.length) {
+            throw new IllegalArgumentException("km.length != ws.length");
+        }
+
+        if (N < 2) {
+            return false;
+        }
+
+        SplineInterpolator interpolator = null;
+
+        for (int i = 1; i < N; ++i) {
+            if (ws[i] <= ws[i-1]) {
+                // no back jump
+                continue;
+            }
+            backjumps.add(km[i]);
+
+            if (corrected == null) {
+                // lazy cloning
+                ws = corrected = (double [])ws.clone();
+            }
+
+            double above = aboveWaterKM(km, ws, i);
+
+            if (Double.isNaN(above)) { // run over start km
+                // fill all previous
+                for (int j = 0; j < i; ++j) {
+                    ws[j] = ws[i];
+                }
+                continue;
+            }
+
+            double distance = Math.abs(km[i] - above);
+
+            double quarterDistance = 0.25*distance;
+
+            double start = above - quarterDistance;
+
+            double startHeight = DoubleUtil.interpolateSorted(km, ws, start);
+
+            if (Double.isNaN(startHeight)) {
+                // run over start km
+                startHeight = ws[0];
+            }
+
+            double between = above + quarterDistance;
+
+            double aboveHeight = ws[i] + 0.25*(startHeight - ws[i]);
+
+            double [] x = { start,  above,  between };
+            double [] y = { startHeight, aboveHeight, ws[i] };
+
+            if (log.isDebugEnabled()) {
+                for (int j = 0; j < x.length; ++j) {
+                    log.debug("   " + x[j] + " -> " + y[j]);
+                }
+            }
+
+            if (interpolator == null) {
+                interpolator = new SplineInterpolator();
+            }
+
+            PolynomialSplineFunction spline;
+
+            try {
+                spline = interpolator.interpolate(x, y);
+            }
+            catch (MathIllegalArgumentException miae) {
+                errors.addProblem("spline.creation.failed");
+                log.error(miae);
+                continue;
+            }
+
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("spline points:");
+                    for (int j = 0; j < x.length; ++j) {
+                        log.debug(x[j] + " " + y[j] + " " + spline.value(x[j]));
+                    }
+                }
+
+                int j = i-1;
+
+                for (; j >= 0 && km[j] >= between; --j) {
+                    ws[j] = ws[i];
+                }
+
+                for (; j >= 0 && ws[j] < startHeight; --j) {
+                    ws[j] = spline.value(km[j]);
+                }
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                errors.addProblem("spline.interpolation.failed");
+                log.error("spline interpolation failed", aode);
+            }
+        } // for all km
+
+        return !backjumps.isEmpty();
+    }
+
+
+    protected static double aboveWaterKM(
+        double [] km, 
+        double [] ws, 
+        int       wIndex
+    ) {
+        double w = ws[wIndex];
+
+        while (--wIndex >= 0) {
+            // still under water
+            if (ws[wIndex] < w) continue;
+
+            if (ws[wIndex] > w) {
+                // f(ws[wIndex])   = km[wIndex]
+                // f(ws[wIndex+1]) = km[wIndex+1]
+                return Linear.linear(
+                    w,
+                    ws[wIndex], ws[wIndex+1],
+                    km[wIndex], km[wIndex+1]);
+            }
+            else {
+                return km[wIndex];
+            }
+        }
+
+        return Double.NaN;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/DifferenceCurveFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,71 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * Facet with the curve of a subtraction of two waterlevel-lines.
+ * TODO inherit directly from DataFacet? Check whether this Facet is obsolete.
+ */
+public class DifferenceCurveFacet extends WaterlevelFacet {
+
+    private static Logger logger = Logger.getLogger(DifferenceCurveFacet.class);
+
+
+    public DifferenceCurveFacet() {
+    }
+
+    public DifferenceCurveFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateID,
+        String      hash
+
+    ) {
+        super(index, name, description, type, stateID, hash);
+    }
+
+    /**
+     * Get difference curve data.
+     * @return a WKms at given index.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        WINFOArtifact winfo = (WINFOArtifact)artifact;
+
+        CalculationResult res = (CalculationResult)
+            winfo.compute(context, hash, stateId, type, false);
+
+        WKms [] wkms = (WKms [])res.getData();
+
+        WKms result = wkms[index];
+        logger.debug("Got difference curve data (" + result.getName() 
+            + ") at index: " + index);
+
+        return result;
+    }
+
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        WaterlevelFacet copy = new DifferenceCurveFacet();
+        copy.set(this);
+        copy.type    = type;
+        copy.stateId = stateId;
+        copy.hash    = hash;
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/FilterFunction.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+package de.intevation.flys.artifacts.math;
+
+public class FilterFunction implements Function {
+
+    protected Function parent;
+
+    public FilterFunction(Function parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public double value(double x) {
+        return parent.value(x);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/Function.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,6 @@
+package de.intevation.flys.artifacts.math;
+
+public interface Function {
+    double value(double x);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/Identity.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,15 @@
+package de.intevation.flys.artifacts.math;
+
+public final class Identity
+implements         Function
+{
+    public static final Identity IDENTITY = new Identity();
+
+    public Identity() {
+    }
+
+    public double value(double x) {
+        return x;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/Linear.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,68 @@
+package de.intevation.flys.artifacts.math;
+
+public final class Linear
+implements         Function
+{
+    private double m;
+    private double b;
+
+    public Linear(
+        double x1, double x2,
+        double y1, double y2
+    ) {
+        // y1 = m*x1 + b
+        // y2 = m*x2 + b
+        // y2 - y1 = m*(x2 - x1)
+        // m = (y2 - y1)/(x2 - x1) # x2 != x1
+        // b = y1 - m*x1
+
+        if (x1 == x2) {
+            m = 0;
+            b = 0.5*(y1 + y2);
+        }
+        else {
+            m = (y2 - y1)/(x2 - x1);
+            b = y1 - m*x1;
+        }
+    }
+
+    public static final double linear(
+        double x,
+        double x1, double x2,
+        double y1, double y2
+    ) {
+        // y1 = m*x1 + b
+        // y2 = m*x2 + b
+        // y2 - y1 = m*(x2 - x1)
+        // m = (y2 - y1)/(x2 - x1) # x2 != x1
+        // b = y1 - m*x1
+
+        if (x1 == x2) {
+            return 0.5*(y1 + y2);
+        }
+        double m = (y2 - y1)/(x2 - x1);
+        double b = y1 - m*x1;
+        return x*m + b;
+    }
+
+    @Override
+    public double value(double x) {
+        return m*x + b;
+    }
+
+    public static final double factor(double x, double p1, double p2) {
+        // 0 = m*p1 + b <=> b = -m*p1
+        // 1 = m*p2 + b
+        // 1 = m*(p2 - p1)
+        // m = 1/(p2 - p1) # p1 != p2
+        // f(x) = x/(p2-p1) - p1/(p2-p1) <=> (x-p1)/(p2-p1)
+
+        return p1 == p2 ? 0.0 : (x-p1)/(p2-p1);
+    }
+
+    public static final double weight(double factor, double a, double b) {
+        //return (1.0-factor)*a + factor*b;
+        return a + factor*(b-a);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/LinearFilterFunction.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,24 @@
+package de.intevation.flys.artifacts.math;
+
+public class LinearFilterFunction 
+extends      FilterFunction
+{
+    protected double m;
+    protected double b;
+
+    public LinearFilterFunction(double m, double b) {
+        this(Identity.IDENTITY, m, b);
+    }
+
+    public LinearFilterFunction(Function parent, double m, double b) {
+        super(parent);
+        this.m = m;
+        this.b = b;
+    }
+
+    @Override
+    public double value(double x) {
+        return super.value(x)*m + b;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/LinearFunction.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,19 @@
+package de.intevation.flys.artifacts.math;
+
+public class LinearFunction 
+implements   Function
+{
+    protected double m;
+    protected double b;
+
+    public LinearFunction(double m, double b) {
+        this.m = m;
+        this.b = b;
+    }
+
+    @Override
+    public double value(double x) {
+        return x*m + b;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/Outlier.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,111 @@
+package de.intevation.flys.artifacts.math;
+
+import org.apache.commons.math.MathException;
+
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.stat.descriptive.moment.StandardDeviation;
+
+import org.apache.commons.math.distribution.TDistributionImpl;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+public class Outlier
+{
+    private static Logger log = Logger.getLogger(Outlier.class);
+
+    public static class IndexedValue {
+        protected int    index;
+        protected double value;
+
+        public IndexedValue() {
+        }
+
+        public IndexedValue(int index, double value) {
+            this.index = index;
+            this.value = value;
+        }
+
+        public int getIndex() {
+            return index;
+        }
+
+        public void setIndex(int index) {
+            this.index = index;
+        }
+
+        public double getValue() {
+            return value;
+        }
+
+        public void setValue(double value) {
+            this.value = value;
+        }
+    } // class IndexedValue
+
+    public Outlier() {
+    }
+
+    public static List<IndexedValue> findOutliers(
+        List<IndexedValue> inputValues,
+        double             alpha
+    ) {
+        ArrayList<IndexedValue> outliers = new ArrayList<IndexedValue>();
+
+        ArrayList<IndexedValue> values =
+            new ArrayList<IndexedValue>(inputValues);
+
+        for (;;) {
+            int N = values.size();
+
+            if (N < 4) {
+                break;
+            }
+
+            Mean mean = new Mean();
+            StandardDeviation std = new StandardDeviation();
+
+            for (IndexedValue value: values) {
+                mean.increment(value.getValue());
+                std.increment(value.getValue());
+            }
+
+            double m = mean.getResult();
+            double s = std.getResult();
+
+            double maxZ = -Double.MAX_VALUE;
+            int iv = -1;
+            for (int i = N-1; i >= 0; --i) {
+                IndexedValue v = values.get(i);
+                double z = Math.abs(m - v.getValue())/s;
+                if (z > maxZ) {
+                    maxZ = z;
+                    iv = i;
+                }
+            }
+
+            double t = Math.sqrt((N*(N-2)*maxZ*maxZ)
+                /((N-1)*(N-1) - N*maxZ*maxZ));
+
+            TDistributionImpl tdist = new TDistributionImpl(N-2);
+
+            try {
+                double p = tdist.cumulativeProbability(t);
+
+                if (p < alpha) {
+                    outliers.add(values.get(iv));
+                    values.remove(iv);
+                }
+            }
+            catch (MathException me) {
+                log.error(me);
+            }
+        }
+
+
+        return outliers;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/Sub.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.artifacts.math;
+
+public final class Sub
+implements         Function
+{
+    private double s;
+
+    public Sub(double s) {
+        this.s = s;
+    }
+
+    @Override
+    public double value(double x) {
+        return x - s;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/WKmsOperation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,148 @@
+package de.intevation.flys.artifacts.math;
+
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WKmsImpl;
+
+import java.util.Arrays;
+
+public abstract class WKmsOperation
+{
+    public static final double EPSILON = 1e-6;
+
+    public static final class KmW 
+    implements                Comparable<KmW>
+    {
+        protected double km;
+        protected double w;
+
+        public KmW(double km, double w) {
+            this.km = km;
+            this.w  = w;
+        }
+
+        public int compareTo(KmW other) {
+            return km < other.km
+                ? -1
+                : km > other.km ? +1 : 0;
+        }
+
+        public boolean kmEquals(KmW other) {
+            return Math.abs(km - other.km) < EPSILON;
+        }
+
+        public double subtract(KmW other) {
+            return w - other.w;
+        }
+    } // class KmW
+
+    public static final WKmsOperation SUBTRACTION = new WKmsOperation() {
+
+        @Override
+        public WKms operate(WKms a, WKms b) {
+            return subtract(a, b);
+        }
+    };
+
+    protected WKmsOperation() {
+    }
+
+    public abstract WKms operate(WKms a, WKms b);
+
+    /**
+     * Subtract two series from each other, interpolate values
+     * missing in one series in the other.
+     */
+    public static WKms subtract(WKms minuend, WKms subtrahend) {
+
+        int M = minuend   .size();
+        int S = subtrahend.size();
+
+        // Don't subtract empty sets
+        if (M < 1 || S < 1) {
+            return new WKmsImpl();
+        }
+
+        KmW [] ms = new KmW[M];
+        KmW [] ss = new KmW[S];
+
+        for (int i = 0; i < M; ++i) {
+            ms[i] = new KmW(minuend.getKm(i), minuend.getW(i));
+        }
+
+        for (int i = 0; i < S; ++i) {
+            ss[i] = new KmW(subtrahend.getKm(i), subtrahend.getW(i));
+        }
+
+        Arrays.sort(ms);
+        Arrays.sort(ss);
+
+        // no overlap -> empty result set
+        if (ms[0].km > ss[S-1].km || ss[0].km > ms[M-1].km) {
+            return new WKmsImpl();
+        }
+
+        WKmsImpl result = new WKmsImpl();
+
+        int mi = 0;
+        int si = 0;
+
+        OUT: while (mi < M && si < S) {
+            KmW m = ms[mi];
+            KmW s = ss[si];
+
+            if (m.km + EPSILON < s.km) {
+                // minuend is before subtrahend
+
+                while (ms[mi].km + EPSILON < s.km) {
+                    if (++mi >= M) {
+                        break OUT;
+                    }
+                }
+
+                if (ms[mi].km + EPSILON > s.km) {
+                    double mw = Linear.linear(
+                        s.km,
+                        ms[mi-1].km, ms[mi].km,
+                        ms[mi-1].w,  ms[mi].w);
+                    result.add(s.km, mw - s.w);
+                    ++si;
+                }
+                else { // s.km == ms[mi].km
+                    result.add(s.km, ms[mi].subtract(s));
+                    ++mi;
+                    ++si;
+                }
+            }
+            else if (m.km > s.km + EPSILON) {
+                // subtrahend is before minuend
+
+                while (m.km > ss[si].km + EPSILON) {
+                    if (++si >= S) {
+                        break OUT;
+                    }
+                }
+
+                if (ss[si].km + EPSILON > m.km) {
+                    double sw = Linear.linear(
+                        m.km,
+                        ss[si-1].km, ss[si].km,
+                        ss[si-1].w,  ss[si].w);
+                    result.add(m.km, m.w - sw);
+                }
+                else { // ss[si].km == m.km
+                    result.add(m.km, m.subtract(ss[si]));
+                    ++mi;
+                    ++si;
+                }
+            }
+            else { // m.km == s.km
+                result.add(s.km, m.subtract(s));
+                ++mi;
+                ++si;
+            }
+        }
+
+        return result;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/App.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,127 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.TreeMap;
+import java.util.Comparator;
+
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.Reader;
+import java.io.InputStreamReader;
+
+import org.apache.commons.math.optimization.fitting.CurveFitter;
+
+import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
+
+import org.apache.commons.math.MathException;
+
+public class App
+{
+    public static final double EPS = 1e-5;
+
+    public static final String FUNCTION_NAME =
+        System.getProperty("function", "linear");
+
+    public static final Comparator<Double> EPS_CMP =
+        new Comparator<Double>()  {
+            @Override
+            public int compare(Double a, Double b) {
+                double diff = a - b;
+                if (diff < -EPS) return -1;
+                if (diff >  EPS) return +1;
+                return 0;
+            }
+        };
+
+    public static final List<Double []>readPoints(Reader reader) 
+    throws IOException
+    {
+        Map<Double, Double> map = new TreeMap<Double, Double>(EPS_CMP);
+
+        BufferedReader input = new BufferedReader(reader);
+
+        String line;
+        while ((line = input.readLine()) != null) {
+            if ((line = line.trim()).length() == 0 || line.startsWith("#")) {
+                continue;
+            }
+
+            String [] parts = line.split("\\s+");
+
+            if (parts.length < 2) {
+                continue;
+            }
+
+            try {
+                Double x = Double.valueOf(parts[0]);
+                Double y = Double.valueOf(parts[1]);
+
+                Double old = map.put(x, y);
+
+                if (old != null) {
+                    System.err.println("duplicate x: " + x);
+                }
+            }
+            catch (NumberFormatException nfe) {
+                nfe.printStackTrace();
+            }
+        }
+
+        List<Double []> list = new ArrayList<Double []>(map.size());
+
+        for (Map.Entry<Double, Double> entry: map.entrySet()) {
+            list.add(new Double [] { entry.getKey(), entry.getValue() });
+        }
+
+        return list;
+    }
+
+    public static void main(String [] args) {
+
+        Function function = FunctionFactory
+            .getInstance()
+            .getFunction(FUNCTION_NAME);
+
+        if (function == null) {
+            System.err.println("Cannot find function '" + FUNCTION_NAME + "'.");
+            System.exit(1);
+        }
+
+        List<Double []> points = null;
+
+        try {
+            points = readPoints(new InputStreamReader(System.in));
+        }
+        catch (IOException ioe) {
+            ioe.printStackTrace();
+            System.exit(1);
+        }
+
+        LevenbergMarquardtOptimizer lmo = new LevenbergMarquardtOptimizer();
+
+        CurveFitter cf = new CurveFitter(lmo);
+
+        for (Double [] point: points) {
+            cf.addObservedPoint(point[0], point[1]);
+        }
+
+        double [] parameters = null;
+
+        try {
+            parameters = cf.fit(function, function.getInitialGuess());
+        }
+        catch (MathException me) {
+            me.printStackTrace();
+            System.exit(1);
+        }
+
+        String [] parameterNames = function.getParameterNames();
+
+        for (int i = 0; i < parameterNames.length; ++i) {
+            System.err.println(parameterNames[i] + ": " + parameters[i]);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/Exp.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,27 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+public class Exp
+extends      Function
+{
+    public Exp() {
+        super(
+            "exp",
+            "W(Q) = m * a^Q + b",
+            new String [] { "m", "a", "b" });
+    }
+
+    @Override
+    public double value(double x, double [] parameters) {
+        return parameters[0]*Math.pow(parameters[1], x) + parameters[2];
+    }
+
+    @Override
+    public double [] gradient(double x, double [] parameters) {
+        return new double [] {
+            Math.pow(parameters[1], x),
+            Math.pow(parameters[1], x-1d)*x,
+            1d
+        };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/Function.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,76 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+import org.apache.commons.math.optimization.fitting.ParametricRealFunction;
+
+import de.intevation.flys.utils.DoubleUtil;
+
+public abstract class Function
+implements            ParametricRealFunction
+{
+    protected String    name;
+    protected String    description;
+    protected String [] parameterNames;
+    protected double [] initialGuess;
+
+    public Function() {
+    }
+
+    public Function(
+        String    name,
+        String    description,
+        String [] parameterNames
+    ) {
+        this(name,
+            description,
+            parameterNames,
+            DoubleUtil.fill(parameterNames.length, 1d));
+    }
+
+    public Function(
+        String    name,
+        String    description,
+        String [] parameterNames,
+        double [] initialGuess
+    ) {
+        this.name           = name;
+        this.description    = description;
+        this.parameterNames = parameterNames;
+        this.initialGuess   = initialGuess;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String [] getParameterNames() {
+        return parameterNames;
+    }
+
+    public double [] getInitialGuess() {
+        return initialGuess;
+    }
+
+    public de.intevation.flys.artifacts.math.Function instantiate(
+        final double [] parameters
+    ) {
+        return new de.intevation.flys.artifacts.math.Function() {
+
+            @Override
+            public double value(double x) {
+                try {
+                    return Function.this.value(x, parameters);
+                }
+                catch (FunctionEvaluationException fee) {
+                    return Double.NaN;
+                }
+            }
+        };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/FunctionFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,43 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class FunctionFactory
+{
+    private static FunctionFactory instance;
+
+    private Map<String, Function> functions;
+
+    private FunctionFactory() {
+        functions = new LinkedHashMap<String, Function>();
+
+        registerFunction(new Log());
+        registerFunction(new Linear());
+        registerFunction(new LogLinear());
+        registerFunction(new Exp());
+        registerFunction(new Quad());
+        registerFunction(new Pow());
+    }
+
+    public static synchronized FunctionFactory getInstance() {
+        if (instance == null) {
+            instance = new FunctionFactory();
+        }
+        return instance;
+    }
+
+    public Function getFunction(String name) {
+        return functions.get(name);
+    }
+
+    public void registerFunction(Function function) {
+        functions.put(function.getName(), function);
+    }
+
+    public Collection<Function> getFunctions() {
+        return functions.values();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/Linear.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,20 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+public class Linear
+extends      Function
+{
+    public Linear() {
+        super("linear", "W(Q) = m*Q + b", new String [] { "m", "b" });
+    }
+
+    @Override
+    public double value(double x, double [] parameters) {
+        return x*parameters[0] + parameters[1];
+    }
+
+    @Override
+    public double [] gradient(double x, double [] parameters) {
+        return new double [] { x, 1d };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/Log.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,23 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+public class Log
+extends      Function
+{
+    public Log() {
+        super("log",  "W(Q) = m*ln(Q + b)", new String [] { "m", "b" });
+    }
+
+    @Override
+    public double value(double x, double [] parameters) {
+        return parameters[0]*Math.log(x + parameters[1]);
+    }
+
+    @Override
+    public double [] gradient(double x, double [] parameters) {
+        return new double [] {
+            Math.log(x + parameters[1]),
+            parameters[0]/(x + parameters[0])
+        };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/LogLinear.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+public class LogLinear
+extends      Function
+{
+    public LogLinear() {
+        super(
+            "log-linear",
+            "W(Q) = a*ln(m*Q + b)",
+            new String [] { "a", "m", "b" });
+    }
+
+    @Override
+    public double value(double x, double [] parameters) {
+        return parameters[0]*Math.log(parameters[1]*x + parameters[2]);
+    }
+
+    @Override
+    public double [] gradient(double x, double [] parameters) {
+        double l = parameters[1]*x + parameters[2];
+        return new double [] {
+            Math.log(l),
+            parameters[0]*x/l,
+            parameters[0]/l
+        };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/Pow.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,30 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+public class Pow
+extends      Function
+{
+    public Pow() {
+        super(
+            "pow",
+            "W(Q) = a*Q^c + d",
+            new String [] { "a", "c", "d" });
+    }
+
+    @Override
+    public double value(double x, double [] parameters) {
+        return parameters[0]*Math.pow(x, parameters[1]) + parameters[2];
+    }
+
+    @Override
+    public double [] gradient(double x, double [] parameters) {
+        double a   = parameters[0];
+        double c   = parameters[1];
+        double x_c = Math.pow(x, c);
+        return new double [] {
+            x_c,
+            a*x_c*Math.log(x),
+            1d
+        };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/fitting/Quad.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,24 @@
+package de.intevation.flys.artifacts.math.fitting;
+
+public class Quad
+extends      Function
+{
+    public Quad() {
+        super(
+            "quad",
+            "W(Q) = n*Q^2 + m*Q + b",
+            new String [] { "n", "m", "b" });
+    }
+
+    @Override
+    public double value(double x, double [] parameters) {
+        // n*Q^2 + m*Q + b <=> Q*(n*Q + m) + b
+        return x*(parameters[0]*x + parameters[1]) + parameters[2];
+    }
+
+    @Override
+    public double [] gradient(double x, double [] parameters) {
+        return new double [] { x*x, x, 1d };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/AnnotationFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,95 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.AnnotationArtifact;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StickyAxisAnnotation;
+
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.FastAnnotations;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * Facet to access Annotations (landmarks, POIs) of a river.
+ */
+public class AnnotationFacet
+extends      DefaultFacet
+{
+    /** Logger for this class. */
+    private static final Logger logger = Logger.getLogger(AnnotationFacet.class);
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public AnnotationFacet() {
+    }
+
+
+    /**
+     * Trivial Constructor for a AnnotationFacet.
+     *
+     * @param index       Database-Index to use.
+     * @param name        Name (~type) of Facet.
+     * @param description Description of Facet.
+     */
+    public AnnotationFacet(int index, String name, String description) {
+        super(index, name, description);
+    }
+
+
+    /**
+     * Get List of Annotations for river from Artifact.
+     *
+     * @param artifact (Annotation-)Artifact to query for list of Annotations.
+     * @param context  Ignored.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        AnnotationArtifact annotationArtifact = (AnnotationArtifact) artifact;
+
+        String riverName = FLYSUtils.getRivername((FLYSArtifact)artifact);
+
+        FastAnnotations fas = LocationProvider.getAnnotations(riverName);
+
+        List<StickyAxisAnnotation> xy =
+            new ArrayList<StickyAxisAnnotation>(fas.size());
+
+        for (Iterator<FastAnnotations.Annotation> iter =
+                fas.filter(FastAnnotations.IS_POINT); iter.hasNext();) {
+            FastAnnotations.Annotation fa = iter.next();
+
+            xy.add(new StickyAxisAnnotation(
+                fa.getPosition(),
+                (float)fa.getA(),
+                StickyAxisAnnotation.SimpleAxis.X_AXIS));
+        }
+
+        return new FLYSAnnotation(description, xy);
+    }
+
+
+    @Override
+    public Facet deepCopy() {
+        AnnotationFacet copy = new AnnotationFacet();
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/AnnotationsFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,115 @@
+package de.intevation.flys.artifacts.model;
+
+import java.math.BigDecimal;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.Collections;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class AnnotationsFactory {
+
+    /**
+     * Get Annotations which do not have a "b" ("to")-value set.
+     *
+     * @param river name of the river of interest.
+     *
+     * @return List of Annotations for river which have only "a" ("from")
+     *          value set.
+     */
+    public static List<Annotation> getPointAnnotations(String river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Annotation as an " +
+            "where an.range.b = null and an.range.river.name=:name " +
+            "order by range.a");
+        query.setParameter("name", river);
+        return query.list();
+    }
+
+
+    public static List<Annotation> getAnnotations(River river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Annotation as an where an.range.river = :river" +
+            " order by an.range.a");
+        query.setParameter("river", river);
+        return query.list();
+    }
+
+
+    public static Annotation getAnnotation(String river, double km) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Annotation as a " +
+            "where a.range.river.name = :river AND a.range.a = :km");
+
+        query.setParameter("river", river);
+        query.setParameter("km", BigDecimal.valueOf(km));
+
+        List<Annotation> result = query.list();
+
+        return result != null && result.size() > 0 ? result.get(0) : null;
+    }
+
+
+    /**
+     * Get minimal "a" ("from") and maximal "b" ("to") value of annotations'
+     * ranges of a river.
+     *
+     * @param river name of the river of interest.
+     *
+     * @return Array containing minimal "a" and max "b" value of any
+     *         annotation stored for the given river.
+     */
+    public static double[] getAnnotationsBreadth(String river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query minAQuery = session.createQuery(
+            "select min(a), max(b) from Range where river.name=:name");
+        minAQuery.setParameter("name", river);
+
+        double[] minAmaxB = {0.0f, 0.0f};
+        Object[] row = (Object[]) minAQuery.list().iterator().next();
+        minAmaxB[0] = ((BigDecimal) row[0]).doubleValue();
+        minAmaxB[1] = ((BigDecimal) row[1]).doubleValue();
+        return minAmaxB;
+    }
+
+
+    public static Iterator<Annotation> getAnnotationsIterator(
+        String riverName
+    ) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query riverQuery = session.createQuery(
+            "from River where name = :name");
+        riverQuery.setParameter("name", riverName);
+        List<River> rivers = riverQuery.list();
+        if (rivers.isEmpty()) {
+            List<Annotation> list = Collections.emptyList();
+            return list.iterator();
+        }
+
+        Query query = session.createQuery(
+            "from Annotation as an" +
+            " where an.range.river = :river order by an.range.a");
+        query.setParameter("river", rivers.get(0));
+
+        return (Iterator<Annotation>)query.iterate();
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/AreaFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,146 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.flys.artifacts.AreaArtifact;
+
+
+/**
+ * Trival Facet for areas.
+ * Note that this Facet comes in two "types" (names):
+ *  <ul>
+ *    <li>CROSS_SECTION_AREA (cross_section.area) and</li>
+ *    <li>LONGITUDINAL_SECTION_AREA (longitudinal.area</li>
+ *  </ul>
+ * This is to support different diagram types without being painted in both
+ * at the same time. The name has to be given when constructing.
+ */
+public class AreaFacet
+extends      DefaultFacet
+implements   FacetTypes {
+
+    private static Logger logger = Logger.getLogger(AreaFacet.class);
+
+    /**
+     * Constructor, set (maybe localized) description and name.
+     * @param idx Index given when querying artifact for data.
+     * @param name important to discern areas in different diagram types.
+     */
+    public AreaFacet(int idx, String name, String description) {
+        super(idx, name, description);
+    }
+
+
+    /**
+     * Gets Cross Section (profile).
+     * @param art artifact to get data from.
+     * @param context ignored
+     */
+    public Object getData(Artifact art, CallContext context) {
+        logger.debug("Get data for area.");
+
+        // Get information from artifact about which
+        // info to grab from blackboard.
+        //
+        // All compatible facets should provide their data
+        // under the key (Artifact-UUID + Facet-Index).
+        AreaArtifact artifact = (AreaArtifact) art;
+        Object lowerData      = null;
+        Object upperData      = null;
+        String stemFacetName  = null;
+
+        List<DataProvider> providers = context.
+            getDataProvider(artifact.getLowerDPKey());
+        if (providers.size() < 1) {
+            logger.warn("No 'lower' provider given for area [" + 
+                artifact.getLowerDPKey() + "]");
+        }
+        else {
+            lowerData = providers.get(0).provideData(
+                artifact.getLowerDPKey(), null, context);
+            logger.debug("'Lower' data provider key for area [" + 
+                artifact.getLowerDPKey() + "]");
+            stemFacetName = artifact.getLowerDPKey().split(":")[1];
+        }
+
+        providers = context.getDataProvider(artifact.getUpperDPKey());
+        if (providers.size() < 1) {
+            logger.warn("No 'upper' provider given for area [" +
+                artifact.getUpperDPKey() + "]");
+        }
+        else {
+            upperData = providers.get(0).provideData(
+                artifact.getUpperDPKey(), null, context);
+            logger.debug("'Upper' data provider key for area [" +
+                artifact.getUpperDPKey() + "]");
+            if (stemFacetName == null) {
+                stemFacetName = artifact.getUpperDPKey().split(":")[1];
+            }
+        }
+
+        if (upperData == null && lowerData == null) {
+            logger.warn("Not given 'upper' and 'lower' for area");
+        }
+
+        return new Data(stemFacetName, lowerData, upperData,
+            Boolean.valueOf(artifact.getPaintBetween()));
+    }
+
+
+    /** Do a deep copy. */
+    @Override 
+    public Facet deepCopy() {
+        AreaFacet copy = new AreaFacet(this.index, this.name, this.description);
+        copy.set(this);
+        return copy;
+    }
+
+    /** Result data bundle. */
+    public class Data {
+        protected String  rootFacetName;
+        protected Object  upperData;
+        protected Object  lowerData;
+        protected boolean doPaintBetween;
+
+        /** Create a new result data bundle. */
+        public Data(String rootName, Object low, Object up, boolean between) {
+            this.rootFacetName  = rootName;
+            this.lowerData      = low;
+            this.upperData      = up;
+            this.doPaintBetween = between;
+        }
+
+        /** Get name of a facet that is involved in area generation
+         * to induce type (e.g. longitudinal_section.w -> "W over km"). */
+        public String getRootFacetName() {
+            return this.rootFacetName;
+        }
+
+        /** Get data for 'upper' curve of area. */
+        public Object getUpperData() {
+            return this.upperData;
+        }
+
+        /** Get data for 'lower' curve of area. */
+        public Object getLowerData() {
+            return this.lowerData;
+        }
+
+        /** Whether to fill whole area between (in contrast to 'under'
+         *  or 'over'). */
+        public boolean doPaintBetween() {
+            return this.doPaintBetween;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/BlackboardDataFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,78 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultFacet;
+
+
+/**
+ * Facet that writes artifact-uui and facet index on the blackboard,
+ * delivers data if asked so.
+ */
+public class BlackboardDataFacet extends DefaultFacet {
+
+    public BlackboardDataFacet() {}
+
+    /** Do not instantiate a BlackboardDataFacet, subclass it instead. */
+    public BlackboardDataFacet(int idx, String name, String description) {
+        super(idx, name, description);
+    }
+
+
+    /** Do not instantiate a BlackboardDataFacet, subclass it instead. */
+    public BlackboardDataFacet(String name, String description) {
+        super(0, name, description);
+    }
+
+
+    /** Define key to which to respond when asked for 'blackboard'
+     * (DataProvider)- data. */
+    public String areaDataKey(Artifact art) {
+        return art.identifier() + ":" + getName() + ":" + getIndex();
+    }
+
+
+    /** Hey, We can ArtifactUUID+:+FacetName+:+FacetIndex (i.e. getData)! */
+    @Override
+    public List getStaticDataProviderKeys(Artifact art) {
+        List list = new ArrayList();
+        list.add(areaDataKey(art));
+        return list;
+    }
+
+
+    /**
+     * Can provide whatever getData returns.
+     * @param key      will respond on uuid+index
+     * @param param    ignored
+     * @param context  ignored
+     * @return whatever getData delivers.
+     */
+    @Override
+    public Object provideBlackboardData(Artifact artifact,
+        Object key,
+        Object param,
+        CallContext context
+    ) {
+        if (key.equals(areaDataKey(artifact))) {
+            return getData(artifact, context);
+        }
+        else {
+            return null;
+        }
+    }
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        BlackboardDataFacet copy = new BlackboardDataFacet();
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,155 @@
+package de.intevation.flys.artifacts.model;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import java.io.Serializable;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.Formatter;
+
+public class Calculation
+implements   Serializable
+{
+    public static class Problem
+    implements          Serializable
+    {
+        protected Double    km;
+        protected String    msg;
+        protected Object [] args;
+
+        public Problem() {
+        }
+
+        public Problem(String msg) {
+            this.msg = msg;
+        }
+
+        public Problem(String msg, Object [] args) {
+            this.msg  = msg;
+            this.args = args;
+        }
+
+        public Problem(double km, String msg) {
+            this.km  = km;
+            this.msg = msg;
+        }
+
+        public Problem(double km, String msg, Object [] args) {
+            this.km   = km;
+            this.msg  = msg;
+            this.args = args;
+        }
+
+        public Element toXML(Document document, CallMeta meta) {
+            Element problem = document.createElement("problem");
+            if (km != null) {
+                problem.setAttribute(
+                    "km",
+                    Formatter.getCalculationKm(meta).format(km));
+            }
+            String text = args != null
+                ? Resources.getMsg(meta, msg, msg, args)
+                : Resources.getMsg(meta, msg, msg);
+            problem.setTextContent(text);
+            return problem;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof Problem)) {
+                return false;
+            }
+            Problem o = (Problem)other;
+            return !(!msg.equals(o.msg)
+                || (km == null && o.km != null)
+                || (km != null && o.km == null)
+                || (km != null && !km.equals(o.km))
+                || !Arrays.equals(args, o.args));
+        }
+    } // class Problem
+
+    protected List<Problem> problems;
+
+    public Calculation() {
+    }
+
+    public Calculation(String msg) {
+        addProblem(msg);
+    }
+
+    protected List<Problem> checkProblems() {
+        if (problems == null) {
+            problems = new ArrayList<Problem>();
+        }
+        return problems;
+    }
+
+    public void addProblems(Calculation other) {
+        List<Problem> otherProblems = other.problems;
+        if (otherProblems != null) {
+            List<Problem> problems = checkProblems();
+            for (Problem problem: otherProblems) {
+                if (!problems.contains(problem)) {
+                    problems.add(problem);
+                }
+            }
+        }
+    }
+
+    public void addProblem(Problem problem) {
+        List<Problem> problems = checkProblems();
+        if (!problems.contains(problem)) {
+            problems.add(problem);
+        }
+    }
+
+    public void addProblem(String msg) {
+        addProblem(new Problem(msg));
+    }
+
+    public void addProblem(String msg, Object ... args) {
+        addProblem(new Problem(msg, args));
+    }
+
+    public void addProblem(double km, String msg) {
+        addProblem(new Problem(km, msg));
+    }
+
+    public void addProblem(double km, String msg, Object ... args) {
+        addProblem(new Problem(km, msg, args));
+    }
+
+    public boolean hasProblems() {
+        return problems != null && !problems.isEmpty();
+    }
+
+    public int numProblems() {
+        return problems != null ? problems.size() : 0;
+    }
+
+    public List<Problem> getProblems() {
+        return problems;
+    }
+
+    public void toXML(Document document, CallMeta meta) {
+
+        Element root = document.createElement("problems");
+
+        if (hasProblems()) {
+            for (Problem problem: problems) {
+                root.appendChild(problem.toXML(document, meta));
+            }
+        }
+
+        document.appendChild(root);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation1.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+public class Calculation1
+extends      Calculation
+{
+    private static Logger logger = Logger.getLogger(Calculation1.class);
+
+    protected double [] kms;
+    protected double [] qs;
+    protected double [] ws;
+    protected double    refKm;
+
+    public Calculation1() {
+    }
+
+    public Calculation1(
+        double [] kms,
+        double [] qs,
+        double [] ws,
+        double    refKm
+    ) {
+        this.kms   = kms;
+        this.qs    = qs;
+        this.ws    = ws;
+        this.refKm = refKm;
+    }
+
+    public CalculationResult calculate(WstValueTable wst) {
+
+        ArrayList<WQKms> results = new ArrayList<WQKms>();
+
+        String    prefix;
+        double [] origData;
+
+        if (ws != null) { prefix = "W="; origData = ws; }
+        else            { prefix = "Q="; origData = qs; }
+
+        int oldNumProblems = numProblems();
+
+        for (int i = 0; i < qs.length; i++) {
+
+            double [] oqs = new double[kms.length];
+            double [] ows = new double[kms.length];
+
+            boolean success =
+                wst.interpolate(qs[i], refKm, kms, ows, oqs, this) != null;
+
+            int newNumProblems = numProblems();
+
+            if (success) {
+                WQKms result = new WQKms(kms, oqs, ows, prefix + origData[i]);
+                if (oldNumProblems != newNumProblems) {
+                    logger.debug(
+                        qs[i] + " caused " + (newNumProblems-oldNumProblems) +
+                        " new problem(s).");
+                    result.removeNaNs();
+                }
+                results.add(result);
+            }
+
+            oldNumProblems = newNumProblems;
+        }
+
+        return new CalculationResult(
+            results.toArray(new WQKms[results.size()]),
+            this);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation2.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,48 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.Arrays;
+
+import org.apache.log4j.Logger;
+
+public class Calculation2
+extends      Calculation
+{
+    private static Logger logger = Logger.getLogger(Calculation2.class);
+
+    protected double km;
+
+    public Calculation2() {
+    }
+
+    public Calculation2(double km) {
+        this.km = km;
+    }
+
+    public CalculationResult calculate(WstValueTable wst) {
+
+        logger.debug("Calculation2.calculate");
+
+        double [][] wqs = wst.interpolateWQ(km, this);
+
+        if (wqs == null || wqs[0].length == 0) {
+            addProblem("cannot.compute.discharge.curve");
+            return new CalculationResult(new WQKms[0], this);
+        }
+
+        double [] ws = wqs[0];
+        double [] qs = wqs[1];
+        double [] kms = new double[ws.length];
+
+        Arrays.fill(kms, km);
+
+        WQKms wqkms = new WQKms(kms, qs, ws, String.valueOf(km));
+
+        if (hasProblems()) {
+            logger.debug("found + "+numProblems()+" problems.");
+            wqkms.removeNaNs();
+        }
+
+        return new CalculationResult(new WQKms[] { wqkms }, this);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation3.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,49 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+public class Calculation3
+extends      Calculation
+{
+    private static Logger logger = Logger.getLogger(Calculation3.class);
+
+    protected double    km;
+    protected int    [] days;
+    protected double [] qs;
+
+    public Calculation3() {
+    }
+
+    public Calculation3(double km, int [] days, double [] qs) {
+        this.km   = km;
+        this.days = days;
+        this.qs   = qs;
+    }
+
+    public CalculationResult calculate(WstValueTable wst) {
+
+        double [] ws = wst.interpolateW(km, qs, new double[qs.length], this);
+
+        if (days == null || days.length == 0) {
+            addProblem(km, "cannot.find.ds");
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Calculate duration curve data:");
+            logger.debug("    km       : " + km);
+            logger.debug("    num Days : " + (days != null ? days.length : 0));
+            logger.debug("    num Qs   : " + (qs != null ? qs.length : 0));
+            logger.debug("    result Ws: " + (ws != null ? ws.length : 0));
+        }
+
+        WQDay wqday = new WQDay(days, ws, qs);
+
+        if (hasProblems()) {
+            logger.debug("calculation caused "+numProblems()+" problem(s).");
+            wqday.removeNaNs();
+        }
+
+        return new CalculationResult(wqday, this);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation4.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,322 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Collections;
+
+import de.intevation.flys.utils.DoubleUtil;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.DischargeTable;
+
+import de.intevation.flys.artifacts.model.WstValueTable.QPosition;
+
+import de.intevation.flys.artifacts.math.Function;
+import de.intevation.flys.artifacts.math.Linear;
+import de.intevation.flys.artifacts.math.Identity;
+import de.intevation.flys.artifacts.math.BackJumpCorrector;
+
+import org.apache.log4j.Logger;
+
+public class Calculation4
+extends      Calculation
+{
+    private static Logger logger = Logger.getLogger(Calculation4.class);
+
+    public static final double MINIMAL_STEP_WIDTH = 1e-5;
+
+    public static final Comparator<Segment> REF_CMP =
+        new Comparator<Segment>() {
+            public int compare(Segment a, Segment b) {
+                double d = a.referencePoint - b.referencePoint;
+                if (d < 0d) return -1;
+                return d > 0d ? +1 : 0;
+            }
+        };
+
+    protected List<Segment> segments;
+
+    protected boolean       isQ;
+
+    public Calculation4() {
+    }
+
+    public Calculation4(List<Segment> segments, River river, boolean isQ) {
+
+        this.segments = segments;
+        this.isQ      = isQ;
+
+        int numResults = -1;
+
+        // assign reference points
+        for (Segment segment: segments) {
+            Gauge gauge = river.maxOverlap(segment.getFrom(), segment.getTo());
+
+            if (gauge == null) {
+                logger.warn("no gauge found. Defaults to mid point.");
+                segment.setReferencePoint(
+                    0.5*(segment.getFrom()+segment.getTo()));
+            }
+            else {
+                double ref = gauge.getStation().doubleValue();
+                logger.debug(
+                    "reference gauge: " + gauge.getName() +
+                    " (km " + ref + ")");
+                segment.setReferencePoint(ref);
+            }
+
+            double [] values = segment.values;
+
+            if (numResults == -1) {
+                numResults = values.length;
+            }
+            else if (numResults != values.length) {
+                throw new IllegalArgumentException("wrong length of values");
+            }
+
+            // convert to Q if needed
+            if (!isQ && gauge != null) {
+
+                DischargeTable dt = gauge.fetchMasterDischargeTable();
+
+                double [][] table =
+                    DischargeTables.loadDischargeTableValues(dt, 1);
+
+                // need the original values for naming
+                segment.backup();
+
+                for (int i = 0; i < values.length; ++i) {
+                    double w = values[i] / 100.0;
+                    double [] qs = DischargeTables.getQsForW(table, w);
+                    if (qs.length == 0) {
+                        logger.warn("No Qs found for W = " + values[i]);
+                        addProblem("cannot.find.w.for.q", values[i]);
+                        values[i] = Double.NaN;
+                    }
+                    else {
+                        values[i] = qs[0];
+                        if (qs.length > 1) {
+                            logger.warn(
+                                "More than one Q found for W = " + values[i]);
+                        }
+                    }
+                }
+            }
+        } // for all segments
+
+        Collections.sort(segments, REF_CMP);
+    }
+
+    public CalculationResult calculate(
+        WstValueTable table,
+        double from, double to, double step
+    ) {
+        boolean debug = logger.isDebugEnabled();
+
+        if (debug) {
+            logger.debug(
+                "calculate from " + from + " to " + to + " step " + step);
+            logger.debug("# segments: " + segments.size());
+            for (Segment segment: segments) {
+                logger.debug("  " + segment);
+            }
+        }
+
+        if (segments.isEmpty()) {
+            logger.debug("no segments found");
+            addProblem("no.segments.found");
+            return new CalculationResult(new WQKms[0], this);
+        }
+
+        int numResults = segments.get(0).values.length;
+
+        if (numResults < 1) {
+            logger.debug("no values given");
+            addProblem("no.values.given");
+            return new CalculationResult(new WQKms[0], this);
+        }
+
+
+        WQKms [] results = new WQKms[numResults];
+        for (int i = 0; i < results.length; ++i) {
+            results[i] = new WQKms();
+        }
+
+        if (Math.abs(step) < MINIMAL_STEP_WIDTH) {
+            step = MINIMAL_STEP_WIDTH;
+        }
+
+        if (from > to) {
+            step = -step;
+        }
+
+        QPosition [] qPositions = new QPosition[numResults];
+
+        Function [] functions = new Function[numResults];
+
+        double [] out = new double[2];
+
+        Segment sentinel = new Segment(Double.MAX_VALUE);
+        Segment s1 = sentinel, s2 = sentinel;
+
+        for (double pos = from;
+             from < to ? pos <= to : pos >= to;
+             pos = DoubleUtil.round(pos + step)
+        ) {
+            if (pos < s1.referencePoint || pos > s2.referencePoint) {
+                if (debug) {
+                    logger.debug("need to find new interval for " + pos);
+                }
+                // find new interval
+                if (pos <= segments.get(0).referencePoint) {
+                    // before first segment -> "gleichwertig"
+                    if (debug) {
+                        logger.debug("before first segment -> gleichwertig");
+                    }
+                    Segment   first  = segments.get(0);
+                    double [] values = first.values;
+                    double    refPos = first.referencePoint;
+                    for (int i = 0; i < qPositions.length; ++i) {
+                        qPositions[i] = table.getQPosition(
+                            refPos, values[i]);
+                    }
+                    sentinel.setReferencePoint(-Double.MAX_VALUE);
+                    s1 = sentinel;
+                    s2 = segments.get(0);
+                    Arrays.fill(functions, Identity.IDENTITY);
+                }
+                else if (pos >= segments.get(segments.size()-1).referencePoint) {
+                    // after last segment -> "gleichwertig"
+                    if (debug) {
+                        logger.debug("after last segment -> gleichwertig");
+                    }
+                    Segment   last   = segments.get(segments.size()-1);
+                    double [] values = last.values;
+                    double    refPos = last.referencePoint;
+                    for (int i = 0; i < qPositions.length; ++i) {
+                        qPositions[i] = table.getQPosition(
+                            refPos, values[i]);
+                    }
+                    sentinel.setReferencePoint(Double.MAX_VALUE);
+                    s1 = last;
+                    s2 = sentinel;
+                    Arrays.fill(functions, Identity.IDENTITY);
+                }
+                else { // "ungleichwertig"
+                    // find matching interval
+                    if (debug) {
+                        logger.debug("in segments -> ungleichwertig");
+                    }
+                    s1 = s2 = null;
+                    for (int i = 1, N = segments.size(); i < N; ++i) {
+                        Segment si1 = segments.get(i-1);
+                        Segment si  = segments.get(i);
+                        if (debug) {
+                            logger.debug("check " + pos + " in " +
+                                si1.referencePoint + " - " + si.referencePoint);
+                        }
+                        if (pos >= si1.referencePoint
+                        &&  pos <= si. referencePoint) {
+                            s1 = si1;
+                            s2 = si;
+                            break;
+                        }
+                    }
+
+                    if (s1 == null) {
+                        throw new IllegalStateException("no interval found");
+                    }
+
+                    Segment anchor, free;
+
+                    if (from > to) { anchor = s1; free = s2; }
+                    else           { anchor = s2; free = s1; }
+
+                    // build transforms based on "gleichwertiger" phase
+                    for (int i = 0; i < qPositions.length; ++i) {
+                        QPosition qi = table.getQPosition(
+                            anchor.referencePoint,
+                            anchor.values[i]);
+
+                        if ((qPositions[i] = qi) == null) {
+                            addProblem(pos, "cannot.find.q", anchor.values[i]);
+                            functions[i] = Identity.IDENTITY;
+                        }
+                        else {
+                            double qA = table.getQ(qi, anchor.referencePoint);
+                            double qF = table.getQ(qi, free  .referencePoint);
+
+                            functions[i] = Double.isNaN(qA) || Double.isNaN(qF)
+                                ? Identity.IDENTITY
+                                : new Linear(
+                                    qA, qF,
+                                    anchor.values[i], free.values[i]);
+
+                            if (debug) {
+                                logger.debug(
+                                    anchor.referencePoint + ": " +
+                                    qA + " -> " + functions[i].value(qA) +
+                                    " / " + free.referencePoint + ": " +
+                                    qF + " -> " + functions[i].value(qF));
+                            }
+                        }
+                    } // build transforms
+                } // "ungleichwertiges" interval
+            } // find matching interval
+
+            for (int i = 0; i < qPositions.length; ++i) {
+                QPosition qPosition = qPositions[i];
+
+                if (qPosition == null) {
+                    continue;
+                }
+
+                if (table.interpolate(pos, out, qPosition, functions[i])) {
+                    results[i].add(out[0], out[1], pos);
+                }
+                else {
+                    addProblem(pos, "cannot.interpolate.w.q");
+                }
+            }
+        }
+
+        // Backjump correction
+        for (int i = 0; i < results.length; ++i) {
+            BackJumpCorrector bjc = new BackJumpCorrector();
+
+            double [] ws  = results[i].getWs();
+            double [] kms = results[i].getKms();
+
+            if (bjc.doCorrection(kms, ws, this)) {
+                results[i] = new WQCKms(results[i], bjc.getCorrected());
+            }
+        }
+
+        // name the curves
+        for (int i = 0; i < results.length; ++i) {
+            results[i].setName(createName(i));
+        }
+
+        return new CalculationResult(results, this);
+    }
+
+    protected String createName(int index) {
+        // TODO: I18N
+        StringBuilder sb = new StringBuilder(isQ ? "Q" : "W");
+        sb.append(" benutzerdefiniert (");
+        for (int i = 0, N = segments.size(); i < N; ++i) {
+            if (i > 0) {
+                sb.append("; ");
+            }
+            Segment segment = segments.get(i);
+            sb.append((segment.backup != null
+                ? segment.backup
+                : segment.values)[index]);
+        }
+        sb.append(')');
+        return sb.toString();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation5.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,77 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.utils.Formatter;
+
+import java.text.NumberFormat;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+public class Calculation5
+extends      Calculation
+{
+    private static Logger log = Logger.getLogger(Calculation5.class);
+
+    protected double    startKm;
+    protected double [] endKms;
+
+    public Calculation5() {
+    }
+
+    public Calculation5(double startKm, double [] endKms) {
+        this.startKm = startKm;
+        this.endKms  = endKms;
+    }
+
+    public CalculationResult calculate(
+        WstValueTable       wst, 
+        Map<Double, Double> kms2gaugeDatums,
+        CallContext         context
+    ) {
+        ArrayList<WWQQ> results = new ArrayList<WWQQ>();
+
+        int numProblems = numProblems();
+
+        CallMeta meta = context.getMeta();
+
+        for (double endKm: endKms) {
+
+            double [][] wws = wst.relateWs(startKm, endKm, this);
+            int newNumProblems = numProblems();
+
+            if (wws.length == 4) {
+                WWQQ wwqq = new WWQQ(
+                    generateName(meta, startKm, endKm),
+                    startKm, kms2gaugeDatums.get(startKm), wws[0], wws[1],
+                    endKm,   kms2gaugeDatums.get(endKm),   wws[2], wws[3]);
+
+                if (newNumProblems > numProblems) {
+                    wwqq.removeNaNs();
+                }
+
+                results.add(wwqq);
+            }
+            numProblems = newNumProblems;
+        }
+
+        return new CalculationResult(
+            results.toArray(new WWQQ[results.size()]),
+            this);
+    }
+
+    protected static String generateName(
+        CallMeta meta,
+        double   startKm, 
+        double   endKm
+    ) {
+        NumberFormat nf = Formatter.getCalculationKm(meta);
+        return "W(km " + nf.format(startKm) + 
+               ") ~ W(km " + nf.format(endKm) + ")";
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation6.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,315 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.TimeInterval;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class Calculation6 extends Calculation {
+
+    private static final Logger logger = Logger.getLogger(Calculation6.class);
+
+    private int      mode;
+    private long[]   timerange;
+    private double[] values;
+
+
+    public static final int MODE_W = 0;
+    public static final int MODE_Q = 1;
+
+    public static final double SCALE = 100d;
+
+
+    public Calculation6(int mode, long[] timerange, double[] values) {
+        this.mode      = mode;
+        this.timerange = timerange;
+        this.values    = values;
+    }
+
+
+    public CalculationResult calculate(Gauge gauge) {
+        if (!checkParameters() || gauge == null) {
+            logger.warn("Parameters not valid for calculation.");
+
+            return null;
+        }
+
+        if (logger.isDebugEnabled()) {
+            debug();
+        }
+
+        DischargeTable  refTable = fetchReferenceTable(gauge);
+        List<DischargeTable> dts = fetchDischargeTables(gauge);
+
+        int numTables = dts.size();
+
+        logger.debug("Take " + numTables + " into account.");
+
+        if (numTables == 0) {
+            addProblem("cannot.find.hist.q.tables");
+        }
+
+        WQTimerange[] wqt = prepareCalculationData(refTable, dts);
+
+        logger.debug("Number of calculation results: " + wqt.length);
+
+        return new CalculationResult(wqt, this);
+    }
+
+
+    protected boolean checkParameters() {
+        if (!(mode == MODE_W || mode == MODE_Q)) {
+            logger.warn("Invalid mode '" + mode + "' for calculation.");
+            return false;
+        }
+
+        if (timerange == null || timerange.length < 2) {
+            logger.warn("Invalid timerange for calculation.");
+            return false;
+        }
+
+        if (values == null || values.length == 0) {
+            logger.warn("No values for W or Q specified.");
+            return false;
+        }
+
+        return true;
+    }
+
+
+    protected DischargeTable fetchReferenceTable(Gauge gauge) {
+        return gauge.fetchMasterDischargeTable();
+    }
+
+
+    protected List<DischargeTable> fetchDischargeTables(Gauge gauge) {
+        List<DischargeTable> relevant = new ArrayList<DischargeTable>();
+        List<DischargeTable> all      = gauge.getDischargeTables();
+
+        for (DischargeTable dt: all) {
+            if (isDischargeTableRelevant(dt)) {
+                relevant.add(dt);
+            }
+        }
+
+        return relevant;
+    }
+
+
+    protected boolean isDischargeTableRelevant(DischargeTable dt) {
+        TimeInterval ti = dt.getTimeInterval();
+
+        if (dt.getKind() == Gauge.MASTER_DISCHARGE_TABLE || ti == null) {
+            return false;
+        }
+
+        Date start     = ti.getStartTime();
+        long startTime = start.getTime();
+
+        if (startTime >= timerange[0] && startTime <= timerange[1]) {
+            return true;
+        }
+
+        Date stop     = ti.getStopTime();
+        long stopTime = stop != null ? stop.getTime() : -1l;
+
+        if (stopTime >= timerange[0] && stopTime <= timerange[1]) {
+            return true;
+        }
+
+        logger.debug("DischargeTable not in range: " + start + " -> " + stop);
+
+        return false;
+    }
+
+
+    protected WQTimerange[] prepareCalculationData(
+        DischargeTable       refTable,
+        List<DischargeTable> dts
+    ) {
+        if (refTable == null) {
+            addProblem("cannot.find.hist.q.reftable");
+            return prepareSimpleData(dts);
+        }
+        else {
+            return prepareData(refTable, dts);
+        }
+    }
+
+
+    protected WQTimerange[] prepareSimpleData(List<DischargeTable> dts) {
+        List<WQTimerange> wqts =
+            new ArrayList<WQTimerange>(values.length);
+
+        for (double value: values) {
+            logger.debug("Prepare data for value: " + value);
+
+            String name      = mode == MODE_W ? "W=" + value : "Q=" + value;
+            WQTimerange wqt  = null;
+
+            for (DischargeTable dt: dts) {
+                Date[]   ti = prepareTimeInterval(dt);
+                Timerange t = new Timerange(ti[0], ti[1]);
+                double    w;
+                double    q;
+
+                if (mode == MODE_W) {
+                    w = value;
+                    q = findValueForW(dt, w);
+
+                    if (Double.isNaN(q)) {
+                        logger.warn("Cannot find Q for W: " + w);
+                        addProblem("cannot.find.hist.q.for.w", w, ti[0], ti[1]);
+                        continue;
+                    }
+                }
+                else {
+                    q = value;
+                    w = findValueForQ(dt, q);
+                }
+
+                logger.debug("Q=" + q + " | W=" + w);
+
+                if (wqt == null) {
+                    wqt = new WQTimerange(name);
+                }
+
+                wqt.add(w, q, t);
+            }
+
+            if (wqt != null) {
+                wqts.add(wqt);
+            }
+        }
+
+        return (WQTimerange[]) wqts.toArray(new WQTimerange[wqts.size()]);
+    }
+
+
+    protected HistoricalWQTimerange[] prepareData(
+        DischargeTable       refTable,
+        List<DischargeTable> dts
+    ) {
+        List<HistoricalWQTimerange> wqts =
+            new ArrayList<HistoricalWQTimerange>(values.length);
+
+        for (double value: values) {
+            logger.debug("Prepare data plus diff for value: " + value);
+
+            String name = mode == MODE_W ? "W=" + value : "Q=" + value;
+            HistoricalWQTimerange wqt  = null;
+
+            double ref;
+            double diff;
+
+            if (refTable != null && mode == MODE_W) {
+                ref = findValueForW(refTable, value);
+            }
+            else if (refTable != null) {
+                ref = findValueForQ(refTable, value);
+            }
+            else {
+                ref = Double.NaN;
+            }
+
+            for (DischargeTable dt: dts) {
+                Date[] ti = prepareTimeInterval(dt);
+
+                Timerange t = new Timerange(ti[0] ,ti[1]);
+                double    w;
+                double    q;
+
+                if (mode == MODE_W) {
+                    w    = value;
+                    q    = findValueForW(dt, w);
+
+                    if (Double.isNaN(q)) {
+                        logger.warn("Cannot find Q for W: " + w);
+                        addProblem("cannot.find.hist.q.for.w", w, ti[0], ti[1]);
+                        continue;
+                    }
+
+                    diff = ref-q;
+                }
+                else {
+                    q    = value;
+                    w    = findValueForQ(dt, q);
+                    diff = ref-w;
+                }
+
+                logger.debug("Q=" + q + " | W=" + w + " | Ref = " + ref);
+
+                if (wqt == null) {
+                    wqt = new HistoricalWQTimerange(name);
+                }
+
+                wqt.add(w, q, diff, t);
+            }
+
+            if (wqt != null) {
+                wqts.add(wqt);
+            }
+        }
+
+        return (HistoricalWQTimerange[])
+            wqts.toArray(new HistoricalWQTimerange[wqts.size()]);
+    }
+
+
+    protected Date[] prepareTimeInterval(DischargeTable dt) {
+        TimeInterval ti = dt.getTimeInterval();
+
+        Date start = ti.getStartTime();
+        Date end   = ti.getStopTime();
+
+        if (end == null) {
+            logger.warn("TimeInterval has no stop time set!");
+
+            end = new Date();
+        }
+
+        return new Date[] { start, end };
+    }
+
+
+    protected double findValueForW(DischargeTable dt, double w) {
+        double[][] vs = DischargeTables.loadDischargeTableValues(dt, SCALE);
+        double [] qs = DischargeTables.getQsForW(vs, w);
+        return qs.length == 0 ? Double.NaN : qs[0];
+    }
+
+
+    protected double findValueForQ(DischargeTable dt, double q) {
+        double[][] vs = DischargeTables.loadDischargeTableValues(dt, SCALE);
+        logger.warn("TODO: IMPLEMENT ME!");
+
+        return 10;
+    }
+
+
+    /**
+     * Writes the parameters used for this calculation to logger.
+     */
+    public void debug() {
+        StringBuilder sb = new StringBuilder();
+        for (double value: values) {
+            sb.append(String.valueOf(value) + " ");
+        }
+
+        logger.debug("========== Calculation6 ==========");
+        logger.debug("   Mode:         " + mode);
+        logger.debug("   Timerange:    " + timerange[0] + " - " + timerange[1]);
+        logger.debug("   Input values: " + sb.toString());
+        logger.debug("==================================");
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/CalculationMessage.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,45 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.Message;
+
+
+public class CalculationMessage implements Message {
+
+    protected String message;
+    protected int    steps;
+    protected int    currentStep;
+
+
+    public CalculationMessage() {
+    }
+
+
+    public CalculationMessage(int steps, int currentStep, String message) {
+        this.steps       = steps;
+        this.currentStep = currentStep;
+        this.message     = message;
+    }
+
+
+    public int getSteps() {
+        return steps;
+    }
+
+
+    public int getCurrentStep() {
+        return currentStep;
+    }
+
+
+    public String getMessage() {
+        return message;
+    }
+
+
+    @Override
+    public String getText() {
+        return
+            String.valueOf(currentStep) + "/" + String.valueOf(steps) +
+            " - " + getMessage();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/CalculationResult.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,45 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+/**
+ * Wraps result(s) of a Calculation and eventual error reports.
+ */
+public class CalculationResult
+implements   Serializable
+{
+    protected Object      data;
+    protected Calculation report;
+
+    public CalculationResult() {
+    }
+
+    public CalculationResult(Calculation report) {
+        this(null, report);
+    }
+
+    /**
+     * @param report report (e.g. error messages).
+     */
+    public CalculationResult(Object data, Calculation report) {
+        this.data   = data;
+        this.report = report;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+    public Calculation getReport() {
+        return report;
+    }
+
+    public void setReport(Calculation report) {
+        this.report = report;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/CrossSectionFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,107 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.CrossSectionArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * Trival Facet for Cross Sections (profiles).
+ */
+public class CrossSectionFacet
+extends      BlackboardDataFacet
+implements   FacetTypes {
+
+    public static String BLACKBOARD_CS_MASTER_DATA
+        = "crosssection.masterprofile.data";
+
+    private static Logger logger = Logger.getLogger(CrossSectionFacet.class);
+
+    protected ComputeType type;
+
+
+    /** Trivial constructor, set (maybe localized) description. */
+    public CrossSectionFacet(int idx, String description) {
+        super(idx, CROSS_SECTION, description);
+        type = ComputeType.ADVANCE;
+    }
+
+
+    /** Tell world we know about crosssection masters data and its index. */
+    public List getStaticDataProviderKeys(Artifact art) {
+        CrossSectionArtifact artifact = (CrossSectionArtifact) art;
+        List keys = new ArrayList();
+        if (artifact.isMaster()) {
+            keys.add(BLACKBOARD_CS_MASTER_DATA);
+        }
+        keys.add(artifact.identifier() + getIndex());
+        keys.addAll(super.getStaticDataProviderKeys(art));
+        return keys;
+    }
+
+
+    /**
+     * Can provide the master cross section lines or its index.
+     * @param artifact crosssection-artifact
+     * @param key      will respond on BLACKBOARD_CS_MASTER_DATA
+     * @param param    ignored
+     * @param context  ignored
+     * @return data from artifact (cross section master track).
+     */
+    public Object provideBlackboardData(Artifact artifact,
+        Object key,
+        Object param,
+        CallContext context
+    ) {
+        CrossSectionArtifact crossSection = (CrossSectionArtifact) artifact;
+
+        if (key.equals(BLACKBOARD_CS_MASTER_DATA)) {
+            return crossSection.searchCrossSectionLine();
+        }
+        else if (key.equals(artifact.identifier() + getIndex())) {
+            return getData(artifact, context);
+        }
+        else {
+            Object obj = super.provideBlackboardData(artifact, key, param,
+                context);
+            if (obj == null) {
+                logger.warn("Cannot provide data for key: " + key);
+            }
+            return obj;
+        }
+    }
+
+
+    /**
+     * Gets Cross Section (profile).
+     * @param art artifact to get data from.
+     * @param context ignored
+     */
+    public Object getData(Artifact art, CallContext context) {
+        logger.debug("Get data for cross section");
+
+        CrossSectionArtifact artifact = (CrossSectionArtifact)art;
+
+        return artifact.getCrossSectionData();
+    }
+
+
+    /** Do a deep copy. */
+    @Override 
+    public Facet deepCopy() {
+        CrossSectionFacet copy = new CrossSectionFacet(this.index, this.description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/CrossSectionFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,112 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.CrossSection;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+
+/**
+ * Get Cross Sections.
+ */
+public class CrossSectionFactory {
+
+    protected final static String CACHE_NAME = "cross_sections";
+
+    // TODO use caching consistently, streamline acces.
+    /**
+     * Get CrossSections for an instantiated River.
+     *
+     * @param river river object.
+     *
+     * @return List of Cross Sections of river.
+     */
+    public static List<CrossSection> getCrossSections(River river) {
+        return getCrossSections(river.getName());
+    }
+
+
+    /**
+     * Get Cross Sections for a river by name.
+     *
+     * @param river name of the river of interest.
+     *
+     * @return List of Cross Sections of river.
+     */
+    public static List<CrossSection> getCrossSections(String riverName) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from CrossSection where river.name = :rivername");
+        query.setParameter("rivername", riverName);
+        return query.list();
+    }
+
+
+    /**
+     * True if the given section is the "newest" for that river.
+     * @param section Given section
+     * @return true if the section has the most advanced end of its validity interval
+     *         or the most advanced start of its validity interval.
+     */
+    public static boolean isNewest(CrossSection section) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from CrossSection where river.id = :riverid "
+            + " order by timeInterval.stopTime desc, timeInterval.startTime desc");
+        query.setParameter("riverid", section.getRiver().getId());
+
+        List result = query.list();
+
+        if (result == null || result.isEmpty()) {
+            return true;
+        }
+        else {
+            CrossSection cs = (CrossSection) result.get(0);
+            return section.getId().equals(cs.getId());
+        }
+    }
+
+
+    /**
+     *  Get a specific CrossSection from db.
+     *  @param id The dbid of the cross-section to load.
+     */
+    public static CrossSection getCrossSection(int id) {
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+        if (cache != null) {
+            Element element = cache.get(Integer.valueOf(id));
+            if (element != null) {
+                return (CrossSection) element.getValue();
+            }
+        }
+
+        CrossSection section = getCrossSectionUncached(id);
+        if (cache != null) {
+            Element element = new Element(Integer.valueOf(id), section);
+            cache.put(element);
+        }
+
+        return section;
+    }
+
+
+    /** Get specific CrossSection from database. */
+    protected static CrossSection getCrossSectionUncached(int id) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+                "from CrossSection where id=:id");
+        query.setParameter("id", id);
+        List<CrossSection> list = query.list();
+        return list.isEmpty() ? null : list.get(0);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/CrossSectionWaterLineFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import java.util.List;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.WaterLineArtifact;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+
+import de.intevation.flys.geom.Lines;
+
+/**
+ * Facet for Waterlines in Cross Sections.
+ */
+public class CrossSectionWaterLineFacet
+extends      BlackboardDataFacet
+implements   FacetTypes {
+
+    private static Logger logger = Logger.getLogger(CrossSectionWaterLineFacet.class);
+
+
+    /** Trivial constructor, set (maybe localized) description. */
+    public CrossSectionWaterLineFacet(int idx, String description) {
+        super(idx, CROSS_SECTION_WATER_LINE, description);
+    }
+
+
+    /**
+     * Gets waterline (crossed with cross section) of waterlevel.
+     */
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("Get data for cross section water line");
+
+        List<DataProvider> providers = context.
+            getDataProvider(CrossSectionFacet.BLACKBOARD_CS_MASTER_DATA);
+        if (providers.size() < 1) {
+            logger.warn("Could not find Cross-Section data provider.");
+            return new Lines.LineData(new double[][] {}, 0d, 0d);
+        }
+        
+        Object crossSection = providers.get(0)
+            .provideData(CrossSectionFacet.BLACKBOARD_CS_MASTER_DATA, null, context);
+
+        WaterLineArtifact winfo = (WaterLineArtifact)artifact;
+
+        if (crossSection != null) {
+            return winfo.getWaterLines(this.getIndex(),
+                (FastCrossSectionLine) crossSection);
+        }
+        else {
+            return new Lines.LineData(new double[][] {}, 0d,0d);
+        }
+    }
+
+
+    /** Do a deep copy. */
+    @Override 
+    public Facet deepCopy() {
+        CrossSectionWaterLineFacet copy = new CrossSectionWaterLineFacet(
+            this.getIndex(),
+            this.description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/DataFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,107 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+public class DataFacet
+extends      BlackboardDataFacet
+{
+    protected ComputeType type;
+    protected String      hash;
+    protected String      stateId;
+
+
+    /** Trivial constructor. */
+    public DataFacet() {
+    }
+
+    /**
+     * Defaults to ADVANCE Compute type.
+     * @param name Name of the facet.
+     * @param description maybe localized description of the facet.
+     */
+    public DataFacet(String name, String description) {
+        this(name, description, ComputeType.ADVANCE);
+    }
+
+
+    public DataFacet(String name, String description, ComputeType type) {
+        this(name, description, type, null);
+    }
+
+
+    public DataFacet(
+        String      name,
+        String      description,
+        ComputeType type,
+        String      hash
+    ) {
+        super(name, description);
+        this.type = type;
+        this.hash = hash;
+    }
+
+
+    public DataFacet(
+        String      name,
+        String      description,
+        ComputeType type,
+        String      hash,
+        String      stateId
+    ) {
+        super(name, description);
+        this.type    = type;
+        this.hash    = hash;
+        this.stateId = stateId;
+    }
+
+
+    public DataFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      hash,
+        String      stateId
+    ) {
+        super(index, name, description);
+        this.type    = type;
+        this.hash    = hash;
+        this.stateId = stateId;
+    }
+
+
+    /**
+     * Return computation result.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        FLYSArtifact flys = (FLYSArtifact)artifact;
+        String    theHash = (hash != null) ? hash : flys.hash();
+
+        return (stateId != null && stateId.length() > 0)
+            ? flys.compute(context, theHash, stateId, type, false)
+            : flys.compute(context, theHash, type, false);
+    }
+
+
+    /**
+     * Return a deep copy.
+     */
+    @Override
+    public Facet deepCopy() {
+        DataFacet copy = new DataFacet();
+        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 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/DischargeTables.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,235 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Arrays;
+
+import java.io.Serializable;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.DischargeTableValue;
+
+import gnu.trove.TDoubleArrayList;
+
+public class DischargeTables
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(DischargeTables.class);
+
+    public static final double DEFAULT_SCALE = 100.0;
+
+    public static final int MASTER = 0;
+
+    protected List<String> gaugeNames;
+
+    protected String riverName;
+
+    protected double scale;
+
+    protected int    kind;
+
+    protected Map<String, double [][]> values;
+
+    public DischargeTables() {
+    }
+
+    public DischargeTables(String riverName, String gaugeName) {
+        this(riverName, gaugeName, MASTER);
+    }
+
+    public DischargeTables(String riverName, String gaugeName, int kind) {
+        this(riverName, new String [] { gaugeName }, kind);
+    }
+
+    public DischargeTables(String riverName, String [] gaugeNames) {
+        this(riverName, gaugeNames, MASTER);
+    }
+
+    public DischargeTables(String riverName, String [] gaugeNames, int kind) {
+        this(riverName, Arrays.asList(gaugeNames), kind);
+    }
+
+    public DischargeTables(
+        String       riverName,
+        List<String> gaugeNames,
+        int          kind
+    ) {
+        scale           = Double.NaN;
+        this.kind       = kind;
+        this.riverName  = riverName;
+        this.gaugeNames = gaugeNames;
+    }
+
+    public double [][] getFirstTable() {
+        return getFirstTable(DEFAULT_SCALE);
+    }
+
+    public double [][] getFirstTable(double scale) {
+        Map<String, double [][]> values = getValues(scale);
+        for (double [][] table: values.values()) {
+            return table;
+        }
+        return null;
+    }
+
+    public Map<String, double [][]> getValues() {
+        return getValues(DEFAULT_SCALE);
+    }
+
+    public Map<String, double [][]> getValues(double scale) {
+        if (values == null || scale != this.scale) {
+            values = loadValues(scale);
+            this.scale = scale;
+        }
+        return values;
+    }
+
+    /**
+     * Returns mapping of gauge name to values.
+     */
+    protected Map<String, double [][]> loadValues(double scale) {
+        Map<String, double [][]> values = new HashMap<String, double [][]>();
+
+        Session session = SessionHolder.HOLDER.get();
+
+        Query gaugeQuery = session.createQuery(
+            "from Gauge where name=:gauge and river.name=:river");
+        gaugeQuery.setParameter("river", riverName);
+
+        for (String gaugeName: gaugeNames) {
+            gaugeQuery.setParameter("gauge", gaugeName);
+            List<Gauge> gauges = gaugeQuery.list();
+            if (gauges.isEmpty()) {
+                log.warn(
+                    "no gauge '"+gaugeName+"' at river '"+riverName+"'");
+                continue;
+            }
+            Gauge gauge = gauges.get(0);
+
+            List<DischargeTable> tables = gauge.getDischargeTables();
+
+            if (tables.isEmpty()) {
+                log.warn(
+                    "no discharge table for gauge '" + gaugeName + "'");
+                continue;
+            }
+
+            // TODO: Filter by time interval
+            DischargeTable table = tables.get(0);
+
+            double [][] vs = loadDischargeTableValues(table, scale);
+
+            values.put(gaugeName, vs);
+        }
+
+        return values;
+    }
+
+
+    /**
+     * @param table The discharge table
+     * @param scale The scale factor to adjust W and Q values.
+     * @param qSorted Boolean flag that is used to evaluate if the values should
+     * be sorted.
+     *
+     * @return the values of a discharge table.
+     */
+    public static double[][] loadDischargeTableValues(
+        DischargeTable table,
+        double         scale
+    ) {
+        List<DischargeTableValue> dtvs = table.getDischargeTableValues();
+
+        final double [][] vs = new double[2][dtvs.size()];
+
+        int idx = 0;
+        for (DischargeTableValue dtv: dtvs) {
+            double q = dtv.getQ().doubleValue();
+            vs[0][idx] = q * scale;
+            vs[1][idx] = dtv.getW().doubleValue() * scale;
+            ++idx;
+        }
+
+        return vs;
+    }
+
+    private static final double EPSILON = 1e-5;
+
+    private static final boolean epsEquals(double a, double b) {
+        return Math.abs(a - b) < EPSILON;
+    }
+
+    private static final boolean between(double a, double b, double x) {
+        if (a > b) { double t = a; a = b; b = t; }
+        return x > a && x < b;
+    }
+
+    public static double [] getQsForW(double [][] values, double w) {
+
+        boolean debug = log.isDebugEnabled();
+
+        if (debug) {
+            log.debug("getQsForW: W = " + w);
+        }
+
+        double [] qs = values[0];
+        double [] ws = values[1];
+
+        int N = Math.min(qs.length, ws.length);
+
+        if (N == 0) {
+            if (debug) {
+                log.debug("Q(" + w + ") = []");
+            }
+            return new double [0];
+        }
+
+        TDoubleArrayList outQs = new TDoubleArrayList();
+
+        if (epsEquals(ws[0], w)) {
+            outQs.add(qs[0]);
+        }
+
+        for (int i = 1; i < N; ++i) {
+            if (epsEquals(ws[i], w)) {
+                outQs.add(qs[i]);
+            }
+            else if (between(ws[i-1], ws[i], w)) {
+                double w1 = ws[i-1];
+                double w2 = ws[i];
+                double q1 = qs[i-1];
+                double q2 = qs[i];
+
+                // q1 = m*w1 + b
+                // q2 = m*w2 + b
+                // q2 - q1 = m*(w2 - w1)
+                // m = (q2 - q1)/(w2 - w1) # w2 != w1
+                // b = q1 - m*w1
+                // w1 != w2
+
+                double m = (q2 - q1)/(w2 - w1);
+                double b = q1 - m*w1;
+                double q = w*m + b;
+
+                outQs.add(q);
+            }
+        }
+
+        double [] result = outQs.toNativeArray();
+
+        if (debug) {
+            log.debug("Q(" + w + ") = " + Arrays.toString(result));
+        }
+
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/DurationCurveFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,98 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * Data of a duration curve.
+ */
+public class DurationCurveFacet extends DefaultFacet {
+
+    private static Logger logger = Logger.getLogger(DurationCurveFacet.class);
+
+    /** Blackboard data provider key for durationcurve (wqday) data. */
+    public static String BB_DURATIONCURVE = "durationcurve";
+
+    /** Blackboard data provider key for km of durationcurve. */
+    public static String BB_DURATIONCURVE_KM = "durationcurve.km";
+
+    public DurationCurveFacet() {
+    }
+
+    public DurationCurveFacet(String name, String description) {
+        super(0, name, description);
+    }
+
+
+    /**
+     * Expose state computation from WINFOArtifact.
+     */
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("Get data for duration curve data");
+
+        WINFOArtifact winfo = (WINFOArtifact)artifact;
+
+        CalculationResult cr = (CalculationResult)winfo.compute(
+            context, ComputeType.ADVANCE, false);
+
+        return cr.getData();
+    }
+
+
+    @Override
+    public List getStaticDataProviderKeys(Artifact art) {
+        List list = new ArrayList();
+        list.add(BB_DURATIONCURVE);
+        list.add(BB_DURATIONCURVE_KM);
+        return list;
+    }
+
+
+    /**
+     * Can provide whatever getData returns and additionally the location.
+     * @param key      will respond on BB_DURATIONCURVE +KM
+     * @param param    ignored
+     * @param context  ignored
+     * @return whatever getData delivers or location.
+     */
+    @Override
+    public Object provideBlackboardData(Artifact artifact,
+        Object key,
+        Object param,
+        CallContext context
+    ) {
+        if (key.equals(BB_DURATIONCURVE)) {
+            return getData(artifact, context);
+        }
+        else if (key.equals(BB_DURATIONCURVE_KM)) {
+            return ((FLYSArtifact)artifact).getDataAsString("ld_locations");
+        }
+        else {
+            return null;
+        }
+    }
+
+
+    /** Create a deep copy. */
+    @Override 
+    public Facet deepCopy() {
+        DurationCurveFacet copy = new DurationCurveFacet();
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/EmptyFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,33 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+public class EmptyFacet
+extends      DefaultFacet
+{
+    /** Trivial constructor. */
+    public EmptyFacet() {
+        super(0, "empty.facet", "empty.facet");
+    }
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        return null;
+    }
+
+
+    /**
+     * Return a deep copy.
+     */
+    @Override
+    public Facet deepCopy() {
+        EmptyFacet copy = new EmptyFacet();
+        copy.set(this);
+        return copy;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FacetTypes.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,161 @@
+package de.intevation.flys.artifacts.model;
+
+/** 'Types' of facets. */
+public interface FacetTypes {
+
+    public class IS {
+        public static boolean WQ_KM(String type) {
+           return type.equals(DISCHARGE_LONGITUDINAL_W)
+               || type.equals(LONGITUDINAL_W);
+        }
+        public static boolean W_KM(String type) {
+            return type.equals(STATIC_WKMS)
+               || type.equals(HEIGHTMARKS_POINTS)
+               || WQ_KM(type);
+        }
+        public static boolean AREA(String type) {
+            return type.equals(AREA)
+                || type.equals(CROSS_SECTION_AREA)
+                || type.equals(LONGITUDINAL_SECTION_AREA);
+        }
+        public static boolean W(String type) {
+            return type.equals(LONGITUDINAL_W)
+                || type.equals(DISCHARGE_LONGITUDINAL_W)
+                || type.equals(DURATION_W)
+                || type.equals(STATIC_WKMS)
+                || type.equals(STATIC_WQKMS_W);
+        }
+        public static boolean Q(String type) {
+            return type.equals(LONGITUDINAL_Q)
+                || type.equals(DISCHARGE_LONGITUDINAL_Q)
+                || type.equals(DURATION_Q)
+                || type.equals(STATIC_WQKMS_Q);
+        }
+        public static boolean V(String type) {
+            return type.equals(FLOW_VELOCITY_MAINCHANNEL)
+                || type.equals(FLOW_VELOCITY_TOTALCHANNEL);
+        }
+        public static boolean T(String type) {
+            return type.equals(FLOW_VELOCITY_TAU);
+        }
+        public static boolean H(String type) {
+            return type.equals(MIDDLE_BED_HEIGHT_SINGLE)
+                || type.equals(MIDDLE_BED_HEIGHT_EPOCH);
+        }
+        public static boolean MANUALPOINTS(String type) {
+            return type.endsWith("manualpoints");
+        }
+    };
+
+    public enum ChartType {
+        LS("longitudinal_section"),
+        CS("cross_section"),
+        DLS("discharge_longitudinal_section"),
+        CDC("computed_discharge_curve"),
+        DUC("duration_curve"),
+        DIC("discharge_curve"),
+        RC("reference_curve"),
+        RCN("reference_curve_normalized"),
+        WD("wdifferences"),
+        HD("historical_discharge");
+
+        private String chartTypeString;
+
+        ChartType(String description) {
+            this.chartTypeString = description;
+        }
+
+        public String toString() {
+            return chartTypeString;
+        }
+    }
+
+    String AREA                        = "area";
+    String CROSS_SECTION_AREA          = "cross_section.area";
+    String LONGITUDINAL_SECTION_AREA   = "longitudinal_section.area";
+
+    String FLOODMAP_WSPLGEN            = "floodmap.wsplgen";
+    String FLOODMAP_BARRIERS           = "floodmap.barriers";
+    String FLOODMAP_USERSHAPE          = "floodmap.usershape";
+    String FLOODMAP_RIVERAXIS          = "floodmap.riveraxis";
+    String FLOODMAP_WMSBACKGROUND      = "floodmap.wmsbackground";
+    String FLOODMAP_KMS                = "floodmap.kms";
+    String FLOODMAP_QPS                = "floodmap.qps";
+    String FLOODMAP_HWS                = "floodmap.hws";
+    String FLOODMAP_HYDR_BOUNDARY      = "floodmap.hydr_boundaries";
+    String FLOODMAP_HYDR_BOUNDARY_POLY = "floodmap.hydr_boundaries_poly";
+    String FLOODMAP_CATCHMENT          = "floodmap.catchment";
+    String FLOODMAP_FLOODPLAIN         = "floodmap.floodplain";
+    String FLOODMAP_LINES              = "floodmap.lines";
+    String FLOODMAP_BUILDINGS          = "floodmap.buildings";
+    String FLOODMAP_FIXPOINTS          = "floodmap.fixpoints";
+    String FLOODMAP_FLOODMAPS          = "floodmap.floodmaps";
+    String FLOODMAP_GAUGE_LOCATION     = "floodmap.gauge_location";
+    String FLOODMAP_EXTERNAL_WMS       = "floodmap.externalwms";
+
+    String DISCHARGE_LONGITUDINAL_W = "discharge_longitudinal_section.w";
+    String DISCHARGE_LONGITUDINAL_Q = "discharge_longitudinal_section.q";
+    String DISCHARGE_LONGITUDINAL_C = "discharge_longitudinal_section.c";
+
+    String LONGITUDINAL_W = "longitudinal_section.w";
+    String LONGITUDINAL_Q = "longitudinal_section.q";
+    String LONGITUDINAL_ANNOTATION   = "longitudinal_section.annotations";
+    String LONGITUDINAL_MANUALPOINTS = "longitudinal_section.manualpoints";
+
+    String W_DIFFERENCES = "w_differences";
+
+    String COMPUTED_DISCHARGE_Q = "computed_discharge_curve.q";
+    String COMPUTED_DISCHARGE_MAINVALUES_Q = "computed_discharge_curve.mainvalues.q";
+    String COMPUTED_DISCHARGE_MAINVALUES_W = "computed_discharge_curve.mainvalues.w";
+
+    String MAINVALUES_Q = "mainvalues.q";
+    String MAINVALUES_W = "mainvalues.w";
+
+    String CROSS_SECTION = "cross_section";
+    String CROSS_SECTION_WATER_LINE = "cross_section_water_line";
+
+    String HYK = "hyk";
+
+    String DISCHARGE_CURVE = "discharge_curve.curve";
+
+    String DURATION_W = "duration_curve.w";
+    String DURATION_Q = "duration_curve.q";
+    String DURATION_MAINVALUES_Q = "duration_curve.mainvalues.q";
+
+    String MANUALPOINTS = "manualpoints";
+
+    String STATIC_WQ      = "other.wq";
+    String STATIC_WQ_ANNOTATIONS = "other.wq.annotations";
+    String STATIC_WKMS    = "other.wkms";
+    String STATIC_WQKMS   = "other.wqkms";
+    String STATIC_WQKMS_W = "other.wqkms.w";
+    String STATIC_WQKMS_Q = "other.wqkms.q";
+    String STATIC_WKMS_INTERPOL = "other.wkms.interpol";
+
+    String HEIGHTMARKS_POINTS = "heightmarks_points";
+
+    String CSV = "csv";
+    String WST = "wst";
+    String AT  = "at";
+    String PDF = "pdf";
+
+    String REPORT = "report";
+
+    String HISTORICAL_DISCHARGE_Q      = "historical_discharge.historicalq";
+    String HISTORICAL_DISCHARGE_Q_DIFF = "historical_discharge.historicalq.diff";
+
+    String REFERENCE_CURVE = "reference_curve";
+    String REFERENCE_CURVE_NORMALIZED = "reference_curve_normalized";
+
+    String FLOW_VELOCITY_MAINCHANNEL  = "flow_velocity.mainchannel";
+    String FLOW_VELOCITY_TOTALCHANNEL = "flow_velocity.totalchannel";
+    String FLOW_VELOCITY_TAU          = "flow_velocity.tau";
+    String FLOW_VELOCITY_ANNOTATION   = "flow_velocity.annotation";
+
+    String MIDDLE_BED_HEIGHT_SINGLE     = "bedheight_middle.single";
+    String MIDDLE_BED_HEIGHT_EPOCH      = "bedheight_middle.epoch";
+    String MIDDLE_BED_HEIGHT_ANNOTATION = "bedheight_middle.annotation";
+
+    String RELATIVE_POINT = "relativepoint";
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionChunk.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,83 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.Collections;
+
+import java.io.Serializable;
+
+import de.intevation.flys.model.CrossSection;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+
+import org.apache.log4j.Logger;
+
+public class FastCrossSectionChunk
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(FastCrossSectionChunk.class);
+
+    public static final String PREFIX = "FCSC:";
+    public static final double KM_RANGE = 1.0;
+
+    protected double startKm;
+    protected int    crossSectionId;
+
+    protected List<FastCrossSectionLine> crossSectionLines;
+
+    public FastCrossSectionChunk() {
+    }
+
+    public FastCrossSectionChunk(CrossSection cs, double km) {
+
+        crossSectionId = cs.getId();
+        startKm = Math.floor(km);
+        double stopKm = startKm + KM_RANGE;
+
+        long startTime = System.currentTimeMillis();
+
+        crossSectionLines = cs.getFastLines(startKm, stopKm);
+
+        long stopTime = System.currentTimeMillis();
+
+        if (log.isDebugEnabled()) {
+            log.debug("Fetching cross section lines took " + 
+                (float)(stopTime-startTime)/1000f + " secs.");
+        }
+    }
+
+    public FastCrossSectionLine getCrossSectionLine(double km) {
+        FastCrossSectionLine key = new FastCrossSectionLine(km);
+        int pos = Collections.binarySearch(
+            crossSectionLines, key, FastCrossSectionLine.KM_CMP);
+        return pos < 0 ? null : crossSectionLines.get(pos);
+    }
+
+    public static String createHashKey(CrossSection cs, double km) {
+        return PREFIX + cs.getId() + ":" + (int)Math.floor(km);
+    }
+
+    public String getHashKey() {
+        return PREFIX + crossSectionId + ":" + (int)Math.floor(startKm);
+    }
+
+    public double getStartKm() {
+        return startKm;
+    }
+
+    public void setStartKm(double startKm) {
+        this.startKm = startKm;
+    }
+
+    public double getStopKm() {
+        return startKm + KM_RANGE;
+    }
+
+    public int getCrossSectionId() {
+        return crossSectionId;
+    }
+
+    public void setCrossSectionId(int crossSectionId) {
+        this.crossSectionId = crossSectionId;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FastCrossSectionLineFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,69 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.model.CrossSection;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class FastCrossSectionLineFactory
+{
+    private static Logger log =
+        Logger.getLogger(FastCrossSectionLineFactory.class);
+
+    public static final String CACHE_NAME = "fast-cross-section-lines";
+
+    private FastCrossSectionLineFactory() {
+    }
+
+    public static FastCrossSectionLine getCrossSectionLine(
+        CrossSection cs,
+        double       km
+    ) {
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        boolean debug = log.isDebugEnabled();
+
+        if (cache == null) {
+            if (debug) {
+                log.debug("No cross section chunk cache configured.");
+            }
+            List<FastCrossSectionLine> lines = cs.getFastLines(km, km);
+            return lines.isEmpty() ? null : lines.get(0);
+        }
+
+        String cacheKey = FastCrossSectionChunk.createHashKey(cs, km);
+
+        Element element = cache.get(cacheKey);
+
+        FastCrossSectionChunk fcsc;
+
+        if (element != null) {
+            if (debug) {
+                log.debug("Found cross section chunk in cache id: " +
+                    cs.getId() + " km: " + km);
+            }
+
+            fcsc = (FastCrossSectionChunk)element.getValue();
+        }
+        else {
+            if (debug) {
+                log.debug("Not found cross section chunk in cache id: " +
+                    cs.getId() + " km: " + km + " -> loading");
+            }
+            fcsc = new FastCrossSectionChunk(cs, km);
+            element = new Element(cacheKey, fcsc);
+            cache.put(element);
+        }
+
+        return fcsc.getCrossSectionLine(km);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsColumn.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,62 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.math.Linear;
+
+import java.util.Arrays;
+
+import java.io.Serializable;
+
+public class FixingsColumn
+implements   Serializable
+{
+    protected double [] kms;
+    protected double [] ws;
+
+    protected QRangeTree qs;
+
+    public FixingsColumn() {
+    }
+
+    public FixingsColumn(
+        double []  kms,
+        double []  ws,
+        QRangeTree qs
+    ) {
+        this.kms = kms;
+        this.ws  = ws;
+        this.qs  = qs;
+    }
+
+    public boolean getW(double km, double [] w) {
+        return getW(km, w, 0);
+    }
+
+    public boolean getW(double km, double [] w, int index) {
+
+        if (kms.length == 0 || km < kms[0] || km > kms[kms.length-1]) {
+            w[index] = Double.NaN;
+            return true;
+        }
+
+        int idx = Arrays.binarySearch(kms, km);
+
+        if (idx >= 0) {
+            w[index] = ws[idx];
+            return true;
+        }
+
+        idx = -idx - 1;
+
+        w[index] = Linear.linear(km, kms[idx-1], kms[idx], ws[idx-1], ws[idx]);
+        return false;
+    }
+
+    public double getQ(double km) {
+        return qs.findQ(km);
+    }
+
+    public QRangeTree getQRanges() {
+        return qs;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsColumnFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,131 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.Fixing;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import java.util.List;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.hibernate.Session;
+import org.hibernate.SQLQuery;
+
+import org.hibernate.type.StandardBasicTypes;
+
+import org.apache.log4j.Logger;
+
+public class FixingsColumnFactory
+{
+    private static Logger log = Logger.getLogger(FixingsColumnFactory.class);
+
+    public static final String CACHE_NAME = "fixings-columns";
+
+    public static final String SQL_COLUMN_WS =
+        "SELECT wcv.position AS km, wcv.w AS w " +
+        "FROM wst_column_values wcv " +
+        "WHERE wst_column_id = :column_id " +
+        "ORDER by wcv.position";
+
+    public static final String SQL_COLUMN_QS =
+        "SELECT wqr.q AS q, r.a AS a, r.b AS b " +
+        "FROM wst_column_q_ranges wcqr " +
+        "JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id " +
+        "JOIN ranges r         ON wqr.range_id        = r.id " +
+        "WHERE wcqr.wst_column_id = :column_id ORDER by r.a";
+
+    public static final FixingsColumnFactory INSTANCE =
+        new FixingsColumnFactory();
+
+    private FixingsColumnFactory() {
+    }
+
+    public static FixingsColumnFactory getInstance() {
+        return INSTANCE;
+    }
+
+    public FixingsColumn getColumnData(Fixing.Column column) {
+
+        boolean debug = log.isDebugEnabled();
+
+        if (debug) {
+            log.debug("FixingsColumnFactory.getColumnData");
+        }
+
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        if (cache == null) {
+            if (debug) {
+                log.debug("Cache unconfigured.");
+            }
+            return getUncached(column);
+        }
+
+        Integer cacheKey = Integer.valueOf(column.getId());
+        Element element  = cache.get(cacheKey);
+
+        if (element != null) {
+            if (debug) {
+                log.debug("Column " + cacheKey + " found in cache.");
+            }
+            return (FixingsColumn)element.getValue();
+        }
+        else {
+            FixingsColumn result = getUncached(column);
+            if (result != null) {
+                if (debug) {
+                    log.debug("Store column " + cacheKey + " into cache.");
+                }
+                cache.put(new Element(cacheKey, result));
+            }
+            return result;
+        }
+    }
+
+    protected FixingsColumn getUncached(Fixing.Column column) {
+        Session session = SessionHolder.HOLDER.get();
+
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_COLUMN_WS)
+            .addScalar("km", StandardBasicTypes.DOUBLE)
+            .addScalar("w",  StandardBasicTypes.DOUBLE);
+
+        sqlQuery.setInteger("column_id", column.getId());
+
+        List<Object []> results = sqlQuery.list();
+
+        if (results.isEmpty()) {
+            return null;
+        }
+
+        double [] kms = new double[results.size()];
+        double [] ws  = new double[kms.length];
+
+        for (int i = 0; i < kms.length; ++i) {
+            Object [] row = results.get(i);
+            kms[i] = ((Double)row[0]).doubleValue();
+            ws [i] = ((Double)row[1]).doubleValue();
+        }
+
+        sqlQuery = session.createSQLQuery(SQL_COLUMN_QS)
+            .addScalar("q", StandardBasicTypes.DOUBLE)
+            .addScalar("a", StandardBasicTypes.DOUBLE)
+            .addScalar("b", StandardBasicTypes.DOUBLE);
+
+        sqlQuery.setInteger("column_id", column.getId());
+
+        results = sqlQuery.list();
+
+        if (results.isEmpty()) {
+            return null;
+        }
+
+        QRangeTree qs = new QRangeTree(
+            results, QRangeTree.WITHOUT_COLUMN, 0, results.size());
+
+        return new FixingsColumn(kms, ws, qs);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsFilterBuilder.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,205 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.AndFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.DateFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.DateRangeFilter;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.Fixing.Filter;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.IdFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.NotFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.OrFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.Range;
+import de.intevation.flys.artifacts.model.FixingsOverview.SectorRangeFilter;
+
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class FixingsFilterBuilder
+{
+    private static Logger log = Logger.getLogger(FixingsFilterBuilder.class);
+
+    protected Filter   filter;
+    protected Range    range;
+
+    protected Document document;
+
+    public FixingsFilterBuilder() {
+    }
+
+    public FixingsFilterBuilder(Document document) {
+        this.document = document;
+    }
+
+    public Filter getFilter() {
+        if (filter == null) {
+            filter = buildFilter();
+        }
+        return filter;
+    }
+
+    public Range getRange() {
+        if (range == null) {
+            range = buildRange();
+        }
+        return range;
+    }
+
+    public Document getDocument() {
+        return document;
+    }
+
+    protected Range buildRange() {
+
+        NodeList ranges = document.getElementsByTagName("range");
+
+        if (ranges.getLength() < 1) {
+            return FixingsOverview.FULL_EXTENT;
+        }
+
+        Element range = (Element)ranges.item(0);
+
+        String from = range.getAttribute("from").trim();
+        String to   = range.getAttribute("to"  ).trim();
+
+        double start = -Double.MAX_VALUE;
+        double end   =  Double.MAX_VALUE;
+
+        if (from.length() > 0) {
+            try {
+                start = Double.parseDouble(from);
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("Invalid from value: " + from);
+            }
+        }
+
+        if (to.length() > 0) {
+            try {
+                end = Double.parseDouble(to);
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("Invalid to value: " + to);
+            }
+        }
+
+        if (start > end) {
+            double t = start;
+            start = end;
+            end = t;
+        }
+
+        return new Range(start, end);
+    }
+
+    protected Filter buildFilter() {
+        NodeList filters = document.getElementsByTagName("filter");
+
+        return filters.getLength() < 1
+            ? FixingsOverview.ACCEPT
+            : buildFilter((Element)filters.item(0));
+    }
+
+    protected static Filter buildFilter(Element root) {
+        List<Filter> filters = buildRecursiveFilter(root);
+        switch (filters.size()) {
+            case  0: return FixingsOverview.ACCEPT;
+            case  1: return filters.get(0);
+            default: return new AndFilter(filters);
+        }
+    }
+
+    protected static final Date parseDate(String text) {
+        SimpleDateFormat format =
+            new SimpleDateFormat(FixingsOverview.DATE_FORMAT);
+        return format.parse(text, new ParsePosition(0));
+    }
+
+    protected static List<Filter> buildRecursiveFilter(Element root) {
+        List<Filter> filters = new ArrayList<Filter>();
+
+        NodeList children = root.getChildNodes();
+
+        for (int i = 0, N = children.getLength(); i < N; ++i) {
+            Node child = children.item(i);
+            if (child.getNodeType() != Node.ELEMENT_NODE) {
+                continue;
+            }
+
+            Element element = (Element)child;
+            String name = element.getLocalName();
+
+            if ("and".equals(name)) {
+                filters.add(new AndFilter(buildRecursiveFilter(element)));
+            }
+            else if ("or".equals(name)) {
+                filters.add(new OrFilter(buildRecursiveFilter(element)));
+            }
+            else if ("not".equals(name)) {
+                List<Filter> childrenFilters = buildRecursiveFilter(element);
+                if (!childrenFilters.isEmpty()) {
+                    filters.add(new NotFilter(childrenFilters.get(0)));
+                }
+            }
+            else if ("column".equals(name)) {
+                String cid = element.getAttribute("cid").trim();
+                if (cid.length() > 0) {
+                    try {
+                        filters.add(new IdFilter(Integer.parseInt(cid)));
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn(nfe);
+                    }
+                }
+            }
+            else if ("date".equals(name)) {
+                String when = element.getAttribute("when").trim();
+                if (when.length() > 0) {
+                    Date date = parseDate(when);
+                    if (date != null) {
+                        filters.add(new DateFilter(date));
+                    }
+                }
+            }
+            else if ("date-range".equals(name)) {
+                String from = element.getAttribute("from").trim();
+                String to   = element.getAttribute("to"  ).trim();
+                if (from.length() > 0 && to.length() > 0) {
+                    Date start = parseDate(from);
+                    Date end   = parseDate(to);
+                    if (start != null && end != null) {
+                        filters.add(new DateRangeFilter(start, end));
+                    }
+                }
+            }
+            else if ("sector-range".equals(name)) {
+                String from = element.getAttribute("from").trim();
+                String to   = element.getAttribute("to"  ).trim();
+                if (from.length() > 0 && to.length() > 0) {
+                    try {
+                        filters.add(new SectorRangeFilter(
+                            Integer.parseInt(from),
+                            Integer.parseInt(to)));
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn(nfe);
+                    }
+                }
+            }
+        }
+
+        return filters;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,939 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import java.text.SimpleDateFormat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+
+import org.hibernate.type.StandardBasicTypes;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class FixingsOverview
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(FixingsOverview.class);
+
+    public static final double EPSILON  = 1e-5;
+    public static final double EPSILON2 = 1e-1;
+
+    public static final String DATE_FORMAT = "dd.MM.yyyy";
+
+    public static final String SQL_RIVER_ID =
+        "SELECT" +
+        "    id AS river_id," +
+        "    km_up " +
+        "FROM rivers " +
+        "WHERE" +
+        "    name = :name";
+
+    public static final String SQL_GAUGES =
+        "SELECT" +
+        "    g.id AS gauge_id," +
+        "    r.a  AS a," +
+        "    r.b  AS b " +
+        "FROM gauges g" +
+        "    JOIN ranges r ON g.range_id = r.id " +
+        "WHERE" +
+        "    g.river_id = :river_id " +
+        "ORDER BY r.a";
+
+    public static final String SQL_DISCHARGE_SECTORS =
+        "SELECT" +
+        "    g.id                            AS gauge_id," +
+        "    nmv.name                        AS name," +
+        "    CAST(mv.value AS NUMERIC(38,2)) AS value " +
+        "FROM gauges g" +
+        "    JOIN main_values       mv  ON g.id = mv.gauge_id" +
+        "    JOIN named_main_values nmv ON nmv.id = mv.named_value_id" +
+        "    JOIN main_value_types  mvt ON nmv.type_id = mvt.id " +
+        "WHERE" +
+        "    mvt.name = 'Q' AND (" +
+        "        nmv.name = 'MNQ'      OR" +
+        "        nmv.name LIKE 'MNQ(%' OR" +
+        "        nmv.name = 'MQ'       OR" +
+        "        nmv.name LIKE 'MQ(%'  OR" +
+        "        nmv.name = 'MHQ'      OR" +
+        "        nmv.name LIKE 'MHQ(%' OR" +
+        "        nmv.name = 'HQ5'      OR" +
+        "        nmv.name LIKE 'HQ5(%') AND" +
+        "    g.river_id = :river_id " +
+        "ORDER BY" +
+        "    g.id";
+
+    public static final String SQL_FIXINGS =
+        "SELECT" +
+        "    id AS wst_id," +
+        "    description " +
+        "FROM wsts " +
+        "WHERE" +
+        "    river_id = :river_id AND kind = 2";
+
+    public static final String SQL_FIXING_COLUMNS =
+        "SELECT" +
+        "    wc.id         AS wst_column_id," +
+        "    ti.start_time AS start_time," +
+        "    wc.name       AS name " +
+        "FROM wst_columns wc" +
+        "    JOIN time_intervals ti ON wc.time_interval_id = ti.id " +
+        "WHERE" +
+        "    wc.wst_id = :wst_id " +
+        "ORDER BY position";
+
+    public static final String SQL_FIXING_COLUMN_Q_RANGES =
+        "SELECT" +
+        "    wqr.q AS q," +
+        "    r.a   AS start_km," +
+        "    r.b   AS stop_km " +
+        "FROM wst_column_q_ranges wcqr" +
+        "    JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id" +
+        "    JOIN ranges       r   ON wqr.range_id        = r.id " +
+        "WHERE" +
+        "    wcqr.wst_column_id = :column_id " +
+        "ORDER BY r.a";
+
+    public static final String SQL_FIXING_COLUMN_KM_RANGE =
+        "SELECT" +
+        "    MIN(position) AS start_km," +
+        "    MAX(position) AS stop_km " +
+        "FROM" +
+        "    wst_column_values " +
+        "WHERE" +
+        "    wst_column_id = :column_id";
+
+    public static class Range implements Serializable {
+
+        protected double start;
+        protected double end;
+
+        public Range() {
+        }
+
+        public Range(double start, double end) {
+            this.start = start;
+            this.end   = end;
+        }
+
+        public double getStart() {
+            return start;
+        }
+
+        public double getEnd() {
+            return end;
+        }
+
+        public boolean disjoint(Range other) {
+            return start > other.end || other.start > end;
+        }
+
+        public boolean intersects(Range other) {
+            return !disjoint(other);
+        }
+
+        public void extend(Range other) {
+            if (other.start < start) start = other.start;
+            if (other.end   > end  ) end   = other.end;
+        }
+
+        public boolean clip(Range other) {
+            if (disjoint(other)) return false;
+
+            if (other.start > start) start = other.start;
+            if (other.end   < end  ) end   = other.end;
+
+            return true;
+        }
+
+        public boolean inside(double x) {
+            return x > start-EPSILON && x < end+EPSILON;
+        }
+    } // class Range
+
+    public static class GaugeRange extends Range {
+
+        private static final class Sector implements Serializable {
+
+            int    sector;
+            double value;
+
+            Sector(int sector, double value) {
+                this.sector = sector;
+                this.value  = value;
+            }
+
+        } // class Sector
+
+        protected int gaugeId;
+
+        protected Map<String, Double> mainValues;
+        protected List<Sector> sectors;
+
+        public GaugeRange() {
+        }
+
+        public GaugeRange(double start, double end, int gaugeId) {
+            super(start, end);
+            this.gaugeId = gaugeId;
+            mainValues = new HashMap<String, Double>();
+            sectors = new ArrayList<Sector>(3);
+        }
+
+        public void addMainValue(String label, Double value) {
+            int idx = label.indexOf('(');
+            if (idx >= 0) {
+                label = label.substring(0, idx);
+            }
+            mainValues.put(label, value);
+        }
+
+        protected Double getMainValue(String label) {
+            Double v = mainValues.get(label);
+            if (v == null) {
+                log.warn("Missing main value '" 
+                    + label + "' for gauge " + gaugeId);
+            }
+            return v;
+        }
+
+        public void buildClasses() {
+            Double mnq = getMainValue("MNQ");
+            Double mq  = getMainValue("MQ");
+            Double mhq = getMainValue("MHQ");
+            Double hq5 = getMainValue("HQ5");
+
+            Double [][] pairs = {
+                { mnq,  mq },
+                {  mq, mhq },
+                { hq5, hq5 } };
+
+            for (int c = 0; c < pairs.length; ++c) {
+                Double [] pair = pairs[c];
+                if (pair[0] != null && pair[1] != null) {
+                    double value = 0.5*(pair[0] + pair[1]);
+                    sectors.add(new Sector(c, value));
+                }
+            }
+        }
+
+        public int classify(double value) {
+            for (Sector sector: sectors) {
+                if (value < sector.value) {
+                    return sector.sector;
+                }
+            }
+            return sectors.size();
+        }
+    } // class GaugeRange
+
+    public static class GaugeFinder {
+
+        protected List<GaugeRange> gauges;
+        protected boolean          isKmUp;
+
+        public GaugeFinder(List<GaugeRange> gauges) {
+            this(gauges, true);
+        }
+
+        public GaugeFinder(
+            List<GaugeRange> gauges,
+            boolean          isKmUp
+        ) {
+            this.gauges = gauges;
+            this.isKmUp = isKmUp;
+        }
+
+        public boolean getIsKmUp() {
+            return isKmUp;
+        }
+
+        public void setIsKmUp(boolean isKmUp) {
+            this.isKmUp = isKmUp;
+        }
+
+        public GaugeRange find(Range range) {
+            double km = isKmUp ? range.start : range.end;
+            for (GaugeRange gauge: gauges) {
+                if (gauge.inside(km)) {
+                    return gauge;
+                }
+            }
+            return null;
+        }
+
+        public GaugeRange find(int gaugeId) {
+            for (GaugeRange gauge: gauges) {
+                if (gauge.gaugeId == gaugeId) {
+                    return gauge;
+                }
+            }
+            return null;
+        }
+
+        public boolean loadDischargeSectors(Session session, int riverId) {
+
+            SQLQuery query = session.createSQLQuery(SQL_DISCHARGE_SECTORS)
+                .addScalar("gauge_id", StandardBasicTypes.INTEGER)
+                .addScalar("name",     StandardBasicTypes.STRING)
+                .addScalar("value",    StandardBasicTypes.DOUBLE);
+
+            query.setInteger("river_id", riverId);
+
+            List<Object []> list = query.list();
+
+            if (list.isEmpty()) {
+                log.warn("River " + riverId + " has no discharge sectors.");
+                return false;
+            }
+
+            GaugeRange gauge = null;
+
+            for (Object [] row: list) {
+                int    gaugeId = (Integer)row[0];
+                String label   = (String) row[1];
+                Double value   = (Double) row[2];
+
+                if (gauge == null || gauge.gaugeId != gaugeId) {
+                    if ((gauge = find(gaugeId)) == null) {
+                        log.warn("Cannot find gauge for id " + gaugeId + ".");
+                        continue;
+                    }
+                }
+
+                gauge.addMainValue(label, value);
+            }
+
+            for (GaugeRange g: gauges) {
+                g.buildClasses();
+            }
+
+            return true;
+        }
+    } // class GaugeFinder
+
+    public static class QRange extends Range {
+
+        protected double q;
+
+        public QRange() {
+        }
+
+        public QRange(double start, double end, double q) {
+            super(start, end);
+            this.q = q;
+        }
+    } // class QRange
+
+    public static class SectorRange extends Range {
+
+        protected int sector;
+
+        public SectorRange() {
+        }
+
+        public SectorRange(SectorRange other) {
+            start  = other.start;
+            end    = other.end;
+            sector = other.sector;
+        }
+
+        public SectorRange(double start, double end, int sector) {
+            super(start, end);
+            this.sector = sector;
+        }
+
+        public int getSector() {
+            return sector;
+        }
+
+        public boolean enlarge(SectorRange other) {
+            if (sector == other.sector
+            && Math.abs(end-other.start) < EPSILON2) {
+                end = other.end;
+                return true;
+            }
+            return false;
+        }
+    } // class SectorRange
+
+    public static class Fixing implements Serializable {
+
+        public static final Comparator<Column> DATE_CMP =
+            new Comparator<Column>() {
+                @Override
+                public int compare(Column a, Column b) {
+                    return a.startTime.compareTo(b.startTime);
+                }
+            };
+
+        public interface Filter {
+
+            boolean accept(Column column);
+
+        } // interface Filter
+
+        public class Column extends Range {
+
+            protected int    columnId;
+            protected Date   startTime;
+            protected String name;
+
+            protected List<SectorRange> sectors;
+
+            public Column() {
+            }
+
+            public Column(int columnId, Date startTime, String name) {
+                this.columnId  = columnId;
+                this.startTime = startTime;
+                this.name      = name;
+                
+                sectors = new ArrayList<SectorRange>();
+            }
+
+            public int getId() {
+                return columnId;
+            }
+
+            public Fixing getFixing() {
+                return Fixing.this;
+            }
+
+            public Date getStartTime() {
+                return startTime;
+            }
+
+            public String getName() {
+                return name;
+            }
+
+            public String getDescription() {
+                return Fixing.this.description + "/" + name;
+            }
+
+            public List<SectorRange> getSectors() {
+                return sectors;
+            }
+
+            public List<SectorRange> getSectors(Range range) {
+
+                List<SectorRange> result =
+                    new ArrayList<SectorRange>(sectors.size());
+
+                for (SectorRange src: sectors) {
+                    SectorRange dst = new SectorRange(src);
+                    if (range == null || dst.clip(range)) {
+                        result.add(dst);
+                    }
+                }
+
+                return result;
+            }
+
+            public void buildSectors(GaugeRange gauge, List<QRange> qRanges) {
+
+                for (QRange qRange: qRanges) {
+                    SectorRange sector = new SectorRange(
+                        qRange.start, qRange.end,
+                        gauge.classify(qRange.q));
+                    if (sectors.isEmpty()
+                    || !sectors.get(sectors.size()-1).enlarge(sector)) {
+                        sectors.add(sector);
+                    }
+                }
+            }
+
+            public void loadKmRange(SQLQuery query) {
+                query.setInteger("column_id", columnId);
+
+                List<Object []> kms = query.list();
+
+                if (kms.isEmpty()) {
+                    log.warn("No km range for column " + columnId + ".");
+                }
+                else {
+                    Object [] obj = kms.get(0);
+                    start = (Double)obj[0];
+                    end   = (Double)obj[1];
+                }
+            }
+
+            public void loadQRanges(
+                SQLQuery    query,
+                GaugeFinder gaugeFinder
+            ) {
+                query.setInteger("column_id", columnId);
+                List<Object []> list = query.list();
+
+                List<QRange> qRanges = new ArrayList<QRange>(list.size());
+
+                for (Object [] row: list) {
+                    double q     = (Double)row[0];
+                    double start = (Double)row[1];
+                    double end   = (Double)row[2];
+                    QRange qRange = new QRange(start, end, q);
+                    if (qRange.clip(this)) {
+                        qRanges.add(qRange);
+                    }
+                }
+
+                GaugeRange gauge = gaugeFinder.find(this);
+
+                if (gauge != null) {
+                    buildSectors(gauge, qRanges);
+                }
+                else {
+                    log.warn("No gauge found for column " + columnId + ".");
+                }
+            }
+        } // class Column
+
+        protected int          wstId;
+        protected String       description;
+        protected List<Column> columns;
+
+        public Fixing() {
+        }
+
+        public int getId() {
+            return wstId;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public Fixing(int wstId, String description) {
+            this.wstId       = wstId;
+            this.description = description;
+            columns = new ArrayList<Column>();
+        }
+
+        public void loadColumns(SQLQuery query) {
+            query.setInteger("wst_id", wstId);
+            List<Object []> list = query.list();
+            for (Object [] row: list) {
+                int    columnId  = (Integer)row[0];
+                Date   startTime = (Date)   row[1];
+                String name      = (String) row[2];
+                columns.add(new Column(columnId, startTime, name));
+            }
+        }
+
+        public void loadColumnsKmRange(SQLQuery query) {
+            for (Column column: columns) {
+                column.loadKmRange(query);
+            }
+        }
+
+        public void adjustExtent(Range extent) {
+            for (Column column: columns) {
+                extent.extend(column);
+            }
+        }
+
+        public void loadColumnsQRanges(
+            SQLQuery    query,
+            GaugeFinder gaugeFinder
+        ) {
+            for (Column column: columns) {
+                column.loadQRanges(query, gaugeFinder);
+            }
+        }
+
+        public void addAllColumns(
+            List<Column> allColumns,
+            Range        range,
+            Filter       filter
+        ) {
+            for (Column column: columns) {
+                if ((range == null || column.intersects(range)) 
+                && (filter == null || filter.accept(column))) { 
+                    allColumns.add(column);
+                }
+            }
+        }
+    } // class Fixing
+
+
+    protected String       riverName;
+    protected int          riverId;
+    protected boolean      isKmUp;
+    protected List<Fixing> fixings;
+    protected Range        extent;
+
+    public FixingsOverview() {
+        fixings = new ArrayList<Fixing>();
+        extent  = new Range(Double.MAX_VALUE, -Double.MAX_VALUE);
+    }
+
+    public FixingsOverview(String riverName) {
+        this();
+        this.riverName = riverName;
+    }
+
+    protected boolean loadRiver(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_RIVER_ID)
+            .addScalar("river_id", StandardBasicTypes.INTEGER)
+            .addScalar("km_up",    StandardBasicTypes.BOOLEAN);
+
+        query.setString("name", riverName);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("No river '" + riverName + "' found.");
+            return false;
+        }
+
+        Object [] row = list.get(0);
+
+        riverId = (Integer)row[0];
+        isKmUp  = (Boolean)row[1];
+
+        return true;
+    }
+
+    protected GaugeFinder loadGauges(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_GAUGES)
+            .addScalar("gauge_id", StandardBasicTypes.INTEGER)
+            .addScalar("a",        StandardBasicTypes.DOUBLE)
+            .addScalar("b",        StandardBasicTypes.DOUBLE);
+
+        query.setInteger("river_id", riverId);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("River " + riverId + " has no gauges.");
+            return null;
+        }
+
+        List<GaugeRange> gauges = new ArrayList<GaugeRange>();
+
+        for (Object [] row: list) {
+            int    gaugeId = (Integer)row[0];
+            double start   = (Double) row[1];
+            double end     = (Double) row[2];
+            GaugeRange gauge = new GaugeRange(start, end, gaugeId);
+            gauges.add(gauge);
+        }
+
+        return new GaugeFinder(gauges, isKmUp);
+    }
+
+
+    protected void loadFixings(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXINGS)
+            .addScalar("wst_id",      StandardBasicTypes.INTEGER)
+            .addScalar("description", StandardBasicTypes.STRING);
+
+        query.setInteger("river_id", riverId);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("River " + riverId + " has no fixings.");
+            // Its pretty fine to have no fixings.
+        }
+
+        for (Object [] row: list) {
+            int    wstId       = (Integer)row[0];
+            String description = (String) row[1];
+            Fixing fixing = new Fixing(wstId, description);
+            fixings.add(fixing);
+        }
+    }
+
+    protected void loadFixingsColumns(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMNS)
+            .addScalar("wst_column_id", StandardBasicTypes.INTEGER)
+            .addScalar("start_time",    StandardBasicTypes.DATE)
+            .addScalar("name",          StandardBasicTypes.STRING);
+
+        for (Fixing fixing: fixings) {
+            fixing.loadColumns(query);
+        }
+    }
+
+    protected void loadFixingsColumnsKmRange(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_KM_RANGE)
+            .addScalar("start_km", StandardBasicTypes.DOUBLE)
+            .addScalar("stop_km",  StandardBasicTypes.DOUBLE);
+
+        for (Fixing fixing: fixings) {
+            fixing.loadColumnsKmRange(query);
+        }
+    }
+
+    protected void loadFixingsColumnsQRanges(
+        Session     session,
+        GaugeFinder gaugeFinder
+    ) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_Q_RANGES)
+            .addScalar("q",        StandardBasicTypes.DOUBLE)
+            .addScalar("start_km", StandardBasicTypes.DOUBLE)
+            .addScalar("stop_km",  StandardBasicTypes.DOUBLE);
+
+        for (Fixing fixing: fixings) {
+            fixing.loadColumnsQRanges(query, gaugeFinder);
+        }
+    }
+
+    protected void adjustExtent() {
+        for (Fixing fixing: fixings) {
+            fixing.adjustExtent(extent);
+        }
+    }
+
+    public boolean load(Session session) {
+
+        if (!loadRiver(session)) {
+            return false;
+        }
+
+        GaugeFinder gaugeFinder = loadGauges(session);
+
+        if (gaugeFinder == null
+        || !gaugeFinder.loadDischargeSectors(session, riverId)) {
+            return false;
+        }
+
+        loadFixings(session);
+        loadFixingsColumns(session);
+        loadFixingsColumnsKmRange(session);
+
+        adjustExtent();
+
+        loadFixingsColumnsQRanges(session, gaugeFinder);
+
+        return true;
+    }
+
+    public static final Range FULL_EXTENT =
+        new Range(-Double.MAX_VALUE, Double.MAX_VALUE);
+
+    public static final Fixing.Filter ACCEPT = new Fixing.Filter() {
+        @Override
+        public boolean accept(Fixing.Column column) {
+            return true;
+        }
+    };
+
+    public static class NotFilter implements Fixing.Filter {
+        protected Fixing.Filter child;
+
+        public NotFilter(Fixing.Filter child) {
+            this.child = child;
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            return !child.accept(column);
+        }
+    } // class NotFilter
+
+    public static abstract class ComponentFilter implements Fixing.Filter {
+        protected List<Fixing.Filter> children;
+
+        public ComponentFilter() {
+            children = new ArrayList<Fixing.Filter>();
+        }
+
+        public ComponentFilter(List<Fixing.Filter> children) {
+            this.children = children;
+        }
+
+        public void add(Fixing.Filter filter) {
+            children.add(filter);
+        }
+    } // class ComponentFilter
+
+    public static class OrFilter extends ComponentFilter {
+
+        public OrFilter() {
+        }
+
+        public OrFilter(List<Fixing.Filter> children) {
+            super(children);
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            for (Fixing.Filter child: children) {
+                if (child.accept(column)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    } // class OrFilter
+
+    public static class AndFilter extends ComponentFilter {
+
+        public AndFilter() {
+        }
+
+        public AndFilter(List<Fixing.Filter> children) {
+            super(children);
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            for (Fixing.Filter child: children) {
+                if (!child.accept(column)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    } // class AndFilter
+
+    public static class IdFilter implements Fixing.Filter {
+
+        protected int columnId;
+
+        public IdFilter(int columnId) {
+            this.columnId = columnId;
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            return column.getId() == columnId;
+        }
+    } // class IdFilter
+
+    public static class DateFilter implements Fixing.Filter {
+
+        protected Date date;
+
+        public DateFilter(Date date) {
+            this.date = date;
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            return date.equals(column.getStartTime());
+        }
+    } // class DateFilter
+
+    public static class DateRangeFilter implements Fixing.Filter {
+
+        protected Date start;
+        protected Date end;
+
+        public DateRangeFilter(Date start, Date end) {
+            this.start = start;
+            this.end   = end;
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            Date date = column.getStartTime();
+            return start.compareTo(date) <= 0 && end.compareTo(date) >= 0;
+        }
+    } // class DateRangeFilter
+
+    public static class SectorRangeFilter implements Fixing.Filter {
+
+        protected int min;
+        protected int max;
+
+        public SectorRangeFilter(int min, int max) {
+            this.min = Math.min(min, max);
+            this.max = Math.max(min, max);
+        }
+
+        @Override
+        public boolean accept(Fixing.Column column) {
+            for (SectorRange s: column.getSectors()) {
+                int v = s.getSector();
+                if (v >= min && v <= max) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    } // class SectorRangeFilter
+
+    public void generateOverview(Document document) {
+        generateOverview(document, FULL_EXTENT, ACCEPT);
+    }
+
+    public List<Fixing.Column> filter(Range range, Fixing.Filter filter) {
+        List<Fixing.Column> allColumns = new ArrayList<Fixing.Column>();
+
+        for (Fixing fixing: fixings) {
+            fixing.addAllColumns(allColumns, range, filter);
+        }
+
+        Collections.sort(allColumns, Fixing.DATE_CMP);
+
+        return allColumns;
+    }
+
+    public void generateOverview(
+        Document      document,
+        Range         range,
+        Fixing.Filter filter
+    ) {
+        List<Fixing.Column> allColumns = filter(range, filter);
+
+        Element fixingsElement = document.createElement("fixings");
+
+        Element riverElement = document.createElement("river");
+
+        riverElement.setAttribute("from", String.valueOf(extent.start));
+        riverElement.setAttribute("to",   String.valueOf(extent.end));
+        riverElement.setAttribute("rid",  String.valueOf(riverId));
+        riverElement.setAttribute("name", riverName);
+
+        fixingsElement.appendChild(riverElement);
+
+        SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT);
+
+        Element esE = document.createElement("events");
+
+        for (Fixing.Column column: allColumns) {
+
+            List<SectorRange> sectors = column.getSectors(range);
+
+            if (!sectors.isEmpty()) {
+                Element eE = document.createElement("event");
+                eE.setAttribute("description",
+                    String.valueOf(column.getDescription()));
+                eE.setAttribute("cid", String.valueOf(column.columnId));
+                eE.setAttribute("date", df.format(column.startTime));
+
+                for (SectorRange sector: sectors) {
+                    Element sE = document.createElement("sector");
+
+                    sE.setAttribute("from",  String.valueOf(sector.start));
+                    sE.setAttribute("to",    String.valueOf(sector.end));
+                    sE.setAttribute("class", String.valueOf(sector.sector));
+
+                    eE.appendChild(sE);
+                }
+
+                esE.appendChild(eE);
+            }
+        }
+
+        fixingsElement.appendChild(esE);
+
+        document.appendChild(fixingsElement);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverviewFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+
+public class FixingsOverviewFactory
+{
+    private static Logger log = Logger.getLogger(FixingsOverviewFactory.class);
+
+    public static final String CACHE_NAME = "fixings-overviews";
+
+    private FixingsOverviewFactory() {
+    }
+
+
+    public static FixingsOverview getOverview(String river) {
+
+        boolean debug = log.isDebugEnabled();
+
+        if (debug) {
+            log.debug(
+                "Looking for fixings overview for river '" + river + "'");
+        }
+
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        if (cache == null) {
+            if (debug) {
+                log.debug("Cache not configured.");
+            }
+            return getUncached(river);
+        }
+
+        String key = "fix-over-" + river;
+
+        Element element = cache.get(key);
+
+        if (element != null) {
+            if (debug) {
+                log.debug("Overview found in cache");
+            }
+            return (FixingsOverview)element.getValue();
+        }
+
+        FixingsOverview overview = getUncached(river);
+
+        if (overview != null) {
+            if (debug) {
+                log.debug("Store overview in cache.");
+            }
+            cache.put(new Element(key, overview));
+        }
+
+        return overview;
+    }
+
+    public static FixingsOverview getUncached(String river) {
+        FixingsOverview overview = new FixingsOverview(river);
+
+        Session session = SessionHolder.HOLDER.get();
+
+        return overview.load(session) ? overview : null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FlowVelocityCalculation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,155 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.model.DischargeZone;
+import de.intevation.flys.model.FlowVelocityModel;
+import de.intevation.flys.model.FlowVelocityModelValue;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.MINFOArtifact;
+import de.intevation.flys.artifacts.model.FlowVelocityData;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class FlowVelocityCalculation extends Calculation {
+
+    private static final Logger logger =
+        Logger.getLogger(FlowVelocityCalculation.class);
+
+
+    public CalculationResult calculate(MINFOArtifact artifact) {
+        logger.info("FlowVelocityCalculation.calculate");
+
+        int[] mainIds  = artifact.getMainChannels();
+        int[] totalIds = artifact.getTotalChannels();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Artifact '" + artifact.identifier() + "' contains:");
+            if (mainIds != null) {
+                logger.debug("   " + mainIds.length + " main channel ids");
+            }
+
+            if (totalIds != null) {
+                logger.debug("   " + totalIds.length + " total channel ids");
+            }
+        }
+
+        List<DischargeZone>     zones  = getDischargeZones(mainIds, totalIds);
+        List<FlowVelocityModel> models = getFlowVelocityModels(artifact, zones);
+
+        return buildCalculationResult(artifact, models);
+    }
+
+
+    protected List<DischargeZone> getDischargeZones(
+        int[] mainIds,
+        int[] totalIds
+    ) {
+        List<DischargeZone> zones = new ArrayList<DischargeZone>();
+
+        if (mainIds != null) {
+            for (int id: mainIds) {
+                DischargeZone zone = DischargeZone.getDischargeZoneById(id);
+
+                if (zone != null) {
+                    zones.add(zone);
+                }
+            }
+        }
+
+        if (totalIds != null) {
+            for (int id: totalIds) {
+                DischargeZone zone = DischargeZone.getDischargeZoneById(id);
+
+                if (zone != null) {
+                    zones.add(zone);
+                }
+            }
+        }
+
+        return zones;
+    }
+
+
+    protected List<FlowVelocityModel> getFlowVelocityModels(
+        MINFOArtifact       artifact,
+        List<DischargeZone> zones
+    ) {
+        River river = FLYSUtils.getRiver(artifact);
+
+        List<FlowVelocityModel> models = new ArrayList<FlowVelocityModel>();
+
+        for (DischargeZone zone: zones) {
+            List<FlowVelocityModel> model =
+                FlowVelocityModel.getModels(river, zone);
+
+            if (model != null) {
+                models.addAll(model);
+            }
+        }
+
+        return models;
+    }
+
+
+    protected void prepareData(
+        FlowVelocityData  data,
+        FlowVelocityModel model,
+        double kmLo,
+        double kmHi
+    ) {
+        List<FlowVelocityModelValue> values =
+            FlowVelocityModelValue.getValues(model, kmLo, kmHi);
+
+        logger.debug("Found " + values.size() + " values for model.");
+
+        for (FlowVelocityModelValue value: values) {
+            data.addKM(value.getStation().doubleValue());
+            data.addQ(value.getQ().doubleValue());
+            data.addVTotal(value.getTotalChannel().doubleValue());
+            data.addVMain(value.getMainChannel().doubleValue());
+            data.addTauMain(value.getShearStress().doubleValue());
+        }
+
+        DischargeZone zone = model.getDischargeZone();
+        String        lo   = zone.getLowerDischarge();
+        String        hi   = zone.getUpperDischarge();
+
+        if (lo.equals(hi)) {
+            data.setZone(lo);
+        }
+        else {
+            data.setZone(lo + " - " + hi);
+        }
+    }
+
+
+    protected CalculationResult buildCalculationResult(
+        MINFOArtifact           artifact,
+        List<FlowVelocityModel> models
+    ) {
+        double kmLo = artifact.getDataAsDouble("ld_from");
+        double kmHi = artifact.getDataAsDouble("ld_to");
+
+        logger.debug("Prepare data for km range: " + kmLo + " - " + kmHi);
+
+        FlowVelocityData[] data = new FlowVelocityData[models.size()];
+
+        for (int i = 0, n = models.size(); i < n; i++) {
+            FlowVelocityData d = new FlowVelocityData();
+
+            prepareData(d, models.get(i), kmLo, kmHi);
+
+            data[i] = d;
+        }
+
+        logger.debug("Calculation contains " + data.length + " data items.");
+
+        return new CalculationResult(data, this);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FlowVelocityData.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,114 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import gnu.trove.TDoubleArrayList;
+
+
+public class FlowVelocityData implements Serializable {
+
+    private TDoubleArrayList km;
+    private TDoubleArrayList vMain;
+    private TDoubleArrayList vTotal;
+    private TDoubleArrayList tauMain;
+    private TDoubleArrayList q;
+    private String zone;
+
+
+    protected FlowVelocityData() {
+        this.km      = new TDoubleArrayList();
+        this.vMain   = new TDoubleArrayList();
+        this.vTotal  = new TDoubleArrayList();
+        this.tauMain = new TDoubleArrayList();
+        this.q       = new TDoubleArrayList();
+    }
+
+
+    public void addKM(double km) {
+        this.km.add(km);
+    }
+
+    public double getKM(int idx) {
+        return km.get(idx);
+    }
+
+    public void addVMain(double vMain) {
+        this.vMain.add(vMain);
+    }
+
+    public double getVMain(int idx) {
+        return vMain.get(idx);
+    }
+
+    public void addVTotal(double vTotal) {
+        this.vTotal.add(vTotal);
+    }
+
+    public double getVTotal(int idx) {
+        return vTotal.get(idx);
+    }
+
+    public void addTauMain(double tauMain) {
+        this.tauMain.add(tauMain);
+    }
+
+    public double getTauMain(int idx) {
+        return tauMain.get(idx);
+    }
+
+    public void addQ(double q) {
+        this.q.add(q);
+    }
+
+    public double getQ(int idx) {
+        return q.get(idx);
+    }
+
+    public void setZone(String zone) {
+        this.zone = zone;
+    }
+
+    public String getZone() {
+        return zone;
+    }
+
+    public int size() {
+        return km.size();
+    }
+
+
+    public double[][] getMainChannelPoints() {
+        double[][] points = new double[2][size()];
+
+        for (int i = 0, n = size(); i < n; i++) {
+            points[0][i] = getKM(i);
+            points[1][i] = getVMain(i);
+        }
+
+        return points;
+    }
+
+
+    public double[][] getTotalChannelPoints() {
+        double[][] points = new double[2][size()];
+
+        for (int i = 0, n = size(); i < n; i++) {
+            points[0][i] = getKM(i);
+            points[1][i] = getVTotal(i);
+        }
+
+        return points;
+    }
+
+
+    public double[][] getTauPoints() {
+        double[][] points = new double[2][size()];
+
+        for (int i = 0, n = size(); i < n; i++) {
+            points[0][i] = getKM(i);
+            points[1][i] = getTauMain(i);
+        }
+
+        return points;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FlowVelocityFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,64 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Facet of a FlowVelocity curve.
+ */
+public class FlowVelocityFacet extends DataFacet {
+
+    private static Logger logger = Logger.getLogger(FlowVelocityFacet.class);
+
+
+    public FlowVelocityFacet() {
+        // required for clone operation deepCopy()
+    }
+
+
+    public FlowVelocityFacet(
+        int         idx,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash
+    ) {
+        super(idx, name, description, type, hash, stateId);
+    }
+
+
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("Get data for flow velocity at index: " + index);
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        CalculationResult res = (CalculationResult)
+            flys.compute(context, hash, stateId, type, false);
+
+        FlowVelocityData[] data = (FlowVelocityData[]) res.getData();
+
+        return data[index];
+    }
+
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        FlowVelocityFacet copy = new FlowVelocityFacet();
+        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=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/GaugeDischargeFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,76 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * Access Discharge Curve of a gauge.
+ */
+public class GaugeDischargeFacet extends DataFacet {
+
+    private static final Logger logger =
+        Logger.getLogger(GaugeDischargeFacet.class);
+
+
+    public GaugeDischargeFacet() {
+    }
+
+
+    public GaugeDischargeFacet(int index, String name, String desc) {
+        super(index, name, desc, ComputeType.ADVANCE, null, null);
+    }
+
+
+    public GaugeDischargeFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateID,
+        String      hash
+
+    ) {
+        super(index, name, description, type, hash, stateID);
+    }
+
+
+    @Override
+    public Facet deepCopy() {
+        GaugeDischargeFacet copy = new GaugeDischargeFacet();
+        copy.set(this);
+        copy.type    = type;
+        copy.hash    = hash;
+        copy.stateId = stateId;
+        return copy;
+    }
+
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Get data for discharge curves at index: " +
+                index + " / stateId: " + stateId);
+        }
+
+        if (stateId == null) {
+            logger.error("GaugeDischargeFacet.getData: stateId is null.");
+        }
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        CalculationResult res = (CalculationResult)
+            flys.compute(context, hash, stateId, type, false);
+
+        WQKms[] discharge = (WQKms[]) res.getData();
+
+        return discharge[index];
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/GaugesFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,70 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.Range;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+public class GaugesFactory
+{
+    public static List<Gauge> getGauges(River river) {
+        return getGauges(river.getName());
+    }
+
+
+    public static Gauge getGauge(String gaugeName) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from Gauge where name=:name");
+        query.setParameter("name", gaugeName);
+
+        List<Gauge> res = query.list();
+
+        return res.isEmpty() ? null : res.get(0);
+    }
+
+
+    public static List<Gauge> getGauges(String river) {
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from Gauge where river.name=:name");
+        query.setParameter("name", river);
+        return query.list();
+    }
+
+    public static List<Gauge> filterRanges(
+        List<Gauge>     gauges,
+        List<double []> ranges
+    ) {
+        // XXX: Inefficent!
+        ArrayList<Range> rs = new ArrayList<Range>();
+        for (double [] range: ranges) {
+            double a = range[0];
+            double b = range[1];
+            rs.add(new Range(Math.min(a, b), Math.max(a, b), null));
+        }
+        return filter(gauges, rs);
+    }
+
+    public static List<Gauge> filter(List<Gauge> gauges, List<Range> ranges) {
+        // TODO: Make it an HQL filter!
+        ArrayList<Gauge> out = new ArrayList<Gauge>();
+        for (Gauge gauge: gauges) {
+            Range range = gauge.getRange();
+            for (Range cmp: ranges) {
+                if (range.intersects(cmp)) {
+                    out.add(gauge);
+                    break;
+                }
+            }
+        }
+        return out;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/HYKFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,76 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.HYKArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+import de.intevation.flys.model.FastCrossSectionLine;
+
+
+/**
+ * Trival Facet for HYKs
+ */
+public class HYKFacet
+extends      DataFacet
+implements   FacetTypes {
+
+    /** House logger. */
+    private static Logger logger = Logger.getLogger(HYKFacet.class);
+
+
+    /** Trivial constructor, set (maybe localized) description. */
+    public HYKFacet(int idx, String description) {
+        super(idx, HYK, description, ComputeType.FEED, null, null);
+    }
+
+
+    /**
+     * Set km from cross section- master to HYKArtifact, then fire up
+     * computation.
+     *
+     * @param art artifact to get data from.
+     * @param context ignored
+     */
+     public Object getData(Artifact art, CallContext context) {
+        logger.debug("HYKFacet.getData");
+
+        String dataKey = CrossSectionFacet.BLACKBOARD_CS_MASTER_DATA;
+
+        List<DataProvider> providers = context.getDataProvider(dataKey);
+        if (providers.size() < 1) {
+            logger.warn("Could not find Cross-Section data provider to get master cs km.");
+            return null;
+        }
+                        
+        FastCrossSectionLine crossSection = (FastCrossSectionLine) providers.get(0)
+            .provideData(dataKey, null, context);
+
+        double km = crossSection.getKm();
+        logger.debug("HYKFacet.getData: Master Cross Section is at km: " + km);
+
+        // Set this km at hyk artifact to be evaluated.
+        HYKArtifact hyk = (HYKArtifact) art;
+        hyk.setKm(km);
+
+        return hyk.compute(context, hash, stateId, type, false);
+    }
+
+
+    /** Do a deep copy. */
+    @Override 
+    public Facet deepCopy() {
+        HYKFacet copy = new HYKFacet(this.index, this.description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/HYKFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,215 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import java.io.Serializable;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+import org.hibernate.SQLQuery;
+import org.hibernate.type.StandardBasicTypes;
+
+import de.intevation.flys.model.HYK;
+import de.intevation.flys.model.HYKFormation;
+import de.intevation.flys.model.HYKFlowZone;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+
+/**
+ * Factory to access HYKs (hydrographic values).
+ */
+public class HYKFactory
+{
+    private static Logger logger = Logger.getLogger(HYKFactory.class);
+
+    public static String HYK_CACHE_NAME = "hykache";
+
+
+    /** Do not instantiate a HYKFactory. */
+    private HYKFactory() {
+    }
+
+
+    /**
+     * Get List of Zones for given river and km.
+     */
+    public static Object getHYKs(int hykid, double km) {
+        logger.debug("HYKFactory.getHYKs");
+
+        Cache cache = CacheFactory.getCache(HYK_CACHE_NAME);
+
+        String cacheKey;
+
+        if (cache != null) {
+            cacheKey = "" + hykid + "_" + km;
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                logger.debug("Got hyk from cache");
+                return (List<Zone>)element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        List<Zone> zones = getZonesUncached(hykid, km);
+
+        if (zones != null && cacheKey != null) {
+            logger.debug("Store hykzones in cache.");
+            Element element = new Element(cacheKey, zones);
+            cache.put(element);
+        }
+
+        return zones;
+    }
+
+
+    /** Return name for hyk with given id. */
+    public static String getHykName(int hykid) {
+        logger.debug("HYKFactory.getHykName " + hykid);
+
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "select description from HYK where id = :hykid ");
+        query.setParameter("hykid", hykid);
+
+        return (String) query.uniqueResult();
+    }
+
+
+    /**
+     * Ask DB for hyk zones.
+     * @param hykid ID of the 'main' HYK to query.
+     * @param km for which to get the hyk-zones.
+     * @return according zones.
+     */
+    public static List<Zone> getZonesUncached(int hykid, double km) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("HYKFactory.getZoneUncached " + hykid + " km " + km);
+        }
+
+        Session session = SessionHolder.HOLDER.get();
+
+        // Find out flow-direction of river.
+        // OPTIMIZE: 1) query kmUp directly 2) merge queries.
+        Query rQuery = session.createQuery("from HYK where id = :hykid");
+        rQuery.setParameter("hykid", hykid);
+        rQuery.setMaxResults(1);
+        HYK hyk = (HYK) rQuery.uniqueResult();
+
+        double flowDir = hyk.getRiver().getKmUp() ? 1 : -1;
+
+        List<HYKFormation> forms = getHYKFormations(hykid, km, flowDir);
+        List<Zone>         zones = new ArrayList<Zone>();
+
+        // Take the first one.
+        if (forms.size() >= 1) {
+            HYKFormation form = forms.get(0);
+            // Create respective zones.
+            for (HYKFlowZone flowZone: form.getZones()) {
+                Zone z = new Zone(flowZone.getA().doubleValue(),
+                    flowZone.getB().doubleValue(),
+                    flowZone.getType().getName());
+                zones.add(z);
+            }
+        }
+
+        return zones;
+    }
+
+
+    protected static List<HYKFormation> getHYKFormations(
+        int        hykid,
+        double     km,
+        double     flowDir
+    ) {
+        Session session = SessionHolder.HOLDER.get();
+
+        String SQL = "SELECT " +
+            "   f.id          AS FID, " +
+            "   f.distance_vl AS DIST, " +
+            "   e.hyk_id      AS HID, " +
+            "   e.km          AS KM " +
+            " FROM hyk_formations f INNER JOIN hyk_entries e " +
+            "   ON e.id = f.hyk_entry_id " +
+            " WHERE e.hyk_id = :hykid " +
+            "   AND :km between " +
+            "     LEAST(e.km, e.km + :flowDir*(f.distance_vl/1000.0+0.001)) " +
+            "   AND " +
+            "     GREATEST(e.km, e.km + :flowDir*(f.distance_vl/1000.0+0.001))";
+
+        SQLQuery sqlQuery = session.createSQLQuery(SQL)
+            .addScalar("FID", StandardBasicTypes.INTEGER)
+            .addScalar("DIST", StandardBasicTypes.DOUBLE)
+            .addScalar("HID", StandardBasicTypes.INTEGER)
+            .addScalar("KM", StandardBasicTypes.DOUBLE);
+
+        sqlQuery.setInteger("hykid", hykid);
+        sqlQuery.setDouble("flowDir", flowDir);
+        sqlQuery.setDouble("km", km);
+
+        logger.debug("HYK SQL: " + sqlQuery.getQueryString());
+
+        List<Object[]> results = sqlQuery.list();
+
+        logger.debug("Found " + results.size() + " HYKFormation IDs in DB.");
+
+        if (results == null || results.isEmpty()) {
+            logger.debug("No HYK found for ID " + hykid + " at km " + km);
+            return new ArrayList<HYKFormation>();
+        }
+
+        Object[] resultSet      = results.get(0);
+        Integer  hykFormationId = (Integer) resultSet[0];
+
+        Query query = session.createQuery("from HYKFormation where id = :id");
+        query.setParameter("id", hykFormationId);
+        query.setMaxResults(1);
+
+        return query.list();
+    }
+
+
+    /** Labelled section. */
+    public static class Zone implements Serializable {
+        /** Lower end of segment. */
+        protected double  from;
+        /** Upper end of segment. */
+        protected double  to;
+        /** The label. */
+        protected String name;
+
+        /** Constructor for labelled section. */
+        public Zone (double from, double to, String name) {
+            this.from = from;
+            this.to   = to;
+            this.name = name;
+        }
+
+        /** Get upper value. */
+        public double getTo() {
+            return to;
+        }
+
+        /** Get lower value. */
+        public double getFrom() {
+            return from;
+        }
+
+        /** Get name (type). */
+        public String getName() {
+            return name;
+        }
+    } // public static class Zone
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/HistoricalDischargeDifferenceFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,61 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeDifferenceFacet
+extends      HistoricalDischargeFacet
+{
+    private static final Logger logger =
+        Logger.getLogger(HistoricalDischargeDifferenceFacet.class);
+
+
+    public HistoricalDischargeDifferenceFacet(
+        int    index,
+        String name,
+        String desc
+    ) {
+        super(index, name, desc, ComputeType.ADVANCE, null, null);
+    }
+
+
+    public HistoricalDischargeDifferenceFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateID,
+        String      hash
+
+    ) {
+        super(index, name, description, type, hash, stateID);
+    }
+
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Get data for historical discharge difference curves" +
+                " at index: " + index + " / stateId: " + stateId);
+        }
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        CalculationResult res = (CalculationResult)
+            flys.compute(context, hash, stateId, type,  false);
+
+        HistoricalWQTimerange[] wqts = (HistoricalWQTimerange[]) res.getData();
+
+        return wqts[index];
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/HistoricalDischargeFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeFacet extends DataFacet {
+
+    private static final Logger logger =
+        Logger.getLogger(HistoricalDischargeFacet.class);
+
+
+    public HistoricalDischargeFacet() {
+    }
+
+
+    public HistoricalDischargeFacet(int index, String name, String desc) {
+        super(index, name, desc, ComputeType.ADVANCE, null, null);
+    }
+
+
+    public HistoricalDischargeFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateID,
+        String      hash
+
+    ) {
+        super(index, name, description, type, hash, stateID);
+    }
+
+
+    @Override
+    public Facet deepCopy() {
+        WaterlevelFacet copy = new WaterlevelFacet();
+        copy.set(this);
+        copy.type    = type;
+        copy.hash    = hash;
+        copy.stateId = stateId;
+        return copy;
+    }
+
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Get data for historical discharge curves at index: " +
+                index + " / stateId: " + stateId);
+        }
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        CalculationResult res = (CalculationResult)
+            flys.compute(context, hash, stateId, type,  false);
+
+        WQTimerange[] wqts = (WQTimerange[]) res.getData();
+
+        return wqts[index];
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/HistoricalWQTimerange.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,49 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+
+/**
+ * A subclass of WQTimerange that stores besides W, Q and Timerange values
+ * another double value.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalWQTimerange extends WQTimerange {
+
+    protected TDoubleArrayList diffs;
+
+
+    public HistoricalWQTimerange(String name) {
+        super(name);
+
+        diffs = new TDoubleArrayList();
+    }
+
+
+    public void add(double w, double q, double diff, Timerange t) {
+        ws.add(w);
+        qs.add(q);
+        ts.add(t);
+        diffs.add(diff);
+    }
+
+
+    /**
+     * This method requires a 3dim double array for <i>res</i>!
+     */
+    @Override
+    public double[] get(int idx, double[] res) {
+        res[0] = ws.getQuick(idx);
+        res[1] = qs.getQuick(idx);
+        res[2] = diffs.getQuick(idx);
+
+        return res;
+    }
+
+
+    public double[] getDiffs() {
+        return diffs.toNativeArray();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/LayerInfo.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,163 @@
+package de.intevation.flys.artifacts.model;
+
+
+public class LayerInfo {
+
+    protected String name;
+    protected String type;
+    protected String directory;
+    protected String data;
+    protected String connection;
+    protected String connectionType;
+    protected String extent;
+    protected String srid;
+    protected String group;
+    protected String groupTitle;
+    protected String title;
+    protected String style;
+    protected String filter;
+    protected String labelItem;
+
+
+    public LayerInfo() {
+    }
+
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+
+    public String getType() {
+        return type;
+    }
+
+
+    public void setDirectory(String directory) {
+        this.directory = directory;
+    }
+
+
+    public String getDirectory() {
+        return directory;
+    }
+
+
+    public void setData(String data) {
+        this.data = data;
+    }
+
+
+    public String getData() {
+        return data;
+    }
+
+
+    public void setConnection(String connection) {
+        this.connection = connection;
+    }
+
+
+    public String getConnection() {
+        return connection;
+    }
+
+
+    public void setConnectionType(String connectionType) {
+        this.connectionType = connectionType;
+    }
+
+
+    public String getConnectionType() {
+        return connectionType;
+    }
+
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+
+    public String getGroup() {
+        return group;
+    }
+
+
+    public void setGroupTitle(String groupTitle) {
+        this.groupTitle = groupTitle;
+    }
+
+
+    public String getGroupTitle() {
+        return groupTitle;
+    }
+
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+
+    public String getTitle() {
+        return title;
+    }
+
+
+    public void setExtent(String extent) {
+        this.extent = extent;
+    }
+
+
+    public String getExtent() {
+        return extent;
+    }
+
+
+    public void setSrid(String srid) {
+        this.srid = srid;
+    }
+
+
+    public String getSrid() {
+        return srid;
+    }
+
+
+    public void setStyle(String style) {
+        this.style = style;
+    }
+
+
+    public String getStyle() {
+        return style;
+    }
+
+
+    public void setFilter(String filter) {
+        this.filter = filter;
+    }
+
+
+    public String getFilter() {
+        return filter;
+    }
+
+    public void setLabelItem(String labelItem) {
+        this.labelItem = labelItem;
+    }
+
+    public String getLabelItem() {
+        return labelItem;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/LocationProvider.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,102 @@
+package de.intevation.flys.artifacts.model;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.model.Annotation;
+import de.intevation.flys.model.FastAnnotations;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.artifacts.model.AnnotationsFactory;
+
+public class LocationProvider {
+
+    private static final Logger log =
+        Logger.getLogger(LocationProvider.class);
+
+
+    public static final String CACHE_KEY = "location-provider";
+
+    public static final String PREFIX = "lp-";
+
+
+    private LocationProvider() {
+    }
+
+    public static String getLocation(String river, double km) {
+
+        FastAnnotations fas = getAnnotations(river, km);
+
+        FastAnnotations.Annotation an = fas.findByKm(km);
+
+        return an != null ? an.getPosition() : null;
+    }
+
+    public static FastAnnotations getAnnotations(String river) {
+        return getAnnotations(river, Double.MAX_VALUE);
+    }
+
+    protected static FastAnnotations getAnnotations(String river, double km) {
+
+        Cache cache = CacheFactory.getCache(CACHE_KEY);
+
+        if (cache == null) {
+            return uncachedAnnotations(river, km);
+        }
+
+        String key = PREFIX + river;
+
+        Element element = cache.get(key);
+
+        if (element != null) {
+            return (FastAnnotations)element.getValue();
+        }
+
+        FastAnnotations fas = uncachedAnnotations(river, Double.MAX_VALUE);
+
+        cache.put(new Element(key, fas));
+
+        return fas;
+    }
+
+    protected static FastAnnotations uncachedAnnotations(
+        String river,
+        double km
+    ) {
+        if (km != Double.MAX_VALUE) {
+            // XXX Fake it by using a standard Annotation.
+
+            Annotation annotation =
+                AnnotationsFactory.getAnnotation(river, km);
+
+            if (annotation != null) {
+                FastAnnotations.Annotation fa =
+                    new FastAnnotations.Annotation(
+                        km, Double.NaN,
+                        annotation.getPosition().getValue(), null, null,
+                        Double.NaN, Double.NaN);
+                return new FastAnnotations(
+                    new FastAnnotations.Annotation [] { fa });
+            }
+
+            return new FastAnnotations(new FastAnnotations.Annotation[0]);
+        }
+
+        long startTime = System.currentTimeMillis();
+
+        FastAnnotations fas = new FastAnnotations(river);
+
+        long stopTime = System.currentTimeMillis();
+
+        if (log.isDebugEnabled()) {
+            log.debug("Loading locations took " +
+                (stopTime-startTime)/1000f + " secs.");
+        }
+
+        return fas;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MainValuesFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,80 @@
+package de.intevation.flys.artifacts.model;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValue;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MainValuesFactory {
+
+    private static Logger logger = Logger.getLogger(MainValuesFactory.class);
+
+    public static List<MainValue> getMainValues(Gauge gauge) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from MainValue where gauge=:gauge");
+        query.setParameter("gauge", gauge);
+
+        return query.list();
+    }
+
+
+    /**
+     * Returns an array of [days, qs] necessary to create duration curves.
+     *
+     * @param gauge The selected gauge.
+     *
+     * @return a 2dim array of [days, qs] where days is an int[] and qs is
+     * an double[].
+     */
+    public static Object[] getDurationCurveData(Gauge gauge) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "select cast(nmv.name as integer) as days, mv.value as q " +
+            "from MainValue as mv " +
+            "join mv.mainValue as nmv " +
+            "join nmv.type mvt " +
+            "where mvt.name = 'D' and mv.gauge.id = :gauge_id " +
+            "order by days");
+
+        query.setParameter("gauge_id", gauge.getId());
+
+        List<Object> results = query.list();
+        int[]        days    = new int[results.size()];
+        double[]     qs      = new double[results.size()];
+
+        int idx = 0;
+
+        for (Object obj: results) {
+            Object[] arr = (Object[]) obj;
+
+            try {
+                int  day = ((Integer)    arr[0]).intValue();
+                double q = ((BigDecimal) arr[1]).doubleValue();
+
+                days[idx] = day;
+                qs[idx++] = q;
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        return new Object[] { days, qs };
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MainValuesQFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,154 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+
+import de.intevation.flys.artifacts.MainValuesArtifact;
+import de.intevation.flys.artifacts.math.Linear;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQDay;
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StickyAxisAnnotation;
+
+import de.intevation.flys.exports.DurationCurveGenerator;
+
+
+/**
+ * Facet to show Main Q Values.
+ * TODO Join with W implementation.
+ */
+public class MainValuesQFacet
+extends      DefaultFacet
+implements   FacetTypes {
+
+    /** Own logger. */
+    private static Logger logger = Logger.getLogger(RelativePointFacet.class);
+
+    /** Do we want MainValues at Gauge (not interpolated)? */
+    protected boolean isAtGauge;
+
+    /** Trivial Constructor. */
+    public MainValuesQFacet(String name, String description, boolean atGauge) {
+        this.description = description;
+        this.name        = name;
+        this.index       = 0;
+        this.isAtGauge   = atGauge;
+    }
+
+    /**
+     * Set the hit-point in Q where a line drawn from the axis would hit the
+     * curve in WQDay (if hit).
+     * Employ linear interpolation.
+     */
+    protected static void setHitPoint(WQDay wqday, StickyAxisAnnotation annotation) {
+        int idx = 0;
+        float q = annotation.getPos();
+        boolean qIncreases = wqday.getQ(0) < wqday.getQ(wqday.size()-1);
+        if (qIncreases) {
+            while (idx < wqday.size() && wqday.getQ(idx) < q) {
+                idx++;
+            }
+        }
+        else {
+            idx = wqday.size() -1;
+            while (idx > 0 && wqday.getQ(idx) > q) {
+                idx--;
+            }
+        }
+
+        double day = 0d;
+        int mod = (qIncreases) ? -1 : +1;
+        if (idx != 0 && idx <= wqday.size()-1) {
+            day = Linear.linear(q, wqday.getQ(idx +mod), wqday.getQ(idx),
+                wqday.getDay(idx+mod), wqday.getDay(idx));
+            annotation.setHitPoint((float)day);
+        }
+        else {
+            logger.debug("StickyAnnotation does not hit wqday curve");
+        }
+    }
+
+
+    /**
+     * Returns the data this facet requires.
+     *
+     * @param artifact the owner artifact.
+     * @param context  the CallContext (ignored).
+     *
+     * @return the data.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        MainValuesArtifact mvArtifact = (MainValuesArtifact) artifact;
+
+        List<NamedDouble>          qs = mvArtifact.getMainValuesQ(isAtGauge);
+        List<StickyAxisAnnotation> xy = new ArrayList<StickyAxisAnnotation>();
+
+        WQDay wqdays = null;
+        List<DataProvider> providers = context.
+            getDataProvider(DurationCurveFacet.BB_DURATIONCURVE);
+        if (providers.size() < 1) {
+            logger.warn("Could not find durationcurve data provider.");
+        }
+        else {
+            wqdays = (WQDay) providers.get(0).provideData(
+                DurationCurveFacet.BB_DURATIONCURVE,
+                null,
+                context);
+        }
+
+        // Rather specific case, Q-Annotations at a maybe second yaxis.
+        StickyAxisAnnotation annotation = null;
+        if (this.name.equals(DURATION_MAINVALUES_Q)) {
+            for (NamedDouble q: qs) {
+                annotation =
+                    new StickyAxisAnnotation(
+                        q.getName(),
+                        (float) q.getValue(),
+                        StickyAxisAnnotation.SimpleAxis.Y_AXIS,
+                        DurationCurveGenerator.YAXIS.Q.idx);
+                xy.add(annotation);
+                if (wqdays != null) {
+                    setHitPoint(wqdays, annotation);
+                }
+            }
+        }
+        else {
+            for (NamedDouble q: qs) {
+                annotation =
+                    new StickyAxisAnnotation(
+                        q.getName(),
+                        (float) q.getValue(),
+                        StickyAxisAnnotation.SimpleAxis.X_AXIS);
+                xy.add(annotation);
+                if (wqdays != null) {
+                    setHitPoint(wqdays, annotation);
+                }
+            }
+        }
+
+        return new FLYSAnnotation(description, xy);
+    }
+
+
+    /**
+     * Create a deep copy of this Facet.
+     * @return a deep copy.
+     */
+    @Override
+    public MainValuesQFacet deepCopy() {
+        MainValuesQFacet copy = new MainValuesQFacet(this.name,
+            description, this.isAtGauge);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MainValuesWFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,135 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+
+import de.intevation.flys.artifacts.MainValuesArtifact;
+import de.intevation.flys.artifacts.math.Linear;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQDay;
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StickyAxisAnnotation;
+
+
+/**
+ * Facet to show Main W Values.
+ */
+public class MainValuesWFacet
+extends      DefaultFacet
+implements   FacetTypes {
+
+    /** Own logger. */
+    private static Logger logger = Logger.getLogger(RelativePointFacet.class);
+
+    /** Do we want MainValues at Gauge (not interpolated)? */
+    protected boolean isAtGauge;
+
+    /** Trivial Constructor. */
+    public MainValuesWFacet(String name, String description, boolean atGauge) {
+        this.description = description;
+        this.name = name;
+        this.index = 0;
+        this.isAtGauge = atGauge;
+    }
+
+
+    /**
+     * Set the hit-point in W where a line drawn from the axis would hit the
+     * curve in WQDay (if hit).
+     * Employ linear interpolation.
+     */
+    protected static void setHitPoint(WQDay wqday, StickyAxisAnnotation annotation) {
+        int idx = 0;
+        float w = annotation.getPos();
+        boolean wIncreases = wqday.getW(0) < wqday.getW(wqday.size()-1);
+        if (wIncreases) {
+            while (idx < wqday.size() && wqday.getW(idx) < w) {
+                idx++;
+            }
+        }
+        else {
+            idx = wqday.size() -1;
+            while (idx > 0 && wqday.getW(idx) > w) {
+                idx--;
+            }
+        }
+
+        double day = 0d;
+        int mod = (wIncreases) ? -1 : +1;
+        if (idx != 0 && idx < wqday.size()-1-mod) {
+            day = Linear.linear(w, wqday.getW(idx +mod), wqday.getW(idx),
+                wqday.getDay(idx+mod), wqday.getDay(idx));
+            annotation.setHitPoint((float)day);
+        }
+        else {
+            logger.debug("StickyAnnotation does not hit wqday curve");
+        }
+    }
+
+
+    /**
+     * Returns the data this facet requires.
+     *
+     * @param artifact the owner artifact.
+     * @param context  the CallContext (ignored).
+     *
+     * @return the data.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        MainValuesArtifact mvArtifact = (MainValuesArtifact) artifact;
+
+        List<NamedDouble>      ws = mvArtifact.getMainValuesW(isAtGauge);
+        List<StickyAxisAnnotation> xy = new ArrayList<StickyAxisAnnotation>();
+
+        // Find whether a duration curve is on the blackboard.
+        WQDay wqdays = null;
+        List<DataProvider> providers = context.
+            getDataProvider(DurationCurveFacet.BB_DURATIONCURVE);
+        if (providers.size() < 1) {
+            logger.warn("Could not find durationcurve data provider.");
+        }
+        else {
+            wqdays = (WQDay) providers.get(0).provideData(
+                DurationCurveFacet.BB_DURATIONCURVE,
+                null,
+                context);
+        }
+
+        for (NamedDouble w: ws) {
+            StickyAxisAnnotation annotation =
+                new StickyAxisAnnotation(
+                    w.getName(),
+                    (float) w.getValue(),
+                    StickyAxisAnnotation.SimpleAxis.Y_AXIS);
+            xy.add(annotation);
+            if (wqdays != null) {
+                setHitPoint(wqdays, annotation);
+            }
+        }
+
+        return new FLYSAnnotation(description, xy);
+    }
+
+
+    /**
+     * Create a deep copy of this Facet.
+     * @return a deep copy.
+     */
+    @Override
+    public MainValuesWFacet deepCopy() {
+        MainValuesWFacet copy = new MainValuesWFacet(this.name,
+            description, this.isAtGauge);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ManagedDomFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,179 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+
+/** 
+ * Use an Element (DOM) to store the information about a facet.
+ * The intent of this facet type is to represent a facet
+ * stored in an Collection attribute. Different facets can have different
+ * attributes that we need to parse, but the only thing ManagedFacets need
+ * to do, is to adjust the attributes "active" and "position". So, those
+ * values are set directly on the Element, the other attributes aren't
+ * touched.
+ */
+public class ManagedDomFacet extends ManagedFacet {
+
+    protected Element facet;
+
+    private static Logger logger = Logger.getLogger(ManagedDomFacet.class);
+
+
+    public ManagedDomFacet(Element facet) {
+        super(null, -1, null, null, -1, -1, -1);
+
+        this.facet = facet;
+    }
+
+
+    @Override
+    public int getIndex() {
+        if (this.index < 0) {
+            String index = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI, "index");
+
+            if (index != null && index.length() > 0) {
+                this.index = Integer.parseInt(index);
+            }
+        }
+
+        return this.index;
+    }
+
+
+    @Override
+    public String getName() {
+        if (this.name == null || this.name.length() == 0) {
+            String name = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI, "facet");
+
+            this.name = name;
+        }
+
+        return this.name;
+    }
+
+
+    @Override
+    public String getDescription() {
+        if (this.description == null || this.description.length() == 0) {
+            String description = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI, "description");
+
+            this.description = description;
+        }
+
+        return this.description;
+    }
+
+
+    @Override
+    public int getPosition() {
+        if (this.position < 0) {
+            String position = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI,
+                "pos");
+
+            if (position != null && position.length() > 0) {
+                this.position = Integer.parseInt(position);
+            }
+        }
+
+        return this.position;
+    }
+
+
+    @Override
+    public void setPosition(int position) {
+        this.position = position;
+
+        // TODO Evaluate whether other set/getAttributes also need
+        // to use the NAMESPACE_PREFIX.
+        facet.setAttributeNS(
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX + ":" + "pos",
+            String.valueOf(position));
+    }
+
+
+    @Override
+    public int getActive() {
+        if (this.active < 0) {
+            String active = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI, "active");
+
+            if (active != null && active.length() > 0) {
+                this.active = Integer.parseInt(active);
+            }
+        }
+
+        return this.active;
+    }
+
+
+    @Override
+    public void setActive(int active) {
+        this.active = active;
+
+        facet.setAttributeNS(
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            "art:active",
+            String.valueOf(active));
+    }
+
+
+    @Override
+    public int getVisible() {
+        if (this.visible < 0) {
+            String visible = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI, "visible");
+
+            if (visible != null && visible.length() > 0) {
+                this.visible = Integer.parseInt(visible);
+            }
+        }
+
+        return this.visible;
+    }
+
+
+    @Override
+    public void setVisible(int visible) {
+        this.visible = visible;
+
+        facet.setAttributeNS(
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            "visible",
+            String.valueOf(getVisible()));
+    }
+
+
+    @Override
+    public String getArtifact() {
+        if (this.uuid == null || this.uuid.length() == 0) {
+            String uuid = facet.getAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI, "artifact");
+
+            this.uuid = uuid;
+        }
+
+        return this.uuid;
+    }
+
+
+    /**
+     * Import into document.
+     * @param doc Document to be imported to.
+     */
+    @Override
+    public Node toXML(Document doc) {
+        return doc.importNode(facet, true);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ManagedFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,134 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+
+/**
+ * Facet with user-supplied theme-control-information (pos in list,
+ * active/disabled etc) attached.
+ */
+public class ManagedFacet extends DefaultFacet {
+
+    /** The uuid of the owner artifact. */
+    protected String uuid;
+
+    /** A property that determines the position of this facet. */
+    protected int position;
+
+    /** A property that determines if this facet is active or not. */
+    protected int active;
+
+    /** The logger that is used in this class. */
+    private static Logger logger = Logger.getLogger(ManagedFacet.class);
+
+    /** A property that determines if this facet is visible or not. */
+    protected int visible;
+
+
+    public ManagedFacet() {
+    }
+
+    public ManagedFacet(
+        String  name,
+        int     index,
+        String  desc,
+        String  uuid,
+        int     pos,
+        int     active,
+        int     visible)
+    {
+        super(index, name, desc);
+
+        this.uuid     = uuid;
+        this.position = pos;
+        this.active   = active;
+        this.visible  = visible;
+    }
+
+
+    /**
+     * Sets position (will be merged to position in ThemeList).
+     */
+    public void setPosition(int pos) {
+        this.position = pos;
+    }
+
+
+    public int getPosition() {
+        return position;
+    }
+
+
+    public void setActive(int active) {
+        this.active = active;
+    }
+
+
+    public int getActive() {
+        return active;
+    }
+
+
+    public void setVisible(int visible) {
+        this.visible = visible;
+    }
+
+
+    public int getVisible() {
+        return visible;
+    }
+
+
+    /**
+     * Get uuid of related artifact.
+     * @return uuid of related artifact.
+     */
+    public String getArtifact() {
+        return uuid;
+    }
+
+
+    public Node toXML(Document doc) {
+        ElementCreator ec = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element facet = ec.create("theme");
+        ec.addAttr(facet, "artifact", getArtifact(), true);
+        ec.addAttr(facet, "facet", getName(), true);
+        ec.addAttr(facet, "pos", String.valueOf(getPosition()), true);
+        ec.addAttr(facet, "active", String.valueOf(getActive()), true);
+        ec.addAttr(facet, "index", String.valueOf(getIndex()), true);
+        ec.addAttr(facet, "description", getDescription(), true);
+        ec.addAttr(facet, "visible", String.valueOf(getVisible()), true);
+
+        return facet;
+    }
+
+    public void set(ManagedFacet other) {
+        uuid     = other.uuid;
+        position = other.position;
+        active   = other.active;
+    }
+
+    @Override 
+    public Facet deepCopy() {
+        ManagedFacet copy = new ManagedFacet();
+        copy.set((DefaultFacet)this);
+        copy.set((ManagedFacet)this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ManagedFacetAdapter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,72 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+
+public class ManagedFacetAdapter extends ManagedFacet {
+
+    protected Facet facet;
+
+    public ManagedFacetAdapter() {
+    }
+
+
+    protected Logger logger = Logger.getLogger(ManagedFacetAdapter.class);
+
+    public ManagedFacetAdapter(
+        Facet   facet,
+        String  uuid,
+        int     pos,
+        int     active,
+        int     visible
+    ) {
+        super(
+            facet.getName(),
+            facet.getIndex(),
+            facet.getDescription(),
+            uuid,
+            pos,
+            active,
+            visible);
+
+        this.facet = facet;
+    }
+
+
+    @Override
+    public Node toXML(Document doc) {
+        ElementCreator ec = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element e = (Element) facet.toXML(doc);
+        ec.addAttr(e, "artifact", getArtifact(), true);
+        ec.addAttr(e, "facet", getName(), true);
+        ec.addAttr(e, "pos", String.valueOf(getPosition()), true);
+        ec.addAttr(e, "active", String.valueOf(getActive()), true);
+        ec.addAttr(e, "visible", String.valueOf(getVisible()), true);
+
+        return e;
+    }
+
+    @Override
+    public Facet deepCopy() {
+        ManagedFacetAdapter copy = new ManagedFacetAdapter();
+        copy.set((DefaultFacet)this);
+        copy.set((ManagedFacet)this);
+        copy.facet = facet.deepCopy();
+        return facet;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ManualPointsFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,64 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.ManualPointsArtifact;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+
+/**
+ * Facet to access ManualPoints that where added by user.
+ */
+public class ManualPointsFacet
+extends      DefaultFacet
+{
+    /** Logger for this class. */
+    private static final Logger logger = Logger.getLogger(ManualPointsFacet.class);
+
+
+    /**
+     * Trivial Constructor.
+     */
+    public ManualPointsFacet() {
+    }
+
+
+    /**
+     * Trivial Constructor for a ManualPointsFacet.
+     *
+     * @param index       Database-Index to use.
+     * @param name        Name (~type) of Facet.
+     * @param description Description of Facet.
+     */
+    public ManualPointsFacet(int index, String name, String description) {
+        super(index, name, description);
+    }
+
+
+    /**
+     * Get List of ManualPointss for river from Artifact.
+     *
+     * @param artifact (ManualPoints-)Artifact to query for list of ManualPoints.
+     * @param context  Ignored.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        ManualPointsArtifact pointsArtifact = (ManualPointsArtifact) artifact;
+        return pointsArtifact.getPointsData(this.name);
+    }
+
+
+    /** Do a deep copy. */
+    @Override
+    public Facet deepCopy() {
+        ManualPointsFacet copy = new ManualPointsFacet();
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MapserverStyle.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,152 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class MapserverStyle {
+
+    public static class Clazz {
+        protected List<ClazzItem> items;
+        protected String    name;
+
+        public Clazz(String name) {
+            this.name  = name;
+            this.items = new ArrayList<ClazzItem>();
+        }
+
+        public void addItem(ClazzItem item) {
+            if (item != null) {
+                items.add(item);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("CLASS\n");
+            sb.append("NAME \"" + name + "\"\n");
+
+            for (ClazzItem item: items) {
+                item.toString(sb);
+            }
+
+            sb.append("END\n");
+
+            return sb.toString();
+        }
+    }
+
+    public interface ClazzItem {
+        void toString(StringBuilder sb);
+    }
+
+    public static class Style implements ClazzItem {
+        protected String color;
+        protected String outlinecolor;
+        protected String symbol;
+        protected int    size;
+
+        public void setColor(String color) {
+            this.color = color;
+        }
+
+        public void setOutlineColor(String outlinecolor) {
+            this.outlinecolor = outlinecolor;
+        }
+
+        public void setSize(int size) {
+            this.size = size;
+        }
+
+        public void setSymbol(String symbol) {
+            if (symbol != null && symbol.length() > 0) {
+                this.symbol = symbol;
+            }
+        }
+
+        public void toString(StringBuilder sb) {
+            sb.append("STYLE\n");
+            sb.append("WIDTH " + String.valueOf(size) + "\n");
+
+            if (outlinecolor != null) {
+                sb.append("OUTLINECOLOR " + outlinecolor + "\n");
+            }
+
+            if (color != null) {
+                sb.append("COLOR " + color + "\n");
+            }
+
+            if (symbol != null) {
+                sb.append("SYMBOL '" + symbol + "'\n");
+            }
+
+            sb.append("END\n");
+        }
+    } // end of Style
+
+    public static class Label implements ClazzItem {
+        protected String color;
+        protected int    size;
+
+        public void setColor(String color) {
+            this.color = color;
+        }
+
+        public void setSize(int size) {
+            this.size = size;
+        }
+
+        @Override
+        public void toString(StringBuilder sb) {
+            sb.append("LABEL\n");
+            sb.append("ANGLE auto\n");
+            sb.append("SIZE " + String.valueOf(size) + "\n");
+            sb.append("COLOR " + color + "\n");
+            sb.append("TYPE truetype\n");
+            sb.append("FONT DefaultFont\n");
+            sb.append("POSITION ur\n");
+            sb.append("OFFSET 2 2\n");
+            sb.append("END\n");
+        }
+    }
+
+    public static class Expression implements ClazzItem {
+        protected String value;
+
+        public Expression(String value) {
+            this.value = value;
+        }
+
+        @Override
+        public void toString(StringBuilder sb) {
+            sb.append("EXPRESSION " + value);
+            sb.append("\n");
+        }
+    }
+
+
+    protected List<Clazz> classes;
+
+
+    public MapserverStyle() {
+        classes = new ArrayList<Clazz>();
+    }
+
+    public void addClazz(Clazz clazz) {
+        if (clazz != null) {
+            classes.add(clazz);
+        }
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        for (Clazz clazz: classes) {
+            sb.append(clazz.toString());
+        }
+
+        return sb.toString();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightCalculation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,184 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.common.utils.DateUtils;
+
+import de.intevation.flys.model.BedHeightEpoch;
+import de.intevation.flys.model.BedHeightEpochValue;
+import de.intevation.flys.model.BedHeightSingle;
+import de.intevation.flys.model.BedHeightSingleValue;
+import de.intevation.flys.model.TimeInterval;
+
+import de.intevation.flys.artifacts.MINFOArtifact;
+import de.intevation.flys.artifacts.model.MiddleBedHeightData;
+
+
+public class MiddleBedHeightCalculation extends Calculation {
+
+    private static final Logger logger =
+        Logger.getLogger(MiddleBedHeightCalculation.class);
+
+
+    public CalculationResult calculate(MINFOArtifact artifact) {
+        logger.info("MiddleBedHeightCalculation.calculate");
+
+        int[] singleIds = artifact.getBedHeightSingleIDs();
+        int[] epochIds  = artifact.getBedHeightEpochIDs();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Artifact '" + artifact.identifier() + "' contains:");
+            if (singleIds != null) {
+                logger.debug("   " + singleIds.length + " single bedheight ids");
+            }
+
+            if (epochIds != null) {
+                logger.debug("   " + epochIds.length + " epoch bedheight ids");
+            }
+        }
+
+        List<BedHeightSingle> singles = getSingles(artifact, singleIds);
+        List<BedHeightEpoch>  epochs  = getEpochs(artifact, epochIds);
+
+        return buildCalculationResult(artifact, singles, epochs);
+    }
+
+
+    protected List<BedHeightSingle> getSingles(MINFOArtifact minfo, int[] ids) {
+        List<BedHeightSingle> singles = new ArrayList<BedHeightSingle>();
+
+        for (int id: ids) {
+            BedHeightSingle s = BedHeightSingle.getBedHeightSingleById(id);
+
+            if (s != null) {
+                singles.add(s);
+            }
+            else {
+                logger.warn("Cannot find Sngle by id: " + id);
+                // TODO ADD WARNING
+            }
+        }
+
+        return singles;
+    }
+
+
+    protected List<BedHeightEpoch> getEpochs(MINFOArtifact minfo, int[] ids) {
+        List<BedHeightEpoch> epochs = new ArrayList<BedHeightEpoch>();
+
+        for (int id: ids) {
+            BedHeightEpoch e = BedHeightEpoch.getBedHeightEpochById(id);
+
+            if (e != null) {
+                epochs.add(e);
+            }
+            else {
+                logger.warn("Cannot find Epoch by id: " + id);
+                // TODO ADD WARNING
+            }
+        }
+
+        return epochs;
+    }
+
+
+    protected CalculationResult buildCalculationResult(
+        MINFOArtifact         artifact,
+        List<BedHeightSingle> singles,
+        List<BedHeightEpoch>  epochs
+    ) {
+        logger.info("MiddleBedHeightCalculation.buildCalculationResult");
+
+        int    size = singles.size() + epochs.size();
+        double kmLo = artifact.getDataAsDouble("ld_from");
+        double kmHi = artifact.getDataAsDouble("ld_to");
+
+        List<MiddleBedHeightData> data = new ArrayList<MiddleBedHeightData>();
+
+        for (BedHeightSingle single: singles) {
+            MiddleBedHeightData d = prepareSingleData(single, kmLo, kmHi);
+
+            if (d != null) {
+                data.add(d);
+            }
+        }
+
+        for (BedHeightEpoch epoch: epochs) {
+            MiddleBedHeightData d = prepareEpochData(epoch, kmLo, kmHi);
+
+            if (d != null) {
+                data.add(d);
+            }
+        }
+
+        logger.debug("Calculation results in " + data.size() + " data objects.");
+
+        return new CalculationResult((MiddleBedHeightData[])
+            data.toArray(new MiddleBedHeightData[data.size()]), this);
+    }
+
+
+    protected MiddleBedHeightData prepareSingleData(
+        BedHeightSingle single,
+        double kmLo,
+        double kmHi
+    ) {
+        logger.debug("Prepare data for single: " + single.getDescription());
+
+        List<BedHeightSingleValue> values =
+            BedHeightSingleValue.getBedHeightSingleValues(single, kmLo, kmHi);
+
+        MiddleBedHeightData data = new MiddleBedHeightData(
+            single.getYear(),
+            single.getYear(),
+            single.getEvaluationBy(),
+            single.getDescription());
+
+        for (BedHeightSingleValue value: values) {
+            data.addKM(value.getStation().doubleValue());
+            data.addMiddleHeight(value.getHeight().doubleValue());
+            data.addUncertainty(value.getUncertainty().doubleValue());
+            data.addSoundingWidth(value.getSoundingWidth().doubleValue());
+            data.addDataGap(value.getDataGap().doubleValue());
+            data.addWidth(value.getWidth().doubleValue());
+        }
+
+        logger.debug("Single contains " + values.size() + " values");
+
+        return data;
+    }
+
+
+    protected MiddleBedHeightData prepareEpochData(
+        BedHeightEpoch epoch,
+        double kmLo,
+        double kmHi
+    ) {
+        logger.debug("Prepare data for epoch: " + epoch.getDescription());
+
+        TimeInterval ti = epoch.getTimeInterval();
+
+        List<BedHeightEpochValue> values =
+            BedHeightEpochValue.getBedHeightEpochValues(epoch, kmLo, kmHi);
+
+        MiddleBedHeightData data = new MiddleBedHeightData(
+            DateUtils.getYearFromDate(ti.getStartTime()),
+            DateUtils.getYearFromDate(ti.getStopTime()),
+            epoch.getEvaluationBy(),
+            epoch.getDescription()
+        );
+
+        for (BedHeightEpochValue value: values) {
+            data.addKM(value.getStation().doubleValue());
+            data.addMiddleHeight(value.getHeight().doubleValue());
+        }
+
+        logger.debug("Epoch contains " + values.size() + " values");
+
+        return data;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightData.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,145 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import gnu.trove.TDoubleArrayList;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+public class MiddleBedHeightData implements Serializable {
+
+    public static final String I18N_SINGLE_NAME = "facet.bedheight_middle.single";
+    public static final String I18N_EPOCH_NAME  = "facet.bedheight_middle.epoch";
+
+    private int    startYear;
+    private int    endYear;
+    private String evaluatedBy;
+    private String description;
+
+    private TDoubleArrayList km;
+    private TDoubleArrayList middleHeight;
+    private TDoubleArrayList uncertainty;
+    private TDoubleArrayList soundingWidth;
+    private TDoubleArrayList dataGap;
+    private TDoubleArrayList width;
+
+
+    protected MiddleBedHeightData(int start, int end, String eval, String desc) {
+        this.startYear   = start;
+        this.endYear     = end;
+        this.evaluatedBy = eval;
+        this.description = desc;
+
+        this.km            = new TDoubleArrayList();
+        this.middleHeight  = new TDoubleArrayList();
+        this.uncertainty   = new TDoubleArrayList();
+        this.soundingWidth = new TDoubleArrayList();
+        this.dataGap       = new TDoubleArrayList();
+        this.width         = new TDoubleArrayList();
+    }
+
+
+    public int getStartYear() {
+        return startYear;
+    }
+
+    public int getEndYear() {
+        return endYear;
+    }
+
+    public String getEvaluatedBy() {
+        return evaluatedBy;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+
+    public void addKM(double km) {
+        this.km.add(km);
+    }
+
+    public double getKM(int idx) {
+        return km.get(idx);
+    }
+
+    public void addMiddleHeight(double middleHeight) {
+        this.middleHeight.add(middleHeight);
+    }
+
+    public double getMiddleHeight(int idx) {
+        return middleHeight.get(idx);
+    }
+
+    public void addUncertainty(double uncertainty) {
+        this.uncertainty.add(uncertainty);
+    }
+
+    public double getUncertainty(int idx) {
+        return uncertainty.get(idx);
+    }
+
+    public void addSoundingWidth(double soundingWidth) {
+        this.soundingWidth.add(soundingWidth);
+    }
+
+    public double getSoundingWidth(int idx) {
+        return soundingWidth.get(idx);
+    }
+
+    public void addDataGap(double gap) {
+        this.dataGap.add(gap);
+    }
+
+    public double getDataGap(int idx) {
+        return dataGap.get(idx);
+    }
+
+    public void addWidth(double width) {
+        this.width.add(width);
+    }
+
+    public double getWidth(int idx) {
+        return width.get(idx);
+    }
+
+    public int size() {
+        return km.size();
+    }
+
+
+    public double[][] getMiddleHeightsPoints() {
+        double[][] points = new double[2][size()];
+
+        for (int i = 0, n = size(); i < n; i++) {
+            points[0][i] = getKM(i);
+            points[1][i] = getMiddleHeight(i);
+        }
+
+        return points;
+    }
+
+
+    public String getSoundingName(CallContext context) {
+        if (getStartYear() == getEndYear()) {
+            return Resources.getMsg(
+                context.getMeta(),
+                I18N_SINGLE_NAME,
+                I18N_SINGLE_NAME,
+                new Object[] { getStartYear() }
+            );
+        }
+        else {
+            return Resources.getMsg(
+                context.getMeta(),
+                I18N_EPOCH_NAME,
+                I18N_EPOCH_NAME,
+                new Object[] { getStartYear(), getEndYear() }
+            );
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/MiddleBedHeightFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,64 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Facet of a MiddleBedHeight curve.
+ */
+public class MiddleBedHeightFacet extends DataFacet {
+
+    private static Logger logger = Logger.getLogger(MiddleBedHeightFacet.class);
+
+
+    public MiddleBedHeightFacet() {
+        // required for clone operation deepCopy()
+    }
+
+
+    public MiddleBedHeightFacet(
+        int         idx,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash
+    ) {
+        super(idx, name, description, type, hash, stateId);
+    }
+
+
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("Get data for middle bed height at index: " + index);
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        CalculationResult res = (CalculationResult)
+            flys.compute(context, hash, stateId, type, false);
+
+        MiddleBedHeightData[] data = (MiddleBedHeightData[]) res.getData();
+
+        return data[index];
+    }
+
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        MiddleBedHeightFacet copy = new MiddleBedHeightFacet();
+        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=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/NamedDouble.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,29 @@
+package de.intevation.flys.artifacts.model;
+
+/**
+ * Implementation of a <String,double> pair.
+ */
+public class NamedDouble
+extends      NamedObjectImpl
+{
+    protected double value;
+
+
+    /**
+     * @param name  name for the given value.
+     * @param value value.
+     */
+    public NamedDouble(String name, double value) {
+        super(name);
+        this.value = value;
+    }
+
+
+    /**
+     * Get the value.
+     * @return the value.
+     */
+    public double getValue() {
+        return this.value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/NamedObject.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,19 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+
+/**
+ * This class represents an object that has a name. The default case would be to
+ * inherit from this class.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface NamedObject
+extends          Serializable
+{
+    void setName(String name);
+
+    String getName();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/NamedObjectImpl.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.model;
+
+public class NamedObjectImpl
+implements   NamedObject
+{
+    /** The name of this object.*/
+    protected String name;
+
+    public NamedObjectImpl() {
+    }
+
+    public NamedObjectImpl(String name) {
+        this.name = name;
+    }
+
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Parameters.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,103 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+public class Parameters
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(Parameters.class);
+
+    protected String []           columnNames;
+    protected TDoubleArrayList [] columns;
+
+    public Parameters() {
+    }
+
+    public Parameters(String [] columnNames) {
+        if (columnNames == null || columnNames.length < 1) {
+            throw new IllegalArgumentException("columnNames too short.");
+        }
+        this.columnNames = columnNames;
+        columns = new TDoubleArrayList[columnNames.length];
+        for (int i = 0; i < columns.length; ++i) {
+            columns[i] = new TDoubleArrayList();
+        }
+    }
+
+    public int columnIndex(String name) {
+        for (int i = 0; i < columnNames.length; ++i) {
+            if (columnNames[i].equals(name)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public int newRow() {
+
+        int N = columns[0].size();
+        log.debug("new row: " + N);
+
+        for (int i = 0; i < columns.length; ++i) {
+            columns[i].add(Double.NaN);
+        }
+
+        return N;
+    }
+
+    public double get(int i, int index) {
+        return columns[index].getQuick(i);
+    }
+
+    public double get(int i, String columnName) {
+        int index = columnIndex(columnName);
+        return index >= 0
+            ? columns[index].getQuick(i)
+            : Double.NaN;
+    }
+
+    public void set(int i, String columnName, double value) {
+        int idx = columnIndex(columnName);
+        if (idx >= 0) {
+            columns[idx].setQuick(i, value);
+        }
+    }
+
+    public int size() {
+        return columns[0].size();
+    }
+
+    public int getNumberColumns() {
+        return columnNames.length;
+    }
+
+    public String [] getColumnNames() {
+        return columnNames;
+    }
+
+    public void removeNaNs() {
+        W.removeNaNs(columns);
+    }
+
+    public int [] columnIndices(String [] columns) {
+        int [] indices = new int[columns.length];
+        for (int i = 0; i < columns.length; ++i) {
+            indices[i] = columnIndex(columns[i]);
+        }
+        return indices;
+    }
+
+    public void get(int row, int [] columnIndices, double [] values) {
+        for (int i = 0; i < columnIndices.length; ++i) {
+            int index = columnIndices[i];
+            values[i] = index >= 0 && index < columns.length
+                ? columns[index].getQuick(row)
+                : Double.NaN;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/QRangeTree.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,271 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+public class QRangeTree
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(QRangeTree.class);
+
+    public static final double EPSILON = 1e-4;
+
+    public static class Node
+    implements          Serializable
+    {
+        Node left;
+        Node right;
+        Node prev;
+        Node next;
+
+        double a;
+        double b;
+        double q;
+
+        public Node() {
+        }
+
+        public Node(double a, double b, double q) {
+            this.a = a;
+            this.b = b;
+            this.q = q;
+        }
+
+        protected final double interpolatePrev(double pos) {
+            /*
+            f(prev.b) = prev.q
+            f(a)      = q
+
+            prev.q = m*prev.b + n
+            q      = m*a      + n <=> n = q - m*a
+
+            q - prev.q = m*(a - prev.b)
+
+            m = (q - prev.q)/(a - prev.b) # a != prev.b
+            */
+
+            if (a == prev.b) {
+                return 0.5*(q + prev.q);
+            }
+            double m = (q - prev.q)/(a - prev.b);
+            double n = q - m*a;
+            return m*pos + n;
+        }
+
+        protected final double interpolateNext(double pos) {
+            /*
+            f(next.a) = next.q
+            f(b)      = q
+
+            next.q = m*next.a + n
+            q      = m*b      + n <=> n = q - m*b
+
+            q - next.q = m*(b - next.a)
+            m = (q - next.q)/(b - next.a) # b != next.a
+            */
+
+            if (b == next.a) {
+                return 0.5*(q + next.q);
+            }
+            double m = (q - next.q)/(b - next.a);
+            double n = q - m*b;
+            return m*pos + n;
+        }
+
+        public double findQ(double pos) {
+
+            Node current = this;
+            for (;;) {
+                if (pos < current.a) {
+                    if (current.left != null) {
+                        current = current.left;
+                        continue;
+                    }
+                    return current.prev != null
+                        ? current.interpolatePrev(pos)
+                        : Double.NaN;
+                }
+                if (pos > current.b) {
+                    if (current.right != null) {
+                        current = current.right;
+                        continue;
+                    }
+                    return current.next != null
+                        ? current.interpolateNext(pos)
+                        : Double.NaN;
+                }
+                return current.q;
+            }
+        }
+    } // class Node
+
+    protected Node root;
+
+    public QRangeTree() {
+    }
+
+    public static final class AccessQAB {
+        private int startIndex;
+
+        public AccessQAB(int startIndex) {
+            this.startIndex = startIndex;
+        }
+
+        public Double getQ(Object [] row) {
+            return (Double)row[startIndex];
+        }
+
+        public Double getA(Object [] row) {
+            return (Double)row[startIndex+1];
+        }
+
+        public Double getB(Object [] row) {
+            return (Double)row[startIndex+2];
+        }
+    }
+
+    public static final AccessQAB WITH_COLUMN    = new AccessQAB(1);
+    public static final AccessQAB WITHOUT_COLUMN = new AccessQAB(0);
+
+    /** wstQRanges need to be sorted by range.a */
+    public QRangeTree(List<Object []> qRanges, int start, int stop) {
+        this(qRanges, WITH_COLUMN, start, stop);
+    }
+
+    public QRangeTree(
+        List<Object []> qRanges,
+        AccessQAB       accessQAB, 
+        int             start,
+        int             stop
+    ) {
+        if (stop <= start) {
+            return;
+        }
+
+        int N = stop-start;
+
+        List<Node> nodes = new ArrayList<Node>(N);
+
+        Node last = null;
+
+        for (int i = 0; i < N; ++i) {
+            Object [] qRange = qRanges.get(start + i);
+            Double q = accessQAB.getQ(qRange);
+            Double a = accessQAB.getA(qRange);
+            Double b = accessQAB.getB(qRange);
+
+            double av = a != null ? a.doubleValue() : -Double.MAX_VALUE;
+            double bv = b != null ? b.doubleValue() :  Double.MAX_VALUE;
+            double qv = q.doubleValue();
+
+            // If nodes are directly neighbored and Qs are the same
+            // join them.
+            if (last != null
+            && Math.abs(last.b - av) < EPSILON
+            && Math.abs(last.q - qv) < EPSILON) {
+                last.b = bv;
+            }
+            else {
+                nodes.add(last = new Node(av, bv, qv));
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Before/after nodes join: " +
+                N + "/" + nodes.size());
+        }
+
+        root = wireTree(nodes);
+    }
+
+    protected static Node wireTree(List<Node> nodes) {
+        int N = nodes.size();
+        for (int i = 0; i < N; ++i) {
+            Node node = nodes.get(i);
+            if (i > 0  ) node.prev = nodes.get(i-1);
+            if (i < N-1) node.next = nodes.get(i+1);
+        }
+
+        return buildTree(nodes, 0, N-1);
+    }
+
+    protected static Node buildTree(List<Node> nodes, int lo, int hi) {
+
+        if (lo > hi) {
+            return null;
+        }
+
+        int mid = (lo + hi) >> 1;
+        Node parent = nodes.get(mid);
+
+        parent.left  = buildTree(nodes, lo, mid-1);
+        parent.right = buildTree(nodes, mid+1, hi);
+
+        return parent;
+    }
+
+    public double findQ(double pos) {
+        return root != null ? root.findQ(pos) : Double.NaN;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        inorder(root, sb);
+        return sb.toString();
+    }
+
+    protected static void inorder(Node node, StringBuilder sb) {
+        if (node != null) {
+            inorder(node.left, sb);
+            sb.append('[')
+              .append(node.a)
+              .append(", ")
+              .append(node.b)
+              .append(": ")
+              .append(node.q)
+              .append(']');
+            inorder(node.right, sb);
+        }
+    }
+
+    private static final String name(Object o) {
+        return String.valueOf(System.identityHashCode(o) & 0xffffffffL);
+    }
+
+    public String toGraph() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("subgraph c");
+        sb.append(name(this));
+        sb.append(" {\n");
+        if (root != null) {
+            java.util.Deque<Node> stack = new java.util.ArrayDeque<Node>();
+            stack.push(root);
+            while (!stack.isEmpty()) {
+                Node current = stack.pop();
+                String name = "n" + name(current);
+                sb.append(name);
+                sb.append(" [label=\"");
+                sb.append(current.a).append(", ").append(current.b);
+                sb.append(": ").append(current.q).append("\"]\n");
+                if (current.left != null) {
+                    String leftName = name(current.left);
+                    sb.append(name).append(" -- n").append(leftName).append("\n");
+                    stack.push(current.left);
+                }
+                if (current.right != null) {
+                    String rightName = name(current.right);
+                    sb.append(name).append(" -- n").append(rightName).append("\n");
+                    stack.push(current.right);
+                }
+            }
+        }
+        sb.append("}\n");
+        return sb.toString();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/RangeWithValues.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,41 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RangeWithValues implements Serializable {
+
+    protected double   lower;
+    protected double   upper;
+    protected double[] values;
+
+
+    public RangeWithValues() {
+    }
+
+
+    public RangeWithValues(double lower, double upper, double[] values) {
+        this.lower  = lower;
+        this.upper  = upper;
+        this.values = values;
+    }
+
+
+    public double getLower() {
+        return lower;
+    }
+
+
+    public double getUpper() {
+        return upper;
+    }
+
+
+    public double[] getValues() {
+        return values;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ReferenceCurveFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,150 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Collections;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+import org.apache.log4j.Logger;
+
+
+/** Facet for W-over-Ws. */
+public class ReferenceCurveFacet 
+extends      DataFacet
+{
+    private static Logger log = Logger.getLogger(ReferenceCurveFacet.class);
+
+    public static final String CONTEXT_KEY = "reference.curve.axis.scale";
+
+    /** Blackboard data provider key for reference curves start km. */
+    public static final String BB_REFERENCECURVE_STARTKM =
+        "reference_curve.startkm";
+
+    /** Blackboard data provider key for reference curves end kms. */
+    public static final String BB_REFERENCECURVE_ENDKMS =
+        "reference_curve.endkms";
+
+
+    public ReferenceCurveFacet() {
+    }
+
+
+    public ReferenceCurveFacet(int index, String name, String description) {
+        super(index, name, description, ComputeType.ADVANCE, null, null);
+    }
+
+
+    public ReferenceCurveFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateID,
+        String      hash
+    ) {
+        super(index, name, description, type, hash, stateID);
+    }
+
+
+    public Object getData(Artifact artifact, CallContext context) {
+
+        if (log.isDebugEnabled()) {
+            log.debug("Get data for reference curve at index: " + index +
+                " /stateId: " + stateId);
+        }
+
+        return getWWQQ(artifact, context);
+    }
+
+
+   /**
+     * Can provide parameters of reference curve
+     * @param key      will respond on BB_REFERENCECURVE START/ENDKMS
+     * @param param    ignored
+     * @param context  ignored
+     * @return whatever parameters for reference curve
+     */
+    @Override
+    public Object provideBlackboardData(Artifact artifact,
+        Object key,
+        Object param,
+        CallContext context
+    ) {
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        if (key.equals(BB_REFERENCECURVE_STARTKM)) {
+            return winfo.getReferenceStartKm();
+        }
+        else if (key.equals(BB_REFERENCECURVE_ENDKMS)) {
+            return winfo.getReferenceEndKms();
+        }
+        else {
+            return null;
+        }
+    }
+
+
+    protected WWQQ getWWQQ(Artifact artifact, CallContext context) {
+        FLYSArtifact winfo = (FLYSArtifact)artifact;
+
+        CalculationResult res = (CalculationResult)
+            winfo.compute(context, hash, stateId, type, false);
+
+        return ((WWQQ [])res.getData())[index];
+    }
+
+
+    @Override
+    public void set(Facet other) {
+        super.set(other);
+        ReferenceCurveFacet o = (ReferenceCurveFacet)other;
+        type                  = o.type;
+        hash                  = o.hash;
+        stateId               = o.stateId;
+    }
+
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        ReferenceCurveFacet copy = new ReferenceCurveFacet();
+        copy.set(this);
+        return copy;
+    }
+
+
+    @Override
+    public List getStaticDataProviderKeys(Artifact art) {
+        List list = new ArrayList();
+        list.add(BB_REFERENCECURVE_STARTKM);
+        list.add(BB_REFERENCECURVE_ENDKMS);
+        return list;
+    }
+
+
+    @Override
+    public List getDataProviderKeys(Artifact art, CallContext context) {
+
+        // compute / get data
+        Object obj = context.getContextValue(CONTEXT_KEY);
+
+        if (!(obj instanceof WWAxisTypes)) {
+            obj = new WWAxisTypes(getWWQQ(art, context));
+            context.putContextValue(CONTEXT_KEY, obj);
+        }
+        else {
+            ((WWAxisTypes)obj).classify(getWWQQ(art, context));
+        }
+
+        return getStaticDataProviderKeys(art);//Collections.emptyList();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/RelativePointFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,183 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.flys.artifacts.StaticWKmsArtifact;
+import de.intevation.flys.artifacts.math.Linear;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.DurationCurveFacet;
+import de.intevation.flys.artifacts.model.ReferenceCurveFacet;
+import de.intevation.flys.artifacts.model.WQDay;
+
+/**
+ * Facet to access a point.
+ */
+public class RelativePointFacet
+extends      BlackboardDataFacet
+implements   FacetTypes {
+
+    private static Logger logger = Logger.getLogger(RelativePointFacet.class);
+
+    /** Trivial Constructor. */
+    public RelativePointFacet(String description) {
+        this(RELATIVE_POINT, description);
+    }
+
+
+    public RelativePointFacet(String name, String description) {
+        this.name        = name;
+        this.description = description;
+        this.index       = 0;
+    }
+
+
+    protected Point2D calculateDurationCurvePoint(CallContext context,
+        StaticWKmsArtifact artifact)
+    {
+        // TODO here and in reference curve calc: Do warn if more than 1
+        // provider found or (way better) handle it.
+        Object wqdays = null;
+        double km = 0d;
+        List<DataProvider> providers = context.
+            getDataProvider(DurationCurveFacet.BB_DURATIONCURVE);
+        if (providers.size() < 1) {
+            logger.warn("Could not find durationcurve data provider.");
+        }
+        else {
+            wqdays = providers.get(0).provideData(
+                DurationCurveFacet.BB_DURATIONCURVE,
+                null,
+                context);
+        }
+        List<DataProvider> kmproviders = context.
+            getDataProvider(DurationCurveFacet.BB_DURATIONCURVE_KM);
+        if (kmproviders.size() < 1) {
+            logger.warn("Could not find durationcurve.km data provider.");
+        }
+        else {
+            logger.debug("Found durationcurve.km data provider.");
+            String dckm = providers.get(0).provideData(
+                DurationCurveFacet.BB_DURATIONCURVE_KM,
+                null,
+                context).toString();
+            km = Double.valueOf(dckm);
+        }
+
+        if (wqdays != null) {
+            // Which W at this km?
+            double w = artifact.getWAtKmLin(artifact.getWKms(0), km);
+            if (w == -1) {
+                logger.warn("w is -1, already bad sign!");
+            }
+            // Where is this W passed by in the wq-curve?
+            WQDay wqday = (WQDay) wqdays;
+            // Doing a linear Day Of KM.
+            int idx = 0;
+            boolean wIncreases = wqday.getW(0) < wqday.getW(wqday.size()-1);
+            if (wIncreases) {
+                while (idx < wqday.size() && wqday.getW(idx) < w) {
+                    idx++;
+                }
+            }
+            else {
+                idx = wqday.size() -1;
+                while (idx > 0 && wqday.getW(idx) > w) {
+                    idx--;
+                }
+            }
+
+            double day = 0d;
+            int mod = (wIncreases) ? -1 : +1;
+            if (idx != 0 && idx <= wqday.size()-1) {
+                day = Linear.linear(w, wqday.getW(idx +mod), wqday.getW(idx),
+                    wqday.getDay(idx+mod), wqday.getDay(idx));
+            }
+
+            return new Point2D.Double((double) day, w);
+        }
+        logger.warn("not wqkms / w / day found");
+        // TODO better signal failure.
+        return new Point2D.Double(0d, 0d);
+        //return null;
+    }
+
+
+    /**
+     * Calculate a reference curve point, that is, a point made of
+     * the Ws from start and end km param of the reference curve.
+     */
+    public Point2D calculateReferenceCurvePoint(CallContext context,
+        StaticWKmsArtifact artifact) {
+        List<DataProvider> providers = context.
+            getDataProvider(ReferenceCurveFacet.BB_REFERENCECURVE_STARTKM);
+        if (providers.size() < 1) {
+            logger.warn("Could not find reference curve startkm data provider.");
+        }
+
+        Double start = (Double) providers.get(0).
+            provideData(ReferenceCurveFacet.BB_REFERENCECURVE_STARTKM, null, context);
+
+        providers = context.
+            getDataProvider(ReferenceCurveFacet.BB_REFERENCECURVE_ENDKMS);
+        if (providers.size() < 1) {
+            logger.warn("Could not find reference curve endkms data provider.");
+        }
+        double[] ends = (double[]) providers.get(0).
+            provideData(ReferenceCurveFacet.BB_REFERENCECURVE_ENDKMS, null, context);
+
+        logger.debug("Got s " + start + " e " + ends);
+        double startW = artifact.getWAtKmLin(artifact.getWKms(0), start.doubleValue());
+        // TODO handle multiple ends.
+        double endW = artifact.getWAtKmLin(artifact.getWKms(0), ends[0]);
+        logger.debug("Gotw s " + startW + " e " + endW);
+        return new Point2D.Double(startW, endW);
+    }
+
+
+    /**
+     * Returns the data this facet requires.
+     *
+     * @param artifact the owner artifact.
+     * @param context  the CallContext (ignored).
+     *
+     * @return the data.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        StaticWKmsArtifact staticData = (StaticWKmsArtifact) artifact;
+        // Find out whether we live in a duration curve context, there we would
+        // provide only a single point.
+
+        if (context.getDataProvider(
+            DurationCurveFacet.BB_DURATIONCURVE_KM).size() > 0) {
+            return calculateDurationCurvePoint(context, staticData);
+        }
+        else if (context.getDataProvider(
+            ReferenceCurveFacet.BB_REFERENCECURVE_STARTKM).size() > 0) {
+            return calculateReferenceCurvePoint(context, staticData);
+        }
+
+        // TODO better signal failure.
+        return new Point2D.Double(0d, 0d);
+    }
+
+
+    /**
+     * Create a deep copy of this Facet.
+     * @return a deep copy.
+     */
+    @Override
+    public RelativePointFacet deepCopy() {
+        RelativePointFacet copy = new RelativePointFacet(description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ReportFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,64 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+import org.apache.log4j.Logger;
+
+public class ReportFacet
+extends      DefaultFacet
+implements   FacetTypes
+{
+    private static Logger logger = Logger.getLogger(ReportFacet.class);
+
+    protected ComputeType type;
+    protected String      hash;
+    protected String      stateId;
+
+    public ReportFacet() {
+        this(ComputeType.ADVANCE);
+    }
+
+    public ReportFacet(ComputeType type) {
+        super(0, REPORT, "report");
+        this.type = type;
+    }
+
+
+    public ReportFacet(ComputeType type, String hash, String stateId) {
+        super(0, REPORT, "report");
+        this.type    = type;
+        this.hash    = hash;
+        this.stateId = stateId;
+    }
+
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("get report data");
+
+        FLYSArtifact flys = (FLYSArtifact)artifact;
+
+        CalculationResult cr = (CalculationResult)flys.compute(
+            context, hash, stateId, type, false);
+
+        return cr.getReport();
+    }
+
+    @Override
+    public Facet deepCopy() {
+        ReportFacet copy = new ReportFacet();
+        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=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/RiverFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,71 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RiverFactory {
+
+    /** We don't need to instantiate concrete objects of this class. */
+    private RiverFactory() {
+    }
+
+
+    /**
+     * Returns all rivers that were found in the backend.
+     *
+     * @return all rivers.
+     */
+    public static List<River> getRivers() {
+        Session session = SessionHolder.HOLDER.get();
+
+        return session.createQuery("from River order by name").list();
+    }
+
+
+    /**
+     * Returns a River object fetched from database based on its id.
+     *
+     * @param river_id The id of the desired river.
+     *
+     * @return the river.
+     */
+    public static River getRiver(int river_id) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery("from River where id=:river_id");
+        query.setParameter("river_id", river_id);
+
+        List<River> rivers = query.list();
+
+        return (rivers != null && rivers.size() > 0) ? rivers.get(0) : null;
+    }
+
+
+    /**
+     * Returns a River object fetched from database based on its name.
+     *
+     * @param river The name of a river.
+     *
+     * @return the River object.
+     */
+    public static River getRiver(String river) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from River where name =:name");
+        query.setParameter("name", river);
+
+        List<River> rivers = query.list();
+
+        return (rivers != null && rivers.size() > 0) ? rivers.get(0) : null;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Segment.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,126 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+import gnu.trove.TDoubleArrayList;
+
+import de.intevation.flys.utils.DoubleUtil;
+
+public class Segment
+implements   Serializable
+{
+    private static Logger logger = Logger.getLogger(Segment.class);
+
+    protected double    from;
+    protected double    to;
+    protected double [] values;
+    protected double [] backup;
+    protected double    referencePoint;
+
+    public Segment() {
+    }
+
+    public Segment(double referencePoint) {
+        this.referencePoint = referencePoint;
+    }
+
+    public Segment(double from, double to, double [] values) {
+        this.from   = from;
+        this.to     = to;
+        this.values = values;
+    }
+
+    public boolean isUp() {
+        return from < to;
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder("Segment: [");
+        sb.append("from: ").append(from).append("; to: ")
+          .append(to)
+          .append("; ref: ").append(referencePoint)
+          .append("; values: (");
+        for (int i = 0; i < values.length; ++i) {
+            if (i > 0) sb.append(", ");
+            sb.append(values[i]);
+        }
+        sb.append(")]");
+        return sb.toString();
+    }
+
+    public void setFrom(double from) {
+        this.from = from;
+    }
+
+    public void backup() {
+        backup = (double [])values.clone();
+    }
+
+    public double getFrom() {
+        return from;
+    }
+
+    public void setTo(double to) {
+        this.to = to;
+    }
+
+    public double getTo() {
+        return to;
+    }
+
+    public void setValues(double [] values) {
+        this.values = values;
+    }
+
+    public double [] getValues() {
+        return values;
+    }
+
+    public void setReferencePoint(double referencePoint) {
+        this.referencePoint = referencePoint;
+    }
+
+    public double getReferencePoint() {
+        return referencePoint;
+    }
+
+    public static List<Segment> parseSegments(String input) {
+
+        ArrayList<Segment> segments = new ArrayList<Segment>();
+
+        TDoubleArrayList vs = new TDoubleArrayList();
+
+        for (String segmentStr: input.split(":")) {
+            String [] parts = segmentStr.split(";");
+            if (parts.length < 3) {
+                logger.warn("invalid segment: '" + segmentStr + "'");
+                continue;
+            }
+            try {
+                double from = Double.parseDouble(parts[0].trim());
+                double to   = Double.parseDouble(parts[1].trim());
+
+                vs.clear();
+
+                for (String valueStr: parts[2].split(",")) {
+                    vs.add(DoubleUtil.round(
+                        Double.parseDouble(valueStr.trim())));
+                }
+
+                double [] values = vs.toNativeArray();
+                segments.add(new Segment(from, to, values));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("invalid segment: '" + segmentStr + "'");
+            }
+        }
+
+        return segments;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/StaticWKmsCacheKey.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,33 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+/**
+ * Caching-Key object for 'static' wst- data.
+ */
+public final class StaticWKmsCacheKey
+implements         Serializable
+{
+    public static final String CACHE_NAME = "wst-value-table-static";
+
+    private int column;
+    private int wst_id;
+
+    public StaticWKmsCacheKey(int column, int wst_id) {
+        this.wst_id  = wst_id;
+        this.column  = column;
+    }
+
+    public int hashCode() {
+        return (wst_id << 8) | column;
+    }
+
+    public boolean equals(Object other) {
+        if (!(other instanceof StaticWKmsCacheKey)) {
+            return false;
+        }
+        StaticWKmsCacheKey o = (StaticWKmsCacheKey) other;
+        return wst_id == o.wst_id && this.column == o.column;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/StaticWQKmsCacheKey.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,33 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+/**
+ * Caching-Key object for 'static' wst- data.
+ */
+public final class StaticWQKmsCacheKey
+implements         Serializable
+{
+    public static final String CACHE_NAME = "wst-wq-value-table-static";
+
+    private int column;
+    private int wst_id;
+
+    public StaticWQKmsCacheKey(int column, int wst_id) {
+        this.wst_id  = wst_id;
+        this.column  = column;
+    }
+
+    public int hashCode() {
+        return (wst_id << 8) | column;
+    }
+
+    public boolean equals(Object other) {
+        if (!(other instanceof StaticWQKmsCacheKey)) {
+            return false;
+        }
+        StaticWQKmsCacheKey o = (StaticWQKmsCacheKey) other;
+        return this.wst_id == o.wst_id && this.column == o.column;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Timerange.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,41 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import java.util.Date;
+
+
+/**
+ * This class represents time ranges specified by start and end time. Start and
+ * end times are stored as long (number of milliseconds since january 1, 1970).
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class Timerange implements Serializable {
+
+    private long start;
+    private long end;
+
+
+    public Timerange(long start, long end) {
+        this.start = start;
+        this.end   = end;
+    }
+
+
+    public Timerange(Date start, Date stop) {
+        this.start = start.getTime();
+        this.end   = stop != null ? stop.getTime() : System.currentTimeMillis();
+    }
+
+
+    public long getStart() {
+        return start;
+    }
+
+
+    public long getEnd() {
+        return end;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/W.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,149 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.utils.DataUtil;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+public class W
+extends      NamedObjectImpl
+{
+    private static Logger log = Logger.getLogger(W.class);
+
+    protected TDoubleArrayList ws;
+
+    public W() {
+        ws = new TDoubleArrayList();
+    }
+
+    public W(String name) {
+        super(name);
+        ws = new TDoubleArrayList();
+    }
+
+    public W(int capacity) {
+        this(capacity, "");
+    }
+
+    public W(int capacity, String name) {
+        super(name);
+        ws = new TDoubleArrayList(capacity);
+    }
+
+    public void add(double value) {
+        ws.add(value);
+    }
+
+    public int size() {
+        return ws.size();
+    }
+
+    public double getW(int idx) {
+        return ws.getQuick(idx);
+    }
+
+    public double [] getWs() {
+        return ws.toNativeArray();
+    }
+
+    public double [] get(int idx) {
+        return get(idx, new double [1]);
+    }
+
+    public double [] get(int idx, double [] dst) {
+        dst[0] = ws.getQuick(idx);
+        return dst;
+    }
+
+    public double minWs() {
+        return ws.min();
+    }
+
+    public static void removeNaNs(TDoubleArrayList [] arrays) {
+
+        int dest = 0;
+
+        int A = arrays.length;
+        int N = arrays[0].size();
+
+        OUTER: for (int i = 0; i < N; ++i) {
+            for (int j = 0; j < A; ++j) {
+                TDoubleArrayList a = arrays[j];
+                double v = a.getQuick(i);
+                if (Double.isNaN(v)) {
+                    continue OUTER;
+                }
+                a.setQuick(dest, v);
+            }
+            ++dest;
+        }
+
+        if (dest < N) {
+            for (int i = 0; i < A; ++i) {
+                arrays[i].remove(dest, N-dest);
+            }
+        }
+    }
+
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { ws });
+    }
+
+    public boolean guessWaterIncreasing() {
+        return guessWaterIncreasing(0.05f);
+    }
+
+    public boolean guessWaterIncreasing(float factor) {
+        return DataUtil.guessWaterIncreasing(ws, factor);
+    }
+
+    public int [] longestIncreasingWRangeIndices() {
+        return longestIncreasingWRangeIndices(new int[2]);
+    }
+
+    public int [] longestIncreasingWRangeIndices(int [] bounds) {
+
+        int N = size();
+        int start = 0;
+        int stop  = 0;
+
+        double lastW = Double.MAX_VALUE;
+
+        for (int i = 0; i < N; ++i) {
+            double v = ws.getQuick(i);
+            if (v <= lastW) {
+                if (stop-start > bounds[1]-bounds[0]) {
+                    bounds[0] = start;
+                    bounds[1] = stop;
+                    if (log.isDebugEnabled()) {
+                        log.debug("new range: " +
+                            bounds[0] + " - " + bounds[1] + " (" +
+                            ws.getQuick(bounds[0]) + ", " +
+                            ws.getQuick(bounds[1]) + ")");
+
+                    }
+                }
+                start = stop = i;
+            }
+            else {
+                stop = i;
+            }
+            lastW = v;
+        }
+
+        if (stop-start > bounds[1]-bounds[0]) {
+            bounds[0] = start;
+            bounds[1] = stop;
+            if (log.isDebugEnabled()) {
+                log.debug("new range @end: " +
+                    bounds[0] + " - " + bounds[1] + " (" +
+                    ws.getQuick(bounds[0]) + ", " +
+                    ws.getQuick(bounds[1]) + ")");
+            }
+        }
+
+        return bounds;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WKms.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,20 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+public interface WKms
+extends          NamedObject
+{
+    int size();
+
+    double getKm(int index);
+
+    double getW(int index);
+
+    TDoubleArrayList allKms();
+
+    TDoubleArrayList allWs();
+
+    public boolean guessWaterIncreasing();
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WKmsFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,55 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.StaticWKmsArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+/**
+ * Facet to show W|km Values.
+ */
+public class WKmsFacet
+extends      BlackboardDataFacet
+implements   FacetTypes {
+
+    /** Trivial Constructor. */
+    public WKmsFacet(String description) {
+        this(STATIC_WKMS, description);
+    }
+
+    public WKmsFacet(String name, String description) {
+        this.name        = name;
+        this.description = description;
+        this.index       = 0;
+    }
+
+
+    /**
+     * Returns the data this facet requires.
+     *
+     * @param artifact the owner artifact.
+     * @param context  the CallContext (ignored).
+     *
+     * @return the data.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        StaticWKmsArtifact staticData =
+            (StaticWKmsArtifact) artifact;
+        return staticData.getWKms(0);
+    }
+
+
+    /**
+     * Create a deep copy of this Facet.
+     * @return a deep copy.
+     */
+    @Override
+    public WKmsFacet deepCopy() {
+        WKmsFacet copy = new WKmsFacet(description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WKmsFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,158 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+
+import org.hibernate.SQLQuery;
+import org.hibernate.type.StandardBasicTypes;
+
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WKmsImpl;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+/**
+ * Factory to access ready-made WKms for other (than computed) 'kinds' of 
+ * WST-data.
+ */
+public class WKmsFactory
+{
+    private static Logger log = Logger.getLogger(WKmsFactory.class);
+
+    /** Query to get km and ws for wst_id and column_pos. */
+    public static final String SQL_SELECT_WS =
+        "SELECT km, w FROM wst_w_values " +
+        "WHERE wst_id = :wst_id AND column_pos = :column_pos";
+
+    /** Query to get name for wst_id and column_pos. */
+    public static final String SQL_SELECT_NAME =
+        "SELECT name " +
+        "FROM wst_columns "+
+        "WHERE wst_id = :wst_id AND position = :column_pos";
+
+    /** Query to get name (description) for wst_id. */
+    public static final String SQL_SELECT_WST_NAME =
+        "SELECT description from wsts "+
+        "WHERE id = :wst_id";
+
+
+    private WKmsFactory() {
+    }
+
+
+    /**
+     * Get WKms for given column and wst_id, caring about the cache.
+     */
+    public static WKms getWKms(int column, int wst_id) {
+        log.debug("WKmsFactory.getWKms");
+        Cache cache = CacheFactory.getCache(StaticWKmsCacheKey.CACHE_NAME);
+
+        StaticWKmsCacheKey cacheKey;
+
+        if (cache != null) {
+            cacheKey = new StaticWKmsCacheKey(wst_id, column);
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                log.debug("Got static wst values from cache");
+                return (WKms)element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        WKms values = getWKmsUncached(column, wst_id);
+
+        if (values != null && cacheKey != null) {
+            log.debug("Store static wst values in cache.");
+            Element element = new Element(cacheKey, values);
+            cache.put(element);
+        }
+        return values;
+    }
+
+    /** Get name for a WKms. */
+    public static String getWKmsName(int wst_id) {
+        log.debug("WKmsFactory.getWKmsName wst_id/" + wst_id);
+
+        String name = null;
+        Session session = SessionHolder.HOLDER.get();
+
+        SQLQuery nameQuery = session.createSQLQuery(SQL_SELECT_WST_NAME)
+            .addScalar("description", StandardBasicTypes.STRING);
+        nameQuery.setInteger("wst_id",     wst_id);
+
+        List<String> names = nameQuery.list();
+        if (names.size() >= 1) {
+            name = names.get(0);
+        }
+
+        return name;
+    }
+
+    /** Get name for a WKms. */
+    public static String getWKmsName(int column, int wst_id) {
+        log.debug("WKmsFactory.getWKmsName c/" + column + ", wst_id/" + wst_id);
+
+        String name = null;
+        Session session = SessionHolder.HOLDER.get();
+
+        SQLQuery nameQuery = session.createSQLQuery(SQL_SELECT_NAME)
+            .addScalar("name", StandardBasicTypes.STRING);
+        nameQuery.setInteger("wst_id",     wst_id);
+        nameQuery.setInteger("column_pos", column);
+
+        List<String> names = nameQuery.list();
+        if (names.size() >= 1) {
+            name = names.get(0);
+        }
+
+        return name;
+    }
+
+
+    /**
+     * Get WKms from db.
+     * @param column the position columns value
+     * @param wst_id database id of the wst
+     * @return according WKms.
+     */
+    public static WKms getWKmsUncached(int column, int wst_id) {
+
+        if (log.isDebugEnabled()) {
+            log.debug("WKmsFactory.getWKmsUncached c/" + column + ", wst_id/" + wst_id);
+        }
+
+        WKmsImpl wkms = new WKmsImpl(getWKmsName(column, wst_id));
+
+        Session session = SessionHolder.HOLDER.get();
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_WS)
+            .addScalar("km", StandardBasicTypes.DOUBLE)
+            .addScalar("w",  StandardBasicTypes.DOUBLE);
+        sqlQuery.setInteger("wst_id",     wst_id);
+        sqlQuery.setInteger("column_pos", column);
+
+        List<Object []> results = sqlQuery.list();
+
+        double kms [] = new double[results.size()];
+        double ws  [] = new double[results.size()];
+
+        int lastColumn = Integer.MAX_VALUE;
+
+        for (int i = 0, N = results.size(); i < N; i++) {
+            Object[] row = results.get(i);
+            wkms.add((Double) row[0], (Double) row[1]);
+        }
+
+        return wkms;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WKmsImpl.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,99 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+import de.intevation.flys.utils.DataUtil;
+
+
+public class WKmsImpl
+extends      NamedObjectImpl
+implements   WKms
+{
+    protected TDoubleArrayList kms;
+    protected TDoubleArrayList ws;
+
+    public WKmsImpl() {
+        super("");
+        kms = new TDoubleArrayList();
+        ws  = new TDoubleArrayList();
+    }
+
+
+    /**
+     * Create named, empty WKms.
+     */
+    public WKmsImpl(String name) {
+        super(name);
+        kms = new TDoubleArrayList();
+        ws  = new TDoubleArrayList();
+    }
+
+
+    public WKmsImpl(int capacity) {
+        super("");
+        kms = new TDoubleArrayList(capacity);
+        ws  = new TDoubleArrayList(capacity);
+    }
+
+
+    public WKmsImpl(TDoubleArrayList kms, TDoubleArrayList ws) {
+        this(kms, ws, "");
+    }
+
+
+    public WKmsImpl(
+        TDoubleArrayList kms,
+        TDoubleArrayList ws,
+        String           name
+    ) {
+        super(name);
+        this.kms = kms;
+        this.ws  = ws;
+    }
+
+
+    /**
+     * Add a W (in NN+m) for a km (in km).
+     */
+    public void add(double km, double w) {
+        kms.add(km);
+        ws .add(w);
+    }
+
+
+    @Override
+    public double getW(int index) {
+        return ws.getQuick(index);
+    }
+
+
+    @Override
+    public double getKm(int index) {
+        return kms.getQuick(index);
+    }
+
+    @Override
+    public boolean guessWaterIncreasing() {
+        return guessWaterIncreasing(0.05f);
+    }
+
+    protected boolean guessWaterIncreasing(float factor) {
+        return DataUtil.guessWaterIncreasing(ws, factor);
+    }
+
+    @Override
+    public int size() {
+        return kms.size();
+    }
+
+    @Override
+    public TDoubleArrayList allKms() {
+        return kms;
+    }
+
+    @Override
+    public TDoubleArrayList allWs() {
+        return ws;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WKmsJRDataSource.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,114 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.log4j.Logger;
+
+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 class WKmsJRDataSource implements JRDataSource
+{
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(WKmsJRDataSource.class);
+
+    /**
+     *
+     */
+    private ArrayList<String[]> data;
+    private HashMap<String, String> metaData;
+
+    private int index = -1;
+
+    /**
+     *
+     */
+    public WKmsJRDataSource()
+    {
+        data = new ArrayList<String[]>();
+        metaData = new HashMap<String, String>();
+    }
+
+
+    /**
+     *
+     */
+    public void addData(String[] data) {
+        this.data.add(data);
+    }
+
+
+    /**
+     *
+     */
+    public void addMetaData(String key, String value) {
+        this.metaData.put(key, value);
+    }
+
+
+    /**
+     *
+     */
+    public boolean next() throws JRException
+    {
+        index++;
+
+        return (index < data.size());
+    }
+
+
+    /**
+     *
+     */
+    public Object getFieldValue(JRField field) throws JRException
+    {
+        Object value = "";
+        String fieldName = field.getName();
+        if ("river".equals(fieldName)) {
+            value = metaData.get("river");
+        }
+        if ("date".equals(fieldName)) {
+            value = metaData.get("date");
+        }
+        if ("range".equals(fieldName)) {
+            value = metaData.get("range");
+        }
+        if ("gauge".equals(fieldName)) {
+            value = metaData.get("gauge");
+        }
+        if ("calculation".equals(fieldName)) {
+            value = metaData.get("calculation");
+        }
+        if ("differences".equals(fieldName)) {
+            value = metaData.get("differences");
+        }
+        if ("km".equals(fieldName)) {
+            value = data.get(index)[0];
+        }
+        if ("W".equals(fieldName)) {
+            value = data.get(index)[1];
+        }
+        if ("Q".equals(fieldName)) {
+            value = data.get(index)[2];
+        }
+        if ("description".equals(fieldName)) {
+            value = data.get(index)[3];
+        }
+        if ("location".equals(fieldName)) {
+            value = data.get(index)[4];
+        }
+        if ("gaugename".equals(fieldName)) {
+            value = data.get(index)[5];
+        }
+        if ("day".equals(fieldName)) {
+            value = data.get(index)[6];
+        }
+        return value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WMSDBLayerFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,106 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+public class WMSDBLayerFacet extends WMSLayerFacet {
+
+    protected String data;
+    protected String filter;
+    protected String labelItem;
+    protected String geometryType;
+    protected String connection;
+    protected String connectionType;
+
+
+    public WMSDBLayerFacet() {
+        super();
+    }
+
+
+    public WMSDBLayerFacet(int index, String name, String description) {
+        this(index, name, description, ComputeType.FEED, null, null);
+    }
+
+
+    public WMSDBLayerFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash
+
+    ) {
+        super(index, name, description, type, stateId, hash);
+    }
+
+
+    public WMSDBLayerFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash,
+        String      url
+    ) {
+        super(index, name, description, type, stateId, hash, url);
+    }
+
+
+    public void setFilter(String filter) {
+        this.filter = filter;
+    }
+
+    public String getFilter() {
+        return filter;
+    }
+
+    public void setData(String data) {
+        this.data = data;
+    }
+
+    public String getData() {
+        return data;
+    }
+
+    public void setGeometryType(String geometryType) {
+        this.geometryType = geometryType;
+    }
+
+    public String getGeometryType() {
+        return geometryType;
+    }
+
+    public void setConnection(String connection) {
+        this.connection = connection;
+    }
+
+    public String getConnection() {
+        return connection;
+    }
+
+    public void setConnectionType(String connectionType) {
+        this.connectionType = connectionType;
+    }
+
+    public String getConnectionType() {
+        return connectionType;
+    }
+
+    public void setLabelItem(String labelItem) {
+        this.labelItem = labelItem;
+    }
+
+    public String getLabelItem() {
+        return labelItem;
+    }
+
+
+    @Override
+    public boolean isQueryable() {
+        return true;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WMSLayerFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,173 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+import de.intevation.flys.utils.GeometryUtils;
+
+
+public class WMSLayerFacet
+extends      DefaultFacet
+{
+    protected ComputeType  type;
+    protected List<String> layers;
+    protected String       stateId;
+    protected String       hash;
+    protected String       url;
+    protected Envelope     extent;
+    protected String       srid;
+
+
+    private static final Logger logger = Logger.getLogger(WMSLayerFacet.class);
+
+    public WMSLayerFacet() {
+    }
+
+
+    public WMSLayerFacet(int index, String name, String description) {
+        this(index, name, description, ComputeType.FEED, null, null);
+    }
+
+
+    public WMSLayerFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash
+
+    ) {
+        super(index, name, description);
+        this.layers  = new ArrayList<String>();
+        this.type    = type;
+        this.stateId = stateId;
+        this.hash    = hash;
+    }
+
+
+    public WMSLayerFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash,
+        String      url
+    ) {
+        this(index, name, description, type, stateId, hash);
+        this.url = url;
+    }
+
+
+    public void addLayer(String name) {
+        if (name != null && name.length() > 0) {
+            layers.add(name);
+        }
+    }
+
+
+    public List<String> getLayers() {
+        return layers;
+    }
+
+
+    public void removeLayer(String layer) {
+        if (layers != null) {
+            layers.remove(layer);
+        }
+    }
+
+
+    public void setExtent(Envelope extent) {
+        if (extent != null) {
+            this.extent = extent;
+        }
+    }
+
+
+    public Envelope getExtent() {
+        return extent;
+    }
+
+
+    public void setSrid(String srid) {
+        if (srid != null) {
+            this.srid = srid;
+        }
+    }
+
+
+    public String getSrid() {
+        return srid;
+    }
+
+
+    public Object getData(Artifact artifact, CallContext context) {
+        return null;
+    }
+
+
+    @Override
+    public Node toXML(Document doc) {
+        ElementCreator ec = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element facet = ec.create("facet");
+        ec.addAttr(facet, "description", description, true);
+        ec.addAttr(facet, "index", String.valueOf(index), true);
+        ec.addAttr(facet, "name", name, true);
+        ec.addAttr(facet, "url", url, true);
+        ec.addAttr(facet, "layers", layers.get(0), true);
+        ec.addAttr(facet, "srid", srid != null ? srid : "", true);
+        ec.addAttr(facet, "extent", extent != null
+            ? GeometryUtils.jtsBoundsToOLBounds(extent)
+            : "", true);
+        ec.addAttr(facet, "queryable", String.valueOf(isQueryable()), true);
+
+        return facet;
+    }
+
+
+    public boolean isQueryable() {
+        return false;
+    }
+
+
+    @Override
+    public Facet deepCopy() {
+        WMSLayerFacet copy = new WMSLayerFacet();
+        copy.set(this);
+
+        copy.type    = type;
+        copy.layers  = new ArrayList<String>(layers);
+        copy.stateId = stateId;
+        copy.hash    = hash;
+        copy.url     = url;
+        copy.extent  = extent;
+        copy.srid    = srid;
+
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQ.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,102 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+public class WQ
+extends      W
+{
+    public static final Pattern NUMBERS_PATTERN =
+        Pattern.compile("\\D*(\\d++.\\d*)\\D*");
+
+    private static Logger log = Logger.getLogger(WQ.class);
+
+    protected TDoubleArrayList qs;
+
+    public WQ() {
+        this("");
+    }
+
+    public WQ(String name) {
+        super(name);
+        qs = new TDoubleArrayList();
+    }
+
+    public WQ(int capacity) {
+        this(capacity, "");
+    }
+
+
+    public WQ(int capacity, String name) {
+        super(capacity, name);
+        qs = new TDoubleArrayList(capacity);
+    }
+
+    public WQ(double [] qs, double [] ws) {
+        this(qs, ws, "");
+    }
+
+    public WQ(double [] qs, double [] ws, String name) {
+        super(name);
+        this.ws = new TDoubleArrayList(ws);
+        this.qs = new TDoubleArrayList(qs);
+    }
+
+
+    public Double getRawValue() {
+        if (name == null || name.length() == 0) {
+            // this should never happen
+            return null;
+        }
+
+        Matcher m = NUMBERS_PATTERN.matcher(name);
+
+        if (m.matches()) {
+            String raw = m.group(1);
+
+            try {
+                return Double.valueOf(raw);
+            }
+            catch (NumberFormatException nfe) {
+                // do nothing
+            }
+        }
+
+        return null;
+    }
+
+    public void add(double w, double q) {
+        ws.add(w);
+        qs.add(q);
+    }
+
+    public double getQ(int idx) {
+        return qs.getQuick(idx);
+    }
+
+    @Override
+    public double [] get(int idx) {
+        return get(idx, new double [2]);
+    }
+
+    @Override
+    public double [] get(int idx, double [] dst) {
+        dst[0] = ws.getQuick(idx);
+        dst[1] = qs.getQuick(idx);
+        return dst;
+    }
+
+    public double [] getQs() {
+        return qs.toNativeArray();
+    }
+
+    @Override
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { ws, qs });
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQCKms.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,93 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * This class represents a pool of data triples that consists of 'W', 'Q' and
+ * 'KM' data with corrected 'W' values computed by a BackJumpCorrector.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQCKms 
+extends      WQKms
+{
+    protected TDoubleArrayList cws;
+
+    public WQCKms() {
+    }
+
+    public WQCKms(WQKms other, double [] cws) {
+        this.ws  = other.ws;
+        this.qs  = other.qs;
+        this.kms = other.kms;
+        this.cws = new TDoubleArrayList(cws);
+    }
+
+
+    public WQCKms(double[] kms, double[] qs, double[] ws, double[] cws) {
+        super(kms, qs, ws);
+
+        this.cws = new TDoubleArrayList(cws);
+    }
+
+    @Override
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { ws, qs, cws, kms });
+    }
+
+    /**
+     * Adds a new row to this data pool with corrected W.
+     *
+     * @param w a W.
+     * @param q a Q.
+     * @param kms a Kms.
+     * @param cw The corrected W.
+     */
+    public void add(double w, double q, double kms, double cw) {
+        super.add(w, q, kms);
+        cws.add(cw);
+    }
+
+    @Override
+    public double[] get(int idx) {
+        return get(idx, new double[4]);
+    }
+
+    /**
+     * This method returns a 4dim array of W, Q,Kms and corrected W.
+     *
+     * @param idx The position of the triple.
+     * @param dst destination array
+     *
+     * @return a 4dim array of [W, Q, Kms, CW] in dst.
+     */
+    @Override
+    public double[] get(int idx, double[] dst) {
+        dst = super.get(idx, dst);
+
+        if (dst.length < 4) {
+            return dst;
+        }
+
+        if (cws != null && cws.size() > idx) {
+            dst[3] = cws.getQuick(idx);
+        }
+
+        return dst;
+    }
+
+    public double getC(int idx) {
+        return cws.getQuick(idx);
+    }
+
+
+    /**
+     * Returns the double array of corrected W values.
+     *
+     * @return the double array of corrected W values.
+     */
+    public double[] getCWs() {
+        return cws.toNativeArray();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQDay.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,77 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TIntArrayList;
+
+/**
+ * This class represents a pool of data triples that consists of 'W', 'Q' and
+ * 'Day' data.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQDay
+extends      WQ
+{
+    protected TIntArrayList days;
+
+    public WQDay() {
+        super("");
+        days = new TIntArrayList();
+    }
+
+    public WQDay(int capacity) {
+        super(capacity);
+        days = new TIntArrayList(capacity);
+    }
+
+    public WQDay(int [] days, double [] ws, double [] qs) {
+        super(qs, ws, "");
+        this.days = new TIntArrayList(days);
+    }
+
+
+    public void add(int day, double w, double q) {
+        super.add(w, q);
+        days.add(day);
+    }
+
+
+    public boolean isIncreasing() {
+        int lo = getDay(0);
+        int hi = getDay(size()-1);
+
+        return lo < hi;
+    }
+
+
+    public int getDay(int idx) {
+        return days.getQuick(idx);
+    }
+
+    @Override
+    public void removeNaNs() {
+
+        int dest = 0;
+        int N = ws.size();
+
+        for (int i = 0; i < N; ++i) {
+            double wi = ws.getQuick(i);
+            double qi = qs.getQuick(i);
+
+            if (Double.isNaN(wi) || Double.isNaN(qi)) {
+                continue;
+            }
+
+            days.setQuick(dest, days.getQuick(i));
+            ws.setQuick(dest, wi);
+            qs.setQuick(dest, qi);
+            ++dest;
+        }
+
+        if (dest < N) {
+            days.remove(dest, N-dest);
+            ws  .remove(dest, N-dest);
+            qs  .remove(dest, N-dest);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,61 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.DefaultFacet;
+
+import de.intevation.flys.artifacts.WQKmsInterpolArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+/**
+ * Facet to show W|Q Values.
+ */
+public class WQFacet
+extends      DefaultFacet
+implements   FacetTypes {
+
+    /** Trivial Constructor. */
+    public WQFacet(String description) {
+        this(STATIC_WQ, description);
+    }
+
+
+    /**
+     * A Facet with WQ data.
+     */
+    public WQFacet(String name, String description) {
+        this.name        = name;
+        this.description = description;
+        this.index       = 0;
+    }
+
+
+    /**
+     * Returns the data this facet requires.
+     *
+     * @param artifact the owner artifact.
+     * @param context  the CallContext (ignored).
+     *
+     * @return the data.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        WQKmsInterpolArtifact interpolData =
+            (WQKmsInterpolArtifact) artifact;
+        return interpolData.getWQAtKm(10);
+    }
+
+
+    /**
+     * Create a deep copy of this Facet.
+     * @return a deep copy.
+     */
+    @Override
+    public WQKmsFacet deepCopy() {
+        WQKmsFacet copy = new WQKmsFacet(description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKms.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,123 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * This class represents a pool of data triples that consists of 'W', 'Q' and
+ * 'KM' data.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQKms
+extends      WQ
+implements   WKms
+{
+    private static Logger logger = Logger.getLogger(WQKms.class);
+
+    /** The array that contains the 'KMs' values. */
+    protected TDoubleArrayList kms;
+
+
+    public WQKms() {
+        this("");
+    }
+
+
+    public WQKms(String name) {
+        super(name);
+        this.kms = new TDoubleArrayList();
+    }
+
+
+    public WQKms(int capacity) {
+        this(capacity, "");
+    }
+
+
+    public WQKms(int capacity, String name) {
+        super(capacity, name);
+        this.kms = new TDoubleArrayList(capacity);
+    }
+
+    public WQKms(double [] kms, double [] qs, double [] ws) {
+        this(kms, qs, ws, "");
+    }
+
+
+    public WQKms(double [] kms, double [] qs, double [] ws, String name) {
+        super(qs, ws, name);
+        this.kms = new TDoubleArrayList(kms);
+    }
+
+    @Override
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { ws, qs, kms });
+    }
+
+    /**
+     * Adds a new row to this data pool.
+     *
+     * @param w a W.
+     * @param q a Q.
+     * @param kms a Kms.
+     */
+    public void add(double w, double q, double km) {
+        super.add(w, q);
+        kms.add(km);
+    }
+
+    @Override
+    public double [] get(int idx) {
+        return get(idx, new double [3]);
+    }
+
+    /**
+     * This method returns a triple of W, Q and Kms in a single 3dim array.
+     *
+     * @param idx The position of the triple.
+     * @param dst destination array
+     *
+     * @return a triple of [W, Q, Kms] in dst.
+     */
+    @Override
+    public double [] get(int idx, double [] dst) {
+        dst[0] = ws .getQuick(idx);
+        dst[1] = qs .getQuick(idx);
+        dst[2] = kms.getQuick(idx);
+        return dst;
+    }
+
+    @Override
+    public double getKm(int idx) {
+        return kms.getQuick(idx);
+    }
+
+    @Override
+    public TDoubleArrayList allKms() {
+        return kms;
+    }
+
+    @Override
+    public TDoubleArrayList allWs() {
+        return ws;
+    }
+
+    public double[] getKms() {
+        return kms.toNativeArray();
+    }
+
+    /**
+     * Returns a string that consist of the first and last kilometer.
+     *
+     * @return a string that consist of the first and last kilometer.
+     */
+    public String toString() {
+        double from = getKm(0);
+        double to   = getKm(size()-1);
+        return from + " - " + to;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKmsFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,73 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.StaticWQKmsArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * Facet to show W|Q|km Values.
+ * We have following 'Types' (from FacetTypes):
+ *   String STATIC_WQKMS = "other.wqkms";
+ *   String STATIC_WQMS_W = "other.wqkms.w";
+ *   String STATIC_WQKMS_Q = "other.wqkms.q";
+ */
+public class WQKmsFacet
+extends      DataFacet
+implements   FacetTypes {
+
+    /** House logger. */
+    private static Logger logger = Logger.getLogger(WQKmsFacet.class);
+
+    /** Trivial Constructor. */
+    public WQKmsFacet(String description) {
+        this(STATIC_WQKMS, description);
+    }
+
+
+    /**
+     * @param name Name of this facet (we have at least two flavors (w and q).
+     */
+    public WQKmsFacet(String name, String description) {
+        super(0, name, description, ComputeType.FEED, null, null);
+    }
+
+
+    /**
+     * Returns the data this facet requires.
+     *
+     * @param artifact the owner artifact.
+     * @param context  the CallContext (ignored).
+     *
+     * @return the data.
+     */
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("WQKmsFacet.getData");
+
+        StaticWQKmsArtifact staticData =
+            (StaticWQKmsArtifact) artifact;
+        Object res = staticData.compute(context, hash, stateId, type, false);
+
+        return res;
+    }
+
+
+    /**
+     * Create a deep copy of this Facet.
+     * @return a deep copy.
+     */
+    @Override
+    public WQKmsFacet deepCopy() {
+        WQKmsFacet copy = new WQKmsFacet(description);
+        copy.set(this);
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKmsFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,114 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+
+import org.hibernate.SQLQuery;
+import org.hibernate.type.StandardBasicTypes;
+
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+/**
+ * Factory to access ready-made WQKms for other (than computed) 'kinds' of 
+ * WST-data.
+ */
+public class WQKmsFactory
+{
+    private static Logger log = Logger.getLogger(WQKmsFactory.class);
+
+    /** Query to get km and wqs for wst_id and column_pos. */
+    public static final String SQL_SELECT_WQS =
+        "SELECT position, w, q FROM wst_value_table " +
+        "WHERE wst_id = :wst_id AND column_pos = :column_pos";
+
+    /** Query to get name for wst_id and column_pos. */
+    public static final String SQL_SELECT_NAME =
+        "SELECT name " +
+        "FROM wst_columns "+
+        "WHERE wst_id = :wst_id AND position = :column_pos";
+
+
+    /** Hidden constructor, use static methods instead. */
+    private WQKmsFactory() {
+    }
+
+
+    /**
+     * Get WKms for given column and wst_id, caring about the cache.
+     */
+    public static WQKms getWQKms(int column, int wst_id) {
+        log.debug("WQKmsFactory.getWQKms");
+        Cache cache = CacheFactory.getCache(StaticWQKmsCacheKey.CACHE_NAME);
+
+        StaticWQKmsCacheKey cacheKey;
+
+        if (cache != null) {
+            cacheKey = new StaticWQKmsCacheKey(wst_id, column);
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                log.debug("Got static wst values from cache");
+                return (WQKms)element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        WQKms values = getWQKmsUncached(column, wst_id);
+
+        if (values != null && cacheKey != null) {
+            log.debug("Store static wst values in cache.");
+            Element element = new Element(cacheKey, values);
+            cache.put(element);
+        }
+        return values;
+    }
+
+
+    /**
+     * Get WQKms from db.
+     * @param column the position columns value
+     * @param wst_id database id of the wst
+     * @return respective WQKms.
+     */
+    public static WQKms getWQKmsUncached(int column, int wst_id) {
+
+        if (log.isDebugEnabled()) {
+            log.debug("WQKmsFactory.getWQKmsUncached, column "
+                + column + ", wst_id " + wst_id);
+        }
+
+        WQKms wqkms = new WQKms(WKmsFactory.getWKmsName(column, wst_id));
+
+        Session session = SessionHolder.HOLDER.get();
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_WQS)
+            .addScalar("position", StandardBasicTypes.DOUBLE)
+            .addScalar("w",  StandardBasicTypes.DOUBLE)
+            .addScalar("q",  StandardBasicTypes.DOUBLE);
+        sqlQuery.setInteger("wst_id",     wst_id);
+        sqlQuery.setInteger("column_pos", column);
+
+        List<Object []> results = sqlQuery.list();
+
+        int lastColumn = Integer.MAX_VALUE;
+
+        for (int i = 0, N = results.size(); i < N; i++) {
+            Object[] row = results.get(i);
+            // add(w, q, km)
+            wqkms.add((Double) row[1], (Double) row[2], (Double) row[0]);
+        }
+
+        return wqkms;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQTJRDataSource.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,99 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.log4j.Logger;
+
+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 class WQTJRDataSource implements JRDataSource
+{
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(WQTJRDataSource.class);
+
+    /**
+     *
+     */
+    private ArrayList<String[]> data;
+    private HashMap<String, String> metaData;
+
+    private int index = -1;
+
+    /**
+     *
+     */
+    public WQTJRDataSource()
+    {
+        data = new ArrayList<String[]>();
+        metaData = new HashMap<String, String>();
+    }
+
+
+    /**
+     *
+     */
+    public void addData(String[] data) {
+        this.data.add(data);
+    }
+
+
+    /**
+     *
+     */
+    public void addMetaData(String key, String value) {
+        this.metaData.put(key, value);
+    }
+
+
+    /**
+     *
+     */
+    public boolean next() throws JRException
+    {
+        index++;
+
+        return (index < data.size());
+    }
+
+
+    /**
+     *
+     */
+    public Object getFieldValue(JRField field) throws JRException
+    {
+        Object value = "";
+        String fieldName = field.getName();
+        if ("river".equals(fieldName)) {
+            value = metaData.get("river");
+        }
+        if ("date".equals(fieldName)) {
+            value = metaData.get("date");
+        }
+        if ("calculation".equals(fieldName)) {
+            value = metaData.get("calculation");
+        }
+        if ("range".equals(fieldName)) {
+            value = data.get(index)[0];
+        }
+        if ("W".equals(fieldName)) {
+            value = data.get(index)[1];
+        }
+        if ("Q".equals(fieldName)) {
+            value = data.get(index)[2];
+        }
+        if ("delta".equals(fieldName)) {
+            value = data.get(index)[3];
+        }
+        if ("gaugename".equals(fieldName)) {
+            value = data.get(index)[4];
+        }
+        return value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQTimerange.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,42 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQTimerange extends WQ {
+
+    protected List<Timerange> ts;
+
+
+    public WQTimerange() {
+        super("");
+    }
+
+
+    public WQTimerange(String name) {
+        super(name);
+        ts = new ArrayList<Timerange>();
+    }
+
+
+    public void add(double w, double q, Timerange t) {
+        ws.add(w);
+        qs.add(q);
+        ts.add(t);
+    }
+
+
+    public Timerange getTimerange(int idx) {
+        return ts.get(idx);
+    }
+
+
+    public Timerange[] getTimeranges() {
+        return (Timerange[]) ts.toArray(new Timerange[ts.size()]);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WSPLGENCalculation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,82 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallMeta;
+
+
+public class WSPLGENCalculation extends Calculation {
+
+    private static final Logger log = Logger.getLogger(WSPLGENCalculation.class);
+
+    protected Map<Integer, String> errors;
+    protected Map<Integer, String> warnings;
+
+
+    public WSPLGENCalculation() {
+        errors   = new HashMap<Integer, String>();
+        warnings = new HashMap<Integer, String>();
+    }
+
+
+    public void addError(Integer key, String msg) {
+        log.debug("New error: (" + key + ") " + msg);
+        errors.put(key, msg);
+    }
+
+
+    public void addWarning(Integer key, String msg) {
+        log.debug("New warning: (" + key + ") " + msg);
+        warnings.put(key, msg);
+    }
+
+
+    public int numErrors() {
+        return errors.size();
+    }
+
+
+    public int numWarnings() {
+        return warnings.size();
+    }
+
+
+    @Override
+    public void toXML(Document document, CallMeta meta) {
+        Element root = document.createElement("problems");
+
+        if (numErrors() > 0) {
+            Set<Map.Entry<Integer, String>> entrySet = errors.entrySet();
+
+            for (Map.Entry<Integer, String> entry: entrySet) {
+                Element problem = document.createElement("problem");
+                problem.setAttribute("error", String.valueOf(entry.getKey()));
+                problem.setTextContent(entry.getValue());
+
+                root.appendChild(problem);
+            }
+        }
+
+        if (numWarnings() > 0) {
+            Set<Map.Entry<Integer, String>> entrySet = warnings.entrySet();
+
+            for (Map.Entry<Integer, String> entry: entrySet) {
+                Element problem = document.createElement("problem");
+                problem.setAttribute("error", String.valueOf(entry.getKey()));
+                problem.setTextContent(entry.getValue());
+
+                root.appendChild(problem);
+            }
+        }
+
+        document.appendChild(root);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WSPLGENJob.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,473 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.wsplgen.FacetCreator;
+
+
+public class WSPLGENJob {
+
+    public static final String GEL_SPERRE   = "SPERRE";
+    public static final String GEL_NOSPERRE = "NOSPERRE";
+
+
+    protected FLYSArtifact artifact;
+
+    protected CallContext callContext;
+
+    protected WSPLGENCalculation calculation;
+
+    protected FacetCreator facetCreator;
+
+    protected File workingDir;
+
+    protected String dgm;
+    protected String pro;
+    protected String wsp;
+    protected String wspTag;
+    protected String axis;
+    protected String area;
+    protected String gel;
+    protected String outFile;
+
+    protected List<String> lin;
+
+    protected int out;
+
+    protected double start;
+    protected double end;
+    protected double from;
+    protected double to;
+    protected double diff;
+    protected double dist;
+
+
+
+    public WSPLGENJob(
+        FLYSArtifact       flys,
+        File               workingDir,
+        FacetCreator       facetCreator,
+        CallContext        context,
+        WSPLGENCalculation calculation)
+    {
+        this.artifact     = flys;
+        this.workingDir   = workingDir;
+        this.facetCreator = facetCreator;
+        this.callContext  = context;
+        this.calculation  = calculation;
+
+        out   = -1;
+        start = Double.NaN;
+        end   = Double.NaN;
+        from  = Double.NaN;
+        to    = Double.NaN;
+        diff  = Double.NaN;
+        dist  = Double.NaN;
+        lin   = new ArrayList<String>(3);
+    }
+
+
+    public File getWorkingDir() {
+        return workingDir;
+    }
+
+
+    public FLYSArtifact getArtifact() {
+        return artifact;
+    }
+
+
+    public FacetCreator getFacetCreator() {
+        return facetCreator;
+    }
+
+
+    public WSPLGENCalculation getCalculation() {
+        return calculation;
+    }
+
+
+    public CallContext getCallContext() {
+        return callContext;
+    }
+
+
+    public void setWsp(String wsp) {
+        this.wsp = wsp;
+    }
+
+
+    public String getWsp() {
+        return wsp;
+    }
+
+
+    public void setWspTag(String wspTag) {
+        this.wspTag = wspTag;
+    }
+
+
+    public String getWspTag() {
+        return wspTag;
+    }
+
+
+    public void addLin(String lin) {
+        this.lin.add(lin);
+    }
+
+
+    public List<String> getLin() {
+        return lin;
+    }
+
+
+    public void setAxis(String axis) {
+        this.axis = axis;
+    }
+
+
+    public String getAxis() {
+        return axis;
+    }
+
+
+    public void setArea(String area) {
+        this.area = area;
+    }
+
+
+    public String getArea() {
+        return area;
+    }
+
+
+    public void setOut(int out) {
+        this.out = out;
+    }
+
+
+    public int getOut() {
+        return out;
+    }
+
+
+    public void setOutFile(String outFile) {
+        this.outFile = outFile;
+    }
+
+
+    public String getOutFile() {
+        return outFile;
+    }
+
+
+    public void setStart(double start) {
+        this.start = start;
+    }
+
+
+    public double getStart() {
+        return start;
+    }
+
+
+    public void setEnd(double end) {
+        this.end = end;
+    }
+
+
+    public double getEnd() {
+        return end;
+    }
+
+
+    public void setPro(String pro) {
+        this.pro = pro;
+    }
+
+
+    public String getPro() {
+        return pro;
+    }
+
+
+    public void setDgm(String dgm) {
+        this.dgm = dgm;
+    }
+
+
+    public String getDgm() {
+        return dgm;
+    }
+
+
+    public void setFrom(double from) {
+        this.from = from;
+    }
+
+
+    public double getFrom() {
+        return from;
+    }
+
+
+    public void setTo(double to) {
+        this.to = to;
+    }
+
+
+    public double getTo() {
+        return to;
+    }
+
+
+    public void setDiff(double diff) {
+        this.diff = diff;
+    }
+
+
+    public double getDiff() {
+        return diff;
+    }
+
+
+    public void setDist(double dist) {
+        this.dist = dist;
+    }
+
+
+    public double getDist() {
+        return dist;
+    }
+
+
+    public void setGel(String gel) {
+        if (gel == null || gel.length() == 0) {
+            return;
+        }
+
+        if (gel.equals(GEL_SPERRE) || gel.equals(GEL_NOSPERRE)) {
+            this.gel = gel;
+        }
+    }
+
+
+    public String getGel() {
+        return gel;
+    }
+
+
+    public void toFile(File file)
+    throws IOException, IllegalArgumentException
+    {
+        PrintWriter writer = null;
+
+        try {
+            writer =
+                new PrintWriter(
+                    new OutputStreamWriter(
+                        new FileOutputStream(file)));
+
+            write(writer);
+        }
+        finally {
+            if (writer != null) {
+                writer.flush();
+                writer.close();
+            }
+        }
+    }
+
+
+    protected void write(PrintWriter writer)
+    throws IOException, IllegalArgumentException
+    {
+        writeWsp(writer);    // required
+        writeWspTag(writer); // required
+        writeLin(writer);
+        writeAxis(writer);
+        writeArea(writer);
+        writeOut(writer);
+        writeOutFile(writer);
+        writeRange(writer);
+        writeDelta(writer);
+        writeGel(writer);
+        writeDist(writer);
+        writePro(writer);
+        writeDgm(writer);    // required
+    }
+
+
+    protected void writeWsp(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        String wsp = getWsp();
+
+        if (wsp != null && wsp.length() > 0) {
+            writer.println("-WSP=\"" + wsp + "\"");
+            return;
+        }
+
+        throw new IllegalArgumentException("Required WSP missing!");
+    }
+
+    protected void writeWspTag(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        String wspTag = getWspTag();
+
+        if (wspTag != null && wspTag.length() > 0) {
+            writer.println("-WSPTAG=\"" + wspTag + "\"");
+            return;
+        }
+
+        throw new IllegalArgumentException("Required WSPTAG missing!");
+    }
+
+    protected void writeLin(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        List<String> lins = getLin();
+
+        if (lins != null && !lins.isEmpty()) {
+            for (String lin: lins) {
+                writer.println("-LIN=\"" + lin + "\"");
+            }
+        }
+    }
+
+    protected void writeAxis(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        String axis = getAxis();
+
+        if (axis != null && axis.length() > 0) {
+            writer.println("-ACHSE=\"" + axis + "\"");
+        }
+    }
+
+    protected void writeGel(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        if (area != null && area.length() > 0) {
+            writer.println("-GEL=" + getGel());
+        }
+    }
+
+    protected void writeArea(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        String area = getArea();
+
+        if (area != null && area.length() > 0) {
+            writer.println("-GEBIET=\"" + area + "\"");
+        }
+    }
+
+
+    protected void writeOut(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        int out = getOut();
+
+        if (out >= 0) {
+            writer.println("-OUTPUT=" + String.valueOf(out));
+        }
+    }
+
+    protected void writeOutFile(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        String outFile = getOutFile();
+
+        if (outFile != null && outFile.length() > 0) {
+            writer.println("-AUSGABE=\""+ outFile + "\"");
+        }
+    }
+
+    protected void writeRange(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        StringBuilder sb = new StringBuilder("-STRECKE=");
+
+        double start = getStart();
+        double end   = getEnd();
+
+        if (Double.isNaN(start) && Double.isNaN(end)) {
+            return;
+        }
+
+        if (! Double.isNaN(getStart())) {
+            sb.append(getStart());
+        }
+
+        sb.append(",");
+
+        if (! Double.isNaN(getEnd())) {
+            sb.append(getEnd());
+        }
+
+        writer.println(sb.toString());
+    }
+
+    protected void writeDelta(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        StringBuilder sb = new StringBuilder("-DELTA=");
+        if (! Double.isNaN(from)) {
+            sb.append(from);
+        }
+
+        sb.append(",");
+
+        if (! Double.isNaN(to)) {
+            sb.append(to);
+        }
+
+        sb.append(",");
+
+        if (! Double.isNaN(diff)) {
+            sb.append(diff);
+        }
+
+        writer.println(sb.toString());
+    }
+
+    protected void writeDist(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        if (! Double.isNaN(getDist())) {
+            writer.println("-DIST=" + String.valueOf(getDist()));
+        }
+    }
+
+    protected void writePro(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        if (pro != null && pro.length() > 0) {
+            writer.println("-PRO=\"" + getPro() + "\"");
+        }
+    }
+
+    protected void writeDgm(PrintWriter writer)
+    throws IllegalArgumentException
+    {
+        if (dgm != null && dgm.length() > 0) {
+            writer.println("-DGM=\"" + getDgm() + "\"");
+            return;
+        }
+
+        throw new IllegalArgumentException("Required DGM missing!");
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WSPLGENLayerFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,45 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+public class WSPLGENLayerFacet
+extends      WMSLayerFacet
+{
+    public WSPLGENLayerFacet() {
+    }
+
+
+    public WSPLGENLayerFacet(int index, String name, String description) {
+        this(index, name, description, ComputeType.FEED, null, null);
+    }
+
+
+    public WSPLGENLayerFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash
+    ) {
+        super(index, name, description, type, stateId, hash);
+    }
+
+    public WSPLGENLayerFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateId,
+        String      hash,
+        String      url
+    ) {
+        super(index, name, description, type, stateId, hash, url);
+    }
+
+
+    @Override
+    public boolean isQueryable() {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WSPLGENReportFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,56 @@
+package de.intevation.flys.artifacts.model;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+/**
+ * This facet is used to provide WSPLGEN reports <b>only</b>.
+ */
+public class WSPLGENReportFacet extends ReportFacet {
+
+    private static Logger logger = Logger.getLogger(WSPLGENReportFacet.class);
+
+
+    protected CalculationResult result;
+
+
+    public WSPLGENReportFacet() {
+    }
+
+
+    public WSPLGENReportFacet(
+        ComputeType       type,
+        String            hash,
+        String            stateId,
+        CalculationResult result
+    ) {
+        super(type, hash, stateId);
+        this.result = result;
+    }
+
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        return result.getReport();
+    }
+
+
+    @Override
+    public Facet deepCopy() {
+        WSPLGENReportFacet copy = new WSPLGENReportFacet();
+        copy.set(this);
+        copy.type    = type;
+        copy.hash    = hash;
+        copy.stateId = stateId;
+        copy.result  = result;
+        return copy;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WW.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,200 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.math.Function;
+import de.intevation.flys.artifacts.math.Identity;
+
+import gnu.trove.TDoubleArrayList;
+
+public class WW
+extends      W
+{
+    public static class ApplyFunctionIterator
+    {
+        protected Function function1;
+        protected Function function2;
+        protected int      pos;
+        protected WW       ww;
+
+        public ApplyFunctionIterator(WW ww) {
+            this(ww, Identity.IDENTITY, Identity.IDENTITY);
+        }
+
+        public ApplyFunctionIterator(
+            WW       ww, 
+            Function function1,
+            Function function2
+        ) {
+            this.ww        = ww;
+            this.function1 = function1;
+            this.function2 = function2;
+        }
+
+        public boolean hasNext() {
+            return pos < ww.size();
+        }
+
+        public int size() {
+            return ww.size();
+        }
+
+        public void reset() {
+            pos = 0;
+        }
+
+        public WW getWW() {
+            return ww;
+        }
+
+        public void get(int idx, double [] wwPair) {
+            wwPair[0] = function1.value(ww.getW(idx));
+            wwPair[1] = function2.value(ww.getW2(idx));
+        }
+
+        public void next(double [] wwPair) {
+            get(pos++, wwPair);
+        }
+    } // class FunctionIterator
+
+    protected TDoubleArrayList ws2;
+
+    protected double startKm;
+    protected double endKm;
+
+    protected Double startDatum;
+    protected Double endDatum;
+
+    public WW() {
+        this("");
+    }
+
+    public WW(String name) {
+        super(name);
+    }
+
+    public WW(int capacity) {
+        this(capacity, "");
+    }
+
+    public WW(int capacity, String name) {
+        super(capacity, name);
+        ws2 = new TDoubleArrayList(capacity);
+    }
+
+    public WW(
+        String    name, 
+        double    startKm,
+        Double    startDatum,
+        double [] ws, 
+        double    endKm,
+        Double    endDatum,
+        double [] ws2
+    ) {
+        this.name       = name;
+        this.ws         = new TDoubleArrayList(ws);
+        this.ws2        = new TDoubleArrayList(ws2);
+        this.startKm    = startKm;
+        this.startDatum = startDatum;
+        this.endKm      = endKm;
+        this.endDatum   = endDatum;
+    }
+
+    public WW(String name, TDoubleArrayList ws, TDoubleArrayList ws2) {
+        this.name = name;
+        this.ws  = ws;
+        this.ws2 = ws2;
+    }
+
+    public void add(double w1, double w2) {
+        ws .add(w1);
+        ws2.add(w2);
+    }
+
+    public double getW1(int idx) {
+        return ws.getQuick(idx);
+    }
+
+    public double getW2(int idx) {
+        return ws2.getQuick(idx);
+    }
+
+    public double [] getWs2() {
+        return ws2.toNativeArray();
+    }
+
+    @Override
+    public double [] get(int idx) {
+        return get(idx, new double[2]);
+    }
+
+    @Override
+    public double [] get(int idx, double [] dst) {
+        dst[0] = ws .getQuick(idx);
+        dst[1] = ws2.getQuick(idx);
+        return dst;
+    }
+
+    public double getStartKm() {
+        return startKm;
+    }
+
+    public void setStartKm(double startKm) {
+        this.startKm = startKm;
+    }
+
+    public double getEndKm() {
+        return endKm;
+    }
+
+    public void setEndKm(double endKm) {
+        this.endKm = endKm;
+    }
+
+    public Double getStartDatum() {
+        return startDatum;
+    }
+
+    public boolean startAtGauge() {
+        return startDatum != null;
+    }
+
+    public boolean endAtGauge() {
+        return endDatum != null;
+    }
+
+    public void setStartDatum(Double startDatum) {
+        this.startDatum = startDatum;
+    }
+
+    public Double getEndDatum() {
+        return endDatum;
+    }
+
+    public void setEndDatum(Double endDatum) {
+        this.endDatum = endDatum;
+    }
+
+    @Override
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { ws, ws2 });
+    }
+
+    public double minWs2() {
+        return ws2.min();
+    }
+
+    // Note that we can also easily define a Function to do so.
+    public double getRelHeight1Cm(int idx) {
+        if (this.startAtGauge()) {
+            return (ws.getQuick(idx) - getStartDatum())*100d;
+        }
+        else return ws.getQuick(idx)*100d;
+    }
+
+    public double getRelHeight2Cm(int idx) {
+        if (this.endAtGauge()) {
+            return (ws2.getQuick(idx) - getEndDatum())*100d;
+        }
+        else return ws2.getQuick(idx)*100d;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WWAxisTypes.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,84 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.math.Function;
+import de.intevation.flys.artifacts.math.Identity;
+import de.intevation.flys.artifacts.math.AddScaleFunction;
+
+public class WWAxisTypes
+{
+    protected boolean inCm1;
+    protected boolean inCm2;
+
+    public WWAxisTypes() {
+        inCm1 = inCm2 = true;
+    }
+
+    public WWAxisTypes(WW ww) {
+        this();
+        classify(ww);
+    }
+
+    public void classify(WW ww) {
+        if (!ww.startAtGauge()) inCm1 = false;
+        if (!ww.endAtGauge())   inCm2 = false;
+    }
+
+    public boolean getInCm(int index) {
+        switch (index) {
+            case 0:  return inCm1;
+            case 1:  return inCm2;
+            default: return false;
+        }
+    }
+
+    public boolean getInCm1() {
+        return inCm1;
+    }
+
+    public void setInCm1(boolean inCm1) {
+        this.inCm1 = inCm1;
+    }
+
+    public boolean getInCm2() {
+        return inCm2;
+    }
+
+    public void setInCm2(boolean inCm2) {
+        this.inCm2 = inCm2;
+    }
+
+    public WW.ApplyFunctionIterator transform(WW ww) {
+        return transform(ww, false);
+    }
+
+    private static final double zero(Double d) {
+        return d == null ? 0 : d;
+    }
+
+    public WW.ApplyFunctionIterator transform(WW ww, boolean normalized) {
+
+        Function function1;
+        Function function2;
+
+        if (!normalized) {
+            function1 = inCm1
+                ? new AddScaleFunction(-ww.getStartDatum(), 100d)
+                : Identity.IDENTITY;
+
+            function2 = inCm2
+                ? new AddScaleFunction(-ww.getEndDatum(), 100d)
+                : Identity.IDENTITY;
+        }
+        else {
+            double minW1 = ww.minWs();
+            double minW2 = ww.minWs2();
+            double scale1 = inCm1 ? 100d : 1d;
+            double scale2 = inCm2 ? 100d : 1d;
+            function1 = new AddScaleFunction(-minW1, scale1);
+            function2 = new AddScaleFunction(-minW2, scale2);
+        }
+
+        return new WW.ApplyFunctionIterator(ww, function1, function2);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WWQQ.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,97 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.math.Identity;
+
+import gnu.trove.TDoubleArrayList;
+
+public class WWQQ
+extends      WW
+{
+    public static class ApplyFunctionIterator
+    extends             WW.ApplyFunctionIterator
+    {
+        public ApplyFunctionIterator(WWQQ ww) {
+            super(ww, Identity.IDENTITY, Identity.IDENTITY);
+        }
+
+        @Override
+        public void get(int idx, double [] wwqqPair) {
+            WWQQ wwqq = (WWQQ)ww;
+            wwqqPair[0] = function1.value(wwqq.getW(idx));
+            wwqqPair[1] = function2.value(wwqq.getW2(idx));
+            wwqqPair[2] = wwqq.getQ1(idx);
+            wwqqPair[3] = wwqq.getQ2(idx);
+        }
+    } // class ApplyFunctionIterator
+
+    protected TDoubleArrayList qs1;
+    protected TDoubleArrayList qs2;
+
+    public WWQQ() {
+        this("");
+    }
+
+    public WWQQ(String name) {
+        super(name);
+    }
+
+    public WWQQ(int capacity) {
+        this(capacity, "");
+    }
+
+    public WWQQ(int capacity, String name) {
+        super(capacity, name);
+    }
+
+    public WWQQ(
+        String    name, 
+        double    startKm,
+        Double    startDatum,
+        double [] ws1,
+        double [] qs1,
+        double    endKm,
+        Double    endDatum,
+        double [] ws2,
+        double [] qs2
+    ) {
+        super(name, startKm, startDatum, ws1, endKm, endDatum, ws2);
+        this.qs1 = new TDoubleArrayList(qs1);
+        this.qs2 = new TDoubleArrayList(qs2);
+    }
+
+    public double getQ1(int idx) {
+        return qs1.getQuick(idx);
+    }
+
+    public double getQ2(int idx) {
+        return qs2.getQuick(idx);
+    }
+
+    public double [] getQs1() {
+        return qs1.toNativeArray();
+    }
+
+    public double [] getQs2() {
+        return qs2.toNativeArray();
+    }
+
+    @Override
+    public double [] get(int idx) {
+        return get(idx, new double[4]);
+    }
+
+    @Override
+    public double [] get(int idx, double [] dst) {
+        dst[0] = ws .getQuick(idx);
+        dst[1] = ws2.getQuick(idx);
+        dst[2] = qs1.getQuick(idx);
+        dst[3] = qs2.getQuick(idx);
+        return dst;
+    }
+
+    @Override
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { ws, ws2, qs1, qs2 });
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WWQQJRDataSource.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,120 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.apache.log4j.Logger;
+
+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 class WWQQJRDataSource implements JRDataSource
+{
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(WWQQJRDataSource.class);
+
+    /**
+     *
+     */
+    private ArrayList<String[]> data;
+    private HashMap<String, String> metaData;
+
+    private int index = -1;
+
+    /**
+     *
+     */
+    public WWQQJRDataSource()
+    {
+        data = new ArrayList<String[]>();
+        metaData = new HashMap<String, String>();
+    }
+
+
+    /**
+     *
+     */
+    public void addData(String[] data) {
+        this.data.add(data);
+    }
+
+
+    /**
+     *
+     */
+    public void addMetaData(String key, String value) {
+        this.metaData.put(key, value);
+    }
+
+
+    /**
+     *
+     */
+    public boolean next() throws JRException
+    {
+        index++;
+
+        return (index < data.size());
+    }
+
+
+    /**
+     *
+     */
+    public Object getFieldValue(JRField field) throws JRException
+    {
+        Object value = "";
+        String fieldName = field.getName();
+        if ("river".equals(fieldName)) {
+            value = metaData.get("river");
+        }
+        if ("date".equals(fieldName)) {
+            value = metaData.get("date");
+        }
+        if ("calculation".equals(fieldName)) {
+            value = metaData.get("calculation");
+        }
+        if ("reference".equals(fieldName)) {
+            value = metaData.get("reference");
+        }
+        if ("location".equals(fieldName)) {
+            value = metaData.get("location");
+        }
+        if ("km1".equals(fieldName)) {
+            value = data.get(index)[0];
+        }
+        if ("location1".equals(fieldName)) {
+            value = data.get(index)[1];
+        }
+        if ("W1".equals(fieldName)) {
+            value = data.get(index)[2];
+        }
+        if ("Q1".equals(fieldName)) {
+            value = data.get(index)[3];
+        }
+        if ("km2".equals(fieldName)) {
+            value = data.get(index)[4];
+        }
+        if ("location2".equals(fieldName)) {
+            value = data.get(index)[5];
+        }
+        if ("W2".equals(fieldName)) {
+            value = data.get(index)[6];
+        }
+        if ("Q2".equals(fieldName)) {
+            value = data.get(index)[7];
+        }
+        if ("Wcm1".equals(fieldName)) {
+            value = data.get(index)[8];
+        }
+        if("Wcm2".equals(fieldName)) {
+            value = data.get(index)[9];
+        }
+        return value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WaterlevelFacet.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,74 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Facet of a Waterlevel (WQKms).
+ */
+public class WaterlevelFacet extends DataFacet {
+
+    private static Logger logger = Logger.getLogger(WaterlevelFacet.class);
+
+    public WaterlevelFacet(int index, String name, String description) {
+        super(index, name, description, ComputeType.ADVANCE, null, null);
+    }
+
+    public WaterlevelFacet(
+        int         index,
+        String      name,
+        String      description,
+        ComputeType type,
+        String      stateID,
+        String      hash
+
+    ) {
+        super(index, name, description, type, hash, stateID);
+    }
+
+    public WaterlevelFacet() {
+    }
+
+
+    /**
+     * Get waterlevel data.
+     * @return a WQKms at given index.
+     */
+    public Object getData(Artifact artifact, CallContext context) {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Get data for waterlevels at index: " + index +
+                " /stateId: " + stateId);
+        }
+
+        FLYSArtifact winfo = (FLYSArtifact)artifact;
+
+        CalculationResult res = (CalculationResult)
+            winfo.compute(context, hash, stateId, type, false);
+
+        WQKms [] wqkms = (WQKms [])res.getData();
+
+        return wqkms[index];
+    }
+
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        WaterlevelFacet copy = new WaterlevelFacet();
+        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=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,48 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.List;
+
+import de.intevation.flys.backend.SessionHolder;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WstFactory {
+
+    public static final int DEFAULT_KIND = 0;
+
+    /** We don't need to instantiate concrete objects of this class. */
+    private WstFactory() {
+    }
+
+
+    /**
+     * Returns the Wst object for a given river.
+     *
+     * @param river The river.
+     *
+     * @return the Wst of <i>river</i>.
+     */
+    public static Wst getWst(River river) {
+        return getWst(river, DEFAULT_KIND);
+    }
+
+    public static Wst getWst(River river, int kind) {
+        Session session = SessionHolder.HOLDER.get();
+
+        Query query = session.createQuery(
+            "from Wst where river=:river and kind = :kind");
+        query.setParameter("river", river);
+        query.setInteger("kind", kind);
+
+        List<Wst> wsts = query.list();
+
+        return wsts.isEmpty() ? null : wsts.get(0);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstLine.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,88 @@
+package de.intevation.flys.artifacts.model;
+
+import gnu.trove.TDoubleArrayList;
+
+
+/**
+ * A model class that is used to store a line of a WST.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WstLine {
+
+    /** The kilometer value of the line.*/
+    protected double km;
+
+    /** The W values.*/
+    protected TDoubleArrayList ws;
+
+    /** The Q values.*/
+    protected TDoubleArrayList qs;
+
+
+    /**
+     * A constructor that builds a new WstLine for a specific kilometer.
+     *
+     * @param km The kilometer.
+     */
+    public WstLine(double km) {
+        this.km = km;
+        this.ws = new TDoubleArrayList();
+        this.qs = new TDoubleArrayList();
+    }
+
+
+    /**
+     * Adds a pair of W/Q to this line.
+     *
+     * @param w The W value.
+     * @param q The Q value.
+     */
+    public void add(double w, double q) {
+        ws.add(w);
+        qs.add(q);
+    }
+
+
+    /**
+     * Returns the kilometer of this line.
+     *
+     * @return the kilomter of this line.
+     */
+    public double getKm() {
+        return km;
+    }
+
+
+    /**
+     * Returns the W value at index <i>idx</i> of this line.
+     *
+     * @param idx The position of the desired W value.
+     *
+     * @return the W at position <i>idx</i>.
+     */
+    public double getW(int idx) {
+        return ws.size() > idx ? ws.get(idx) : -1d;
+    }
+
+
+    /**
+     * Returns the Q values of this line.
+     *
+     * @return the Q values of this line.
+     */
+    public double[] getQs() {
+        return qs.toNativeArray();
+    }
+
+
+    /**
+     * Returns the number of columns this line consists of.
+     *
+     * @return the columns this line consists of.
+     */
+    public int getSize() {
+        return qs.size();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1216 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import de.intevation.flys.artifacts.math.Linear;
+import de.intevation.flys.artifacts.math.Function;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Collections;
+
+import org.apache.log4j.Logger;
+
+import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
+
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+
+import org.apache.commons.math.ArgumentOutsideDomainException;
+
+import org.apache.commons.math.exception.MathIllegalArgumentException;
+
+import gnu.trove.TDoubleArrayList;
+
+/**
+ * W, Q and km data from database 'wsts' spiced with interpolation algorithms.
+ */
+public class WstValueTable
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(WstValueTable.class);
+
+    public static final int DEFAULT_Q_STEPS = 500;
+
+    public static final int RELATE_WS_SAMPLES = 200;
+
+    /**
+     * A Column in the table, typically representing one measurement session.
+     */
+    public static final class Column
+    implements                Serializable
+    {
+        protected String name;
+
+        protected QRangeTree qRangeTree;
+
+        public Column() {
+        }
+
+        public Column(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+    public QRangeTree getQRangeTree() {
+            return qRangeTree;
+        }
+
+        public void setQRangeTree(QRangeTree qRangeTree) {
+            this.qRangeTree = qRangeTree;
+        }
+    } // class Column
+
+    /**
+     * A (weighted) position used for interpolation.
+     */
+    public static final class QPosition {
+
+        protected int    index;
+        protected double weight;
+
+        public QPosition() {
+        }
+
+        public QPosition(int index, double weight) {
+            this.index  = index;
+            this.weight = weight;
+        }
+
+        public QPosition set(int index, double weight) {
+            this.index  = index;
+            this.weight = weight;
+            return this;
+        }
+
+    } // class Position
+
+    public static final class SplineFunction {
+
+        public PolynomialSplineFunction spline;
+        public double []                splineQs;
+        public double []                splineWs;
+
+        public SplineFunction(
+            PolynomialSplineFunction spline,
+            double []                splineQs, 
+            double []                splineWs
+        ) {
+            this.spline   = spline;
+            this.splineQs = splineQs;
+            this.splineWs = splineWs;
+        }
+
+        public double [][] sample(
+            int         numSamples, 
+            double      km, 
+            Calculation errors
+        ) {
+            double minQ = getQMin();
+            double maxQ = getQMax();
+
+            double [] outWs = new double[numSamples];
+            double [] outQs = new double[numSamples];
+
+            Arrays.fill(outWs, Double.NaN);
+            Arrays.fill(outQs, Double.NaN);
+
+            double stepWidth = (maxQ - minQ)/numSamples;
+
+            try {
+                double q = minQ;
+                for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
+                    outWs[i] = spline.value(outQs[i] = q);
+                }
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                if (errors != null) {
+                    errors.addProblem(km, "spline.interpolation.failed");
+                }
+                log.error("spline interpolation failed.", aode);
+            }
+
+            return new double [][] { outWs, outQs };
+        }
+
+        public double getQMin() {
+            return Math.min(splineQs[0], splineQs[splineQs.length-1]);
+        }
+
+        public double getQMax() {
+            return Math.max(splineQs[0], splineQs[splineQs.length-1]);
+        }
+
+        /** Constructs a continues index between the columns to Qs. */
+        public PolynomialSplineFunction createIndexQRelation() {
+
+            double [] indices = new double[splineQs.length];
+            for (int i = 0; i < indices.length; ++i) {
+                indices[i] = i;
+            }
+
+            try {
+                SplineInterpolator interpolator = new SplineInterpolator();
+                return interpolator.interpolate(indices, splineQs);
+            }
+            catch (MathIllegalArgumentException miae) {
+                // Ignore me!
+            }
+            return null;
+        }
+    } // class SplineFunction
+
+    /**
+     * A row, typically a position where measurements were taken.
+     */
+    public static final class Row
+    implements                Serializable, Comparable<Row>
+    {
+        double    km;
+        double [] ws;
+
+        public Row() {
+        }
+
+        public Row(double km) {
+            this.km = km;
+        }
+
+        public Row(double km, double [] ws) {
+            this(km);
+            this.ws = ws;
+        }
+
+        /**
+         * Compare according to place of measurement (km).
+         */
+        public int compareTo(Row other) {
+            double d = km - other.km;
+            if (d < -0.0001) return -1;
+            if (d >  0.0001) return +1;
+            return 0;
+        }
+
+        /**
+         * Interpolate Ws, given Qs and a km.
+         *
+         * @param iqs Given ("input") Qs.
+         * @param ows Resulting ("output") Ws.
+         * @param table Table of which to use data for interpolation.
+         */
+        public void interpolateW(
+            Row           other,
+            double        km,
+            double []     iqs,
+            double []     ows,
+            WstValueTable table,
+            Calculation   errors
+        ) {
+            double kmWeight = Linear.factor(km, this.km, other.km);
+
+            QPosition qPosition = new QPosition();
+
+            for (int i = 0; i < iqs.length; ++i) {
+                if (table.getQPosition(km, iqs[i], qPosition) != null) {
+                    double wt =       getW(qPosition);
+                    double wo = other.getW(qPosition);
+                    if (Double.isNaN(wt) || Double.isNaN(wo)) {
+                        if (errors != null) {
+                            errors.addProblem(
+                                km, "cannot.find.w.for.q", iqs[i]);
+                        }
+                        ows[i] = Double.NaN;
+                    }
+                    else {
+                        ows[i] = Linear.weight(kmWeight, wt, wo);
+                    }
+                }
+                else {
+                    if (errors != null) {
+                        errors.addProblem(km, "cannot.find.q", iqs[i]);
+                    }
+                    ows[i] = Double.NaN;
+                }
+            }
+        }
+
+
+        public SplineFunction createSpline(
+            WstValueTable table,
+            Calculation   errors
+        ) {
+            int W = ws.length;
+
+            if (W < 1) {
+                if (errors != null) {
+                    errors.addProblem(km, "no.ws.found");
+                }
+                return null;
+            }
+
+            double [] splineQs = new double[W];
+
+            for (int i = 0; i < W; ++i) {
+                double sq = table.getQIndex(i, km);
+                if (Double.isNaN(sq) && errors != null) {
+                    errors.addProblem(
+                        km, "no.q.found.in.column", (i+1));
+                }
+                splineQs[i] = sq;
+            }
+
+            try {
+                SplineInterpolator interpolator = new SplineInterpolator();
+                PolynomialSplineFunction spline =
+                    interpolator.interpolate(splineQs, ws);
+
+                return new SplineFunction(spline, splineQs, ws);
+            }
+            catch (MathIllegalArgumentException miae) {
+                if (errors != null) {
+                    errors.addProblem(km, "spline.creation.failed");
+                }
+                log.error("spline creation failed", miae);
+            }
+            return null;
+        }
+
+        public SplineFunction createSpline(
+            Row           other,
+            double        km,
+            WstValueTable table,
+            Calculation   errors
+        ) {
+            int W = Math.min(ws.length, other.ws.length);
+
+            if (W < 1) {
+                if (errors != null) {
+                    errors.addProblem("no.ws.found");
+                }
+                return null;
+            }
+
+            double factor = Linear.factor(km, this.km, other.km);
+
+            double [] splineQs = new double[W];
+            double [] splineWs = new double[W];
+
+            for (int i = 0; i < W; ++i) {
+                double wws = Linear.weight(factor, ws[i], other.ws[i]);
+                double wqs = Linear.weight(
+                    factor,
+                    table.getQIndex(i, km),
+                    table.getQIndex(i, other.km));
+
+                if (Double.isNaN(wws) || Double.isNaN(wqs)) {
+                    if (errors != null) {
+                        errors.addProblem(km, "cannot.find.w.or.q");
+                    }
+                }
+
+                splineWs[i] = wws;
+                splineQs[i] = wqs;
+            }
+
+            SplineInterpolator interpolator = new SplineInterpolator();
+
+            try {
+                PolynomialSplineFunction spline =
+                    interpolator.interpolate(splineQs, splineWs);
+
+                return new SplineFunction(spline, splineQs, splineWs);
+            }
+            catch (MathIllegalArgumentException miae) {
+                if (errors != null) {
+                    errors.addProblem(km, "spline.creation.failed");
+                }
+                log.error("spline creation failed", miae);
+            }
+
+            return null;
+        }
+
+        public double [][] interpolateWQ(
+            Row           other,
+            double        km,
+            int           steps,
+            WstValueTable table,
+            Calculation   errors
+        ) {
+            SplineFunction sf = createSpline(other, km, table, errors);
+
+            return sf != null
+                ? sf.sample(steps, km, errors)
+                : new double[2][0];
+        }
+
+
+        public double [][] interpolateWQ(
+            int           steps,
+            WstValueTable table,
+            Calculation   errors
+        ) {
+            SplineFunction sf = createSpline(table, errors);
+
+            return sf != null
+                ? sf.sample(steps, km, errors)
+                : new double[2][0];
+        }
+
+
+        public double getW(QPosition qPosition) {
+            int    index  = qPosition.index;
+            double weight = qPosition.weight;
+
+            return weight == 1.0
+                ? ws[index]
+                : Linear.weight(weight, ws[index-1], ws[index]);
+        }
+
+        public double getW(
+            Row       other,
+            double    km,
+            QPosition qPosition
+        ) {
+            double kmWeight = Linear.factor(km, this.km, other.km);
+
+            int    index  = qPosition.index;
+            double weight = qPosition.weight;
+
+            double tw, ow;
+
+            if (weight == 1.0) {
+                tw = ws[index];
+                ow = other.ws[index];
+            }
+            else {
+                tw = Linear.weight(weight, ws[index-1], ws[index]);
+                ow = Linear.weight(weight, other.ws[index-1], other.ws[index]);
+            }
+
+            return Linear.weight(kmWeight, tw, ow);
+        }
+
+        public double [] findQsForW(double w, WstValueTable table) {
+
+            TDoubleArrayList qs = new TDoubleArrayList();
+
+            if (ws.length > 0 && Math.abs(ws[0]-w) < 0.000001) {
+                double q = table.getQIndex(0, km);
+                if (!Double.isNaN(q)) {
+                    qs.add(q);
+                }
+            }
+
+            for (int i = 1; i < ws.length; ++i) {
+                double w2 = ws[i];
+                if (Double.isNaN(w2)) {
+                    continue;
+                }
+                if (Math.abs(w2-w) < 0.000001) {
+                    double q = table.getQIndex(i, km);
+                    if (!Double.isNaN(q)) {
+                        qs.add(q);
+                    }
+                    continue;
+                }
+                double w1 = ws[i-1];
+                if (Double.isNaN(w1)) {
+                    continue;
+                }
+
+                if (w < Math.min(w1, w2) || w > Math.max(w1, w2)) {
+                    continue;
+                }
+
+                double q1 = table.getQIndex(i-1, km);
+                double q2 = table.getQIndex(i,   km);
+                if (Double.isNaN(q1) || Double.isNaN(q2)) {
+                    continue;
+                }
+
+                double q = Linear.linear(w, w1, w2, q1, q2);
+                qs.add(q);
+            }
+
+            return qs.toNativeArray();
+        }
+
+        public double [] findQsForW(
+            Row           other,
+            double        w,
+            double        km, 
+            WstValueTable table
+        ) {
+            TDoubleArrayList qs = new TDoubleArrayList();
+
+            double factor = Linear.factor(km, this.km, other.km);
+
+            if (ws.length > 0) {
+                double wt = Linear.weight(factor, ws[0], other.ws[0]);
+                if (!Double.isNaN(wt)) {
+                    double q = table.getQIndex(0, km);
+                    if (!Double.isNaN(q)) {
+                        qs.add(q);
+                    }
+                }
+            }
+
+            for (int i = 1; i < ws.length; ++i) {
+                double w2 = Linear.weight(factor, ws[i], other.ws[i]);
+                if (Double.isNaN(w2)) {
+                    continue;
+                }
+                if (Math.abs(w2-w) < 0.000001) {
+                    double q = table.getQIndex(i, km);
+                    if (!Double.isNaN(q)) {
+                        qs.add(q);
+                    }
+                    continue;
+                }
+                double w1 = Linear.weight(factor, ws[i-1], other.ws[i-1]);
+                if (Double.isNaN(w1)) {
+                    continue;
+                }
+
+                if (w < Math.min(w1, w2) || w > Math.max(w1, w2)) {
+                    continue;
+                }
+
+                double q1 = table.getQIndex(i-1, km);
+                double q2 = table.getQIndex(i,   km);
+                if (Double.isNaN(q1) || Double.isNaN(q2)) {
+                    continue;
+                }
+
+                double q = Linear.linear(w, w1, w2, q1, q2);
+                qs.add(q);
+            }
+
+            return qs.toNativeArray();
+        }
+
+        public double [] getMinMaxW(double [] result) {
+            double minW =  Double.MAX_VALUE;
+            double maxW = -Double.MAX_VALUE;
+            for (int i = 0; i < ws.length; ++i) {
+                double w = ws[i];
+                if (w < minW) minW = w;
+                if (w > maxW) maxW = w;
+            }
+            result[0] = minW;
+            result[1] = maxW;
+            return result;
+        }
+
+        public double [] getMinMaxW(Row other, double km, double [] result) {
+            double [] m1 = this .getMinMaxW(new double [2]);
+            double [] m2 = other.getMinMaxW(new double [2]);
+            double factor = Linear.factor(km, this.km, other.km);
+            result[0] = Linear.weight(factor, m1[0], m2[0]);
+            result[1] = Linear.weight(factor, m1[1], m2[1]);
+            return result;
+        }
+    } // class Row
+
+    /** Rows in table. */
+    protected List<Row> rows;
+
+    /** Columns in table. */
+    protected Column [] columns;
+
+    public WstValueTable() {
+        rows = new ArrayList<Row>();
+    }
+
+    public WstValueTable(Column [] columns) {
+        this();
+        this.columns = columns;
+    }
+
+    public WstValueTable(Column [] columns, List<Row> rows) {
+        this.columns = columns;
+        this.rows    = rows;
+    }
+
+    /** Sort rows (by km). */
+    public void sortRows() {
+        Collections.sort(rows);
+    }
+
+    /**
+     * @param km Given kilometer.
+     * @param qs Given Q values.
+     * @param ws output parameter.
+     */
+    public double [] interpolateW(double km, double [] qs, double [] ws) {
+        return interpolateW(km, qs, ws, null);
+    }
+
+
+    /**
+     * @param ws (output parameter), gets returned.
+     * @return output parameter ws.
+     */
+    public double [] interpolateW(
+        double      km,
+        double []   qs,
+        double []   ws,
+        Calculation errors
+    ) {
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        QPosition qPosition = new QPosition();
+
+        if (rowIndex >= 0) { // direct row match
+            Row row = rows.get(rowIndex);
+            for (int i = 0; i < qs.length; ++i) {
+                if (getQPosition(km, qs[i], qPosition) == null) {
+                    if (errors != null) {
+                        errors.addProblem(km, "cannot.find.q", qs[i]);
+                    }
+                    ws[i] = Double.NaN;
+                }
+                else {
+                    if (Double.isNaN(ws[i] = row.getW(qPosition))
+                    && errors != null) {
+                        errors.addProblem(
+                            km, "cannot.find.w.for.q", qs[i]);
+                    }
+                }
+            }
+        }
+        else { // needs bilinear interpolation
+            rowIndex = -rowIndex -1;
+
+            if (rowIndex < 1 || rowIndex >= rows.size()) {
+                // do not extrapolate
+                Arrays.fill(ws, Double.NaN);
+                if (errors != null) {
+                    errors.addProblem(km, "km.not.found");
+                }
+            }
+            else {
+                Row r1 = rows.get(rowIndex-1);
+                Row r2 = rows.get(rowIndex);
+                r1.interpolateW(r2, km, qs, ws, this, errors);
+            }
+        }
+
+        return ws;
+    }
+
+    public double [] getMinMaxQ(double km) {
+        return getMinMaxQ(km, new double [2]);
+    }
+
+    public double [] getMinMaxQ(double km, double [] result) {
+        double minQ =  Double.MAX_VALUE;
+        double maxQ = -Double.MAX_VALUE;
+
+        for (int i = 0; i < columns.length; ++i) {
+            double q = columns[i].getQRangeTree().findQ(km);
+            if (!Double.isNaN(q)) {
+                if (q < minQ) minQ = q;
+                if (q > maxQ) maxQ = q;
+            }
+        }
+
+        if (minQ < Double.MAX_VALUE) {
+            result[0] = minQ;
+            result[1] = maxQ;
+            return result;
+        }
+
+        return null;
+    }
+
+    public double [] getMinMaxQ(double from, double to, double step) {
+        double [] result = new double[2];
+
+        double minQ =  Double.MAX_VALUE;
+        double maxQ = -Double.MAX_VALUE;
+
+        if (from > to) {
+            double tmp = from;
+            from = to;
+            to = tmp;
+        }
+
+        step = Math.max(Math.abs(step), 0.0001);
+
+        double d = from;
+        for (; d <= to; d += step) {
+            if (getMinMaxQ(d, result) != null) {
+                if (result[0] < minQ) minQ = result[0];
+                if (result[1] > maxQ) maxQ = result[1];
+            }
+        }
+
+        if (d != to) {
+            if (getMinMaxQ(to, result) != null) {
+                if (result[0] < minQ) minQ = result[0];
+                if (result[1] > maxQ) maxQ = result[1];
+            }
+        }
+
+        return minQ < Double.MAX_VALUE
+            ? new double [] { minQ, maxQ }
+            : null;
+    }
+
+    public double [] getMinMaxW(double km) {
+        return getMinMaxW(km, new double [2]);
+
+    }
+    public double [] getMinMaxW(double km, double [] result) {
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) {
+            return rows.get(rowIndex).getMinMaxW(result);
+        }
+
+        rowIndex = -rowIndex -1;
+
+        if (rowIndex < 1 || rowIndex >= rows.size()) {
+            // do not extrapolate
+            return null;
+        }
+
+        Row r1 = rows.get(rowIndex-1);
+        Row r2 = rows.get(rowIndex);
+
+        return r1.getMinMaxW(r2, km, result);
+    }
+
+    public double [] getMinMaxW(double from, double to, double step) {
+        double [] result = new double[2];
+
+        double minW =  Double.MAX_VALUE;
+        double maxW = -Double.MAX_VALUE;
+
+        if (from > to) {
+            double tmp = from;
+            from = to;
+            to = tmp;
+        }
+
+        step = Math.max(Math.abs(step), 0.0001);
+
+        double d = from;
+        for (; d <= to; d += step) {
+            if (getMinMaxW(d, result) != null) {
+                if (result[0] < minW) minW = result[0];
+                if (result[1] > maxW) maxW = result[1];
+            }
+        }
+
+        if (d != to) {
+            if (getMinMaxW(to, result) != null) {
+                if (result[0] < minW) minW = result[0];
+                if (result[1] > maxW) maxW = result[1];
+            }
+        }
+
+        return minW < Double.MAX_VALUE
+            ? new double [] { minW, maxW }
+            : null;
+    }
+
+    /**
+     * Interpolate W and Q values at a given km.
+     */
+    public double [][] interpolateWQ(double km) {
+        return interpolateWQ(km, null);
+    }
+
+    /**
+     * Interpolate W and Q values at a given km.
+     */
+    public double [][] interpolateWQ(double km, Calculation errors) {
+        return interpolateWQ(km, DEFAULT_Q_STEPS, errors);
+    }
+
+    /**
+     * Interpolate W and Q values at a given km.
+     */
+    public double [][] interpolateWQ(double km, int steps, Calculation errors) {
+
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) { // direct row match
+            Row row = rows.get(rowIndex);
+            return row.interpolateWQ(steps, this, errors);
+        }
+
+        rowIndex = -rowIndex -1;
+
+        if (rowIndex < 1 || rowIndex >= rows.size()) {
+            // do not extrapolate
+            if (errors != null) {
+                errors.addProblem(km, "km.not.found");
+            }
+            return new double[2][0];
+        }
+
+        Row r1 = rows.get(rowIndex-1);
+        Row r2 = rows.get(rowIndex);
+
+        return r1.interpolateWQ(r2, km, steps, this, errors);
+    }
+
+    public boolean interpolate(
+        double    km,
+        double [] out,
+        QPosition qPosition,
+        Function  qFunction
+    ) {
+        int R1 = rows.size()-1;
+
+        out[1] = qFunction.value(getQ(qPosition, km));
+
+        if (Double.isNaN(out[1])) {
+            return false;
+        }
+
+        QPosition nPosition = new QPosition();
+        if (getQPosition(km, out[1], nPosition) == null) {
+            return false;
+        }
+
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) {
+            // direct row match
+            out[0] = rows.get(rowIndex).getW(nPosition);
+            return !Double.isNaN(out[0]);
+        }
+
+        rowIndex = -rowIndex -1;
+
+        if (rowIndex < 1 || rowIndex > R1) {
+            // do not extrapolate
+            return false;
+        }
+
+        Row r1 = rows.get(rowIndex-1);
+        Row r2 = rows.get(rowIndex);
+        out[0] = r1.getW(r2, km, nPosition);
+
+        return !Double.isNaN(out[0]);
+    }
+
+
+    /**
+     * Look up interpolation of a Q at given positions.
+     *
+     * @param q           the non-interpolated Q value.
+     * @param referenceKm the reference km (e.g. gauge position).
+     * @param kms         positions for which to interpolate.
+     * @param ws          (output) resulting interpolated ws.
+     * @param qs          (output) resulting interpolated qs.
+     * @param errors      calculation object to store errors.
+     */
+    public QPosition interpolate(
+        double      q,
+        double      referenceKm,
+        double []   kms,
+        double []   ws,
+        double []   qs,
+        Calculation errors
+    ) {
+        return interpolate(
+            q, referenceKm, kms, ws, qs, 0, kms.length, errors);
+    }
+
+    public QPosition interpolate(
+        double      q,
+        double      referenceKm,
+        double []   kms,
+        double []   ws,
+        double []   qs,
+        int         startIndex,
+        int         length,
+        Calculation errors
+    ) {
+        QPosition qPosition = getQPosition(referenceKm, q);
+
+        if (qPosition == null) {
+            // we cannot locate q at km
+            Arrays.fill(ws, Double.NaN);
+            Arrays.fill(qs, Double.NaN);
+            if (errors != null) {
+                errors.addProblem(referenceKm, "cannot.find.q", q);
+            }
+            return null;
+        }
+
+        Row kmKey = new Row();
+
+        int R1 = rows.size()-1;
+
+        for (int i = startIndex, end = startIndex+length; i < end; ++i) {
+
+            if (Double.isNaN(qs[i] = getQ(qPosition, kms[i]))) {
+                if (errors != null) {
+                    errors.addProblem(kms[i], "cannot.find.q", q);
+                }
+                ws[i] = Double.NaN;
+                continue;
+            }
+
+            kmKey.km = kms[i];
+            int rowIndex = Collections.binarySearch(rows, kmKey);
+
+            if (rowIndex >= 0) {
+                // direct row match
+                if (Double.isNaN(ws[i] = rows.get(rowIndex).getW(qPosition))
+                && errors != null) {
+                    errors.addProblem(kms[i], "cannot.find.w.for.q", q);
+                }
+                continue;
+            }
+
+            rowIndex = -rowIndex -1;
+
+            if (rowIndex < 1 || rowIndex > R1) {
+                // do not extrapolate
+                if (errors != null) {
+                    errors.addProblem(kms[i], "km.not.found");
+                }
+                ws[i] = Double.NaN;
+                continue;
+            }
+            Row r1 = rows.get(rowIndex-1);
+            Row r2 = rows.get(rowIndex);
+
+            if (Double.isNaN(ws[i] = r1.getW(r2, kms[i], qPosition))
+            && errors != null) {
+                errors.addProblem(kms[i], "cannot.find.w.for.q", q);
+            }
+        }
+
+        return qPosition;
+    }
+
+    /**
+     * Linearly interpolate w at a km at a column of two rows.
+     *
+     * @param km   position for which to interpolate.
+     * @param row1 first row.
+     * @param row2 second row.
+     * @param col  column-index at which to look.
+     *
+     * @return Linearly interpolated w, NaN if one of the given rows was null.
+     */
+    public static double linearW(double km, Row row1, Row row2, int col) {
+        if (row1 == null || row2 == null) {
+            return Double.NaN;
+        }
+
+        return Linear.linear(km,
+            row1.km, row2.km,
+            row1.ws[col], row2.ws[col]);
+    }
+
+    /**
+     * Do interpolation/lookup of W and Q within columns (i.e. ignoring values
+     * of other columns).
+     * @param km position (km) at which to interpolate/lookup.
+     * @return [[q0, q1, .. qx] , [w0, w1, .. wx]] (can contain NaNs)
+     */
+    public double [][] interpolateWQColumnwise(double km) {
+        log.debug("WstValueTable.interpolateWQColumnwise");
+        double [] qs = new double[columns.length];
+        double [] ws = new double[columns.length];
+
+        // Find out row from where we will start searching.
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex < 0) {
+            rowIndex = -rowIndex -1;
+        }
+
+        // TODO Beyond definition, we could stop more clever.
+        if (rowIndex >= rows.size()) {
+            rowIndex = rows.size() -1;
+        }
+
+        Row startRow = rows.get(rowIndex);
+
+        for (int col = 0; col < columns.length; col++) {
+            qs[col] = columns[col].getQRangeTree().findQ(km);
+            if (startRow.km == km && startRow.ws[col] != Double.NaN) {
+                // Great. W is defined at km.
+                ws[col] = startRow.ws[col];
+                continue;
+            }
+
+            // Search neighbouring rows that define w at this col.
+            Row rowBefore = null;
+            Row rowAfter  = null;
+            for (int before = rowIndex -1; before >= 0; before--) {
+                if (!Double.isNaN(rows.get(before).ws[col])) {
+                    rowBefore = rows.get(before);
+                    break;
+                }
+            }
+            if (rowBefore != null) {
+                for (int after = rowIndex, R = rows.size(); after < R; after++) {
+                    if (!Double.isNaN(rows.get(after).ws[col])) {
+                        rowAfter = rows.get(after);
+                        break;
+                    }
+                }
+            }
+
+            ws[col] = linearW(km, rowBefore, rowAfter, col);
+        }
+
+        return new double [][] {qs, ws};
+    }
+
+    public double [] findQsForW(double km, double w) {
+
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) {
+            return rows.get(rowIndex).findQsForW(w, this);
+        }
+
+        rowIndex = -rowIndex - 1;
+
+        if (rowIndex < 1 || rowIndex >= rows.size()) {
+            // Do not extrapolate.
+            return new double[0];
+        }
+
+        // Needs bilinear interpolation.
+        Row r1 = rows.get(rowIndex-1);
+        Row r2 = rows.get(rowIndex);
+
+        return r1.findQsForW(r2, w, km, this);
+    }
+
+    protected SplineFunction createSpline(double km, Calculation errors) {
+
+        int rowIndex = Collections.binarySearch(rows, new Row(km));
+
+        if (rowIndex >= 0) {
+            SplineFunction sf = rows.get(rowIndex).createSpline(this, errors);
+            if (sf == null && errors != null) {
+                errors.addProblem(km, "cannot.create.wq.relation");
+            }
+            return sf;
+        }
+
+        rowIndex = -rowIndex - 1;
+
+        if (rowIndex < 1 || rowIndex >= rows.size()) {
+            // Do not extrapolate.
+            if (errors != null) {
+                errors.addProblem(km, "km.not.found");
+            }
+            return null;
+        }
+
+        // Needs bilinear interpolation.
+        Row r1 = rows.get(rowIndex-1);
+        Row r2 = rows.get(rowIndex);
+
+        SplineFunction sf = r1.createSpline(r2, km, this, errors);
+        if (sf == null && errors != null) {
+            errors.addProblem(km, "cannot.create.wq.relation");
+        }
+
+        return sf;
+    }
+
+    /** 'Bezugslinienverfahren' */
+    public double [][] relateWs(
+        double      km1, 
+        double      km2,
+        Calculation errors
+    ) {
+        return relateWs(km1, km2, RELATE_WS_SAMPLES, errors);
+    }
+
+    private static class ErrorHandler {
+
+        boolean     hasErrors;
+        Calculation errors;
+
+        ErrorHandler(Calculation errors) {
+            this.errors = errors;
+        }
+
+        void error(double km, String key, Object ... args) {
+            if (errors != null && !hasErrors) {
+                hasErrors = true;
+                errors.addProblem(km, key, args);
+            }
+        }
+    } // class ErrorHandler
+
+
+    /* TODO: Add optimized methods of relateWs to relate one
+     *       start km to many end kms. The index generation/spline stuff for 
+     *       the start km is always the same.
+     */
+    public double [][] relateWs(
+        double      km1, 
+        double      km2,
+        int         numSamples,
+        Calculation errors
+    ) {
+        SplineFunction sf1 = createSpline(km1, errors);
+        if (sf1 == null) {
+            return new double[2][0];
+        }
+
+        SplineFunction sf2 = createSpline(km2, errors);
+        if (sf2 == null) {
+            return new double[2][0];
+        }
+
+        PolynomialSplineFunction iQ1 = sf1.createIndexQRelation();
+        if (iQ1 == null) {
+            if (errors != null) {
+                errors.addProblem(km1, "cannot.create.index.q.relation");
+            }
+            return new double[2][0];
+        }
+
+        PolynomialSplineFunction iQ2 = sf2.createIndexQRelation();
+        if (iQ2 == null) {
+            if (errors != null) {
+                errors.addProblem(km2, "cannot.create.index.q.relation");
+            }
+            return new double[2][0];
+        }
+
+        int N = Math.min(sf1.splineQs.length, sf2.splineQs.length);
+        double stepWidth = N/(double)numSamples;
+
+        PolynomialSplineFunction qW1 = sf1.spline;
+        PolynomialSplineFunction qW2 = sf2.spline;
+
+        TDoubleArrayList ws1 = new TDoubleArrayList(numSamples);
+        TDoubleArrayList ws2 = new TDoubleArrayList(numSamples);
+        TDoubleArrayList qs1 = new TDoubleArrayList(numSamples);
+        TDoubleArrayList qs2 = new TDoubleArrayList(numSamples);
+
+        ErrorHandler err = new ErrorHandler(errors);
+
+        int i = 0;
+        for (double p = 0d; p <= N-1; p += stepWidth, ++i) {
+
+            double q1;
+            try {
+                q1 = iQ1.value(p);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km1, "w.w.qkm1.failed", p);
+                continue;
+            }
+
+            double w1;
+            try {
+                w1 = qW1.value(q1);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km1, "w.w.wkm1.failed", q1, p);
+                continue;
+            }
+
+            double q2;
+            try {
+                q2 = iQ2.value(p);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km2, "w.w.qkm2.failed", p);
+                continue;
+            }
+
+            double w2;
+            try {
+                w2 = qW2.value(q2);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km2, "w.w.wkm2.failed", q2, p);
+                continue;
+            }
+
+            ws1.add(w1);
+            ws2.add(w2);
+            qs1.add(q1);
+            qs2.add(q2);
+        }
+
+        return new double [][] {
+            ws1.toNativeArray(),
+            qs1.toNativeArray(), 
+            ws2.toNativeArray(),
+            qs2.toNativeArray() };
+    }
+
+    public QPosition getQPosition(double km, double q) {
+        return getQPosition(km, q, new QPosition());
+    }
+
+    public QPosition getQPosition(double km, double q, QPosition qPosition) {
+
+        if (columns.length == 0) {
+            return null;
+        }
+
+        double qLast = columns[0].getQRangeTree().findQ(km);
+
+        if (Math.abs(qLast - q) < 0.00001) {
+            return qPosition.set(0, 1d);
+        }
+
+        for (int i = 1; i < columns.length; ++i) {
+            double qCurrent = columns[i].getQRangeTree().findQ(km);
+            if (Math.abs(qCurrent - q) < 0.00001) {
+                return qPosition.set(i, 1d);
+            }
+
+            double qMin, qMax;
+            if (qLast < qCurrent) { qMin = qLast; qMax = qCurrent; }
+            else                  { qMin = qCurrent; qMax = qLast; }
+
+            if (q > qMin && q < qMax) {
+                double weight = Linear.factor(q, qLast, qCurrent);
+                return qPosition.set(i, weight);
+            }
+            qLast = qCurrent;
+        }
+
+        return null;
+    }
+
+    public double getQIndex(int index, double km) {
+        return columns[index].getQRangeTree().findQ(km);
+    }
+
+    public double getQ(QPosition qPosition, double km) {
+        int    index  = qPosition.index;
+        double weight = qPosition.weight;
+
+        if (weight == 1d) {
+            return columns[index].getQRangeTree().findQ(km);
+        }
+        double q1 = columns[index-1].getQRangeTree().findQ(km);
+        double q2 = columns[index  ].getQRangeTree().findQ(km);
+        return Linear.weight(weight, q1, q2);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTableCacheKey.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,33 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+/**
+ * Cache Key (identifier) for WstValueTables.
+ */
+public final class WstValueTableCacheKey
+implements         Serializable
+{
+    public static final String CACHE_NAME = "wst-value-table";
+
+    private int riverId;
+    private int kind;
+
+    public WstValueTableCacheKey(int riverId, int kind) {
+        this.riverId = riverId;
+        this.kind    = kind;
+    }
+
+    public int hashCode() {
+        return (riverId << 8) | kind;
+    }
+
+    public boolean equals(Object other) {
+        if (!(other instanceof WstValueTableCacheKey)) {
+            return false;
+        }
+        WstValueTableCacheKey o = (WstValueTableCacheKey)other;
+        return riverId == o.riverId && kind == o.kind;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTableFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,467 @@
+package de.intevation.flys.artifacts.model;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import org.hibernate.Session;
+import org.hibernate.Query;
+import org.hibernate.SQLQuery;
+
+import org.hibernate.type.StandardBasicTypes;
+
+/**
+ * Creates WstValueTable s from database.
+ * WstValueTable s are used to interpolate given w/q/km values.
+ */
+public class WstValueTableFactory
+{
+    private static Logger log = Logger.getLogger(WstValueTableFactory.class);
+
+    public static final int DEFAULT_KIND = 0;
+
+    // TODO: put this into a property file
+
+    public static final String HQL_WST =
+        "from Wst where river=:river and kind=:kind";
+
+    public static final String SQL_SELECT_NAMES_POS =
+        "SELECT position, name FROM wst_columns " +
+        "WHERE wst_id = :wst_id ORDER BY position";
+
+    /** Select Qs for wst (view sorted by column). */
+    public static final String SQL_SELECT_QS =
+        "SELECT column_pos, q, a, b FROM wst_q_values " +
+        "WHERE wst_id = :wst_id";
+
+    // (sorted by km)
+    public static final String SQL_SELECT_WS =
+        "SELECT km, w, column_pos FROM wst_w_values " +
+        "WHERE wst_id = :wst_id";
+
+    /** Statement to query qranges of a single column. */
+    public static final String SQL_SELECT_QS_AT_COL =
+        "SELECT q, a, b FROM wst_q_values " +
+        "WHERE wst_id = :wst_id AND column_pos = :column_pos";
+
+    // (sorted by km)
+    public static final String SQL_SELECT_WS_AT_COL =
+        "SELECT km, w FROM wst_w_values " +
+        "WHERE wst_id = :wst_id AND column_pos = :column_pos";
+
+
+    private WstValueTableFactory() {
+    }
+
+
+    public static WstValueTable getTable(River river) {
+        return getTable(river, DEFAULT_KIND);
+    }
+
+
+    /**
+     * Get WstValueTable to interpolate values of a given Wst.
+     */
+    public static WstValueTable getTable(int wst_id) {
+
+        Cache cache = CacheFactory.getCache(WstValueTableCacheKey.CACHE_NAME);
+
+        WstValueTableCacheKey cacheKey;
+
+        if (cache != null) {
+            // "-1" is the symbolic river-id for "no river, but wst_id".
+            cacheKey = new WstValueTableCacheKey(-1, wst_id);
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                log.debug("Got specific wst value table from cache");
+                return (WstValueTable) element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        Session session = SessionHolder.HOLDER.get();
+
+        // Fetch data for one column only.
+        
+        WstValueTable.Column [] columns = loadColumns(session, wst_id);
+        loadQRanges(session, columns, wst_id);
+        List<WstValueTable.Row> rows = loadRows(session, wst_id, columns.length);
+
+        WstValueTable valueTable = new WstValueTable(columns, rows);
+
+        if (valueTable != null && cacheKey != null) {
+            log.debug("Store wst value table in cache");
+            Element element = new Element(cacheKey, valueTable);
+            cache.put(element);
+        }
+
+        return valueTable;
+    }
+
+    /**
+     * Get Table for a specific column of a wst.
+     */
+    public static WstValueTable getWstColumnTable(int wst_id, int col_pos) {
+
+        Cache cache = CacheFactory.getCache(WstValueTableCacheKey.CACHE_NAME);
+
+        WstValueTableCacheKey cacheKey;
+
+        if (cache != null) {
+            // A negaitve/negative number is the symbolic 'river-id' for
+            // "no river and kind but wst_id and colpos".
+            cacheKey = new WstValueTableCacheKey(-wst_id, -col_pos);
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                log.debug("Got specific wst value table from cache");
+                return (WstValueTable) element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        Session session = SessionHolder.HOLDER.get();
+
+        // Fetch data for one column only.
+        
+        WstValueTable.Column [] columns = loadColumn(session, wst_id, col_pos);
+        loadQRanges(session, columns, wst_id, col_pos);
+        List<WstValueTable.Row> rows = loadRowsOneColumn(session, wst_id, col_pos);
+
+        WstValueTable valueTable = new WstValueTable(columns, rows);
+
+        if (valueTable != null && cacheKey != null) {
+            log.debug("Store wst value table in cache (wst: "
+                + wst_id + "/ col: " + col_pos + ")");
+            Element element = new Element(cacheKey, valueTable);
+            cache.put(element);
+        }
+
+        return valueTable;
+    }
+
+
+    /**
+     * Get table for first wst of given kind at given river.
+     */
+    public static WstValueTable getTable(River river, int kind) {
+
+        Cache cache = CacheFactory.getCache(WstValueTableCacheKey.CACHE_NAME);
+
+        WstValueTableCacheKey cacheKey;
+
+        if (cache != null) {
+            cacheKey = new WstValueTableCacheKey(river.getId(), kind);
+            Element element = cache.get(cacheKey);
+            if (element != null) {
+                log.debug("got wst value table from cache");
+                return (WstValueTable)element.getValue();
+            }
+        }
+        else {
+            cacheKey = null;
+        }
+
+        WstValueTable valueTable = getTableUncached(river, kind);
+
+        if (valueTable != null && cacheKey != null) {
+            log.debug("store wst value table in cache");
+            Element element = new Element(cacheKey, valueTable);
+            cache.put(element);
+        }
+
+        return valueTable;
+    }
+
+    public static WstValueTable getTableUncached(River river) {
+        return getTableUncached(river, DEFAULT_KIND);
+    }
+
+    public static WstValueTable getTableUncached(River river, int kind) {
+
+        Session session = SessionHolder.HOLDER.get();
+
+        Wst wst = loadWst(session, river, kind);
+
+        if (wst == null) {
+            return null;
+        }
+
+        WstValueTable.Column [] columns = loadColumns(session, wst);
+
+        loadQRanges(session, columns, wst);
+
+        List<WstValueTable.Row> rows = loadRows(session, wst, columns.length);
+
+        return new WstValueTable(columns, rows);
+    }
+
+    /**
+     * @param kind Kind of wst.
+     */
+    protected static Wst loadWst(Session session, River river, int kind) {
+        Query query = session.createQuery(HQL_WST);
+        query.setParameter("river", river);
+        query.setInteger("kind",    kind);
+
+        List<Wst> wsts = query.list();
+
+        // TODO Multiple wsts can match, why return just the first one?
+        return wsts.isEmpty() ? null : wsts.get(0);
+    }
+
+
+    /**
+     * Load rows with a single columns result.
+     * @param session    session to use for querying db.
+     * @param wst_id     id of wst (in db).
+     * @param column_pos the column_pos (within the db) of the wst_value_table
+     *                   of which the values shall be fetched.
+     * @return resultant rows.
+     */
+    protected static List<WstValueTable.Row> loadRowsOneColumn(
+        Session session,
+        int     wstId,
+        int     column_pos
+    ) {
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_WS_AT_COL)
+            .addScalar("km", StandardBasicTypes.DOUBLE)
+            .addScalar("w",  StandardBasicTypes.DOUBLE);
+
+        sqlQuery.setInteger("wst_id", wstId);
+        sqlQuery.setInteger("column_pos", column_pos);
+
+        List<Object []> results = sqlQuery.list();
+
+        double [] ws = null;
+
+        List<WstValueTable.Row> rows =
+            new ArrayList<WstValueTable.Row>(results.size());
+
+        // Walk over rows.
+        for (Object [] result: results) {
+            ws = new double[1];
+            WstValueTable.Row row =
+                new WstValueTable.Row((Double) result[0], ws);
+            rows.add(row);
+
+            Double w = (Double) result[1];
+            ws[0] = w != null ? w : Double.NaN;
+        }
+
+        return rows;
+    }
+
+    protected static List<WstValueTable.Row> loadRows(
+        Session session,
+        int     wst_id,
+        int     numColumns
+    ) {
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_WS)
+            .addScalar("km",         StandardBasicTypes.DOUBLE)
+            .addScalar("w",          StandardBasicTypes.DOUBLE)
+            .addScalar("column_pos", StandardBasicTypes.INTEGER);
+
+        sqlQuery.setInteger("wst_id", wst_id);
+
+        List<Object []> results = sqlQuery.list();
+
+        int lastColumn = Integer.MAX_VALUE;
+        double [] ws = null;
+
+        ArrayList<WstValueTable.Row> rows = new ArrayList<WstValueTable.Row>();
+
+        for (Object [] result: results) {
+            int column = (Integer)result[2];
+            if (column < lastColumn) {
+                ws = new double[numColumns];
+                Arrays.fill(ws, Double.NaN);
+                WstValueTable.Row row =
+                    new WstValueTable.Row((Double)result[0], ws);
+                rows.add(row);
+            }
+            Double w = (Double)result[1];
+            ws[column] = w != null ? w : Double.NaN;
+            lastColumn = column;
+        }
+
+        rows.trimToSize();
+        return rows;
+    }
+
+    protected static List<WstValueTable.Row> loadRows(
+        Session session,
+        Wst     wst,
+        int     numColumns
+    ) {
+        return loadRows(session, wst.getId(), numColumns);
+    }
+
+
+    protected static WstValueTable.Column [] loadColumn(
+        Session session,
+        int wst_id,
+        int col_pos
+    ) {
+        return new WstValueTable.Column [] {
+            new WstValueTable.Column(WKmsFactory.getWKmsName(col_pos, wst_id))};
+    }
+
+
+    /**
+     * Get columns from wst-id.
+     */
+    protected static WstValueTable.Column [] loadColumns(
+        Session session,
+        int wst_id
+    ) {
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_NAMES_POS)
+            .addScalar("position",   StandardBasicTypes.INTEGER)
+            .addScalar("name",       StandardBasicTypes.STRING);
+
+        sqlQuery.setInteger("wst_id", wst_id);
+
+        List<Object []> columnNames = sqlQuery.list();
+
+        WstValueTable.Column [] columns =
+            new WstValueTable.Column[columnNames.size()];
+
+        for (int i = 0; i < columns.length; ++i) {
+            columns[i] = new WstValueTable.Column(
+                (String)columnNames.get(i)[1]);
+        }
+        return columns;
+    }
+
+    /**
+     * Get columns from Wst.
+     */
+    protected static WstValueTable.Column [] loadColumns(
+        Session session,
+        Wst     wst
+    ) {
+        return loadColumns(session, wst.getId());
+    }
+
+
+    /**
+     * Build a QRange-Tree.
+     */
+    protected static void loadQRanges(
+        Session                 session,
+        WstValueTable.Column [] columns,
+        int                     wst_id,
+        int                     column_pos
+    ) {
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_QS_AT_COL)
+            .addScalar("q", StandardBasicTypes.DOUBLE)
+            .addScalar("a", StandardBasicTypes.DOUBLE)
+            .addScalar("b", StandardBasicTypes.DOUBLE);
+
+        sqlQuery.setInteger("wst_id",     wst_id);
+        sqlQuery.setInteger("column_pos", column_pos);
+
+        List<Object []> qRanges = sqlQuery.list();
+
+        int qSize = qRanges.size();
+
+        QRangeTree qRangeTree = new QRangeTree(
+            qRanges, QRangeTree.WITHOUT_COLUMN, 0, qSize);
+        columns[0].setQRangeTree(qRangeTree);
+    }
+
+    protected static void loadQRanges(
+        Session                 session,
+        WstValueTable.Column [] columns,
+        int                     wst_id
+    ) {
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_QS)
+            .addScalar("column_pos", StandardBasicTypes.INTEGER)
+            .addScalar("q",          StandardBasicTypes.DOUBLE)
+            .addScalar("a",          StandardBasicTypes.DOUBLE)
+            .addScalar("b",          StandardBasicTypes.DOUBLE);
+
+        sqlQuery.setInteger("wst_id", wst_id);
+
+        List<Object []> qRanges = sqlQuery.list();
+
+        int     start      = -1;
+        int     Q          = qRanges.size();
+        Integer lastColumn = null;
+
+        for (int i = 0; i < Q; ++i) {
+            Object [] qRange = qRanges.get(i);
+            Integer columnId = (Integer)qRange[0];
+            if (lastColumn == null) {
+                lastColumn = columnId;
+                start = i;
+            }
+            else if (!lastColumn.equals(columnId)) {
+                QRangeTree qRangeTree = new QRangeTree(qRanges, start, i);
+                columns[lastColumn].setQRangeTree(qRangeTree);
+                lastColumn = columnId;
+                start = i;
+            }
+        }
+
+        if (start != -1) {
+            QRangeTree qRangeTree = new QRangeTree(qRanges, start, Q);
+            columns[lastColumn].setQRangeTree(qRangeTree);
+        }
+
+        /* This is debug code to visualize the q ranges trees
+
+        java.io.PrintWriter out = null;
+        try {
+            out = new java.io.PrintWriter(
+                new java.io.FileWriter(
+                    "/tmp/qranges" + System.currentTimeMillis() + ".dot"));
+
+            out.println("graph \"Q ranges trees\" {");
+
+            for (int i = 0; i < columns.length; ++i) {
+                QRangeTree tree = columns[i].getQRangeTree();
+                out.println(tree.toGraph());
+            }
+
+            out.println("}");
+
+            out.flush();
+        }
+        catch (java.io.IOException ioe) {
+            log.error(ioe);
+        }
+        finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+        */
+    
+    }
+
+    protected static void loadQRanges(
+        Session                 session,
+        WstValueTable.Column [] columns,
+        Wst                     wst
+    ) {
+        loadQRanges(session, columns, wst.getId());
+    }
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWT.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,47 @@
+package de.intevation.flys.artifacts.model.fixings;
+
+import java.io.Serializable;
+
+import java.util.Date;
+
+public class DeltaWT
+implements   Serializable
+{
+    protected double deltaW;
+    protected Date   date;
+    protected String description;
+
+    public DeltaWT() {
+    }
+
+    public DeltaWT(double deltaW, Date date, String description) {
+        this.deltaW      = deltaW;
+        this.date        = date;
+        this.description = description;
+    }
+
+    public double getDeltaW() {
+        return deltaW;
+    }
+
+    public void setDeltaW(double deltaW) {
+        this.deltaW = deltaW;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public void setDate(Date date) {
+        this.date = date;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/fixings/DeltaWTsKM.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,90 @@
+package de.intevation.flys.artifacts.model.fixings;
+
+import java.io.Serializable;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class DeltaWTsKM
+implements   Serializable
+{
+    public static final double EPSILON = 1e-5;
+
+    public static class KM
+    implements          Serializable, Comparable<KM>
+    {
+        protected double km;
+
+        protected List<DeltaWT> deltaWTs;
+
+        public KM() {
+            deltaWTs = new ArrayList<DeltaWT>();
+        }
+
+        public KM(double km) {
+            this();
+            this.km = km;
+        }
+
+        public double getKm() {
+            return km;
+        }
+
+        public void setKm(double km) {
+            this.km = km;
+        }
+
+        public List<DeltaWT> getDeltaWTs() {
+            return deltaWTs;
+        }
+
+        public void setDeltaWTs(List<DeltaWT> deltaWTs) {
+            this.deltaWTs = deltaWTs;
+        }
+
+        public void add(DeltaWT deltaWT) {
+            deltaWTs.add(deltaWT);
+        }
+
+        public int size() {
+            return deltaWTs.size();
+        }
+
+        @Override
+        public int compareTo(KM other) {
+            double d = km - other.km;
+            if (d < -EPSILON) return -1;
+            if (d >  EPSILON) return +1;
+            return 0;
+        }
+    } // class KM
+
+    protected List<KM> kms;
+
+    public DeltaWTsKM() {
+        this(10);
+    }
+
+    public DeltaWTsKM(int capacity) {
+        kms = new ArrayList<KM>(capacity);
+    }
+
+    public void add(KM km) {
+        kms.add(km);
+    }
+
+    public void sort() {
+        Collections.sort(kms);
+    }
+
+    public KM findByKm(double km) {
+        int idx = Collections.binarySearch(kms, new KM(km));
+        return idx >= 0 ? kms.get(idx) : null;
+    }
+
+    public List<KM> getKms() {
+        return kms;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/fixings/FixCalculation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,443 @@
+package de.intevation.flys.artifacts.model.fixings;
+
+import de.intevation.flys.artifacts.FixationArtifactAccess;
+
+import de.intevation.flys.artifacts.math.fitting.Function;
+import de.intevation.flys.artifacts.math.fitting.FunctionFactory;
+
+import de.intevation.flys.artifacts.model.Calculation;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.FixingsColumn;
+import de.intevation.flys.artifacts.model.FixingsColumnFactory;
+import de.intevation.flys.artifacts.model.FixingsOverview;
+import de.intevation.flys.artifacts.model.FixingsOverviewFactory;
+import de.intevation.flys.artifacts.model.Parameters;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.Range;
+import de.intevation.flys.artifacts.model.FixingsOverview.Fixing;
+import de.intevation.flys.artifacts.model.FixingsOverview.IdFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.AndFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.DateRangeFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.SectorRangeFilter;
+
+import de.intevation.flys.utils.DoubleUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Date;
+
+import org.apache.commons.math.MathException;
+
+import org.apache.commons.math.optimization.fitting.CurveFitter;
+
+import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
+
+import org.apache.log4j.Logger;
+
+public class FixCalculation
+extends      Calculation
+{
+    private static Logger log = Logger.getLogger(FixCalculation.class);
+
+    protected String    river;
+    protected double    from;
+    protected double    to;
+    protected double    step;
+    protected boolean   preprocessing;
+    protected String    function;
+    protected int []    events;
+    protected long [][] analysisPeriods;
+    protected int       qSectorStart;
+    protected int       qSectorEnd;
+
+    public FixCalculation() {
+    }
+
+    public FixCalculation(FixationArtifactAccess access) {
+
+        String    river           = access.getRiver();
+        Double    from            = access.getFrom();
+        Double    to              = access.getTo();
+        Double    step            = access.getStep();
+        String    function        = access.getFunction();
+        int []    events          = access.getEvents();
+        long [][] analysisPeriods = access.getAnalysisPeriods();
+        Integer   qSectorStart    = access.getQSectorStart();
+        Integer   qSectorEnd      = access.getQSectorEnd();
+
+        if (river == null) {
+            // TODO: i18n
+            addProblem("fix.missing.river");
+        }
+
+        if (from == null) {
+            // TODO: i18n
+            addProblem("fix.missing.from");
+        }
+
+        if (to == null) {
+            // TODO: i18n
+            addProblem("fix.missing.to");
+        }
+
+        if (step == null) {
+            // TODO: i18n
+            addProblem("fix.missing.step");
+        }
+
+        if (function == null) {
+            // TODO: i18n
+            addProblem("fix.missing.function");
+        }
+
+        if (events == null || events.length < 1) {
+            // TODO: i18n
+            addProblem("fix.missing.events");
+        }
+
+        if (analysisPeriods == null || analysisPeriods.length < 1) {
+            // TODO: i18n
+            addProblem("fix.missing.analysis.periods");
+        }
+
+        if (qSectorStart == null) {
+            // TODO: i18n
+            addProblem("fix.missing.qstart.sector");
+        }
+
+        if (qSectorEnd == null) {
+            // TODO: i18n
+            addProblem("fix.missing.qend.sector");
+        }
+
+        if (!hasProblems()) {
+            this.river           = river;
+            this.from            = from;
+            this.to              = to;
+            this.step            = step;
+            this.function        = function;
+            this.events          = events;
+            this.analysisPeriods = analysisPeriods;
+            this.qSectorStart    = qSectorStart;
+            this.qSectorEnd      = qSectorEnd;
+        }
+    }
+
+    public CalculationResult calculate() {
+
+        boolean debug = log.isDebugEnabled();
+
+        FixingsOverview overview =
+            FixingsOverviewFactory.getOverview(river);
+
+        if (overview == null) {
+            addProblem("fix.no.overview.available");
+        }
+
+        Function func = FunctionFactory.getInstance()
+            .getFunction(function);
+
+        if (func == null) {
+            // TODO: i18n
+            addProblem("fix.invalid.function.name");
+        }
+
+        if (hasProblems()) {
+            return new CalculationResult(this);
+        }
+
+        FixingsColumnFactory fcf = FixingsColumnFactory.getInstance();
+
+        List<FixingsColumn> dataColumns =
+            new ArrayList<FixingsColumn>(events.length);
+
+        for (int eventId: events) {
+            IdFilter idFilter = new IdFilter(eventId);
+
+            List<Fixing.Column> columns = overview.filter(null, idFilter);
+            if (columns.isEmpty()) {
+                // TODO: i18n
+                addProblem("fix.missing.column", eventId);
+                continue;
+            }
+            FixingsColumn dataColumn = fcf.getColumnData(columns.get(0));
+            if (dataColumn == null) {
+                // TODO: i18n
+                addProblem("fix.cannot.load.data", eventId);
+                continue;
+            }
+            dataColumns.add(dataColumn);
+        }
+
+        if (dataColumns.size() < 2) {
+            // TODO: i18n
+            addProblem("fix.too.less.data.columns");
+            return new CalculationResult(this);
+        }
+
+        double [] kms = DoubleUtil.explode(from, to, step / 1000.0);
+
+        double [] ws = new double[dataColumns.size()];
+        double [] qs = new double[ws.length];
+
+        String [] parameterNames = func.getParameterNames();
+
+        Parameters results =
+            new Parameters(createColumnNames(parameterNames));
+
+        boolean invalid = false;
+
+        if (debug) {
+            log.debug("number of kms: " + kms.length);
+        }
+
+        int numFailed = 0;
+
+        for (int i = 0; i < kms.length; ++i) {
+            double km = kms[i];
+
+            for (int j = 0; j < ws.length; ++j) {
+                FixingsColumn column = dataColumns.get(j);
+                qs[j] = column.getQ(km);
+                boolean interpolated = column.getW(km, ws, j);
+                // TODO: mark as interpolated.
+            }
+
+            // TODO: Do preprocessing here!
+            double [] parameters = fit(func, km, ws, qs);
+            if (parameters == null) { // Problems are reported already.
+                ++numFailed;
+                continue;
+            }
+
+            int row = results.newRow();
+
+            results.set(row, "km", km);
+            for (int j = 0; j < parameters.length; ++j) {
+                if (Double.isNaN(parameters[j])) {
+                    invalid = true;
+                }
+                else {
+                    results.set(row, parameterNames[j], parameters[j]);
+                }
+            }
+            // TODO: Calculate statistics, too!
+        }
+
+        if (debug) {
+            log.debug("success: " + (kms.length - numFailed));
+            log.debug("failed: " + numFailed);
+        }
+
+        if (invalid) {
+            // TODO: i18n
+            addProblem("fix.invalid.values");
+            results.removeNaNs();
+        }
+
+        // Calculate Delta W/t
+        DeltaWTsKM deltaWTsKM = calculateDeltaWTs(
+            func,
+            overview,
+            results);
+
+        FixResult fr = new FixResult(results, deltaWTsKM);
+
+        return new CalculationResult(fr, this);
+    }
+
+    public DeltaWTsKM calculateDeltaWTs(
+        Function        function,
+        FixingsOverview overview,
+        Parameters      results
+    ) {
+        boolean debug = log.isDebugEnabled();
+
+        DeltaWTsKM deltaWTsKM = new DeltaWTsKM(results.size());
+
+        Column [][] analysisColumns = getAnalysisColumns(overview);
+
+        int [] parameterIndices = 
+            results.columnIndices(function.getParameterNames());
+
+        double [] parameterValues =
+            new double[parameterIndices.length];
+
+        double [] ow = new double[1];
+
+        int kmIdx = results.columnIndex("km");
+
+        for (int i = 0, N = results.size(); i < N; ++i) {
+            double km = results.get(i, kmIdx);
+            results.get(i, parameterIndices, parameterValues);
+
+            DeltaWTsKM.KM dwtkm = new DeltaWTsKM.KM(km);
+            deltaWTsKM.add(dwtkm);
+
+            // This is the paraterized function for a given km.
+            de.intevation.flys.artifacts.math.Function instance =
+                function.instantiate(parameterValues);
+
+            // Evaluate all columns for all analysis periods.
+            for (int j = 0; j < analysisColumns.length; ++j) {
+                Column [] periodColumns = analysisColumns[j];
+
+                int failedQ = 0;
+                int failedW = 0;
+                int failedC = 0;
+
+                for (int k = 0; k < periodColumns.length; ++k) {
+                    Column pc = periodColumns[k];
+
+                    // Q from real data.
+                    double q = pc.data.getQ(km);
+                    if (Double.isNaN(q)) {
+                        ++failedQ;
+                        continue;
+                    }
+
+                    // Calculate W from function.
+                    double nw = instance.value(q);
+                    if (Double.isNaN(nw)) {
+                        ++failedC;
+                        continue;
+                    }
+
+                    // W from real data.
+                    pc.data.getW(km, ow);
+
+                    if (Double.isNaN(ow[0])) {
+                        ++failedW;
+                        continue;
+                    }
+
+                    double deltaW = (ow[0] - nw)*100.0; // in cm
+
+                    DeltaWT deltaWT = new DeltaWT(
+                        deltaW,
+                        pc.meta.getStartTime(),
+                        pc.meta.getDescription());
+
+                    dwtkm.add(deltaWT);
+                }
+                if (debug) {
+                    log.debug("failed W: " + failedW);
+                    log.debug("failed Q: " + failedQ);
+                    log.debug("failed C: " + failedC);
+                    log.debug("input size: " + periodColumns.length);
+                    log.debug("outpt size: " + dwtkm.size());
+                }
+            }
+        }
+
+        return deltaWTsKM;
+    }
+
+    /** Helper class to bundle the meta information of a column 
+     *  and the real data.
+     */
+    protected static class Column {
+
+        protected Fixing.Column meta;
+        protected FixingsColumn data;
+
+        public Column() {
+        }
+
+        public Column(Fixing.Column meta, FixingsColumn data) {
+            this.meta = meta;
+            this.data = data;
+        }
+    } // class Column
+
+    /** Fetch meta and data columns for analysis periods. */
+    protected Column [][] getAnalysisColumns(FixingsOverview overview) {
+
+        boolean debug = log.isDebugEnabled();
+        if (debug) {
+            log.debug("number analysis periods: " + analysisPeriods.length);
+        }
+
+        Column columns [][] = new Column[analysisPeriods.length][];
+
+        Range range = new Range(from, to);
+        SectorRangeFilter sectorRangeFilter =
+            new SectorRangeFilter(qSectorStart, qSectorEnd);
+
+        FixingsColumnFactory fcf = FixingsColumnFactory.getInstance();
+
+        for (int i = 0; i < columns.length; ++i) {
+
+            // Construct filter for period.
+            long [] period = analysisPeriods[i];
+
+            AndFilter filter = new AndFilter();
+
+            DateRangeFilter dateRangeFilter =
+                new DateRangeFilter(
+                    new Date(period[0]),
+                    new Date(period[1]));
+
+            filter.add(dateRangeFilter);
+            filter.add(sectorRangeFilter);
+
+            List<Fixing.Column> metaCols = overview.filter(range, filter);
+
+            if (debug) {
+                log.debug("number of filtered columns: " + metaCols.size());
+            }
+
+            ArrayList<Column> cols = new ArrayList<Column>(metaCols.size());
+
+            // Only use columns which have data.
+            for (Fixing.Column meta: metaCols) {
+                FixingsColumn data = fcf.getColumnData(meta);
+                if (data != null) {
+                    cols.add(new Column(meta, data));
+                }
+            }
+
+            if (debug) {
+                log.debug("failed loading: " + (metaCols.size()-cols.size()));
+            }
+            columns[i] = cols.toArray(new Column[cols.size()]);
+        }
+
+        return columns;
+    }
+
+    protected static String [] createColumnNames(String [] parameters) {
+        String [] result = new String[parameters.length + 1];
+        result[0] = "km";
+        // TODO: Add statistic columns, too.
+        System.arraycopy(parameters, 0, result, 1, parameters.length);
+        return result;
+    }
+
+    protected double [] fit(
+        Function  function,
+        double    km,
+        double [] ws, 
+        double [] qs
+    ) {
+        LevenbergMarquardtOptimizer lmo = new LevenbergMarquardtOptimizer();
+        CurveFitter cf = new CurveFitter(lmo);
+
+        for (int i = 0; i < ws.length; ++i) {
+            if (!Double.isNaN(ws[i]) && !Double.isNaN(qs[i])) {
+                cf.addObservedPoint(qs[i], ws[i]);
+            }
+        }
+
+        try {
+            return cf.fit(function, function.getInitialGuess());
+        }
+        catch (MathException me) {
+            log.warn(me, me);
+            addProblem(km, "fix.fitting.failed");
+        }
+
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/fixings/FixResult.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.artifacts.model.fixings;
+
+import de.intevation.flys.artifacts.model.Parameters;
+
+import java.io.Serializable;
+
+public class FixResult
+implements   Serializable
+{
+    protected Parameters parameters;
+    protected DeltaWTsKM deltaWTsKM;
+
+    public FixResult() {
+    }
+
+    public FixResult(Parameters parameters, DeltaWTsKM deltaWTsKM) {
+        this.parameters = parameters;
+        this.deltaWTsKM = deltaWTsKM;
+    }
+
+    public Parameters getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Parameters parameters) {
+        this.parameters = parameters;
+    }
+
+    public DeltaWTsKM getDeltaWTsKM() {
+        return deltaWTsKM;
+    }
+
+    public void setDeltaWTsKM(DeltaWTsKM deltaWTsKM) {
+        this.deltaWTsKM = deltaWTsKM;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/fixings/FixResults.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.artifacts.model.fixings;
+
+import de.intevation.flys.artifacts.model.Parameters;
+
+import java.io.Serializable;
+
+public class FixResults
+implements   Serializable
+{
+    protected Parameters parameters;
+    protected DeltaWTsKM deltaWTsKM;
+
+    public FixResults() {
+    }
+
+    public FixResults(Parameters parameters, DeltaWTsKM deltaWTsKM) {
+        this.parameters = parameters;
+        this.deltaWTsKM = deltaWTsKM;
+    }
+
+    public Parameters getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Parameters parameters) {
+        this.parameters = parameters;
+    }
+
+    public DeltaWTsKM getDeltaWTsKM() {
+        return deltaWTsKM;
+    }
+
+    public void setDeltaWTsKM(DeltaWTsKM deltaWTsKM) {
+        this.deltaWTsKM = deltaWTsKM;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/resources/Resources.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,155 @@
+package de.intevation.flys.artifacts.resources;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallMeta;
+
+/**
+ * This class provides methods for i18n.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class Resources {
+
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(Resources.class);
+
+    /** The singleton instance.*/
+    private static Resources INSTANCE;
+
+    /** The locales supported by this server.*/
+    protected Locale[] locales;
+
+    /**
+     * No instance of this class is necessary.
+     */
+    private Resources() {
+    }
+
+
+    /**
+     * Returns the locales supported by this server.
+     *
+     * @return the supported locales.
+     */
+    public synchronized Locale [] getLocales() {
+        if (locales == null) {
+            readLocales();
+        }
+
+        return locales;
+    }
+
+
+    /**
+     * Read the locales configured for this server.
+     */
+    protected void readLocales() {
+        // TODO IMPLEMENT ME
+
+        locales = new Locale[2];
+        locales[0] = Locale.GERMANY;
+        locales[1] = Locale.ENGLISH;
+    }
+
+
+    private static synchronized void ensureInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = new Resources();
+        }
+    }
+
+
+    public static Locale getLocale(CallMeta meta) {
+        ensureInstance();
+
+        Locale[] locales = INSTANCE.getLocales();
+        return meta.getPreferredLocale(locales);
+    }
+
+
+    /**
+     * This method returns the translated value for <i>key</i> or <i>def</i> if
+     * <i>key</i> is not existing in the resource bundle.
+     *
+     * @param meta The CallMeta object of the request that contains the
+     * preferred locale.
+     * @param key The key that should be translated.
+     * @param def A default value that is returned, if <i>key</i> was not found.
+     *
+     * @return the translated message.
+     */
+    public static String getMsg(CallMeta meta, String key, String def) {
+
+        ensureInstance();
+
+        Locale[] locales = INSTANCE.getLocales();
+        Locale   locale  = meta.getPreferredLocale(locales);
+
+        return getMsg(locale, key, def);
+    }
+
+    public static String getMsg(
+        CallMeta meta,
+        String   key,
+        Object[] args
+    ) {
+        return getMsg(meta, key, key, args);
+    }
+
+    /**
+     * Returns a translated message based on a template specified by <i>key</i>
+     * that has necessary values to be filled in.
+     *
+     * @param meta The CallMeta object.
+     * @param key The key of the template in the resource bundle.
+     * @param def the default value if no template was found with <i>key</i>.
+     * @param args The arguments that are necessary for the template.
+     *
+     * @return a translated string.
+     */
+    public static String getMsg(
+        CallMeta meta,
+        String   key,
+        String   def,
+        Object[] args)
+    {
+        String template = getMsg(meta, key, (String)null);
+
+        if (template == null) {
+            return def;
+        }
+
+        return MessageFormat.format(template, args);
+    }
+
+
+    /**
+     * This method returns the translated value for <i>key</i> or <i>def</i> if
+     * <i>key</i> is not existing in the resource bundle.
+     *
+     * @param locale The locale.
+     * @param key The key that should be translated.
+     * @param def A default value that is returned, if <i>key</i> was not found.
+     *
+     * @return the translated message.
+     */
+    public static String getMsg(Locale locale, String key, String def) {
+        ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
+
+        try {
+            return bundle.getString(key);
+        }
+        catch (MissingResourceException mre) {
+            logger.warn("No message found for key: " + key);
+
+            return def;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/CacheInvalidationService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,54 @@
+package de.intevation.flys.artifacts.services;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.XMLService;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import net.sf.ehcache.Cache;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class CacheInvalidationService 
+extends      XMLService
+{
+    @Override
+    public Document processXML(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        Document result = XMLUtils.newDocument();
+
+        Element all = result.createElement("caches");
+
+        NodeList caches = data.getElementsByTagName("cache");
+
+        for (int i = 0, C = caches.getLength(); i < C; ++i) {
+            Element c = (Element)caches.item(i);
+            String name = c.getAttribute("name");
+            Element e = result.createElement("cache");
+            e.setAttribute("name", name);
+            Cache cache = CacheFactory.getCache(name);
+            if (cache != null) {
+                cache.removeAll();
+                e.setTextContent("All elements removed.");
+            }
+            else {
+                e.setTextContent("Error: Cache not found.");
+            }
+            all.appendChild(e);
+        }
+
+        result.appendChild(all);
+
+        return result;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/CrossSectionKMService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,242 @@
+package de.intevation.flys.artifacts.services;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.flys.model.CrossSection;
+import de.intevation.flys.model.CrossSectionLine;
+
+import java.util.AbstractMap;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import net.sf.ehcache.Cache;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * Service to find the next/previous km (measurement) of cross sections.
+ * Looking at the query for a single cross-section id at a single km, the
+ * service does the following:
+ *
+ * It returns the km itself if a measurement at that km was found and
+ * the N nearest other measurement points in both directions.
+ *
+ * That means, you can pass N=0 to find out whether a measurement at given km
+ * exists.
+ *
+ * If less than N neighbours exist in one direction, less are delivered
+ * (e.g. given measurements at [0,2,3,4,5,7,8,9] a query for km=8, N=3 will
+ * result in [4,5,7,8,9]).
+ */
+public class CrossSectionKMService
+extends      FLYSService
+{
+    private static Logger logger =
+        Logger.getLogger(CrossSectionKMService.class);
+
+    public static final String CACHE_NAME = "cross-section-kms";
+
+
+    /** Trivial constructor. */
+    public CrossSectionKMService() {
+    }
+
+
+    /**
+     * @param data
+     */
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        logger.debug("CrossSectionKMService.doProcess");
+
+        NodeList crossSectionNodes =
+            data.getElementsByTagName("art:cross-section");
+
+        Document document = XMLUtils.newDocument();
+
+        Element all = document.createElement("cross-sections");
+
+        for (int i = 0, CS = crossSectionNodes.getLength(); i < CS; ++i) {
+            Element crossSectionElement = (Element)crossSectionNodes.item(i);
+
+            String idString = crossSectionElement.getAttribute("id");
+            String kmString = crossSectionElement.getAttribute("km");
+            String neighborsString = crossSectionElement.getAttribute("n");
+
+            if (idString.length() == 0 || kmString.length() == 0) {
+                logger.debug("missing attributes in cross-section element");
+                continue;
+            }
+
+            double  km;
+            Integer crossSectionId;
+            int     N = 2;
+
+            try {
+                km             = Double.parseDouble(kmString);
+                crossSectionId = Integer.valueOf(idString);
+
+                if (neighborsString.length() > 0) {
+                    N = Integer.parseInt(neighborsString);
+                }
+            }
+            catch (NumberFormatException nfe) {
+                logger.debug("converting number failed", nfe);
+                continue;
+            }
+
+            NavigableMap<Double, Integer> map = getKms(crossSectionId);
+
+            if (map == null) {
+                logger.debug("cannot find cross section " + crossSectionId);
+                continue;
+            }
+
+            Deque<Map.Entry<Double, Integer>> result =
+                nearestNeighbors(map, km, N);
+
+            if (!result.isEmpty()) {
+                Element csE = document.createElement("cross-section");
+                csE.setAttribute("id", idString);
+                for (Map.Entry<Double, Integer> entry: result) {
+                    Element lineE = document.createElement("line");
+                    lineE.setAttribute(
+                        "line-id", String.valueOf(entry.getValue()));
+                    lineE.setAttribute(
+                        "km", String.valueOf(entry.getKey()));
+                    csE.appendChild(lineE);
+                }
+                all.appendChild(csE);
+            }
+        }
+
+        document.appendChild(all);
+
+        return document;
+    }
+
+    public static NavigableMap<Double, Integer> getKms(int crossSectionId) {
+
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        if (cache == null) {
+            return getUncached(crossSectionId);
+        }
+
+        NavigableMap<Double, Integer> map;
+
+        net.sf.ehcache.Element element = cache.get(crossSectionId);
+        if (element == null) {
+            map = getUncached(crossSectionId);
+            if (map != null) {
+                element = new net.sf.ehcache.Element(
+                    crossSectionId, map);
+                cache.put(element);
+            }
+        }
+        else {
+            map = (NavigableMap<Double, Integer>)element.getValue();
+        }
+
+        return map;
+    }
+
+
+    /**
+     * @param km  the kilometer from which to start searching for other
+     *            measurements
+     * @param N   number of neighboring measurements to find.
+     */
+    public static Deque<Map.Entry<Double, Integer>> nearestNeighbors(
+        NavigableMap<Double, Integer> map,
+        double                        km,
+        int                           N
+    ) {
+        Deque<Map.Entry<Double, Integer>> result =
+            new ArrayDeque<Map.Entry<Double, Integer>>(2*N);
+
+        Integer v = map.get(km);
+
+        if (v != null) {
+            result.add(new AbstractMap.SimpleEntry<Double, Integer>(km, v));
+        }
+
+        int i = 0;
+        for (Map.Entry<Double, Integer> entry:
+             map.headMap(km, false).descendingMap().entrySet()) {
+            if (i++ >= N) {
+                break;
+            }
+            result.addFirst(entry);
+        }
+
+        i = 0;
+        for (Map.Entry<Double, Integer> entry:
+             map.tailMap(km, false).entrySet()) {
+            if (i++ >= N) {
+                break;
+            }
+            result.addLast(entry);
+        }
+
+        return result;
+    }
+
+
+    /**
+     * @param crossSectionId id of queried cross-section (in db).
+     * @return Mapping from kilometer to db-id.
+     */
+    public static NavigableMap<Double, Integer> getUncached(
+        Integer crossSectionId
+    ) {
+        NavigableMap<Double, Integer> result =
+            new ConcurrentSkipListMap<Double, Integer>();
+
+        Session session = SessionHolder.HOLDER.get();
+        Query query = session.createQuery(
+            "from CrossSection where id=:id");
+        query.setParameter("id", crossSectionId);
+
+        List<CrossSection> crossSections = query.list();
+        if (crossSections.isEmpty()) {
+            return null;
+        }
+
+        CrossSection crossSection = crossSections.get(0);
+        List<CrossSectionLine> lines = crossSection.getLines();
+
+        for (CrossSectionLine line: lines) {
+            Double  km = line.getKm().doubleValue();
+            Integer id = line.getId();
+            result.put(km, id);
+        }
+
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/DischargeInfoService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,127 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.List;
+import java.util.Date;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.DischargeTable;
+import de.intevation.flys.model.TimeInterval;
+
+/**
+ * This service provides information about discharges at a defined gauge.
+ *
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class DischargeInfoService extends FLYSService {
+
+    /** The logger used in this service. */
+    private static Logger logger = Logger.getLogger(DischargeInfoService.class);
+
+    public static final String GAUGE_XPATH = "/art:gauge/text()";
+
+    public DischargeInfoService() {
+    }
+
+
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        logger.debug("DischargeInfoService.process");
+
+        String gaugeNumber = XMLUtils.xpathString(
+            data, GAUGE_XPATH, ArtifactNamespaceContext.INSTANCE);
+
+        if(gaugeNumber == null ||
+           (gaugeNumber = gaugeNumber.trim()).length() == 0) {
+            logger.warn("No gauge specified. Cannot return discharge info.");
+            return XMLUtils.newDocument();
+        }
+
+        logger.debug("Getting discharge for gauge: " + gaugeNumber);
+
+        long gn;
+        try {
+            gn = Long.parseLong(gaugeNumber);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Invalid gauge number. Cannot return discharg info.");
+            return XMLUtils.newDocument();
+        }
+
+        Gauge gauge = Gauge.getGaugeByOfficialNumber(gn);
+
+        logger.debug("Found gauge: " + gauge.getName());
+
+        return buildDocument(gauge);
+    }
+
+
+    protected Document buildDocument(Gauge gauge) {
+        Document result = XMLUtils.newDocument();
+
+        List<DischargeTable> tables =gauge.getDischargeTables();
+
+        Element all = result.createElement("discharges");
+        for (DischargeTable dt: tables) {
+            if (dt.getKind() == Gauge.MASTER_DISCHARGE_TABLE) {
+                continue;
+            }
+
+            Element discharge = result.createElement("discharge");
+            discharge.setAttribute("description", dt.getDescription());
+
+            // Get time interval.
+            TimeInterval ti = dt.getTimeInterval();
+
+            if (ti == null) {
+                logger.warn("DischargeTable has no TimeInterval set!");
+                continue;
+            }
+
+            Date startTime = ti.getStartTime();
+            Date stopTime = ti.getStopTime();
+
+            // Get the year for start end end date.
+            int startYear;
+            int stopYear;
+            Calendar c = new GregorianCalendar();
+            if (startTime != null) {
+                c.setTime(startTime);
+                startYear = c.get(Calendar.YEAR);
+            }
+            else {
+                startYear = -1;
+            }
+            if (stopTime != null) {
+                c.setTime(stopTime);
+                stopYear = c.get(Calendar.YEAR);
+            }
+            else {
+                stopYear = -1;
+            }
+
+            discharge.setAttribute("start", String.valueOf(startYear));
+            discharge.setAttribute("end", String.valueOf(stopYear));
+
+            all.appendChild(discharge);
+        }
+        result.appendChild(all);
+        return result;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,139 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.FastAnnotations;
+
+import de.intevation.flys.artifacts.model.LocationProvider;
+
+
+/**
+ * This service provides information about distances of a specified river.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DistanceInfoService extends FLYSService {
+
+    /** The logger used in this service. */
+    private static Logger logger = Logger.getLogger(DistanceInfoService.class);
+
+    public static final String RIVER_XPATH = "/art:river/text()";
+
+    public static final String FILTER_XPATH = "/art:river/art:filter/text()";
+
+
+    /**
+     * The default constructor.
+     */
+    public DistanceInfoService() {
+    }
+
+
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        logger.debug("DistanceInfoService.process");
+
+        String river = XMLUtils.xpathString(
+            data, RIVER_XPATH, ArtifactNamespaceContext.INSTANCE);
+
+        String filterName  = XMLUtils.xpathString(
+            data, FILTER_XPATH, ArtifactNamespaceContext.INSTANCE);
+
+        if (river == null || (river = river.trim()).length() == 0) {
+            logger.warn("No river specified. Cannot return distance info!");
+            return XMLUtils.newDocument();
+        }
+
+        logger.debug("Search distances for river: " + river);
+
+        FastAnnotations fas = LocationProvider.getAnnotations(river);
+
+        FastAnnotations.Filter filter = selectFilter(filterName);
+
+        return buildDocument(fas.filter(filter));
+    }
+
+    protected Document buildDocument(
+        Iterator<FastAnnotations.Annotation> iter
+    ) {
+        Document result = XMLUtils.newDocument();
+
+        Element all = result.createElement("distances");
+
+        while (iter.hasNext()) {
+            all.appendChild(buildNode(result, iter.next()));
+        }
+
+        result.appendChild(all);
+
+        return result;
+    }
+
+    protected static FastAnnotations.Filter selectFilter(String name) {
+
+        if (name != null) {
+            if ("locations".equals(name)) return FastAnnotations.IS_POINT;
+            if ("distances".equals(name)) return FastAnnotations.IS_RANGE;
+        }
+
+        return FastAnnotations.ALL;
+    }
+
+    /**
+     * Builds an Element for a distance info.
+     *
+     * @param an The Annotation that provides information about the distance.
+     *
+     * @return an Element that contains information about a distance.
+     */
+    protected static Element buildNode(
+        Document                   document,
+        FastAnnotations.Annotation an
+    ) {
+        Element distance = document.createElement("distance");
+
+        distance.setAttribute("description", an.getPosition());
+
+        String riverSide = an.getAttribute();
+
+        if (riverSide != null && riverSide.length() > 0) {
+            distance.setAttribute("riverside", riverSide);
+        }
+
+        distance.setAttribute("from", String.valueOf(an.getA()));
+
+        double b      = an.getB();
+        double bottom = an.getBottom();
+        double top    = an.getTop();
+
+        if (!Double.isNaN(b)) {
+            distance.setAttribute("to", String.valueOf(b));
+        }
+
+        if (!Double.isNaN(bottom)) {
+            distance.setAttribute("bottom", String.valueOf(bottom));
+        }
+
+        if (!Double.isNaN(top)) {
+            distance.setAttribute("top", String.valueOf(top));
+        }
+
+        return distance;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FLYSService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,59 @@
+package de.intevation.flys.artifacts.services;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifactdatabase.XMLService;
+
+import de.intevation.flys.backend.SessionHolder;
+
+
+public abstract class FLYSService extends XMLService {
+
+    private static final Logger logger = Logger.getLogger(FLYSService.class);
+
+
+    @Override
+    public Document processXML(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        init();
+
+        try {
+            return doProcess(data, globalContext, callMeta);
+        }
+        finally {
+            shutdown();
+        }
+    }
+
+
+    protected abstract Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta);
+
+
+    protected void init() {
+        logger.debug("init");
+        SessionHolder.acquire();
+    }
+
+
+    protected void shutdown() {
+        logger.debug("shutdown");
+        Session session = SessionHolder.HOLDER.get();
+        session.close();
+
+        SessionHolder.release();
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FileUploadService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,103 @@
+package de.intevation.flys.artifacts.services;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import org.apache.commons.codec.binary.Base64;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.utils.FileTools;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FileUploadService extends FLYSService {
+
+    /** The logger used in this service.*/
+    private static Logger logger = Logger.getLogger(FileUploadService.class);
+
+    /** XPath that points to the artifact uuid.*/
+    public static final String XPATH_ARTIFACT_UUID =
+        "/upload/artifact-uuid/text()";
+
+    /** XPath that points to the base64 encoded data.*/
+    public static final String XPATH_DATA = "/upload/data/text()";
+
+    public FileUploadService() {
+    }
+
+
+    @Override
+    protected Document doProcess(
+        Document data,
+        GlobalContext context,
+        CallMeta callMeta
+    ) {
+        logger.debug("FileUploadService.process");
+
+        String uuid = extractUuid(data);
+
+        byte[] fileData = extractData(data);
+        if (fileData != null) {
+            try {
+                String shapePath = FLYSUtils.getXPathString(
+                    FLYSUtils.XPATH_SHAPEFILE_DIR);
+
+                File artifactDir = FileTools.getDirectory(shapePath, uuid);
+                FileOutputStream fos =
+                    new FileOutputStream(
+                    new File(artifactDir, "user-rgd.zip"));
+                try {
+                    fos.write(fileData);
+                }
+                finally {
+                    fos.close();
+                }
+            }
+            catch (IOException ioe) {
+                logger.warn(ioe, ioe);
+            }
+        }
+        else {
+            logger.debug("No data in uploaded xml.");
+        }
+
+        Document doc = XMLUtils.newDocument();
+        ElementCreator ec = new ElementCreator(doc, null, null);
+        Element resp = ec.create("response");
+        doc.appendChild(resp);
+
+        return doc;
+    }
+
+
+    protected String extractUuid(Document data) {
+        return XMLUtils.xpathString(
+            data, XPATH_ARTIFACT_UUID, ArtifactNamespaceContext.INSTANCE);
+    }
+
+
+    protected byte[] extractData(Document data) {
+        String b64Data = XMLUtils.xpathString(
+            data, XPATH_DATA, ArtifactNamespaceContext.INSTANCE);
+
+        if (b64Data != null && b64Data != "") {
+           byte[] fileData = Base64.decodeBase64(b64Data);
+           return fileData;
+        }
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FixingsKMChartService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,312 @@
+package de.intevation.flys.artifacts.services;
+
+import de.intevation.artifactdatabase.DefaultService;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.Service;
+
+import de.intevation.flys.artifacts.model.FixingsColumn;
+import de.intevation.flys.artifacts.model.FixingsColumnFactory;
+import de.intevation.flys.artifacts.model.FixingsFilterBuilder;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.Fixing;
+
+import de.intevation.flys.artifacts.model.FixingsOverview;
+import de.intevation.flys.artifacts.model.FixingsOverviewFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.flys.utils.Pair;
+
+import gnu.trove.TDoubleArrayList;
+
+import java.awt.Dimension;
+import java.awt.Transparency;
+
+import java.awt.image.BufferedImage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+
+import org.jfree.data.xy.DefaultXYDataset;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class FixingsKMChartService
+extends      DefaultService
+{
+    private static final Logger log =
+        Logger.getLogger(FixingsKMChartService.class);
+
+    public static final int DEFAULT_WIDTH  = 240;
+    public static final int DEFAULT_HEIGHT = 180;
+
+    public static final String DEFAULT_FORMAT = "png";
+
+    // TODO: Load fancy image from resources.
+    public static final byte [] EMPTY = {
+        (byte)0x89, (byte)0x50, (byte)0x4e, (byte)0x47, 
+        (byte)0x0d, (byte)0x0a, (byte)0x1a, (byte)0x0a, 
+        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0d, 
+        (byte)0x49, (byte)0x48, (byte)0x44, (byte)0x52, 
+        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, 
+        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, 
+        (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00, 
+        (byte)0x00, (byte)0x3a, (byte)0x7e, (byte)0x9b, 
+        (byte)0x55, (byte)0x00, (byte)0x00, (byte)0x00, 
+        (byte)0x01, (byte)0x73, (byte)0x52, (byte)0x47, 
+        (byte)0x42, (byte)0x00, (byte)0xae, (byte)0xce, 
+        (byte)0x1c, (byte)0xe9, (byte)0x00, (byte)0x00, 
+        (byte)0x00, (byte)0x09, (byte)0x70, (byte)0x48, 
+        (byte)0x59, (byte)0x73, (byte)0x00, (byte)0x00, 
+        (byte)0x0b, (byte)0x13, (byte)0x00, (byte)0x00, 
+        (byte)0x0b, (byte)0x13, (byte)0x01, (byte)0x00, 
+        (byte)0x9a, (byte)0x9c, (byte)0x18, (byte)0x00, 
+        (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x74, 
+        (byte)0x49, (byte)0x4d, (byte)0x45, (byte)0x07, 
+        (byte)0xdc, (byte)0x04, (byte)0x04, (byte)0x10, 
+        (byte)0x30, (byte)0x15, (byte)0x7d, (byte)0x77, 
+        (byte)0x36, (byte)0x0b, (byte)0x00, (byte)0x00, 
+        (byte)0x00, (byte)0x08, (byte)0x74, (byte)0x45, 
+        (byte)0x58, (byte)0x74, (byte)0x43, (byte)0x6f, 
+        (byte)0x6d, (byte)0x6d, (byte)0x65, (byte)0x6e, 
+        (byte)0x74, (byte)0x00, (byte)0xf6, (byte)0xcc, 
+        (byte)0x96, (byte)0xbf, (byte)0x00, (byte)0x00, 
+        (byte)0x00, (byte)0x0a, (byte)0x49, (byte)0x44, 
+        (byte)0x41, (byte)0x54, (byte)0x08, (byte)0xd7, 
+        (byte)0x63, (byte)0xf8, (byte)0x0f, (byte)0x00, 
+        (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x00, 
+        (byte)0x1b, (byte)0xb6, (byte)0xee, (byte)0x56, 
+        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 
+        (byte)0x49, (byte)0x45, (byte)0x4e, (byte)0x44, 
+        (byte)0xae, (byte)0x42, (byte)0x60, (byte)0x82
+    };
+
+    private static final Output empty() {
+        return new Output(EMPTY, "image/png");
+    }
+
+    @Override
+    public Service.Output process(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        log.debug("FixingsKMChartService.process");
+
+        SessionHolder.acquire();
+        try {
+            return doProcess(data, globalContext, callMeta);
+        }
+        finally {
+            SessionHolder.HOLDER.get().close();
+            SessionHolder.release();
+        }
+    }
+
+    protected Service.Output doProcess(
+        Document      input,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        String    river  = getRiverName(input);
+        Double    km     = getKM(input);
+        Dimension extent = getExtent(input);
+        String    format = getFormat(input);
+
+        if (river == null || km == null) {
+            log.warn("River and/or km invalid.");
+            return empty();
+        }
+
+        FixingsOverview overview = FixingsOverviewFactory.getOverview(river);
+
+        if (overview == null) {
+            log.warn("No overview found for river '" + river + "'");
+            return empty();
+        }
+
+        FixingsFilterBuilder ffb = new FixingsFilterBuilder(input);
+
+        List<Fixing.Column> columns = overview.filter(
+            ffb.getRange(),
+            ffb.getFilter());
+
+        List<Pair<Fixing.Column, FixingsColumn>> cols =
+            new ArrayList<Pair<Fixing.Column, FixingsColumn>>();
+
+        for (Fixing.Column col: columns) {
+            FixingsColumn data =
+                FixingsColumnFactory.INSTANCE.getColumnData(col);
+            if (data != null) {
+                cols.add(new Pair<Fixing.Column, FixingsColumn>(col, data));
+            }
+        }
+
+        JFreeChart chart = createChart(cols, river, km);
+
+        return encode(chart, extent, format);
+    }
+
+    protected static Output encode(
+        JFreeChart chart,
+        Dimension  extent, 
+        String     format
+    ) {
+        BufferedImage image = chart.createBufferedImage(
+            extent.width, extent.height,
+            Transparency.BITMASK,
+            null);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        try {
+            ImageIO.write(image, format, out);
+        }
+        catch (IOException ioe) {
+            log.warn("writing image failed", ioe);
+            return empty();
+        }
+
+        return new Output(out.toByteArray(), "image/" + format);
+    }
+
+    protected static JFreeChart createChart(
+        List<Pair<Fixing.Column, FixingsColumn>> cols,
+        String river,
+        double km
+    ) {
+
+        TDoubleArrayList ws = new TDoubleArrayList(cols.size());
+        TDoubleArrayList qs = new TDoubleArrayList(cols.size());
+
+        double [] w = new double[1];
+        for (Pair<Fixing.Column, FixingsColumn> col: cols) {
+            boolean interpolated = col.getB().getW(km, w);
+            // TODO: Do something special with the interpolated values.
+            double q = col.getB().getQ(km);
+            if (!Double.isNaN(w[0]) && !Double.isNaN(q)) {
+                ws.add(w[0]);
+                qs.add(q);
+                // TODO: Generate labels depending on sectors.
+            }
+        }
+
+        DefaultXYDataset dataset = new DefaultXYDataset();
+
+        dataset.addSeries(
+            "Fixierungen", // TODO: i18n
+            new double [][] { qs.toNativeArray(), ws.toNativeArray() });
+
+        JFreeChart chart = ChartFactory.createScatterPlot(
+            "Fixierungen " + river + ": km " + km, // TODO: i18n
+            "Q", // TODO: i18n
+            "W", // TODO: i18n
+            dataset,
+            PlotOrientation.VERTICAL,
+            true,
+            false,
+            false);
+
+        //XYPlot plot = chart.getXYPlot();
+
+        //plot.setDataset(dataset);
+
+        ChartUtilities.applyCurrentTheme(chart);
+
+        return chart;
+    }
+
+    protected static String getRiverName(Document input) {
+        NodeList rivers = input.getElementsByTagName("river");
+
+        if (rivers.getLength() == 0) {
+            return null;
+        }
+
+        String river = ((Element)rivers.item(0)).getAttribute("name");
+
+        return river.length() > 0 ? river : null;
+    }
+
+    protected static Double getKM(Document input) {
+        NodeList kms = input.getElementsByTagName("km");
+
+        if (kms.getLength() == 0) {
+            return null;
+        }
+
+        String km = ((Element)kms.item(0)).getAttribute("value");
+
+        try {
+            return Double.valueOf(km);
+        }
+        catch (NumberFormatException nfe) {
+            log.warn("Km '" + km + " is not a valid number.");
+            return null;
+        }
+    }
+
+    protected static Dimension getExtent(Document input) {
+
+        int width  = DEFAULT_WIDTH;
+        int height = DEFAULT_HEIGHT;
+
+        NodeList extents = input.getElementsByTagName("extent");
+
+        if (extents.getLength() > 0) {
+            Element element = (Element)extents.item(0);
+            String w = element.getAttribute("width");
+            String h = element.getAttribute("height");
+
+            try {
+                width = Math.max(1, Integer.parseInt(w));
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("width '" + w + "' is not a valid.");
+            }
+
+            try {
+                height = Math.max(1, Integer.parseInt(h));
+            }
+            catch (NumberFormatException nfe) {
+                log.warn("height '" + h + "' is not a valid");
+            }
+        }
+
+        return new Dimension(width, height);
+    }
+
+    protected static String getFormat(Document input) {
+        String format = DEFAULT_FORMAT;
+
+        NodeList formats = input.getElementsByTagName("format");
+
+        if (formats.getLength() > 0) {
+            String type = ((Element)formats.item(0)).getAttribute("type");
+            if (type.length() > 0) {
+                format = type;
+            }
+        }
+
+        return format;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,63 @@
+package de.intevation.flys.artifacts.services;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.FixingsFilterBuilder;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.Fixing.Filter;
+
+import de.intevation.flys.artifacts.model.FixingsOverview.Range;
+
+import de.intevation.flys.artifacts.model.FixingsOverview;
+import de.intevation.flys.artifacts.model.FixingsOverviewFactory;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class FixingsOverviewService
+extends      FLYSService
+{
+    private static Logger log =
+        Logger.getLogger(FixingsOverviewService.class);
+
+    public FixingsOverviewService() {
+    }
+
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        log.debug("FixingsOverviewService.doProcess");
+
+        Document document = XMLUtils.newDocument();
+
+        NodeList nodes = data.getElementsByTagName("river");
+
+        String river = nodes.getLength() > 0
+            ? ((Element)nodes.item(0)).getAttribute("name")
+            : "";
+
+        FixingsOverview overview = FixingsOverviewFactory.getOverview(river);
+
+        if (overview != null) {
+            FixingsFilterBuilder ffb = new FixingsFilterBuilder(data);
+            Range  range  = ffb.getRange();
+            Filter filter = ffb.getFilter();
+            overview.generateOverview(document, range, filter);
+        }
+        else {
+            log.warn("No overview for river '" + river + "' available.");
+        }
+
+        return document;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/GaugeInfoService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,160 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.RiverFactory;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class GaugeInfoService extends FLYSService {
+
+    interface Filter {
+        boolean apply(Gauge gauge);
+    }
+
+
+    protected class ReferenceNumberFilter implements Filter {
+        private long refNr;
+
+        public ReferenceNumberFilter(long refNr) {
+            this.refNr = refNr;
+        }
+
+        @Override
+        public boolean apply(Gauge  gauge) {
+            logger.debug("Test gauge '" + gauge.getName() + "'");
+
+            if (gauge != null && gauge.getOfficialNumber() == refNr) {
+                return true;
+            }
+            else {
+                return false;
+            }
+        }
+    } // end of ReferenceNumberFilter class
+
+
+    /** The logger that is used by this service.*/
+    private static Logger logger = Logger.getLogger(GaugeInfoService.class);
+
+
+    public static final String XPATH_RIVERNAME = "/art:river/@name";
+
+    public static final String XPATH_REFERENCE_NR
+        = "/art:river/art:filter/art:gauge/text()";
+
+
+    public GaugeInfoService() {
+    }
+
+
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext context,
+        CallMeta      callMeta
+    ) {
+        logger.debug("GaugeInfoService.process");
+
+        // XXX REMOVE THIS
+        logger.debug(XMLUtils.toString(data));
+
+        River river = getRiverFromRequest(data);
+
+        List<Filter> filters  = getFilters(data);
+        List<Gauge> allGauges = river.getGauges();
+        List<Gauge> filtered  = new ArrayList<Gauge>();
+
+        for (Gauge g: allGauges) {
+            for (Filter f: filters) {
+                if (f.apply(g)) {
+                    filtered.add(g);
+                }
+            }
+        }
+
+        return buildInfoDocument(filtered);
+    }
+
+
+    protected River getRiverFromRequest(Document data) {
+        String rivername = XMLUtils.xpathString(
+            data,
+            XPATH_RIVERNAME,
+            ArtifactNamespaceContext.INSTANCE);
+
+        logger.debug("Return Gauge info for River '" + rivername + "'");
+
+        return rivername != null ? RiverFactory.getRiver(rivername) : null;
+    }
+
+
+    protected List<Filter> getFilters(Document data) {
+        List<Filter> filters = new ArrayList<Filter>();
+
+        String refNr = XMLUtils.xpathString(
+            data,
+            XPATH_REFERENCE_NR,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (refNr != null && refNr.length() > 0) {
+            try {
+                filters.add(new ReferenceNumberFilter(Long.valueOf(refNr)));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        return filters;
+    }
+
+
+    protected Document buildInfoDocument(List<Gauge> gauges) {
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element service = cr.create("service");
+
+        logger.debug("Append " + gauges.size() + " gauges to info doc.");
+
+        for (Gauge g: gauges) {
+            Range r = g.getRange();
+
+            Element el = cr.create("gauge");
+            cr.addAttr(el, "name", g.getName());
+            cr.addAttr(el, "lower", String.valueOf(r.getA().doubleValue()));
+            cr.addAttr(el, "upper", String.valueOf(r.getB().doubleValue()));
+
+            service.appendChild(el);
+        }
+
+        doc.appendChild(service);
+
+        return doc;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/MainValuesService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,308 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.MainValueType;
+import de.intevation.flys.model.NamedMainValue;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.MainValuesFactory;
+import de.intevation.flys.artifacts.model.RiverFactory;
+
+
+/**
+ * This service returns the main values of a river's gauge based on the start
+ * and end point of the river.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MainValuesService extends FLYSService {
+
+    /** The logger that is used by this service.*/
+    private static Logger logger = Logger.getLogger(MainValuesService.class);
+
+
+    /** The XPath that points to the river definition of the incoming request.*/
+    public static final String XPATH_RIVER = "/art:mainvalues/art:river/text()";
+
+    /** The XPath that points to the start definition of the incoming request.*/
+    public static final String XPATH_START = "/art:mainvalues/art:start/text()";
+
+    /** The XPath that points to the end definition of the incoming request.*/
+    public static final String XPATH_END = "/art:mainvalues/art:end/text()";
+
+    /**
+     * The default constructor.
+     */
+    public MainValuesService() {
+    }
+
+
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext context,
+        CallMeta      callMeta
+    ) {
+        logger.debug("MainValuesService.process");
+
+        try {
+            River river     = getRequestedRiver(data);
+            double[] minmax = getRequestedStartEnd(data, river);
+            Gauge gauge     = river.determineGauge(minmax[0], minmax[1]);
+
+            logger.debug("Found gauge: " + gauge.getName());
+
+            List<MainValue> mainValues = getMainValues(river, gauge);
+
+            return buildDocument(river, gauge, mainValues, context);
+        }
+        catch (NullPointerException npe) {
+            logger.error("Could not process the request.");
+            logger.error(npe, npe);
+
+            return XMLUtils.newDocument();
+        }
+    }
+
+
+    /**
+     * This method extracts the river from the incoming request. If no river
+     * string was found or no river is found in the database based on this
+     * string a NullPointerException is thrown.
+     *
+     * @param data The incoming request data.
+     *
+     * @return the River object.
+     */
+    protected River getRequestedRiver(Document data)
+    throws    NullPointerException
+    {
+        logger.debug("MainValuesService.getRiver");
+
+        String riverStr = XMLUtils.xpathString(
+            data, XPATH_RIVER, ArtifactNamespaceContext.INSTANCE);
+
+        if (riverStr == null || riverStr.trim().length() == 0) {
+            throw new NullPointerException("No river found in the request.");
+        }
+
+        River river = RiverFactory.getRiver(riverStr);
+
+        if (river == null) {
+            throw new NullPointerException("No such river found: " + riverStr);
+        }
+
+        return river;
+    }
+
+
+    /**
+     * This method extracts the start and end point from incoming request
+     * document and returns both values in an array. If no start and end strings
+     * are found in the document, the min/max values of the <i>river</i> are
+     * returned.
+     *
+     * @param data The incoming request data.
+     * @param river The river of the request.
+     *
+     * @return the start and end point.
+     */
+    protected double[] getRequestedStartEnd(Document data, River river) {
+        logger.debug("MainValuesService.getStartEnd");
+
+        String startStr = XMLUtils.xpathString(
+            data, XPATH_START, ArtifactNamespaceContext.INSTANCE);
+
+        String endStr = XMLUtils.xpathString(
+            data, XPATH_END, ArtifactNamespaceContext.INSTANCE);
+
+        try {
+            double start = Double.parseDouble(startStr);
+            double end   = Double.parseDouble(endStr);
+
+            logger.debug("Found start: " + start);
+            logger.debug("Found end: " + end);
+
+            return new double[] { start, end };
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn(nfe, nfe);
+
+            return river.determineMinMaxDistance();
+        }
+    }
+
+
+    /**
+     * This method creates the result document that includes the main values of
+     * the specified <i>gauge</i>.
+     *
+     * @param river The river.
+     * @param gauge The gauge.
+     *
+     * @return a document that includes the main values of the specified river
+     * at the specified gauge.
+     */
+    protected List<MainValue> getMainValues(River river, Gauge gauge)
+    throws    NullPointerException
+    {
+        if (logger.isDebugEnabled()) {
+            logger.debug("MainValuesService.buildMainValues");
+            logger.debug("River: " + river.getName());
+            logger.debug("Gauge: " + gauge.getName());
+        }
+
+        List<MainValue> mainValues = MainValuesFactory.getMainValues(gauge);
+
+        if (mainValues == null || mainValues.isEmpty()) {
+            throw new NullPointerException("No main values found.");
+        }
+
+        logger.debug(mainValues.size() + " main values found.");
+
+        return mainValues;
+    }
+
+
+    protected Document buildDocument(
+        River           river,
+        Gauge           gauge,
+        List<MainValue> mainValues,
+        Object          context)
+    {
+        logger.debug("MainValuesService.buildDocument");
+
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element rootEl = cr.create("service");
+        cr.addAttr(rootEl, "name", "mainvalues");
+
+        doc.appendChild(rootEl);
+
+        appendMetaInformation(doc, rootEl, river, gauge, context);
+        appendMainValues(doc, rootEl, mainValues, context);
+
+        return doc;
+    }
+
+
+    /**
+     * This method appends some meta information to the result document.
+     * Currently, the river's and gauge's names and the gauge's range are
+     * appended.
+     *
+     * @param root The root element of the result document.
+     * @param river The river.
+     * @param gauge The gauge.
+     * @param context The context object.
+     */
+    protected void appendMetaInformation(
+        Document doc,
+        Element  root,
+        River    river,
+        Gauge    gauge,
+        Object   context)
+    {
+        logger.debug("MainValuesService.appendMetaInformation");
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Range range = gauge.getRange();
+
+        Element riverEl = cr.create("river");
+        cr.addAttr(riverEl, "name", river.getName());
+
+        Element gaugeEl = cr.create("gauge");
+        cr.addAttr(gaugeEl, "name", gauge.getName());
+        cr.addAttr(gaugeEl, "from", range.getA().toString());
+        cr.addAttr(gaugeEl, "to", range.getB().toString());
+
+        root.appendChild(riverEl);
+        root.appendChild(gaugeEl);
+    }
+
+
+    protected void appendMainValues(
+        Document        doc,
+        Element         root,
+        List<MainValue> mainValues,
+        Object          context)
+    {
+        logger.debug("MainValuesService.appendMainValues");
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element list = cr.create("mainvalues");
+
+        for (MainValue mainValue: mainValues) {
+            Element newEl = buildMainValueElement(doc, mainValue, context);
+
+            if (newEl != null) {
+                list.appendChild(newEl);
+            }
+        }
+
+        root.appendChild(list);
+    }
+
+
+    /**
+     * This method builds a concrete mainvalue element. This element consists of
+     * three attributes: the value, its name and its type.
+     *
+     * @param doc The owner document.
+     * @param mainValue The mainvalue.
+     * @param context The context object.
+     *
+     * @return a mainvalue element.
+     */
+    protected Element buildMainValueElement(
+        Document  doc,
+        MainValue mainValue,
+        Object    context)
+    {
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        NamedMainValue namedMainValue = mainValue.getMainValue();
+        MainValueType  mainValueType  = namedMainValue.getType();
+
+        Element el = cr.create("mainvalue");
+
+        cr.addAttr(el, "value", mainValue.getValue().toString());
+        cr.addAttr(el, "name", namedMainValue.getName());
+        cr.addAttr(el, "type", mainValueType.getName());
+
+        return el;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/MapInfoService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,148 @@
+package de.intevation.flys.artifacts.services;
+
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+
+import javax.xml.xpath.XPathConstants;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.Config;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.XMLService;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.utils.GeometryUtils;
+
+/**
+ * This service provides information about the supported rivers by this
+ * application.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MapInfoService extends XMLService {
+
+    /** XPath that points to the river.*/
+    public static final String XPATH_RIVER = "/mapinfo/river/text()";
+
+    public static final String XPATH_RIVER_PROJECTION =
+        "/artifact-database/floodmap/river[@name=$river]/srid/@value";
+
+    public static final String XPATH_RIVER_BACKGROUND =
+        "/artifact-database/floodmap/river[@name='%RIVER%']/background-wms";
+
+    public static final String XPATH_RIVER_WMS =
+        "/artifact-database/floodmap/river[@name=$river]/river-wms/@url";
+
+
+    /** The logger used in this service.*/
+    private static Logger logger = Logger.getLogger(MapInfoService.class);
+
+
+    /**
+     * The default constructor.
+     */
+    public MapInfoService() {
+    }
+
+    protected static String getStringXPath(
+        String              query, 
+        Map<String, String> variables
+    ) {
+        return (String)XMLUtils.xpath(
+            Config.getConfig(), query, XPathConstants.STRING,
+            null, variables);
+    }
+
+    protected static Node getNodeXPath(
+        String              query, 
+        Map<String, String> variables
+    ) {
+        return (Node)XMLUtils.xpath(
+            Config.getConfig(), query, XPathConstants.NODE,
+            null, variables);
+    }
+
+    @Override
+    public Document processXML(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        logger.debug("MapInfoService.process");
+
+        Document result   = XMLUtils.newDocument();
+        ElementCreator cr = new ElementCreator(result, null, null);
+
+        Element mapinfo = cr.create("mapinfo");
+        result.appendChild(mapinfo);
+
+        String river = extractRiver(data);
+        if (river == null || river.length() == 0) {
+            logger.warn("Cannot generate information: river is empty!");
+            return result;
+        }
+
+        Element root = cr.create("river");
+        cr.addAttr(root, "name", river);
+        mapinfo.appendChild(root);
+
+        Envelope env = GeometryUtils.getRiverBoundary(river);
+        if (env != null) {
+            String bounds = GeometryUtils.jtsBoundsToOLBounds(env);
+            logger.debug("River '" + river + "' bounds: " + bounds);
+
+            Element bbox = cr.create("bbox");
+            cr.addAttr(bbox, "value", bounds);
+            root.appendChild(bbox);
+        }
+
+        Map<String, String> vars = new HashMap<String, String>();
+        vars.put("river", river);
+
+        String sridStr = getStringXPath(XPATH_RIVER_PROJECTION, vars);
+
+        if (sridStr != null && sridStr.length() > 0) {
+            Element srid = cr.create("srid");
+            cr.addAttr(srid, "value", sridStr);
+            root.appendChild(srid);
+        }
+
+        Element back = (Element)getNodeXPath(XPATH_RIVER_BACKGROUND, vars);
+        if (back != null) {
+            Element background = cr.create("background-wms");
+            cr.addAttr(background, "url", back.getAttribute("url"));
+            cr.addAttr(background, "layers", back.getAttribute("layers"));
+            root.appendChild(background);
+        }
+
+        String wmsStr = getStringXPath(XPATH_RIVER_WMS, vars);
+        if (wmsStr != null && wmsStr.length() > 0) {
+            Element wms = cr.create("river-wms");
+            cr.addAttr(wms, "url", wmsStr);
+            root.appendChild(wms);
+        }
+
+        return result;
+    }
+
+
+    protected String extractRiver(Document data) {
+        return XMLUtils.xpathString(
+            data, XPATH_RIVER, ArtifactNamespaceContext.INSTANCE);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/MetaDataService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,169 @@
+package de.intevation.flys.artifacts.services;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.StringUtils;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+
+import de.intevation.flys.artifacts.datacage.Recommendations;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+public class MetaDataService
+extends      FLYSService
+{
+    private static Logger log = Logger.getLogger(MetaDataService.class);
+
+    public static final String XPATH_ARTIFACT_ID = "/art:meta/art:artifact-id/@value";
+    public static final String XPATH_USER_ID     = "/art:meta/art:user-id/@value";
+    public static final String XPATH_OUTS        = "/art:meta/art:outs/@value";
+    public static final String XPATH_PARAMETERS  = "/art:meta/art:parameters/@value";
+
+    /** The global context key of the artifact database. */
+    public static final String ARTIFACT_DATA_BASE_KEY =
+        "global.artifact.database";
+
+    public MetaDataService() {
+    }
+
+    @Override
+    protected Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        log.debug("MetaDataService.process");
+
+        String artifactId = XMLUtils.xpathString(
+            data, XPATH_ARTIFACT_ID, ArtifactNamespaceContext.INSTANCE);
+
+        if (artifactId != null
+        && (artifactId = artifactId.trim()).length() == 0) {
+            artifactId = null;
+        }
+
+        String userId = XMLUtils.xpathString(
+            data, XPATH_USER_ID, ArtifactNamespaceContext.INSTANCE);
+
+        if (userId != null
+        && (userId = userId.trim()).length() == 0) {
+            userId = null;
+        }
+
+        String outs = XMLUtils.xpathString(
+            data, XPATH_OUTS, ArtifactNamespaceContext.INSTANCE);
+
+        String parameters = XMLUtils.xpathString(
+            data, XPATH_PARAMETERS, ArtifactNamespaceContext.INSTANCE);
+
+        return doService(
+            artifactId, userId, outs, parameters, globalContext);
+    }
+
+    protected static Map<String, Object> splitParameters(
+        String              parameters,
+        Map<String, Object> data
+    ) {
+        if (parameters != null) {
+            String [] parts = parameters.split("\\s*;\\s*");
+            for (String part: parts) {
+                String [] kv = part.split("\\s*:\\s*");
+                if (kv.length < 2 || (kv[0] = kv[0].trim()).length() == 0) {
+                    continue;
+                }
+                String [] values = kv[1].split("\\s*,\\s*");
+                data.put(kv[0], values.length == 1 ? values[0] : values);
+            }
+        }
+        return data;
+    }
+
+    protected Document doService(
+        String        artifactId,
+        String        userId,
+        String        outsString,
+        String        parameters,
+        GlobalContext globalContext
+    ) {
+        Document result = XMLUtils.newDocument();
+
+        FLYSArtifact flysArtifact;
+
+        if (log.isDebugEnabled()) {
+            log.debug("artifact  : " + artifactId);
+            log.debug("user      : " + userId);
+            log.debug("outs      : " + outsString);
+            log.debug("parameters: " + parameters);
+        }
+
+        if (userId != null && !StringUtils.checkUUID(userId)) {
+            log.warn("'" + userId + "' is not a UUID");
+            return result;
+        }
+
+        if (artifactId != null) {
+            if (!StringUtils.checkUUID(artifactId)) {
+                log.warn("'" + artifactId + "' is not a UUID");
+                return result;
+            }
+
+            Object dbObject =
+                (ArtifactDatabase)globalContext.get(ARTIFACT_DATA_BASE_KEY);
+
+            if (!(dbObject instanceof ArtifactDatabase)) {
+                log.error("Cannot find artifact database");
+                return result;
+            }
+
+            ArtifactDatabase db = (ArtifactDatabase)dbObject;
+
+            Artifact artifact;
+
+            try {
+                artifact = db.getRawArtifact(artifactId);
+            }
+            catch (ArtifactDatabaseException adbe) {
+                log.warn("fetching artifact failed", adbe);
+                return result;
+            }
+
+            if (!(artifact instanceof FLYSArtifact)) {
+                log.warn("artifact is not a FLYS artifact.");
+                return result;
+            }
+
+            flysArtifact = (FLYSArtifact)artifact;
+        }
+        else {
+            flysArtifact = null;
+        }
+
+
+        Map<String, Object> data = splitParameters(
+            parameters, new HashMap<String, Object>());
+
+        String [] outs = outsString == null
+            ? new String [0]
+            : outsString.split("\\s*,\\s*");
+        
+        Recommendations rec = Recommendations.getInstance();
+        rec.recommend(
+            flysArtifact, userId, outs, data, result);
+
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/RiverService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,63 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.model.RiverFactory;
+
+
+/**
+ * This service provides information about the supported rivers by this
+ * application.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RiverService extends FLYSService {
+
+    /** The logger used in this service.*/
+    private static Logger logger = Logger.getLogger(RiverService.class);
+
+
+    protected Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        logger.debug("RiverService.process");
+
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        List<River> allRivers = RiverFactory.getRivers();
+
+        Element rivers = ec.create("rivers");
+
+        for (River river: allRivers) {
+            Element r = ec.create("river");
+            ec.addAttr(r, "name", river.getName(), true);
+
+            rivers.appendChild(r);
+        }
+
+        result.appendChild(rivers);
+
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/ThemeListingService.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,74 @@
+package de.intevation.flys.artifacts.services;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.themes.Theme;
+import de.intevation.flys.themes.ThemeGroup;
+
+import de.intevation.flys.themes.ThemeFactory;
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+/**
+ * This service provides a list of themes filtered by the theme name.
+ *
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class ThemeListingService extends FLYSService {
+
+    /** The logger used in this service.*/
+    private static Logger logger = Logger.getLogger(ThemeListingService.class);
+
+    private static final String XPATH_THEME_NAME = "/theme/@name";
+
+    protected Document doProcess(
+        Document      data,
+        GlobalContext context,
+        CallMeta      callMeta
+    ) {
+        logger.debug("ThemeListingService.process");
+        String name = XMLUtils.xpathString(data, XPATH_THEME_NAME, null);
+
+        if (name == null) {
+            logger.warn("No theme name provided.");
+        }
+        Document result = XMLUtils.newDocument();
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            result,
+            null,
+            null);
+
+        List<ThemeGroup> tgs =
+            ThemeFactory.getThemeGroups((FLYSContext) context);
+
+        Element te = ec.create("themes");
+
+        for (ThemeGroup tg: tgs) {
+            Element elem = ec.create("themegroup");
+            if (tg.getName().equals("virtual")) {
+                continue;
+            }
+            ec.addAttr(elem, "name", tg.getName());
+            Theme theme = tg.getThemeByName(name);
+            Document d = theme.toXML();
+            Node imported = result.importNode(d.getDocumentElement(), true);
+            elem.appendChild(imported);
+            te.appendChild(elem);
+        }
+
+        result.appendChild(te);
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/AnnotationRiverState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,63 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.AnnotationFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * The only state for an AnnotationArtifact (River is known).
+ */
+public class AnnotationRiverState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** Developer-centric description of facet. */
+    public static final String I18N_DESCRIPTION = "facet.longitudinal_section.annotations";
+
+    /** The logger that is used in this state. */
+    private static final Logger logger = Logger.getLogger(AnnotationRiverState.class);
+
+
+    /**
+     * Add an AnnotationFacet to list of Facets.
+     *
+     * @param artifact Ignored.
+     * @param hash Ignored.
+     * @param context Ignored.
+     * @param meta CallMeta to be used for internationalization.
+     * @param facets List to add AnnotationFacet to.
+     *
+     * @return null.
+     */
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        logger.debug("AnnotationRiverState.computeInit()");
+
+        AnnotationFacet facet = new AnnotationFacet(
+            0,
+            LONGITUDINAL_ANNOTATION,
+            Resources.getMsg(meta, I18N_DESCRIPTION, I18N_DESCRIPTION));
+        facets.add(facet);
+
+        return null;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/AreaCreationState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,86 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.model.AreaFacet;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.AreaArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+
+/** Trivial state to create areafacets, no caching. */
+public class AreaCreationState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(AreaCreationState.class);
+
+
+    /**
+     * From this state can only be continued trivially.
+     */
+    @Override
+    protected String getUIProvider() {
+        return "continue";
+    }
+
+
+    /** Just reproduce the Facet. */
+    protected Object compute(
+        FLYSArtifact  areaArtifact,
+        CallContext   cc,
+        String        hash,
+        List<Facet>   facets,
+        Object        old
+    ) {
+        logger.debug("AreaCreationState.compute");
+
+        if (facets != null) {
+            AreaArtifact aArt = (AreaArtifact) areaArtifact;
+
+            facets.add(new AreaFacet(0, aArt.getFacetName(), aArt.getAreaName()));
+        }
+
+        // TODO use compute to exploit caching strategies.
+
+        return null;
+    }
+
+
+    /**
+     */
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return compute((FLYSArtifact) artifact, context, hash, facets, old);
+    }
+
+
+    /**
+     *
+     */
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return compute((FLYSArtifact) artifact, context, hash, facets, old);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/CalculationSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,135 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class CalculationSelect extends DefaultState {
+
+    /** The logger that is used in this class. */
+    private static Logger logger = Logger.getLogger(CalculationSelect.class);
+
+
+    public static final String FIELD_MODE = "calculation_mode";
+
+    /** Constant value for the reference line calculation. */
+    public static final String CALCULATION_SURFACE_CURVE =
+        "calc.surface.curve";
+
+    /** Constant value for the differences calculation. */
+    public static final String CALCULATION_DURATION_CURVE =
+        "calc.duration.curve";
+
+    /** Constant value for the flood map calculation. */
+    public static final String CALCULATION_FLOOD_MAP =
+        "calc.flood.map";
+
+    /** Constant value for the profile calculation. */
+    public static final String CALCULATION_DISCHARGE_LONGITUDINAL_CURVE =
+        "calc.discharge.longitudinal.section";
+
+    /** Constant value for the state discharge curve calculation. */
+    public static final String CALCULATION_DISCHARGE_CURVE =
+        "calc.discharge.curve";
+
+    /** Constant value for the state w differences calculation. */
+    public static final String CALCULATION_W_DIFFERENCES =
+        "calc.w.differences";
+
+    /** Constant value for the state reference curve calculation. */
+    public static final String CALCULATION_REFERENCE_CURVE =
+        "calc.reference.curve";
+
+    /** Constant value for the historical discharge curve calculation. */
+    public static final String CALCULATION_HISTORICAL_DISCHARGE_CURVE =
+        "calc.historical.discharge.curve";
+
+    /** An array that holds all available calculation modes. */
+    public static final String[] CALCULATIONS = {
+        CALCULATION_SURFACE_CURVE,
+        CALCULATION_FLOOD_MAP,
+        CALCULATION_DISCHARGE_CURVE,
+        CALCULATION_HISTORICAL_DISCHARGE_CURVE,
+        CALCULATION_DURATION_CURVE,
+        CALCULATION_DISCHARGE_LONGITUDINAL_CURVE,
+        CALCULATION_W_DIFFERENCES,
+        CALCULATION_REFERENCE_CURVE };
+
+
+    /** Error message that is thrown if no mode has been chosen. */
+    public 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. */
+    public static  final String ERROR_INVALID_CALCULATION_MODE =
+        "error_feed_invalid_calculation_mode";
+
+
+    public CalculationSelect() {
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        CallMeta meta   = context.getMeta();
+        Element[] calcs = new Element[CALCULATIONS.length];
+
+        for (int i = 0; i < CALCULATIONS.length; ++i) {
+            String calc = CALCULATIONS[i];
+            calcs[i] = createItem(
+                cr, new String[] {
+                    Resources.getMsg(meta, calc, calc),
+                    calc
+                });
+        }
+
+        return calcs;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("CalculationSelect.validate");
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData data = getData(flys, FIELD_MODE);
+        String    calc = (data != null) ? (String) data.getValue() : null;
+
+        if (calc == null) {
+            throw new IllegalArgumentException(ERROR_NO_CALCULATION_MODE);
+        }
+
+        calc = calc.trim().toLowerCase();
+
+        for (String mode: CALCULATIONS) {
+            if (mode.equals(calc)) {
+                return true;
+            }
+        }
+
+        throw new IllegalArgumentException(ERROR_INVALID_CALCULATION_MODE);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/CalculationSelectMinfo.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,109 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class CalculationSelectMinfo extends DefaultState {
+
+    /** The logger that is used in this class. */
+    private static Logger logger = Logger.getLogger(CalculationSelectMinfo.class);
+
+
+    public static final String FIELD_MODE = "calculation_mode";
+
+    public static final String CALC_BED_MIDDLE    = "calc.bed.middle";
+    public static final String CALC_BED_DIFF      = "calc.bed.diff";
+    public static final String CALC_BED_QUALITY   = "calc.bed.quality";
+    public static final String CALC_SEDIMENT_LOAD = "calc.sediment.load";
+    public static final String CALC_FLOW_VELOCITY = "calc.flow.velocity";
+    public static final String CALC_SQ_RELATION   = "calc.sq.relation";
+
+    /** An array that holds all available calculation modes. */
+    public static final String[] CALCULATIONS = {
+        CALC_BED_MIDDLE,
+        CALC_BED_DIFF,
+        CALC_BED_QUALITY,
+        CALC_SEDIMENT_LOAD,
+        CALC_FLOW_VELOCITY,
+        CALC_SQ_RELATION
+    };
+
+
+    /** Error message that is thrown if no mode has been chosen. */
+    public 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. */
+    public static  final String ERROR_INVALID_CALCULATION_MODE =
+        "error_feed_invalid_calculation_mode";
+
+
+    public CalculationSelectMinfo() {
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        CallMeta meta   = context.getMeta();
+        Element[] calcs = new Element[CALCULATIONS.length];
+
+        for (int i = 0; i < CALCULATIONS.length; ++i) {
+            String calc = CALCULATIONS[i];
+            calcs[i] = createItem(
+                cr, new String[] {
+                    Resources.getMsg(meta, calc, calc),
+                    calc
+                });
+        }
+
+        return calcs;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("CalculationSelect.validate");
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData data = getData(flys, FIELD_MODE);
+        String    calc = (data != null) ? (String) data.getValue() : null;
+
+        if (calc == null) {
+            throw new IllegalArgumentException(ERROR_NO_CALCULATION_MODE);
+        }
+
+        calc = calc.trim().toLowerCase();
+
+        for (String mode: CALCULATIONS) {
+            if (mode.equals(calc)) {
+                return true;
+            }
+        }
+
+        throw new IllegalArgumentException(ERROR_INVALID_CALCULATION_MODE);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ComputationRangeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,169 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputationRangeState
+extends    RangeState
+implements FacetTypes
+{
+    private static Logger logger =
+        Logger.getLogger(ComputationRangeState.class);
+
+    /** The name of the 'from' field. */
+    public static final String FROM = "ld_from";
+
+    /** The name of the 'to' field. */
+    public static final String TO = "ld_to";
+
+    /** The name of the 'step' field. */
+    public static final String STEP = "ld_step";
+
+    /** The default step width. */
+    public static final int DEFAULT_STEP = 100;
+
+
+    public ComputationRangeState() {
+    }
+
+
+    @Override
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select", null, null);
+
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        Element choices = ProtocolUtils.createArtNode(
+            cr, "choices", null, null);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            data.getName(),
+            data.getName()));
+
+        select.appendChild(label);
+
+        return select;
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        double[] minmax = getMinMax(artifact);
+
+        double minVal = Double.MIN_VALUE;
+        double maxVal = Double.MAX_VALUE;
+
+        if (minmax != null) {
+            minVal = minmax[0];
+            maxVal = minmax[1];
+        }
+        else {
+            logger.warn("Could not read min/max distance values!");
+        }
+
+        if (name.equals("ld_from")) {
+            Element min = createItem(
+                cr,
+                new String[] {"min", new Double(minVal).toString()});
+
+            return new Element[] { min };
+        }
+        else if (name.equals("ld_to")) {
+            Element max = createItem(
+                cr,
+                new String[] {"max", new Double(maxVal).toString()});
+
+            return new Element[] { max };
+        }
+        else {
+            Element step = createItem(
+                cr,
+                new String[] {"step", String.valueOf(getDefaultStep())});
+            return new Element[] { step };
+        }
+
+    }
+
+
+    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;
+    }
+
+
+    @Override
+    protected double[] getMinMax(Artifact artifact) {
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+        return FLYSUtils.getRiverMinMax(flysArtifact);
+    }
+
+
+    protected double getDefaultStep() {
+        return DEFAULT_STEP;
+    }
+
+
+    @Override
+    protected String getLowerField() {
+        return FROM;
+    }
+
+
+    @Override
+    protected String getUpperField() {
+        return TO;
+    }
+
+
+    @Override
+    protected String getStepField() {
+        return STEP;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ComputedDischargeCurveState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,99 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.ChartArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WaterlevelFacet;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.EmptyFacet;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+/**
+ * The final state that will be reached after the discharge curve calculation
+ * mode has been chosen.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state.*/
+    private static Logger logger =
+        Logger.getLogger(ComputedDischargeCurveState.class);
+
+    public ComputedDischargeCurveState() {
+    }
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        logger.debug("ComputedDischargeCurveState.computeAdvance");
+        if(artifact instanceof WINFOArtifact) {
+            WINFOArtifact winfo = (WINFOArtifact)artifact;
+
+            CalculationResult res = old instanceof CalculationResult
+                ? (CalculationResult)old
+                : winfo.getComputedDischargeCurveData();
+
+            WQKms [] wqkms = (WQKms [])res.getData();
+
+            if (facets != null && wqkms.length > 0) {
+                for (int i = 0; i < wqkms.length; ++i) {
+
+                    Object[] args = new Object[] {
+                        FLYSUtils.getRiver(winfo).getName(),
+                        wqkms[i].getName()
+                    };
+
+                    String name = Resources.getMsg(
+                        context.getMeta(),
+                        "chart.computed.discharge.curve.curve.label",
+                        "",
+                        args);
+
+                    facets.add(new WaterlevelFacet(i, COMPUTED_DISCHARGE_Q, name));
+                    facets.add(new WaterlevelFacet(i, AT, "AT data"));
+                }
+
+                facets.add(new DataFacet(CSV, "CSV data"));
+                facets.add(new DataFacet(PDF, "PDF data"));
+
+                if (res.getReport().hasProblems()) {
+                    facets.add(new ReportFacet());
+                }
+            }
+
+            return res;
+        }
+        else if(artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return null;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DGMSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,140 @@
+package de.intevation.flys.artifacts.states;
+
+import java.io.File;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.model.DGM;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DGMSelect extends DefaultState {
+
+    private static final Logger logger = Logger.getLogger(DGMSelect.class);
+
+    public static final String ERR_EMPTY         = "error_no_dgm_selected";
+    public static final String ERR_INVALID_DGM   = "error_invalid_dgm_selected";
+    public static final String ERR_BAD_DGM_RANGE = "error_bad_dgm_range";
+    public static final String ERR_BAD_DGM_RIVER = "error_bad_dgm_river";
+
+
+    @Override
+    protected String getUIProvider() {
+        return "dgm_datacage_panel";
+    }
+
+
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        Element dataElement = creator.create("data");
+        creator.addAttr(dataElement, "name", name, true);
+        creator.addAttr(dataElement, "type", type, true);
+
+        Element itemElement = creator.create("item");
+        creator.addAttr(itemElement, "value", value, true);
+
+        creator.addAttr(itemElement, "label", getLabel(cc, value), true);
+        dataElement.appendChild(itemElement);
+
+        return dataElement;
+    }
+
+
+    public static String getLabel(CallContext cc, String value) {
+        logger.debug("Create label for value: " + value);
+
+        try {
+            DGM dgm = DGM.getDGM(Integer.parseInt(value));
+
+            File file = new File(dgm.getPath());
+            return file.getName();
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Cannot parse int value: '" + value + "'");
+        }
+
+        return "";
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        DGM dgm = getDGM(flys);
+
+        if (dgm == null) {
+            throw new IllegalArgumentException(ERR_INVALID_DGM);
+        }
+
+        double l = dgm.getLower().doubleValue();
+        double u = dgm.getUpper().doubleValue();
+
+        double[] range = FLYSUtils.getKmFromTo(flys);
+
+        if (range[0] < l || range[0] > u || range[1] < l || range[1] > u) {
+            throw new IllegalArgumentException(ERR_BAD_DGM_RANGE);
+        }
+
+        River selectedRiver = FLYSUtils.getRiver(flys);
+        River dgmRiver      = dgm.getRiver();
+
+        if (selectedRiver != dgmRiver) {
+            throw new IllegalArgumentException(ERR_BAD_DGM_RIVER);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Returns the DGM specified in the parameters of <i>flys</i>.
+     *
+     * @param flys The FLYSArtifact that knows the ID of a DGM.
+     *
+     * @throws IllegalArgumentException If the FLYSArtifact doesn't know the ID
+     * of a DGM.
+     *
+     * @return the DGM specified by FLYSArtifact's parameters.
+     */
+    public static DGM getDGM(FLYSArtifact flys)
+    throws IllegalArgumentException
+    {
+        try {
+            Integer dgmId = flys.getDataAsInteger("dgm");
+            if (dgmId == null) {
+                throw new IllegalArgumentException(ERR_EMPTY);
+            }
+
+            logger.debug("Found selected dgm: '" + dgmId + "'");
+
+            return DGM.getDGM(dgmId);
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException(ERR_INVALID_DGM);
+        }
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DefaultState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,456 @@
+package de.intevation.flys.artifacts.states;
+
+import java.text.NumberFormat;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifactdatabase.state.AbstractState;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class DefaultState extends AbstractState {
+
+    /** The logger that is used in this class. */
+    private static Logger logger = Logger.getLogger(DefaultState.class);
+
+
+    /** Determines, if the DESCRIBE document should contain default values or
+     * not. */
+    public static final boolean USE_DEFAULTS =
+        Boolean.getBoolean("flys.use.default.values");
+
+    /** The three possible compute types. */
+    public static enum ComputeType {
+        FEED, ADVANCE, INIT
+    }
+
+
+    protected StateData getData(FLYSArtifact artifact,  String name) {
+        return artifact.getData(name);
+    }
+
+
+    /**
+     * Append to a node and return xml description relevant for gui.
+     */
+    public Element describeStatic(
+        Artifact    artifact,
+        Document    document,
+        Node        root,
+        CallContext context,
+        String      uuid)
+    {
+        ElementCreator creator = new ElementCreator(
+            document,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        CallMeta meta = context.getMeta();
+
+        String helpText = Resources.getMsg(meta, getHelpText(), getHelpText());
+
+        String label = Resources.getMsg(meta, getID(), getID());
+        Element ui   = ProtocolUtils.createArtNode(
+            creator, "state",
+            new String[] { "name", "uiprovider", "label", "helpText"},
+            new String[] { getID(), getUIProvider(), label, helpText });
+
+        Map<String, StateData> theData = getData();
+        if (theData == null) {
+            return ui;
+        }
+
+        Iterator<String> iter = theData.keySet().iterator();
+        FLYSArtifact     flys = (FLYSArtifact) artifact;
+
+        while (iter.hasNext()) {
+            String name = iter.next();
+            appendStaticData(flys, context, creator, ui, name);
+        }
+
+        return ui;
+    }
+
+
+    protected void appendStaticData(
+        FLYSArtifact   flys,
+        CallContext    context,
+        ElementCreator cr,
+        Element        ui,
+        String         name
+    ) {
+        StateData data  = getData(flys, name);
+        String    value = (data != null) ? (String) data.getValue() : null;
+
+        if (value == null) {
+            return;
+        }
+
+        String type = data.getType();
+
+        logger.debug(
+            "Append element " + type + "'" +
+            name + "' (" + value + ")");
+
+        Element e = createStaticData(flys, cr, context, name, value, type);
+
+        ui.appendChild(e);
+
+    }
+
+
+    /**
+     * Creates a <i>data</i> element used in the static part of the DESCRIBE
+     * document.
+     *
+     * @param creator The ElementCreator that is used to build new Elements.
+     * @param meta The CallMeta object used for i18n.
+     * @param name The name of the data item.
+     * @param value The value as string.
+     *
+     * @return an Element.
+     */
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        Element dataElement = creator.create("data");
+        creator.addAttr(dataElement, "name", name, true);
+        creator.addAttr(dataElement, "type", type, true);
+
+        Element itemElement = creator.create("item");
+        creator.addAttr(itemElement, "value", value, true);
+
+        creator.addAttr(
+            itemElement,
+            "label",
+            getLabelFor(cc, name, value, type),
+            true);
+
+        dataElement.appendChild(itemElement);
+
+        return dataElement;
+    }
+
+
+    /**
+     * @param cc
+     * @param name
+     * @param value
+     * @param type
+     *
+     * @return
+     */
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        CallMeta meta = cc.getMeta();
+
+        try {
+            // XXX A better way to format the output would be to use the
+            // 'type' value of the data objects.
+            double doubleVal = Double.valueOf(value);
+            Locale         l = Resources.getLocale(meta);
+            NumberFormat  nf = NumberFormat.getInstance(l);
+
+            return nf.format(doubleVal);
+        }
+        catch (NumberFormatException nfe) {
+            return Resources.getMsg(meta, value, value);
+        }
+    }
+
+
+    public Element describe(
+        Artifact    artifact,
+        Document    document,
+        Node        root,
+        CallContext context,
+        String      uuid)
+    {
+        ElementCreator creator = new ElementCreator(
+            document,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        String helpText = Resources.getMsg(
+            context.getMeta(), getHelpText(), getHelpText());
+
+        Element ui        = null;
+        String uiprovider = getUIProvider();
+        if (uiprovider != null) {
+            ui = ProtocolUtils.createArtNode(
+                creator, "dynamic",
+                new String[] { "uiprovider", "helpText" },
+                new String[] { uiprovider, helpText });
+        }
+        else {
+            ui = ProtocolUtils.createArtNode(
+                creator, "dynamic",
+                new String[] { "helpText" },
+                new String[] { helpText });
+        }
+
+        Map<String, StateData> theData = getData();
+        if (theData == null) {
+            return ui;
+        }
+
+        Iterator<String> iter = theData.keySet().iterator();
+        FLYSArtifact     flys = (FLYSArtifact) artifact;
+
+        while (iter.hasNext()) {
+            String    name = iter.next();
+            StateData data = getData(flys, name);
+
+            data = data != null ? data : getData(name);
+
+            Element select = createData(creator, artifact, data, context);
+
+            if (USE_DEFAULTS) {
+                String defValue = (String) data.getValue();
+                String defDesc  = null;
+
+                if (defValue != null && defValue.length() > 0) {
+                    defDesc = Resources.getMsg(
+                        context.getMeta(),
+                        defValue,
+                        defValue);
+                }
+
+                if (defValue != null && defDesc != null) {
+                    creator.addAttr(select, "defaultValue", defValue, true);
+                    creator.addAttr(select, "defaultLabel", defDesc, true);
+                }
+            }
+
+            appendItems(artifact, creator, name, context, select);
+            ui.appendChild(select);
+        }
+
+        return ui;
+    }
+
+
+    /**
+     * @param artifact
+     * @param creator
+     * @param name
+     * @param context
+     * @param select
+     */
+    protected void appendItems(
+        Artifact       artifact,
+        ElementCreator creator,
+        String         name,
+        CallContext    context,
+        Element        select
+    ) {
+        Element choices = ProtocolUtils.createArtNode(
+            creator, "choices", null, null);
+
+        select.appendChild(choices);
+
+        Element[] items = createItems(creator, artifact, name, context);
+        if (items != null) {
+            for (Element item: items) {
+                choices.appendChild(item);
+            }
+        }
+    }
+
+
+    /**
+     * This method creates the root node that contains the list of selectable
+     * items.
+     *
+     * @param cr The ElementCreator.
+     * @param name The name of the amount of data.
+     *
+     * @return the root node of the item list.
+     */
+    protected Element createData(
+        ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select", null, null);
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        select.appendChild(label);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            getID(),
+            getID()));
+
+        return select;
+    }
+
+
+    /**
+     * This method creates a list of items. These items represent the amount of
+     * input data that is possible for this state.
+     *
+     * @param cr The ElementCreator.
+     * @param name The name of the amount of data.
+     *
+     * @return a list of items.
+     */
+    protected Element[] createItems(
+        ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context
+    ) {
+        return null;
+    }
+
+
+    /**
+     * This method is used to create an <i>item</i> Element that contains two
+     * further elements <i>label</i> and <i>value</i>. The label and value
+     * elements both have text nodes.
+     *
+     * @param cr The ElementCreator used to build new Elements.
+     * @param obj This implementation awaits a String array with [0] = label and
+     * [1] = value.
+     *
+     * @return an Element.
+     */
+    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;
+    }
+
+
+    /**
+     * This method transform a given value into a StateData object.
+     *
+     * @param flys The FLYSArtifact.
+     * @param name The name of the data object.
+     * @param val The value of the data object.
+     *
+     * @return a StateData object with <i>name</i> and <i>val</i>ue.
+     */
+    public StateData transform(
+        FLYSArtifact flys,
+        CallContext  cc,
+        StateData    stateData,
+        String       name,
+        String       val
+    ) {
+        logger.debug("Transform data ('" + name + "','" + val + "')");
+
+        stateData.setValue(val);
+
+        return stateData;
+    }
+
+
+    /**
+     * This method validates the inserted data and returns true, if everything
+     * was correct, otherwise an exception is thrown.
+     *
+     * @param artifact A reference to the owner artifact.
+     *
+     * @return true, if everything was fine.
+     */
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        return true;
+    }
+
+
+    /**
+     * Returns which UIProvider shall be used to aid user input.
+     */
+    protected String getUIProvider() {
+        return null;
+    }
+
+
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return null;
+    }
+
+
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return null;
+    }
+
+
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets)
+    {
+        return null;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DischargeLongitudinalSection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,115 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.ChartArtifact;
+
+import de.intevation.flys.artifacts.model.CrossSectionWaterLineFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.WaterlevelFacet;
+import de.intevation.flys.artifacts.model.EmptyFacet;
+
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+import de.intevation.flys.artifacts.model.DataFacet;
+
+public class DischargeLongitudinalSection
+extends      DefaultState
+implements   FacetTypes
+{
+    private static Logger logger =
+        Logger.getLogger(DischargeLongitudinalSection.class);
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        if (artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+
+        WINFOArtifact winfo = (WINFOArtifact)artifact;
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult)old
+            : winfo.getDischargeLongitudinalSectionData();
+
+        if (facets == null) {
+            return res;
+        }
+
+        WQKms [] wqkms = (WQKms [])res.getData();
+
+        for (int i = 0; i < wqkms.length; i++) {
+            String nameW = null;
+            String nameQ = null;
+
+            if (winfo.isQ()) {
+                nameQ = wqkms[i].getName();
+                nameW = "W(" + nameQ + ")";
+            }
+            else {
+                nameW = wqkms[i].getName();
+                nameQ = "Q(" + nameW + ")";
+            }
+
+            Facet w = new WaterlevelFacet(
+                i, DISCHARGE_LONGITUDINAL_W, nameW);
+
+            Facet q = new WaterlevelFacet(
+                i, DISCHARGE_LONGITUDINAL_Q, nameQ);
+
+            Facet s = new CrossSectionWaterLineFacet(i, nameW);
+
+            facets.add(s);
+            facets.add(w);
+            facets.add(q);
+
+            if (wqkms[i] instanceof WQCKms) {
+                // TODO DO i18n
+
+                String nameC = nameW.replace(
+                    "benutzerdefiniert",
+                    "benutzerdefiniert [korrigiert]");
+
+                Facet c = new WaterlevelFacet(
+                    i, DISCHARGE_LONGITUDINAL_C, nameC);
+
+                Facet r = new CrossSectionWaterLineFacet(i, nameC);
+
+                facets.add(c);
+                facets.add(r);
+            }
+        }
+
+        if (wqkms.length > 0) {
+            facets.add(new DataFacet(CSV, "CSV data"));
+            facets.add(new DataFacet(WST, "WST data"));
+        }
+
+        if (res.getReport().hasProblems()) {
+            facets.add(new ReportFacet());
+        }
+
+        return res;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DischargeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,160 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.model.KVP;
+
+import de.intevation.flys.model.DischargeZone;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class DischargeState extends MultiIntArrayState {
+
+    public static final String MAIN_CHANNEL  = "main.channel";
+    public static final String TOTAL_CHANNEL = "total.channel";
+
+
+    private static final Logger logger = Logger.getLogger(DischargeState.class);
+
+
+    @Override
+    public String getUIProvider() {
+        return "parameter-matrix";
+    }
+
+
+    /**
+     * This method fetches all DischargeZones for a given river (extracted from
+     * <i>artifact</i>) and returns a KVP[] where the key is the ID of the
+     * DischargeZone and the value is a string that consists of lower discharge
+     * and upper discharge.
+     *
+     * @param artifact Needs to be a FLYSArtifact that provides river
+     * information.
+     * @param parameterName The name of a parameter.
+     *
+     * @return a KVP[].
+     */
+    @Override
+    protected KVP<Integer, String>[] getOptions(
+        Artifact artifact,
+        String   parameterName
+    )
+    throws IllegalArgumentException
+    {
+        if (!testParameterName(parameterName)) {
+            throw new IllegalArgumentException(
+                "Invalid parameter for state: '" + parameterName + "'");
+        }
+
+        List<DischargeZone> zones = getDischargeZones(artifact);
+
+        KVP[] kvp = new KVP[zones.size()];
+
+        for (int i = 0; i < zones.size(); i++) {
+            DischargeZone zone = zones.get(i);
+
+            String lower = zone.getLowerDischarge();
+            String upper = zone.getUpperDischarge();
+
+            if (lower.equals(upper)) {
+                kvp[i] = new KVP(zone.getId(), lower);
+            }
+            else {
+                kvp[i] = new KVP(zone.getId(), lower + " - " + upper);
+            }
+        }
+
+        return kvp;
+    }
+
+
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      parameterName,
+        int         value
+    ) throws IllegalArgumentException
+    {
+        if (!testParameterName(parameterName)) {
+            throw new IllegalArgumentException(
+                "Invalid parameter for state: '" + parameterName + "'");
+        }
+
+        DischargeZone zone = DischargeZone.getDischargeZoneById(value);
+
+        if (zone == null) {
+            throw new IllegalArgumentException(
+                "Invalid id for DischargeZone: '" + value + "'");
+        }
+
+        String lo = zone.getLowerDischarge();
+        String hi = zone.getUpperDischarge();
+
+        return hi != null && lo.equals(hi)
+            ? lo + " - " + hi
+            : lo;
+    }
+
+
+    /**
+     * This method might be used to test, if a parameter name is handled by this
+     * state.
+     *
+     * @param parameterName The name of a parameter.
+     *
+     * @return true, if parameterName is one of <i>MAIN_CHANNEL</i> or
+     * <i>TOTAL_CHANNEL</i>. Otherwise false.
+     */
+    protected boolean testParameterName(String parameterName) {
+        if (parameterName == null || parameterName.length() == 0) {
+            return false;
+        }
+        else if (parameterName.equals(MAIN_CHANNEL)) {
+            return true;
+        }
+        else if (parameterName.equals(TOTAL_CHANNEL)) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+
+    /**
+     * Returns all discharge zones for a given river. The river information is
+     * extracted from <i>artifact</i> using FLYSUtils.getRiver().
+     *
+     * @param artifact Needs to be a FLYSArtifact that stores a rivername.
+     *
+     * @return a list of DischargeZones.
+     *
+     * @throws IllegalArgumentException if no river information is provided by
+     * <i>artifact</i>.
+     */
+    protected List<DischargeZone> getDischargeZones(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        River river = FLYSUtils.getRiver((FLYSArtifact) artifact);
+
+        if (river == null) {
+            throw new IllegalArgumentException("No river found");
+        }
+
+        List<DischargeZone> zones = DischargeZone.getDischargeZones(river);
+
+        logger.debug("Found " + zones.size() + " DischargeZones.");
+
+        return zones;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DistanceOnlySelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,51 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+public class DistanceOnlySelect extends DistanceSelect {
+
+    private static Logger logger = Logger.getLogger(DistanceOnlySelect.class);
+
+    @Override
+    protected String getUIProvider() {
+        return "distance_only_panel";
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData dFrom = getData(flys, getLowerField());
+        StateData dTo   = getData(flys, getUpperField());
+
+        String fromStr = dFrom != null ? (String) dFrom.getValue() : null;
+        String toStr   = dTo   != null ? (String) dTo.getValue()   : null;
+
+        if (fromStr == null || toStr == null) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        try {
+            double from = Double.parseDouble(fromStr);
+            double to   = Double.parseDouble(toStr);
+
+            double[] minmax = getMinMax(flys);
+
+            return validateBounds(minmax[0], minmax[1], from, to);
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException("error_invalid_double_value");
+        }
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DistanceSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,24 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DistanceSelect extends ComputationRangeState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(DistanceSelect.class);
+
+
+    public DistanceSelect() {
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "distance_panel";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DurationCurveState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,111 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.ChartArtifact;
+
+import de.intevation.flys.artifacts.model.DurationCurveFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQDay;
+
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.EmptyFacet;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * The final state that will be reached after the duration curve calculation
+ * mode has been chosen.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(DurationCurveState.class);
+
+    public DurationCurveState() {
+    }
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        if (artifact instanceof WINFOArtifact) {
+            WINFOArtifact winfo = (WINFOArtifact)artifact;
+
+            CalculationResult res;
+
+            if (old instanceof CalculationResult) {
+                res = (CalculationResult)old;
+            }
+            else {
+                res = winfo.getDurationCurveData();
+            }
+
+            WQDay wqday = (WQDay)res.getData();
+
+            if (wqday != null && facets != null) {
+                // Create an i18ed name for a (w or q) duration curve facet.
+                Object[] args = new Object[] {
+                    FLYSUtils.getRiver(winfo).getName(),
+                    FLYSUtils.getLocations(winfo)[0]
+                };
+
+                String nameW = Resources.getMsg(
+                    context.getMeta(),
+                    "chart.duration.curve.curve.w",
+                    "",
+                    args);
+
+                String nameQ = Resources.getMsg(
+                    context.getMeta(),
+                    "chart.duration.curve.curve.q",
+                    "",
+                    args);
+
+                Facet w = new DurationCurveFacet(DURATION_W, nameW);
+                Facet q = new DurationCurveFacet(DURATION_Q, nameQ);
+
+                facets.add(w);
+                facets.add(q);
+
+                facets.add(new DataFacet(CSV, "CSV data"));
+                facets.add(new DataFacet(PDF, "PDF data"));
+
+                if (res.getReport().hasProblems()) {
+                    facets.add(new ReportFacet());
+                }
+            }
+
+            return res;
+        }
+        else if (artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return null;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/EnterLocationState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,41 @@
+package de.intevation.flys.artifacts.states;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * Get me a double (km).
+ */
+public class EnterLocationState extends InputDoubleState {
+
+    /** Provoke this kind of provider in the UI. */
+    @Override
+    protected String getUIProvider() {
+        return "location_panel";
+    }
+
+
+    /** Allow from min km of river. */
+    @Override
+    protected Object getLower(FLYSArtifact flys) {
+        double[] lowerUpper = FLYSUtils.getRiverMinMax(flys);
+
+        return lowerUpper != null
+            ? lowerUpper[0]
+            : 0;
+    }
+
+
+    /** Allow to max km of river. */
+    @Override
+    protected Object getUpper(FLYSArtifact flys) {
+        double[] lowerUpper = FLYSUtils.getRiverMinMax(flys);
+
+        return lowerUpper != null
+            ? lowerUpper[1]
+            : 0;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/EnterMultipleLocationsState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,35 @@
+package de.intevation.flys.artifacts.states;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.common.utils.StringUtils;
+
+
+/**
+ * Get me doubles (km).
+ */
+public class EnterMultipleLocationsState extends EnterLocationState {
+
+    @Override
+    protected String getUIProvider() {
+        return "multi_location_panel";
+    }
+
+    
+    /** Deal with multiple double values. */
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        String label = "";
+        String[] vals = value.split(" ");
+        for (int i = 0; i < vals.length; i++) {
+            vals[i] = super.getLabelFor(cc, name, vals[i], type);
+        }
+
+        return StringUtils.join(" ", vals);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/FloodMapState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,877 @@
+package de.intevation.flys.artifacts.states;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.Polygon;
+
+import org.apache.log4j.Logger;
+
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureCollections;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.FileTools;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.model.CrossSectionTrack;
+import de.intevation.flys.model.DGM;
+import de.intevation.flys.model.Floodplain;
+import de.intevation.flys.model.RiverAxis;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.model.CalculationMessage;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WMSLayerFacet;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WSPLGENCalculation;
+import de.intevation.flys.artifacts.model.WSPLGENJob;
+import de.intevation.flys.artifacts.model.WSPLGENReportFacet;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+import de.intevation.flys.exports.WstWriter;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.MapfileGenerator;
+import de.intevation.flys.utils.GeometryUtils;
+import de.intevation.flys.wsplgen.FacetCreator;
+import de.intevation.flys.wsplgen.JobObserver;
+import de.intevation.flys.wsplgen.Scheduler;
+
+
+public class FloodMapState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(FloodMapState.class);
+
+
+    public static final String KEEP_ARTIFACT_DIR =
+        System.getProperty("flys.uesk.keep.artifactsdir", "false");
+
+
+    public static final String OUTPUT_NAME = "floodmap";
+
+    public static final String WSP_ARTIFACT = "wsp";
+
+    public static final String WINFO_WSP_STATE_ID = "state.winfo.waterlevel";
+
+    public static final String WSPLGEN_PARAMETER_FILE = "wsplgen.par";
+    public static final String WSPLGEN_BARRIERS_LINES = "barrier_lines.shp";
+    public static final String WSPLGEN_BARRIERS_POLY  = "barrier_polygons.shp";
+    public static final String WSPLGEN_AXIS           = "axis.shp";
+    public static final String WSPLGEN_QPS            = "qps.shp";
+    public static final String WSPLGEN_FLOODPLAIN     = "talaue.shp";
+    public static final String WSPLGEN_WSP_FILE       = "waterlevel.wst";
+    public static final String WSPLGEN_OUTPUT_FILE    = "wsplgen.shp";
+    public static final String WSPLGEN_USER_SHAPE     = "user-rgd.shp";
+    public static final String WSPLGEN_USER_ZIP       = "user-rgd.zip";
+    public static final String WSPLGEN_USER_FILENAME  = "user-rgd";
+
+    public static final int WSPLGEN_DEFAULT_OUTPUT = 0;
+
+
+    /**
+     * @param orig
+     * @param owner
+     * @param context
+     * @param callMeta
+     */
+    @Override
+    public void initialize(
+        Artifact orig,
+        Artifact owner,
+        Object   context,
+        CallMeta callMeta
+    ) {
+        logger.info("Initialize State with Artifact: " + orig.identifier());
+
+        copyShapeDir(orig, owner);
+        modifyFacets(orig, owner, context, callMeta);
+
+        MapfileGenerator.getInstance().update();
+    }
+
+
+    protected void copyShapeDir(Artifact orig, Artifact owner) {
+        File origDir = getDirectory((FLYSArtifact) orig);
+        File thisDir = getDirectory((FLYSArtifact) owner);
+
+        FileTools.copyDirectory(origDir, thisDir);
+    }
+
+
+    protected void modifyFacets(
+        Artifact orig,
+        Artifact owner,
+        Object   context,
+        CallMeta callMeta
+    ) {
+        FLYSArtifact flys  = (FLYSArtifact) owner;
+        List<Facet> facets = flys.getFacets();
+        if (facets == null || facets.size() == 0) {
+            logger.warn("No facets for '" + OUTPUT_NAME + "' given!");
+            return;
+        }
+
+        for (Facet facet: facets) {
+            if (facet instanceof WMSLayerFacet) {
+                WMSLayerFacet wms = (WMSLayerFacet) facet;
+
+                List<String> layers = wms.getLayers();
+
+                for (String layer: layers) {
+                    if (layer.startsWith(MapfileGenerator.MS_WSPLGEN_PREFIX)) {
+                        wms.removeLayer(layer);
+
+                        String newLayer = MapfileGenerator.MS_WSPLGEN_PREFIX +
+                            owner.identifier();
+
+                        wms.addLayer(newLayer);
+
+                        logger.debug(
+                            "Replaced layer: " + layer + " with " + newLayer);
+                    }
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        logger.debug("FloodMapState.computeAdvance");
+
+        File artifactDir = getDirectory(artifact);
+
+        if (artifactDir == null) {
+            logger.error("Could not create directory for WSPLGEN results!");
+            return null;
+        }
+
+        WSPLGENCalculation calculation = new WSPLGENCalculation();
+
+        FacetCreator facetCreator = new FacetCreator(
+            artifact, context, hash, getID(), facets);
+
+        WSPLGENJob job = prepareWSPLGENJob(
+            artifact,
+            facetCreator,
+            artifactDir,
+            context,
+            calculation);
+
+        CalculationResult  res   = new CalculationResult(null, calculation);
+        WSPLGENReportFacet report= new WSPLGENReportFacet(
+            ComputeType.ADVANCE, hash, getID(), res);
+
+        facets.add(report);
+
+        if (job == null) {
+            if (KEEP_ARTIFACT_DIR.equals("false")) {
+                removeDirectory(artifact);
+            }
+
+            calculation.addError(-1, Resources.getMsg(
+                context.getMeta(),
+                "wsplgen.job.error",
+                "wsplgen.job.error"));
+
+            logger.error("No WSPLGEN processing has been started!");
+
+            return null;
+        }
+
+        context.afterCall(CallContext.BACKGROUND);
+        context.addBackgroundMessage(new CalculationMessage(
+            JobObserver.STEPS.length,
+            0,
+            Resources.getMsg(
+                context.getMeta(),
+                "wsplgen.job.queued",
+                "wsplgen.job.queued")
+        ));
+
+        GlobalContext gc    = (GlobalContext) context.globalContext();
+        Scheduler scheduler = (Scheduler) gc.get(FLYSContext.SCHEDULER);
+        scheduler.addJob(job);
+
+        return null;
+    }
+
+
+    /**
+     * Returns (and creates if not existing) the directory for storing WSPLEN
+     * data for the owner artifact.
+     *
+     * @param artifact The owner Artifact.
+     *
+     * @return the directory for WSPLEN data.
+     */
+    protected File getDirectory(FLYSArtifact artifact) {
+        String shapePath = FLYSUtils.getXPathString(
+            FLYSUtils.XPATH_SHAPEFILE_DIR);
+
+        File artifactDir = FileTools.getDirectory(
+            shapePath, artifact.identifier());
+
+        return artifactDir;
+    }
+
+
+    /**
+     * Removes the directory and all its content where the required data and the
+     * results of WSPLGEN are stored. Should be called in endOfLife().
+     */
+    protected void removeDirectory(FLYSArtifact artifact) {
+        String shapePath = FLYSUtils.getXPathString(
+            FLYSUtils.XPATH_SHAPEFILE_DIR);
+
+        File artifactDir = new File(shapePath, artifact.identifier());
+
+        if (artifactDir.exists()) {
+            logger.info("Delete directory: " + artifactDir.getAbsolutePath());
+            boolean success = FileTools.deleteRecursive(artifactDir);
+        }
+        else {
+            logger.debug("There is no directory to remove.");
+        }
+    }
+
+
+    @Override
+    public void endOfLife(Artifact artifact, Object callContext) {
+        logger.info("FloodMapState.endOfLife: " + artifact.identifier());
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        Scheduler scheduler = Scheduler.getInstance();
+        scheduler.cancelJob(flys.identifier());
+
+        MapfileGenerator.getInstance().update();
+    }
+
+
+    protected WSPLGENJob prepareWSPLGENJob(
+        FLYSArtifact       artifact,
+        FacetCreator       facetCreator,
+        File               artifactDir,
+        CallContext        context,
+        WSPLGENCalculation calculation
+    ) {
+        logger.debug("FloodMapState.prepareWSPLGENJob");
+
+        WSPLGENJob job = new WSPLGENJob(
+            artifact,
+            artifactDir,
+            facetCreator,
+            context,
+            calculation);
+
+        File paraFile = new File(artifactDir, WSPLGEN_PARAMETER_FILE);
+
+        setOut(artifact, job);
+        setRange(artifact, job);
+        setDelta(artifact, job);
+        setGel(artifact, job);
+        setDist(artifact, job);
+        setLine(artifact, facetCreator, artifactDir, job);
+        setUserShape(artifact, facetCreator, artifactDir, job);
+        setAxis(artifact, artifactDir, job);
+        setPro(artifact, artifactDir, job);
+        setDgm(artifact, job);
+        setArea(artifact, artifactDir, job);
+        setOutFile(artifact, job);
+        setWsp(artifact, context, artifactDir, job);    // WSP
+
+        // TODO
+        // setWspTag(artifact, job);
+
+        try {
+            job.toFile(paraFile);
+
+            return job;
+        }
+        catch (IOException ioe) {
+            logger.warn("Cannot write PAR file: " + ioe.getMessage());
+        }
+        catch (IllegalArgumentException iae) {
+            logger.warn("Cannot write PAR file: " + iae.getMessage());
+        }
+
+        return null;
+    }
+
+
+    protected void setOut(FLYSArtifact artifact, WSPLGENJob job) {
+        job.setOut(WSPLGEN_DEFAULT_OUTPUT);
+    }
+
+
+    protected void setRange(FLYSArtifact artifact, WSPLGENJob job) {
+        double[] range = FLYSUtils.getKmRange(artifact);
+
+        job.setStart(range[0]);
+        job.setEnd(range[1]);
+    }
+
+
+    protected void setDelta(FLYSArtifact artifact, WSPLGENJob job) {
+        String from = artifact.getDataAsString("diff_from");
+        String to   = artifact.getDataAsString("diff_to");
+        String diff = artifact.getDataAsString("diff_diff");
+
+        try {
+            job.setFrom(Double.parseDouble(from));
+        }
+        catch (NumberFormatException nfe) {
+        }
+
+        try {
+            job.setTo(Double.parseDouble(to));
+        }
+        catch (NumberFormatException nfe) {
+        }
+
+        try {
+            job.setDiff(Double.parseDouble(diff));
+        }
+        catch (NumberFormatException nfe) {
+        }
+    }
+
+
+    protected void setGel(FLYSArtifact artifact, WSPLGENJob job) {
+        String gel = artifact.getDataAsString("scenario");
+
+        logger.debug("Selected gel = '" + gel + "'");
+
+        if (gel == null || gel.length() == 0) {
+            job.setGel(WSPLGENJob.GEL_NOSPERRE);
+        }
+        else if (gel.equals("scenario.current")) {
+            job.setGel(WSPLGENJob.GEL_SPERRE);
+        }
+        else if (gel.equals("scenario.scenario")) {
+            job.setGel(WSPLGENJob.GEL_SPERRE);
+        }
+        else {
+            job.setGel(WSPLGENJob.GEL_NOSPERRE);
+        }
+    }
+
+
+    protected void setDist(FLYSArtifact artifact, WSPLGENJob job) {
+        String dist = artifact.getDataAsString("profile_distance");
+
+        try {
+            job.setDist(Double.parseDouble(dist));
+        }
+        catch (NumberFormatException nfe) {
+            // nothing to do here
+        }
+    }
+
+
+    protected void setLine(
+        FLYSArtifact artifact,
+        FacetCreator facetCreator,
+        File         dir,
+        WSPLGENJob   job
+    ) {
+        String geoJSON = artifact.getDataAsString("uesk.barriers");
+        String srid    = FLYSUtils.getRiverSrid(artifact);
+        String srs     = "EPSG:" + srid;
+
+        if (geoJSON == null || geoJSON.length() == 0) {
+            logger.debug("No barrier features in parameterization existing.");
+            return;
+        }
+
+        SimpleFeatureType ft = getBarriersFeatureType(
+            "barriers", srs, Geometry.class);
+
+        List<SimpleFeature> features = GeometryUtils.parseGeoJSON(geoJSON, ft);
+        if (features == null || features.size() == 0) {
+            logger.debug("No barrier features extracted.");
+            return;
+        }
+
+        FeatureCollection[] fcs = splitLinesAndPolygons(features);
+
+        File shapeLines = new File(dir, WSPLGEN_BARRIERS_LINES);
+        File shapePolys = new File(dir, WSPLGEN_BARRIERS_POLY);
+
+        Object[][] obj = new Object[][] {
+            new Object[] { "typ", String.class }
+        };
+
+        String scenario = job.getGel();
+
+        boolean l = GeometryUtils.writeShapefile(
+            shapeLines,
+            GeometryUtils.buildFeatureType("lines", srs, LineString.class, obj),
+            fcs[0]);
+
+        if (l) {
+            logger.debug(
+                "Successfully created barrier line shapefile. " +
+                "Write shapefile path into WSPLGEN job.");
+
+            if (scenario.equals(WSPLGENJob.GEL_NOSPERRE)) {
+                logger.debug("WSPLGEN will not use barrier features.");
+            }
+            else {
+                job.addLin(shapeLines.getAbsolutePath());
+            }
+        }
+
+        boolean p = GeometryUtils.writeShapefile(
+            shapePolys,
+            GeometryUtils.buildFeatureType("polygons", srs, Polygon.class, obj),
+            fcs[1]);
+
+        if (p) {
+            logger.debug(
+                "Successfully created barrier polygon shapefile. " +
+                "Write shapefile path into WSPLGEN job.");
+
+            if (scenario.equals(WSPLGENJob.GEL_NOSPERRE)) {
+                logger.debug("WSPLGEN will not use barrier features.");
+            }
+            else {
+                job.addLin(shapePolys.getAbsolutePath());
+            }
+        }
+
+        if (p || l) {
+            facetCreator.createBarrierFacet();
+        }
+    }
+
+
+    protected void setUserShape(
+        FLYSArtifact artifact,
+        FacetCreator facetCreator,
+        File         dir,
+        WSPLGENJob   job
+    ) {
+        File archive = new File(dir, WSPLGEN_USER_ZIP);
+        boolean exists = archive.exists();
+        logger.debug("Zip file exists: " + exists);
+        if (exists) {
+            try {
+                File tmpDir = new File(dir, "usr_tmp");
+                FileTools.extractArchive(archive, tmpDir);
+                moveFiles(tmpDir, dir);
+            }
+            catch (IOException ioe) {
+                logger.warn("Zip archive " + dir + "/" + WSPLGEN_USER_ZIP + " could not be extracted.");
+                return;
+            }
+
+            job.addLin(dir + "/" + WSPLGEN_USER_SHAPE);
+            facetCreator.createUserShapeFacet();
+        }
+    }
+
+    protected SimpleFeatureType getBarriersFeatureType(
+        String name,
+        String srs,
+        Class  type
+    ) {
+        Object[][] attrs = new Object[3][];
+        attrs[0] = new Object[] { "typ", String.class };
+        attrs[1] = new Object[] { "elevation", Double.class };
+        attrs[2] = new Object[] { "mark.selected", Integer.class };
+
+        return GeometryUtils.buildFeatureType(name, srs, type, attrs);
+    }
+
+
+    protected FeatureCollection[] splitLinesAndPolygons(List<SimpleFeature> f) {
+        FeatureCollection lines    = FeatureCollections.newCollection();
+        FeatureCollection polygons = FeatureCollections.newCollection();
+
+        for (SimpleFeature feature: f) {
+            Geometry geom = (Geometry) feature.getDefaultGeometry();
+
+
+            if (geom instanceof LineString) {
+                geom = applyElevationAttribute(feature, (LineString) geom);
+                lines.add(feature);
+            }
+            else if (geom instanceof Polygon) {
+                geom = applyElevationAttribute(feature, (Polygon) geom);
+                polygons.add(feature);
+            }
+            else {
+                logger.warn("Feature not supported: " + geom.getClass());
+            }
+        }
+
+        logger.debug("Found " + lines.size() + " barrier lines.");
+        logger.debug("Found " + polygons.size() + " barrier polygons.");
+
+        return new FeatureCollection[] { lines, polygons };
+    }
+
+
+    protected static Geometry applyElevationAttribute(
+        SimpleFeature feature,
+        Geometry      geom
+    ) {
+        logger.debug("Apply elevations for: " + geom.getClass());
+
+        List<Double> elevations = extractElevations(feature);
+        int           numPoints = geom.getNumPoints();
+        int        numElevation = elevations.size();
+
+        String typ = (String) feature.getAttribute("typ");
+
+        if (numPoints > numElevation) {
+            logger.warn("More vertices in Geometry than elevations given.");
+        }
+
+        Coordinate[] c = geom.getCoordinates();
+        for (int i = 0; i < numPoints; i++) {
+            if (i < numElevation) {
+                c[i].z = elevations.get(i);
+            }
+            else if (typ != null && typ.equals("Graben")) {
+                c[i].z = -9999d;
+            }
+            else {
+                c[i].z = 9999d;
+            }
+        }
+
+        return geom;
+    }
+
+
+    protected static List<Double> extractElevations(SimpleFeature feature) {
+        String tmp = (String) feature.getAttribute("elevation");
+        String typ = (String) feature.getAttribute("typ");
+
+        String[] elevations = tmp == null ? null : tmp.split(" ");
+
+        int num = elevations != null ? elevations.length : 0;
+
+        List<Double> list = new ArrayList<Double>(num);
+
+        for (int i = 0; i < num; i++) {
+            try {
+                list.add(Double.parseDouble(elevations[i]));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Error while parsing elevation at pos: " + i);
+                if (typ != null && typ.equals("Graben")) {
+                    list.add(new Double(-9999.0));
+                }
+                else {
+                    list.add(new Double(9999.0));
+                }
+            }
+        }
+
+        return list;
+    }
+
+
+    protected void setAxis(FLYSArtifact artifact, File dir, WSPLGENJob job) {
+        String river = artifact.getDataAsString("river");
+        String srid    = FLYSUtils.getRiverSrid(artifact);
+        String srs     = "EPSG:" + srid;
+
+        List<RiverAxis> axes = RiverAxis.getRiverAxis(river);
+        if (axes == null || axes.size() == 0) {
+            logger.warn("Could not find river axis for: '" + river + "'");
+            return;
+        }
+
+        SimpleFeatureType ft = GeometryUtils.buildFeatureType(
+            "axis", srs, LineString.class);
+
+        SimpleFeatureBuilder builder = new SimpleFeatureBuilder(ft);
+        FeatureCollection collection = FeatureCollections.newCollection();
+
+        for (int i = 0, n = axes.size(); i < n; i++) {
+            RiverAxis axis = axes.get(i);
+
+            builder.add(axis.getGeom());
+            collection.add(builder.buildFeature(String.valueOf(i)));
+
+            builder.reset();
+        }
+
+        File axisShape = new File(dir, WSPLGEN_AXIS);
+
+        boolean a = GeometryUtils.writeShapefile(
+            axisShape,
+            GeometryUtils.buildFeatureType("axis", srs, LineString.class),
+            collection);
+
+        if (a) {
+            job.setAxis(axisShape.getAbsolutePath());
+        }
+    }
+
+
+    protected void setPro(FLYSArtifact artifact, File dir, WSPLGENJob job) {
+        String river = artifact.getDataAsString("river");
+        String srid    = FLYSUtils.getRiverSrid(artifact);
+        String srs     = "EPSG:" + srid;
+
+        List<CrossSectionTrack> cst =
+            CrossSectionTrack.getCrossSectionTrack(river);
+
+        logger.debug("Found " + cst.size() + " CrossSectionTracks.");
+
+        Object[][] attrs = new Object[2][];
+        attrs[0] = new Object[] { "ELEVATION", Double.class };
+        attrs[1] = new Object[] { "KILOMETER", Double.class };
+
+        SimpleFeatureType ft = GeometryUtils.buildFeatureType(
+            "qps", srs, LineString.class, attrs);
+
+        SimpleFeatureBuilder builder = new SimpleFeatureBuilder(ft);
+        FeatureCollection collection = FeatureCollections.newCollection();
+
+        int i = 0;
+        for (CrossSectionTrack track: cst) {
+            builder.reset();
+            builder.add(track.getGeom());
+            builder.add(track.getZ().doubleValue());
+            builder.add(track.getKm().doubleValue());
+
+            collection.add(builder.buildFeature(String.valueOf(i++)));
+        }
+
+        File qpsShape = new File(dir, WSPLGEN_QPS);
+
+        boolean q = GeometryUtils.writeShapefile(
+            qpsShape,
+            GeometryUtils.buildFeatureType("qps", srs, LineString.class, attrs),
+            collection);
+
+        if (q) {
+            job.setPro(qpsShape.getAbsolutePath());
+        }
+    }
+
+
+    protected void setDgm(FLYSArtifact artifact, WSPLGENJob job) {
+        String dgm_id = artifact.getDataAsString("dgm");
+
+        int id = -1;
+        try {
+            id = Integer.parseInt(dgm_id);
+        }
+        catch (NumberFormatException nfe) { /* do nothing */ }
+
+        DGM dgm = DGM.getDGM(id);
+
+        if (dgm == null) {
+            logger.warn("Could not find specified DGM.");
+
+            return;
+        }
+
+        job.setDgm(dgm.getPath());
+    }
+
+
+    protected void setArea(FLYSArtifact artifact, File dir, WSPLGENJob job) {
+        String useFloodplain = artifact.getDataAsString("use_floodplain");
+        if (!Boolean.valueOf(useFloodplain)) {
+            logger.debug("WSPLGEN will not use floodplain.");
+            return;
+        }
+
+        String river = artifact.getDataAsString("river");
+        String srid  = FLYSUtils.getRiverSrid(artifact);
+        String srs   = "EPSG:" + srid;
+
+        Floodplain plain = Floodplain.getFloodplain(river);
+
+        SimpleFeatureType ft = GeometryUtils.buildFeatureType(
+            "talaue", srs, Polygon.class);
+
+        SimpleFeatureBuilder builder = new SimpleFeatureBuilder(ft);
+        builder.add(plain.getGeom());
+
+        FeatureCollection collection = FeatureCollections.newCollection();
+        collection.add(builder.buildFeature("0"));
+
+        File talaueShape = new File(dir, WSPLGEN_FLOODPLAIN);
+
+        boolean t = GeometryUtils.writeShapefile(
+            talaueShape,
+            GeometryUtils.buildFeatureType("talaue", srs, Polygon.class),
+            collection);
+
+        if (t) {
+            job.setArea(talaueShape.getAbsolutePath());
+        }
+    }
+
+
+    protected void setOutFile(FLYSArtifact artifact, WSPLGENJob job) {
+        job.setOutFile(WSPLGEN_OUTPUT_FILE);
+    }
+
+
+    protected WQKms getWQKms(FLYSArtifact flys, CallContext cc) {
+        String   wspString = flys.getDataAsString(WSP_ARTIFACT);
+        String[] parts     = wspString.split(";");
+
+        String otherArtifact = parts[0];
+
+        int idx = -1;
+        try {
+            idx = Integer.parseInt(parts[2]);
+        }
+        catch (NumberFormatException nfe) { /* do nothing */ }
+
+        FLYSArtifact src = otherArtifact != null
+            ? FLYSUtils.getArtifact(otherArtifact, cc)
+            : flys;
+
+        logger.debug("Use waterlevel provided by Artifact: " + src.identifier());
+
+        CalculationResult rawData = (CalculationResult) src.compute(
+            cc,
+            null,
+            WINFO_WSP_STATE_ID,
+            ComputeType.ADVANCE,
+            false);
+
+        WQKms[] wqkms = (WQKms[]) rawData.getData();
+
+        return wqkms == null || idx == -1 || idx >= wqkms.length
+            ? null
+            : wqkms[idx];
+    }
+
+
+    protected void setWsp(
+        FLYSArtifact artifact,
+        CallContext  context,
+        File         dir,
+        WSPLGENJob   job)
+    {
+        logger.debug("FloodMapState.setWsp");
+
+        WQKms data = getWQKms(artifact, context);
+
+        if (data == null) {
+            logger.warn("No WST data found!");
+            return;
+        }
+
+        WstWriter writer = new WstWriter(1);
+
+        // TODO REMOVE job.setWspTag(...) This is only used until the user is
+        // able to select the WSP column himself!
+        boolean writeWspTag = true;
+
+        double[] buf = new double[4];
+        logger.debug("Add WST column: " + data.getName());
+        writer.addColumn(data.getName());
+
+        if (writeWspTag) {
+            job.setWspTag(data.getName());
+            writeWspTag = false;
+        }
+
+        for (int i = 0, num = data.size(); i < num; i++) {
+            data.get(i, buf);
+            writer.add(buf);
+        }
+
+        FileOutputStream fout = null;
+
+        try {
+            File wspFile = new File(dir, WSPLGEN_WSP_FILE);
+            fout         = new FileOutputStream(wspFile);
+
+            writer.write(fout);
+
+            job.setWsp(wspFile.getAbsolutePath());
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.warn("Error while writing wsp file: " + fnfe.getMessage());
+        }
+        finally {
+            if (fout != null) {
+                try {
+                    fout.close();
+                }
+                catch (IOException ioe) { /* do nothing */ }
+            }
+        }
+    }
+
+
+    protected void moveFiles(File source, final File target)
+    throws IOException
+    {
+        if (!source.exists()) {
+            return;
+        }
+        if (!target.exists()) {
+            target.mkdir();
+        }
+        FileTools.walkTree(source, new FileTools.FileVisitor() {
+            public boolean visit(File file) {
+                if (!file.isDirectory()) {
+                    String name = file.getName();
+                    String suffix = "";
+                    int pos = name.lastIndexOf('.');
+                    if (pos > 0 && pos < name.length() - 1) {
+                        suffix = name.substring(pos + 1);
+                    }
+                    else {
+                        return true;
+                    }
+                    try {
+                        FileTools.copyFile(file, new File(target, WSPLGEN_USER_FILENAME + "." + suffix));
+                    }
+                    catch (IOException ioe) {
+                        logger.warn ("Error while copying file " + file.getName());
+                        return true;
+                    }
+                }
+                return true;
+            }
+        });
+
+        FileTools.deleteRecursive(source);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/FloodplainChoice.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,87 @@
+package de.intevation.flys.artifacts.states;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FloodplainChoice extends DefaultState {
+
+    public static final String OPTION   = "floodplain.option";
+    public static final String ACTIVE   = "floodplain.active";
+    public static final String INACTIVE = "floodplain.inactive";
+
+    private static final Logger logger =
+        Logger.getLogger(FloodplainChoice.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, OPTION, OPTION), "true" });
+
+        return new Element[] { option };
+    }
+
+
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        logger.debug("GET LABEL FOR '" + name + "' / '" + value + "'");
+        if (value != null && value.equals("true")) {
+            return Resources.getMsg(cc.getMeta(), ACTIVE, ACTIVE);
+        }
+        else {
+            return Resources.getMsg(cc.getMeta(), INACTIVE, INACTIVE);
+        }
+    }
+
+
+    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 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/FlowVelocityState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,157 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.MINFOArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.FlowVelocityCalculation;
+import de.intevation.flys.artifacts.model.FlowVelocityData;
+import de.intevation.flys.artifacts.model.FlowVelocityFacet;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+public class FlowVelocityState extends DefaultState implements FacetTypes {
+
+    private static Logger logger = Logger.getLogger(FlowVelocityState.class);
+
+
+    public static final String I18N_MAINCHANNEL_FACET =
+        "facet.flow_velocity.mainchannel";
+
+    public static final String I18N_TOTALCHANNEL_FACET =
+        "facet.flow_velocity.totalchannel";
+
+    public static final String I18N_TAU_FACET =
+        "facet.flow_velocity.tauchannel";
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        logger.debug("FlowVelocityState.computeAdvance");
+
+        List<Facet> newFacets = new ArrayList<Facet>();
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult) old
+            : new FlowVelocityCalculation().calculate((MINFOArtifact) artifact);
+
+        if (facets == null || res == null) {
+            return res;
+        }
+
+        FlowVelocityData[] data = (FlowVelocityData[]) res.getData();
+
+        logger.debug("Calculated " + data.length + " FlowVelocityData objects");
+
+        String id  = getID();
+        int    idx = 0;
+
+        for (FlowVelocityData d: data) {
+            logger.error("TODO: Implement Facet creation for chart!");
+
+            newFacets.add(new FlowVelocityFacet(
+                idx,
+                FLOW_VELOCITY_MAINCHANNEL,
+                buildMainChannelName(artifact, context, d),
+                ComputeType.ADVANCE,
+                id,
+                hash
+            ));
+
+            newFacets.add(new FlowVelocityFacet(
+                idx,
+                FLOW_VELOCITY_TOTALCHANNEL,
+                buildTotalChannelName(artifact, context, d),
+                ComputeType.ADVANCE,
+                id,
+                hash
+            ));
+
+            newFacets.add(new FlowVelocityFacet(
+                idx,
+                FLOW_VELOCITY_TAU,
+                buildTauName(artifact, context, d),
+                ComputeType.ADVANCE,
+                id,
+                hash
+            ));
+
+            idx++;
+        }
+
+        Facet csv = new DataFacet(
+            CSV, "CSV data", ComputeType.ADVANCE, hash, id);
+
+        // TODO ADD PDF FACET
+
+        newFacets.add(csv);
+
+        logger.debug("Created " + newFacets.size() + " new Facets.");
+
+        facets.addAll(newFacets);
+
+        return res;
+    }
+
+
+    protected String buildFacetName(
+        FLYSArtifact     flys,
+        CallContext      cc,
+        FlowVelocityData data,
+        String           resourceId
+    ) {
+        Object[] args = new Object[] {
+            data.getZone()
+        };
+
+        return Resources.getMsg(
+            cc.getMeta(),
+            resourceId,
+            resourceId,
+            args);
+    }
+
+
+    protected String buildMainChannelName(
+        FLYSArtifact     flys,
+        CallContext      cc,
+        FlowVelocityData data
+    ) {
+        return buildFacetName(flys, cc, data, I18N_MAINCHANNEL_FACET);
+    }
+
+
+    protected String buildTotalChannelName(
+        FLYSArtifact     flys,
+        CallContext      cc,
+        FlowVelocityData data
+    ) {
+        return buildFacetName(flys, cc, data, I18N_TOTALCHANNEL_FACET);
+    }
+
+
+    protected String buildTauName(
+        FLYSArtifact     flys,
+        CallContext      cc,
+        FlowVelocityData data
+    ) {
+        return buildFacetName(flys, cc, data, I18N_TAU_FACET);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/GaugeDischargeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,157 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.ChartArtifact;
+import de.intevation.flys.artifacts.GaugeDischargeArtifact;
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.GaugeDischargeFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.EmptyFacet;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * The only state for an GaugeDischargeState (River and km known).
+ */
+public class GaugeDischargeState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** Developer-centric description of facet. */
+    public static final String I18N_DESCRIPTION = "facet.discharge.curve";
+
+    /** The logger that is used in this state. */
+    private static final Logger logger = Logger.getLogger(GaugeDischargeState.class);
+
+
+    /**
+     * Add an GaugeDischargeFacet to list of Facets.
+     *
+     * @param artifact Ignored.
+     * @param hash Ignored.
+     * @param context Ignored.
+     * @param meta CallMeta to be used for internationalization.
+     * @param facets List to add AnnotationFacet to.
+     *
+     * @return null.
+     */
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        logger.debug("GaugeDischargeState.computeInit()");
+
+        // TODO caching, / CalculationResultusage
+
+        GaugeDischargeFacet facet = new GaugeDischargeFacet(
+            0,
+            DISCHARGE_CURVE,
+            DISCHARGE_CURVE);
+            //Resources.getMsg(meta, I18N_DESCRIPTION, I18N_DESCRIPTION));
+        facets.add(facet);
+
+        return null;
+    }
+    //WINFOArtifact#getDischargeCurveData
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        // TODO why are the facets null here?
+        //if (facets == null) facets = new List<Facet>();
+        if (artifact instanceof GaugeDischargeArtifact) {
+            logger.debug("GaugeDischargeState.computeAdvance()");
+            GaugeDischargeArtifact dischargeArtifact = (GaugeDischargeArtifact) artifact;
+
+            CalculationResult res;
+
+            /*if (old instanceof CalculationResult) {
+                res = (CalculationResult) old;
+            }
+            else {*/
+                res = dischargeArtifact.getDischargeCurveData();
+            //}
+
+            WQKms[] wqkms = (WQKms[]) res.getData();
+
+            if (wqkms != null && facets != null) {
+                logger.debug("GaugeDischargeState.computeAdvance(): create facets");
+                // Create an i18ed name for a (w or q) duration curve facet.
+                Object[] args = new Object[] {
+                    FLYSUtils.getRiver(dischargeArtifact).getName(),
+                    FLYSUtils.getLocations(dischargeArtifact)[0]
+                };
+
+                String nameW = Resources.getMsg(
+                    context.getMeta(),
+                    "chart.duration.curve.curve.w",
+                    "",
+                    args);
+
+                String nameQ = Resources.getMsg(
+                    context.getMeta(),
+                    "chart.duration.curve.curve.q",
+                    "",
+                    args);
+
+                /*Facet w = new DurationCurveFacet(DURATION_W, nameW);
+                Facet q = new DurationCurveFacet(DURATION_Q, nameQ);
+
+                facets.add(w);*/
+                GaugeDischargeFacet facet = new GaugeDischargeFacet(
+                    0,
+                    "A"+DISCHARGE_CURVE,
+                    "A"+DISCHARGE_CURVE);
+                    //Resources.getMsg(meta, I18N_DESCRIPTION, I18N_DESCRIPTION));
+                facets.add(facet);
+
+                //facets.add(new DataFacet(CSV, "CSV data"));
+                //facets.add(new DataFacet(PDF, "PDF data"));
+
+                if (res.getReport().hasProblems()) {
+                    facets.add(new ReportFacet());
+                }
+            }
+            else {
+                if (wqkms == null)
+                logger.debug("GaugeDischargeState.computeAdvance(): wqkms 0");
+                else
+                logger.debug("GaugeDischargeState.computeAdvance(): facets 0");
+            }
+
+            return res;
+        }
+        else if (artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return null;
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/GaugeTimerangeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,87 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+import org.hibernate.SQLQuery;
+import org.hibernate.type.StandardBasicTypes;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.backend.SessionHolder;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class GaugeTimerangeState extends IntRangeState {
+
+    private static final Logger logger =
+        Logger.getLogger(GaugeTimerangeState.class);
+
+
+    protected int[] getLowerUpper(FLYSArtifact flys) {
+        Gauge gauge = FLYSUtils.getReferenceGauge(flys);
+
+        if (gauge == null) {
+            logger.warn("No reference gauge specified!");
+            return new int[] { 0, 0 };
+        }
+
+        Session session = SessionHolder.HOLDER.get();
+
+        SQLQuery query = session.createSQLQuery(
+            "SELECT min(start_time) as min, max(stop_time) as max " +
+            "FROM time_intervals WHERE id in " +
+            "(SELECT time_interval_id FROM discharge_tables " +
+            "WHERE gauge_id =:gid)");
+
+        query.addScalar("min", StandardBasicTypes.CALENDAR);
+        query.addScalar("max", StandardBasicTypes.CALENDAR);
+
+        query.setInteger("gid", gauge.getId());
+
+        List results = query.list();
+
+        if (results != null) {
+            Object[] res = (Object[]) results.get(0);
+
+            Calendar lo = (Calendar) res[0];
+            Calendar up = (Calendar) res[1];
+
+            return new int[] { lo.get(Calendar.YEAR), up.get(Calendar.YEAR) };
+        }
+
+        logger.warn("Could not determine time range for gauge: " + gauge);
+
+        return null;
+    }
+
+
+    @Override
+    protected Object getLower(FLYSArtifact flys) {
+        int[] lowerUpper = getLowerUpper(flys);
+
+        return lowerUpper != null ? lowerUpper[0] : 0;
+    }
+
+
+    @Override
+    protected Object getUpper(FLYSArtifact flys) {
+        int[] lowerUpper = getLowerUpper(flys);
+
+        return lowerUpper != null ? lowerUpper[1] : 0;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "gaugetimerange";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeComputeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,120 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.HistoricalDischargeFacet;
+import de.intevation.flys.artifacts.model.HistoricalDischargeDifferenceFacet;
+import de.intevation.flys.artifacts.model.HistoricalWQTimerange;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.WQTimerange;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeComputeState
+extends      DefaultState
+implements   FacetTypes
+{
+
+    private static final Logger logger =
+        Logger.getLogger(HistoricalDischargeComputeState.class);
+
+
+    @Override
+    protected void appendItems(
+        Artifact       artifact,
+        ElementCreator creator,
+        String         name,
+        CallContext    context,
+        Element        select
+    ) {
+        // TODO IMPLEMENT ME
+    }
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        logger.debug("HistoricalDischargeComputeState.computeAdvance");
+
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult) old
+            : winfo.getHistoricalDischargeData();
+
+        if (facets == null) {
+            return res;
+        }
+
+        if (res.getReport().hasProblems()) {
+            facets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
+        }
+
+        WQTimerange[] data = (WQTimerange[]) res.getData();
+        if (data == null || data.length == 0) {
+            logger.warn("Historical Discharge calculation has no results!");
+            return res;
+        }
+
+        facets.add(
+            new DataFacet(CSV, "CSV data", ComputeType.ADVANCE, hash, id));
+
+        facets.add(
+            new DataFacet(PDF, "PDF data", ComputeType.ADVANCE, hash, id));
+
+        prepareFacets(facets, data);
+
+        return res;
+    }
+
+
+    protected void prepareFacets(List<Facet> facets, WQTimerange[] wqts) {
+        int i = 0;
+
+        for (WQTimerange wqt: wqts) {
+            logger.debug("Prepare facet for: " + wqt.getName());
+
+            // TODO CREATE BETTER TITLE FOR FACETS
+
+            facets.add(new HistoricalDischargeFacet(
+                i,
+                HISTORICAL_DISCHARGE_Q,
+                wqt.getName()));
+
+            if (wqt instanceof HistoricalWQTimerange) {
+                logger.debug("Create another facet for historical differences.");
+
+                facets.add(new HistoricalDischargeDifferenceFacet(
+                    i,
+                    HISTORICAL_DISCHARGE_Q_DIFF,
+                    "DIFF: " + wqt.getName()));
+            }
+
+            i++;
+        }
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/HistoricalDischargeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,152 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeState extends DefaultState {
+
+    private static final Logger logger =
+        Logger.getLogger(HistoricalDischargeState.class);
+
+
+    public static final String I18N_MODE_W = "historical.mode.w";
+    public static final String I18N_MODE_Q = "historical.mode.q";
+
+    public static final String DATA_MODE   = "historical_mode";
+    public static final String DATA_VALUES = "historical_values";
+    public static final int    DATA_MODE_W = 0;
+    public static final int    DATA_MODE_Q = 1;
+
+
+    @Override
+    protected String getUIProvider() {
+        return "wq_simple_array";
+    }
+
+    protected void appendItems(
+        Artifact       artifact,
+        ElementCreator creator,
+        String         name,
+        CallContext    context,
+        Element        select
+    ) {
+        if (name != null && name.equals(DATA_VALUES)) {
+            select.setAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI,
+                "art:type",
+                "doublearray");
+        }
+        else if (name != null && name.equals(DATA_MODE)) {
+            select.setAttributeNS(
+                ArtifactNamespaceContext.NAMESPACE_URI,
+                "art:type",
+                "intoptions");
+        }
+
+        super.appendItems(artifact, creator, name, context, select);
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        ElementCreator creator,
+        Artifact       artifact,
+        String         name,
+        CallContext    context
+    ) {
+        logger.debug("createItems()");
+
+        if (name != null && name.equals(DATA_MODE)) {
+            return createModeItem(creator, artifact, name, context);
+        }
+        else if (name != null && name.equals(DATA_VALUES)) {
+            return createValuesItem(creator, artifact, name, context);
+        }
+
+        logger.warn("Tried to create item for invalid data: " + name);
+
+        return new Element[0];
+    }
+
+
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        CallMeta meta = cc.getMeta();
+
+        if (name.equals(DATA_MODE)) {
+            if (value.equals(String.valueOf(DATA_MODE_W))) {
+                return Resources.getMsg(meta, I18N_MODE_W, I18N_MODE_W);
+            }
+            else {
+                return Resources.getMsg(meta, I18N_MODE_Q, I18N_MODE_Q);
+            }
+        }
+        else {
+            return value;
+        }
+    }
+
+
+    protected Element[] createModeItem(
+        ElementCreator creator,
+        Artifact       artifact,
+        String         name,
+        CallContext    context
+    ) {
+        logger.debug("createModeItem()");
+
+        CallMeta meta = context.getMeta();
+
+        Element modeW = createItem(
+            creator,
+            new String[] {
+                Resources.getMsg(meta, I18N_MODE_W, I18N_MODE_W),
+                String.valueOf(DATA_MODE_W) } );
+
+        Element modeQ = createItem(
+            creator,
+            new String[] {
+                Resources.getMsg(meta, I18N_MODE_Q, I18N_MODE_Q),
+                String.valueOf(DATA_MODE_Q) } );
+
+        return new Element[] { modeW, modeQ };
+    }
+
+
+    protected Element[] createValuesItem(
+        ElementCreator creator,
+        Artifact       artifact,
+        String         name,
+        CallContext    context
+    ) {
+        logger.debug("createValuesItem()");
+
+        Element valuesW = createItem(
+            creator, new String[] { "ws", "" } );
+
+        Element valuesQ = createItem(
+            creator, new String[] { "qs", "" } );
+
+        return new Element[] { valuesW, valuesQ };
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/InputDoubleState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,39 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * State to keep a double value and validate it against a range
+ */
+public class InputDoubleState extends MinMaxState {
+
+    private static final Logger logger = Logger.getLogger(InputDoubleState.class);
+
+
+    @Override
+    protected String getUIProvider() {
+        return "location_panel";
+    }
+
+
+    @Override
+    protected Object getLower(FLYSArtifact flys) {
+        return 0;
+    }
+
+
+    @Override
+    protected Object getUpper(FLYSArtifact flys) {
+        return 0;
+    }
+
+
+    @Override
+    protected String getType() {
+        return "double";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/IntRangeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,39 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class IntRangeState extends MinMaxState {
+
+    private static final Logger logger = Logger.getLogger(IntRangeState.class);
+
+
+    @Override
+    protected String getUIProvider() {
+        return "timerange";
+    }
+
+
+    @Override
+    protected Object getLower(FLYSArtifact flys) {
+        return 0;
+    }
+
+
+    @Override
+    protected Object getUpper(FLYSArtifact flys) {
+        return 0;
+    }
+
+
+    @Override
+    protected String getType() {
+        return "intrange";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationDistanceSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,145 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import gnu.trove.TDoubleArrayList;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LocationDistanceSelect
+extends      ComputationRangeState
+{
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(LocationDistanceSelect.class);
+
+    /** The name of the 'mode' field. */
+    public static final String MODE = "ld_mode";
+
+    /** The name of the 'locations' field. */
+    public static final String LOCATIONS = "ld_locations";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public LocationDistanceSelect() {
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "location_distance_panel";
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("LocationDistanceSelect.validate");
+
+        WINFOArtifact flys = (WINFOArtifact) artifact;
+
+        if (flys.isRange()) {
+            return super.validate(flys);
+        }
+        else {
+            return validateLocations(flys);
+        }
+    }
+
+
+    protected boolean validateLocations(WINFOArtifact flys)
+    throws    IllegalArgumentException
+    {
+        StateData dValues = getData(flys, LOCATIONS);
+        String    values  = dValues != null ? (String)dValues.getValue() : null;
+
+        if (values == null || values.length() == 0) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        double[] absMinMax = getMinMax(flys);
+        double[] relMinMax = getMinMaxFromString(values);
+
+        if (relMinMax[0] < absMinMax[0] || relMinMax[0] > absMinMax[1]) {
+            throw new IllegalArgumentException("error_feed_from_out_of_range");
+        }
+
+        if (relMinMax[1] > absMinMax[1] || relMinMax[1] < absMinMax[0]) {
+            throw new IllegalArgumentException("error_feed_to_out_of_range");
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Extracts the min/max values from String <i>s</i>. An
+     * IllegalArgumentException is thrown if there is a value that throws a
+     * NumberFormatException.
+     *
+     * @param s String that contains whitespace separated double values.
+     *
+     * @return a 2dmin array [min,max].
+     */
+    public static double[] getMinMaxFromString(String s)
+    throws IllegalArgumentException
+    {
+        String[] values = s.split(" ");
+
+        double[] minmax = new double[] {
+            Double.MAX_VALUE,
+            -Double.MAX_VALUE };
+
+        for (String v: values) {
+            try {
+                double value = Double.valueOf(v);
+
+                minmax[0] = minmax[0] < value ? minmax[0] : value;
+                minmax[1] = minmax[1] > value ? minmax[1] : value;
+            }
+            catch (NumberFormatException nfe) {
+                throw new IllegalArgumentException(
+                    "error_invalid_double_value");
+            }
+        }
+
+        return minmax;
+    }
+
+
+    public static double[] getLocations(WINFOArtifact flys) {
+        StateData data  = flys.getData(LOCATIONS);
+        String    value = data != null ? (String) data.getValue() : null;
+
+        if (value == null || value.length() == 0) {
+            logger.warn("No location data given.");
+            return null;
+        }
+
+        String[]         splitted = value.split(" ");
+        TDoubleArrayList values   = new TDoubleArrayList();
+
+        for (String split: splitted) {
+            try {
+                values.add(Double.valueOf(split));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        return values.toNativeArray();
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,132 @@
+package de.intevation.flys.artifacts.states;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * This state is used to realize the input of multiple locations as string. The
+ * string should be a whitespace separated list of double values where each
+ * double value represents a location.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LocationSelect extends LocationDistanceSelect {
+
+    /** The logger used in this class.*/
+    private static Logger logger = Logger.getLogger(LocationSelect.class);
+
+
+    public LocationSelect() {
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "location_panel";
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        double[] minmax = getMinMax(artifact);
+
+        double minVal = Double.MIN_VALUE;
+        double maxVal = Double.MAX_VALUE;
+
+        if (minmax != null) {
+            minVal = minmax[0];
+            maxVal = minmax[1];
+        }
+        else {
+            logger.warn("Could not read min/max distance values!");
+        }
+
+        if (name.equals(LOCATIONS)) {
+            Element min = createItem(
+                cr,
+                new String[] {"min", new Double(minVal).toString()});
+
+            Element max = createItem(
+                cr,
+                new String[] {"max", new Double(maxVal).toString()});
+
+            return new Element[] { min, max };
+        }
+
+        return null;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("LocationSelect.validate");
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+        StateData    data = getData(flys, LOCATIONS);
+
+        String locationStr = data != null
+            ? (String) data.getValue()
+            : null;
+
+        if (locationStr == null || locationStr.length() == 0) {
+            logger.error("No locations given.");
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        double[] minmax = getMinMax(artifact);
+        double[] mm     = extractLocations(locationStr);
+
+        logger.debug("Inserted min location: " + mm[0]);
+        logger.debug("Inserted max location: " + mm[mm.length-1]);
+
+        return validateBounds(minmax[0], minmax[1], mm[0], mm[mm.length-1], 0d);
+    }
+
+
+    /**
+     * This method takes a string that consist of whitespace separated double
+     * values and returns the double values as array.
+     *
+     * @param locationStr The locations inserted in this state.
+     *
+     * @return the locations as array.
+     */
+    protected double[] extractLocations(String locationStr) {
+        String[] tmp               = locationStr.split(" ");
+        TDoubleArrayList locations = new TDoubleArrayList();
+
+        for (String l: tmp) {
+            try {
+                locations.add(Double.parseDouble(l));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        locations.sort();
+
+        return locations.toNativeArray();
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ManualPointsSingleState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,99 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.ManualPointsArtifact;
+
+import de.intevation.flys.artifacts.model.ManualPointsFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * The only state for an ManualPointArtifact.
+ */
+public class ManualPointsSingleState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** Developer-centric description of facet. */
+    public static final String I18N_DESCRIPTION = "facet.longitudinal_section.manualpoint";
+
+    /** The logger that is used in this state. */
+    private static final Logger logger = Logger.getLogger(ManualPointsSingleState.class);
+
+
+    /**
+     * Add an ManualPointsFacet to list of Facets.
+     *
+     * @param artifact Ignored.
+     * @param hash Ignored.
+     * @param context Ignored.
+     * @param meta CallMeta to be used for internationalization.
+     * @param facets List to add ManualPointsFacet to.
+     *
+     * @return null.
+     */
+    public Object compute(
+        FLYSArtifact artifact,
+        String       hash,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        logger.debug("ManualPointsSingleState.compute()");
+        ManualPointsArtifact points = (ManualPointsArtifact) artifact;
+
+        // Add Facet per Diagram type if data given.
+        for (ChartType ct: ChartType.values()) {
+            String pointData = points.getDataAsString(ct + "." + MANUALPOINTS + ".data");
+            if (pointData != null && !pointData.equals("") && !pointData.equals("[]")) {
+                String fName = ct + "." + MANUALPOINTS;
+                ManualPointsFacet facet = new ManualPointsFacet(
+                    0,
+                    fName,
+                    Resources.getMsg(meta, "manualpoints", "Manual Points"));
+        
+                facets.add(facet);
+            }
+            else {
+                //logger.debug("No points for " + ct);
+            }
+        }
+
+        return null;
+    }
+
+
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+     ) {
+        return compute(artifact, hash, meta, facets);
+    }
+
+
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String hash,
+        CallContext context,
+        List<Facet> facets,
+        Object old
+    ) {
+        return compute(artifact, hash, context.getMeta(), facets);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/MiddleBedHeight.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,93 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.MINFOArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.MiddleBedHeightData;
+import de.intevation.flys.artifacts.model.MiddleBedHeightFacet;
+import de.intevation.flys.artifacts.model.MiddleBedHeightCalculation;
+
+
+public class MiddleBedHeight extends DefaultState implements FacetTypes {
+
+    private static final Logger logger = Logger.getLogger(MiddleBedHeight.class);
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        logger.debug("MiddleBedHeight.computeAdvance");
+
+        List<Facet> newFacets = new ArrayList<Facet>();
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult) old
+            : new MiddleBedHeightCalculation().calculate((MINFOArtifact) artifact);
+
+        if (facets == null || res == null) {
+            return res;
+        }
+
+        MiddleBedHeightData[] data = (MiddleBedHeightData[]) res.getData();
+
+        logger.debug("Calculated " + data.length + " MiddleBedHeightData objects");
+
+        String id  = getID();
+        int    idx = 0;
+
+        for (MiddleBedHeightData d: data) {
+            if (d.getStartYear() == d.getEndYear()) {
+                newFacets.add(new MiddleBedHeightFacet(
+                    idx,
+                    MIDDLE_BED_HEIGHT_SINGLE,
+                    d.getSoundingName(context),
+                    ComputeType.ADVANCE,
+                    id,
+                    hash
+                ));
+            }
+            else {
+                newFacets.add(new MiddleBedHeightFacet(
+                    idx,
+                    MIDDLE_BED_HEIGHT_EPOCH,
+                    d.getSoundingName(context),
+                    ComputeType.ADVANCE,
+                    id,
+                    hash
+                ));
+            }
+
+            idx++;
+        }
+
+        Facet csv = new DataFacet(
+            CSV, "CSV data", ComputeType.ADVANCE, hash, id);
+
+        // TODO ADD PDF FACET
+
+        newFacets.add(csv);
+
+        logger.debug("Created " + newFacets.size() + " new Facets.");
+
+        facets.addAll(newFacets);
+
+        return res;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/MinMaxState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,173 @@
+package de.intevation.flys.artifacts.states;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * State that holds minimun and maximum (for validation).
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class MinMaxState extends DefaultState {
+
+    private static final Logger logger = Logger.getLogger(MinMaxState.class);
+
+    @Override
+    protected void appendItems(
+        Artifact       artifact,
+        ElementCreator creator,
+        String         name,
+        CallContext    context,
+        Element        select
+    ) {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        select.setAttributeNS(
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            "art:type",
+            getType());
+
+        String[] defMinMax = getDefaults(artifact, name);
+
+        Element min = ProtocolUtils.createArtNode(
+            creator,
+            "min",
+            new String[] { "value", "default" },
+            new String[] { String.valueOf(getLower(flys)), defMinMax[0] });
+
+        Element max = ProtocolUtils.createArtNode(
+            creator,
+            "max",
+            new String[] { "value", "default" },
+            new String[] { String.valueOf(getUpper(flys)), defMinMax[1] });
+
+        select.appendChild(min);
+        select.appendChild(max);
+    }
+
+
+    /**
+     * @param cc
+     * @param name
+     * @param value
+     * @param type
+     *
+     * @return
+     */
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        if (type.indexOf("range") > 0) {
+            String[] minmax = extractRangeAsString(value);
+
+            if (minmax != null) {
+                return minmax[0] + " - " + minmax[1];
+            }
+        }
+
+        return super.getLabelFor(cc, name, value, type);
+    }
+
+
+    /**
+     * Returns a string array with [min,max] from <i>rawValue</i>.
+     * <i>rawValue</i> should be a string like "1999;2001".
+     *
+     * @param rawValue A string with min and max separated by a ';'.
+     *
+     * @return the min and max as string array ([min,max]).
+     */
+    protected String[] extractRangeAsString(String rawValue) {
+        return rawValue.split(";");
+    }
+
+
+    /**
+     * This method returns the default values for min and max. If the static
+     * field DefaultState.USE_DEFAULTS is set, the minimum and maximum inserted
+     * by the user is returned as string. Otherwise, the absolute minimum and
+     * maximum are returned.
+     *
+     * @param artifact The FLYSArtifact.
+     * @param name The name of the parameter.
+     *
+     * @return a string array [min,max] that contains the minimum and maximum
+     * values for the parameter <i>name</i>.
+     */
+    protected String[] getDefaults(Artifact artifact, String name) {
+        if (DefaultState.USE_DEFAULTS) {
+            String[] tmp = getMinMaxByParameter(artifact, name);
+
+            return tmp != null ? tmp : getMinMaxDefaults(artifact, name);
+        }
+        else {
+            return getMinMaxDefaults(artifact, name);
+        }
+    }
+
+
+    /**
+     * Returns a string array with minimum and maximum inserted by the user as
+     * [min,max].
+     *
+     * @param artifact The FLYSArtifact that stores the parameter.
+     * @param name The name of the parameter for raw min/max value string.
+     *
+     * @return a string array [min,max].
+     */
+    protected String[] getMinMaxByParameter(Artifact artifact, String name) {
+        FLYSArtifact flys     = (FLYSArtifact) artifact;
+        String       rawValue = flys.getDataAsString(name);
+
+        if (rawValue == null) {
+            logger.debug("No value for '" + rawValue + "' existing.");
+            return null;
+        }
+
+        logger.debug("Raw value for '" + name + "' = " + rawValue);
+
+        return extractRangeAsString(rawValue);
+    }
+
+
+    /**
+     * Returns a string array with absolute minimum and maximum as [min,max].
+     *
+     * @param artifact The FLYSArtifact (not used in this implementation).
+     * @param name The parameter name (not used in this implementation).
+     *
+     * @return a string array [min,max].
+     */
+    protected String[] getMinMaxDefaults(Artifact artifact, String name) {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        Object lower = getLower(flys);
+        Object upper = getUpper(flys);
+
+        return new String[] { String.valueOf(lower), String.valueOf(upper) };
+    }
+
+
+    protected abstract Object getLower(FLYSArtifact flys);
+
+    protected abstract Object getUpper(FLYSArtifact flys);
+
+    protected abstract String getType();
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/MultiIntArrayState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,100 @@
+package de.intevation.flys.artifacts.states;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.model.KVP;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * State that holds minimun and maximum (for validation).
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class MultiIntArrayState extends DefaultState {
+
+    private static final Logger logger =
+        Logger.getLogger(MultiIntArrayState.class);
+
+
+    @Override
+    protected void appendItems(
+        Artifact       artifact,
+        ElementCreator creator,
+        String         name,
+        CallContext    context,
+        Element        select
+    ) {
+        try {
+            creator.addAttr(select, "type", "intoptions", true);
+
+            for (KVP kvp: getOptions(artifact, name)) {
+                Element item = creator.create("item");
+                creator.addAttr(item, "label", kvp.getValue().toString(), true);
+                creator.addAttr(item, "value", kvp.getKey().toString(), true);
+
+                select.appendChild(item);
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            logger.warn("Illegal argument", iae);
+        }
+    }
+
+
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        Element data = creator.create("data");
+        creator.addAttr(data, "name",  name, true);
+        creator.addAttr(data, "type",  type, true);
+        creator.addAttr(data, "label",
+            Resources.getMsg(cc.getMeta(), name, name), true);
+
+        int[] values = FLYSUtils.intArrayFromString(value);
+
+        for (int val: values) {
+            try {
+                Element item = creator.create("item");
+                creator.addAttr(item, "value", String.valueOf(val), true);
+                creator.addAttr(item, "label", getLabelFor(cc, name, val), true);
+
+                data.appendChild(item);
+            }
+            catch (IllegalArgumentException iae) {
+                logger.warn("Cannot append item: " + val, iae);
+            }
+        }
+
+        return data;
+    }
+
+
+    protected abstract KVP<Integer, String>[] getOptions(
+        Artifact artifact,
+        String   parameterName
+    )
+    throws IllegalArgumentException;
+
+
+    protected abstract String getLabelFor(
+        CallContext cc,
+        String      parameterName,
+        int         value)
+    throws IllegalArgumentException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/MultiStringArrayState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,94 @@
+package de.intevation.flys.artifacts.states;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.model.KVP;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class MultiStringArrayState extends DefaultState {
+
+    private static final Logger logger =
+        Logger.getLogger(MultiStringArrayState.class);
+
+
+    @Override
+    protected void appendItems(
+        Artifact       artifact,
+        ElementCreator creator,
+        String         name,
+        CallContext    context,
+        Element        select
+    ) {
+        try {
+            creator.addAttr(select, "type", "options", true);
+
+            for (KVP kvp: getOptions(artifact, name)) {
+                Element item = creator.create("item");
+                creator.addAttr(item, "label", kvp.getValue().toString(), true);
+                creator.addAttr(item, "value", kvp.getKey().toString(), true);
+
+                select.appendChild(item);
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            logger.warn("Illegal argument", iae);
+        }
+    }
+
+
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        Element data = creator.create("data");
+        creator.addAttr(data, "name",  name, true);
+        creator.addAttr(data, "type",  type, true);
+        creator.addAttr(data, "label",
+            Resources.getMsg(cc.getMeta(), name, name), true);
+
+        String[] values = value.split(";");
+
+        for (String val: values) {
+            Element item = creator.create("item");
+            creator.addAttr(item, "value", val, true);
+            creator.addAttr(item, "label", getLabelFor(cc, name, val), true);
+
+            data.appendChild(item);
+        }
+
+        return data;
+    }
+
+
+    protected abstract KVP<String, String>[] getOptions(
+        Artifact artifact,
+        String   parameterName
+    )
+    throws IllegalArgumentException;
+
+
+    protected abstract String getLabelFor(
+        CallContext cc,
+        String      parameterName,
+        String      value
+    )
+    throws IllegalArgumentException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/OutputState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,43 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+
+public class OutputState extends DefaultState implements FacetTypes {
+
+    private static final Logger logger = Logger.getLogger(OutputState.class);
+
+
+    @Override
+    public Element describeStatic(
+        Artifact    artifact,
+        Document    document,
+        Node        root,
+        CallContext context,
+        String      uuid)
+    {
+        return null;
+    }
+
+
+    @Override
+    public Element describe(
+        Artifact    artifact,
+        Document    document,
+        Node        root,
+        CallContext context,
+        String      uuid)
+    {
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ProfileDistanceSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,13 @@
+package de.intevation.flys.artifacts.states;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ProfileDistanceSelect extends DefaultState {
+
+    @Override
+    protected String getUIProvider() {
+        return "auto_integer";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/RangeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,106 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class RangeState extends DefaultState {
+
+    /** The logger that is used in this class. */
+    private Logger logger = Logger.getLogger(RangeState.class);
+
+
+    public RangeState() {
+    }
+
+
+    protected abstract String   getLowerField();
+    protected abstract String   getUpperField();
+    protected abstract String   getStepField();
+    protected abstract double[] getMinMax(Artifact artifact);
+
+
+    protected boolean validateBounds(
+        double fromValid, double toValid,
+        double from,      double to)
+    throws IllegalArgumentException
+    {
+        if (from < fromValid) {
+            logger.error(
+                "Invalid 'from'. " + from + " is smaller than " + fromValid);
+            throw new IllegalArgumentException("error_feed_from_out_of_range");
+        }
+        else if (to > toValid) {
+            logger.error(
+                "Invalid 'to'. " + to + " is bigger than " + toValid);
+            throw new IllegalArgumentException("error_feed_to_out_of_range");
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Validates a given range with a given valid range.
+     *
+     * @param fromValid Valid lower value of the range.
+     * @param toValid Valid upper value of the range.
+     * @param from The lower value.
+     * @param to The upper value.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateBounds(
+        double fromValid, double toValid,
+        double from,      double to,      double step)
+    throws IllegalArgumentException
+    {
+        logger.debug("RangeState.validateRange");
+
+        // XXX The step width is not validated at the moment!
+        return validateBounds(fromValid, toValid, from, to);
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData dFrom = getData(flys, getLowerField());
+        StateData dTo   = getData(flys, getUpperField());
+        StateData dStep = getData(flys, getStepField());
+
+        String fromStr = dFrom != null ? (String) dFrom.getValue() : null;
+        String toStr   = dTo   != null ? (String) dTo.getValue()   : null;
+        String stepStr = dStep != null ? (String) dStep.getValue() : null;
+
+        if (fromStr == null || toStr == null || stepStr == null) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        try {
+            double from = Double.parseDouble(fromStr);
+            double to   = Double.parseDouble(toStr);
+            double step = Double.parseDouble(stepStr);
+
+            double[] minmax = getMinMax(flys);
+
+            return validateBounds(minmax[0], minmax[1], from, to, step);
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException("error_invalid_double_value");
+        }
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ReferenceCurveState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,87 @@
+package de.intevation.flys.artifacts.states;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.ReferenceCurveFacet;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.WWQQ;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+
+/** State of WINFO in which reference curves can be produced. */
+public class ReferenceCurveState
+extends      DefaultState
+implements   FacetTypes
+{
+    private static Logger logger = Logger.getLogger(ReferenceCurveState.class);
+
+
+    public ReferenceCurveState() {
+    }
+
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        if (!(artifact instanceof WINFOArtifact)) {
+            return null;
+        }
+
+        String id = getID();
+
+        WINFOArtifact winfo = (WINFOArtifact)artifact;
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult)old
+            : winfo.getReferenceCurveData(context);
+
+        if (facets == null) {
+            return res;
+        }
+
+        WWQQ [] wws = (WWQQ [])res.getData();
+
+        for (int i = 0; i < wws.length; ++i) {
+            String wwsName = wws[i].getName();
+            facets.add(new ReferenceCurveFacet(i,
+                REFERENCE_CURVE,
+                wwsName
+                ));
+            facets.add(new ReferenceCurveFacet(i,
+                REFERENCE_CURVE_NORMALIZED,
+                wwsName
+                ));
+        }
+
+        if (wws.length > 0) {
+            logger.debug("Adding CSV and PDF data facet.");
+            Facet csv = new DataFacet (CSV, "CSV data", ComputeType.ADVANCE, hash, id);
+            Facet pdf = new DataFacet (PDF, "PDF data", ComputeType.ADVANCE, hash, id);
+            facets.add(csv);
+            facets.add(pdf);
+        }
+
+        if (res.getReport().hasProblems()) {
+            facets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
+        }
+
+        return res;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ReferenceGaugeState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,88 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ReferenceGaugeState extends DefaultState {
+
+    private static final Logger logger =
+        Logger.getLogger(ReferenceGaugeState.class);
+
+
+    public static final String DATA_NAME = "reference_gauge";
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        CallMeta  meta = context.getMeta();
+
+        River       river   = FLYSUtils.getRiver((FLYSArtifact) artifact);
+        List<Gauge> gauges  = river.getGauges();
+
+        int num = gauges != null ? gauges.size() : 0;
+
+        Element[] opts = new Element[num];
+
+        for (int i = 0; i < num; i++ ) {
+            Gauge g = gauges.get(i);
+
+            String gaugeName      = g.getName();
+            long   officialNumber = g.getOfficialNumber();
+
+            opts[i] = createItem(
+                cr, new String[] { gaugeName, String.valueOf(officialNumber) });
+        }
+
+        return opts;
+    }
+
+
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      name,
+        String      value,
+        String      type
+    ) {
+        if (name.equals(DATA_NAME)) {
+            try {
+                long  number = Long.valueOf(value);
+                Gauge gauge  = Gauge.getGaugeByOfficialNumber(number);
+
+                if (gauge != null) {
+                    return gauge.getName();
+                }
+            }
+            catch (NumberFormatException nfe) {
+                // do nothing
+            }
+        }
+
+        return super.getLabelFor(cc, name, value, type);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/RiverSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,163 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class RiverSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(RiverSelect.class);
+
+
+    /** Error message that is thrown if no river was found based on a given
+     * name.*/
+    public static final String ERROR_NO_SUCH_RIVER =
+        "error_feed_no_such_river";
+
+    /** Error message that is thrown if no river was found based on a given
+     * name.*/
+    public static final String ERROR_NO_RIVER_SELECTED =
+        "error_feed_no_river_selected";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public RiverSelect() {
+    }
+
+
+    /**
+     * Initialize the state based on the state node in the configuration.
+     *
+     * @param config The state configuration node.
+     */
+    public void setup(Node config) {
+        super.setup(config);
+    }
+
+
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select",
+            new String[] { "uiprovider" },
+            new String[] { "select_with_map" });
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        Element choices = ProtocolUtils.createArtNode(
+            cr, "choices", null, null);
+
+        select.appendChild(label);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            getID(),
+            getID()));
+
+        return select;
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        List<River> rivers = RiverFactory.getRivers();
+        Element[] items    = new Element[rivers.size()];
+
+        int idx = 0;
+        for (River river: rivers) {
+            items[idx++] = createRiverItem(cr, river);
+        }
+
+        return items;
+    }
+
+
+    /**
+     * This method creates a node that represents a river item. This node
+     * contains the label and the value that describe the river.
+     *
+     * @param cr The ElementCreator.
+     * @param river The river.
+     *
+     * @return the element that contains the information about the river.
+     */
+    protected Element createRiverItem(XMLUtils.ElementCreator cr, River river) {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        label.setTextContent(river.getName());
+        value.setTextContent(river.getName());
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        return item;
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("RiverSelect.validate");
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData dRiver = getData(flys, "river");
+
+        if (dRiver == null || dRiver.getValue() == null) {
+            throw new IllegalArgumentException(ERROR_NO_RIVER_SELECTED);
+        }
+
+        River river = RiverFactory.getRiver((String) dRiver.getValue());
+
+        if (river == null) {
+            throw new IllegalArgumentException(ERROR_NO_SUCH_RIVER);
+        }
+
+        return true;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "river_panel";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/ScenarioSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,157 @@
+package de.intevation.flys.artifacts.states;
+
+import java.io.File;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+import de.intevation.artifacts.common.utils.FileTools;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.utils.FLYSUtils;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ScenarioSelect extends DefaultState {
+
+    /** The logger that is used in this class.*/
+    private static Logger logger = Logger.getLogger(ScenarioSelect.class);
+
+
+    public static final String FIELD_MODE     = "scenario";
+    public static final String FIELD_BARRIERS = "uesk.barriers";
+
+    public static final String SCENARIO_CURRENT   = "scenario.current";
+    public static final String SCENARIO_POTENTIEL = "scenario.potentiel";
+    public static final String SCENARIO_SCENRAIO  = "scenario.scenario";
+
+    public static final String[] SCENARIOS = {
+        SCENARIO_CURRENT,
+        SCENARIO_POTENTIEL,
+        SCENARIO_SCENRAIO };
+
+
+
+    @Override
+    protected String getUIProvider() {
+        return "map_digitize";
+    }
+
+
+    @Override
+    protected void appendStaticData(
+        FLYSArtifact   flys,
+        CallContext    cc,
+        ElementCreator creator,
+        Element        ui,
+        String         name
+    ) {
+        if (name != null && name.equals(FIELD_BARRIERS)) {
+            return;
+        }
+        else {
+            super.appendStaticData(flys, cc, creator, ui, name);
+        }
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        CallMeta meta = context.getMeta();
+
+        if (name.equals(FIELD_MODE)) {
+            Element[] scenarios = new Element[SCENARIOS.length];
+
+            int i = 0;
+
+            for (String scenario: SCENARIOS) {
+                scenarios[i++] = createItem(
+                    cr, new String[] {
+                        Resources.getMsg(meta, scenario, scenario),
+                        scenario
+                    });
+            }
+
+            return scenarios;
+        }
+        else {
+            FLYSArtifact flys = (FLYSArtifact) artifact;
+            String       data = flys.getDataAsString(name);
+
+            return new Element[] { createItem(
+                cr,
+                new String[] {
+                    Resources.getMsg(meta, name, name),
+                    data
+                }
+            )};
+        }
+    }
+
+
+    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;
+    }
+
+
+
+    @Override
+    public void endOfLife(Artifact artifact, Object callContext) {
+        super.endOfLife(artifact, callContext);
+        logger.info("ScenarioSelect.endOfLife: " + artifact.identifier());
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+        removeDirectory(flys);
+    }
+
+
+    /**
+     * Removes the directory and all its content where the required data and the
+     * results of WSPLGEN are stored. Should be called in endOfLife().
+     */
+    protected void removeDirectory(FLYSArtifact artifact) {
+        String shapePath = FLYSUtils.getXPathString(
+            FLYSUtils.XPATH_SHAPEFILE_DIR);
+
+        File artifactDir = new File(shapePath, artifact.identifier());
+
+        if (artifactDir.exists()) {
+            logger.info("Delete directory: " + artifactDir.getAbsolutePath());
+            boolean success = FileTools.deleteRecursive(artifactDir);
+        }
+        else {
+            logger.debug("There is no directory to remove.");
+        }
+    }
+
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/SoundingsSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,198 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.model.KVP;
+
+import de.intevation.flys.model.BedHeightEpoch;
+import de.intevation.flys.model.BedHeightSingle;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class SoundingsSelect extends MultiStringArrayState {
+
+    public static final String SOUNDINGS  = "soundings";
+
+    public static final String PREFIX_SINGLE = "single-";
+
+    public static final String PREFIX_EPOCH  = "epoch-";
+
+
+    private static final Logger logger = Logger.getLogger(SoundingsSelect.class);
+
+
+    @Override
+    public String getUIProvider() {
+        return "parameter-matrix";
+    }
+
+
+    @Override
+    protected KVP<String, String>[] getOptions(
+        Artifact artifact,
+        String   parameterName
+    )
+    throws IllegalArgumentException
+    {
+        logger.debug("Get options for parameter: '" + parameterName + "'");
+
+        if (!testParameterName(parameterName)) {
+            throw new IllegalArgumentException(
+                "Invalid parameter for state: '" + parameterName + "'");
+        }
+
+        River river = FLYSUtils.getRiver((FLYSArtifact) artifact);
+        double lo = ((FLYSArtifact) artifact).getDataAsDouble("ld_from");
+        double hi = ((FLYSArtifact) artifact).getDataAsDouble("ld_to");
+
+        double kmLo = Math.min(lo, hi);
+        double kmHi = Math.max(lo, hi);
+
+        List<KVP<String, String>> kvp = new ArrayList<KVP<String, String>>();
+
+        appendSingles(river, kmLo, kmHi, kvp);
+        appendEpochs(river, kmLo, kmHi, kvp);
+
+        return (KVP<String,String>[]) kvp.toArray(new KVP[kvp.size()]);
+    }
+
+
+    protected void appendSingles(
+        River river,
+        double kmLo,
+        double kmHi,
+        List<KVP<String, String>> kvp
+    ) {
+        List<BedHeightSingle> singles =
+            BedHeightSingle.getBedHeightSingles(river, kmLo, kmHi);
+
+        if (singles != null) {
+            for (int i = 0; i < singles.size(); i++) {
+                BedHeightSingle s = singles.get(i);
+
+                String id    = PREFIX_SINGLE + s.getId();
+                String value = s.getDescription();
+
+                kvp.add(new KVP(id, value));
+            }
+        }
+    }
+
+
+    protected void appendEpochs(
+        River river,
+        double kmLo,
+        double kmHi,
+        List<KVP<String, String>> kvp
+    ) {
+        List<BedHeightEpoch> epochs =
+            BedHeightEpoch.getBedHeightEpochs(river, kmLo, kmHi);
+
+        if (epochs != null) {
+            for (int i = 0; i < epochs.size(); i++) {
+                BedHeightEpoch e = epochs.get(i);
+
+                String id    = PREFIX_EPOCH + e.getId();
+                String value = e.getDescription();
+
+                kvp.add(new KVP(id, value));
+            }
+        }
+    }
+
+
+    @Override
+    protected String getLabelFor(
+        CallContext cc,
+        String      parameterName,
+        String      value
+    ) throws IllegalArgumentException
+    {
+        if (!testParameterName(parameterName)) {
+            throw new IllegalArgumentException(
+                "Invalid parameter for state: '" + parameterName + "'");
+        }
+
+        if (value.indexOf(PREFIX_SINGLE) >= 0) {
+            return getLabelForSingle(cc, value);
+        }
+        else if (value.indexOf(PREFIX_EPOCH) >= 0) {
+            return getLabelForEpoch(cc, value);
+        }
+
+        return value;
+    }
+
+
+    protected String getLabelForSingle(CallContext cc, String value) {
+        String id = value.replace(PREFIX_SINGLE, "");
+        try {
+            BedHeightSingle s = BedHeightSingle.getBedHeightSingleById(
+                Integer.parseInt(id));
+
+            if (s != null) {
+                return s.getDescription();
+            }
+            else {
+                return "no value for '" + id + "'";
+            }
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Could not parse id from string '" + id + "'", nfe);
+        }
+
+        return "n.A.";
+    }
+
+
+    protected String getLabelForEpoch(CallContext cc, String value) {
+        String id = value.replace(PREFIX_EPOCH, "");
+        try {
+            BedHeightEpoch e = BedHeightEpoch.getBedHeightEpochById(
+                Integer.parseInt(id));
+
+            if (e != null) {
+                return e.getDescription();
+            }
+            else {
+                return "no value for '" + id + "'";
+            }
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Could not parse id from string '" + id + "'", nfe);
+        }
+
+        return "n.A.";
+    }
+
+
+    /**
+     * This method might be used to test, if a parameter name is handled by this
+     * state.
+     *
+     * @param parameterName The name of a parameter.
+     *
+     * @return true, if parameterName is one of <i>MAIN_CHANNEL</i> or
+     * <i>TOTAL_CHANNEL</i>. Otherwise false.
+     */
+    protected boolean testParameterName(String parameterName) {
+        if (parameterName == null || parameterName.length() == 0) {
+            return false;
+        }
+        else if (parameterName.equals(SOUNDINGS)) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/StateFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,122 @@
+package de.intevation.flys.artifacts.states;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class StateFactory {
+
+    /** The logger used in this class */
+    private static Logger logger = Logger.getLogger(StateFactory.class);
+
+    /** The XPath to the classname of the state */
+    public static final String XPATH_STATE = "@state";
+
+    /** The XPath to the data items of the state relative to the state node. */
+    public static final String XPATH_DATA = "data";
+
+    /** The XPath to the data name relative to the data node.*/
+    public static final String XPATH_DATA_NAME = "@name";
+
+    /** The XPath to the data type relative to the data node.*/
+    public static final String XPATH_DATA_TYPE = "@type";
+
+    /** The XPath to the data description relative to the data node.*/
+    public static final String XPATH_DATA_DESCRIPTION = "@description";
+
+
+    /**
+     * Creates a new State based on the configured class provided by
+     * <code>stateConf</code>.
+     *
+     * @param stateConf The configuration of the state.
+     *
+     * @return a State.
+     */
+    public static State createState(Node stateConf) {
+        String clazz = (String) XMLUtils.xpath(
+            stateConf, XPATH_STATE, XPathConstants.STRING);
+
+        State state = null;
+
+        try {
+            logger.debug("Create a new State for class: " + clazz);
+            state = (State) Class.forName(clazz).newInstance();
+            state.setup(stateConf);
+
+            initializeStateData(state, stateConf);
+        }
+        catch (InstantiationException ie) {
+            logger.error(ie, ie);
+        }
+        catch (IllegalAccessException iae) {
+            logger.error(iae, iae);
+        }
+        catch (ClassNotFoundException cnfe) {
+            logger.error(cnfe, cnfe);
+        }
+
+        return state;
+    }
+
+
+    /**
+     * This method extracts the configured input data of a state and adds new
+     * StateData objects to the State.
+     *
+     * @param state The state.
+     * @param stateConf The state configuration node.
+     */
+    protected static void initializeStateData(State state, Node stateConf) {
+        NodeList dataList = (NodeList) XMLUtils.xpath(
+            stateConf, XPATH_DATA, XPathConstants.NODESET);
+
+        if (dataList == null || dataList.getLength() == 0) {
+            logger.debug("The state has no input data configured.");
+
+            return;
+        }
+
+        int items = dataList.getLength();
+
+        logger.debug("The state has " + items + " data items configured.");
+
+        for (int i = 0; i < items; i++) {
+            Node data = dataList.item(i);
+
+            String name = (String) XMLUtils.xpath(
+                data, XPATH_DATA_NAME, XPathConstants.STRING);
+            String type = (String) XMLUtils.xpath(
+                data, XPATH_DATA_TYPE, XPathConstants.STRING);
+            String desc = (String) XMLUtils.xpath(
+                data, XPATH_DATA_DESCRIPTION, XPathConstants.STRING);
+
+            if (name == null || name.equals("")) {
+                logger.warn("No name for data item at pos " + i + " found.");
+                continue;
+            }
+
+            if (type == null || type.equals("")) {
+                logger.warn("No type for data item at pos " + i + " found.");
+                logger.warn("Default type 'string' used.");
+                type = "string";
+            }
+
+            logger.debug("add StateData '" + name + "' (type '" + type + "')");
+            state.addData(name, new DefaultStateData(name, desc, type));
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/StaticHYKState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,110 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.HYKArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.HYKFacet;
+import de.intevation.flys.artifacts.model.HYKFactory;
+
+/**
+ * Only state of a HYKArtifact.
+ */
+public class StaticHYKState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    transient private static final Logger logger = Logger.getLogger(StaticHYKState.class);
+
+
+    /**
+     * From this state can not be continued.
+     */
+    @Override
+    protected String getUIProvider() {
+        return "noinput";
+    }
+
+
+    /**
+     * Compute, create Facets, do the same stuff as all the other states do.
+     */
+    protected Object compute(
+        HYKArtifact   hyk,
+        CallMeta      metaLocale,
+        String        hash,
+        List<Facet>   facets,
+        Object        old
+    ) {
+        logger.debug("StaticHYKState.compute");
+        String id = getID();
+
+        // Prepare comparison against cached result.
+        List<HYKFactory.Zone> resZones = old instanceof List
+            ? (List<HYKFactory.Zone>)old
+            : null;
+
+        // TODO Compare against cached object.
+
+        // Get Zones from HYKFactory
+        List<HYKFactory.Zone> zones = (List<HYKFactory.Zone>)
+            HYKFactory.getHYKs(hyk.getHykId(), hyk.getKm());
+
+        if (facets == null) {
+            logger.debug("StaticHYKState.compute no facets");
+            return zones;
+        }
+
+        // Spawn Facets.
+        Facet facet = new HYKFacet(0, HYKFactory.getHykName(hyk.getHykId()));
+        facets.add(facet);
+
+        return zones;
+    }
+
+
+    /**
+     * Get data, create the facets.
+     *
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return compute((HYKArtifact) artifact, context.getMeta(),
+            hash, facets, old);
+    }
+
+
+    /**
+     * Create the facets.
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        return compute((HYKArtifact) artifact, meta, hash, facets,
+            null);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/StaticState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,111 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.DefaultOutput;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+/**
+ * Yet, a non-abstract DefaultState.
+ */
+public class StaticState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static final Logger logger = Logger.getLogger(StaticState.class);
+
+
+    /**
+     * Trivial constructor, sets id and description.
+     * @param id String used for both id and description.
+     */
+    public StaticState(String id) {
+        this(id, id);
+    }
+
+
+    public StaticState(String id, String description) {
+        super();
+        setID(id);
+        setDescription(description);
+    }
+
+    public void addDefaultChartOutput(String nameDesc, List<Facet> facets) {
+        DefaultOutput output = new DefaultOutput(nameDesc,
+            nameDesc, "image/png", facets, "chart");
+        getOutputs().add(output);
+    }
+
+    public static void addDefaultChartOutput(
+        DefaultState state,
+        String nameDesc,
+        List<Facet> facets
+    ) {
+        DefaultOutput output = new DefaultOutput(nameDesc,
+            nameDesc, "image/png", facets, "chart");
+        state.getOutputs().add(output);
+    }
+
+
+    /**
+     * Do nothing (override to include your logic).
+     * @param facets List of facets (to add to).
+     */
+    public Object staticCompute(List<Facet> facets, FLYSArtifact artifact) {
+        return staticCompute(facets);
+    }
+
+    public Object staticCompute(List<Facet> facets) {
+        return null;
+    }
+
+
+    /** Call staticCompute to allow easy adjustments. */
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return staticCompute(facets, artifact);
+    }
+
+
+    /** Call staticCompute to allow easy adjustments. */
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return staticCompute(facets, artifact);
+    }
+
+
+    /** Call staticCompute to allow easy adjustments. */
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        return staticCompute(facets, artifact);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/StaticWQKmsState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,136 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.StaticWQKmsArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WQKmsFacet;
+
+/**
+ * Only state of WQKmsArtifact.
+ */
+public class StaticWQKmsState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(StaticWQKmsState.class);
+
+
+    /**
+     * From this state can not be continued.
+     */
+    @Override
+    protected String getUIProvider() {
+        return "noinput";
+    }
+
+
+    /**
+     * Compute, create Facets, do the same stuff as all the other states do.
+     */
+    protected Object compute(
+        StaticWQKmsArtifact winfo,
+        CallMeta      metaLocale,
+        String        hash,
+        List<Facet>   facets,
+        Object        old
+    ) {
+        String id = getID();
+
+        WQKms res = old instanceof WQKms
+            ? (WQKms)old
+            : winfo.getWQKms();
+
+        WQKms wqkms = res;
+
+        if (facets == null) {
+            return res;
+        }
+
+        /*
+         * TODO: re-enable HEIGHTMARKS_POINTS-thing
+
+           String name;
+           if (parts[0].equals(HEIGHTMARKS_POINTS)) {
+               name = HEIGHTMARKS_POINTS;
+           }
+           else {
+               name = STATIC_WQKMS;
+           }
+        */
+
+        String wkmsName = wqkms.getName();
+        Facet qfacet = new WQKmsFacet(
+            STATIC_WQKMS_Q,
+            wkmsName
+            // TODO re-enable translations.
+            /*
+            Resources.getMsg(
+                metaLocale,
+                wkmsName,
+                wkmsName)*/);
+        facets.add(qfacet);
+
+        wkmsName = "W (" + wkmsName + ")";
+
+        Facet wfacet = new WQKmsFacet(
+            STATIC_WQKMS_W,
+            wkmsName
+            /*
+            // TODO re-enable translations.
+            Resources.getMsg(
+                metaLocale,
+                wkmsName,
+                wkmsName)*/);
+        facets.add(wfacet);
+
+        return res;
+    }
+
+
+    /**
+     * Get data, create the facets.
+     *
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return compute((StaticWQKmsArtifact) artifact, context.getMeta(),
+            hash, facets, old);
+    }
+
+
+    /**
+     * Create the facets.
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        return compute((StaticWQKmsArtifact) artifact, meta, hash, facets,
+            null);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WDifferencesState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,188 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.Artifact;
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.StaticWKmsArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.ChartArtifact;
+
+import de.intevation.flys.artifacts.math.WKmsOperation;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.DifferenceCurveFacet;
+import de.intevation.flys.artifacts.model.EmptyFacet;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.StringUtil;
+
+
+public class WDifferencesState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(WDifferencesState.class);
+
+
+    public WDifferencesState() {
+    }
+
+
+    /** Specify to display nothing (this is kind of a "final" state). */
+    @Override
+    protected String getUIProvider() {
+        return "noinput";
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData data = flys.getData("diffids");
+
+        if (data == null) {
+            throw new IllegalArgumentException("diffids is empty");
+        }
+
+        // TODO: Also validate format.
+
+        return true;
+    }
+
+
+    protected WKms getWKms(String mingle, CallContext context) {
+        String[] def  = mingle.split(";");
+        String   uuid = def[0];
+        String   name = def[1];
+        int      idx  = Integer.parseInt(def[2]);
+
+        if (name.startsWith("staticwkms")) {
+            StaticWKmsArtifact staticWKms =
+                (StaticWKmsArtifact) FLYSUtils.getArtifact(
+                    uuid,
+                    context);
+            logger.debug("WDifferencesState obtain data from StaticWKms");
+            WKms wkms = staticWKms.getWKms(idx);
+            if (wkms == null)
+                logger.error("No WKms from artifact.");
+            return wkms;
+        }
+    
+        WINFOArtifact flys = (WINFOArtifact) FLYSUtils.getArtifact(
+            uuid,
+            context);
+
+        if (flys == null) {
+            logger.warn("One of the artifacts (1) for diff calculation could not be loaded");
+            return null;
+        }
+        else{
+            WQKms[] wqkms = (WQKms[]) flys.getWaterlevelData().
+                                              getData();
+            if (wqkms == null)
+            logger.warn("not  waterlevels in artifact");
+            else if (wqkms.length < idx)
+            logger.warn("not enough waterlevels in artifact");
+            return wqkms[idx];
+        }
+    }
+
+
+    /**
+     * Return CalculationResult with Array of WKms that are difference of
+     * Waterlevels. Add respective facets (DifferencesCurveFacet, DataFacet).
+     */
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        if (artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        String id = getID();
+
+        // Load the Artifacts/facets that we want to subtract and display.
+        // Expected format is:
+        // [42537f1e-3522-42ef-8968-635b03d8e9c6;longitudinal_section.w;0]#[1231f2-....]
+        String diffids = winfo.getDataAsString("diffids");
+        logger.debug("WDifferencesState has: " + diffids);
+        String datas[] = diffids.split("#");
+
+        // Validate the Data-Strings.
+        for (String s: datas) {
+            if (!WaterlevelSelectState.isValueValid(winfo.getDataAsString("diffids"))) {
+                // TODO: escalate.
+            }
+        }
+
+        if (datas.length < 2) {
+            // TODO crash with style
+        }
+
+        List<WKms> wkmss = new ArrayList<WKms>();
+
+        for(int i = 0; i < datas.length; i+=2) {
+            // e.g.:
+            // 42537f1e-3522-42ef-8968-635b03d8e9c6;longitudinal_section.w;1
+            WKms minuendWKms = getWKms(StringUtil.unbracket(datas[i+0]),
+                context);
+            WKms subtrahendWKms = getWKms(StringUtil.unbracket(datas[i+1]),
+                context);
+
+            String facetName = "diff ()";
+
+            if (minuendWKms != null && subtrahendWKms != null) {
+                facetName = StringUtil.wWrap(minuendWKms.getName()) 
+                    + " - " + StringUtil.wWrap(subtrahendWKms.getName());
+                WKms wkms = WKmsOperation.SUBTRACTION.operate(minuendWKms,
+                     subtrahendWKms);
+                wkms.setName(facetName);
+                wkmss.add(wkms);
+                logger.debug("WKMSSubtraction happened");
+            }
+            if (facets != null) {
+                facets.add(new DifferenceCurveFacet(i/2, W_DIFFERENCES, facetName,
+                    ComputeType.ADVANCE, id, hash));
+            }
+        }
+
+        if (facets != null) {
+            facets.add(new DataFacet(CSV, "CSV data"));
+            facets.add(new DataFacet(PDF, "PDF data"));
+            logger.debug("Adding facets in WDifferencesState.");
+        }
+        else {
+            logger.debug("Not adding facets in WDifferencesState.");
+        }
+
+        // TODO Evaluate whether null is okay as reports.
+        WKms[] diffs = wkmss.toArray(new WKms[wkmss.size()]);
+        CalculationResult result = new CalculationResult(diffs, null);
+        return result;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WMSBackgroundState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,161 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.Config;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.WMSLayerFacet;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+
+
+public class WMSBackgroundState extends OutputState {
+
+    public static final String I18N_DESCRIPTION = "floodmap.wmsbackground";
+
+    public static final String XPATH_SRID =
+        "/artifact-database/floodmap/river[@name=$name]/srid/@value";
+
+    public static final String XPATH_WMS_URL =
+        "/artifact-database/floodmap/river[@name=$name]/background-wms/@url";
+
+    public static final String XPATH_WMS_LAYER =
+        "/artifact-database/floodmap/river[@name=$name]/background-wms/@layers";
+
+
+    protected String url;
+    protected String layer;
+    protected String srid;
+
+    protected Document cfg;
+
+    protected Map<String, String> variables;
+
+
+    private static final Logger logger = Logger.getLogger(WMSBackgroundState.class);
+
+
+    @Override
+    public void setup(Node config) {
+        super.setup(config);
+
+        logger.debug("WMSBackgroundState.setup()");
+    }
+
+
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        logger.debug("WMSBackgroundState.computeInit()");
+
+        initVariables(artifact);
+
+        if (url == null || layer == null) {
+            // XXX I don't remember why 'srid', 'url' and 'layer' are member
+            // variables. I think the reason was buffering those values.
+            srid  = getSrid();
+            url   = getUrl();
+            layer = getLayer();
+        }
+
+        if (url == null || layer == null) {
+            logger.warn("No background layers currently configured:");
+            logger.warn("... add config for WMS url: " + XPATH_WMS_URL);
+            logger.warn("... add config for WMS layer: " + XPATH_WMS_LAYER);
+            return null;
+        }
+
+        WMSLayerFacet facet = new WMSLayerFacet(
+            0,
+            getFacetType(),
+            getTitle(meta),
+            ComputeType.INIT,
+            getID(), hash,
+            url);
+
+        facet.addLayer(layer);
+        facet.setSrid(srid);
+
+        facets.add(facet);
+
+        return null;
+    }
+
+
+    protected Document getConfig() {
+        if (cfg == null) {
+            cfg = Config.getConfig();
+        }
+
+        return cfg;
+    }
+
+
+    protected void initVariables(FLYSArtifact artifact) {
+        String river = artifact.getDataAsString("river");
+
+        variables = new HashMap<String, String>();
+        variables.put("name", river);
+    }
+
+
+    protected String getFacetType() {
+        return FLOODMAP_WMSBACKGROUND;
+    }
+
+
+    protected String getSrid() {
+        return (String) XMLUtils.xpath(
+            getConfig(),
+            XPATH_SRID,
+            XPathConstants.STRING,
+            null,
+            variables);
+    }
+
+
+    protected String getUrl() {
+        return (String) XMLUtils.xpath(
+            getConfig(),
+            XPATH_WMS_URL,
+            XPathConstants.STRING,
+            null,
+            variables);
+    }
+
+
+    protected String getLayer() {
+        return (String) XMLUtils.xpath(
+            getConfig(),
+            XPATH_WMS_LAYER,
+            XPathConstants.STRING,
+            null,
+            variables);
+    }
+
+
+    protected String getTitle(CallMeta meta) {
+        return Resources.getMsg(meta, I18N_DESCRIPTION, I18N_DESCRIPTION);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,459 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Comparator;
+import java.util.Collections;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.Range;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.RangeWithValues;
+import de.intevation.flys.artifacts.model.WstFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQAdapted extends DefaultState {
+
+    /** The logger used in this state.*/
+    private static Logger logger = Logger.getLogger(WQAdapted.class);
+
+
+    public static final String FIELD_WQ_MODE = "wq_isq";
+
+    public static final String FIELD_WQ_VALUES = "wq_values";
+
+    public static final class GaugeOrder implements Comparator<Gauge> {
+        private int order;
+
+        public GaugeOrder(boolean up) {
+            order = up ? 1 : -1;
+        }
+
+        public int compare(Gauge a, Gauge b) {
+            return order * a.getRange().getA().compareTo(b.getRange().getA());
+        }
+    } // class GaugeOrder
+
+    public static final GaugeOrder GAUGE_UP   = new GaugeOrder(true);
+    public static final GaugeOrder GAUGE_DOWN = new GaugeOrder(false);
+
+    public WQAdapted() {
+    }
+
+    /**
+     * This method creates one element for each gauge of the selected river that
+     * is intersected by the given kilometer range. Each element is a tuple of
+     * (from;to) where <i>from</i> is the lower bounds of the gauge or the lower
+     * kilometer range. <i>to</i> is the upper bounds of the gauge or the upper
+     * kilometer range.
+     *
+     * @param cr The ElementCreator.
+     * @param artifact The FLYS artifact.
+     * @param name The name of the data item.
+     * @param context The CallContext.
+     *
+     * @return a list of elements that consist of tuples of the intersected
+     * gauges of the selected river.
+     */
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WQAdapted.createItems");
+
+        if (name != null && name.equals(FIELD_WQ_MODE)) {
+            return createModeItems(cr, artifact, name, context);
+        }
+        else if (name != null && name.equals(FIELD_WQ_VALUES)) {
+            return createValueItems(cr, artifact, name, context);
+        }
+        else {
+            logger.warn("Unknown data object: " + name);
+            return null;
+        }
+    }
+
+
+    protected Element[] createModeItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WQAdapted.createModeItems");
+
+        Element w = createItem(cr, new String[] { "w", "W" });
+        Element q = createItem(cr, new String[] { "q", "Q" });
+
+        return new Element[] { w, q };
+    }
+
+
+    protected Element[] createValueItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("WQAdapted.createValueItems");
+
+        WINFOArtifact flysArtifact = (WINFOArtifact) artifact;
+
+        double[]    dist   = FLYSUtils.getKmRange(flysArtifact);
+        River       river  = FLYSUtils.getRiver(flysArtifact);
+        Wst         wst    = WstFactory.getWst(river);
+        List<Gauge> gauges = flysArtifact.getGauges();
+
+        int num = gauges != null ? gauges.size() : 0;
+
+        if (num == 0) {
+            logger.warn("Selected distance matches no gauges.");
+            return null;
+        }
+
+        Element[] elements = new Element[num];
+
+        double rangeFrom = dist[0];
+        double rangeTo   = dist[1];
+
+        int idx = 0;
+
+        if (rangeFrom < rangeTo) {
+            Collections.sort(gauges, GAUGE_UP);
+            for (Gauge gauge: gauges) {
+                Range range = gauge.getRange();
+                double lower = range.getA().doubleValue();
+                double upper = range.getB().doubleValue();
+
+                double from = lower < rangeFrom ? rangeFrom : lower;
+                double to   = upper > rangeTo   ? rangeTo   : upper;
+
+                double[] mmQ = determineMinMaxQ(gauge, wst);
+                double[] mmW = gauge.determineMinMaxW();
+
+                elements[idx++] = createItem(
+                    cr, new String[] { from + ";" + to, ""}, mmQ, mmW);
+            }
+        }
+        else {
+            Collections.sort(gauges, GAUGE_DOWN);
+            rangeFrom = dist[1];
+            rangeTo   = dist[0];
+            for (Gauge gauge: gauges) {
+                Range range = gauge.getRange();
+                double lower = range.getA().doubleValue();
+                double upper = range.getB().doubleValue();
+
+                double from = lower < rangeFrom ? rangeFrom : lower;
+                double to   = upper > rangeTo   ? rangeTo   : upper;
+
+                double[] mmQ = determineMinMaxQ(gauge, wst);
+                double[] mmW = gauge.determineMinMaxW();
+
+                elements[idx++] = createItem(
+                    cr, new String[] { to + ";" + from, ""}, mmQ, mmW);
+            }
+        }
+
+        return elements;
+    }
+
+
+    protected Element createItem(XMLUtils.ElementCreator cr, Object obj) {
+        return createItem(cr, obj, null, null);
+    }
+
+
+    protected Element createItem(
+        XMLUtils.ElementCreator cr,
+        Object   obj,
+        double[] q,
+        double[] w)
+    {
+        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);
+
+        if (q != null) {
+            Element qRange = createRangeElement(cr, q, "Q");
+            item.appendChild(qRange);
+        }
+
+        if (w != null) {
+            Element wRange = createRangeElement(cr, w, "W");
+            item.appendChild(wRange);
+        }
+
+        return item;
+    }
+
+
+    protected Element createRangeElement(
+        XMLUtils.ElementCreator cr,
+        double[] mm,
+        String   type)
+    {
+        Element range = ProtocolUtils.createArtNode(
+            cr, "range",
+            new String[] {"type"},
+            new String[] {type});
+
+        Element min = ProtocolUtils.createArtNode(cr, "min", null, null);
+        min.setTextContent(String.valueOf(mm[0]));
+
+        Element max = ProtocolUtils.createArtNode(cr, "max", null, null);
+        max.setTextContent(String.valueOf(mm[1]));
+
+        range.appendChild(min);
+        range.appendChild(max);
+
+        return range;
+    }
+
+
+    /**
+     * Determines the min and max Q value for the given gauge. If no min and
+     * max values could be determined, this method will return
+     * [Double.MIN_VALUE, Double.MAX_VALUE].
+     *
+     * @param gauge
+     * @param wst
+     *
+     * @return the min and max Q values for the given gauge.
+     */
+    protected double[] determineMinMaxQ(Gauge gauge, Wst wst) {
+        logger.debug("WQAdapted.determineMinMaxQ");
+
+        double[] minmaxQ = gauge != null
+            ? wst.determineMinMaxQ(gauge.getRange())
+            : null;
+
+        double minQ = minmaxQ != null ? minmaxQ[0] : Double.MIN_VALUE;
+        double maxQ = minmaxQ != null ? minmaxQ[1] : Double.MAX_VALUE;
+
+        return new double[] { minQ, maxQ };
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "wq_panel_adapted";
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQAdapted.validate");
+
+        WINFOArtifact flys = (WINFOArtifact) artifact;
+        StateData    data = getData(flys, FIELD_WQ_MODE);
+
+        String mode = data != null ? (String) data.getValue() : null;
+        boolean isQ = mode != null
+            ? Boolean.valueOf(mode)
+            : false;
+
+        if (!isQ) {
+            return validateW(artifact);
+        }
+        else if (isQ) {
+            return validateQ(artifact);
+        }
+        else {
+            throw new IllegalArgumentException("error_feed_no_wq_mode_selected");
+        }
+    }
+
+
+    protected boolean validateW(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQAdapted.validateW");
+        WINFOArtifact flys = (WINFOArtifact) artifact;
+
+        RangeWithValues[] rwvs = extractInput(getData(flys, "wq_values"));
+
+        if (rwvs == null) {
+            throw new IllegalArgumentException("error_missing_wq_data");
+        }
+
+        List<Gauge>     gauges = ((WINFOArtifact) artifact).getGauges();
+
+        for (Gauge gauge: gauges) {
+            Range range  = gauge.getRange();
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            for (RangeWithValues rwv: rwvs) {
+                if (lower <= rwv.getLower() && upper >= rwv.getUpper()) {
+                    compareWsWithGauge(gauge, rwv.getValues());
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    protected boolean validateQ(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQAdapted.validateQ");
+        WINFOArtifact flys = (WINFOArtifact) artifact;
+
+        RangeWithValues[] rwvs = extractInput(getData(flys, "wq_values"));
+
+        if (rwvs == null) {
+            throw new IllegalArgumentException("error_missing_wq_data");
+        }
+
+        List<Gauge> gauges = flys.getGauges();
+        River        river = FLYSUtils.getRiver(flys);
+        Wst            wst = WstFactory.getWst(river);
+
+        for (Gauge gauge: gauges) {
+            Range range  = gauge.getRange();
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            for (RangeWithValues rwv: rwvs) {
+                if (lower <= rwv.getLower() && upper >= rwv.getUpper()) {
+                    compareQsWithGauge(wst, gauge, rwv.getValues());
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    protected boolean compareQsWithGauge(Wst wst, Gauge gauge, double[] qs)
+    throws IllegalArgumentException
+    {
+        double[] minmax = gauge != null
+            ? wst.determineMinMaxQ(gauge.getRange())
+            : null;
+
+        if (minmax == null) {
+            logger.warn("Could not determine min/max Q of gauge.");
+            return true;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Validate Qs with:");
+            logger.debug("-- Gauge: " + gauge.getName());
+            logger.debug("-- Gauge min: " + minmax[0]);
+            logger.debug("-- Gauge max: " + minmax[1]);
+        }
+
+        for (double q: qs) {
+            if (q < minmax[0] || q > minmax[1]) {
+                throw new IllegalArgumentException(
+                    "error_feed_q_values_invalid");
+            }
+        }
+
+        return true;
+    }
+
+
+    protected boolean compareWsWithGauge(Gauge gauge, double[] ws)
+    throws IllegalArgumentException
+    {
+        double[] minmax = gauge != null
+            ? gauge.determineMinMaxW()
+            : null;
+
+        if (minmax == null) {
+            logger.warn("Could not determine min/max W of gauge.");
+            return true;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Validate Ws with:");
+            logger.debug("-- Gauge: " + gauge.getName());
+            logger.debug("-- Gauge min: " + minmax[0]);
+            logger.debug("-- Gauge max: " + minmax[1]);
+        }
+
+        for (double w: ws) {
+            if (w < minmax[0] || w > minmax[1]) {
+                throw new IllegalArgumentException(
+                    "error_feed_w_values_invalid");
+            }
+        }
+
+        return true;
+    }
+
+
+    protected RangeWithValues[] extractInput(StateData data) {
+        if (data == null) {
+            return null;
+        }
+
+        String dataString = (String) data.getValue();
+        String[]   ranges = dataString.split(":");
+
+        List<RangeWithValues> rwv = new ArrayList<RangeWithValues>();
+
+        for (String range: ranges) {
+            String[] parts = range.split(";");
+
+            double lower = Double.parseDouble(parts[0]);
+            double upper = Double.parseDouble(parts[1]);
+
+            String[] values = parts[2].split(",");
+
+            int      num = values.length;
+            double[] res = new double[num];
+
+            for (int i = 0; i < num; i++) {
+                try {
+                    res[i] = Double.parseDouble(values[i]);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn(nfe, nfe);
+                }
+            }
+
+            rwv.add(new RangeWithValues(lower, upper, res));
+        }
+
+        return (RangeWithValues[]) rwv.toArray(new RangeWithValues[rwv.size()]);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,764 @@
+package de.intevation.flys.artifacts.states;
+
+import java.text.NumberFormat;
+
+import gnu.trove.TDoubleArrayList;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.WstFactory;
+import de.intevation.flys.artifacts.model.WstValueTable;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WQSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(WQSelect.class);
+
+    /** The default step width for Qs. */
+    public static final String DEFAULT_STEP_Q = "50";
+
+    /** The default step width for Qs. */
+    public static final String DEFAULT_STEP_W = "30";
+
+    /** The max number of steps for Qs and Ws. */
+    public static final int MAX_STEPS = 30;
+
+    /** The name of the 'mode' field. */
+    public static final String WQ_MODE = "wq_isq";
+
+    /** Them name fo the 'free' field. */
+    public static final String WQ_FREE = "wq_isfree";
+
+    /** The name of the 'selection' field. */
+    public static final String WQ_SELECTION = "wq_isrange";
+
+    /** The name of the 'from' field. */
+    public static final String WQ_FROM = "wq_from";
+
+    /** The name of the 'to' field. */
+    public static final String WQ_TO = "wq_to";
+
+    /** The name of the 'step' field. */
+    public static final String WQ_STEP = "wq_step";
+
+    /** The name of the 'single' field. */
+    public static final String WQ_SINGLE = "wq_single";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public WQSelect() {
+    }
+
+
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        if (!name.equals(WQ_SINGLE)) {
+            return super.createStaticData(flys, creator, cc, name, value, type);
+        }
+
+        Boolean isQ = flys.getDataAsBoolean(WQ_MODE);
+        Boolean isFree = flys.getDataAsBoolean(WQ_FREE);
+
+        WINFOArtifact winfo = (WINFOArtifact) flys;
+
+        Element dataElement = creator.create("data");
+        creator.addAttr(dataElement, "name", name, true);
+        creator.addAttr(dataElement, "type", type, true);
+
+        Element itemElement = creator.create("item");
+        creator.addAttr(itemElement, "value", value, true);
+
+        String label;
+
+        if (!isQ || isFree) {
+            label = getLabel(winfo, cc, value);
+        }
+        else {
+            label = getSpecialLabel(winfo, cc, value);
+        }
+
+        creator.addAttr(itemElement, "label", label, true);
+
+        dataElement.appendChild(itemElement);
+
+        return dataElement;
+    }
+
+
+    protected static String getLabel(
+        WINFOArtifact winfo,
+        CallContext   cc,
+        String        raw
+    ) {
+        String[] values = raw.split(" ");
+
+        if (values.length < 1) {
+            return null;
+        }
+
+        StringBuilder label = new StringBuilder();
+
+        NumberFormat nf = NumberFormat.getInstance(
+            Resources.getLocale(cc.getMeta()));
+
+        for (String value: values) {
+            try {
+                double v = Double.parseDouble(value.trim());
+
+                String formatted = nf.format(v);
+
+                if (label.length() > 0) {
+                    label.append(';');
+                }
+                label.append(formatted);
+            }
+            catch (NumberFormatException nfe) {
+                // do nothing here
+            }
+        }
+
+        return label.toString();
+    }
+
+
+    protected static String getSpecialLabel(
+        WINFOArtifact winfo,
+        CallContext   cc,
+        String        raw
+    ) {
+        String[] values = raw.split(" ");
+
+        if (values.length < 1) {
+            return null;
+        }
+
+        NumberFormat nf = NumberFormat.getInstance(
+            Resources.getLocale(cc.getMeta()));
+
+        Gauge gauge = winfo.getGauge();
+
+        boolean debug = logger.isDebugEnabled();
+
+        StringBuilder label = new StringBuilder();
+
+        for (String value: values) {
+            try {
+                double v = Double.parseDouble(value.trim());
+
+                String tmp = nf.format(v);
+                String mv  = FLYSUtils.getNamedMainValue(gauge, v);
+
+                if (mv != null && mv.length() > 0) {
+                    tmp = mv + ": " + tmp;
+                    if (debug) {
+                        logger.debug("Add main value: '" + mv + "'");
+                    }
+                }
+                if (label.length() > 0) {
+                    label.append(';');
+                }
+                label.append(tmp);
+            }
+            catch (NumberFormatException nfe) {
+                // do nothing here
+            }
+        }
+
+        return label.toString();
+    }
+
+
+    @Override
+    protected Element createData(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        StateData   data,
+        CallContext context)
+    {
+        Element select = ProtocolUtils.createArtNode(
+            cr, "select", null, null);
+
+        cr.addAttr(select, "name", data.getName(), true);
+
+        Element label = ProtocolUtils.createArtNode(
+            cr, "label", null, null);
+
+        Element choices = ProtocolUtils.createArtNode(
+            cr, "choices", null, null);
+
+        label.setTextContent(Resources.getMsg(
+            context.getMeta(),
+            data.getName(),
+            data.getName()));
+
+        select.appendChild(label);
+
+        return select;
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        double[] minmaxW     = determineMinMaxW(artifact);
+        double[] minmaxWFree = determineMinMaxWFree(artifact);
+        double[] minmaxQ     = determineMinMaxQAtGauge(artifact);
+        double[] minmaxQFree = determineMinMaxQ(artifact);
+
+        if (name.equals("wq_from")) {
+            Element minW = createItem(cr, new String[] {
+                "minW",
+                String.valueOf(minmaxW[0])});
+
+            Element minQ = createItem(cr, new String[] {
+                "minQ",
+                String.valueOf(minmaxQ[0])});
+
+            Element minQFree = createItem(cr, new String[] {
+                "minQFree",
+                String.valueOf(minmaxQFree[0])});
+
+            Element minWFree = createItem(cr, new String[] {
+                "minWFree",
+                String.valueOf(minmaxWFree[0])});
+
+            return new Element[] { minW, minQ, minQFree, minWFree };
+        }
+        else if (name.equals("wq_to")) {
+            Element maxW = createItem(cr, new String[] {
+                "maxW",
+                String.valueOf(minmaxW[1])});
+
+            Element maxQ = createItem(cr, new String[] {
+                "maxQ",
+                String.valueOf(minmaxQ[1])});
+
+            Element maxQFree = createItem(cr, new String[] {
+                "maxQFree",
+                String.valueOf(minmaxQFree[1])});
+
+            Element maxWFree = createItem(cr, new String[] {
+                "maxWFree",
+                String.valueOf(minmaxWFree[1])});
+
+            return new Element[] { maxW, maxQ, maxQFree, maxWFree };
+        }
+        else {
+            Element stepW = createItem(
+                cr, new String[] {
+                    "stepW",
+                    String.valueOf(getStepsW(minmaxW[0], minmaxW[1]))});
+            Element stepQ = createItem(
+                cr, new String[] {
+                    "stepQ",
+                    String.valueOf(getStepsQ(minmaxQ[0], minmaxQ[1]))});
+            Element stepQFree = createItem(
+                cr, new String[] {
+                    "stepQFree",
+                    String.valueOf(getStepsQ(minmaxQFree[0], minmaxQFree[1]))});
+            Element stepWFree = createItem(
+                cr, new String[] {
+                    "stepWFree",
+                    String.valueOf(getStepsW(minmaxWFree[0], minmaxWFree[1]))});
+
+            return new Element[] { stepW, stepQ, stepQFree, stepWFree };
+        }
+    }
+
+
+    protected static double getStepsW(double min, double max) {
+        double diff = min < max ? max - min : min - max;
+        double step = diff / MAX_STEPS;
+
+        if (step < 10) {
+            return getSteps(step, 1);
+        }
+        else if (step < 100) {
+            return getSteps(step, 10);
+        }
+        else if (step < 1000) {
+            return getSteps(step, 100);
+        }
+        else {
+            return step;
+        }
+    }
+
+
+    protected static double getStepsQ(double min, double max) {
+        double diff = min < max ? max - min : min - max;
+        double step = diff / MAX_STEPS;
+
+        if (step < 10) {
+            return getSteps(step, 1);
+        }
+        else if (step < 100) {
+            return getSteps(step, 10);
+        }
+        else if (step < 1000) {
+            return getSteps(step, 100);
+        }
+        else {
+            return step;
+        }
+    }
+
+
+    protected static double getSteps(double steps, double factor) {
+        int    fac  = (int) (steps / factor);
+        double diff = steps - fac * factor;
+
+        if (diff == 0) {
+            return steps;
+        }
+
+        return factor * (fac + 1);
+    }
+
+
+    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;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "wq_panel";
+    }
+
+
+    /**
+     * Determines the min and max W value for the current gauge. If no min and
+     * max values could be determined, this method will return
+     * [Double.MIN_VALUE, Double.MAX_VALUE].
+     *
+     * @param artifact The FLYSArtifact.
+     *
+     * @return the min and max W values for the current gauge.
+     */
+    protected double[] determineMinMaxW(Artifact artifact) {
+        logger.debug("WQSelect.determineCurrentGauge");
+
+        Gauge    gauge   = ((WINFOArtifact) artifact).getGauge();
+        double[] minmaxW = gauge != null ? gauge.determineMinMaxW() : null;
+
+        double minW = minmaxW != null ? minmaxW[0] : Double.MIN_VALUE;
+        double maxW = minmaxW != null ? minmaxW[1] : Double.MAX_VALUE;
+
+        return new double[] { minW, maxW };
+    }
+
+
+    /**
+     * Determines the min and max W value. If no min and
+     * max values could be determined, this method will return
+     * [Double.MIN_VALUE, Double.MAX_VALUE].
+     *
+     * @param artifact The FLYSArtifact.
+     *
+     * @return the min and max W values.
+     */
+    protected double[] determineMinMaxWFree(Artifact artifact) {
+        logger.debug("WQSelect.determineMinMaxWFree");
+
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        WstValueTable valueTable = winfo.getWstValueTable();
+
+        double[] minmaxW = null;
+        if(valueTable != null) {
+            double[] km = null;
+            if(winfo.isRange()) {
+                km = winfo.getFromToStep();
+                // Use the start km to determine the min max values.
+                minmaxW = valueTable.getMinMaxW(km[0]);
+            }
+            else {
+                km = winfo.getKms();
+                minmaxW = valueTable.getMinMaxW(km[0]);
+            }
+        }
+        return minmaxW != null
+            ? minmaxW
+            : new double[] { Double.MIN_VALUE, Double.MAX_VALUE };
+    }
+
+
+    /**
+     * Determines the min and max Q value for the current gauge. If no min and
+     * max values could be determined, this method will return
+     * [Double.MIN_VALUE, Double.MAX_VALUE].
+     *
+     * @param artifact The FLYSArtifact.
+     *
+     * @return the min and max Q values for the current gauge.
+     */
+    protected double[] determineMinMaxQAtGauge(Artifact artifact) {
+        logger.debug("WQSelect.determineMinMaxQAtGauge");
+
+        WINFOArtifact flysArtifact = (WINFOArtifact) artifact;
+
+        River river = FLYSUtils.getRiver(flysArtifact);
+        Gauge gauge = flysArtifact.getGauge();
+        Wst   wst   = WstFactory.getWst(river);
+
+        double[] minmaxQ = gauge != null
+            ? wst.determineMinMaxQ(gauge.getRange())
+            : null;
+
+        double minQ = minmaxQ != null ? minmaxQ[0] : Double.MIN_VALUE;
+        double maxQ = minmaxQ != null ? minmaxQ[1] : Double.MAX_VALUE;
+
+        return new double[] { minQ, maxQ };
+    }
+
+
+    /**
+     * Determines the min and max Q value for the current kilometer range. If no
+     * min and max values could be determined, this method will return
+     *
+     * @param artifact The FLYSArtifact.
+     *
+     * @return the min and max Q values for the current kilometer range.
+     */
+    protected double[] determineMinMaxQ(Artifact artifact) {
+        logger.debug("WQSelect.determineMinMaxQ");
+
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        WstValueTable valueTable = winfo.getWstValueTable();
+
+        double[] minmaxQ = null;
+        if(valueTable != null) {
+            double[] km = null;
+            if(winfo.isRange()) {
+                km = winfo.getFromToStep();
+                minmaxQ = valueTable.getMinMaxQ(km[0], km[1], km[2]);
+            }
+            else {
+                km = winfo.getKms();
+                minmaxQ = valueTable.getMinMaxQ(km[0]);
+                for (int i = 1; i < km.length; i++) {
+                    double[] tmp = valueTable.getMinMaxQ(km[i]);
+                    if(tmp[0] < minmaxQ[0]) {
+                        minmaxQ[0] = tmp[0];
+                    }
+                    if(tmp[1] > minmaxQ[1]) {
+                        minmaxQ[1] = tmp[1];
+                    }
+                }
+            }
+        }
+        return minmaxQ != null
+            ? minmaxQ
+            : new double[] { Double.MIN_VALUE, Double.MAX_VALUE };
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQSelect.validate");
+
+        WINFOArtifact flys = (WINFOArtifact) artifact;
+
+        StateData data       = getData(flys, WQ_SELECTION);
+        boolean isRange = data != null
+            ? Boolean.valueOf((String) data.getValue())
+            : false;
+
+
+
+        if (!isRange) {
+            return validateSingle(artifact);
+        }
+        else {
+            return validateRange(artifact);
+        }
+    }
+
+
+    protected boolean validateBounds(
+        double fromValid, double toValid,
+        double from,      double to,      double step)
+    throws IllegalArgumentException
+    {
+        logger.debug("RangeState.validateRange");
+
+        if (from < fromValid) {
+            logger.error(
+                "Invalid 'from'. " + from + " is smaller than " + fromValid);
+            throw new IllegalArgumentException("error_feed_from_out_of_range");
+        }
+        else if (to > toValid) {
+            logger.error(
+                "Invalid 'to'. " + to + " is bigger than " + toValid);
+            throw new IllegalArgumentException("error_feed_to_out_of_range");
+        }
+
+        return true;
+    }
+
+
+    protected boolean validateSingle(Artifact artifact)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateSingle");
+
+        WINFOArtifact flys = (WINFOArtifact) artifact;
+        StateData    data = getData(flys, WQ_SINGLE);
+
+        String tmp = data != null ? (String) data.getValue() : null;
+
+        if (tmp == null || tmp.length() == 0) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        String[] strValues = tmp.split(" ");
+        TDoubleArrayList all = new TDoubleArrayList();
+
+        for (String strValue: strValues) {
+            try {
+                all.add(Double.parseDouble(strValue));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn(nfe, nfe);
+            }
+        }
+
+        all.sort();
+
+        FLYSUtils.WQ_MODE mode = FLYSUtils.getWQMode(flys);
+
+        logger.debug("WQ Mode: " + mode);
+
+        double[] minmax = null;
+
+        if (mode == FLYSUtils.WQ_MODE.WGAUGE) {
+            minmax = determineMinMaxW(artifact);
+        }
+        else if (mode == FLYSUtils.WQ_MODE.QGAUGE) {
+            minmax = determineMinMaxQAtGauge(artifact);
+        }
+        else if (mode == FLYSUtils.WQ_MODE.QFREE) {
+            minmax = determineMinMaxQ(artifact);
+        }
+        else {
+            minmax = determineMinMaxWFree(artifact);
+        }
+
+        double min = all.get(0);
+        double max = all.get(all.size()-1);
+
+        logger.debug("Inserted min value = " + min);
+        logger.debug("Inserted max value = " + max);
+
+        return validateBounds(minmax[0], minmax[1], min, max, 0d);
+    }
+
+
+    protected boolean validateRange(Artifact artifact)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateRange");
+
+        WINFOArtifact     flys = (WINFOArtifact) artifact;
+        FLYSUtils.WQ_MODE mode = FLYSUtils.getWQMode(flys);
+
+        if (mode == null) {
+            throw new IllegalArgumentException("error_feed_invalid_wq_mode");
+        }
+
+        StateData dFrom = flys.getData(WQ_FROM);
+        StateData dTo   = flys.getData(WQ_TO);
+        StateData dStep = flys.getData(WQ_STEP);
+
+        String fromStr = dFrom != null ? (String) dFrom.getValue() : null;
+        String toStr   = dTo != null ? (String) dTo.getValue() : null;
+        String stepStr = dStep != null ? (String) dStep.getValue() : null;
+
+        if (fromStr == null || toStr == null || stepStr == null) {
+            throw new IllegalArgumentException("error_empty_state");
+        }
+
+        try {
+            double from = Double.parseDouble(fromStr);
+            double to   = Double.parseDouble(toStr);
+            double step = Double.parseDouble(stepStr);
+
+            if (mode == FLYSUtils.WQ_MODE.WGAUGE) {
+                return validateGaugeW(artifact, from, to, step);
+            }
+            else if (mode == FLYSUtils.WQ_MODE.QGAUGE) {
+                return validateGaugeQ(artifact, from, to, step);
+            }
+            else if (mode == FLYSUtils.WQ_MODE.QFREE) {
+                return validateFreeQ(artifact, from, to, step);
+            }
+            else if (mode == FLYSUtils.WQ_MODE.WFREE) {
+                return validateFreeW(artifact, from, to, step);
+            }
+            else {
+                throw new IllegalArgumentException(
+                    "error_feed_invalid_wq_mode");
+            }
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException("error_feed_number_format");
+        }
+    }
+
+
+    /**
+     * Validates the inserted W values.
+     *
+     * @param artifact The owner artifact.
+     * @param from The lower value of the W range.
+     * @param to The upper value of the W range.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateGaugeW(
+        Artifact    artifact,
+        double from,
+        double to,
+        double step)
+    throws    IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateGaugeW");
+
+        double[] minmaxW = determineMinMaxW(artifact);
+
+        return validateBounds(minmaxW[0], minmaxW[1], from, to, step);
+    }
+
+
+    /**
+     * Validates the inserted Q values based on the Q range for the current
+     * gauge.
+     *
+     * @param artifact The owner artifact.
+     * @param from The lower value of the Q range.
+     * @param to The upper value of the Q range.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateGaugeQ(
+        Artifact artifact,
+        double   from,
+        double   to,
+        double   step)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateGaugeQ");
+
+        double[] minmaxQ = determineMinMaxQAtGauge(artifact);
+
+        return validateBounds(minmaxQ[0], minmaxQ[1], from, to, step);
+    }
+
+
+    /**
+     * Validates the inserted Q values based on the Q range for the current
+     * kilometer range.
+     *
+     * @param artifact The owner artifact.
+     * @param from The lower value of the Q range.
+     * @param to The upper value of the Q range.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateFreeQ(
+        Artifact artifact,
+        double   from,
+        double   to,
+        double   step)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateFreeQ");
+
+        double[] minmaxQ = determineMinMaxQ(artifact);
+
+        return validateBounds(minmaxQ[0], minmaxQ[1], from, to, step);
+    }
+
+
+    /**
+     * Validates the inserted W values based on the W range for the current
+     * kilometer range.
+     *
+     * @param artifact The owner artifact.
+     * @param from The lower value of the W range.
+     * @param to The upper value of the W range.
+     * @param step The step width.
+     *
+     * @return true, if everything was fine, otherwise an exception is thrown.
+     */
+    protected boolean validateFreeW(
+        Artifact artifact,
+        double   from,
+        double   to,
+        double   step)
+    throws IllegalArgumentException
+    {
+        logger.debug("WQSelect.validateFreeW");
+
+        double[] minmaxW = determineMinMaxWFree(artifact);
+
+        return validateBounds(minmaxW[0], minmaxW[1], from, to, step);
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WaterlevelGroundDifferences.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,126 @@
+package de.intevation.flys.artifacts.states;
+
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WaterlevelGroundDifferences extends RangeState {
+
+    public static final String LOWER_FIELD  = "diff_from";
+    public static final String UPPER_FIELD  = "diff_to";
+    public static final String DIFF_FIELD   = "diff_diff";
+
+    public static final double DEFAULT_STEP = 0d;
+
+
+    private static Logger logger =
+        Logger.getLogger(WaterlevelGroundDifferences.class);
+
+
+
+    @Override
+    protected String getLowerField() {
+        return LOWER_FIELD;
+    }
+
+
+    @Override
+    protected String getUpperField() {
+        return UPPER_FIELD;
+    }
+
+
+    @Override
+    protected String getStepField() {
+        return DIFF_FIELD;
+    }
+
+
+    @Override
+    protected double[] getMinMax(Artifact artifact) {
+        return new double[] { -Double.MAX_VALUE, Double.MAX_VALUE };
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "waterlevel_ground_panel";
+    }
+
+
+    protected double getDefaultStep() {
+        return DEFAULT_STEP;
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        double[] minmax = getMinMax(artifact);
+
+        double minVal = Double.MIN_VALUE;
+        double maxVal = Double.MAX_VALUE;
+
+        if (minmax != null) {
+            minVal = minmax[0];
+            maxVal = minmax[1];
+        }
+        else {
+            logger.warn("Could not read min/max distance values!");
+        }
+
+        if (name.equals(LOWER_FIELD)) {
+            Element min = createItem(
+                cr,
+                new String[] {"min", new Double(minVal).toString()});
+
+            return new Element[] { min };
+        }
+        else if (name.equals(UPPER_FIELD)) {
+            Element max = createItem(
+                cr,
+                new String[] {"max", new Double(maxVal).toString()});
+
+            return new Element[] { max };
+        }
+        else {
+            Element step = createItem(
+                cr,
+                new String[] {"step", String.valueOf(getDefaultStep())});
+            return new Element[] { step };
+        }
+    }
+
+
+    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 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WaterlevelInfoState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,145 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.WaterlevelFacet;
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.CrossSectionWaterLineFacet;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+
+public class WaterlevelInfoState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(WaterlevelInfoState.class);
+
+
+    @Override
+    protected String getUIProvider() {
+        return "noinput";
+    }
+
+
+    @Override
+    public Object computeInit(
+        FLYSArtifact artifact,
+        String       hash,
+        Object       context,
+        CallMeta     meta,
+        List<Facet>  facets
+    ) {
+        return compute((WINFOArtifact) artifact, hash, facets, null);
+    }
+
+
+    protected Object compute(
+        WINFOArtifact winfo,
+        String        hash,
+        List<Facet>   facets,
+        Object        old
+    ) {
+        logger.debug("WaterlevelInfoState.compute");
+        String id = getID();
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult)old
+            : winfo.getWaterlevelData();
+
+        if (facets == null) {
+            return res;
+        }
+
+        WQKms [] wqkms = (WQKms [])res.getData();
+
+        for (int i = 0; i < wqkms.length; i++) {
+            String nameW = null;
+            String nameQ = null;
+
+            if (winfo.isQ()) {
+                nameQ = wqkms[i].getName();
+                nameW = "W(" + nameQ + ")";
+            }
+            else {
+                nameW = wqkms[i].getName();
+                nameQ = "Q(" + nameQ + ")";
+            }
+
+            logger.debug("WaterlevelInfoState Create facet: " + nameW);
+            logger.debug("WaterlevelInfoState Create facet: " + nameQ);
+
+            Facet w = new WaterlevelFacet(
+                i, LONGITUDINAL_W, nameW, ComputeType.ADVANCE, id, hash);
+            Facet q = new WaterlevelFacet(
+                i, LONGITUDINAL_Q, nameQ, ComputeType.ADVANCE, id, hash);
+
+            facets.add(w);
+            facets.add(q);
+        }
+
+        if (wqkms.length > 0) {
+            Facet wst = new DataFacet(
+                WST, "WST data", ComputeType.ADVANCE, hash, id);
+            Facet csv = new DataFacet(
+                CSV, "CSV data", ComputeType.ADVANCE, hash, id);
+
+            facets.add(wst);
+            facets.add(csv);
+        }
+
+        if (res.getReport().hasProblems()) {
+            facets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
+        }
+
+        // TODO Adjust to WaterlevelState - implementation.
+        facets.add(new CrossSectionWaterLineFacet(0, "Q=" + winfo.getDataAsString("wq_single")));
+
+        // Assume to be in wq_single mode.
+        return res;
+    }
+
+
+    /**
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return compute((WINFOArtifact) artifact, hash, facets, old);
+    }
+
+
+    /**
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        return compute((WINFOArtifact) artifact, hash, facets, old);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WaterlevelPairSelectState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,162 @@
+package de.intevation.flys.artifacts.states;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.StringUtil;
+
+/**
+ * State in which the user selects 1 to n pairs of Waterlevels and alikes.
+ */
+public class WaterlevelPairSelectState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(
+         WaterlevelPairSelectState.class);
+
+
+    /** Trivial constructor. */
+    public WaterlevelPairSelectState() {
+    }
+
+
+    /** Specify to display a datacage_twin_panel. */
+    @Override
+    protected String getUIProvider() {
+        return "datacage_twin_panel";
+    }
+
+
+    /**
+     * Overridden to do nothing.
+     */
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        //Get data and do stuff, do not calculate
+        return "";
+    }
+
+
+    /**
+     * Create elements for document (prepopulated with data, if any).
+     * @param artifact FLYSArtifact to get data from.
+     * @param name DataName, expceted to be "diffids".
+     */
+    @Override
+    protected Element[] createItems(
+        ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("createItems: " + name);
+        if (name.equals("diffids")) {
+            Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+            Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+            Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+            FLYSArtifact flys = (FLYSArtifact) artifact;
+            String s = flys.getDataAsString("diffids");
+            value.setTextContent(s);
+            item.appendChild(label);
+            item.appendChild(value);
+            return new Element[] { item };
+        }
+        return new Element[] {};
+    }
+
+
+    /**
+     * Creats the data element used for the static part of DESCRIBE document.
+     */
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        Element dataElement = creator.create("data");
+        creator.addAttr(dataElement, "name", name, true);
+        creator.addAttr(dataElement, "type", type, true);
+
+        Element itemElement = creator.create("item");
+        creator.addAttr(itemElement, "value", value, true);
+
+        String[] labels = getLabels(cc, value);
+        Object[] obj    = new Object[] { labels[0] };
+
+        // TODO own i18n
+        String attrValue = Resources.getMsg(
+            cc.getMeta(), "wsp.selected.string", "wsp.selected.string", obj);
+        //I18N_STATIC_KEY, I18N_STATIC_KEY, obj);
+
+        creator.addAttr(itemElement, "label", attrValue, true);
+        dataElement.appendChild(itemElement);
+
+        return dataElement;
+    }
+
+
+    /**
+     * Get name to display for selected watelerlevels (for example "Q=123")
+     * from the CalculationResult. 
+     */
+    public static String[] getLabels(CallContext cc, String value) {
+        String[] recommendations = value.split("#");
+        String displayString = "";
+
+        // Walk over all selected recommendations and create label
+        // like "W (Q=1) - W (Q=2)".
+        for (int i = 0; i < recommendations.length; i+=2) {
+            String[] minuendParts = StringUtil
+                .unbracket(recommendations[i+0])
+                .split(";");
+            if(minuendParts.length >= 4) {
+                displayString += "(" + minuendParts[3];
+            }
+            else {
+                displayString += "([error]";
+            }
+
+            displayString += " - ";
+
+            String[] subtrahendParts = StringUtil
+                .unbracket(recommendations[i+1])
+                .split(";");
+            if(subtrahendParts.length >= 4) {
+                displayString += subtrahendParts[3] + ") ";
+            }
+            else {
+                displayString += "[error])";
+            }
+        }
+
+        return new String[] { displayString };
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WaterlevelSelectState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,184 @@
+package de.intevation.flys.artifacts.states;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.data.DefaultStateData;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.StringUtil;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WaterlevelSelectState extends DefaultState {
+
+    private static final Logger logger =
+        Logger.getLogger(WaterlevelSelectState.class);
+
+    public static final String SPLIT_CHAR = ";";
+
+    public static final String WINFO_WSP_STATE_ID = "state.winfo.waterlevel";
+
+    public static final String I18N_STATIC_KEY = "wsp.selected.string";
+
+
+    @Override
+    protected String getUIProvider() {
+        return "wsp_datacage_panel";
+    }
+
+
+    /**
+     * @param flys ignored
+     * @param cc   ignrored
+     */
+    @Override
+    public StateData transform(
+        FLYSArtifact flys,
+        CallContext  cc,
+        StateData    stateData,
+        String       name,
+        String       val
+    ) {
+        if (!isValueValid(val)) {
+            logger.error("The given input string is not valid: '" + val + "'");
+            return null;
+        }
+
+        return new DefaultStateData(name, null, null, StringUtil.unbracket(val));
+    }
+
+
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+
+        StateData data = flys.getData("wsp");
+
+        if (data == null) {
+            throw new IllegalArgumentException("WSP is empty");
+        }
+
+        return true;
+    }
+
+
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        Element dataElement = creator.create("data");
+        creator.addAttr(dataElement, "name", name, true);
+        creator.addAttr(dataElement, "type", type, true);
+
+        Element itemElement = creator.create("item");
+        creator.addAttr(itemElement, "value", value, true);
+
+        String[] labels = getLabels(cc, value);
+        Object[] obj    = new Object[] { labels[0] };
+
+        String attrValue = Resources.getMsg(
+            cc.getMeta(), I18N_STATIC_KEY, I18N_STATIC_KEY, obj);
+
+        creator.addAttr(itemElement, "label", attrValue, true);
+        dataElement.appendChild(itemElement);
+
+        return dataElement;
+    }
+
+
+    /**
+     * Get name to display for selected watelerlevel (for example "Q=123")
+     * from the CalculationResult. 
+     */
+    public static String[] getLabels(CallContext cc, String value) {
+        String[] parts = value.split(SPLIT_CHAR);
+
+        FLYSArtifact artifact = FLYSUtils.getArtifact(parts[0], cc);
+
+        CalculationResult rawData = (CalculationResult) artifact.compute(
+            cc,
+            null,
+            WINFO_WSP_STATE_ID,
+            ComputeType.ADVANCE,
+            false);
+
+        WQKms[] wqkms = (WQKms[]) rawData.getData();
+
+        int idx = -1;
+        try {
+            idx = Integer.parseInt(parts[2]);
+        }
+        catch (NumberFormatException nfe) { /* do nothing */ }
+
+        String name = wqkms[idx].getName();
+
+        return new String[] { StringUtil.wWrap(name) };
+    }
+
+
+    /**
+     * Validates the given String. A valid string for this state requires the
+     * format: "UUID;FACETNAME;FACETINDEX".
+     *
+     * @param value The string value requires validation.
+     *
+     * @return true, if the string applies the specified format, otherwise
+     * false.
+     */
+    public static boolean isValueValid(String value) {
+        logger.debug("Validate string: '" + value + "'");
+
+        value = StringUtil.unbracket(value);
+
+        logger.debug("Validate substring: '" + value + "'");
+
+        if (value == null || value.length() == 0) {
+            return false;
+        }
+
+        String[] parts = value.split(SPLIT_CHAR);
+
+        if (parts == null || parts.length < 3) {
+            return false;
+        }
+
+        if (parts[0] == null || parts[0].length() == 0) {
+            return false;
+        }
+
+        if (parts[1] == null || parts[1].length() == 0) {
+            return false;
+        }
+
+        try {
+            Integer.parseInt(parts[2]);
+        }
+        catch (NumberFormatException nfe) {
+            logger.error("Index is not a valid integer!", nfe);
+        }
+
+        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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WaterlevelState.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,157 @@
+package de.intevation.flys.artifacts.states;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.ChartArtifact;
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.CrossSectionWaterLineFacet;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.EmptyFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.ReportFacet;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WaterlevelFacet;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class WaterlevelState
+extends      DefaultState
+implements   FacetTypes
+{
+    /** The logger that is used in this state. */
+    private static Logger logger = Logger.getLogger(WaterlevelState.class);
+
+
+    /**
+     * From this state can only be continued trivially.
+     */
+    @Override
+    protected String getUIProvider() {
+        return "continue";
+    }
+
+
+    /**
+     * Compute result or returned object from cache, create facets.
+     * @param old Object that was cached.
+     */
+    protected Object compute(
+        WINFOArtifact winfo,
+        CallContext   cc,
+        String        hash,
+        List<Facet>   facets,
+        Object        old
+    ) {
+        String id = getID();
+
+        CalculationResult res = old instanceof CalculationResult
+            ? (CalculationResult) old
+            : winfo.getWaterlevelData();
+
+        if (facets == null) {
+            return res;
+        }
+
+        boolean debug = logger.isDebugEnabled();
+
+        WQKms [] wqkms = (WQKms []) res.getData();
+
+        for (int i = 0; i < wqkms.length; i++) {
+            String name = wqkms[i].getName();
+
+            String nameW = FLYSUtils.createWspWTitle(winfo, cc, name);
+            String nameQ = FLYSUtils.createWspQTitle(winfo, cc, name);
+
+            // Hotfix for theme names. Themes with the same name cause problems
+            // aggregating chart legend items.
+            if (i > 0 && name.equals(wqkms[i - 1].getName())) {
+                nameW += "; Q=" + wqkms[i].get(0, new double[3])[1];
+                nameQ += " = " + wqkms[i].get(0, new double[3])[1];
+            }
+
+            if (debug) {
+                logger.debug("Create facet: " + nameW);
+                logger.debug("Create facet: " + nameQ);
+            }
+
+            Facet w = new WaterlevelFacet(
+                i, LONGITUDINAL_W, nameW, ComputeType.ADVANCE, id, hash);
+            Facet q = new WaterlevelFacet(
+                i, LONGITUDINAL_Q, nameQ, ComputeType.ADVANCE, id, hash);
+
+            facets.add(new CrossSectionWaterLineFacet(i, nameW));
+
+            facets.add(w);
+            facets.add(q);
+        }
+
+        if (wqkms.length > 0) {
+            Facet wst = new DataFacet(
+                WST, "WST data", ComputeType.ADVANCE, hash, id);
+            Facet csv = new DataFacet(
+                CSV, "CSV data", ComputeType.ADVANCE, hash, id);
+            Facet pdf = new DataFacet(
+                PDF, "PDF data", ComputeType.ADVANCE, hash, id);
+
+            facets.add(wst);
+            facets.add(csv);
+            facets.add(pdf);
+        }
+
+        if (res.getReport().hasProblems()) {
+            facets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
+        }
+
+        return res;
+    }
+
+
+    /**
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeFeed(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        if (artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return compute((WINFOArtifact) artifact, context, hash, facets, old);
+    }
+
+
+    /**
+     * @param context Ignored.
+     */
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        if (artifact instanceof ChartArtifact) {
+            ChartArtifact chart = (ChartArtifact)artifact;
+            facets.add(new EmptyFacet());
+            return null;
+        }
+        return compute((WINFOArtifact) artifact, context, hash, facets, old);
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/AnalysisPeriods.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class AnalysisPeriods extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(AnalysisPeriods.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public AnalysisPeriods() {
+    }
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.period_ana_panel";
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/EventSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,97 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.utils.FLYSUtils;
+
+import de.intevation.flys.artifacts.model.FixingsOverview;
+import de.intevation.flys.artifacts.model.FixingsOverviewFactory;
+import de.intevation.flys.artifacts.model.FixingsOverview.IdFilter;
+import de.intevation.flys.artifacts.model.FixingsOverview.Fixing;
+
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class EventSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(EventSelect.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public EventSelect() {
+    }
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.event_panel";
+    }
+
+    @Override
+    protected Element createStaticData(
+        FLYSArtifact   flys,
+        ElementCreator creator,
+        CallContext    cc,
+        String         name,
+        String         value,
+        String         type
+    ) {
+        int[] array = FLYSUtils.intArrayFromString(value);
+
+        Element dataElement = creator.create("data");
+        creator.addAttr(dataElement, "name", name, true);
+        creator.addAttr(dataElement, "type", type, true);
+
+        String river = FLYSUtils.getRiver(flys).getName();
+
+        FixingsOverview overview = FixingsOverviewFactory.getOverview(river);
+
+        for (int i = 0; i < array.length; i++) {
+            Element itemElement = creator.create("item");
+            creator.addAttr(
+                itemElement,
+                "value",
+                String.valueOf(array[i]),
+                true);
+
+            creator.addAttr(
+                itemElement,
+                "label",
+                getLabel(cc, array[i], overview),
+                true);
+            dataElement.appendChild(itemElement);
+        }
+        return dataElement;
+    }
+
+
+    public static String getLabel(
+        CallContext cc,
+        int value,
+        FixingsOverview overview
+    ) {
+        logger.debug("Create label for value: " + value);
+
+        IdFilter filter = new IdFilter(value);
+        List<Fixing.Column> columns = overview.filter(null, filter);
+        if (!columns.isEmpty()) {
+        return columns.get(0).getDescription();
+        }
+
+        return "";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/FixationCompute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,79 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.FixationArtifactAccess;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.DataFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.ReportFacet;
+
+import de.intevation.flys.artifacts.model.fixings.FixCalculation;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FixationCompute
+extends      DefaultState
+implements   FacetTypes
+{
+
+    /** The log used in this class. */
+    private static Logger log = Logger.getLogger(FixationCompute.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public FixationCompute() {
+    }
+
+    @Override
+    public Object computeAdvance(
+        FLYSArtifact artifact,
+        String       hash,
+        CallContext  context,
+        List<Facet>  facets,
+        Object       old
+    ) {
+        log.debug("FixationCompute.computeAdvance");
+
+        CalculationResult res;
+
+        if (old instanceof CalculationResult) {
+            res = (CalculationResult)old;
+        }
+        else {
+            FixationArtifactAccess access =
+                new FixationArtifactAccess(artifact);
+
+            FixCalculation calc =
+                new FixCalculation(access);
+
+            res = calc.calculate();
+        }
+
+        if (facets == null) {
+            return res;
+        }
+
+        if (res.getReport().hasProblems()) {
+            facets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
+        }
+        facets.add(
+            new DataFacet(CSV, "CSV data", ComputeType.ADVANCE, hash, id));
+
+        return res;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/FixationPeriod.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FixationPeriod extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(FixationPeriod.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public FixationPeriod() {
+    }
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.period_panel";
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/FixationSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,74 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FixationSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(FixationSelect.class);
+
+
+    /** Constant value for the default fixation calculation. */
+    public static final String CALCULATION_DEFAULT = "calculation.analysis";
+
+    /** Constant value for the volmer fixation analysis. */
+    public static final String CALCULATION_VOLMER = "calculation.volmer";
+
+    /** An Array that holds all available calculation modes.*/
+    public static final String[] CALCULATIONS = {
+        CALCULATION_DEFAULT,
+        CALCULATION_VOLMER
+    };
+
+    /** Error message that is thrown if no mode has been chosen. */
+    public 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. */
+    public static  final String ERROR_INVALID_CALCULATION_MODE =
+        "error_feed_invalid_calculation_mode";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public FixationSelect() {
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator ec,
+        Artifact                artifact,
+        String                  name,
+        CallContext             context)
+    {
+        CallMeta meta = context.getMeta();
+        Element[] calculations = new Element[CALCULATIONS.length];
+
+        for (int i = 0; i < CALCULATIONS.length; i++) {
+            String calc = CALCULATIONS[i];
+            calculations[i] = createItem(
+                ec,
+                new String[] {Resources.getMsg(meta, calc, calc), calc});
+        }
+        return calculations;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/FixationVolmerCompute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,24 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FixationVolmerCompute extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(FixationVolmerCompute.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public FixationVolmerCompute() {
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/FunctionSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,60 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.math.fitting.Function;
+import de.intevation.flys.artifacts.math.fitting.FunctionFactory;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class FunctionSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(FunctionSelect.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public FunctionSelect() {
+    }
+
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator ec,
+        Artifact                artifact,
+        String                  name,
+        CallContext             context)
+    {
+        FunctionFactory ff = FunctionFactory.getInstance();
+        Collection fc = ff.getFunctions();
+        Element[] functions = new Element[fc.size()];
+        Iterator i = fc.iterator();
+
+        int j = 0;
+        while(i.hasNext()) {
+            Function f = (Function)i.next();
+            String n = f.getName();
+            String d = f.getDescription();
+            functions[j] = createItem(ec, new String[] {d, n});
+            j++;
+        }
+
+        return functions;
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/GaugeRange.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class GaugeRange extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(GaugeRange.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public GaugeRange() {
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.gaugeselect_panel";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/LocationSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,61 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.states.RangeState;
+
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class LocationSelect extends RangeState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(LocationSelect.class);
+
+    private static final String LOWER = "from";
+
+    private static final String UPPER = "to";
+
+    private static final String STEP = "step";
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public LocationSelect() {
+    }
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.location_panel";
+    }
+
+    @Override
+    protected double[] getMinMax(Artifact artifact) {
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+        return FLYSUtils.getRiverMinMax(flysArtifact);
+    }
+
+    @Override
+    protected String getLowerField() {
+        return LOWER;
+    }
+
+    @Override
+    protected String getUpperField() {
+        return UPPER;
+    }
+
+    @Override
+    protected String getStepField() {
+        return STEP;
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/PreprocessingSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,56 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.artifacts.states.DefaultState;
+
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class PreprocessingSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(PreprocessingSelect.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public PreprocessingSelect() {
+    }
+
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator ec,
+        Artifact                artifact,
+        String                  name,
+        CallContext             context)
+    {
+        CallMeta meta = context.getMeta();
+        Element[] elements = new Element[1];
+        elements[0] = createItem(
+                ec,
+                new String[] {
+                    Resources.getMsg(meta, "preprocess", "preprocess"),
+                    "preprocess"});
+
+        return elements;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.preprocessing_panel";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/QSelect.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class QSelect extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(QSelect.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public QSelect() {
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.qselect_panel";
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/fixation/ReferencePeriod.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,28 @@
+package de.intevation.flys.artifacts.states.fixation;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.states.DefaultState;
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class ReferencePeriod extends DefaultState {
+
+    /** The logger used in this class. */
+    private static Logger logger = Logger.getLogger(ReferencePeriod.class);
+
+
+    /**
+     * The default constructor that initializes an empty State object.
+     */
+    public ReferencePeriod() {
+    }
+
+    @Override
+    protected String getUIProvider() {
+        return "fix.period_ref_panel";
+    }
+
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/artifacts/transitions/DefaultTransition.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,104 @@
+package de.intevation.flys.artifacts.transitions;
+
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.artifactdatabase.transition.Transition;
+
+
+/**
+ * The default implementation of a <code>Transition</code>.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultTransition implements Transition {
+
+    /** The ID of the current state */
+    protected String from;
+
+    /** The ID of the target state */
+    protected String to;
+
+
+    /**
+     * The default constructor.
+     */
+    public DefaultTransition() {
+    }
+
+
+    /**
+     * The default constructor.
+     *
+     * @param from The current state.
+     * @param to The target state.
+     */
+    public DefaultTransition(String from, String to) {
+        this.from = from;
+        this.to   = to;
+    }
+
+
+    public void init(Node config) {
+        // nothing to do in the default transition
+    }
+
+
+    /**
+     * Returns the current state ID.
+     *
+     * @return the current state ID.
+     */
+    public String getFrom() {
+        return from;
+    }
+
+
+    /**
+     * Returns the target state ID.
+     *
+     * @return the target state ID.
+     */
+    public String getTo() {
+        return to;
+    }
+
+
+    /**
+     * Set the current state ID.
+     *
+     * @param to the current state ID.
+     */
+    public void setFrom(String from) {
+        this.from = from;
+    }
+
+
+    /**
+     * Set the target state ID.
+     *
+     * @param to the target state ID.
+     */
+    public void setTo(String to) {
+        this.to = to;
+    }
+
+
+    /**
+     * Determines if its valid to step from state <i>a</i> of an artifact
+     * <i>artifact</i> to state <i>b</i>. This method always returns true - no
+     * validation takes place.
+     *
+     * @param artifact The owner artifact of state a and b.
+     * @param a The current state.
+     * @param b The target state.
+     *
+     * @return true, if it is valid to step from a to b, otherwise false.
+     */
+    public boolean isValid(Artifact artifact, State a, State b) {
+        return true;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/transitions/TransitionFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,71 @@
+package de.intevation.flys.artifacts.transitions;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+
+import de.intevation.artifactdatabase.transition.Transition;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class TransitionFactory {
+
+    /** The logger used in this class */
+    private static Logger logger = Logger.getLogger(TransitionFactory.class);
+
+    /** The XPath to the classname of the transition */
+    public static final String XPATH_TRANSITION = "@transition";
+
+    /** The XPath to the current state ID */
+    public static final String XPATH_CURRENT_STATE = "from/@state";
+
+    /** The XPath to the target state ID */
+    public static final String XPATH_TARGET_STATE = "to/@state";
+
+
+    /**
+     * Creates a new Transition based on the configured class provided by
+     * <code>transitionConf</code>.
+     *
+     * @param transitionConf The configuration of the transition.
+     *
+     * @return a Transition.
+     */
+    public static Transition createTransition(Node transitionConf) {
+        String clazz = (String) XMLUtils.xpath(
+            transitionConf, XPATH_TRANSITION, XPathConstants.STRING);
+
+        Transition transition = null;
+
+        try {
+            transition = (Transition) Class.forName(clazz).newInstance();
+
+            String from = (String) XMLUtils.xpath(
+                transitionConf, XPATH_CURRENT_STATE, XPathConstants.STRING);
+            String to = (String) XMLUtils.xpath(
+                transitionConf, XPATH_TARGET_STATE, XPathConstants.STRING);
+
+            transition.init(transitionConf);
+            transition.setFrom(from);
+            transition.setTo(to);
+        }
+        catch (InstantiationException ie) {
+            logger.error(ie, ie);
+        }
+        catch (IllegalAccessException iae) {
+            logger.error(iae, iae);
+        }
+        catch (ClassNotFoundException cnfe) {
+            logger.error(cnfe, cnfe);
+        }
+
+        return transition;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/transitions/ValueCompareTransition.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,118 @@
+package de.intevation.flys.artifacts.transitions;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.Artifact;
+
+import de.intevation.artifactdatabase.data.StateData;
+import de.intevation.artifactdatabase.state.State;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+
+/**
+ * This transition compares data objects with a <i>equal</i> or <i>notequal</i>
+ * operator.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ValueCompareTransition extends DefaultTransition {
+
+    /** The logger that is used in this transition.*/
+    private static Logger log = Logger.getLogger(ValueCompareTransition.class);
+
+
+    /** XPath that points to the condition's operator.*/
+    public static final String XPATH_OPERATOR = "condition/@operator";
+
+    /** XPath that points to the condition's value.*/
+    public static final String XPATH_VALUE    = "condition/@value";
+
+    /** XPath that points  to the condition's dataname.*/
+    public static final String XPATH_DATANAME = "condition/@data";
+
+    /** The value of the 'equal' operator.*/
+    public static final String OPERATOR_EQUAL = "equal";
+
+    /** The value of the 'not equal' operator.*/
+    public static final String OPERATOR_NOTEQUAL = "notequal";
+
+
+    /** The operator.*/
+    protected String operator;
+
+    /** The value used for comparison.*/
+    protected String value;
+
+    /** The name of the data used for the comparison.*/
+    protected String dataname;
+
+
+
+    public ValueCompareTransition() {
+    }
+
+
+    public ValueCompareTransition(String from, String to) {
+        super(from, to);
+    }
+
+
+    @Override
+    public void init(Node config) {
+        log.debug("ValueCompareTransition.init");
+
+        String tmp = (String) XMLUtils.xpath(
+            config,
+            XPATH_OPERATOR,
+            XPathConstants.STRING);
+        operator = tmp.trim().toLowerCase();
+
+        value = (String) XMLUtils.xpath(
+            config,
+            XPATH_VALUE,
+            XPathConstants.STRING);
+
+        dataname = (String) XMLUtils.xpath(
+            config,
+            XPATH_DATANAME,
+            XPathConstants.STRING);
+    }
+
+
+    @Override
+    public boolean isValid(Artifact artifact, State a, State b) {
+        log.debug("ValueCompareTransition.isValid");
+
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+
+        StateData dataObj = flysArtifact.getData(dataname);
+        String    dataVal = dataObj != null ? (String) dataObj.getValue() : "";
+
+        if (log.isDebugEnabled()) {
+            log.debug("Compare settings:");
+            log.debug("-- dataname: " + dataname);
+            log.debug("-- operator: " + operator);
+            log.debug("-- compare value: " + value);
+            log.debug("-- state value: " + dataVal);
+        }
+
+        if (operator.equals(OPERATOR_EQUAL)) {
+            return value.equals(dataVal);
+        }
+        else if (operator.equals(OPERATOR_NOTEQUAL)) {
+            return !(value.equals(dataVal));
+        }
+
+        log.error("Wrong operator set! No comparison takes place.");
+
+        return false;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/AttributeParser.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,159 @@
+package de.intevation.flys.collections;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.ManagedDomFacet;
+import de.intevation.flys.exports.ChartSettings;
+
+/**
+ * Access parts of the Attribute parts of a FLYSCollections description
+ * document.
+ */
+public class AttributeParser {
+
+    /** Constant XPath that points to the outputmodes of an artifact. */
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:attribute/art:outputs/art:output";
+
+
+    private static Logger logger = Logger.getLogger(AttributeParser.class);
+
+
+    protected Document attributeDocument;
+
+    protected CollectionAttribute attribute;
+
+
+    public AttributeParser(Document attributeDocument) {
+        this.attributeDocument = attributeDocument;
+    }
+
+
+    public void parse() {
+        logger.debug("AttributeParser.parse");
+
+        attribute = new CollectionAttribute();
+
+        NodeList outs = (NodeList) XMLUtils.xpath(
+            attributeDocument,
+            XPATH_ARTIFACT_OUTPUTMODES,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        int num = outs != null ? outs.getLength() : 0;
+
+        logger.debug("Attribute has " + num + " outputs.");
+
+        for (int i = 0; i < num; i++) {
+            Node out = outs.item(i);
+
+            parseOutput(out);
+        }
+    }
+
+
+    public CollectionAttribute getCollectionAttribute() {
+        if (attribute == null) {
+            parse();
+        }
+
+        return attribute;
+    }
+
+
+    public Document getAttributeDocument() {
+        return attributeDocument;
+    }
+
+
+    public Map<String, Output> getOuts() {
+        return attribute.getOutputs();
+    }
+
+
+    /**
+     * Access all facets.
+     * @return list of all facets.
+     */
+    public List<Facet> getFacets() {
+        return attribute.getFacets();
+    }
+
+
+    protected void parseOutput(Node out) {
+        String name = ((Element)out).getAttribute("name");
+
+        if (name.length() == 0) {
+            logger.warn("No Output name specified. Cancel parsing!");
+            return;
+        }
+
+        Output o = attribute.getOutput(name);
+
+        if (o == null) {
+            logger.debug("Create new output: " + name);
+
+            o = new DefaultOutput(name, null, null);
+            attribute.addOutput(name, o);
+        }
+
+        parseSettings(out, name);
+        parseItems(out, name);
+    }
+
+
+    protected void parseSettings(Node out, String outname) {
+        Node settingsNode = (Node) XMLUtils.xpath(
+            out, "settings",
+            XPathConstants.NODE,
+            null);
+
+        if (settingsNode == null) {
+            logger.debug("No Settings found for Output '" + outname + "'");
+            return;
+        }
+
+        Settings settings = ChartSettings.parse(settingsNode);
+        attribute.setSettings(outname, settings);
+    }
+
+
+    protected void parseItems(Node out, String outname) {
+        NodeList themes = (NodeList) XMLUtils.xpath(
+            out, "art:facet",
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        int num = themes != null ? themes.getLength() : 0;
+
+        logger.debug("Output has " + num + " themes.");
+
+        String uri = ArtifactNamespaceContext.NAMESPACE_URI;
+
+        for (int i = 0; i < num; i++) {
+            Element theme = (Element) themes.item(i);
+
+            attribute.addFacet(outname, new ManagedDomFacet(theme));
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/AttributeWriter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,291 @@
+package de.intevation.flys.collections;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.ManagedFacet;
+
+/**
+ * Create attribute- element of describe document of an ArtifactCollection.
+ * The attribute-element contains the merged output of all outputmodes and
+ * facets that are part of the collection.
+ */
+public class AttributeWriter {
+
+    /** ArtifactDatabase used to fetch Artifacts. */
+    protected ArtifactDatabase db = null;
+
+    protected Map<String, Output> oldAttr;
+
+    protected Map<String, Output> newAttr;
+
+    /** List of already seen facets. */
+    protected List<Facet>         oldFacets;
+
+    /** List of "new" facets. */
+    protected List<Facet>         newFacets;
+
+    /**
+     * "Compatibility matrix", mapws list of facet names to output names.
+     * Any facet that is not found in the list for a specific output will
+     * not be added to the resulting document.
+     */
+    protected Map<String, List<String>> compatibilities;
+
+
+    /** The result of the <i>write()</i> operation.*/
+    protected CollectionAttribute attribute;
+
+
+    private static Logger logger = Logger.getLogger(AttributeWriter.class);
+
+
+    /**
+     * Create a AttributeWriter.
+     * Attributes not present in newAttr will not be included in the document.
+     * @param db      Database to fetch artifacts.
+     * @param oldAttr "Old" (possibly user-changed) outputs.
+     * @param newAttr "New" (eventually re-read in its original, unchanged
+     *                form) outputs.
+     */
+    public AttributeWriter(
+        ArtifactDatabase    db,
+        CollectionAttribute attribute,
+        Map<String, Output> oldAttr,
+        List<Facet>         oldFacets,
+        Map<String, Output> newAttr,
+        List<Facet>         newFacets,
+        Map<String, List<String>> matrix)
+    {
+        this.db        = db;
+        this.attribute = attribute;
+        this.oldAttr   = oldAttr;
+        this.newAttr   = newAttr;
+        this.oldFacets = oldFacets;
+        this.newFacets = newFacets;
+        this.compatibilities = matrix;
+    }
+
+
+    /**
+     * Create document by merging outputs given in
+     * constructor.
+     *
+     * The "new" set rules about existance of attributes, so anything not
+     * present in it will not be included in the resulting document.
+     * The "old" set rules about the content of attributes (as user changes
+     * are recorded here and not in the new set).
+     *
+     * @return document with merged outputs as described.
+     */
+    protected CollectionAttribute write() {
+        for (Map.Entry<String, Output> entry: newAttr.entrySet()) {
+            String outName = entry.getKey();
+            Output a       = entry.getValue();
+
+
+            Output exists = attribute.getOutput(outName);
+            if (exists == null) {
+                attribute.addOutput(outName, a);
+            }
+
+            attribute.clearFacets(outName);
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Merge Output: " + outName);
+                logger.debug("   old Facets: " + oldFacets.size());
+                logger.debug("   new Facets: " + newFacets.size());
+            }
+
+            writeOutput(a.getName(), newFacets, oldFacets);
+        }
+
+        // THIS CALL IS ABSOLUTELY NECESSARY!
+        attribute.cleanEmptyOutputs();
+
+        return attribute;
+    }
+
+
+    /**
+     * @param outputName the "new" outputs name
+     * @param newOutFacets Facets of the new outputs
+     * @param oldOutFacets Facets of the old outputs (can be null)
+     */
+    protected void writeOutput(
+        String      outputName,
+        List<Facet> newOutFacets,
+        List<Facet> oldOutFacets
+    ) {
+        List<String> compatFacets = this.compatibilities.get(outputName);
+
+        if (logger.isDebugEnabled() && compatFacets != null) {
+            logger.debug("Compabitle Facets:");
+            for (String compatible: compatFacets) {
+                logger.debug(   "- " + compatible);
+            }
+        }
+
+        try {
+            writeFacets(outputName, newOutFacets, oldOutFacets, compatFacets);
+        }
+        catch (ArtifactDatabaseException ade) {
+            logger.error(ade, ade);
+        }
+    }
+
+
+    /**
+     * @param newFacets the new facets
+     * @param oldFacets the old facets
+     * @param compatibleFacets List of facets to accept
+     * @return true if any facets are written to the out.
+     */
+    protected boolean writeFacets(
+        String         outputName,
+        List<Facet>    newFacets,
+        List<Facet>    oldFacets,
+        List<String>   compatibleFacets)
+    throws ArtifactDatabaseException
+    {
+        if (compatibleFacets == null) {
+            logger.warn("No compatible facets, not generating out.");
+            return false;
+        }
+
+        int num = newFacets.size();
+
+        // Add all new Facets either in their old state or (if really
+        // new) as they are.
+        List<ManagedFacet> currentFacets      = new ArrayList<ManagedFacet>();
+        List<ManagedFacet> genuinelyNewFacets = new ArrayList<ManagedFacet>();
+
+        for (int i = 0; i < num; i++) {
+            ManagedFacet facet = (ManagedFacet) newFacets.get(i);
+
+            logger.debug("Try to add Facet: " + facet.getName());
+
+            if (!compatibleFacets.contains(facet.getName())) {
+                //logger.debug("Have incompatible facet, skip: " + facet.getName());
+                continue;
+            }
+            //else logger.debug("Have compatible facet: " + facet.getName());
+
+            ManagedFacet picked = pickFacet(facet, oldFacets);
+
+            if (facet.equals(picked)) {
+                genuinelyNewFacets.add(picked);
+            }
+            else {
+                currentFacets.add(picked);
+            }
+        }
+
+        // With each genuinely new Facet, ask Artifact whether it comes to live
+        // in/activate.
+        for (ManagedFacet newMF: genuinelyNewFacets) {
+            FLYSArtifact flys = (FLYSArtifact) db.getRawArtifact(newMF.getArtifact());
+            newMF.setActive(flys.getInitialFacetActivity(
+                outputName,
+                newMF.getName(),
+                newMF.getIndex()));
+        }
+
+        // For each genuinely new Facet check positional conflicts.
+        for (ManagedFacet newMF: genuinelyNewFacets) {
+            boolean conflicts = true;
+            // Loop until all conflicts resolved.
+            while (conflicts) {
+                conflicts = false;
+                for (ManagedFacet oldMF: currentFacets) {
+                    if (newMF.getPosition() == oldMF.getPosition()) {
+                        conflicts = true;
+                        logger.debug("Positional conflict while merging " +
+                            "facets, pushing newest facet 1 up (" + newMF.getPosition() + ")");
+                        newMF.setPosition(newMF.getPosition() + 1);
+                        break;
+                    }
+                }
+            }
+            currentFacets.add(newMF);
+        }
+
+        // Fill/correct "gaps" (e.g. position 1,2,5 are taken, after gap filling
+        // expect positions 1,2,3 [5->3])
+        // Preparations to be able to detect gaps.
+        Map<Integer, ManagedFacet> mfmap = new HashMap<Integer, ManagedFacet>();
+        int max = 0;
+        for (ManagedFacet mf: currentFacets) {
+            int pos = mf.getPosition();
+            mfmap.put(Integer.valueOf(pos), mf);
+            if (pos > max) max = pos;
+        }
+
+        // Finally do gap correction.
+        if (max != currentFacets.size()) {
+            int gap = 0;
+            for (int i = 1; i <= max; i++) {
+                ManagedFacet mf = mfmap.get(Integer.valueOf(i));
+                if (mf == null) {
+                    gap++;
+                    continue;
+                }
+                mf.setPosition(mf.getPosition() - gap);
+            }
+        }
+
+        // Now add all facets.
+        for (ManagedFacet oldMF: currentFacets) {
+            attribute.addFacet(outputName, oldMF);
+        }
+
+        return currentFacets.size() > 0;
+    }
+
+
+    /**
+     * Returns the facet to be added to Document.
+     * Return the new facet only if the "same" facet was not present before.
+     * Return the "old" facet otherwise (user-defined information sticks
+     * to it).
+     * @param facet     the new facet.
+     * @param oldFacets the old facets, new facet is compared against each of
+     *                  these.
+     * @return facet if genuinely new, matching old facet otherwise.
+     */
+    protected ManagedFacet pickFacet(ManagedFacet facet, List<Facet> oldFacets)
+    {
+        if (oldFacets == null) {
+            logger.debug("No old facets to compare a new to found.");
+            return facet;
+        }
+
+        String hash = facet.getName() + facet.getIndex() + facet.getArtifact();
+
+        // Compare "new" facet with all old facets.
+        // Take oldFacet if that facet was already present (otherwise
+        // information is lost, the new one otherwise.
+        for (Facet oFacet: oldFacets) {
+            ManagedFacet oldFacet = (ManagedFacet) oFacet;
+            String oldHash = oldFacet.getName()
+                           + oldFacet.getIndex()
+                           + oldFacet.getArtifact();
+            if (hash.equals(oldHash)) {
+                return oldFacet;
+            }
+        }
+        return facet;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/collections/CollectionAttribute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,314 @@
+package de.intevation.flys.collections;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.Settings;
+
+
+public class CollectionAttribute {
+
+    private static final Logger logger =
+        Logger.getLogger(CollectionAttribute.class);
+
+
+    protected ElementCreator ec;
+
+    protected Map<String, Output> outputMap;
+
+    protected Node loadedRecommendations;
+
+
+    public CollectionAttribute() {
+    }
+
+
+    public void addOutput(String key, Output output) {
+        if (outputMap == null) {
+            outputMap = new HashMap<String, Output>();
+        }
+
+        if (key != null && key.length() > 0 && output != null) {
+            outputMap.put(
+                key,
+                new DefaultOutput(
+                    output.getName(),
+                    output.getDescription(),
+                    output.getMimeType(),
+                    new ArrayList<Facet>(),
+                    output.getType()));
+        }
+    }
+
+
+    public void cleanEmptyOutputs() {
+        if (outputMap == null) {
+            return;
+        }
+
+        List<String> removeUs = new ArrayList<String>();
+
+        Set<Map.Entry<String, Output>> entries = outputMap.entrySet();
+        for (Map.Entry<String, Output> entry: entries) {
+            Output o = entry.getValue();
+
+            List<Facet> facets = o.getFacets();
+            if (facets == null || facets.isEmpty()) {
+                removeUs.add(entry.getKey());
+            }
+        }
+
+        for (String key: removeUs) {
+            outputMap.remove(key);
+        }
+    }
+
+
+    public void setSettings(String outputKey, Settings settings) {
+        if (settings == null) {
+            logger.warn("Tried to set empty Settings for '" + outputKey + "'");
+            return;
+        }
+
+        if (outputMap == null) {
+            logger.warn("Tried to add facet but no Outputs are existing yet.");
+            return;
+        }
+
+        Output output = outputMap.get(outputKey);
+
+        if (output == null) {
+            logger.warn("Tried to add facet for unknown Output: " + outputKey);
+            return;
+        }
+
+        output.setSettings(settings);
+    }
+
+
+    public void addFacet(String outputKey, Facet facet) {
+        if (facet == null) {
+            logger.warn("Tried to add empty facet.");
+            return;
+        }
+
+        if (outputMap == null) {
+            logger.warn("Tried to add facet but no Outputs are existing yet.");
+            return;
+        }
+
+        Output output = outputMap.get(outputKey);
+
+        if (output == null) {
+            logger.warn("Tried to add facet for unknown Output: " + outputKey);
+            return;
+        }
+
+        logger.debug("Add facet for '" + outputKey + "': " + facet.getName());
+        output.addFacet(facet);
+    }
+
+
+    public void setLoadedRecommendations(Node loadedRecommendations) {
+        // TODO Replace this Node with a Java class object.
+        this.loadedRecommendations = loadedRecommendations;
+    }
+
+
+    public void clearFacets(String outputKey) {
+        if (outputKey == null || outputKey.length() == 0) {
+            logger.warn("Tried to clear Facets, but no Output key specified!");
+            return;
+        }
+
+        if (outputMap == null) {
+            logger.warn("Tried to clear Facets, but no Outputs existing!");
+            return;
+        }
+
+        Output output = outputMap.get(outputKey);
+        if (output == null) {
+            logger.warn("Tried to clear Facets for unknown Out: " + outputKey);
+            return;
+        }
+
+        output.setFacets(new ArrayList<Facet>());
+    }
+
+
+    public Document toXML() {
+        Document doc = XMLUtils.newDocument();
+
+        ec = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("attribute");
+
+        appendOutputs(root);
+        appendLoadedRecommendations(root);
+
+        doc.appendChild(root);
+
+        return doc;
+    }
+
+
+    public Map<String, Output> getOutputs() {
+        return outputMap;
+    }
+
+
+    public Output getOutput(String name) {
+        if (name == null || name.length() == 0) {
+            logger.warn("No Output name specified.");
+            return null;
+        }
+
+        if (outputMap == null || outputMap.size() == 0) {
+            logger.warn("Tried to retrieve Output, but no Outputs existing.");
+            return null;
+        }
+
+        return outputMap.get(name);
+    }
+
+
+    public List<Facet> getFacets(String output) {
+        if (output == null || output.length() == 0) {
+            logger.warn("No Output name specified.");
+            return new ArrayList<Facet>();
+        }
+
+        if (outputMap == null) {
+            logger.warn("Tried to retrieve facets, but no Outputs existing.");
+            return new ArrayList<Facet>();
+        }
+
+        Output o = outputMap.get(output);
+
+        if (o == null) {
+            logger.warn("No Output '" + output + "' existing.");
+            return new ArrayList<Facet>();
+        }
+
+        return o.getFacets();
+    }
+
+
+    public List<Facet> getFacets() {
+        List<Facet> allFacets = new ArrayList<Facet>();
+
+        if (outputMap == null || outputMap.size() == 0) {
+            logger.warn("No Outputs existing.");
+            return allFacets;
+        }
+
+        Set<String> outputNames = outputMap.keySet();
+
+        for (String outputName: outputNames) {
+            allFacets.addAll(getFacets(outputName));
+        }
+
+        return allFacets;
+    }
+
+
+    protected void appendOutputs(Element root) {
+        if (outputMap == null || outputMap.size() == 0) {
+            logger.warn("No outputs to append.");
+            return;
+        }
+
+        logger.debug("Append " + outputMap.size() + " Output Elements.");
+
+        Element outputsEl = ec.create("outputs");
+
+        Set<Map.Entry<String, Output>> entrySet = outputMap.entrySet();
+
+        for (Map.Entry<String, Output> entry: entrySet) {
+            appendOutput(outputsEl, entry.getKey(), entry.getValue());
+        }
+
+        root.appendChild(outputsEl);
+    }
+
+
+    protected void appendOutput(Element root, String name, Output output) {
+        if (name == null || name.length() == 0 || output == null) {
+            logger.warn("Tried to appendOutput, but Output is invalid.");
+            return;
+        }
+
+        logger.debug("Append Output Element for '" + name + "'");
+
+        Element outputEl = ec.create("output");
+        ec.addAttr(outputEl, "name", name);
+
+        appendSettings(outputEl, output.getSettings());
+        appendFacets(outputEl, output.getFacets());
+
+        root.appendChild(outputEl);
+    }
+
+
+    protected void appendSettings(Element root, Settings settings) {
+        if (settings == null) {
+            logger.warn("Tried to append Settings, but Settings is empty!");
+            return;
+        }
+
+        settings.toXML(root);
+    }
+
+
+    protected void appendFacets(Element root, List<Facet> facets) {
+        if (facets == null || facets.size() == 0) {
+            logger.warn("Tried to append 0 Facets.");
+            return;
+        }
+
+        Document owner = root.getOwnerDocument();
+
+        logger.debug("Append " + facets.size() + " facets.");
+
+        for (Facet facet: facets) {
+            Node facetNode = facet.toXML(owner);
+
+            if (facetNode != null) {
+                root.appendChild(facetNode);
+            }
+        }
+    }
+
+
+    protected void appendLoadedRecommendations(Element root) {
+        if (loadedRecommendations == null) {
+            logger.debug("No loaded recommendations existing yet.");
+            return;
+        }
+
+        Document owner = root.getOwnerDocument();
+
+        root.appendChild(owner.importNode(loadedRecommendations, true));
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/CollectionDescriptionHelper.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,205 @@
+package de.intevation.flys.collections;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+
+public class CollectionDescriptionHelper {
+
+    private static final Logger logger =
+        Logger.getLogger(CollectionDescriptionHelper.class);
+
+
+    public static final String XPATH_ARTIFACT_STATE_DATA =
+        "/art:result/art:ui/art:static/art:state/art:data";
+
+    /** Constant XPath that points to the outputmodes of an artifact. */
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:result/art:outputmodes";
+
+
+    protected ElementCreator ec;
+
+    protected CallContext      context;
+    protected ArtifactDatabase database;
+
+    protected String name;
+    protected String uuid;
+    protected Date   creation;
+    protected long   ttl;
+
+    protected List<String>        artifacts;
+    protected CollectionAttribute attribute;
+
+
+    /**
+     * @param name The name of the collection.
+     * @param uuid The uuid of the collection.
+     * @param creation The creation time of the collection.
+     * @param ttl The time to live of the collection.
+     */
+    public CollectionDescriptionHelper(
+        String      name,
+        String      uuid,
+        Date        creation,
+        long        ttl,
+        CallContext callContext
+    ) {
+        this.name     = name;
+        this.uuid     = uuid;
+        this.creation = creation;
+        this.ttl      = ttl;
+        this.context  = callContext;
+        this.database = callContext.getDatabase();
+    }
+
+
+    public void addArtifact(String uuid) {
+        if (artifacts == null) {
+            artifacts = new ArrayList<String>();
+        }
+
+        if (uuid != null && uuid.length() > 0) {
+            artifacts.add(uuid);
+        }
+    }
+
+
+    public void setAttribute(CollectionAttribute attribute) {
+        if (attribute != null) {
+            this.attribute = attribute;
+        }
+    }
+
+
+    public Document toXML() {
+        Document doc = XMLUtils.newDocument();
+
+        ec = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("artifact-collection");
+        doc.appendChild(root);
+
+        String creationTime = creation != null
+            ? Long.toString(creation.getTime())
+            : "";
+
+        ec.addAttr(root, "name", name, true);
+        ec.addAttr(root, "uuid", uuid, true);
+        ec.addAttr(root, "creation", creationTime, true);
+        ec.addAttr(root, "ttl", String.valueOf(ttl), true);
+
+        appendArtifacts(root);
+        appendAttribute(root);
+
+        return doc;
+    }
+
+
+    /**
+     * Appends parts of the DESCRIBE document of each Artifact to <i>root</i>.
+     *
+     * @param root The root node.
+     */
+    protected void appendArtifacts(Element root) {
+        Element artifactsEl = ec.create("artifacts");
+
+        for (String uuid: artifacts) {
+            try {
+                Element e = buildArtifactNode(uuid);
+
+                if (e != null) {
+                    artifactsEl.appendChild(e);
+                }
+            }
+            catch (ArtifactDatabaseException dbe) {
+                logger.warn(dbe, dbe);
+            }
+        }
+
+        root.appendChild(artifactsEl);
+    }
+
+
+    /**
+     * Create the Artifacts Node that contains outputmode and statedata.
+     *
+     * @param uuid uuid of the artifact.
+     */
+    protected Element buildArtifactNode(String uuid)
+    throws    ArtifactDatabaseException
+    {
+        logger.debug("Append artifact '" + uuid + "' to collection description");
+
+        // TODO
+        String hash = "MYHASH";
+
+        Element ci = ec.create("artifact");
+        ec.addAttr(ci, "uuid", uuid, true);
+        ec.addAttr(ci, "hash", hash, true);
+
+        // XXX I am not sure if it works well every time with an empty document
+        // in the describe operation of an artifact.
+        Document description = database.describe(uuid, null, context.getMeta());
+
+        // Add outputmode element(s).
+        Node outputModes = (Node) XMLUtils.xpath(
+            description,
+            XPATH_ARTIFACT_OUTPUTMODES,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (outputModes != null) {
+            Document doc = ci.getOwnerDocument();
+            ci.appendChild(doc.importNode(outputModes, true));
+        }
+
+        // Add state-data element(s).
+        Node dataNode = ci.appendChild(
+            ci.getOwnerDocument().createElement("art:data-items"));
+
+        NodeList dataNodes = (NodeList) XMLUtils.xpath(
+            description,
+            XPATH_ARTIFACT_STATE_DATA,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (dataNodes != null) {
+            Document doc = ci.getOwnerDocument();
+            for (int i = 0; i < dataNodes.getLength(); i++) {
+                dataNode.appendChild(doc.importNode(dataNodes.item(i), true));
+            }
+        }
+
+        return ci;
+    }
+
+
+    protected void appendAttribute(Element root) {
+        Document owner = root.getOwnerDocument();
+        Document attr  = attribute.toXML();
+
+        root.appendChild(owner.importNode(attr.getFirstChild(), true));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1063 @@
+package de.intevation.flys.collections;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.ClientProtocolUtils;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.Backend;
+import de.intevation.artifactdatabase.Backend.PersistentArtifact;
+import de.intevation.artifactdatabase.DefaultArtifactCollection;
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Output;
+import de.intevation.artifactdatabase.state.Settings;
+import de.intevation.artifactdatabase.state.StateEngine;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.ManagedFacet;
+import de.intevation.flys.artifacts.model.ManagedDomFacet;
+import de.intevation.flys.exports.OutGenerator;
+import de.intevation.flys.themes.Theme;
+import de.intevation.flys.themes.ThemeFactory;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FLYSArtifactCollection extends DefaultArtifactCollection {
+    /** The logger used in this class. */
+    private static Logger log = Logger.getLogger(FLYSArtifactCollection.class);
+
+    /** Constant XPath that points to the outputmodes of an artifact. */
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:result/art:outputmodes";
+
+    public static final String XPATH_ARTIFACT_STATE_DATA =
+        "/art:result/art:ui/art:static/art:state/art:data";
+
+    public static final String XPATH_COLLECTION_ITEMS =
+        "/art:result/art:artifact-collection/art:collection-item";
+
+    public static final String XPATH_OUT_NAME = "/art:action/@art:name";
+
+    public static final String XPATH_OUT_TYPE = "/art:action/@art:type";
+
+    /** Xpath to master artifacts uuid. */
+    public static final String XPATH_MASTER_UUID =
+        "/art:artifact-collection/art:artifact/@art:uuid";
+
+    public static final String XPATH_LOADED_RECOMMENDATIONS =
+        "/art:attribute/art:loaded-recommendations";
+
+
+    /**
+     * Return description Document for this collection.
+     */
+    @Override
+    public Document describe(CallContext context) {
+        log.debug("FLYSArtifactCollection.describe: " + identifier);
+
+        CollectionDescriptionHelper helper = new CollectionDescriptionHelper(
+            getName(), identifier(), getCreationTime(), getTTL(),
+            context);
+
+        ArtifactDatabase db = context.getDatabase();
+
+        Document        oldAttrs = getAttribute();
+        AttributeParser parser   = new AttributeParser(oldAttrs);
+
+        try {
+            String[]            aUUIDs  = getArtifactUUIDs(context);
+
+            oldAttrs = removeAttributes(oldAttrs, context);
+            parser   = new AttributeParser(oldAttrs);
+
+            CollectionAttribute newAttr = mergeAttributes(
+                db, context, parser, aUUIDs);
+
+            if (checkOutputSettings(newAttr, context)) {
+                saveCollectionAttribute(db, context, newAttr);
+            }
+
+            helper.setAttribute(newAttr);
+
+            // Make it an empty array if null.
+            if (aUUIDs == null) {
+                aUUIDs = new String[] {};
+            }
+
+            for (String uuid: aUUIDs) {
+                helper.addArtifact(uuid);
+            }
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error("Error while merging attribute documents.", ade);
+
+            helper.setAttribute(parser.getCollectionAttribute());
+        }
+
+        return helper.toXML();
+    }
+
+
+    /**
+     * Merge the current art:outputs nodes with the the outputs provided by the
+     * artifacts in the Collection.
+     *
+     * @param uuids Artifact uuids.
+     */
+    protected CollectionAttribute mergeAttributes(
+        ArtifactDatabase db,
+        CallContext      context,
+        AttributeParser  oldParser,
+        String[]         uuids
+    ) {
+        CollectionAttribute cAttribute =
+            buildOutAttributes(db, context, oldParser, uuids);
+
+        cAttribute.setLoadedRecommendations(
+            getLoadedRecommendations(oldParser.getAttributeDocument()));
+
+        saveCollectionAttribute(db, context, cAttribute);
+
+        return cAttribute;
+    }
+
+
+    protected Document removeAttributes(Document attrs, CallContext context) {
+        Node outs = (Node) XMLUtils.xpath(
+            attrs,
+            "/art:attribute/art:outputs",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        NodeList nodes = (NodeList) XMLUtils.xpath(
+            attrs,
+            "/art:attribute/art:outputs/art:output",
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (nodes != null) {
+            for (int i = 0; i < nodes.getLength(); i++) {
+                Element e = (Element)nodes.item(i);
+                if(!outputExists(e.getAttribute("name"), context)) {
+                    outs.removeChild(e);
+                }
+            }
+        }
+        return attrs;
+    }
+
+
+    /**
+     * True if current MasterArtifact has given output.
+     * @param name Name of the output of interest.
+     * @param context current context
+     * @return true iff current mater artifact has given output.
+     */
+    protected boolean outputExists(String name, CallContext context) {
+        FLYSArtifact master = getMasterArtifact(context);
+        List<Output> outList = master.getOutputs(context);
+
+        for (Output o : outList) {
+            if (name.equals(o.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param db The ArtifactDatabase which is required to save the attribute
+     * into.
+     * @param attribute The CollectionAttribute that should be stored in the
+     * database.
+     *
+     * @return true, if the transaction was successful, otherwise false.
+     */
+    protected boolean saveCollectionAttribute(
+        ArtifactDatabase    db,
+        CallContext         context,
+        CollectionAttribute attribute
+    ) {
+        log.info("Save new CollectionAttribute into database.");
+
+        Document doc = attribute.toXML();
+
+        try {
+            // Save the merged document into database.
+            db.setCollectionAttribute(identifier(), context.getMeta(), doc);
+
+            log.info("Saving CollectionAttribute was successful.");
+
+            return true;
+        }
+        catch (ArtifactDatabaseException adb) {
+            log.error(adb, adb);
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Merge the recommendations which have already been loaded from the old
+     * attribute document into the new attribute document. This is necessary,
+     * because mergeAttributes() only merges the art:outputs nodes - all
+     * other nodes are skiped.
+     */
+    protected Node getLoadedRecommendations(Document oldAttrs) {
+        Element loadedRecoms = (Element) XMLUtils.xpath(
+            oldAttrs,
+            XPATH_LOADED_RECOMMENDATIONS,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        return loadedRecoms;
+    }
+
+
+    /**
+     * Evaluates the Output settings. If an Output has no Settings set, the
+     * relevant OutGenerator is used to initialize a default Settings object.
+     *
+     * @param attribute The CollectionAttribute.
+     * @param cc The CallContext.
+     *
+     * @return true, if the CollectionAttribute was modified, otherwise false.
+     */
+    protected boolean checkOutputSettings(
+        CollectionAttribute attribute,
+        CallContext         cc
+    ) {
+        boolean modified = false;
+
+        Map<String, Output> outputMap = attribute != null
+            ? attribute.getOutputs()
+            : null;
+
+        if (outputMap == null || outputMap.size() == 0) {
+            log.debug("No Output Settings check necessary.");
+            return modified;
+        }
+
+        Set<Map.Entry<String, Output>> entries = outputMap.entrySet();
+
+        for (Map.Entry<String, Output> entry: entries) {
+            String outName = entry.getKey();
+            Output output  = entry.getValue();
+
+            Settings settings = output.getSettings();
+
+            if (settings == null) {
+                log.debug("No Settings set for Output '" + outName + "'.");
+                output.setSettings(
+                    createInitialOutputSettings(cc, attribute, outName));
+
+                modified = true;
+            }
+        }
+
+        return modified;
+    }
+
+
+    /**
+     * This method uses the the OutGenerator for the specified Output
+     * <i>out</i> to create an initial Settings object.
+     *
+     * @param cc The CallContext object.
+     * @param attr The CollectionAttribute.
+     * @param out The name of the output.
+     *
+     * @return a default Settings object for the specified Output.
+     */
+    protected Settings createInitialOutputSettings(
+        CallContext         cc,
+        CollectionAttribute attr,
+        String              out
+    ) {
+        OutGenerator outGen = getOutGenerator(cc, out, null);
+
+        if (outGen == null) {
+            return null;
+        }
+
+        // XXX NOTE: the outGen is not able to process its generate() operation,
+        // because it has no OutputStream set!
+        outGen.init(XMLUtils.newDocument(), null, cc);
+        prepareMasterArtifact(outGen, cc);
+
+        try {
+            Document outAttr = getAttribute(cc, attr, out);
+            doOut(outGen, out, out, outAttr, cc);
+        }
+        catch (ArtifactDatabaseException adbe) {
+            log.error(adbe, adbe);
+        }
+        catch (IOException ioe) {
+            log.error(ioe, ioe);
+        }
+
+        return outGen.getSettings();
+    }
+
+
+    @Override
+    public void out(
+        String       type,
+        Document     format,
+        OutputStream out,
+        CallContext  context)
+    throws IOException
+    {
+        long reqBegin = System.currentTimeMillis();
+
+        log.info("FLYSArtifactCollection.out");
+
+        String name = XMLUtils.xpathString(
+            format, XPATH_OUT_NAME, ArtifactNamespaceContext.INSTANCE);
+
+        String subtype = XMLUtils.xpathString(
+            format, XPATH_OUT_TYPE, ArtifactNamespaceContext.INSTANCE);
+
+        log.info("-> Output name = " + name);
+        log.info("-> Output type = " + type);
+        log.info("-> Output subtype = " + subtype);
+
+        OutGenerator generator = null;
+        if (type != null
+             && type.length() > 0
+             && type.indexOf("chartinfo") > 0)
+        {
+            generator = getOutGenerator(context, type, subtype);
+        }
+        else {
+            generator = getOutGenerator(context, name, subtype);
+        }
+
+        if (generator == null) {
+            log.error("There is no generator specified for output: " + name);
+            // TODO Throw an exception.
+
+            return;
+        }
+
+        Document        oldAttrs  = getAttribute();
+        AttributeParser parser    = new AttributeParser(oldAttrs);
+        CollectionAttribute cAttr = parser.getCollectionAttribute();
+
+        Output   output   = cAttr.getOutput(name);
+        Settings settings = output.getSettings();
+
+        generator.init(format, out, context);
+        generator.setSettings(settings);
+        prepareMasterArtifact(generator, context);
+
+        try {
+            Document attr = getAttribute(context, cAttr, name);
+            doOut(generator, name, subtype, attr, context);
+            generator.generate();
+        }
+        catch (ArtifactDatabaseException adbe) {
+            log.error(adbe, adbe);
+        }
+
+        long duration = System.currentTimeMillis() -reqBegin;
+        log.info("Processing out(" + name + ") took " + duration + " ms.");
+    }
+
+
+    /**
+     * Sets the master Artifact at the given <i>generator</i>.
+     *
+     * @param generator The generator that gets a master Artifact.
+     * @param cc The CallContext.
+     */
+    protected void prepareMasterArtifact(OutGenerator generator, CallContext cc
+    ) {
+        // Get master artifact.
+        FLYSArtifact master = getMasterArtifact(cc);
+        if (master != null) {
+            log.debug("Set master Artifact to uuid: " + master.identifier());
+            generator.setMasterArtifact(master);
+        }
+        else {
+            log.warn("Could not set master artifact.");
+        }
+    }
+
+
+    /**
+     * Creates the concrete output.
+     *
+     * @param generator The OutGenerator that creates the output.
+     * @param outputName The name of the requested output.
+     * @param attributes The collection's attributes for this concrete output
+     * type.
+     * @param context The context object.
+     */
+    protected void doOut(
+        OutGenerator generator,
+        String       outName,
+        String       facet,
+        Document     attributes,
+        CallContext  context)
+    throws IOException
+    {
+        log.info("FLYSArtifactCollection.doOut: " + outName);
+
+        ThemeList themeList = new ThemeList(attributes);
+
+        int size = themeList.size();
+        log.debug("Output will contain " + size + " elements.");
+
+        List<ArtifactAndFacet> dataProviders =
+            doBlackboardPass(themeList, context);
+
+        try {
+            for (int i = 0; i < size; i++) {
+                ManagedFacet theme = themeList.get(i);
+
+                if (theme == null) {
+                    log.debug("Theme is empty - no output is generated.");
+                    continue;
+                }
+
+                String art = theme.getArtifact();
+                String facetName = theme.getName();
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Do output for...");
+                    log.debug("... artifact: " + art);
+                    log.debug("... facet: " + facetName);
+                }
+
+                if (outName.equals("export") && !facetName.equals(facet)) {
+                    continue;
+                }
+
+                // Skip invisible themes.
+                if (theme.getVisible() == 0) {
+                    continue;
+                }
+
+                generator.doOut(
+                    dataProviders.get(i),
+                    getFacetThemeFromAttribute(
+                        art,
+                        outName,
+                        facetName,
+                        theme.getDescription(),
+                        theme.getIndex(),
+                        context),
+                    theme.getActive() == 1);
+            }
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error(ade, ade);
+        }
+    }
+
+
+    /**
+     * Show blackboard (context) to each facet and create a list of
+     * ArtifactAndFacets on the fly (with the same ordering as the passed
+     * ThemeList).
+     * @param themeList ThemeList to create a ArtifactAndFacetList along.
+     * @param contect   The "Blackboard".
+     */
+    protected List<ArtifactAndFacet> doBlackboardPass(
+        ThemeList themeList, CallContext context
+    ) {
+        ArrayList<ArtifactAndFacet> dataProviders =
+            new ArrayList<ArtifactAndFacet>();
+        int size = themeList.size();
+
+        try {
+            // Collect all ArtifactAndFacets for blackboard pass.
+            for (int i = 0; i < size; i++) {
+                ManagedFacet theme = themeList.get(i);
+                if (theme == null) {
+                    log.warn("A ManagedFacet in ThemeList is null.");
+                    continue;
+                }
+                String uuid        = theme.getArtifact();
+                Artifact artifact  = getArtifact(uuid, context);
+                FLYSArtifact flys  = (FLYSArtifact) artifact;
+
+                ArtifactAndFacet artifactAndFacet = new ArtifactAndFacet(
+                    artifact,
+                    flys.getNativeFacet(theme));
+
+                // XXX HELP ME PLEASE
+                artifactAndFacet.setFacetDescription(theme.getDescription());
+
+                // Show blackboard to facet.
+                artifactAndFacet.register(context);
+
+                // Add to themes.
+                dataProviders.add(i, artifactAndFacet);
+            }
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error("ArtifactDatabaseException!", ade);
+        }
+
+        return dataProviders;
+    }
+
+
+    /**
+     * @return masterartifact or null if exception/not found.
+     */
+    protected FLYSArtifact getMasterArtifact(CallContext context)
+    {
+        try {
+            ArtifactDatabase db = context.getDatabase();
+            CallMeta callMeta   = context.getMeta();
+            Document document   = db.getCollectionsMasterArtifact(
+                identifier(), callMeta);
+
+            String masterUUID   = XMLUtils.xpathString(
+                document, XPATH_MASTER_UUID, ArtifactNamespaceContext.INSTANCE);
+            FLYSArtifact masterArtifact =
+                (FLYSArtifact) getArtifact(masterUUID, context);
+            return masterArtifact;
+        }
+        catch (ArtifactDatabaseException ade) {
+            log.error(ade, ade);
+        }
+        return null;
+    }
+
+
+    /**
+     * Return merged output document.
+     * @param uuids List of artifact uuids.
+     */
+    protected CollectionAttribute buildOutAttributes(
+        ArtifactDatabase db,
+        CallContext      context,
+        AttributeParser  aParser,
+        String[]         uuids)
+    {
+        Document doc = XMLUtils.newDocument();
+
+        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
+        StateEngine engine = (StateEngine) flysContext.get(
+        FLYSContext.STATE_ENGINE_KEY);
+
+        FLYSArtifact masterArtifact = getMasterArtifact(context);
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        OutputParser oParser = new OutputParser(db, context);
+
+        if (uuids != null) {
+            for (String uuid: uuids) {
+                try {
+                    oParser.parse(uuid);
+                }
+                catch (ArtifactDatabaseException ade) {
+                    log.warn(ade, ade);
+                }
+            }
+        }
+
+        aParser.parse();
+
+        return new AttributeWriter(
+            db,
+            aParser.getCollectionAttribute(),
+            aParser.getOuts(),
+            aParser.getFacets(),
+            oParser.getOuts(),
+            oParser.getFacets(),
+            engine.getCompatibleFacets(masterArtifact.getStateHistoryIds())
+            ).write();
+    }
+
+
+    /**
+     * Returns the "attribute" (part of description document) for a specific
+     * output type.
+     *
+     * @param context The CallContext object.
+     * @param cAttr The CollectionAttribute.
+     * @param output The name of the desired output type.
+     *
+     * @return the attribute for the desired output type.
+     */
+    protected Document getAttribute(
+        CallContext         context,
+        CollectionAttribute cAttr,
+        String              output)
+    throws    ArtifactDatabaseException
+    {
+        Document attr = cAttr.toXML();
+
+        Node out = (Node) XMLUtils.xpath(
+            attr,
+            "art:attribute/art:outputs/art:output[@name='" + output + "']",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+
+        if (out != null) {
+            Document o = XMLUtils.newDocument();
+
+            o.appendChild(o.importNode(out, true));
+
+            return o;
+        }
+
+        return null;
+    }
+
+
+    /**
+     * This method returns the list of artifact UUIDs that this collections
+     * contains.
+     *
+     * @param context The CallContext that is necessary to get information about
+     * the ArtifactDatabase.
+     *
+     * @return a list of uuids.
+     */
+    protected String[] getArtifactUUIDs(CallContext context)
+    throws    ArtifactDatabaseException
+    {
+        log.debug("FLYSArtifactCollection.getArtifactUUIDs");
+
+        ArtifactDatabase db = context.getDatabase();
+        CallMeta meta       = context.getMeta();
+
+        Document itemList = db.listCollectionArtifacts(identifier(), meta);
+        NodeList items    = (NodeList) XMLUtils.xpath(
+            itemList,
+            XPATH_COLLECTION_ITEMS,
+            XPathConstants.NODESET,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (items == null || items.getLength() == 0) {
+            log.debug("No artifacts found in this collection.");
+            return null;
+        }
+
+        int num = items.getLength();
+
+        List<String> uuids = new ArrayList<String>(num);
+
+        for (int i = 0; i < num; i++) {
+            String uuid = XMLUtils.xpathString(
+                items.item(i),
+                "@art:uuid",
+                ArtifactNamespaceContext.INSTANCE);
+
+            if (uuid != null && uuid.trim().length() != 0) {
+                uuids.add(uuid);
+            }
+        }
+
+        return (String[]) uuids.toArray(new String[uuids.size()]);
+    }
+
+
+    /**
+     * Returns a concrete Artifact of this collection specified by its uuid.
+     *
+     * @param uuid The Artifact's uuid.
+     * @param context The CallContext.
+     *
+     * @return an Artifact.
+     */
+    protected Artifact getArtifact(String uuid, CallContext context)
+    throws    ArtifactDatabaseException
+    {
+        log.debug("FLYSArtifactCollection.getArtifact");
+
+        Backend backend               = Backend.getInstance();
+        PersistentArtifact persistent = backend.getArtifact(uuid);
+
+        return persistent != null ? persistent.getArtifact() : null;
+    }
+
+
+    /**
+     * Returns the attribute that belongs to an artifact and facet stored in
+     * this collection.
+     *
+     * @param uuid The Artifact's uuid.
+     * @param outname The name of the requested output.
+     * @param facet The name of the requested facet.
+     * @param context The CallContext.
+     *
+     * @return an attribute in form of a document.
+     */
+    protected Document getFacetThemeFromAttribute(
+        String      uuid,
+        String      outName,
+        String      facet,
+        String      pattern,
+        int         index,
+        CallContext context)
+    throws    ArtifactDatabaseException
+    {
+        log.debug("FLYSArtifactCollection.getFacetThemeFromAttribute");
+
+        ArtifactDatabase db = context.getDatabase();
+        CallMeta       meta = context.getMeta();
+
+        FLYSContext flysContext = context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) context.globalContext();
+
+        Document attr = db.getCollectionItemAttribute(identifier(), uuid, meta);
+
+        if (attr == null) {
+            attr = initItemAttribute(uuid, facet, pattern, index, outName, context);
+
+            if (attr == null) {
+                return null;
+            }
+        }
+
+        log.debug("Search attribute of collection item: " + uuid);
+
+        Node tmp = (Node) XMLUtils.xpath(
+            attr,
+            "/art:attribute",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (tmp == null) {
+            log.warn("No attribute found. Operation failed.");
+            return null;
+        }
+
+        log.debug("Search theme for facet '" + facet + "' in attribute.");
+
+        Node theme = (Node) XMLUtils.xpath(
+            tmp,
+            "art:themes/theme[@facet='" + facet +
+            "' and @index='" + String.valueOf(index) + "']",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (theme == null) {
+            log.warn("Could not find the theme in attribute of: " + uuid);
+
+            Theme t = getThemeForFacet(
+                uuid, facet, pattern, index, outName, context);
+
+            if (t == null) {
+                log.warn("No theme found for facet: " + facet);
+                return null;
+            }
+
+            addThemeToAttribute(uuid, attr, t, context);
+            theme = t.toXML().getFirstChild();
+        }
+
+        Document doc = XMLUtils.newDocument();
+        doc.appendChild(doc.importNode(theme, true));
+
+        return doc;
+    }
+
+
+    /**
+     * Adds the theme of a facet to a CollectionItem's attribute.
+     *
+     * @param uuid The uuid of the artifact.
+     * @param attr The current attribute of an artifact.
+     * @param t The theme to add.
+     * @param context The CallContext.
+     */
+    protected void addThemeToAttribute(
+        String      uuid,
+        Document    attr,
+        Theme       t,
+        CallContext context)
+    {
+        log.debug("FLYSArtifactCollection.addThemeToAttribute: " + uuid);
+
+        if (t == null) {
+            log.warn("Theme is empty - cancel adding it to attribute!");
+            return;
+        }
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            attr,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Node tmp = (Node) XMLUtils.xpath(
+            attr,
+            "/art:attribute",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (tmp == null) {
+            tmp = ec.create("attribute");
+            attr.appendChild(tmp);
+        }
+
+        Node themes = (Node) XMLUtils.xpath(
+            tmp,
+            "art:themes",
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (themes == null) {
+            themes = ec.create("themes");
+            tmp.appendChild(themes);
+        }
+
+        themes.appendChild(attr.importNode(t.toXML().getFirstChild(), true));
+
+        try {
+            setCollectionItemAttribute(uuid, attr, context);
+
+            log.debug("Successfully added theme to item attribute.");
+        }
+        catch (ArtifactDatabaseException e) {
+            // do nothing
+            log.warn("Cannot set attribute of item: " + uuid);
+        }
+    }
+
+
+    /**
+     * Initializes the attribute of an collection item with the theme of a
+     * specific facet.
+     *
+     * @param uuid The uuid of an artifact.
+     * @param facet The name of a facet.
+     * @param context The CallContext.
+     *
+     * @param the new attribute.
+     */
+    protected Document initItemAttribute(
+        String      uuid,
+        String      facet,
+        String      pattern,
+        int         index,
+        String      outName,
+        CallContext context)
+    {
+        log.info("FLYSArtifactCollection.initItemAttribute");
+
+        Theme t = getThemeForFacet(uuid, facet, pattern, index, outName, context);
+
+        if (t == null) {
+            log.info("Could not find theme for facet. Cancel initialization.");
+            return null;
+        }
+
+        Document attr = XMLUtils.newDocument();
+
+        addThemeToAttribute(uuid, attr, t, context);
+
+        return attr;
+    }
+
+
+    /**
+     * Sets the attribute of a CollectionItem specified by <i>uuid</i> to a new
+     * value <i>attr</i>.
+     *
+     * @param uuid The uuid of the CollectionItem.
+     * @param attr The new attribute for the CollectionItem.
+     * @param context The CallContext.
+     */
+    public void setCollectionItemAttribute(
+        String      uuid,
+        Document    attr,
+        CallContext context)
+    throws ArtifactDatabaseException
+    {
+        Document doc = ClientProtocolUtils.newSetItemAttributeDocument(
+            uuid,
+            attr);
+
+        if (doc == null) {
+            log.warn("Cannot set item attribute: No attribute found.");
+            return;
+        }
+
+        ArtifactDatabase db = context.getDatabase();
+        CallMeta       meta = context.getMeta();
+
+        db.setCollectionItemAttribute(identifier(), uuid, doc, meta);
+    }
+
+
+    /**
+     * Returns the theme of a specific facet.
+     *
+     * @param uuid The uuid of an artifact.
+     * @param facet The name of the facet.
+     * @param context The CallContext object.
+     *
+     * @return the desired theme.
+     */
+    protected Theme getThemeForFacet(
+        String uuid,
+        String facet,
+        String pattern,
+        int    index,
+        String outName,
+        CallContext context)
+    {
+        log.info("FLYSArtifactCollection.getThemeForFacet: " + facet);
+
+        FLYSContext flysContext = context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) context.globalContext();
+
+        // Push artifact in flysContext.
+        ArtifactDatabase db = context.getDatabase();
+        try {
+            FLYSArtifact artifact = (FLYSArtifact) db.getRawArtifact(uuid);
+            log.debug("Got raw artifact");
+            flysContext.put(flysContext.ARTIFACT_KEY, artifact);
+        }
+        catch (ArtifactDatabaseException dbe) {
+            log.error("Exception caught when trying to get art.", dbe);
+        }
+
+        Theme t = ThemeFactory.getTheme(
+                      flysContext,
+                      facet,
+                      pattern,
+                      outName,
+                      "default");
+
+        if (t != null) {
+            t.setFacet(facet);
+            t.setIndex(index);
+        }
+
+        return t;
+    }
+
+
+    /**
+     * Returns the OutGenerator for a specified <i>type</i>.
+     *
+     * @param name The name of the output type.
+     * @param type Defines the type of the desired OutGenerator.
+     *
+     * @return Instance of an OutGenerator for specified <i>type</i>.
+     */
+    protected OutGenerator getOutGenerator(
+        CallContext context,
+        String      name,
+        String      type)
+    {
+        log.debug("Search OutGenerator for Output '" + name + "'");
+
+        FLYSContext flysContext = context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) context.globalContext();
+
+        Map<String, Class> generators = (Map<String, Class>)
+            flysContext.get(FLYSContext.OUTGENERATORS_KEY);
+
+        if (generators == null) {
+            log.error("No output generators found in the running application!");
+            return null;
+        }
+
+        Class clazz = generators.get(name);
+
+        try {
+            return clazz != null ? (OutGenerator) clazz.newInstance() : null;
+        }
+        catch (InstantiationException ie) {
+            log.error(ie, ie);
+        }
+        catch (IllegalAccessException iae) {
+            log.error(iae, iae);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Inner class to structure/order the themes of a chart.
+     */
+    private static class ThemeList {
+        private Logger logger = Logger.getLogger(ThemeList.class);
+        protected Map<Integer, ManagedFacet> themes;
+
+        public ThemeList(Document output) {
+            themes = new HashMap<Integer, ManagedFacet>();
+            parse(output);
+        }
+
+        protected void parse(Document output) {
+            NodeList themeList = (NodeList) XMLUtils.xpath(
+                output,
+                "art:output/art:facet",
+                XPathConstants.NODESET,
+                ArtifactNamespaceContext.INSTANCE);
+
+            int num = themeList != null ? themeList.getLength() : 0;
+
+            logger.debug("Output has " +  num + " elements.");
+
+            if (num == 0) {
+                return;
+            }
+
+            String uri = ArtifactNamespaceContext.NAMESPACE_URI;
+
+            for (int i = 0; i < num; i++) {
+                Element theme = (Element) themeList.item(i);
+
+                ManagedDomFacet facet = new ManagedDomFacet(theme);
+                themes.put(Integer.valueOf(facet.getPosition()-1), facet);
+            }
+        }
+
+        public ManagedFacet get(int idx) {
+            return themes.get(Integer.valueOf(idx));
+        }
+
+        public int size() {
+            return themes.size();
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/collections/OutputParser.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,135 @@
+package de.intevation.flys.collections;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifactdatabase.state.DefaultOutput;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Output;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.ManagedFacetAdapter;
+
+
+public class OutputParser {
+
+    /** Constant XPath that points to the outputmodes of an artifact. */
+    public static final String XPATH_ARTIFACT_OUTPUTMODES =
+        "/art:result/art:outputmodes/art:output";
+
+    private static Logger logger = Logger.getLogger(OutputParser.class);
+
+    protected ArtifactDatabase db;
+    protected CallMeta         meta;
+    protected CallContext      context;
+
+    /** Map outputs name to Output. */
+    protected Map<String, Output> outs;
+
+    /** Map facets name to list of Facets. */
+    protected List<Facet> facets;
+
+
+    /**
+     * @param db Database used to fetch artifacts, outputs and facets.
+     */
+    public OutputParser(ArtifactDatabase db, CallContext context) {
+        this.db      = db;
+        this.meta    = context.getMeta();
+        this.context = context;
+        this.outs    = new HashMap<String, Output>();
+        this.facets  = new ArrayList<Facet>();
+    }
+
+
+    /**
+     * Gets raw artifact with given id and sorts outputs in mapping.
+     * Converts Facets to ManagedFacets on the way.
+     * @param uuid uuid of artifact to load from database.
+     */
+    public void parse(String uuid)
+    throws ArtifactDatabaseException
+    {
+        logger.debug("OutputParser.parse: " + uuid);
+
+        FLYSArtifact flys = (FLYSArtifact) db.getRawArtifact(uuid);
+
+        List<Output> outList = flys.getOutputs(context);
+
+        logger.debug("   has " + outList.size() + " Outputs.");
+
+        for (Output out: outList) {
+            String name = out.getName();
+            logger.debug("Process Output '" + name + "'");
+
+            Output o = outs.get(name);
+            int  pos = 1;
+
+            if (o == null) {
+                o = new DefaultOutput(
+                    out.getName(),
+                    out.getDescription(),
+                    out.getMimeType(),
+                    new ArrayList<Facet>(),
+                    out.getType());
+                outs.put(name, o);
+            }
+            else {
+                logger.debug("OutputParser.parse: Use 'old' Output");
+                pos = o.getFacets().size() + 1;
+            }
+
+            List<Facet> mfacets = facet2ManagedFacet(uuid, out.getFacets(), pos);
+            o.addFacets(mfacets);
+            this.facets.addAll(mfacets);
+        }
+    }
+
+
+    /**
+     * Access mapping of Outputname to Output.
+     */
+    public Map<String, Output> getOuts() {
+        return outs;
+    }
+
+
+    /**
+     * Access all facets.
+     */
+    public List<Facet> getFacets() {
+        return this.facets;
+    }
+
+
+    /**
+     * Creates a list of ManagedFacets from list of Facets.
+     * @param pos Position of first facet (for each other the positions
+     *            will be increased).
+     */
+    protected List<Facet> facet2ManagedFacet(
+        String      uuid,
+        List<Facet> old,
+        int         pos)
+    {
+        List<Facet> newFacets = new ArrayList<Facet>(old.size());
+
+        logger.debug("There are " + old.size() + " Facets for this Output.");
+
+        for (Facet f: old) {
+            newFacets.add(new ManagedFacetAdapter(f, uuid, pos++, 1, 1));
+        }
+
+        return newFacets;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ATExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,108 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.WQ;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+public class ATExporter
+implements   OutGenerator
+{
+    private static Logger logger = Logger.getLogger(ATExporter.class);
+
+    public static final String DEFAULT_ENCODING = "UTF-8";
+
+    protected WQ           data;
+    protected CallContext  context;
+    protected OutputStream out;
+    protected FLYSArtifact master;
+
+    public ATExporter() {
+    }
+
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        this.context = context;
+        this.out     = out;
+    }
+
+    @Override
+    public void setMasterArtifact(Artifact master) {
+        this.master = (FLYSArtifact) master;
+    }
+
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactf,
+        Document attr,
+        boolean  visible
+    ) {
+        data = (WQ)artifactf.getData(context);
+    }
+
+    @Override
+    public void generate() throws IOException {
+
+        if (data == null) {
+            logger.debug("no W/Q data");
+            return;
+        }
+
+        ATWriter at;
+        try {
+            at = new ATWriter(data);
+        }
+        catch (IllegalArgumentException iae) {
+            logger.error("creating ATWriter failed", iae);
+            throw new IOException(iae);
+        }
+
+        String   river = FLYSUtils.getRiver(master).getName();
+        double[] kms   = FLYSUtils.getLocations(master);
+
+        at.write(
+            new OutputStreamWriter(out, DEFAULT_ENCODING),
+            context.getMeta(),
+            river,
+            kms[0]);
+    }
+
+
+    /**
+     * Returns an instance of <i>EmptySettings</i> currently!
+     *
+     * @return an instance of <i>EmptySettings</i>.
+     */
+    @Override
+    public Settings getSettings() {
+        return new EmptySettings();
+    }
+
+
+    /**
+     * This method is not implemented!
+     *
+     * @param settings A settings object.
+     */
+    @Override
+    public void setSettings(Settings settings) {
+        // do nothing here
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ATWriter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,166 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.io.PrintWriter;
+
+import java.util.Locale;
+
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.model.WQ;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
+import org.apache.commons.math.analysis.interpolation.LinearInterpolator;
+
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+import org.apache.log4j.Logger;
+
+public class ATWriter
+{
+    private static Logger logger = Logger.getLogger(ATWriter.class);
+
+    public static final int COLUMNS = 10;
+
+    public static final String I18N_AT_HEADER =
+        "export.discharge.curve.at.header";
+
+    public static final String EMPTY = "         ";
+
+    protected double minW;
+    protected double maxW;
+    protected double minQ;
+    protected double maxQ;
+
+    protected UnivariateRealFunction qFunc;
+
+    public ATWriter() {
+    }
+
+    public ATWriter(WQ wq) throws IllegalArgumentException {
+
+        int [] bounds = wq.longestIncreasingWRangeIndices();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("exporting w between indices " +
+                bounds[0] + " and " + bounds[1] + " (" +
+                wq.getW(bounds[0]) + ", " + wq.getW(bounds[1]));
+        }
+
+        if (bounds[1]-bounds[0] < 1) { // Only first w can be written out.
+            minW = maxW = wq.getW(bounds[0]);
+            minQ = maxQ = wq.getQ(bounds[0]);
+            // constant function
+            qFunc = new PolynomialFunction(new double [] { minQ });
+            return;
+        }
+
+        double [] ws = new double[bounds[1]-bounds[0]];
+        double [] qs = new double[ws.length];
+
+        for (int i = 0; i < ws.length; ++i) {
+            int idx = bounds[0]+i;
+            ws[i] = wq.getW(idx);
+            qs[i] = wq.getQ(idx);
+        }
+
+        qFunc = ws.length < 3
+            ? new LinearInterpolator().interpolate(ws, qs)
+            : new SplineInterpolator().interpolate(ws, qs);
+
+        minW = wq.getW(bounds[0]);
+        maxW = wq.getW(bounds[1]);
+        minQ = wq.getQ(bounds[0]);
+        maxQ = wq.getQ(bounds[1]);
+    }
+
+    public double getQ(double w) {
+
+        try {
+            return qFunc.value(w);
+        }
+        catch (FunctionEvaluationException aode) {
+            // should not happen
+            logger.warn("spline interpolation failed", aode);
+            return w <= minW ? minQ : maxQ;
+        }
+    }
+
+    protected static void printQ(PrintWriter out, double q) {
+        String format;
+             if (q <   1d) format = " % 8.3f";
+        else if (q <  10d) format = " % 8.2f";
+        else if (q < 100d) format = " % 8.1f";
+        else {
+            format = " % 8.0f";
+            if (q > 1000d) q = Math.rint(q/10d)*10d;
+        }
+        out.printf(Locale.US, format, q);
+    }
+
+
+    protected static void printHeader(
+        PrintWriter out,
+        CallMeta    callMeta,
+        String      river,
+        double      km
+    ) {
+        out.println(Resources.getMsg(
+            callMeta,
+            I18N_AT_HEADER,
+            I18N_AT_HEADER,
+            new Object[] { river, km } ));
+    }
+
+
+    public void write(Writer writer, CallMeta meta, String river, double km)
+    throws IOException
+    {
+        PrintWriter out = new PrintWriter(writer);
+
+        // a header is required, because the desktop version of FLYS will skip
+        // the first row.
+        printHeader(out, meta, river, km);
+
+        double rest = (minW * 100.0) % 10.0;
+
+        double startW = Math.rint((minW - rest*0.01)*10.0)*0.1;
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("startW: " + startW);
+            logger.debug("rest: " + rest);
+        }
+
+        int col = 0;
+        for (double w = startW; w <= maxW; w += 0.01) {
+            if (col == 0) {
+                out.printf(Locale.US, "%8d", (int)Math.round(w*100.0));
+            }
+
+            if (w < minW) {
+                out.print(EMPTY);
+            }
+            else {
+                printQ(out, getQ(w));
+            }
+
+            if (++col >= COLUMNS) {
+                out.println();
+                col = 0;
+            }
+        }
+
+        if (col > 0) {
+            out.println();
+        }
+
+        out.flush();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/AbstractExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,301 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+import java.text.NumberFormat;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * An abstract exporter that implements some basic methods for exporting data of
+ * artifacts.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class AbstractExporter implements OutGenerator {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(AbstractExporter.class);
+
+
+    /** The name of the CSV facet which triggers the CSV creation. */
+    public static final String FACET_CSV = "csv";
+
+    /** The name of the PDF facet which triggers the PDF creation. */
+    public static final String FACET_PDF = "pdf";
+
+    /** The default charset for the CSV export. */
+    public static final String DEFAULT_CSV_CHARSET = "UTF-8";
+
+    /** The default separator for the CSV export. */
+    public static final char DEFAULT_CSV_SEPARATOR = ',';
+
+    /** XPath that points to the desired export facet. */
+    public static final String XPATH_FACET = "/art:action/@art:type";
+
+
+    /** The document of the incoming out() request. */
+    protected Document request;
+
+    /** The output stream where the data should be written to. */
+    protected OutputStream out;
+
+    /** The CallContext object. */
+    protected CallContext context;
+
+    /** The selected facet. */
+    protected String facet;
+
+    /** The master artifact. */
+    protected Artifact master;
+
+
+    /**
+     * Concrete subclasses need to use this method to write their special data
+     * objects into the CSV document.
+     *
+     * @param writer The CSVWriter.
+     */
+    protected abstract void writeCSVData(CSVWriter writer) throws IOException;
+
+
+    /**
+     * Concrete subclasses need to use this method to write their special data
+     * objects into the PDF document.
+     */
+    protected abstract void writePDF(OutputStream out);
+
+
+    /**
+     * This method enables concrete subclasses to collected its own special
+     * data.
+     *
+     * @param artifacts The artifact that stores the data that has to be
+     * exported.
+     */
+    protected abstract void addData(Object data);
+
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("AbstractExporter.init");
+
+        this.request = request;
+        this.out     = out;
+        this.context = context;
+    }
+
+
+    @Override
+    public void setMasterArtifact(Artifact master) {
+        this.master = master;
+    }
+
+
+    /**
+     * This doOut() just collects the data of multiple artifacts. Therefore, it
+     * makes use of the addData() method which enables concrete subclasses to
+     * store its data on its own. The real output creation takes place in the
+     * concrete generate() methods.
+     *
+     * @param artifact The artifact.
+     * @param facet The facet to add - NOTE: the facet needs to fit to the first
+     * facet inserted into this exporter. Otherwise this artifact/facet is
+     * skipped.
+     * @param attr The attr document.
+     */
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+
+        logger.debug("AbstractExporter.doOut: " + name);
+
+        if (!isFacetValid(name)) {
+            logger.warn("Facet '" + name + "' not valid. No output created!");
+            return;
+        }
+
+        addData(artifactFacet.getData(context));
+    }
+
+
+    /**
+     * Generates an export based on a specified facet.
+     */
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("AbstractExporter.generate");
+
+        if (facet != null && facet.equals(FACET_CSV)) {
+            generateCSV();
+        }
+        else if (facet != null && facet.equals(FACET_PDF)) {
+            generatePDF();
+        }
+        else {
+            throw new IOException("invalid facet for exporter.");
+        }
+    }
+
+
+    /**
+     * Determines if the desired facet is valid for this exporter. If no facet
+     * is currently set, <i>facet</i> is set.
+     *
+     * @param facet The desired facet.
+     *
+     * @return true, if <i>facet</i> is valid, otherwise false.
+     */
+    protected boolean isFacetValid(String facet) {
+        logger.debug("AbstractExporter.isFacetValid : " + facet + " (" + getFacet() + ")" );
+
+        String thisFacet = getFacet();
+
+        if (thisFacet == null || thisFacet.length() == 0) {
+            return false;
+        }
+        else if (facet == null || facet.length() == 0) {
+            return false;
+        }
+        else {
+            return thisFacet.equals(facet);
+        }
+    }
+
+
+    /**
+     * Returns the name of the desired facet.
+     *
+     * @return the name of the desired facet.
+     */
+    protected String getFacet() {
+        if (facet == null) {
+            facet = getFacetFromRequest();
+        }
+
+        return facet;
+    }
+
+
+    /**
+     * Extracts the name of the requested facet from request document.
+     *
+     * @return the name of the requested facet.
+     */
+    protected String getFacetFromRequest() {
+        return XMLUtils.xpathString(
+            request, XPATH_FACET, ArtifactNamespaceContext.INSTANCE);
+    }
+
+
+    protected String msg(String key, String def) {
+        return Resources.getMsg(context.getMeta(), key, def);
+    }
+
+
+    /**
+     * This method starts CSV creation. It makes use of writeCSVData() which has
+     * to be implemented by concrete subclasses.
+     */
+    protected void generateCSV()
+    throws    IOException
+    {
+        logger.info("AbstractExporter.generateCSV");
+
+        CSVWriter writer = new CSVWriter(
+            new OutputStreamWriter(
+                out,
+                DEFAULT_CSV_CHARSET),
+            DEFAULT_CSV_SEPARATOR);
+
+        writeCSVData(writer);
+
+        writer.close();
+    }
+
+
+    /**
+     * This method starts PDF creation.
+     */
+    protected void generatePDF()
+    throws    IOException
+    {
+        logger.info("AbstractExporter.generatePDF");
+        writePDF(this.out);
+    }
+
+
+    /**
+     * Returns an instance of <i>EmptySettings</i> currently!
+     *
+     * @return an instance of <i>EmptySettings</i>.
+     */
+    public Settings getSettings() {
+        return new EmptySettings();
+    }
+
+
+    /**
+     * This method is not implemented. Override it in subclasses if those need a
+     * <i>Settings</i> object.
+     */
+    public void setSettings(Settings settings) {
+        // do nothing
+    }
+
+
+    /**
+     * Returns the number formatter for kilometer values.
+     *
+     * @return the number formatter for kilometer values.
+     */
+    protected NumberFormat getKmFormatter() {
+        return Formatter.getWaterlevelKM(context);
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    protected NumberFormat getWFormatter() {
+        return Formatter.getWaterlevelW(context);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values.
+     *
+     * @return the number formatter for Q values.
+     */
+    protected NumberFormat getQFormatter() {
+        return Formatter.getWaterlevelQ(context);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/AxisSection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,106 @@
+package de.intevation.flys.exports;
+
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifactdatabase.state.Attribute;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class AxisSection extends TypeSection {
+
+    public static final String IDENTIFIER_ATTR = "id";
+    public static final String LABEL_ATTR      = "label";
+    public static final String FONTSIZE_ATTR   = "font-size";
+    public static final String FIXATION_ATTR   = "fixation";
+    public static final String UPPERRANGE_ATTR = "upper";
+    public static final String LOWERRANGE_ATTR = "lower";
+
+
+    public AxisSection() {
+        super("axis");
+    }
+
+
+    public void setIdentifier(String identifier) {
+        setStringValue(IDENTIFIER_ATTR, identifier);
+    }
+
+
+    public String getIdentifier() {
+        return getStringValue(IDENTIFIER_ATTR);
+    }
+
+
+    public void setLabel(String label) {
+        setStringValue(LABEL_ATTR, label);
+    }
+
+
+    public String getLabel() {
+        return getStringValue(LABEL_ATTR);
+    }
+
+
+    public void setFontSize(int fontSize) {
+        if (fontSize <= 0) {
+            return;
+        }
+
+        setIntegerValue(FONTSIZE_ATTR, fontSize);
+    }
+
+
+    public Integer getFontSize() {
+        return getIntegerValue(FONTSIZE_ATTR);
+    }
+
+
+    public void setFixed(boolean fixed) {
+        setBooleanValue(FIXATION_ATTR, fixed);
+    }
+
+
+    public Boolean isFixed() {
+        return getBooleanValue(FIXATION_ATTR);
+    }
+
+
+    public void setUpperRange(double upperRange) {
+        setDoubleValue(UPPERRANGE_ATTR, upperRange);
+    }
+
+
+    public Double getUpperRange() {
+        return getDoubleValue(UPPERRANGE_ATTR);
+    }
+
+
+    public void setLowerRange(double lowerRange) {
+        setDoubleValue(LOWERRANGE_ATTR, lowerRange);
+    }
+
+
+    public Double getLowerRange() {
+        return getDoubleValue(LOWERRANGE_ATTR);
+    }
+
+
+    @Override
+    public void toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+        Element   axis = owner.createElement("axis");
+
+        parent.appendChild(axis);
+
+        for (String key: getKeys()) {
+            Attribute attr = getAttribute(key);
+            attr.toXML(axis);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/BooleanAttribute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.exports;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class BooleanAttribute extends VisibleAttribute {
+
+
+    public BooleanAttribute(String name, boolean value, boolean visible) {
+        super(name, value, visible);
+    }
+
+
+    /**
+     * Calls VisibleAttribute.toXML() and appends afterwards an attribute
+     * <i>type</i> with value <i>boolean</i>.
+     *
+     * @param parent The parent Node.
+     *
+     * @return the new Node that represents this Attribute.
+     */
+    @Override
+    public Node toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+
+        Element ele = (Element) super.toXML(parent);
+        ele.setAttribute("type", "boolean");
+
+        return ele;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartExportHelper.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2011 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+package de.intevation.flys.exports;
+
+import com.lowagie.text.Document;
+import com.lowagie.text.DocumentException;
+import com.lowagie.text.PageSize;
+import com.lowagie.text.Rectangle;
+
+import com.lowagie.text.pdf.PdfContentByte;
+import com.lowagie.text.pdf.PdfTemplate;
+import com.lowagie.text.pdf.PdfWriter;
+
+import java.awt.Graphics2D;
+import java.awt.Transparency;
+
+import java.awt.geom.Rectangle2D.Double;
+
+import java.awt.geom.Rectangle2D;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import org.jfree.chart.ChartRenderingInfo;
+
+import javax.imageio.ImageIO;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.svggen.SVGGraphics2DIOException;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.xy.XYDataset;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * This class is a helper class which supports some methods to export charts
+ * into specific formats.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ChartExportHelper {
+
+    public static final String FORMAT_PNG = "png";
+
+    public static final String FORMAT_PDF = "pdf";
+
+    public static final String FORMAT_SVG = "svg";
+
+    public static final String FORMAT_CSV = "csv";
+
+    /**
+     * Constant field to define A4 as default page size.
+     */
+    public static final String  DEFAULT_PAGE_SIZE = "A4";
+
+    /**
+     * Constant field to define UTF-8 as default encoding.
+     */
+    public static final String  DEFAULT_ENCODING  = "UTF-8";
+
+    /** The default separator for the CSV export. */
+    public static final char DEFAULT_CSV_SEPARATOR = ',';
+
+
+    /**
+     * Logger used for logging with log4j.
+     */
+    private static Logger log = Logger.getLogger(ChartExportHelper.class);
+
+
+    /**
+     * A method to export a <code>JFreeChart</code> as image to an
+     * <code>OutputStream</code> with a given format, width and height.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart object to be exported.
+     * @param format Format (e.g. png, gif, jpg)
+     * @param width Width, the image used to be
+     * @param height Height, the image used to be
+     *
+     * @throws IOException if writing image to OutputStream failed.
+     */
+    public static void exportImage(
+        OutputStream out,
+        JFreeChart   chart,
+        CallContext  cc
+    )
+    throws IOException
+    {
+        log.info("export chart as png");
+
+        ChartRenderingInfo info = new ChartRenderingInfo();
+
+        String format = (String) cc.getContextValue("chart.image.format");
+
+        int[] size = getSize(cc);
+
+        ImageIO.write(
+            chart.createBufferedImage(
+                size[0], size[1], Transparency.BITMASK, info
+            ),
+            format,
+            out
+        );
+    }
+
+
+    /**
+     * A method to export a <code>JFreeChart</code> as SVG to an
+     * <code>OutputStream</code>.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart to be exported
+     * @param context The CallContext object that contains extra chart
+     * parameters.
+     */
+    public static void exportSVG(
+        OutputStream out,
+        JFreeChart   chart,
+        CallContext  context
+    ) {
+        String encoding = (String) context.getContextValue("chart.encoding");
+
+        log.info("export chart as svg");
+
+        if (encoding == null)
+            encoding = DEFAULT_ENCODING;
+
+        org.w3c.dom.Document document = XMLUtils.newDocument();
+        SVGGraphics2D        graphics = new SVGGraphics2D(document);
+
+        int[] size = getSize(context);
+
+        ChartRenderingInfo info = new ChartRenderingInfo();
+
+        chart.draw(graphics, new Rectangle2D.Double(0.0D, 0.0D,size[0],size[1]), info);
+
+        try {
+            graphics.stream(new OutputStreamWriter(out, encoding));
+        }
+        catch (SVGGraphics2DIOException svge) {
+            log.error("Error while writing svg export to output stream.", svge);
+        }
+        catch (UnsupportedEncodingException uee) {
+            log.error("Unsupported encoding: " + encoding, uee);
+        }
+    }
+
+
+    /**
+     * A method to export a <code>JFreeChart</code> as PDF to an
+     * <code>OutputStream</code>.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart
+     * @param pageFormat String to specify a page format, {@link
+     * #DEFAULT_PAGE_SIZE} is used if no pageFormat is given
+     * @param landscape If this is true, the pdf is delivered in landscape
+     * format
+     * @param marginLeft Space to left border
+     * @param marginRight Space to right border
+     * @param marginTop Space to upper border
+     * @param marginBottom Space to lower border
+     */
+    public static void exportPDF(
+        OutputStream out,
+        JFreeChart   chart,
+        CallContext  cc
+    ) {
+        log.info("export chart as pdf.");
+
+        String pageFormat = (String) cc.getContextValue("chart.page.format");
+
+        if (pageFormat == null)
+            pageFormat = DEFAULT_PAGE_SIZE;
+
+        // max size of the chart
+        Rectangle page = PageSize.getRectangle(pageFormat);
+        float pageWidth  = page.getWidth();
+        float pageHeight = page.getHeight();
+
+        // the chart width
+        int[] size = getSize(cc);
+
+        boolean landscape = size[0] > size[1];
+
+        float width  = 0;
+        float height = 0;
+        if (landscape) {
+            width  = pageHeight;
+            height = pageWidth;
+        }
+        else {
+            width  = pageWidth;
+            height = pageHeight;
+        }
+
+        float marginLeft = (Float) cc.getContextValue(
+            "chart.marginLeft");
+
+        float marginRight = (Float) cc.getContextValue(
+            "chart.marginRight");
+
+        float marginTop = (Float) cc.getContextValue(
+            "chart.marginTop");
+
+        float marginBottom = (Float) cc.getContextValue(
+            "chart.marginBottom");
+
+        float spaceX = width  - marginLeft - marginRight;
+        if (size[0] > spaceX) {
+            log.warn("Width of the chart is too big for pdf -> resize it now.");
+            double ratio = ((double)spaceX) / size[0];
+            size[0]  *= ratio;
+            size[1] *= ratio;
+            log.debug("Resized chart to " + size[0] + "x" + size[1]);
+        }
+
+        float spaceY = height - marginTop  - marginBottom;
+        if (size[1] > spaceY) {
+            log.warn("Height of the chart is too big for pdf -> resize it now.");
+            double ratio = ((double)spaceY) / size[1];
+            size[0]  *= ratio;
+            size[1] *= ratio;
+            log.debug("Resized chart to " + size[0] + "x" + size[1]);
+        }
+
+        Document document = null;
+        if (landscape) {
+            document = new Document(page.rotate());
+            log.debug("Create landscape pdf.");
+        }
+        else
+            document = new Document(page);
+
+        try {
+            PdfWriter writer = PdfWriter.getInstance(document, out);
+
+            document.addSubject(chart.getTitle().getText());
+            document.addCreationDate();
+            document.open();
+
+            PdfContentByte content  = writer.getDirectContent();
+
+            PdfTemplate template = content.createTemplate(width, height);
+            Graphics2D  graphics = template.createGraphics(width, height);
+
+            double[] origin = getCenteredAnchor(
+                marginLeft, marginRight, marginBottom, marginTop,
+                width, height,
+                size[0], size[1]);
+
+            Rectangle2D area = new Rectangle2D.Double(
+                origin[0], origin[1], size[0], size[1]);
+
+            ChartRenderingInfo info = new ChartRenderingInfo();
+
+            chart.draw(graphics, area, info);
+            graphics.dispose();
+            content.addTemplate(template, 0f, 0f);
+        }
+        catch (DocumentException de) {
+            log.error("Error while exporting chart to pdf.", de);
+        }
+        finally {
+            document.close();
+        }
+    }
+
+
+    /**
+     * A method to export a CSV file to an
+     * <code>OutputStream</code>.
+     *
+     * @param out OutputStream
+     * @param chart JFreeChart containing the data.
+     * @param context The CallContext object that contains extra parameters.
+     */
+    public static void exportCSV(
+        OutputStream out,
+        JFreeChart chart,
+        CallContext context)
+    {
+        log.debug("export chart as CSV");
+        CSVWriter writer = null;
+        try {
+            writer = new CSVWriter(
+                new OutputStreamWriter(
+                    out,
+                    DEFAULT_ENCODING),
+                DEFAULT_CSV_SEPARATOR);
+        }
+        catch(UnsupportedEncodingException uee) {
+            log.warn("Wrong encoding for CSV export.");
+            return;
+        }
+        XYPlot plot = chart.getXYPlot();
+        int count = plot.getDatasetCount();
+        for (int i = 0; i < count; i++) {
+            XYDataset data = plot.getDataset(i);
+            int scount = data.getSeriesCount();
+            for (int j = 0; j < scount; j++) {
+                Comparable seriesKey = data.getSeriesKey(j);
+                log.debug("series key: " + seriesKey.toString());
+                writeCSVHeader(writer, seriesKey.toString());
+                writeCSVData(writer, data);
+            }
+        }
+        try {
+            writer.close();
+        }
+        catch(IOException ioe) {
+            log.error("Writing CSV export failed!");
+        }
+    }
+
+
+    protected static void writeCSVHeader(CSVWriter writer, String key) {
+        writer.writeNext(new String[] {"#"});
+        writer.writeNext(new String[] {"# " + key});
+        writer.writeNext(new String[] {"#"});
+        writer.writeNext(new String[] {"X", "Y"});
+    }
+
+
+    protected static void writeCSVData(CSVWriter writer, XYDataset data) {
+        int series = data.getSeriesCount();
+        for (int i = 0; i < series; i++) {
+            int items = data.getItemCount(i);
+            for (int j = 0; j < items; j++) {
+                log.debug("write data: " + data.getX(i, j) + ", " + data.getY(i, j));
+                writer.writeNext(new String[] {
+                    data.getX(i, j).toString(),
+                    data.getY(i, j).toString()});
+            }
+        }
+    }
+
+
+    public static int[] getSize(CallContext cc) {
+        int[] size = new int[2];
+
+        size[0] = (Integer) cc.getContextValue("chart.width");
+        size[1] = (Integer) cc.getContextValue("chart.height");
+
+        return size;
+    }
+
+
+    /**
+     * This method returns the anchor of the chart so that the chart is centered
+     * according to the given parameters.
+     *
+     * @param mLeft Left margin
+     * @param mRight Right margin
+     * @param mBottom Bottom margin
+     * @param mTop Top margin
+     * @param width The complete width of the drawing area.
+     * @param height The complete height of the drawing area.
+     * @param chartWidth The width of the chart.
+     * @param chartHeight The height of the chart.
+     *
+     * @return an array that contains the anchor for a chart with the given
+     * parameters. The first value is the x point, the second value is the y
+     * point.
+     */
+    public static double[] getCenteredAnchor(
+        double mLeft,      double mRight,      double mBottom, double mTop,
+        double width,      double height,
+        double chartWidth, double chartHeight
+    ) {
+        if (log.isDebugEnabled()) {
+            log.debug("Calculate centered origin...");
+            log.debug("-> PDF width    : " + width);
+            log.debug("-> PDF height   : " + height);
+            log.debug("-> Chart width  : " + chartWidth);
+            log.debug("-> Chart height : " + chartHeight);
+            log.debug("-> margin left  : " + mLeft);
+            log.debug("-> margin right : " + mRight);
+            log.debug("-> margin bottom: " + mBottom);
+            log.debug("-> margin top   : " + mTop);
+        }
+
+        double[] origin = new double[2];
+
+        double centerX = width  / 2;
+        double centerY = height / 2;
+
+        origin[0] = centerX - chartWidth / 2;
+        origin[1] = centerY - chartHeight / 2;
+
+        origin[0] = origin[0] >= mLeft ? origin[0] : mLeft;
+        origin[1] = origin[1] >= mTop ? origin[1] : mTop;
+
+        if (log.isDebugEnabled()) {
+            log.debug("==> centered left origin: " + origin[0]);
+            log.debug("==> centered top  origin: " + origin[1]);
+        }
+
+        return origin;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1420 @@
+package de.intevation.flys.exports;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Stroke;
+import java.awt.TexturePaint;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.SortedMap;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.LegendItem;
+import org.jfree.chart.LegendItemCollection;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.Range;
+import org.jfree.data.general.Series;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.ui.RectangleInsets;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.PreferredLocale;
+
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.DoubleBounds;
+import de.intevation.flys.jfree.EnhancedLineAndShapeRenderer;
+import de.intevation.flys.jfree.StableXYDifferenceRenderer;
+import de.intevation.flys.jfree.StyledAreaSeriesCollection;
+import de.intevation.flys.jfree.Style;
+import de.intevation.flys.jfree.StyledSeries;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.ThemeAccess;
+
+
+/**
+ * The base class for chart creation. It should provide some basic things that
+ * equal in all chart types.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class ChartGenerator implements OutGenerator {
+
+    private static Logger logger = Logger.getLogger(ChartGenerator.class);
+
+    public static final int    DEFAULT_CHART_WIDTH     = 600;
+    public static final int    DEFAULT_CHART_HEIGHT    = 400;
+    public static final String DEFAULT_CHART_FORMAT    = "png";
+    public static final Color  DEFAULT_GRID_COLOR      = Color.GRAY;
+    public static final float  DEFAULT_GRID_LINE_WIDTH = 0.3f;
+    public static final int    DEFAULT_FONT_SIZE       = 12;
+    public static final String DEFAULT_FONT_NAME       = "Tahoma";
+
+
+    public static final String XPATH_CHART_SIZE =
+        "/art:action/art:attributes/art:size";
+
+    public static final String XPATH_CHART_FORMAT =
+        "/art:action/art:attributes/art:format/@art:value";
+
+    public static final String XPATH_CHART_X_RANGE =
+        "/art:action/art:attributes/art:xrange";
+
+    public static final String XPATH_CHART_Y_RANGE =
+        "/art:action/art:attributes/art:yrange";
+
+
+    /** The document of the incoming out() request.*/
+    protected Document request;
+
+    /** The output stream where the data should be written to.*/
+    protected OutputStream out;
+
+    /** The CallContext object.*/
+    protected CallContext context;
+
+    /** The artifact that is used to decorate the chart with meta information.*/
+    protected Artifact master;
+
+    /** The settings that should be used during output creation.*/
+    protected Settings settings;
+
+    /** Map of datasets ("index"). */
+    protected SortedMap<Integer, AxisDataset> datasets;
+
+
+
+    /**
+     * A mini interface that allows to walk over the YAXIS enums defined in
+     * subclasses.
+     */
+    public interface YAxisWalker {
+
+        int length();
+
+        String getId(int idx);
+    } // end of YAxisWalker interface
+
+
+
+    public interface AxisDataset {
+
+        void addDataset(XYDataset dataset);
+
+        XYDataset[] getDatasets();
+
+        boolean isEmpty();
+
+        void setRange(Range range);
+
+        Range getRange();
+
+        boolean isArea(XYDataset dataset);
+
+        void setPlotAxisIndex(int idx);
+
+        int getPlotAxisIndex();
+
+    } // end of AxisDataset interface
+
+
+
+    /**
+     * Default constructor that initializes internal data structures.
+     */
+    public ChartGenerator() {
+        datasets = new TreeMap<Integer, AxisDataset>();
+    }
+
+
+
+    /**
+     * This method needs to be implemented by concrete subclasses to create new
+     * instances of JFreeChart.
+     *
+     * @return a new instance of a JFreeChart.
+     */
+    public abstract JFreeChart generateChart();
+
+
+    public abstract void doOut(
+        ArtifactAndFacet bundle,
+        Document         attr,
+        boolean          visible);
+
+
+    protected abstract YAxisWalker getYAxisWalker();
+
+
+    protected abstract Series getSeriesOf(XYDataset dataset, int idx);
+
+    /**
+     * Returns the default title of a chart.
+     *
+     * @return the default title of a chart.
+     */
+    protected abstract String getDefaultChartTitle();
+
+
+    /**
+     * Returns the default X-Axis label of a chart.
+     *
+     * @return the default X-Axis label of a chart.
+     */
+    protected abstract String getDefaultXAxisLabel();
+
+
+    /**
+     * This method is called to retrieve the default label for an Y axis at
+     * position <i>pos</i>.
+     *
+     * @param pos The position of an Y axis.
+     *
+     * @return the default Y axis label at position <i>pos</i>.
+     */
+    protected abstract String getDefaultYAxisLabel(int pos);
+
+
+    /**
+     * This method is used to create new AxisDataset instances which may differ
+     * in concrete subclasses.
+     *
+     * @param idx The index of an axis.
+     */
+    protected abstract AxisDataset createAxisDataset(int idx);
+
+
+    /**
+     * Combines the ranges of the X axis at index <i>idx</i>.
+     *
+     * @param range A new range.
+     * @param idx The index of the X axis that should be comined with
+     * <i>range</i>.
+     */
+    protected abstract void combineXBounds(Bounds bounds, int idx);
+
+
+    /**
+     * Combines the ranges of the Y axis at index <i>idx</i>.
+     *
+     * @param range A new range.
+     * @param idx The index of the Y axis that should be comined with
+     * <i>range</i>.
+     */
+    protected abstract void combineYBounds(Bounds bounds, int index);
+
+
+    /**
+     * This method is used to determine the ranges for axes at a given index.
+     *
+     * @param index The index of the axes at the plot.
+     *
+     * @return a Range[] with [xrange, yrange];
+     */
+    public abstract Range[] getRangesForAxis(int index);
+
+    public abstract Bounds getXBounds(int axis);
+
+    protected abstract void setXBounds(int axis, Bounds bounds);
+
+    public abstract Bounds getYBounds(int axis);
+
+    protected abstract void setYBounds(int axis, Bounds bounds);
+
+
+    /**
+     * This method should be used by concrete subclasses to add subtitle to
+     * <i>chart</i>. <b>The method in this implementation is empty</b>.
+     *
+     * @param chart The JFreeChart chart object.
+     */
+    protected void addSubtitles(JFreeChart chart) {
+        // do nothing
+    }
+
+
+    /**
+     * Generate chart.
+     */
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("ChartGenerator.generate");
+
+        JFreeChart chart = generateChart();
+
+        String format = getFormat();
+        int[]  size   = getSize();
+
+        if (size == null) {
+            size = getExportDimension();
+        }
+
+        context.putContextValue("chart.width",  size[0]);
+        context.putContextValue("chart.height", size[1]);
+
+        if (format.equals(ChartExportHelper.FORMAT_PNG)) {
+            context.putContextValue("chart.image.format", "png");
+
+            ChartExportHelper.exportImage(
+                out,
+                chart,
+                context);
+        }
+        else if (format.equals(ChartExportHelper.FORMAT_PDF)) {
+            preparePDFContext(context);
+
+            ChartExportHelper.exportPDF(
+                out,
+                chart,
+                context);
+        }
+        else if (format.equals(ChartExportHelper.FORMAT_SVG)) {
+            prepareSVGContext(context);
+
+            ChartExportHelper.exportSVG(
+                out,
+                chart,
+                context);
+        }
+        else if (format.equals(ChartExportHelper.FORMAT_CSV)) {
+            context.putContextValue("chart.image.format", "csv");
+
+            ChartExportHelper.exportCSV(
+                out,
+                chart,
+                context);
+        }
+    }
+
+
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("ChartGenerator.init");
+
+        this.request = request;
+        this.out     = out;
+        this.context = context;
+    }
+
+
+    @Override
+    public void setMasterArtifact(Artifact master) {
+        this.master = master;
+    }
+
+
+    @Override
+    public void setSettings(Settings settings) {
+        this.settings = settings;
+    }
+
+
+    /**
+     * Returns an instance of <i>ChartSettings</i> with a chart specific section
+     * but with no axes settings.
+     *
+     * @return an instance of <i>ChartSettings</i>.
+     */
+    @Override
+    public Settings getSettings() {
+        if (this.settings != null) {
+            return this.settings;
+        }
+
+        ChartSettings settings = new ChartSettings();
+
+        ChartSection  chartSection  = buildChartSection();
+        LegendSection legendSection = buildLegendSection();
+        ExportSection exportSection = buildExportSection();
+
+        settings.setChartSection(chartSection);
+        settings.setLegendSection(legendSection);
+        settings.setExportSection(exportSection);
+
+        List<AxisSection> axisSections = buildAxisSections();
+        for (AxisSection axisSection: axisSections) {
+            settings.addAxisSection(axisSection);
+        }
+
+        return settings;
+    }
+
+
+    /**
+     * Creates a new <i>ChartSection</i>.
+     *
+     * @return a new <i>ChartSection</i>.
+     */
+    protected ChartSection buildChartSection() {
+        ChartSection chartSection = new ChartSection();
+        chartSection.setTitle(getChartTitle());
+        chartSection.setSubtitle(getChartSubtitle());
+        chartSection.setDisplayGird(isGridVisible());
+        return chartSection;
+    }
+
+
+    /**
+     * Creates a new <i>LegendSection</i>.
+     *
+     * @return a new <i>LegendSection</i>.
+     */
+    protected LegendSection buildLegendSection() {
+        LegendSection legendSection = new LegendSection();
+        legendSection.setVisibility(isLegendVisible());
+        legendSection.setFontSize(getLegendFontSize());
+        return legendSection;
+    }
+
+
+    /**
+     * Creates a new <i>ExportSection</i> with default values <b>WIDTH=600</b>
+     * and <b>HEIGHT=400</b>.
+     *
+     * @return a new <i>ExportSection</i>.
+     */
+    protected ExportSection buildExportSection() {
+        ExportSection exportSection = new ExportSection();
+        exportSection.setWidth(600);
+        exportSection.setHeight(400);
+        return exportSection;
+    }
+
+
+    /**
+     * Creates a list of Sections that contains all axes of the chart (including
+     * X and Y axes).
+     *
+     * @return a list of Sections for each axis in this chart.
+     */
+    protected List<AxisSection> buildAxisSections() {
+        List<AxisSection> axisSections = new ArrayList<AxisSection>();
+
+        axisSections.addAll(buildXAxisSections());
+        axisSections.addAll(buildYAxisSections());
+
+        return axisSections;
+    }
+
+
+    /**
+     * Creates a new Section for chart's X axis.
+     *
+     * @return a List that contains a Section for the X axis.
+     */
+    protected List<AxisSection> buildXAxisSections() {
+        List<AxisSection> axisSections = new ArrayList<AxisSection>();
+
+        String identifier = "X";
+
+        AxisSection axisSection = new AxisSection();
+        axisSection.setIdentifier(identifier);
+        axisSection.setLabel(getXAxisLabel());
+        axisSection.setFontSize(14);
+        axisSection.setFixed(false);
+
+        // XXX We are able to find better default ranges that [0,0], but the Y
+        // axes currently have no better ranges set.
+        axisSection.setUpperRange(0d);
+        axisSection.setLowerRange(0d);
+
+        axisSections.add(axisSection);
+
+        return axisSections;
+    }
+
+
+    /**
+     * Creates a list of Section for the chart's Y axes. This method makes use
+     * of <i>getYAxisWalker</i> to be able to access all Y axes defined in
+     * subclasses.
+     *
+     * @return a list of Y axis sections.
+     */
+    protected List<AxisSection> buildYAxisSections() {
+        List<AxisSection> axisSections = new ArrayList<AxisSection>();
+
+        YAxisWalker walker = getYAxisWalker();
+        for (int i = 0, n = walker.length(); i < n; i++) {
+            AxisSection ySection = new AxisSection();
+            ySection.setIdentifier(walker.getId(i));
+            ySection.setLabel(getYAxisLabel(i));
+            ySection.setFontSize(14);
+            ySection.setFixed(false);
+
+            // XXX We are able to find better default ranges that [0,0], the
+            // only problem is, that we do NOT have a better range than [0,0]
+            // for each axis, because the initial chart will not have a dataset
+            // for each axis set!
+            ySection.setUpperRange(0d);
+            ySection.setLowerRange(0d);
+
+            axisSections.add(ySection);
+        }
+
+        return axisSections;
+    }
+
+
+    /**
+     * Returns the <i>settings</i> as <i>ChartSettings</i>.
+     *
+     * @return the <i>settings</i> as <i>ChartSettings</i> or null, if
+     * <i>settings</i> is not an instance of <i>ChartSettings</i>.
+     */
+    public ChartSettings getChartSettings() {
+        if (settings instanceof ChartSettings) {
+            return (ChartSettings) settings;
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Returns the chart title provided by <i>settings</i>.
+     *
+     * @param settings A ChartSettings object.
+     *
+     * @return the title provided by <i>settings</i> or null if no
+     * <i>ChartSection</i> is provided by <i>settings</i>.
+     *
+     * @throws NullPointerException if <i>settings</i> is null.
+     */
+    public String getChartTitle(ChartSettings settings) {
+        ChartSection cs = settings.getChartSection();
+        return cs != null ? cs.getTitle() : null;
+    }
+
+
+    /**
+     * Returns the chart subtitle provided by <i>settings</i>.
+     *
+     * @param settings A ChartSettings object.
+     *
+     * @return the subtitle provided by <i>settings</i> or null if no
+     * <i>ChartSection</i> is provided by <i>settings</i>.
+     *
+     * @throws NullPointerException if <i>settings</i> is null.
+     */
+    public String getChartSubtitle(ChartSettings settings) {
+        ChartSection cs = settings.getChartSection();
+        return cs != null ? cs.getSubtitle() : null;
+    }
+
+
+    /**
+     * Returns a boolean object that determines if the chart grid should be
+     * visible or not. This information needs to be provided by <i>settings</i>,
+     * otherweise the default is true.
+     *
+     * @param settings A ChartSettings object.
+     *
+     * @return true, if the chart grid should be visible otherwise false.
+     *
+     * @throws NullPointerException if <i>settings</i> is null.
+     */
+    public boolean isGridVisible(ChartSettings settings) {
+        ChartSection     cs = settings.getChartSection();
+        Boolean displayGrid = cs.getDisplayGrid();
+
+        return displayGrid != null ? displayGrid : true;
+    }
+
+
+    /**
+     * Returns a boolean object that determines if the chart legend should be
+     * visible or not. This information needs to be provided by <i>settings</i>,
+     * otherwise the default is true.
+     *
+     * @param settings A ChartSettings object.
+     *
+     * @return true, if the chart legend should be visible otherwise false.
+     *
+     * @throws NullPointerException if <i>settings</i> is null.
+     */
+    public boolean isLegendVisible(ChartSettings settings) {
+        LegendSection      ls = settings.getLegendSection();
+        Boolean displayLegend = ls.getVisibility();
+
+        return displayLegend != null ? displayLegend : true;
+    }
+
+
+    /**
+     * Returns the legend font size specified in <i>settings</i> or null if no
+     * <i>LegendSection</i> is provided by <i>settings</i>.
+     *
+     * @param settings A ChartSettings object.
+     *
+     * @return the legend font size or null.
+     *
+     * @throws NullPointerException if <i>settings</i> is null.
+     */
+    public Integer getLegendFontSize(ChartSettings settings) {
+        LegendSection ls = settings.getLegendSection();
+        return ls != null ? ls.getFontSize() : null;
+    }
+
+
+    /**
+     * Returns the title of a chart. The return value depends on the existence
+     * of ChartSettings: if there are ChartSettings set, this method returns the
+     * chart title provided by those settings. Otherwise, this method returns
+     * getDefaultChartTitle().
+     *
+     * @return the title of a chart.
+     */
+    protected String getChartTitle() {
+        ChartSettings chartSettings = getChartSettings();
+
+        if (chartSettings != null) {
+            return getChartTitle(chartSettings);
+        }
+
+        return getDefaultChartTitle();
+    }
+
+
+    /**
+     * Returns the subtitle of a chart. The return value depends on the
+     * existence of ChartSettings: if there are ChartSettings set, this method
+     * returns the chart title provided by those settings. Otherwise, this
+     * method returns getDefaultChartSubtitle().
+     *
+     * @return the subtitle of a chart.
+     */
+    protected String getChartSubtitle() {
+        ChartSettings chartSettings = getChartSettings();
+
+        if (chartSettings != null) {
+            return getChartSubtitle(chartSettings);
+        }
+
+        return getDefaultChartSubtitle();
+    }
+
+
+    /**
+     * This method always returns null. Override it in subclasses that require
+     * subtitles.
+     *
+     * @return null.
+     */
+    protected String getDefaultChartSubtitle() {
+        // Override this method in subclasses
+        return null;
+    }
+
+
+    /**
+     * This method is used to determine, if the chart's legend is visible or
+     * not. If a <i>settings</i> instance is set, this instance determines the
+     * visibility otherwise, this method returns true as default if no
+     * <i>settings</i> is set.
+     *
+     * @return true, if the legend should be visible, otherwise false.
+     */
+    protected boolean isLegendVisible() {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings != null) {
+            return isLegendVisible(chartSettings);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * This method is used to determine the font size of the chart's legend. If
+     * a <i>settings</i> instance is set, this instance determines the font
+     * size, otherwise this method returns 12 as default if no <i>settings</i>
+     * is set or if it doesn't provide a legend font size.
+     *
+     * @return a legend font size.
+     */
+    protected int getLegendFontSize() {
+        Integer fontSize = null;
+
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings != null) {
+            fontSize = getLegendFontSize(chartSettings);
+        }
+
+        return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
+    }
+
+
+    /**
+     * This method is used to determine if the resulting chart should display
+     * grid lines or not. <b>Note: this method always returns true!</b>
+     *
+     * @return true, if the chart should display grid lines, otherwise false.
+     */
+    protected boolean isGridVisible() {
+        return true;
+    }
+
+
+    /**
+     * Returns the X-Axis label of a chart.
+     *
+     * @return the X-Axis label of a chart.
+     */
+    protected String getXAxisLabel() {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return getDefaultXAxisLabel();
+        }
+
+        AxisSection as = chartSettings.getAxisSection("X");
+        if (as != null) {
+            String label = as.getLabel();
+
+            if (label != null) {
+                return label;
+            }
+        }
+
+        return getDefaultXAxisLabel();
+    }
+
+
+    /**
+     * This method returns the font size for the X axis. If the font size is
+     * specified in ChartSettings (if <i>chartSettings</i> is set), this size is
+     * returned. Otherwise the default font size 12 is returned.
+     *
+     * @return the font size for the x axis.
+     */
+    protected int getXAxisLabelFontSize() {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return DEFAULT_FONT_SIZE;
+        }
+
+        AxisSection   as = chartSettings.getAxisSection("X");
+        Integer fontSize = as.getFontSize();
+
+        return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
+    }
+
+
+    /**
+     * This method returns the font size for an Y axis. If the font size is
+     * specified in ChartSettings (if <i>chartSettings</i> is set), this size is
+     * returned. Otherwise the default font size 12 is returned.
+     *
+     * @return the font size for the x axis.
+     */
+    protected int getYAxisFontSize(int pos) {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return DEFAULT_FONT_SIZE;
+        }
+
+        YAxisWalker walker = getYAxisWalker();
+
+        AxisSection   as = chartSettings.getAxisSection(walker.getId(pos));
+        Integer fontSize = as.getFontSize();
+
+        return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
+    }
+
+
+    /**
+     * This method returns the export dimension specified in ChartSettings as
+     * int array [width,height].
+     *
+     * @return an int array with [width,height].
+     */
+    protected int[] getExportDimension() {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return new int[] { 600, 400 };
+        }
+
+        ExportSection export = chartSettings.getExportSection();
+        Integer width  = export.getWidth();
+        Integer height = export.getHeight();
+
+        if (width != null && height != null) {
+            return new int[] { width, height };
+        }
+
+        return new int[] { 600, 400 };
+    }
+
+
+    /**
+     * Returns the Y-Axis label of a chart at position <i>pos</i>.
+     *
+     * @return the Y-Axis label of a chart at position <i>0</i>.
+     */
+    protected String getYAxisLabel(int pos) {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return getDefaultYAxisLabel(pos);
+        }
+
+        YAxisWalker walker = getYAxisWalker();
+        AxisSection     as = chartSettings.getAxisSection(walker.getId(pos));
+        if (as != null) {
+            String label = as.getLabel();
+
+            if (label != null) {
+                return label;
+            }
+        }
+
+        return getDefaultYAxisLabel(pos);
+    }
+
+
+    /**
+     * This method searches for a specific axis in the <i>settings</i> if
+     * <i>settings</i> is set. If the axis was found, this method returns the
+     * specified axis range if the axis range is fixed. Otherwise, this method
+     * returns null.
+     *
+     * @param axisId The identifier of an axis.
+     *
+     * @return the specified axis range from <i>settings</i> if the axis is
+     * fixed, otherwise null.
+     */
+    public Range getRangeForAxisFromSettings(String axisId) {
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return null;
+        }
+
+        AxisSection as = chartSettings.getAxisSection(axisId);
+        Boolean  fixed = as.isFixed();
+
+        if (fixed != null && fixed) {
+            Double upper = as.getUpperRange();
+            Double lower = as.getLowerRange();
+
+            if (upper != null && lower != null) {
+                return lower < upper
+                    ? new Range(lower, upper)
+                    : new Range(upper, lower);
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Adds a new AxisDataset which contains <i>dataset</i> at index <i>idx</i>.
+     *
+     * @param dataset An XYDataset.
+     * @param idx The axis index.
+     * @param visible Determines, if the dataset should be visible or not.
+     */
+    public void addAxisDataset(XYDataset dataset, int idx, boolean visible) {
+        if (dataset == null || idx < 0) {
+            return;
+        }
+
+        AxisDataset axisDataset = getAxisDataset(idx);
+
+        Bounds[] xyBounds = ChartHelper.getBounds(dataset);
+
+        if (xyBounds == null) {
+            logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
+            return;
+        }
+
+        if (visible) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Add new AxisDataset at index: " + idx);
+                logger.debug("X extent: " + xyBounds[0]);
+                logger.debug("Y extent: " + xyBounds[1]);
+            }
+
+            axisDataset.addDataset(dataset);
+        }
+
+        combineXBounds(xyBounds[0], 0);
+        combineYBounds(xyBounds[1], idx);
+    }
+
+
+    /**
+     * This method grants access to the AxisDatasets stored in <i>datasets</i>.
+     * If no AxisDataset exists for index <i>idx</i>, a new AxisDataset is
+     * created using <i>createAxisDataset()</i>.
+     *
+     * @param idx The index of the desired AxisDataset.
+     *
+     * @return an existing or new AxisDataset.
+     */
+    public AxisDataset getAxisDataset(int idx) {
+        AxisDataset axisDataset = datasets.get(idx);
+
+        if (axisDataset == null) {
+            axisDataset = createAxisDataset(idx);
+            datasets.put(idx, axisDataset);
+        }
+
+        return axisDataset;
+    }
+
+
+    /**
+     * Adjust some Stroke/Grid parameters for <i>plot</i>. The chart
+     * <i>Settings</i> are applied in this method.
+     *
+     * @param plot The XYPlot which is adapted.
+     */
+    protected void adjustPlot(XYPlot plot) {
+        Stroke gridStroke = new BasicStroke(
+            DEFAULT_GRID_LINE_WIDTH,
+            BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_MITER,
+            3.0f,
+            new float[] { 3.0f },
+            0.0f);
+
+        ChartSettings      cs = getChartSettings();
+        boolean isGridVisible = cs != null ? isGridVisible(cs) : true;
+
+        plot.setDomainGridlineStroke(gridStroke);
+        plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR);
+        plot.setDomainGridlinesVisible(isGridVisible);
+
+        plot.setRangeGridlineStroke(gridStroke);
+        plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR);
+        plot.setRangeGridlinesVisible(isGridVisible);
+
+        plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));
+    }
+
+
+    /**
+     * This helper mehtod is used to extract the current locale from instance
+     * vairable <i>context</i>.
+     *
+     * @return the current locale.
+     */
+    protected Locale getLocale() {
+        CallMeta           meta = context.getMeta();
+        PreferredLocale[] prefs = meta.getLanguages();
+
+        int len = prefs != null ? prefs.length : 0;
+
+        Locale[] locales = new Locale[len];
+
+        for (int i = 0; i < len; i++) {
+            locales[i] = prefs[i].getLocale();
+        }
+
+        return meta.getPreferredLocale(locales);
+    }
+
+
+    protected String msg(String key, String def) {
+        return Resources.getMsg(context.getMeta(), key, def);
+    }
+
+    protected String msg(String key) {
+        return Resources.getMsg(context.getMeta(), key, key);
+    }
+
+    protected String msg(String key, String def, Object[] args) {
+        return Resources.getMsg(context.getMeta(), key, def, args);
+    }
+
+
+    protected String getRiverName() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        River river = FLYSUtils.getRiver(flys);
+        return (river != null) ? river.getName() : "";
+    }
+
+
+    protected double[] getRange() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return FLYSUtils.getKmRange(flys);
+    }
+
+
+    /**
+     * Returns the size of a chart export as array which has been specified by
+     * the incoming request document.
+     *
+     * @return the size of a chart as [width, height] or null if no width or
+     * height are given in the request document.
+     */
+    protected int[] getSize() {
+        int[] size = new int[2];
+
+        Element sizeEl = (Element)XMLUtils.xpath(
+            request,
+            XPATH_CHART_SIZE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (sizeEl != null) {
+            String uri = ArtifactNamespaceContext.NAMESPACE_URI;
+
+            String w = sizeEl.getAttributeNS(uri, "width");
+            String h = sizeEl.getAttributeNS(uri, "height");
+
+            if (w.length() > 0 && h.length() > 0) {
+                try {
+                    size[0] = Integer.parseInt(w);
+                    size[1] = Integer.parseInt(h);
+                }
+                catch (NumberFormatException nfe) {
+                    logger.warn("Wrong values for chart width/height.");
+                }
+            }
+        }
+
+        return size[0] > 0 && size[1] > 0 ? size : null;
+    }
+
+
+    /**
+     * This method returns the format specified in the <i>request</i> document
+     * or <i>DEFAULT_CHART_FORMAT</i> if no format is specified in
+     * <i>request</i>.
+     *
+     * @return the format used to export this chart.
+     */
+    protected String getFormat() {
+        String format = (String) XMLUtils.xpath(
+            request,
+            XPATH_CHART_FORMAT,
+            XPathConstants.STRING,
+            ArtifactNamespaceContext.INSTANCE);
+
+        return format == null || format.length() == 0
+            ? DEFAULT_CHART_FORMAT
+            : format;
+    }
+
+
+    /**
+     * Returns the X-Axis range as String array from request document.
+     *
+     * @return a String array with [lower, upper].
+     */
+    protected String[] getDomainAxisRangeFromRequest() {
+        Element xrange = (Element)XMLUtils.xpath(
+            request,
+            XPATH_CHART_X_RANGE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (xrange == null) {
+            return null;
+        }
+
+        String uri = ArtifactNamespaceContext.NAMESPACE_URI;
+
+        String lower = xrange.getAttributeNS(uri, "from");
+        String upper = xrange.getAttributeNS(uri, "to");
+
+        return new String[] { lower, upper };
+    }
+
+
+    protected String[] getValueAxisRangeFromRequest() {
+        Element yrange = (Element)XMLUtils.xpath(
+            request,
+            XPATH_CHART_Y_RANGE,
+            XPathConstants.NODE,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (yrange == null) {
+            return null;
+        }
+
+
+        String uri = ArtifactNamespaceContext.NAMESPACE_URI;
+
+        String lower = yrange.getAttributeNS(uri, "from");
+        String upper = yrange.getAttributeNS(uri, "to");
+
+        return new String[] { lower, upper };
+    }
+
+
+    /**
+     * Returns the default size of a chart export as array.
+     *
+     * @return the default size of a chart as [width, height].
+     */
+    protected int[] getDefaultSize() {
+        return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT };
+    }
+
+
+    /**
+     * Add datasets stored in instance variable <i>datasets</i> to plot.
+     * <i>datasets</i> actually stores instances of AxisDataset, so each of this
+     * datasets is mapped to a specific axis as well.
+     *
+     * @param plot plot to add datasets to.
+     */
+    protected void addDatasets(XYPlot plot) {
+        // AxisDatasets are sorted, but some might be empty.
+        // Thus, generate numbering on the fly.
+        int axisIndex    = 0;
+        int datasetIndex = 0;
+
+        for (Map.Entry<Integer, AxisDataset> entry: datasets.entrySet()) {
+            if (!entry.getValue().isEmpty()) {
+                // Add axis and range information.
+                AxisDataset axisDataset = entry.getValue();
+                NumberAxis    axis      = createYAxis(entry.getKey());
+
+                plot.setRangeAxis(axisIndex, axis);
+
+                if (axis.getAutoRangeIncludesZero()) {
+                    axisDataset.setRange(
+                        Range.expandToInclude(axisDataset.getRange(), 0d));
+                }
+
+                setYBounds(axisIndex, expandPointRange(axisDataset.getRange()));
+
+                // Add contained datasets, mapping to axis.
+                for (XYDataset dataset: axisDataset.getDatasets()) {
+                    plot.setDataset(datasetIndex, dataset);
+                    plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
+
+                    applyThemes(plot, dataset,
+                        datasetIndex,
+                        axisDataset.isArea(dataset));
+
+                    datasetIndex++;
+                }
+
+                axisDataset.setPlotAxisIndex(axisIndex);
+                axisIndex++;
+            }
+        }
+    }
+
+
+    /**
+     * @param idx "index" of dataset/series (first dataset to be drawn has
+     *            index 0), correlates with renderer index.
+     * @param isArea true if the series describes an area and shall be rendered
+     *                as such.
+     * @return idx increased by number of items addded.
+     */
+    protected void applyThemes(
+        XYPlot    plot,
+        XYDataset series,
+        int       idx,
+        boolean   isArea
+    ) {
+        if (isArea) {
+            applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx);
+        }
+        else {
+            applyLineTheme(plot, series, idx);
+        }
+    }
+
+
+    /**
+     * This method applies the themes defined in the series itself. Therefore,
+     * <i>StyledXYSeries.applyTheme()</i> is called, which modifies the renderer
+     * for the series.
+     *
+     * @param plot The plot.
+     * @param dataset The XYDataset which needs to support Series objects.
+     * @param idx The index of the renderer / dataset.
+     */
+    protected void applyLineTheme(XYPlot plot, XYDataset dataset, int idx) {
+        logger.debug("Apply LineTheme for dataset at index: " + idx);
+
+        LegendItemCollection lic  = new LegendItemCollection();
+        LegendItemCollection anno = plot.getFixedLegendItems();
+
+        Font legendFont = createLegendLabelFont();
+
+        XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
+
+        for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
+            Series series = getSeriesOf(dataset, s);
+
+            if (series instanceof StyledSeries) {
+                Style style = ((StyledSeries) series).getStyle();
+                style.applyTheme(renderer, s);
+            }
+
+            // special case: if there is just one single item, we need to enable
+            // points for this series, otherwise we would not see anything in
+            // the chart area.
+            if (series.getItemCount() == 1) {
+                renderer.setSeriesShapesVisible(s, true);
+            }
+
+            LegendItem legendItem = renderer.getLegendItem(idx, s);
+            if (legendItem != null) {
+                legendItem.setLabelFont(legendFont);
+                lic.add(legendItem);
+            }
+            else {
+                logger.warn("Could not get LegentItem for renderer: "
+                    + idx + ", series-idx " + s);
+            }
+        }
+
+        if (anno != null) {
+            lic.addAll(anno);
+        }
+
+        plot.setFixedLegendItems(lic);
+
+        plot.setRenderer(idx, renderer);
+    }
+
+
+    /**
+     * @param plot The plot.
+     * @param area A StyledAreaSeriesCollection object.
+     * @param idx The index of the dataset.
+     *
+     * @return
+     */
+    protected void applyAreaTheme(
+        XYPlot                     plot,
+        StyledAreaSeriesCollection area,
+        int                        idx
+    ) {
+        LegendItemCollection lic  = new LegendItemCollection();
+        LegendItemCollection anno = plot.getFixedLegendItems();
+
+        Font legendFont = createLegendLabelFont();
+
+        logger.debug("Registering an 'area'renderer at idx: " + idx);
+
+        StableXYDifferenceRenderer dRenderer =
+            new StableXYDifferenceRenderer();
+
+        if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
+            dRenderer.setPositivePaint(createTransparentPaint());
+        }
+
+        plot.setRenderer(idx, dRenderer);
+
+        area.applyTheme(dRenderer);
+
+        LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
+        if (legendItem != null) {
+            legendItem.setLabelFont(legendFont);
+            lic.add(legendItem);
+        }
+        else {
+            logger.warn("Could not get LegentItem for renderer: "
+                + idx + ", series-idx " + 0);
+        }
+
+        if (anno != null) {
+            lic.addAll(anno);
+        }
+
+        plot.setFixedLegendItems(lic);
+    }
+
+
+    /**
+     * Expands a given range if it collapses into one point.
+     *
+     * @param Range to be expanded if upper == lower bound.
+     */
+    private Bounds expandPointRange(Range range) {
+        if (range == null) {
+            return null;
+        }
+        else if (range.getLowerBound() == range.getUpperBound()) {
+            double hi  = range.getUpperBound();
+            double lo  = range.getLowerBound();
+            double add = (hi - lo) / 100 * 5;
+
+            return new DoubleBounds(lo-add, hi+add);
+        }
+
+        return new DoubleBounds(range.getLowerBound(), range.getUpperBound());
+    }
+
+
+    /**
+     * Creates a new instance of EnhancedLineAndShapeRenderer.
+     *
+     * @param plot The plot which is set for the new renderer.
+     * @param idx This value is not used in the current implementation.
+     *
+     * @return a new instance of EnhancedLineAndShapeRenderer.
+     */
+    protected XYLineAndShapeRenderer createRenderer(XYPlot plot, int idx) {
+        logger.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx);
+
+        EnhancedLineAndShapeRenderer r =
+            new EnhancedLineAndShapeRenderer(true, false);
+
+        r.setPlot(plot);
+
+        return r;
+    }
+
+
+    /**
+     * Creates a new instance of <i>IdentifiableNumberAxis</i>.
+     *
+     * @param idx The index of the new axis.
+     * @param label The label of the new axis.
+     *
+     * @return an instance of IdentifiableNumberAxis.
+     */
+    protected NumberAxis createNumberAxis(int idx, String label) {
+        YAxisWalker walker = getYAxisWalker();
+
+        return new IdentifiableNumberAxis(walker.getId(idx), label);
+    }
+
+
+    /**
+     * Create Y (range) axis for given index.
+     * Shall be overriden by subclasses.
+     */
+    protected NumberAxis createYAxis(int index) {
+        YAxisWalker walker = getYAxisWalker();
+
+        Font labelFont = new Font(
+            DEFAULT_FONT_NAME,
+            Font.BOLD,
+            getYAxisFontSize(index));
+
+        IdentifiableNumberAxis axis = new IdentifiableNumberAxis(
+            walker.getId(index),
+            getYAxisLabel(index));
+
+        axis.setAutoRangeIncludesZero(false);
+        axis.setLabelFont(labelFont);
+        axis.setTickLabelFont(labelFont);
+
+        return axis;
+    }
+
+
+    /**
+     * Creates a new LegendItem with <i>name</i> and font provided by
+     * <i>createLegendLabelFont()</i>.
+     *
+     * @param theme The theme of the chart line.
+     * @param The displayed name of the item.
+     *
+     * @return a new LegendItem instance.
+     */
+    public LegendItem createLegendItem(Document theme, String name) {
+        // OPTIMIZE Pass font, parsed Theme items.
+        ThemeAccess themeAccess = new ThemeAccess(theme);
+
+        Color      color       = themeAccess.parseLineColorField();
+        LegendItem legendItem  = new LegendItem(name, color);
+
+        legendItem.setLabelFont(createLegendLabelFont());
+        return legendItem;
+    }
+
+
+    /**
+     * Creates Font (Family and size) to use when creating Legend Items. The
+     * font size depends in the return value of <i>getLegendFontSize()</i>.
+     *
+     * @return a new Font instance with <i>DEFAULT_FONT_NAME</i>.
+     */
+    protected Font createLegendLabelFont() {
+        return new Font(
+            DEFAULT_FONT_NAME,
+            Font.PLAIN,
+            getLegendFontSize()
+        );
+    }
+
+
+    /**
+     * Returns a transparently textured paint.
+     *
+     * @return a transparently textured paint.
+     */
+    protected static Paint createTransparentPaint() {
+        // TODO why not use a transparent color?
+        BufferedImage texture = new BufferedImage(
+            1, 1, BufferedImage.TYPE_4BYTE_ABGR);
+
+        return new TexturePaint(
+            texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
+    }
+
+
+    protected void preparePDFContext(CallContext context) {
+        int[] dimension = getExportDimension();
+
+        context.putContextValue("chart.width", dimension[0]);
+        context.putContextValue("chart.height", dimension[1]);
+        context.putContextValue("chart.marginLeft",   5f);
+        context.putContextValue("chart.marginRight",  5f);
+        context.putContextValue("chart.marginTop",    5f);
+        context.putContextValue("chart.marginBottom", 5f);
+        context.putContextValue(
+            "chart.page.format",
+            ChartExportHelper.DEFAULT_PAGE_SIZE);
+    }
+
+
+    protected void prepareSVGContext(CallContext context) {
+        int[] dimension = getExportDimension();
+
+        context.putContextValue("chart.width", dimension[0]);
+        context.putContextValue("chart.height", dimension[1]);
+        context.putContextValue(
+            "chart.encoding",
+            ChartExportHelper.DEFAULT_ENCODING);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartHelper.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,178 @@
+package de.intevation.flys.exports;
+
+import org.jfree.data.Range;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.data.time.RegularTimePeriod;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.data.time.TimeSeries;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.DoubleBounds;
+import de.intevation.flys.jfree.TimeBounds;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ChartHelper {
+
+    private static final Logger logger = Logger.getLogger(ChartHelper.class);
+
+
+    /**
+     * This method returns the ranges of the XYDataset <i>dataset</i> as array
+     * with [xrange, yrange].
+     *
+     * @param dataset The dataset which should be evaluated.
+     *
+     * @return an array with x and y ranges.
+     */
+    public static Bounds[] getBounds(XYSeriesCollection dataset) {
+        int seriesCount = dataset != null ? dataset.getSeriesCount() : 0;
+
+        if (seriesCount == 0) {
+            logger.warn("Dataset is empty or has no Series set.");
+            return null;
+        }
+
+        boolean foundValue = false;
+
+        double minX = Double.MAX_VALUE;
+        double maxX = -Double.MAX_VALUE;
+        double minY = Double.MAX_VALUE;
+        double maxY = -Double.MAX_VALUE;
+
+        for (int i = 0, m = seriesCount; i < m; i++) {
+            for (int j = 0, n = dataset.getItemCount(i); j < n; j++) {
+                double x = dataset.getXValue(i, j);
+                double y = dataset.getYValue(i, j);
+
+                if (Double.isNaN(x) || Double.isNaN(y)) {
+                    logger.warn("Item " + j + " in Series " + i + " is broken");
+                    continue;
+                }
+
+                foundValue = true;
+
+                if (x < minX) {
+                    minX = x;
+                }
+
+                if (x > maxX) {
+                    maxX = x;
+                }
+
+                if (y < minY) {
+                    minY = y;
+                }
+
+                if (y > maxY) {
+                    maxY = y;
+                }
+            }
+        }
+
+        return foundValue
+            ? new Bounds[] {
+                    new DoubleBounds(minX, maxX),
+                    new DoubleBounds(minY, maxY) }
+            : null;
+    }
+
+
+    public static Bounds[] getBounds(XYDataset dataset) {
+        if (dataset instanceof XYSeriesCollection) {
+            return getBounds((XYSeriesCollection) dataset);
+        }
+        else if(dataset instanceof TimeSeriesCollection) {
+            return getBounds((TimeSeriesCollection) dataset);
+        }
+        else {
+            logger.warn("Unknown XYDataset instance: " + dataset.getClass());
+            return null;
+        }
+    }
+
+
+    public static Bounds[] getBounds(TimeSeriesCollection collection) {
+        int seriesCount = collection != null ? collection.getSeriesCount() : 0;
+
+        if (seriesCount == 0) {
+            logger.warn("TimeSeriesCollection is empty or has no Series set.");
+            return null;
+        }
+
+        boolean foundValue = false;
+
+        long lowerX = Long.MAX_VALUE;
+        long upperX = -Long.MAX_VALUE;
+
+        double lowerY = Double.MAX_VALUE;
+        double upperY = -Double.MAX_VALUE;
+
+        for (int i = 0, m = seriesCount; i < m; i++) {
+            TimeSeries series = collection.getSeries(i);
+
+            for (int j = 0, n = collection.getItemCount(i); j < n; j++) {
+                RegularTimePeriod rtp = series.getTimePeriod(j);
+
+                if (rtp == null) {
+                    continue;
+                }
+
+                foundValue = true;
+
+                long start = rtp.getFirstMillisecond();
+                long end   = rtp.getLastMillisecond();
+
+                if (start < lowerX) {
+                    lowerX = start;
+                }
+
+                if (end > upperX) {
+                    upperX = end;
+                }
+
+                double y = series.getValue(j).doubleValue();
+
+                lowerY = Math.min(lowerY, y);
+                upperY = Math.max(upperY, y);
+            }
+        }
+
+        if (foundValue) {
+            return new Bounds[] {
+                new TimeBounds(lowerX, upperX),
+                new DoubleBounds(lowerY, upperY)
+            };
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Expand range by percent.
+     *
+     * @param range The range to expand.
+     * @param percent The percentage to expand.
+     *
+     * @param an expanded range.
+     */
+    public static Range expandRange(Range range, double percent) {
+        if (range == null) {
+            return null;
+        }
+
+        double value  = range.getLowerBound();
+        double expand = Math.abs(value / 100 * percent);
+
+        return expand != 0
+            ? new Range(value-expand, value+expand)
+            : new Range(-0.01 * percent, 0.01 * percent);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,178 @@
+package de.intevation.flys.exports;
+
+import de.intevation.flys.java2d.NOPGraphics2D;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.awt.Transparency;
+import java.awt.Graphics2D;
+
+import java.awt.geom.Rectangle2D;
+
+import java.awt.image.BufferedImage;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.ChartRenderingInfo;
+import org.jfree.chart.JFreeChart;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * An OutGenerator that generates meta information for charts. A concrete
+ * ChartInfoGenerator need to instantiate a concrete ChartGenerator and dispatch
+ * the methods to that instance. The only thing this ChartInfoGenerator needs
+ * to, is to overrite the generate() method which doesn't write the chart image
+ * to the OutputStream but a Document that contains some meta information of the
+ * created chart.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class ChartInfoGenerator implements OutGenerator {
+
+    public static final boolean USE_NOP_GRAPHICS =
+        Boolean.getBoolean("info.rendering.nop.graphics");
+
+    /** The logger used in this generator.*/
+    private static Logger logger =
+        Logger.getLogger(ChartInfoGenerator.class);
+
+
+    /** The OutGenerator that creates the charts.*/
+    protected ChartGenerator generator;
+
+    protected OutputStream out;
+
+
+
+    public ChartInfoGenerator(ChartGenerator generator) {
+        this.generator = generator;
+    }
+
+
+    /**
+     * Dispatches the operation to the instantiated generator.
+     *
+     * @param request
+     * @param out
+     * @param context
+     */
+    public void init(Document request, OutputStream out, CallContext context) {
+        this.out = out;
+
+        generator.init(request, out, context);
+    }
+
+
+    /**
+     * Dispatches the operation to the instantiated generator.
+     *
+     * @param master The master artifact
+     */
+    public void setMasterArtifact(Artifact master) {
+        generator.setMasterArtifact(master);
+    }
+
+
+    /**
+     * Dispatches the operation to the instantiated generator.
+     */
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        generator.doOut(artifactFacet, attr, visible);
+    }
+
+
+    /**
+     * This method generates the chart using a concrete ChartGenerator but
+     * doesn't write the chart itself to the OutputStream but a Document that
+     * contains meta information of the created chart.
+     */
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("ChartInfoGenerator.generate");
+
+        JFreeChart chart = generator.generateChart();
+
+        int[] size = generator.getSize();
+        if (size == null) {
+            size = generator.getDefaultSize();
+        }
+
+        ChartRenderingInfo info = new ChartRenderingInfo();
+
+        long startTime = System.currentTimeMillis();
+
+        if (USE_NOP_GRAPHICS) {
+            BufferedImage image =
+                new BufferedImage(size[0], size[1], Transparency.BITMASK);
+
+            Graphics2D g2d  = image.createGraphics();
+            Graphics2D nop = new NOPGraphics2D(g2d);
+
+            chart.draw(
+                nop,
+                new Rectangle2D.Double(0, 0, size[0], size[1]),
+                null,
+                info);
+
+            nop.dispose();
+        }
+        else {
+            chart.createBufferedImage(
+                size[0], size[1], Transparency.BITMASK, info);
+        }
+
+        long stopTime = System.currentTimeMillis();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Rendering info took: " +
+                (stopTime-startTime) + "ms");
+        }
+
+
+        InfoGeneratorHelper helper = new InfoGeneratorHelper(generator);
+        Document doc = helper.createInfoDocument(chart, info);
+
+        XMLUtils.toStream(doc, out);
+    }
+
+
+    /**
+     * A proxy method which calls <i>generator</i>.getSettings() and returns its
+     * return value.
+     *
+     * @return a Settings object provided by <i>generator</i>.
+     */
+    @Override
+    public Settings getSettings() {
+        return generator.getSettings();
+    }
+
+
+    /**
+     * A proxy method which calls <i>generator</i>.setSettings().
+     *
+     * @param settings A settings object for the <i>generator</i>.
+     */
+    @Override
+    public void setSettings(Settings settings) {
+        generator.setSettings(settings);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartSection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,49 @@
+package de.intevation.flys.exports;
+
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ChartSection extends TypeSection {
+
+    public static final String TITLE_ATTR       = "title";
+    public static final String SUBTITLE_ATTR    = "subtitle";
+    public static final String DISPLAYGRID_ATTR = "display-grid";
+
+
+    public ChartSection() {
+        super("chart");
+    }
+
+
+    public void setTitle(String title) {
+        setStringValue(TITLE_ATTR, title);
+    }
+
+
+    public String getTitle() {
+        return getStringValue(TITLE_ATTR);
+    }
+
+
+    public void setSubtitle(String subtitle) {
+        setStringValue(SUBTITLE_ATTR, subtitle);
+    }
+
+
+    public String getSubtitle() {
+        return getStringValue(SUBTITLE_ATTR);
+    }
+
+
+    public void setDisplayGird(boolean displayGrid) {
+        setBooleanValue(DISPLAYGRID_ATTR, displayGrid);
+    }
+
+
+    public Boolean getDisplayGrid() {
+        return getBooleanValue(DISPLAYGRID_ATTR);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartSettings.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,283 @@
+package de.intevation.flys.exports;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.state.DefaultSection;
+import de.intevation.artifactdatabase.state.DefaultSettings;
+import de.intevation.artifactdatabase.state.Section;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ChartSettings extends DefaultSettings {
+
+    private static final Logger logger = Logger.getLogger(ChartSettings.class);
+
+    protected ChartSection  chartSection;
+    protected LegendSection legendSection;
+    protected ExportSection exportSection;
+    protected Section       axesSection;
+
+
+    public ChartSettings() {
+        super();
+
+        axesSection = new DefaultSection("axes");
+        addSection(axesSection);
+    }
+
+
+    /**
+     * Sets the chart section. Old chart sections are removed.
+     *
+     * @param chartSection A new Section that stores chart specific attributes.
+     */
+    public void setChartSection(ChartSection chartSection) {
+        ChartSection oldChartSection = getChartSection();
+
+        if (oldChartSection != null) {
+            removeSection(oldChartSection);
+        }
+
+        this.chartSection = chartSection;
+        addSection(chartSection);
+    }
+
+
+    /**
+     * Returns the Section that stores chart specific attributes.
+     *
+     * @return the Section that stores chart specific attributes.
+     */
+    public ChartSection getChartSection() {
+        return chartSection;
+    }
+
+
+    /**
+     * Sets the legend section. Old legend sections are removed.
+     *
+     * @param legendSection A new Section that stores legend specific
+     * attributes.
+     */
+    public void setLegendSection(LegendSection legendSection) {
+        LegendSection oldLegendSection = getLegendSection();
+
+        if (oldLegendSection != null) {
+            removeSection(oldLegendSection);
+        }
+
+        this.legendSection = legendSection;
+        addSection(legendSection);
+    }
+
+
+    /**
+     * Returns the Section that stores legend specific attributes.
+     *
+     * @return the Section that stores legend specific attributes.
+     */
+    public LegendSection getLegendSection() {
+        return legendSection;
+    }
+
+
+    /**
+     * Sets the export section. Old export sections are removed.
+     *
+     * @param exportSection A new Section that stores export specific
+     * attributes.
+     */
+    public void setExportSection(ExportSection exportSection) {
+        ExportSection oldExportSection = getExportSection();
+
+        if (oldExportSection != null) {
+            removeSection(oldExportSection);
+        }
+
+        this.exportSection = exportSection;
+        addSection(exportSection);
+    }
+
+
+    /**
+     * Returns the Section that stores export specific attributes.
+     *
+     * @return the Section that stores export specific attributes.
+     */
+    public ExportSection getExportSection() {
+        return exportSection;
+    }
+
+
+    /**
+     * Adds a Section for a new axis of the chart.
+     *
+     * @param axisSection The Section specific for a chart axis.
+     */
+    public void addAxisSection(AxisSection axisSection) {
+        if (axisSection != null) {
+            axesSection.addSubsection(axisSection);
+        }
+    }
+
+
+    /**
+     * This method returns an AxisSection specified by <i>axisId</i> or null if
+     * no AxisSection is existing with identifier <i>axisId</i>.
+     *
+     * @param axisId The identifier of the wanted AxisSection.
+     *
+     * @return the AxisSection specified by <i>axisId</i> or null.
+     */
+    public AxisSection getAxisSection(String axisId) {
+        for (int i = 0, n = axesSection.getSubsectionCount(); i < n; i++) {
+            AxisSection as = (AxisSection) axesSection.getSubsection(i);
+            String      id = as.getIdentifier();
+
+            if (id != null && id.equals(axisId)) {
+                return as;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Parses the settings from <i>settings</i>. The result is a new
+     * ChartSettings instance.
+     *
+     * @param settings A <i>settings</i> node.
+     *
+     * @return a new <i>ChartSettings</i> instance.
+     */
+    public static ChartSettings parse(Node settings) {
+        if (settings == null) {
+            logger.warn("Tried to parse ChartSettings from empty Node!");
+            return null;
+        }
+
+        ChartSettings chartSettings = new ChartSettings();
+
+        parseAxes(chartSettings, settings);
+        parseChart(chartSettings, settings);
+        parseLegend(chartSettings, settings);
+        parseExport(chartSettings, settings);
+
+        return chartSettings;
+    }
+
+
+    protected static void parseAxes(ChartSettings target, Node settings) {
+        NodeList axesList = (NodeList) XMLUtils.xpath(
+            settings, "axes/axis", XPathConstants.NODESET, null);
+
+        int num = axesList != null ? axesList.getLength() : 0;
+
+        if (num <= 0) {
+            logger.debug("No axis sections found.");
+            return;
+        }
+
+        for (int i = 0; i < num; i++) {
+            parseAxis(target, axesList.item(i));
+        }
+    }
+
+
+    protected static void parseAxis(ChartSettings target, Node axis) {
+        AxisSection section = new AxisSection();
+
+        String id       = XMLUtils.xpathString(axis, "id", null);
+        String label    = XMLUtils.xpathString(axis, "label", null);
+        String fSize    = XMLUtils.xpathString(axis, "font-size", null);
+        String fixation = XMLUtils.xpathString(axis, "fixation", null);
+        String low      = XMLUtils.xpathString(axis, "lower", null);
+        String up       = XMLUtils.xpathString(axis, "upper", null);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Fount axis id:        '" + id + "'");
+            logger.debug("Fount axis label:     '" + label + "'");
+            logger.debug("Fount axis font size: '" + fSize + "'");
+            logger.debug("Fount axis fixation:  '" + fixation + "'");
+            logger.debug("Fount axis lower:     '" + low + "'");
+            logger.debug("Fount axis upper:     '" + up + "'");
+        }
+
+        section.setIdentifier(id);
+        section.setLabel(label);
+        section.setFontSize(Integer.valueOf(fSize.length() > 0 ? fSize : "-1"));
+        section.setFixed(Boolean.valueOf(fixation));
+        section.setLowerRange(Double.valueOf(low.length() > 0 ? low : "0"));
+        section.setUpperRange(Double.valueOf(up.length() > 0 ? up : "0"));
+
+        target.addAxisSection(section);
+    }
+
+
+    protected static void parseChart(ChartSettings target, Node chart) {
+        ChartSection chartSection = new ChartSection();
+
+        String title = XMLUtils.xpathString(chart, "chart/title", null);
+        String sub   = XMLUtils.xpathString(chart, "chart/subtitle", null);
+        String grid  = XMLUtils.xpathString(chart, "chart/display-grid", null);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Found chart title:    '" + title + "'");
+            logger.debug("Found chart subtitle: '" + sub + "'");
+            logger.debug("Found chart grid:     '" + grid + "'");
+        }
+
+        chartSection.setTitle(title);
+        chartSection.setSubtitle(sub);
+        chartSection.setDisplayGird(Boolean.valueOf(grid));
+
+        target.setChartSection(chartSection);
+    }
+
+
+    protected static void parseLegend(ChartSettings target, Node legend) {
+        LegendSection section = new LegendSection();
+
+        String vis   = XMLUtils.xpathString(legend, "legend/visibility", null);
+        String fSize = XMLUtils.xpathString(legend, "legend/font-size", null);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Found legend visibility: '" + vis + "'");
+            logger.debug("Found legend font size : '" + fSize + "'");
+        }
+
+        section.setVisibility(Boolean.valueOf(vis));
+        section.setFontSize(Integer.valueOf(fSize.length() > 0 ? fSize : "-1"));
+
+        target.setLegendSection(section);
+    }
+
+
+    protected static void parseExport(ChartSettings target, Node export) {
+        ExportSection section = new ExportSection();
+
+        String width  = XMLUtils.xpathString(export, "export/width", null);
+        String height = XMLUtils.xpathString(export, "export/height", null);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Found export width : '" + width + "'");
+            logger.debug("Found export height: '" + height + "'");
+        }
+
+        section.setWidth(Integer.valueOf(width.length() > 0 ? width : "-1"));
+        section.setHeight(Integer.valueOf(height.length() > 0 ? height : "-1"));
+
+        target.setExportSection(section);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ComputedDischargeCurveExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,220 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Date;
+import java.text.DateFormat;
+import java.util.Locale;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+import net.sf.jasperreports.engine.JRException;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WKmsJRDataSource;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveExporter extends AbstractExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger =
+        Logger.getLogger(ComputedDischargeCurveExporter.class);
+
+
+    public static final String CSV_W_HEADER =
+        "export.computed.discharge.curve.csv.header.w";
+
+    public static final String CSV_Q_HEADER =
+        "export.computed.discharge.curve.csv.header.q";
+
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m\u00b3/s]";
+
+    public static final String PDF_HEADER_MODE = "export.computed.discharge.pdf.mode";
+    public static final String JASPER_FILE = "export.computed.discharge.pdf.file";
+
+    protected List<WQKms> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("ComputedDischargeCurveExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WQKms>();
+    }
+
+
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult)d).getData();
+            if (d instanceof WQKms []) {
+                data.addAll(Arrays.asList((WQKms [])d));
+            }
+        }
+    }
+
+
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("ComputedDischargeCurveExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        double[] res = new double[3];
+
+        for (WQKms wqkms: data) {
+            int size = wqkms.size();
+
+            for (int i = 0; i < size; i++) {
+                res = wqkms.get(i, res);
+
+                writer.writeNext(new String[] {
+                    wf.format(res[0]),
+                    qf.format(res[1])
+                });
+            }
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.debug("ComputedDischargeCurveExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER)
+        });
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    protected NumberFormat getWFormatter() {
+        return Formatter.getComputedDischargeW(context);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values.
+     *
+     * @return the number formatter for Q values.
+     */
+    protected NumberFormat getQFormatter() {
+        return Formatter.getComputedDischargeQ(context);
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        WKmsJRDataSource source = createJRData();
+
+        String jasperFile = Resources.getMsg(
+                                context.getMeta(),
+                                JASPER_FILE,
+                                "/jasper/computed-discharge_en.jasper");
+        String confPath = Config.getConfigDirectory().toString();
+
+        Map parameters = new HashMap();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            JasperPrint print = JasperFillManager.fillReport(
+                confPath + jasperFile,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, out);
+        }
+        catch(JRException je) {
+            logger.warn("Error generating PDF Report!");
+            je.printStackTrace();
+        }
+    }
+
+
+    protected WKmsJRDataSource createJRData() {
+        WKmsJRDataSource source = new WKmsJRDataSource();
+
+        addMetaData(source);
+        addWQData(source);
+
+        return source;
+    }
+
+
+    protected void addMetaData(WKmsJRDataSource source) {
+        CallMeta meta = context.getMeta();
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        source.addMetaData ("river", FLYSUtils.getRivername(flys));
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        source.addMetaData("date", df.format(new Date()));
+
+        double[] kms = FLYSUtils.getKmRange(flys);
+        source.addMetaData("range", String.valueOf(kms[0]));
+
+        source.addMetaData("calculation", Resources.getMsg(
+                                            locale,
+                                            PDF_HEADER_MODE,
+                                            "Computed Discharge"));
+    }
+
+    protected void addWQData(WKmsJRDataSource source) {
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        double[] res = new double[3];
+
+        for (WQKms wqkms: data) {
+            int size = wqkms.size();
+
+            for (int i = 0; i < size; i++) {
+                res = wqkms.get(i, res);
+
+                source.addData(new String[] {
+                    "",   // Empty, the WKmsJRDtasource stores km here.
+                    wf.format(res[0]),
+                    qf.format(res[1])
+                });
+            }
+        }
+
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ComputedDischargeCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,272 @@
+package de.intevation.flys.exports;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.xy.XYSeries;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WKms;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StickyAxisAnnotation;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+
+/**
+ * An OutGenerator that generates discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveGenerator
+extends      DischargeCurveGenerator
+implements   FacetTypes
+{
+    /** The logger used in this generator. */
+    private static Logger logger =
+        Logger.getLogger(ComputedDischargeCurveGenerator.class);
+
+    public static final String I18N_CHART_TITLE =
+        "chart.computed.discharge.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.computed.discharge.curve.subtitle";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.computed.discharge.curve.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT = "Abflusskurve";
+    public static final String I18N_YAXIS_LABEL_DEFAULT = "W [NN + m]";
+    public static final String I18N_MAINVALUES_Q_LABEL = "Q (Haupt- und Extremwerte)";
+    public static final String I18N_MAINVALUES_W_LABEL = "W (Haupt- und Extremwerte)";
+
+
+    /** Trivial Constructor. */
+    public ComputedDischargeCurveGenerator () {
+        super();
+    }
+
+
+    @Override
+    protected String getDefaultChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected String getDefaultChartSubtitle() {
+        double[] dist = getRange();
+
+        Object[] args = new Object[] {
+            getRiverName(),
+            dist[0]
+        };
+
+        return msg(I18N_CHART_SUBTITLE, "", args);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+
+        if (subtitle != null && subtitle.length() > 0) {
+            chart.addSubtitle(new TextTitle(subtitle));
+        }
+    }
+
+
+    @Override
+    protected String getDefaultYAxisLabel(int pos) {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    /**
+     * Process data, build up plot.
+     */
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+
+        logger.debug("ComputedDischargeCurveGenerator.doOut: " + name);
+
+        if (name == null) {
+            logger.warn("Broken facet in computed discharge out generation.");
+            return;
+        }
+
+        Facet facet = artifactFacet.getFacet();
+
+        if (name.equals(COMPUTED_DISCHARGE_Q)) {
+            doQOut((WQKms) artifactFacet.getData(context), artifactFacet, attr, visible);
+        }
+        else if (name.equals(STATIC_WQ)) {
+            doWQOut(artifactFacet.getData(context), artifactFacet, attr, visible);
+        }
+        else if (name.equals(STATIC_WQ_ANNOTATIONS)) {
+            doWQAnnotations(
+                artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(COMPUTED_DISCHARGE_MAINVALUES_Q)
+                || name.equals(MAINVALUES_Q)
+                || name.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
+                || name.equals(MAINVALUES_W)
+        ) {
+            doAnnotations((FLYSAnnotation)
+                artifactFacet.getData(context), artifactFacet, attr, visible);
+        }
+        else if (name.equals(STATIC_WKMS_INTERPOL) || name.equals(HEIGHTMARKS_POINTS)) {
+            doWAnnotations(
+                artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(STATIC_WKMS)) {
+            doWAnnotations(
+                artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(artifactFacet.getData(context),
+                artifactFacet,
+                attr, visible, YAXIS.W.idx);
+        }
+        else if (name.equals(DISCHARGE_CURVE)) {
+            doDischargeOut(
+                (WINFOArtifact) artifactFacet.getArtifact(),
+                artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                attr,
+                visible);
+        }
+        else {
+            logger.warn("Unknown facet type for computed discharge: " + name);
+            return;
+        }
+    }
+
+
+    /**
+     * Add WQ Data to plot.
+     * @param wqkms data as double[][]
+     */
+    protected void doWQOut(
+        Object           wqkms,
+        ArtifactAndFacet aaf,
+        Document         theme,
+        boolean          visible
+    ) {
+        double [][] data = (double [][]) wqkms;
+
+        XYSeries series = new StyledXYSeries(aaf.getFacetDescription(), theme);
+        StyledSeriesBuilder.addPoints(series, data, true);
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+
+
+    /**
+     * Add Q-Series to plot.
+     * @param wqkms actual data
+     * @param theme theme to use.
+     */
+    protected void doQOut(
+        WQKms            wqkms,
+        ArtifactAndFacet aaf,
+        Document         theme,
+        boolean          visible
+    ) {
+        XYSeries series = new StyledXYSeries(aaf.getFacetDescription(), theme);
+        StyledSeriesBuilder.addPointsQW(series, wqkms);
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+
+
+    /**
+     * Add WQ-Annotations to plot.
+     * @param wqkms actual data
+     * @param theme theme to use.
+     */
+    protected void doWQAnnotations(
+        Object   wqkms,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean  visible
+    ) {
+        List<StickyAxisAnnotation> xy = new ArrayList<StickyAxisAnnotation>();
+        double [][] data = (double [][]) wqkms;
+        for (int i = 0; i< data[0].length; i++) {
+            xy.add(new StickyAxisAnnotation(aandf.getFacetDescription(),
+                (float) data[0][i], StickyAxisAnnotation.SimpleAxis.X_AXIS));
+            xy.add(new StickyAxisAnnotation(aandf.getFacetDescription(),
+                (float) data[1][i], StickyAxisAnnotation.SimpleAxis.Y_AXIS));
+        }
+
+        doAnnotations(new FLYSAnnotation(aandf.getFacetDescription(), xy),
+            aandf, theme, visible);
+    }
+
+
+    /**
+     * Add W-Annotations to plot.
+     * @param wqkms actual data (double[][]).
+     * @param theme theme to use.
+     */
+    protected void doWAnnotations(
+        Object   wqkms,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean  visible
+    ) {
+        Facet facet = aandf.getFacet();
+
+        List<StickyAxisAnnotation> xy = new ArrayList<StickyAxisAnnotation>();
+        // Try to find them as WKms as well...
+        if (wqkms instanceof double[][]) {
+            double [][] data = (double [][]) wqkms;
+            for (int i = 0; i< data[0].length; i++) {
+                xy.add(new StickyAxisAnnotation(aandf.getFacetDescription(),
+                    (float) data[1][i], StickyAxisAnnotation.SimpleAxis.Y_AXIS));
+            }
+
+            doAnnotations(new FLYSAnnotation(facet.getDescription(), xy),
+                aandf, theme, visible);
+        }
+        else {
+            WKms data = (WKms) wqkms;
+            // assume its WKms
+            xy.add(new StickyAxisAnnotation(aandf.getFacetDescription(),
+              (float) data.getW(0), StickyAxisAnnotation.SimpleAxis.Y_AXIS));
+            xy.add(new StickyAxisAnnotation(aandf.getFacetDescription(),
+              (float) 180f, StickyAxisAnnotation.SimpleAxis.Y_AXIS));
+
+            doAnnotations(new FLYSAnnotation(facet.getDescription(), xy),
+                aandf, theme, visible);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ComputedDischargeCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific computed
+ * discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ComputedDischargeCurveInfoGenerator extends ChartInfoGenerator {
+
+    public ComputedDischargeCurveInfoGenerator() {
+        super(new ComputedDischargeCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/CrossSectionGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,303 @@
+package de.intevation.flys.exports;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.xy.XYSeries;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.DataProvider;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.CrossSectionFacet;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+import de.intevation.flys.model.FastCrossSectionLine;
+import de.intevation.flys.artifacts.model.HYKFactory;
+
+import de.intevation.flys.geom.Lines;
+
+import de.intevation.flys.utils.ThemeUtil;
+
+
+/**
+ * An OutGenerator that generates cross section graphs.
+ */
+public class CrossSectionGenerator
+extends      LongitudinalSectionGenerator
+implements   FacetTypes
+{
+    /** The logger that is used in this generator. */
+    private static Logger logger =
+        Logger.getLogger(CrossSectionGenerator.class);
+
+    public static final String I18N_CHART_TITLE =
+        "chart.cross_section.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.cross_section.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.cross_section.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.cross_section.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT = "Querprofildiagramm";
+    public static final String I18N_XAXIS_LABEL_DEFAULT = "Abstand [m]";
+    public static final String I18N_YAXIS_LABEL_DEFAULT = "W [NN + m]";
+
+
+    /** Trivial Constructor. */
+    public CrossSectionGenerator() {
+        super();
+    }
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return 1;
+            }
+
+            /** Get identifier for this index. */
+            @Override
+            public String getId(int idx) {
+                return "W";
+            }
+        };
+    }
+
+
+    /**
+     * Get localized chart title.
+     */
+    @Override
+    public String getDefaultChartTitle() {
+        Object[] i18n_msg_args = new Object[] {
+            getRiverName()
+        };
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT, i18n_msg_args);
+    }
+
+
+    /** Always return default subtitle. */
+    @Override
+    protected String getChartSubtitle() {
+        // XXX NOTE: overriding this method disables ChartSettings subtitle!
+        // The default implementation of this method in ChartGenerator returns
+        // the subtitle changed via the chart settings dialog. This method
+        // always returns the subtitle containing river and km, NEVER the
+        // ChartSettings subtitle!
+        return getDefaultChartSubtitle();
+    }
+
+
+    /** Get Charts default subtitle. */
+    @Override
+    protected String getDefaultChartSubtitle() {
+        List<DataProvider> providers =
+            context.getDataProvider(CrossSectionFacet.BLACKBOARD_CS_MASTER_DATA);
+        double km = 0d;
+        if (providers.size() > 0) {
+            FastCrossSectionLine csl = (FastCrossSectionLine) providers.get(0).
+                provideData(CrossSectionFacet.BLACKBOARD_CS_MASTER_DATA,
+                    null, context);
+            km = csl.getKm();
+        }
+
+        Object[] args = new Object[] {
+            getRiverName(),
+            km
+        };
+
+        return msg(I18N_CHART_SUBTITLE, "", args);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+        chart.addSubtitle(new TextTitle(subtitle));
+    }
+
+
+    @Override
+    protected String getDefaultXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
+    }
+
+
+    @Override
+    protected String getDefaultYAxisLabel(int pos) {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    /**
+     * Let one facet do its job.
+     */
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+
+        logger.debug("CrossSectionGenerator.doOut: " + name);
+
+        if (name == null) {
+            logger.error("No facet name for doOut(). No output generated!");
+            return;
+        }
+
+        if (name.equals(CROSS_SECTION)) {
+            doCrossSectionOut(
+                artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                attr,
+                visible);
+        }
+        else if (name.equals(CROSS_SECTION_WATER_LINE)) {
+            doCrossSectionWaterLineOut(
+                artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.AREA(name)) {
+            doArea(artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(HYK)) {
+            doHyk(artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(artifactFacet.getData(context),
+                artifactFacet,
+                attr, visible, YAXIS.W.idx);
+        }
+        else {
+            logger.warn("CrossSection.doOut: Unknown facet name: " + name);
+            return;
+        }
+    }
+
+
+    /** Look up the axis identifier for a given facet type. */
+    public int axisIdxForFacet(String facetName) {
+        // TODO Where to add thid axis too.
+        return 0;
+    }
+
+
+    /**
+     * Do cross sections waterline out.
+     *
+     * @param seriesName name of the data (line) to display in legend.
+     * @param theme Theme for the data series.
+     */
+    protected void doCrossSectionWaterLineOut(
+        Object   o,
+        String   seriesName,
+        Document theme,
+        boolean  visible
+    ) {
+        logger.debug("CrossSectionGenerator.doCrossSectionWaterLineOut");
+
+        Lines.LineData lines = (Lines.LineData) o;
+        // DO NOT SORT DATA! This destroys the gaps indicated by NaNs.
+        StyledXYSeries series = new StyledXYSeries(seriesName, false, theme);
+
+        // TODO i18n for all the label text and number format.
+        if (ThemeUtil.parseShowWidth(theme)) {
+            series.setLabel(series.getLabel() + ", b=" + lines.width + "m");
+        }
+        if (ThemeUtil.parseShowLevel(theme) && lines.points.length >0
+            && lines.points[1].length > 0) {
+            series.setLabel(series.getLabel() + ", W=" + lines.points[1][0] + "NN+m");
+        }
+        if (ThemeUtil.parseShowMiddleHeight(theme) && lines.width != 0) {
+            series.setLabel(series.getLabel() + ",H=" + lines.area / lines.width + "m : " + lines.area + "/" + lines.width);    
+        }
+
+        StyledSeriesBuilder.addPoints(series, lines.points, false);
+
+        addAxisSeries(series, 0, visible);
+    }
+
+
+    /** Add HYK-Annotations (colorize and label some areas, draw lines. */
+    protected void doHyk(
+        Object   o,
+        String   seriesName,
+        Document theme,
+        boolean  visible
+    ) {
+        logger.debug("CrossSectionGenerator.doHyk");
+
+        List<HYKFactory.Zone> zones = (List<HYKFactory.Zone>) o;
+
+        if (zones == null || zones.size() == 0) {
+            logger.warn("CrossSectionGenerator.doHYK: empty zone list received.");
+            return;
+        }
+
+        // Actual Styling is done in XYChartGenerator.
+        if (visible) {
+            addVisibleAnnotations(new FLYSAnnotation(seriesName, null, zones, theme));
+        }
+    }
+
+
+    /**
+     * Do cross sections out.
+     *
+     * @param seriesName name of the data (line) to display in legend.
+     * @param theme Theme for the data series.
+     */
+    protected void doCrossSectionOut(
+        Object   o,
+        String   seriesName,
+        Document theme,
+        boolean  visible
+    ) {
+        logger.debug("CrossSectionGenerator.doCrossSectionOut");
+
+        XYSeries series = new StyledXYSeries(seriesName, theme);
+
+        StyledSeriesBuilder.addPoints(series, (double [][]) o, false);
+
+        addAxisSeries(series, 0, visible);
+    }
+
+
+    /**
+     * Creates a new <i>ChartSection</i>.
+     *
+     * @return a new <i>ChartSection</i>.
+     */
+    @Override
+    protected ChartSection buildChartSection() {
+        ChartSection chartSection = new ChartSection();
+        chartSection.setTitle(getChartTitle());
+        chartSection.setDisplayGird(isGridVisible());
+        return chartSection;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/CrossSectionInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,14 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific cross
+ * sections.
+ */
+public class CrossSectionInfoGenerator extends ChartInfoGenerator {
+
+    public CrossSectionInfoGenerator() {
+        super(new CrossSectionGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DeltaWtExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,174 @@
+package de.intevation.flys.exports;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+import de.intevation.flys.artifacts.model.fixings.DeltaWT;
+import de.intevation.flys.artifacts.model.fixings.DeltaWTsKM;
+import de.intevation.flys.artifacts.model.fixings.FixResult;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.Formatter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+public class DeltaWtExporter
+extends      AbstractExporter
+{
+    private static Logger log = Logger.getLogger(DeltaWtExporter.class);
+
+    public static final String CSV_KM_HEADER =
+        "export.fixings.deltawt.csv.header.km";
+
+    public static final String CSV_DELTA_W_HEADER =
+        "export.fixings.deltawt.csv.header.deltaw";
+
+    public static final String CSV_T_HEADER =
+        "export.fixings.deltawt.csv.header.t";
+
+    public static final String CSV_T_DESC_HEADER =
+        "export.fixings.deltawt.csv.header.description";
+
+    public static final String DEFAULT_CSV_KM_HEADER = "km";
+
+    public static final String DEFAULT_CSV_DELTA_W_HEADER = "\u0394 W [cm]";
+
+    public static final String DEFAULT_CSV_T_HEADER = "Datum";
+
+    public static final String DEFAULT_CSV_T_DESC_HEADER = "Beschreibung";
+
+    protected List<DeltaWTsKM> deltaWTsKMs;
+
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        log.debug("DeltaWtExporter.init");
+        super.init(request, out, context);
+        deltaWTsKMs = new ArrayList<DeltaWTsKM>();
+    }
+
+    @Override
+    public void generate() throws IOException {
+        log.debug("DeltaWtExporter.generate");
+        if (facet == null) {
+            throw new IOException("invalid (null) facet for exporter");
+        }
+        else if (facet.equals(FACET_CSV)) {
+            generateCSV();
+        }
+        else if (facet.equals(FACET_PDF)) {
+            generatePDF();
+        }
+        else {
+            throw new IOException("invalid facet (" + facet + ") for exporter");
+        }
+    }
+
+    @Override
+    protected void addData(Object d) {
+        log.debug("DeltaWtExporter.addData");
+        if (!(d instanceof CalculationResult)) {
+            log.warn("Invalid data type");
+            return;
+        }
+
+        Object data = ((CalculationResult)d).getData();
+        if (!(data instanceof FixResult)) {
+            log.warn("Invalid data stored in result.");
+        }
+        FixResult result = (FixResult)data;
+        deltaWTsKMs.add(result.getDeltaWTsKM());
+    }
+
+    @Override
+    protected void writeCSVData(CSVWriter writer) throws IOException {
+
+        boolean debug = log.isDebugEnabled();
+
+        writeCSVHeader(writer);
+
+        NumberFormat kmF = getKMFormatter();
+        NumberFormat dwF = getDeltaWFormatter();
+
+        CallMeta meta = context.getMeta();
+        Locale locale = Resources.getLocale(meta);
+        DateFormat dF = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        if (debug) {
+            log.debug("DeltaWTsKMs: " + deltaWTsKMs.size());
+        }
+
+        for (DeltaWTsKM deltaWTsKM: deltaWTsKMs) {
+            List<DeltaWTsKM.KM> dkms = deltaWTsKM.getKms();
+            if (debug) {
+                log.debug("DeltaWTsKM.KM: " + dkms.size());
+            }
+            for (DeltaWTsKM.KM dkm: dkms) {
+                double km = dkm.getKm();
+                String kmS = kmF.format(km);
+                List<DeltaWT> deltaWTs = dkm.getDeltaWTs();
+                if (debug) {
+                    log.debug("deltaWTs: " + deltaWTs.size());
+                }
+                for (DeltaWT dwt: deltaWTs) {
+                    double deltaW = dwt.getDeltaW();
+                    Date   date   = dwt.getDate();
+                    String desc   = dwt.getDescription();
+
+                    String deltaWS = dwF.format(deltaW);
+                    String dateS   = dF.format(date);
+
+                    writer.writeNext(new String[] {
+                        kmS,
+                        deltaWS,
+                        dateS,
+                        desc != null ? desc : "" });
+                }
+            }
+        }
+        writer.flush();
+    }
+
+
+    protected NumberFormat getKMFormatter() {
+        return Formatter.getFixDeltaWKM(context);
+    }
+
+    protected NumberFormat getDeltaWFormatter() {
+        return Formatter.getFixDeltaWDeltaW(context);
+    }
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        log.debug("DeltaWtExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_KM_HEADER,      DEFAULT_CSV_KM_HEADER),
+            msg(CSV_DELTA_W_HEADER, DEFAULT_CSV_DELTA_W_HEADER),
+            msg(CSV_T_HEADER,       DEFAULT_CSV_T_HEADER),
+            msg(CSV_T_DESC_HEADER,  DEFAULT_CSV_T_DESC_HEADER)
+        });
+    }
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        // TODO: Implement me!
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,199 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.Range;
+import org.jfree.data.xy.XYSeries;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.River;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+
+/**
+ * An OutGenerator that generates discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeCurveGenerator
+extends      XYChartGenerator
+implements   FacetTypes {
+
+    public static enum YAXIS {
+        W(0);
+        protected int idx;
+        private YAXIS(int c) {
+            idx = c;
+        }
+    }
+
+    /** The logger used in this generator. */
+    private static Logger logger =
+        Logger.getLogger(DischargeCurveGenerator.class);
+
+    public static final String I18N_CHART_TITLE =
+        "chart.discharge.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.discharge.curve.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.discharge.curve.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.discharge.curve.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  = "Abflusskurven";
+    public static final String I18N_XAXIS_LABEL_DEFAULT  = "Q [m\u00b3/s]";
+    public static final String I18N_YAXIS_LABEL_DEFAULT  = "W [cm]";
+
+
+    public DischargeCurveGenerator() {
+        super();
+    }
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+
+    @Override
+    protected String getDefaultChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    /**
+     * Empty (suppress subtitle).
+     */
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+    }
+
+
+    @Override
+    protected String getDefaultXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
+    }
+
+    @Override
+    protected String getDefaultYAxisLabel(int pos) {
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    /* TODO is this one really needed? */
+    @Override
+    protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
+        boolean zoomin = super.zoom(plot, axis, bounds, x);
+
+        if (!zoomin) {
+            axis.setLowerBound(0d);
+        }
+
+        return zoomin;
+    }
+
+
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         theme,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+        logger.debug("DischargeCurveGenerator.doOut: " + name);
+
+        if (name.equals(DISCHARGE_CURVE)) {
+            doDischargeOut(
+                (WINFOArtifact) artifactFacet.getArtifact(),
+                artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                theme,
+                visible);
+        }
+        else if (name.equals(COMPUTED_DISCHARGE_MAINVALUES_Q)
+                || name.equals(MAINVALUES_Q)
+                || name.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
+                || name.equals(MAINVALUES_W))
+        {
+            doAnnotations((FLYSAnnotation) artifactFacet.getData(context),
+                artifactFacet, theme, visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(artifactFacet.getData(context),
+                artifactFacet,
+                theme, visible, YAXIS.W.idx);
+        }
+        else {
+           logger.warn("DischargeCurveGenerator.doOut: Unknown facet name: " + name);
+           return;
+        }
+    }
+
+
+    /**
+     * Add series with discharge curve to diagram.
+     */
+    protected void doDischargeOut(
+        WINFOArtifact artifact,
+        Object        o,
+        String        description,
+        Document      theme,
+        boolean       visible)
+    {
+        WQKms wqkms = (WQKms) o;
+
+        String gaugeName = wqkms.getName();
+
+        River river = FLYSUtils.getRiver(artifact);
+
+        if (river == null) {
+            logger.debug("no river found");
+            return;
+        }
+
+        Gauge gauge = river.determineGaugeByName(gaugeName);
+
+        if (gauge == null) {
+            logger.debug("no gauge found");
+            return;
+        }
+
+        XYSeries series = new StyledXYSeries(description, theme);
+
+        StyledSeriesBuilder.addPointsQW(series, wqkms);
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,16 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific discharge
+ * curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeCurveInfoGenerator extends ChartInfoGenerator {
+
+    public DischargeCurveInfoGenerator() {
+        super(new DischargeCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,132 @@
+package de.intevation.flys.exports;
+
+import java.text.NumberFormat;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.CalculationResult;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeLongitudinalSectionExporter extends WaterlevelExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger =
+        Logger.getLogger(DischargeLongitudinalSectionExporter.class);
+
+
+    public static final String CSV_KM_HEADER =
+        "export.discharge.longitudinal.section.csv.header.km";
+
+    public static final String CSV_W_HEADER =
+        "export.discharge.longitudinal.section.csv.header.w";
+
+    public static final String CSV_CW_HEADER =
+        "export.discharge.longitudinal.section.csv.header.cw";
+
+    public static final String CSV_Q_HEADER =
+        "export.discharge.longitudinal.section.csv.header.q";
+
+    public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km";
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_CW_HEADER = "W korr.";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m\u00b3/s]";
+
+
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult)d).getData();
+            if (d instanceof WQKms []) {
+                data.add((WQKms [])d);
+            }
+        }
+    }
+
+
+    @Override
+    protected void writeCSVHeader(
+        CSVWriter writer,
+        boolean   atGauge,
+        boolean   isQ
+    ) {
+        logger.info("WaterlevelExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_CW_HEADER, DEFAULT_CSV_CW_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER)
+        });
+    }
+
+
+    @Override
+    protected void wQKms2CSV(
+        CSVWriter writer,
+        WQKms     wqkms,
+        boolean   atGauge,
+        boolean   isQ,
+        boolean   isRange
+    ) {
+        logger.debug("WaterlevelExporter.wQKms2CSV");
+
+        int      size   = wqkms.size();
+        double[] result = new double[4];
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        for (int i = 0; i < size; i ++) {
+            result = wqkms.get(i, result);
+
+            String wc = "";
+            if (wqkms instanceof WQCKms) {
+                wc = wf.format(result[3]);
+            }
+
+            writer.writeNext(new String[] {
+                kmf.format(result[2]),
+                wf.format(result[0]),
+                wc,
+                qf.format(result[1])
+            });
+        }
+    }
+
+
+    @Override
+    protected void addWSTColumn(WstWriter writer, WQKms wqkms) {
+        String name = wqkms.getName();
+
+        // is it a W or a Q mode?
+        int wIdx = name.indexOf("W");
+        int qIdx = name.indexOf("Q");
+
+        String wq = null;
+        if (wIdx >= 0) {
+            wq = "W";
+        }
+        else if (qIdx >= 0) {
+            wq = "Q";
+        }
+
+        // we just want to display the first W or Q value in the WST
+        int start = name.indexOf("(");
+        int end   = name.indexOf(")");
+
+        String   tmp    = name.substring(start+1, end);
+        String[] values = tmp.split(";");
+
+        String column = wq + "=" + values[0];
+
+        writer.addColumn(column);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,131 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.data.xy.XYSeries;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WKms;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+
+
+/**
+ * An OutGenerator that generates discharge longitudinal section curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeLongitudinalSectionGenerator
+extends      LongitudinalSectionGenerator
+implements   FacetTypes
+{
+    private static Logger logger =
+        Logger.getLogger(DischargeLongitudinalSectionGenerator.class);
+
+
+    public DischargeLongitudinalSectionGenerator() {
+        super();
+    }
+
+
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        logger.debug("DischargeLongitudinalSectionGenerator.doOut");
+
+        String name = artifactFacet.getFacetName();
+
+        if (name == null) {
+            return;
+        }
+
+        Facet facet = artifactFacet.getFacet();
+
+        if (IS.WQ_KM(name)) {
+            doWOut(
+                (WQKms) artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(DISCHARGE_LONGITUDINAL_Q)) {
+            doQOut(
+                (WQKms) artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(DISCHARGE_LONGITUDINAL_C)) {
+            doCorrectedWOut(
+                (WQCKms) artifactFacet.getData(context),
+                facet,
+                attr,
+                visible);
+        }
+        else if (IS.W_KM(name)) {
+            doWOut(
+                (WKms) artifactFacet.getData(context),
+                artifactFacet,
+                attr, visible);
+        }
+        else if (name.equals(LONGITUDINAL_ANNOTATION)) {
+            doAnnotations((FLYSAnnotation) artifactFacet.getData(context),
+                 artifactFacet, attr, visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(artifactFacet.getData(context),
+                artifactFacet,
+                attr, visible, YAXIS.W.idx);
+        }
+        else {
+            logger.warn("Unknown facet name: " + name);
+        }
+    }
+
+
+    /**
+     * Adds a new series for the corrected W curve.
+     *
+     * @param wqckms The object that contains the corrected W values.
+     * @param theme The theme that contains styling information.
+     */
+    protected void doCorrectedWOut(
+        WQCKms   wqckms,
+        Facet    facet,
+        Document theme,
+        boolean  visible
+    ) {
+        logger.debug("DischargeLongitudinalSectionGenerator.doCorrectedWOut");
+
+        int size = wqckms.size();
+
+        if (size > 0) {
+            XYSeries series = new StyledXYSeries(
+                facet.getDescription(),
+                theme);
+
+            for (int i = 0; i < size; i++) {
+                series.add(wqckms.getKm(i), wqckms.getC(i));
+            }
+
+            addAxisSeries(series, YAXIS.W.idx, visible);
+        }
+
+        if (wqckms.guessWaterIncreasing()) {
+            setInverted(true);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific discharge
+ * longitudinal section curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DischargeLongitudinalSectionInfoGenerator
+extends      ChartInfoGenerator
+{
+    public DischargeLongitudinalSectionInfoGenerator() {
+        super(new DischargeLongitudinalSectionGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DoubleAttribute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.exports;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DoubleAttribute extends VisibleAttribute {
+
+
+    public DoubleAttribute(String name, double value, boolean visible) {
+        super(name, value, visible);
+    }
+
+
+    /**
+     * Calls VisibleAttribute.toXML() and appends afterwards an attribute
+     * <i>type</i> with value <i>double</i>.
+     *
+     * @param parent The parent Node.
+     *
+     * @return the new Node that represents this Attribute.
+     */
+    @Override
+    public Node toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+
+        Element ele = (Element) super.toXML(parent);
+        ele.setAttribute("type", "double");
+
+        return ele;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,260 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Date;
+import java.text.DateFormat;
+import java.util.Locale;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+import net.sf.jasperreports.engine.JRException;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.WQDay;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.WKmsJRDataSource;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveExporter extends AbstractExporter {
+
+    /** The logger used in this exporter. */
+    private static Logger logger = Logger.getLogger(WaterlevelExporter.class);
+
+
+    public static final String CSV_DURATION_HEADER =
+        "export.duration.curve.csv.header.duration";
+
+    public static final String CSV_W_HEADER =
+        "export.duration.curve.csv.header.w";
+
+    public static final String CSV_Q_HEADER =
+        "export.duration.curve.csv.header.q";
+
+    public static final String DEFAULT_CSV_DURATION_HEADER = "D [Tagen]";
+    public static final String DEFAULT_CSV_W_HEADER  = "W [NN + m]";
+    public static final String DEFAULT_CSV_Q_HEADER  = "Q [m\u00b3/s]";
+
+    public static final String PDF_HEADER_MODE = "export.duration.pdf.mode";
+    public static final String JASPER_FILE = "export.duration.pdf.file";
+
+    /** The storage that contains all WQKms objects for the different facets. */
+    protected List<WQDay> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("DurationCurveExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WQDay>();
+    }
+
+
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult)d).getData();
+            if (d instanceof WQDay) {
+                data.add((WQDay)d);
+            }
+        }
+    }
+
+
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("DurationCurveExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        for (WQDay wqday: data) {
+            wQDay2CSV(writer, wqday);
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.info("DurationCurveExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+            msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
+            msg(CSV_DURATION_HEADER, DEFAULT_CSV_DURATION_HEADER)
+        });
+    }
+
+
+    protected void wQDay2CSV(CSVWriter writer, WQDay wqday) {
+        logger.debug("DurationCurveExporter.wQDay2CSV");
+
+        int size = wqday.size();
+
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+        NumberFormat df  = getDFormatter();
+
+        if (wqday.isIncreasing()) {
+            for (int i = size-1; i >= 0; i --) {
+                writer.writeNext(new String[] {
+                    wf.format(wqday.getW(i)),
+                    qf.format(wqday.getQ(i)),
+                    df.format(wqday.getDay(i))
+                });
+            }
+        }
+        else {
+            for (int i = 0; i < size; i ++) {
+                writer.writeNext(new String[] {
+                    wf.format(wqday.getW(i)),
+                    qf.format(wqday.getQ(i)),
+                    df.format(wqday.getDay(i))
+                });
+            }
+        }
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    @Override
+    protected NumberFormat getWFormatter() {
+        return Formatter.getDurationW(context);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values.
+     *
+     * @return the number formatter for Q values.
+     */
+    @Override
+    protected NumberFormat getQFormatter() {
+        return Formatter.getDurationQ(context);
+    }
+
+
+    /**
+     * Returns the number formatter for duration values.
+     *
+     * @return the number formatter for duration values.
+     */
+    protected NumberFormat getDFormatter() {
+        return Formatter.getDurationD(context);
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        WKmsJRDataSource source = createJRData();
+
+        String jasperFile = Resources.getMsg(
+                                context.getMeta(),
+                                JASPER_FILE,
+                                "/jasper/duration_en.jasper");
+        String confPath = Config.getConfigDirectory().toString();
+
+        Map parameters = new HashMap();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            JasperPrint print = JasperFillManager.fillReport(
+                confPath + jasperFile,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, out);
+        }
+        catch(JRException je) {
+            logger.warn("Error generating PDF Report!");
+            je.printStackTrace();
+        }
+    }
+
+    protected WKmsJRDataSource createJRData() {
+        WKmsJRDataSource source = new WKmsJRDataSource();
+
+        addMetaData(source);
+        for (WQDay wqday: data) {
+            addWQDayData(source, wqday);
+        }
+
+        return source;
+    }
+
+
+    protected void addMetaData(WKmsJRDataSource source) {
+        CallMeta meta = context.getMeta();
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        source.addMetaData ("river", FLYSUtils.getRivername(flys));
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        source.addMetaData("date", df.format(new Date()));
+
+        double[] kms = FLYSUtils.getKmRange(flys);
+        source.addMetaData("range", String.valueOf(kms[0]));
+
+        source.addMetaData("calculation", Resources.getMsg(
+                                            locale,
+                                            PDF_HEADER_MODE,
+                                            "Duration"));
+    }
+
+    protected void addWQDayData(WKmsJRDataSource source, WQDay wqday) {
+        int size = wqday.size();
+
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+        NumberFormat df  = getDFormatter();
+
+        if (wqday.isIncreasing()) {
+            for (int i = size-1; i >= 0; i --) {
+                source.addData(new String[] {
+                    "",
+                    wf.format(wqday.getW(i)),
+                    qf.format(wqday.getQ(i)),
+                    "", "", "",
+                    df.format(wqday.getDay(i))
+                });
+            }
+        }
+        else {
+            for (int i = 0; i < size; i ++) {
+                source.addData(new String[] {
+                    "",
+                    wf.format(wqday.getW(i)),
+                    qf.format(wqday.getQ(i)),
+                    "", "", "",
+                    df.format(wqday.getDay(i))
+                });
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,328 @@
+package de.intevation.flys.exports;
+
+import java.awt.Font;
+import java.awt.geom.Point2D;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.Range;
+import org.jfree.data.xy.XYSeries;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WQDay;
+
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+
+/**
+ * An OutGenerator that generates duration curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveGenerator
+extends      XYChartGenerator
+implements   FacetTypes
+{
+    public static enum YAXIS {
+        W(0),
+        Q(1);
+        public int idx;
+        private YAXIS(int c) {
+           idx = c;
+        }
+    }
+
+    private static Logger logger =
+        Logger.getLogger(DurationCurveGenerator.class);
+
+    public static final String I18N_CHART_TITLE =
+        "chart.duration.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.duration.curve.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.duration.curve.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.duration.curve.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  =
+        "Dauerlinie";
+
+    public static final String I18N_XAXIS_LABEL_DEFAULT  =
+        "Unterschreitungsdauer [Tage]";
+
+    public static final String I18N_YAXIS_LABEL_DEFAULT  =
+        "W [NN + m]";
+
+
+    public DurationCurveGenerator() {
+        super();
+    }
+
+
+    /**
+     * Create Axis for given index.
+     * @return axis with according internationalized label.
+     */
+    @Override
+    protected NumberAxis createYAxis(int index) {
+        Font labelFont = new Font("Tahoma", Font.BOLD, 14);
+        String label   = getYAxisLabel(index);
+
+        NumberAxis axis = createNumberAxis(index, label);
+        if (index == YAXIS.W.idx) {
+            axis.setAutoRangeIncludesZero(false);
+        }
+        axis.setLabelFont(labelFont);
+        return axis;
+    }
+
+
+    @Override
+    protected String getDefaultChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected String getDefaultChartSubtitle() {
+        double[] dist  = getRange();
+
+        Object[] args = new Object[] {
+            getRiverName(),
+            dist[0]
+        };
+
+        return msg(I18N_CHART_SUBTITLE, "", args);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+
+        if (subtitle != null && subtitle.length() > 0) {
+            chart.addSubtitle(new TextTitle(subtitle));
+        }
+    }
+
+
+    @Override
+    protected String getDefaultXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
+    }
+
+
+    @Override
+    protected String getDefaultYAxisLabel(int index) {
+        String label = "default";
+        if (index == YAXIS.W.idx) {
+            label = msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+        }
+        else if (index == YAXIS.Q.idx) {
+            // TODO i18n for this label
+            label = "Q [m\u00b3/s]";
+            //label = msg(get2YAxisLabelKey(), get2YAxisDefaultLabel());
+        }
+
+        return label;
+    }
+
+
+    @Override
+    protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
+        boolean zoomin = super.zoom(plot, axis, bounds, x);
+
+        if (!zoomin) {
+            axis.setLowerBound(0d);
+        }
+
+        axis.setUpperBound(364);
+
+        return zoomin;
+    }
+
+
+    /**
+     * This method overrides the method in the parent class to set the lower
+     * bounds of the Q axis to 0. This axis should never display negative
+     * values on its own.
+     */
+    @Override
+    protected boolean zoomY(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
+        boolean zoomin = super.zoom(plot, axis, bounds, x);
+
+        if (!zoomin && axis instanceof IdentifiableNumberAxis) {
+            String id = ((IdentifiableNumberAxis) axis).getId();
+
+            if (YAXIS.Q.toString().equals(id)) {
+                axis.setLowerBound(0d);
+            }
+        }
+
+        return zoomin;
+    }
+
+
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+
+        logger.debug("DurationCurveGenerator.doOut: " + name);
+
+        if (name == null || name.length() == 0) {
+            logger.error("No facet given. Cannot create dataset.");
+            return;
+        }
+
+        if (name.equals(DURATION_W)) {
+            doWOut(
+                (WQDay) artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(DURATION_Q)) {
+            doQOut(
+                (WQDay) artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(DURATION_MAINVALUES_Q)
+                || name.equals(MAINVALUES_Q)
+                || name.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
+                || name.equals(MAINVALUES_W)
+        ) {
+            doAnnotations(
+                (FLYSAnnotation) artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(RELATIVE_POINT)) {
+            doPointOut((Point2D) artifactFacet.getData(context),
+                artifactFacet,
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(
+                artifactFacet.getData(context),
+                artifactFacet,
+                attr, visible, YAXIS.W.idx);
+        }
+        else {
+            logger.warn("Unknown facet name: " + name);
+            return;
+        }
+    }
+
+
+    /**
+     * Creates the series for a duration curve's W facet.
+     *
+     * @param wqdays The WQDay store that contains the Ws.
+     * @param theme
+     */
+    protected void doWOut(
+        WQDay            wqdays,
+        ArtifactAndFacet aaf,
+        Document         theme,
+        boolean          visible
+    ) {
+        logger.debug("DurationCurveGenerator.doWOut");
+
+        XYSeries series = new StyledXYSeries(aaf.getFacetDescription(), theme);
+
+        int size = wqdays.size();
+        for (int i = 0; i < size; i++) {
+            int  day = wqdays.getDay(i);
+            double w = wqdays.getW(i);
+
+            series.add((double) day, w);
+        }
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+
+    protected void doPointOut(
+        Point2D point,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean visible
+    ){
+        logger.debug("DurationCurveGenerator.doPointOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        series.add(point.getX(), point.getY());
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+
+
+    /**
+     * Creates the series for a duration curve's Q facet.
+     *
+     * @param wqdays The WQDay store that contains the Qs.
+     * @param theme
+     */
+    protected void doQOut(
+        WQDay            wqdays,
+        ArtifactAndFacet aaf,
+        Document         theme,
+        boolean          visible
+    ) {
+        logger.debug("DurationCurveGenerator.doQOut");
+
+        XYSeries series = new StyledXYSeries(aaf.getFacetDescription(), theme);
+
+        int size = wqdays.size();
+        for (int i = 0; i < size; i++) {
+            int  day = wqdays.getDay(i);
+            double q = wqdays.getQ(i);
+
+            series.add((double) day, q);
+        }
+
+        addAxisSeries(series, YAXIS.Q.idx, visible);
+    }
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+    // MainValue-Annotations should be visualized by a line that goes to the curve itself.
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific duration
+ * curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DurationCurveInfoGenerator
+extends      ChartInfoGenerator
+{
+    public DurationCurveInfoGenerator() {
+        super(new DurationCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/EmptySettings.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,77 @@
+package de.intevation.flys.exports;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import de.intevation.artifactdatabase.state.Settings;
+import de.intevation.artifactdatabase.state.Section;
+
+
+/**
+ * An implementation of <i>Settings</i> that doesn't take new <i>Section</i>s
+ * and that always creates an empty <b>settings</b> DOM node in its
+ * <i>toXML()</i> operation.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class EmptySettings implements Settings {
+
+    public EmptySettings() {
+    }
+
+
+    /**
+     * This method has no function. It is not implemented!
+     *
+     * @param section A Section.
+     */
+    @Override
+    public void addSection(Section section) {
+        // do nothing
+    }
+
+
+    /**
+     * Always returns 0.
+     *
+     * @return 0.
+     */
+    @Override
+    public int getSectionCount() {
+        return 0;
+    }
+
+
+    /**
+     * This method always returns null. It is not implemented!
+     *
+     * @param pos A position.
+     *
+     * @return null.
+     */
+    @Override
+    public Section getSection(int pos) {
+        return null;
+    }
+
+
+    /**
+     * This method has no function. It is not implemented!
+     */
+    @Override
+    public void removeSection(Section section) {
+        // do nothing
+    }
+
+
+    /**
+     * This method creates an empty <i>settings</i> DOM node.
+     *
+     * @param parent A parent DOM node.
+     */
+    @Override
+    public void toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+        parent.appendChild(owner.createElement("settings"));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ExportSection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,46 @@
+package de.intevation.flys.exports;
+
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class ExportSection extends TypeSection {
+
+    public static final String WIDTH_ATTR  = "width";
+    public static final String HEIGHT_ATTR = "height";
+
+
+    public ExportSection() {
+        super("export");
+    }
+
+
+    public void setWidth(int width) {
+        if (width <= 0) {
+            return;
+        }
+
+        setIntegerValue(WIDTH_ATTR, width);
+    }
+
+
+    public Integer getWidth() {
+        return getIntegerValue(WIDTH_ATTR);
+    }
+
+
+    public void setHeight(int height) {
+        if (height <= 0) {
+            return;
+        }
+
+        setIntegerValue(HEIGHT_ATTR, height);
+    }
+
+
+    public Integer getHeight() {
+        return getIntegerValue(HEIGHT_ATTR);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/FlowVelocityExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,127 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.FlowVelocityData;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FlowVelocityExporter extends AbstractExporter {
+
+    private static final Logger logger =
+        Logger.getLogger(FlowVelocityExporter.class);
+
+
+    public static final String CSV_KM =
+        "export.flow_velocity.csv.header.km";
+
+    public static final String CSV_V_TOTAL =
+        "export.flow_velocity.csv.header.v_total";
+
+    public static final String CSV_V_MAIN =
+        "export.flow_velocity.csv.header.v_main";
+
+    public static final String CSV_TAU_MAIN =
+        "export.flow_velocity.csv.header.tau_main";
+
+    public static final String CSV_Q =
+        "export.flow_velocity.csv.header.q";
+
+    public static final String CSV_LOCATIONS =
+        "export.flow_velocity.csv.header.locations";
+
+
+    protected List<FlowVelocityData[]> data;
+
+
+    public void init(Document request, OutputStream out, CallContext cc) {
+        super.init(request, out, cc);
+        data = new ArrayList<FlowVelocityData[]>();
+    }
+
+
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult) d).getData();
+
+            if (d instanceof FlowVelocityData[]) {
+                logger.debug("Add new data of type FlowVelocityData");
+                data.add((FlowVelocityData[]) d);
+            }
+        }
+    }
+
+
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("FlowVelocityExporter.writeCSVData");
+        logger.debug("CSV gets " + data.size() + " FlowVelocityData objects.");
+
+        writeCSVHeader(writer);
+
+        for (FlowVelocityData[] d: data) {
+            data2CSV(writer, d);
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        writer.writeNext(new String[] {
+            msg(CSV_KM, CSV_KM),
+            msg(CSV_V_TOTAL, CSV_V_TOTAL),
+            msg(CSV_V_MAIN, CSV_V_MAIN),
+            msg(CSV_TAU_MAIN, CSV_TAU_MAIN),
+            msg(CSV_Q, CSV_Q),
+            msg(CSV_LOCATIONS, CSV_LOCATIONS)
+        });
+    }
+
+
+    protected void data2CSV(CSVWriter writer, FlowVelocityData[] fData) {
+        logger.debug("Add next FlowVelocityData to CSV");
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        for (FlowVelocityData data: fData) {
+            for (int i = 0, n = data.size(); i < n; i++) {
+                NumberFormat kmF  = Formatter.getFlowVelocityKM(context);
+                NumberFormat valF = Formatter.getFlowVelocityValues(context);
+                NumberFormat qF   = Formatter.getFlowVelocityQ(context);
+
+                writer.writeNext(new String[] {
+                    kmF.format(data.getKM(i)),
+                    valF.format(data.getVMain(i)),
+                    valF.format(data.getVTotal(i)),
+                    valF.format(data.getTauMain(i)),
+                    qF.format(data.getQ(i)) + "=" + data.getZone(),
+                    FLYSUtils.getLocationDescription(flys, data.getKM(i)),
+                });
+            }
+        }
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        logger.error("TODO: Implement FlowVelocityExporter.writePDF");
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/exports/FlowVelocityGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,347 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.data.xy.XYSeries;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.FlowVelocityData;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+
+/**
+ * An OutGenerator that generates flow velocity curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FlowVelocityGenerator
+extends      XYChartGenerator
+implements   FacetTypes
+{
+    public enum YAXIS {
+        V(0),
+        T(1);
+        protected int idx;
+        private YAXIS(int c) {
+           idx = c;
+        }
+    }
+
+    /** The logger that is used in this generator. */
+    private static Logger logger = Logger.getLogger(FlowVelocityGenerator.class);
+
+    /** Key to look up internationalized String for annotations label. */
+    public static final String I18N_ANNOTATIONS_LABEL =
+        "chart.flow_velocity.annotations.label";
+
+    /**
+     * Key to look up internationalized String for LongitudinalSection diagrams
+     * titles.
+     */
+    public static final String I18N_CHART_TITLE =
+        "chart.flow_velocity.section.title";
+
+    /**
+     * Key to look up internationalized String for LongitudinalSection diagrams
+     * subtitles.
+     */
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.flow_velocity.section.subtitle";
+
+    /**
+     * Key to look up internationalized String for LongitudinalSection diagrams
+     * short subtitles.
+     */
+    public static final String I18N_CHART_SHORT_SUBTITLE =
+        "chart.flow_velocity.section.shortsubtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.flow_velocity.section.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.flow_velocity.section.yaxis.label";
+
+    public static final String I18N_2YAXIS_LABEL =
+        "chart.flow_velocity.section.yaxis.second.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  = "Geschwindigkeit- und Schubspannung";
+    public static final String I18N_XAXIS_LABEL_DEFAULT  = "km";
+    public static final String I18N_YAXIS_LABEL_DEFAULT  = "Geschwindigkeit v [m/s]";
+    public static final String I18N_2YAXIS_LABEL_DEFAULT = "Schubspannung Tau [N]";
+
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+
+    /**
+     * Returns the default title for this chart.
+     *
+     * @return the default title for this chart.
+     */
+    @Override
+    public String getDefaultChartTitle() {
+        Object[] args = new Object[] {
+            getRiverName()
+        };
+
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT, args);
+    }
+
+
+    /**
+     * Get internationalized label for the x axis.
+     */
+    @Override
+    protected String getDefaultXAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return msg(
+            I18N_XAXIS_LABEL,
+            I18N_XAXIS_LABEL_DEFAULT,
+            new Object[] { FLYSUtils.getRiver(flys).getName() });
+    }
+
+
+    @Override
+    protected String getDefaultYAxisLabel(int index) {
+        String label = "default";
+
+        if (index == YAXIS.V.idx) {
+            label = getVAxisLabel();
+        }
+        else if (index == YAXIS.T.idx) {
+            label = getTAxisLabel();
+        }
+
+        return label;
+    }
+
+
+    /**
+     * Get internationalized label for the y axis.
+     */
+    protected String getVAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    /**
+     * Get internationalized label for the y axis.
+     */
+    protected String getTAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return msg(I18N_2YAXIS_LABEL, I18N_2YAXIS_LABEL_DEFAULT);
+    }
+
+
+    /**
+     * Produce output.
+     * @param facet current facet.
+     * @param attr  theme for facet
+     */
+    public void doOut(
+        ArtifactAndFacet artifactAndFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactAndFacet.getFacetName();
+
+        logger.debug("FlowVelocityGenerator.doOut: " + name);
+
+        if (name == null) {
+            logger.error("No facet name for doOut(). No output generated!");
+            return;
+        }
+
+        Facet facet = artifactAndFacet.getFacet();
+
+        if (facet == null) {
+            return;
+        }
+
+        if (name.equals(FLOW_VELOCITY_MAINCHANNEL)) {
+            doMainChannelOut(
+                (FlowVelocityData) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(FLOW_VELOCITY_TOTALCHANNEL)) {
+            doTotalChannelOut(
+                (FlowVelocityData) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(FLOW_VELOCITY_TAU)) {
+            doTauOut(
+                (FlowVelocityData) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(FLOW_VELOCITY_ANNOTATION)) {
+            doAnnotations(
+                (FLYSAnnotation) artifactAndFacet.getData(context),
+                 artifactAndFacet,
+                 attr,
+                 visible);
+        }
+        else if (FacetTypes.IS.AREA(name)) {
+            doArea(
+                artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(
+                artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible,
+                YAXIS.V.idx);
+        }
+        else {
+            logger.warn("Unknown facet name: " + name);
+            return;
+        }
+    }
+
+
+    /**
+     * Process the output for W facets in a longitudinal section curve.
+     *
+     * @param data A FlowVelocityData object
+     * @param facet The facet. This facet does NOT support any data objects. Use
+     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
+     * data.
+     * @param theme The theme that contains styling information.
+     * @param visible The visibility of the curve.
+     */
+    protected void doMainChannelOut(
+        FlowVelocityData data,
+        ArtifactAndFacet aandf,
+        Document         theme,
+        boolean          visible
+    ) {
+        logger.debug("FlowVelocityGenerator.doMainChannelOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        StyledSeriesBuilder.addPoints(series, data.getMainChannelPoints(), true);
+
+        addAxisSeries(series, YAXIS.V.idx, visible);
+    }
+
+
+    /**
+     * Add items to dataseries which describes the differences.
+     */
+    protected void doTotalChannelOut(
+        FlowVelocityData data,
+        ArtifactAndFacet aandf,
+        Document         theme,
+        boolean          visible
+    ) {
+        logger.debug("FlowVelocityGenerator.doTotalChannelOut");
+
+        if (data == null) {
+            logger.warn("No data to add to FlowVelocity chart.");
+            return;
+         }
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        StyledSeriesBuilder.addPoints(series, data.getTotalChannelPoints(), true);
+
+        addAxisSeries(series, YAXIS.V.idx, visible);
+    }
+
+
+
+    /**
+     * @param data A FlowVelocityData object
+     * @param facet The facet. This facet does NOT support any data objects. Use
+     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
+     * data.
+     * @param theme The theme that contains styling information.
+     * @param visible The visibility of the curve.
+     */
+    protected void doTauOut(
+        FlowVelocityData data,
+        ArtifactAndFacet aandf,
+        Document         theme,
+        boolean          visible
+    ) {
+        logger.debug("FlowVelocityGenerator.doTauOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        StyledSeriesBuilder.addPoints(series, data.getTauPoints(), true);
+
+        addAxisSeries(series, YAXIS.T.idx, visible);
+    }
+
+
+    /** Look up the axis identifier for a given facet type. */
+    public int axisIdxForFacet(String facetName) {
+        if (FacetTypes.IS.V(facetName)) {
+            return YAXIS.V.idx;
+        }
+        else if (FacetTypes.IS.T(facetName)) {
+            return YAXIS.T.idx;
+        }
+        else {
+            logger.warn("Could not find axis for facet " + facetName);
+            return YAXIS.V.idx;
+        }
+    }
+
+
+    /**
+     * Do Area out.
+     * @param theme styling information.
+     * @param visible whether or not visible.
+     */
+    protected void doArea(
+        Object     o,
+        ArtifactAndFacet aandf,
+        Document   theme,
+        boolean    visible
+    ) {
+        logger.debug("FlowVelocityGenerator.doArea");
+        logger.warn("TODO: Implement FlowVelocityGenerator.doArea");
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/FlowVelocityInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific
+ * flow velocity curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class FlowVelocityInfoGenerator
+extends      ChartInfoGenerator
+{
+    public FlowVelocityInfoGenerator() {
+        super(new FlowVelocityGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,242 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Locale;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+import net.sf.jasperreports.engine.JRException;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.Timerange;
+import de.intevation.flys.artifacts.model.WQTimerange;
+import de.intevation.flys.artifacts.model.WQTJRDataSource;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeCurveExporter extends AbstractExporter {
+
+    private static final Logger logger =
+        Logger.getLogger(HistoricalDischargeCurveExporter.class);
+
+
+    public static final String CSV_TIMERANGE_HEADER =
+        "export.historical.discharge.csv.header.timerange";
+
+    public static final String CSV_WATERLEVEL_HEADER =
+        "export.historical.discharge.csv.header.waterlevel";
+
+    public static final String CSV_DISCHARGE_HEADER =
+        "export.historical.discharge.csv.header.discharge";
+
+    public static final String CSV_DIFF_HEADER =
+        "export.historical.discharge.csv.header.diff";
+
+    public static final String CSV_GAUGENAME_HEADER =
+        "export.historical.discharge.csv.header.gaugename";
+
+    public static final String PDF_HEADER_MODE =
+        "export.historical.discharge.pdf.mode";
+
+    public static final String JASPER_FILE =
+        "export.historical.discharge.pdf.file";
+
+    protected List<WQTimerange[]> data;
+
+
+    public void init(Document request, OutputStream out, CallContext cc) {
+        super.init(request, out, cc);
+
+        data = new ArrayList<WQTimerange[]>();
+    }
+
+
+    @Override
+    protected void addData(Object d) {
+        logger.debug("Add data of class: " + d.getClass());
+
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult) d).getData();
+
+            logger.debug("Internal data of CalculationResult: " + d.getClass());
+
+            if (d instanceof WQTimerange[]) {
+                logger.debug("Add new data of type WQTimerange");
+                data.add((WQTimerange[]) d);
+            }
+        }
+    }
+
+
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("HistoricalDischargeCurveExporter.writeCSVData");
+        logger.debug("CSV gets " + data.size() + " WQTimerange[] objects.");
+
+        writeCSVHeader(writer);
+
+        for (WQTimerange[] arr: data) {
+            for (WQTimerange wqt: arr) {
+                wqt2CSV(writer, wqt);
+            }
+        }
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        WQTJRDataSource source = createJRData();
+
+        String jasperFile = Resources.getMsg(
+                                context.getMeta(),
+                                JASPER_FILE,
+                                "/jasper/historical-discharge_en.jasper");
+        String confPath = Config.getConfigDirectory().toString();
+
+        Map parameters = new HashMap();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            JasperPrint print = JasperFillManager.fillReport(
+                confPath + jasperFile,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, out);
+        }
+        catch(JRException je) {
+            logger.warn("Error generating PDF Report!");
+            je.printStackTrace();
+        }
+
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        writer.writeNext(new String[] {
+            msg(CSV_TIMERANGE_HEADER, CSV_TIMERANGE_HEADER),
+            msg(CSV_WATERLEVEL_HEADER, CSV_WATERLEVEL_HEADER),
+            msg(CSV_DISCHARGE_HEADER, CSV_DISCHARGE_HEADER),
+            msg(CSV_DIFF_HEADER, CSV_DIFF_HEADER),
+            msg(CSV_GAUGENAME_HEADER, CSV_GAUGENAME_HEADER)
+        });
+    }
+
+
+    protected void wqt2CSV(CSVWriter writer, WQTimerange wqt) {
+        logger.debug("Add next WQTimerange to CSV");
+
+        DateFormat   df = Formatter.getMediumDateFormat(context);
+        NumberFormat wf = Formatter.getHistoricalDischargeW(context);
+        NumberFormat qf = Formatter.getHistoricalDischargeQ(context);
+
+        double[] wq = new double[3];
+
+        String gaugeName = getReferenceGaugename();
+
+        for (int i = 0, n = wqt.size(); i < n; i++) {
+            Timerange tr = wqt.getTimerange(i);
+            Date   start = new Date(tr.getStart());
+            Date     end = new Date(tr.getEnd());
+
+            wqt.get(i, wq);
+
+            writer.writeNext(new String[] {
+                df.format(start) + " - " + df.format(end),
+                wf.format(wq[0]),
+                qf.format(wq[1]),
+                qf.format(wq[2]),
+                gaugeName
+            });
+        }
+    }
+
+
+    protected WQTJRDataSource createJRData() {
+        WQTJRDataSource source = new WQTJRDataSource();
+
+        addMetaData(source);
+        for (WQTimerange[] arr: data) {
+            for (WQTimerange wqt: arr) {
+                addWQTData(source, wqt);
+            }
+        }
+
+        return source;
+    }
+
+
+    protected void addMetaData(WQTJRDataSource source) {
+        CallMeta meta = context.getMeta();
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        source.addMetaData ("river", FLYSUtils.getRivername(flys));
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        source.addMetaData("date", df.format(new Date()));
+
+        source.addMetaData("calculation", Resources.getMsg(
+                                            locale,
+                                            PDF_HEADER_MODE,
+                                            "Historical Discharge"));
+    }
+
+
+    protected void addWQTData(WQTJRDataSource source, WQTimerange wqt) {
+        DateFormat   df = Formatter.getShortDateFormat(context);
+        NumberFormat wf = Formatter.getHistoricalDischargeW(context);
+        NumberFormat qf = Formatter.getHistoricalDischargeQ(context);
+
+        double[] wq = new double[3];
+
+        String gaugeName = getReferenceGaugename();
+
+        for (int i = 0, n = wqt.size(); i < n; i++) {
+            Timerange tr = wqt.getTimerange(i);
+            Date   start = new Date(tr.getStart());
+            Date     end = new Date(tr.getEnd());
+
+            wqt.get(i, wq);
+
+            source.addData(new String[] {
+                df.format(start) + " - " + df.format(end),
+                wf.format(wq[0]),
+                qf.format(wq[1]),
+                qf.format(wq[2]),
+                gaugeName
+            });
+        }
+    }
+
+
+    public String getReferenceGaugename() {
+        return FLYSUtils.getReferenceGaugeName((FLYSArtifact) master);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,274 @@
+package de.intevation.flys.exports;
+
+import java.util.Date;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.title.TextTitle;
+
+import org.jfree.data.general.SeriesException;
+import org.jfree.data.time.Day;
+import org.jfree.data.time.RegularTimePeriod;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.HistoricalWQTimerange;
+import de.intevation.flys.artifacts.model.Timerange;
+import de.intevation.flys.artifacts.model.WQTimerange;
+import de.intevation.flys.jfree.StyledTimeSeries;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeCurveGenerator
+extends      TimeseriesChartGenerator
+implements   FacetTypes
+{
+    private static Logger logger =
+        Logger.getLogger(HistoricalDischargeCurveGenerator.class);
+
+
+    public static final String I18N_CHART_TITLE =
+        "chart.historical.discharge.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.historical.discharge.subtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.historical.discharge.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.historical.discharge.yaxis.label";
+
+    public static final String I18N_YAXIS_SECOND_LABEL =
+        "chart.historical.discharge.yaxis.second.label";
+
+
+    public static enum YAXIS {
+        Q(0);
+        protected int idx;
+        private YAXIS(int c) {
+            idx = c;
+        }
+    }
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+
+    @Override
+    protected String getDefaultChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE);
+    }
+
+
+    @Override
+    protected String getDefaultChartSubtitle() {
+        String[] args = new String[] {
+            FLYSUtils.getReferenceGaugeName((FLYSArtifact) master)
+        };
+
+        return msg(I18N_CHART_SUBTITLE, "", args);
+    }
+
+
+    /**
+     * Empty (suppress subtitle).
+     */
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+
+        if (subtitle != null && subtitle.length() > 0) {
+            chart.addSubtitle(new TextTitle(subtitle));
+        }
+    }
+
+
+    @Override
+    protected String getDefaultXAxisLabel() {
+        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL);
+    }
+
+    @Override
+    protected String getDefaultYAxisLabel(int pos) {
+        if (pos == 0) {
+            return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL);
+        }
+        else if (pos == 1) {
+            return msg(I18N_YAXIS_SECOND_LABEL, I18N_YAXIS_SECOND_LABEL);
+        }
+        else {
+            return "NO TITLE FOR Y AXIS: " + pos;
+        }
+    }
+
+
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         theme,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+        logger.debug("HistoricalDischargeCurveGenerator.doOut: " + name);
+        logger.debug("Theme description is: " + artifactFacet.getFacetDescription());
+
+
+        if (name.equals(HISTORICAL_DISCHARGE_Q)) {
+            doHistoricalDischargeOut(
+                (FLYSArtifact) artifactFacet.getArtifact(),
+                artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                theme,
+                visible);
+        }
+        else if (name.equals(HISTORICAL_DISCHARGE_Q_DIFF)) {
+            doHistoricalDischargeDifferenceOut(
+                (FLYSArtifact) artifactFacet.getArtifact(),
+                artifactFacet.getData(context),
+                artifactFacet.getFacetDescription(),
+                theme,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints (artifactFacet.getData(context),
+                      artifactFacet,
+                      theme, visible, YAXIS.Q.idx);
+        }
+        // TODO ADD THE CASE FOR DISPLAYING W VALUES
+        else {
+           logger.warn("doOut(): unknown facet name: " + name);
+           return;
+        }
+    }
+
+
+    protected void doHistoricalDischargeOut(
+        FLYSArtifact artifact,
+        Object       data,
+        String       desc,
+        Document     theme,
+        boolean      visible)
+    {
+        logger.debug("doHistoricalDischargeOut(): description = " + desc);
+
+        WQTimerange wqt = (WQTimerange) data;
+
+        TimeSeriesCollection tsc = newTimeSeriesCollection(
+            wqt.getTimeranges(),
+            wqt.getQs(),
+            theme,
+            desc);
+
+        addAxisDataset(tsc, 0, visible);
+    }
+
+
+    protected void doHistoricalDischargeDifferenceOut(
+        FLYSArtifact artifact,
+        Object       data,
+        String       desc,
+        Document     theme,
+        boolean      visible
+    ) {
+        logger.debug("doHistoricalDischargeDifferenceOut: desc = " + desc);
+
+        HistoricalWQTimerange wqt = (HistoricalWQTimerange) data;
+
+        TimeSeriesCollection tsc = newTimeSeriesCollection(
+            wqt.getTimeranges(),
+            wqt.getDiffs(),
+            theme,
+            desc);
+
+        addAxisDataset(tsc, 0, visible);
+    }
+
+
+    /**
+     * Creates a new TimeSeriesCollection with a single TimeSeries. The
+     * TimeSeries will consist of two RegularTimePeriods for each W/Q value
+     * provided by <i>wqt</i>. This has the effect, that the line in the chart
+     * looks like a "step chart".
+     */
+    protected TimeSeriesCollection newTimeSeriesCollection(
+        Timerange[] timeranges,
+        double[]    values,
+        Document    theme,
+        String      desc
+    ) {
+        logger.debug("Create new TimeSeriesCollection for: " + desc);
+
+        TimeSeriesCollection tsc = new TimeSeriesCollection();
+        TimeSeries        series = new StyledTimeSeries(desc, theme);
+
+        for (int i = 0, n = timeranges.length; i < n; i++) {
+            RegularTimePeriod[] rtp = newRegularTimePeriod(timeranges[i]);
+
+            try {
+                if (Double.isNaN(values[i])) {
+                    logger.warn("Skip TimePeriod because value is NaN.");
+                    continue;
+                }
+
+                series.add(rtp[0], values[i]);
+                series.add(rtp[1], values[i]);
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("added Item to TimeSeries:");
+                    logger.debug("   TimePeriod: " + rtp[0] + " - " + rtp[1]);
+                    logger.debug("   Value:      " + values[i]);
+                }
+            }
+            catch (SeriesException se) {
+                logger.warn("Error while adding TimePeriod: " + se);
+            }
+        }
+
+        tsc.addSeries(series);
+
+        return tsc;
+    }
+
+
+    /**
+     * Creates an array that consists of two <i>Minute</i> periods [start, end].
+     *
+     * @param timerange Supports start and end time.
+     *
+     * @return an array with two <i>Minute</i> periods [start, end].
+     */
+    protected RegularTimePeriod[] newRegularTimePeriod(Timerange timerange) {
+        Date start = new Date(timerange.getStart());
+        Date end   = new Date(timerange.getEnd() - 1000 * 60 * 60 * 24);
+
+        return new RegularTimePeriod[] {
+            new Day(start),
+            new Day(end)
+        };
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/exports/HistoricalDischargeCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific
+ * historical discharge curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class HistoricalDischargeCurveInfoGenerator
+extends      ChartInfoGenerator
+{
+    public HistoricalDischargeCurveInfoGenerator() {
+        super(new HistoricalDischargeCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/IdentifiableNumberAxis.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,22 @@
+package de.intevation.flys.exports;
+
+import org.jfree.chart.axis.NumberAxis;
+
+
+public class IdentifiableNumberAxis extends NumberAxis {
+
+
+    protected String key;
+
+
+    protected IdentifiableNumberAxis(String key, String label) {
+        super(label);
+        this.key = key;
+    }
+
+
+    public String getId() {
+        return key;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,405 @@
+package de.intevation.flys.exports;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+
+import java.util.Date;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.ChartRenderingInfo;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.DateAxis;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.Range;
+import org.jfree.data.xy.XYDataset;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.flys.jfree.Bounds;
+
+
+/**
+ * This class helps generating chart info documents.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class InfoGeneratorHelper {
+
+    private static final Logger logger =
+        Logger.getLogger(InfoGeneratorHelper.class);
+
+
+    protected ChartGenerator generator;
+
+
+    public InfoGeneratorHelper(ChartGenerator generator) {
+        this.generator = generator;
+    }
+
+
+    /**
+     * Triggers the creation of the chart info document.
+     *
+     * @param chart The JFreeChart chart.
+     * @param info An info object that has been created while chart creation.
+     *
+     * @return the info document.
+     */
+    public Document createInfoDocument(
+        JFreeChart         chart,
+        ChartRenderingInfo info)
+    {
+        logger.debug("InfoGeneratorHelper.createInfoDocument");
+
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(
+            doc,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element chartinfo = cr.create("chartinfo");
+
+        chartinfo.appendChild(createAxesElements(cr, chart));
+        chartinfo.appendChild(createTransformationElements(cr, chart, info));
+
+        doc.appendChild(chartinfo);
+
+        return doc;
+    }
+
+
+    /**
+     * This method create a axes element that contains all domain and range
+     * axes of the given chart.
+     *
+     * @param cr The ElementCreator.
+     * @param chart The chart that provides range information of its axes.
+     *
+     * @return an element with axes information.
+     */
+    protected Element createAxesElements(
+        ElementCreator     cr,
+        JFreeChart         chart)
+    {
+        logger.debug("InfoGeneratorHelper.createRangeElements");
+
+        Element axes = cr.create("axes");
+
+        XYPlot plot = (XYPlot) chart.getPlot();
+
+        int dAxisCount = plot.getDomainAxisCount();
+        for (int i = 0; i < dAxisCount; i++) {
+            ValueAxis axis = plot.getDomainAxis(i);
+            XYDataset data = plot.getDataset(i);
+
+            if (axis != null) {
+                Element e = createAxisElement(cr, axis, data, "domain", i);
+                axes.appendChild(e);
+            }
+        }
+
+        int rAxisCount = plot.getRangeAxisCount();
+        for (int i = 0; i < rAxisCount; i++) {
+            ValueAxis axis = plot.getRangeAxis(i);
+            XYDataset data = plot.getDataset(i);
+
+            if (axis == null || data == null) {
+                logger.warn("Axis or dataset is empty at pos: " + i);
+                continue;
+            }
+
+            Element e = createAxisElement(cr, axis, data, "range", i);
+            axes.appendChild(e);
+        }
+
+        return axes;
+    }
+
+
+    /**
+     * This method create a axis element for a given <i>axis</i> and
+     * <i>type</i>. Type can be one of 'domain' or 'range'.
+     *
+     * @param cr The ElementCreator
+     * @param axis The axis that provides range information.
+     * @param dataset The dataset for min/max determination.
+     * @param type The axis type ('domain' or 'range').
+     * @param pos The position in the chart.
+     *
+     * @return An element that contains range information of a given axis.
+     */
+    protected Element createAxisElement(
+        ElementCreator cr,
+        ValueAxis      axis,
+        XYDataset      dataset,
+        String         type,
+        int            pos)
+    {
+        logger.debug("createAxisElement " + pos);
+        logger.debug("Axis is from type: " + axis.getClass());
+
+        Element e = cr.create(type);
+        cr.addAttr(e, "pos",  String.valueOf(pos), true);
+
+        if (axis instanceof DateAxis) {
+            prepareDateAxisElement(
+                e, cr, (DateAxis) axis, dataset, type, pos);
+        }
+        else {
+            prepareNumberAxisElement(
+                e, cr, (NumberAxis) axis, dataset, type, pos);
+        }
+
+        return e;
+    }
+
+
+    protected Element prepareNumberAxisElement(
+        Element        e,
+        ElementCreator cr,
+        NumberAxis     axis,
+        XYDataset      dataset,
+        String         type,
+        int            pos
+    ) {
+        Range range = axis.getRange();
+
+        cr.addAttr(e, "from", String.valueOf(range.getLowerBound()), true);
+        cr.addAttr(e, "to",   String.valueOf(range.getUpperBound()), true);
+        cr.addAttr(e, "axistype", "number", true);
+
+        Range[] rs = generator.getRangesForAxis(pos);
+        Range   r  = null;
+
+        if (type.equals("range")) {
+            r = rs[1];
+        }
+        else {
+            r = rs[0];
+        }
+
+        cr.addAttr(e, "min", String.valueOf(r.getLowerBound()), true);
+        cr.addAttr(e, "max", String.valueOf(r.getUpperBound()), true);
+
+        return e;
+    }
+
+
+    protected Element prepareDateAxisElement(
+        Element        e,
+        ElementCreator cr,
+        DateAxis       axis,
+        XYDataset      dataset,
+        String         type,
+        int            pos
+    ) {
+        Date from = axis.getMinimumDate();
+        Date to   = axis.getMaximumDate();
+
+        Bounds bounds = null;
+        if (type.equals("range")) {
+            bounds = generator.getYBounds(pos);
+        }
+        else {
+            bounds = generator.getXBounds(pos);
+        }
+
+        cr.addAttr(e, "axistype", "date", true);
+        cr.addAttr(e, "from", String.valueOf(from.getTime()), true);
+        cr.addAttr(e, "to", String.valueOf(to.getTime()), true);
+
+        cr.addAttr(e, "min", bounds.getLower().toString(), true);
+        cr.addAttr(e, "max", bounds.getUpper().toString(), true);
+
+        return e;
+    }
+
+
+    /**
+     * This method appends the values of a transformation matrix to transform
+     * image pixel coordinates into chart coordinates.
+     *
+     * @param cr The ElementCreator.
+     * @param chart The chart object.
+     * @param info The ChartRenderingInfo that is filled while chart creation.
+     *
+     * @return an element that contains one or more transformation matrix.
+     */
+    protected Element createTransformationElements(
+        ElementCreator     cr,
+        JFreeChart         chart,
+        ChartRenderingInfo info)
+    {
+        logger.debug("InfoGeneratorHelper.createTransformationElements");
+
+        Element tf = cr.create("transformation-matrix");
+
+        Rectangle2D dataArea = info.getPlotInfo().getDataArea();
+
+        XYPlot    plot  = (XYPlot) chart.getPlot();
+        ValueAxis xAxis = plot.getDomainAxis();
+
+        if (xAxis == null) {
+            logger.error("There is no x axis in the chart!");
+            return null;
+        }
+
+        for (int i  = 0, num = plot.getRangeAxisCount(); i < num; i++) {
+            ValueAxis yAxis = plot.getRangeAxis(i);
+
+            if (yAxis == null) {
+                logger.warn("No y axis at pos " + i + " existing.");
+                continue;
+            }
+
+            Element matrix = createTransformationElement(
+                cr, xAxis, yAxis, dataArea, i);
+
+            tf.appendChild(matrix);
+        }
+
+        return tf;
+    }
+
+
+    /**
+     * Creates an element that contains values used to transform coordinates
+     * of a coordinate system A into a coordinate system B.
+     *
+     * @param cr The ElementCreator.
+     * @param xAxis The x axis of the target coordinate system.
+     * @param yAxis The y axis of the target coordinate system.
+     * @param dataArea The pixel coordinates of the chart image.
+     * @param pos The dataset position.
+     *
+     * @return an element that contains transformation matrix values.
+     */
+    protected Element createTransformationElement(
+        ElementCreator cr,
+        ValueAxis      xAxis,
+        ValueAxis      yAxis,
+        Rectangle2D    dataArea,
+        int            pos)
+    {
+        double[] tm = createTransformationMatrix(dataArea, xAxis, yAxis);
+
+        Element matrix = cr.create("matrix");
+
+        cr.addAttr(matrix, "pos", String.valueOf(pos), true);
+        cr.addAttr(matrix, "sx", String.valueOf(tm[0]), true);
+        cr.addAttr(matrix, "sy", String.valueOf(tm[1]), true);
+        cr.addAttr(matrix, "tx", String.valueOf(tm[2]), true);
+        cr.addAttr(matrix, "ty", String.valueOf(tm[3]), true);
+
+        if (xAxis instanceof DateAxis) {
+            cr.addAttr(matrix, "xtype", "date", true);
+        }
+        else {
+            cr.addAttr(matrix, "xtype", "number", true);
+        }
+
+        if (yAxis instanceof DateAxis) {
+            cr.addAttr(matrix, "ytype", "date", true);
+        }
+        else {
+            cr.addAttr(matrix, "ytype", "number", true);
+        }
+
+        return matrix;
+    }
+
+
+    /**
+     * This method determines a transformation matrix to transform pixel
+     * coordinates of the chart image into chart coordinates.
+     *
+     * @param dataArea The rectangle that contains the data points of the chart.
+     * @param xRange The x axis range.
+     * @param yRange The y axis range.
+     *
+     * @return a double array as follows: [sx, sy, tx, ty].
+     */
+    protected static double[] createTransformationMatrix(
+        Rectangle2D dataArea,
+        ValueAxis   xAxis,
+        ValueAxis   yAxis)
+    {
+        logger.debug("InfoGeneratorHelper.createTransformationMatrix");
+
+        double offsetX = dataArea.getX();
+        double width   = dataArea.getWidth();
+        double offsetY = dataArea.getY();
+        double height  = dataArea.getHeight();
+
+        Range xRange = getRangeFromAxis(xAxis);
+        Range yRange = getRangeFromAxis(yAxis);
+
+        double lowerX  = xRange.getLowerBound();
+        double upperX  = xRange.getUpperBound();
+        double lowerY  = yRange.getLowerBound();
+        double upperY  = yRange.getUpperBound();
+
+        if (xAxis.isInverted()) {
+            logger.info("X-Axis is inverted!");
+
+            double tmp = upperX;
+            upperX = lowerX;
+            lowerX = tmp;
+        }
+
+        double dMoveX = upperX - lowerX;
+        double fMoveX = width * lowerX;
+        double dMoveY = lowerY - upperY;
+        double fMoveY = height * upperY;
+
+        AffineTransform t1 = AffineTransform.getTranslateInstance(
+                offsetX - ( fMoveX / dMoveX ),
+                offsetY - ( fMoveY / dMoveY ) );
+
+        AffineTransform t2 = AffineTransform.getScaleInstance(
+                width / (upperX - lowerX),
+                height / (lowerY - upperY));
+
+        t1.concatenate(t2);
+
+        try {
+            t1.invert();
+
+            double[] c = new double[6];
+            t1.getMatrix(c);
+
+            return new double[] { c[0], c[3], c[4], c[5] };
+        }
+        catch (NoninvertibleTransformException e) {
+            // do nothing
+            logger.warn("Matrix is not invertible.");
+        }
+
+        return new double[] { 1d, 1d, 0d, 0d };
+    }
+
+
+    protected static Range getRangeFromAxis(ValueAxis axis) {
+        if  (axis instanceof DateAxis) {
+            DateAxis dAxis = (DateAxis) axis;
+            Date     min   = dAxis.getMinimumDate();
+            Date     max   = dAxis.getMaximumDate();
+
+            return new Range(min.getTime(), max.getTime());
+        }
+        else {
+            return axis.getRange();
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/IntegerAttribute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.exports;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class IntegerAttribute extends VisibleAttribute {
+
+
+    public IntegerAttribute(String name, int value, boolean visible) {
+        super(name, value, visible);
+    }
+
+
+    /**
+     * Calls VisibleAttribute.toXML() and appends afterwards an attribute
+     * <i>type</i> with value <i>integer</i>.
+     *
+     * @param parent The parent Node.
+     *
+     * @return the new Node that represents this Attribute.
+     */
+    @Override
+    public Node toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+
+        Element ele = (Element) super.toXML(parent);
+        ele.setAttribute("type", "integer");
+
+        return ele;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/LegendSection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,42 @@
+package de.intevation.flys.exports;
+
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LegendSection extends TypeSection {
+
+    public static final String VISIBILITY_ATTR = "visibility";
+    public static final String FONTSIZE_ATTR   = "font-size";
+
+
+    public LegendSection() {
+        super("legend");
+    }
+
+
+    public void setFontSize(int fontSize) {
+        if (fontSize <= 0) {
+            return;
+        }
+
+        setIntegerValue(FONTSIZE_ATTR, fontSize);
+    }
+
+
+    public Integer getFontSize() {
+        return getIntegerValue(FONTSIZE_ATTR);
+    }
+
+
+    public void setVisibility(boolean visibility) {
+        setBooleanValue(VISIBILITY_ATTR, visibility);
+    }
+
+
+    public Boolean getVisibility() {
+        return getBooleanValue(VISIBILITY_ATTR);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,665 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.xy.XYSeries;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.AreaFacet;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StyledAreaSeriesCollection;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.DataUtil;
+
+import de.intevation.flys.geom.Lines;
+
+
+/**
+ * An OutGenerator that generates longitudinal section curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LongitudinalSectionGenerator
+extends      XYChartGenerator
+implements   FacetTypes
+{
+    public enum YAXIS {
+        W(0),
+        Q(1),
+        D(2);
+        protected int idx;
+        private YAXIS(int c) {
+           idx = c;
+        }
+    }
+
+    /** The logger that is used in this generator. */
+    private static Logger logger =
+        Logger.getLogger(LongitudinalSectionGenerator.class);
+
+    /** Key to look up internationalized String for annotations label. */
+    public static final String I18N_ANNOTATIONS_LABEL =
+        "chart.longitudinal.annotations.label";
+
+    /**
+     * Key to look up internationalized String for LongitudinalSection diagrams
+     * titles.
+     */
+    public static final String I18N_CHART_TITLE =
+        "chart.longitudinal.section.title";
+
+    /**
+     * Key to look up internationalized String for LongitudinalSection diagrams
+     * subtitles.
+     */
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.longitudinal.section.subtitle";
+
+    /**
+     * Key to look up internationalized String for LongitudinalSection diagrams
+     * short subtitles.
+     */
+    public static final String I18N_CHART_SHORT_SUBTITLE =
+        "chart.longitudinal.section.shortsubtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.longitudinal.section.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.longitudinal.section.yaxis.label";
+
+    public static final String I18N_2YAXIS_LABEL =
+        "chart.longitudinal.section.yaxis.second.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  = "W-L\u00e4ngsschnitt";
+    public static final String I18N_XAXIS_LABEL_DEFAULT  = "km";
+    public static final String I18N_YAXIS_LABEL_DEFAULT  = "W [NN + m]";
+    public static final String I18N_2YAXIS_LABEL_DEFAULT = "Q [m\u00b3/s]";
+
+    public final static String I18N_WDIFF_YAXIS_LABEL =
+        "chart.w_differences.yaxis.label";
+
+    public final static String I18N_WDIFF_YAXIS_LABEL_DEFAULT = "m";
+
+    /** Whether or not the plot is inverted (left-right). */
+    protected boolean inverted;
+
+
+    public LongitudinalSectionGenerator() {
+        super();
+    }
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+
+    public boolean isInverted() {
+        return inverted;
+    }
+
+
+    public void setInverted(boolean inverted) {
+        this.inverted = inverted;
+    }
+
+
+    /**
+     * Returns the default title for this chart.
+     *
+     * @return the default title for this chart.
+     */
+    @Override
+    public String getDefaultChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+
+    /**
+     * Returns the default subtitle for this chart.
+     *
+     * @return the default subtitle for this chart.
+     */
+    @Override
+    protected String getDefaultChartSubtitle() {
+        double[] dist = getRange();
+
+        Object[] args = null;
+        if (dist == null) {
+            args = new Object[] {getRiverName()};
+            return msg(getChartShortSubtitleKey(), "", args);
+        }
+        args = new Object[] {
+            getRiverName(),
+            dist[0],
+            dist[1]
+        };
+        return msg(getChartSubtitleKey(), "", args);
+    }
+
+
+    /**
+     * Gets key to look up internationalized String for the charts subtitle.
+     * @return key to look up translated subtitle.
+     */
+    protected String getChartSubtitleKey() {
+        return I18N_CHART_SUBTITLE;
+    }
+
+
+    /**
+     * Gets key to look up internationalized String for the charts short 
+     * subtitle.
+     * @return key to look up translated subtitle.
+     */
+    protected String getChartShortSubtitleKey() {
+        return I18N_CHART_SHORT_SUBTITLE;
+    }
+
+
+    /**
+     * Add (internationalized) subtitle to chart.
+     * @see getChartSubtitleKey
+     */
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+
+        if (subtitle != null && subtitle.length() > 0) {
+            chart.addSubtitle(new TextTitle(subtitle));
+        }
+    }
+
+
+    /**
+     * Get internationalized label for the x axis.
+     */
+    @Override
+    protected String getDefaultXAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return msg(
+            I18N_XAXIS_LABEL,
+            I18N_XAXIS_LABEL_DEFAULT,
+            new Object[] { FLYSUtils.getRiver(flys).getName() });
+    }
+
+
+    @Override
+    protected String getDefaultYAxisLabel(int index) {
+        String label = "default";
+
+        if (index == YAXIS.W.idx) {
+            label = getWAxisLabel();
+        }
+        else if (index == YAXIS.Q.idx) {
+            label = msg(getQAxisLabelKey(), getQAxisDefaultLabel());
+        }
+        else if (index == YAXIS.D.idx) {
+            label = msg(I18N_WDIFF_YAXIS_LABEL, I18N_WDIFF_YAXIS_LABEL_DEFAULT);
+        }
+
+        return label;
+    }
+
+
+    /**
+     * Get internationalized label for the y axis.
+     */
+    protected String getWAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        String unit = FLYSUtils.getRiver(flys).getWstUnit().getName();
+
+        return msg(
+            I18N_YAXIS_LABEL,
+            I18N_YAXIS_LABEL_DEFAULT,
+            new Object[] { unit });
+    }
+
+
+    /**
+     * Create Axis for given index.
+     * @return axis with according internationalized label.
+     */
+    @Override
+    protected NumberAxis createYAxis(int index) {
+        NumberAxis axis = super.createYAxis(index);
+
+        // "Q" Axis shall include 0.
+        if (index == YAXIS.Q.idx) {
+            axis.setAutoRangeIncludesZero(true);
+        }
+        else {
+            axis.setAutoRangeIncludesZero(false);
+        }
+
+        return axis;
+    }
+
+
+    /**
+     * Get default value for the second Y-Axis' label (if no translation was
+     * found).
+     */
+    protected String getQAxisDefaultLabel() {
+        return I18N_2YAXIS_LABEL_DEFAULT;
+    }
+
+
+    /**
+     * Get key for internationalization of the second Y-Axis' label.
+     */
+    protected String getQAxisLabelKey() {
+        return I18N_2YAXIS_LABEL;
+    }
+
+
+    /**
+     * Trigger inversion.
+     */
+    @Override
+    protected void adjustAxes(XYPlot plot) {
+        super.adjustAxes(plot);
+        invertXAxis(plot.getDomainAxis());
+    }
+
+
+    /**
+     * This method inverts the x-axis based on the kilometer information of the
+     * selected river. If the head of the river is at kilometer 0, the axis is
+     * not inverted, otherwise it is.
+     *
+     * @param xaxis The domain axis.
+     */
+    protected void invertXAxis(ValueAxis xaxis) {
+        if (inverted) {
+            logger.debug("X-Axis.setInverted(true)");
+            xaxis.setInverted(true);
+        }
+    }
+
+
+    /**
+     * Produce output.
+     * @param facet current facet.
+     * @param attr  theme for facet
+     */
+    public void doOut(
+        ArtifactAndFacet artifactAndFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactAndFacet.getFacetName();
+
+        logger.debug("LongitudinalSectionGenerator.doOut: " + name);
+
+        if (name == null) {
+            logger.error("No facet name for doOut(). No output generated!");
+            return;
+        }
+
+        Facet facet = artifactAndFacet.getFacet();
+
+        if (facet == null) {
+            return;
+        }
+
+        if (name.equals(LONGITUDINAL_W)) {
+            doWOut(
+                (WQKms) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(LONGITUDINAL_Q)) {
+            doQOut(
+                (WQKms) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(LONGITUDINAL_ANNOTATION)) {
+            doAnnotations(
+                (FLYSAnnotation) artifactAndFacet.getData(context),
+                 artifactAndFacet,
+                 attr,
+                 visible);
+        }
+        else if (name.equals(STATIC_WKMS)
+                || name.equals(HEIGHTMARKS_POINTS)
+                || name.equals(STATIC_WQKMS)) {
+            doWOut(
+                (WKms) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(STATIC_WQKMS_W)) {
+            doWOut(
+                (WQKms) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(STATIC_WQKMS_Q)) {
+            doQOut(
+                (WQKms) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(W_DIFFERENCES)) {
+            doWDifferencesOut(
+                (WKms) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.AREA(name)) {
+            doArea(
+                artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(
+                artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible,
+                YAXIS.W.idx);
+        }
+        else {
+            logger.warn("Unknown facet name: " + name);
+            return;
+        }
+    }
+
+
+    /**
+     * Process the output for W facets in a longitudinal section curve.
+     *
+     * @param wqkms An array of WQKms values.
+     * @param facet The facet. This facet does NOT support any data objects. Use
+     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
+     * data.
+     * @param theme The theme that contains styling information.
+     * @param visible The visibility of the curve.
+     */
+    protected void doWOut(
+        WKms             wkms,
+        ArtifactAndFacet aandf,
+        Document         theme,
+        boolean          visible
+    ) {
+        logger.debug("LongitudinalSectionGenerator.doWOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        StyledSeriesBuilder.addPoints(series, wkms);
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+
+        if (needInvertAxis(wkms)) {
+            setInverted(true);
+        }
+    }
+
+
+    /**
+     * Add items to dataseries which describes the differences.
+     */
+    protected void doWDifferencesOut(
+        WKms       wkms,
+        ArtifactAndFacet aandf,
+        Document   theme,
+        boolean    visible
+    ) {
+        logger.debug("WDifferencesCurveGenerator.doWDifferencesOut");
+        if (wkms == null) {
+            logger.warn("No data to add to WDifferencesChart.");
+            return;
+         }
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        if (logger.isDebugEnabled()) {
+            if (wkms.size() > 0) {
+                logger.debug("Generate series: " + series.getKey());
+                logger.debug("Start km: " + wkms.getKm(0));
+                logger.debug("End   km: " + wkms.getKm(wkms.size() - 1));
+                logger.debug("Values  : " + wkms.size());
+            }
+        }
+
+        StyledSeriesBuilder.addPoints(series, wkms);
+
+        addAxisSeries(series, YAXIS.D.idx, visible);
+        if (DataUtil.guessWaterIncreasing(wkms.allWs())) {
+            setInverted(true);
+        }
+    }
+
+
+
+    /**
+     * Process the output for Q facets in a longitudinal section curve.
+     *
+     * @param wqkms An array of WQKms values.
+     * @param facet The facet. This facet does NOT support any data objects. Use
+     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
+     * data.
+     * @param theme The theme that contains styling information.
+     * @param visible The visibility of the curve.
+     */
+    protected void doQOut(
+        WQKms    wqkms,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean  visible
+    ) {
+        logger.debug("LongitudinalSectionGenerator.doQOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        StyledSeriesBuilder.addPointsKmQ(series, wqkms);
+
+        addAxisSeries(series, YAXIS.Q.idx, visible);
+
+        if (needInvertAxis(wqkms)) {
+            setInverted(true);
+        }
+    }
+
+    /**
+     * This method determines - taking JFreeCharts auto x value ordering into
+     * account - if the x axis need to be inverted. Waterlines in these charts
+     * should decrease.
+     *
+     * @param wqkms The data object that stores the x and y values used for this
+     * chart.
+     */
+    public boolean needInvertAxis(WKms wkms) {
+        boolean wsUp = wkms.guessWaterIncreasing();
+        boolean kmUp = DataUtil.guessWaterIncreasing(wkms.allKms());
+        boolean inv = (wsUp && kmUp) || (!wsUp && !kmUp);
+
+        int size = wkms.size();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("(Wkms)Values  : " + size);
+            if (size > 0) {
+                logger.debug("Start km: " + wkms.getKm(0));
+                logger.debug("End   km: " + wkms.getKm(size-1));
+            }
+            logger.debug("wsUp: " + wsUp);
+            logger.debug("kmUp: " + kmUp);
+            logger.debug("inv:  " + inv);
+        }
+
+        return inv;
+    }
+
+
+    /**
+     * Get name of series (displayed in legend).
+     * @return name of the series.
+     */
+    protected String getSeriesName(WQKms wqkms, String mode) {
+        String name   = wqkms.getName();
+        String prefix = name != null && name.indexOf(mode) >= 0 ? null : mode;
+
+        return prefix != null && prefix.length() > 0
+            ? prefix + "(" + name +")"
+            : name;
+    }
+
+
+    /** Look up the axis identifier for a given facet type. */
+    public int axisIdxForFacet(String facetName) {
+        if (FacetTypes.IS.W(facetName)) {
+            return YAXIS.W.idx;
+        }
+        else if (FacetTypes.IS.Q(facetName)) {
+            return YAXIS.Q.idx;
+        }
+        else {
+            logger.warn("Could not find axis for facet " + facetName);
+            return YAXIS.W.idx;
+        }
+    }
+
+
+    /**
+     * Do Area out.
+     * @param theme styling information.
+     * @param visible whether or not visible.
+     */
+    protected void doArea(
+        Object     o,
+        ArtifactAndFacet aandf,
+        Document   theme,
+        boolean    visible
+    ) {
+        logger.debug("LongitudinalSectionGenerator.doArea");
+        StyledAreaSeriesCollection area = new StyledAreaSeriesCollection(theme);
+
+        String seriesName = aandf.getFacetDescription();
+
+        AreaFacet.Data data = (AreaFacet.Data) o;
+
+        XYSeries up   = null;
+        XYSeries down = null;
+
+        if (data.getUpperData() != null) {
+            up = new StyledXYSeries(seriesName, false, theme);
+            if (data.getUpperData() instanceof WQKms) {
+                if (FacetTypes.IS.Q(data.getRootFacetName())) {
+                    StyledSeriesBuilder.addPointsKmQ(up, (WQKms) data.getUpperData());
+                }
+                else {
+                    StyledSeriesBuilder.addPoints(up, (WKms) data.getUpperData());
+                }
+            }
+            else if (data.getUpperData() instanceof double[][]) {
+                StyledSeriesBuilder.addPoints(up, (double [][]) data.getUpperData(), false);
+            }
+            else if (data.getUpperData() instanceof WKms) {
+                StyledSeriesBuilder.addPoints(up, (WKms) data.getUpperData());
+            }
+            else if (data.getUpperData() instanceof Lines.LineData) {
+                StyledSeriesBuilder.addPoints(up, ((Lines.LineData) data.getUpperData()).points, false);
+            }
+            else {
+                logger.error("Do not know how to deal with (up) area info from: "
+                    + data.getUpperData());
+            }
+        }
+
+        // TODO Depending on style, the area (e.g. 20m^2) should be added as annotation.
+
+        if (data.getLowerData() != null) {
+            // TODO: Sort this out: when the two series have the same name,
+            // the renderer (or anything in between) will not work correctly.
+            down = new StyledXYSeries(seriesName + " ", false, theme);
+            if (data.getLowerData() instanceof WQKms) {
+                if (FacetTypes.IS.Q(data.getRootFacetName())) {
+                    StyledSeriesBuilder.addPointsKmQ(down, (WQKms) data.getLowerData());
+                }
+                else {
+                    StyledSeriesBuilder.addPoints(down, (WQKms) data.getLowerData());
+                }
+            }
+            else if (data.getLowerData() instanceof double[][]) {
+                StyledSeriesBuilder.addPoints(down, (double[][]) data.getLowerData(), false);
+            }
+            else if (data.getLowerData() instanceof WKms) {
+                StyledSeriesBuilder.addPoints(down, (WKms) data.getLowerData());
+            }
+            else if (data.getLowerData() instanceof Lines.LineData) {
+                StyledSeriesBuilder.addPoints(down, ((Lines.LineData) data.getLowerData()).points, false);
+            }
+            else {
+                logger.error("Do not know how to deal with (down) area info from: "
+                    + data.getLowerData());
+            }
+        }
+
+        if (up == null && down != null) {
+            area.setMode(StyledAreaSeriesCollection.FILL_MODE.ABOVE);
+            down.setKey(seriesName);
+            area.addSeries(down);
+            area.addSeries(StyledSeriesBuilder.createGroundAtInfinity(down));
+        }
+        else if (up != null && down == null) {
+            area.setMode(StyledAreaSeriesCollection.FILL_MODE.UNDER);
+            area.addSeries(up);
+            area.addSeries(StyledSeriesBuilder.createGroundAtInfinity(up));
+        }
+        else if (up != null && down != null) {
+            if (data.doPaintBetween()) {
+                area.setMode(StyledAreaSeriesCollection.FILL_MODE.BETWEEN);
+            }
+            else {
+                area.setMode(StyledAreaSeriesCollection.FILL_MODE.ABOVE);
+            }
+            area.addSeries(up);
+            area.addSeries(down);
+        }
+        // Add area to the respective axis.
+        addAreaSeries(area, axisIdxForFacet(data.getRootFacetName()), visible);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/LongitudinalSectionInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific
+ * longitudinal section curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class LongitudinalSectionInfoGenerator
+extends      ChartInfoGenerator
+{
+    public LongitudinalSectionInfoGenerator() {
+        super(new LongitudinalSectionGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/MapGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,324 @@
+package de.intevation.flys.exports;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.ArtifactNamespaceContext;
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WMSDBLayerFacet;
+import de.intevation.flys.artifacts.model.WMSLayerFacet;
+import de.intevation.flys.artifacts.model.WSPLGENLayerFacet;
+import de.intevation.flys.utils.GeometryUtils;
+import de.intevation.flys.utils.MapfileGenerator;
+import de.intevation.flys.utils.ThemeUtil;
+
+
+public class MapGenerator implements OutGenerator, FacetTypes {
+
+    private static Logger logger = Logger.getLogger(MapGenerator.class);
+
+    protected Artifact master;
+
+    protected Settings settings;
+
+    protected Document request;
+
+    protected OutputStream out;
+
+    protected CallContext context;
+
+    protected List<WMSLayerFacet> layers;
+
+    protected Envelope maxExtent;
+    protected Envelope initialExtent;
+
+    protected String srid;
+
+
+
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("MapGenerator.init");
+
+        this.request  = request;
+        this.out      = out;
+        this.context  = context;
+
+        this.layers = new ArrayList<WMSLayerFacet>();
+
+        this.maxExtent = null;
+        this.initialExtent = null;
+    }
+
+
+    @Override
+    public void setMasterArtifact(Artifact master) {
+        logger.debug("MapGenerator.setMasterArtifact");
+        this.master = master;
+    }
+
+
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible)
+    {
+        String name = artifactFacet.getFacetName();
+
+        logger.debug("MapGenerator.doOut: " +
+            artifactFacet.getArtifact().identifier() + " | " + name);
+        FLYSArtifact flys = (FLYSArtifact) artifactFacet.getArtifact();
+
+        Facet nativeFacet = artifactFacet.getFacet();
+
+        if (nativeFacet instanceof WMSLayerFacet) {
+            WMSLayerFacet wms = (WMSLayerFacet) nativeFacet;
+            Envelope   extent = wms.getExtent();
+
+            layers.add(wms);
+
+            setMaxExtent(extent);
+            setSrid(wms.getSrid());
+
+            if (FLOODMAP_WSPLGEN.equals(name)) {
+                setInitialExtent(extent);
+                createWSPLGENLayer(flys, wms, attr);
+            }
+            else if (FLOODMAP_BARRIERS.equals(name)) {
+                createBarriersLayer(flys, wms);
+            }
+            else if (FLOODMAP_USERSHAPE.equals(name)) {
+                createUserShapeLayer(flys, wms);
+            }
+            else {
+                createDatabaseLayer(flys, wms, attr);
+            }
+        }
+        else {
+            logger.warn("Facet not supported: " + nativeFacet.getClass());
+        }
+    }
+
+
+    protected void createWSPLGENLayer(
+        FLYSArtifact  flys,
+        WMSLayerFacet wms,
+        Document      attr
+    ) {
+        try {
+            if(wms instanceof WSPLGENLayerFacet) {
+                MapfileGenerator mfg = MapfileGenerator.getInstance();
+                mfg.createUeskLayer(
+                    flys,
+                    (WSPLGENLayerFacet) wms,
+                    ThemeUtil.createWSPLGENStyle(attr),
+                    context);
+            }
+            else {
+                logger.warn("Cannot create WSPLGEN layer from: " +
+                            wms.getClass());
+            }
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+    }
+
+
+    protected void createBarriersLayer(FLYSArtifact flys, WMSLayerFacet wms) {
+        MapfileGenerator mfg = MapfileGenerator.getInstance();
+
+        try {
+            mfg.createBarriersLayer(flys, wms);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+    }
+
+
+    protected void createUserShapeLayer(FLYSArtifact flys, WMSLayerFacet wms) {
+        MapfileGenerator mfg = MapfileGenerator.getInstance();
+
+        try {
+            mfg.createUserShapeLayer(flys, wms);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+    }
+
+
+    protected void createDatabaseLayer(
+        FLYSArtifact  flys,
+        WMSLayerFacet wms,
+        Document      attr
+    ) {
+        logger.debug("createDatabaseLayer for facet: " + wms.getName());
+
+        MapfileGenerator mfg = MapfileGenerator.getInstance();
+
+        try {
+            File baseDir = mfg.getShapefileBaseDir();
+            File artDir  = new File(baseDir, flys.identifier());
+
+            if (artDir != null && !artDir.exists()) {
+                logger.debug("Create new directory: " + artDir.getPath());
+                artDir.mkdir();
+            }
+
+            if (wms instanceof WMSDBLayerFacet) {
+                mfg.createDatabaseLayer(
+                    flys,
+                    (WMSDBLayerFacet) wms,
+                    ThemeUtil.createMapserverStyle(attr));
+            }
+            else {
+                logger.warn("Cannot create DB layer from: " + wms.getClass());
+            }
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+    }
+
+
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("MapGenerator.generate");
+
+        MapfileGenerator.getInstance().update();
+
+        Document response = XMLUtils.newDocument();
+        ElementCreator c  = new ElementCreator(
+            response,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root   = c.create("floodmap");
+        Element layers = c.create("layers");
+
+        response.appendChild(root);
+        root.appendChild(layers);
+
+        appendLayers(layers);
+        appendMapInformation(root, c);
+
+        XMLUtils.toStream(response, out);
+    }
+
+
+    protected void appendLayers(Element parent) {
+        for (WMSLayerFacet facet: layers) {
+            parent.appendChild(facet.toXML(parent.getOwnerDocument()));
+        }
+    }
+
+
+    protected void setMaxExtent(Envelope maxExtent) {
+        if (maxExtent == null) {
+            return;
+        }
+
+        if (this.maxExtent == null) {
+            logger.debug("Set max extent to: " + maxExtent);
+            this.maxExtent = new Envelope(maxExtent);
+            return;
+        }
+
+        this.maxExtent.expandToInclude(maxExtent);
+    }
+
+
+    protected void setInitialExtent(Envelope initialExtent) {
+        if (this.initialExtent == null && initialExtent != null) {
+            logger.debug("Set initial extent to: " + initialExtent);
+            this.initialExtent = new Envelope(initialExtent);
+        }
+    }
+
+
+    protected void setSrid(String srid) {
+        if (srid == null || srid.length() == 0) {
+            return;
+        }
+
+        this.srid = srid;
+    }
+
+
+    protected void appendMapInformation(Element parent, ElementCreator c) {
+        String mE = GeometryUtils.jtsBoundsToOLBounds(this.maxExtent);
+
+        Element maxExtent = c.create("maxExtent");
+        maxExtent.setTextContent(mE);
+
+        if(this.initialExtent != null) {
+            String iE = GeometryUtils.jtsBoundsToOLBounds(this.initialExtent);
+            Element initExtent = c.create("initialExtent");
+            initExtent.setTextContent(iE);
+            parent.appendChild(initExtent);
+        }
+
+        Element srid = c.create("srid");
+        srid.setTextContent(this.srid);
+
+        // TODO zoom levels
+        // TODO resolutation
+
+        parent.appendChild(maxExtent);
+        parent.appendChild(srid);
+    }
+
+
+    /**
+     * Returns an instance of <i>EmptySettings</i> currently!
+     *
+     * @return an instance of <i>EmptySettings</i>.
+     */
+    @Override
+    public Settings getSettings() {
+        return new EmptySettings();
+    }
+
+
+    @Override
+    public void setSettings(Settings settings) {
+        this.settings = settings;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/MiddleBedHeightExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,158 @@
+package de.intevation.flys.exports;
+
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.MiddleBedHeightData;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MiddleBedHeightExporter extends AbstractExporter {
+
+    private static final Logger logger =
+        Logger.getLogger(MiddleBedHeightExporter.class);
+
+
+    public static final String CSV_KM =
+        "export.bedheight_middle.csv.header.km";
+
+    public static final String CSV_SOUNDING =
+        "export.bedheight_middle.csv.header.sounding";
+
+    public static final String CSV_HEIGHT =
+        "export.bedheight_middle.csv.header.height";
+
+    public static final String CSV_UNCERTAINTY =
+        "export.bedheight_middle.csv.header.uncertainty";
+
+    public static final String CSV_DATA_GAP =
+        "export.bedheight_middle.csv.header.datagap";
+
+    public static final String CSV_SOUNDING_WIDTH =
+        "export.bedheight_middle.csv.header.soundingwidth";
+
+    public static final String CSV_WIDTH =
+        "export.bedheight_middle.csv.header.width";
+
+    public static final String CSV_LOCATIONS =
+        "export.bedheight_middle.csv.header.locations";
+
+
+    protected List<MiddleBedHeightData[]> data;
+
+
+    public void init(Document request, OutputStream out, CallContext cc) {
+        super.init(request, out, cc);
+        data = new ArrayList<MiddleBedHeightData[]>();
+    }
+
+
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult) d).getData();
+
+            if (d instanceof MiddleBedHeightData[]) {
+                logger.debug("Add new data of type MiddleBedHeightData");
+                data.add((MiddleBedHeightData[]) d);
+            }
+        }
+    }
+
+
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("MiddleBedHeightExporter.writeCSVData");
+        logger.debug("CSV gets " + data.size() + " MiddleBedHeightData objects.");
+
+        writeCSVHeader(writer);
+
+        for (MiddleBedHeightData[] d: data) {
+            data2CSV(writer, d);
+        }
+    }
+
+
+    protected void writeCSVHeader(CSVWriter writer) {
+        writer.writeNext(new String[] {
+            msg(CSV_KM, CSV_KM),
+            msg(CSV_SOUNDING, CSV_SOUNDING),
+            msg(CSV_HEIGHT, CSV_HEIGHT),
+            msg(CSV_UNCERTAINTY, CSV_UNCERTAINTY),
+            msg(CSV_DATA_GAP, CSV_DATA_GAP),
+            msg(CSV_SOUNDING_WIDTH, CSV_SOUNDING_WIDTH),
+            msg(CSV_WIDTH, CSV_WIDTH),
+            msg(CSV_LOCATIONS, CSV_LOCATIONS)
+        });
+    }
+
+
+    protected void data2CSV(CSVWriter writer, MiddleBedHeightData[] mData) {
+        logger.debug("Add next MiddleBedHeightData to CSV");
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        NumberFormat kmF     = Formatter.getMiddleBedHeightKM(context);
+        NumberFormat heightF = Formatter.getMiddleBedHeightHeight(context);
+        NumberFormat uncertF = Formatter.getMiddleBedHeightUncert(context);
+        NumberFormat gapF    = Formatter.getMiddleBedHeightDataGap(context);
+        NumberFormat soundF  = Formatter.getMiddleBedHeightSounding(context);
+        NumberFormat widthF  = Formatter.getMiddleBedHeightWidth(context);
+
+        for (MiddleBedHeightData data: mData) {
+            for (int i = 0, n = data.size(); i < n; i++) {
+                int start = data.getStartYear();
+                int end   = data.getEndYear();
+
+                if (start == end) {
+                    writer.writeNext(new String[] {
+                        kmF.format(data.getKM(i)),
+                        data.getSoundingName(context),
+                        heightF.format(data.getMiddleHeight(i)),
+                        uncertF.format(data.getUncertainty(i)),
+                        gapF.format(data.getDataGap(i)) + "%",
+                        soundF.format(data.getSoundingWidth(i)),
+                        widthF.format(data.getWidth(i)),
+                        FLYSUtils.getLocationDescription(flys, data.getKM(i)),
+                    });
+                }
+                else {
+                    writer.writeNext(new String[] {
+                        kmF.format(data.getKM(i)),
+                        data.getSoundingName(context),
+                        heightF.format(data.getMiddleHeight(i)),
+                        "",
+                        "",
+                        "",
+                        "",
+                        FLYSUtils.getLocationDescription(flys, data.getKM(i)),
+                    });
+                }
+
+            }
+        }
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        logger.error("TODO: Implement FlowVelocityExporter.writePDF");
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/exports/MiddleBedHeightGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,248 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.data.xy.XYSeries;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.MiddleBedHeightData;
+
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/**
+ * An OutGenerator that generates middle bed height charts.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MiddleBedHeightGenerator
+extends      XYChartGenerator
+implements   FacetTypes
+{
+    public enum YAXIS {
+        H(0);
+        protected int idx;
+        private YAXIS(int c) {
+           idx = c;
+        }
+    }
+
+    /** The logger that is used in this generator. */
+    private static Logger logger = Logger.getLogger(MiddleBedHeightGenerator.class);
+
+    /** Key to look up internationalized String for annotations label. */
+    public static final String I18N_ANNOTATIONS_LABEL =
+        "chart.bedheight_middle.annotations.label";
+
+    public static final String I18N_CHART_TITLE =
+        "chart.bedheight_middle.section.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.bedheight_middle.section.subtitle";
+
+    public static final String I18N_CHART_SHORT_SUBTITLE =
+        "chart.bedheight_middle.section.shortsubtitle";
+
+    public static final String I18N_XAXIS_LABEL =
+        "chart.bedheight_middle.section.xaxis.label";
+
+    public static final String I18N_YAXIS_LABEL =
+        "chart.bedheight_middle.section.yaxis.label";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  = "Mittlere Sohlöhe";
+    public static final String I18N_XAXIS_LABEL_DEFAULT  = "km";
+    public static final String I18N_YAXIS_LABEL_DEFAULT  = "mittlere Sohlhöhen [müNN]";
+
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+
+    /**
+     * Returns the default title for this chart.
+     *
+     * @return the default title for this chart.
+     */
+    @Override
+    public String getDefaultChartTitle() {
+        Object[] args = new Object[] {
+            getRiverName()
+        };
+
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT, args);
+    }
+
+
+    /**
+     * Get internationalized label for the x axis.
+     */
+    @Override
+    protected String getDefaultXAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return msg(
+            I18N_XAXIS_LABEL,
+            I18N_XAXIS_LABEL_DEFAULT,
+            new Object[] { FLYSUtils.getRiver(flys).getName() });
+    }
+
+
+    @Override
+    protected String getDefaultYAxisLabel(int index) {
+        String label = "default";
+
+        if (index == YAXIS.H.idx) {
+            label = getHAxisLabel();
+        }
+
+        return label;
+    }
+
+
+    /**
+     * Get internationalized label for the y axis.
+     */
+    protected String getHAxisLabel() {
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
+    }
+
+
+    /**
+     * Produce output.
+     * @param facet current facet.
+     * @param attr  theme for facet
+     */
+    public void doOut(
+        ArtifactAndFacet artifactAndFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        String name = artifactAndFacet.getFacetName();
+
+        logger.debug("MiddleBedHeightGenerator.doOut: " + name);
+
+        if (name == null) {
+            logger.error("No facet name for doOut(). No output generated!");
+            return;
+        }
+
+        Facet facet = artifactAndFacet.getFacet();
+
+        if (facet == null) {
+            return;
+        }
+
+        if (name.equals(MIDDLE_BED_HEIGHT_SINGLE) || name.equals(MIDDLE_BED_HEIGHT_EPOCH)) {
+            doHeightOut(
+                (MiddleBedHeightData) artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (name.equals(MIDDLE_BED_HEIGHT_ANNOTATION)) {
+            doAnnotations(
+                (FLYSAnnotation) artifactAndFacet.getData(context),
+                 artifactAndFacet,
+                 attr,
+                 visible);
+        }
+        else if (FacetTypes.IS.AREA(name)) {
+            doArea(
+                artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(
+                artifactAndFacet.getData(context),
+                artifactAndFacet,
+                attr,
+                visible,
+                YAXIS.H.idx);
+        }
+        else {
+            logger.warn("Unknown facet name: " + name);
+            return;
+        }
+    }
+
+
+    /**
+     * @param data A data object
+     * @param facet The facet. This facet does NOT support any data objects. Use
+     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
+     * data.
+     * @param theme The theme that contains styling information.
+     * @param visible The visibility of the curve.
+     */
+    protected void doHeightOut(
+        MiddleBedHeightData data,
+        ArtifactAndFacet    aandf,
+        Document            theme,
+        boolean             visible
+    ) {
+        logger.debug("MiddleBedHeightGenerator.doMainChannelOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        StyledSeriesBuilder.addPoints(series, data.getMiddleHeightsPoints(), true);
+
+        addAxisSeries(series, YAXIS.H.idx, visible);
+    }
+
+
+    /** Look up the axis identifier for a given facet type. */
+    public int axisIdxForFacet(String facetName) {
+        if (FacetTypes.IS.H(facetName)) {
+            return YAXIS.H.idx;
+        }
+        else {
+            logger.warn("Could not find axis for facet " + facetName);
+            return YAXIS.H.idx;
+        }
+    }
+
+
+    /**
+     * Do Area out.
+     * @param theme styling information.
+     * @param visible whether or not visible.
+     */
+    protected void doArea(
+        Object     o,
+        ArtifactAndFacet aandf,
+        Document   theme,
+        boolean    visible
+    ) {
+        logger.debug("FlowVelocityGenerator.doArea");
+        logger.warn("TODO: Implement FlowVelocityGenerator.doArea");
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/MiddleBedHeightInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,17 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific
+ * middle bed height curves.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MiddleBedHeightInfoGenerator
+extends      ChartInfoGenerator
+{
+    public MiddleBedHeightInfoGenerator() {
+        super(new MiddleBedHeightGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,42 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+/**
+ * An OutGenerator that generates reference curves.
+ */
+public class NormalizedReferenceCurveGenerator
+extends      ReferenceCurveGenerator
+{
+    /** House logger. */
+    private static Logger logger =
+        Logger.getLogger(NormalizedReferenceCurveGenerator.class);
+
+    public static final String I18N_NORMALIZED_CHART_TITLE =
+        "chart.normalized.reference.curve.title";
+
+    public static final String I18N_NORMALIZED_CHART_TITLE_DEFAULT  =
+        "Reduzierte Bezugslinie";
+
+    public NormalizedReferenceCurveGenerator() {
+    }
+
+    /** Get default chart title. */
+    @Override
+    protected String getDefaultChartTitle() {
+        return msg(
+            I18N_NORMALIZED_CHART_TITLE,
+            I18N_NORMALIZED_CHART_TITLE_DEFAULT);
+    }
+
+    @Override
+    protected String facetName() {
+        return REFERENCE_CURVE_NORMALIZED;
+    }
+
+    @Override
+    protected boolean doNormalize() {
+        return true;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/NormalizedReferenceCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,15 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific reference
+ * curves.
+ */
+public class NormalizedReferenceCurveInfoGenerator
+extends      ChartInfoGenerator
+{
+    public NormalizedReferenceCurveInfoGenerator() {
+        super(new NormalizedReferenceCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/OutGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,75 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Settings;
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+
+/**
+ * An OutGenerator is used to create a collected outputs of a list of Artifacts.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface OutGenerator {
+
+    /**
+     * Initializes the OutGenerator with meta information which are necessary
+     * for the output generation.
+     *
+     * @param request The incomding request document.
+     * @param out     The output stream.
+     * @param context The CallContext that provides further information and
+     * objects used for the output generation.
+     */
+    void init(Document request, OutputStream out, CallContext context);
+
+    /**
+     * This method is used to tell the OutGenerator which artifact is the master
+     * artifact which is used for special operations.
+     *
+     * @param master The master artifact.
+     */
+    void setMasterArtifact(Artifact master);
+
+    /**
+     * Creates the output of an Artifact and appends that single output to the
+     * total output.
+     *
+     * @param artifact The artifact that provides information and data for the
+     * single output.
+     * @param attr A document that might contain some attributes used while
+     * producing the output.
+     * @param visible Specifies, if this output should be visible or not.
+     */
+    void doOut(ArtifactAndFacet bundle, Document attr, boolean visible);
+
+    /**
+     * Writes the collected output of all artifacts specified in the
+     * <i>request</i> (see init()) document to the OutputStream <i>out</i> (see
+     * init()).
+     */
+    void generate() throws IOException;
+
+    /**
+     * This method is used to set a <i>Settings</i> object for the <i>Output</i>
+     * that is produced by this <i>OutGenerator</i>.
+     *
+     * @param settings The <i>Settings</i> that might be used while
+     * <i>Output</i> creation.
+     */
+    void setSettings(Settings settings);
+
+    /**
+     * Returns the Settings for the Output produced by this OutGenerator.
+     *
+     * @return the Settings for the Output produced by this OutGenerator.
+     */
+    Settings getSettings();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ReferenceCurveExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,370 @@
+package de.intevation.flys.exports;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.WWQQ;
+import de.intevation.flys.artifacts.model.WWQQJRDataSource;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import net.sf.jasperreports.engine.JRException;
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+/**
+ * (CSV)Exporter for Reference Curves.
+ */
+public class ReferenceCurveExporter extends AbstractExporter {
+
+    /** The logger used in this exporter. */
+    private static Logger logger = Logger.getLogger(ReferenceCurveExporter.class);
+
+    public static final String RC_CSV_KM_HEADER =
+        "export.reference_curve.csv.header.km";
+
+    public static final String RC_CSV_W_CM_HEADER =
+        "export.reference_curve.csv.header.w.cm";
+
+    public static final String RC_CSV_W_M_HEADER =
+        "export.reference_curve.csv.header.w.m";
+
+    public static final String RC_CSV_Q_HEADER =
+        "export.reference_curve.csv.header.w.q";
+
+    public static final String CSV_LOCATION_HEADER =
+        "export.waterlevel.csv.header.location";
+
+    public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage";
+
+    public static final String RC_DEFAULT_CSV_KM_HEADER = "Fluss-Km";
+    public static final String RC_DEFAULT_CSV_W_M_HEADER  = "W (m + NHN)";
+    public static final String RC_DEFAULT_CSV_W_CM_HEADER  = "W (cm am Pegel)";
+    public static final String RC_DEFAULT_CSV_Q_HEADER  = "gleichw. Q (m\u00b3/s)";
+
+    public static final String PDF_HEADER_MODE = "export.reference_curve.pdf.mode";
+    public static final String JASPER_FILE = "export.reference_curve.pdf.file";
+    public static final String JASPER_FILE_GAUGE = "export.reference_curve.pdf.file.gauge";
+    public static final String JASPER_FILE_GAUGE_END = "export.reference_curve.pdf.file.gauge.end";
+    public static final String JASPER_FILE_GAUGE_START_END = "export.reference_curve.pdf.file.gauge.start.end";
+
+    /** The storage that contains all WKms objects for the different facets. */
+    protected List<WWQQ[]> data;
+
+    protected boolean startAtGauge = false;
+
+    protected boolean endAtGauge = false;
+
+    
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("ReferenceCurveExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WWQQ[]>();
+    }
+
+
+    /**
+     * Genereate data in csv format.
+     */
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("ReferenceCurveExporter.generate");
+
+        if (facet == null) {
+            throw new IOException("invalid (null) facet for exporter");
+        }
+        else if (facet.equals(AbstractExporter.FACET_CSV)) {
+            generateCSV();
+        }
+        else if (facet.equals(AbstractExporter.FACET_PDF)) {
+            generatePDF();
+        }
+        else {
+            throw new IOException("invalid facet (" + facet + ") for exporter");
+        }
+    }
+
+
+    /**
+     * Adds given data.
+     * @param d A CalculationResult with WWQQ[].
+     */
+    @Override
+    protected void addData(Object d) {
+        logger.debug("ReferenceCurveExporter.addData");
+
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult)d).getData();
+            if (d instanceof WWQQ []) {
+                WWQQ[] wwqqs = (WWQQ []) d;
+                for (WWQQ wwqq: wwqqs) {
+                    if (wwqq.startAtGauge()) {
+                        startAtGauge = true;
+                    }
+                    // TODO this one probably has to be inverted.
+                    if (wwqq.endAtGauge()) {
+                        endAtGauge = true;
+                    }
+                }
+                data.add(wwqqs);
+                logger.debug("ReferenceCurveExporter.addData wwqq[].");
+            }
+            else {
+                logger.warn("ReferenceCurveExporter.addData/1 unknown type ("
+                    + d + ").");
+            }
+        }
+        else {
+            logger.warn("ReferenceCurveExporter.addData/2 unknown type ("
+                + d + ").");
+        }
+    }
+
+
+    /**
+     * Lets writer write all data (including header).
+     * @param writer Writer to write data with.
+     */
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        logger.debug("ReferenceCurveExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        for (WWQQ[] tmp: data) {
+            for (WWQQ ww: tmp) {
+                wWQQ2CSV(writer, ww);
+            }
+        }
+    }
+
+
+    /**
+     * Lets csvwriter write the header (first line in file).
+     * @param write Writer to write header with.
+     */
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.info("ReferenceCurveExporter.writeCSVHeader");
+
+        StepCSVWriter stepWriter = new StepCSVWriter();
+        stepWriter.setCSVWriter(writer);
+
+        stepWriter.addNexts(
+            msg(RC_CSV_KM_HEADER, RC_DEFAULT_CSV_KM_HEADER),
+            msg(RC_CSV_W_M_HEADER, RC_DEFAULT_CSV_W_M_HEADER)
+            );
+        if (startAtGauge) {
+            stepWriter.addNext(
+                msg(RC_CSV_W_CM_HEADER, RC_DEFAULT_CSV_W_CM_HEADER));
+        }
+        stepWriter.addNexts(
+            msg(RC_CSV_Q_HEADER, RC_DEFAULT_CSV_Q_HEADER),
+            msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER),
+            msg(RC_CSV_KM_HEADER, RC_DEFAULT_CSV_KM_HEADER),
+            msg(RC_CSV_W_M_HEADER, RC_DEFAULT_CSV_W_M_HEADER)
+            );
+        if (endAtGauge) {
+            stepWriter.addNext(
+                msg(RC_CSV_W_CM_HEADER, RC_DEFAULT_CSV_W_CM_HEADER)
+                );
+        }
+        stepWriter.addNexts(
+                msg(RC_CSV_Q_HEADER, RC_DEFAULT_CSV_Q_HEADER),
+                msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER)
+                );
+
+        stepWriter.flush();
+    }
+
+
+    protected void wWQQ2CSV(CSVWriter writer, WWQQ ww) {
+        logger.debug("ReferenceCurveExporter.wWQQ2CSV");
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        int         size = ww.size();
+
+        FLYSArtifact flys       = (FLYSArtifact) master;
+
+        StepCSVWriter stepWriter = new StepCSVWriter();
+        stepWriter.setCSVWriter(writer);
+
+        String startLocationDescription = FLYSUtils.getLocationDescription(
+            flys, ww.getStartKm());
+
+        String endLocationDescription = FLYSUtils.getLocationDescription(
+            flys, ww.getEndKm());
+
+        for (int i = 0; i < size; i ++) {
+            stepWriter.addNexts(kmf.format(ww.getStartKm()));
+            stepWriter.addNext(wf.format(ww.getW1(i)));
+            if (startAtGauge) {
+                stepWriter.addNext(wf.format(ww.getRelHeight1Cm(i)));
+            }
+            stepWriter.addNexts(
+                qf.format(ww.getQ1(i)), // "Q"
+                startLocationDescription,
+                kmf.format(ww.getEndKm())
+                );
+            stepWriter.addNext(wf.format(ww.getW2(i)));
+            if (endAtGauge) {
+                if (ww.endAtGauge()) {
+                    stepWriter.addNext(wf.format(ww.getRelHeight2Cm(i)));
+                }
+                else {
+                    stepWriter.addNext("-");
+                }
+            }
+            stepWriter.addNexts(
+                qf.format(ww.getQ2(i)), // "Q"
+                endLocationDescription
+                );
+            stepWriter.flush();
+        }
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        WWQQJRDataSource source = createJRData();
+
+        String filename = JASPER_FILE;
+        if (startAtGauge && endAtGauge) {
+            filename = JASPER_FILE_GAUGE_START_END;
+        }
+        else if (startAtGauge) {
+            filename = JASPER_FILE_GAUGE;
+        }
+        else if (endAtGauge) {
+            filename = JASPER_FILE_GAUGE_END;
+        }
+
+        String jasperFile = Resources.getMsg(
+                                context.getMeta(),
+                                filename,
+                                "/jasper/reference_en.jasper");
+        String confPath = Config.getConfigDirectory().toString();
+
+        Map parameters = new HashMap();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            JasperPrint print = JasperFillManager.fillReport(
+                confPath + jasperFile,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, out);
+        }
+        catch(JRException je) {
+            logger.warn("Error generating PDF Report!");
+            je.printStackTrace();
+        }
+    }
+
+    protected WWQQJRDataSource createJRData() {
+        WWQQJRDataSource source = new WWQQJRDataSource();
+
+        addMetaData(source);
+
+        for (WWQQ[] tmp: data) {
+            for (WWQQ ww: tmp) {
+                addWWQQData(source, ww);
+            }
+        }
+        return source;
+    }
+
+
+    protected void addMetaData(WWQQJRDataSource source) {
+        CallMeta meta = context.getMeta();
+
+        WINFOArtifact flys = (WINFOArtifact) master;
+
+        source.addMetaData ("river", FLYSUtils.getRivername(flys));
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        source.addMetaData("date", df.format(new Date()));
+
+        source.addMetaData("calculation", Resources.getMsg(
+                                            locale,
+                                            PDF_HEADER_MODE,
+                                            "Reference Curve"));
+    }
+
+
+    protected void addWWQQData(WWQQJRDataSource source, WWQQ ww) {
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        int          size = ww.size();
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        String startLocationDescription = FLYSUtils.getLocationDescription(
+            flys, ww.getStartKm());
+
+        String endLocationDescription = FLYSUtils.getLocationDescription(
+            flys, ww.getEndKm());
+
+        for (int i = 0; i < size; i ++) {
+            String start = "-";
+            String end = "-";
+            if (startAtGauge) {
+                start = wf.format(ww.getRelHeight1Cm(i));
+            }
+            if (ww.endAtGauge()) {
+                end = wf.format(ww.getRelHeight2Cm(i));
+            }
+            source.addData(new String[] {
+                kmf.format(ww.getStartKm()),
+                startLocationDescription,
+                wf.format(ww.getW1(i)),
+                qf.format(ww.getQ1(i)), // "Q"
+                kmf.format(ww.getEndKm()),
+                endLocationDescription,
+                wf.format(ww.getW2(i)),
+                qf.format(ww.getQ2(i)), // "Q"
+                start,
+                end
+            });
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ReferenceCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,282 @@
+package de.intevation.flys.exports;
+
+import java.awt.geom.Point2D;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.JFreeChart;
+
+import org.jfree.chart.title.TextTitle;
+
+import org.jfree.data.xy.XYSeries;
+
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.NumberTickUnit;
+import org.jfree.chart.axis.TickUnits;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WW;
+import de.intevation.flys.artifacts.model.WW.ApplyFunctionIterator;
+import de.intevation.flys.artifacts.model.WWAxisTypes;
+
+import de.intevation.flys.jfree.StyledXYSeries;
+import de.intevation.flys.jfree.FLYSAnnotation;
+
+import de.intevation.flys.utils.Formatter;
+
+/**
+ * An OutGenerator that generates reference curves.
+ */
+public class ReferenceCurveGenerator
+extends      XYChartGenerator
+implements   FacetTypes
+{
+    public static enum YAXIS {
+        W(0);
+
+        public int idx;
+        private YAXIS(int c) {
+           idx = c;
+        }
+    }
+
+    /** House logger. */
+    private static Logger logger =
+        Logger.getLogger(ReferenceCurveGenerator.class);
+
+    public static final String I18N_CHART_TITLE =
+        "chart.reference.curve.title";
+
+    public static final String I18N_CHART_SUBTITLE =
+        "chart.reference.curve.subtitle";
+
+    public static final String I18N_X_AXIS_IN_CM =
+        "chart.reference.curve.x.axis.in.cm";
+
+    public static final String I18N_X_AXIS_IN_M =
+        "chart.reference.curve.x.axis.in.m";
+
+    public static final String I18N_Y_AXIS_IN_CM =
+        "chart.reference.curve.y.axis.in.cm";
+
+    public static final String I18N_Y_AXIS_IN_M =
+        "chart.reference.curve.y.axis.in.m";
+
+    public static final String I18N_CHART_TITLE_DEFAULT  =
+        "Bezugslinie";
+
+
+    public ReferenceCurveGenerator() {
+    }
+
+    /**
+     * Create Axis for given index.
+     * @return axis with according internationalized label.
+     */
+    @Override
+    protected NumberAxis createYAxis(int index) {
+        NumberAxis axis = super.createYAxis(index);
+        axis.setAutoRangeIncludesZero(false);
+        return axis;
+    }
+
+
+    /** Get default chart title. */
+    @Override
+    protected String getDefaultChartTitle() {
+        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
+    }
+
+    @Override
+    protected String getDefaultChartSubtitle() {
+        Object[] args = new Object[] {
+            getRiverName(),
+        };
+
+        return msg(I18N_CHART_SUBTITLE, "", args);
+    }
+
+
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+
+        if (subtitle != null && subtitle.length() > 0) {
+            chart.addSubtitle(new TextTitle(subtitle));
+        }
+    }
+
+    protected boolean getInCm(int index) {
+        Object obj = context.getContextValue("reference.curve.axis.scale");
+        return obj instanceof WWAxisTypes && ((WWAxisTypes)obj).getInCm(index);
+    }
+
+
+    /** Get Label for X-axis (W). */
+    @Override
+    protected String getDefaultXAxisLabel() {
+        return msg(getInCm(0) ? I18N_X_AXIS_IN_CM : I18N_X_AXIS_IN_M);
+    }
+
+
+    /**
+     * Get Label for primary and other Y Axes.
+     * @param index Axis-Index (0-based).
+     */
+    @Override
+    protected String getDefaultYAxisLabel(int index) {
+        return msg(getInCm(1) ? I18N_Y_AXIS_IN_CM : I18N_Y_AXIS_IN_M);
+    }
+
+    protected String facetName() {
+        return REFERENCE_CURVE;
+    }
+
+
+    /**
+     * Called for each facet/them in the out mapped to this generator.
+     * @param artifactFacet artifact and facet for this theme.
+     * @param theme         styling info.
+     * @param visible       Whether or not the theme is visible.
+     */
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         theme,
+        boolean          visible
+    ) {
+        String name = artifactFacet.getFacetName();
+
+        logger.debug("ReferenceCurveGenerator.doOut: " + name);
+
+        if (name == null || name.length() == 0) {
+            logger.error("No facet given. Cannot create dataset.");
+            return;
+        }
+
+        if (name.equals(facetName())) {
+            doReferenceOut(artifactFacet.getData(context), theme, visible);
+        }
+        else if (FacetTypes.IS.MANUALPOINTS(name)) {
+            doPoints(
+                artifactFacet.getData(context),
+                artifactFacet,
+                theme,
+                visible,
+                YAXIS.W.idx);
+        }
+        else if (name.equals(RELATIVE_POINT)) {
+            doPointOut(
+                (Point2D) artifactFacet.getData(context),
+                artifactFacet,
+                theme,
+                visible);
+        }
+        else if (name.equals(MAINVALUES_W)) {
+            doAnnotations(
+                ((FLYSAnnotation) artifactFacet.getData(context)).flipStickyAxis(),
+                artifactFacet,
+                theme,
+                visible);
+        
+        }
+        else {
+            logger.warn("Unknown facet name: " + name);
+        }
+    }
+
+    protected boolean doNormalize() {
+        return false;
+    }
+
+
+    /** Register DataSeries with (maybe transformed) points. */
+    public void doReferenceOut(
+        Object   data,
+        Document theme,
+        boolean  visible
+    ) {
+        WW ww = (WW)data;
+
+        Object obj = context.getContextValue("reference.curve.axis.scale");
+
+        WWAxisTypes wwat = obj instanceof WWAxisTypes
+            ? (WWAxisTypes)obj
+            : new WWAxisTypes(ww);
+
+        ApplyFunctionIterator iter = wwat.transform(ww, doNormalize());
+
+        XYSeries series = new StyledXYSeries(
+            ww.getName(), false, theme);
+
+        double [] values = new double[2];
+
+        while (iter.hasNext()) {
+            iter.next(values);
+            series.add(values[0], values[1], false);
+        }
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+
+    // TODO resolve duplicate in DurationCurveGenerator
+    protected void doPointOut(
+        Point2D point,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean visible
+    ){
+        logger.debug("ReferenceCurveGenerator.doPointOut");
+
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+
+        series.add(point.getX(), point.getY());
+
+        addAxisSeries(series, YAXIS.W.idx, visible);
+    }
+
+    protected void setAxisTickUnit(ValueAxis axis) {
+        TickUnits units = new TickUnits();
+        units.add(new NumberTickUnit(1d, Formatter.getWaterlevelW(context)));
+        axis.setStandardTickUnits(units);
+        axis.setAutoTickUnitSelection(true);
+    }
+
+    @Override
+    protected void localizeDomainAxis(ValueAxis domainAxis) {
+        super.localizeDomainAxis(domainAxis);
+        setAxisTickUnit(domainAxis);
+    }
+
+
+    @Override
+    protected void localizeRangeAxis(ValueAxis rangeAxis) {
+        super.localizeRangeAxis(rangeAxis);
+        setAxisTickUnit(rangeAxis);
+    }
+
+    /** Get Walker to iterate over all axes. */
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            /** Get number of items. */
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            /** Get identifier for this index. */
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ReferenceCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,15 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific reference
+ * curves.
+ */
+public class ReferenceCurveInfoGenerator
+extends      ChartInfoGenerator
+{
+    public ReferenceCurveInfoGenerator() {
+        super(new ReferenceCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ReportGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,90 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+import de.intevation.artifactdatabase.state.Settings;
+
+import de.intevation.flys.artifacts.model.Calculation;
+
+import org.w3c.dom.Document;
+
+public class ReportGenerator
+implements   OutGenerator
+{
+    private static Logger logger = Logger.getLogger(ReportGenerator.class);
+
+    protected Document     result;
+    protected OutputStream out;
+    protected CallContext  context;
+
+    public ReportGenerator() {
+    }
+
+    @Override
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("init");
+        this.out     = out;
+        this.context = context;
+        result       = null;
+    }
+
+    @Override
+    public void setMasterArtifact(Artifact master) {
+        // not needed
+    }
+
+    @Override
+    public void doOut(
+        ArtifactAndFacet artifactFacet,
+        Document         attr,
+        boolean          visible
+    ) {
+        logger.debug("doOut");
+        Facet facet = artifactFacet.getFacet();
+        if (facet != null) {
+            Calculation report = (Calculation) artifactFacet.getData(context);
+            if (result == null) {
+                result = XMLUtils.newDocument();
+                report.toXML(result, context.getMeta());
+            }
+        }
+    }
+
+    @Override
+    public void generate() throws IOException {
+        logger.debug("generate");
+        XMLUtils.toStream(result != null
+            ? result
+            : XMLUtils.newDocument(), out);
+    }
+
+
+    /**
+     * Returns an instance of <i>EmptySettings</i> currently!
+     *
+     * @return an instance of <i>EmptySettings</i>.
+     */
+    public Settings getSettings() {
+        return new EmptySettings();
+    }
+
+
+    /**
+     * This method is not implemented. Override it in subclasses if those need a
+     * <i>Settings</i> object.
+     */
+    public void setSettings(Settings settings) {
+        // do nothing
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/StepCSVWriter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,51 @@
+package de.intevation.flys.exports;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Class to overcome shortcoming of CSVWriter to accept String-Arrays only.
+ * The StepCSVWriter buffers incoming values, such that rows in a csv can be
+ * created more dynamically. Do not forget to call flush().
+ */
+public class StepCSVWriter {
+
+    /** Writer to use when calling flush. */
+    CSVWriter writer = null;
+    /** Buffer of strings (values). */
+    ArrayList<String> buffer;
+
+
+    /** Trivial constructor. */
+    public StepCSVWriter() {
+        buffer = new ArrayList<String>();
+    }
+
+
+    /** Set writer. */
+    public void setCSVWriter(CSVWriter writer) {
+        this.writer = writer;
+    }
+
+
+    /** Add a value to next flush. */
+    public void addNext(String value) {
+        buffer.add(value);
+    }
+
+
+    /** Add many values to next flush. */
+    public void addNexts(String ... values) {
+        buffer.addAll(Arrays.asList(values));
+    }
+
+
+    /** Write the row with csvwriter. */
+    public void flush() {
+        writer.writeNext(buffer.toArray(new String[buffer.size()]));
+        buffer.clear();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/StringAttribute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.exports;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class StringAttribute extends VisibleAttribute {
+
+
+    public StringAttribute(String name, String value, boolean visible) {
+        super(name, value, visible);
+    }
+
+
+    /**
+     * Calls VisibleAttribute.toXML() and appends afterwards an attribute
+     * <i>type</i> with value <i>string</i>.
+     *
+     * @param parent The parent Node.
+     *
+     * @return the new Node that represents this Attribute.
+     */
+    @Override
+    public Node toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+
+        Element ele = (Element) super.toXML(parent);
+        ele.setAttribute("type", "string");
+
+        return ele;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/StyledSeriesBuilder.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,158 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.data.xy.XYSeries;
+
+import de.intevation.flys.artifacts.model.WKms; 
+import de.intevation.flys.artifacts.model.WQKms; 
+import de.intevation.flys.artifacts.model.WWQQ; 
+
+/**
+ * Helper to create and modify StyledXYSeries.
+ */
+public class StyledSeriesBuilder {
+
+    /**
+     * JFreeChart and the area calculation will fail if we use Double.INFINITY
+     * or Double.MAX_VALUE (probably because these are really used in
+     * calculations). We define and use a more handy value instead.
+     */
+    final static double BIG_DOUBLE_VALUE = 1234567d;
+
+    private static final Logger logger = Logger.getLogger
+        (StyledSeriesBuilder.class);
+
+
+    /**
+     * Trivial, hidden constructor.
+     */
+    private StyledSeriesBuilder() {
+    }
+
+
+    /**
+     * Add points to series.
+     *
+     * @param series Series to add points to.
+     * @param points Points to add to series, points[0] to 1st dim, points[1]
+     *               to 2nd dim.
+     * @param skipNANs if true, skip NAN values in points parameter.
+     */
+    public static void addPoints(XYSeries series, double[][] points, boolean skipNANs) {
+        if (points == null || points.length <= 1) {
+            return;
+        }
+        double [] xPoints = points[0];
+        double [] yPoints = points[1];
+        for (int i = 0; i < xPoints.length; i++) {
+            if (skipNANs &&
+                (Double.isNaN(xPoints[i]) || Double.isNaN(yPoints[i]))) {
+                logger.warn ("Skipping NaN in StyledSeriesBuilder.");
+                continue;
+            }
+            series.add(xPoints[i], yPoints[i], false);
+        }
+    }
+
+
+    /**
+     * Add points to series (km to 1st dim, w to 2nd dim).
+     *
+     * @param series Series to add points to.
+     * @param points Points to add to series.
+     */
+    public static void addPoints(XYSeries series, WKms wkms) {
+        if (wkms == null) {
+            return;
+        }
+
+        int size = wkms.size();
+
+        for (int i = 0; i < size; i++) {
+            series.add(wkms.getKm(i), wkms.getW(i), false);
+        }
+    }
+
+
+    /**
+     * Add points to series (km to 1st dim, q to 2nd dim).
+     *
+     * @param series Series to add points to.
+     * @param points Points to add to series.
+     */
+    public static void addPointsKmQ(XYSeries series, WQKms wqkms) {
+        if (wqkms == null) {
+            return;
+        }
+
+        int size = wqkms.size();
+
+        for (int i = 0; i < size; i++) {
+            series.add(wqkms.getKm(i), wqkms.getQ(i), false);
+        }
+    }
+
+
+    /**
+     * Add points to series (q to 1st dim, w to 2nd dim).
+     *
+     * @param series Series to add points to.
+     * @param points Points to add to series.
+     */
+    public static void addPointsQW(XYSeries series, WQKms wqkms) {
+        if (wqkms == null) {
+            return;
+        }
+
+        int size = wqkms.size();
+
+        for (int i = 0; i < size; i++) {
+            series.add(wqkms.getQ(i), wqkms.getW(i));
+        }
+    }
+
+
+    /**
+     * Add points to series (q to 1st dim, w to 2nd dim).
+     *
+     * @param series Series to add points to.
+     * @param points Points to add to series.
+     */
+    public static void addPoints(XYSeries series, WWQQ wwqq) {
+        if (wwqq == null) {
+            return;
+        }
+
+        int size = wwqq.size();
+
+        for (int i = 0; i < size; i++) {
+            series.add(wwqq.getW1(i), wwqq.getW2(i));
+        }
+    }
+
+
+    /**
+     * Create a Series such that an infinitely big area can be filled
+     * between the newly created and the given series.
+     */
+    public static XYSeries createGroundAtInfinity(XYSeries series) {
+        XYSeries ground = new XYSeries(series.getKey() + /** TODO rand + */ "INF");
+        ground.add(series.getMinX(), -BIG_DOUBLE_VALUE);
+        ground.add(series.getMaxX(), -BIG_DOUBLE_VALUE);
+        return ground;
+    }
+
+
+    /**
+     * Create a Series such that an infinitely big area can be filled
+     * between the newly created and the given series.
+     */
+    public static XYSeries createCeilingAtInfinity(XYSeries series) {
+        XYSeries ground = new XYSeries(series.getKey() + /** TODO rand + */ "INF");
+        ground.add(series.getMinX(), BIG_DOUBLE_VALUE);
+        ground.add(series.getMaxX(), BIG_DOUBLE_VALUE);
+        return ground;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,704 @@
+package de.intevation.flys.exports;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.BasicStroke;
+import java.awt.Stroke;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Date;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.annotations.XYTextAnnotation;
+import org.jfree.chart.LegendItemCollection;
+
+import org.jfree.data.Range;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.data.general.Series;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.data.time.Day;
+import org.jfree.data.time.TimeSeries;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.DoubleBounds;
+import de.intevation.flys.jfree.TimeBounds;
+import de.intevation.flys.jfree.StyledTimeSeries;
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
+
+import de.intevation.flys.utils.ThemeAccess;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class TimeseriesChartGenerator extends ChartGenerator {
+
+
+    /**
+     * Inner class TimeseriesAxisDataset stores TimeSeriesCollection.
+     */
+    public class TimeseriesAxisDataset implements AxisDataset {
+
+        protected int axisSymbol;
+
+        protected List<TimeSeriesCollection> datasets;
+
+        protected Range range;
+
+        protected int plotAxisIndex;
+
+
+        public TimeseriesAxisDataset(int axisSymbol) {
+            this.axisSymbol = axisSymbol;
+            this.datasets   = new ArrayList<TimeSeriesCollection>();
+        }
+
+
+        @Override
+        public void addDataset(XYDataset dataset) {
+            if (!(dataset instanceof TimeSeriesCollection)) {
+                logger.warn("Skip non TimeSeriesCollection dataset.");
+                return;
+            }
+
+            TimeSeriesCollection tsc = (TimeSeriesCollection) dataset;
+
+            datasets.add(tsc);
+            mergeRanges(tsc);
+        }
+
+
+        @Override
+        public XYDataset[] getDatasets() {
+            return (XYDataset[])
+                datasets.toArray(new XYDataset[datasets.size()]);
+        }
+
+
+        @Override
+        public boolean isEmpty() {
+            return datasets.isEmpty();
+        }
+
+
+        @Override
+        public void setRange(Range range) {
+            this.range = range;
+        }
+
+
+        @Override
+        public Range getRange() {
+            return range;
+        }
+
+
+        @Override
+        public void setPlotAxisIndex(int plotAxisIndex) {
+            this.plotAxisIndex = plotAxisIndex;
+        }
+
+
+        @Override
+        public int getPlotAxisIndex() {
+            return plotAxisIndex;
+        }
+
+
+        @Override
+        public boolean isArea(XYDataset dataset) {
+            logger.warn("This AxisDataset doesn't support Areas yet!");
+            return false;
+        }
+
+
+        protected void mergeRanges(TimeSeriesCollection dataset) {
+            logger.debug("Range after merging: " + range);
+
+            Bounds[] xyRanges = ChartHelper.getBounds(dataset);
+
+            // TODO COMBINE BOUNDS!
+
+            logger.debug("Range after merging: " + range);
+        }
+
+    } // end of TimeseriesAxisDataset class
+
+
+    /** List of annotations to insert in plot. */
+    protected List<FLYSAnnotation> annotations;
+
+
+    private static final Logger logger =
+        Logger.getLogger(TimeseriesChartGenerator.class);
+
+
+    public static final int AXIS_SPACE = 5;
+
+
+    protected Map<Integer, Bounds> xBounds;
+
+    protected Map<Integer, Bounds> yBounds;
+
+
+
+    /**
+     * The default constructor that initializes internal datastructures.
+     */
+    public TimeseriesChartGenerator() {
+        super();
+
+        xBounds = new HashMap<Integer, Bounds>();
+        yBounds = new HashMap<Integer, Bounds>();
+    }
+
+
+
+    @Override
+    public JFreeChart generateChart() {
+        logger.info("Generate Timeseries Chart.");
+
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+            getChartTitle(),
+            getXAxisLabel(),
+            getYAxisLabel(0),
+            null,
+            isLegendVisible(),
+            false,
+            false);
+
+        XYPlot plot = (XYPlot) chart.getPlot();
+
+        chart.setBackgroundPaint(Color.WHITE);
+        plot.setBackgroundPaint(Color.WHITE);
+
+        addSubtitles(chart);
+        adjustPlot(plot);
+        addDatasets(plot);
+        adjustAxes(plot);
+
+        adaptZoom(plot);
+
+        addAnnotationsToRenderer(plot);
+
+        return chart;
+    }
+
+
+    @Override
+    protected Series getSeriesOf(XYDataset dataset, int idx) {
+        return ((TimeSeriesCollection) dataset).getSeries(idx);
+    }
+
+
+    /**
+     * This method creates new instances of TimeseriesAxisDataset.
+     *
+     * @param idx The symbol for the new TimeseriesAxisDataset.
+     */
+    @Override
+    protected AxisDataset createAxisDataset(int idx) {
+        logger.debug("Create a new AxisDataset for index: " + idx);
+        return new TimeseriesAxisDataset(idx);
+    }
+
+
+    @Override
+    protected void combineXBounds(Bounds bounds, int index) {
+        if (bounds != null) {
+            Bounds old = getXBounds(index);
+
+            if (old != null) {
+                bounds = bounds.combine(old);
+            }
+
+            setXBounds(index, bounds);
+        }
+    }
+
+
+    @Override
+    protected void combineYBounds(Bounds bounds, int index) {
+        if (bounds != null) {
+            Bounds old = getYBounds(index);
+
+            if (old != null) {
+                bounds = bounds.combine(old);
+            }
+
+            setYBounds(index, bounds);
+        }
+    }
+
+
+    // TODO REPLACE THIS METHOD WITH getBoundsForAxis(index)
+    @Override
+    public Range[] getRangesForAxis(int index) {
+        // TODO
+        Bounds[] bounds = getBoundsForAxis(index);
+
+        return new Range[] {
+            new Range(
+                bounds[0].getLower().doubleValue(),
+                bounds[0].getUpper().doubleValue()),
+            new Range(
+                bounds[1].getLower().doubleValue(),
+                bounds[1].getUpper().doubleValue())
+        };
+    }
+
+
+    @Override
+    public Bounds getXBounds(int axis) {
+        return xBounds.get(axis);
+    }
+
+
+    @Override
+    protected void setXBounds(int axis, Bounds bounds) {
+        xBounds.put(axis, bounds);
+    }
+
+
+    @Override
+    public Bounds getYBounds(int axis) {
+        return yBounds.get(axis);
+    }
+
+
+    @Override
+    protected void setYBounds(int axis, Bounds bounds) {
+        if (bounds != null) {
+            yBounds.put(axis, bounds);
+        }
+    }
+
+
+    public Bounds[] getBoundsForAxis(int index) {
+        logger.debug("Return x and y bounds for axis at: " + index);
+
+        Bounds rx = getXBounds(Integer.valueOf(index));
+        Bounds ry = getYBounds(Integer.valueOf(index));
+
+        if (rx == null) {
+            logger.warn("Range for x axis not set." +
+                        " Using default values: 0 - 1.");
+            rx = new TimeBounds(0l, 1l);
+        }
+
+        if (ry == null) {
+            logger.warn("Range for y axis not set." +
+                        " Using default values: 0 - 1.");
+            ry = new DoubleBounds(0l, 1l);
+        }
+
+        logger.debug("X Bounds at index " + index + " is: " + rx);
+        logger.debug("Y Bounds at index " + index + " is: " + ry);
+
+        return new Bounds[] {rx, ry};
+    }
+
+
+    public Bounds getDomainAxisRange() {
+        String[] ranges = getDomainAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No zoom range for domain axis specified.");
+            return null;
+        }
+
+        if (ranges[0] == null || ranges[1] == null) {
+            logger.warn("Invalid ranges for domain axis specified!");
+            return null;
+        }
+
+        try {
+            double lower = Double.parseDouble(ranges[0]);
+            double upper = Double.parseDouble(ranges[1]);
+
+            return new DoubleBounds(lower, upper);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Invalid ranges for domain axis specified: " + nfe);
+        }
+
+        return null;
+    }
+
+
+    public Bounds getValueAxisRange() {
+        String[] ranges = getValueAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No zoom range for domain axis specified.");
+            return null;
+        }
+
+        if (ranges[0] == null || ranges[1] == null) {
+            logger.warn("Invalid ranges for domain axis specified!");
+            return null;
+        }
+
+        try {
+            double lower = Double.parseDouble(ranges[0]);
+            double upper = Double.parseDouble(ranges[1]);
+
+            return new DoubleBounds(lower, upper);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Invalid ranges for domain axis specified: " + nfe);
+        }
+
+        return null;
+    }
+
+
+    protected void adaptZoom(XYPlot plot) {
+        logger.debug("Adapt zoom of Timeseries chart.");
+
+        zoomX(plot, plot.getDomainAxis(), getXBounds(0), getDomainAxisRange());
+
+        Bounds valueAxisBounds = getValueAxisRange();
+
+        for (int j = 0, n = plot.getRangeAxisCount(); j < n; j++) {
+            zoomY(
+                plot,
+                plot.getRangeAxis(j),
+                getYBounds(j),
+                valueAxisBounds);
+        }
+    }
+
+
+    protected void zoomX(
+        XYPlot    plot,
+        ValueAxis axis,
+        Bounds    total,
+        Bounds    user
+    ) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("== Zoom X axis ==");
+            logger.debug("    Total axis range  : " + total);
+            logger.debug("    User defined range: " + user);
+        }
+
+        if (user != null) {
+            long min  = total.getLower().longValue();
+            long max  = total.getUpper().longValue();
+            long diff = max > min ? max - min : min - max;
+
+            long newMin = (long) Math.round(min + user.getLower().doubleValue() * diff);
+            long newMax = (long) Math.round(min + user.getUpper().doubleValue() * diff);
+
+            TimeBounds newBounds = new TimeBounds(newMin, newMax);
+
+            logger.debug("    Zoom axis to: " + newBounds);
+
+            newBounds.applyBounds(axis, AXIS_SPACE);
+        }
+        else {
+            logger.debug("No user specified zoom values found!");
+            total.applyBounds(axis, AXIS_SPACE);
+        }
+    }
+
+
+    protected void zoomY(
+        XYPlot    plot,
+        ValueAxis axis,
+        Bounds    total,
+        Bounds    user
+    ) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("== Zoom Y axis ==");
+            logger.debug("    Total axis range  : " + total);
+            logger.debug("    User defined range: " + user);
+        }
+
+        if (user != null) {
+            double min  = total.getLower().doubleValue();
+            double max  = total.getUpper().doubleValue();
+            double diff = max > min ? max - min : min - max;
+
+            double newMin = min + user.getLower().doubleValue() * diff;
+            double newMax = min + user.getUpper().doubleValue() * diff;
+
+            DoubleBounds newBounds = new DoubleBounds(newMin, newMax);
+
+            logger.debug("    Zoom axis to: " + newBounds);
+
+            newBounds.applyBounds(axis, AXIS_SPACE);
+        }
+        else {
+            logger.debug("No user specified zoom values found!");
+            total.applyBounds(axis, AXIS_SPACE);
+        }
+    }
+
+
+    /**
+     * Adjusts the axes of a plot. This method sets the <i>labelFont</i> of the
+     * X axis.
+     *
+     * @param plot The XYPlot of the chart.
+     */
+    protected void adjustAxes(XYPlot plot) {
+        ValueAxis xaxis = plot.getDomainAxis();
+
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return;
+        }
+
+        Font labelFont = new Font(
+            DEFAULT_FONT_NAME,
+            Font.BOLD,
+            getXAxisLabelFontSize());
+
+        xaxis.setLabelFont(labelFont);
+        xaxis.setTickLabelFont(labelFont);
+    }
+
+
+    /**
+     * Do Points out.
+     */
+    protected void doPoints(
+        Object     o,
+        ArtifactAndFacet aandf,
+        Document   theme,
+        boolean    visible,
+        int        axisIndex
+    ) {
+        String seriesName = aandf.getFacetDescription();
+        TimeSeries series = new StyledTimeSeries(seriesName, theme);
+
+        // Add text annotations for single points.
+        List<XYTextAnnotation> xy = new ArrayList<XYTextAnnotation>();
+        HashMap<Day, String> names = new HashMap<Day, String>();
+
+        try {
+            JSONArray points = new JSONArray((String) o);
+            for (int i = 0; i < points.length(); i++) {
+                JSONArray array = points.getJSONArray(i);
+                double x    = array.getDouble(0);
+                double y    = array.getDouble(1);
+                String name = array.getString(2);
+                boolean act = array.getBoolean(3);
+                if (!act) {
+                    continue;
+                }
+                long l = (new Double(x)).longValue();
+                Date date = new Date(l);
+                Day day = new Day(date);
+                series.add(day, y, false);
+                names.put(day, name);
+            }
+        }
+        catch(JSONException e){
+            logger.error("Could not decode json.");
+        }
+
+        TimeSeriesCollection tsc = new TimeSeriesCollection();
+        tsc.addSeries(series);
+        // Add Annotations.
+        for (int i = 0; i < series.getItemCount(); i++) {
+            double x = tsc.getXValue(0, i);
+            double y = tsc.getYValue(0, i);
+            xy.add(new CollisionFreeXYTextAnnotation(
+                       names.get(series.getTimePeriod(i)), x, y));
+        }
+        FLYSAnnotation annotations =
+            new FLYSAnnotation(null, null, null, theme);
+        annotations.setTextAnnotations(xy);
+
+        // Do not generate second legend entry. (null was passed for the aand before).
+        doAnnotations(annotations, null, theme, visible);
+
+        addAxisDataset(tsc, axisIndex, visible);
+    }
+
+    /**
+     * Register annotations like MainValues for later plotting
+     *
+     * @param o     list of annotations (data of facet).
+     * @param facet The facet. This facet does NOT support any data objects. Use
+     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
+     * data.
+     * @param theme   Theme document for given annotations.
+     * @param visible The visibility of the annotations.
+     */
+    protected void doAnnotations(
+        FLYSAnnotation annotations,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean visible
+    ){
+        // Running into trouble here.
+        logger.debug("doAnnotations");
+
+        // Add all annotations to our annotation pool.
+        annotations.setTheme(theme);
+        if (aandf != null) {
+            Facet facet = aandf.getFacet();
+            annotations.setLabel(aandf.getFacetDescription());
+        }
+        else {
+            logger.debug(
+                "Art/Facet for Annotations is null. " +
+                "This should never happen!");
+        }
+
+        addAnnotations(annotations, visible);
+    }
+
+
+
+    /**
+     * Adds annotations to list (if visible is true).
+     */
+    public void addAnnotations(FLYSAnnotation annotation, boolean visible) {
+        if (!visible) {
+            return;
+        }
+
+        if (annotations == null) {
+            annotations = new ArrayList<FLYSAnnotation>();
+        }
+
+        annotations.add(annotation);
+    }
+
+
+    /** Add annotations (Sticky, Text and hyk zones). */
+    public void addAnnotationsToRenderer(XYPlot plot) {
+        logger.debug("XYChartGenerator.addAnnotationsToRenderer");
+
+        if (annotations == null) {
+            logger.debug("XYChartGenerator.addBoxAnnotations: no annotations.");
+            return;
+        }
+
+        // Paints for the boxes/lines.
+        Stroke basicStroke = new BasicStroke(1.0f);
+
+        Paint linePaint = new Color(255,  0,0,60);
+        Paint fillPaint = new Color(0,  255,0,60);
+        Paint tranPaint = new Color(0,    0,0, 0);
+
+        // OPTMIMIZE: Pre-calculate positions
+        Area area = new Area(
+            plot.getDomainAxis(0).getRange(),
+            plot.getRangeAxis().getRange());
+
+        // Walk over all Annotation sets.
+        for (FLYSAnnotation fa: annotations) {
+
+            // Access text styling, if any.
+            Document theme = fa.getTheme();
+            ThemeAccess.TextStyle textStyle = null;
+            ThemeAccess.LineStyle lineStyle = null;
+
+            // Get Themeing information and add legend item.
+            if (theme != null) {
+                ThemeAccess themeAccess = new ThemeAccess(theme);
+                textStyle = themeAccess.parseTextStyle();
+                lineStyle = themeAccess.parseLineStyle();
+                if (fa.getLabel() != null) {
+                    LegendItemCollection lic = new LegendItemCollection();
+                    LegendItemCollection old = plot.getFixedLegendItems();
+                    lic.add(createLegendItem(theme, fa.getLabel()));
+                    // (Re-)Add prior legend entries.
+                    if (old != null) {
+                        old.addAll(lic);
+                    }
+                    else {
+                        old = lic;
+                    }
+                    plot.setFixedLegendItems(old);
+                }
+            }
+
+            // Other Text Annotations (e.g. labels of manual points).
+            for (XYTextAnnotation ta: fa.getTextAnnotations()) {
+                // Style the text.
+                if (textStyle != null) {
+                    textStyle.apply(ta);
+                }
+                ta.setY(area.above(0.05d, ta.getY()));
+                plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
+            }
+        }
+    }
+
+
+    /** Two Ranges that span a rectangular area. */
+    public static class Area {
+        protected Range xRange;
+        protected Range yRange;
+
+        public Area(Range rangeX, Range rangeY) {
+            this.xRange = rangeX;
+            this.yRange = rangeY;
+        }
+
+        public Area(ValueAxis axisX, ValueAxis axisY) {
+            this.xRange = axisX.getRange();
+            this.yRange = axisY.getRange();
+        }
+
+        public double ofLeft(double percent) {
+            return xRange.getLowerBound()
+                + xRange.getLength() * percent;
+        }
+
+        public double ofRight(double percent) {
+            return xRange.getUpperBound()
+                - xRange.getLength() * percent;
+        }
+
+        public double ofGround(double percent) {
+            return yRange.getLowerBound()
+                + yRange.getLength() * percent;
+        }
+
+        public double atTop() {
+            return yRange.getUpperBound();
+        }
+
+        public double atGround() {
+            return yRange.getLowerBound();
+        }
+
+        public double atRight() {
+            return xRange.getUpperBound();
+        }
+
+        public double atLeft() {
+            return xRange.getLowerBound();
+        }
+
+        public double above(double percent, double base) {
+            return base + yRange.getLength() * percent;
+        }
+    }
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/TypeSection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,113 @@
+package de.intevation.flys.exports;
+
+import de.intevation.artifactdatabase.state.Attribute;
+import de.intevation.artifactdatabase.state.DefaultSection;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class TypeSection extends DefaultSection {
+
+    public TypeSection(String key) {
+        super(key);
+    }
+
+
+    public void setStringValue(String key, String value) {
+        if (value == null || value.length() == 0) {
+            return;
+        }
+
+        Attribute attr = getAttribute(key);
+        if (attr == null) {
+            attr = new StringAttribute(key, value, true);
+            addAttribute(key, attr);
+        }
+        else {
+            attr.setValue(value);
+        }
+    }
+
+
+    public String getStringValue(String key) {
+        Attribute attr = getAttribute(key);
+
+        if (attr instanceof StringAttribute) {
+            return (String) attr.getValue();
+        }
+
+        return null;
+    }
+
+
+    public void setIntegerValue(String key, int value) {
+        Attribute attr = getAttribute(key);
+        if (attr == null) {
+            attr = new IntegerAttribute(key, value, true);
+            addAttribute(key, attr);
+        }
+        else {
+            attr.setValue(value);
+        }
+    }
+
+
+    public Integer getIntegerValue(String key) {
+        Attribute attr = getAttribute(key);
+
+        if (attr instanceof IntegerAttribute) {
+            return (Integer) attr.getValue();
+        }
+
+        return null;
+    }
+
+
+
+    public void setDoubleValue(String key, double value) {
+        Attribute attr = getAttribute(key);
+        if (attr == null) {
+            attr = new DoubleAttribute(key, value, true);
+            addAttribute(key, attr);
+        }
+        else {
+            attr.setValue(value);
+        }
+    }
+
+
+    public Double getDoubleValue(String key) {
+        Attribute attr = getAttribute(key);
+
+        if (attr instanceof DoubleAttribute) {
+            return (Double) attr.getValue();
+        }
+
+        return null;
+    }
+
+
+    public void setBooleanValue(String key, boolean value) {
+        Attribute attr = getAttribute(key);
+        if (attr == null) {
+            attr = new BooleanAttribute(key, value, true);
+            addAttribute(key, attr);
+        }
+        else {
+            attr.setValue(value);
+        }
+    }
+
+
+    public Boolean getBooleanValue(String key) {
+        Attribute attr = getAttribute(key);
+
+        if (attr instanceof BooleanAttribute) {
+            return (Boolean) attr.getValue();
+        }
+
+        return null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/VisibleAttribute.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,42 @@
+package de.intevation.flys.exports;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifactdatabase.state.DefaultAttribute;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class VisibleAttribute extends DefaultAttribute {
+
+    protected boolean visible;
+
+
+    public VisibleAttribute(String name, Object value, boolean visible) {
+        super(name, value);
+        this.visible = visible;
+    }
+
+
+    /**
+     * This implementation of Attribute calls DefaultAttribute.toXML() first.
+     * After this, a new Attr <i>display</i> is added to the resulting Node.
+     *
+     * @param parent The parent Node.
+     *
+     * @return a new Node that represents this Attribute.
+     */
+    @Override
+    public Node toXML(Node parent) {
+        Document owner = parent.getOwnerDocument();
+
+        Element ele = (Element) super.toXML(parent);
+        ele.setAttribute("display", String.valueOf(visible));
+
+        return ele;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/WDifferencesCurveGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,132 @@
+package de.intevation.flys.exports;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.title.TextTitle;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WKms;
+
+
+/**
+ * An OutGenerator that generates w differences curves.
+ */
+public class WDifferencesCurveGenerator
+extends      LongitudinalSectionGenerator
+implements   FacetTypes
+{
+    public enum YAXIS {
+        W(0),
+        D(1),
+        Q(2);
+        protected int idx;
+        private YAXIS(int c) {
+           idx = c;
+        }
+    }
+
+    /** The logger that is used in this generator. */
+    private static Logger logger =
+        Logger.getLogger(WDifferencesCurveGenerator.class);
+
+    /** Key for internationalized title of WDiff charts. */
+    public final static String I18N_WDIFF_TITLE = "chart.w_differences.title";
+
+    /** Default for internationalized title (when no translation found). */
+    public final static String I18N_WDIFF_TITLE_DEFAULT = "Differences";
+
+    public final static String I18N_WDIFF_SUBTITLE =
+        "chart.w_differences.subtitle";
+
+
+    @Override
+    protected YAxisWalker getYAxisWalker() {
+        return new YAxisWalker() {
+            @Override
+            public int length() {
+                return YAXIS.values().length;
+            }
+
+            @Override
+            public String getId(int idx) {
+                YAXIS[] yaxes = YAXIS.values();
+                return yaxes[idx].toString();
+            }
+        };
+    }
+
+
+    /**
+     * Get internationalized title for chart.
+     * @return internationalized Chart title.
+     */
+    @Override
+    public String getDefaultChartTitle() {
+        return msg(I18N_WDIFF_TITLE, I18N_WDIFF_TITLE_DEFAULT);
+    }
+
+
+    @Override
+    protected String getDefaultChartSubtitle() {
+        return getRiverName();
+    }
+
+
+    /**
+     * Gets key to look up internationalized String for the charts subtitle.
+     * @return key to look up translated subtitle.
+     */
+    @Override
+    protected String getChartSubtitleKey() {
+        return I18N_WDIFF_SUBTITLE;
+    }
+
+
+    /**
+     * Add (internationalized) subtitle to chart.
+     * Overridden to avoid trying to access the range of masterartifact.
+     * @see getChartSubtitleKey
+     */
+    @Override
+    protected void addSubtitles(JFreeChart chart) {
+        String subtitle = getChartSubtitle();
+
+        if (subtitle != null && subtitle.length() > 0) {
+            chart.addSubtitle(new TextTitle(subtitle));
+        }
+    }
+
+
+    /**
+     * 
+     */
+    @Override
+    public JFreeChart generateChart() {
+        JFreeChart chart = super.generateChart();
+        if (chart != null && chart.getPlot() != null) {
+            XYPlot plot = (XYPlot) chart.getPlot();
+            plot.setRangeZeroBaselineVisible(true);
+        }
+        return chart;
+    }
+
+
+    /**
+     * Get name of series (displayed in legend).
+     * @return name of the series.
+     */
+    protected String getSeriesName(WKms wqkms, String mode) {
+        String name   = wqkms.getName();
+        String prefix = (name != null && name.indexOf(mode) >= 0)
+                      ? null
+                      : mode;
+
+        return (prefix != null && prefix.length() > 0)
+                ? prefix + "(" + name +")"
+                : name;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/WDifferencesCurveInfoGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,15 @@
+package de.intevation.flys.exports;
+
+
+/**
+ * A ChartInfoGenerator that generates meta information for specific
+ * w differences.
+ */
+public class WDifferencesCurveInfoGenerator
+extends      ChartInfoGenerator
+{
+    public WDifferencesCurveInfoGenerator() {
+        super(new WDifferencesCurveGenerator());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/WDifferencesExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,258 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Date;
+import java.text.DateFormat;
+import java.util.Locale;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+import net.sf.jasperreports.engine.JRException;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.flys.artifacts.WINFOArtifact;
+
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WKmsJRDataSource;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.Formatter;
+
+/**
+ * (CSV)Exporter for WDifferences.
+ */
+public class WDifferencesExporter extends AbstractExporter {
+
+    /** The logger used in this exporter. */
+    private static Logger logger = Logger.getLogger(WDifferencesExporter.class);
+
+
+    public static final String WDIFF_CSV_KM_HEADER =
+        "export.w_differences.csv.header.km";
+
+    public static final String WDIFF_CSV_W_HEADER =
+        "export.w_differences.csv.header.w";
+
+    public static final String WDIFF_DEFAULT_CSV_KM_HEADER = "Fluss-Km";
+    public static final String WDIFF_DEFAULT_CSV_W_HEADER  = "m";
+
+    public static final String PDF_HEADER_MODE = "export.wdifferences.pdf.mode";
+    public static final String JASPER_FILE = "export.wdifferences.pdf.file";
+
+    /** The storage that contains all WKms objects for the different facets. */
+    protected List<WKms[]> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("WDifferencesExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WKms[]>();
+    }
+
+
+    /**
+     * Genereate data in csv format.
+     */
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("WDifferencesExporter.generate");
+
+        if (facet == null) {
+            throw new IOException("invalid (null) facet for exporter");
+        }
+        else if (facet.equals(AbstractExporter.FACET_CSV)) {
+            generateCSV();
+        }
+        else if (facet.equals(AbstractExporter.FACET_PDF)) {
+            generatePDF();
+        }
+        else {
+            throw new IOException("invalid facet (" + facet + ") for exporter");
+        }
+    }
+
+
+    /**
+     * Adds given data.
+     * @param d either a WKms or a CalculationResult to add to data.
+     */
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult)d).getData();
+            if (d instanceof WKms []) {
+                data.add((WKms [])d);
+            }
+        }
+        else if (d instanceof WKms) {
+            data.add(new WKms[] { (WKms) d });
+        }
+    }
+
+
+    /**
+     * Lets writer write all data (including header).
+     * @param writer Writer to write data with.
+     */
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("WDifferencesExporter.writeData");
+
+        writeCSVHeader(writer);
+
+        for (WKms[] tmp: data) {
+            for (WKms wkms: tmp) {
+                wKms2CSV(writer, wkms);
+            }
+        }
+    }
+
+
+    /**
+     * Lets csvwriter write the header (first line in file).
+     * @param write Writer to write header with.
+     */
+    protected void writeCSVHeader(CSVWriter writer) {
+        logger.info("WDifferencesExporter.writeCSVHeader");
+
+        writer.writeNext(new String[] {
+            msg(WDIFF_CSV_KM_HEADER, WDIFF_DEFAULT_CSV_KM_HEADER),
+            msg(WDIFF_CSV_W_HEADER, WDIFF_DEFAULT_CSV_W_HEADER)
+        });
+    }
+
+
+    protected void wKms2CSV(CSVWriter writer, WKms wkms) {
+        logger.debug("WDifferencesExporter.wKms2CSV");
+
+        NumberFormat kmf  = getKmFormatter();
+        NumberFormat wf   = getWFormatter();
+        int          size = wkms.size();
+
+        for (int i = 0; i < size; i ++) {
+
+            writer.writeNext(new String[] {
+                kmf.format(wkms.getKm(i)),
+                wf.format(wkms.getW(i))
+            });
+        }
+    }
+
+
+    /**
+     * Returns the number formatter for kilometer values.
+     *
+     * @return the number formatter for kilometer values.
+     */
+    protected NumberFormat getKmFormatter() {
+        return Formatter.getWaterlevelKM(context);
+    }
+
+
+    /**
+     * Returns the number formatter for W values.
+     *
+     * @return the number formatter for W values.
+     */
+    protected NumberFormat getWFormatter() {
+        return Formatter.getWaterlevelW(context);
+    }
+
+
+    @Override
+    protected void writePDF(OutputStream out) {
+        WKmsJRDataSource source = createJRData();
+
+        String jasperFile = Resources.getMsg(
+                                context.getMeta(),
+                                JASPER_FILE,
+                                "/jasper/wdifferences_en.jasper");
+        String confPath = Config.getConfigDirectory().toString();
+
+        Map parameters = new HashMap();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            JasperPrint print = JasperFillManager.fillReport(
+                confPath + jasperFile,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, out);
+        }
+        catch(JRException je) {
+            logger.warn("Error generating PDF Report!");
+            je.printStackTrace();
+        }
+    }
+
+    protected WKmsJRDataSource createJRData() {
+        WKmsJRDataSource source = new WKmsJRDataSource();
+
+        addMetaData(source);
+        for (WKms[] tmp: data) {
+            for (WKms wkms: tmp) {
+                addWKmsData(source, wkms);
+            }
+        }
+        return source;
+    }
+
+
+    protected void addMetaData(WKmsJRDataSource source) {
+        CallMeta meta = context.getMeta();
+
+        WINFOArtifact flys = (WINFOArtifact) master;
+
+        source.addMetaData ("river", FLYSUtils.getRivername(flys));
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        source.addMetaData("date", df.format(new Date()));
+
+        String differences = FLYSUtils.getWDifferences(flys, context);
+        source.addMetaData("differences", differences);
+
+        source.addMetaData("calculation", Resources.getMsg(
+                                            locale,
+                                            PDF_HEADER_MODE,
+                                            "W Differences"));
+    }
+
+    protected void addWKmsData(WKmsJRDataSource source, WKms wkms) {
+        NumberFormat kmf  = getKmFormatter();
+        NumberFormat wf   = getWFormatter();
+        int          size = wkms.size();
+
+        for (int i = 0; i < size; i ++) {
+
+            source.addData(new String[] {
+                kmf.format(wkms.getKm(i)),
+                wf.format(wkms.getW(i))
+            });
+        }
+    }
+
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/WaterlevelExporter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,719 @@
+package de.intevation.flys.exports;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import au.com.bytecode.opencsv.CSVWriter;
+
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+import net.sf.jasperreports.engine.JRException;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.flys.model.Gauge;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.model.CalculationResult;
+import de.intevation.flys.artifacts.model.WQCKms;
+import de.intevation.flys.artifacts.model.WQKms;
+import de.intevation.flys.artifacts.model.WKmsJRDataSource;
+import de.intevation.flys.artifacts.resources.Resources;
+
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.FLYSUtils.WQ_MODE;
+import de.intevation.flys.utils.Formatter;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WaterlevelExporter extends AbstractExporter {
+
+    /** The logger used in this exporter.*/
+    private static Logger logger = Logger.getLogger(WaterlevelExporter.class);
+
+    public static final String FACET_WST = "wst";
+
+    public static final String CSV_KM_HEADER =
+        "export.waterlevel.csv.header.km";
+
+    public static final String CSV_W_HEADER =
+        "export.waterlevel.csv.header.w";
+
+    public static final String CSV_Q_HEADER =
+        "export.waterlevel.csv.header.q";
+
+    public static final String CSV_Q_DESC_HEADER =
+        "export.waterlevel.csv.header.q.desc";
+
+    public static final String CSV_W_DESC_HEADER =
+        "export.waterlevel.csv.header.w.desc";
+
+    public static final String CSV_LOCATION_HEADER =
+        "export.waterlevel.csv.header.location";
+
+    public static final String CSV_GAUGE_HEADER =
+        "export.waterlevel.csv.header.gauge";
+
+    public static final String CSV_META_RESULT =
+        "export.waterlevel.csv.meta.result";
+
+    public static final String CSV_META_CREATION =
+        "export.waterlevel.csv.meta.creation";
+
+    public static final String CSV_META_CALCULATIONBASE =
+        "export.waterlevel.csv.meta.calculationbase";
+
+    public static final String CSV_META_RIVER =
+        "export.waterlevel.csv.meta.river";
+
+    public static final String CSV_META_RANGE =
+        "export.waterlevel.csv.meta.range";
+
+    public static final String CSV_META_GAUGE =
+        "export.waterlevel.csv.meta.gauge";
+
+    public static final String CSV_META_Q =
+        "export.waterlevel.csv.meta.q";
+
+    public static final String CSV_META_W =
+        "export.waterlevel.csv.meta.w";
+
+    public static final String CSV_NOT_IN_GAUGE_RANGE =
+        "export.waterlevel.csv.not.in.gauge.range";
+
+
+    public static final Pattern NUMBERS_PATTERN =
+        Pattern.compile("\\D*(\\d++.\\d*)\\D*");
+
+    public static final String DEFAULT_CSV_KM_HEADER       = "Fluss-Km";
+    public static final String DEFAULT_CSV_W_HEADER        = "W [NN + m]";
+    public static final String DEFAULT_CSV_Q_HEADER        = "Q [m\u00b3/s]";
+    public static final String DEFAULT_CSV_Q_DESC_HEADER   = "Bezeichnung";
+    public static final String DEFAULT_CSV_W_DESC_HEADER   = "W/Pegel [cm]";
+    public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage";
+    public static final String DEFAULT_CSV_GAUGE_HEADER    = "Bezugspegel";
+    public static final String DEFAULT_CSV_NOT_IN_GAUGE_RANGE =
+        "außerhalb des gewählten Bezugspegels";
+
+    public static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode";
+    public static final String JASPER_FILE     = "export.waterlevel.pdf.file";
+
+    /** The storage that contains all WQKms objects for the different facets.*/
+    protected List<WQKms[]> data;
+
+
+    public void init(Document request, OutputStream out, CallContext context) {
+        logger.debug("WaterlevelExporter.init");
+
+        super.init(request, out, context);
+
+        this.data = new ArrayList<WQKms[]>();
+    }
+
+
+    @Override
+    public void generate()
+    throws IOException
+    {
+        logger.debug("WaterlevelExporter.generate");
+
+        if (facet != null && facet.equals(AbstractExporter.FACET_CSV)) {
+            generateCSV();
+        }
+        else if (facet != null && facet.equals(FACET_WST)) {
+            generateWST();
+        }
+        else if (facet != null && facet.equals(AbstractExporter.FACET_PDF)) {
+            generatePDF();
+        }
+        else {
+            throw new IOException("invalid facet for exporter");
+        }
+    }
+
+
+    @Override
+    protected void addData(Object d) {
+        if (d instanceof CalculationResult) {
+            d = ((CalculationResult)d).getData();
+            if (d instanceof WQKms []) {
+                data.add((WQKms [])d);
+            }
+        }
+    }
+
+
+    /**
+     * This method is used to prepare the column titles of waterlevel exports.
+     * Titles in this export include the Q value. If a Q value matches a named
+     * main value (as HQ100 or MNQ) this named main value should be used as
+     * title. This method resets the name of the <i>wqkms</i> object if such
+     * named main value fits to the chosen Q.
+     *
+     * @param winfo A WINFO Artifact.
+     * @param wqkms A WQKms object that should be prepared.
+     */
+    protected String getColumnTitle(WINFOArtifact winfo, WQKms wqkms) {
+        logger.debug("WaterlevelExporter.prepareNamedValue");
+
+        String name = wqkms.getName();
+
+        logger.debug("Name of WQKms = '" + name + "'");
+
+        if (name.indexOf("W=") >= 0) {
+            return name;
+        }
+
+        Matcher m = NUMBERS_PATTERN.matcher(name);
+
+        if (m.matches()) {
+            String raw = m.group(1);
+
+            try {
+                double v = Double.valueOf(raw);
+
+                String nmv = FLYSUtils.getNamedMainValue(winfo, v);
+
+                if (nmv != null && nmv.length() > 0) {
+                    nmv  = FLYSUtils.stripNamedMainValue(nmv);
+                    nmv += "=" + String.valueOf(v);
+                    logger.debug("Set named main value '" + nmv + "'");
+
+                    return nmv;
+                }
+            }
+            catch (NumberFormatException nfe) {
+                // do nothing here
+            }
+        }
+
+        return name;
+    }
+
+
+    protected String getCSVRowTitle(WINFOArtifact winfo, WQKms wqkms) {
+        logger.debug("WaterlevelExporter.prepareNamedValue");
+
+        String name = wqkms.getName();
+
+        logger.debug("Name of WQKms = '" + name + "'");
+
+        WQ_MODE wqmode = FLYSUtils.getWQMode(winfo);
+
+        if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.QGAUGE) {
+            return localizeWQKms(winfo, wqkms);
+        }
+
+        Double v = wqkms.getRawValue();
+
+        String nmv = FLYSUtils.getNamedMainValue(winfo, v);
+
+        if (nmv != null && nmv.length() > 0) {
+            nmv = FLYSUtils.stripNamedMainValue(nmv);
+            logger.debug("Set named main value '" + nmv + "'");
+
+            return nmv;
+        }
+
+        return localizeWQKms(winfo, wqkms);
+    }
+
+
+    protected String localizeWQKms(WINFOArtifact winfo, WQKms wqkms) {
+        WQ_MODE wqmode   = FLYSUtils.getWQMode(winfo);
+        Double  rawValue = wqkms.getRawValue();
+
+        if (rawValue == null) {
+            return wqkms.getName();
+        }
+
+        NumberFormat nf = Formatter.getRawFormatter(context);
+
+        if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.WGAUGE) {
+            return "W=" + nf.format(rawValue);
+        }
+        else {
+            return "Q=" + nf.format(rawValue);
+        }
+    }
+
+
+    @Override
+    protected void writeCSVData(CSVWriter writer) {
+        logger.info("WaterlevelExporter.writeData");
+
+        WQ_MODE mode     = FLYSUtils.getWQMode((FLYSArtifact)master);
+        boolean atGauge  = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
+        boolean isQ      = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
+        FLYSUtils.WQ_INPUT input
+            = FLYSUtils.getWQInputMode((FLYSArtifact)master);
+        boolean isRange = input == FLYSUtils.WQ_INPUT.RANGE;
+
+        writeCSVMeta(writer);
+        writeCSVHeader(writer, atGauge, isQ);
+
+        for (WQKms[] tmp: data) {
+            for (WQKms wqkms: tmp) {
+                wQKms2CSV(writer, wqkms, atGauge, isQ, isRange);
+            }
+        }
+    }
+
+
+    protected void writeCSVMeta(CSVWriter writer) {
+        logger.info("WaterlevelExporter.writeCSVMeta");
+
+        CallMeta meta = context.getMeta();
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        writer.writeNext(new String[] {
+            Resources.getMsg(
+                meta,
+                CSV_META_RESULT,
+                CSV_META_RESULT,
+                new Object[] { FLYSUtils.getRivername(flys) })
+        });
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        writer.writeNext(new String[] {
+            Resources.getMsg(
+                meta,
+                CSV_META_CREATION,
+                CSV_META_CREATION,
+                new Object[] { df.format(new Date()) })
+        });
+
+        writer.writeNext(new String[] {
+            Resources.getMsg(
+                meta,
+                CSV_META_CALCULATIONBASE,
+                CSV_META_CALCULATIONBASE,
+                new Object[] { "" }) // TODO what is required at this place?
+        });
+
+        writer.writeNext(new String[] {
+            Resources.getMsg(
+                meta,
+                CSV_META_RIVER,
+                CSV_META_RIVER,
+                new Object[] { FLYSUtils.getRivername(flys) })
+        });
+
+        double[] kms = FLYSUtils.getKmRange(flys);
+        writer.writeNext(new String[] {
+            Resources.getMsg(
+                meta,
+                CSV_META_RANGE,
+                CSV_META_RANGE,
+                new Object[] { kms[0], kms[kms.length-1] })
+        });
+
+        writer.writeNext(new String[] {
+            Resources.getMsg(
+                meta,
+                CSV_META_GAUGE,
+                CSV_META_GAUGE,
+                new Object[] { FLYSUtils.getGaugename(flys) })
+        });
+
+        FLYSUtils.WQ_MODE wq = FLYSUtils.getWQMode(flys);
+        if (wq == FLYSUtils.WQ_MODE.QFREE || wq == FLYSUtils.WQ_MODE.QGAUGE) {
+            double[] qs  = FLYSUtils.getQs(flys);
+            FLYSUtils.WQ_INPUT input = FLYSUtils.getWQInputMode(flys);
+
+            String data = "";
+
+            if ((input == FLYSUtils.WQ_INPUT.ADAPTED ||
+                input == FLYSUtils.WQ_INPUT.RANGE) &&
+                qs != null && qs.length > 0)
+            {
+                data = String.valueOf(qs[0]);
+                data += " - " + String.valueOf(qs[qs.length-1]);
+            }
+            else if (input == FLYSUtils.WQ_INPUT.SINGLE && qs != null){
+                data = String.valueOf(qs[0]);
+                for (int i = 1; i < qs.length; i++) {
+                    data += ", " + String.valueOf(qs[i]);
+                }
+            }
+            else {
+                logger.warn("Could not determine Q range!");
+            }
+
+            writer.writeNext(new String[] {
+                Resources.getMsg(
+                    meta,
+                    CSV_META_Q,
+                    CSV_META_Q,
+                    new Object[] {data})
+            });
+        }
+        else {
+            double[] ws = FLYSUtils.getWs(flys);
+
+            String lower = "";
+            String upper = "";
+
+            if (ws != null && ws.length > 0) {
+                lower = String.valueOf(ws[0]);
+                upper = String.valueOf(ws[ws.length-1]);
+            }
+            else {
+                logger.warn("Could not determine W range!");
+            }
+
+            writer.writeNext(new String[] {
+                Resources.getMsg(
+                    meta,
+                    CSV_META_W,
+                    CSV_META_W,
+                    new Object[] { lower, upper })
+            });
+        }
+
+        writer.writeNext(new String[] { "" });
+    }
+
+
+    protected void writeCSVHeader(
+        CSVWriter writer,
+        boolean   atGauge,
+        boolean   isQ
+    ) {
+        logger.info("WaterlevelExporter.writeCSVHeader");
+
+        if (atGauge) {
+            writer.writeNext(new String[] {
+                msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
+                msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+                msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
+                (isQ
+                    ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER)
+                    : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)),
+                msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER),
+                msg(CSV_GAUGE_HEADER, DEFAULT_CSV_GAUGE_HEADER)
+            });
+        }
+        else {
+            writer.writeNext(new String[] {
+                msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
+                msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
+                msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
+                msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER)
+            });
+        }
+    }
+
+
+    protected void wQKms2CSV(
+        CSVWriter writer,
+        WQKms     wqkms,
+        boolean   atGauge,
+        boolean   isQ,
+        boolean   isRange
+    ) {
+        logger.debug("WaterlevelExporter.wQKms2CSV");
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        int      size   = wqkms.size();
+        double[] result = new double[3];
+
+        FLYSArtifact flys       = (FLYSArtifact) master;
+        Gauge        gauge      = FLYSUtils.getGauge(flys);
+        String       gaugeName  = gauge.getName();
+        String       desc       = "";
+        String       notinrange = msg(
+            CSV_NOT_IN_GAUGE_RANGE,
+            DEFAULT_CSV_NOT_IN_GAUGE_RANGE);
+
+        double a = gauge.getRange().getA().doubleValue();
+        double b = gauge.getRange().getB().doubleValue();
+
+        if (flys instanceof WINFOArtifact && isQ) {
+            desc = getCSVRowTitle((WINFOArtifact)flys, wqkms);
+        }
+        else if (!isQ) {
+            Double value = FLYSUtils.getValueFromWQ(wqkms);
+            desc         = value != null
+                ? Formatter.getWaterlevelW(context).format(value) : null;
+        }
+
+        long startTime = System.currentTimeMillis();
+
+        String colDesc = !isRange
+            ? FLYSUtils.getNamedMainValue((WINFOArtifact)flys, wqkms.getRawValue())
+            : desc;
+
+        for (int i = 0; i < size; i ++) {
+            result = wqkms.get(i, result);
+
+            if (atGauge) {
+                writer.writeNext(new String[] {
+                    kmf.format(result[2]),
+                    wf.format(result[0]),
+                    qf.format(result[1]),
+                    colDesc,
+                    FLYSUtils.getLocationDescription(flys, result[2]),
+                    result[2] >= a && result[2] <= b
+                        ? gaugeName
+                        : notinrange
+                });
+            }
+            else {
+                writer.writeNext(new String[] {
+                    kmf.format(result[2]),
+                    wf.format(result[0]),
+                    qf.format(result[1]),
+                    FLYSUtils.getLocationDescription(flys, result[2])
+                });
+            }
+        }
+
+        long stopTime = System.currentTimeMillis();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Writing CSV took " +
+                (float)(stopTime-startTime)/1000f + " secs.");
+        }
+    }
+
+
+    /**
+     * Generates the output in WST format.
+     */
+    protected void generateWST()
+    throws    IOException
+    {
+        logger.info("WaterlevelExporter.generateWST");
+
+        int cols = data.get(0).length;
+        WstWriter writer = new WstWriter(cols);
+
+        writeWSTData(writer);
+
+        writer.write(out);
+    }
+
+
+    protected void writeWSTData(WstWriter writer) {
+        logger.debug("WaterlevelExporter.writeWSTData");
+
+        double[] result = new double[4];
+
+        for (WQKms[] tmp: data) {
+            for (WQKms wqkms: tmp) {
+                int size = wqkms != null ? wqkms.size() : 0;
+
+                addWSTColumn(writer, wqkms);
+
+                for (int i = 0; i < size; i++) {
+                    result = wqkms.get(i, result);
+
+                    writer.add(result);
+                }
+
+                if (wqkms instanceof WQCKms) {
+                    addWSTColumn(writer, wqkms);
+
+                    for (int c = 0; c < size; c++) {
+                        result = wqkms.get(c, result);
+
+                        writer.addCorrected(result);
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * This method is used to register a new column at <i>writer</i>. The name /
+     * title of the column depends on the Q or W value of <i>wqkms</i>. If a Q
+     * was selected and the Q fits to a named main value, the title is set to
+     * the named main value. Otherwise, the name returned by
+     * <i>WQKms.getName()</i> is set.
+     *
+     * @param writer The WstWriter.
+     * @param wqkms The new WST column.
+     */
+    protected void addWSTColumn(WstWriter writer, WQKms wqkms) {
+        if (master instanceof WINFOArtifact) {
+            writer.addColumn(getColumnTitle((WINFOArtifact) master, wqkms));
+        }
+        else {
+            writer.addColumn(wqkms.getName());
+        }
+    }
+
+
+    /**
+     *
+     */
+    @Override
+    protected void writePDF(OutputStream out) {
+        logger.debug("write PDF");
+        WKmsJRDataSource source = createJRData();
+
+        String jasperFile = Resources.getMsg(
+                                context.getMeta(),
+                                JASPER_FILE,
+                                "/jasper/waterlevel_en.jasper");
+        String confPath = Config.getConfigDirectory().toString();
+
+
+        Map parameters = new HashMap();
+        parameters.put("ReportTitle", "Exported Data");
+        try {
+            JasperPrint print = JasperFillManager.fillReport(
+                confPath + jasperFile,
+                parameters,
+                source);
+            JasperExportManager.exportReportToPdfStream(print, out);
+        }
+        catch(JRException je) {
+            logger.warn("Error generating PDF Report!");
+            je.printStackTrace();
+        }
+    }
+
+    protected WKmsJRDataSource createJRData() {
+        WKmsJRDataSource source = new WKmsJRDataSource();
+
+        WQ_MODE mode    = FLYSUtils.getWQMode((FLYSArtifact)master);
+        boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
+        boolean isQ     = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
+
+        addMetaData(source);
+        for (WQKms[] tmp: data) {
+            for (WQKms wqkms: tmp) {
+                addWKmsData(wqkms, atGauge, isQ, source);
+            }
+        }
+        return source;
+    }
+
+    protected void addMetaData(WKmsJRDataSource source) {
+        CallMeta meta = context.getMeta();
+
+        FLYSArtifact flys = (FLYSArtifact) master;
+
+        source.addMetaData ("river", FLYSUtils.getRivername(flys));
+
+        Locale locale = Resources.getLocale(meta);
+        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+
+        source.addMetaData("date", df.format(new Date()));
+
+        double[] kms = FLYSUtils.getKmRange(flys);
+        source.addMetaData("range", kms[0] + " - " + kms[kms.length-1]);
+
+        source.addMetaData("gauge", FLYSUtils.getGaugename(flys));
+
+        source.addMetaData("calculation", Resources.getMsg(
+                                            locale,
+                                            PDF_HEADER_MODE,
+                                            "Waterlevel"));
+    }
+
+    protected void addWKmsData(
+        WQKms wqkms,
+        boolean atGauge,
+        boolean isQ,
+        WKmsJRDataSource source)
+    {
+        logger.debug("WaterlevelExporter.addWKmsData");
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        int      size   = wqkms.size();
+        double[] result = new double[3];
+
+        FLYSArtifact flys       = (FLYSArtifact) master;
+        Gauge        gauge      = FLYSUtils.getGauge(flys);
+        String       gaugeName  = gauge.getName();
+        String       desc       = "";
+        String       notinrange = msg(
+            CSV_NOT_IN_GAUGE_RANGE,
+            DEFAULT_CSV_NOT_IN_GAUGE_RANGE);
+
+        double a = gauge.getRange().getA().doubleValue();
+        double b = gauge.getRange().getB().doubleValue();
+
+        if (flys instanceof WINFOArtifact && isQ) {
+            desc = getCSVRowTitle((WINFOArtifact)flys, wqkms);
+        }
+        else if (!isQ) {
+            Double value = FLYSUtils.getValueFromWQ(wqkms);
+            desc         = value != null
+                ? Formatter.getWaterlevelW(context).format(value) : null;
+        }
+
+        long startTime = System.currentTimeMillis();
+
+        for (int i = 0; i < size; i ++) {
+            result = wqkms.get(i, result);
+
+            if (atGauge) {
+                source.addData(new String[] {
+                    kmf.format(result[2]),
+                    wf.format(result[0]),
+                    qf.format(result[1]),
+                    desc,
+                    FLYSUtils.getLocationDescription(flys, result[2]),
+                    result[2] >= a && result[2] <= b
+                        ? gaugeName
+                        : notinrange
+                });
+            }
+            else {
+                source.addData(new String[] {
+                    kmf.format(result[2]),
+                    wf.format(result[0]),
+                    qf.format(result[1]),
+                    desc,
+                    FLYSUtils.getLocationDescription(flys, result[2]),
+                    result[2] >= a && result[2] <= b
+                        ? gaugeName
+                        : notinrange
+                });
+            }
+        }
+
+        long stopTime = System.currentTimeMillis();
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Writing PDF data took " +
+                (float)(stopTime-startTime)/1000f + " secs.");
+        }
+
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/WstWriter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,237 @@
+package de.intevation.flys.exports;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.model.WstLine;
+
+
+/**
+ * A writer that creates WSTs.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WstWriter {
+
+    /** The logger used in this class.*/
+    private static Logger logger = Logger.getLogger(WstWriter.class);
+
+
+    /** The default unit that is written into the header of the WST.*/
+    public static final String DEFAULT_UNIT = "Wassserstand [NN + m]";
+
+
+    /** The lines that need to be included for the export.*/
+    protected Map<Double, WstLine> lines;
+
+    /** The column names.*/
+    protected List<String> columnNames;
+
+    /** The locale used to format the values.*/
+    protected Locale locale;
+
+    /** The number of discharge columns.*/
+    protected int cols;
+
+    /** The last Q values.*/
+    protected double[] qs;
+
+
+
+    /**
+     * This constructor creates a new WstWriter with an OutputStream and a
+     * number of Q columns.
+     *
+     * @param out The output stream where the WST is written to.
+     * @param cols The number of columns of the resulting WST.
+     */
+    public WstWriter(int cols) {
+        this.columnNames = new ArrayList<String>(cols);
+        this.lines       = new HashMap<Double, WstLine>();
+        this.qs          = new double[cols];
+        this.locale      = Locale.US;
+    }
+
+
+    /**
+     * This method is used to create the WST from the data that has been
+     * inserted using add(double[]) before.
+     */
+    public void write(OutputStream out) {
+        logger.info("WstWriter.write");
+
+        PrintWriter writer = new PrintWriter(
+            new BufferedWriter(
+                new OutputStreamWriter(out)));
+
+        this.qs = new double[cols];
+
+        writeHeader(writer);
+
+        Collection<WstLine> collection = new TreeMap(lines).values();
+
+        for (WstLine line: collection) {
+            writeWLine(writer, line);
+        }
+
+        writer.flush();
+        writer.close();
+    }
+
+
+    /**
+     * This method is used to add a new line to the WST.
+     *
+     * @param wqkms A 3dim double array with [W,Q, KM].
+     */
+    public void add(double[] wqkms) {
+        Double km = wqkms[2];
+
+        WstLine line = lines.get(km);
+
+        if (line == null) {
+            line = new WstLine(km.doubleValue());
+            lines.put(km, line);
+        }
+
+        line.add(wqkms[0], wqkms[1]);
+    }
+
+
+    public void addCorrected(double[] wqckms) {
+        Double km = wqckms[2];
+
+        WstLine line = lines.get(km);
+
+        if (line == null) {
+            line = new WstLine(km.doubleValue());
+            lines.put(km, line);
+        }
+
+        line.add(wqckms[3], wqckms[1]);
+    }
+
+
+    /**
+     * Adds a further column name.
+     *
+     * @param name The name of the new column.
+     */
+    public void addColumn(String name) {
+        if (name != null) {
+            cols++;
+
+            String basename = name;
+
+            int i = 0;
+            while (columnNames.contains(name)) {
+                name = basename + "_" + i++;
+
+                if (name.length() > 9) {
+                    name = name.substring(name.length() - 9);
+                }
+            }
+
+            columnNames.add(name);
+        }
+    }
+
+
+    /**
+     * This method writes the header of the WST.
+     *
+     * @param writer The PrintWriter that creates the output.
+     */
+    protected void writeHeader(PrintWriter writer) {
+        logger.debug("WstWriter.writeHeader");
+
+        writer.println(cols);
+        writer.print("        ");
+
+        for (String columnName: columnNames) {
+            writer.printf(locale, "%9s", columnName);
+        }
+
+        writer.println();
+
+        writer.write("*   KM     ");
+        writer.write(DEFAULT_UNIT);
+        writer.println();
+    }
+
+
+    /**
+     * This method writes a line with W values and a certain kilometer.
+     *
+     * @param writer The PrintWriter that is used to create the output.
+     * @param line The WstLine that should be written to the output.
+     */
+    protected void writeWLine(PrintWriter writer, WstLine line) {
+        double   km  = line.getKm();
+        double[] qs  = line.getQs();
+        int      num = line.getSize();
+
+        if (dischargesChanged(qs)) {
+            writeQLine(writer, qs);
+        }
+
+        writer.printf(locale, "%8.3f", km);
+
+        for (int i = 0; i < num; i++) {
+            writer.printf(locale, "%9.2f", line.getW(i));
+        }
+
+        writer.println();
+    }
+
+
+    /**
+     * Writes a discharge line (Q values) into a WST.
+     *
+     * @param qs the Q values for the next range.
+     */
+    protected void writeQLine(PrintWriter writer, double[] qs) {
+        writer.write("*\u001f      ");
+
+        for (int i = 0; i < qs.length; i++) {
+            this.qs[i] = qs[i];
+
+            writer.printf(locale, "%9.2f", qs[i]);
+        }
+
+        writer.println();
+    }
+
+
+    /**
+     * This method determines if a Q has changed from the last line to the
+     * current one.
+     *
+     * @param newQs The Q values of the next line.
+     *
+     * @return true, if a Q value have changed, otherwise false.
+     */
+    protected boolean dischargesChanged(double[] newQs) {
+        // XXX maybe there is a way to do this faster
+        for (int i = 0; i < cols; i++) {
+            if (Math.abs(newQs[i] - qs[i]) >= 0.001) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1347 @@
+package de.intevation.flys.exports;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Stroke;
+
+import java.text.NumberFormat;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.LegendItem;
+import org.jfree.chart.LegendItemCollection;
+import org.jfree.chart.annotations.XYBoxAnnotation;
+import org.jfree.chart.annotations.XYLineAnnotation;
+import org.jfree.chart.annotations.XYTextAnnotation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.Range;
+import org.jfree.data.general.Series;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.data.xy.XYDataset;
+
+import org.jfree.ui.TextAnchor;
+
+import de.intevation.artifactdatabase.state.ArtifactAndFacet;
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.DoubleBounds;
+import de.intevation.flys.jfree.FLYSAnnotation;
+import de.intevation.flys.jfree.StickyAxisAnnotation;
+import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
+import de.intevation.flys.jfree.StyledAreaSeriesCollection;
+import de.intevation.flys.jfree.StyledXYSeries;
+
+import de.intevation.flys.utils.ThemeAccess;
+
+import de.intevation.flys.artifacts.model.HYKFactory;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+
+/**
+ * An abstract base class for creating XY charts.
+ *
+ * With respect to datasets, ranges and axis, there are following requirements:
+ * <ul>
+ *   <li> First in, first drawn: "Early" datasets should be of lower Z-Oder
+ *        than later ones (only works per-axis). </li>
+ *   <li> Visible axis should initially show the range of all datasets that
+ *        show data for this axis (even invisible ones). Motivation: Once
+ *        a dataset (theme) has been activated, it should be on screen. </li>
+ *   <li> There should always be a Y-Axis on the "left". </li>
+ * </ul>
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class XYChartGenerator extends ChartGenerator {
+
+    // TODO Consider storing the renderer here.
+    private class XYAxisDataset implements AxisDataset {
+        /** Symbolic integer, but also coding the priority (0 goes first). */
+        protected int axisSymbol;
+        /** List of assigned datasets (in order). */
+        protected List<XYDataset> datasets;
+        /** Range to use to include all given datasets. */
+        protected Range range;
+        /** Index of axis in plot. */
+        protected int plotAxisIndex;
+
+        /** Create AxisDataset. */
+        public XYAxisDataset(int symb) {
+            this.axisSymbol = symb;
+            datasets        = new ArrayList<XYDataset>();
+        }
+
+        /** Merge (or create given range with range so far (if any). */
+        private void mergeRanges(Range subRange) {
+            // Avoid merging NaNs, as they take min/max place forever.
+            if (subRange == null ||
+                Double.isNaN(subRange.getLowerBound()) ||
+                Double.isNaN(subRange.getUpperBound())) {
+                return;
+            }
+            if (range == null) {
+                range = subRange;
+                return;
+            }
+            range = Range.combine(range, subRange);
+        }
+
+
+        /** Add a dataset to internal list for this axis. */
+        @Override
+        public void addDataset(XYDataset dataset) {
+            datasets.add(dataset);
+            includeYRange(((XYSeriesCollection) dataset).getSeries(0));
+        }
+
+        /** Add a dataset, include its range. */
+        public void addDataset(XYSeries series) {
+            addDataset(new XYSeriesCollection(series));
+        }
+
+
+        /** Set Range for this axis. */
+        @Override
+        public void setRange(Range range) {
+            this.range = range;
+        }
+
+
+        /** Get Range for this axis. */
+        @Override
+        public Range getRange() {
+            return range;
+        }
+
+
+        /** Get Array of Datasets. */
+        @Override
+        public XYDataset[] getDatasets() {
+            return (XYDataset[])
+                datasets.toArray(new XYDataset[datasets.size()]);
+        }
+
+
+        /** Add a Dataset that describes an area. */
+        public void addArea(StyledAreaSeriesCollection series) {
+            this.datasets.add(series);
+        }
+
+        // TODO obsolete?
+        /** True if to be rendered as area. */
+        @Override
+        public boolean isArea(XYDataset series) {
+            return (series instanceof StyledAreaSeriesCollection);
+        }
+
+        /** Adjust range to include given dataset. */
+        public void includeYRange(XYSeries dataset) {
+            mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY()));
+        }
+
+        /** True if no datasets given. */
+        @Override
+        public boolean isEmpty() {
+            return this.datasets.isEmpty();
+        }
+
+        /** Set the 'real' axis index that this axis is mapped to. */
+        @Override
+        public void setPlotAxisIndex(int axisIndex) {
+            this.plotAxisIndex = axisIndex;
+        }
+
+        /** Get the 'real' axis index that this axis is mapped to. */
+        @Override
+        public int getPlotAxisIndex() {
+            return this.plotAxisIndex;
+        }
+    } // class AxisDataset
+
+    /** Enumerator over existing axes. */
+    protected abstract YAxisWalker getYAxisWalker();
+
+    protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
+
+    public static final int AXIS_SPACE = 5;
+
+    /** The logger that is used in this generator. */
+    private static Logger logger = Logger.getLogger(XYChartGenerator.class);
+
+    /** List of annotations to insert in plot. */
+    protected List<FLYSAnnotation> annotations;
+
+    /** The max X range to include all X values of all series for each axis. */
+    protected Map<Integer, Bounds> xBounds;
+
+    /** The max Y range to include all Y values of all series for each axis. */
+    protected Map<Integer, Bounds> yBounds;
+
+
+    public XYChartGenerator() {
+        super();
+
+        xBounds  = new HashMap<Integer, Bounds>();
+        yBounds  = new HashMap<Integer, Bounds>();
+    }
+
+
+    /**
+     * Generate the chart anew (including localized axis and all).
+     */
+    public JFreeChart generateChart() {
+        logger.debug("XYChartGenerator.generateChart");
+
+        JFreeChart chart = ChartFactory.createXYLineChart(
+            getChartTitle(),
+            getXAxisLabel(),
+            getYAxisLabel(0),
+            null,
+            PlotOrientation.VERTICAL,
+            isLegendVisible(),
+            false,
+            false);
+
+        XYPlot plot = (XYPlot) chart.getPlot();
+        chart.setBackgroundPaint(Color.WHITE);
+        plot.setBackgroundPaint(Color.WHITE);
+        addSubtitles(chart);
+        adjustPlot(plot);
+
+        //debugAxis(plot);
+
+        addDatasets(plot);
+
+        //debugDatasets(plot);
+
+        recoverEmptyPlot(plot);
+        preparePointRanges(plot);
+
+        //debugAxis(plot);
+
+        localizeAxes(plot);
+        adjustAxes(plot);
+        autoZoom(plot);
+
+        // These have to go after the autozoom.
+        addAnnotationsToRenderer(plot);
+
+        //aggregateLegendEntries(plot);
+
+        return chart;
+    }
+
+
+    @Override
+    protected Series getSeriesOf(XYDataset dataset, int idx) {
+        return ((XYSeriesCollection) dataset).getSeries(idx);
+    }
+
+
+    @Override
+    protected AxisDataset createAxisDataset(int idx) {
+        logger.debug("Create new XYAxisDataset for index: " + idx);
+        return new XYAxisDataset(idx);
+    }
+
+
+    /**
+     * Put debug output about datasets.
+     */
+    public void debugDatasets(XYPlot plot) {
+        logger.debug("Number of datasets: " + plot.getDatasetCount());
+        for (int i = 0; i < plot.getDatasetCount(); i++) {
+            if (plot.getDataset(i) == null) {
+                logger.debug("Dataset #" + i + " is null");
+                continue;
+            }
+            logger.debug("Dataset #" + i + ":" + plot.getDataset(i));
+            XYSeriesCollection series = (XYSeriesCollection) plot.getDataset(i);
+            logger.debug("X-Extend of Dataset: " + series.getSeries(0).getMinX()
+                    + " " + series.getSeries(0).getMaxX());
+            logger.debug("Y-Extend of Dataset: " + series.getSeries(0).getMinY()
+                    + " " + series.getSeries(0).getMaxY());
+        }
+    }
+
+
+    /**
+     * Put debug output about axes.
+     */
+    public void debugAxis(XYPlot plot) {
+        logger.debug("...............");
+        for (int i = 0; i < plot.getRangeAxisCount(); i++) {
+            if (plot.getRangeAxis(i) == null)
+                logger.debug("Range-Axis #" + i + " == null");
+            else {
+                logger.debug("Range-Axis " + i + " != null [" +
+                    plot.getRangeAxis(i).getRange().getLowerBound() +
+                    "  " + plot.getRangeAxis(i).getRange().getUpperBound() +
+                    "]");
+            }
+
+        }
+        logger.debug("...............");
+    }
+
+
+    /**
+     * Registers an area to be drawn.
+     * @param area Area to be drawn.
+     * @param index 'axis index'
+     * @param visible Whether or not to be visible (important for range calculations).
+     */
+    public void addAreaSeries(StyledAreaSeriesCollection area, int index, boolean visible) {
+        if (area == null) {
+            logger.warn("Cannot yet render above/under curve.");
+            return;
+        }
+
+        XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index);
+
+        if (visible) {
+            axisDataset.addArea(area);
+        }
+        else {
+            // TODO only range merging.
+        }
+        //TODO range merging.
+    }
+
+
+    /**
+     * Add given series if visible, if not visible adjust ranges (such that
+     * all points in data would be plotted once visible).
+     * @param series the dataseries to include in plot.
+     * @param index  ('symbolic') index of the series and of its axis.
+     * @param visible whether or not the data should be plotted.
+     */
+    public void addAxisSeries(XYSeries series, int index, boolean visible) {
+        if (series == null) {
+            return;
+        }
+
+        logger.debug("Y Range of XYSeries: " +
+            series.getMinY() + " | " + series.getMaxY());
+
+        addAxisDataset(new XYSeriesCollection(series), index, visible);
+
+        XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index);
+
+        if (!visible) {
+            // Do this also when not visible to have axis scaled by default such
+            // that every data-point could be seen (except for annotations).
+            axisDataset.includeYRange(series);
+        }
+    }
+
+
+    /**
+     * Effect: extend range of x axis to include given limits.
+     *
+     * @param range the given ("minimal") range.
+     * @param index index of axis to be merged.
+     */
+    @Override
+    protected void combineXBounds(Bounds bounds, int index) {
+        if (!(bounds instanceof DoubleBounds)) {
+            logger.warn("Unsupported Bounds type: " + bounds.getClass());
+            return;
+        }
+
+        DoubleBounds dBounds = (DoubleBounds) bounds;
+
+        if (dBounds == null
+            || Double.isNaN((Double) dBounds.getLower())
+            || Double.isNaN((Double) dBounds.getUpper())) {
+            return;
+        }
+
+        Bounds old = getXBounds(index);
+
+        if (old != null) {
+            dBounds = (DoubleBounds) dBounds.combine(old);
+        }
+
+        setXBounds(index, dBounds);
+    }
+
+
+    @Override
+    protected void combineYBounds(Bounds bounds, int index) {
+        if (!(bounds instanceof DoubleBounds)) {
+            logger.warn("Unsupported Bounds type: " + bounds.getClass());
+            return;
+        }
+
+        DoubleBounds dBounds = (DoubleBounds) bounds;
+
+        if (dBounds == null
+            || Double.isNaN((Double) dBounds.getLower())
+            || Double.isNaN((Double) dBounds.getUpper())) {
+            return;
+        }
+
+        Bounds old = getYBounds(index);
+
+        if (old != null) {
+            dBounds = (DoubleBounds) dBounds.combine(old);
+        }
+
+        setYBounds(index, dBounds);
+    }
+
+
+    /**
+     * Adds annotations to list (if visible is true).
+     */
+    public void addVisibleAnnotations(FLYSAnnotation annotation) {
+        if (annotations == null) {
+            annotations = new ArrayList<FLYSAnnotation>();
+        }
+
+        annotations.add(annotation);
+    }
+
+
+    /**
+     * If no data is visible, draw at least empty axis.
+     */
+    private void recoverEmptyPlot(XYPlot plot) {
+        if (plot.getRangeAxis() == null) {
+            logger.debug("debug: No range axis");
+            plot.setRangeAxis(createYAxis(0));
+        }
+    }
+
+
+    /**
+     * Expands X axes if only a point is shown.
+     */
+    private void preparePointRanges(XYPlot plot) {
+        for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) {
+            logger.debug("Check whether to expand a x axis.");
+
+            Integer key = Integer.valueOf(i);
+            Bounds  b   = getXBounds(key);
+
+            if (b != null && b.getLower() == b.getUpper()) {
+                double lo  = (Double) b.getLower();
+                double hi  = (Double) b.getUpper();
+                double add = (hi - lo) / 100 * 5;
+
+                setXBounds(key, new DoubleBounds(lo-add, hi+add));
+            }
+        }
+    }
+
+
+    /**
+     * This method zooms the plot to the specified ranges in the attribute
+     * document or to the ranges specified by the min/max values in the
+     * datasets. <b>Note:</b> We determine the range manually if no zoom ranges
+     * are given, because JFreeCharts auto-zoom adds a margin to the left and
+     * right of the data area.
+     *
+     * @param plot The XYPlot.
+     */
+    protected void autoZoom(XYPlot plot) {
+        logger.debug("Zoom to specified ranges.");
+
+        Range xrange = getDomainAxisRange();
+        Range yrange = getValueAxisRange();
+
+        ValueAxis xAxis = plot.getDomainAxis();
+
+        Range fixedXRange = getRangeForAxisFromSettings("X");
+        if (fixedXRange != null) {
+            xAxis.setRange(fixedXRange);
+        }
+        else {
+            zoomX(plot, xAxis, getXBounds(0), xrange);
+        }
+
+        for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) {
+            ValueAxis yaxis = plot.getRangeAxis(i);
+
+            if (yaxis instanceof IdentifiableNumberAxis) {
+                IdentifiableNumberAxis idAxis = (IdentifiableNumberAxis) yaxis;
+
+                Range fixedRange = getRangeForAxisFromSettings(idAxis.getId());
+                if (fixedRange != null) {
+                    yaxis.setRange(fixedRange);
+                    continue;
+                }
+            }
+
+            if (yaxis == null) {
+                logger.debug("Zoom problem: no Y Axis for index: " + i);
+                continue;
+            }
+
+            logger.debug("Prepare zoom settings for y axis at index: " + i);
+            zoomY(plot, yaxis, getYBounds(Integer.valueOf(i)), yrange);
+        }
+    }
+
+
+    protected Range getDomainAxisRange() {
+        String[] ranges = getDomainAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No zoom range for domain axis specified.");
+            return null;
+        }
+
+        if (ranges[0].length() > 0 && ranges[1].length() > 0) {
+            try {
+                double from = Double.parseDouble(ranges[0]);
+                double to   = Double.parseDouble(ranges[1]);
+
+                if (from == 0 && to == 0) {
+                    logger.debug("No range specified. Lower and upper X == 0");
+                    return null;
+                }
+
+                if (from > to) {
+                    double tmp = to;
+                    to         = from;
+                    from       = tmp;
+                }
+
+                return new Range(from, to);
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Wrong values for domain axis range.");
+            }
+        }
+
+        return null;
+    }
+
+
+    protected Range getValueAxisRange() {
+        String[] ranges = getValueAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No range specified. Lower and upper Y == 0");
+            return null;
+        }
+
+        if (ranges[0].length() > 0 && ranges[1].length() > 0) {
+            try {
+                double from = Double.parseDouble(ranges[0]);
+                double to   = Double.parseDouble(ranges[1]);
+
+                if (from == 0 && to == 0) {
+                    logger.debug("No range specified. Lower and upper Y == 0");
+                    return null;
+                }
+
+                return from > to
+                       ? new Range(to, from)
+                       : new Range(from, to);
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Wrong values for value axis range.");
+            }
+        }
+
+        return null;
+    }
+
+
+    protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
+        return zoom(plot, axis, bounds, x);
+    }
+
+
+    protected boolean zoomY(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
+        return zoom(plot, axis, bounds, x);
+    }
+
+
+    /**
+     * Zooms the x axis to the range specified in the attribute document.
+     *
+     * @param plot  The XYPlot.
+     * @param axis  The axis the shoud be modified.
+     * @param range The whole range specified by a dataset.
+     * @param x     A user defined range (null permitted).
+     *
+     * @return true, if a zoom range was specified, otherwise false.
+     */
+    protected boolean zoom(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
+
+        if (bounds == null) {
+            return false;
+        }
+
+        if (x != null) {
+            double min  = bounds.getLower().doubleValue();
+            double max  = bounds.getUpper().doubleValue();
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Minimum is: " + min);
+                logger.debug("Maximum is: " + max);
+                logger.debug("Lower zoom is: " + x.getLowerBound());
+                logger.debug("Upper zoom is: " + x.getUpperBound());
+            }
+
+            double diff = max > min ? max - min : min - max;
+
+            DoubleBounds computed = new DoubleBounds(
+                min + x.getLowerBound() * diff,
+                min + x.getUpperBound() * diff);
+
+            computed.applyBounds(axis, AXIS_SPACE);
+
+            logger.debug("Zoom axis to: " + computed);
+
+            return true;
+        }
+
+        bounds.applyBounds(axis, AXIS_SPACE);
+        return false;
+    }
+
+
+    /**
+     * Extract the minimum and maximum values for x and y axes
+     * which are stored in <i>xRanges</i> and <i>yRanges</i>.
+     *
+     * @param index The index of the y-Axis.
+     *
+     * @return a Range[] as follows: [x-Range, y-Range].
+     */
+    @Override
+    public Range[] getRangesForAxis(int index) {
+        logger.debug("getRangesForAxis " + index);
+
+        Bounds rx = getXBounds(Integer.valueOf(0));
+        Bounds ry = getYBounds(Integer.valueOf(index));
+
+        if (rx == null) {
+            logger.warn("Range for x axis not set." +
+                        " Using default values: 0 - 1.");
+            rx = new DoubleBounds(0, 1);
+        }
+        if (ry == null) {
+            logger.warn("Range for y" + index +
+                        " axis not set. Using default values: 0 - 1.");
+            ry = new DoubleBounds(0, 1);
+        }
+
+        return new Range[] {
+            new Range(rx.getLower().doubleValue(), rx.getUpper().doubleValue()),
+            new Range(ry.getLower().doubleValue(), ry.getUpper().doubleValue())
+        };
+    }
+
+
+    /** Get X (usually horizontal) extent for given axis. */
+    @Override
+    public Bounds getXBounds(int axis) {
+        return xBounds.get(axis);
+    }
+
+
+    /** Set X (usually horizontal) extent for given axis. */
+    @Override
+    protected void setXBounds(int axis, Bounds bounds) {
+        xBounds.put(axis, bounds);
+    }
+
+
+    /** Get Y (usually vertical) extent for given axis. */
+    @Override
+    public Bounds getYBounds(int axis) {
+        return yBounds.get(axis);
+    }
+
+
+    /** Set Y (usually vertical) extent for given axis. */
+    @Override
+    protected void setYBounds(int axis, Bounds bounds) {
+        yBounds.put(axis, bounds);
+    }
+
+
+    /** Get color for hyk zones by their type (which is the name). */
+    public Paint colorForHYKZone(String zoneName) {
+        if (zoneName.startsWith("R")) {
+            // Brownish.
+            return new Color(153, 60, 0);
+        }
+        else if (zoneName.startsWith("V")) {
+            // Greenish.
+            return new Color(0, 255, 0);
+        }
+        else if (zoneName.startsWith("B")) {
+            // Grayish.
+            return new Color(128, 128, 128);
+        }
+        else if (zoneName.startsWith("H")) {
+            // Blueish.
+            return new Color(0, 0, 255);
+        }
+        else {
+            // Default.
+            logger.debug("Unknown zone type found.");
+            return new Color(255, 0, 0);
+        }
+    }
+
+    protected static XYLineAnnotation createGroundStickAnnotation(
+        Area area, float pos, ThemeAccess.LineStyle lineStyle
+    ) {
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                pos, area.atGround(),
+                pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET),
+                new BasicStroke(lineStyle.getWidth()),lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                pos, area.atGround(),
+                pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET));
+        }
+    }
+
+
+    protected static XYLineAnnotation createRightStickAnnotation(
+        Area area, float pos, ThemeAccess.LineStyle lineStyle
+    ) {
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos,
+                area.atRight(), pos,
+                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                area.atRight(), pos,
+                area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos);
+        }
+    }
+
+
+    protected static XYLineAnnotation createLeftStickAnnotation(
+        Area area, float pos, ThemeAccess.LineStyle lineStyle
+    ) {
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                area.atLeft(), pos,
+                area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos,
+                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                area.atLeft(), pos,
+                area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos);
+        }
+    }
+
+    /**
+     * Add a text and a line annotation.
+     * @param area convenience to determine positions in plot.
+     */
+    public void addStickyAnnotation(
+        StickyAxisAnnotation annotation,
+        XYPlot plot,
+        Area area,
+        ThemeAccess.LineStyle lineStyle,
+        ThemeAccess.TextStyle textStyle
+    ) {
+        // OPTIMIZE pre-calculate area-related values
+        final float TEXT_OFF = 0.03f;
+
+        XYLineAnnotation lineAnnotation = null;
+        XYTextAnnotation textAnnotation = null;
+
+        int rendererIndex = 0;
+
+        if (annotation.atX()) {
+            textAnnotation = new CollisionFreeXYTextAnnotation(
+                annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
+            // OPTIMIZE externalize the calculation involving PI.
+            textAnnotation.setRotationAngle(270f*Math.PI/180f);
+            lineAnnotation = createGroundStickAnnotation(
+                area, annotation.getPos(), lineStyle);
+            textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
+            textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
+        }
+        else {
+            // Do the more complicated case where we stick to the Y-Axis.
+            // There is one nasty case (duration curves, where annotations
+            // might stick to the second y-axis).
+            XYAxisDataset dataset = (XYAxisDataset) getAxisDataset(
+                new Integer(annotation.getAxisSymbol()));
+            if (dataset == null) {
+                logger.warn("Annotation should stick to unfindable y-axis: "
+                    + annotation.getAxisSymbol());
+                rendererIndex = 0;
+            }
+            else {
+                rendererIndex = dataset.getPlotAxisIndex();
+            }
+
+            // Stick to the "right" (opposed to left) Y-Axis.
+            if (rendererIndex != 0) {
+                // OPTIMIZE: Pass a different area to this function,
+                //           do the adding to renderer outside (let this
+                //           function return the annotations).
+                //           Note that this path is travelled rarely.
+                Area area2 = new Area(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex));
+                textAnnotation = new CollisionFreeXYTextAnnotation(
+                    annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos());
+                textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
+                textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
+                lineAnnotation = createRightStickAnnotation(
+                    area2, annotation.getPos(), lineStyle);
+                // TODO line to curve
+                if (!Float.isNaN(annotation.getHitPoint())) {
+                    // TODO let line be styled and respect X/Y-ness.
+                    // New line annotation to hit curve.
+                    XYLineAnnotation hitLineAnnotation = new XYLineAnnotation(
+                        area2.atRight(), annotation.getPos(),
+                        annotation.getHitPoint(), annotation.getPos());
+                    XYLineAnnotation lineBackAnnotation = new XYLineAnnotation(
+                        annotation.getHitPoint(), annotation.getPos(),
+                        annotation.getHitPoint(), area2.atGround());
+                    plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
+                        org.jfree.ui.Layer.BACKGROUND);
+                    plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
+                        org.jfree.ui.Layer.BACKGROUND);
+                }
+            }
+            else {
+                textAnnotation = new CollisionFreeXYTextAnnotation(
+                    annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
+                textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
+                textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
+                lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle);
+                // TODO line to curve
+                if (!Float.isNaN(annotation.getHitPoint())) {
+                    // TODO let line be styled and respect X/Y-ness.
+                    // New line annotation to hit curve.
+                    XYLineAnnotation hitLineAnnotation = new XYLineAnnotation(area.atLeft(),
+                        annotation.getPos(), annotation.getHitPoint(), annotation.getPos());
+                    XYLineAnnotation lineBackAnnotation = new XYLineAnnotation(
+                        annotation.getHitPoint(), annotation.getPos(),
+                        annotation.getHitPoint(), area.atGround());
+                    plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
+                        org.jfree.ui.Layer.BACKGROUND);
+                    plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
+                        org.jfree.ui.Layer.BACKGROUND);
+                }
+            }
+        }
+
+        // Style the text.
+        if (textStyle != null) {
+            textStyle.apply(textAnnotation);
+        }
+
+        // Add the Annotations to renderer.
+        plot.getRenderer(rendererIndex).addAnnotation(textAnnotation,
+            org.jfree.ui.Layer.FOREGROUND);
+        plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation,
+            org.jfree.ui.Layer.FOREGROUND);
+    }
+
+
+    /**
+     * Add the annotations (Sticky, Text and hyk zones) stored
+     * in the annotations field.
+     */
+    public void addAnnotationsToRenderer(XYPlot plot) {
+        logger.debug("XYChartGenerator.addAnnotationsToRenderer");
+
+        if (annotations == null) {
+            logger.debug("XYChartGenerator.addAnnotationsToRenderer: no annotations.");
+            return;
+        }
+
+        // Paints for the boxes/lines.
+        Stroke basicStroke = new BasicStroke(1.0f);
+
+        Paint linePaint = new Color(255,  0,0,60);
+        Paint fillPaint = new Color(0,  255,0,60);
+        Paint tranPaint = new Color(0,    0,0, 0);
+
+        // OPTMIMIZE: Pre-calculate positions
+        Area area = new Area(
+            plot.getDomainAxis(0).getRange(),
+            plot.getRangeAxis().getRange());
+
+        // Walk over all Annotation sets.
+        for (FLYSAnnotation fa: annotations) {
+
+            // Access text styling, if any.
+            Document theme = fa.getTheme();
+            ThemeAccess.TextStyle textStyle = null;
+            ThemeAccess.LineStyle lineStyle = null;
+
+            // Get Themeing information and add legend item.
+            if (theme != null) {
+                ThemeAccess themeAccess = new ThemeAccess(theme);
+                textStyle = themeAccess.parseTextStyle();
+                lineStyle = themeAccess.parseLineStyle();
+                if (fa.getLabel() != null) {
+                    LegendItemCollection lic = new LegendItemCollection();
+                    LegendItemCollection old = plot.getFixedLegendItems();
+                    lic.add(createLegendItem(theme, fa.getLabel()));
+                    // (Re-)Add prior legend entries.
+                    if (old != null) {
+                        old.addAll(lic);
+                    }
+                    else {
+                        old = lic;
+                    }
+                    plot.setFixedLegendItems(old);
+                }
+            }
+
+            // The 'Sticky' Annotations (at axis, with line and text).
+            for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) {
+                addStickyAnnotation(sta, plot, area, lineStyle, textStyle);
+            }
+
+            // Other Text Annotations (e.g. labels of manual points).
+            for (XYTextAnnotation ta: fa.getTextAnnotations()) {
+                // Style the text.
+                if (textStyle != null) {
+                    textStyle.apply(ta);
+                }
+                ta.setY(area.above(0.05d, ta.getY()));
+                plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
+            }
+
+            // Hyks.
+            for (HYKFactory.Zone zone: fa.getBoxes()) {
+                // For each zone, create a box to fill with color, a box to draw
+                // the lines and a text to display the type.
+                fillPaint = colorForHYKZone(zone.getName());
+
+                XYBoxAnnotation boxA = new XYBoxAnnotation(zone.getFrom(), area.atGround(),
+                    zone.getTo(), area.ofGround(0.03f), basicStroke, tranPaint, fillPaint);
+                XYBoxAnnotation boxB = new XYBoxAnnotation(zone.getFrom(), area.atGround(),
+                    zone.getTo(), area.atTop(), basicStroke, fillPaint, tranPaint);
+
+                XYTextAnnotation tex = new XYTextAnnotation(zone.getName(),
+                    zone.getFrom() + (zone.getTo() - zone.getFrom()) / 2.0d,
+                    area.ofGround(0.015f));
+                if (textStyle != null) {
+                    textStyle.apply(tex);
+                }
+
+                plot.getRenderer().addAnnotation(boxA, org.jfree.ui.Layer.BACKGROUND);
+                plot.getRenderer().addAnnotation(boxB, org.jfree.ui.Layer.BACKGROUND);
+                plot.getRenderer().addAnnotation(tex,  org.jfree.ui.Layer.BACKGROUND);
+            }
+        }
+    }
+
+
+    /**
+     * Adjusts the axes of a plot. This method sets the <i>labelFont</i> of the
+     * X axis.
+     *
+     * @param plot The XYPlot of the chart.
+     */
+    protected void adjustAxes(XYPlot plot) {
+        ValueAxis xaxis = plot.getDomainAxis();
+
+        ChartSettings chartSettings = getChartSettings();
+        if (chartSettings == null) {
+            return;
+        }
+
+        Font labelFont = new Font(
+            DEFAULT_FONT_NAME,
+            Font.BOLD,
+            getXAxisLabelFontSize());
+
+        xaxis.setLabelFont(labelFont);
+        xaxis.setTickLabelFont(labelFont);
+    }
+
+
+    /**
+     * This method walks over all axes (domain and range) of <i>plot</i> and
+     * calls localizeDomainAxis() for domain axes or localizeRangeAxis() for
+     * range axes.
+     *
+     * @param plot The XYPlot.
+     */
+    private void localizeAxes(XYPlot plot) {
+        for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) {
+            ValueAxis axis = plot.getDomainAxis(i);
+
+            if (axis != null) {
+                localizeDomainAxis(axis);
+            }
+            else {
+                logger.warn("Domain axis at " + i + " is null.");
+            }
+        }
+
+        for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) {
+            ValueAxis axis = plot.getRangeAxis(i);
+
+            if (axis != null) {
+                localizeRangeAxis(axis);
+            }
+            else {
+                logger.warn("Range axis at " + i + " is null.");
+            }
+        }
+    }
+
+
+    /**
+     * Overrides the NumberFormat with the NumberFormat for the current locale
+     * that is provided by getLocale().
+     *
+     * @param domainAxis The domain axis that needs localization.
+     */
+    protected void localizeDomainAxis(ValueAxis domainAxis) {
+        NumberFormat nf = NumberFormat.getInstance(getLocale());
+        ((NumberAxis) domainAxis).setNumberFormatOverride(nf);
+    }
+
+
+    /**
+     * Overrides the NumberFormat with the NumberFormat for the current locale
+     * that is provided by getLocale().
+     *
+     * @param domainAxis The domain axis that needs localization.
+     */
+    protected void localizeRangeAxis(ValueAxis rangeAxis) {
+        NumberFormat nf = NumberFormat.getInstance(getLocale());
+        ((NumberAxis) rangeAxis).setNumberFormatOverride(nf);
+    }
+
+
+    /**
+     * Register annotations like MainValues for later plotting
+     *
+     * @param annotations list of annotations (data of facet).
+     * @param aandf   Artifact and the facet.
+     * @param theme   Theme document for given annotations.
+     * @param visible The visibility of the annotations.
+     */
+    protected void doAnnotations(
+        FLYSAnnotation annotations,
+        ArtifactAndFacet aandf,
+        Document theme,
+        boolean visible
+    ){
+        // Running into trouble here.
+        logger.debug("doAnnotations");
+
+        // Add all annotations to our annotation pool.
+        annotations.setTheme(theme);
+        if (aandf != null) {
+            Facet facet = aandf.getFacet();
+            annotations.setLabel(aandf.getFacetDescription());
+        }
+        else {
+            logger.debug(
+                "Art/Facet for Annotations is null. " +
+                "This should never happen!");
+        }
+
+        if (visible) {
+            addVisibleAnnotations(annotations);
+        }
+    }
+
+
+    /**
+     * Do Points out.
+     */
+    protected void doPoints(
+        Object     o,
+        ArtifactAndFacet aandf,
+        Document   theme,
+        boolean    visible,
+        int        axisIndex
+    ) {
+        String seriesName = aandf.getFacetDescription();
+        XYSeries series = new StyledXYSeries(seriesName, theme);
+
+        // Add text annotations for single points.
+        List<XYTextAnnotation> xy = new ArrayList<XYTextAnnotation>();
+
+        try {
+            JSONArray points = new JSONArray((String) o);
+            for (int i = 0; i < points.length(); i++) {
+                JSONArray array = points.getJSONArray(i);
+                double x    = array.getDouble(0);
+                double y    = array.getDouble(1);
+                String name = array.getString(2);
+                boolean act = array.getBoolean(3);
+                if (!act) {
+                    continue;
+                }
+                //logger.debug(" x " + x + " y " + y );
+                series.add(x, y, false);
+                xy.add(new CollisionFreeXYTextAnnotation(name, x, y));
+            }
+        }
+        catch(JSONException e){
+            logger.error("Could not decode json.");
+        }
+        
+        FLYSAnnotation annotations = new FLYSAnnotation(null, null, null, theme);
+        annotations.setTextAnnotations(xy);
+
+        // Do not generate second legend entry. (null was passed for the aand before).
+        doAnnotations(annotations, null, theme, visible);
+        addAxisSeries(series, axisIndex, visible);
+    }
+
+
+    /**
+     * Create a hash from a legenditem.
+     * This hash can then be used to merge legend items labels.
+     * @return hash for given legenditem to identify mergeables.
+     */
+    public static String legendItemHash(LegendItem li) {
+        // TODO Do proper implementation. Ensure that only mergable sets are created.
+        // getFillPaint()
+        // getFillPaintTransformer()
+        // getLabel()
+        // getLine()
+        // getLinePaint()
+        // getLineStroke()
+        // getOutlinePaint()
+        // getOutlineStroke()  
+        // Shape getShape()
+        // String getToolTipText()
+        // String getURLText()
+        // boolean isLineVisible()
+        // boolean isShapeFilled()
+        // boolean isShapeOutlineVisible()
+        // boolean isShapeVisible()
+        String hash = li.getLinePaint().toString();
+        String label = li.getLabel();
+        if (label.startsWith("W (") || label.startsWith("W(")) {
+            hash += "-W-";
+        }
+        else if (label.startsWith("Q(") || label.startsWith("Q (")) {
+            hash += "-Q-";
+        }
+
+        // WQ.java holds example of using regex Matcher/Pattern.
+
+        return hash;
+    }
+
+
+    /**
+     * Create a label for multiple items.
+     *
+     * For example from "W(Q=100)" and "W(Q=220)"
+     * "W(Q= {100, 22})" would result.
+     *
+     * @param items list of legenditems.
+     * @return the merged label.
+     */
+    public String mergeLegendNames(List<LegendItem> items) {
+        if (items.size() == 0) {
+            return null;
+        }
+        if (items.size() == 1) {
+            return items.get(0).getLabel();
+        }
+
+        // TODO consider using regionMathches for implementation.
+        int lastMatchedChar = 0;
+        boolean first = true;
+        String startPattern = "";
+        String endPattern = "";
+        String name = "";
+        // First, figure out beginning and end patterns, then merge.
+        for (LegendItem item : items) {
+            if (first) {
+                startPattern = item.getLabel();
+                endPattern = item.getLabel();
+                first = false;
+                continue;
+            }
+            while (startPattern.length() > 0 && !item.getLabel().startsWith(startPattern)) {
+                startPattern = startPattern.substring(0, startPattern.length() -1);
+            }
+            while (endPattern.length() > 0 && !item.getLabel().endsWith(endPattern)) {
+                endPattern = endPattern.substring(1);
+            }
+        }
+
+        // Then, merge.
+        name = startPattern + " {";
+        first = true;
+        for (LegendItem item : items) {
+            if (!first) {
+                name += ", ";
+            }
+            else {
+                first = false;
+            }
+            // Possible case in differences
+            if (startPattern.length() + endPattern.length() < item.getLabel().length()) {
+                // Note that we do not really want this.
+                logger.error("Legend aggregation does not cover case: " +
+                    startPattern + "  -- " + item.getLabel() + " -- " + endPattern);
+                name += item.getLabel();
+            }
+            else {
+                name += item.getLabel().substring(startPattern.length(),
+                        item.getLabel().length() - endPattern.length());
+            }
+        }
+        name += "} ";
+        name += endPattern;
+
+        return name;
+    }
+
+
+    /**
+     * Create new legend entries, dependant on settings.
+     * @param plot The plot for which to modify the legend.
+     */
+    public void aggregateLegendEntries(XYPlot plot) {
+        LegendItemCollection old = plot.getLegendItems();
+        // Find "similar" entries if aggregation is enabled.
+
+        int maxListSize = 0;
+        int AGGR_THRESHOLD = 2;
+
+        HashMap<String, List<LegendItem>> entries = new LinkedHashMap<String, List<LegendItem>>();
+        for (Iterator i = old.iterator(); i.hasNext();) {
+            LegendItem item = (LegendItem) i.next();
+            String hash = legendItemHash(item);
+            List<LegendItem> itemList = entries.get(hash);
+            if (itemList == null) {
+                itemList = new ArrayList<LegendItem>();
+            }
+            itemList.add(item);
+
+            if (itemList.size() > maxListSize) {
+                maxListSize = itemList.size();
+            }
+
+            entries.put(legendItemHash(item), itemList);
+        }
+
+        if (maxListSize < AGGR_THRESHOLD) {
+            // No need to do anything.
+            return;
+        }
+
+        // Run over collected entries, merge their names and create new
+        // entry if needed.
+        LegendItemCollection newLegend = new LegendItemCollection();
+        for (Map.Entry<String, List<LegendItem>> cursor: entries.entrySet()) {
+            List<LegendItem> itemList = cursor.getValue();
+            if (itemList.size() >= AGGR_THRESHOLD) {
+                // TODO now do merging
+            }
+            else {
+                // TODO create singular new entry
+            }
+
+           LegendItem item = (LegendItem) itemList.get(0);
+           // Unfortunately we cannot clone and just setDescription, as this
+           // method was added in JFreeChart 1.0.14 (we are at .13).
+           LegendItem merged = new LegendItem(
+               mergeLegendNames(itemList), item.getDescription(), item.getToolTipText(),
+               item.getURLText(), item.isShapeVisible(), item.getShape(),
+               item.isShapeFilled(), item.getFillPaint(), item.isShapeOutlineVisible(),
+               item.getOutlinePaint(), item.getOutlineStroke(), item.isLineVisible(),
+               item.getLine(), item.getLineStroke(), item.getLinePaint());
+           newLegend.add(merged);
+        }
+
+        plot.setFixedLegendItems (newLegend);
+    }
+
+
+    /** Two Ranges that span a rectangular area. */
+    public static class Area {
+        protected Range xRange;
+        protected Range yRange;
+
+        public Area(Range rangeX, Range rangeY) {
+            this.xRange = rangeX;
+            this.yRange = rangeY;
+        }
+
+        public Area(ValueAxis axisX, ValueAxis axisY) {
+            this.xRange = axisX.getRange();
+            this.yRange = axisY.getRange();
+        }
+
+        public double ofLeft(double percent) {
+            return xRange.getLowerBound()
+                + xRange.getLength() * percent;
+        }
+
+        public double ofRight(double percent) {
+            return xRange.getUpperBound()
+                - xRange.getLength() * percent;
+        }
+
+        public double ofGround(double percent) {
+            return yRange.getLowerBound()
+                + yRange.getLength() * percent;
+        }
+
+        public double atTop() {
+            return yRange.getUpperBound();
+        }
+
+        public double atGround() {
+            return yRange.getLowerBound();
+        }
+
+        public double atRight() {
+            return xRange.getUpperBound();
+        }
+
+        public double atLeft() {
+            return xRange.getLowerBound();
+        }
+
+        public double above(double percent, double base) {
+            return base + yRange.getLength() * percent;
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/java2d/NOPGraphics2D.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,528 @@
+package de.intevation.flys.java2d;
+
+import java.util.Map;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Image;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Paint;
+import java.awt.GraphicsConfiguration;
+import java.awt.Stroke;
+import java.awt.Rectangle;
+import java.awt.Font;
+import java.awt.FontMetrics;
+
+import java.awt.image.RenderedImage;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
+
+import java.awt.image.renderable.RenderableImage;
+
+import java.awt.geom.AffineTransform;
+
+import java.text.AttributedCharacterIterator;
+
+import java.awt.font.GlyphVector;
+import java.awt.font.FontRenderContext;
+
+import java.awt.RenderingHints;
+
+public final class NOPGraphics2D
+extends            Graphics2D
+{
+    private Graphics2D parent;
+
+    public NOPGraphics2D(Graphics2D parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public final void addRenderingHints(Map<?,?> hints) {
+        parent.addRenderingHints(hints);
+    }
+
+    @Override
+    public final void clip(Shape s) {
+    }
+
+    @Override
+    public final void draw(Shape s) {
+    }
+
+    @Override
+    public final void drawGlyphVector(GlyphVector g, float x, float y) {
+    }
+
+    @Override
+    public final void drawImage(
+        BufferedImage   img,
+        BufferedImageOp op,
+        int x,
+        int y
+    ) {
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image           img,
+        AffineTransform xform,
+        ImageObserver   obs
+    ) {
+        return true;
+    }
+
+    @Override
+    public final void drawRenderableImage(
+        RenderableImage img,
+        AffineTransform xform
+    ) {
+    }
+
+    @Override
+    public final void drawRenderedImage(
+        RenderedImage   img,
+        AffineTransform xform
+    ) {
+    }
+
+    @Override
+    public final void drawString(
+        AttributedCharacterIterator iterator,
+        float x,
+        float y
+    ) {
+    }
+
+    @Override
+    public final void drawString(
+        AttributedCharacterIterator iterator,
+        int x,
+        int y
+    ) {
+    }
+
+    @Override
+    public final void drawString(String str, float x, float y) {
+    }
+
+    @Override
+    public final void drawString(String str, int x, int y) {
+    }
+
+    @Override
+    public final void fill(Shape s) {
+    }
+
+    @Override
+    public final Color getBackground() {
+        return parent.getBackground();
+    }
+
+    @Override
+    public final Composite getComposite() {
+        return parent.getComposite();
+    }
+
+    @Override
+    public final GraphicsConfiguration getDeviceConfiguration() {
+        return parent.getDeviceConfiguration();
+    }
+
+    @Override
+    public final FontRenderContext getFontRenderContext() {
+        return parent.getFontRenderContext();
+    }
+
+    @Override
+    public final Paint getPaint() {
+        return parent.getPaint();
+    }
+
+    @Override
+    public final Object getRenderingHint(RenderingHints.Key hintKey) {
+        return parent.getRenderingHint(hintKey);
+    }
+
+    @Override
+    public final RenderingHints getRenderingHints() {
+        return parent.getRenderingHints();
+    }
+
+    @Override
+    public final Stroke getStroke() {
+        return parent.getStroke();
+    }
+
+    @Override
+    public final AffineTransform getTransform() {
+        return parent.getTransform();
+    }
+
+    @Override
+    public final boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+        return parent.hit(rect, s, onStroke);
+    }
+
+    @Override
+    public final void rotate(double theta) {
+        parent.rotate(theta);
+    }
+
+    @Override
+    public final void rotate(double theta, double x, double y) {
+        parent.rotate(theta);
+    }
+
+    @Override
+    public final void scale(double sx, double sy) {
+        parent.scale(sx, sy);
+    }
+
+    @Override
+    public final void setBackground(Color color) {
+        parent.setBackground(color);
+    }
+
+    @Override
+    public final void setComposite(Composite comp) {
+        parent.setComposite(comp);
+    }
+
+    @Override
+    public final void setPaint(Paint paint) {
+        parent.setPaint(paint);
+    }
+
+    @Override
+    public final void setRenderingHint(
+        RenderingHints.Key hintKey,
+        Object             hintValue
+    ) {
+        parent.setRenderingHint(hintKey, hintValue);
+    }
+
+    @Override
+    public final void setRenderingHints(Map<?,?> hints) {
+        parent.setRenderingHints(hints);
+    }
+
+    @Override
+    public final void setStroke(Stroke s) {
+        parent.setStroke(s);
+    }
+
+    @Override
+    public final void setTransform(AffineTransform Tx) {
+        parent.setTransform(Tx);
+    }
+
+
+    @Override
+    public final void shear(double shx, double shy) {
+        parent.shear(shx, shy);
+    }
+
+    @Override
+    public final void transform(AffineTransform Tx) {
+        parent.transform(Tx);
+    }
+
+    @Override
+    public final void translate(double tx, double ty) {
+        parent.translate(tx, ty);
+    }
+
+    @Override
+    public final void translate(int tx, int ty) {
+        parent.translate(tx, ty);
+    }
+
+    @Override
+    public final void dispose() {
+        parent.dispose();
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image img,
+        int x,
+        int y,
+        int width,
+        int height,
+        Color bgcolor,
+        ImageObserver observer
+    ) {
+        return true;
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image img,
+        int dx1,
+        int dy1,
+        int dx2,
+        int dy2,
+        int sx1,
+        int sy1,
+        int sx2,
+        int sy2,
+        Color bgcolor,
+        ImageObserver observer
+    ) {
+        return true;
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image img,
+        int dx1,
+        int dy1,
+        int dx2,
+        int dy2,
+        int sx1,
+        int sy1,
+        int sx2,
+        int sy2,
+        ImageObserver observer
+    ) {
+        return true;
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image img,
+        int x,
+        int y,
+        Color bgcolor,
+        ImageObserver observer
+    ) {
+        return true;
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image img,
+        int x,
+        int y,
+        int width,
+        int height,
+        ImageObserver observer
+    ) {
+        return true;
+    }
+
+    @Override
+    public final boolean drawImage(
+        Image img,
+        int x,
+        int y,
+        ImageObserver observer
+    ) {
+        return true;
+    }
+
+    @Override
+    public final void fillPolygon(
+        int [] xPoints,
+        int [] yPoints,
+        int    nPoints
+    ) {
+    }
+
+    @Override
+    public final void drawPolygon(
+        int [] xPoints,
+        int [] yPoints,
+        int    nPoints
+    ) {
+    }
+
+    @Override
+    public final void drawPolyline(
+        int [] xPoints,
+        int [] yPoints,
+        int    nPoints
+    ) {
+    }
+
+    @Override
+    public final void fillArc(
+        int x,
+        int y,
+        int width,
+        int height,
+        int startAngle,
+        int arcAngle
+    ) {
+    }
+
+    @Override
+    public final void drawArc(
+        int x,
+        int y,
+        int width,
+        int height,
+        int startAngle,
+        int arcAngle
+    ) {
+    }
+
+    @Override
+    public final void fillOval(
+        int x,
+        int y,
+        int width,
+        int height
+    ) {
+    }
+
+    @Override
+    public final void drawOval(
+        int x,
+        int y,
+        int width,
+        int height
+    ) {
+    }
+
+    @Override
+    public final void fillRoundRect(
+        int x,
+        int y,
+        int width,
+        int height,
+        int arcWidth,
+        int arcHeight
+    ) {
+    }
+
+    @Override
+    public final void drawRoundRect(
+        int x,
+        int y,
+        int width,
+        int height,
+        int arcWidth,
+        int arcHeight
+    ) {
+    }
+
+    @Override
+    public final void clearRect(
+        int x,
+        int y,
+        int width,
+        int height
+    ) {
+    }
+
+    @Override
+    public final void fillRect(
+        int x,
+        int y,
+        int width,
+        int height
+    ) {
+    }
+
+    @Override
+    public final void drawLine(
+        int x1,
+        int y1,
+        int x2,
+        int y2
+    ) {
+    }
+
+    @Override
+    public final void copyArea(
+        int x,
+        int y,
+        int width,
+        int height,
+        int dx,
+        int dy
+    ) {
+    }
+
+    @Override
+    public final void setClip(
+        int x,
+        int y,
+        int width,
+        int height
+    ) {
+        parent.setClip(x, y, width, height);
+    }
+
+    @Override
+    public final void setClip(Shape shape) {
+        parent.setClip(shape);
+    }
+
+    @Override
+    public final Shape getClip() {
+        return parent.getClip();
+    }
+
+    @Override
+    public final void clipRect(
+        int x,
+        int y,
+        int width,
+        int height
+    ) {
+        parent.clipRect(x, y, width, height);
+    }
+
+    @Override
+    public final Rectangle getClipBounds() {
+        return parent.getClipBounds();
+    }
+
+    @Override
+    public final FontMetrics getFontMetrics(Font f) {
+        return parent.getFontMetrics(f);
+    }
+
+    @Override
+    public final void setFont(Font font) {
+        parent.setFont(font);
+    }
+
+    @Override
+    public final Font getFont() {
+        return parent.getFont();
+    }
+
+    @Override
+    public final void setXORMode(Color c1) {
+        parent.setXORMode(c1);
+    }
+
+    @Override
+    public final void setPaintMode() {
+        parent.setPaintMode();
+    }
+
+    @Override
+    public final void setColor(Color c) {
+        parent.setColor(c);
+    }
+
+    @Override
+    public final Color getColor() {
+        return parent.getColor();
+    }
+
+    @Override
+    public final Graphics create() {
+        return new NOPGraphics2D((Graphics2D)parent.create());
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/Bounds.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,23 @@
+package de.intevation.flys.jfree;
+
+import java.io.Serializable;
+
+import org.jfree.chart.axis.ValueAxis;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Bounds extends Serializable {
+
+    Number getLower();
+
+    Number getUpper();
+
+    void applyBounds(ValueAxis axis);
+
+    void applyBounds(ValueAxis axis, int percent);
+
+    Bounds combine(Bounds bounds);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/CollisionFreeXYTextAnnotation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,157 @@
+package de.intevation.flys.jfree;
+
+import org.apache.log4j.Logger;
+
+import java.util.Iterator;
+
+import java.awt.Shape;
+import java.awt.geom.Rectangle2D;
+
+import org.jfree.chart.entity.EntityCollection;
+import org.jfree.chart.annotations.XYTextAnnotation;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.entity.XYAnnotationEntity;
+import org.jfree.chart.plot.PlotRenderingInfo;
+import org.jfree.chart.plot.Plot;
+
+import org.jfree.text.TextUtilities;
+
+import org.jfree.ui.RectangleEdge;
+
+
+/**
+ * Custom Annotations class that is drawn only if no collisions with other
+ * already drawn CustomAnnotations in current plot are found.
+ */
+public class CollisionFreeXYTextAnnotation extends XYTextAnnotation {
+
+    /** Logger for this class. */
+    private static Logger logger =
+        Logger.getLogger(CollisionFreeXYTextAnnotation.class);
+
+
+    public CollisionFreeXYTextAnnotation(String text, double x, double y) {
+        super(text, x, y);
+    }
+
+
+    /**
+     * Draw the Annotation only if it does not collide with other
+     * already drawn Annotations- texts.
+     *
+     * @param g2            the graphics device.
+     * @param plot          the plot.
+     * @param dataArea      the data area.
+     * @param domainAxis    the domain axis.
+     * @param rangeAxis     the range axis.
+     * @param rendererIndex the render index.
+     * @param info          state information, escpecially collects info about
+     *                      already drawn shapes (and thus annotations), used
+     *                      for collision detection.
+     */
+    @Override
+    public void draw(
+        java.awt.Graphics2D g2,
+        XYPlot plot,
+        java.awt.geom.Rectangle2D dataArea,
+        ValueAxis domainAxis,
+        ValueAxis rangeAxis,
+        int rendererIndex,
+        PlotRenderingInfo info
+    ) {
+        // From superclass, adjusted access only.
+        PlotOrientation orientation = plot.getOrientation();
+        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
+                plot.getDomainAxisLocation(), orientation);
+        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
+                plot.getRangeAxisLocation(), orientation);
+
+        float anchorX = (float) domainAxis.valueToJava2D(
+                this.getX(), dataArea, domainEdge);
+        float anchorY = (float) rangeAxis.valueToJava2D(
+                this.getY(), dataArea, rangeEdge);
+
+        if (orientation == PlotOrientation.HORIZONTAL) {
+            float tempAnchor = anchorX;
+            anchorX = anchorY;
+            anchorY = tempAnchor;
+        }
+
+        g2.setFont(getFont());
+        Shape hotspot = TextUtilities.calculateRotatedStringBounds(
+                getText(), g2, anchorX, anchorY, getTextAnchor(),
+                getRotationAngle(), getRotationAnchor());
+
+        // Deviation from superclass: prevent collision.
+        Rectangle2D hotspotBox = hotspot.getBounds2D();
+
+        for (Iterator i = info.getOwner().getEntityCollection().iterator();
+                i.hasNext(); ) {
+            Object next = i.next();
+            // Collision with other stuff than XYAnnotations are okay.
+            if (next instanceof CollisionFreeXYTextAnnotationEntity) {
+                XYAnnotationEntity drawnShape = (XYAnnotationEntity) next;
+                if (drawnShape.getArea().intersects(hotspotBox)) {
+                    // Found collision, early stop.
+                    return;
+                }
+            }
+        }
+
+        if (this.getBackgroundPaint() != null) {
+            g2.setPaint(this.getBackgroundPaint());
+            g2.fill(hotspot);
+        }
+        g2.setPaint(getPaint());
+        TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
+                getTextAnchor(), getRotationAngle(), getRotationAnchor());
+        if (this.isOutlineVisible()) {
+            g2.setStroke(this.getOutlineStroke());
+            g2.setPaint(this.getOutlinePaint());
+            g2.draw(hotspot);
+        }
+
+        //String toolTip = getToolTipText();
+        //String url = getURL();
+        String toolTip = "CollisionFreeXYTextAnnotation";
+        String url     = toolTip;
+
+        if (toolTip != null || url != null) {
+            addEntity(info, hotspot, rendererIndex, toolTip, url);
+        }
+        else {
+            addEntity(info, hotspot, rendererIndex,
+                "CollisionFreeXYTextAnnotation",
+                "CollisionFreeXYTextAnnotation");
+        }
+    }
+
+    /**
+     * A utility method for adding an {@link CollisionFreeXYAnnotationEntity} to
+     * a {@link PlotRenderingInfo} instance.
+     *
+     * @param info  the plot rendering info (<code>null</code> permitted).
+     * @param hotspot  the hotspot area.
+     * @param rendererIndex  the renderer index.
+     * @param toolTipText  the tool tip text.
+     * @param urlText  the URL text.
+     */
+    protected void addEntity(PlotRenderingInfo info,
+                             Shape hotspot, int rendererIndex,
+                             String toolTipText, String urlText) {
+        if (info == null) {
+            return;
+        }
+        EntityCollection entities = info.getOwner().getEntityCollection();
+        if (entities == null) {
+            return;
+        }
+        CollisionFreeXYTextAnnotationEntity entity =
+            new CollisionFreeXYTextAnnotationEntity(hotspot,
+                rendererIndex, toolTipText, urlText);
+        entities.add(entity);
+    } 
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/CollisionFreeXYTextAnnotationEntity.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,19 @@
+package de.intevation.flys.jfree;
+
+import java.awt.Shape;
+
+import org.jfree.chart.entity.XYAnnotationEntity;
+
+
+public class CollisionFreeXYTextAnnotationEntity
+extends XYAnnotationEntity {
+    public CollisionFreeXYTextAnnotationEntity(
+        Shape hotspot,
+        int rendererIndex,
+        String toolTip,
+        String url
+    ) {
+        super(hotspot, rendererIndex, toolTip, url);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/DoubleBounds.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,74 @@
+package de.intevation.flys.jfree;
+
+
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.data.Range;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DoubleBounds implements Bounds {
+
+    protected double lower;
+    protected double upper;
+
+
+    /**
+     * Default constructor. <b>A DoubleBounds has always set lower &lt;
+     * upper!</b>
+     */
+    public DoubleBounds(double lower, double upper) {
+        this.lower = Math.min(lower, upper);
+        this.upper = Math.max(lower, upper);
+    }
+
+
+    @Override
+    public Number getLower() {
+        return Double.valueOf(lower);
+    }
+
+
+    @Override
+    public Number getUpper() {
+        return Double.valueOf(upper);
+    }
+
+
+    @Override
+    public void applyBounds(ValueAxis axis) {
+        axis.setRange(new Range(lower, upper));
+    }
+
+
+    @Override
+    public void applyBounds(ValueAxis axis, int percent) {
+        double space = (upper - lower) / 100 * percent;
+        axis.setRange(new Range(lower-space, upper+space));
+    }
+
+
+    @Override
+    public Bounds combine(Bounds bounds) {
+        if (bounds == null) {
+            return this;
+        }
+
+        DoubleBounds other = (DoubleBounds) bounds;
+
+        double otherLower = other.getLower().doubleValue();
+        double otherUpper = other.getUpper().doubleValue();
+
+        return new DoubleBounds(
+            otherLower < lower ? otherLower : lower,
+            otherUpper > upper ? otherUpper : upper);
+    }
+
+
+    @Override
+    public String toString() {
+        return "DoubleBounds=[" + lower + " ; " + upper + "]";
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,508 @@
+package de.intevation.flys.jfree;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.geom.Rectangle2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.entity.EntityCollection;
+import org.jfree.chart.plot.CrosshairState;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.ui.RectangleEdge;
+import org.jfree.util.BooleanList;
+import org.jfree.util.ShapeUtilities;
+
+import de.intevation.flys.jfree.HasLabel;
+
+
+/**
+ * Renderer with additional the additional functionality of renderering minima
+ * and/or maxima of dataseries contained in datasets.
+ */
+public class EnhancedLineAndShapeRenderer extends XYLineAndShapeRenderer {
+
+    /** Own logger. */
+    private static final Logger logger =
+        Logger.getLogger(EnhancedLineAndShapeRenderer.class);
+
+    protected BooleanList isMinimumShapeVisible;
+    protected BooleanList isMaximumShapeVisible;
+    protected BooleanList showLineLabel;
+
+    protected Map<Integer, Double> seriesMinimum;
+    protected Map<Integer, Double> seriesMinimumX;
+    protected Map<Integer, Double> seriesMaximum;
+
+    protected Map<Integer, Font> lineLabelFonts;
+    protected Map<Integer, Color> lineLabelTextColors;
+    protected BooleanList showLineLabelBG;
+    protected Map<Integer, Color> lineLabelBGColors;
+
+
+    public EnhancedLineAndShapeRenderer(boolean lines, boolean shapes) {
+        super(lines, shapes);
+        this.isMinimumShapeVisible = new BooleanList();
+        this.isMaximumShapeVisible = new BooleanList();
+        this.showLineLabel         = new BooleanList();
+        this.showLineLabelBG       = new BooleanList();
+        this.seriesMinimum         = new HashMap<Integer, Double>();
+        this.seriesMaximum         = new HashMap<Integer, Double>();
+        this.seriesMinimumX        = new HashMap<Integer, Double>();
+        this.lineLabelFonts        = new HashMap<Integer, Font>();
+        this.lineLabelTextColors   = new HashMap<Integer, Color>();
+        this.lineLabelBGColors     = new HashMap<Integer, Color>();
+    }
+
+
+    /** Draw a background-box of a text to render. */
+    public static void drawTextBox(Graphics2D g2, String text, float textX, float textY, Color bgColor) {
+        Rectangle2D hotspotBox = g2.getFontMetrics().getStringBounds(text, g2);
+        float w = (float) hotspotBox.getWidth(), h = (float) hotspotBox.getHeight();
+        hotspotBox.setRect(textX, textY-h, w, h);
+        Color oldColor = g2.getColor();
+        g2.setColor(bgColor);
+        g2.fill(hotspotBox);
+        g2.setColor(oldColor);
+    }
+
+
+    public boolean getItemShapeVisible(XYDataset dataset, int series, int item){
+        if (super.getItemShapeVisible(series, item)) {
+            return true;
+        }
+
+        if (isMinimumShapeVisible(series) && isMinimum(dataset, series, item)) {
+            return true;
+        }
+
+        if (isMaximumShapeVisible(series) && isMaximum(dataset, series, item)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    public Shape getMaximumShape(int series, int column) {
+        return new Rectangle2D.Double(-5d, -5d, 10d, 10d);
+    }
+
+
+    public Shape getMinimumShape(int series, int column) {
+        return new Rectangle2D.Double(-5d, -5d, 10d, 10d);
+    }
+
+
+    public Paint getMaximumFillPaint(int series, int column) {
+        Paint p = getItemPaint(series, column);
+
+        if (p instanceof Color) {
+            Color c = (Color) p;
+            Color b = c;
+
+            for (int i = 0; i < 2; i++) {
+                b = b.darker();
+            }
+
+            return b;
+        }
+
+        logger.warn("Item paint is no instance of Color!");
+        return p;
+    }
+
+
+    public Paint getMinimumFillPaint(int series, int column) {
+        Paint p = getItemPaint(series, column);
+
+        if (p instanceof Color) {
+            Color c = (Color) p;
+            Color b = c;
+
+            for (int i = 0; i < 2; i++) {
+                b = b.darker();
+            }
+
+            return b;
+        }
+
+        logger.warn("Item paint is no instance of Color!");
+        return p;
+    }
+
+
+    /**
+     * Overrides XYLineAndShapeRenderer.drawSecondaryPass() to call an adapted
+     * method getItemShapeVisible() which now takes an XYDataset. So, 99% of
+     * code equal the code in XYLineAndShapeRenderer.
+     */
+    @Override
+    protected void drawSecondaryPass(
+        Graphics2D       g2,
+        XYPlot           plot,
+        XYDataset        dataset,
+        int              pass,
+        int              series,
+        int              item,
+        ValueAxis        domainAxis,
+        Rectangle2D      dataArea,
+        ValueAxis        rangeAxis,
+        CrosshairState   crosshairState,
+        EntityCollection entities
+    ) {
+        Shape entityArea = null;
+
+        // get the data point...
+        double x1 = dataset.getXValue(series, item);
+        double y1 = dataset.getYValue(series, item);
+        if (Double.isNaN(y1) || Double.isNaN(x1)) {
+            return;
+        }
+
+        PlotOrientation orientation = plot.getOrientation();
+        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
+        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
+        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
+        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
+
+        if (getItemShapeVisible(dataset, series, item)) {
+            Shape shape = null;
+
+            // OPTIMIZE: instead of calculating minimum and maximum for every
+            //           point, calculate it just once (assume that dataset
+            //           content does not change during rendering).
+            // NOTE:     Above OPTIMIZE might already be fulfilled to most extend.
+            boolean isMinimum = isMinimumShapeVisible(series)
+                && isMinimum(dataset, series, item);
+
+            boolean isMaximum = isMaximumShapeVisible(series)
+                && isMaximum(dataset, series, item);
+
+            if (isMinimum) {
+                logger.debug("Create a Minimum shape.");
+                shape = getMinimumShape(series, item);
+            }
+            else if (isMaximum) {
+                logger.debug("Create a Maximum shape.");
+                shape = getMaximumShape(series, item);
+            }
+            else {
+                shape = getItemShape(series, item);
+            }
+
+            if (orientation == PlotOrientation.HORIZONTAL) {
+                shape = ShapeUtilities.createTranslatedShape(shape, transY1,
+                        transX1);
+            }
+            else if (orientation == PlotOrientation.VERTICAL) {
+                shape = ShapeUtilities.createTranslatedShape(shape, transX1,
+                        transY1);
+            }
+            entityArea = shape;
+            if (shape.intersects(dataArea)) {
+                if (getItemShapeFilled(series, item)) {
+                    if (getUseFillPaint()) {
+                        g2.setPaint(getItemFillPaint(series, item));
+                    }
+                    else {
+                        g2.setPaint(getItemPaint(series, item));
+                    }
+                    g2.fill(shape);
+                }
+                if (getDrawOutlines()) {
+                    if (getUseOutlinePaint()) {
+                        g2.setPaint(getItemOutlinePaint(series, item));
+                    }
+                    else {
+                        g2.setPaint(getItemPaint(series, item));
+                    }
+                    g2.setStroke(getItemOutlineStroke(series, item));
+                    g2.draw(shape);
+                }
+
+                if (isMinimum) {
+                    g2.setPaint(getMinimumFillPaint(series, item));
+                    g2.fill(shape);
+                    g2.setPaint(getItemOutlinePaint(series, item));
+                    g2.setStroke(getItemOutlineStroke(series, item));
+                    g2.draw(shape);
+                }
+                else if (isMaximum) {
+                    g2.setPaint(getMaximumFillPaint(series, item));
+                    g2.fill(shape);
+                    g2.setPaint(getItemOutlinePaint(series, item));
+                    g2.setStroke(getItemOutlineStroke(series, item));
+                    g2.draw(shape);
+                }
+            }
+        } // if (getItemShapeVisible(dataset, series, item))
+
+        double xx = transX1;
+        double yy = transY1;
+        if (orientation == PlotOrientation.HORIZONTAL) {
+            xx = transY1;
+            yy = transX1;
+        }
+
+        // draw the item label if there is one...
+        if (isItemLabelVisible(series, item)) {
+            drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
+                    (y1 < 0.0));
+        }
+
+        // Draw label of line.
+        if (isShowLineLabel(series) && isMinimumX (dataset, series, item)) {
+            XYSeries xYSeries = ((XYSeriesCollection) dataset).getSeries(series);
+            String waterlevelLabel = (xYSeries instanceof HasLabel)
+                ? ((HasLabel)xYSeries).getLabel()
+                : xYSeries.getKey().toString();
+            // TODO Force water of some German rivers to flow direction mountains.
+            Font oldFont = g2.getFont();
+
+            Color oldColor = g2.getColor();
+            g2.setFont(this.getLineLabelFont(series));
+            g2.setColor(this.getLineLabelTextColor(series));
+            g2.setBackground(Color.black);
+
+            // Fill background.
+            if (isShowLineLabelBG(series)) {
+                drawTextBox(g2, waterlevelLabel, (float)xx, (float)yy-3f,
+                    getLineLabelBGColor(series));
+            }
+
+            g2.drawString(waterlevelLabel, (float)xx, (float)yy-3f);
+
+            g2.setFont(oldFont);
+            g2.setColor(oldColor);
+        }
+
+        int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
+        int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
+        updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
+                rangeAxisIndex, transX1, transY1, orientation);
+
+        // add an entity for the item, but only if it falls within the data
+        // area...
+        if (entities != null && isPointInRect(dataArea, xx, yy)) {
+            addEntity(entities, entityArea, dataset, series, item, xx, yy);
+        }
+    }
+
+
+    public void setIsMinimumShapeVisisble(int series, boolean isVisible) {
+        this.isMinimumShapeVisible.setBoolean(series, isVisible);
+    }
+
+
+    public boolean isMinimumShapeVisible(int series) {
+        if (this.isMinimumShapeVisible.size() <= series) {
+            return false;
+        }
+
+        return isMinimumShapeVisible.getBoolean(series);
+    }
+
+
+    public void setIsMaximumShapeVisible(int series, boolean isVisible) {
+        this.isMaximumShapeVisible.setBoolean(series, isVisible);
+    }
+
+
+    public boolean isMaximumShapeVisible(int series) {
+        if (this.isMaximumShapeVisible.size() <= series) {
+            return false;
+        }
+
+        return isMaximumShapeVisible.getBoolean(series);
+    }
+
+    /** Whether or not a label should be shown for series. */
+    public boolean isShowLineLabel(int series) {
+        if (this.showLineLabel.size() <= series) {
+            return false;
+        }
+
+        return showLineLabel.getBoolean(series);
+    }
+
+
+    /** Sets whether or not a label should be shown for series. */
+    public void setShowLineLabel(boolean showLineLabel, int series) {
+        this.showLineLabel.setBoolean(series, showLineLabel);
+    }
+
+    /** Whether or not a label should be shown for series. */
+    public boolean isShowLineLabelBG(int series) {
+        if (this.showLineLabelBG.size() <= series) {
+            return false;
+        }
+
+        return showLineLabelBG.getBoolean(series);
+    }
+
+    public void setShowLineLabelBG(int series, boolean doShow) {
+        this.showLineLabelBG.setBoolean(series, doShow);
+    }
+
+    public Color getLineLabelBGColor(int series) {
+        if (this.lineLabelBGColors.size() <= series) {
+            return null;
+        }
+
+        return this.lineLabelBGColors.get(series);
+    }
+
+    public void setLineLabelBGColor(int series, Color color) {
+        this.lineLabelBGColors.put(series, color);
+    }
+
+    public Color getLineLabelTextColor(int series) {
+        if (this.lineLabelTextColors.size() <= series) {
+            return null;
+        }
+
+        return this.lineLabelTextColors.get(series);
+    }
+
+    public void setLineLabelTextColor(int series, Color color) {
+        this.lineLabelTextColors.put(series, color);
+    }
+
+    public void setLineLabelFont(Font font, int series) {
+        this.lineLabelFonts.put(series, font);
+    }
+
+    public Font getLineLabelFont(int series) {
+        return this.lineLabelFonts.get(series);
+    }
+
+
+    /**
+     * True if the given item of given dataset has the smallest
+     * X value within this set.
+     */
+    public boolean isMinimumX(XYDataset dataset, int series, int item) {
+        return dataset.getXValue(series, item) == getMinimumX(dataset, series);
+    }
+
+
+    /**
+     * Get Minimum X Value of a given series in a dataset.
+     * The value is stored for later use if queried the first time.
+     */
+    public double getMinimumX(XYDataset dataset, int series) {
+        Integer key = Integer.valueOf(series);
+        Double  old = seriesMinimumX.get(key);
+
+        if (old != null) {
+            return old.doubleValue();
+        }
+
+        logger.debug("Compute minimum of Series: " + series);
+
+        double min = Double.MAX_VALUE;
+
+        for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
+            double tmpValue = dataset.getXValue(series, i);
+
+            if (tmpValue < min) {
+                min = tmpValue;
+            }
+        }
+
+        seriesMinimumX.put(key, Double.valueOf(min));
+
+        return min;
+    }
+
+
+    /**
+     * True if the given item of given dataset has the smallest
+     * Y value within this set.
+     */
+    public boolean isMinimum(XYDataset dataset, int series, int item) {
+        return dataset.getYValue(series, item) == getMinimum(dataset, series);
+    }
+
+
+    /**
+     * Get Minimum Y Value of a given series in a dataset.
+     * The value is stored for later use if queried the first time.
+     */
+    public double getMinimum(XYDataset dataset, int series) {
+        Integer key = Integer.valueOf(series);
+        Double  old = seriesMinimum.get(key);
+
+        if (old != null) {
+            return old.doubleValue();
+        }
+
+        logger.debug("Compute minimum of Series: " + series);
+
+        double min = Double.MAX_VALUE;
+
+        for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
+            double tmpValue = dataset.getYValue(series, i);
+
+            if (tmpValue < min) {
+                min = tmpValue;
+            }
+        }
+
+        seriesMinimum.put(key, Double.valueOf(min));
+
+        return min;
+    }
+
+
+    /**
+     * True if the given item of given dataset has the biggest
+     * Y value within this set.
+     */
+    public boolean isMaximum(XYDataset dataset, int series, int item) {
+        return dataset.getYValue(series, item) == getMaximum(dataset, series);
+    }
+
+
+    /**
+     * Get maximum Y Value of a given series in a dataset.
+     * The value is stored for later use if queried the first time.
+     */
+    public double getMaximum(XYDataset dataset, int series) {
+        Integer key = Integer.valueOf(series);
+        Double  old = seriesMaximum.get(key);
+
+        if (old != null) {
+            return old.doubleValue();
+        }
+
+        logger.debug("Compute maximum of Series: " + series);
+
+        double max = -Double.MAX_VALUE;
+
+        for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
+            double tmpValue = dataset.getYValue(series, i);
+
+            if (tmpValue > max) {
+                max = tmpValue;
+            }
+        }
+
+        seriesMaximum.put(key, Double.valueOf(max));
+
+        return max;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/FLYSAnnotation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,110 @@
+package de.intevation.flys.jfree;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.annotations.XYTextAnnotation;
+
+import de.intevation.flys.jfree.StickyAxisAnnotation;
+
+import de.intevation.flys.artifacts.model.HYKFactory;
+
+
+/**
+ * List of Text- Annotations with name and theme.
+ */
+public class FLYSAnnotation {
+
+    /* 'Other' Text Annotations. */
+    protected List<XYTextAnnotation> textAnnotations;
+
+    /** Annotations at axis. */
+    protected List<StickyAxisAnnotation> axisTextAnnotations;
+
+    /** Areas at axis. */
+    protected List<HYKFactory.Zone> boxes;
+
+    /** Styling information. */
+    protected Document theme;
+
+    /** Chart-legend information. */
+    protected String label;
+
+
+    public FLYSAnnotation(String label, List<StickyAxisAnnotation> annotations) {
+        this(label, annotations, null, null);
+    }
+
+
+    /** Create annotations, parameter might be null. */
+    public FLYSAnnotation(String label, List<StickyAxisAnnotation> annotations,
+        List<HYKFactory.Zone> bAnnotations
+    ) {
+        this(label, annotations, bAnnotations, null);
+    }
+
+
+    /** Create annotations, parameter might be null. */
+    public FLYSAnnotation(String label, List<StickyAxisAnnotation> annotations,
+        List<HYKFactory.Zone> bAnnotations, Document theme
+    ) {
+        this.label           = label;
+        this.axisTextAnnotations = (annotations != null)
+                               ? annotations
+                               : Collections.<StickyAxisAnnotation>emptyList();
+        this.boxes  = (bAnnotations != null)
+                               ? bAnnotations
+                               : Collections.<HYKFactory.Zone>emptyList();
+        this.textAnnotations = Collections.<XYTextAnnotation>emptyList();
+        this.setTheme(theme);
+    }
+
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public List<StickyAxisAnnotation> getAxisTextAnnotations() {
+        return axisTextAnnotations;
+    }
+
+    public void setTextAnnotations(List<XYTextAnnotation> annotations) {
+        this.textAnnotations = annotations;
+    }
+
+    /** Set the "other" Text Annotations. */
+    public List<XYTextAnnotation> getTextAnnotations() {
+        return textAnnotations;
+    }
+
+    public List<HYKFactory.Zone> getBoxes() {
+        return boxes;
+    }
+
+    public void setTheme(Document theme) {
+        this.theme = theme;
+    }
+
+    public Document getTheme() {
+        return theme;
+    }
+
+    /**
+     * Set sticky axis of all axisTextAnnotations
+     * to the X axis if it is currently Y, and vice versa.
+     * @return this
+     */
+    public FLYSAnnotation flipStickyAxis() {
+        for (StickyAxisAnnotation saa: axisTextAnnotations) {
+            saa.flipStickyAxis();
+        }
+        return this;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/HasLabel.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,8 @@
+package de.intevation.flys.jfree;
+
+public interface HasLabel {
+    public String getLabel();
+    public void setLabel(String label);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,1831 @@
+/* ===========================================================
+ * JFreeChart : a free chart library for the Java(tm) platform
+ * ===========================================================
+ *
+ * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
+ *
+ * Project Info:  http://www.jfree.org/jfreechart/index.html
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
+ * in the United States and other countries.]
+ *
+ * -------------------------
+ * StableXYDifferenceRenderer.java
+ * -------------------------
+ * (C) Copyright 2003-2008, by Object Refinery Limited and Contributors.
+ *
+ * Original Author:  David Gilbert (for Object Refinery Limited);
+ * Contributor(s):   Richard West, Advanced Micro Devices, Inc. (major rewrite
+ *                   of difference drawing algorithm);
+ *
+ * Changes:
+ * --------
+ * 30-Apr-2003 : Version 1 (DG);
+ * 30-Jul-2003 : Modified entity constructor (CZ);
+ * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
+ * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
+ * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
+ * 10-Feb-2004 : Added default constructor, setter methods and updated
+ *               Javadocs (DG);
+ * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
+ * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
+ * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
+ *               getYValue() (DG);
+ * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
+ * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
+ * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
+ * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
+ * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
+ * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
+ * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
+ *               get/setShapesVisible (DG);
+ * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
+ * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
+ * ------------- JFREECHART 1.0.x ---------------------------------------------
+ * 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed
+ *               bug in clone() (DG);
+ * 05-Feb-2007 : Added an extra call to updateCrosshairValues() in
+ *               drawItemPass1(), to fix bug 1564967 (DG);
+ * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
+ * 08-Mar-2007 : Fixed entity generation (DG);
+ * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
+ * 23-Apr-2007 : Rewrite of difference drawing algorithm to allow use of
+ *               series with disjoint x-values (RW);
+ * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
+ * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
+ * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
+ * 05-Nov-2007 : Draw item labels if visible (RW);
+ * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
+ */
+/*
+ * For further changes within the FLYS project, refer to the ChangeLog.
+ */
+package de.intevation.flys.jfree;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.geom.Point2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jfree.chart.LegendItem;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.entity.EntityCollection;
+import org.jfree.chart.entity.XYItemEntity;
+import org.jfree.chart.event.RendererChangeEvent;
+import org.jfree.chart.labels.XYToolTipGenerator;
+import org.jfree.chart.plot.CrosshairState;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.PlotRenderingInfo;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.urls.XYURLGenerator;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.data.xy.DefaultXYDataset;
+import org.jfree.io.SerialUtilities;
+import org.jfree.ui.RectangleEdge;
+import org.jfree.util.PaintUtilities;
+import org.jfree.util.PublicCloneable;
+import org.jfree.util.ShapeUtilities;
+
+import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.chart.renderer.xy.XYItemRendererState;
+
+import gnu.trove.TDoubleArrayList;
+
+import de.intevation.flys.artifacts.math.Linear;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A renderer for an {@link XYPlot} that highlights the differences between two
+ * series.  The example shown here is generated by the
+ * <code>DifferenceChartDemo1.java</code> program included in the JFreeChart
+ * demo collection:
+ * <br><br>
+ * <img src="../../../../../images/StableXYDifferenceRendererSample.png"
+ * alt="StableXYDifferenceRendererSample.png" />
+ */
+public class StableXYDifferenceRenderer extends AbstractXYItemRenderer
+        implements XYItemRenderer, PublicCloneable {
+
+    private static Logger log = Logger.getLogger(StableXYDifferenceRenderer.class);
+
+    public static final int CALCULATE_POSITIVE_AREA = 1;
+    public static final int CALCULATE_NEGATIVE_AREA = 2;
+    public static final int CALCULATE_ALL_AREA =
+                            CALCULATE_POSITIVE_AREA | CALCULATE_NEGATIVE_AREA;
+
+    /** For serialization. */
+    private static final long serialVersionUID = -8447915602375584857L;
+
+    /** The paint used to highlight positive differences (y(0) > y(1)). */
+    private transient Paint positivePaint;
+
+    /** The paint used to highlight negative differences (y(0) < y(1)). */
+    private transient Paint negativePaint;
+
+    /** Display shapes at each point? */
+    private boolean shapesVisible;
+
+    /** Display shapes at each point? */
+    protected boolean drawOutline;
+
+    /** Which stroke to draw outline with? */
+    protected Stroke outlineStroke;
+
+    /** Which paint to draw outline with? */
+    protected Paint outlinePaint;
+
+    /** The shape to display in the legend item. */
+    private transient Shape legendShape;
+
+    protected boolean drawOriginalSeries;
+
+    /** The color of the label showing the calculated area. */
+    protected Color labelColor;
+
+    /** The background color of the label showing the calculated area. */
+    protected Color labelBGColor;
+
+    /** Font to draw label of calculated area with. */
+    protected Font labelFont;
+
+    protected int areaCalculationMode;
+
+    protected double positiveArea;
+    protected double negativeArea;
+
+    /** Whether or not to draw a label in the area. */
+    protected boolean labelArea = true;
+
+
+    /** Arithmetic centroid of drawn polygons. */
+    protected Point2D.Double centroid;
+
+
+    /** Number of points that contributed to the centroid. */
+    protected int centroidNPoints = 0;
+
+
+    /**
+     * This flag controls whether or not the x-coordinates (in Java2D space)
+     * are rounded to integers.  When set to true, this can avoid the vertical
+     * striping that anti-aliasing can generate.  However, the rounding may not
+     * be appropriate for output in high resolution formats (for example,
+     * vector graphics formats such as SVG and PDF).
+     *
+     * @since 1.0.4
+     */
+    private boolean roundXCoordinates;
+
+    /**
+     * Creates a new renderer with default attributes.
+     */
+    public StableXYDifferenceRenderer() {
+        this(Color.green, Color.red, false /*,  null */);
+    }
+
+    public StableXYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
+                                boolean shapes) {
+        this(positivePaint, negativePaint, shapes, CALCULATE_ALL_AREA);
+    }
+
+    /**
+     * Creates a new renderer.
+     *
+     * @param positivePaint  the highlight color for positive differences
+     *                       (<code>null</code> not permitted).
+     * @param negativePaint  the highlight color for negative differences
+     *                       (<code>null</code> not permitted).
+     * @param shapes  draw shapes?
+     */
+    public StableXYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
+                                boolean shapes, int areaCalculationMode) {
+        if (positivePaint == null) {
+            throw new IllegalArgumentException(
+                    "Null 'positivePaint' argument.");
+        }
+        if (negativePaint == null) {
+            throw new IllegalArgumentException(
+                    "Null 'negativePaint' argument.");
+        }
+        this.positivePaint = positivePaint;
+        this.negativePaint = negativePaint;
+        this.shapesVisible = shapes;
+        this.legendShape   = new Rectangle2D.Double(-3.0, -3.0, 10.0, 10.0);
+        this.roundXCoordinates = false;
+        this.drawOutline   = true;
+        this.outlineStroke = new BasicStroke(1);
+        this.outlinePaint  = Color.black;
+        this.drawOriginalSeries = false;
+        this.areaCalculationMode = areaCalculationMode;
+        this.labelBGColor  = null;
+        this.centroid      = new Point2D.Double(0,0);
+    }
+
+    public int getAreaCalculationMode() {
+        return areaCalculationMode;
+    }
+
+    public void setAreaCalculationMode(int areaCalculationMode) {
+        this.areaCalculationMode = areaCalculationMode;
+    }
+
+    public boolean isLabelArea() {
+        return this.labelArea;
+    }
+
+    public void setLabelArea(boolean label) {
+        this.labelArea = label;
+    }
+
+
+    /** Set font to paint label with. */
+    public void setLabelFont(Font font) {
+        this.labelFont = font;
+    }
+
+
+    /** Get font with which label is painted. */
+    public Font getLabelFont() {
+        return this.labelFont;
+    }
+
+    
+    /** Set color with which to paint label. */
+    public void setLabelColor(Color color) {
+        this.labelColor = color;
+    }
+
+
+    /** Get color with which label is painted. */
+    public Color getLabelColor() {
+        return this.labelColor;
+    }
+
+
+    /** Set color with which to paint label bg. */
+    public void setLabelBGColor(Color color) {
+        this.labelBGColor = color;
+    }
+
+
+    /** Get color with which label is painted. */
+    public Color getLabelBGColor() {
+        return this.labelBGColor;
+    }
+
+
+    public double getCalculatedArea() {
+        return positiveArea + negativeArea;
+    }
+
+    /**
+     * Sets color that is used if drawOutline is true.
+     */
+    public void setOutlinePaint(Paint outlinePaint) {
+        this.outlinePaint = outlinePaint;
+    }
+
+
+    /**
+     * Gets color which is used if drawOutline is true.
+     */
+    public Paint getOutlinePaint() {
+        return this.outlinePaint;
+    }
+
+
+    /**
+     * Sets Stroke that is used if drawOutline is true.
+     */
+    public void setOutlineStroke(Stroke stroke) {
+        this.outlineStroke = stroke;
+    }
+
+
+    /**
+     * Returns Stroke that is used if drawOutline is true.
+     */
+    public Stroke getOutlineStroke() {
+        return this.outlineStroke;
+    }
+
+
+    /**
+     * Whether or not to draw the 'Shape' of the area (in contrast to
+     * shapes at data items).
+     */
+    public void setDrawOutline(boolean doDrawOutline) {
+        this.drawOutline = doDrawOutline;
+    }
+
+
+    /**
+     * Returns whether or not to draw the shape of the outline.
+     */
+    public boolean getDrawOutline() {
+        return this.drawOutline;
+    }
+
+
+    /**
+     * Returns the paint used to highlight positive differences.
+     *
+     * @return The paint (never <code>null</code>).
+     *
+     * @see #setPositivePaint(Paint)
+     */
+    public Paint getPositivePaint() {
+        return this.positivePaint;
+    }
+
+    /**
+     * Sets the paint used to highlight positive differences and sends a
+     * {@link RendererChangeEvent} to all registered listeners.
+     *
+     * @param paint  the paint (<code>null</code> not permitted).
+     *
+     * @see #getPositivePaint()
+     */
+    public void setPositivePaint(Paint paint) {
+        if (paint == null) {
+            throw new IllegalArgumentException("Null 'paint' argument.");
+        }
+        this.positivePaint = paint;
+        fireChangeEvent();
+    }
+
+    /**
+     * Returns the paint used to highlight negative differences.
+     *
+     * @return The paint (never <code>null</code>).
+     *
+     * @see #setNegativePaint(Paint)
+     */
+    public Paint getNegativePaint() {
+        return this.negativePaint;
+    }
+
+    /**
+     * Sets the paint used to highlight negative differences.
+     *
+     * @param paint  the paint (<code>null</code> not permitted).
+     *
+     * @see #getNegativePaint()
+     */
+    public void setNegativePaint(Paint paint) {
+        if (paint == null) {
+            throw new IllegalArgumentException("Null 'paint' argument.");
+        }
+        this.negativePaint = paint;
+        notifyListeners(new RendererChangeEvent(this));
+    }
+
+    /**
+     * Returns a flag that controls whether or not shapes are drawn for each
+     * data value.
+     *
+     * @return A boolean.
+     *
+     * @see #setShapesVisible(boolean)
+     */
+    public boolean getShapesVisible() {
+        return this.shapesVisible;
+    }
+
+    /**
+     * Sets a flag that controls whether or not shapes are drawn for each
+     * data value, and sends a {@link RendererChangeEvent} to all registered
+     * listeners.
+     *
+     * @param flag  the flag.
+     *
+     * @see #getShapesVisible()
+     */
+    public void setShapesVisible(boolean flag) {
+        this.shapesVisible = flag;
+        fireChangeEvent();
+    }
+
+    /**
+     * Returns the shape used to represent a line in the legend.
+     *
+     * @return The legend line (never <code>null</code>).
+     *
+     * @see #setLegendLine(Shape)
+     */
+    public Shape getLegendLine() {
+        return this.legendShape;
+    }
+
+    /**
+     * Sets the shape used as a line in each legend item and sends a
+     * {@link RendererChangeEvent} to all registered listeners.
+     *
+     * @param line  the line (<code>null</code> not permitted).
+     *
+     * @see #getLegendLine()
+     */
+    public void setLegendLine(Shape line) {
+        if (line == null) {
+            throw new IllegalArgumentException("Null 'line' argument.");
+        }
+        this.legendShape = line;
+        fireChangeEvent();
+    }
+
+    /**
+     * Returns the flag that controls whether or not the x-coordinates (in
+     * Java2D space) are rounded to integer values.
+     *
+     * @return The flag.
+     *
+     * @since 1.0.4
+     *
+     * @see #setRoundXCoordinates(boolean)
+     */
+    public boolean getRoundXCoordinates() {
+        return this.roundXCoordinates;
+    }
+
+    /**
+     * Sets the flag that controls whether or not the x-coordinates (in
+     * Java2D space) are rounded to integer values, and sends a
+     * {@link RendererChangeEvent} to all registered listeners.
+     *
+     * @param round  the new flag value.
+     *
+     * @since 1.0.4
+     *
+     * @see #getRoundXCoordinates()
+     */
+    public void setRoundXCoordinates(boolean round) {
+        this.roundXCoordinates = round;
+        fireChangeEvent();
+    }
+
+    /**
+     * Initialises the renderer and returns a state object that should be
+     * passed to subsequent calls to the drawItem() method.  This method will
+     * be called before the first item is rendered, giving the renderer an
+     * opportunity to initialise any state information it wants to maintain.
+     * The renderer can do nothing if it chooses.
+     *
+     * @param g2  the graphics device.
+     * @param dataArea  the area inside the axes.
+     * @param plot  the plot.
+     * @param data  the data.
+     * @param info  an optional info collection object to return data back to
+     *              the caller.
+     *
+     * @return A state object.
+     */
+    public XYItemRendererState initialise(Graphics2D g2,
+                                          Rectangle2D dataArea,
+                                          XYPlot plot,
+                                          XYDataset data,
+                                          PlotRenderingInfo info) {
+
+        XYItemRendererState state = super.initialise(g2, dataArea, plot, data,
+                info);
+        state.setProcessVisibleItemsOnly(false);
+        return state;
+    }
+
+    /**
+     * Returns <code>2</code>, the number of passes required by the renderer.
+     * The {@link XYPlot} will run through the dataset this number of times.
+     *
+     * @return The number of passes required by the renderer.
+     */
+    public int getPassCount() {
+        return 2;
+    }
+
+    private static final void addSeries(
+        DefaultXYDataset ds,
+        Comparable       key,
+        TDoubleArrayList xs,
+        TDoubleArrayList ys
+    ) {
+        ds.addSeries(
+            key,
+            new double [][] {
+                xs.toNativeArray(),
+                ys.toNativeArray()
+            });
+    }
+
+    protected static List<XYDataset> splitByNaNsOneSeries(
+        XYDataset dataset
+    ) {
+        List<XYDataset> datasets = new ArrayList<XYDataset>();
+
+        int N = dataset.getItemCount(0);
+        TDoubleArrayList xs = new TDoubleArrayList(N);
+        TDoubleArrayList ys = new TDoubleArrayList(N);
+        for (int i = 0; i < N; ++i) {
+            double x = dataset.getXValue(0, i);
+            double y = dataset.getYValue(0, i);
+            if (Double.isNaN(x) || Double.isNaN(y)) {
+                if (!xs.isEmpty()) {
+                    DefaultXYDataset ds = new DefaultXYDataset();
+                    addSeries(ds, dataset.getSeriesKey(0), xs, ys);
+                    datasets.add(ds);
+                    xs.resetQuick();
+                    ys.resetQuick();
+                }
+            }
+            else {
+                xs.add(x);
+                ys.add(y);
+            }
+        }
+        if (!xs.isEmpty()) {
+            DefaultXYDataset ds = new DefaultXYDataset();
+            addSeries(ds, dataset.getSeriesKey(0), xs, ys);
+            datasets.add(ds);
+        }
+
+        return datasets;
+    }
+
+    private static final boolean add(TDoubleArrayList xs, double x) {
+        int N = xs.size();
+        if (N == 0 || xs.getQuick(N-1) < x) {
+            xs.add(x);
+            return true;
+        }
+        log.debug("pushed smaller");
+        return false;
+    }
+
+    protected static List<XYDataset> splitByNaNsTwoSeries(
+        XYDataset dataset
+    ) {
+        boolean debug = log.isDebugEnabled();
+
+        List<XYDataset> datasets = new ArrayList<XYDataset>();
+
+        int N = dataset.getItemCount(0);
+        int M = dataset.getItemCount(1);
+
+        int i = 0, j = 0;
+        // ignore leading NaNs
+        for (; i < N; ++i) {
+            double x = dataset.getXValue(0, i);
+            double y = dataset.getYValue(0, i);
+            if (!Double.isNaN(x) && !Double.isNaN(y)) {
+                break;
+            }
+        }
+
+        for (; j < M; ++j) {
+            double x = dataset.getXValue(1, j);
+            double y = dataset.getYValue(1, j);
+            if (!Double.isNaN(x) && !Double.isNaN(y)) {
+                break;
+            }
+        }
+
+        TDoubleArrayList six = new TDoubleArrayList();
+        TDoubleArrayList siy = new TDoubleArrayList();
+        TDoubleArrayList sjx = new TDoubleArrayList();
+        TDoubleArrayList sjy = new TDoubleArrayList();
+
+        while (i < N && j < M) {
+            int ni = i+1;
+            for (; ni < N && !Double.isNaN(dataset.getXValue(0, ni)); ++ni);
+            for (; ni < N &&  Double.isNaN(dataset.getXValue(0, ni)); ++ni);
+
+            int nj = j+1;
+            for (; nj < M && !Double.isNaN(dataset.getXValue(1, nj)); ++nj);
+            for (; nj < M &&  Double.isNaN(dataset.getXValue(1, nj)); ++nj);
+
+            if (ni == N && nj == M) { // no more splits
+                log.debug("no more splits ....");
+                for (; i < ni; ++i) {
+                    double x = dataset.getXValue(0, i);
+                    double y = dataset.getYValue(0, i);
+                    if (!Double.isNaN(x) 
+                    &&  !Double.isNaN(y)
+                    &&  add(six, x)) {
+                        siy.add(y);
+                    }
+                }
+                for (; j < nj; ++j) {
+                    double x = dataset.getXValue(1, j);
+                    double y = dataset.getYValue(1, j);
+                    if (!Double.isNaN(x)
+                    &&  !Double.isNaN(y)
+                    &&  add(sjx, x)) {
+                        sjy.add(y);
+                    }
+                }
+                if (!six.isEmpty() && !sjx.isEmpty()) {
+                    DefaultXYDataset ds = new DefaultXYDataset();
+                    addSeries(ds, dataset.getSeriesKey(0), six, siy);
+                    addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
+                    datasets.add(ds);
+                }
+                break;
+            }
+
+            if (debug) {
+                log.debug("ni: " + ni + " " + N);
+                log.debug("nj: " + nj + " " + M);
+            }
+
+            double xni = ni < N
+                ? dataset.getXValue(0, ni)
+                : Double.MAX_VALUE;
+
+            double xnj = nj < M
+                ? dataset.getXValue(1, nj)
+                : Double.MAX_VALUE;
+
+            double xns = Math.min(xni, xnj);
+
+            double pushxi = Double.NaN;
+            double pushyi = Double.NaN;
+            double pushxj = Double.NaN;
+            double pushyj = Double.NaN;
+
+            for (; i < ni; ++i) {
+                double x = dataset.getXValue(0, i);
+                double y = dataset.getYValue(0, i);
+                if (Double.isNaN(x) || Double.isNaN(y)) {
+                    continue;
+                }
+                if (x < xns) {
+                    if (add(six, x)) {
+                        siy.add(y);
+                    }
+                    continue;
+                }
+                if (x == xns) { // exact match
+                    if (add(six, x)) {
+                        siy.add(y);
+                    }
+                    pushxi = x; pushyi = y;
+                }
+                else { // x > xns: intersection
+                    if (debug) {
+                        log.debug("xns: " + xns);
+                        log.debug("x/y: " + x + " / " + y);
+                    }
+                    int SIX = six.size();
+                    if (SIX > 0) { // should always be true
+                        double yns = Linear.linear(
+                            xns,
+                            six.getQuick(SIX-1), x, 
+                            siy.getQuick(SIX-1), y);
+                        if (debug) {
+                            log.debug("intersection at: " + yns);
+                        }
+                        if (add(six, xns)) {
+                            siy.add(yns);
+                        }
+                        pushxi = xns;
+                        pushyi = yns;
+                    }
+                }
+                break; // Split point reached.
+            }
+
+            for (; j < nj; ++j) {
+                double x = dataset.getXValue(1, j);
+                double y = dataset.getYValue(1, j);
+                if (Double.isNaN(x) || Double.isNaN(y)) {
+                    continue;
+                }
+                if (x < xns) {
+                    if (add(sjx, x)) {
+                        sjy.add(y);
+                    }
+                    continue;
+                }
+                if (x == xns) { // exact match
+                    if (add(sjx, x)) {
+                        sjy.add(y);
+                    }
+                    pushxj = x; pushyj = y;
+                }
+                else { // x > xns: intersection
+                    int SJX = sjx.size();
+                    if (SJX > 0) { // should always be true
+                        double yns = Linear.linear(
+                            xns,
+                            sjx.getQuick(SJX-1), x,
+                            sjy.getQuick(SJX-1), y);
+                        if (debug) {
+                            log.debug("intersection at: " + yns);
+                        }
+                        if (add(sjx, xns)) {
+                            sjy.add(yns);
+                        }
+                        pushxj = xns; pushyj = yns;
+                    }
+                }
+                break; // Split point reached.
+            }
+
+            if (!six.isEmpty() && !sjx.isEmpty()) {
+                DefaultXYDataset ds = new DefaultXYDataset();
+                addSeries(ds, dataset.getSeriesKey(0), six, siy);
+                addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
+                datasets.add(ds);
+            }
+
+            six.resetQuick(); siy.resetQuick();
+            sjx.resetQuick(); sjy.resetQuick();
+
+            // Push split points.
+            if (!Double.isNaN(pushxi)) {
+                six.add(pushxi);
+                siy.add(pushyi);
+            }
+
+            if (!Double.isNaN(pushxj)) {
+                sjx.add(pushxj);
+                sjy.add(pushyj);
+            }
+        }
+
+        // Copy the rest.
+        for (; i < N; ++i) {
+            double x = dataset.getXValue(0, i);
+            double y = dataset.getXValue(0, i);
+            if (!Double.isNaN(x) 
+            &&  !Double.isNaN(y)
+            &&  add(six, x)) {
+                siy.add(y);
+            }
+        }
+
+        for (; j < M; ++j) {
+            double x = dataset.getXValue(1, j);
+            double y = dataset.getXValue(1, j);
+            if (!Double.isNaN(x)
+            &&  !Double.isNaN(y)
+            &&  add(sjx, x)) {
+                sjy.add(y);
+            }
+        }
+
+        // Build final dataset.
+        if (!six.isEmpty() && !sjx.isEmpty()) {
+            DefaultXYDataset ds = new DefaultXYDataset();
+            addSeries(ds, dataset.getSeriesKey(0), six, siy);
+            addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
+            datasets.add(ds);
+        }
+
+        if (debug) {
+            log.debug("datasets after split: " + datasets.size());
+        }
+
+        return datasets;
+    }
+
+    public static List<XYDataset> splitByNaNs(XYDataset dataset)  {
+
+        switch (dataset.getSeriesCount()) {
+            case 0: 
+                return Collections.emptyList();
+            case 1:
+                return splitByNaNsOneSeries(dataset);
+            default: // two or more
+                return splitByNaNsTwoSeries(dataset);
+        }
+    }
+
+    /**
+     * Draws the visual representation of a single data item.
+     *
+     * @param g2  the graphics device.
+     * @param state  the renderer state.
+     * @param dataArea  the area within which the data is being drawn.
+     * @param info  collects information about the drawing.
+     * @param plot  the plot (can be used to obtain standard color
+     *              information etc).
+     * @param domainAxis  the domain (horizontal) axis.
+     * @param rangeAxis  the range (vertical) axis.
+     * @param dataset  the dataset.
+     * @param series  the series index (zero-based).
+     * @param item  the item index (zero-based).
+     * @param crosshairState  crosshair information for the plot
+     *                        (<code>null</code> permitted).
+     * @param pass  the pass index.
+     */
+    public void drawItem(Graphics2D g2,
+                         XYItemRendererState state,
+                         Rectangle2D dataArea,
+                         PlotRenderingInfo info,
+                         XYPlot plot,
+                         ValueAxis domainAxis,
+                         ValueAxis rangeAxis,
+                         XYDataset dataset,
+                         int series,
+                         int item,
+                         CrosshairState crosshairState,
+                         int pass) {
+        switch (pass) {
+            case 0:
+                for (XYDataset ds: splitByNaNs(dataset)) {
+                    drawItemPass0(g2, dataArea, info, 
+                        plot, domainAxis, rangeAxis,
+                        ds, series, item, crosshairState);
+                }
+                break;
+            case 1:
+                drawItemPass1(g2, dataArea, info,
+                    plot, domainAxis, rangeAxis,
+                    dataset, series, item, crosshairState);
+        }
+
+        // Find geometric middle, calculate area and paint a string with it here.
+        // TODO also i18n
+        if (pass == 1 && this.labelArea) {
+            double center_x = centroid.getX();
+            double center_y = centroid.getY();
+            center_x = domainAxis.valueToJava2D(center_x, dataArea,
+                plot.getDomainAxisEdge());
+            center_y = rangeAxis.valueToJava2D(center_y, dataArea,
+                plot.getRangeAxisEdge());
+
+            // Respect text-extend if text should appear really centered.
+
+            float area = 0f;
+            if (areaCalculationMode == CALCULATE_POSITIVE_AREA
+               || areaCalculationMode == CALCULATE_ALL_AREA) {
+                area += Math.abs(positiveArea);
+            }
+            if (areaCalculationMode == CALCULATE_NEGATIVE_AREA
+               || areaCalculationMode == CALCULATE_ALL_AREA) {
+                area += Math.abs(negativeArea);
+            }
+            if (area != 0f) {
+                Color oldColor = g2.getColor();
+                Font oldFont = g2.getFont();
+                g2.setFont(labelFont);
+                String labelText = "Area= " + area + "m2";
+                if (labelBGColor != null) {
+                    EnhancedLineAndShapeRenderer.drawTextBox(g2, labelText,
+                        (float)center_x, (float)center_y, labelBGColor);
+                }
+                g2.setColor(labelColor);
+                g2.drawString(labelText, (float)center_x, (float)center_y);
+                g2.setFont(oldFont);
+                g2.setColor(oldColor);
+            }
+        }
+    }
+
+    /**
+     * Draws the visual representation of a single data item, first pass.
+     *
+     * @param x_graphics  the graphics device.
+     * @param x_dataArea  the area within which the data is being drawn.
+     * @param x_info  collects information about the drawing.
+     * @param x_plot  the plot (can be used to obtain standard color
+     *                information etc).
+     * @param x_domainAxis  the domain (horizontal) axis.
+     * @param x_rangeAxis  the range (vertical) axis.
+     * @param x_dataset  the dataset.
+     * @param x_series  the series index (zero-based).
+     * @param x_item  the item index (zero-based).
+     * @param x_crosshairState  crosshair information for the plot
+     *                          (<code>null</code> permitted).
+     */
+    protected void drawItemPass0(Graphics2D x_graphics,
+                                 Rectangle2D x_dataArea,
+                                 PlotRenderingInfo x_info,
+                                 XYPlot x_plot,
+                                 ValueAxis x_domainAxis,
+                                 ValueAxis x_rangeAxis,
+                                 XYDataset x_dataset,
+                                 int x_series,
+                                 int x_item,
+                                 CrosshairState x_crosshairState) {
+
+        if (!((0 == x_series) && (0 == x_item))) {
+            return;
+        }
+
+        boolean b_impliedZeroSubtrahend = (1 == x_dataset.getSeriesCount());
+
+        // check if either series is a degenerate case (i.e. less than 2 points)
+        if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
+            return;
+        }
+
+        // check if series are disjoint (i.e. domain-spans do not overlap)
+        if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
+            return;
+        }
+
+        // polygon definitions
+        LinkedList l_minuendXs    = new LinkedList();
+        LinkedList l_minuendYs    = new LinkedList();
+        LinkedList l_subtrahendXs = new LinkedList();
+        LinkedList l_subtrahendYs = new LinkedList();
+        LinkedList l_polygonXs    = new LinkedList();
+        LinkedList l_polygonYs    = new LinkedList();
+
+        // state
+        int l_minuendItem      = 0;
+        int l_minuendItemCount = x_dataset.getItemCount(0);
+        Double l_minuendCurX   = null;
+        Double l_minuendNextX  = null;
+        Double l_minuendCurY   = null;
+        Double l_minuendNextY  = null;
+        double l_minuendMaxY   = Double.NEGATIVE_INFINITY;
+        double l_minuendMinY   = Double.POSITIVE_INFINITY;
+
+        int l_subtrahendItem      = 0;
+        int l_subtrahendItemCount = 0; // actual value set below
+        Double l_subtrahendCurX   = null;
+        Double l_subtrahendNextX  = null;
+        Double l_subtrahendCurY   = null;
+        Double l_subtrahendNextY  = null;
+        double l_subtrahendMaxY   = Double.NEGATIVE_INFINITY;
+        double l_subtrahendMinY   = Double.POSITIVE_INFINITY;
+
+        // if a subtrahend is not specified, assume it is zero
+        if (b_impliedZeroSubtrahend) {
+            l_subtrahendItem      = 0;
+            l_subtrahendItemCount = 2;
+            l_subtrahendCurX      = new Double(x_dataset.getXValue(0, 0));
+            l_subtrahendNextX     = new Double(x_dataset.getXValue(0,
+                    (l_minuendItemCount - 1)));
+            l_subtrahendCurY      = new Double(0.0);
+            l_subtrahendNextY     = new Double(0.0);
+            l_subtrahendMaxY      = 0.0;
+            l_subtrahendMinY      = 0.0;
+
+            l_subtrahendXs.add(l_subtrahendCurX);
+            l_subtrahendYs.add(l_subtrahendCurY);
+        }
+        else {
+            l_subtrahendItemCount = x_dataset.getItemCount(1);
+        }
+
+        boolean b_minuendDone           = false;
+        boolean b_minuendAdvanced       = true;
+        boolean b_minuendAtIntersect    = false;
+        boolean b_minuendFastForward    = false;
+        boolean b_subtrahendDone        = false;
+        boolean b_subtrahendAdvanced    = true;
+        boolean b_subtrahendAtIntersect = false;
+        boolean b_subtrahendFastForward = false;
+        boolean b_colinear              = false;
+
+        boolean b_positive;
+
+        // coordinate pairs
+        double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
+        double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
+        double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
+        double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
+
+        // fast-forward through leading tails
+        boolean b_fastForwardDone = false;
+        while (!b_fastForwardDone) {
+            // get the x and y coordinates
+            l_x1 = x_dataset.getXValue(0, l_minuendItem);
+            l_y1 = x_dataset.getYValue(0, l_minuendItem);
+            l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
+            l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
+
+            l_minuendCurX  = new Double(l_x1);
+            l_minuendCurY  = new Double(l_y1);
+            l_minuendNextX = new Double(l_x2);
+            l_minuendNextY = new Double(l_y2);
+
+            if (b_impliedZeroSubtrahend) {
+                l_x3 = l_subtrahendCurX.doubleValue();
+                l_y3 = l_subtrahendCurY.doubleValue();
+                l_x4 = l_subtrahendNextX.doubleValue();
+                l_y4 = l_subtrahendNextY.doubleValue();
+            }
+            else {
+                l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
+                l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
+                l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
+                l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
+
+                l_subtrahendCurX  = new Double(l_x3);
+                l_subtrahendCurY  = new Double(l_y3);
+                l_subtrahendNextX = new Double(l_x4);
+                l_subtrahendNextY = new Double(l_y4);
+            }
+
+            if (l_x2 <= l_x3) {
+                // minuend needs to be fast forwarded
+                l_minuendItem++;
+                b_minuendFastForward = true;
+                continue;
+            }
+
+            if (l_x4 <= l_x1) {
+                // subtrahend needs to be fast forwarded
+                l_subtrahendItem++;
+                b_subtrahendFastForward = true;
+                continue;
+            }
+
+            // check if initial polygon needs to be clipped
+            if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
+                // project onto subtrahend
+                double l_slope   = (l_y4 - l_y3) / (l_x4 - l_x3);
+                l_subtrahendCurX = l_minuendCurX;
+                l_subtrahendCurY = new Double((l_slope * l_x1)
+                        + (l_y3 - (l_slope * l_x3)));
+
+                l_subtrahendXs.add(l_subtrahendCurX);
+                l_subtrahendYs.add(l_subtrahendCurY);
+            }
+
+            if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
+                // project onto minuend
+                double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
+                l_minuendCurX  = l_subtrahendCurX;
+                l_minuendCurY  = new Double((l_slope * l_x3)
+                        + (l_y1 - (l_slope * l_x1)));
+
+                l_minuendXs.add(l_minuendCurX);
+                l_minuendYs.add(l_minuendCurY);
+            }
+
+            l_minuendMaxY    = l_minuendCurY.doubleValue();
+            l_minuendMinY    = l_minuendCurY.doubleValue();
+            l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
+            l_subtrahendMinY = l_subtrahendCurY.doubleValue();
+
+            b_fastForwardDone = true;
+        }
+
+        // start of algorithm
+        while (!b_minuendDone && !b_subtrahendDone) {
+            if (!b_minuendDone && !b_minuendFastForward && b_minuendAdvanced) {
+                l_x1 = x_dataset.getXValue(0, l_minuendItem);
+                l_y1 = x_dataset.getYValue(0, l_minuendItem);
+                l_minuendCurX = new Double(l_x1);
+                l_minuendCurY = new Double(l_y1);
+
+                if (!b_minuendAtIntersect) {
+                    l_minuendXs.add(l_minuendCurX);
+                    l_minuendYs.add(l_minuendCurY);
+                }
+
+                l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
+                l_minuendMinY = Math.min(l_minuendMinY, l_y1);
+
+                l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
+                l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
+                l_minuendNextX = new Double(l_x2);
+                l_minuendNextY = new Double(l_y2);
+            }
+
+            // never updated the subtrahend if it is implied to be zero
+            if (!b_impliedZeroSubtrahend && !b_subtrahendDone
+                    && !b_subtrahendFastForward && b_subtrahendAdvanced) {
+                l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
+                l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
+                l_subtrahendCurX = new Double(l_x3);
+                l_subtrahendCurY = new Double(l_y3);
+
+                if (!b_subtrahendAtIntersect) {
+                    l_subtrahendXs.add(l_subtrahendCurX);
+                    l_subtrahendYs.add(l_subtrahendCurY);
+                }
+
+                l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
+                l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
+
+                l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
+                l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
+                l_subtrahendNextX = new Double(l_x4);
+                l_subtrahendNextY = new Double(l_y4);
+            }
+
+            // deassert b_*FastForward (only matters for 1st time through loop)
+            b_minuendFastForward    = false;
+            b_subtrahendFastForward = false;
+
+            Double l_intersectX = null;
+            Double l_intersectY = null;
+            boolean b_intersect = false;
+
+            b_minuendAtIntersect    = false;
+            b_subtrahendAtIntersect = false;
+
+            // check for intersect
+            if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
+                // check if line segments are colinear
+                if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
+                    b_colinear = true;
+                }
+                else {
+                    // the intersect is at the next point for both the minuend
+                    // and subtrahend
+                    l_intersectX = new Double(l_x2);
+                    l_intersectY = new Double(l_y2);
+
+                    b_intersect             = true;
+                    b_minuendAtIntersect    = true;
+                    b_subtrahendAtIntersect = true;
+                 }
+            }
+            else {
+                // compute common denominator
+                double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
+                        - ((l_x4 - l_x3) * (l_y2 - l_y1));
+
+                // compute common deltas
+                double l_deltaY = l_y1 - l_y3;
+                double l_deltaX = l_x1 - l_x3;
+
+                // compute numerators
+                double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
+                        - ((l_y4 - l_y3) * l_deltaX);
+                double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
+                        - ((l_y2 - l_y1) * l_deltaX);
+
+                // check if line segments are colinear
+                if ((0 == l_numeratorA) && (0 == l_numeratorB)
+                        && (0 == l_denominator)) {
+                    b_colinear = true;
+                }
+                else {
+                    // check if previously colinear
+                    if (b_colinear) {
+                        // clear colinear points and flag
+                        l_minuendXs.clear();
+                        l_minuendYs.clear();
+                        l_subtrahendXs.clear();
+                        l_subtrahendYs.clear();
+                        l_polygonXs.clear();
+                        l_polygonYs.clear();
+
+                        b_colinear = false;
+
+                        // set new starting point for the polygon
+                        boolean b_useMinuend = ((l_x3 <= l_x1)
+                                && (l_x1 <= l_x4));
+                        l_polygonXs.add(b_useMinuend ? l_minuendCurX
+                                : l_subtrahendCurX);
+                        l_polygonYs.add(b_useMinuend ? l_minuendCurY
+                                : l_subtrahendCurY);
+                    }
+
+                    // compute slope components
+                    double l_slopeA = l_numeratorA / l_denominator;
+                    double l_slopeB = l_numeratorB / l_denominator;
+
+                    // check if the line segments intersect
+                    if ((0 < l_slopeA) && (l_slopeA <= 1) && (0 < l_slopeB)
+                            && (l_slopeB <= 1)) {
+                        // compute the point of intersection
+                        double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
+                        double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
+
+                        l_intersectX            = new Double(l_xi);
+                        l_intersectY            = new Double(l_yi);
+                        b_intersect             = true;
+                        b_minuendAtIntersect    = ((l_xi == l_x2)
+                                && (l_yi == l_y2));
+                        b_subtrahendAtIntersect = ((l_xi == l_x4)
+                                && (l_yi == l_y4));
+
+                        // advance minuend and subtrahend to intesect
+                        l_minuendCurX    = l_intersectX;
+                        l_minuendCurY    = l_intersectY;
+                        l_subtrahendCurX = l_intersectX;
+                        l_subtrahendCurY = l_intersectY;
+                    }
+                }
+            }
+
+            if (b_intersect) {
+                // create the polygon
+                // add the minuend's points to polygon
+                l_polygonXs.addAll(l_minuendXs);
+                l_polygonYs.addAll(l_minuendYs);
+
+                // add intersection point to the polygon
+                l_polygonXs.add(l_intersectX);
+                l_polygonYs.add(l_intersectY);
+
+                // add the subtrahend's points to the polygon in reverse
+                Collections.reverse(l_subtrahendXs);
+                Collections.reverse(l_subtrahendYs);
+                l_polygonXs.addAll(l_subtrahendXs);
+                l_polygonYs.addAll(l_subtrahendYs);
+
+                // create an actual polygon
+                b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
+                        && (l_subtrahendMinY <= l_minuendMinY);
+                createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
+                        x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
+
+                // clear the point vectors
+                l_minuendXs.clear();
+                l_minuendYs.clear();
+                l_subtrahendXs.clear();
+                l_subtrahendYs.clear();
+                l_polygonXs.clear();
+                l_polygonYs.clear();
+
+                // set the maxY and minY values to intersect y-value
+                double l_y       = l_intersectY.doubleValue();
+                l_minuendMaxY    = l_y;
+                l_subtrahendMaxY = l_y;
+                l_minuendMinY    = l_y;
+                l_subtrahendMinY = l_y;
+
+                // add interection point to new polygon
+                l_polygonXs.add(l_intersectX);
+                l_polygonYs.add(l_intersectY);
+            }
+
+            // advance the minuend if needed
+            if (l_x2 <= l_x4) {
+                l_minuendItem++;
+                b_minuendAdvanced = true;
+            }
+            else {
+                b_minuendAdvanced = false;
+            }
+
+            // advance the subtrahend if needed
+            if (l_x4 <= l_x2) {
+                l_subtrahendItem++;
+                b_subtrahendAdvanced = true;
+            }
+            else {
+                b_subtrahendAdvanced = false;
+            }
+
+            b_minuendDone    = (l_minuendItem == (l_minuendItemCount - 1));
+            b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount
+                    - 1));
+        }
+
+        // check if the final polygon needs to be clipped
+        if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
+            // project onto subtrahend
+            double l_slope    = (l_y4 - l_y3) / (l_x4 - l_x3);
+            l_subtrahendNextX = l_minuendNextX;
+            l_subtrahendNextY = new Double((l_slope * l_x2)
+                    + (l_y3 - (l_slope * l_x3)));
+        }
+
+        if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
+            // project onto minuend
+            double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
+            l_minuendNextX = l_subtrahendNextX;
+            l_minuendNextY = new Double((l_slope * l_x4)
+                    + (l_y1 - (l_slope * l_x1)));
+        }
+
+        // consider last point of minuend and subtrahend for determining
+        // positivity
+        l_minuendMaxY    = Math.max(l_minuendMaxY,
+                l_minuendNextY.doubleValue());
+        l_subtrahendMaxY = Math.max(l_subtrahendMaxY,
+                l_subtrahendNextY.doubleValue());
+        l_minuendMinY    = Math.min(l_minuendMinY,
+                l_minuendNextY.doubleValue());
+        l_subtrahendMinY = Math.min(l_subtrahendMinY,
+                l_subtrahendNextY.doubleValue());
+
+        // add the last point of the minuned and subtrahend
+        l_minuendXs.add(l_minuendNextX);
+        l_minuendYs.add(l_minuendNextY);
+        l_subtrahendXs.add(l_subtrahendNextX);
+        l_subtrahendYs.add(l_subtrahendNextY);
+
+        // create the polygon
+        // add the minuend's points to polygon
+        l_polygonXs.addAll(l_minuendXs);
+        l_polygonYs.addAll(l_minuendYs);
+
+        // add the subtrahend's points to the polygon in reverse
+        Collections.reverse(l_subtrahendXs);
+        Collections.reverse(l_subtrahendYs);
+        l_polygonXs.addAll(l_subtrahendXs);
+        l_polygonYs.addAll(l_subtrahendYs);
+
+        // create an actual polygon
+        b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
+                && (l_subtrahendMinY <= l_minuendMinY);
+        createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
+                x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
+    }
+
+    /**
+     * Draws the visual representation of a single data item, second pass.  In
+     * the second pass, the renderer draws the lines and shapes for the
+     * individual points in the two series.
+     *
+     * @param x_graphics  the graphics device.
+     * @param x_dataArea  the area within which the data is being drawn.
+     * @param x_info  collects information about the drawing.
+     * @param x_plot  the plot (can be used to obtain standard color
+     *         information etc).
+     * @param x_domainAxis  the domain (horizontal) axis.
+     * @param x_rangeAxis  the range (vertical) axis.
+     * @param x_dataset  the dataset.
+     * @param x_series  the series index (zero-based).
+     * @param x_item  the item index (zero-based).
+     * @param x_crosshairState  crosshair information for the plot
+     *                          (<code>null</code> permitted).
+     */
+    protected void drawItemPass1(Graphics2D x_graphics,
+                                 Rectangle2D x_dataArea,
+                                 PlotRenderingInfo x_info,
+                                 XYPlot x_plot,
+                                 ValueAxis x_domainAxis,
+                                 ValueAxis x_rangeAxis,
+                                 XYDataset x_dataset,
+                                 int x_series,
+                                 int x_item,
+                                 CrosshairState x_crosshairState) {
+
+        Shape l_entityArea = null;
+        EntityCollection l_entities = null;
+        if (null != x_info) {
+            l_entities = x_info.getOwner().getEntityCollection();
+        }
+
+        Paint l_seriesPaint   = getItemPaint(x_series, x_item);
+        Stroke l_seriesStroke = getItemStroke(x_series, x_item);
+        x_graphics.setPaint(l_seriesPaint);
+        x_graphics.setStroke(l_seriesStroke);
+
+        PlotOrientation l_orientation      = x_plot.getOrientation();
+        RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
+        RectangleEdge l_rangeAxisLocation  = x_plot.getRangeAxisEdge();
+
+        double l_x0 = x_dataset.getXValue(x_series, x_item);
+        double l_y0 = x_dataset.getYValue(x_series, x_item);
+        double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
+                l_domainAxisLocation);
+        double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
+                l_rangeAxisLocation);
+
+        // These are the shapes of the series items.
+        if (getShapesVisible()) {
+            Shape l_shape = getItemShape(x_series, x_item);
+            if (l_orientation == PlotOrientation.HORIZONTAL) {
+                l_shape = ShapeUtilities.createTranslatedShape(l_shape,
+                        l_y1, l_x1);
+            }
+            else {
+                l_shape = ShapeUtilities.createTranslatedShape(l_shape,
+                        l_x1, l_y1);
+            }
+            if (l_shape.intersects(x_dataArea)) {
+                x_graphics.setPaint(getItemPaint(x_series, x_item));
+                x_graphics.fill(l_shape);
+                /* TODO We could draw the shapes of single items here.
+                if (drawOutline) {
+                    x_graphics.setPaint(this.outlinePaint);
+                    x_graphics.setStroke(this.outlineStroke);
+                    x_graphics.draw(l_shape);
+                }
+                */
+            }
+            l_entityArea = l_shape;
+        } // if (getShapesVisible())
+
+        // add an entity for the item...
+        if (null != l_entities) {
+            if (null == l_entityArea) {
+                l_entityArea = new Rectangle2D.Double((l_x1 - 2), (l_y1 - 2),
+                        4, 4);
+            }
+            String l_tip = null;
+            XYToolTipGenerator l_tipGenerator = getToolTipGenerator(x_series,
+                    x_item);
+            if (null != l_tipGenerator) {
+                l_tip = l_tipGenerator.generateToolTip(x_dataset, x_series,
+                        x_item);
+            }
+            String l_url = null;
+            XYURLGenerator l_urlGenerator = getURLGenerator();
+            if (null != l_urlGenerator) {
+                l_url = l_urlGenerator.generateURL(x_dataset, x_series,
+                        x_item);
+            }
+            XYItemEntity l_entity = new XYItemEntity(l_entityArea, x_dataset,
+                    x_series, x_item, l_tip, l_url);
+            l_entities.add(l_entity);
+        }
+
+        // draw the item label if there is one...
+        if (isItemLabelVisible(x_series, x_item)) {
+            drawItemLabel(x_graphics, l_orientation, x_dataset, x_series,
+                          x_item, l_x1, l_y1, (l_y1 < 0.0));
+        }
+
+        int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
+        int l_rangeAxisIndex  = x_plot.getRangeAxisIndex(x_rangeAxis);
+        updateCrosshairValues(x_crosshairState, l_x0, l_y0, l_domainAxisIndex,
+                              l_rangeAxisIndex, l_x1, l_y1, l_orientation);
+
+        if (0 == x_item) {
+            return;
+        }
+
+        double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(x_series,
+                (x_item - 1)), x_dataArea, l_domainAxisLocation);
+        double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(x_series,
+                (x_item - 1)), x_dataArea, l_rangeAxisLocation);
+
+        Line2D l_line = null;
+        if (PlotOrientation.HORIZONTAL == l_orientation) {
+            l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
+        }
+        else if (PlotOrientation.VERTICAL == l_orientation) {
+            l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
+        }
+
+        if ((null != l_line) && l_line.intersects(x_dataArea)) {
+            x_graphics.setPaint(getItemPaint(x_series, x_item));
+            x_graphics.setStroke(getItemStroke(x_series, x_item));
+            if (drawOriginalSeries) {
+                x_graphics.setPaint(this.outlinePaint);
+                x_graphics.setStroke(this.outlineStroke);
+                x_graphics.draw(l_line);
+            }
+        }
+    }
+
+    /**
+     * Determines if a dataset is degenerate.  A degenerate dataset is a
+     * dataset where either series has less than two (2) points.
+     *
+     * @param x_dataset  the dataset.
+     * @param x_impliedZeroSubtrahend  if false, do not check the subtrahend
+     *
+     * @return true if the dataset is degenerate.
+     */
+    private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
+            boolean x_impliedZeroSubtrahend) {
+
+        if (x_impliedZeroSubtrahend) {
+            return (x_dataset.getItemCount(0) < 2);
+        }
+
+        return ((x_dataset.getItemCount(0) < 2)
+                || (x_dataset.getItemCount(1) < 2));
+    }
+
+    /**
+     * Determines if the two (2) series are disjoint.
+     * Disjoint series do not overlap in the domain space.
+     *
+     * @param x_dataset  the dataset.
+     *
+     * @return true if the dataset is degenerate.
+     */
+    private boolean areSeriesDisjoint(XYDataset x_dataset) {
+
+        int l_minuendItemCount = x_dataset.getItemCount(0);
+        double l_minuendFirst  = x_dataset.getXValue(0, 0);
+        double l_minuendLast   = x_dataset.getXValue(0, l_minuendItemCount - 1);
+
+        int l_subtrahendItemCount = x_dataset.getItemCount(1);
+        double l_subtrahendFirst  = x_dataset.getXValue(1, 0);
+        double l_subtrahendLast   = x_dataset.getXValue(1,
+                l_subtrahendItemCount - 1);
+
+        return ((l_minuendLast < l_subtrahendFirst)
+                || (l_subtrahendLast < l_minuendFirst));
+    }
+
+
+    public void updateCentroid(Object [] xValues, Object [] yValues) {
+        double x = 0d, y = 0d;
+
+        for (int i = 0, N = xValues.length; i < N; ++i) {
+            x += ((Double)xValues[i]).doubleValue();
+            y += ((Double)yValues[i]).doubleValue();
+        }
+
+        x /= xValues.length;
+        y /= yValues.length;
+
+        centroidNPoints++;
+        double factorNew = 1d / centroidNPoints;
+        double factorOld = 1d - factorNew;
+
+        centroid = new Point2D.Double((factorNew * x + factorOld * centroid.x),
+            (factorNew * y + factorOld * centroid.y));
+    }
+
+
+    public static double calculateArea(Object [] xValues, Object [] yValues) {
+        double area = 0d;
+
+        for (int i = 0, N = xValues.length; i < N; ++i) {
+            int j = (i + 1) % N;
+            double xi = ((Double)xValues[i]).doubleValue();
+            double yi = ((Double)yValues[i]).doubleValue();
+            double xj = ((Double)xValues[j]).doubleValue();
+            double yj = ((Double)yValues[j]).doubleValue();
+
+            area += xi*yj;
+            area -= xj*yi;
+            // TODO centroid calculation here?
+        }
+
+        return 0.5d*area;
+    }
+
+    /**
+     * Draws the visual representation of a polygon
+     *
+     * @param x_graphics  the graphics device.
+     * @param x_dataArea  the area within which the data is being drawn.
+     * @param x_plot  the plot (can be used to obtain standard color
+     *                information etc).
+     * @param x_domainAxis  the domain (horizontal) axis.
+     * @param x_rangeAxis  the range (vertical) axis.
+     * @param x_positive  indicates if the polygon is positive (true) or
+     *                    negative (false).
+     * @param x_xValues  a linked list of the x values (expects values to be
+     *                   of type Double).
+     * @param x_yValues  a linked list of the y values (expects values to be
+     *                   of type Double).
+     */
+    private void createPolygon (Graphics2D x_graphics,
+                                Rectangle2D x_dataArea,
+                                XYPlot      x_plot,
+                                ValueAxis   x_domainAxis,
+                                ValueAxis   x_rangeAxis,
+                                boolean     x_positive,
+                                LinkedList  x_xValues,
+                                LinkedList  x_yValues) {
+
+        PlotOrientation l_orientation      = x_plot.getOrientation();
+        RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
+        RectangleEdge l_rangeAxisLocation  = x_plot.getRangeAxisEdge();
+
+        Object[] l_xValues = x_xValues.toArray();
+        Object[] l_yValues = x_yValues.toArray();
+
+        double area = calculateArea(l_xValues, l_yValues);
+        if (x_positive) positiveArea += area;
+        else            negativeArea += area;
+        updateCentroid(l_xValues, l_yValues);
+
+        GeneralPath l_path = new GeneralPath();
+
+        if (PlotOrientation.VERTICAL == l_orientation) {
+            double l_x = x_domainAxis.valueToJava2D((
+                    (Double) l_xValues[0]).doubleValue(), x_dataArea,
+                    l_domainAxisLocation);
+            if (this.roundXCoordinates) {
+                l_x = Math.rint(l_x);
+            }
+
+            double l_y = x_rangeAxis.valueToJava2D((
+                    (Double) l_yValues[0]).doubleValue(), x_dataArea,
+                    l_rangeAxisLocation);
+
+            l_path.moveTo((float) l_x, (float) l_y);
+            for (int i = 1; i < l_xValues.length; i++) {
+                l_x = x_domainAxis.valueToJava2D((
+                        (Double) l_xValues[i]).doubleValue(), x_dataArea,
+                        l_domainAxisLocation);
+                if (this.roundXCoordinates) {
+                    l_x = Math.rint(l_x);
+                }
+
+                l_y = x_rangeAxis.valueToJava2D((
+                        (Double) l_yValues[i]).doubleValue(), x_dataArea,
+                        l_rangeAxisLocation);
+                l_path.lineTo((float) l_x, (float) l_y);
+            }
+            l_path.closePath();
+        }
+        else {
+            double l_x = x_domainAxis.valueToJava2D((
+                    (Double) l_xValues[0]).doubleValue(), x_dataArea,
+                    l_domainAxisLocation);
+            if (this.roundXCoordinates) {
+                l_x = Math.rint(l_x);
+            }
+
+            double l_y = x_rangeAxis.valueToJava2D((
+                    (Double) l_yValues[0]).doubleValue(), x_dataArea,
+                    l_rangeAxisLocation);
+
+            l_path.moveTo((float) l_y, (float) l_x);
+            for (int i = 1; i < l_xValues.length; i++) {
+                l_x = x_domainAxis.valueToJava2D((
+                        (Double) l_xValues[i]).doubleValue(), x_dataArea,
+                        l_domainAxisLocation);
+                if (this.roundXCoordinates) {
+                    l_x = Math.rint(l_x);
+                }
+
+                l_y = x_rangeAxis.valueToJava2D((
+                        (Double) l_yValues[i]).doubleValue(), x_dataArea,
+                        l_rangeAxisLocation);
+                l_path.lineTo((float) l_y, (float) l_x);
+            }
+            l_path.closePath();
+        }
+
+        if (l_path.intersects(x_dataArea)) {
+            x_graphics.setPaint(x_positive ? getPositivePaint()
+                    : getNegativePaint());
+            x_graphics.fill(l_path);
+            if (drawOutline) {
+                x_graphics.setStroke(this.outlineStroke);
+                x_graphics.setPaint(this.outlinePaint);
+                x_graphics.draw(l_path);
+            }
+        }
+    }
+
+    /**
+     * Returns a default legend item for the specified series.  Subclasses
+     * should override this method to generate customised items.
+     *
+     * @param datasetIndex  the dataset index (zero-based).
+     * @param series  the series index (zero-based).
+     *
+     * @return A legend item for the series.
+     */
+    public LegendItem getLegendItem(int datasetIndex, int series) {
+        LegendItem result = null;
+        XYPlot p = getPlot();
+        if (p != null) {
+            XYDataset dataset = p.getDataset(datasetIndex);
+            if (dataset != null) {
+                if (getItemVisible(series, 0)) {
+                    String label = getLegendItemLabelGenerator().generateLabel(
+                            dataset, series);
+                    String description = label;
+                    String toolTipText = null;
+                    if (getLegendItemToolTipGenerator() != null) {
+                        toolTipText
+                            = getLegendItemToolTipGenerator().generateLabel(
+                                    dataset, series);
+                    }
+                    String urlText = null;
+                    if (getLegendItemURLGenerator() != null) {
+                        urlText = getLegendItemURLGenerator().generateLabel(
+                                dataset, series);
+                    }
+                    // Individualized Paints:
+                    //Paint paint = lookupSeriesPaint(series);
+
+                    // "Area-Style"- Paint.
+                    Paint paint = getPositivePaint();
+                    Stroke stroke = lookupSeriesStroke(series);
+                    Shape line = getLegendLine();
+                    // Not-filled Shape:
+                    //result = new LegendItem(label, description,
+                    //        toolTipText, urlText, line, stroke, paint);
+
+                    if (drawOutline) {
+                        // TODO Include outline style in legenditem (there is a constructor for that)
+                    }
+
+                    // Filled Shape ("Area-Style").
+                    result = new LegendItem(label, description,
+                            toolTipText, urlText, line, paint);
+                    result.setLabelFont(lookupLegendTextFont(series));
+                    Paint labelPaint = lookupLegendTextPaint(series);
+                    if (labelPaint != null) {
+                        result.setLabelPaint(labelPaint);
+                    }
+                    result.setDataset(dataset);
+                    result.setDatasetIndex(datasetIndex);
+                    result.setSeriesKey(dataset.getSeriesKey(series));
+                    result.setSeriesIndex(series);
+                }
+            }
+
+        }
+
+        return result;
+    }
+
+    /**
+     * Tests this renderer for equality with an arbitrary object.
+     *
+     * @param obj  the object (<code>null</code> permitted).
+     *
+     * @return A boolean.
+     */
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof StableXYDifferenceRenderer)) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        StableXYDifferenceRenderer that = (StableXYDifferenceRenderer) obj;
+        if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
+            return false;
+        }
+        if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
+            return false;
+        }
+        if (this.shapesVisible != that.shapesVisible) {
+            return false;
+        }
+        if (!ShapeUtilities.equal(this.legendShape, that.legendShape)) {
+            return false;
+        }
+        if (this.roundXCoordinates != that.roundXCoordinates) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns a clone of the renderer.
+     *
+     * @return A clone.
+     *
+     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
+     */
+    public Object clone() throws CloneNotSupportedException {
+        StableXYDifferenceRenderer clone = (StableXYDifferenceRenderer) super.clone();
+        clone.legendShape = ShapeUtilities.clone(this.legendShape);
+        return clone;
+    }
+
+    /**
+     * Provides serialization support.
+     *
+     * @param stream  the output stream.
+     *
+     * @throws IOException  if there is an I/O error.
+     */
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        stream.defaultWriteObject();
+        SerialUtilities.writePaint(this.positivePaint, stream);
+        SerialUtilities.writePaint(this.negativePaint, stream);
+        SerialUtilities.writeShape(this.legendShape, stream);
+    }
+
+    /**
+     * Provides serialization support.
+     *
+     * @param stream  the input stream.
+     *
+     * @throws IOException  if there is an I/O error.
+     * @throws ClassNotFoundException  if there is a classpath problem.
+     */
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+        this.positivePaint = SerialUtilities.readPaint(stream);
+        this.negativePaint = SerialUtilities.readPaint(stream);
+        this.legendShape = SerialUtilities.readShape(stream);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,133 @@
+package de.intevation.flys.jfree;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Custom Annotations class that is drawn only if no collisions with other
+ * already drawn CustomAnnotations in current plot are found. A mark on
+ * the axis is shown in all cases, though.
+ * Draws a given text and a line to it from either axis.
+ */
+public class StickyAxisAnnotation {
+
+    /** Logger for this class. */    
+    private static Logger logger =
+        Logger.getLogger(StickyAxisAnnotation.class);
+
+    /** Simplified view on axes. */
+    public static enum SimpleAxis {
+        X_AXIS, /** Usually "horizontal". */
+        Y_AXIS  /** Usually "vertical". */
+    }
+
+    /** The "symbolic" integer representing which axis to stick to. */
+    protected int axisSymbol;
+
+    /** Which axis to stick to. */
+    protected SimpleAxis stickyAxis = SimpleAxis.X_AXIS;
+
+    /** The 1-dimensional position of this annotation. */
+    protected float pos;
+
+    /**
+     * Optional field used when from axis a line should be drawn that
+     * hits a curve or something similar (current scenario: duration curves).
+     * This value is in the "other" dimension than the pos - field.
+     */
+    protected float hitPoint;
+
+    /** The text to display at axis. */
+    String text;
+
+
+    /**
+     * Constructor with implicit sticky x-axis.
+     * @param text       the text to display.
+     * @param pos        the position at which to draw the text and mark.
+     */
+    public StickyAxisAnnotation(String text, float pos) {
+        this(text, pos, SimpleAxis.X_AXIS);
+    }
+
+
+    /**
+     * Constructor with given explicit axis.
+     * @param text       the text to display.
+     * @param pos        the position at which to draw the text and mark.
+     * @param stickyAxis the axis at which to stick (and to which 'pos' is
+     *                   relative).
+     * @param lineTo     upto where to draw a line (NaN for none).
+     */
+    public StickyAxisAnnotation(String text, float pos, SimpleAxis stickAxis
+    ) {
+        this(text, pos, stickAxis, 0);
+    }
+
+
+    public StickyAxisAnnotation(String text, float pos, SimpleAxis stickAxis,
+            int axisSymbol
+    ) {
+        setStickyAxis(stickAxis);
+        this.text   = text;
+        this.pos    = pos;
+        this.axisSymbol = axisSymbol;
+        this.hitPoint = Float.NaN;
+    }
+
+
+    /**
+     * Sets the "sticky axis" (whether to draw annotations at the
+     * X- or the Y-Axis.
+     *
+     * @param stickyAxis axis to stick to.
+     */
+    public void setStickyAxis(SimpleAxis stickyAxis) {
+        this.stickyAxis = stickyAxis;
+    }
+
+
+    public float getPos() {
+        return this.pos;
+    }
+
+    public SimpleAxis getStickyAxis() {
+        return this.stickyAxis;
+    }
+
+    public boolean atX() {
+        return this.getStickyAxis() == SimpleAxis.X_AXIS;
+    }
+
+    /** Get text to be displayed at axis. */
+    public String getText() {
+        return this.text;
+    }
+
+
+    public int getAxisSymbol() {
+        return this.axisSymbol;
+    }
+
+
+    /** Set where to hit a curve (if any). */
+    public void setHitPoint(float pos) {
+        this.hitPoint = pos;
+    }
+
+    /** Get where to hit a curve (if any). */
+    public float getHitPoint() {
+        return this.hitPoint;
+    }
+
+    /** Set sticky axis to the X axis if it is currently Y, and vice versa. */
+    public void flipStickyAxis() {
+        if (this.getStickyAxis() == SimpleAxis.X_AXIS) {
+            this.setStickyAxis(SimpleAxis.Y_AXIS);
+        }
+        else {
+            this.setStickyAxis(SimpleAxis.X_AXIS);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/Style.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,13 @@
+package de.intevation.flys.jfree;
+
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Style {
+
+    XYLineAndShapeRenderer applyTheme(XYLineAndShapeRenderer r, int idx);
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StyledAreaSeriesCollection.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,166 @@
+package de.intevation.flys.jfree;
+
+import java.awt.Color;
+import java.awt.Stroke;
+import java.awt.BasicStroke;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.data.xy.XYSeriesCollection;
+
+import de.intevation.flys.utils.ThemeUtil;
+import de.intevation.flys.utils.ThemeAccess;
+import de.intevation.flys.jfree.StableXYDifferenceRenderer;
+
+
+/**
+ * One or more dataseries to draw a polygon (either "open up/downwards", or
+ * the area between two curves), a theme-document and further display options.
+ * The theme-document will later "style" the graphical representation.
+ * The display options can be used to control the z-order and the axis of the 
+ * dataset.
+ */
+public class StyledAreaSeriesCollection extends XYSeriesCollection {
+    /** Mode, how to draw/which areas to fill. */
+    public enum FILL_MODE {UNDER, ABOVE, BETWEEN};
+
+    /** MODE in use. */
+    protected FILL_MODE mode;
+
+    /** The theme-document with attributes about actual visual representation. */
+    protected Document theme;
+
+    /** Own logger. */
+    private static final Logger logger =
+        Logger.getLogger(StyledAreaSeriesCollection.class);
+
+
+    /**
+     * @param theme the theme-document.
+     */
+    public StyledAreaSeriesCollection(Document theme) {
+        this.theme = theme;
+        this.mode = FILL_MODE.BETWEEN;
+   }
+
+
+    /** Gets the Fill mode. */
+    public FILL_MODE getMode() {
+        return this.mode;
+    }
+
+
+    /** Sets the Fill mode. */
+    public void setMode(FILL_MODE fMode) {
+        this.mode = fMode;
+    }
+
+
+    /**
+     * Applies line color, size and type attributes to renderer, also
+     * whether to draw lines and/or points.
+     */
+    public StableXYDifferenceRenderer applyTheme(
+        StableXYDifferenceRenderer renderer
+    ) {
+        applyFillColor(renderer);
+        applyShowShape(renderer);
+        applyOutlineColor(renderer);
+        applyOutlineStyle(renderer);
+        applyShowArea(renderer);
+        if (mode == FILL_MODE.UNDER) {
+            renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_NEGATIVE_AREA);
+        }
+        else if (mode == FILL_MODE.ABOVE) {
+            renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_POSITIVE_AREA);
+        }
+        else {
+            renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_ALL_AREA);
+        }
+
+        // Apply text style.
+        new ThemeAccess(theme).parseTextStyle().apply(renderer);
+        return renderer;
+    }
+
+
+    /**
+     * Blindly (for now) apply the postiviepaint of renderer.
+     */
+    protected void applyFillColor(StableXYDifferenceRenderer renderer) {
+        // Get color.
+        Color paint = ThemeUtil.parseFillColorField(theme);
+        // Get half-transparency flag.
+        if (ThemeUtil.parseTransparency(theme)) {
+            paint = new Color(paint.getRed(), paint.getGreen(), paint.getBlue(),
+                128);
+        }
+        if (paint != null && this.getMode() == FILL_MODE.ABOVE) {
+            renderer.setPositivePaint(paint);
+            renderer.setNegativePaint(new Color(0,0,0,0));
+        }
+        else if (paint != null && this.getMode() == FILL_MODE.UNDER) {
+            renderer.setNegativePaint(paint);
+            renderer.setPositivePaint(new Color(0,0,0,0));
+        }
+        else {
+            if (paint == null) paint = new Color(177, 117, 102);
+            renderer.setPositivePaint(paint);
+            renderer.setNegativePaint(paint);
+        }
+    }
+
+    /**
+     * Blindly (for now) apply the postiviepaint of renderer.
+     */
+    protected void applyShowShape(StableXYDifferenceRenderer renderer) {
+        boolean show = ThemeUtil.parseShowBorder(theme);
+        renderer.setDrawOutline(show);
+    }
+
+    protected void applyShowLine(StableXYDifferenceRenderer renderer) {
+        boolean show = ThemeUtil.parseShowLine(theme);
+        renderer.setShapesVisible(show);
+    }
+
+    /**
+     *
+     */
+    protected void applyOutlineColor(StableXYDifferenceRenderer renderer) {
+        Color c = ThemeUtil.parseLineColorField(theme);
+        renderer.setOutlinePaint(c);
+    }
+
+    protected void applyOutlineWidth(StableXYDifferenceRenderer renderer) {
+        int size = ThemeUtil.parseLineWidth(theme);
+    }
+
+    /** Inform renderer whether it should draw a label. */
+    protected void applyShowArea(StableXYDifferenceRenderer renderer) {
+        renderer.setLabelArea(ThemeUtil.parseShowArea(theme));
+    }
+
+    protected void applyOutlineStyle(StableXYDifferenceRenderer renderer) {
+        float[] dashes = ThemeUtil.parseLineStyle(theme);
+        int size       = ThemeUtil.parseLineWidth(theme);
+
+        Stroke stroke = null;
+
+        if (dashes.length <= 1) {
+            stroke = new BasicStroke(Integer.valueOf(size));
+        }
+        else {
+            stroke = new BasicStroke(Integer.valueOf(size),
+                BasicStroke.CAP_BUTT,
+                BasicStroke.JOIN_ROUND,
+                1.0f,
+                dashes,
+                0.0f);
+        }
+
+        renderer.setOutlineStroke(stroke);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StyledSeries.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,13 @@
+package de.intevation.flys.jfree;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface StyledSeries {
+
+    void setStyle(Style style);
+
+    Style getStyle();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StyledTimeSeries.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,39 @@
+package de.intevation.flys.jfree;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.data.time.TimeSeries;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class StyledTimeSeries extends TimeSeries implements StyledSeries {
+
+    private static final Logger logger =
+        Logger.getLogger(StyledTimeSeries.class);
+
+
+    protected Style style;
+
+
+    public StyledTimeSeries(String key, Document theme) {
+        super(key);
+        setStyle(new XYStyle(theme));
+    }
+
+
+    @Override
+    public void setStyle(Style style) {
+        this.style = style;
+    }
+
+
+    @Override
+    public Style getStyle() {
+        return style;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StyledXYSeries.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,66 @@
+package de.intevation.flys.jfree;
+
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+
+import org.jfree.data.xy.XYSeries;
+
+import de.intevation.flys.jfree.HasLabel;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class StyledXYSeries extends XYSeries implements StyledSeries, HasLabel {
+
+    private static final Logger logger = Logger.getLogger(StyledXYSeries.class);
+
+    protected Style style;
+
+    /** If this Series is to be labelled, use this String as label. */
+    protected String label;
+
+
+    public StyledXYSeries(String key, Document theme) {
+        this(key, true, theme);
+        this.label = key.toString();
+    }
+
+
+    /**
+     * @param sorted whether or not to sort the points. Sorting will move NANs
+     *               to one extrema which can cause problems in certain
+     *               algorithms.
+     */
+    public StyledXYSeries(String key, boolean sorted, Document theme) {
+        super(key, sorted);
+        setStyle(new XYStyle(theme));
+        this.label = key.toString();
+    }
+
+
+    @Override
+    public void setStyle(Style style) {
+        this.style = style;
+    }
+
+
+    @Override
+    public Style getStyle() {
+        return style;
+    }
+
+    
+    @Override
+    public String getLabel() {
+        return label;
+    }
+
+    @Override
+    public void setLabel(String label) {
+        this.label = label;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/TimeBounds.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,88 @@
+package de.intevation.flys.jfree;
+
+import java.util.Date;
+
+import org.jfree.chart.axis.DateAxis;
+import org.jfree.chart.axis.ValueAxis;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class TimeBounds implements Bounds {
+
+    protected long lower;
+    protected long upper;
+
+
+    public TimeBounds(long lower, long upper) {
+        this.lower = lower;
+        this.upper = upper;
+    }
+
+
+    @Override
+    public Number getLower() {
+        return Long.valueOf(lower);
+    }
+
+
+    public Date getLowerAsDate() {
+        return new Date(lower);
+    }
+
+
+    @Override
+    public Number getUpper() {
+        return Long.valueOf(upper);
+    }
+
+
+    public Date getUpperAsDate() {
+        return new Date(upper);
+    }
+
+
+    @Override
+    public void applyBounds(ValueAxis axis) {
+        DateAxis dateAxis = (DateAxis) axis;
+
+        dateAxis.setMinimumDate(new Date(lower));
+        dateAxis.setMaximumDate(new Date(upper));
+    }
+
+
+    @Override
+    public void applyBounds(ValueAxis axis, int percent) {
+        DateAxis dateAxis = (DateAxis) axis;
+
+        long space = (upper - lower) / 100 * percent;
+
+        dateAxis.setMinimumDate(new Date(lower-space));
+        dateAxis.setMaximumDate(new Date(upper+space));
+    }
+
+
+    @Override
+    public Bounds combine(Bounds bounds) {
+        if (bounds == null) {
+            return this;
+        }
+
+        TimeBounds other = (TimeBounds) bounds;
+
+        long otherLower = other.getLower().longValue();
+        long otherUpper = other.getUpper().longValue();
+
+        return new TimeBounds(
+            otherLower < lower ? otherLower : lower,
+            otherUpper > upper ? otherUpper : upper);
+    }
+
+
+    @Override
+    public String toString() {
+        return "TimeBounds=["+ getLowerAsDate() + " ; " + getUpperAsDate() +"]";
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/XYStyle.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,184 @@
+package de.intevation.flys.jfree;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.geom.Ellipse2D;
+
+
+import org.w3c.dom.Document;
+
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+
+import de.intevation.flys.utils.ThemeUtil;
+
+
+/**
+ * Utility to apply theme-settings to a renderer.
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class XYStyle implements Style {
+
+    protected Document theme;
+
+
+    public XYStyle(Document theme) {
+        this.theme = theme;
+    }
+
+
+    /**
+     * Applies line color, size and type attributes to renderer, also
+     * whether to draw lines and/or points.
+     */
+    public XYLineAndShapeRenderer applyTheme(XYLineAndShapeRenderer r, int idx){
+        applyLineColor(r, idx);
+        applyLineSize(r, idx);
+        applyLineType(r, idx);
+        applyShowLine(r, idx);
+        applyShowPoints(r, idx);
+        applyPointSize(r, idx);
+        applyShowMinimum(r, idx);
+        applyShowMaximum(r, idx);
+        applyShowLineLabel(r, idx);
+        applyLineLabelFont(r, idx);
+        applyLineLabelColor(r, idx);
+        applyShowLineLabelBG(r, idx);
+        applyLineLabelBGColor(r, idx);
+
+        return r;
+    }
+
+
+    /** Set line color to renderer. */
+    protected void applyLineColor(XYLineAndShapeRenderer r, int idx) {
+        Color c = ThemeUtil.parseLineColorField(theme);
+        r.setSeriesPaint(idx, c);
+    }
+
+
+    /** Tells the renderer whether or not to add a label to a line. */
+    protected void applyShowLineLabel(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+        boolean showLabelLine = ThemeUtil.parseShowLineLabel(theme);
+        ((EnhancedLineAndShapeRenderer)r).setShowLineLabel(showLabelLine, idx);
+    }
+
+
+    /** Tells the renderer whether or not to fill the bg of a lines label. */
+    protected void applyShowLineLabelBG(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+        boolean showLabelLine = ThemeUtil.parseShowLineLabelBG(theme);
+        ((EnhancedLineAndShapeRenderer)r).setShowLineLabelBG(idx, showLabelLine);
+    }
+
+    /** Tell the renderer which font (and -size and -style) to use for
+     * linelabels. */
+    protected void applyLineLabelFont(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+        ((EnhancedLineAndShapeRenderer)r).setLineLabelFont(ThemeUtil.parseLineLabelFont(theme), idx);
+    }
+
+    /** Tell the renderer which color to use for
+     * linelabels. */
+    protected void applyLineLabelColor(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+        ((EnhancedLineAndShapeRenderer)r).setLineLabelTextColor(idx, ThemeUtil.parseLineLabelTextColor(theme));
+    }
+
+    /** Tell the renderer which color to use for bg of
+     * linelabels. */
+    protected void applyLineLabelBGColor(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+        ((EnhancedLineAndShapeRenderer)r).setLineLabelBGColor(idx,
+            ThemeUtil.parseLineLabelBGColor(theme));
+    }
+
+    /** Set stroke of series. */
+    protected void applyLineSize(XYLineAndShapeRenderer r, int idx) {
+        int size = ThemeUtil.parseLineWidth(theme);
+        r.setSeriesStroke(
+            idx,
+            new BasicStroke(Integer.valueOf(size)));
+    }
+
+
+    /** Set stroke strength of series. */
+    protected void applyLineType(XYLineAndShapeRenderer r, int idx) {
+        int size = ThemeUtil.parseLineWidth(theme);
+        float[] dashes = ThemeUtil.parseLineStyle(theme);
+
+        // Do not apply the dashed style.
+        if (dashes.length <= 1) {
+            return;
+        }
+
+        r.setSeriesStroke(
+            idx,
+            new BasicStroke(Integer.valueOf(size),
+                            BasicStroke.CAP_BUTT,
+                            BasicStroke.JOIN_ROUND,
+                            1.0f,
+                            dashes,
+                            0.0f));
+    }
+
+
+    protected void applyPointSize(XYLineAndShapeRenderer r, int idx) {
+        int size = ThemeUtil.parsePointWidth(theme);
+        int dim  = 2 * size;
+
+        r.setSeriesShape(idx, new Ellipse2D.Double(-size, -size, dim, dim));
+    }
+
+
+    /**
+     * Sets form and visibility of points.
+     */
+    protected void applyShowPoints(XYLineAndShapeRenderer r, int idx) {
+        boolean show = ThemeUtil.parseShowPoints(theme);
+
+        r.setSeriesShapesVisible(idx, show);
+        r.setDrawOutlines(true);
+    }
+
+
+    protected void applyShowLine(XYLineAndShapeRenderer r, int idx) {
+        boolean show = ThemeUtil.parseShowLine(theme);
+        r.setSeriesLinesVisible(idx, show);
+    }
+
+
+    protected void applyShowMinimum(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+
+        boolean visible = ThemeUtil.parseShowMinimum(theme);
+
+        EnhancedLineAndShapeRenderer er = (EnhancedLineAndShapeRenderer) r;
+        er.setIsMinimumShapeVisisble(idx, visible);
+    }
+
+
+    protected void applyShowMaximum(XYLineAndShapeRenderer r, int idx) {
+        if (!(r instanceof EnhancedLineAndShapeRenderer)) {
+            return;
+        }
+
+        boolean visible = ThemeUtil.parseShowMaximum(theme);
+
+        EnhancedLineAndShapeRenderer er = (EnhancedLineAndShapeRenderer) r;
+        er.setIsMaximumShapeVisible(idx, visible);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/DefaultTheme.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,193 @@
+package de.intevation.flys.themes;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultTheme implements Theme {
+
+    /** The name of the theme.*/
+    protected String name;
+
+    /** The description of the theme.*/
+    protected String description;
+
+    protected String facet;
+
+    protected int index;
+
+
+    /** The map storing the fields of this theme.*/
+    protected Map<String, ThemeField> fields;
+
+    /** The map storing the attributes of this theme.*/
+    protected Map<String, String> attr;
+
+
+    /**
+     * Initializes the components of this Theme.
+     */
+    public DefaultTheme(String name, String description) {
+        this.name        = name;
+        this.description = description;
+        this.fields      = new HashMap<String, ThemeField>();
+        this.attr        = new HashMap<String, String>();
+    }
+
+
+    public void init(Node config) {
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+
+    public String getDescription() {
+        return description;
+    }
+
+
+    public String getFacet() {
+        return facet;
+    }
+
+
+    public void setFacet(String facet) {
+        this.facet = facet;
+    }
+
+
+    public int getIndex() {
+        return index;
+    }
+
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+
+    public void addAttribute(String name, String value) {
+        if (name != null && value != null) {
+            attr.put(name, value);
+        }
+    }
+
+
+    public String getAttribute(String name) {
+        return attr.get(name);
+    }
+
+
+    public void addField(String name, ThemeField field) {
+        if (name != null && field != null) {
+            fields.put(name, field);
+        }
+    }
+
+
+    public void setFieldValue(String name, Object value) {
+        if (name != null && value != null) {
+            ThemeField field = fields.get(name);
+
+            if (field != null) {
+                field.setValue(value);
+            }
+        }
+    }
+
+
+    public ThemeField getField(String name) {
+        return fields.get(name);
+    }
+
+
+    public String getFieldType(String name) {
+        ThemeField field = fields.get(name);
+
+        return field != null ? field.getType() : null;
+    }
+
+
+    public Object getFieldValue(String name) {
+        ThemeField field = fields.get(name);
+
+        return field != null ? field.getValue() : null;
+    }
+
+
+    public Document toXML() {
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(doc, null, null);
+
+        Element theme = cr.create("theme");
+        theme.setAttribute("facet", facet);
+        theme.setAttribute("index", String.valueOf(index));
+
+        appendAttributes(cr, theme);
+        appendFields(cr, theme);
+
+        doc.appendChild(theme);
+
+        return doc;
+    }
+
+
+    /**
+     * Appends the attributes configured for this theme.
+     *
+     * @param cr The ElementCreator.
+     * @param theme The document root element.
+     */
+    protected void appendAttributes(ElementCreator cr, Element theme) {
+        Iterator<String> iter = attr.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String key = iter.next();
+            String val = getAttribute(key);
+
+            if (key == null || val == null) {
+                continue;
+            }
+
+            cr.addAttr(theme, key, val);
+        }
+    }
+
+
+    /**
+     * Appends the fields configured for this theme.
+     *
+     * @param cr The ElementCreator.
+     * @param theme The document root element.
+     */
+    protected void appendFields(ElementCreator cr, Element theme) {
+        Iterator<String> iter = fields.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String name = iter.next();
+
+            ThemeField field = getField(name);
+
+            Document doc = field.toXML();
+            Node    root = doc.getFirstChild();
+
+            theme.appendChild(theme.getOwnerDocument().importNode(root, true));
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/DefaultThemeField.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,85 @@
+package de.intevation.flys.themes;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class DefaultThemeField implements ThemeField {
+
+    protected String name;
+
+    protected Map<String, Object> attr;
+
+
+    public DefaultThemeField(String name) {
+        this.name = name;
+        this.attr = new HashMap<String, Object>();
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+
+    public String getType() {
+        return (String) getAttribute("type");
+    }
+
+
+    public Object getValue() {
+        return getAttribute("value");
+    }
+
+
+    public void setValue(Object value) {
+        setAttribute("value", value);
+    }
+
+
+    public Object getAttribute(String name) {
+        return attr.get(name);
+    }
+
+
+    public void setAttribute(String name, Object value) {
+        if (name == null || value == null) {
+            return;
+        }
+
+        attr.put(name, value);
+    }
+
+
+    public Document toXML() {
+        Document doc = XMLUtils.newDocument();
+
+        ElementCreator cr = new ElementCreator(doc, null, null);
+
+        Element field = cr.create("field");
+
+        Iterator<String> iter = attr.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String name  = iter.next();
+            String value = (String) getAttribute(name);
+
+            cr.addAttr(field, name, value);
+        }
+
+        doc.appendChild(field);
+
+        return doc;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/Theme.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,119 @@
+package de.intevation.flys.themes;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface Theme {
+
+    /**
+     * Method to initialize the theme.
+     *
+     * @param config The configuration node.
+     */
+    void init(Node config);
+
+
+    /**
+     * Returns the name of the theme.
+     *
+     * @return the name of the theme.
+     */
+    String getName();
+
+
+    /**
+     * Returns the description of the theme.
+     *
+     * @return the description of the theme.
+     */
+    String getDescription();
+
+
+    String getFacet();
+
+    void setFacet(String facet);
+
+    int getIndex();
+
+    void setIndex(int index);
+
+
+    /**
+     * Adds a new attribute.
+     *
+     * @param name The name of the attribute.
+     * @param value The value of the attribute.
+     */
+    void addAttribute(String name, String value);
+
+
+    /**
+     * Returns the value of a specific attribute.
+     *
+     * @param name the name of the attribute.
+     *
+     * @return the value of the attribute <i>name</i>.
+     */
+    String getAttribute(String name);
+
+
+    /**
+     * Adds a new field to the theme.
+     *
+     * @param name The name of the field.
+     * @param field The field.
+     */
+    void addField(String name, ThemeField field);
+
+
+    /**
+     * Sets the value of an field.
+     *
+     * @param name The name of the field.
+     * @param value The new value of the field.
+     */
+    void setFieldValue(String name, Object value);
+
+
+    /**
+     * Returns the field specified by name.
+     *
+     * @param name The name of the desired field.
+     *
+     * @return an field.
+     */
+    ThemeField getField(String name);
+
+
+    /**
+     * Returns the typename of a field.
+     *
+     * @param name the name of the field.
+     *
+     * @return the typename of a field.
+     */
+    String getFieldType(String name);
+
+
+    /**
+     * Returns the value of a field.
+     *
+     * @param name The name of the field.
+     *
+     * @return the value of a field.
+     */
+    Object getFieldValue(String name);
+
+
+    /**
+     * Dumps the theme to XML.
+     *
+     * @return a document.
+     */
+    Document toXML();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeAccess.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,176 @@
+package de.intevation.flys.utils;
+
+import java.awt.Color;
+import java.awt.Font;
+
+import org.w3c.dom.Document;
+
+import de.intevation.flys.jfree.StableXYDifferenceRenderer;
+
+import org.jfree.chart.annotations.XYTextAnnotation;
+
+
+/** Undocumented. */
+public class ThemeAccess
+{
+    protected Document theme;
+
+    protected Integer lineWidth;
+
+    protected Color   lineColor;
+    protected Color   textColor;
+    protected Font    font;
+    protected String  textOrientation;
+    protected Color   textBackground;
+    protected Boolean showTextBackground;
+
+
+    public ThemeAccess(Document theme) {
+        this.theme = theme;
+    }
+
+
+    public int parseLineWidth() {
+        if (lineWidth == null) {
+            lineWidth = ThemeUtil.parseLineWidth(theme);
+        }
+        return lineWidth;
+    }
+
+
+    public Color parseLineColorField() {
+        if (lineColor == null) {
+            lineColor = ThemeUtil.parseLineColorField(theme);
+            if (lineColor == null) {
+                lineColor = Color.BLACK;
+            }
+        }
+        return lineColor;
+    }
+
+    
+    public Color parseTextColor() {
+        if (textColor == null) {
+            textColor = ThemeUtil.parseTextColor(theme);
+            if (textColor == null) {
+                textColor = Color.BLACK;
+            }
+        }
+        return textColor;
+    }
+
+
+    public Font parseTextFont() {
+        if (font == null) {
+            font = ThemeUtil.parseTextFont(theme);
+            if (font == null) {
+                font = new Font("Arial", Font.BOLD, 10);
+            }
+        }
+        return font;
+    }
+
+
+    public String parseTextOrientation() {
+        if (textOrientation == null) {
+            textOrientation = ThemeUtil.parseTextOrientation(theme);
+            if (textOrientation == null) {
+                textOrientation = "horizontal";
+            }
+        }
+        return textOrientation;
+    }
+
+
+    public Color parseTextBackground() {
+        if (textBackground == null) {
+            textBackground = ThemeUtil.parseTextBackground(theme);
+            if (textBackground == null) {
+                textBackground = Color.WHITE;
+            }
+        }
+        return textBackground;
+    }
+
+    public boolean parseShowTextBackground() {
+        if (showTextBackground == null) {
+            showTextBackground = ThemeUtil.parseShowTextBackground(theme);
+        }
+        return showTextBackground;
+    }
+
+    /**
+    more of this
+    */
+
+    public LineStyle parseLineStyle() {
+        return new LineStyle(parseLineColorField(), Integer.valueOf(parseLineWidth()));
+    }
+
+    public static class LineStyle {
+        protected Color lineColor;
+        protected int   lineWidth;
+
+        public LineStyle(Color color, int width) {
+            this.lineColor = color;
+            this.lineWidth = width;
+        }
+
+        public int getWidth() {
+            return lineWidth;
+        }
+
+        public Color getColor() {
+            return lineColor;
+        }
+    }
+
+
+    public TextStyle parseTextStyle() {
+        return new TextStyle(parseTextColor(), parseTextFont(),
+            parseTextBackground(), parseShowTextBackground(),
+            !parseTextOrientation().equals("horizontal"));
+    }
+
+
+    public static class TextStyle {
+        protected Color   textColor;
+        protected Font    font;
+        protected Color   bgColor;
+        protected boolean showBg;
+        protected boolean isVertical;
+
+        public TextStyle(Color fgColor, Font font, Color bgColor,
+            boolean showBg, boolean isVertical
+        ) {
+            this.textColor  = fgColor;
+            this.font       = font;
+            this.bgColor    = bgColor;
+            this.showBg     = showBg;
+            this.isVertical = isVertical;
+        }
+
+        public void apply(XYTextAnnotation ta) {
+            ta.setPaint(textColor);
+            ta.setFont(font);
+            if (this.showBg) {
+                ta.setBackgroundPaint(bgColor);
+            }
+            if (this.isVertical) {
+                ta.setRotationAngle(270f*Math.PI/180f);
+            }
+            else {
+                ta.setRotationAngle(0f*Math.PI/180f);
+            }
+        }
+
+        public void apply(StableXYDifferenceRenderer renderer) {
+            renderer.setLabelColor(textColor);
+            renderer.setLabelFont(font);
+            if (this.showBg) {
+                renderer.setLabelBGColor(bgColor);
+            }
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeFactory.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,302 @@
+package de.intevation.flys.themes;
+
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ *
+ * Mapping-matching rules:
+ *
+ */
+public class ThemeFactory {
+
+    private static Logger logger = Logger.getLogger(ThemeFactory.class);
+
+    /** Trivial, hidden constructor. */
+    private ThemeFactory() {
+    }
+
+
+    /**
+     * Creates a new theme from <i>config</i>.
+     *
+     * @param themeCfg The theme config document that is used to fetch parent
+     * themes.
+     * @param config The theme config node.
+     *
+     * @return a new theme.
+     */
+    public static Theme createTheme(Document themeCfg, Node config) {
+        String name = getName(config);
+        String desc = getDescription(config);
+
+        logger.debug("Create new theme: " + name);
+
+        Theme theme = new DefaultTheme(name, desc);
+
+        parseInherits(themeCfg, config, theme);
+        parseFields(config, theme);
+        parseAttrs(config, theme);
+
+        return theme;
+    }
+
+
+    /**
+     * Get first matching theme for facet.
+     *
+     * @param name    Name to match "from" of theme mapping.
+     * @param pattern String to 'compare' to pattern in mapping.
+     * @param output  Name of the current output
+     *
+     * @return First matching theme.
+     */
+    public static Theme getTheme(
+        FLYSContext c,
+        String name,
+        String pattern,
+        String output,
+        String groupName)
+    {
+        logger.debug("Search theme for: " + name + " - pattern: " + pattern);
+
+        if (c == null || name == null) {
+            logger.warn("Cannot search for theme.");
+            return null;
+        }
+
+        // Fetch mapping and themes.
+        Map<String, List<ThemeMapping>> map = (Map<String, List<ThemeMapping>>)
+            c.get(FLYSContext.THEME_MAPPING);
+
+        List<ThemeGroup> tgs = (List<ThemeGroup>)
+            c.get(FLYSContext.THEMES);
+
+        ThemeGroup group = null;
+        for (ThemeGroup tg: tgs) {
+            if(tg.getName().equals(groupName)) {
+                group = tg;
+            }
+        }
+
+        if (group == null) {
+            return null;
+        }
+
+        Map<String, Theme> t = group.getThemes();
+
+        FLYSArtifact artifact = (FLYSArtifact) c.get(FLYSContext.ARTIFACT_KEY);
+
+        if (map == null || map.size() == 0 || t == null || t.size() == 0) {
+            logger.warn("No mappings or themes found. Cannot retrieve theme!");
+            return null;
+        }
+
+        List<ThemeMapping> mapping = map.get(name);
+
+        if (mapping == null) {
+            logger.warn("No theme found for mapping: " + name);
+            return null;
+        }
+
+        // Take first mapping of which all conditions are satisfied.
+        for (ThemeMapping tm: mapping) {
+            if (name.equals(tm.getFrom())
+                && tm.applyPattern(pattern)
+                && tm.masterAttrMatches(artifact)
+                && tm.outputMatches(output))
+            {
+                return t.get(tm.getTo());
+            }
+        }
+
+        String msg =
+            "No theme found for '" + name +
+            "' with pattern '" + pattern + "' and output " + output + ".";
+
+        logger.warn(msg);
+
+        return null;
+    }
+
+
+    public static List<ThemeGroup> getThemeGroups(FLYSContext c) {
+        List<ThemeGroup> tgs = (List<ThemeGroup>)
+            c.get(FLYSContext.THEMES);
+        return tgs;
+    }
+
+
+    public static List<Theme> getThemes (FLYSContext c, String name) {
+        List<ThemeGroup> tgs = (List<ThemeGroup>)
+            c.get(FLYSContext.THEMES);
+        if (tgs == null) {
+            return null;
+        }
+
+        List<Theme> themes = new ArrayList<Theme>();
+        for (ThemeGroup tg: tgs) {
+            themes.add(tg.getThemeByName(name));
+        }
+        return themes;
+    }
+
+    protected static String getName(Node config) {
+        return ((Element)config).getAttribute("name");
+    }
+
+
+    protected static String getDescription(Node config) {
+        return ((Element)config).getAttribute("desc");
+    }
+
+
+    protected static void parseInherits(Document themeCfg, Node cfg, Theme t) {
+        parseInherits(themeCfg, cfg, t, null);
+    }
+
+    protected static void parseInherits(
+        Document themeCfg,
+        Node     cfg,
+        Theme    t,
+        Map<String, Node> themes
+    ) {
+        logger.debug("ThemeFactory.parseInherits");
+
+        NodeList inherits = ((Element)cfg).getElementsByTagName("inherit");
+
+        int num = inherits.getLength();
+
+        if (num == 0) {
+            logger.debug("Theme does not inherit from other themes.");
+            return;
+        }
+
+        logger.debug("Theme inherits from " + num + " other themes.");
+
+        if (themes == null) {
+            themes = buildThemeMap(themeCfg);
+        }
+
+        for (int i = 0; i < num; i++) {
+            Node inherit = inherits.item(i);
+            String from = ((Element)inherit).getAttribute("from");
+
+            Node tmp = themes.get(from);
+
+            parseInherits(themeCfg, tmp, t, themes);
+            parseFields(tmp, t);
+        }
+    }
+
+    protected static Map<String, Node> buildThemeMap(Document themeCfg) {
+        Map<String, Node> map = new HashMap<String, Node>();
+        String xpath = "/themes/themegroup/theme";
+
+        NodeList nodes = (NodeList)XMLUtils.xpath(
+            themeCfg, xpath, XPathConstants.NODESET);
+
+        if (nodes != null) {
+            for (int i = 0, N = nodes.getLength(); i < N; ++i) {
+                Node node = nodes.item(i);
+                String name = ((Element)node).getAttribute("name");
+                map.put(name, node);
+            }
+        }
+        return map;
+    }
+
+
+    protected static void parseFields(Node config, Theme theme) {
+        if (config == null || theme == null) {
+            logger.warn("Parsing fields without node or theme is senseless!");
+            return;
+        }
+
+        NodeList fields = ((Element)config).getElementsByTagName("field");
+
+        int num = fields.getLength();
+
+        logger.debug("Found " + num + " own fields in this theme.");
+
+        if (num == 0) {
+            logger.debug("Theme has no own fields.");
+            return;
+        }
+
+        for (int i = 0; i < num; i++) {
+            Node field = fields.item(i);
+
+            addField(theme, field);
+        }
+    }
+
+
+    protected static void addField(Theme theme, Node field) {
+        String name = ((Element)field).getAttribute("name");
+
+        logger.debug("Add field: " + name);
+
+        NamedNodeMap attrs = field.getAttributes();
+
+        int num = attrs != null ? attrs.getLength() : 0;
+
+        if (num == 0) {
+            logger.warn("This field has no attributes.");
+            return;
+        }
+
+        ThemeField theField = new DefaultThemeField(name);
+
+        for (int i = 0; i < num; i++) {
+            Node attr    = attrs.item(i);
+
+            String key   = attr.getNodeName();
+            String value = attr.getNodeValue();
+
+            theField.setAttribute(key, value);
+        }
+
+        theme.addField(name, theField);
+    }
+
+
+    protected static void parseAttrs(Node config, Theme theme) {
+        NamedNodeMap attrs = config.getAttributes();
+
+        int num = attrs != null ? attrs.getLength() : 0;
+
+        if (num == 0) {
+            logger.debug("Theme has no attributes set.");
+            return;
+        }
+
+        for (int i = 0; i < num; i++) {
+            Node attr = attrs.item(i);
+
+            String name  = attr.getNodeName();
+            String value = attr.getNodeValue();
+
+            theme.addAttribute(name, value);
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeField.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,58 @@
+package de.intevation.flys.themes;
+
+import org.w3c.dom.Document;
+
+
+/**
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public interface ThemeField {
+
+    /**
+     * Returns the name of this field.
+     *
+     * @return the name of this field.
+     */
+    String getName();
+
+    /**
+     * Returns the type of this field.
+     *
+     * @return the type of this field.
+     */
+    String getType();
+
+
+    /**
+     * Returns the value of this field.
+     *
+     * @return the value of this field.
+     */
+    Object getValue();
+
+
+    /**
+     * Changes the value of this field.
+     *
+     * @param value The new value.
+     */
+    void setValue(Object value);
+
+
+    /**
+     * Sets the value of an attribute.
+     *
+     * @param name The name of an attribute.
+     * @param value The value of an attribute.
+     */
+    void setAttribute(String name, Object value);
+
+
+    /**
+     * Dumps the field to XML.
+     *
+     * @return a document.
+     */
+    Document toXML();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeGroup.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,33 @@
+package de.intevation.flys.themes;
+
+import java.util.Map;
+
+
+
+/**
+ * @author <a href="mailto:raimund.renkert@intevation.de">Raimund Renkert</a>
+ */
+public class ThemeGroup {
+
+    protected String name;
+
+    protected Map<String, Theme> themes;
+
+
+    public ThemeGroup(String name, Map<String, Theme> themes) {
+        this.name = name;
+        this.themes = themes;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public Map<String, Theme> getThemes() {
+        return this.themes;
+    }
+
+    public Theme getThemeByName(String name) {
+        return themes.get(name);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/themes/ThemeMapping.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,135 @@
+package de.intevation.flys.themes;
+
+import org.apache.log4j.Logger;
+
+import java.io.Serializable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+/**
+ * Represents mapping to a theme (including conditions).
+ */
+public class ThemeMapping implements Serializable {
+
+    /** The logger that is used in this class */
+    private static Logger logger = Logger.getLogger(ThemeMapping.class);
+
+    /** Name from which to map. */
+    protected String from;
+
+    /** Name to which to map. */
+    protected String to;
+
+    /** Given pattern (held against facet description). */
+    protected String patternStr;
+
+    /** Given masterAttr pattern (held against masterartifacts attributes). */
+    protected String masterAttr;
+
+    /** Given output for which mapping is valid. */
+    protected String output;
+
+    protected Pattern pattern;
+
+
+    public ThemeMapping(String from, String to) {
+        this(from, to, null, null, null);
+    }
+
+
+    public ThemeMapping(
+        String from,
+        String to,
+        String patternStr,
+        String masterAttr,
+        String output)
+   {
+        this.from       = from;
+        this.to         = to;
+        this.patternStr = patternStr;
+        this.masterAttr = masterAttr;
+        this.output     = output;
+
+        this.pattern = Pattern.compile(patternStr);
+    }
+
+
+    public String getFrom() {
+        return from;
+    }
+
+
+    /**
+     * Get name of theme that is mapped to.
+     */
+    public String getTo() {
+        return to;
+    }
+
+
+    /**
+     * Get pattern.
+     */
+    public String getPatternStr() {
+        return patternStr;
+    }
+
+
+    /**
+     * Match regular expression against text.
+     *
+     * @param text string to be matched against.
+     * @return true if pattern matches text or pattern is empty.
+     */
+    public boolean applyPattern(String text) {
+        if (patternStr == null || patternStr.equals("")) {
+            return true;
+        }
+        Matcher m = pattern.matcher(text);
+        return m.matches();
+    }
+
+
+    /**
+     * Inspects Artifacts data given the masterAttr-condition.
+     *
+     * The only condition implemented so far is 'key==value', for which
+     * the Artifacts data with name "key" has to be of value "value" in order
+     * for true to be returned.
+     *
+     * @param artifact Artifact of which to inspect data.
+     * @return true if no condition is specified or condition is met.
+     */
+    public boolean masterAttrMatches(FLYSArtifact artifact) {
+        if (masterAttr == null || masterAttr.equals("")) {
+           return true;
+        }
+
+        // Operator split.
+        String[] parts = masterAttr.split("==");
+        if (parts.length != 2) {
+            logger.error("ThemeMapping could not parse masterAttr.-condition:_"
+                + masterAttr + "_");
+            return false;
+        }
+
+        // Test.
+        return artifact.getDataAsString(parts[0]).equals(parts[1]);
+    }
+
+
+    /**
+     * Returns true if no output condition exists, or the condition is met
+     * in parameter output.
+     */
+    public boolean outputMatches(String output) {
+        if (this.output == null || this.output.equals("")) {
+            return true;
+        }
+
+        return this.output.equals(output);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/DataUtil.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,34 @@
+package de.intevation.flys.utils;
+
+import java.util.Random;
+
+import gnu.trove.TDoubleArrayList;
+
+public class DataUtil
+{
+    public static boolean guessWaterIncreasing(TDoubleArrayList data) {
+        return guessWaterIncreasing(data, 0.05f);
+    }
+
+    public static boolean guessWaterIncreasing(TDoubleArrayList data, float factor) {
+        int N = data.size();
+        if (N < 2) return false;
+    
+        int samples = (int)(factor*N) + 1;
+    
+        int up = 0;
+    
+        Random rand = new Random();
+    
+        for (int i = 0; i < samples; ++i) {
+            int    pos2 = rand.nextInt(N-1) + 1;
+            int    pos1 = rand.nextInt(pos2);
+            double w1   = data.getQuick(pos1);
+            double w2   = data.getQuick(pos2);
+            if (w2 > w1) ++up;
+        }
+    
+        return up > samples/2;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/DoubleUtil.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,142 @@
+package de.intevation.flys.utils;
+
+import java.util.Arrays;
+
+import de.intevation.flys.artifacts.math.Linear;
+
+
+public class DoubleUtil
+{
+    public static final double DEFAULT_STEP_PRECISION = 1e6;
+
+    private DoubleUtil() {
+    }
+
+    public static final double [] explode(double from, double to, double step) {
+        return explode(from, to, step, DEFAULT_STEP_PRECISION);
+    }
+
+    public static final double round(double x, double precision) {
+        return Math.round(x * precision)/precision;
+    }
+
+    public static final double round(double x) {
+        return Math.round(x * DEFAULT_STEP_PRECISION)/DEFAULT_STEP_PRECISION;
+    }
+
+    public static final double [] explode(
+        double from,
+        double to,
+        double step,
+        double precision
+    ) {
+        double lower = from;
+
+        double diff = to - from;
+        double tmp  = diff / step;
+        int    num = (int)Math.abs(Math.ceil(tmp)) + 1;
+
+        double [] values = new double[num];
+
+        if (from > to) {
+            step = -step;
+        }
+
+        double max = Math.max(from, to);
+
+        for (int idx = 0; idx < num; idx++) {
+            if (lower > max) {
+                return Arrays.copyOfRange(values, 0, idx);
+            }
+
+            values[idx] = round(lower, precision);
+            lower      += step;
+        }
+
+        return values;
+    }
+
+    public static final double interpolateSorted(
+        double [] xs,
+        double [] ys,
+        double x
+    ) {
+        int lo = 0, hi = xs.length-1;
+
+        int mid = -1;
+
+        while (lo <= hi) {
+            mid = (lo + hi) >> 1;
+            double mx = xs[mid];
+                 if (x < mx) hi = mid - 1;
+            else if (x > mx) lo = mid + 1;
+            else return ys[mid];
+        }
+        if (mid < lo) {
+            return lo >= xs.length
+                ? Double.NaN
+                : Linear.linear(x, xs[mid], xs[mid+1], ys[mid], ys[mid+1]);
+        }
+        return hi < 0
+            ? Double.NaN
+            : Linear.linear(x, xs[mid-1], xs[mid], ys[mid-1], ys[mid]);
+    }
+
+    public static final boolean isIncreasing(double [] array) {
+        int inc = 0;
+        int dec = 0;
+        int sweet = (array.length-1)/2;
+        for (int i = 1; i < array.length; ++i) {
+            if (array[i] > array[i-1]) {
+                if (++inc > sweet) {
+                    return true;
+                }
+            }
+            else if (++dec > sweet) {
+                return false;
+            }
+        }
+        return inc > sweet;
+    }
+
+    public static final double [] swap(double [] array) {
+        int lo = 0;
+        int hi = array.length-1;
+        while (hi > lo) {
+            double t  = array[lo];
+            array[lo] = array[hi];
+            array[hi] = t;
+            ++lo;
+            --hi;
+        }
+
+        return array;
+    }
+
+    public static final double [] swapClone(double [] in) {
+        double [] out = new double[in.length];
+
+        for (int j = out.length-1, i = 0; j >= 0;) {
+            out[j--] = in[i++];
+        }
+
+        return out;
+    }
+
+    public static final double [] sumDiffs(double [] in) {
+        double [] out = new double[in.length];
+
+        for (int i = 1; i < out.length; ++i) {
+            out[i] = out[i-1] + Math.abs(in[i-1] - in[i]);
+        }
+
+        return out;
+    }
+
+    public static final double [] fill(int N, double value) {
+        double [] result = new double[N];
+        Arrays.fill(result, value);
+        return result;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/FLYSUtils.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,887 @@
+package de.intevation.flys.utils;
+
+import org.apache.log4j.Logger;
+
+import java.text.NumberFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.w3c.dom.Document;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.impl.SessionFactoryImpl;
+
+import gnu.trove.TDoubleArrayList;
+import gnu.trove.TIntArrayList;
+import gnu.trove.TLongArrayList;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifacts.common.utils.Config;
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.backend.SessionFactoryProvider;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.WINFOArtifact;
+import de.intevation.flys.artifacts.StaticWKmsArtifact;
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.artifacts.model.LocationProvider;
+import de.intevation.flys.artifacts.model.WQ;
+import de.intevation.flys.artifacts.model.WKms;
+import de.intevation.flys.artifacts.model.WQKms;
+
+import de.intevation.artifactdatabase.state.State;
+import de.intevation.flys.artifacts.states.WaterlevelSelectState;
+import de.intevation.flys.artifacts.states.WDifferencesState;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.model.MainValue;
+import de.intevation.flys.model.River;
+
+public class FLYSUtils {
+
+    /** The logger that is used in this utility. */
+    private static Logger logger = Logger.getLogger(FLYSUtils.class);
+
+    public static enum KM_MODE { RANGE, LOCATIONS, NONE };
+
+    /**
+     * An enum that represents the 5 possible WQ modes in FLYS. The 5 values are
+     * <i>QFREE</i> <i>QGAUGE</i> <i>WGAUGE</i> <i>WFREE</i> and <i>NONE</i>.
+     */
+    public static enum WQ_MODE { QFREE, QGAUGE, WFREE, WGAUGE, NONE };
+
+    /**
+     * An enum that represents the 4 possible WQ input modes in FLYS. The 4
+     * values are
+     * <i>ADAPTED</i> <i>SINGLE</i> <i>RANGE</i> and <i>NONE</i>.
+     */
+    public static enum WQ_INPUT { ADAPTED, SINGLE, RANGE, NONE };
+
+    public static final Pattern NUMBERS_PATTERN =
+        Pattern.compile("\\D*(\\d++.\\d*)\\D*");
+
+    public static final String XPATH_RIVER_PROJECTION =
+        "/artifact-database/floodmap/river[@name=$name]/srid/@value";
+
+    public static final String XPATH_SHAPEFILE_DIR =
+        "/artifact-database/floodmap/shapefile-path/@value";
+
+    public static final String XPATH_VELOCITY_LOGFILE =
+        "/artifact-database/floodmap/velocity/logfile/@path";
+
+    public static final String XPATH_MAPSERVER_URL =
+        "/artifact-database/floodmap/mapserver/server/@path";
+
+    public static final String XPATH_MAPFILE_PATH =
+        "/artifact-database/floodmap/mapserver/mapfile/@path";
+
+    public static final String XPATH_MAPFILE_TEMPLATE =
+        "/artifact-database/floodmap/mapserver/map-template/@path";
+
+    public static final String XPATH_MAPSERVER_TEMPLATE_PATH =
+        "/artifact-database/floodmap/mapserver/templates/@path";
+
+
+    private FLYSUtils() {
+    }
+
+
+    /**
+     * Pulls Artifact with given UUID fromm database.
+     * @return FLYSArtifact with given UUID or null (in case of errors).
+     */
+    public static FLYSArtifact getArtifact(String uuid, CallContext context) {
+        try {
+            Artifact artifact = context.getDatabase().getRawArtifact(uuid);
+
+            if (artifact == null) {
+                logger.error("Artifact '" + uuid + "' does not exist.");
+                return null;
+            }
+
+            if (!(artifact instanceof FLYSArtifact)) {
+                logger.error("Artifact '" +uuid+ "' is no valid FLYSArtifact.");
+                return null;
+            }
+
+            return (FLYSArtifact) artifact;
+        }
+        // TODO: catch more selective
+        catch (Exception e) {
+            logger.error("Cannot get FLYSArtifact " + uuid
+                + " from database (" + e.getMessage() + ").");
+            return null;
+        }
+    }
+
+
+    /**
+     * Returns the FLYSContext from context object.
+     *
+     * @param context The CallContext or the FLYSContext.
+     *
+     * @return the FLYSContext.
+     */
+    public static FLYSContext getFlysContext(Object context) {
+        return context instanceof FLYSContext
+            ? (FLYSContext) context
+            : (FLYSContext) ((CallContext) context).globalContext();
+    }
+
+
+    /**
+     * Convinience function to retrieve an XPath as string with replaced config
+     * directory.
+     *
+     * @param xpath The XPath expression.
+     *
+     * @return a string with replaced config directory.
+     */
+    public static String getXPathString(String xpath) {
+        String tmp = Config.getStringXPath(xpath);
+        tmp        = Config.replaceConfigDir(tmp);
+
+        return tmp;
+    }
+
+
+    public static boolean isUsingOracle() {
+        SessionFactory sf = SessionFactoryProvider.getSessionFactory();
+
+        String d = SessionFactoryProvider.getDriver((SessionFactoryImpl) sf);
+
+        return d != null ? d.indexOf("Oracle") >= 0 : false;
+    }
+
+
+    /**
+     * This method returns an WQ_MODE enum which is based on the parameters
+     * stored in <i>flys</i> Artifact. If there is no <i>wq_isq</i> parameter
+     * existing, WQ_MODE.NONE is returned.
+     *
+     * @param flys The FLYSArtifact that stores wq mode relevant parameters.
+     *
+     * @return an enum WQ_MODE.
+     */
+    public static WQ_MODE getWQMode(FLYSArtifact flys) {
+        if (flys == null) {
+            return WQ_MODE.NONE;
+        }
+
+        String values = flys.getDataAsString("wq_values");
+        Boolean isQ    = flys.getDataAsBoolean("wq_isq");
+
+        if (values != null) {
+            return isQ ? WQ_MODE.QGAUGE : WQ_MODE.WGAUGE;
+        }
+
+        Boolean isFree = flys.getDataAsBoolean("wq_isfree");
+
+        if (isQ) {
+            return isFree ? WQ_MODE.QFREE : WQ_MODE.QGAUGE;
+        }
+        else if (!isQ) {
+            return isFree ? WQ_MODE.WFREE : WQ_MODE.WGAUGE;
+        }
+        else {
+            return WQ_MODE.NONE;
+        }
+    }
+
+
+    public static WQ_INPUT getWQInputMode(FLYSArtifact flys) {
+        if (flys == null) {
+            return WQ_INPUT.NONE;
+        }
+
+        Boolean selection = flys.getDataAsBoolean("wq_isrange");
+        String adapted = flys.getDataAsString("wq_values");
+
+        if(adapted != null && adapted.length() > 0) {
+            return WQ_INPUT.ADAPTED;
+        }
+
+        if (selection != null && selection) {
+            return WQ_INPUT.RANGE;
+        }
+        else {
+            return WQ_INPUT.SINGLE;
+        }
+    }
+
+    public static KM_MODE getKmRangeMode(FLYSArtifact flys) {
+        String mode = flys.getDataAsString("ld_mode");
+
+        if (mode == null || mode.length() == 0) {
+            return KM_MODE.NONE;
+        }
+        else if (mode.equals("distance"))  {
+            return KM_MODE.RANGE;
+        }
+        else if (mode.equals("locations")) {
+            return KM_MODE.LOCATIONS;
+        }
+        else {
+            return KM_MODE.NONE;
+        }
+    }
+
+    /**
+     * Get min and max kilometer, independent of parametization
+     * (ld_from/to vs ld_locations).
+     */
+    public static double[] getKmRange(FLYSArtifact flys) {
+        switch (getKmRangeMode(flys)) {
+            case RANGE: {
+                return getKmFromTo(flys);
+            }
+
+            case LOCATIONS: {
+                double[] locs = getLocations(flys);
+                return new double[] { locs[0], locs[locs.length-1] };
+            }
+
+            case NONE: {
+                double[] locs = getLocations(flys);
+                if (locs != null) {
+                    return new double[] { locs[0], locs[locs.length-1] };
+                }
+                else {
+                    return getKmFromTo(flys);
+                }
+            }
+        }
+
+        return new double[] { Double.NaN, Double.NaN };
+    }
+
+
+    /**
+     * Get bounds for river of artifact.
+     * @param flysArtifact artifact which has a "river" data.
+     * @return double array. min is at[0], max at[1]. null if given artifact is null
+     */
+    public static double[] getRiverMinMax(FLYSArtifact flysArtifact) {
+        if (flysArtifact == null) {
+            return null;
+        }
+
+        String riverName = flysArtifact.getDataAsString("river");
+
+        if (riverName == null) {
+            riverName = "";
+        }
+
+        logger.debug("Search for the min/max distances of '" + riverName + "'");
+
+        River river = RiverFactory.getRiver(riverName);
+
+        return river != null
+            ? river.determineMinMaxDistance()
+            : null;
+    }
+
+
+    public static double[] getKmFromTo(FLYSArtifact flys) {
+        String strFrom = flys.getDataAsString("ld_from");
+        String strTo   = flys.getDataAsString("ld_to");
+
+        if (strFrom == null || strTo == null) {
+            return null;
+        }
+
+        try {
+            return new double[] {
+                Double.parseDouble(strFrom),
+                Double.parseDouble(strTo) };
+        }
+        catch (NumberFormatException nfe) {
+            return null;
+        }
+    }
+
+
+    /**
+     * Return sorted array of locations at which stuff was calculated
+     * (from ld_locations data), null if not parameterized this way.
+     */
+    public static double[] getLocations(FLYSArtifact flys) {
+        String locationStr = flys.getDataAsString("ld_locations");
+
+        if (locationStr == null || locationStr.length() == 0) {
+            if (flys instanceof WINFOArtifact) {
+                WINFOArtifact winfo = (WINFOArtifact) flys;
+                if (winfo.getReferenceStartKm() != null) {
+                    return new double[]
+                        {
+                            winfo.getReferenceStartKm().doubleValue(),
+                            winfo.getReferenceEndKms()[0]
+                        };
+                }
+            }
+            return null;
+        }
+
+        String[] tmp               = locationStr.split(" ");
+        TDoubleArrayList locations = new TDoubleArrayList();
+
+        for (String l: tmp) {
+            try {
+                locations.add(Double.parseDouble(l));
+            }
+            catch (NumberFormatException nfe) {
+            }
+        }
+
+        locations.sort();
+
+        return locations.toNativeArray();
+    }
+
+
+    /**
+     * Returns the Qs for a given FLYSArtifact. This method currently accepts
+     * only instances of WINFOArtifact.
+     *
+     * @param flys A FLYSArtifact.
+     *
+     * @return the Qs.
+     */
+    public static double[] getQs(FLYSArtifact flys) {
+        // XXX this is not nice!
+        if (flys instanceof WINFOArtifact) {
+            return ((WINFOArtifact) flys).getQs();
+        }
+
+        logger.warn("This method currently supports WINFOArtifact only!");
+
+        return null;
+    }
+
+
+    /**
+     * Returns the Ws for a given FLYSArtifact. This method currently accepts
+     * only instances of WINFOArtifact.
+     *
+     * @param flys A FLYSArtifact.
+     *
+     * @return the Ws.
+     */
+    public static double[] getWs(FLYSArtifact flys) {
+        // XXX this is not nice!
+        if (flys instanceof WINFOArtifact) {
+            return ((WINFOArtifact) flys).getWs();
+        }
+
+        logger.warn("This method currently supports WINFOArtifact only!");
+
+        return null;
+    }
+
+
+    /**
+     * Returns the selected River object based on the 'river' data that might
+     * have been inserted by the user.
+     *
+     * @return the selected River or null if no river has been chosen yet.
+     */
+    public static River getRiver(FLYSArtifact flys) {
+        String sRiver = getRivername(flys);
+
+        return (sRiver != null)
+            ? RiverFactory.getRiver(sRiver)
+            : null;
+    }
+
+
+    /**
+     * Returns the name of the river specified in the given <i>flys</i>
+     * Artifact.
+     *
+     * @param flys The FLYSArtifact that stores a river relevant information.
+     *
+     * @return the name of the specified river or null.
+     */
+    public static String getRivername(FLYSArtifact flys) {
+        return flys != null ? flys.getDataAsString("river") : null;
+    }
+
+
+    /**
+     * Extracts the SRID defined in the global configuration for the river
+     * specified in <i>artifact</i>.
+     *
+     * @param artifact The FLYSArtifact that stores the name of the river.
+     *
+     * @return the SRID as string (e.g. "31466").
+     */
+    public static String getRiverSrid(FLYSArtifact artifact) {
+        String river = artifact.getDataAsString("river");
+
+        if (river == null || river.length() == 0) {
+            return null;
+        }
+
+        return getRiverSrid(river);
+    }
+
+
+    public static String getRiverSrid(String rivername) {
+        Map<String, String> variables = new HashMap<String, String>(1);
+        variables.put("name", rivername);
+
+        Document cfg = Config.getConfig();
+
+        return (String) XMLUtils.xpath(
+            cfg,
+            XPATH_RIVER_PROJECTION,
+            XPathConstants.STRING,
+            null,
+            variables);
+    }
+
+
+    public static Gauge getGauge(FLYSArtifact flys) {
+        River river = getRiver(flys);
+
+        if (river == null) {
+            logger.debug("no river found");
+            return null;
+        }
+
+        double[] dist  = getKmRange(flys);
+
+        if (dist == null) {
+            logger.debug("no range found");
+            return null;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Determine gauge for:");
+            logger.debug("... river: " + river.getName());
+            logger.debug("... distance: " + dist[0] + " - " + dist[1]);
+        }
+
+        Gauge gauge = river.determineGauge(dist[0], dist[1]);
+
+        String name = gauge != null ? gauge.getName() : "'n/a";
+        logger.debug("Found gauge: " + name);
+
+        return gauge;
+    }
+
+
+    public static String getGaugename(FLYSArtifact flys) {
+        Gauge gauge = getGauge(flys);
+
+        return gauge != null ? gauge.getName() : null;
+    }
+
+
+    public static Gauge getReferenceGauge(FLYSArtifact flys) {
+        Long officialNumber = flys.getDataAsLong("reference_gauge");
+
+        return officialNumber != null
+            ? Gauge.getGaugeByOfficialNumber(officialNumber)
+            : null;
+    }
+
+
+    public static String getReferenceGaugeName(FLYSArtifact flys) {
+        Gauge refGauge = getReferenceGauge(flys);
+
+        return refGauge != null
+            ? refGauge.getName()
+            : "-- not found --";
+    }
+
+
+    public static Double getValueFromWQ(WQ wq) {
+        if (wq == null) {
+            return null;
+        }
+
+        Matcher m = NUMBERS_PATTERN.matcher(wq.getName());
+
+        if (m.matches()) {
+            logger.debug("Found a number.");
+
+            String raw = m.group(1);
+
+            try {
+                return Double.valueOf(raw);
+            }
+            catch (NumberFormatException nfe) {
+            }
+        }
+
+        return null;
+    }
+
+
+    public static String createWspWTitle(
+        WINFOArtifact winfo,
+        CallContext   cc,
+        String        name
+    ) {
+        String[] parts = name.split("=");
+
+        NumberFormat nf = Formatter.getWaterlevelW(cc);
+
+        String namedMainValue = null;
+
+        boolean isQ    = winfo.isQ();
+        boolean isFree = winfo.isFreeQ();
+
+        double v;
+
+        try {
+            v = Double.valueOf(parts[1]);
+
+            namedMainValue = getNamedMainValue(winfo.getGauge(), v);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Cannot parse Double of: '" + parts[1] + "'");
+            return name;
+        }
+
+        String prefix = null;
+
+        if (isQ && !isFree && namedMainValue != null) {
+            return "W (" + namedMainValue + ")";
+        }
+
+        if (isQ) {
+            prefix = "Q=";
+        }
+
+        return prefix == null
+            ? "W(" + nf.format(v) + ")"
+            : "W(" + prefix + nf.format(v) + ")";
+    }
+
+
+    public static String createWspQTitle(
+        WINFOArtifact winfo,
+        CallContext   cc,
+        String        name
+    ) {
+        String[] parts = name.split("=");
+
+        NumberFormat nf = Formatter.getWaterlevelQ(cc);
+
+        String namedMainValue = null;
+
+        boolean isQ    = winfo.isQ();
+        boolean isFree = winfo.isFreeQ();
+
+        double v;
+
+        try {
+            v = Double.valueOf(parts[1]);
+
+            namedMainValue = getNamedMainValue(winfo.getGauge(), v);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Cannot parse Double of: '" + parts[1] + "'");
+            return name;
+        }
+
+        String prefix = null;
+
+        if (isQ && !isFree && namedMainValue != null) {
+            return namedMainValue;
+        }
+
+        if (!isQ) {
+            prefix = "W=";
+        }
+
+        return prefix == null
+            ? "Q(" + nf.format(v) + ")"
+            : "Q(" + prefix + nf.format(v) + ")";
+    }
+
+
+    /**
+     * Returns the named main value if a Q was selected and if this Q fits to a
+     * named main value. Otherwise, this function returns null.
+     *
+     * @param winfo The WINFO Artifact.
+     * @param value The Q (or W) value.
+     *
+     * @return a named main value or null.
+     */
+    public static String getNamedMainValue(WINFOArtifact winfo, double value) {
+        WQ_MODE wqmode = getWQMode(winfo);
+
+        if (wqmode != WQ_MODE.QGAUGE) {
+            return null;
+        }
+        else {
+            return getNamedMainValue(winfo.getGauge(), value);
+        }
+    }
+
+
+    public static String getNamedMainValue(Gauge gauge, double value) {
+        List<MainValue> mainValues = gauge.getMainValues();
+        logger.debug("Search named main value for: " + value);
+
+        for (MainValue mv: mainValues) {
+            if (mv.getValue().doubleValue() == value) {
+                logger.debug("Found named main value: " + mv.getMainValue().getName());
+                return mv.getMainValue().getName();
+            }
+        }
+
+        logger.debug("Did not find a named main value for: " + value);
+        return null;
+    }
+
+
+    /**
+     *
+     * @param nmv A string that represents a named main value.
+     *
+     * @throws NullPointerException if nmv is null.
+     */
+    public static String stripNamedMainValue(String nmv) {
+        int startIndex = nmv.indexOf("(");
+        int endIndex   = nmv.indexOf(")");
+
+        if (startIndex > 0 && endIndex > 0 && startIndex < endIndex) {
+            return nmv.substring(0, startIndex);
+        }
+
+        return nmv;
+    }
+
+
+    /**
+     * Returns the URL of user mapfile for the owner of Artifact
+     * <i>artifactId</i>.
+     *
+     * @param artifactId The UUID of an artifact.
+     *
+     * @return the URL of the user wms.
+     */
+    public static String getUserWMSUrl(String artifactId) {
+        String url = getXPathString(XPATH_MAPSERVER_URL);
+        url = url.endsWith("/") ? url + "user-wms" : url + "/" + "user-wms";
+
+        return url;
+    }
+
+
+    /**
+     * This method returns the description for a given <i>km</i> for a specific
+     * river. The river is provided by the FLYSArtifact <i>flys</i>.
+     *
+     * @param flys The FLYSArtifact that provides a river.
+     * @param km The kilometer.
+     *
+     * @return the description for <i>km</i> or an empty string if no
+     * description was found.
+     */
+    public static String getLocationDescription(FLYSArtifact flys, double km) {
+        String river = getRivername(flys);
+
+        if (river == null) {
+            return "";
+        }
+
+        return LocationProvider.getLocation(river, km);
+    }
+
+
+    /**
+     * This method returns the differences for a w-differences calculation.
+     *
+     * @param winfo The WINFOArtifact.
+     * @param context The context.
+     *
+     * @return The differences as string separated by semicolon and linebreak.
+     */
+    public static String getWDifferences(
+        WINFOArtifact winfo,
+        CallContext context)
+    {
+        State state = winfo.getCurrentState(context);
+        if(state instanceof WDifferencesState) {
+            String diffids = winfo.getDataAsString("diffids");
+            String datas[] = diffids.split("#");
+
+            // Validate the Data-Strings.
+            for (String s: datas) {
+                if (!WaterlevelSelectState.isValueValid(winfo.getDataAsString("diffids"))) {
+                    return "";
+                }
+            }
+
+            if (datas.length < 2) {
+                return "";
+            }
+
+            String diffs = "";
+            for(int i = 0; i < datas.length; i+=2) {
+                // e.g.:
+                // 42537f1e-3522-42ef-8968-635b03d8e9c6;longitudinal_section.w;1
+                WKms minuendWKms = getWKms(StringUtil.unbracket(datas[i+0]),
+                    context);
+                WKms subtrahendWKms = getWKms(StringUtil.unbracket(datas[i+1]),
+                    context);
+                if (minuendWKms != null && subtrahendWKms != null) {
+                    diffs += StringUtil.wWrap(minuendWKms.getName()) 
+                        + " - " + StringUtil.wWrap(subtrahendWKms.getName());
+                }
+                diffs += ";\n";
+            }
+            return diffs;
+        }
+        else {
+            logger.warn("Not a valid state for differences.");
+            return "";
+        }
+    }
+
+
+    protected static WKms getWKms(String mingle, CallContext context) {
+        String[] def  = mingle.split(";");
+        String   uuid = def[0];
+        String   name = def[1];
+        int      idx  = Integer.parseInt(def[2]);
+
+        if (name.startsWith("staticwkms")) {
+            StaticWKmsArtifact staticWKms =
+                (StaticWKmsArtifact) FLYSUtils.getArtifact(
+                    uuid,
+                    context);
+            WKms wkms = staticWKms.getWKms(idx);
+            if (wkms == null)
+                logger.error("No WKms from artifact.");
+            return wkms;
+        }
+
+        WINFOArtifact flys = (WINFOArtifact) FLYSUtils.getArtifact(
+            uuid,
+            context);
+
+        if (flys == null) {
+            logger.warn("One of the artifacts (1) for diff calculation could not be loaded");
+            return null;
+        }
+        else{
+            WQKms[] wqkms = (WQKms[]) flys.getWaterlevelData().
+                                              getData();
+            if (wqkms == null)
+            logger.warn("not  waterlevels in artifact");
+            else if (wqkms.length < idx)
+            logger.warn("not enough waterlevels in artifact");
+            return wqkms[idx];
+        }
+    }
+
+
+    /**
+     * This method transform a string into an int array. Therefore, the string
+     * <i>raw</i> must consist of int values separated by a <i>';'</i>.
+     *
+     * @param raw The raw integer array as string separated by a ';'.
+     *
+     * @return an array of int values.
+     */
+    public static int[] intArrayFromString(String raw) {
+        String[] splitted = raw != null ? raw.split(";") : null;
+
+        if (splitted == null || splitted.length == 0) {
+            logger.warn("No integer values found in '" + raw + "'");
+            return new int[0];
+        }
+
+        TIntArrayList integers = new TIntArrayList(splitted.length);
+
+        for (String value: splitted) {
+            try {
+                integers.add(Integer.valueOf(value));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Parsing integer failed: " + nfe);
+            }
+        }
+
+        return integers.toNativeArray();
+    }
+
+
+    /**
+     * This method transform a string into a long array. Therefore, the string
+     * <i>raw</i> must consist of int values separated by a <i>';'</i>.
+     *
+     * @param raw The raw long array as string separated by a ';'.
+     *
+     * @return an array of int values.
+     */
+    public static long[] longArrayFromString(String raw) {
+        String[] splitted = raw != null ? raw.split(";") : null;
+
+        if (splitted == null || splitted.length == 0) {
+            logger.warn("No long values found in '" + raw + "'");
+            return new long[0];
+        }
+
+        TLongArrayList longs = new TLongArrayList(splitted.length);
+
+        for (String value: splitted) {
+            try {
+                longs.add(Long.valueOf(value));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Parsing long failed: " + nfe);
+            }
+        }
+
+        return longs.toNativeArray();
+    }
+
+
+    /**
+     * This method transform a string into an double array. Therefore, the
+     * string <i>raw</i> must consist of double values separated by a
+     * <i>';'</i>.
+     *
+     * @param raw The raw double array as string separated by a ';'.
+     *
+     * @return an array of double values.
+     */
+    public static double[] doubleArrayFromString(String raw) {
+        String[] splitted = raw != null ? raw.split(";") : null;
+
+        if (splitted == null || splitted.length == 0) {
+            logger.warn("No double values found in '" + raw + "'");
+            return new double[0];
+        }
+
+        TDoubleArrayList doubles = new TDoubleArrayList(splitted.length);
+
+        for (String value: splitted) {
+            try {
+                doubles.add(Double.valueOf(value));
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Parsing double failed: " + nfe);
+            }
+        }
+
+        return doubles.toNativeArray();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/Formatter.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,351 @@
+package de.intevation.flys.utils;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+
+import de.intevation.flys.artifacts.resources.Resources;
+
+
+public final class Formatter {
+
+    // KMS IN ERROR REPORTS.
+    public static final int CALCULATION_REPORT_KM_MIN_DIGITS = 1;
+    public static final int CALCULATION_REPORT_KM_MAX_DIGITS = 3;
+
+    // WATERLEVEL FORMATTER CONSTANTS
+    public static final int WATERLEVEL_KM_MIN_DIGITS = 3;
+    public static final int WATERLEVEL_KM_MAX_DIGITS = 3;
+    public static final int WATERLEVEL_W_MIN_DIGITS  = 0;
+    public static final int WATERLEVEL_W_MAX_DIGITS  = 2;
+    public static final int WATERLEVEL_Q_MIN_DIGITS  = 0;
+    public static final int WATERLEVEL_Q_MAX_DIGITS  = 2;
+
+
+    // COMPUTED DISCHARGE CURVE FORMATTER CONSTANTS
+    public static final int COMPUTED_DISCHARGE_W_MIN_DIGITS  = 2;
+    public static final int COMPUTED_DISCHARGE_W_MAX_DIGITS  = 2;
+    public static final int COMPUTED_DISCHARGE_Q_MIN_DIGITS  = 0;
+    public static final int COMPUTED_DISCHARGE_Q_MAX_DIGITS  = 2;
+
+
+    // HISTORICAL DISCHARGE CURVE FORMATTER CONSTANTS
+    public static final int HISTORICAL_DISCHARGE_W_MIN_DIGITS = 0;
+    public static final int HISTORICAL_DISCHARGE_W_MAX_DIGITS = 2;
+    public static final int HISTORICAL_DISCHARGE_Q_MIN_DIGITS = 0;
+    public static final int HISTORICAL_DISCHARGE_Q_MAX_DIGITS = 2;
+
+
+    // DURATION CURVE FORMATTER CONSTANTS
+    public static final int DURATION_W_MIN_DIGITS = 0;
+    public static final int DURATION_W_MAX_DIGITS = 2;
+    public static final int DURATION_Q_MIN_DIGITS = 0;
+    public static final int DURATION_Q_MAX_DIGITS = 1;
+    public static final int DURATION_D_MIN_DIGITS = 0;
+    public static final int DURATION_D_MAX_DIGITS = 0;
+
+
+    // FLOW VELOCITY FORMATTER CONSTANTS
+    public static final int FLOW_VELOCITY_KM_MIN_DIGITS     = 3;
+    public static final int FLOW_VELOCITY_KM_MAX_DIGITS     = 3;
+    public static final int FLOW_VELOCITY_VALUES_MIN_DIGITS = 2;
+    public static final int FLOW_VELOCITY_VALUES_MAX_DIGITS = 2;
+    public static final int FLOW_VELOCITY_Q_MIN_DIGITS      = 0;
+    public static final int FLOW_VELOCITY_Q_MAX_DIGITS      = 2;
+
+
+    // MIDDLE BED HEIGHT FORMATTER CONSTANTS
+    public static final int MIDDLE_BED_HEIGHT_KM_MIN_DIGITS             = 3;
+    public static final int MIDDLE_BED_HEIGHT_KM_MAX_DIGITS             = 3;
+    public static final int MIDDLE_BED_HEIGHT_HEIGHT_MIN_DIGITS         = 3;
+    public static final int MIDDLE_BED_HEIGHT_HEIGHT_MAX_DIGITS         = 3;
+    public static final int MIDDLE_BED_HEIGHT_UNCERT_MIN_DIGITS         = 3;
+    public static final int MIDDLE_BED_HEIGHT_UNCERT_MAX_DIGITS         = 3;
+    public static final int MIDDLE_BED_HEIGHT_DATAGAP_MIN_DIGITS        = 2;
+    public static final int MIDDLE_BED_HEIGHT_DATAGAP_MAX_DIGITS        = 2;
+    public static final int MIDDLE_BED_HEIGHT_SOUNDING_WIDTH_MIN_DIGITS = 0;
+    public static final int MIDDLE_BED_HEIGHT_SOUNDING_WIDTH_MAX_DIGITS = 0;
+    public static final int MIDDLE_BED_HEIGHT_WIDTH_MIN_DIGITS          = 3;
+    public static final int MIDDLE_BED_HEIGHT_WIDTH_MAX_DIGITS          = 3;
+
+    public static final int FIX_DELTA_W_KM_MIN_DIGITS = 3;
+    public static final int FIX_DELTA_W_KM_MAX_DIGITS = 3;
+    public static final int FIX_DELTA_W_DELTA_W_MIN_DIGITS = 3;
+    public static final int FIX_DELTA_W_DELTA_W_MAX_DIGITS = 3;
+
+    public static NumberFormat getFormatter(CallMeta m, int min, int max){
+        Locale       locale = Resources.getLocale(m);
+        NumberFormat nf     = NumberFormat.getInstance(locale);
+
+        nf.setMaximumFractionDigits(max);
+        nf.setMinimumFractionDigits(min);
+
+        return nf;
+    }
+
+    public static NumberFormat getFormatter(CallContext c, int min, int max){
+        return getFormatter(c.getMeta(), min, max);
+    }
+
+
+    /**
+     * Returns a number formatter with no max or min digits set.
+     *
+     * @param c The CallContext.
+     *
+     * @return a number formatter.
+     */
+    public static NumberFormat getRawFormatter(CallContext c) {
+        Locale locale = Resources.getLocale(c.getMeta());
+        return NumberFormat.getInstance(locale);
+    }
+
+
+    /**
+     * Returns a date formatter with SHORT style.
+     */
+    public static DateFormat getShortDateFormat(CallContext cc) {
+        Locale locale = Resources.getLocale(cc.getMeta());
+        return DateFormat.getDateInstance(DateFormat.SHORT, locale);
+    }
+
+
+    /**
+     * Returns a date formatter with MEDIUM style.
+     */
+    public static DateFormat getMediumDateFormat(CallContext cc) {
+        Locale locale = Resources.getLocale(cc.getMeta());
+        return DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
+    }
+
+
+    /**
+     * Returns the number formatter for kilometer values in waterlevel exports.
+     *
+     * @return the number formatter for kilometer values.
+     */
+    public static NumberFormat getWaterlevelKM(CallContext context) {
+        return getFormatter(
+            context,
+            WATERLEVEL_KM_MIN_DIGITS,
+            WATERLEVEL_KM_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for W values in waterlevel exports.
+     *
+     * @return the number formatter for W values.
+     */
+    public static NumberFormat getWaterlevelW(CallContext context) {
+        return getFormatter(
+            context,
+            WATERLEVEL_W_MIN_DIGITS,
+            WATERLEVEL_W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values in waterlevel exports.
+     *
+     * @return the number formatter for Q values.
+     */
+    public static NumberFormat getWaterlevelQ(CallContext context) {
+        return getFormatter(
+            context,
+            WATERLEVEL_Q_MIN_DIGITS,
+            WATERLEVEL_Q_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for W values in exports of computed
+     * discharge curves.
+     *
+     * @return the number formatter for W values.
+     */
+    public static NumberFormat getComputedDischargeW(CallContext context) {
+        return getFormatter(
+            context,
+            COMPUTED_DISCHARGE_W_MIN_DIGITS,
+            COMPUTED_DISCHARGE_W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values in exports of computed
+     * discharge curves.
+     *
+     * @return the number formatter for Q values.
+     */
+    public static NumberFormat getComputedDischargeQ(CallContext context) {
+        return getFormatter(
+            context,
+            COMPUTED_DISCHARGE_Q_MIN_DIGITS,
+            COMPUTED_DISCHARGE_Q_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for W values in exports of historical
+     * discharge curves.
+     *
+     * @return the number formatter for W values.
+     */
+    public static NumberFormat getHistoricalDischargeW(CallContext context) {
+        return getFormatter(
+            context,
+            HISTORICAL_DISCHARGE_W_MIN_DIGITS,
+            HISTORICAL_DISCHARGE_W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values in exports of historical
+     * discharge curves.
+     *
+     * @return the number formatter for Q values.
+     */
+    public static NumberFormat getHistoricalDischargeQ(CallContext context) {
+        return getFormatter(
+            context,
+            HISTORICAL_DISCHARGE_Q_MIN_DIGITS,
+            HISTORICAL_DISCHARGE_Q_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for W values in duration curve exports.
+     *
+     * @return the number formatter for W values.
+     */
+    public static NumberFormat getDurationW(CallContext context) {
+        return getFormatter(
+            context,
+            DURATION_W_MIN_DIGITS,
+            DURATION_W_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for Q values in duration curve exports.
+     *
+     * @return the number formatter for W values.
+     */
+    public static NumberFormat getDurationQ(CallContext context) {
+        return getFormatter(
+            context,
+            DURATION_Q_MIN_DIGITS,
+            DURATION_Q_MAX_DIGITS);
+    }
+
+
+    /**
+     * Returns the number formatter for D values in duration curve exports.
+     *
+     * @return the number formatter for W values.
+     */
+    public static NumberFormat getDurationD(CallContext context) {
+        return getFormatter(
+            context,
+            DURATION_D_MIN_DIGITS,
+            DURATION_D_MAX_DIGITS);
+    }
+
+    public static NumberFormat getCalculationKm(CallMeta meta) {
+        return getFormatter(
+            meta,
+            CALCULATION_REPORT_KM_MIN_DIGITS,
+            CALCULATION_REPORT_KM_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getFlowVelocityKM(CallContext context) {
+        return getFormatter(
+            context,
+            FLOW_VELOCITY_KM_MIN_DIGITS,
+            FLOW_VELOCITY_KM_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getFlowVelocityValues(CallContext context) {
+        return getFormatter(
+            context,
+            FLOW_VELOCITY_VALUES_MIN_DIGITS,
+            FLOW_VELOCITY_VALUES_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getFlowVelocityQ(CallContext context) {
+        return getFormatter(
+            context,
+            FLOW_VELOCITY_Q_MIN_DIGITS,
+            FLOW_VELOCITY_Q_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getMiddleBedHeightKM(CallContext context) {
+        return getFormatter(
+            context,
+            MIDDLE_BED_HEIGHT_KM_MIN_DIGITS,
+            MIDDLE_BED_HEIGHT_KM_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getMiddleBedHeightHeight(CallContext context) {
+        return getFormatter(
+            context,
+            MIDDLE_BED_HEIGHT_HEIGHT_MIN_DIGITS,
+            MIDDLE_BED_HEIGHT_HEIGHT_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getMiddleBedHeightUncert(CallContext context) {
+        return getFormatter(
+            context,
+            MIDDLE_BED_HEIGHT_UNCERT_MIN_DIGITS,
+            MIDDLE_BED_HEIGHT_UNCERT_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getMiddleBedHeightDataGap(CallContext context) {
+        return getFormatter(
+            context,
+            MIDDLE_BED_HEIGHT_DATAGAP_MIN_DIGITS,
+            MIDDLE_BED_HEIGHT_DATAGAP_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getMiddleBedHeightSounding(CallContext context) {
+        return getFormatter(
+            context,
+            MIDDLE_BED_HEIGHT_SOUNDING_WIDTH_MIN_DIGITS,
+            MIDDLE_BED_HEIGHT_SOUNDING_WIDTH_MAX_DIGITS);
+    }
+
+
+    public static NumberFormat getMiddleBedHeightWidth(CallContext context) {
+        return getFormatter(
+            context,
+            MIDDLE_BED_HEIGHT_WIDTH_MIN_DIGITS,
+            MIDDLE_BED_HEIGHT_WIDTH_MAX_DIGITS);
+    }
+
+    public static NumberFormat getFixDeltaWKM(CallContext context) {
+        return getFormatter(
+            context,
+            FIX_DELTA_W_KM_MIN_DIGITS,
+            FIX_DELTA_W_KM_MAX_DIGITS);
+    }
+
+    public static NumberFormat getFixDeltaWDeltaW(CallContext context) {
+        return getFormatter(
+            context,
+            FIX_DELTA_W_DELTA_W_MIN_DIGITS,
+            FIX_DELTA_W_DELTA_W_MAX_DIGITS);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/GeometryUtils.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,305 @@
+package de.intevation.flys.utils;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+import org.geotools.data.DataStoreFactorySpi;
+import org.geotools.data.DefaultTransaction;
+import org.geotools.data.FeatureWriter;
+import org.geotools.data.Transaction;
+import org.geotools.data.shapefile.ShapefileDataStore;
+import org.geotools.data.shapefile.ShapefileDataStoreFactory;
+import org.geotools.data.simple.SimpleFeatureIterator;
+import org.geotools.feature.FeatureIterator;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
+import org.geotools.geojson.feature.FeatureJSON;
+import org.geotools.geometry.jts.JTS;
+import org.geotools.referencing.CRS;
+
+import de.intevation.flys.model.RiverAxis;
+
+
+public class GeometryUtils {
+
+    private static final Logger logger = Logger.getLogger(GeometryUtils.class);
+
+    public static final String DEFAULT_EPSG = "EPSG:31467";
+
+
+    private GeometryUtils() {
+    }
+
+
+    public static Envelope getRiverBoundary(String rivername) {
+        List<RiverAxis> axes = RiverAxis.getRiverAxis(rivername);
+        if (axes != null && axes.size() > 0) {
+            Envelope max = null;
+
+            for (RiverAxis axis: axes) {
+                // TODO Take the correct EPSG into account. Maybe, we need to
+                // reproject the geometry.
+                Envelope env = axis.getGeom().getEnvelopeInternal();
+
+                if (max == null) {
+                    max = env;
+                }
+                else {
+                    max.expandToInclude(env);
+                }
+            }
+
+            return max;
+        }
+
+        return null;
+    }
+
+
+    public static String getRiverBounds(String rivername) {
+        Envelope env = getRiverBoundary(rivername);
+
+        if (env == null) {
+            return jtsBoundsToOLBounds(env);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Returns the boundary of Envelope <i>env</i> in OpenLayers
+     * representation.
+     *
+     * @param env The envelope of a geometry.
+     *
+     * @return the OpenLayers boundary of <i>env</i>.
+     */
+    public static String jtsBoundsToOLBounds(Envelope env) {
+        return "" +
+            env.getMinX() + " " +
+            env.getMinY() + " " +
+            env.getMaxX() + " " +
+            env.getMaxY();
+    }
+
+
+    public static String createOLBounds(Geometry a, Geometry b) {
+        Coordinate[] ca = a.getCoordinates();
+        Coordinate[] cb = b.getCoordinates();
+
+        double lowerX = Double.MAX_VALUE;
+        double lowerY = Double.MAX_VALUE;
+        double upperX = -Double.MAX_VALUE;
+        double upperY = -Double.MAX_VALUE;
+
+        for (Coordinate c: ca) {
+            lowerX = lowerX < c.x ? lowerX : c.x;
+            lowerY = lowerY < c.y ? lowerY : c.y;
+
+            upperX = upperX > c.x ? upperX : c.x;
+            upperY = upperY > c.y ? upperY : c.y;
+        }
+
+        for (Coordinate c: cb) {
+            lowerX = lowerX < c.x ? lowerX : c.x;
+            lowerY = lowerY < c.y ? lowerY : c.y;
+
+            upperX = upperX > c.x ? upperX : c.x;
+            upperY = upperY > c.y ? upperY : c.y;
+        }
+
+        return "" + lowerX + " " + lowerY + " " + upperX + " " + upperY;
+    }
+
+
+    public static SimpleFeatureType buildFeatureType(
+        String name, String srs, Class geometryType)
+    {
+        return buildFeatureType(name, srs, geometryType, null);
+    }
+
+
+    /**
+     * Creates a new SimpleFeatureType using a SimpleFeatureTypeBuilder.
+     *
+     * @param name The name of the FeatureType.
+     * @param srs The SRS (e.g. "EPSG:31466").
+     * @param geometryType The geometry type's class (e.g. Polygon.class).
+     * @param attrs Optional. An object with attribute-name/attribute-class
+     * pairs where index 0 specifies the name as string and index 1 the type
+     * as class.
+     *
+     * @return a new SimpleFeatureType.
+     */
+    public static SimpleFeatureType buildFeatureType(
+        String name, String srs, Class geometryType, Object[][] attrs)
+    {
+        try {
+            SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
+            CoordinateReferenceSystem crs    = CRS.decode(srs);
+
+            builder.setName("flys");
+            builder.setNamespaceURI("http://www.intevation.de/");
+            builder.setCRS(crs);
+            builder.setSRS(srs);
+
+            builder.add("geometry", geometryType, crs);
+
+            if (attrs != null) {
+                for (Object[] attr: attrs) {
+                    builder.add((String) attr[0], (Class) attr[1]);
+                }
+            }
+
+            return builder.buildFeatureType();
+        }
+        catch (NoSuchAuthorityCodeException nsae) {
+        }
+        catch (FactoryException fe) {
+        }
+
+        return null;
+    }
+
+
+    public static List<SimpleFeature> parseGeoJSON(
+        String geojson, SimpleFeatureType ft
+    ) {
+        List<SimpleFeature> collection = new ArrayList<SimpleFeature>();
+
+        try {
+            FeatureJSON fjson = new FeatureJSON();
+            fjson.setFeatureType(ft);
+
+            FeatureIterator<SimpleFeature> iterator =
+                fjson.streamFeatureCollection(geojson);
+
+            while (iterator.hasNext()) {
+                collection.add(iterator.next());
+            }
+        }
+        catch (IOException ioe) {
+            // TODO handle exception
+        }
+
+        return collection;
+    }
+
+
+    public static boolean writeShapefile(
+        File              shape,
+        SimpleFeatureType featureType,
+        FeatureCollection collection
+    ) {
+        return writeShapefile(
+            shape, featureType, collection, featureType.getCoordinateReferenceSystem());
+    }
+
+
+    public static boolean writeShapefile(
+        File                      shape,
+        SimpleFeatureType         featureType,
+        FeatureCollection         collection,
+        CoordinateReferenceSystem crs
+    ) {
+        if (collection.isEmpty()) {
+            logger.warn("Shapefile is not written - no features given!");
+            return false;
+        }
+
+        Transaction   transaction = null;
+
+        try {
+            MathTransform transform = CRS.findMathTransform(
+                CRS.decode(DEFAULT_EPSG), crs);
+
+            Map<String, Serializable> params =
+                new HashMap<String, Serializable>();
+
+            params.put("url", shape.toURI().toURL());
+            params.put("create spatial index", Boolean.TRUE);
+
+            DataStoreFactorySpi dataStoreFactory =
+                new ShapefileDataStoreFactory();
+
+            ShapefileDataStore newDataStore =
+                (ShapefileDataStore)dataStoreFactory.createNewDataStore(params);
+            newDataStore.createSchema(featureType);
+
+            transaction = new DefaultTransaction("create");
+
+            String typeName = newDataStore.getTypeNames()[0];
+
+            FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
+                newDataStore.getFeatureWriter(typeName, transaction);
+
+            SimpleFeatureIterator iterator = (SimpleFeatureIterator) collection.features();
+
+            while (iterator.hasNext()){
+                SimpleFeature feature = iterator.next();
+                SimpleFeature copy    = writer.next();
+
+                copy.setAttributes(feature.getAttributes());
+
+                Geometry orig        = (Geometry) feature.getDefaultGeometry();
+                Geometry reprojected = JTS.transform(orig, transform);
+
+                copy.setDefaultGeometry(reprojected);
+                writer.write();
+            }
+
+            transaction.commit();
+
+            return true;
+        }
+        catch (MalformedURLException mue) {
+            logger.error("Unable to prepare shapefile: " + mue.getMessage());
+        }
+        catch (IOException ioe) {
+            logger.error("Unable to write shapefile: " + ioe.getMessage());
+        }
+        catch (NoSuchAuthorityCodeException nsae) {
+            logger.error("Cannot get CoordinateReferenceSystem for '"
+                + DEFAULT_EPSG + "'");
+        }
+        catch (FactoryException fe) {
+            logger.error("Cannot get CoordinateReferenceSystem for '"
+                + DEFAULT_EPSG + "'");
+        }
+        catch (TransformException te) {
+            logger.error("Was not able to transform geometry!", te);
+        }
+        finally {
+            if (transaction != null) {
+                try {
+                    transaction.close();
+                }
+                catch (IOException ioe) { /* do nothing */ }
+            }
+        }
+
+        return false;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/MapfileGenerator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,760 @@
+package de.intevation.flys.utils;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.geotools.data.shapefile.ShpFiles;
+import org.geotools.data.shapefile.shp.ShapefileReader;
+import org.geotools.data.shapefile.shp.ShapefileHeader;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.common.utils.Config;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.LayerInfo;
+import de.intevation.flys.artifacts.model.WMSLayerFacet;
+import de.intevation.flys.artifacts.model.WSPLGENLayerFacet;
+import de.intevation.flys.artifacts.model.WMSDBLayerFacet;
+import de.intevation.flys.artifacts.resources.Resources;
+
+/**
+ * This class iterates over a bunch of directories, searches for meta
+ * information coresponding to shapefiles and creates a mapfile which is used by
+ * a <i>MapServer</i>.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class MapfileGenerator
+extends Thread
+{
+    public static final String WSPLGEN_RESULT_SHAPE   = "wsplgen.shp";
+    public static final String WSPLGEN_LINES_SHAPE    = "barrier_lines.shp";
+    public static final String WSPLGEN_POLYGONS_SHAPE = "barrier_polygons.shp";
+    public static final String WSPLGEN_USER_SHAPE     = "user-rgd.shp";
+
+    public static final String WSPLGEN_LAYER_TEMPLATE = "wsplgen_layer.vm";
+    public static final String SHP_LAYER_TEMPLATE = "shapefile_layer.vm";
+    public static final String DB_LAYER_TEMPLATE  = "db_layer.vm";
+
+    public static final String MS_WSPLGEN_PREFIX   = "wsplgen-";
+    public static final String MS_BARRIERS_PREFIX  = "barriers-";
+    public static final String MS_LINE_PREFIX      = "lines-";
+    public static final String MS_POLYGONS_PREFIX  = "polygons-";
+    public static final String MS_LAYER_PREFIX     = "ms_layer-";
+    public static final String MS_USERSHAPE_PREFIX = "user-";
+
+    protected static final long SLEEPTIME = 10 * 1000L; // 10 seconds
+
+    private static Logger logger = Logger.getLogger(MapfileGenerator.class);
+
+    private static MapfileGenerator instance;
+
+    private File mapfile;
+    private File shapefileDirectory;
+
+    private String mapServerUrl;
+    private String templatePath;
+    private String velocityLogfile;
+
+    private VelocityEngine velocityEngine;
+    private boolean lock[];
+
+
+
+    private MapfileGenerator() {
+        lock = new boolean[1];
+    }
+
+
+    /**
+     * A main method which can be used to create a mapfile without a running
+     * artifact server.<br>
+     * <b>NOTE:</b> This method is not implemented yet!
+     *
+     * @param args Arguments.
+     */
+    public static void main(String[] args) {
+        throw new Error("MapfileGenerator.main() is CURRENTLY NOT IMPLEMENTED!");
+    }
+
+
+    /**
+     * Returns the instance of this generator.
+     *
+     * @return the instance.
+     */
+    public static synchronized MapfileGenerator getInstance() {
+        if (instance == null) {
+            instance = new MapfileGenerator();
+            instance.setDaemon(true);
+            instance.start();
+        }
+
+        return instance;
+    }
+
+
+    /**
+     * Triggers the mapfile generation process.
+     */
+    public void update() {
+        synchronized (lock) {
+            logger.debug("update");
+            lock[0] = true;
+            lock.notify();
+        }
+    }
+
+
+    /**
+     * Thread to generate a mapfile.
+     */
+    @Override
+    public void run() {
+        logger.debug("Start MapfileGenerator thread...");
+        try {
+            for (;;) {
+                synchronized (lock) {
+                    while (!lock[0]) {
+                        lock.wait(SLEEPTIME);
+                    }
+                    lock[0] = false;
+                }
+
+                logger.debug("Start sync process now...");
+                generate();
+            }
+        }
+        catch (InterruptedException ie) {
+            logger.debug("MapfileGenerator thread got an interrupt.");
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.debug("Error while mapfile creation: " + fnfe.getMessage());
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+        finally {
+            logger.debug("THREAD END");
+        }
+    }
+
+    /**
+     * Method to check the existance of a template file.
+     *
+     * @param templateID The name of a template.
+     * @return true, of the template exists - otherwise false.
+     */
+    public boolean templateExists(String templateID){
+        Template template = getTemplateByName(templateID);
+        return template != null;
+    }
+
+
+    /**
+     * Method which starts searching for meta information file and mapfile
+     * generation.
+     */
+    protected void generate()
+    throws    FileNotFoundException, IOException
+    {
+        File[]        userDirs = getUserDirs();
+
+        List<String> layers = parseLayers(userDirs);
+
+        logger.info("Found " + layers.size() + " layers for user mapfile.");
+
+        writeMapfile(layers);
+    }
+
+
+    /**
+     * Returns the VelocityEngine used for the template mechanism.
+     *
+     * @return the velocity engine.
+     */
+    protected VelocityEngine getVelocityEngine() {
+        if (velocityEngine == null) {
+            velocityEngine = new VelocityEngine();
+            try {
+                setupVelocity(velocityEngine);
+            }
+            catch (Exception e) {
+                logger.error(e, e);
+                return null;
+            }
+        }
+        return velocityEngine;
+    }
+
+
+    /**
+     * Initialize velocity.
+     *
+     * @param engine Velocity engine.
+     * @throws Exception if an error occured while initializing velocity.
+     */
+    protected void setupVelocity(VelocityEngine engine)
+    throws Exception
+    {
+        engine.setProperty(
+            "input.encoding",
+            "UTF-8");
+
+        engine.setProperty(
+            VelocityEngine.RUNTIME_LOG,
+            FLYSUtils.getXPathString(FLYSUtils.XPATH_VELOCITY_LOGFILE));
+
+        engine.setProperty(
+            "resource.loader",
+            "file");
+
+        engine.setProperty(
+            "file.resource.loader.path",
+            FLYSUtils.getXPathString(FLYSUtils.XPATH_MAPSERVER_TEMPLATE_PATH));
+
+        engine.init();
+    }
+
+
+    protected VelocityContext getVelocityContext() {
+        VelocityContext context = new VelocityContext();
+
+        try {
+            context.put("MAPSERVERURL",
+                FLYSUtils.getXPathString(FLYSUtils.XPATH_MAPSERVER_URL));
+            context.put("SHAPEFILEPATH",
+                getShapefileBaseDir().getCanonicalPath());
+            context.put("CONFIGDIR",
+                Config.getConfigDirectory().getCanonicalPath());
+        }
+        catch (FileNotFoundException fnfe) {
+            // this is bad
+        }
+        catch (IOException ioe) {
+            // this is also bad
+        }
+
+        return context;
+    }
+
+
+    /**
+     * Returns a template specified by <i>model</i>.
+     *
+     * @param model The name of the template.
+     * @return a template.
+     */
+    protected Template getTemplateByName(String model) {
+        if (model.indexOf(".vm") < 0) {
+            model = model.concat(".vm");
+        }
+
+        try {
+            VelocityEngine engine = getVelocityEngine();
+            if (engine == null) {
+                logger.error("Error while fetching VelocityEngine.");
+                return null;
+            }
+
+            return engine.getTemplate(model);
+        }
+        catch (Exception e) {
+            logger.warn(e, e);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Returns the mapfile  template.
+     *
+     * @return the mapfile template.
+     * @throws Exception if an error occured while reading the configuration.
+     */
+    protected Template getMapfileTemplate()
+    throws Exception
+    {
+        String mapfileName = FLYSUtils.getXPathString(
+            FLYSUtils.XPATH_MAPFILE_TEMPLATE);
+
+        return getTemplateByName(mapfileName);
+    }
+
+
+    /**
+     * Returns the base directory storing the shapefiles.
+     *
+     * @return the shapefile base directory.
+     *
+     * @throws FileNotFoundException if no shapefile path is found or
+     * configured.
+     */
+    public File getShapefileBaseDir()
+    throws    FileNotFoundException, IOException
+    {
+        if (shapefileDirectory == null) {
+            String path = FLYSUtils.getXPathString(
+                FLYSUtils.XPATH_SHAPEFILE_DIR);
+
+            if (path != null) {
+                shapefileDirectory = new File(path);
+            }
+
+            if (shapefileDirectory == null) {
+                throw new FileNotFoundException("No shapefile directory given");
+            }
+
+            if (!shapefileDirectory.exists()) {
+                shapefileDirectory.createNewFile();
+            }
+        }
+
+        return shapefileDirectory;
+    }
+
+
+    protected File[] getUserDirs()
+    throws    FileNotFoundException, IOException
+    {
+        File   baseDir      = getShapefileBaseDir();
+        File[] artifactDirs = baseDir.listFiles();
+
+        // TODO ONLY RETURN DIRECTORIES OF THE SPECIFIED USER
+
+        return artifactDirs;
+    }
+
+
+
+    protected List<String> parseLayers(File[] dirs) {
+        List<String> layers = new ArrayList<String>();
+
+        for (File dir: dirs) {
+            File[] layerFiles = dir.listFiles(new FilenameFilter() {
+                @Override
+                public boolean accept(File directory, String name) {
+                    return name.startsWith(MS_LAYER_PREFIX);
+                }
+            });
+
+            for (File layer: layerFiles) {
+                try {
+                    layers.add(layer.getCanonicalPath());
+                }
+                catch (IOException ioe) {
+                    logger.warn(ioe, ioe);
+                }
+            }
+        }
+
+        return layers;
+    }
+
+
+    /**
+     * Creates a layer file used for Mapserver's mapfile which represents the
+     * floodmap.
+     *
+     * @param flys The FLYSArtifact that owns <i>wms</i>.
+     * @param wms The WMSLayerFacet that contains information for the layer.
+     */
+    public void createUeskLayer(
+        FLYSArtifact  flys,
+        WSPLGENLayerFacet wms,
+        String        style,
+        CallContext context
+    ) throws FileNotFoundException, IOException
+    {
+        logger.debug("createUeskLayer");
+
+        LayerInfo layerinfo = new LayerInfo();
+        layerinfo.setName(MS_WSPLGEN_PREFIX + flys.identifier());
+        layerinfo.setType("POLYGON");
+        layerinfo.setDirectory(flys.identifier());
+        layerinfo.setData(WSPLGEN_RESULT_SHAPE);
+        layerinfo.setTitle(Resources.getMsg(Resources.getLocale(context.getMeta()),
+                                            "floodmap.uesk",
+                                            "Floodmap"));
+        layerinfo.setStyle(style);
+        layerinfo.setSrid(wms.getSrid());
+
+        String name = MS_LAYER_PREFIX + wms.getName();
+
+        Template template = getTemplateByName(WSPLGEN_LAYER_TEMPLATE);
+        if (template == null) {
+            logger.warn("Template '" + WSPLGEN_LAYER_TEMPLATE + "' found.");
+            return;
+        }
+
+        try {
+            File dir = new File(getShapefileBaseDir(), flys.identifier());
+            writeLayer(layerinfo, dir, name, template);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+            logger.warn("Unable to write layer: " + name);
+        }
+    }
+
+
+    /**
+     * Creates a layer file used for Mapserver's mapfile which represents the
+     * user defined barriers.
+     *
+     * @param flys The FLYSArtifact that owns <i>wms</i>.
+     * @param wms The WMSLayerFacet that contains information for the layer.
+     */
+    public void createBarriersLayer(FLYSArtifact flys, WMSLayerFacet wms)
+    throws FileNotFoundException, IOException
+    {
+        logger.debug("createBarriersLayer");
+
+        String uuid = flys.identifier();
+        File   dir  = new File(getShapefileBaseDir(), uuid);
+
+        createBarriersLineLayer(flys, wms);
+        createBarriersPolygonLayer(flys, wms);
+    }
+
+
+    protected void createBarriersLineLayer(
+        FLYSArtifact  flys,
+        WMSLayerFacet wms
+    )
+    throws FileNotFoundException, IOException
+    {
+        String uuid       = flys.identifier();
+        String group      = MS_BARRIERS_PREFIX + uuid;
+        String groupTitle = "I18N_BARRIERS_TITLE";
+
+        File dir  = new File(getShapefileBaseDir(), uuid);
+        File test = new File(dir, WSPLGEN_LINES_SHAPE);
+
+        if (!test.exists() || !test.canRead()) {
+            logger.debug("No barrier line layer existing.");
+            return;
+        }
+
+        LayerInfo lineInfo = new LayerInfo();
+        lineInfo.setName(MS_LINE_PREFIX + uuid);
+        lineInfo.setType("LINE");
+        lineInfo.setDirectory(uuid);
+        lineInfo.setData(WSPLGEN_LINES_SHAPE);
+        lineInfo.setTitle("I18N_LINE_SHAPE");
+        lineInfo.setGroup(group);
+        lineInfo.setGroupTitle(groupTitle);
+        lineInfo.setSrid(wms.getSrid());
+
+        String nameLines = MS_LAYER_PREFIX + wms.getName() + "-lines";
+
+        Template tpl = getTemplateByName(SHP_LAYER_TEMPLATE);
+        if (tpl == null) {
+            logger.warn("Template '" + SHP_LAYER_TEMPLATE + "' found.");
+            return;
+        }
+
+        try {
+            writeLayer(lineInfo, dir, nameLines, tpl);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+            logger.warn("Unable to write layer: " + nameLines);
+        }
+    }
+
+
+    protected void createBarriersPolygonLayer(
+        FLYSArtifact  flys,
+        WMSLayerFacet wms
+    )
+    throws FileNotFoundException, IOException
+    {
+        String uuid       = flys.identifier();
+        String group      = uuid + MS_BARRIERS_PREFIX;
+        String groupTitle = "I18N_BARRIERS_TITLE";
+
+        File dir  = new File(getShapefileBaseDir(), uuid);
+        File test = new File(dir, WSPLGEN_POLYGONS_SHAPE);
+
+        if (!test.exists() || !test.canRead()) {
+            logger.debug("No barrier line layer existing.");
+            return;
+        }
+
+        LayerInfo polygonInfo = new LayerInfo();
+        polygonInfo.setName(MS_POLYGONS_PREFIX + uuid);
+        polygonInfo.setType("POLYGON");
+        polygonInfo.setDirectory(uuid);
+        polygonInfo.setData(WSPLGEN_POLYGONS_SHAPE);
+        polygonInfo.setTitle("I18N_POLYGON_SHAPE");
+        polygonInfo.setGroup(group);
+        polygonInfo.setGroupTitle(groupTitle);
+        polygonInfo.setSrid(wms.getSrid());
+
+        String namePolygons = MS_LAYER_PREFIX + wms.getName() + "-polygons";
+
+        Template tpl = getTemplateByName(SHP_LAYER_TEMPLATE);
+        if (tpl == null) {
+            logger.warn("Template '" + SHP_LAYER_TEMPLATE + "' found.");
+            return;
+        }
+
+        try {
+            writeLayer(polygonInfo, dir, namePolygons, tpl);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+            logger.warn("Unable to write layer: " + namePolygons);
+        }
+    }
+
+
+    /**
+     * Creates a layer file used for Mapserver's mapfile which represents the
+     * shape files uploaded by the user.
+     *
+     * @param flys The FLYSArtifact that owns <i>wms</i>.
+     * @param wms The WMSLayerFacet that contains information for the layer.
+     */
+    public void createUserShapeLayer(FLYSArtifact flys, WMSLayerFacet wms)
+    throws FileNotFoundException, IOException
+    {
+        logger.debug("createUserShapeLayer");
+
+        String uuid = flys.identifier();
+        File   dir  = new File(getShapefileBaseDir(), uuid);
+        File   test = new File(dir, WSPLGEN_USER_SHAPE);
+
+        if (!test.exists() || !test.canRead()) {
+            logger.debug("No user layer existing.");
+            return;
+        }
+
+        File userShape = new File(dir, WSPLGEN_USER_SHAPE);
+        ShpFiles sf = new ShpFiles(userShape);
+        ShapefileReader sfr = new ShapefileReader(sf, true, false, null);
+        ShapefileHeader sfh = sfr.getHeader();
+
+        String group      = uuid + MS_USERSHAPE_PREFIX;
+        String groupTitle = "I18N_USER_SHAPE_TITLE";
+
+        LayerInfo info = new LayerInfo();
+        info.setName(MS_USERSHAPE_PREFIX + uuid);
+        if (sfh.getShapeType().isLineType()) {
+            info.setType("LINE");
+        }
+        else if (sfh.getShapeType().isPolygonType()) {
+            info.setType("POLYGON");
+        }
+        else {
+            return;
+        }
+        info.setDirectory(uuid);
+        info.setData(WSPLGEN_USER_SHAPE);
+        info.setTitle("I18N_USER_SHAPE");
+        info.setGroup(group);
+        info.setGroupTitle(groupTitle);
+        info.setSrid(wms.getSrid());
+
+        String nameUser = MS_LAYER_PREFIX + wms.getName();
+
+        Template tpl = getTemplateByName(SHP_LAYER_TEMPLATE);
+        if (tpl == null) {
+            logger.warn("Template '" + SHP_LAYER_TEMPLATE + "' found.");
+            return;
+        }
+
+        try {
+            writeLayer(info, dir, nameUser, tpl);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+            logger.warn("Unable to write layer: " + nameUser);
+        }
+
+    }
+
+
+    /**
+     * Creates a layer file used for Mapserver's mapfile which represents
+     * geometries from database.
+     *
+     * @param flys The FLYSArtifact that owns <i>wms</i>.
+     * @param wms The WMSLayerFacet that contains information for the layer.
+     */
+    public void createDatabaseLayer(
+        FLYSArtifact    flys,
+        WMSDBLayerFacet wms,
+        String          style
+    )
+    throws FileNotFoundException, IOException
+    {
+        logger.debug("createDatabaseLayer");
+
+        LayerInfo layerinfo = new LayerInfo();
+        layerinfo.setName(wms.getName() + "-" + flys.identifier());
+        layerinfo.setType(wms.getGeometryType());
+        layerinfo.setFilter(wms.getFilter());
+        layerinfo.setData(wms.getData());
+        layerinfo.setTitle(wms.getDescription());
+        layerinfo.setStyle(style);
+        layerinfo.setExtent(GeometryUtils.jtsBoundsToOLBounds(wms.getExtent()));
+        layerinfo.setConnection(wms.getConnection());
+        layerinfo.setConnectionType(wms.getConnectionType());
+        layerinfo.setLabelItem(wms.getLabelItem());
+        layerinfo.setSrid(wms.getSrid());
+
+        String name = MS_LAYER_PREFIX + wms.getName();
+
+        Template template = getTemplateByName(DB_LAYER_TEMPLATE);
+        if (template == null) {
+            logger.warn("Template '" + DB_LAYER_TEMPLATE + "' found.");
+            return;
+        }
+
+        try {
+            File dir = new File(getShapefileBaseDir(), flys.identifier());
+            writeLayer(layerinfo, dir, name, template);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+            logger.warn("Unable to write layer: " + name);
+        }
+    }
+
+
+    /**
+     * Creates a layer snippet which might be included in the mapfile.
+     *
+     * @param layerinfo A LayerInfo object that contains all necessary
+     * information to build a Mapserver LAYER section.
+     * @param dir The base dir for the LAYER snippet.
+     * @param filename The name of the file that is written.
+     * @param tpl The Velocity template which is used to create the LAYER
+     * section.
+     */
+    protected void writeLayer(
+        LayerInfo layerinfo,
+        File      dir,
+        String    filename,
+        Template  tpl
+    )
+    throws    FileNotFoundException
+    {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Write layer for:");
+            logger.debug("   directory: " + dir.getName());
+            logger.debug("   name:      " + filename);
+        }
+
+        File   layer  = new File(dir, filename);
+        Writer writer = null;
+
+        try {
+            writer = new FileWriter(layer);
+
+            VelocityContext context = getVelocityContext();
+            context.put("LAYER", layerinfo);
+
+            tpl.merge(context, writer);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+        catch (Exception e) {
+            logger.error(e, e);
+        }
+        finally {
+            try {
+                if (writer != null) {
+                    writer.close();
+                }
+            }
+            catch (IOException ioe) {
+                logger.debug(ioe, ioe);
+            }
+        }
+    }
+
+
+    /**
+     * Creates a mapfile with the layer information stored in <i>layers</i>.
+     *
+     * @param layers Layer information.
+     */
+    protected void writeMapfile(List<String> layers) {
+        String tmpMapName = "mapfile" + new Date().getTime();
+
+        File mapfile = new File(
+            FLYSUtils.getXPathString(FLYSUtils.XPATH_MAPFILE_PATH));
+
+        File   tmp     = null;
+        Writer writer  = null;
+
+        try {
+            tmp = new File(mapfile.getParent(), tmpMapName);
+            tmp.createNewFile();
+
+            writer = new FileWriter(tmp);
+
+            VelocityContext context = getVelocityContext();
+            context.put("LAYERS", layers);
+
+            Template mapTemplate = getMapfileTemplate();
+            if (mapTemplate == null) {
+                logger.warn("No mapfile template found.");
+                return;
+            }
+
+            mapTemplate.merge(context, writer);
+
+            // we need to create a temporary mapfile first und rename it into
+            // real mapfile because we don't run into race conditions on this
+            // way. (iw)
+            tmp.renameTo(mapfile);
+        }
+        catch (FileNotFoundException fnfe) {
+            logger.error(fnfe, fnfe);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe, ioe);
+        }
+        catch (Exception e) {
+            logger.error(e, e);
+        }
+        finally {
+            try {
+                if (writer != null) {
+                    writer.close();
+                }
+
+                if (tmp.exists()) {
+                    tmp.delete();
+                }
+            }
+            catch (IOException ioe) {
+                logger.debug(ioe, ioe);
+            }
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/Pair.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+
+package de.intevation.flys.utils;
+
+import java.io.Serializable;
+
+/**
+ * @param <A>
+ * @param <B>
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public final class Pair<A, B>
+implements         Serializable
+{
+    private A a;
+    private B b;
+
+    private Pair() {
+    }
+
+    public Pair(A a, B b) {
+        this.a = a;
+        this.b = b;
+    }
+
+    public A getA() {
+        return a;
+    }
+
+    public B getB() {
+        return b;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/utils/ThemeUtil.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,640 @@
+package de.intevation.flys.utils;
+
+import org.apache.log4j.Logger;
+
+import java.awt.Color;
+import java.awt.Font;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.MapserverStyle;
+import de.intevation.flys.artifacts.model.MapserverStyle.Clazz;
+import de.intevation.flys.artifacts.model.MapserverStyle.Expression;
+import de.intevation.flys.artifacts.model.MapserverStyle.Style;
+import de.intevation.flys.artifacts.model.MapserverStyle.Label;
+
+
+/**
+ * Utility to deal with themes and their representations.
+ */
+public class ThemeUtil {
+
+    private static Logger logger =
+        Logger.getLogger(ThemeUtil.class);
+
+    public final static String XPATH_FILL_COLOR =
+        "/theme/field[@name='fillcolor']/@default";
+
+    public final static String XPATH_LINE_COLOR =
+        "/theme/field[@name='linecolor']/@default";
+
+    public static final String XPATH_LINE_SIZE =
+        "/theme/field[@name='linesize']/@default";
+
+    public static final String XPATH_LINE_STYLE =
+        "/theme/field[@name='linetype']/@default";
+
+    public static final String XPATH_POINT_SIZE =
+        "/theme/field[@name='pointsize']/@default";
+
+    public final static String XPATH_SHOW_BORDER =
+        "/theme/field[@name='showborder']/@default";
+
+    public final static String XPATH_SHOW_POINTS =
+        "/theme/field[@name='showpoints']/@default";
+
+    public final static String XPATH_SHOW_LINE =
+        "/theme/field[@name='showlines']/@default";
+
+    public final static String XPATH_SHOW_LINE_LABEL =
+        "/theme/field[@name='showlinelabel']/@default";
+
+    public final static String XPATH_LINE_LABEL_FONT =
+        "/theme/field[@name='linelabelfont']/@default";
+
+    public final static String XPATH_LINE_LABEL_COLOR =
+        "/theme/field[@name='linelabelcolor']/@default";
+
+    public final static String XPATH_LINE_LABEL_SIZE =
+        "/theme/field[@name='linelabelsize']/@default";
+
+    public final static String XPATH_LINE_LABEL_STYLE =
+        "/theme/field[@name='linelabelstyle']/@default";
+
+    public final static String XPATH_LINE_LABEL_BGCOLOR =
+        "/theme/field[@name='linelabelbgcolor']/@default";
+
+    public final static String XPATH_LINE_LABEL_SHOWBG =
+        "/theme/field[@name='linelabelshowbg']/@default";
+
+    public final static String XPATH_SHOW_WIDTH =
+        "/theme/field[@name='showwidth']/@default";
+
+    public final static String XPATH_SHOW_LEVEL =
+        "/theme/field[@name='showwidth']/@default";
+
+    public final static String XPATH_TRANSPARENCY =
+        "/theme/field[@name='transparent']/@default";
+
+    public final static String XPATH_SHOW_AREA =
+        "/theme/field[@name='showarea']/@default";
+
+    public final static String XPATH_SHOW_MIDDLE_HEIGHT =
+        "/theme/field[@name='showmiddleheight']/@default";
+
+    public final static String XPATH_TEXT_COLOR =
+        "/theme/field[@name='textcolor']/@default";
+
+    public final static String XPATH_TEXT_SIZE =
+        "/theme/field[@name='textsize']/@default";
+
+    public final static String XPATH_TEXT_FONT =
+        "/theme/field[@name='font']/@default";
+
+    public final static String XPATH_TEXT_STYLE =
+        "/theme/field[@name='textstyle']/@default";
+
+    public final static String XPATH_TEXT_ORIENTATION =
+        "/theme/field[@name='textorientation']/@default";
+
+    public final static String XPATH_TEXT_BACKGROUND =
+        "/theme/field[@name='backgroundcolor']/@default";
+
+    public final static String XPATH_SHOW_BACKGROUND =
+        "/theme/field[@name='showbackground']/@default";
+
+    public final static String XPATH_SYMBOL =
+        "/theme/field[@name='symbol']/@default";
+
+    public final static String XPATH_SHOW_MINIMUM =
+        "/theme/field[@name='showminimum']/@default";
+
+    public final static String XPATH_SHOW_MAXIMUM =
+        "/theme/field[@name='showmaximum']/@default";
+
+    public final static String XPATH_WSPLGEN_FIELDS =
+        "/theme[@name='WSPLGEN']/field";
+
+
+    /** Parse string to be boolean with default if empty or unrecognized. */
+    public static boolean parseBoolean(String value, boolean defaultsTo) {
+        if (value == null || value.length() == 0) {
+            return defaultsTo;
+        }
+        if (value.equals("false")) {
+            return false;
+        }
+        else if (value.equals("true")) {
+            return true;
+        }
+        else {
+            return defaultsTo;
+        }
+    }
+
+
+    public static int parseInteger(String value, int defaultsTo) {
+        if (value == null || value.length() == 0) {
+            return defaultsTo;
+        }
+
+        try {
+            return Integer.valueOf(value);
+        }
+        catch (NumberFormatException nfe) {
+            // do nothing
+        }
+
+        return defaultsTo;
+    }
+
+
+    /**
+     * Parses line width, defaulting to 0.
+     * @param theme the theme
+     */
+    public static int parseLineWidth(Document theme) {
+        String size = XMLUtils.xpathString(theme, XPATH_LINE_SIZE, null);
+        if (size == null || size.length() == 0) {
+            return 0;
+        }
+
+        try {
+            return Integer.valueOf(size);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Unable to set line size from string: '" + size + "'");
+        }
+        return 0;
+    }
+
+
+    public static int parsePointWidth(Document theme) {
+        String width = XMLUtils.xpathString(theme, XPATH_POINT_SIZE, null);
+
+        return parseInteger(width, 3);
+    }
+
+
+    /**
+     * Parses the line style, defaulting to '10'.
+     * @param theme The theme.
+     */
+    public static float[] parseLineStyle(Document theme) {
+        String dash = XMLUtils.xpathString(theme, XPATH_LINE_STYLE, null);
+
+        float[] def = {10};
+        if (dash == null || dash.length() == 0) {
+            return def;
+        }
+
+        String[] pattern = dash.split(",");
+        if(pattern.length == 1) {
+            return def;
+        }
+
+        try {
+            float[] dashes = new float[pattern.length];
+            for (int i = 0; i < pattern.length; i++) {
+                dashes[i] = Float.parseFloat(pattern[i]);
+            }
+            return dashes;
+        }
+        catch(NumberFormatException nfe) {
+            logger.warn("Unable to set dash from string: '" + dash + "'");
+            return def;
+        }
+    }
+
+
+    /**
+     * Parses text size, defaulting to 10.
+     * @param theme The theme.
+     */
+    public static int parseTextSize(Document theme, String path) {
+        String size = XMLUtils.xpathString(theme, path, null);
+        if (size == null || size.length() == 0) {
+            return 10;
+        }
+
+        try {
+            return Integer.valueOf(size);
+        }
+        catch (NumberFormatException nfe) {
+        }
+        return 10;
+    }
+
+
+    public static int parseTextSize(Document theme) {
+        return parseTextSize(theme, XPATH_TEXT_SIZE);
+    }
+
+
+    public static int parseLineLabelSize(Document theme) {
+        return parseTextSize(theme, XPATH_LINE_LABEL_SIZE);
+    }
+
+
+    /**
+     * Parses the attribute 'showpoints', defaults to false.
+     * @param theme The theme.
+     */
+    public static boolean parseShowPoints(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_POINTS, null);
+        return parseBoolean(show, false);
+    }
+
+    /**
+     * Parses the attribute 'showmiddleheight', defaults to false.
+     * @param theme The theme.
+     */
+    public static boolean parseShowMiddleHeight(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_MIDDLE_HEIGHT, null);
+        return parseBoolean(show, false);
+    }
+
+    /**
+     * Parses the attribute 'showarea', defaults to false.
+     * @param theme The theme.
+     */
+    public static boolean parseShowArea(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_AREA, null);
+        return parseBoolean(show, false);
+    }
+
+    /**
+     * Parses the attribute 'showlines', defaults to true.
+     * @param theme The theme.
+     */
+    public static boolean parseShowLine(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_LINE, null);
+        return parseBoolean(show, true);
+    }
+
+    /**
+     * Parses the attribute 'showlinelabel', defaults to true.
+     * @param theme The theme.
+     */
+    public static boolean parseShowLineLabel(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_LINE_LABEL, null);
+        return parseBoolean(show, true);
+    }
+
+    /**
+     * Parses text color.
+     * @param theme The theme.
+     */
+    public static Color parseTextColor(Document theme) {
+        return parseRGB(getTextColorString(theme));
+    }
+
+    
+    /** Parse color for line label(s text). */
+    public static Color parseLineLabelTextColor(Document theme) {
+        return parseRGB(getLineLabelTextColorString(theme));
+    }
+
+    /** Parse bg color for line label(s text). */
+    public static Color parseLineLabelBGColor(Document theme) {
+        return parseRGB(getLineLabelBGColorString(theme));
+    }
+
+    // TODO showbg and bg color for linelabels
+
+
+    /**
+     * Parses the font.
+     * @param theme The theme.
+     */
+    public static Font parseTextFont(Document theme) {
+        String font = XMLUtils.xpathString(theme, XPATH_TEXT_FONT, null);
+        if (font == null || font.length() == 0) {
+            return null;
+        }
+
+        int size = parseTextSize(theme);
+        int style = parseTextStyle(theme);
+        Font f = new Font (font, style, size);
+        return f;
+    }
+
+    /** Parse font (name, size and style) for linelabels. */
+    public static Font parseLineLabelFont(Document theme) {
+        String font = XMLUtils.xpathString(theme, XPATH_LINE_LABEL_FONT, null);
+        if (font == null || font.length() == 0) {
+            return null;
+        }
+
+        int size = parseLineLabelSize(theme);
+        int style = parseLineLabelStyle(theme);
+
+        Font f = new Font (font, style, size);
+        return f;
+    }
+
+
+    /**
+     * Parses the text style, defaults to 'Font.PLAIN'.
+     * @param theme The theme.
+     */
+    public static int parseTextStyle(Document theme, String path) {
+        String style = XMLUtils.xpathString(theme, path, null);
+        if (style == null || style.length() == 0) {
+            return Font.PLAIN;
+        }
+
+        if (style.equals("italic")) {
+            return Font.ITALIC;
+        }
+        else if (style.equals("bold")) {
+            return Font.BOLD;
+        }
+        else {
+            return Font.PLAIN;
+        }
+    }
+
+
+    public static int parseTextStyle(Document theme) {
+        return parseTextStyle(theme, XPATH_TEXT_STYLE);
+    }
+
+
+    public static int parseLineLabelStyle(Document theme) {
+        return parseTextStyle(theme, XPATH_LINE_LABEL_STYLE);
+    }
+
+
+    public static boolean parseShowWidth(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_WIDTH, null);
+        return parseBoolean(show, false);
+    }
+
+    public static boolean parseShowLevel(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_LEVEL, null);
+        return parseBoolean(show, false);
+    }
+
+    /**
+     * Parses the textorientation, defaults to 'vertical'.
+     * @param theme The theme.
+     */
+    public static String parseTextOrientation(Document theme) {
+        String o = XMLUtils.xpathString(theme, XPATH_TEXT_ORIENTATION, null);
+        if (o == null || o.length() == 0) {
+            return "vertical";
+        }
+        if(o.equals("true")) {
+            return "horizontal";
+        }
+        else {
+            return "vertical";
+        }
+    }
+
+
+    /**
+     * Parses the text background color, defaults to white.
+     * @param theme The theme.
+     */
+    public static Color parseTextBackground(Document theme) {
+        String color = getBackgroundColorString(theme);
+        if (color == null || color.length() == 0) {
+            return Color.WHITE;
+        }
+        return parseRGB(color);
+    }
+
+
+    /**
+     * Parses the attribute whether to show background or not, defaults to
+     * false.
+     * @param theme The theme.
+     */
+    public static boolean parseShowTextBackground(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_SHOW_BACKGROUND, null);
+        return parseBoolean(show, false);
+    }
+
+
+    /**
+     * Parses the attribute whether to show line labels background or not,
+     * defaults to false.
+     * @param theme The theme.
+     */
+    public static boolean parseShowLineLabelBG(Document theme) {
+        String show = XMLUtils.xpathString(theme, XPATH_LINE_LABEL_SHOWBG, null);
+        return parseBoolean(show, false);
+    }
+
+    /**
+     * Parse a string like "103, 100, 0" and return a corresping color.
+     * @param rgbtext Color as string representation, e.g. "255,0,20".
+     * @return Color, null in case of issues.
+     */
+    public static Color parseRGB(String rgbtext) {
+        if (rgbtext == null) {
+            return null;
+        }
+        String rgb[] = rgbtext.split(",");
+        Color c = null;
+        try {
+            c = new Color(
+                    Integer.valueOf(rgb[0].trim()),
+                    Integer.valueOf(rgb[1].trim()),
+                    Integer.valueOf(rgb[2].trim()));
+        }
+        catch (NumberFormatException nfe) {
+            c = null;
+        }
+        return c;
+    }
+
+
+    public static String getLineColorString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_LINE_COLOR, null);
+    }
+
+    /** Get show border as string. */
+    public static String getShowBorderString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_SHOW_BORDER, null);
+    }
+
+    /** Get fill color as string. */
+    public static String getFillColorString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_FILL_COLOR, null);
+    }
+
+    public static String getBackgroundColorString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_TEXT_BACKGROUND, null);
+    }
+
+
+    public static String getTextColorString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_TEXT_COLOR, null);
+    }
+
+    public static String getLineLabelTextColorString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_LINE_LABEL_COLOR, null);
+    }
+
+    public static String getLineLabelBGColorString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_LINE_LABEL_BGCOLOR, null);
+    }
+
+    public static String getSymbol(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_SYMBOL, null);
+    }
+
+    public static String getTransparencyString(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_TRANSPARENCY, null);
+    }
+
+
+    public static String getShowMinimum(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_SHOW_MINIMUM, null);
+    }
+
+
+    public static String getShowMaximum(Document theme) {
+        return XMLUtils.xpathString(theme, XPATH_SHOW_MAXIMUM, null);
+    }
+
+
+    /**
+     * Gets color from color field.
+     * @param theme    the theme document.
+     * @return color.
+     */
+    public static Color parseFillColorField(Document theme) {
+        return parseRGB(getFillColorString(theme));
+    }
+
+    public static boolean parseShowBorder(Document theme) {
+        return parseBoolean(getShowBorderString(theme), false);
+    }
+
+    public static boolean parseTransparency(Document theme) {
+        return parseBoolean(getTransparencyString(theme), false);
+    }
+
+    /**
+     * Gets color from color field.
+     * @param theme    the theme document.
+     * @return color.
+     */
+    public static Color parseLineColorField(Document theme) {
+        return parseRGB(getLineColorString(theme));
+    }
+
+
+    public static boolean parseShowMinimum(Document theme) {
+        return parseBoolean(getShowMinimum(theme), false);
+    }
+
+    public static boolean parseShowMaximum(Document theme) {
+        return parseBoolean(getShowMaximum(theme), false);
+    }
+
+
+    public static String createWSPLGENStyle(Document theme) {
+        NodeList categories = (NodeList) XMLUtils.xpath(
+            theme,
+            XPATH_WSPLGEN_FIELDS,
+            XPathConstants.NODESET);
+
+        return createWSPLGENStyle(categories).toString();
+    }
+
+    protected static MapserverStyle createWSPLGENStyle(NodeList categories) {
+        MapserverStyle ms = new MapserverStyle();
+
+        for (int i = 0, n = categories.getLength(); i < n; i++) {
+            Element  cat = (Element) categories.item(i);
+            String color = cat.getAttribute("default");
+            String name  = cat.getAttribute("name");
+
+            String expr;
+
+            try {
+                int length = name.length();
+                int idx    = Integer.valueOf(name.substring(length-1, length));
+
+                expr = createWSPLGENExpression(idx);
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Error while parsing WSPLGEN category.", nfe);
+                continue;
+            }
+
+            Clazz      c = new Clazz(expr);
+            Style      s = new Style();
+            s.setColor(color.replace(",", ""));
+            s.setSize(5);
+
+            c.addItem(new Expression("(" + expr + ")"));
+            c.addItem(s);
+
+            ms.addClazz(c);
+        }
+
+        return ms;
+    }
+
+
+    protected static String createWSPLGENExpression(int idx) {
+        if (idx < 5) {
+            int lower = idx - 1;
+            return "[DIFF] >= " + lower + " AND  [DIFF] < " + idx;
+        }
+        else {
+            return "[DIFF] >= 4";
+        }
+    }
+
+
+    public static String createMapserverStyle(Document theme) {
+        String symbol    = getSymbol(theme);
+        String backcolor = getBackgroundColorString(theme);
+        String linecolor = getLineColorString(theme);
+
+        int linewidth = parseLineWidth(theme);
+
+        MapserverStyle ms = new MapserverStyle();
+
+        Clazz c = new Clazz(" ");
+
+        Style s = new Style();
+        s.setOutlineColor(linecolor.replace(",", ""));
+
+        if (backcolor != null && backcolor.length() > 0) {
+            s.setColor(backcolor.replace(",", ""));
+        }
+
+        s.setSize(linewidth);
+        s.setSymbol(symbol);
+        c.addItem(s);
+
+        String textcolor = getTextColorString(theme);
+        int    textsize  = parseTextSize(theme);
+
+        if (textcolor != null && textcolor.length() > 0 && textsize > 0) {
+            Label l = new Label();
+            l.setColor(textcolor.replace(",", ""));
+            l.setSize(textsize);
+            c.addItem(l);
+        }
+
+        ms.addClazz(c);
+
+        return ms.toString();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/FacetCreator.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,188 @@
+package de.intevation.flys.wsplgen;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jts.geom.Envelope;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.artifactdatabase.state.Facet;
+
+import de.intevation.flys.model.CrossSectionTrack;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+import de.intevation.flys.artifacts.model.FacetTypes;
+import de.intevation.flys.artifacts.model.WMSLayerFacet;
+import de.intevation.flys.artifacts.model.WSPLGENLayerFacet;
+import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
+import de.intevation.flys.utils.FLYSUtils;
+import de.intevation.flys.utils.GeometryUtils;
+import de.intevation.flys.utils.MapfileGenerator;
+
+
+public class FacetCreator implements FacetTypes {
+
+    public static final String I18N_WSPLGEN_RESULT    = "floodmap.uesk";
+    public static final String I18N_WSPLGEN_DEFAULT   = "floodmap.uesk";
+    public static final String I18N_BARRIERS          = "floodmap.barriers";
+    public static final String I18N_BARRIERS_DEFAULT  = "floodmap.barriers";
+    public static final String I18N_USERSHAPE         = "floodmap.usershape";
+    public static final String I18N_USERSHAPE_DEFAULT = "floodmap.usershape";
+
+    protected FLYSArtifact artifact;
+
+    protected CallContext  cc;
+
+    protected List<Facet> facets;
+    protected List<Facet> tmpFacets;
+
+    protected String url;
+    protected String hash;
+    protected String stateId;
+
+
+    private static Logger logger = Logger.getLogger(FacetCreator.class);
+
+
+    public FacetCreator(
+        FLYSArtifact artifact,
+        CallContext  cc,
+        String       hash,
+        String       sId,
+        List<Facet>  facets
+    ) {
+        this.tmpFacets  = new ArrayList<Facet>(2);
+        this.facets     = facets;
+        this.artifact   = artifact;
+        this.cc         = cc;
+        this.hash       = hash;
+        this.stateId    = sId;
+    }
+
+    protected String getRiver() {
+        return artifact.getDataAsString("river");
+    }
+
+    protected String getUrl() {
+        return FLYSUtils.getUserWMSUrl(artifact.identifier());
+    }
+
+    protected String getSrid() {
+        return FLYSUtils.getRiverSrid(artifact);
+    }
+
+    protected Envelope getWSPLGENBounds() {
+        String river = getRiver();
+        double kms[] = FLYSUtils.getKmRange(artifact);
+
+        logger.debug("### getWSPLGENBounds");
+        logger.debug("###    from km: " + kms[0]);
+        logger.debug("###    to   km: " + kms[1]);
+
+        CrossSectionTrack a =
+            CrossSectionTrack.getCrossSectionTrack(river, kms[0]);
+
+        CrossSectionTrack b =
+            CrossSectionTrack.getCrossSectionTrack(river, kms[1]);
+
+        if (a == null || b == null) {
+            return null;
+        }
+
+        Envelope envA = a.getGeom().getEnvelopeInternal();
+        Envelope envB = b.getGeom().getEnvelopeInternal();
+
+        envA.expandToInclude(envB);
+
+        logger.debug("###    => " + envA);
+
+        return envA;
+    }
+
+    protected Envelope getBounds() {
+        return GeometryUtils.getRiverBoundary(getRiver());
+    }
+
+    public List<Facet> getFacets() {
+        return tmpFacets;
+    }
+
+    public void createWSPLGENFacet() {
+        WSPLGENLayerFacet wsplgen = new WSPLGENLayerFacet(
+            0,
+            FLOODMAP_WSPLGEN,
+            Resources.getMsg(
+                cc.getMeta(),
+                I18N_WSPLGEN_RESULT,
+                I18N_WSPLGEN_DEFAULT),
+            ComputeType.ADVANCE,
+            stateId,
+            hash,
+            getUrl());
+
+        Envelope bounds = getWSPLGENBounds();
+
+        if (bounds == null) {
+            bounds = getBounds();
+        }
+
+        wsplgen.addLayer(
+            MapfileGenerator.MS_WSPLGEN_PREFIX + artifact.identifier());
+        wsplgen.setSrid(getSrid());
+        wsplgen.setExtent(bounds);
+
+        tmpFacets.add(wsplgen);
+    }
+
+    public void createBarrierFacet() {
+        WMSLayerFacet barriers = new WMSLayerFacet(
+            1,
+            FLOODMAP_BARRIERS,
+            Resources.getMsg(
+                cc.getMeta(),
+                I18N_BARRIERS,
+                I18N_BARRIERS_DEFAULT),
+            ComputeType.ADVANCE,
+            stateId,
+            hash,
+            getUrl());
+
+        barriers.addLayer(
+            MapfileGenerator.MS_BARRIERS_PREFIX + artifact.identifier());
+        barriers.setSrid(getSrid());
+        barriers.setExtent(getBounds());
+
+        tmpFacets.add(barriers);
+    }
+
+
+    public void createUserShapeFacet() {
+        WMSLayerFacet shape = new WMSLayerFacet(
+            1,
+            FLOODMAP_USERSHAPE,
+            Resources.getMsg(
+                cc.getMeta(),
+                I18N_USERSHAPE,
+                I18N_USERSHAPE_DEFAULT),
+            ComputeType.ADVANCE,
+            stateId,
+            hash,
+            getUrl());
+
+        shape.addLayer(
+            MapfileGenerator.MS_USERSHAPE_PREFIX + artifact.identifier());
+        shape.setSrid(getSrid());
+        shape.setExtent(getBounds());
+
+        tmpFacets.add(shape);
+    }
+
+
+    public void finish() {
+        facets.addAll(getFacets());
+    }
+} // end of FacetCreator
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/JobObserver.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,106 @@
+package de.intevation.flys.wsplgen;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.model.CalculationMessage;
+import de.intevation.flys.artifacts.model.WSPLGENJob;
+
+
+public class JobObserver extends Thread {
+
+    private static Logger logger = Logger.getLogger(JobObserver.class);
+
+
+    public static final String WSPLGEN_ENCODING =
+        "ISO-8859-1";
+
+    public static final String WSPLGEN_LOG_OUTPUT =
+        System.getProperty("wsplgen.log.output", "false");
+
+    public static final String[] STEPS = {
+        ".*<-Auswertung der Kommandozeilen-Parameter beendet.*",
+        ".*->Laden des DGM in Datei '.*' gestartet.*",
+        ".*->Triangulierung der Knoten gestartet.*",
+        ".*->Anpassung der Elemente an Dämme und Gräben gestartet.*",
+        ".*<-WSPLGEN Version .* beendet.*"
+    };
+
+
+    protected WSPLGENJob job;
+
+    protected InputStream in;
+
+    protected Pattern[] patterns;
+
+    protected int len;
+
+    protected boolean copy;
+
+
+    public JobObserver(WSPLGENJob job) {
+        this.job  = job;
+        this.len  = 0;
+        this.copy = Boolean.parseBoolean(WSPLGEN_LOG_OUTPUT);
+
+        patterns = new Pattern[STEPS.length];
+    }
+
+
+    protected void prepareRegexes() {
+        for (int num = STEPS.length, i = 0; i < num; i++) {
+            patterns[i] = Pattern.compile(STEPS[i], Pattern.DOTALL);
+        }
+    }
+
+
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+
+    public void run() {
+        logger.debug("Start observation...");
+        prepareRegexes();
+
+        try {
+            BufferedReader reader =
+                new BufferedReader(
+                    new InputStreamReader(in, WSPLGEN_ENCODING));
+
+            String line = null;
+
+            while ((line = reader.readLine()) != null) {
+                if (copy) {
+                    logger.debug(line);
+                }
+
+                update(line);
+            }
+        }
+        catch (IOException ioe) {
+            logger.warn("Observation canceled: " + ioe.getMessage());
+        }
+    }
+
+
+    protected void update(String log) {
+        for (int num = patterns.length, i = 0; i < num; i++) {
+            Matcher m = patterns[i].matcher(log);
+
+            if (m.matches()) {
+                job.getCallContext().addBackgroundMessage(
+                    new CalculationMessage(num, i+1, log));
+
+                logger.info("Finished step " + (i+1) + " / " + num);
+            }
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=5 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/ProblemObserver.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,115 @@
+package de.intevation.flys.wsplgen;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.artifacts.model.WSPLGENCalculation;
+import de.intevation.flys.artifacts.model.WSPLGENJob;
+
+
+public class ProblemObserver extends JobObserver {
+
+    private static Logger logger = Logger.getLogger(ProblemObserver.class);
+
+
+    public static final Pattern WSPLGEN_ERROR_START = Pattern.compile(
+        ".*->(.*Fehler)(\\s*\\((\\d+)\\).*)*",
+        Pattern.DOTALL);
+
+    public static final Pattern WSPLGEN_ERROR_END = Pattern.compile(
+        ".*<-(.*Fehler).*",
+        Pattern.DOTALL);
+
+    public static final Pattern WSPLGEN_WARNING_START = Pattern.compile(
+        ".*->Warnung\\s*\\((\\d+)\\).*",
+        Pattern.DOTALL);
+
+    public static final Pattern WSPLGEN_WARNING_END = Pattern.compile(
+        ".*<-Warnung .*",
+        Pattern.DOTALL);
+
+
+    protected int error;
+    protected int warning;
+
+    protected WSPLGENCalculation calculation;
+
+
+    public ProblemObserver(WSPLGENJob job) {
+        super(job);
+        error       = -1;
+        warning     = -1;
+        calculation = job.getCalculation();
+    }
+
+
+    public void run() {
+        logger.debug("Start observation...");
+
+        super.run();
+    }
+
+
+    @Override
+    protected void prepareRegexes() {
+        // do nothing
+    }
+
+
+    @Override
+    protected void update(String log) {
+        Matcher startError = WSPLGEN_ERROR_START.matcher(log);
+        if (startError.matches()) {
+            if (startError.groupCount() >= 2) {
+                String tmp = startError.group(3);
+
+                if (tmp != null && tmp.length() > 0) {
+                    error = Integer.parseInt(tmp);
+                }
+                else error = 1;
+            }
+            else {
+                error = 1;
+            }
+
+            return;
+        }
+
+        Matcher endError = WSPLGEN_ERROR_END.matcher(log);
+        if (endError.matches()) {
+            error = -1;
+        }
+
+        if (error > 0) {
+            calculation.addError(new Integer(error), log);
+        }
+
+        Matcher startWarning = WSPLGEN_WARNING_START.matcher(log);
+        if (startWarning.matches()) {
+            warning = Integer.parseInt(startWarning.group(1));
+            return;
+        }
+
+        Matcher endWarning = WSPLGEN_WARNING_END.matcher(log);
+        if (endWarning.matches()) {
+            warning = -1;
+        }
+
+        if (warning > 0) {
+            calculation.addWarning(new Integer(warning), log);
+        }
+    }
+
+
+    public int numErrors() {
+        return calculation.numErrors();
+    }
+
+
+    public int numWarnings() {
+        return calculation.numWarnings();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=5 fenc=utf-8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/Scheduler.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,112 @@
+package de.intevation.flys.wsplgen;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.model.WSPLGENJob;
+
+
+/**
+ * The Scheduler is used to retrieve new WSPLGENJob. The incoming jobs are added
+ * to a ScheduledThreadPoolExecutor. This thread pool has a number of worker
+ * threads that processes the WSPLGENJobs. The number of worker threads can be
+ * set using a System property <i>wsplgen.max.threads</i> ; its default value is
+ * 1.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class Scheduler {
+
+    private class FutureJob {
+        public Future     future;
+        public WSPLGENJob job;
+
+        public FutureJob(Future future, WSPLGENJob job) {
+            this.future = future;
+            this.job    = job;
+        }
+    }
+
+    public static final int MAX_WSPLGEN_PROCESSES =
+        Integer.getInteger("wsplgen.max.threads", 1);
+
+
+    protected ScheduledThreadPoolExecutor pool;
+    protected Map<String, FutureJob> jobs;
+
+
+    private static Scheduler INSTANCE;
+
+    private static final Logger logger = Logger.getLogger(Scheduler.class);
+
+
+
+    private Scheduler() {
+        jobs = new HashMap<String, FutureJob>();
+        pool = new ScheduledThreadPoolExecutor(MAX_WSPLGEN_PROCESSES);
+    }
+
+
+    public static Scheduler getInstance() {
+        if (INSTANCE == null) {
+            logger.info("Create new WSPLGEN Scheduler...");
+
+            INSTANCE = new Scheduler();
+        }
+
+        return INSTANCE;
+    }
+
+
+    public void addJob(final WSPLGENJob job) {
+        synchronized (jobs) {
+            WSPLGENFuture f = new WSPLGENFuture(new WSPLGENCallable(this, job));
+            pool.execute(f);
+
+            jobs.put(job.getArtifact().identifier(), new FutureJob(f, job));
+
+            logger.info("New WSPLGEN job successfully added.");
+        }
+    }
+
+
+    /**
+     * Cancels a running (or queued) job.
+     *
+     * @param jobId The id of the job (which is the identifier of an Artifact).
+     */
+    public void cancelJob(String jobId) {
+        logger.debug("Search job in queue: " + jobId);
+
+        synchronized (jobs) {
+            FutureJob fj = jobs.get(jobId);
+
+            if (fj != null) {
+                logger.info("Try to cancel job: " + jobId);
+
+                fj.future.cancel(true);
+
+                removeJob(jobId);
+
+                fj.job.getCallContext().afterBackground(
+                    CallContext.STORE);
+
+                logger.info("Canceled job: " + jobId);
+            }
+        }
+    }
+
+
+    protected void removeJob(String id) {
+        synchronized (jobs) {
+            jobs.remove(id);
+        }
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/SchedulerSetup.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,37 @@
+package de.intevation.flys.wsplgen;
+
+import org.w3c.dom.Document;
+
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifactdatabase.LifetimeListener;
+
+import de.intevation.flys.artifacts.context.FLYSContext;
+
+
+/**
+ * A LifetimeListener that is used to create an instance of Scheduler. This
+ * instance is put into the GlobalContext using FLYSContext.SCHEDULER.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class SchedulerSetup implements LifetimeListener {
+
+    @Override
+    public void setup(Document document) {
+    }
+
+
+    @Override
+    public void systemUp(GlobalContext globalContext) {
+        Scheduler scheduler = Scheduler.getInstance();
+        globalContext.put(FLYSContext.SCHEDULER, scheduler);
+    }
+
+
+    @Override
+    public void systemDown(GlobalContext globalContext) {
+        // TODO IMPLEMENT ME!
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/WSPLGENCallable.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,140 @@
+package de.intevation.flys.wsplgen;
+
+import java.io.IOException;
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.model.WSPLGENJob;
+
+
+/**
+ * A Callable that is used to start and observe an external Process for WSPLGEN.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WSPLGENCallable implements Callable {
+
+    public static final String WSPLGEN_PARAMETER_FILE =
+        "wsplgen.par";
+
+    public static final String WSPLGEN_BIN_PATH =
+        System.getProperty("wsplgen.bin.path");
+
+
+    private Logger logger = Logger.getLogger(WSPLGENCallable.class);
+
+    private Process process;
+
+    protected Scheduler scheduler;
+
+    protected WSPLGENJob job;
+
+    protected JobObserver     logObserver;
+    protected ProblemObserver errorObserver;
+
+
+    public WSPLGENCallable(Scheduler scheduler, WSPLGENJob job) {
+        this.scheduler     = scheduler;
+        this.job           = job;
+        this.logObserver   = new JobObserver(job);
+        this.errorObserver = new ProblemObserver(job);
+    }
+
+
+    @Override
+    public WSPLGENJob call() {
+        File dir       = job.getWorkingDir();
+        File parameter = new File(dir, WSPLGEN_PARAMETER_FILE);
+
+        String[] args = new String[] {
+            WSPLGEN_BIN_PATH,
+            "-PAR=\"" + parameter.getAbsolutePath() + "\""
+        };
+
+        execute(args, dir);
+
+        return job;
+    }
+
+
+    protected void execute(String[] args, File dir) {
+        logger.info("Start JobExecutor for artifact: " + dir.getName());
+
+        String errorMsg = null;
+
+        try {
+            synchronized (this) {
+                process = Runtime.getRuntime().exec(args, null, dir);
+
+                logObserver.setInputStream(process.getInputStream());
+                errorObserver.setInputStream(process.getErrorStream());
+
+                logObserver.start();
+                errorObserver.start();
+
+                try {
+                    process.waitFor();
+                }
+                catch (InterruptedException ie) {
+                    logger.warn("WSPLGEN job interrupted: " + ie.getMessage());
+                }
+
+                try {
+                    logObserver.join();
+                    errorObserver.join();
+                }
+                catch (InterruptedException iee) { /* do nothing */ }
+
+                logger.info("WSPLGEN exit value: " + process.exitValue());
+                logger.info(
+                    "WSPLGEN throw " +
+                    errorObserver.numErrors() + " errors.");
+                logger.info(
+                    "WSPLGEN throw " +
+                    errorObserver.numWarnings() + " warnings.");
+
+                if (process.exitValue() < 2 && errorObserver.numErrors() == 0) {
+                    FacetCreator fc = job.getFacetCreator();
+                    fc.createWSPLGENFacet();
+                    fc.finish();
+                }
+
+                job.getCallContext().afterBackground(CallContext.STORE);
+
+                scheduler.removeJob(getJob().getArtifact().identifier());
+
+                return;
+            }
+        }
+        catch (SecurityException se) {
+            logger.error(se);
+        }
+        catch (IOException ioe) {
+            logger.error(ioe);
+        }
+        catch (NullPointerException npe) {
+            logger.error(npe, npe);
+        }
+        catch (IndexOutOfBoundsException ioobe) {
+            logger.error(ioobe, ioobe);
+        }
+    }
+
+
+    public void cancelWSPLGEN() {
+        if (process != null) {
+            logger.debug("Cancel running WSPLGEN process.");
+            process.destroy();
+        }
+    }
+
+
+    public WSPLGENJob getJob() {
+        return job;
+    }
+}
+// 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/flys-artifacts/src/main/java/de/intevation/flys/wsplgen/WSPLGENFuture.java	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,41 @@
+package de.intevation.flys.wsplgen;
+
+import java.util.concurrent.FutureTask;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * This FutureTask overrides the <i>cancel()</i> method. Before super.cancel()
+ * is called, WSPLGENCallable.cancelWSPLGEN() is executed to kill a running
+ * WSPLGEN process.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public class WSPLGENFuture extends FutureTask {
+
+    private static final Logger logger = Logger.getLogger(WSPLGENFuture.class);
+
+    protected WSPLGENCallable wsplgenCallable;
+
+
+    public WSPLGENFuture(WSPLGENCallable callable) {
+        super(callable);
+        this.wsplgenCallable = callable;
+    }
+
+
+    public WSPLGENCallable getWSPLGENCallable() {
+        return wsplgenCallable;
+    }
+
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        logger.debug("WSPLGENFuture.cancel");
+
+        wsplgenCallable.cancelWSPLGEN();
+        return super.cancel(mayInterruptIfRunning);
+    }
+}
+// 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/flys-artifacts/src/main/resources/datacage-sql/org-h2-driver.properties	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,29 @@
+delete.all.users = DELETE FROM users
+delete.all.artifacts = DELETE FROM artifacts
+user.id.nextval = SELECT NEXTVAL('USERS_ID_SEQ')
+user.by.gid = SELECT id FROM users WHERE gid = ?
+insert.user = INSERT INTO users (id, gid) VALUES (?, ?)
+collection.by.gid = SELECT id FROM collections WHERE gid = ?
+collection.id.nextval = SELECT NEXTVAL('COLLECTIONS_ID_SEQ')
+insert.collection = INSERT INTO collections (id, gid, user_id, name, creation) VALUES (?, ?, ?, ?, ?)
+artifact.by.gid = SELECT id FROM artifacts WHERE gid = ?
+collection.item.id.nextval = SELECT NEXTVAL('COLLECTION_ITEMS_ID_SEQ')
+insert.collection.item = INSERT INTO collection_items (id, collection_id, artifact_id) VALUES (?, ?, ?)
+artifact.id.nextval = SELECT NEXTVAL('ARTIFACTS_ID_SEQ')
+insert.artifact = INSERT INTO artifacts (id, gid, state, creation) VALUES (?, ?, ?, ?)
+artifact.data.id.nextval = SELECT NEXTVAL('ARTIFACT_DATA_ID_SEQ')
+insert.artifact.data = INSERT INTO artifact_data (id, artifact_id, kind, k, v) VALUES (?, ?, ?, ?, ?)
+out.id.nextval = SELECT NEXTVAL('OUTS_ID_SEQ')
+insert.out = INSERT INTO outs (id, artifact_id, name, description, out_type) VALUES (?, ?, ?, ?, ?)
+facet.id.nextval = SELECT NEXTVAL('FACETS_ID_SEQ')
+insert.facet = INSERT INTO facets (id, out_id, name, num, state, description) VALUES (?, ?, ?, ?, ?, ?)
+
+update.collection.name = UPDATE collections SET name = ? WHERE gid = ?
+delete.artifact.from.collection = DELETE FROM collection_items WHERE collection_id = ? AND artifact_id = ?
+delete.collection.by.gid = DELETE FROM collections WHERE gid = ?
+delete.user.by.gid = DELETE FROM user WHERE gid = ?
+delete.artifact.data.by.artifact.id = DELETE FROM artifact_data WHERE artifact_id = ?
+delete.outs.by.artifact.id = DELETE FROM outs WHERE artifact_id = ?
+delete.facets.by.artifact.id = DELETE FROM facets WHERE out_id IN (SELECT id FROM outs WHERE artifact_id = ?)
+
+delete.artifact.by.gid = DELETE FROM artifacts WHERE gid = ?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/datacage-sql/org-postgresql-driver.properties	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,29 @@
+delete.all.users = DELETE FROM users
+delete.all.artifacts = DELETE FROM artifacts
+user.id.nextval = SELECT NEXTVAL('USERS_ID_SEQ')
+user.by.gid = SELECT id FROM users WHERE gid = ?::uuid
+insert.user = INSERT INTO users (id, gid) VALUES (?, ?::uuid)
+collection.by.gid = SELECT id FROM collections WHERE gid = ?::uuid
+collection.id.nextval = SELECT NEXTVAL('COLLECTIONS_ID_SEQ')
+insert.collection = INSERT INTO collections (id, gid, user_id, name, creation) VALUES (?, ?::uuid, ?, ?, ?)
+artifact.by.gid = SELECT id FROM artifacts WHERE gid = ?::uuid
+collection.item.id.nextval = SELECT NEXTVAL('COLLECTION_ITEMS_ID_SEQ')
+insert.collection.item = INSERT INTO collection_items (id, collection_id, artifact_id) VALUES (?, ?, ?)
+artifact.id.nextval = SELECT NEXTVAL('ARTIFACTS_ID_SEQ')
+insert.artifact = INSERT INTO artifacts (id, gid, state, creation) VALUES (?, ?::uuid, ?, ?)
+artifact.data.id.nextval = SELECT NEXTVAL('ARTIFACT_DATA_ID_SEQ')
+insert.artifact.data = INSERT INTO artifact_data (id, artifact_id, kind, k, v) VALUES (?, ?, ?, ?, ?)
+out.id.nextval = SELECT NEXTVAL('OUTS_ID_SEQ')
+insert.out = INSERT INTO outs (id, artifact_id, name, description, out_type) VALUES (?, ?, ?, ?, ?)
+facet.id.nextval = SELECT NEXTVAL('FACETS_ID_SEQ')
+insert.facet = INSERT INTO facets (id, out_id, name, num, state, description) VALUES (?, ?, ?, ?, ?, ?)
+
+update.collection.name = UPDATE collections SET name = ? WHERE gid = ?::uuid
+delete.artifact.from.collection = DELETE FROM collection_items WHERE collection_id = ? AND artifact_id = ?
+delete.collection.by.gid = DELETE FROM collections WHERE gid = ?::uuid
+delete.user.by.gid = DELETE FROM user WHERE gid = ?::uuid
+delete.artifact.data.by.artifact.id = DELETE FROM artifact_data WHERE artifact_id = ?
+delete.outs.by.artifact.id = DELETE FROM outs WHERE artifact_id = ?
+delete.facets.by.artifact.id = DELETE FROM facets WHERE out_id IN (SELECT id FROM outs WHERE artifact_id = ?)
+
+delete.artifact.by.gid = DELETE FROM artifacts WHERE gid = ?::uuid
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages.properties	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,298 @@
+state.winfo.river = River
+state.winfo.calculation_mode = Calculation Mode
+state.winfo.location_distance = Location or distance selection
+state.winfo.wq = Input for W/Q data
+state.winfo.wq_adapted = Input for W/Q data
+state.winfo.location = Choose the location
+state.winfo.distance = Choose the range
+state.winfo.distance_only = Range selection
+state.winfo.uesk.wsp = Choose the waterlevel
+state.winfo.uesk.dgm = Digital Terrain Model
+state.winfo.uesk.profiles = Interpolated Profiles
+state.winfo.uesk.floodplain = Lateral Boundary
+state.winfo.uesk.differences = Differenzen between waterlevel and terrain
+state.winfo.uesk.scenario = Flood Plain / Scenario
+state.winfo.waterlevel_pair_select = Chosen differences
+state.winfo.historicalq.reference_gauge = Selection of Reference Gauge
+state.winfo.historicalq.timerange = Selection of Evaluation time
+state.winfo.historicalq.mode = Selecion of analyses
+state.winfo.reference.curve.input.start = Chosen Reference
+state.winfo.reference.curve.input.end = Chosen Evaluation
+state.fix.river = River
+state.fix.calculation.mode = Calculation Mode
+state.fix.location = Distance
+state.fix.period = Period
+state.fix.gaugerange = Gaugerange
+state.fix.eventselect = Event selection
+state.fix.analysis.referenceperiod = Reference period
+state.fix.analysis.analysisperiods = Analysis period
+state.fix.analysis.function = Function
+state.fix.analysis.preprocessing = Preprocessing
+
+state.minfo.river = River
+state.minfo.calculation_mode = Calculation Mode
+state.minfo.distance_only = Range selection
+state.minfo.dischargestate = Selection of discharge state and channel
+
+historical.mode.w = Waterlevel Analyse
+historical.mode.q = Discharge Analyse
+
+calc.surface.curve = Water Level/Surface Curve
+calc.flood.map = Flood Plain
+calc.discharge.curve = State Discharge Curve/Stage Discharge Relation
+calc.duration.curve = Duration Curve
+calc.discharge.longitudinal.section = TODO (W bei...)
+calc.w.differences = Differences
+calc.historical.discharge.curve = Historical Discharge Curve
+calc.reference.curve = Reference Curve
+calc.fixation.default = Fixation
+calc.fixation.volmer = Balanced Waterlevel
+calc.bed.middle = Middle Bed Height
+calc.bed.diff = Bed Height Difference
+calc.bed.quality = Bed Quality
+calc.sediment.load = Sediment Load
+calc.flow.velocity = Flow Velocity
+calc.sq.relation = Load Discharge Relation
+
+calculation.analysis = Fixinganalysis
+calculation.volmer = relocated Waterlevel Calculation
+
+state.chart.river = River
+state.chart.type = Charttype
+
+chart.new.durationcurve = Duration Curve
+chart.new.computeddischargecurve = Discharge Curve
+chart.new.longitudinal_section = Longitudinal Section
+chart.new.w_differences = Differences
+chart.new.crosssection = Cross Section
+
+cross_section = Cross Section
+
+reference_curve = Reference Curve
+reference_curve_normalized = Normalized Reference Curve
+
+scenario.current = Current
+scenario.potentiel = Potentiel
+scenario.scenario = Scenario
+
+floodplain.option = Use Floodplain?
+floodplain.active = Activ
+floodplain.inactive = Inactiv
+
+river = River
+calculation_mode = Calculation Mode
+ld_locations = Location(s)
+main.channel = Main channel
+total.channel = Total channel
+
+chart.longitudinal.section.title = W-Longitudinal Section
+chart.longitudinal.section.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.shortsubtitle = {0}
+chart.longitudinal.section.xaxis.label = {0}-km
+chart.longitudinal.section.yaxis.label = W [{0}]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.longitudinal.annotations.label = {0}.km
+
+chart.cross_section.title = Cross Section for river {0}
+chart.cross_section.subtitle = {0}-km: {1,number,#.###}
+chart.cross_section.xaxis.label = Distance [m]
+chart.cross_section.yaxis.label = W [NN + m]
+
+chart.discharge.curve.title = Discharge Curve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.discharge.curve.curve.valid.from = {0} (valid from {1,date,short})
+chart.discharge.curve.curve.valid.range = {0} (valid from {1,date,short} - {2,date,short})
+chart.computed.discharge.curve.title = Discharge Curve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.computed.discharge.curve.curve.label = Discharge Curve {0} km {1}
+chart.duration.curve.title = Duration Curve
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Duration of Non-Exceedence [Days]
+chart.duration.curve.yaxis.label = W [NN + m]
+chart.duration.curve.curve.w = Waterlevel duration curve for {0} (km: {1})
+chart.duration.curve.curve.q = Discharge duration curve for {0} (km: {1})
+chart.historical.discharge.title = Historical Discharge Curves
+chart.historical.discharge.subtitle = Gauge {0}
+chart.historical.discharge.xaxis.label = Time
+chart.historical.discharge.yaxis.label = Q [m\u00b3/s]
+chart.historical.discharge.yaxis.second.label = W [cm]
+
+chart.reference.curve.title = Reference Curve
+chart.reference.curve.subtitle = {0}
+
+chart.reference.curve.x.axis.in.cm = Reference Gauge(s) [cm]
+chart.reference.curve.x.axis.in.m = Reference Station(s) [NN + m]
+chart.reference.curve.y.axis.in.cm = Target Gauge(s) [cm]
+chart.reference.curve.y.axis.in.m = Target Station(s) [NN + m] 
+
+chart.w_differences.title = Differences
+chart.w_differences.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.w_differences.yaxis.label = m
+chart.w_differences.yaxis.second.label = W [NN + m]
+
+chart.normalized.reference.curve.title = Reduced Reference Curve
+
+facet.longitudinal_section.annotations = POIs
+facet.discharge_curves.mainvalues.q = Q (main values)
+facet.discharge_curves.mainvalues.w = W (main values)
+facet.flow_velocity.mainchannel = v Mainchannel at {0}
+facet.flow_velocity.totalchannel = v Totalchannel at {0}
+facet.flow_velocity.tauchannel = TAU Mainchannel at {0}
+facet.bedheight_middle.single = Bed Height {0,number,####}
+facet.bedheight_middle.epoch = Bed Height Epoch {0,number,####} - {1,number,####}
+
+export.waterlevel.csv.header.km = River-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.waterlevel.csv.header.q.desc = Description
+export.waterlevel.csv.header.location = Location
+export.waterlevel.csv.header.gauge = Reference Gauge
+export.waterlevel.csv.meta.result = # Calculation Output - {0} - Waterlevel - FLYS 3
+export.waterlevel.csv.meta.creation = # Time of creation: {0}
+export.waterlevel.csv.meta.calculationbase = # Calculation base: {0}
+export.waterlevel.csv.meta.river = # River: {0}
+export.waterlevel.csv.meta.range = # Location/Range (km): {0} - {1}
+export.waterlevel.csv.meta.gauge = # Gauge: {0}
+export.waterlevel.csv.meta.q = # Q (m\u00b3/s): {0}
+export.waterlevel.csv.meta.w = # W (NN + m): {0} - {1}
+export.waterlevel.csv.not.in.gauge.range = Outside selected gauge
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Days]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = River-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W corr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
+export.discharge.curve.at.header = Computed Discharge Curve for {0} {0}-km: {1}
+export.historical.discharge.csv.header.timerange = Timerange
+export.historical.discharge.csv.header.waterlevel = Waterlevel
+export.historical.discharge.csv.header.discharge = Discharge
+export.historical.discharge.csv.header.diff = Difference
+export.historical.discharge.csv.header.gaugename = Gaugename
+export.reference_curve.csv.header.km = km
+export.reference_curve.csv.header.w.cm = W (cm at Gauge)
+export.reference_curve.csv.header.w.m = W (m + NHN)
+export.reference_curve.csv.header.w.q = equiv. Q (m\u00b3/s)
+
+export.waterlevel.pdf.mode = Waterlevel
+export.computed.discharge.pdf.mode = Computed Dischargecurve
+export.duration.pdf.mode = Durationcurve
+export.wdifferences.pdf.mode = W Differences
+export.historical.discharge.pdf.mode = Historical Discharge
+export.reference_curve.pdf.mode = Reference Curve
+export.computed.discharge.pdf.file = /jasper/computed-discharge_en.jasper
+export.duration.pdf.file = /jasper/duration_en.jasper
+export.waterlevel.pdf.file = /jasper/waterlevel_en.jasper
+export.wdifferences.pdf.file = /jasper/wdifferences_en.jasper
+export.historical.discharge.pdf.file = /jasper/historical-discharge_en.jasper
+export.reference_curve.pdf.file = /jasper/reference_en.jasper
+export.reference_curve.pdf.file.gauge = /jasper/reference_en_gauge.jasper
+export.reference_curve.pdf.file.gauge.end = /jasper/reference_en_gauge_end.jasper
+export.reference_curve.pdf.file.gauge.start.end = /jasper/reference_en_gauge_start_end.jasper
+export.flow_velocity.csv.header.km = River Km
+export.flow_velocity.csv.header.v_total = v Total Channel
+export.flow_velocity.csv.header.v_main = v Main Channel
+export.flow_velocity.csv.header.tau_main = TAU Main Channel
+export.flow_velocity.csv.header.q = Q [m\u00b3/s]
+export.flow_velocity.csv.header.locations = Location
+export.bedheight_middle.csv.header.km = River km
+export.bedheight_middle.csv.header.sounding = Sounding / Epoch
+export.bedheight_middle.csv.header.height = middle Bed Height
+export.bedheight_middle.csv.header.uncertainty = Uncertainty [m]
+export.bedheight_middle.csv.header.datagap = Data Gap
+export.bedheight_middle.csv.header.soundingwidth = Sounding Width [m]
+export.bedheight_middle.csv.header.width = morphological active width [m]
+export.bedheight_middle.csv.header.locations = Location
+
+floodmap.wmsbackground = Background Map
+floodmap.riveraxis = River Axis
+floodmap.uesk = Floodmap
+floodmap.barriers = Digitized Objects
+floodmap.kms = Kilometrage
+floodmap.qps = Crosssection Tracks
+floodmap.hws = Flood Control Works
+floodmap.catchment = Catchment
+floodmap.floodplain = Floodplain
+floodmap.lines = Lines
+floodmap.buildings = Buildings
+floodmap.fixpoints = Fixpoints
+floodmap.floodmaps = Floodmaps
+floodmap.usershape = User data
+
+wsplgen.job.queued = WSPLGEN job in queue.
+wsplgen.job.error = An unexpected error while starting WSPLGEN occured.
+
+wsp.selected.string = {0}
+
+Mosel = Mosel
+Saar = Saar
+Elbe = Elbe
+
+state.map.river = River
+
+spline.interpolation.failed = Spline interpolation failed.
+cannot.find.w.for.q = Cannot find W for Q = {0,number,#.##}.
+cannot.find.q = Cannot find Q = {0,number,#.##}.
+no.ws.found = No Ws found.
+no.q.found.in.column = No Q found in {0,integer} column.
+spline.creation.failed = Spline creation failed.
+cannot.find.w.or.q = Cannot find W or Q.
+km.not.found = Cannot find km.
+cannot.create.wq.relation = Cannot create W/Q relation.
+cannot.create.index.q.relation = Cannot create index/Q relation.
+
+w.w.qkm1.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm1.failed = Calculating W for Q = {0,number,#.##} /  WST index {1,number,#.##} failed.
+w.w.qkm2.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm2.failed = Calculating W for Q = {0,number,#.##} /  WST index {1,number,#.##} failed.
+
+cannot.find.hist.q.for.w = Cannot find Q for W = {0,number,#.##} in timerange {1, date} - {2, date}
+cannot.find.hist.q.tables = Cannot find Discharge Tables for given timerange.
+cannot.find.hist.q.reftable = Cannot find reference Discharge Table for specified Gauge.
+
+more.than.one.q.for.w = Found more Qs for W = {0,number,#.##}.
+
+no.river.selected = No river selected.
+no.gauge.selected = No gauge selected.
+no.locations.selected = No locations selected.
+no.kms.selected = No KMs selected.
+converting.ws.to.qs.failed = Converting Ws to Qs failed.
+no.wst.for.river = No WST found for selected river.
+no.range.found = No range found.
+no.gauge.found.for.km = No gauge found for KM.
+cannot.create.segments = Cannot create segments.
+cannot.compute.discharge.curve = Cannot create discharge curve.
+cannot.find.ds = Cannot find Ds.
+no.segments.found = No segments found.
+no.values.given = No values given.
+cannot.interpolate.w.q = Cannot interpolate W/Q.
+manualpoints = Manual Points
+
+no.reference.start.km = No reference start station given.
+no.reference.end.kms = No reference end station(s) given.
+waterlevels = Waterlevels
+
+help.index=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe
+help.winfo=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO
+help.winfo.wsp.location_distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.wsp.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_W.2BAC8-Q_Daten
+help.winfo.discharge.longitudinal.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Berechnungsstrecke
+help.winfo.duration.locations=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.discharge.longitudinal.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_f.2BAPw-r_W.2BAC8-Q_Daten
+help.winfo.diff.diffs=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Zusammenstellen_der_Differenzen
+help.winfo.reference.curve.start=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Bezugsortes_.2BAC8_Bezugspegels
+help.winfo.reference.curve.end=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Zielortes_.2BAC8_Zielpegels
+help.winfo.uesk.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Berechnungsstrecke
+help.winfo.uesk.wsp=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Wasserspiegellage
+help.winfo.uesk.dem=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Digitalen_Gel.2BAOQ-ndemodells
+help.winfo.uesk.profiles=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Abstand_Interpolierte_Profile
+help.winfo.uesk.floodplain=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Laterale_Begrenzung
+help.winfo.uesk.differences=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Differenz_Wasserspiegellage_und_Gel.2BAOQ-nde
+help.winfo.uesk.scenario=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#A.2BANw-berschwemmungsfl.2BAOQ-che_.2BAC8_Szenario
+help.winfo.historical.discharge.reference_gauge=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Bezugspegels
+help.winfo.historical.discharge.timerange=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_des_Auswertezeitraumes
+help.winfo.historical.discharge.mode=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_der_Analyseart
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages_de.properties	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,299 @@
+state.winfo.river = Gew\u00e4sser
+state.winfo.calculation_mode = Berechnungsart
+state.winfo.location_distance = Wahl des Berechnungsortes/strecke
+state.winfo.wq = Eingabe f\u00fcr W/Q Daten
+state.winfo.wq_adapted = Eingabe f\u00fcr W/Q Daten
+state.winfo.location = Wahl des Berechnungsortes
+state.winfo.distance = Wahl der Berechnungsstrecke
+state.winfo.distance_only = Wahl der Berechnungsstrecke
+state.winfo.uesk.wsp = Wahl der Wasserspiegellage
+state.winfo.uesk.dgm = Digitales Gel\u00e4ndemodell
+state.winfo.uesk.profiles = Interpolierte Profile
+state.winfo.uesk.floodplain = Laterale Begrenzung
+state.winfo.uesk.differences = Differenzen zwischen Wasserspiegellage und Gel\u00e4nde
+state.winfo.uesk.scenario = \u00dcberschwemmungsfl\u00e4che / Szenario
+state.winfo.waterlevel_pair_select = Ausgew\u00e4hlte Differenzen
+state.winfo.historicalq.reference_gauge = Wahl des Bezugspegels
+state.winfo.historicalq.timerange = Wahl des Auswertezeitraums
+state.winfo.historicalq.mode = Wahl der Analyseart
+state.winfo.reference.curve.input.start = Bezugsort
+state.winfo.reference.curve.input.end = Zielort(e)
+state.fix.river = Gew\u00e4sser
+state.fix.calculation.mode = Berechnungsart
+state.fix.location = Strecke
+state.fix.period = Zeitraum
+state.fix.gaugerange = Abflussklassen
+state.fix.eventselect = Ereignisauswahl
+state.fix.analysis.referenceperiod = Bezugszeitraum
+state.fix.analysis.analysisperiods = Analysezeitr\u00e4ume
+state.fix.analysis.function = Funktion
+state.fix.analysis.preprocessing = Preprocessing
+
+state.minfo.river = Gew\u00e4sser
+state.minfo.calculation_mode = Berechnungsart
+state.minfo.distance_only = Wahl der Berechnungsstrecke
+state.minfo.dischargestate = Abflusszustand und Gerinne
+
+historical.mode.w = Wasserstandsanalyse
+historical.mode.q = Abflussanalyse
+
+calc.surface.curve = Wasserstand/Wasserspiegellage
+calc.flood.map = \u00dcberschwemmungsfl\u00e4che
+calc.discharge.curve = Abflusskurve/Abflusstafel
+calc.duration.curve = Dauerlinie
+calc.discharge.longitudinal.section = W f\u00fcr ungleichwertigen Abflussl\u00e4ngsschnitt
+calc.w.differences = Differenzen
+calc.historical.discharge.curve = Hist. Abflusskurven
+calc.reference.curve = Bezugslinie
+calc.fixation.default = Fixierungsanalyse
+calc.fixation.volmer = Ausgeglichene Wasserspiegellage
+calc.bed.middle = Mittlere Sohlh\u00f6he
+calc.bed.diff = Sohlh\u00f6hendifferenz
+calc.bed.quality = Sohlbeschaffenheit
+calc.sediment.load = Sedimentfracht
+calc.flow.velocity = Flie\u00dfgeschwindigkeit
+calc.sq.relation = Transport-Abfluss Beziehung
+
+calculation.analysis = Fixierungsanalyse
+calculation.volmer = ausgelagerte Wasserspiegellage
+
+state.chart.river = Gew\u00e4sser
+state.chart.type = Diagrammtyp
+
+chart.new.durationcurve = Dauerlinie
+chart.new.computeddischargecurve = Abflusskurve
+chart.new.longitudinal_section = L\u00e4ngsschnitt
+chart.new.w_differences = Differenzen
+chart.new.crosssection = Querprofil
+
+cross_section = Querprofil
+
+reference_curve = Bezugslinie
+reference_curve_normalized = Reduzierte Bezugslinie
+
+scenario.current = Aktuell
+scenario.potentiel = Potenziell
+scenario.scenario = Szenario
+
+floodplain.option = Talaue verwenden?
+floodplain.active = Aktiv
+floodplain.inactive = Inaktiv
+
+river = Fluss
+calculation_mode = Berechnungsart
+ld_locations = Ort(e)
+main.channel = Hauptgerinne
+total.channel = Gesamtgerinne
+
+chart.cross_section.title = Querprofildiagram f\u00fcr Gew\u00e4sser {0}
+chart.cross_section.subtitle = {0}-km: {1,number,#.###}
+chart.cross_section.xaxis.label = Abstand [m]
+chart.cross_section.yaxis.label = W [NN + m]
+
+chart.longitudinal.section.title = W-L\u00e4ngsschnitt
+chart.longitudinal.section.subtitle = Bereich: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.shortsubtitle = {0}
+chart.longitudinal.section.xaxis.label = {0}-km
+chart.longitudinal.section.yaxis.label = W [{0}]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.longitudinal.annotations.label = {0}.km
+chart.discharge.curve.title = Abflusskurve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.discharge.curve.curve.valid.from = {0} (g\u00fcltig ab {1,date,medium})
+chart.discharge.curve.curve.valid.range = {0} (g\u00fcltig ab {1,date,medium} - {2,date,medium})
+chart.computed.discharge.curve.title = Abflusskurve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.computed.discharge.curve.curve.label = Abflusskurve {0} km {1}
+chart.duration.curve.title = Dauerlinie
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Unterschreitungsdauer [Tage]
+chart.duration.curve.yaxis.label = W [NN + m]
+chart.duration.curve.curve.w = Wasserstandsdauerline f\u00fcr {0} (km:{1})
+chart.duration.curve.curve.q = Abflussdauerline f\u00fcr {0} (km:{1})
+chart.historical.discharge.title = Historische Abflusskurven
+chart.historical.discharge.subtitle = Pegel {0}
+chart.historical.discharge.xaxis.label = Zeit
+chart.historical.discharge.yaxis.label = Q [m\u00b3/s]
+chart.historical.discharge.yaxis.second.label = W [cm]
+
+chart.reference.curve.title = Bezugslinie
+chart.reference.curve.subtitle = {0}
+
+chart.reference.curve.x.axis.in.cm = Bezugspegel [cm]
+chart.reference.curve.x.axis.in.m = Bezugsort(e) [NN + m]
+chart.reference.curve.y.axis.in.cm = Zielpegel [cm]
+chart.reference.curve.y.axis.in.m = Zielort(e) [NN + m] 
+
+chart.normalized.reference.curve.title = Reduzierte Bezugslinie
+
+chart.w_differences.title = Differenzen
+chart.w_differences.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.w_differences.yaxis.label = m
+chart.w_differences.yaxis.second.label = W [NN + m]
+
+facet.longitudinal_section.annotations = Streckenfavoriten
+facet.discharge_curves.mainvalues.q = Q (Haupt- und Extremwerte)
+facet.discharge_curves.mainvalues.w = W (Haupt- und Extremwerte)
+facet.flow_velocity.mainchannel = v Hauptgerinne bei {0}
+facet.flow_velocity.totalchannel = v Gesamtgerinne bei {0}
+facet.flow_velocity.tauchannel = TAU Hauptgerinne bei {0}
+facet.bedheight_middle.single = Sohlh\u00f6he {0,number,####}
+facet.bedheight_middle.epoch = Sohlh\u00f6he Epoche {0,number,####} - {1,number,####}
+
+export.waterlevel.csv.header.km = Fluss-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.waterlevel.csv.header.q.desc = Bezeichnung
+export.waterlevel.csv.header.location = Lage
+export.waterlevel.csv.header.gauge = Bezugspegel
+export.waterlevel.csv.meta.result = # Ergebnisausgabe - {0} - Wasserstand - FLYS 3
+export.waterlevel.csv.meta.creation = # Datum der Erstellung: {0}
+export.waterlevel.csv.meta.calculationbase = # Berechnungsgrundlage: {0}
+export.waterlevel.csv.meta.river = # Gew\u00e4sser: {0}
+export.waterlevel.csv.meta.range = # Ort/Bereich (km): {0} - {1}
+export.waterlevel.csv.meta.gauge = # Bezugspegel: {0}
+export.waterlevel.csv.meta.q = # Q (m\u00b3/s): {0}
+export.waterlevel.csv.meta.w = # W (NN + m): {0} - {1}
+export.waterlevel.csv.not.in.gauge.range = au\u00dferhalb gew\u00e4hlter Bezugspegels
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Tagen]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = Fluss-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W korr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
+export.discharge.curve.at.header = Berechnete Abflusskurve f\u00fcr {0} {0}-km: {1}
+export.historical.discharge.csv.header.timerange = Zeitraum
+export.historical.discharge.csv.header.waterlevel = Wasserstand
+export.historical.discharge.csv.header.discharge = Abfluss
+export.historical.discharge.csv.header.diff = Abflussdifferenz zur Bezugskurve
+export.historical.discharge.csv.header.gaugename = Pegelname
+export.reference_curve.csv.header.km = km
+export.reference_curve.csv.header.w.cm = W (cm am Pegel)
+export.reference_curve.csv.header.w.m = W (m + NHN)
+export.reference_curve.csv.header.w.q = gleichw. Q (m\u00b3/s)
+
+
+export.waterlevel.pdf.mode = Wasserstand
+export.computed.discharge.pdf.mode = Abflusskurve
+export.duration.pdf.mode = Dauerlinie
+export.wdifferences.pdf.mode = W Differenzen
+export.historical.discharge.pdf.mode = Historischer Abfluss
+export.reference_curve.pdf.mode = Bezugslinie
+export.computed.discharge.pdf.file = /jasper/computed-discharge.jasper
+export.duration.pdf.file = /jasper/duration.jasper
+export.waterlevel.pdf.file = /jasper/waterlevel.jasper
+export.wdifferences.pdf.file = /jasper/wdifferences.jasper
+export.historical.discharge.pdf.file = /jasper/historical-discharge.jasper
+export.reference_curve.pdf.file = /jasper/reference.jasper
+export.reference_curve.pdf.file.gauge = /jasper/reference_gauge.jasper
+export.reference_curve.pdf.file.gauge.end = /jasper/reference_gauge_end.jasper
+export.reference_curve.pdf.file.gauge.start.end = /jasper/reference_gauge_start_end.jasper
+export.flow_velocity.csv.header.km = Fluss-Km
+export.flow_velocity.csv.header.v_total = v Gesamtgerinne
+export.flow_velocity.csv.header.v_main = v Hauptgerinne
+export.flow_velocity.csv.header.tau_main = TAU Hauptgerinne
+export.flow_velocity.csv.header.q = Q [m\u00b3/s]
+export.flow_velocity.csv.header.locations = Streckendaten
+export.bedheight_middle.csv.header.km = Fluss-km
+export.bedheight_middle.csv.header.sounding = Peilung / Epoche
+export.bedheight_middle.csv.header.height = gemittelte Sohl\u00f6he
+export.bedheight_middle.csv.header.uncertainty = Unsicherheit [m]
+export.bedheight_middle.csv.header.datagap = Datenl\u00fccke
+export.bedheight_middle.csv.header.soundingwidth = Peilbreite [m]
+export.bedheight_middle.csv.header.width = morphologisch aktive Breite [m]
+export.bedheight_middle.csv.header.locations = Streckendaten
+
+floodmap.wmsbackground = Hintergrundkarte
+floodmap.riveraxis = Flussachse
+floodmap.uesk = \u00dcberschwemmungsfl\u00e4che
+floodmap.barriers = Digitalisierte Objekte
+floodmap.kms = Kilometrierung
+floodmap.qps = Querprofilspuren
+floodmap.hws = Hochwasserschutzanlagen
+floodmap.catchment = Einzugsgebiet
+floodmap.floodplain = Talaue
+floodmap.lines = Linien
+floodmap.buildings = Bauwerke
+floodmap.fixpoints = Festpunkte
+floodmap.floodmaps = \u00dcberschwemmungsfl\u00e4che
+floodmap.usershape = Benutzerdaten
+
+wsplgen.job.queued = WSPLGEN Berechnung befindet sich in Warteschlange.
+wsplgen.job.error = Ein unerwarteter Fehler beim Starten von WSPLGEN ist aufgetreten.
+
+wsp.selected.string = {0}
+
+Mosel = Mosel
+Saar = Saar
+Elbe = Elbe
+
+state.map.river = Gew\u00e4sser
+
+spline.interpolation.failed = Spline interpolation fehlgeschlagen.
+cannot.find.w.for.q = W zu Q = {0,number,#.##} kann nicht ermittelt werden.
+cannot.find.q = Q = {0,number,#.##} konnte nicht gefunden werden.
+no.ws.found = Keine Ws gefunden.
+no.q.found.in.column = In Spalte {0,integer} wurde kein Q gefunden.
+spline.creation.failed = Spline creation failed.
+cannot.find.w.or.q = W oder Q konnten nicht ermittelt werden.
+km.not.found = Passender Kilometer konnte nicht gefunden werden.
+cannot.create.wq.relation = W/Q-Beziehung konnte nicht ermittelt werden.
+cannot.create.index.q.relation = Spaltenindex/Q-Beziehung konnte nicht erstellt werden.
+
+w.w.qkm1.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen.
+w.w.wkm1.failed = Berechnung von W f\u00fcr Q = {0,number,#.##} / WST-Index {1,number,#.##} fehlgeschlagen.
+w.w.qkm2.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen. 
+w.w.wkm2.failed = Berechnung von W f\u00fcr Q = {0,number,#.##} / WST-Index {1,number,#.##} fehlgeschlagen. 
+
+cannot.find.hist.q.for.w = Konnte zu W = {0,number,#.##} im Zeitraum ({1,date} - {2,date}) kein Abfluss ermitteln.
+cannot.find.hist.q.tables = Konnte f\u00fcr den angegebenen Zeitraum keine Abflusstafeln finden.
+cannot.find.hist.q.reftable = Konnte f\u00fcr den gew\u00e4hlten Pegel keine Bezugs-Abflusstafel ermitteln.
+
+more.than.one.q.for.w = Mehr als ein Q f\u00fcr W = {0,number,#.##} gefunden.
+
+no.river.selected = Kein Gew\u00e4sser ausgew\u00e4hlt.
+no.gauge.selected = Kein Pegel ausgew\u00e4hlt.
+no.locations.selected = Keine Orte angegeben.
+no.kms.selected = Keine Kilometerstationen angegeben.
+converting.ws.to.qs.failed = Konvertierung von Ws zu Qs fehlgeschlagen.
+no.wst.for.river = Keine zum Gew\u00e4sser passende WST gefunden.
+no.range.found = Kein passender Pegel gefunden.
+no.gauge.found.for.km = Zur gegebenen Kilometerstation existiert kein Pegel.
+cannot.create.segments = Flussabschnitte konnten nicht erzeugt werden.
+cannot.compute.discharge.curve = Die Abflusskurve konnte nicht berechnet werden..
+cannot.find.ds = Dauerzahlen konnten nicht gefunden werden.
+no.segments.found = Keine Flussabschnitte gefunden.
+no.values.given = Keine Werte angegeben.
+cannot.interpolate.w.q = W/Q-Wert konnte nicht interpoliert werden.
+
+manualpoints = Manuelle Punkte
+
+no.reference.start.km = Keine Startkilometerstation angegeben.
+no.reference.end.kms = Keine Endkilometerstation(en) angegeben.
+waterlevels = Wasserst\u00e4nde
+
+help.index=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe
+help.winfo=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO
+help.winfo.wsp.location_distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.wsp.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_W.2BAC8-Q_Daten
+help.winfo.discharge.longitudinal.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Berechnungsstrecke
+help.winfo.duration.locations=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.discharge.longitudinal.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_f.2BAPw-r_W.2BAC8-Q_Daten
+help.winfo.diff.diffs=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Zusammenstellen_der_Differenzen
+help.winfo.reference.curve.start=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Bezugsortes_.2BAC8_Bezugspegels
+help.winfo.reference.curve.end=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Zielortes_.2BAC8_Zielpegels
+help.winfo.uesk.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Berechnungsstrecke
+help.winfo.uesk.wsp=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Wasserspiegellage
+help.winfo.uesk.dem=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Digitalen_Gel.2BAOQ-ndemodells
+help.winfo.uesk.profiles=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Abstand_Interpolierte_Profile
+help.winfo.uesk.floodplain=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Laterale_Begrenzung
+help.winfo.uesk.differences=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Differenz_Wasserspiegellage_und_Gel.2BAOQ-nde
+help.winfo.uesk.scenario=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#A.2BANw-berschwemmungsfl.2BAOQ-che_.2BAC8_Szenario
+help.winfo.historical.discharge.reference_gauge=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Bezugspegels
+help.winfo.historical.discharge.timerange=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_des_Auswertezeitraumes
+help.winfo.historical.discharge.mode=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_der_Analyseart
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages_de_DE.properties	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,296 @@
+state.winfo.river = Gew\u00e4sser
+state.winfo.calculation_mode = Berechnungsart
+state.winfo.location_distance = Wahl des Berechnungsortes/strecke
+state.winfo.wq = Eingabe f\u00fcr W/Q Daten
+state.winfo.wq_adapted = Eingabe f\u00fcr W/Q Daten
+state.winfo.location = Wahl des Berechnungsortes
+state.winfo.distance = Wahl der Berechnungsstrecke
+state.winfo.distance_only = Wahl der Berechnungsstrecke
+state.winfo.uesk.wsp = Wahl der Wasserspiegellage
+state.winfo.uesk.dgm = Digitales Gel\u00e4ndemodell
+state.winfo.uesk.profiles = Interpolierte Profile
+state.winfo.uesk.floodplain = Laterale Begrenzung
+state.winfo.uesk.differences = Differenzen zwischen Wasserspiegellage und Gel\u00e4nde
+state.winfo.uesk.scenario = \u00dcberschwemmungsfl\u00e4che / Szenario
+state.winfo.waterlevel_pair_select = Ausgew\u00e4hlte Differenzen
+state.winfo.historicalq.reference_gauge = Wahl des Bezugspegels
+state.winfo.historicalq.timerange = Wahl des Auswertezeitraums
+state.winfo.historicalq.mode = Wahl der Analyseart
+state.winfo.reference.curve.input.start = Bezugsort
+state.winfo.reference.curve.input.end = Zielort(e)
+state.fix.river = Gew\u00e4sser
+state.fix.calculation.mode = Berechnungsart
+state.fix.location = Strecke
+state.fix.period = Zeitraum
+state.fix.gaugerange = Abflussklassen
+state.fix.eventselect = Ereignisauswahl
+state.fix.analysis.referenceperiod = Bezugszeitraum
+state.fix.analysis.analysisperiods = Analysezeitr\u00e4ume
+state.fix.analysis.function = Funktion
+state.fix.analysis.preprocessing = Preprocessing
+
+state.minfo.river = Gew\u00e4sser
+state.minfo.calculation_mode = Berechnungsart
+state.minfo.distance_only = Wahl der Berechnungsstrecke
+state.minfo.dischargestate = Abflusszustand und Gerinne
+
+historical.mode.w = Wasserstandsanalyse
+historical.mode.q = Abflussanalyse
+
+calc.surface.curve = Wasserstand/Wasserspiegellage
+calc.flood.map = \u00dcberschwemmungsfl\u00e4che
+calc.discharge.curve = Abflusskurve/Abflusstafel
+calc.duration.curve = Dauerlinie
+calc.discharge.longitudinal.section = W f\u00fcr ungleichwertigen Abflussl\u00e4ngsschnitt
+calc.w.differences = Differenzen
+calc.historical.discharge.curve = Hist. Abflusskurven
+calc.reference.curve = Bezugslinie
+calc.bed.middle = Mittlere Sohlh\u00f6he
+calc.bed.diff = Sohlh\u00f6hendifferenz
+calc.bed.quality = Sohlbeschaffenheit
+calc.sediment.load = Sedimentfracht
+calc.flow.velocity = Flie\u00dfgeschwindigkeit
+calc.sq.relation = Transport-Abfluss Beziehung
+
+calculation.analysis = Fixierungsanalyse
+calculation.volmer = ausgelagerte Wasserspiegellage
+
+state.chart.river = Gew\u00e4sser
+state.chart.type = Diagrammtyp
+
+chart.new.durationcurve = Dauerlinie
+chart.new.computeddischargecurve = Abflusskurve
+chart.new.longitudinal_section = L\u00e4ngsschnitt
+chart.new.w_differences = Differenzen
+chart.new.crosssection = Querprofil
+
+cross_section = Querprofil
+
+reference_curve = Bezugslinie
+reference_curve_normalized = Reduzierte Bezugslinie
+
+scenario.current = Aktuell
+scenario.potentiel = Potenziell
+scenario.scenario = Szenario
+
+floodplain.option = Talaue verwenden?
+floodplain.active = Aktiv
+floodplain.inactive = Inaktiv
+
+river = Fluss
+calculation_mode = Berechnungsart
+ld_locations = Ort(e)
+main.channel = Hauptgerinne
+total.channel = Gesamtgerinne
+
+chart.cross_section.title = Querprofildiagram f\u00fcr Gew\u00e4sser {0}
+chart.cross_section.subtitle = {0}-km: {1,number,#.###}
+chart.cross_section.xaxis.label = Abstand [m]
+chart.cross_section.yaxis.label = W [NN + m]
+
+chart.longitudinal.section.title = W-L\u00e4ngsschnitt
+chart.longitudinal.section.subtitle = Bereich: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.shortsubtitle = {0}
+chart.longitudinal.section.xaxis.label = {0}-km
+chart.longitudinal.section.yaxis.label = W [{0}]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.longitudinal.annotations.label = {0}.km
+chart.discharge.curve.title = Abflusskurve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.discharge.curve.curve.valid.from = {0} (g\u00fcltig ab {1,date,medium})
+chart.discharge.curve.curve.valid.range = {0} (g\u00fcltig ab {1,date,medium} - {2,date,medium})
+chart.computed.discharge.curve.title = Abflusskurve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.computed.discharge.curve.curve.label = Abflusskurve {0} km {1}
+chart.duration.curve.title = Dauerlinie
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Unterschreitungsdauer [Tage]
+chart.duration.curve.yaxis.label = W [NN + m]
+chart.duration.curve.curve.w = Wasserstandsdauerline f\u00fcr {0} (km:{1})
+chart.duration.curve.curve.q = Abflussdauerline f\u00fcr {0} (km:{1})
+chart.historical.discharge.title = Historische Abflusskurven
+chart.historical.discharge.subtitle = Pegel {0}
+chart.historical.discharge.xaxis.label = Zeit
+chart.historical.discharge.yaxis.label = Q [m\u00b3/s]
+chart.historical.discharge.yaxis.second.label = W [cm]
+
+chart.reference.curve.title = Bezugslinie
+chart.reference.curve.subtitle = {0}
+
+chart.reference.curve.x.axis.in.cm = Bezugspegel [cm]
+chart.reference.curve.x.axis.in.m = Bezugsort(e) [NN + m]
+chart.reference.curve.y.axis.in.cm = Zielpegel [cm]
+chart.reference.curve.y.axis.in.m = Zielort(e) [NN + m] 
+
+chart.normalized.reference.curve.title = Reduzierte Bezugslinie
+
+chart.w_differences.title = Differenzen
+chart.w_differences.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.w_differences.yaxis.label = m
+chart.w_differences.yaxis.second.label = W [NN + m]
+
+facet.longitudinal_section.annotations = Streckenfavoriten
+facet.discharge_curves.mainvalues.q = Q (Haupt- und Extremwerte)
+facet.discharge_curves.mainvalues.w = W (Haupt- und Extremwerte)
+facet.flow_velocity.mainchannel = v Hauptgerinne bei {0}
+facet.flow_velocity.totalchannel = v Gesamtgerinne bei {0}
+facet.flow_velocity.tauchannel = TAU Hauptgerinne bei {0}
+facet.bedheight_middle.single = Sohlh\u00f6he {0,number,####}
+facet.bedheight_middle.epoch = Sohlh\u00f6he Epoche {0,number,####} - {1,number,####}
+
+export.waterlevel.csv.header.km = Fluss-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.waterlevel.csv.header.q.desc = Bezeichnung
+export.waterlevel.csv.header.location = Lage
+export.waterlevel.csv.header.gauge = Bezugspegel
+export.waterlevel.csv.meta.result = # Ergebnisausgabe - {0} - Wasserstand - FLYS 3
+export.waterlevel.csv.meta.creation = # Datum der Erstellung: {0}
+export.waterlevel.csv.meta.calculationbase = # Berechnungsgrundlage: {0}
+export.waterlevel.csv.meta.river = # Gew\u00e4sser: {0}
+export.waterlevel.csv.meta.range = # Ort/Bereich (km): {0} - {1}
+export.waterlevel.csv.meta.gauge = # Bezugspegel: {0}
+export.waterlevel.csv.meta.q = # Q (m\u00b3/s): {0}
+export.waterlevel.csv.meta.w = # W (NN + m): {0} - {1}
+export.waterlevel.csv.not.in.gauge.range = au\u00dferhalb gew\u00e4hlter Bezugspegels
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Tagen]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = Fluss-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W korr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
+export.discharge.curve.at.header = Berechnete Abflusskurve f\u00fcr {0} {0}-km: {1}
+export.historical.discharge.csv.header.timerange = Zeitraum
+export.historical.discharge.csv.header.waterlevel = Wasserstand
+export.historical.discharge.csv.header.discharge = Abfluss
+export.historical.discharge.csv.header.diff = Abflussdifferenz zur Bezugskurve
+export.historical.discharge.csv.header.gaugename = Pegelname
+export.reference_curve.csv.header.km = km
+export.reference_curve.csv.header.w.cm = W (cm am Pegel)
+export.reference_curve.csv.header.w.m = W (m + NHN)
+export.reference_curve.csv.header.w.q = gleichw. Q (m\u00b3/s)
+
+export.waterlevel.pdf.mode = Wasserstand
+export.computed.discharge.pdf.mode = Abflusskurve
+export.duration.pdf.mode = Dauerline
+export.wdifferences.pdf.mode = W Differenzen
+export.historical.discharge.pdf.mode = Historischer Abfluss
+export.reference_curve.pdf.mode = Bezugslinie
+export.computed.discharge.pdf.file = /jasper/computed-discharge.jasper
+export.duration.pdf.file = /jasper/duration.jasper
+export.waterlevel.pdf.file = /jasper/waterlevel.jasper
+export.wdifferences.pdf.file = /jasper/wdifferences.jasper
+export.historical.discharge.pdf.file = /jasper/historical-discharge.jasper
+export.reference_curve.pdf.file = /jasper/reference.jasper
+export.reference_curve.pdf.file.gauge = /jasper/reference_gauge.jasper
+export.reference_curve.pdf.file.gauge.end = /jasper/reference_gauge_end.jasper
+export.reference_curve.pdf.file.gauge.start.end = /jasper/reference_gauge_start_end.jasper
+export.flow_velocity.csv.header.km = Fluss-Km
+export.flow_velocity.csv.header.v_total = v Gesamtgerinne
+export.flow_velocity.csv.header.v_main = v Hauptgerinne
+export.flow_velocity.csv.header.tau_main = TAU Hauptgerinne
+export.flow_velocity.csv.header.q = Q [m\u00b3/s]
+export.flow_velocity.csv.header.locations = Streckendaten
+export.bedheight_middle.csv.header.km = Fluss-km
+export.bedheight_middle.csv.header.sounding = Peilung / Epoche
+export.bedheight_middle.csv.header.height = gemittelte Sohl\u00f6he
+export.bedheight_middle.csv.header.uncertainty = Unsicherheit [m]
+export.bedheight_middle.csv.header.datagap = Datenl\u00fccke
+export.bedheight_middle.csv.header.soundingwidth = Peilbreite [m]
+export.bedheight_middle.csv.header.width = morphologisch aktive Breite [m]
+export.bedheight_middle.csv.header.locations = Streckendaten
+
+floodmap.wmsbackground = Hintergrundkarte
+floodmap.riveraxis = Flussachse
+floodmap.uesk = \u00dcberschwemmungsfl\u00e4che
+floodmap.barriers = Digitalisierte Objekte
+floodmap.kms = Kilometrierung
+floodmap.qps = Querprofilspuren
+floodmap.hws = Hochwasserschutzanlagen
+floodmap.catchment = Einzugsgebiet
+floodmap.floodplain = Talaue
+floodmap.lines = Linien
+floodmap.buildings = Bauwerke
+floodmap.fixpoints = Festpunkte
+floodmap.floodmaps = \u00dcberschwemmungsfl\u00e4che
+floodmap.usershape = Benutzerdaten
+
+wsplgen.job.queued = WSPLGEN Berechnung befindet sich in Warteschlange.
+wsplgen.job.error = Ein unerwarteter Fehler beim Starten von WSPLGEN ist aufgetreten.
+
+wsp.selected.string = {0}
+
+Mosel = Mosel
+Saar = Saar
+Elbe = Elbe
+
+state.map.river = Gew\u00e4sser
+
+spline.interpolation.failed = Spline interpolation fehlgeschlagen.
+cannot.find.w.for.q = W zu Q = {0,number,#.##} kann nicht ermittelt werden.
+cannot.find.q = Q = {0,number,#.##} konnte nicht gefunden werden.
+no.ws.found = Keine Ws gefunden.
+no.q.found.in.column = In Spalte {0,integer} wurde kein Q gefunden.
+spline.creation.failed = Spline creation failed.
+cannot.find.w.or.q = W oder Q konnten nicht ermittelt werden.
+km.not.found = Passender Kilometer konnte nicht gefunden werden.
+cannot.create.wq.relation = W/Q-Beziehung konnte nicht ermittelt werden.
+cannot.create.index.q.relation = Spaltenindex/Q-Beziehung konnte nicht erstellt werden.
+
+w.w.qkm1.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen.
+w.w.wkm1.failed = Berechnung von W f\u00fcr Q = {0,number,#.##} / WST-Index {1,number,#.##} fehlgeschlagen.
+w.w.qkm2.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen. 
+w.w.wkm2.failed = Berechnung von W f\u00fcr Q = {0,number,#.##} / WST-Index {1,number,#.##} fehlgeschlagen. 
+
+cannot.find.hist.q.for.w = Konnte zu W = {0,number,#.##} im Zeitraum ({1,date} - {2,date}) kein Abfluss ermitteln.
+cannot.find.hist.q.tables = Konnte f\u00fcr den angegebenen Zeitraum keine Abflusstafeln finden.
+cannot.find.hist.q.reftable = Konnte f\u00fcr den gew\u00e4hlten Pegel keine Bezugs-Abflusstafel ermitteln.
+
+more.than.one.q.for.w = Mehr als ein Q f\u00fcr W = {0,number,#.##} gefunden.
+
+no.river.selected = Kein Gew\u00e4sser ausgew\u00e4hlt.
+no.gauge.selected = Kein Pegel ausgew\u00e4hlt.
+no.locations.selected = Keine Orte angegeben.
+no.kms.selected = Keine Kilometerstationen angegeben.
+converting.ws.to.qs.failed = Konvertierung von Ws zu Qs fehlgeschlagen.
+no.wst.for.river = Keine zum Gew\u00e4sser passende WST gefunden.
+no.range.found = Kein passender Pegel gefunden.
+no.gauge.found.for.km = Zur gegebenen Kilometerstation existiert kein Pegel.
+cannot.create.segments = Flussabschnitte konnten nicht erzeugt werden.
+cannot.compute.discharge.curve = Die Abflusskurve konnte nicht berechnet werden..
+cannot.find.ds = Dauerzahlen konnten nicht gefunden werden.
+no.segments.found = Keine Flussabschnitte gefunden.
+no.values.given = Keine Werte angegeben.
+cannot.interpolate.w.q = W/Q-Wert konnte nicht interpoliert werden.
+
+manualpoints = Manuelle Punkte
+
+no.reference.start.km = Keine Startkilometerstation angegeben.
+no.reference.end.kms = Keine Endkilometerstation(en) angegeben.
+waterlevels = Wasserst\u00e4nde
+
+help.index=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe
+help.winfo=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO
+help.winfo.wsp.location_distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.wsp.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_W.2BAC8-Q_Daten
+help.winfo.discharge.longitudinal.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Berechnungsstrecke
+help.winfo.duration.locations=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.discharge.longitudinal.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_f.2BAPw-r_W.2BAC8-Q_Daten
+help.winfo.diff.diffs=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Zusammenstellen_der_Differenzen
+help.winfo.reference.curve.start=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Bezugsortes_.2BAC8_Bezugspegels
+help.winfo.reference.curve.end=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Zielortes_.2BAC8_Zielpegels
+help.winfo.uesk.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Berechnungsstrecke
+help.winfo.uesk.wsp=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Wasserspiegellage
+help.winfo.uesk.dem=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Digitalen_Gel.2BAOQ-ndemodells
+help.winfo.uesk.profiles=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Abstand_Interpolierte_Profile
+help.winfo.uesk.floodplain=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Laterale_Begrenzung
+help.winfo.uesk.differences=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Differenz_Wasserspiegellage_und_Gel.2BAOQ-nde
+help.winfo.uesk.scenario=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#A.2BANw-berschwemmungsfl.2BAOQ-che_.2BAC8_Szenario
+help.winfo.historical.discharge.reference_gauge=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Bezugspegels
+help.winfo.historical.discharge.timerange=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_des_Auswertezeitraumes
+help.winfo.historical.discharge.mode=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_der_Analyseart
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/resources/messages_en.properties	Fri Sep 28 12:14:30 2012 +0200
@@ -0,0 +1,298 @@
+state.winfo.river = River
+state.winfo.calculation_mode = Calculation Mode
+state.winfo.location_distance = Location or distance selection
+state.winfo.wq = Input for W/Q data
+state.winfo.wq_adapted = Input for W/Q data
+state.winfo.location = Choose the location
+state.winfo.distance = Choose the range
+state.winfo.distance_only = Range selection
+state.winfo.uesk.wsp = Choose the waterlevel
+state.winfo.uesk.dgm = Digital Terrain Model
+state.winfo.uesk.profiles = Interpolated Profiles
+state.winfo.uesk.floodplain = Lateral Boundary
+state.winfo.uesk.differences = Differences between waterlevel and terrain
+state.winfo.uesk.scenario = Flood Plain / Scenario
+state.winfo.waterlevel_pair_select = Chosen Differences
+state.winfo.historicalq.reference_gauge = Selection of Reference Gauge
+state.winfo.historicalq.timerange = Selection of Evaluation time
+state.winfo.historicalq.mode = Selecion of analyses
+state.winfo.reference.curve.input.start = Chosen Reference
+state.winfo.reference.curve.input.end = Chosen Evaluation
+state.fix.river = River
+state.fix.calculation.mode = Calculation Mode
+state.fix.location = Distance
+state.fix.period = Period
+state.fix.gaugerange = Gaugerange
+state.fix.eventselect = Event selection
+state.fix.analysis.referenceperiod = Reference period
+state.fix.analysis.analysisperiods = Analysis period
+state.fix.analysis.function = Function
+state.fix.analysis.preprocessing = Preprocessing
+
+state.minfo.river = River
+state.minfo.calculation_mode = Calculation Mode
+state.minfo.distance_only = Range selection
+state.minfo.dischargestate = Selection of discharge state and channel
+
+historical.mode.w = Waterlevel Analyse
+historical.mode.q = Discharge Analyse
+
+calc.surface.curve = Water Level/Surface Curve
+calc.flood.map = Flood Plain
+calc.discharge.curve = State Discharge Curve/Stage Discharge Relation
+calc.duration.curve = Duration Curve
+calc.discharge.longitudinal.section = TODO (W bei...)
+calc.w.differences = Differences
+calc.historical.discharge.curve = Historical Discharge Curve
+calc.reference.curve = Reference Curve
+calc.fixation.default = Fixation
+calc.fixation.volmer = Balanced Waterlevel
+calc.bed.middle = Middle Bed Height
+calc.bed.diff = Bed Height Difference
+calc.bed.quality = Bed Quality
+calc.sediment.load = Sediment Load
+calc.flow.velocity = Flow Velocity
+calc.sq.relation = Load Discharge Relation
+
+calculation.analysis = Fixinganalysis
+calculation.volmer = relocated Waterlevel Calculation
+
+state.chart.river = River
+state.chart.type = Charttype
+
+chart.new.durationcurve = Duration Curve
+chart.new.computeddischargecurve = Discharge Curve
+chart.new.longitudinal_section = Longitudinal Section
+chart.new.w_differences = Differences
+chart.new.crosssection = Cross Section
+
+cross_section = Cross Section
+
+reference_curve = Reference Curve
+reference_curve_normalized = Normalized Reference Curve
+
+scenario.current = Current
+scenario.potentiel = Potentiel
+scenario.scenario = Scenario
+
+floodplain.option = Use Floodplain?
+floodplain.active = Activ
+floodplain.inactive = Inactiv
+
+river = River
+calculation_mode = Calculation Mode
+ld_locations = Location(s)
+main.channel = Main channel
+total.channel = Total channel
+
+chart.cross_section.title = Cross Section for river {0}
+chart.cross_section.subtitle = {0}-km: {1,number,#.###}
+chart.cross_section.xaxis.label = Distance [m]
+chart.cross_section.yaxis.label = W [NN + m]
+
+chart.longitudinal.section.title = W-Longitudinal Section
+chart.longitudinal.section.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.longitudinal.section.shortsubtitle = {0}
+chart.longitudinal.section.xaxis.label = {0}-km
+chart.longitudinal.section.yaxis.label = W [{0}]
+chart.longitudinal.section.yaxis.second.label = Q [m\u00b3/s]
+chart.longitudinal.annotations.label = {0}.km
+chart.discharge.curve.title = Discharge Curve
+chart.discharge.curve.xaxis.label = Q [m\u00b3/s]
+chart.discharge.curve.yaxis.label = W [cm]
+chart.discharge.curve.curve.valid.from = {0} (valid from {1,date,short})
+chart.discharge.curve.curve.valid.range = {0} (valid from {1,date,short} - {2,date,short})
+chart.computed.discharge.curve.title = Discharge Curve
+chart.computed.discharge.curve.subtitle = {0}-km: {1,number,#.###}
+chart.computed.discharge.curve.yaxis.label = W [NN + m]
+chart.computed.discharge.curve.curve.label = Discharge Curve {0} km {1}
+chart.duration.curve.title = Duration Curve
+chart.duration.curve.subtitle = {0}-km: {1,number,#.###}
+chart.duration.curve.xaxis.label = Duration of Non-Exceedence [Days]
+chart.duration.curve.yaxis.label = W [NN + m]
+chart.duration.curve.curve.w = Waterlevel duration curve for {0} (km: {1})
+chart.duration.curve.curve.q = Discharge duration curve for {0} (km: {1})
+chart.historical.discharge.title = Historical Discharge Curves for Gauge {0}
+chart.historical.discharge.subtitle = Gauge {0}
+chart.historical.discharge.xaxis.label = Time
+chart.historical.discharge.yaxis.label = Q [m\u00b3/s]
+chart.historical.discharge.yaxis.second.label = W [cm]
+
+chart.reference.curve.title = Reference Curve
+chart.reference.curve.subtitle = {0}
+
+chart.reference.curve.x.axis.in.cm = Reference Gauge(s) [cm]
+chart.reference.curve.x.axis.in.m = Reference Station(s) [NN + m]
+chart.reference.curve.y.axis.in.cm = Target Gauge(s) [cm]
+chart.reference.curve.y.axis.in.m = Target Station(s) [NN + m] 
+
+chart.normalized.reference.curve.title = Reduced Reference Curve
+
+chart.w_differences.title = Differences
+chart.w_differences.subtitle = Range: {0}-km {1,number,#.###} - {2,number,#.###}
+chart.w_differences.yaxis.label = m
+chart.w_differences.yaxis.second.label = W [NN + m]
+
+facet.longitudinal_section.annotations = POIs
+facet.discharge_curves.mainvalues.q = Q (main values)
+facet.discharge_curves.mainvalues.w = W (main values)
+facet.flow_velocity.mainchannel = v Mainchannel at {0}
+facet.flow_velocity.totalchannel = v Totalchannel at {0}
+facet.flow_velocity.tauchannel = TAU Mainchannel at {0}
+facet.bedheight_middle.single = Bed Height {0,number,####}
+facet.bedheight_middle.epoch = Bed Height Epoch {0,number,####} - {1,number,####}
+
+export.waterlevel.csv.header.km = River-Km
+export.waterlevel.csv.header.w = W [NN + m]
+export.waterlevel.csv.header.q = Q [m\u00b3/s]
+export.waterlevel.csv.header.q.desc = Description
+export.waterlevel.csv.header.location = Location
+export.waterlevel.csv.header.gauge = Reference Gauge
+export.waterlevel.csv.meta.result = # Calculation Output - {0} - Waterlevel - FLYS 3
+export.waterlevel.csv.meta.creation = # Time of creation: {0}
+export.waterlevel.csv.meta.calculationbase = # Calculation base: {0}
+export.waterlevel.csv.meta.river = # River: {0}
+export.waterlevel.csv.meta.range = # Location/Range (km): {0} - {1}
+export.waterlevel.csv.meta.gauge = # Gauge: {0}
+export.waterlevel.csv.meta.q = # Q (m\u00b3/s): {0}
+export.waterlevel.csv.meta.w = # W (NN + m): {0} - {1}
+export.waterlevel.csv.not.in.gauge.range = Outside selected gauge
+export.computed.discharge.curve.csv.header.w = W [NN + m]
+export.computed.discharge.curve.csv.header.q = Q [m\u00b3/s]
+export.duration.curve.csv.header.duration = D [Days]
+export.duration.curve.csv.header.w = W [NN + m]
+export.duration.curve.csv.header.q = Q [m\u00b3/s]
+export.discharge.longitudinal.section.csv.header.km = River-Km
+export.discharge.longitudinal.section.csv.header.w = W [NN + m]
+export.discharge.longitudinal.section.csv.header.cw = W corr.
+export.discharge.longitudinal.section.csv.header.q = Q [m\u00b3/s]
+export.discharge.curve.at.header = Computed Discharge Curve for {0} {0}-km: {1}
+export.historical.discharge.csv.header.timerange = Timerange
+export.historical.discharge.csv.header.waterlevel = Waterlevel
+export.historical.discharge.csv.header.discharge = Discharge
+export.historical.discharge.csv.header.diff = Difference
+export.historical.discharge.csv.header.gaugename = Gaugename
+export.reference_curve.csv.header.km = km
+export.reference_curve.csv.header.w.cm = W (cm at Gauge)
+export.reference_curve.csv.header.w.m = W (m + NHN)
+export.reference_curve.csv.header.w.q = equiv. Q (m\u00b3/s)
+
+export.waterlevel.pdf.mode = Waterlevel
+export.computed.discharge.pdf.mode = Computed Dischargecurve
+export.duration.pdf.mode = Durationcurve
+export.wdifferences.pdf.mode = W Differences
+export.historical.discharge.pdf.mode = Historical Discharge
+export.reference_curve.pdf.mode = Reference Curve
+export.computed.discharge.pdf.file = /jasper/computed-discharge_en.jasper
+export.duration.pdf.file = /jasper/duration_en.jasper
+export.waterlevel.pdf.file = /jasper/waterlevel_en.jasper
+export.wdifferences.pdf.file = /jasper/wdifferences_en.jasper
+export.historical.discharge.pdf.file = /jasper/historical-discharge_en.jasper
+export.reference_curve.pdf.file = /jasper/reference_en.jasper
+export.reference_curve.pdf.file.gauge = /jasper/reference_en_gauge.jasper
+export.reference_curve.pdf.file.gauge.end = /jasper/reference_en_gauge_end.jasper
+export.reference_curve.pdf.file.gauge.start.end = /jasper/reference_en_gauge_start_end.jasper
+export.flow_velocity.csv.header.km = River Km
+export.flow_velocity.csv.header.v_total = v Total Channel
+export.flow_velocity.csv.header.v_main = v Main Channel
+export.flow_velocity.csv.header.tau_main = TAU Main Channel
+export.flow_velocity.csv.header.q = Q [m\u00b3/s]
+export.flow_velocity.csv.header.locations = Location
+export.bedheight_middle.csv.header.km = River km
+export.bedheight_middle.csv.header.sounding = Sounding / Epoch
+export.bedheight_middle.csv.header.height = middle Bed Height
+export.bedheight_middle.csv.header.uncertainty = Uncertainty [m]
+export.bedheight_middle.csv.header.datagap = Data Gap
+export.bedheight_middle.csv.header.soundingwidth = Sounding Width [m]
+export.bedheight_middle.csv.header.width = morphological active width [m]
+export.bedheight_middle.csv.header.locations = Location
+
+floodmap.wmsbackground = Background Map
+floodmap.riveraxis = River Axis
+floodmap.uesk = Floodmap
+floodmap.barriers = Digitized Objects
+floodmap.kms = Kilometrage
+floodmap.qps = Crosssection Tracks
+floodmap.hws = Flood Control Works
+floodmap.catchment = Catchment
+floodmap.floodplain = Floodplain
+floodmap.lines = Lines
+floodmap.buildings = Buildings
+floodmap.fixpoints = Fixpoints
+floodmap.floodmaps = Floodmaps
+floodmap.usershape = User data
+
+wsplgen.job.queued = WSPLGEN job in queue.
+wsplgen.job.error = An unexpected error while starting WSPLGEN occured.
+
+wsp.selected.string = {0}
+
+Mosel = Mosel
+Saar = Saar
+Elbe = Elbe
+
+state.map.river = River
+
+spline.interpolation.failed = Spline interpolation failed.
+cannot.find.w.for.q = Cannot find W for Q = {0,number,#.##}.
+cannot.find.q = Cannot find Q = {0,number,#.##}.
+no.ws.found = No Ws found.
+no.q.found.in.column = No Q found in {0,integer} column.
+spline.creation.failed = Spline creation failed.
+cannot.find.w.or.q = Cannot find W or Q.
+km.not.found = Cannot find km.
+cannot.create.wq.relation = Cannot create W/Q relation.
+cannot.create.index.q.relation = Cannot create index/Q relation.
+
+w.w.qkm1.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm1.failed = Calculating W for Q = {0,number,#.##} /  WST index {1,number,#.##} failed.
+w.w.qkm2.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm2.failed = Calculating W for Q = {0,number,#.##} /  WST index {1,number,#.##} failed.
+
+cannot.find.hist.q.for.w = Cannot find Q for W = {0,number,#.##} in timerange {1, date} - {2, date}
+cannot.find.hist.q.tables = Cannot find Discharge Tables for given timerange.
+cannot.find.hist.q.reftable = Cannot find reference Discharge Table for specified Gauge.
+
+more.than.one.q.for.w = Found more Qs for W = {0,number,#.##}.
+
+no.river.selected = No river selected.
+no.gauge.selected = No gauge selected.
+no.locations.selected = No locations selected.
+no.kms.selected = No KMs selected.
+converting.ws.to.qs.failed = Converting Ws to Qs failed.
+no.wst.for.river = No WST found for selected river.
+no.range.found = No range found.
+no.gauge.found.for.km = No gauge found for KM.
+cannot.create.segments = Cannot create segments.
+cannot.compute.discharge.curve = Cannot create discharge curve.
+cannot.find.ds = Cannot find Ds.
+no.segments.found = No segments found.
+no.values.given = No values given.
+cannot.interpolate.w.q = Cannot interpolate W/Q.
+
+manualpoints = Manual Points
+
+no.reference.start.km = No reference start station given.
+no.reference.end.kms = No reference end station(s) given.
+waterlevels = Waterlevels
+
+help.index=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe
+help.winfo=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO
+help.winfo.wsp.location_distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.wsp.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_W.2BAC8-Q_Daten
+help.winfo.discharge.longitudinal.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Berechnungsstrecke
+help.winfo.duration.locations=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Streckendaten
+help.winfo.discharge.longitudinal.wq=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_f.2BAPw-r_W.2BAC8-Q_Daten
+help.winfo.diff.diffs=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Zusammenstellen_der_Differenzen
+help.winfo.reference.curve.start=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Bezugsortes_.2BAC8_Bezugspegels
+help.winfo.reference.curve.end=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_des_Zielortes_.2BAC8_Zielpegels
+help.winfo.uesk.distance=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Berechnungsstrecke
+help.winfo.uesk.wsp=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_der_Wasserspiegellage
+help.winfo.uesk.dem=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Digitalen_Gel.2BAOQ-ndemodells
+help.winfo.uesk.profiles=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Abstand_Interpolierte_Profile
+help.winfo.uesk.floodplain=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Laterale_Begrenzung
+help.winfo.uesk.differences=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Eingabe_Differenz_Wasserspiegellage_und_Gel.2BAOQ-nde
+help.winfo.uesk.scenario=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#A.2BANw-berschwemmungsfl.2BAOQ-che_.2BAC8_Szenario
+help.winfo.historical.discharge.reference_gauge=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Wahl_des_Bezugspegels
+help.winfo.historical.discharge.timerange=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_des_Auswertezeitraumes
+help.winfo.historical.discharge.mode=https://flys-intern.intevation.de/Flys-3.0/OnlineHilfe/WINFO#Auswahl_der_Analyseart

http://dive4elements.wald.intevation.org