changeset 6410:df867072d003 double-precision

merge changes from default into double-precision branch
author Tom Gottfried <tom.gottfried@intevation.de>
date Mon, 24 Jun 2013 15:24:07 +0200
parents b95504c34060 (current diff) 19066b1220b5 (diff)
children 61e55f36e764
files backend/src/main/java/org/dive4elements/river/model/OfficialLine.java
diffstat 37 files changed, 1484 insertions(+), 283 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/doc/conf/artifacts/minfo.xml	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/doc/conf/artifacts/minfo.xml	Mon Jun 24 15:24:07 2013 +0200
@@ -508,6 +508,7 @@
                         <facet name="sedimentload.susp_sand_bed"/>
                         <facet name="sedimentload.susp_sediment"/>
                         <facet name="sedimentload.total_load"/>
+                        <facet name="sedimentload.unknown"/>
                         <facet name="sedimentload_ls.manualpoints"/>
                         <facet name="sedimentload.total"/>
                         <facet name="flow_velocity.totalchannel" description="A facet for total channels"/>
--- a/artifacts/doc/conf/cache.xml	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/doc/conf/cache.xml	Mon Jun 24 15:24:07 2013 +0200
@@ -142,6 +142,13 @@
            memoryStoreEvictionPolicy="LRU"
            overflowToDisk="true"
            diskPersistent="true"
+       />
+
+    <!-- This one is used to associate the offical lines to the respective
+         main values. -->
+    <cache name="official-lines"
+           maxElementsInMemory="2"
+           timeToLiveSeconds="14400"
            />
 
     <!-- This one is used for the cross section lookup
--- a/artifacts/doc/conf/meta-data.xml	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/doc/conf/meta-data.xml	Mon Jun 24 15:24:07 2013 +0200
@@ -2318,60 +2318,28 @@
     </dc:macro>
 
     <dc:macro name="officiallines_user">
-      <dc:comment comment=".wst -------------------------------">
-      <!-- =============== THIS IS BROKEN! ============== -->
-      <officiallines>
-        <dc:for-each>
-          <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,
-                     ardg.v     AS gaugy,
-                     arv.v      AS wqsingle
-              FROM   master_artifacts m,
-              artifact_data ardg,
-              artifact_data arv
-              WHERE  m.collection_id = ${collection_id}
-              AND m.gid = CAST(${artifact-id} AS uuid)
-              AND ardg.artifact_id = m.id
-              AND ardg.k = 'ld_gaugename'
-              AND arv.artifact_id = m.id
-              AND arv.k = 'wq_single'
-              AND EXISTS (
-              SELECT id
-              FROM artifact_data ad
-              WHERE ad.artifact_id = m.id
-              AND k = 'river'
-              AND v = ${river})
-            </dc:statement>
-            <dc:for-each>
-              <dc:context connection="system">
-                <dc:statement>
-                  SELECT ol.wst_id         AS wstid,
-                         ol.wst_column_pos AS wstcolpos,
-                         ol.name           AS olname,
-                         ol.value          AS oval
-                  FROM official_q_values ol
-                  WHERE ol.value = CAST(${wqsingle} AS NUMERIC(10,2))
-                  AND ol.gauge_name = ${gaugy}
-                </dc:statement>
-                <dc:for-each>
-                  <dc:element name="${olname}">
-                    <dc:attribute name="name" value="${olname}"/>
-                    <dc:attribute name="ids" value="additionals-wstv-${wstcolpos}-${wstid}"/>
-                    <dc:attribute name="factory" value="staticwkms"/>
-                    <dc:attribute name="target_out" value="${out}"/>
-                    <dc:attribute name="out" value="${out}"/>
-                  </dc:element>
-                </dc:for-each>
-              </dc:context>
-            </dc:for-each>
-          </dc:context>
-        </dc:for-each>
-      </officiallines>
-    </dc:comment>
+      <dc:if test="dc:contains($parameters, 'official-lines')">
+        <dc:container-context container="official-lines">
+          <dc:properties>
+            <dc:property name="name" alias="olname"/>
+            <dc:property name="wst" alias="wstid"/>
+            <dc:property name="columnPos" alias="wstcolpos"/>
+          </dc:properties>
+          <dc:if test="dc:has-result()">
+            <officiallines>
+              <dc:for-each>
+                <dc:element name="${olname}">
+                  <dc:attribute name="name" value="${olname}"/>
+                  <dc:attribute name="ids" value="additionals-wstv-${wstcolpos}-${wstid}"/>
+                  <dc:attribute name="factory" value="staticwkms"/>
+                  <dc:attribute name="target_out" value="${out}"/>
+                  <dc:attribute name="out" value="${out}"/>
+                </dc:element>
+              </dc:for-each>
+            </officiallines>
+          </dc:if>
+        </dc:container-context>
+      </dc:if>
     </dc:macro>
 
     <!-- Common stuff -->
--- a/artifacts/doc/conf/themes.xml	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/doc/conf/themes.xml	Mon Jun 24 15:24:07 2013 +0200
@@ -310,6 +310,7 @@
         <mapping from="sedimentload.susp_sediment" to="SedimentLoadSediment" />
         <mapping from="sedimentload.total_load" to="SedimentLoadTotalLoad" />
         <mapping from="sedimentload.total" to="SedimentLoadTotal" />
+        <mapping from="sedimentload.unknown" to="SedimentLoadUnknown" />
     </mappings>
 
 </themes>
--- a/artifacts/doc/conf/themes/default.xml	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/doc/conf/themes/default.xml	Mon Jun 24 15:24:07 2013 +0200
@@ -1145,6 +1145,17 @@
         </fields>
     </theme>
 
+    <theme name="SedimentLoadUnknown">
+        <inherits>
+            <inherit from="LongitudinalSection" />
+        </inherits>
+        <fields>
+            <field name="linecolor" type="Color" default="0, 0, 0" />
+            <field name="showlinelabel" type="boolean"
+                display="Beschriftung anzeigen" default="false" hints="hidden" />
+        </fields>
+    </theme>
+
     <theme name="LongitudinalSectionArea">
         <inherits>
             <inherit from="Areas" />
--- a/artifacts/doc/conf/themes/second.xml	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/doc/conf/themes/second.xml	Mon Jun 24 15:24:07 2013 +0200
@@ -1145,6 +1145,17 @@
         </fields>
     </theme>
 
+    <theme name="SedimentLoadUnknown">
+        <inherits>
+            <inherit from="LongitudinalSection" />
+        </inherits>
+        <fields>
+            <field name="linecolor" type="Color" default="0, 0, 0" />
+            <field name="showlinelabel" type="boolean"
+                display="Beschriftung anzeigen" default="false" hints="hidden" />
+        </fields>
+    </theme>
+
     <theme name="LongitudinalSectionArea">
         <inherits>
             <inherit from="Areas" />
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/datacage/Recommendations.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/datacage/Recommendations.java	Mon Jun 24 15:24:07 2013 +0200
@@ -45,6 +45,7 @@
 
 import org.dive4elements.river.artifacts.datacage.templating.Builder;
 import org.dive4elements.river.artifacts.datacage.templating.BuilderPool;
