changeset 686:3dc61e00385e facets-slt

Merged with trunk and introduced hashing of computed values. flys-artifacts/branches/facets-slt@2126 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Wed, 15 Jun 2011 15:28:54 +0000
parents 434146596838
children 06689035024c
files flys-artifacts/ChangeLog flys-artifacts/doc/conf/cache.xml flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation1.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation2.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation3.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation4.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ComputeCallback.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Segment.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQCKms.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQDay.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKms.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DefaultState.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java flys-artifacts/src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java flys-artifacts/src/main/resources/messages.properties flys-artifacts/src/main/resources/messages_de.properties flys-artifacts/src/main/resources/messages_de_DE.properties flys-artifacts/src/main/resources/messages_en.properties
diffstat 29 files changed, 1223 insertions(+), 310 deletions(-) [+]
line wrap: on
line diff
--- a/flys-artifacts/ChangeLog	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/ChangeLog	Wed Jun 15 15:28:54 2011 +0000
@@ -1,3 +1,160 @@
+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:
--- a/flys-artifacts/doc/conf/cache.xml	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/doc/conf/cache.xml	Wed Jun 15 15:28:54 2011 +0000
@@ -36,4 +36,14 @@
            timeToLiveSeconds="86400"
            memoryStoreEvictionPolicy="LFU"
            />
+
+    <!-- This one is used to cache the computed values.-->
+    <cache name="computed.values"
+           maxElementsInMemory="1000"
+           eternal="false"
+           timeToLiveSeconds="172800"
+           overflowToDisk="true"
+           diskPersistent="true"
+           memoryStoreEvictionPolicy="LFU"
+           />
 </ehcache>
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java	Wed Jun 15 15:28:54 2011 +0000
@@ -3,7 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.TreeMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -12,6 +12,8 @@
 
 import gnu.trove.TDoubleArrayList;
 
+import net.sf.ehcache.Cache;
+
 import org.apache.log4j.Logger;
 
 import org.w3c.dom.Document;
@@ -40,6 +42,9 @@
 
 import de.intevation.flys.artifacts.context.FLYSContext;
 
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.artifacts.model.ComputeCallback;
 import de.intevation.flys.artifacts.model.DischargeTables;
 import de.intevation.flys.artifacts.model.RiverFactory;
 import de.intevation.flys.artifacts.model.Segment;
@@ -59,6 +64,8 @@
     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";
@@ -98,7 +105,7 @@
      * The default constructor that creates an empty FLYSArtifact.
      */
     public FLYSArtifact() {
-        data             = new HashMap<String, StateData>();
+        data             = new TreeMap<String, StateData>();
         previousStateIds = new ArrayList<String>();
     }
 
@@ -1001,6 +1008,54 @@
         return DoubleUtil.explode(from, to, step);
     }
 
+
+    /**
+     * Computes the hash code of the entered values.
+     *
+     * @return a hash code.
+     */
+    @Override
+    public String hash() {
+        Set<Map.Entry<String, StateData>> entries = data.entrySet();
+
+        int hash  = 0;
+        int shift = 3;
+
+        for (Map.Entry<String, StateData> entry: entries) {
+            String key   = entry.getKey();
+            Object value = entry.getValue().getValue();
+
+            hash ^= (key.hashCode() << shift) | (value.hashCode() << 2 * shift);
+            shift += 2;
+        }
+
+        return getCurrentStateId() + hash;
+    }
+
+
+    public Object compute(String key, ComputeCallback callback) {
+        Cache cache = CacheFactory.getCache(COMPUTING_CACHE);
+
+        if (cache == null) {
+            return callback.compute();
+        }
+
+        net.sf.ehcache.Element element = cache.get(key);
+        if (element != null) {
+            logger.debug("Got computation values from cache.");
+            return element.getValue();
+        }
+
+        Object result = callback.compute();
+
+        if (result != null) {
+            cache.put(new net.sf.ehcache.Element(key, result));
+        }
+
+        return result;
+    }
+
+
     /**
      * Method to dump the artifacts state/data.
      */
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java	Wed Jun 15 15:28:54 2011 +0000
@@ -1,9 +1,6 @@
 package de.intevation.flys.artifacts;
 
 import java.util.List;
-import java.util.Set;
-import java.util.HashSet;
-import java.util.ArrayList;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -25,15 +22,19 @@
 import de.intevation.flys.model.Gauge;
 import de.intevation.flys.model.River;
 
+import de.intevation.flys.artifacts.states.CalculationSelect;
 import de.intevation.flys.artifacts.states.DefaultState;
 import de.intevation.flys.artifacts.context.FLYSContext;
 
+import de.intevation.flys.artifacts.model.ComputeCallback;
 import de.intevation.flys.artifacts.model.MainValuesFactory;
 import de.intevation.flys.artifacts.model.WQDay;
 import de.intevation.flys.artifacts.model.WQKms;
 import de.intevation.flys.artifacts.model.WstValueTable;
-import de.intevation.flys.artifacts.model.WstValueTable.QPosition;
 import de.intevation.flys.artifacts.model.WstValueTableFactory;
+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.Segment;
 
@@ -241,6 +242,76 @@
     // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
     //
 
