changeset 2561:b3f6d49cdc80

Service to generate the data needed to build the "Fixerungen pro Fluss Uebersicht" flys-artifacts/trunk@4087 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 19 Feb 2012 17:14:39 +0000
parents 0d8c97374dc9
children ba35dfb7c09a
files flys-artifacts/ChangeLog flys-artifacts/doc/conf/cache.xml flys-artifacts/doc/conf/conf.xml flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverviewFactory.java flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java
diffstat 6 files changed, 858 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/flys-artifacts/ChangeLog	Fri Feb 17 14:16:50 2012 +0000
+++ b/flys-artifacts/ChangeLog	Sun Feb 19 17:14:39 2012 +0000
@@ -1,3 +1,56 @@
+2012-02-19  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	  Service to generate the data needed to build the "Fixerungen pro Fluss Uebersicht"
+
+	* src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java:
+	  New. A service that serves an XML overview version of the fixings of a given river.
+
+	  The input documents look like this:
+
+	    <?xml version="1.0" encoding="UTF-8"?>
+	    <fixings>
+	      <river name="Saar"/>
+	    </fixings>
+
+	  The output documents looks like this:
+
+	  <?xml version="1.0" encoding="UTF-8"?>
+	  <fixings>
+	    <river from="0.0" rid="1" to="93.0"/>
+	    <events>
+	      ...
+	      <event cid="85" date="29.01.1995 00:00" name="Fixierungen/0-93_1988-2001.wst">
+	        <sector class="0" from="82.7" to="92.85"/>
+	      </event>
+	      <event cid="86" date="13.11.1996 00:00" name="Fixierungen/0-93_1988-2001.wst">
+	        <sector class="2" from="0.0" to="50.599999"/>
+	        <sector class="1" from="50.6" to="65.89"/>
+	      </event>
+	      ...
+	    </events>
+	  </fixings>
+
+	  The <river> element contains the extent of the whole river
+	  and its db id.
+	  The <events> sections contains serveral <event> elements.
+	  They have a meassure date, db id, a name and a number of <sector>s.
+	  These sectors are q classified from/to ranges. Classes are:
+
+	  0: smaller (MNQ+MQ)/2
+	  1: (MNQ+MQ)/2 to (MQ+MHQ)/2
+	  2: (MQ+MHQ)/2 to HQ5
+	  3: greater HQ5
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java:
+	  New. Cacheable instance of the overview.
+	  TODO: Apply a filter mechanism when generating the output.
+
+	* src/main/java/de/intevation/flys/artifacts/model/FixingsOverviewFactory.java:
+	  New. Fetched the fixing overviews from cache if configured.
+
+	* doc/conf/conf.xml: Added the service.
+	* doc/conf/cache.xml: Configured the cache for the fixing overviews.
+
 2012-02-17  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
 
 	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(getMinMaxQ):
--- a/flys-artifacts/doc/conf/cache.xml	Fri Feb 17 14:16:50 2012 +0000
+++ b/flys-artifacts/doc/conf/cache.xml	Sun Feb 19 17:14:39 2012 +0000
@@ -94,6 +94,16 @@
            diskPersistent="true"
            />
 
+    <!-- This one is used to hold the fixings overviews. -->
+    <cache name="fixings-overviews"
+           maxElementsInMemory="15"
+           eternal="false"
+           timeToLiveSeconds="7200"
+           memoryStoreEvictionPolicy="LFU"
+           overflowToDisk="true"
+           diskPersistent="true"
+           />
+
     <!-- This one is used to load single HYK Zones  -->
     <cache name="hykache"
            maxElementsInMemory="500"
--- a/flys-artifacts/doc/conf/conf.xml	Fri Feb 17 14:16:50 2012 +0000
+++ b/flys-artifacts/doc/conf/conf.xml	Sun Feb 19 17:14:39 2012 +0000
@@ -120,6 +120,10 @@
                 name="dischargeinfo"
                 service="de.intevation.flys.artifacts.services.DischargeInfoService"
                 description="Returns description, start year and end year of discharges at a specific gauge.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
+            <service-factory
+                name="fixings-overview"
+                service="de.intevation.flys.artifacts.services.FixingsOverviewService"
+                description="Returns an overview of the fixings of a given river.">de.intevation.artifactdatabase.DefaultServiceFactory</service-factory>
         </service-factories>
 
     </factories>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java	Sun Feb 19 17:14:39 2012 +0000