+import org.dive4elements.river.artifacts.model.OfficialLineFinder;
 
 
 /**
@@ -136,7 +137,7 @@
     }
 
     protected static void artifactToParameters(
-        D4EArtifact        artifact,
+        D4EArtifact         artifact,
         Map<String, Object> parameters
     ) {
         parameters.put("CURRENT-STATE-ID", artifact.getCurrentStateId());
@@ -150,6 +151,10 @@
             String key = sd.getName().replace('.', '-').toUpperCase();
             parameters.put(key, value);
         }
+
+        // XXX: THIS IS THE HACK TO BRING THE OFFICIAL LINES INTO THE DATACAGE!
+        parameters.put(
+            "OFFICIAL-LINES", OfficialLineFinder.findOfficialLines(artifact));
     }
 
     /**
@@ -171,7 +176,7 @@
      * @param extraParameters parameters (typicall example: 'recommended')
      */
     public void  recommend(
-        D4EArtifact        artifact,
+        D4EArtifact         artifact,
         String              userId,
         String []           outs,
         Map<String, Object> extraParameters,
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/datacage/templating/Builder.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/datacage/templating/Builder.java	Mon Jun 24 15:24:07 2013 +0200
@@ -12,6 +12,8 @@
 
 import org.dive4elements.river.utils.Pair;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.sql.Connection;
 import java.sql.SQLException;
 
@@ -164,6 +166,14 @@
          * macros but not doing evaluation (e.g. of <dc:if>s).
          */
         private Node findStatementNode(NodeList nodes) {
+            return findSelectNode(nodes, "statement");
+        }
+
+        private Node findPropertiesNode(NodeList nodes) {
+            return findSelectNode(nodes, "properties");
+        }
+
+        private Node findSelectNode(NodeList nodes, String selectName) {
             int S = nodes.getLength();
 
             // Check direct children and take special care of macros.
@@ -172,7 +182,7 @@
                 String ns;
                 // Regular statement node.
                 if (node.getNodeType() == Node.ELEMENT_NODE
-                && node.getLocalName().equals("statement")
+                && node.getLocalName().equals(selectName)
                 && (ns = node.getNamespaceURI()) != null
                 && ns.equals(DC_NAMESPACE_URI)) {
                     return node;
@@ -185,7 +195,7 @@
 
                     String macroName = ((Element)node).getAttribute("name");
                     Node inMacroNode =
-                        findStatementNode(getMacroChildren(macroName));
+                        findSelectNode(getMacroChildren(macroName), selectName);
                     if (inMacroNode != null) {
                         return inMacroNode;
                     }
@@ -196,6 +206,131 @@
             return null;
         }
 
+        private String[][] extractProperties(Element propertiesNode) {
+            ArrayList<String[]> props = new ArrayList<String[]>();
+            NodeList list = propertiesNode.getElementsByTagNameNS(
+                DC_NAMESPACE_URI, "property");
+            for (int i = 0, L = list.getLength(); i < L; ++i) {
+                Element property = (Element)list.item(i);
+                String name = property.getAttribute("name");
+                if (name.isEmpty()) {
+                    log.warn("dc:property without name");
+                    continue;
+                }
+                String alias = property.getAttribute("alias");
+                if (alias.isEmpty()) {
+                    alias = name;
+                }
+                props.add(new String [] { name, alias });
+            }
+            return props.toArray(new String[props.size()][]);
+        }
+
+        /**
+         * Handle a dc:context node.
+         */
+        protected void containerContext(Node parent, Element current)
+        throws SQLException
+        {
+            log.debug("dc:container-context");
+
+            String container = expand(current.getAttribute("container"));
+
+            if (container.isEmpty()) {
+                log.warn("dc:container-context: no 'container' attribute found");
+                return;
+            }
+
+            NodeList subs = current.getChildNodes();
+            Node propertiesNode = findPropertiesNode(subs);
+
+            if (propertiesNode == null) {
+                log.warn("dc:container-context: cannot find properties.");
+                return;
+            }
+
+            String [][] properties = extractProperties((Element)propertiesNode);
+
+            if (properties.length == 0) {
+                log.warn("dc:properties: No properties defined.");
+            }
+
+            Object [] result = new Object[1];
+            if (!frames.getStore(container, result)) {
+                log.warn("dc:container-context: cannot find container.");
+                return;
+            }
+            Object c = result[0];
+            if (c instanceof Object []) {
+                c = Arrays.asList((Object [])c);
+            }
+            if (!(c instanceof Collection)) {
+                log.warn("dc:container-context: container is not a collection.");
+                return;
+            }
+
+            Collection<?> collection = (Collection<?>)c;
+
+            // only descent if there are results
+            if (collection.isEmpty()) {
+                return;
+            }
+
+            String [] columnNames = new String[properties.length];
+            for (int i = 0; i < columnNames.length; ++i) {
+                columnNames[i] = properties[i][1];
+            }
+
+            ResultData rd = new ResultData(columnNames);
+
+            for (Object obj: collection) {
+                Object [] row = new Object[properties.length];
+                for (int i = 0; i < properties.length; ++i) {
+                    row[i] = getProperty(obj, properties[i][0]);
+                }
+                rd.add(row);
+            }
+
+            // A bit of a fake because the data is not from a
+            // real connection.
+            NamedConnection connection = connectionsStack.isEmpty()
+                ? connections.get(0)
+                : connectionsStack.peek().getA();
+
+            connectionsStack.push(
+                new Pair<NamedConnection, ResultData>(connection, rd));
+            try {
+                for (int i = 0, S = subs.getLength(); i < S; ++i) {
+                    build(parent, subs.item(i));
+                }
+            }
+            finally {
+                connectionsStack.pop();
+            }
+        }
+
+        /** Poor man's bean access. */
+        private Object getProperty(Object obj, String name) {
+            String mname =
+                "get" + Character.toUpperCase(name.charAt(0))
+                + name.substring(1);
+
+            try {
+                Method meth = obj.getClass().getMethod(mname);
+                return meth.invoke(obj);
+            }
+            catch (InvocationTargetException ite) {
+                log.warn(ite);
+            }
+            catch (IllegalAccessException iae) {
+                log.warn(iae);
+            }
+            catch (NoSuchMethodException nsme) {
+                log.warn(nsme);
+            }
+            return null;
+        }
+
         /**
          * Handle a dc:context node.
          */
@@ -206,7 +341,6 @@
 
             NodeList subs = current.getChildNodes();
             Node stmntNode = findStatementNode(subs);
-            int S = subs.getLength();
 
             if (stmntNode == null) {
                 log.warn("dc:context: cannot find statement");
@@ -250,7 +384,7 @@
                 connectionsStack.push(
                     new Pair<NamedConnection, ResultData>(connection, rd));
                 try {
-                    for (int i = 0; i < S; ++i) {
+                    for (int i = 0, S = subs.getLength(); i < S; ++i) {
                         build(parent, subs.item(i));
                     }
                 }
@@ -973,6 +1107,9 @@
                     else if ("context".equals(localName)) {
                         context(parent, curr);
                     }
+                    else if ("container-context".equals(localName)) {
+                        containerContext(parent, curr);
+                    }
                     else if ("if".equals(localName)) {
                         ifClause(parent, curr);
                     }
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/FacetTypes.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/FacetTypes.java	Mon Jun 24 15:24:07 2013 +0200
@@ -112,7 +112,10 @@
             return false;
         }
         public static boolean SEDIMENT_LOAD(String type) {
-            return type.startsWith("sedimentload");
+            return type.startsWith("sedimentload") && !type.contains("unknown");
+        }
+        public static boolean SEDIMENT_LOAD_UNKNOWN(String type) {
+            return type.equals("sedimentload.unknown");
         }
         public static boolean SEDIMENT_LOAD_NO_FLOAT(String type) {
             return type.startsWith("sedimentload") && !type.contains("susp");
@@ -310,6 +313,7 @@
     String SEDIMENT_LOAD_SUSP_SEDIMENT = "sedimentload.susp_sediment";
     String SEDIMENT_LOAD_TOTAL         = "sedimentload.total";
     String SEDIMENT_LOAD_TOTAL_LOAD    = "sedimentload.total_load";
+    String SEDIMENT_LOAD_UNKOWN        = "sedimentload.unknown";
 
     String SQ_OVERVIEW       = "sq_overview";
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/OfficialLineFinder.java	Mon Jun 24 15:24:07 2013 +0200
@@ -0,0 +1,329 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+
+package org.dive4elements.river.artifacts.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.cache.CacheFactory;
+import org.dive4elements.river.model.Gauge;
+import org.dive4elements.river.model.MainValue;
+import org.dive4elements.river.model.NamedMainValue;
+import org.dive4elements.river.model.OfficialLine;
+import org.dive4elements.river.model.River;
+import org.dive4elements.river.model.Wst;
+import org.dive4elements.river.model.WstColumn;
+
+public class OfficialLineFinder
+{
+    public static final String CACHE_NAME = "official-lines";
+
+    // We will only have one entry in this cache.
+    public static final String CACHE_KEY = CACHE_NAME;
+
+    public static final double EPSILON = 1e-4;
+
+
+    public static class ValueRange extends Range {
+
+        private double value;
+        private int    wstId;
+        private int    columnPos;
+        private String name;
+
+        public ValueRange(
+            double start,
+            double end, 
+            double value,
+            int    wstId,
+            int    columnPos,
+            String name
+        ) {
+            super(start, end);
+            this.value     = value;
+            this.wstId     = wstId;
+            this.columnPos = columnPos;
+            this.name      = name;
+        }
+
+        public boolean sameValue(double value) {
+            return Math.abs(value - this.value) < EPSILON;
+        }
+
+        public int getWstId() {
+            return wstId;
+        }
+
+        public int getColumnPos() {
+            return columnPos;
+        }
+
+        public boolean intersectsValueRange(Range r) {
+            return r.inside(value);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof ValueRange)) {
+                return false;
+            }
+            ValueRange r = (ValueRange)o;
+            return wstId == r.wstId && columnPos == r.columnPos;
+        }
+    }
+
+    public OfficialLineFinder() {
+    }
+
+    public static Map<String, List<ValueRange>> getAll() {
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        if (cache == null) {
+            return getAllUncached();
+        }
+
+        Element element  = cache.get(CACHE_KEY);
+
+        if (element != null) {
+            return (Map<String, List<ValueRange>>)element.getValue();
+        }
+
+        Map<String, List<ValueRange>> result = getAllUncached();
+        if (result != null) {
+            cache.put(new Element(CACHE_KEY, result));
+        }
+        return result;
+
+    }
+
+    public static Map<String, List<ValueRange>> getAllUncached() {
+
+        Map<String, List<ValueRange>> rivers2officialLines =
+            new HashMap<String, List<ValueRange>>();
+
+
+        for (OfficialLine line: OfficialLine.fetchAllOfficalLines()) {
+            String   name = line.getNamedMainValue().getName();
+            WstColumn wc  = line.getWstColumn();
+            Wst       wst = wc.getWst();
+
+            List<ValueRange> ranges = new ArrayList<ValueRange>();
+
+            River river = wst.getRiver();
+            List<Gauge> gauges = river.getGauges();
+            for (Gauge gauge: gauges) {
+                List<MainValue> mainValues = gauge.getMainValues();
+                for (MainValue mainValue: mainValues) {
+                    NamedMainValue nmv = mainValue.getMainValue();
+                    if (nmv.getName().equalsIgnoreCase(name)) {
+                        // found gauge with this main value
+
+                        double from = gauge.getRange().getA().doubleValue();
+                        double to   = gauge.getRange().getA().doubleValue();
+                        double value = mainValue.getValue().doubleValue();
+                        int    wstId = wst.getId();
+                        int    pos   = wc.getPosition();
+                        ValueRange range =
+                            new ValueRange(from, to, value, wstId, pos, name);
+                        ranges.add(range);
+                        break;
+                    }
+                }
+            }
+
+            if (!ranges.isEmpty()) {
+                rivers2officialLines.put(river.getName(), ranges);
+            }
+        }
+
+        return rivers2officialLines;
+    }
+
+    public static final Range MAX_RANGE = new Range(-Double.MAX_VALUE, +Double.MAX_VALUE);
+
+    private static final String nn(String s) {
+        return s != null ? s : "";
+    }
+
+    public static Range extractRange(D4EArtifact artifact) {
+        String mode      = nn(artifact.getDataAsString("ld_mode"));
+        String locations = nn(artifact.getDataAsString("ld_locations"));
+        String from      = nn(artifact.getDataAsString("ld_from"));
+        String to        = nn(artifact.getDataAsString("ld_to"));
+
+        if (mode.equals("location")) {
+            try {
+                String loc = locations.replace(" ", "");
+                String[] split = loc.split(",");
+                if (split.length < 1) {
+                    return MAX_RANGE;
+                }
+                double min = Double.parseDouble(split[0]);
+                double max = min;
+                for (int i = 1; i < split.length; ++i) {
+                    double v = Double.parseDouble(split[i]);
+                    if (v > max) max = v;
+                    if (v < min) min = v;
+                }
+                return new Range(min, max);
+            }
+            catch (NumberFormatException nfe) {
+                return MAX_RANGE;
+            }
+        }
+        try {
+            return new Range(Double.parseDouble(from), Double.parseDouble(to));
+        }
+        catch (NumberFormatException nfe) {
+            return MAX_RANGE;
+        }
+    }
+
+    private static List<ValueRange> filterByRange(Range range, List<ValueRange> ranges) {
+        List<ValueRange> list = new ArrayList<ValueRange>(ranges.size());
+        for (ValueRange r: ranges) {
+            if (r.intersects(range)) {
+                list.add(r);
+            }
+        }
+        return list;
+    }
+
+    private static List<ValueRange> filterByQRange(Range range, List<ValueRange> ranges) {
+        List<ValueRange> list = new ArrayList<ValueRange>(ranges.size());
+        for (ValueRange r: ranges) {
+            if (r.intersectsValueRange(range) && !list.contains(r)) {
+                list.add(r);
+            }
+        }
+        return list;
+    }
+
+    private static boolean isQ(D4EArtifact artifact) {
+        Boolean b = artifact.getDataAsBoolean("wq_isq");
+        return b != null && b;
+    }
+
+    private static boolean isRange(D4EArtifact artifact) {
+        Boolean b = artifact.getDataAsBoolean("wq_isrange");
+        return b != null && b;
+    }
+
+    public static final Range Q_OUT_OF_RANGE = new Range(-10000, -9999);
+
+    private static Range singleQs(D4EArtifact artifact) {
+        String singleData = nn(artifact.getDataAsString("wq_single"));
+        double min =  Double.MAX_VALUE;
+        double max = -Double.MAX_VALUE;
+
+        for (String value: singleData.split(" ")) {
+            try {
+                double x = Double.parseDouble(value);
+                if (x < min) min = x;
+                if (x > max) max = x;
+            }
+            catch (NumberFormatException nfe) {
+            }
+        }
+
+        return min == Double.MAX_VALUE
+            ? Q_OUT_OF_RANGE
+            : new Range(min, max);
+
+    }
+
+    private static Range qRange(D4EArtifact artifact) {
+        try {
+            Double from = artifact.getDataAsDouble("wq_from");
+            Double to   = artifact.getDataAsDouble("wq_to");
+
+            if (from == null || to == null) {
+                return Q_OUT_OF_RANGE;
+            }
+            double f = from;
+            double t = to;
+            return new Range(Math.min(f, t), Math.max(f, t));
+        }
+        catch (NumberFormatException nfe) {
+            return Q_OUT_OF_RANGE;
+        }
+    }
+
+    private static Range tripleQRange(D4EArtifact artifact) {
+        String rangesData = nn(artifact.getDataAsString("wq_values"));
+
+        double min =  Double.MAX_VALUE;
+        double max = -Double.MAX_VALUE;
+
+        for (String range: rangesData.split(":")) {
+            String [] parts = range.split(";");
+            if (parts.length < 3) {
+                continue;
+            }
+            String [] values = parts[2].split(",");
+            for (String value: values) {
+                try {
+                    double x = Double.parseDouble(value);
+                    if (x < min) min = x;
+                    if (x > max) max = x;
+                }
+                catch (NumberFormatException nfe) {
+                }
+            }
+        }
+
+        return min == Double.MAX_VALUE
+            ? Q_OUT_OF_RANGE
+            : new Range(min, max);
+    }
+
+    public static List<ValueRange> findOfficialLines(D4EArtifact artifact) {
+
+        if (!isQ(artifact)) { // Only handle Q calculations
+            return Collections.<ValueRange>emptyList();
+        }
+
+        Map<String, List<ValueRange>> rivers2officialLines = getAll();
+
+        String riverName = nn(artifact.getDataAsString("river"));
+
+        List<ValueRange> ranges = rivers2officialLines.get(riverName);
+
+        if (ranges == null) {
+            return Collections.<ValueRange>emptyList();
+        }
+
+        ranges = filterByRange(extractRange(artifact), ranges);
+
+        if (ranges.isEmpty()) {
+            return Collections.<ValueRange>emptyList();
+        }
+
+        Range qRange = isRange(artifact)
+            ? qRange(artifact)
+            : singleQs(artifact);
+
+        if (qRange == Q_OUT_OF_RANGE) {
+            qRange = tripleQRange(artifact);
+        }
+
+        return filterByQRange(qRange, ranges);
+    }
+}
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/Range.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/Range.java	Mon Jun 24 15:24:07 2013 +0200
@@ -31,10 +31,19 @@
         this.end   = end;
     }
 
+    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;
     }