+
+    public Object compute() {
+        return compute(hash());
+    }
+
+
+    public Object compute(String hash) {
+        String calc = (String) getData(CalculationSelect.FIELD_MODE).getValue();
+
+        ComputeCallback callback = null;
+
+        if (calc.equals(CalculationSelect.CALCULATION_SURFACE_CURVE)) {
+            callback = createSurfaceCurveCallback();
+        }
+        else if (calc.equals(CalculationSelect.CALCULATION_DURATION_CURVE)) {
+            callback = createDurationCurveCallback();
+        }
+        else if (
+            calc.equals(
+                CalculationSelect.CALCULATION_DISCHARGE_LONGITUDINAL_CURVE))
+        {
+            callback = createDischargeLongitudinalCurveCallback();
+        }
+        else if (calc.equals(CalculationSelect.CALCULATION_DISCHARGE_CURVE)) {
+            callback = createDischargeCurveCallback();
+        }
+        else {
+            return null;
+        }
+
+        return compute(hash, callback);
+    }
+
+
+    protected ComputeCallback createSurfaceCurveCallback() {
+        return new ComputeCallback() {
+            public Object compute() {
+                return getWaterlevelData();
+            }
+        };
+    }
+
+
+    protected ComputeCallback createDurationCurveCallback() {
+        return new ComputeCallback() {
+            public Object compute() {
+                return getDurationCurveData();
+            }
+        };
+    }
+
+
+    protected ComputeCallback createDischargeLongitudinalCurveCallback() {
+        return new ComputeCallback() {
+            public Object compute() {
+                return getDischargeLongitudinalSectionData();
+            }
+        };
+    }
+
+
+    protected ComputeCallback createDischargeCurveCallback() {
+        return new ComputeCallback() {
+            public Object compute() {
+                return getComputedDischargeCurveData();
+            }
+        };
+    }
+
+
     /**
      * Returns the data that is computed by a waterlevel computation.
      *
@@ -277,43 +348,12 @@
             throw new NullPointerException("No Wst found for selected river.");
         }
 
-        HashSet<Integer> failed = new HashSet<Integer>();
-
         WQKms[] results = computeWaterlevelData(
-            kms, qs, wst, river.getKmUp(), failed);
-
-        // TODO Introduce a caching mechanism here!
-
-        setWaterlevelNames(
-            results, qSel ? qs : ws, qSel ? "Q" : "W", failed);
+            kms, qs, ws, wst, river.getKmUp());
 
         return results;
     }
 
-
-    /**
-     * Sets the name for waterlevels where each WQKms in <i>r</i> represents a
-     * column.
-     *
-     * @param r The waterlevel columns.
-     * @param v The input values of the computations.
-     * @param wq The WQ mode - can be one of "W" or "Q".
-     */
-    public static void setWaterlevelNames(
-        WQKms[]  r, 
-        double[] v, 
-        String   wq, 
-        Set      failed
-    ) {
-        int pos = 0;
-        for (int i = 0; i < v.length; i++) {
-            if (!failed.contains(i)) {
-                r[pos++].setName(wq + "=" + v[i]);
-            }
-        }
-    }
-
-
     /**
      * Computes the data of a waterlevel computation based on the interpolation
      * in WstValueTable.
@@ -325,35 +365,19 @@
      * @return an array of data triples that consist of W, Q and Kms.
      */
     public static WQKms[] computeWaterlevelData(
-        double[]      kms,
-        double[]      qs,
+        double []     kms,
+        double []     qs,
+        double []     ws,
         WstValueTable wst,
-        boolean       up,
-        Set<Integer>  failed
+        boolean       up
     ) {
         logger.info("WINFOArtifact.computeWaterlevelData");
 
-        WQKms[] wqkms = new WQKms[qs.length];
-
-        ArrayList<WQKms> results = new ArrayList<WQKms>();
-
-        int referenceIndex = up ? 0 : kms.length-1; 
+        Calculation1 calculation = new Calculation1(kms, qs, ws, up);
 
-        for (int i = 0; i < qs.length; i++) {
-            double [] oqs = new double[kms.length];
-            double [] ows = new double[kms.length];
-            WstValueTable.QPosition qPosition =
-                wst.interpolate(qs[i], kms[referenceIndex], kms, ows, oqs);
-            if (qPosition != null) {
-                results.add(new WQKms(kms, oqs, ows));
-            }
-            else {
-                logger.warn("interpolation failed for q = " + qs[i]);
-                failed.add(i);
-            }
-        }
+        WQKms[] wqkms = calculation.calculate(wst);
 
-        return results.toArray(new WQKms[results.size()]);
+        return wqkms;
     }
 
 
@@ -390,8 +414,6 @@
             throw new NullPointerException("No Wst found for selected river.");
         }
 
-        // TODO Introduce a caching mechanism here!
-
         return computeDurationCurveData(g, wst, locations[0]);
     }
 
@@ -416,16 +438,10 @@
         int[]    days = (int[]) obj[0];
         double[] qs   = (double[]) obj[1];
 
-        double[] interpolatedW = new double[qs.length];
-        interpolatedW          = wst.interpolateW(location, qs, interpolatedW);
-
-        WQDay wqday = new WQDay(qs.length);
+        Calculation3 calculation = new Calculation3(location, days, qs);
 
-        for (int i = 0; i < days.length; i++) {
-            wqday.add(days[i], interpolatedW[i], qs[i]);
-        }
-
-        return wqday;
+        // TODO: report the errors to the user.
+        return calculation.calculate(wst);
     }
 
 
@@ -458,26 +474,11 @@
 
         WQKms wqkms = computeDischargeCurveData(wst, locations[0]);
 