@@ -0,0 +1,677 @@
+package de.intevation.flys.artifacts.model;
+
+import java.io.Serializable;
+
+import java.text.SimpleDateFormat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+
+import org.hibernate.type.StandardBasicTypes;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class FixingsOverview
+implements   Serializable
+{
+    private static Logger log = Logger.getLogger(FixingsOverview.class);
+
+    public static final double EPSILON  = 1e-5;
+    public static final double EPSILON2 = 1e-1;
+
+    public static final String SQL_RIVER_ID =
+        "SELECT" +
+        "    id AS river_id," +
+        "    km_up " +
+        "FROM rivers " +
+        "WHERE" +
+        "    name = :name";
+
+    public static final String SQL_RIVER_EXTENT =
+        "SELECT" +
+        "    MIN(wcv.position) AS start_km," +
+        "    MAX(wcv.position) AS end_km " +
+        "FROM wst_column_values wcv" +
+        "    JOIN wst_columns wc ON wcv.wst_column_id = wc.id " +
+        "    JOIN wsts        w  ON wc.wst_id = w.id " +
+        "WHERE" +
+        "    w.kind = 0 AND w.river_id = :river_id";
+
+    public static final String SQL_GAUGES =
+        "SELECT" +
+        "    g.id AS gauge_id," +
+        "    r.a  AS a," +
+        "    r.b  AS b " +
+        "FROM gauges g" +
+        "    JOIN ranges r ON g.range_id = r.id " +
+        "WHERE" +
+        "    g.river_id = :river_id " +
+        "ORDER BY r.a";
+
+    public static final String SQL_DISCHARGE_SECTORS =
+        "SELECT" +
+        "    g.id                            AS gauge_id," +
+        "    nmv.name                        AS name," +
+        "    CAST(mv.value AS NUMERIC(38,2)) AS value " +
+        "FROM gauges g" +
+        "    JOIN main_values       mv  ON g.id = mv.gauge_id" +
+        "    JOIN named_main_values nmv ON nmv.id = mv.named_value_id" +
+        "    JOIN main_value_types  mvt ON nmv.type_id = mvt.id " +
+        "WHERE" +
+        "    mvt.name = 'Q' AND (" +
+        "        nmv.name = 'MNQ'      OR" +
+        "        nmv.name LIKE 'MNQ(%' OR" +
+        "        nmv.name = 'MQ'       OR" +
+        "        nmv.name LIKE 'MQ(%'  OR" +
+        "        nmv.name = 'MHQ'      OR" +
+        "        nmv.name LIKE 'MHQ(%' OR" +
+        "        nmv.name = 'HQ5'      OR" +
+        "        nmv.name LIKE 'HQ5(%') AND" +
+        "    g.river_id = :river_id " +
+        "ORDER BY" +
+        "    g.id";
+
+    public static final String SQL_FIXINGS =
+        "SELECT" +
+        "    id AS wst_id," +
+        "    description " +
+        "FROM wsts " +
+        "WHERE" +
+        "    river_id = :river_id AND kind = 2";
+
+    public static final String SQL_FIXING_COLUMNS =
+        "SELECT" +
+        "    wc.id         AS wst_column_id," +
+        "    ti.start_time AS start_time," +
+        "    wc.name       AS name " +
+        "FROM wst_columns wc" +
+        "    JOIN time_intervals ti ON wc.time_interval_id = ti.id " +
+        "WHERE" +
+        "    wc.wst_id = :wst_id " +
+        "ORDER BY position";
+
+    public static final String SQL_FIXING_COLUMN_Q_RANGES =
+        "SELECT" +
+        "    wqr.q AS q," +
+        "    r.a   AS start_km," +
+        "    r.b   AS stop_km " +
+        "FROM wst_column_q_ranges wcqr" +
+        "    JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id" +
+        "    JOIN ranges       r   ON wqr.range_id        = r.id " +
+        "WHERE" +
+        "    wcqr.wst_column_id = :column_id " +
+        "ORDER BY r.a";
+
+    public static final String SQL_FIXING_COLUMN_KM_RANGE =
+        "SELECT" +
+        "    MIN(position) AS start_km," +
+        "    MAX(position) AS stop_km " +
+        "FROM" +
+        "    wst_column_values " +
+        "WHERE " +
+        "    wst_column_id = :column_id";
+
+    public static class Range implements Serializable {
+
+        protected double start;
+        protected double end;
+
+        public Range() {
+        }
+
+        public Range(double start, double end) {
+            this.start = start;
+            this.end   = end;
+        }
+
+        public boolean disjoint(Range other) {
+            return start > other.end || other.start > end;
+        }
+
+        public boolean clip(Range other) {
+            if (disjoint(other)) return false;
+
+            if (other.start > start) start = other.start;
+            if (other.end   < end  ) end   = other.end;
+
+            return true;
+        }
+
+        public boolean inside(double x) {
+            return x > start-EPSILON && x < end+EPSILON;
+        }
+    } // class Range
+
+    public static class GaugeRange extends Range {
+
+        private static final class Sector implements Serializable {
+
+            int    sector;
+            double value;
+
+            Sector(int sector, double value) {
+                this.sector = sector;
+                this.value  = value;
+            }
+
+        } // class Sector
+
+        protected int gaugeId;
+
+        protected Map<String, Double> mainValues;
+        protected List<Sector> sectors;
+
+        public GaugeRange() {
+        }
+
+        public GaugeRange(double start, double end, int gaugeId) {
+            super(start, end);
+            this.gaugeId = gaugeId;
+            mainValues = new HashMap<String, Double>();
+            sectors = new ArrayList<Sector>(3);
+        }
+
+        public void addMainValue(String label, Double value) {
+            int idx = label.indexOf('(');
+            if (idx >= 0) {
+                label = label.substring(0, idx);
+            }
+            mainValues.put(label, value);
+        }
+
+        protected Double getMainValue(String label) {
+            Double v = mainValues.get(label);
+            if (v == null) {
+                log.warn("Missing main value '" 
+                    + label + "' for gauge " + gaugeId);
+            }
+            return v;
+        }
+
+        public void buildClasses() {
+            Double mnq = getMainValue("MNQ");
+            Double mq  = getMainValue("MQ");
+            Double mhq = getMainValue("MHQ");
+            Double hq5 = getMainValue("HQ5");
+
+            Double [][] pairs = {
+                { mnq,  mq },
+                {  mq, mhq },
+                { hq5, hq5 } };
+
+            for (int c = 0; c < pairs.length; ++c) {
+                Double [] pair = pairs[c];
+                if (pair[0] != null && pair[1] != null) {
+                    double value = 0.5*(pair[0] + pair[1]);
+                    sectors.add(new Sector(c, value));
+                }
+            }
+        }
+
+        public int classify(double value) {
+            for (Sector sector: sectors) {
+                if (value < sector.value) {
+                    return sector.sector;
+                }
+            }
+            return sectors.size();
+        }
+    } // class GaugeRange
+
+    public static class QRange extends Range {
+
+        protected double q;
+
+        public QRange() {
+        }
+
+        public QRange(double start, double end, double q) {
+            super(start, end);
+            this.q = q;
+        }
+    } // class QRange
+
+    public static class SectorRange extends Range {
+
+        protected int sector;
+
+        public SectorRange() {
+        }
+
+        public SectorRange(double start, double end, int sector) {
+            super(start, end);
+            this.sector = sector;
+        }
+
+        public boolean enlarge(SectorRange other) {
+            if (sector == other.sector
+            && Math.abs(end-other.start) < EPSILON2) {
+                end = other.end;
+                return true;
+            }
+            return false;
+        }
+    } // class SectorRange
+
+    public static class Fixing implements Serializable {
+
+        public static final Comparator<Column> DATE_CMP =
+            new Comparator<Column>() {
+                @Override
+                public int compare(Column a, Column b) {
+                    return a.startTime.compareTo(b.startTime);
+                }
+            };
+
+        public class Column extends Range {
+
+            protected int    columnId;
+            protected Date   startTime;
+            protected String name;
+
+            protected List<QRange> qRanges;
+
+            public Column() {
+            }
+
+            public Column(int columnId, Date startTime, String name) {
+                this.columnId  = columnId;
+                this.startTime = startTime;
+                this.name      = name;
+                
+                qRanges = new ArrayList<QRange>();
+            }
+
+            public Fixing getFixing() {
+                return Fixing.this;
+            }
+
+            public List<SectorRange> classify(GaugeRange gauge) {
+
+                List<SectorRange> sectors = new ArrayList<SectorRange>();
+
+                for (QRange qRange: qRanges) {
+                    SectorRange sector = new SectorRange(
+                        qRange.start, qRange.end,
+                        gauge.classify(qRange.q));
+                    if (sectors.isEmpty()
+                    || !sectors.get(sectors.size()-1).enlarge(sector)) {
+                        sectors.add(sector);
+                    }
+                }
+
+                return sectors;
+            }
+
+            public void loadKmRange(SQLQuery query) {
+                query.setInteger("column_id", columnId);
+
+                List<Object []> kms = query.list();
+
+                if (kms.isEmpty()) {
+                    log.warn("No km range for column " + columnId + ".");
+                }
+                else {
+                    Object [] obj = kms.get(0);
+                    start = (Double)obj[0];
+                    end   = (Double)obj[1];
+                }
+            }
+
+            public void loadQRanges(SQLQuery query) {
+                query.setInteger("column_id", columnId);
+                List<Object []> list = query.list();
+
+                for (Object [] row: list) {
+                    double q     = (Double)row[0];
+                    double start = (Double)row[1];
+                    double end   = (Double)row[2];
+                    QRange qRange = new QRange(start, end, q);
+                    if (qRange.clip(this)) {
+                        qRanges.add(qRange);
+                    }
+                }
+            }
+        } // class Column
+
+        protected int                wstId;
+        protected String             description;
+        protected List<Column> columns;
+
+        public Fixing() {
+        }
+
+        public Fixing(int wstId, String description) {
+            this.wstId       = wstId;
+            this.description = description;
+            columns = new ArrayList<Column>();
+        }
+
+        public void loadColumns(SQLQuery query) {
+            query.setInteger("wst_id", wstId);
+            List<Object []> list = query.list();
+            for (Object [] row: list) {
+                int    columnId  = (Integer)row[0];
+                Date   startTime = (Date)   row[1];
+                String name      = (String) row[2];
+                columns.add(new Column(columnId, startTime, name));
+            }
+        }
+
+        public void loadColumnsKmRange(SQLQuery query) {
+            for (Column column: columns) {
+                column.loadKmRange(query);
+            }
+        }
+
+        public void loadColumnsQRanges(SQLQuery query) {
+            for (Column column: columns) {
+                column.loadQRanges(query);
+            }
+        }
+
+        public void addAllColumns(List<Column> allColumns) {
+            allColumns.addAll(columns);
+        }
+    } // class Fixing
+
+
+    protected String           riverName;
+    protected int              riverId;
+    protected boolean          isKmUp;
+    protected List<GaugeRange> gauges;
+    protected List<Fixing>     fixings;
+    protected Range            extent;
+
+    public FixingsOverview() {
+        gauges  = new ArrayList<GaugeRange>();
+        fixings = new ArrayList<Fixing>();
+    }
+
+    public FixingsOverview(String riverName) {
+        this();
+        this.riverName = riverName;
+    }
+
+    protected boolean loadRiver(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_RIVER_ID)
+            .addScalar("river_id", StandardBasicTypes.INTEGER)
+            .addScalar("km_up",    StandardBasicTypes.BOOLEAN);
+
+        query.setString("name", riverName);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("No river '" + riverName + "' found.");
+            return false;
+        }
+
+        Object [] row = list.get(0);
+
+        riverId = (Integer)row[0];
+        isKmUp  = (Boolean)row[1];
+
+        return true;
+    }
+
+    protected boolean loadRiverExtent(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_RIVER_EXTENT)
+            .addScalar("start_km", StandardBasicTypes.DOUBLE)
+            .addScalar("end_km",   StandardBasicTypes.DOUBLE);
+
+        query.setInteger("river_id", riverId);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("River " + riverId + " has no extent.");
+            return false;
+        }
+
+        Object [] row = list.get(0);
+        extent = new Range((Double)row[0], (Double)row[1]);
+
+        return true;
+    }
+
+
+    protected boolean loadGauges(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_GAUGES)
+            .addScalar("gauge_id", StandardBasicTypes.INTEGER)
+            .addScalar("a",        StandardBasicTypes.DOUBLE)
+            .addScalar("b",        StandardBasicTypes.DOUBLE);
+
+        query.setInteger("river_id", riverId);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("River " + riverId + " has no gauges.");
+            return false;
+        }
+
+        for (Object [] row: list) {
+            int    gaugeId = (Integer)row[0];
+            double start   = (Double) row[1];
+            double end     = (Double) row[2];
+            GaugeRange gauge = new GaugeRange(start, end, gaugeId);
+            gauges.add(gauge);
+        }
+
+        return true;
+    }
+
+    protected GaugeRange findGaugeById(int gaugeId) {
+        for (GaugeRange gauge: gauges) {
+            if (gauge.gaugeId == gaugeId) {
+                return gauge;
+            }
+        }
+        return null;
+    }
+
+    protected GaugeRange findGaugeByKm(double km) {
+        for (GaugeRange gauge: gauges) {
+            if (gauge.inside(km)) {
+                return gauge;
+            }
+        }
+        return null;
+    }
+
+    protected boolean loadDischargeSectors(Session session) {
+
+        SQLQuery query = session.createSQLQuery(SQL_DISCHARGE_SECTORS)
+            .addScalar("gauge_id", StandardBasicTypes.INTEGER)
+            .addScalar("name",     StandardBasicTypes.STRING)
+            .addScalar("value",    StandardBasicTypes.DOUBLE);
+
+        query.setInteger("river_id", riverId);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("River " + riverId + " has no discharge sectors.");
+            return false;
+        }
+
+        GaugeRange gauge = null;
+
+        for (Object [] row: list) {
+            int    gaugeId = (Integer)row[0];
+            String label   = (String) row[1];
+            Double value   = (Double) row[2];
+
+            if (gauge == null || gauge.gaugeId != gaugeId) {
+                if ((gauge = findGaugeById(gaugeId)) == null) {
+                    log.warn("Cannot find gauge for id " + gaugeId + ".");
+                    continue;
+                }
+            }
+
+            gauge.addMainValue(label, value);
+        }
+
+        for (GaugeRange g: gauges) {
+            g.buildClasses();
+        }
+
+        return true;
+    }
+
+    protected void loadFixings(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXINGS)
+            .addScalar("wst_id",      StandardBasicTypes.INTEGER)
+            .addScalar("description", StandardBasicTypes.STRING);
+
+        query.setInteger("river_id", riverId);
+
+        List<Object []> list = query.list();
+
+        if (list.isEmpty()) {
+            log.warn("River " + riverId + " has no fixings.");
+            // Its pretty fine to have no fixings.
+        }
+
+        for (Object [] row: list) {
+            int    wstId       = (Integer)row[0];
+            String description = (String) row[1];
+            Fixing fixing = new Fixing(wstId, description);
+            fixings.add(fixing);
+        }
+    }
+
+    protected void loadFixingsColumns(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMNS)
+            .addScalar("wst_column_id", StandardBasicTypes.INTEGER)
+            .addScalar("start_time",    StandardBasicTypes.DATE)
+            .addScalar("name",          StandardBasicTypes.STRING);
+
+        for (Fixing fixing: fixings) {
+            fixing.loadColumns(query);
+        }
+    }
+
+    protected void loadFixingsColumnsKmRange(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_KM_RANGE)
+            .addScalar("start_km", StandardBasicTypes.DOUBLE)
+            .addScalar("stop_km",  StandardBasicTypes.DOUBLE);
+
+        for (Fixing fixing: fixings) {
+            fixing.loadColumnsKmRange(query);
+        }
+    }
+
+    protected void loadFixingsColumnsQRanges(Session session) {
+        SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_Q_RANGES)
+            .addScalar("q",        StandardBasicTypes.DOUBLE)
+            .addScalar("start_km", StandardBasicTypes.DOUBLE)
+            .addScalar("stop_km",  StandardBasicTypes.DOUBLE);
+
+        for (Fixing fixing: fixings) {
+            fixing.loadColumnsQRanges(query);
+        }
+    }
+
+    public boolean load(Session session) {
+
+        if (!loadRiver(session)) {
+            return false;
+        }
+
+        if (!loadRiverExtent(session)) {
+            return false;
+        }
+
+        if (!loadGauges(session)) {
+            return false;
+        }
+
+        if (!loadDischargeSectors(session)) {
+            return false;
+        }
+
+        loadFixings(session);
+        loadFixingsColumns(session);
+        loadFixingsColumnsKmRange(session);
+        loadFixingsColumnsQRanges(session);
+
+        return true;
+    }
+
+    public void generateOverview(Document document) {
+
+        List<Fixing.Column> allColumns = new ArrayList<Fixing.Column>();
+
+        for (Fixing fixing: fixings) {
+            fixing.addAllColumns(allColumns);
+        }
+
+        Collections.sort(allColumns, Fixing.DATE_CMP);
+
+        Element fixingsElement = document.createElement("fixings");
+
+        Element riverElement = document.createElement("river");
+
+        riverElement.setAttribute("from", String.valueOf(extent.start));
+        riverElement.setAttribute("to",   String.valueOf(extent.end));
+        riverElement.setAttribute("rid",  String.valueOf(riverId));
+
+        fixingsElement.appendChild(riverElement);
+
+        SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm");
+
+        Element esE = document.createElement("events");
+
+        for (Fixing.Column column: allColumns) {
+
+            double km = isKmUp ? column.start : column.end;
+
+            GaugeRange gauge = findGaugeByKm(km);
+
+            if (gauge == null) {
+                log.warn("Cannot find gauge for km " + km + ".");
+                continue;
+            }
+
+            List<SectorRange> sectors = column.classify(gauge);
+
+            if (!sectors.isEmpty()) {
+                Element eE = document.createElement("event");
+                eE.setAttribute("name",
+                    String.valueOf(column.getFixing().description));
+                eE.setAttribute("cid", String.valueOf(column.columnId));
+                eE.setAttribute("date", df.format(column.startTime));
+
+                for (SectorRange sector: sectors) {
+                    Element sE = document.createElement("sector");
+
+                    sE.setAttribute("from",  String.valueOf(sector.start));
+                    sE.setAttribute("to",    String.valueOf(sector.end));
+                    sE.setAttribute("class", String.valueOf(sector.sector));
+
+                    eE.appendChild(sE);
+                }
+
+                esE.appendChild(eE);
+            }
+        }
+
+        fixingsElement.appendChild(esE);
+
+        document.appendChild(fixingsElement);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverviewFactory.java	Sun Feb 19 17:14:39 2012 +0000
@@ -0,0 +1,60 @@
+package de.intevation.flys.artifacts.model;
+
+import de.intevation.flys.artifacts.cache.CacheFactory;
+
+import de.intevation.flys.backend.SessionHolder;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+
+import org.hibernate.Session;
+
+public class FixingsOverviewFactory
+{
+    private static Logger log = Logger.getLogger(FixingsOverviewFactory.class);
+
+    public static final String CACHE_NAME = "fixings-overviews";
+
+    private FixingsOverviewFactory() {
+    }
+
+
+    public static FixingsOverview getOverview(String river) {
+
+        log.debug("Looking for fixings overview for river '" + river + "'");
+
+        Cache cache = CacheFactory.getCache(CACHE_NAME);
+
+        if (cache == null) {
+            return getUncached(river);
+        }
+
+        String key = "fix-over-" + river;
+
+        Element element = cache.get(key);
+
+        if (element != null) {
+            return (FixingsOverview)element.getValue();
+        }
+
+        FixingsOverview overview = getUncached(river);
+
+        if (overview != null) {
+            element = new Element(key, overview);
+            cache.put(element);
+        }
+
+        return overview;
+    }
+
+    public static FixingsOverview getUncached(String river) {
+        FixingsOverview overview = new FixingsOverview(river);
+
+        Session session = SessionHolder.HOLDER.get();
+
+        return overview.load(session) ? overview : null;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FixingsOverviewService.java	Sun Feb 19 17:14:39 2012 +0000
@@ -0,0 +1,54 @@
+package de.intevation.flys.artifacts.services;
+
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.GlobalContext;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.FixingsOverview;
+import de.intevation.flys.artifacts.model.FixingsOverviewFactory;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class FixingsOverviewService
+extends      FLYSService
+{
+    private static Logger log =
+        Logger.getLogger(FixingsOverviewService.class);
+
+    public FixingsOverviewService() {
+    }
+
+    @Override
+    public Document doProcess(
+        Document      data,
+        GlobalContext globalContext,
+        CallMeta      callMeta
+    ) {
+        log.debug("FixingsOverviewService.doProcess");
+
+        Document document = XMLUtils.newDocument();
+
+        NodeList nodes = data.getElementsByTagName("river");
+
+        String river = nodes.getLength() > 0
+            ? ((Element)nodes.item(0)).getAttribute("name")
+            : "";
+
+        FixingsOverview overview = FixingsOverviewFactory.getOverview(river);
+
+        if (overview != null) {
+            overview.generateOverview(document);
+        }
+        else {
+            log.warn("No overview for river '" + river + "' available.");
+        }
+
+        return document;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org