@@ -69,5 +78,38 @@
     public boolean inside(double x) {
         return x > start-EPSILON && x < end+EPSILON;
     }
+
+    public boolean contains(double x) {
+        return inside(x);
+    }
+
+
+    /** Hash Code. */
+    @Override
+    public int hashCode() {
+        return new Double(this.start).hashCode() ^
+               new Double(this.end).hashCode();
+    }
+
+
+    /**
+     * Compares start and end values with some epsilon.
+     */
+    @Override
+    public boolean equals(Object otherRange) {
+        if (otherRange instanceof Range) {
+            Range oRange = (Range) otherRange;
+            return
+                Math.abs(oRange.start - this.start) <= EPSILON
+                && Math.abs(oRange.end - this.end) <= EPSILON;
+        }
+        return false;
+    }
+
+    /** Returns clone with same start and end values. */
+    @Override
+    public Object clone() {
+        return new Range(this.start, this.end);
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoad.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoad.java	Mon Jun 24 15:24:07 2013 +0200
@@ -13,6 +13,8 @@
 import java.util.Set;
 
 import org.dive4elements.river.artifacts.model.NamedObjectImpl;
+import org.dive4elements.river.artifacts.model.Range;
+
 
 /** Gives access to Fractions (at kms). */
 public class SedimentLoad
@@ -22,6 +24,7 @@
     protected Date start;
     protected Date end;
     protected boolean isEpoch;
+    protected String unit;
 
     protected HashMap<Double, SedimentLoadFraction> kms;
 
@@ -33,13 +36,15 @@
         String description,
         Date start,
         Date end,
-        boolean isEpoch
+        boolean isEpoch,
+        String unit
     ) {
         this();
         this.description = description;
         this.start = start;
         this.end = end;
         this.isEpoch = isEpoch;
+        this.unit = unit;
     }
 
     public String getDescription() {
@@ -89,68 +94,79 @@
         return kms.get(km);
     }
 