-        // TODO Introduce a caching mechanism here!
-
-        setComputedDischargeCurveNames(wqkms, locations[0]);
-
         return wqkms;
     }
 
 
     /**
-     * Sets the name of the computed discharge curve data.
-     *
-     * @param wqkms The computed WQKms object.
-     * @param l The location used for the computation.
-     */
-    public static void setComputedDischargeCurveNames(WQKms wqkms, double l) {
-        wqkms.setName(Double.toString(l));
-    }
-
-
-    /**
      * Computes the data used to create computed discharge curves.
      *
      * @param wst The WstValueTable that is used for the interpolation.
@@ -492,21 +493,11 @@
     {
         logger.info("WINFOArtifact.computeDischargeCurveData");
 
-        double[][] wqs = wst.interpolateWQ(location);
-
-        if (wqs == null) {
-            logger.error("Cannot compute discharge curve data.");
-            return null;
-        }
+        Calculation2 calculation = new Calculation2(location);
 
-        double[] ws = wqs[0];
-        double[] qs = wqs[1];
+        WQKms wqkms = calculation.calculate(wst);
 
-        WQKms wqkms = new WQKms(ws.length);
-
-        for (int i = 0; i < ws.length; i++) {
-            wqkms.add(ws[i], qs[i], location);
-        }
+        // TODO: Report errors to the user
 
         return wqkms;
     }
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/math/BackJumpCorrector.java	Wed Jun 15 15:28:54 2011 +0000
@@ -15,6 +15,8 @@
 
 import org.apache.log4j.Logger;
 
+import de.intevation.flys.artifacts.model.Calculation;
+
 public class BackJumpCorrector
 implements   Serializable
 {
@@ -40,8 +42,11 @@
         return corrected;
     }
 
-    public boolean doCorrection(double [] km, double [] ws) {
-
+    public boolean doCorrection(
+        double []   km,
+        double []   ws,
+        Calculation errors
+    ) {
         boolean wsUp = isIncreasing(ws);
 
         if (wsUp) {
@@ -62,7 +67,7 @@
             log.debug("BackJumpCorrector.doCorrection ------- leave");
         }
 
-        boolean hasBackJumps = doCorrectionClean(km, ws);
+        boolean hasBackJumps = doCorrectionClean(km, ws, errors);
 
         if (hasBackJumps && wsUp) {
             // mirror back
@@ -72,7 +77,11 @@
         return hasBackJumps;
     }
 
-    protected boolean doCorrectionClean(double [] km, double [] ws) {
+    protected boolean doCorrectionClean(
+        double []   km,
+        double []   ws,
+        Calculation errors
+    ) {
         int N = km.length;
 
         if (N != ws.length) {
@@ -201,6 +210,8 @@
                 spline = interpolator.interpolate(x, y);
             }
             catch (MathIllegalArgumentException miae) {
+                // TODO: I18N
+                errors.addProblem("creating spline interpolation failed.");
                 log.error(miae);
                 continue;
             }
@@ -226,6 +237,8 @@
                 }
             }
             catch (ArgumentOutsideDomainException aode) {
+                // TODO: I18N
+                errors.addProblem("spline interpolation failed.");
                 log.error("spline interpolation failed", aode);
             }
         } // for all km
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation.java	Wed Jun 15 15:28:54 2011 +0000
@@ -0,0 +1,87 @@
+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.io.Serializable;
+
+public class Calculation
+implements   Serializable
+{
+    public static class Problem 
+    implements          Serializable
+    {
+        protected Double km;
+        protected String msg;
+
+        public Problem() {
+        }
+
+        public Problem(String msg) {
+            this.msg = msg;
+        }
+
+        public Problem(double km, String msg) {
+            this.km  = km;
+            this.msg = msg;
+        }
+
+        public Element toXML(Document document) {
+            Element problem = document.createElement("problem");
+            if (km != null) {
+                problem.setAttribute("km", String.valueOf(km));
+            }
+            problem.setTextContent(msg);
+            return problem;
+        }
+    } // class Problem
+
+    protected List<Problem> problems;
+
+    public Calculation() {
+    }
+
+    protected List<Problem> checkProblems() {
+        if (problems == null) {
+            problems = new ArrayList<Problem>();
+        }
+        return problems;
+    }
+
+    public void addProblem(String msg) {
+        checkProblems().add(new Problem(msg));
+    }
+
+    public void addProblem(double km, String msg) {
+        checkProblems().add(new Problem(km, msg));
+    }
+
+    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) {
+
+        Element root = document.createElement("problems");
+
+        if (hasProblems()) {
+            for (Problem problem: problems) {
+                root.appendChild(problem.toXML(document));
+            }
+        }
+
+        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	Wed Jun 15 15:28:54 2011 +0000
@@ -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 boolean   up;
+
+    public Calculation1() {
+    }
+
+    public Calculation1(
+        double [] kms, 
+        double [] qs, 
+        double [] ws,
+        boolean   up
+    ) {
+        this.kms = kms;
+        this.qs  = qs;
+        this.ws  = ws;
+        this.up  = up;
+    }
+
+    public WQKms [] calculate(WstValueTable wst) {
+
+        ArrayList<WQKms> results = new ArrayList<WQKms>();
+
+        double ref = kms[up ? 0 : kms.length-1];
+
+        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], ref, 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 results.toArray(new WQKms[results.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/Calculation2.java	Wed Jun 15 15:28:54 2011 +0000
@@ -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 WQKms calculate(WstValueTable wst) {
+
+        logger.debug("Calculation2.calculate");
+
+        double [][] wqs = wst.interpolateWQ(km, this);
+
+        if (wqs == null || wqs[0].length == 0) {
+            logger.debug("Cannot compute discharge curve data.");
+            return null;
+        }
+
+        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 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/Calculation3.java	Wed Jun 15 15:28:54 2011 +0000
@@ -0,0 +1,37 @@
+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 WQDay calculate(WstValueTable wst) {
+
+        double [] ws = wst.interpolateW(km, qs, new double[qs.length], this);
+
+        WQDay wqday = new WQDay(days, ws, qs);
+
+        if (hasProblems()) {
+            logger.debug("calculation caused "+numProblems()+" problem(s).");
+            wqday.removeNaNs();
+        }
+
+        return wqday;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation4.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Calculation4.java	Wed Jun 15 15:28:54 2011 +0000
@@ -20,6 +20,7 @@
 import org.apache.log4j.Logger;
 
 public class Calculation4
+extends      Calculation
 {
     private static Logger logger = Logger.getLogger(Calculation4.class);
 
@@ -50,7 +51,7 @@
 
         // assign reference points
         for (Segment segment: segments) {
-            Gauge gauge = river.determineGauge(
+            Gauge gauge = river.determineGaugeByStation(
                 segment.getFrom(), segment.getTo());
 
             segment.setReferencePoint(gauge != null
@@ -93,10 +94,15 @@
             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");
+            // TODO: I18N
+            addProblem("no segments found");
             return new WQKms[0];
         }
 
@@ -104,14 +110,13 @@
 
         if (numResults < 1) {
             logger.debug("no values given");
+            // TODO: I18N
+            addProblem("no values given");
             return new WQKms[0];
         }
 
 
         WQKms [] results = new WQKms[numResults];
-                    if (debug) {
-                        logger.debug("after last segment -> gleichwertig");
-                    }
         for (int i = 0; i < results.length; ++i) {
             results[i] = new WQKms();
         }
@@ -213,7 +218,8 @@
                             anchor.values[i]);
 
                         if ((qPositions[i] = qi) == null) {
-                            // TODO: error report
+                            // TODO: I18N
+                            addProblem(pos, "cannot find q = " + anchor.values[i]);
                             functions[i] = Identity.IDENTITY;
                         }
                         else {
@@ -225,6 +231,14 @@
                                 : 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
@@ -241,7 +255,8 @@
                     results[i].add(out[0], out[1], pos);
                 }
                 else {
-                    // TODO: error report
+                    // TODO: I18N
+                    addProblem(pos, "cannot interpolate w/q");
                 }
             }
         }
@@ -253,10 +268,9 @@
             double [] ws  = results[i].getWs();
             double [] kms = results[i].getKms();
 
-            if (bjc.doCorrection(kms, ws)) {
+            if (bjc.doCorrection(kms, ws, this)) {
                 results[i] = new WQCKms(results[i], bjc.getCorrected());
             }
-            // TODO: error report
         }
 
         // name the curves
@@ -268,7 +282,7 @@
     }
 
     protected String createName(int index) {
-        // TODO: i18n
+        // TODO: I18N
         StringBuilder sb = new StringBuilder(isQ ? "Q" : "W");
         sb.append(" benutzerdefiniert (");
         for (int i = 0, N = segments.size(); i < N; ++i) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/ComputeCallback.java	Wed Jun 15 15:28:54 2011 +0000
@@ -0,0 +1,7 @@
+package de.intevation.flys.artifacts.model;
+
+public interface ComputeCallback {
+
+    Object compute();
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Segment.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/Segment.java	Wed Jun 15 15:28:54 2011 +0000
@@ -42,7 +42,9 @@
     public String toString() {
         StringBuilder sb = new StringBuilder("Segment: [");
         sb.append("from: ").append(from).append("; to: ")
-          .append(to).append("; values: (");
+          .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]);
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQCKms.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQCKms.java	Wed Jun 15 15:28:54 2011 +0000
@@ -30,6 +30,11 @@
         this.cw = new TDoubleArrayList(cws);
     }
 
+    @Override
+    public void removeNaNs() {
+        removeNaNs(new TDoubleArrayList [] { w, q, cw, kms });
+    }
+
 
     /**
      * Adds a new row to this data pool with corrected W.
@@ -53,6 +58,7 @@
      *
      * @return a 4dim array of [W, Q, Kms, CW] in dst.
      */
+    @Override
     public double[] get(int idx, double[] dst) {
         dst = super.get(idx, dst);
 
@@ -61,7 +67,7 @@
         }
 
         if (cw != null && cw.size() > idx) {
-            dst[3] = cw.get(idx);
+            dst[3] = cw.getQuick(idx);
         }
 
         return dst;
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQDay.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQDay.java	Wed Jun 15 15:28:54 2011 +0000
@@ -34,6 +34,12 @@
         qs   = new TDoubleArrayList(capacity);
     }
 
+    public WQDay(int [] days, double [] ws, double [] qs) {
+        this.days = new TIntArrayList(days);
+        this.ws   = new TDoubleArrayList(ws);
+        this.qs   = new TDoubleArrayList(qs);
+    }
+
 
     public void add(int day, double w, double q) {
         days.add(day);
@@ -48,17 +54,43 @@
 
 
     public int getDay(int idx) {
-        return days.get(idx);
+        return days.getQuick(idx);
     }
 
 
     public double getW(int idx) {
-        return ws.get(idx);
+        return ws.getQuick(idx);
     }
 
 
     public double getQ(int idx) {
-        return qs.get(idx);
+        return qs.getQuick(idx);
+    }
+
+    public void removeNaNs() {
+
+        int dest = 0;
+        int N = ws.size();
+
+        for (int i = 0; i < N; ++i) {
+            double w = ws.getQuick(i);
+            double q = qs.getQuick(i);
+
+            if (Double.isNaN(w) || Double.isNaN(q)) {
+                continue;
+            }
+
+            days.setQuick(dest, days.getQuick(i));
+            ws.setQuick(dest, w);
+            qs.setQuick(dest, q);
+            ++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 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKms.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WQKms.java	Wed Jun 15 15:28:54 2011 +0000
@@ -53,6 +53,36 @@
         this.kms = new TDoubleArrayList(capacity);
     }
 
+    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 [] { w, q, kms });
+    }
+
 
     public WQKms(double[] kms, double[] qs, double[] ws) {
         this(kms, qs, ws, "");
@@ -81,14 +111,6 @@
         this.kms.add(kms);
     }
 
-
-    public void add(double[] w, double[] q, double[] kms) {
-        this.w.add(w);
-        this.q.add(q);
-        this.kms.add(kms);
-    }
-
-
     /**
      * Returns the number of triples stored in this data pool.
      *
@@ -107,15 +129,15 @@
      * @return a triple of [W, Q, Kms] in dst.
      */
     public double[] get(int idx, double [] dst) {
-        dst[0] = w  .get(idx);
-        dst[1] = q  .get(idx);
-        dst[2] = kms.get(idx);
+        dst[0] = w  .getQuick(idx);
+        dst[1] = q  .getQuick(idx);
+        dst[2] = kms.getQuick(idx);
         return dst;
     }
 
 
     public double getKms(int idx) {
-        return kms.get(idx);
+        return kms.getQuick(idx);
     }
 
 
@@ -142,37 +164,7 @@
     public String toString() {
         double from = getKms(0);
         double to   = getKms(size()-1);
-        return Double.toString(from) + " - " + Double.toString(to);
-    }
-
-
-    /**
-     * Merges the WQKms objects of an incoming 2dim array (table) where the
-     * objects of a column belong together.
-     *
-     * @param toMerge The objects that need to be merged.
-     *
-     * @return an array of merged WQKms objects.
-     */
-    public static WQKms[] merge(WQKms[][] toMerge) {
-        int num = toMerge[0].length;
-
-        // TODO IS THE LENGTH CORRECT?
-        WQKms[] merged = new WQKms[num];
-        for (int i = 0; i < num; i++) {
-            merged[i] = new WQKms();
-        }
-
-        for (int i = 0; i < toMerge.length; i++) {
-            WQKms[] tmp = toMerge[i];
-
-            for (int j = 0; j < num; j++) {
-                WQKms toAdd = tmp[j];
-                merged[j].add(toAdd.getWs(), toAdd.getQs(), toAdd.getKms());
-            }
-        }
-
-        return merged;
+        return from + " - " + to;
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java	Wed Jun 15 15:28:54 2011 +0000
@@ -18,6 +18,8 @@
 
 import org.apache.commons.math.ArgumentOutsideDomainException;
 
+import org.apache.commons.math.exception.MathIllegalArgumentException;
+
 public class WstValueTable
 implements   Serializable
 {
@@ -107,7 +109,8 @@
             double        km,
             double []     iqs,
             double []     ows,
-            WstValueTable table
+            WstValueTable table,
+            Calculation   errors
         ) {
             double kmWeight = Linear.factor(km, this.km, other.km);
 
@@ -117,9 +120,23 @@
                 if (table.getQPosition(km, iqs[i], qPosition) != null) {
                     double wt =       getW(qPosition);
                     double wo = other.getW(qPosition);
-                    ows[i] = Linear.weight(kmWeight, wt, wo);
+                    if (Double.isNaN(wt) || Double.isNaN(wo)) {
+                        if (errors != null) {
+                            // TODO: I18N
+                            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) {
+                        // TODO: I18N
+                        errors.addProblem(km, "cannot find q = " + iqs[i]);
+                    }
                     ows[i] = Double.NaN;
                 }
             }
@@ -129,11 +146,16 @@
             Row           other,
             double        km,
             int           steps,
-            WstValueTable table
+            WstValueTable table,
+            Calculation   errors
         ) {
             int W = Math.min(ws.length, other.ws.length);
 
             if (W < 1) {
+                if (errors != null) {
+                    // TODO: I18N
+                    errors.addProblem("no ws found");
+                }
                 return new double[2][0];
             }
 
@@ -152,21 +174,44 @@
                     table.getQIndex(i, km),
                     table.getQIndex(i, other.km));
 
+                if (Double.isNaN(wws) || Double.isNaN(wqs)) {
+                    if (errors != null) {
+                        // TODO: I18N
+                        errors.addProblem(km, "cannot find w or q");
+                    }
+                }
+                else {
+                    if (wqs < minQ) minQ = wqs;
+                    if (wqs > maxQ) maxQ = wqs;
+                }
+
                 splineW[i] = wws;
                 splineQ[i] = wqs;
-                if (wqs < minQ) minQ = wqs;
-                if (wqs > maxQ) maxQ = wqs;
             }
 
             double stepWidth = (maxQ - minQ)/steps;
 
             SplineInterpolator interpolator = new SplineInterpolator();
-            PolynomialSplineFunction spline =
-                interpolator.interpolate(splineQ, splineW);
+            PolynomialSplineFunction spline;
+
+            try {
+                spline = interpolator.interpolate(splineQ, splineW);
+            }
+            catch (MathIllegalArgumentException miae) {
+                if (errors != null) {
+                    // TODO: I18N
+                    errors.addProblem(km, "spline creation failed");
+                }
+                log.error("spline creation failed");
+                return new double[2][0];
+            }
 
             double [] outWs = new double[steps];
             double [] outQs = new double[steps];
 
+            Arrays.fill(outWs, Double.NaN);
+            Arrays.fill(outQs, Double.NaN);
+
             try {
                 double q = minQ;
                 for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
@@ -174,17 +219,28 @@
                 }
             }
             catch (ArgumentOutsideDomainException aode) {
-                log.error("Spline interpolation failed.", aode);
+                if (errors != null) {
+                    // TODO: I18N
+                    errors.addProblem(km, "spline interpolation failed");
+                }
+                log.error("spline interpolation failed", aode);
             }
 
             return new double [][] { outWs, outQs };
         }
 