-    public void setCoarse(double km, double coarse) {
+    public void setCoarse(double km, double coarse, Range range) {
         if (kms.containsKey(km)) {
             kms.get(km).setCoarse(coarse);
         }
         else {
             SedimentLoadFraction f = new SedimentLoadFraction();
             f.setCoarse(coarse);
-            kms.put(km, f);
-        }
-    }
-
-    public void setFineMiddle(double km, double fine_middle) {
-        if (kms.containsKey(km)) {
-            kms.get(km).setFine_middle(fine_middle);
-        }
-        else {
-            SedimentLoadFraction f = new SedimentLoadFraction();
-            f.setFine_middle(fine_middle);
-            kms.put(km, f);
-        }
-    }
-
-    public void setSand(double km, double sand) {
-        if (kms.containsKey(km)) {
-            kms.get(km).setSand(sand);
-        }
-        else {
-            SedimentLoadFraction f = new SedimentLoadFraction();
-            f.setSand(sand);
+            f.setCoarseRange(range);
             kms.put(km, f);
         }
     }
 
-    public void setSuspSand(double km, double susp_sand) {
+    public void setFineMiddle(double km, double fine_middle, Range range) {
         if (kms.containsKey(km)) {
-            kms.get(km).setSusp_sand(susp_sand);
+            kms.get(km).setFineMiddle(fine_middle);
+            kms.get(km).setFineMiddleRange(range);
         }
         else {
             SedimentLoadFraction f = new SedimentLoadFraction();
-            f.setSusp_sand(susp_sand);
+            f.setFineMiddle(fine_middle);
+            f.setFineMiddleRange(range);
             kms.put(km, f);
         }
     }
 
-    public void setSuspSandBed(double km, double susp_sand_bed) {
+    public void setSand(double km, double sand, Range range) {
         if (kms.containsKey(km)) {
-            kms.get(km).setSusp_sand_bed(susp_sand_bed);
+            kms.get(km).setSand(sand);
+            kms.get(km).setSandRange(range);
         }
         else {
             SedimentLoadFraction f = new SedimentLoadFraction();
-            f.setSusp_sand_bed(susp_sand_bed);
+            f.setSand(sand);
+            f.setSandRange(range);
             kms.put(km, f);
         }
     }
 
-    public void setSuspSediment(double km, double susp_sediment) {
+    public void setSuspSand(double km, double susp_sand, Range range) {
         if (kms.containsKey(km)) {
-            kms.get(km).setSusp_sediment(susp_sediment);
+            kms.get(km).setSuspSand(susp_sand);
+            kms.get(km).setSuspSandRange(range);
         }
         else {
             SedimentLoadFraction f = new SedimentLoadFraction();
-            f.setSusp_sediment(susp_sediment);
+            f.setSuspSand(susp_sand);
+            f.setSuspSandRange(range);
+            kms.put(km, f);
+        }
+    }
+
+    public void setSuspSandBed(double km, double susp_sand_bed, Range range) {
+        if (kms.containsKey(km)) {
+            kms.get(km).setSuspSandBed(susp_sand_bed);
+            kms.get(km).setSuspSandBedRange(range);
+        }
+        else {
+            SedimentLoadFraction f = new SedimentLoadFraction();
+            f.setSuspSandBed(susp_sand_bed);
+            f.setSuspSandBedRange(range);
+            kms.put(km, f);
+        }
+    }
+
+    public void setSuspSediment(double km, double susp_sediment, Range range) {
+        if (kms.containsKey(km)) {
+            kms.get(km).setSuspSediment(susp_sediment);
+            kms.get(km).setSuspSedimentRange(range);
+        }
+        else {
+            SedimentLoadFraction f = new SedimentLoadFraction();
+            f.setSuspSediment(susp_sediment);
+            f.setSuspSedimentRange(range);
             kms.put(km, f);
         }
     }
@@ -166,17 +182,40 @@
         }
     }
 
-    public void setTotal(double km, double total) {
+    public void setTotal(double km, double total, Range range) {
         if (kms.containsKey(km)) {
             kms.get(km).setTotal(total);
+            kms.get(km).setTotalRange(range);
         }
         else {
             SedimentLoadFraction f = new SedimentLoadFraction();
             f.setTotal(total);
+            f.setTotalRange(range);
             kms.put(km, f);
         }
     }
 
+    public void setUnknown(double km, double unknown, Range range) {
+        if (kms.containsKey(km)) {
+            kms.get(km).setUnknown(unknown);
+            kms.get(km).setUnknownRange(range);
+        }
+        else {
+            SedimentLoadFraction f = new SedimentLoadFraction();
+            f.setUnknown(unknown);
+            f.setUnknownRange(range);
+            kms.put(km, f);
+        }
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+
     public boolean hasCoarse() {
         for (SedimentLoadFraction slf : kms.values()) {
             if (slf.getCoarse() > 0d) {
@@ -188,7 +227,7 @@
 
     public boolean hasFineMiddle() {
         for (SedimentLoadFraction slf : kms.values()) {
-            if (slf.getFine_middle() > 0d) {
+            if (slf.getFineMiddle() > 0d) {
                 return true;
             }
         }
@@ -206,7 +245,7 @@
 
     public boolean hasSuspSand() {
         for (SedimentLoadFraction slf : kms.values()) {
-            if (slf.getSusp_sand() > 0d) {
+            if (slf.getSuspSand() > 0d) {
                 return true;
             }
         }
@@ -215,7 +254,7 @@
 
     public boolean hasSuspSediment() {
         for (SedimentLoadFraction slf : kms.values()) {
-            if (slf.getSusp_sediment() > 0d) {
+            if (slf.getSuspSediment() > 0d) {
                 return true;
             }
         }
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadCalculation.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadCalculation.java	Mon Jun 24 15:24:07 2013 +0200
@@ -18,6 +18,7 @@
 import org.dive4elements.river.artifacts.access.SedimentLoadAccess;
 import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.Range;
 
 
 /** Calculate sediment load. */
@@ -155,42 +156,42 @@
                 SedimentLoadFraction f = load.getFraction(km);
                 if (f.getCoarse() > 0d) {
                     double c = resLoad.getFraction(km).getCoarse();
-                    resLoad.setCoarse(km, c + f.getCoarse());
+                    resLoad.setCoarse(km, c + f.getCoarse(), f.getCoarseRange());
                     cSum++;
                 }
-                if (f.getFine_middle() > 0d) {
-                    double fm = resLoad.getFraction(km).getFine_middle();
-                    resLoad.setFineMiddle(km, fm + f.getFine_middle());
+                if (f.getFineMiddle() > 0d) {
+                    double fm = resLoad.getFraction(km).getFineMiddle();
+                    resLoad.setFineMiddle(km, fm + f.getFineMiddle(), f.getFineMiddleRange());
                     fmSum++;
                 }
                 if (f.getSand() > 0d) {
                     double s = resLoad.getFraction(km).getSand();
-                    resLoad.setSand(km, s + f.getSand());
+                    resLoad.setSand(km, s + f.getSand(), f.getSandRange());
                     sSum++;
                 }
-                if (f.getSusp_sand() > 0d) {
-                    double s = resLoad.getFraction(km).getSusp_sand();
-                    resLoad.setSuspSand(km, s + f.getSusp_sand());
+                if (f.getSuspSand() > 0d) {
+                    double s = resLoad.getFraction(km).getSuspSand();
+                    resLoad.setSuspSand(km, s + f.getSuspSand(), f.getSuspSandRange());
                     ssSum++;
                 }
-                if (f.getSusp_sand_bed() > 0d) {
-                    double s = resLoad.getFraction(km).getSusp_sand_bed();
-                    resLoad.setSuspSandBed(km, s + f.getSusp_sand_bed());
+                if (f.getSuspSandBed() > 0d) {
+                    double s = resLoad.getFraction(km).getSuspSandBed();
+                    resLoad.setSuspSandBed(km, s + f.getSuspSandBed(), f.getSuspSandBedRange());
                     ssbSum++;
                 }
-                if (f.getSusp_sediment() > 0d) {
-                    double s = resLoad.getFraction(km).getSusp_sediment();
-                    resLoad.setSuspSediment(km, s + f.getSusp_sediment());
+                if (f.getSuspSediment() > 0d) {
+                    double s = resLoad.getFraction(km).getSuspSediment();
+                    resLoad.setSuspSediment(km, s + f.getSuspSediment(), f.getSuspSedimentRange());
                     sseSum++;
                 }
             }
             SedimentLoadFraction fr = resLoad.getFraction(km);
-            resLoad.setCoarse(km, fr.getCoarse()/cSum);
-            resLoad.setFineMiddle(km, fr.getFine_middle()/fmSum);
-            resLoad.setSand(km, fr.getSand()/sSum);
-            resLoad.setSuspSand(km, fr.getSusp_sand()/ssSum);
-            resLoad.setSuspSandBed(km, fr.getSusp_sand_bed()/ssbSum);
-            resLoad.setSuspSediment(km, fr.getSusp_sediment()/sseSum);
+            resLoad.setCoarse(km, fr.getCoarse()/cSum, fr.getCoarseRange());
+            resLoad.setFineMiddle(km, fr.getFineMiddle()/fmSum, fr.getFineMiddleRange());
+            resLoad.setSand(km, fr.getSand()/sSum, fr.getSandRange());
+            resLoad.setSuspSand(km, fr.getSuspSand()/ssSum, fr.getSuspSandRange());
+            resLoad.setSuspSandBed(km, fr.getSuspSandBed()/ssbSum, fr.getSuspSandBedRange());
+            resLoad.setSuspSediment(km, fr.getSuspSediment()/sseSum, fr.getSuspSedimentRange());
         }
         resLoad.setDescription("");
         resLoad.setEpoch(true);
@@ -264,6 +265,7 @@
         return result;
     }
 
+    /** Add up the loads of a year. */
     private SedimentLoad calculateTotalLoad(SedimentLoad load, int year) {
         logger.debug("calculateTotalLoad");
         boolean problemThisYear = false;
@@ -291,39 +293,180 @@
             logger.warn("Some problem, not calculating total load.");
             return load;
         }
-        for(double km : load.getKms()) {
+        return partialTotal(load);
+    }
+
+
+    /** Returns true if all fraction values except SuspSediment are unset. */
+    private boolean hasOnlySuspValues(SedimentLoadFraction fraction) {
+        return (fraction.getSuspSediment() != 0d &&
+            fraction.getSuspSand() != 0d &&
+            fraction.getCoarse() == 0d &&
+            fraction.getFineMiddle() == 0d &&
+            fraction.getSand() == 0d);
+    }
+
+
+    /** Returns true if all fraction values except SuspSediment are set. */
+    private boolean hasButSuspValues(SedimentLoadFraction fraction) {
+        return (fraction.getSuspSediment() == 0d &&
+            fraction.getSuspSand() == 0d &&
+            fraction.getCoarse() != 0d &&
+            fraction.getFineMiddle() != 0d &&
+            fraction.getSand() != 0d);
+    }
+
+
+    /** Returns true if all fraction needed for total calculation are set. */
+    private boolean complete(SedimentLoadFraction fraction) {
+        return (fraction.getCoarse() != 0d &&
+                fraction.getFineMiddle() != 0d &&
+                fraction.getSand() != 0d &&
+                fraction.getSuspSand() != 0d &&
+                fraction.getSuspSediment() != 0d);
+    }
+
+
+    /**
+     * Set total values in load.
+     * Therefore, run over the kms and find ranges where either all
+     * or all Geschiebe or just the Schwebstoff fractions are set.
+     * Merge these ranges and add (maybe new) respective fractions to
+     * load.
+     * @param load SedimentLoad to add total values (and ranges) to.
+     * @return input param load.
+     */
+    private SedimentLoad partialTotal(SedimentLoad load) {
+        SedimentLoad fairLoad = load;
+
+        Range lastOtherRange = null;
+        double lastOtherValue = 0d;
+
+        Range lastSuspRange = null;
+        double lastSuspValue = 0d;
+
+        for (double km: load.getKms()) {
+            logger.debug ("Trying to add at km " + km);
             SedimentLoadFraction fraction = load.getFraction(km);
-            double total = 0d;
-            if ((fraction.getCoarse() <= 0d && load.hasCoarse())){
-                addProblem(km, "missing.data.coarse");
-                continue;
-            }
-            if (fraction.getFine_middle() <= 0d && load.hasFineMiddle()) {
-                addProblem(km, "missing.data.fine_middle");
-                continue;
-            }
-            if (fraction.getSand() <= 0d && load.hasSand()) {
-                addProblem(km, "missing data.sand");
-                continue;
+            if (complete(fraction)) {
+                double total = fraction.getCoarse() +
+                    fraction.getFineMiddle() +
+                    fraction.getSand() +
+                    fraction.getSuspSand() +
+                    fraction.getSuspSediment();
+                // Easiest case. Add values up and set'em.
+                if (fraction.getCoarseRange().equals(
+                    fraction.getSuspSedimentRange())) {
+                    lastOtherRange = null;
+                    lastSuspRange = null;
+                    fairLoad.setTotal(km, total, fraction.getCoarseRange());
+                }
+                else {
+                    // Need to split a range.
+                    if (fraction.getCoarseRange().getEnd()
+                        < fraction.getSuspSedimentRange().getEnd()) {
+                        // Schwebstoff is longer.
+                        // Adjust and remember schwebstoffs range and value.
+                        lastSuspRange = (Range) fraction.getSuspSedimentRange().clone();
+                        lastSuspRange.setStart(fraction.getCoarseRange().getEnd());
+                        lastSuspValue = fraction.getSuspSediment() + fraction.getSuspSand();
+                        lastOtherRange = null;
+                        fairLoad.setTotal(km, total, fraction.getCoarseRange());
+                    }
+                    else {
+                        // Geschiebe is longer.
+                        // Adjust and remember other values.
+                        lastOtherRange = (Range) fraction.getSuspSedimentRange().clone();
+                        lastOtherRange.setStart(fraction.getSuspSedimentRange().getEnd());
+                        lastOtherValue = (total - fraction.getSuspSediment());
+                        lastSuspRange = null;
+                        fairLoad.setTotal(km, total, fraction.getSuspSedimentRange());
+                    }
+                }
             }
-            if (fraction.getSusp_sand() <= 0d && load.hasSuspSand()) {
-                addProblem(km, "missing.data.susp_sand");
-                continue;
-            }
-            if (fraction.getSusp_sediment() <= 0d && load.hasSuspSediment()) {
-                addProblem(km, "missing.data.susp_sediment");
-                continue;
+            else if (hasOnlySuspValues(fraction) && lastOtherRange != null) {
+                // Split stuff.
+                Range suspSedimentRange = fraction.getSuspSedimentRange();
+                // if intersects with last other range, cool! merge and add!
+                if (lastOtherRange.contains(km)) {
+                    double maxStart = 0d;
+                    double minEnd = 0d;
+                    maxStart = Math.max(suspSedimentRange.getStart(),
+                        lastOtherRange.getStart());
+
+                    minEnd = Math.min(suspSedimentRange.getEnd(),
+                        lastOtherRange.getEnd());
+                    double total = lastOtherValue + fraction.getSuspSediment();
+                    Range totalRange = new Range(maxStart, minEnd);
+                    if (suspSedimentRange.getEnd() > lastOtherRange.getEnd()) {
+                        lastSuspRange = (Range) suspSedimentRange.clone();
+                        lastSuspRange.setStart(lastOtherRange.getEnd());
+                        lastSuspValue = fraction.getSuspSediment() + fraction.getSuspSand();
+                        lastOtherRange = null;
+                    }
+                    else {
+                        // Other is "longer".
+                        lastOtherRange.setStart(suspSedimentRange.getEnd());
+                        lastSuspRange = null;
+                    }
+                    if (Math.abs(suspSedimentRange.getEnd() - lastOtherRange.getEnd()) < 0.1d) {
+                        lastOtherRange = null;
+                        lastSuspRange = null;
+                    }
+                    fairLoad.setTotal(km, total + fraction.getSuspSediment(), totalRange);
+                }
+                else {
+                    lastSuspRange = suspSedimentRange;
+                    lastSuspValue = fraction.getSuspSediment() + fraction.getSuspSand();
+                    lastOtherRange = null;
+                }
             }
-            total += fraction.getCoarse() +
-                fraction.getFine_middle() +
-                fraction.getSand() +
-                fraction.getSusp_sand() +
-                fraction.getSusp_sediment();
-            load.setTotal(km, total);
+            else if (hasButSuspValues(fraction) && lastSuspRange != null) {
+                // If intersects with last suspsed range, merge and add
+                double total = fraction.getCoarse() +
+                    fraction.getFineMiddle() +
+                    fraction.getSand() +
+                    lastSuspValue;
+                double maxStart = Math.max(fraction.getCoarseRange().getStart(),
+                    lastSuspRange.getStart());
+                if (lastSuspRange.contains(km)) {
+                    double minEnd = Math.min(fraction.getCoarseRange().getEnd(),
+                        lastSuspRange.getEnd());
+                    Range totalRange = new Range(maxStart, minEnd);
+                    if (lastSuspRange.getEnd() > fraction.getCoarseRange().getEnd()) {
+                        // SuspSed longer.
+                        lastSuspRange.setStart(fraction.getCoarseRange().getEnd());
+                        lastOtherRange = null;
+                    }
+                    else {
+                        // Other longer
+                        lastOtherRange = (Range) fraction.getCoarseRange().clone();
+                        lastOtherRange.setStart(lastSuspRange.getEnd());
+                        lastSuspRange = null;
+                        lastOtherValue = total - lastSuspValue;
+                    }
+                    if (Math.abs(lastSuspRange.getEnd() - lastOtherRange.getEnd()) < 0.1d) {
+                        lastOtherRange = null;
+                        lastSuspRange = null;
+                    }
+                    fairLoad.setTotal(km, total, totalRange);
+                }
+                else {
+                    // Ranges are disjoint.
+                    lastOtherRange = fraction.getCoarseRange();
+                    lastOtherValue = total - fraction.getSuspSediment();
+                    lastSuspRange = null;
+                }
+            }
+            else {
+                // Some values are missing or no intersection with former values.
+                // Stay as we are.
+            }
         }
-        return load;
+        return fairLoad;
     }
 
+
     private SedimentLoad calculateUnit(SedimentLoad load, int year) {
         SedimentDensity density =
             SedimentDensityFactory.getSedimentDensity(river, kmLow, kmUp, year);
@@ -331,19 +474,19 @@
             double dens = density.getDensity(km, year);
             SedimentLoadFraction fraction = load.getFraction(km);
             double coarse = fraction.getCoarse();
-            double fineMiddle = fraction.getFine_middle();
+            double fineMiddle = fraction.getFineMiddle();
             double sand = fraction.getSand();
-            double suspSand = fraction.getSusp_sand();
-            double bedSand = fraction.getSusp_sand_bed();
-            double sediment = fraction.getSusp_sediment();
+            double suspSand = fraction.getSuspSand();
+            double bedSand = fraction.getSuspSandBed();
+            double sediment = fraction.getSuspSediment();
             double total = fraction.getTotal();
-            load.setCoarse(km, (coarse * dens));
-            load.setFineMiddle(km, (fineMiddle * dens));
-            load.setSand(km, (sand * dens));
-            load.setSuspSand(km, (suspSand * dens));
-            load.setSuspSandBed(km, (bedSand * dens));
-            load.setSuspSediment(km, (sediment * dens));
-            load.setTotal(km, (total * dens));
+            load.setCoarse(km, (coarse * dens), fraction.getCoarseRange());
+            load.setFineMiddle(km, (fineMiddle * dens), fraction.getFineMiddleRange());
+            load.setSand(km, (sand * dens), fraction.getSandRange());
+            load.setSuspSand(km, (suspSand * dens), fraction.getSuspSandRange());
+            load.setSuspSandBed(km, (bedSand * dens), fraction.getSuspSandBedRange());
+            load.setSuspSediment(km, (sediment * dens), fraction.getSuspSedimentRange());
+            load.setTotal(km, (total * dens), fraction.getTotalRange());
         }
         return load;
     }
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFacet.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFacet.java	Mon Jun 24 15:24:07 2013 +0200
@@ -8,6 +8,8 @@
 
 package org.dive4elements.river.artifacts.model.minfo;
 
+import gnu.trove.TDoubleArrayList;
+
 import org.dive4elements.artifactdatabase.state.Facet;
 
 import org.dive4elements.artifacts.Artifact;
@@ -70,6 +72,11 @@
             return null;
         }
 
+        // These complicated calculations were necessary because
+        // SedimentLoad/Fraction did not contain the ranges of the given
+        // values. Since this changed, the code is somewhat obsolete, but stable.
+        // For an example of easier calculation, see the "total" part below.
+
         List<Double> sortedStarts = new ArrayList<Double>();
         // Filter stations according to type.
         List<MeasurementStation> stations = new ArrayList<MeasurementStation>();
@@ -90,7 +97,31 @@
         }
         Collections.sort(sortedStarts);
 
-        // Access data according to type.
+        // Handle sediment load differently, as it respects already
+        // the ranges that were added to SedimentLoad/Fraction.
+        if (getName().equals(FacetTypes.SEDIMENT_LOAD_TOTAL)) {
+            SedimentLoad load = result.getLoad();
+            TDoubleArrayList xPos = new TDoubleArrayList();
+            TDoubleArrayList yPos = new TDoubleArrayList();
+            double lastX = -1d;
+            for (double km: load.getKms()) {
+                SedimentLoadFraction fraction = load.getFraction(km);
+                if (fraction.getTotal() != 0) {
+                    if (Math.abs(lastX-km) >= EPSILON) {
+                       xPos.add(Double.NaN);
+                       yPos.add(Double.NaN);
+                    }
+                    xPos.add(km);
+                    yPos.add(fraction.getTotal());
+                    xPos.add(fraction.getTotalRange().getEnd());
+                    yPos.add(fraction.getTotal());
+                    lastX = fraction.getTotalRange().getEnd();
+                }
+            }
+            return new double[][] {xPos.toNativeArray(), yPos.toNativeArray()};
+        }
+
+        // Access data according to type (except total - see above).
         double[][] sd = getLoadData(result);
 
         // Sort by km.
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFactory.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFactory.java	Mon Jun 24 15:24:07 2013 +0200
@@ -13,19 +13,22 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.TreeMap;
 
 import net.sf.ehcache.Cache;
 import net.sf.ehcache.Element;
 
 import org.apache.log4j.Logger;
+import org.dive4elements.river.artifacts.cache.CacheFactory;
+import org.dive4elements.river.artifacts.model.Range;
+import org.dive4elements.river.artifacts.model.RiverFactory;
+import org.dive4elements.river.artifacts.model.StaticSedimentLoadCacheKey;
+import org.dive4elements.river.backend.SessionHolder;
+import org.dive4elements.river.model.MeasurementStation;
 import org.hibernate.SQLQuery;
 import org.hibernate.Session;
 import org.hibernate.type.StandardBasicTypes;
 
-import org.dive4elements.river.artifacts.cache.CacheFactory;
-import org.dive4elements.river.artifacts.model.StaticSedimentLoadCacheKey;
-import org.dive4elements.river.backend.SessionHolder;
-
 /** Pull Sediment Loads out of db. */
 public class SedimentLoadFactory
 {
@@ -53,11 +56,13 @@
         "SELECT DISTINCT " +
         "       sy.description AS description, " +
         "       ti.start_time AS start, " +
-        "       ti.stop_time AS end " +
+        "       ti.stop_time AS end, " +
+        "       u.name AS unit" +
         "   FROM     sediment_yield sy " +
         "       JOIN rivers r ON sy.river_id = r.id " +
         "       JOIN sediment_yield_values syv ON sy.id = syv.sediment_yield_id " +
         "       JOIN time_intervals ti ON sy.time_interval_id = ti.id " +
+        "       JOIN units u ON u.id = sy.unit_id " +
         "   WHERE   r.name = :name " +
         "       AND ti.stop_time IS NOT NULL " +
         "       AND syv.station BETWEEN :startKm AND :endKm";
@@ -67,17 +72,51 @@
         "       sy.description AS description, " +
         "       ti.start_time AS year, " +
         "       syv.value AS load, " +
-        "       syv.station AS km " +
+        "       syv.station AS km, " +
+        "       u.name AS unit " +
         "   FROM     sediment_yield sy " +
         "       JOIN rivers r ON sy.river_id = r.id " +
         "       JOIN time_intervals ti ON sy.time_interval_id = ti.id " +
         "       JOIN sediment_yield_values syv ON sy.id = syv.sediment_yield_id " +
         "       JOIN grain_fraction gf ON sy.grain_fraction_id = gf.id " +
+        "       JOIN units u ON u.id = sy.unit_id" +
         "   WHERE   r.name = :name " +
         "       AND ti.start_time BETWEEN :begin AND :end " +
         "       AND ti.stop_time IS NULL " +
         "       AND gf.name = :grain " +
-        "       AND syv.station BETWEEN :startKm AND :endKm";
+        "       AND syv.station BETWEEN :startKm AND :endKm " +
+        "   ORDER BY syv.station";
+
+    public static final String SQL_SELECT_UNKNOWN_DATA =
+        "SELECT" +
+        "       sy.description AS description, " +
+        "       ti.start_time AS start, " +
+        "       ti.stop_time AS end, " +
+        "       syv.value AS load, " +
+        "       syv.station AS km, " +
+        "       u.name AS unit " +
+        "   FROM     sediment_yield sy " +
+        "       JOIN rivers r ON sy.river_id = r.id " +
+        "       JOIN time_intervals ti ON sy.time_interval_id = ti.id " +
+        "       JOIN sediment_yield_values syv ON sy.id = syv.sediment_yield_id " +
+        "       JOIN grain_fraction gf ON sy.grain_fraction_id = gf.id " +
+        "       JOIN units u ON u.id = sy.unit_id" +
+        "   WHERE   r.name = :name " +
+        "       AND sy.description = :descr " +
+        "       AND gf.name = 'unknown' " +
+        "   ORDER BY syv.station";
+
+    public static final String SQL_SELECT_UNKNOWN =
+        "SELECT DISTINCT " +
+        "    sy.description AS description, " +
+        "    ti.start_time AS start, " +
+        "    ti.stop_time AS end " +
+        "FROM sediment_yield sy " +
+        "    JOIN rivers r ON sy.river_id = r.id " +
+        "    JOIN time_intervals ti ON sy.time_interval_id = ti.id " +
+        "    JOIN grain_fraction gf ON sy.grain_fraction_id = gf.id " +
+        "WHERE r.name = :river" +
+        "    AND gf.name = 'unknown'";
 
     public static final String SQL_SELECT_EPOCHS_DATA =
         "SELECT" +
@@ -85,17 +124,20 @@
         "       ti.start_time AS startYear, " +
         "       syv.value AS load, " +
         "       syv.station AS km," +
-        "       ti.stop_time AS endYear " +
+        "       ti.stop_time AS endYear, " +
+        "       u.name AS unit" +
         "   FROM     sediment_yield sy " +
         "       JOIN rivers r ON sy.river_id = r.id " +
         "       JOIN time_intervals ti ON sy.time_interval_id = ti.id " +
         "       JOIN sediment_yield_values syv ON sy.id = syv.sediment_yield_id " +
         "       JOIN grain_fraction gf ON sy.grain_fraction_id = gf.id " +
+        "       JOIN units u ON u.id = sy.unit_id " +
         "   WHERE   r.name = :name " +
         "       AND ti.start_time BETWEEN :sbegin AND :send " +
         "       AND ti.stop_time BETWEEN :ebegin AND :eend " +
         "       AND gf.name = :grain " +
-        "       AND syv.station BETWEEN :startKm AND :endKm";
+        "       AND syv.station BETWEEN :startKm AND :endKm " +
+        "   ORDER BY syv.station";
 
     private SedimentLoadFactory() {
     }
@@ -218,7 +260,8 @@
                     (String) row[0],
                     (Date) row[1],
                     null,
-                    false);
+                    false,
+                    "");
             }
             return loads;
         }
@@ -239,7 +282,8 @@
                     (String) row[0],
                     (Date) row[1],
                     (Date) row[2],
-                    true);
+                    true,
+                    "");
             }
             return loads;
         }
@@ -264,6 +308,22 @@
         Session session = SessionHolder.HOLDER.get();
         SQLQuery sqlQuery = null;
 
+        List<MeasurementStation> allStations = RiverFactory.getRiver(river).getMeasurementStations();
+        TreeMap<Double,MeasurementStation> floatStations = new TreeMap<Double, MeasurementStation>();
+        TreeMap<Double,MeasurementStation> suspStations = new TreeMap<Double, MeasurementStation>();
+        for (MeasurementStation measurementStation: allStations) {
+            if (measurementStation.getMeasurementType() == null ||
+                measurementStation.getRange() == null) {
+                continue;
+            }
+            if (measurementStation.getMeasurementType().equals("Schwebstoff")) {
+                suspStations.put(measurementStation.getRange().getA().doubleValue(), measurementStation);
+            }
+            else if (measurementStation.getMeasurementType().equals("Geschiebe")) {
+                floatStations.put(measurementStation.getRange().getA().doubleValue(), measurementStation);
+            }
+        }
+
         Calendar start = Calendar.getInstance();
         start.set(syear - 1, 11, 31);
         Calendar end = Calendar.getInstance();
@@ -274,7 +334,8 @@
                 .addScalar("description", StandardBasicTypes.STRING)
                 .addScalar("year", StandardBasicTypes.DATE)
                 .addScalar("load", StandardBasicTypes.DOUBLE)
-                .addScalar("km", StandardBasicTypes.DOUBLE);
+                .addScalar("km", StandardBasicTypes.DOUBLE)
+                .addScalar("unit", StandardBasicTypes.STRING);
             sqlQuery.setString("name", river);
             sqlQuery.setDouble("startKm", startKm);
             sqlQuery.setDouble("endKm", endKm);
@@ -288,13 +349,14 @@
                     (String) row[0],
                     (Date) row[1],
                     null,
-                    false);
-            getValues("coarse", sqlQuery, load);
-            getValues("fine_middle", sqlQuery, load);
-            getValues("sand", sqlQuery, load);
-            getValues("suspended_sediment", sqlQuery, load);
-            getValues("susp_sand_bed", sqlQuery, load);
-            getValues("susp_sand", sqlQuery, load);
+                    false,
+                    (String) row[4]);
+            getValues("coarse", sqlQuery, load, floatStations);
+            getValues("fine_middle", sqlQuery, load, floatStations);
+            getValues("sand", sqlQuery, load, floatStations);
+            getValues("suspended_sediment", sqlQuery, load, suspStations);
+            getValues("susp_sand_bed", sqlQuery, load, suspStations);
+            getValues("susp_sand", sqlQuery, load, suspStations);
 
             return load;
         }
@@ -308,7 +370,8 @@
                 .addScalar("startYear", StandardBasicTypes.DATE)
                 .addScalar("load", StandardBasicTypes.DOUBLE)
                 .addScalar("km", StandardBasicTypes.DOUBLE)
-                .addScalar("endYear", StandardBasicTypes.DATE);
+                .addScalar("endYear", StandardBasicTypes.DATE)
+                .addScalar("unit", StandardBasicTypes.STRING);
             sqlQuery.setString("name", river);
             sqlQuery.setDouble("startKm", startKm);
             sqlQuery.setDouble("endKm", endKm);
@@ -327,19 +390,20 @@
                     (String) row[0],
                     (Date) row[1],
                     (Date) row[4],
-                    true);
+                    true,
+                    (String)row[5]);
             TDoubleArrayList kms = new TDoubleArrayList();
             for (int i = 0; i < results.size(); i++) {
                 row = results.get(i);
                 kms.add((Double)row[3]);
                 load.setLoadTotal((Double)row[3], (Double)row[2]);
             }
-            getValues("coarse", sqlQuery, load);
-            getValues("fine_middle", sqlQuery, load);
-            getValues("sand", sqlQuery, load);
-            getValues("suspended_sediment", sqlQuery, load);
-            getValues("susp_sand_bed", sqlQuery, load);
-            getValues("susp_sand", sqlQuery, load);
+            getValues("coarse", sqlQuery, load, floatStations);
+            getValues("fine_middle", sqlQuery, load, floatStations);
+            getValues("sand", sqlQuery, load, floatStations);
+            getValues("suspended_sediment", sqlQuery, load, suspStations);
+            getValues("susp_sand_bed", sqlQuery, load, suspStations);
+            getValues("susp_sand", sqlQuery, load, suspStations);
             return load;
         }
         return new SedimentLoad();
@@ -356,36 +420,151 @@
     protected static void getValues (
         String fraction,
         SQLQuery query,
-        SedimentLoad load
+        SedimentLoad load,
+        TreeMap<Double, MeasurementStation> stations
     ) {
         query.setString("grain", fraction);
         List<Object[]> results = query.list();
         for (int i = 0; i < results.size(); i++) {
             Object[] row = results.get(i);
             double km = (Double)row[3];
+            MeasurementStation station = stations.get(km);
+            MeasurementStation nextStation = null;
+            if (stations.ceilingEntry(km + 0.1d) != null) {
+                nextStation = stations.ceilingEntry(km + 0.1d).getValue();
+            }
+            Range range = null;
+            if (station == null) {
+                log.warn("No measurement station for " + fraction + " km " + km);
+            }
+            else {
+                if (nextStation != null)
+                    range = new Range(station.getRange().getA().doubleValue(),
+                        nextStation.getRange().getA().doubleValue());
+                else {
+                    // TODO end-of-river instead of B.
+                    range = new Range(station.getRange().getA().doubleValue(),
+                        station.getRange().getB().doubleValue());
+                }
+            }
             double v = -1;
             if (row[2] != null) {
                 v = ((Double)row[2]).doubleValue();
             }
             if (fraction.equals("coarse")) {
-                load.setCoarse(km, v);
+                load.setCoarse(km, v, range);
             }
             else if (fraction.equals("sand")) {
-                load.setSand(km, v);
+                load.setSand(km, v, range);
             }
             else if (fraction.equals("fine_middle")) {
-                load.setFineMiddle(km, v);
+                load.setFineMiddle(km, v, range);
             }
             else if (fraction.equals("suspended_sediment")) {
-                load.setSuspSediment(km, v);
+                load.setSuspSediment(km, v, range);
             }
             else if (fraction.equals("susp_sand")) {
-                load.setSuspSand(km, v);
+                load.setSuspSand(km, v, range);
             }
             else if (fraction.equals("susp_sand_bed")) {
-                load.setSuspSandBed(km, v);
+                load.setSuspSandBed(km, v, range);
             }
         }
     }