-        public double [][] interpolateWQ(int steps, WstValueTable table) {
-
+        public double [][] interpolateWQ(
+            int           steps, 
+            WstValueTable table,
+            Calculation   errors
+        ) {
             int W = ws.length;
 
             if (W < 1) {
+                if (errors != null) {
+                    // TODO: I18N
+                    errors.addProblem(km, "no ws found");
+                }
                 return new double[2][0];
             }
 
@@ -193,24 +249,46 @@
             double minQ =  Double.MAX_VALUE; 
             double maxQ = -Double.MAX_VALUE; 
 
-            QPosition qPosition = new QPosition();
-
             for (int i = 0; i < W; ++i) {
-                splineQ[i] = table.getQIndex(i, km);
-                if (splineQ[i] < minQ) minQ = splineQ[i];
-                if (splineQ[i] > maxQ) maxQ = splineQ[i];
+                double sq = table.getQIndex(i, km);
+                if (Double.isNaN(sq)) {
+                    if (errors != null) {
+                        // TODO: I18N
+                        errors.addProblem(
+                            km, "no q found in " + (i+1) + " column");
+                    }
+                }
+                else {
+                    if (sq < minQ) minQ = sq;
+                    if (sq > maxQ) maxQ = sq;
+                }
+                splineQ[i] = sq;
             }
 
             double stepWidth = (maxQ - minQ)/steps;
 
             SplineInterpolator interpolator = new SplineInterpolator();
 
-            PolynomialSplineFunction spline =
-                interpolator.interpolate(splineQ, ws);
+            PolynomialSplineFunction spline;
+            
+            try {
+                spline = interpolator.interpolate(splineQ, ws);
+            }
+            catch (MathIllegalArgumentException miae) {
+                if (errors != null) {
+                    // TODO: I18N
+                    errors.addProblem(km, "spline creation failed");
+                }
+                log.error("spline creation failed");
+                return new double[2][0];
+            }
 
             double [] outWs = new double[steps];
             double [] outQs = new double[steps];
 
+            Arrays.fill(outWs, Double.NaN);
+            Arrays.fill(outQs, Double.NaN);
+
             try {
                 double q = minQ;
                 for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
@@ -218,7 +296,11 @@
                 }
             }
             catch (ArgumentOutsideDomainException aode) {
-                log.error("Spline interpolation failed.", aode);
+                if (errors != null) {
+                    // TODO: I18N
+                    errors.addProblem(km, "spline interpolation failed");
+                }
+                log.error("spline interpolation failed.", aode);
             }
 
             return new double [][] { outWs, outQs };
@@ -282,7 +364,15 @@
     }
 
     public double [] interpolateW(double km, double [] qs, double [] ws) {
+        return interpolateW(km, qs, ws, null);
+    }
 
+    public double [] interpolateW(
+        double      km,
+        double []   qs, 
+        double []   ws,
+        Calculation errors
+    ) {
         int rowIndex = Collections.binarySearch(rows, new Row(km));
 
         QPosition qPosition = new QPosition();
@@ -290,9 +380,21 @@
         if (rowIndex >= 0) { // direct row match
             Row row = rows.get(rowIndex);
             for (int i = 0; i < qs.length; ++i) {
-                ws[i] = getQPosition(km, qs[i], qPosition) != null
-                    ? row.getW(qPosition)
-                    : Double.NaN;
+                if (getQPosition(km, qs[i], qPosition) == null) {
+                    if (errors != null) {
+                        // TODO: I18N
+                        errors.addProblem(km, "cannot find q = " + qs[i]);
+                    }
+                    ws[i] = Double.NaN;
+                }
+                else {
+                    if (Double.isNaN(ws[i] = row.getW(qPosition))
+                    && errors != null) {
+                        // TODO: I18N
+                        errors.addProblem(
+                            km, "cannot find w for q = " + qs[i]);
+                    }
+                }
             }
         }
         else { // needs bilinear interpolation
@@ -301,11 +403,15 @@
             if (rowIndex < 1 || rowIndex >= rows.size()) {
                 // do not extrapolate
                 Arrays.fill(ws, Double.NaN);
+                if (errors != null) {
+                    // TODO: I18N
+                    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);
+                r1.interpolateW(r2, km, qs, ws, this, errors);
             }
         }
 
@@ -313,29 +419,37 @@
     }
 
     public double [][] interpolateWQ(double km) {
-        return interpolateWQ(km, DEFAULT_Q_STEPS);
+        return interpolateWQ(km, null);
     }
 
-    public double [][] interpolateWQ(double km, int steps) {
+    public double [][] interpolateWQ(double km, Calculation errors) {
+        return interpolateWQ(km, DEFAULT_Q_STEPS, errors);
+    }
+
+    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);
+            return row.interpolateWQ(steps, this, errors);
         }
 
         rowIndex = -rowIndex -1;
 
         if (rowIndex < 1 || rowIndex >= rows.size()) {
             // do not extrapolate
+            if (errors != null) {
+                // TODO: I18N
+                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);
+        return r1.interpolateWQ(r2, km, steps, this, errors);
     }
 
     public boolean interpolate(
@@ -380,28 +494,36 @@
     }
 
     public QPosition interpolate(
-        double    q,
-        double    referenceKm,
-        double [] kms,
-        double [] ws,
-        double [] qs
+        double      q,
+        double      referenceKm,
+        double []   kms,
+        double []   ws,
+        double []   qs,
+        Calculation errors
     ) {
-        return interpolate(q, referenceKm, kms, ws, qs, 0, kms.length);
+        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
+        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");
+            }
             return null;
         }
 
@@ -410,15 +532,24 @@
         int R1 = rows.size()-1;
 
         for (int i = startIndex, end = startIndex+length; i < end; ++i) {
-            kmKey.km = kms[i];
 
+            if (Double.isNaN(qs[i] = getQ(qPosition, kms[i]))) {
+                if (errors != null) {
+                    errors.addProblem(kms[i], "cannot find q");
+                }
+                ws[i] = Double.NaN;
+                continue;
+            }
+
+            kmKey.km = kms[i];
             int rowIndex = Collections.binarySearch(rows, kmKey);
 
-            qs[i] = getQ(qPosition, kms[i]);
-
             if (rowIndex >= 0) {
                 // direct row match
-                ws[i] = rows.get(rowIndex).getW(qPosition);
+                if (Double.isNaN(ws[i] = rows.get(rowIndex).getW(qPosition))
+                && errors != null) {
+                    errors.addProblem(kms[i], "cannot find w");
+                }
                 continue;
             }
 
@@ -426,13 +557,19 @@
 
             if (rowIndex < 1 || rowIndex >= R1) {
                 // do not extrapolate
+                if (errors != null) {
+                    errors.addProblem(kms[i], "cannot find km");
+                }
                 ws[i] =  Double.NaN;
                 continue;
             }
             Row r1 = rows.get(rowIndex-1);
             Row r2 = rows.get(rowIndex);
 
-            ws[i] = r1.getW(r2, kms[i], qPosition);
+            if (Double.isNaN(ws[i] = r1.getW(r2, kms[i], qPosition))
+            && errors != null) {
+                errors.addProblem(kms[i], "cannot find w");
+            }
         }
 
         return qPosition;
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/DistanceInfoService.java	Wed Jun 15 15:28:54 2011 +0000
@@ -12,7 +12,6 @@
 
 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.DefaultService;
 
@@ -21,6 +20,7 @@
 import de.intevation.flys.model.Attribute;
 import de.intevation.flys.model.Position;
 import de.intevation.flys.model.Range;
+import de.intevation.flys.model.Edge;
 
 import de.intevation.flys.artifacts.model.AnnotationsFactory;
 
@@ -98,22 +98,17 @@
 
         Document result = XMLUtils.newDocument();
 
-        ElementCreator ec = new ElementCreator(
-            result,
-            ArtifactNamespaceContext.NAMESPACE_URI,
-            ArtifactNamespaceContext.NAMESPACE_PREFIX);
-
         Session session = SessionHolder.acquire();
 
         try {
             Iterator<Annotation> iter =
                 AnnotationsFactory.getAnnotationsIterator(river);
 
-            Element all = ec.create("distances");
+            Element all = result.createElement("distances");
 
             while (iter.hasNext()) {
                 Annotation a = iter.next();
-                Element distance = buildDistanceNode(ec, a);
+                Element distance = buildDistanceNode(result, a);
                 all.appendChild(distance);
             }
 
@@ -136,19 +131,43 @@
      *
      * @return an Element that contains information about a distance.
      */
-    protected static Element buildDistanceNode(ElementCreator ec, Annotation anno) {
-        Position  pos   = anno.getPosition();
-        Range     range = anno.getRange();
-        Attribute attr  = anno.getAttribute();
+    protected static Element buildDistanceNode(
+        Document   document,
+        Annotation anno
+    ) {
+        Position   pos   = anno.getPosition();
+        Range      range = anno.getRange();
+        Attribute  attr  = anno.getAttribute();
+        Edge       edge  = anno.getEdge();
+        BigDecimal a     = range.getA();
+        BigDecimal b     = range.getB();
 
-        BigDecimal a = range.getA();
-        BigDecimal b = range.getB();
+        Element distance = document.createElement("distance");
 
-        Element distance = ec.create("distance");
-        ec.addAttr(distance, "description", pos.getValue(), true);
-        ec.addAttr(distance, "from", a != null ? a.toString() : "", true);
-        ec.addAttr(distance, "to", b != null ? b.toString() : "", true);
-        ec.addAttr(distance, "riverside", attr.getValue(), true);
+        distance.setAttribute("description", pos.getValue());
+
+        String riverSide = attr.getValue();
+
+        if (riverSide != null && riverSide.length() > 0) {
+            distance.setAttribute("riverside", riverSide);
+        }
+
+        if (a != null) {
+            distance.setAttribute("from", a.toString());
+        }
+        if (b != null) {
+            distance.setAttribute("to", b.toString());
+        }
+        if (edge != null) {
+            BigDecimal bottom = edge.getBottom();
+            BigDecimal top    = edge.getTop();
+            if (bottom != null) {
+                distance.setAttribute("bottom", bottom.toString());
+            }
+            if (top != null) {
+                distance.setAttribute("top", top.toString());
+            }
+        }
 
         return distance;
     }
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DefaultState.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/DefaultState.java	Wed Jun 15 15:28:54 2011 +0000
@@ -1,6 +1,8 @@
 package de.intevation.flys.artifacts.states;
 
+import java.text.NumberFormat;
 import java.util.Iterator;
+import java.util.Locale;
 import java.util.Map;
 
 import org.apache.log4j.Logger;
@@ -84,11 +86,22 @@
 
             Element itemElement = creator.create("item");
             creator.addAttr(itemElement, "value", value, true);
-            creator.addAttr(
-                itemElement,
-                "label",
-                Resources.getMsg(meta, value, value),
-                true);
+
+            String attrValue = "";
+            try {
+                // XXX A better way to format the output would be to use the
+                // 'type' value if the data objects.
+                double doubleVal = Double.valueOf(value);
+                Locale         l = Resources.getLocale(meta);
+                NumberFormat  nf = NumberFormat.getInstance(l);
+
+                attrValue = nf.format(doubleVal);
+            }
+            catch (NumberFormatException nfe) {
+                attrValue = Resources.getMsg(meta, value, value);
+            }
+
+            creator.addAttr(itemElement, "label", attrValue, true);
 
             dataElement.appendChild(itemElement);
             ui.appendChild(dataElement);
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/LocationSelect.java	Wed Jun 15 15:28:54 2011 +0000
@@ -4,9 +4,13 @@
 
 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;
@@ -40,6 +44,42 @@
 
 
     @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        double[] minmax = getMinMaxDistance(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(FIELD_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, CallContext context)
     throws IllegalArgumentException
     {
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/WQAdapted.java	Wed Jun 15 15:28:54 2011 +0000
@@ -121,6 +121,8 @@
         FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
 
         double[]    dist   = flysArtifact.getDistance();
+        River       river  = flysArtifact.getRiver();
+        Wst         wst    = WstFactory.getWst(river);
         List<Gauge> gauges = flysArtifact.getGauges();
 
         int num = gauges != null ? gauges.size() : 0;
@@ -147,8 +149,11 @@
                 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, ""});
+                    cr, new String[] { from + ";" + to, ""}, mmQ, mmW);
             }
         }
         else {
@@ -173,6 +178,16 @@
 
 
     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);
@@ -185,10 +200,67 @@
         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";
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartInfoGenerator.java	Wed Jun 15 15:28:54 2011 +0000
@@ -101,7 +101,8 @@
 
         chart.createBufferedImage(size[0], size[1], Transparency.BITMASK, info);
 
-        Document doc = InfoGeneratorHelper.createInfoDocument(chart, info);
+        InfoGeneratorHelper helper = new InfoGeneratorHelper(generator);
+        Document doc = helper.createInfoDocument(chart, info);
 
         XMLUtils.toStream(doc, out);
     }
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DischargeLongitudinalSectionGenerator.java	Wed Jun 15 15:28:54 2011 +0000
@@ -83,6 +83,13 @@
     }
 
 