+
+    public static SedimentLoad getLoadUnknown(
+        String river,
+        String description
+    ) {
+        log.debug("SedimentLoadFactory.getLoadWithData");
+        Cache cache = CacheFactory.getCache(LOAD_DATA_CACHE_NAME);
+
+        if (cache == null) {
+            log.debug("Cache not configured.");
+            return getSedimentLoadUnknownUncached(river, description);
+        }
+
+        StaticSedimentLoadCacheKey key =
+            new StaticSedimentLoadCacheKey(river, 0d, 0d, 0, 0);
+
+        Element element = cache.get(key);
+
+        if (element != null) {
+            log.debug("SedimentLoad found in cache");
+            return (SedimentLoad)element.getValue();
+        }
+
+        SedimentLoad values = getSedimentLoadUnknownUncached(river, description);
+
+        if (values != null && key != null) {
+            log.debug("Store static bed height values in cache.");
+            element = new Element(key, values);
+            cache.put(element);
+        }
+        return values;
+    }
+
+    /**
+     * Get sediment loads with fraction 'unknown' from db.
+     * @param river the river
+     * @param type the sediment load type (year or epoch)
+     * @return according sediment loads.
+     */
+    public static SedimentLoad getSedimentLoadUnknownUncached(
+        String river,
+        String description
+    ) {
+        log.debug("SedimentLoadFactory.getSedimentLoadWithDataUncached");
+        Session session = SessionHolder.HOLDER.get();
+        SQLQuery sqlQuery = null;
+
+        sqlQuery = session.createSQLQuery(SQL_SELECT_UNKNOWN_DATA)
+            .addScalar("description", StandardBasicTypes.STRING)
+            .addScalar("start", StandardBasicTypes.DATE)
+            .addScalar("end", StandardBasicTypes.DATE)
+            .addScalar("load", StandardBasicTypes.DOUBLE)
+            .addScalar("km", StandardBasicTypes.DOUBLE)
+            .addScalar("unit", StandardBasicTypes.STRING);
+        sqlQuery.setString("name", river);
+        sqlQuery.setString("descr", description);
+        List<Object []> results = sqlQuery.list();
+        SedimentLoad load = new SedimentLoad();
+        if (results.isEmpty()) {
+            return new SedimentLoad();
+        }
+        Object[] row = results.get(0);
+        load = new SedimentLoad(
+            (String) row[0],
+            (Date) row[1],
+            (Date) row[2],
+            false,
+            (String)row[5]);
+
+        for (int i = 0; i < results.size(); i++) {
+            row = results.get(i);
+            SedimentLoadFraction fraction = new SedimentLoadFraction();
+            fraction.setUnknown((Double)row[3]);
+            load.addKm((Double)row[4], fraction);
+        }
+        return load;
+    }
+
+    public static SedimentLoad[] getSedimentLoadUnknown(String river) {
+        Session session = SessionHolder.HOLDER.get();
+        SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_UNKNOWN)
+            .addScalar("description", StandardBasicTypes.STRING)
+            .addScalar("start", StandardBasicTypes.DATE)
+            .addScalar("end", StandardBasicTypes.DATE);
+        sqlQuery.setString("river", river);
+        List<Object[]> results = sqlQuery.list();
+        SedimentLoad[] loads = new SedimentLoad[results.size()];
+        int counter = 0;
+        for (Object[] row: results) {
+            loads[counter] = new SedimentLoad(
+                (String)row[0], (Date)row[1], (Date)row[2], false, "");
+            counter++;
+        }
+        return loads;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFraction.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFraction.java	Mon Jun 24 15:24:07 2013 +0200
@@ -9,28 +9,41 @@
 package org.dive4elements.river.artifacts.model.minfo;
 
 import org.dive4elements.river.artifacts.model.NamedObjectImpl;
+import org.dive4elements.river.artifacts.model.Range;
 