+    protected void adjustAxes(XYPlot plot) {
+        super.adjustAxes(plot);
+
+        plot.mapDatasetToRangeAxis(2, 1);
+    }
+
+
 
     @Override
     public void doOut(Artifact artifact, String facet, Document attr) {
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/DurationCurveGenerator.java	Wed Jun 15 15:28:54 2011 +0000
@@ -16,8 +16,11 @@
 
 import de.intevation.artifacts.Artifact;
 
+import de.intevation.flys.model.River;
+
 import de.intevation.flys.artifacts.WINFOArtifact;
 import de.intevation.flys.artifacts.model.WQDay;
+import de.intevation.flys.artifacts.resources.Resources;
 
 
 /**
@@ -27,6 +30,9 @@
  */
 public class DurationCurveGenerator extends XYChartGenerator {
 
+    public static final String I18N_DURATION_W = "chart.duration.curve.curve.w";
+    public static final String I18N_DURATION_Q = "chart.duration.curve.curve.q";
+
     private static Logger logger =
         Logger.getLogger(DurationCurveGenerator.class);
 
@@ -146,8 +152,8 @@
 
         NumberAxis qAxis = new NumberAxis("Q [m\u00b3/s]");
 
-        plot.setRangeAxis(2, qAxis);
-        plot.mapDatasetToRangeAxis(1, 2);
+        plot.setRangeAxis(1, qAxis);
+        plot.mapDatasetToRangeAxis(1, 1);
     }
 
 
@@ -159,11 +165,14 @@
             return;
         }
 
+        WINFOArtifact winfo = (WINFOArtifact) artifact;
+        River         river = winfo.getRiver();
+
         if (facet.equals(DURATION_CURVE_W)) {
-            doWOut(getDurationCurveData(artifact));
+            doWOut(getDurationCurveData(artifact), river.getName());
         }
         else if (facet.equals(DURATION_CURVE_Q)) {
-            doQOut(getDurationCurveData(artifact));
+            doQOut(getDurationCurveData(artifact), river.getName());
         }
         else {
             logger.warn("Unknown facet name: " + facet);
@@ -176,12 +185,14 @@
      * Creates the series for a duration curve's W facet.
      *
      * @param wqdays The WQDay store that contains the Ws.
+     * @param river The name of the river.
      */
-    protected void doWOut(WQDay wqdays) {
+    protected void doWOut(WQDay wqdays, String river) {
         logger.debug("DurationCurveGenerator.doWOut");
 
         // TODO find the correct series name
-        XYSeries series = new XYSeries("W-1");
+        XYSeries series = new XYSeries(
+            getSeriesName(river, DURATION_CURVE_W));
 
         int size = wqdays.size();
         for (int i = 0; i < size; i++) {
@@ -199,12 +210,14 @@
      * Creates the series for a duration curve's Q facet.
      *
      * @param wqdays The WQDay store that contains the Qs.
+     * @param river The name of the river.
      */
-    protected void doQOut(WQDay wqdays) {
+    protected void doQOut(WQDay wqdays, String river) {
         logger.debug("DurationCurveGenerator.doQOut");
 
         // TODO find the correct series name
-        XYSeries series = new XYSeries("Q-1");
+        XYSeries series = new XYSeries(
+            getSeriesName(river, DURATION_CURVE_Q));
 
         int size = wqdays.size();
         for (int i = 0; i < size; i++) {
@@ -230,5 +243,32 @@
         WINFOArtifact winfoArtifact = (WINFOArtifact) artifact;
         return winfoArtifact.getDurationCurveData();
     }
+
+
+    protected String getSeriesName(String river, String type) {
+        Object[] args = new Object[] { river };
+
+        if (type == null || type.length() == 0) {
+            logger.warn("No duration curve type given.");
+            return "n/a";
+        }
+        else if (type.equals(DURATION_CURVE_W)) {
+            return Resources.getMsg(
+                context.getMeta(),
+                I18N_DURATION_W,
+                "W",
+                args);
+        }
+        else if (type.equals(DURATION_CURVE_Q)) {
+            return Resources.getMsg(
+                context.getMeta(),
+                I18N_DURATION_Q,
+                "W",
+                args);
+        }
+
+        logger.warn("Could not determine chart curve type: " + type);
+        return type;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java	Wed Jun 15 15:28:54 2011 +0000
@@ -14,6 +14,7 @@
 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;
@@ -31,7 +32,11 @@
         Logger.getLogger(InfoGeneratorHelper.class);
 
 
-    private InfoGeneratorHelper() {
+    protected XYChartGenerator generator;
+
+
+    public InfoGeneratorHelper(XYChartGenerator generator) {
+        this.generator = generator;
     }
 
 
@@ -43,7 +48,7 @@
      *
      * @return the info document.
      */
-    public static Document createInfoDocument(
+    public Document createInfoDocument(
         JFreeChart         chart,
         ChartRenderingInfo info)
     {
@@ -59,7 +64,7 @@
         Element chartinfo = cr.create("chartinfo");
 
         chartinfo.appendChild(createAxesElements(cr, chart));
-        chartinfo.appendChild(createTransformationElement(cr, chart, info));
+        chartinfo.appendChild(createTransformationElements(cr, chart, info));
 
         doc.appendChild(chartinfo);
 
@@ -76,7 +81,7 @@
      *
      * @return an element with axes information.
      */
-    protected static Element createAxesElements(
+    protected Element createAxesElements(
         ElementCreator     cr,
         JFreeChart         chart)
     {
@@ -89,8 +94,10 @@
         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, "domain", i);
+                Element e = createAxisElement(cr, axis, data, "domain", i);
                 axes.appendChild(e);
             }
         }
@@ -98,10 +105,15 @@
         int rAxisCount = plot.getRangeAxisCount();
         for (int i = 0; i < rAxisCount; i++) {
             ValueAxis axis = plot.getRangeAxis(i);
-            if (axis != null) {
-                Element e = createAxisElement(cr, axis, "range", i);
-                axes.appendChild(e);
+            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;
@@ -114,14 +126,16 @@
      *
      * @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 static Element createAxisElement(
+    protected Element createAxisElement(
         ElementCreator cr,
         ValueAxis      axis,
+        XYDataset      dataset,
         String         type,
         int            pos)
     {
@@ -132,6 +146,19 @@
         cr.addAttr(e, "from", String.valueOf(range.getLowerBound()), true);
         cr.addAttr(e, "to", String.valueOf(range.getUpperBound()), true);
 
+        Range[] rs = generator.getRangesForDataset(dataset);
+        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;
     }
 
@@ -146,12 +173,12 @@
      *
      * @return an element that contains one or more transformation matrix.
      */
-    protected static Element createTransformationElement(
+    protected Element createTransformationElements(
         ElementCreator     cr,
         JFreeChart         chart,
         ChartRenderingInfo info)
     {
-        logger.debug("InfoGeneratorHelper.createTransformationElement");
+        logger.debug("InfoGeneratorHelper.createTransformationElements");
 
         Element tf = cr.create("transformation-matrix");
 
@@ -159,16 +186,60 @@
 
         XYPlot    plot  = (XYPlot) chart.getPlot();
         ValueAxis xAxis = plot.getDomainAxis();
-        ValueAxis yAxis = plot.getRangeAxis();
 
+        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);
 
-        cr.addAttr(tf, "sx", String.valueOf(tm[0]), true);
-        cr.addAttr(tf, "sy", String.valueOf(tm[1]), true);
-        cr.addAttr(tf, "tx", String.valueOf(tm[2]), true);
-        cr.addAttr(tf, "ty", String.valueOf(tm[3]), true);
+        Element matrix = cr.create("matrix");
 
-        return tf;
+        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);
+
+        return matrix;
     }
 
 
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java	Wed Jun 15 15:28:54 2011 +0000
@@ -106,7 +106,7 @@
         addSubtitles(chart);
         adjustPlot(plot);
         adjustAxes(plot);
-        zoom(plot);
+        autoZoom(plot);
 
         return chart;
     }
@@ -121,35 +121,28 @@
      *
      * @param plot The XYPlot.
      */
-    protected void zoom(XYPlot plot) {
+    protected void autoZoom(XYPlot plot) {
         logger.debug("Zoom to specified ranges.");
 
-        Range[] ranges = null;
-
-        boolean x = zoomX(plot);
-        if (!x) {
-            XYDataset dataset = plot.getDataset();
-
-            ranges = getRangesForDataset(dataset);
-
-            logger.debug("No x range specified. Set manually: " + ranges[0]);
+        Range xrange = getDomainAxisRange();
+        Range yrange = getValueAxisRange();
 
-            ValueAxis axis = plot.getDomainAxis();
-            axis.setRange(ranges[0]);
-        }
+        for (int i = 0, num = plot.getDatasetCount(); i < num; i++) {
+            XYDataset dataset = plot.getDataset(i);
+            Range[]   ranges  = getRangesForDataset(dataset);
 
-        boolean y = zoomY(plot);
-        if (!y) {
-            XYDataset dataset = plot.getDataset();
-
-            if (ranges == null) {
-                ranges = getRangesForDataset(dataset);
+            if (i == 0) {
+                ValueAxis xaxis = plot.getDomainAxis();
+                zoom(plot, xaxis, ranges[0], xrange);
             }
 
-            logger.debug("No y range specified. Set manually: " + ranges[1]);
+            ValueAxis yaxis = plot.getRangeAxis(i);
 
-            ValueAxis axis = plot.getRangeAxis();
-            axis.setRange(ranges[1]);
+            if (yaxis == null) {
+                continue;
+            }
+
+            zoom(plot, yaxis, ranges[1], yrange);
         }
     }
 
@@ -158,42 +151,30 @@
      * 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 zoomX(XYPlot plot) {
-        Range xrange = getDomainAxisRange();
-        if (xrange != null) {
-            ValueAxis xaxis = plot.getDomainAxis();
-            xaxis.setRange(xrange);
+    protected boolean zoom(XYPlot plot, ValueAxis axis, Range range, Range x) {
+        if (x != null) {
+            double min  = range.getLowerBound();
+            double max  = range.getUpperBound();
+            double diff = max > min ? max - min : min - max;
 
-            logger.debug("Zoom chart to X: " + xrange);
+            Range computed = new Range(
+                min + x.getLowerBound() * diff,
+                min + x.getUpperBound() * diff);
+
+            axis.setRange(computed);
+
+            logger.debug("Zoom axis to: " + computed);
 
             return true;
         }
 
-        return false;
-    }
-
-
-    /**
-     * Zooms the y axis to the range specified in the attribute document.
-     *
-     * @param plot The XYPlot.
-     *
-     * @return true, if a zoom range was specified, otherwise false.
-     */
-    protected boolean zoomY(XYPlot plot) {
-        Range yrange = getValueAxisRange();
-        if (yrange != null) {
-            ValueAxis yaxis = plot.getRangeAxis();
-            yaxis.setRange(yrange);
-
-            logger.debug("Zoom chart to Y: " + yrange);
-
-            return true;
-        }
-
+        axis.setRange(range);
         return false;
     }
 
@@ -205,7 +186,7 @@
      *
      * @return a Range[] as follows: [x-Range, y-Range].
      */
-    protected Range[] getRangesForDataset(XYDataset dataset) {
+    public static Range[] getRangesForDataset(XYDataset dataset) {
         double[] xr = new double[] { Double.MAX_VALUE, -Double.MAX_VALUE };
         double[] yr = new double[] { Double.MAX_VALUE, -Double.MAX_VALUE };
 
--- a/flys-artifacts/src/main/resources/messages.properties	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/resources/messages.properties	Wed Jun 15 15:28:54 2011 +0000
@@ -33,6 +33,8 @@
 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}
+chart.duration.curve.curve.q = Discharge duration curve for {0}
 
 export.waterlevel.csv.header.km = River-Km
 export.waterlevel.csv.header.w = W [NN + m]
--- a/flys-artifacts/src/main/resources/messages_de.properties	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/resources/messages_de.properties	Wed Jun 15 15:28:54 2011 +0000
@@ -9,7 +9,7 @@
 calc.flood.map = \u00dcberschwemmungsfl\u00e4che
 calc.discharge.curve = Abflusskurve/Abflusstafel
 calc.duration.curve = Dauerlinie
-calc.discharge.longitudinal.section = W bei ungleichm\u00e4\u00dfigem Abflussl\u00e4ngsschnitt
+calc.discharge.longitudinal.section = W bei ungleichwertigem Abflussl\u00e4ngsschnitt
 
 river = Fluss
 calculation_mode = Berechnungsart
@@ -33,6 +33,8 @@
 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}
+chart.duration.curve.curve.q = Abflussdauerline f\u00fcr {0}
 
 export.waterlevel.csv.header.km = Fluss-Km
 export.waterlevel.csv.header.w = W [NN + m]
--- a/flys-artifacts/src/main/resources/messages_de_DE.properties	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/resources/messages_de_DE.properties	Wed Jun 15 15:28:54 2011 +0000
@@ -9,7 +9,7 @@
 calc.flood.map = \u00dcberschwemmungsfl\u00e4che
 calc.discharge.curve = Abflusskurve/Abflusstafel
 calc.duration.curve = Dauerlinie
-calc.discharge.longitudinal.section = W bei ungleichm\u00e4\u00dfigem Abflussl\u00e4ngsschnitt
+calc.discharge.longitudinal.section = W bei ungleichwertigem Abflussl\u00e4ngsschnitt
 
 river = Fluss
 calculation_mode = Berechnungsart
@@ -33,6 +33,8 @@
 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}
+chart.duration.curve.curve.q = Abflussdauerline f\u00fcr {0}
 
 export.waterlevel.csv.header.km = Fluss-Km
 export.waterlevel.csv.header.w = W [NN + m]
--- a/flys-artifacts/src/main/resources/messages_en.properties	Wed Jun 08 13:03:21 2011 +0000
+++ b/flys-artifacts/src/main/resources/messages_en.properties	Wed Jun 15 15:28:54 2011 +0000
@@ -33,6 +33,8 @@
 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}
+chart.duration.curve.curve.q = Discharge duration curve for {0}
 
 export.waterlevel.csv.header.km = River-Km
 export.waterlevel.csv.header.w = W [NN + m]

http://dive4elements.wald.intevation.org