-
+/** One part of sedimentload. */
 public class SedimentLoadFraction
 extends NamedObjectImpl
 {
     double sand;
-    double fine_middle;
+    double fineMiddle;
     double coarse;
-    double susp_sand;
-    double susp_sand_bed;
-    double susp_sediment;
+    double suspSand;
+    double suspSandBed;
+    double suspSediment;
     double loadTotal;
     double total;
+    double unknown;
+    /** Values are valid within this km range. */
+    Range sandRange = null;
+    Range fineMiddleRange = null;
+    Range coarseRange = null;
+    Range suspSandRange = null;
+    Range suspSandBedRange = null;
+    Range suspSedimentRange = null;
+    Range loadTotalRange = null;
+    Range totalRange = null;
+    Range unknownRange = null;
 
     public SedimentLoadFraction() {
         sand = 0d;
-        fine_middle = 0d;
+        fineMiddle = 0d;
         coarse = 0d;
-        susp_sand = 0d;
-        susp_sand_bed = 0d;
-        susp_sediment = 0d;
+        suspSand = 0d;
+        suspSandBed = 0d;
+        suspSediment = 0d;
         loadTotal = 0d;
+        unknown = 0d;
     }
 
     public double getSand() {
@@ -41,12 +54,28 @@
         this.sand = sand;
     }
 
-    public double getFine_middle() {
-        return fine_middle;
+    public void setSandRange(Range range) {
+        this.sandRange = range;
     }
 
-    public void setFine_middle(double fine_middle) {
-        this.fine_middle = fine_middle;
+    public Range getSandRange() {
+        return this.sandRange;
+    }
+
+    public double getFineMiddle() {
+        return fineMiddle;
+    }
+
+    public void setFineMiddle(double fineMiddle) {
+        this.fineMiddle = fineMiddle;
+    }
+
+    public void setFineMiddleRange(Range range) {
+        this.fineMiddleRange = range;
+    }
+
+    public Range getFineMiddleRange() {
+        return this.fineMiddleRange;
     }
 
     public double getCoarse() {
@@ -57,28 +86,60 @@
         this.coarse = coarse;
     }
 
-    public double getSusp_sand() {
-        return susp_sand;
-    }
-
-    public void setSusp_sand(double susp_sand) {
-        this.susp_sand = susp_sand;
+    public Range getCoarseRange() {
+        return this.coarseRange;
     }
 
-    public double getSusp_sand_bed() {
-        return susp_sand_bed;
+    public void setCoarseRange(Range range) {
+        this.coarseRange = range;
     }
 
-    public void setSusp_sand_bed(double susp_sand_bed) {
-        this.susp_sand_bed = susp_sand_bed;
+    public double getSuspSand() {
+        return suspSand;
     }
 
-    public double getSusp_sediment() {
-        return susp_sediment;
+    public void setSuspSand(double suspSand) {
+        this.suspSand = suspSand;
     }
 
-    public void setSusp_sediment(double susp_sediment) {
-        this.susp_sediment = susp_sediment;
+    public void setSuspSandRange(Range range) {
+        this.suspSandRange = range;
+    }
+
+    public Range getSuspSandRange() {
+        return this.suspSandRange;
+    }
+
+    public double getSuspSandBed() {
+        return suspSandBed;
+    }
+
+    public void setSuspSandBed(double suspSandBed) {
+        this.suspSandBed = suspSandBed;
+    }
+
+    public void setSuspSandBedRange(Range range) {
+        this.suspSandRange = range;
+    }
+
+    public Range getSuspSandBedRange() {
+        return this.suspSandRange;
+    }
+
+    public double getSuspSediment() {
+        return suspSediment;
+    }
+
+    public void setSuspSediment(double suspSediment) {
+        this.suspSediment = suspSediment;
+    }
+
+    public void setSuspSedimentRange(Range range) {
+        this.suspSedimentRange = range;
+    }
+
+    public Range getSuspSedimentRange() {
+        return this.suspSedimentRange;
     }
 
     public double getTotal() {
@@ -89,6 +150,14 @@
         this.total = total;
     }
 
+    public void setTotalRange(Range range) {
+        this.totalRange = range;
+    }
+
+    public Range getTotalRange() {
+        return this.totalRange;
+    }
+
     public double getLoadTotal() {
         return loadTotal;
     }
@@ -96,4 +165,25 @@
     public void setLoadTotal(double total) {
         this.loadTotal = total;
     }
+
+    public void setLoadTotalRange(Range range) {
+        this.loadTotalRange = range;
+    }
+
+    public double getUnknown() {
+        return unknown;
+    }
+
+    public void setUnknown(double unknown) {
+        this.unknown = unknown;
+    }
+
+    public Range getUnknownRange() {
+        return unknownRange;
+    }
+
+    public void setUnknownRange(Range unknownRange) {
+        this.unknownRange = unknownRange;
+    }
 }
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadResult.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadResult.java	Mon Jun 24 15:24:07 2013 +0200
@@ -39,6 +39,10 @@
         this.load = load;
     }
 
+    public SedimentLoad getLoad() {
+        return this.load;
+    }
+
     public int getStartYear() {
         return this.startYear;
     }
@@ -94,9 +98,9 @@
         TDoubleArrayList k = new TDoubleArrayList();
         TDoubleArrayList fm = new TDoubleArrayList();
         for (double km : kms) {
-            if (load.getFraction(km).getFine_middle() > 0d) {
+            if (load.getFraction(km).getFineMiddle() > 0d) {
                 k.add(km);
-                fm.add(load.getFraction(km).getFine_middle());
+                fm.add(load.getFraction(km).getFineMiddle());
             }
         }
         return new double [][] {
@@ -126,9 +130,9 @@
         TDoubleArrayList k = new TDoubleArrayList();
         TDoubleArrayList ss = new TDoubleArrayList();
         for (double km : kms) {
-            if (load.getFraction(km).getSusp_sand() > 0d) {
+            if (load.getFraction(km).getSuspSand() > 0d) {
                 k.add(km);
-                ss.add(load.getFraction(km).getSusp_sand());
+                ss.add(load.getFraction(km).getSuspSand());
             }
         }
         return new double [][] {
@@ -142,9 +146,9 @@
         TDoubleArrayList k = new TDoubleArrayList();
         TDoubleArrayList ss = new TDoubleArrayList();
         for (double km : kms) {
-            if (load.getFraction(km).getSusp_sand_bed() > 0d) {
+            if (load.getFraction(km).getSuspSandBed() > 0d) {
                 k.add(km);
-                ss.add(load.getFraction(km).getSusp_sand_bed());
+                ss.add(load.getFraction(km).getSuspSandBed());
             }
         }
         return new double [][] {
@@ -158,9 +162,9 @@
         TDoubleArrayList k = new TDoubleArrayList();
         TDoubleArrayList ss = new TDoubleArrayList();
         for (double km : kms) {
-            if (load.getFraction(km).getSusp_sediment() > 0d) {
+            if (load.getFraction(km).getSuspSediment() > 0d) {
                 k.add(km);
-                ss.add(load.getFraction(km).getSusp_sediment());
+                ss.add(load.getFraction(km).getSuspSediment());
             }
         }
         return new double [][] {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadUnknownFacet.java	Mon Jun 24 15:24:07 2013 +0200
@@ -0,0 +1,66 @@
+package org.dive4elements.river.artifacts.model.minfo;
+
+import org.apache.log4j.Logger;
+import org.dive4elements.artifactdatabase.state.Facet;
+import org.dive4elements.artifacts.Artifact;
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.access.SedimentLoadAccess;
+import org.dive4elements.river.artifacts.model.DataFacet;
+import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
+
+
+public class SedimentLoadUnknownFacet
+extends DataFacet
+{
+    /** Very own logger. */
+    private static Logger logger = Logger.getLogger(SedimentLoadFacet.class);
+
+    public SedimentLoadUnknownFacet() {
+    }
+
+    public SedimentLoadUnknownFacet(int idx, String name, String description,
+        ComputeType type, String stateId, String hash) {
+        super(idx, name, description, type, hash, stateId);
+    }
+
+    @Override
+    public Object getData(Artifact artifact, CallContext context) {
+        logger.debug("Get data for sediment load at index: " + index);
+
+        D4EArtifact flys = (D4EArtifact) artifact;
+
+        SedimentLoadAccess access = new SedimentLoadAccess(flys);
+        String river = access.getRiver();
+        SedimentLoad[] unknown =
+            SedimentLoadFactory.getSedimentLoadUnknown(river);
+
+        SedimentLoad load = SedimentLoadFactory.getLoadUnknown(
+            river, unknown[index].getDescription());
+        if (access.getUnit().equals("t/a") && load.getUnit().equals("m3/a")) {
+            for (Double km: load.getKms()) {
+                SedimentLoadFraction fraction = load.getFraction(km);
+                fraction.setUnknown(fraction.getUnknown() / 1.8);
+                load.addKm(km, fraction);
+            }
+        }
+        else if (access.getUnit().equals("m3/a") && load.getUnit().equals("t/a")) {
+            for (Double km: load.getKms()) {
+                SedimentLoadFraction fraction = load.getFraction(km);
+                fraction.setUnknown(fraction.getUnknown() * 1.8);
+                load.addKm(km, fraction);
+            }
+        }
+        return load;
+    }
+
+    /** Copy deeply. */
+    @Override
+    public Facet deepCopy() {
+        SedimentLoadUnknownFacet copy = new SedimentLoadUnknownFacet();
+        copy.set(this);
+        copy.hash = hash;
+        copy.stateId = stateId;
+        return copy;
+    }
+}
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/minfo/SedimentLoadCalculate.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/minfo/SedimentLoadCalculate.java	Mon Jun 24 15:24:07 2013 +0200
@@ -9,10 +9,10 @@
 package org.dive4elements.river.artifacts.states.minfo;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
 import org.apache.log4j.Logger;
-
 import org.dive4elements.artifactdatabase.state.Facet;
 import org.dive4elements.artifactdatabase.state.FacetActivity;
 import org.dive4elements.artifacts.Artifact;
@@ -21,14 +21,17 @@
 import org.dive4elements.river.artifacts.D4EArtifact;
 import org.dive4elements.river.artifacts.access.SedimentLoadAccess;
 import org.dive4elements.river.artifacts.model.CalculationResult;
-import org.dive4elements.river.artifacts.model.DataFacet;
 import org.dive4elements.river.artifacts.model.FacetTypes;
 import org.dive4elements.river.artifacts.model.ReportFacet;
+import org.dive4elements.river.artifacts.model.minfo.SedimentLoad;
 import org.dive4elements.river.artifacts.model.minfo.SedimentLoadCalculation;
 import org.dive4elements.river.artifacts.model.minfo.SedimentLoadFacet;
+import org.dive4elements.river.artifacts.model.minfo.SedimentLoadFactory;
 import org.dive4elements.river.artifacts.model.minfo.SedimentLoadResult;
+import org.dive4elements.river.artifacts.model.minfo.SedimentLoadUnknownFacet;
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.artifacts.states.DefaultState;
+import org.dive4elements.river.utils.DateGuesser;
 
 
 public class SedimentLoadCalculate
@@ -70,6 +73,42 @@
                         name.equals(SEDIMENT_LOAD_SUSP_SAND_BED)){
                         return Boolean.FALSE;
                     }
+                    else if (name.equals(SEDIMENT_LOAD_UNKOWN)) {
+                        D4EArtifact d4e = (D4EArtifact)artifact;
+                        SedimentLoadUnknownFacet f =
+                            (SedimentLoadUnknownFacet)
+                                d4e.getNativeFacet(facet, null);
+                        SedimentLoad load =
+                            (SedimentLoad)f.getData(artifact, null);
+                        SedimentLoadAccess access =
+                            new SedimentLoadAccess(d4e);
+                        List<int[]> dates = new ArrayList<int[]>();
+                        if (access.getYearEpoch().equals("year")) {
+                            dates.add(access.getPeriod());
+                        }
+                        else {
+                            int[][] epochs = access.getEpochs();
+                            for (int i = 0; i < epochs.length; i++) {
+                                dates.add(epochs[i]);
+                            }
+                        }
+                        for (int[] date: dates) {
+                            try {
+                                Date s =
+                                    DateGuesser.guessDate(String.valueOf(date[0]));
+                                Date e =
+                                    DateGuesser.guessDate(String.valueOf(date[1]));
+                                if (!(s.after(load.getEnd()) ||
+                                      e.before(load.getStart()))) {
+                                    return Boolean.TRUE;
+                                }
+                            }
+                            catch (IllegalArgumentException iae) {
+                                return Boolean.FALSE;
+                            }
+                        }
+                        return Boolean.FALSE;
+                    }
                     else {
                         return null;
                     }
@@ -100,6 +139,10 @@
             return res;
         }
 
+        String river = access.getRiver();
+        SedimentLoad[] unknown =
+            SedimentLoadFactory.getSedimentLoadUnknown(river);
+
         String type = access.getYearEpoch();
         if (type.equals("year")) {
             generateYearFacets(context, newFacets, results, getID(), hash);
@@ -114,6 +157,16 @@
         if (res.getReport().hasProblems()) {
             newFacets.add(new ReportFacet(ComputeType.ADVANCE, hash, id));
         }
+
+        for (int i = 0; i < unknown.length; i++) {
+            newFacets.add(new SedimentLoadUnknownFacet(
+                i,
+                SEDIMENT_LOAD_UNKOWN,
+                unknown[i].getDescription(),
+                ComputeType.ADVANCE,
+                getID(),
+                hash));
+        }
         facets.addAll(newFacets);
 
         return res;
--- a/artifacts/src/main/java/org/dive4elements/river/exports/ComputedDischargeCurveGenerator.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/ComputedDischargeCurveGenerator.java	Mon Jun 24 15:24:07 2013 +0200
@@ -199,7 +199,7 @@
         Document         theme,
         boolean          visible
     ) {
-        logger.debug("ComputedDischargeCurveGenerator: doWQOut");
+        logger.debug("ComputedDischargeCurveGenerator: doQOut");
         XYSeries series = new StyledXYSeries(aaf.getFacetDescription(), theme);
         StyledSeriesBuilder.addPointsQW(series, wqkms);
 
--- a/artifacts/src/main/java/org/dive4elements/river/exports/minfo/SedimentLoadLSGenerator.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/minfo/SedimentLoadLSGenerator.java	Mon Jun 24 15:24:07 2013 +0200
@@ -9,6 +9,7 @@
 package org.dive4elements.river.exports.minfo;
 
 import java.util.Arrays;
+import java.util.Set;
 
 import org.apache.log4j.Logger;
 import org.jfree.data.xy.XYSeries;
@@ -24,6 +25,8 @@
 import org.dive4elements.river.artifacts.model.WKms;
 import org.dive4elements.river.artifacts.model.minfo.BedDiffEpochResult;
 import org.dive4elements.river.artifacts.model.minfo.BedDiffYearResult;
+import org.dive4elements.river.artifacts.model.minfo.SedimentLoad;
+import org.dive4elements.river.artifacts.model.minfo.SedimentLoadFraction;
 import org.dive4elements.river.exports.StyledSeriesBuilder;
 import org.dive4elements.river.exports.XYChartGenerator;
 import org.dive4elements.river.jfree.Bounds;
@@ -145,6 +148,13 @@
                 attr,
                 visible);
         }
+        else if (FacetTypes.IS.SEDIMENT_LOAD_UNKNOWN(name)) {
+            doSedimentLoadUnknownOut(
+                (SedimentLoad)bundle.getData(context),
+                bundle,
+                attr,
+                visible);
+        }
         else if (name.equals(FLOW_VELOCITY_TOTALCHANNEL)) {
             doFlowVelocityTotalOut(
                 (FlowVelocityData) bundle.getData(context),
@@ -257,6 +267,24 @@
         addAxisSeries(series, YAXIS.L.idx, visible);
     }
 
+    protected void doSedimentLoadUnknownOut(SedimentLoad load,
+        ArtifactAndFacet aandf, Document theme, boolean visible) {
+
+        Set<Double> kms = load.getKms();
+        double[][] data = new double[2][kms.size()];
+        int counter = 0;
+        for (Double km: kms) {
+            SedimentLoadFraction fraction = load.getFraction(km);
+            data[0][counter] = km;
+            data[1][counter] = fraction.getUnknown();
+            counter++;
+        }
+        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
+        StyledSeriesBuilder.addPoints(series, data, false);
+
+        addAxisSeries(series, YAXIS.L.idx, visible);
+    }
+
     protected void doFlowVelocityMainOut(
         FlowVelocityData data,
         ArtifactAndFacet aandf,
--- a/backend/doc/schema/oracle.sql	Fri Jun 21 14:35:18 2013 +0200
+++ b/backend/doc/schema/oracle.sql	Mon Jun 24 15:24:07 2013 +0200
@@ -351,9 +351,8 @@
     id                  NUMBER(38,0) NOT NULL,
     wst_column_id       NUMBER(38,0) NOT NULL,
     named_main_value_id NUMBER(38,0) NOT NULL,
-
-    PRIMARY KEY (id),
-    UNIQUE (wst_column_id, named_main_value_id)
+    UNIQUE (wst_column_id, named_main_value_id),
+    PRIMARY KEY (id)
 );
 
 -- WSTS
--- a/backend/src/main/java/org/dive4elements/river/importer/parsers/PRFParser.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/backend/src/main/java/org/dive4elements/river/importer/parsers/PRFParser.java	Mon Jun 24 15:24:07 2013 +0200
@@ -364,13 +364,13 @@
 
                 List<XY> kmData = data.get(station);
 
-                // When the station changed we know we are expecting skip/dummy lines.
+                // When the station changed (no data yet in line) we expect
+                // skip/dummy lines to follow
                 if (kmData == null) {
-                    //log.debug("found new km: " + station);
                     kmData = new ArrayList<XY>();
                     data.put(station, kmData);
                     // When a station change occurs, dummy lines will occur, too.
-                    skip = lineSkipCount;
+                    skip = lineSkipCount -1;
                     continue;
                 }
 
--- a/backend/src/main/java/org/dive4elements/river/model/OfficialLine.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/backend/src/main/java/org/dive4elements/river/model/OfficialLine.java	Mon Jun 24 15:24:07 2013 +0200
@@ -9,6 +9,7 @@
 package org.dive4elements.river.model;
 
 import java.io.Serializable;
+import java.util.List;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -20,6 +21,9 @@
 import javax.persistence.SequenceGenerator;
 import javax.persistence.Table;
 
+import org.dive4elements.river.backend.SessionHolder;
+import org.hibernate.Session;
+
 @Entity
 @Table(name = "official_lines")
 public class OfficialLine
@@ -74,5 +78,10 @@
     public void setNamedMainValue(NamedMainValue namedMainValue) {
         this.namedMainValue = namedMainValue;
     }
+
+    public static List<OfficialLine> fetchAllOfficalLines() {
+        Session session = SessionHolder.HOLDER.get();
+        return session.createQuery("from OfficialLine").list();
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.java	Mon Jun 24 15:24:07 2013 +0200
@@ -1301,5 +1301,7 @@
     String WATERBODY();
 
     String FEDSTATE_KM();
+
+    String official_regulation();
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.properties	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants.properties	Mon Jun 24 15:24:07 2013 +0200
@@ -681,3 +681,5 @@
 SOURCE = Source
 WATERBODY = Waterbody
 FEDSTATE_KM = Station (Federal State)
+
+official_regulation = official Regulation
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_de.properties	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_de.properties	Mon Jun 24 15:24:07 2013 +0200
@@ -680,3 +680,4 @@
 SOURCE = Quelle
 WATERBODY = Gewässer
 FEDSTATE_KM = Landes-Stationierung
+official_regulation = amtl. Festlegung
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_en.properties	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/FLYSConstants_en.properties	Mon Jun 24 15:24:07 2013 +0200
@@ -621,4 +621,4 @@
 
 # Get Feature Info Window
 PATH = Path
-
+official_regulation = Official Regulation
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/map/MapPrintPanel.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/map/MapPrintPanel.java	Mon Jun 24 15:24:07 2013 +0200
@@ -22,6 +22,7 @@
 import org.dive4elements.river.client.shared.model.Property;
 import org.dive4elements.river.client.shared.model.PropertySetting;
 import org.dive4elements.river.client.shared.model.Settings;
+import org.dive4elements.river.client.shared.MapUtils;
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.Window;
@@ -274,18 +275,19 @@
                 catch (MissingResourceException mre) {
                     localized = props.getName();
                 }
-                url.append(toJavaEncodedString(localized));
+                url.append(MapUtils.toSaveHTMLJavaString(localized));
                 url.append("=");
-                url.append(props.getValue());
+                url.append(MapUtils.toSaveHTMLJavaString((String)props.getValue()));
             }
         }
         // O.o
         String river = findRiver(((MapOutputTab)mapToolbar.getOutputTab()
                     ).getCollectionView().getArtifact());
-        url.append("&" + toJavaEncodedString(MSG.getString(MAPFISH_RIVER)) + "=" + river);
+        url.append("&" + MapUtils.toSaveHTMLJavaString(MSG.getString(MAPFISH_RIVER)) + "=" + 
+                MapUtils.toSaveHTMLJavaString(river));
     }
 
-    // Copy of DatacageWindow's findRiver
+    // Copy of DatacageWindow's findRiver with added state for map.river
     protected String findRiver(Artifact artifact) {
         ArtifactDescription adescr = artifact.getArtifactDescription();
         DataList [] data = adescr.getOldData();
@@ -293,7 +295,8 @@
         if (data != null && data.length > 0) {
             for (int i = 0; i < data.length; i++) {
                 DataList dl = data[i];
-                if (dl.getState().equals("state.winfo.river")) {
+                if (dl.getState().equals("state.winfo.river") ||
+                        dl.getState().equals("state.map.river")) {
                     for (int j = dl.size()-1; j >= 0; --j) {
                         Data d = dl.get(j);
                         DataItem [] di = d.getItems();
@@ -308,26 +311,6 @@
         return "";
     }
 
-    public static String toJavaEncodedString(String str) {
-        if (str == null) {
-            return null;
-        }
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0, len = str.length(); i < len; i++) {
-            int unipoint = Character.codePointAt(str, i);
-            if ((unipoint < 32) || (unipoint > 127)) {
-                sb.append("\\u");
-                sb.append(Integer.toHexString((unipoint >> 3*4) & 0xf));
-                sb.append(Integer.toHexString((unipoint >> 2*4) & 0xf));
-                sb.append(Integer.toHexString((unipoint >> 1*4) & 0xf));
-                sb.append(Integer.toHexString((unipoint >> 0*4) & 0xf));
-            } else {
-                sb.append(str.charAt(i));
-            }
-        }
-        return sb.toString();
-    }
-
     protected void updateCollection() {
         final Config config = Config.getInstance();
         final String loc    = config.getLocale();
@@ -335,16 +318,16 @@
         GWT.log("MapPrintPanel.updateCollection via RPC now");
 
         List<Property> properties = new ArrayList<Property>();
-        properties.add(new PropertySetting(MAPFISH_MAPTITLE, toJavaEncodedString(pageTitle.getValueAsString())));
-//        properties.add(new PropertySetting(MAPFISH_LAYOUT, toJavaEncodedString(pageFormat.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_SUBTITLE, toJavaEncodedString(pageSubtitle.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_RANGE, toJavaEncodedString(pageRange.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_STRETCH, toJavaEncodedString(pageStretch.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_INSTITUTION, toJavaEncodedString(pageInstitution.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_SOURCE, toJavaEncodedString(pageSource.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_CREATOR, toJavaEncodedString(pageCreator.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_DATEPLACE, toJavaEncodedString(pageDatePlace.getValueAsString())));
-        properties.add(new PropertySetting(MAPFISH_LOGO, toJavaEncodedString(pageLogo.getValueAsString())));
+        properties.add(new PropertySetting(MAPFISH_MAPTITLE, pageTitle.getValueAsString()));
+//        properties.add(new PropertySetting(MAPFISH_LAYOUT, pageFormat.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_SUBTITLE, pageSubtitle.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_RANGE, pageRange.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_STRETCH, pageStretch.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_INSTITUTION, pageInstitution.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_SOURCE, pageSource.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_CREATOR, pageCreator.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_DATEPLACE, pageDatePlace.getValueAsString()));
+        properties.add(new PropertySetting(MAPFISH_LOGO, pageLogo.getValueAsString()));
         settings.setSettings("default", properties);
 
         collection.addSettings("print-settings", settings);
--- a/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/wq/QDTable.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/client/ui/wq/QDTable.java	Mon Jun 24 15:24:07 2013 +0200
@@ -66,7 +66,7 @@
 
         ListGridField type = new ListGridField("type", MESSAGE.type());
         type.setType(ListGridFieldType.TEXT);
-        type.setWidth("20%");
+        type.setWidth("10%");
 
         final NumberFormat nf = NumberFormat.getDecimalFormat();
 
@@ -88,9 +88,14 @@
                 }
             }
         });
-        value.setWidth("20%");
+        value.setWidth("15%");
 
-        setFields(addMax, addMin, select, name, type, value);
+        ListGridField official = new ListGridField("official", MESSAGE.official_regulation());
+        official.setType(ListGridFieldType.TEXT);
+        official.setWidth("25%");
+
+
+        setFields(addMax, addMin, select, name, type, value, official);
     }
 
     public void hideIconFields () {
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/MapPrintServiceImpl.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/MapPrintServiceImpl.java	Mon Jun 24 15:24:07 2013 +0200
@@ -212,7 +212,7 @@
             Map<String, Object> legend = new LinkedHashMap<String, Object>();
             List<Object> classes = new ArrayList<Object>(1);
             Map<String, Object> clazz = new LinkedHashMap<String, Object>();
-            String lgu = MapUtils.getLegendGraphicUrl(layer.url, layer.layers, dpi);
+            String lgu = encode(MapUtils.getLegendGraphicUrl(layer.url, layer.layers, dpi));
             clazz.put("icon", lgu);
             clazz.put("name", layer.description);
             classes.add(clazz);
@@ -399,6 +399,7 @@
 
     private static final String encode(String s) {
         try {
+            if (s == null) return null;
             return URLEncoder.encode(s, "UTF-8");
         }
         catch (UnsupportedEncodingException usee) {
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/WQInfoServiceImpl.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/WQInfoServiceImpl.java	Mon Jun 24 15:24:07 2013 +0200
@@ -195,12 +195,16 @@
         String value = XMLUtils.xpathString(
             node, "@value", ArtifactNamespaceContext.INSTANCE);
 
+        String official = XMLUtils.xpathString(
+            node, "@official", ArtifactNamespaceContext.INSTANCE);
+
         if (name != null && type != null) {
             try {
                 return new WQInfoObjectImpl(
                     name,
                     type,
-                    new Double(value));
+                    new Double(value),
+                    official != null && official.equalsIgnoreCase("true"));
             }
             catch (NumberFormatException nfe) {
                 logger.warn(nfe.getLocalizedMessage());
--- a/gwt-client/src/main/java/org/dive4elements/river/client/shared/MapUtils.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/shared/MapUtils.java	Mon Jun 24 15:24:07 2013 +0200
@@ -10,6 +10,8 @@
 
 import java.util.Date;
 
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
 
 public class MapUtils {
 
@@ -47,5 +49,29 @@
 
         return url;
     }
+
+    public static String toSaveHTMLJavaString(String str) {
+        return str == null ? null : SafeHtmlUtils.htmlEscape(toJavaEncodedString(str));
+    }
+
+    public static String toJavaEncodedString(String str) {
+        if (str == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0, len = str.length(); i < len; i++) {
+            int unipoint = Character.codePointAt(str, i);
+            if ((unipoint < 32) || (unipoint > 127)) {
+                sb.append("\\u");
+                sb.append(Integer.toHexString((unipoint >> 3*4) & 0xf));
+                sb.append(Integer.toHexString((unipoint >> 2*4) & 0xf));
+                sb.append(Integer.toHexString((unipoint >> 1*4) & 0xf));
+                sb.append(Integer.toHexString((unipoint >> 0*4) & 0xf));
+            } else {
+                sb.append(str.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/WQInfoObject.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/WQInfoObject.java	Mon Jun 24 15:24:07 2013 +0200
@@ -21,5 +21,7 @@
     String getType();
 
     Double getValue();
+
+    boolean isOfficial();
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/WQInfoObjectImpl.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/WQInfoObjectImpl.java	Mon Jun 24 15:24:07 2013 +0200
@@ -20,6 +20,8 @@
 
     protected Double value;
 
+    protected boolean isOfficial;
+
 
     public WQInfoObjectImpl() {
     }
@@ -28,26 +30,36 @@
     public WQInfoObjectImpl(
         String name,
         String type,
-        Double value)
-    {
+        Double value,
+        boolean isOfficial
+    ) {
         this.name  = name;
         this.type  = type;
         this.value = value;
+        this.isOfficial = isOfficial;
     }
 
 
+    @Override
     public String getName() {
         return name;
     }
 
 
+    @Override
     public String getType() {
         return type;
     }
 
 
+    @Override
     public Double getValue() {
         return value;
     }
+
+    @Override
+    public boolean isOfficial() {
+        return isOfficial;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/WQInfoRecord.java	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/shared/model/WQInfoRecord.java	Mon Jun 24 15:24:07 2013 +0200
@@ -33,10 +33,8 @@
 
         setName(info.getName());
         setType(info.getType());
-        if (info.getValue() != null)
-            setValue(info.getValue());
-        else
-            setValue(info.getValue());
+        setValue(info.getValue());
+        setOfficial(info.isOfficial() ? "X" : "");
     }
 
 
@@ -68,6 +66,14 @@
         return getAttributeAsDouble("value");
     }
 
+    public void setOfficial(String value) {
+        setAttribute("official", value);
+    }
+
+    public String getOfficial() {
+        return getAttributeAsString("official");
+    }
+
 
     public WQInfoObject getWQInfo() {
         return wqInfo;
--- a/gwt-client/src/main/webapp/images/FLYS_Karte_interactive.html	Fri Jun 21 14:35:18 2013 +0200
+++ b/gwt-client/src/main/webapp/images/FLYS_Karte_interactive.html	Mon Jun 24 15:24:07 2013 +0200
@@ -54,7 +54,7 @@
   <body>
     <!-- Create  Mappings -->
     <div id="riverContainer">
-      <img src="images/FLYS_transp.png" usemap="#river" style="position: absolute;z-index: 100;">
+      <img src="images/FLYS_transp.png" usemap="#river" style="position: absolute; top: 8px; left: 8px; z-index: 100;" width="640" height="751">
       <map name="river">
 
         <!--FLYS_Karte.map-->

http://dive4elements.wald.intevation.org