Mercurial > dive4elements > river
diff flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java @ 3818:dc18457b1cef
merged flys-artifacts/pre2.7-2012-03-16
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:59 +0200 |
parents | 72f77b2210c2 |
children | 938e2f6c0c9a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java Fri Sep 28 12:14:59 2012 +0200 @@ -0,0 +1,914 @@ +package de.intevation.flys.artifacts.model; + +import java.io.Serializable; + +import java.text.SimpleDateFormat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + +import org.hibernate.SQLQuery; +import org.hibernate.Session; + +import org.hibernate.type.StandardBasicTypes; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class FixingsOverview +implements Serializable +{ + private static Logger log = Logger.getLogger(FixingsOverview.class); + + public static final double EPSILON = 1e-5; + public static final double EPSILON2 = 1e-1; + + public static final String DATE_FORMAT = "dd.MM.yyyy HH:mm"; + + public static final String SQL_RIVER_ID = + "SELECT" + + " id AS river_id," + + " km_up " + + "FROM rivers " + + "WHERE" + + " name = :name"; + + public static final String SQL_GAUGES = + "SELECT" + + " g.id AS gauge_id," + + " r.a AS a," + + " r.b AS b " + + "FROM gauges g" + + " JOIN ranges r ON g.range_id = r.id " + + "WHERE" + + " g.river_id = :river_id " + + "ORDER BY r.a"; + + public static final String SQL_DISCHARGE_SECTORS = + "SELECT" + + " g.id AS gauge_id," + + " nmv.name AS name," + + " CAST(mv.value AS NUMERIC(38,2)) AS value " + + "FROM gauges g" + + " JOIN main_values mv ON g.id = mv.gauge_id" + + " JOIN named_main_values nmv ON nmv.id = mv.named_value_id" + + " JOIN main_value_types mvt ON nmv.type_id = mvt.id " + + "WHERE" + + " mvt.name = 'Q' AND (" + + " nmv.name = 'MNQ' OR" + + " nmv.name LIKE 'MNQ(%' OR" + + " nmv.name = 'MQ' OR" + + " nmv.name LIKE 'MQ(%' OR" + + " nmv.name = 'MHQ' OR" + + " nmv.name LIKE 'MHQ(%' OR" + + " nmv.name = 'HQ5' OR" + + " nmv.name LIKE 'HQ5(%') AND" + + " g.river_id = :river_id " + + "ORDER BY" + + " g.id"; + + public static final String SQL_FIXINGS = + "SELECT" + + " id AS wst_id," + + " description " + + "FROM wsts " + + "WHERE" + + " river_id = :river_id AND kind = 2"; + + public static final String SQL_FIXING_COLUMNS = + "SELECT" + + " wc.id AS wst_column_id," + + " ti.start_time AS start_time," + + " wc.name AS name " + + "FROM wst_columns wc" + + " JOIN time_intervals ti ON wc.time_interval_id = ti.id " + + "WHERE" + + " wc.wst_id = :wst_id " + + "ORDER BY position"; + + public static final String SQL_FIXING_COLUMN_Q_RANGES = + "SELECT" + + " wqr.q AS q," + + " r.a AS start_km," + + " r.b AS stop_km " + + "FROM wst_column_q_ranges wcqr" + + " JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id" + + " JOIN ranges r ON wqr.range_id = r.id " + + "WHERE" + + " wcqr.wst_column_id = :column_id " + + "ORDER BY r.a"; + + public static final String SQL_FIXING_COLUMN_KM_RANGE = + "SELECT" + + " MIN(position) AS start_km," + + " MAX(position) AS stop_km " + + "FROM" + + " wst_column_values " + + "WHERE" + + " wst_column_id = :column_id"; + + public static class Range implements Serializable { + + protected double start; + protected double end; + + public Range() { + } + + public Range(double start, double end) { + this.start = start; + this.end = end; + } + + public double getStart() { + return start; + } + + public double getEnd() { + return end; + } + + public boolean disjoint(Range other) { + return start > other.end || other.start > end; + } + + public boolean intersects(Range other) { + return !disjoint(other); + } + + public void extend(Range other) { + if (other.start < start) start = other.start; + if (other.end > end ) end = other.end; + } + + public boolean clip(Range other) { + if (disjoint(other)) return false; + + if (other.start > start) start = other.start; + if (other.end < end ) end = other.end; + + return true; + } + + public boolean inside(double x) { + return x > start-EPSILON && x < end+EPSILON; + } + } // class Range + + public static class GaugeRange extends Range { + + private static final class Sector implements Serializable { + + int sector; + double value; + + Sector(int sector, double value) { + this.sector = sector; + this.value = value; + } + + } // class Sector + + protected int gaugeId; + + protected Map<String, Double> mainValues; + protected List<Sector> sectors; + + public GaugeRange() { + } + + public GaugeRange(double start, double end, int gaugeId) { + super(start, end); + this.gaugeId = gaugeId; + mainValues = new HashMap<String, Double>(); + sectors = new ArrayList<Sector>(3); + } + + public void addMainValue(String label, Double value) { + int idx = label.indexOf('('); + if (idx >= 0) { + label = label.substring(0, idx); + } + mainValues.put(label, value); + } + + protected Double getMainValue(String label) { + Double v = mainValues.get(label); + if (v == null) { + log.warn("Missing main value '" + + label + "' for gauge " + gaugeId); + } + return v; + } + + public void buildClasses() { + Double mnq = getMainValue("MNQ"); + Double mq = getMainValue("MQ"); + Double mhq = getMainValue("MHQ"); + Double hq5 = getMainValue("HQ5"); + + Double [][] pairs = { + { mnq, mq }, + { mq, mhq }, + { hq5, hq5 } }; + + for (int c = 0; c < pairs.length; ++c) { + Double [] pair = pairs[c]; + if (pair[0] != null && pair[1] != null) { + double value = 0.5*(pair[0] + pair[1]); + sectors.add(new Sector(c, value)); + } + } + } + + public int classify(double value) { + for (Sector sector: sectors) { + if (value < sector.value) { + return sector.sector; + } + } + return sectors.size(); + } + } // class GaugeRange + + public static class GaugeFinder { + + protected List<GaugeRange> gauges; + protected boolean isKmUp; + + public GaugeFinder(List<GaugeRange> gauges) { + this(gauges, true); + } + + public GaugeFinder( + List<GaugeRange> gauges, + boolean isKmUp + ) { + this.gauges = gauges; + this.isKmUp = isKmUp; + } + + public boolean getIsKmUp() { + return isKmUp; + } + + public void setIsKmUp(boolean isKmUp) { + this.isKmUp = isKmUp; + } + + public GaugeRange find(Range range) { + double km = isKmUp ? range.start : range.end; + for (GaugeRange gauge: gauges) { + if (gauge.inside(km)) { + return gauge; + } + } + return null; + } + + public GaugeRange find(int gaugeId) { + for (GaugeRange gauge: gauges) { + if (gauge.gaugeId == gaugeId) { + return gauge; + } + } + return null; + } + + public boolean loadDischargeSectors(Session session, int riverId) { + + SQLQuery query = session.createSQLQuery(SQL_DISCHARGE_SECTORS) + .addScalar("gauge_id", StandardBasicTypes.INTEGER) + .addScalar("name", StandardBasicTypes.STRING) + .addScalar("value", StandardBasicTypes.DOUBLE); + + query.setInteger("river_id", riverId); + + List<Object []> list = query.list(); + + if (list.isEmpty()) { + log.warn("River " + riverId + " has no discharge sectors."); + return false; + } + + GaugeRange gauge = null; + + for (Object [] row: list) { + int gaugeId = (Integer)row[0]; + String label = (String) row[1]; + Double value = (Double) row[2]; + + if (gauge == null || gauge.gaugeId != gaugeId) { + if ((gauge = find(gaugeId)) == null) { + log.warn("Cannot find gauge for id " + gaugeId + "."); + continue; + } + } + + gauge.addMainValue(label, value); + } + + for (GaugeRange g: gauges) { + g.buildClasses(); + } + + return true; + } + } // class GaugeFinder + + public static class QRange extends Range { + + protected double q; + + public QRange() { + } + + public QRange(double start, double end, double q) { + super(start, end); + this.q = q; + } + } // class QRange + + public static class SectorRange extends Range { + + protected int sector; + + public SectorRange() { + } + + public SectorRange(SectorRange other) { + start = other.start; + end = other.end; + sector = other.sector; + } + + public SectorRange(double start, double end, int sector) { + super(start, end); + this.sector = sector; + } + + public int getSector() { + return sector; + } + + public boolean enlarge(SectorRange other) { + if (sector == other.sector + && Math.abs(end-other.start) < EPSILON2) { + end = other.end; + return true; + } + return false; + } + } // class SectorRange + + public static class Fixing implements Serializable { + + public static final Comparator<Column> DATE_CMP = + new Comparator<Column>() { + @Override + public int compare(Column a, Column b) { + return a.startTime.compareTo(b.startTime); + } + }; + + public interface Filter { + + boolean accept(Column column); + + } // interface Filter + + public class Column extends Range { + + protected int columnId; + protected Date startTime; + protected String name; + + protected List<SectorRange> sectors; + + public Column() { + } + + public Column(int columnId, Date startTime, String name) { + this.columnId = columnId; + this.startTime = startTime; + this.name = name; + + sectors = new ArrayList<SectorRange>(); + } + + public int getId() { + return columnId; + } + + public Fixing getFixing() { + return Fixing.this; + } + + public Date getStartTime() { + return startTime; + } + + public String getName() { + return name; + } + + public List<SectorRange> getSectors() { + return sectors; + } + + public List<SectorRange> getSectors(Range range) { + + List<SectorRange> result = + new ArrayList<SectorRange>(sectors.size()); + + for (SectorRange src: sectors) { + SectorRange dst = new SectorRange(src); + if (dst.clip(range)) { + result.add(dst); + } + } + + return result; + } + + public void buildSectors(GaugeRange gauge, List<QRange> qRanges) { + + for (QRange qRange: qRanges) { + SectorRange sector = new SectorRange( + qRange.start, qRange.end, + gauge.classify(qRange.q)); + if (sectors.isEmpty() + || !sectors.get(sectors.size()-1).enlarge(sector)) { + sectors.add(sector); + } + } + } + + public void loadKmRange(SQLQuery query) { + query.setInteger("column_id", columnId); + + List<Object []> kms = query.list(); + + if (kms.isEmpty()) { + log.warn("No km range for column " + columnId + "."); + } + else { + Object [] obj = kms.get(0); + start = (Double)obj[0]; + end = (Double)obj[1]; + } + } + + public void loadQRanges( + SQLQuery query, + GaugeFinder gaugeFinder + ) { + query.setInteger("column_id", columnId); + List<Object []> list = query.list(); + + List<QRange> qRanges = new ArrayList<QRange>(list.size()); + + for (Object [] row: list) { + double q = (Double)row[0]; + double start = (Double)row[1]; + double end = (Double)row[2]; + QRange qRange = new QRange(start, end, q); + if (qRange.clip(this)) { + qRanges.add(qRange); + } + } + + GaugeRange gauge = gaugeFinder.find(this); + + if (gauge != null) { + buildSectors(gauge, qRanges); + } + else { + log.warn("No gauge found for column " + columnId + "."); + } + } + } // class Column + + protected int wstId; + protected String description; + protected List<Column> columns; + + public Fixing() { + } + + public int getId() { + return wstId; + } + + public String getDescription() { + return description; + } + + public Fixing(int wstId, String description) { + this.wstId = wstId; + this.description = description; + columns = new ArrayList<Column>(); + } + + public void loadColumns(SQLQuery query) { + query.setInteger("wst_id", wstId); + List<Object []> list = query.list(); + for (Object [] row: list) { + int columnId = (Integer)row[0]; + Date startTime = (Date) row[1]; + String name = (String) row[2]; + columns.add(new Column(columnId, startTime, name)); + } + } + + public void loadColumnsKmRange(SQLQuery query) { + for (Column column: columns) { + column.loadKmRange(query); + } + } + + public void adjustExtent(Range extent) { + for (Column column: columns) { + extent.extend(column); + } + } + + public void loadColumnsQRanges( + SQLQuery query, + GaugeFinder gaugeFinder + ) { + for (Column column: columns) { + column.loadQRanges(query, gaugeFinder); + } + } + + public void addAllColumns( + List<Column> allColumns, + Range range, + Filter filter + ) { + for (Column column: columns) { + if (column.intersects(range) && filter.accept(column)) { + allColumns.add(column); + } + } + } + } // class Fixing + + + protected String riverName; + protected int riverId; + protected boolean isKmUp; + protected List<Fixing> fixings; + protected Range extent; + + public FixingsOverview() { + fixings = new ArrayList<Fixing>(); + extent = new Range(Double.MAX_VALUE, -Double.MAX_VALUE); + } + + public FixingsOverview(String riverName) { + this(); + this.riverName = riverName; + } + + protected boolean loadRiver(Session session) { + SQLQuery query = session.createSQLQuery(SQL_RIVER_ID) + .addScalar("river_id", StandardBasicTypes.INTEGER) + .addScalar("km_up", StandardBasicTypes.BOOLEAN); + + query.setString("name", riverName); + + List<Object []> list = query.list(); + + if (list.isEmpty()) { + log.warn("No river '" + riverName + "' found."); + return false; + } + + Object [] row = list.get(0); + + riverId = (Integer)row[0]; + isKmUp = (Boolean)row[1]; + + return true; + } + + protected GaugeFinder loadGauges(Session session) { + SQLQuery query = session.createSQLQuery(SQL_GAUGES) + .addScalar("gauge_id", StandardBasicTypes.INTEGER) + .addScalar("a", StandardBasicTypes.DOUBLE) + .addScalar("b", StandardBasicTypes.DOUBLE); + + query.setInteger("river_id", riverId); + + List<Object []> list = query.list(); + + if (list.isEmpty()) { + log.warn("River " + riverId + " has no gauges."); + return null; + } + + List<GaugeRange> gauges = new ArrayList<GaugeRange>(); + + for (Object [] row: list) { + int gaugeId = (Integer)row[0]; + double start = (Double) row[1]; + double end = (Double) row[2]; + GaugeRange gauge = new GaugeRange(start, end, gaugeId); + gauges.add(gauge); + } + + return new GaugeFinder(gauges, isKmUp); + } + + + protected void loadFixings(Session session) { + SQLQuery query = session.createSQLQuery(SQL_FIXINGS) + .addScalar("wst_id", StandardBasicTypes.INTEGER) + .addScalar("description", StandardBasicTypes.STRING); + + query.setInteger("river_id", riverId); + + List<Object []> list = query.list(); + + if (list.isEmpty()) { + log.warn("River " + riverId + " has no fixings."); + // Its pretty fine to have no fixings. + } + + for (Object [] row: list) { + int wstId = (Integer)row[0]; + String description = (String) row[1]; + Fixing fixing = new Fixing(wstId, description); + fixings.add(fixing); + } + } + + protected void loadFixingsColumns(Session session) { + SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMNS) + .addScalar("wst_column_id", StandardBasicTypes.INTEGER) + .addScalar("start_time", StandardBasicTypes.DATE) + .addScalar("name", StandardBasicTypes.STRING); + + for (Fixing fixing: fixings) { + fixing.loadColumns(query); + } + } + + protected void loadFixingsColumnsKmRange(Session session) { + SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_KM_RANGE) + .addScalar("start_km", StandardBasicTypes.DOUBLE) + .addScalar("stop_km", StandardBasicTypes.DOUBLE); + + for (Fixing fixing: fixings) { + fixing.loadColumnsKmRange(query); + } + } + + protected void loadFixingsColumnsQRanges( + Session session, + GaugeFinder gaugeFinder + ) { + SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_Q_RANGES) + .addScalar("q", StandardBasicTypes.DOUBLE) + .addScalar("start_km", StandardBasicTypes.DOUBLE) + .addScalar("stop_km", StandardBasicTypes.DOUBLE); + + for (Fixing fixing: fixings) { + fixing.loadColumnsQRanges(query, gaugeFinder); + } + } + + protected void adjustExtent() { + for (Fixing fixing: fixings) { + fixing.adjustExtent(extent); + } + } + + public boolean load(Session session) { + + if (!loadRiver(session)) { + return false; + } + + GaugeFinder gaugeFinder = loadGauges(session); + + if (gaugeFinder == null + || !gaugeFinder.loadDischargeSectors(session, riverId)) { + return false; + } + + loadFixings(session); + loadFixingsColumns(session); + loadFixingsColumnsKmRange(session); + + adjustExtent(); + + loadFixingsColumnsQRanges(session, gaugeFinder); + + return true; + } + + public static final Range FULL_EXTENT = + new Range(-Double.MAX_VALUE, Double.MAX_VALUE); + + public static final Fixing.Filter ACCEPT = new Fixing.Filter() { + @Override + public boolean accept(Fixing.Column column) { + return true; + } + }; + + public static class NotFilter implements Fixing.Filter { + protected Fixing.Filter child; + + public NotFilter(Fixing.Filter child) { + this.child = child; + } + + @Override + public boolean accept(Fixing.Column column) { + return !child.accept(column); + } + } // class NotFilter + + public static abstract class ComponentFilter implements Fixing.Filter { + protected List<Fixing.Filter> children; + + public ComponentFilter(List<Fixing.Filter> children) { + this.children = children; + } + } // class ComponentFilter + + public static class OrFilter extends ComponentFilter { + + public OrFilter(List<Fixing.Filter> children) { + super(children); + } + + @Override + public boolean accept(Fixing.Column column) { + for (Fixing.Filter child: children) { + if (child.accept(column)) { + return true; + } + } + return false; + } + } // class OrFilter + + public static class AndFilter extends ComponentFilter { + + public AndFilter(List<Fixing.Filter> children) { + super(children); + } + + @Override + public boolean accept(Fixing.Column column) { + for (Fixing.Filter child: children) { + if (!child.accept(column)) { + return false; + } + } + return true; + } + } // class AndFilter + + public static class IdFilter implements Fixing.Filter { + + protected int columnId; + + public IdFilter(int columnId) { + this.columnId = columnId; + } + + @Override + public boolean accept(Fixing.Column column) { + return column.getId() == columnId; + } + } // class IdFilter + + public static class DateFilter implements Fixing.Filter { + + protected Date date; + + public DateFilter(Date date) { + this.date = date; + } + + @Override + public boolean accept(Fixing.Column column) { + return date.equals(column.getStartTime()); + } + } // class DateFilter + + public static class DateRangeFilter implements Fixing.Filter { + + protected Date start; + protected Date end; + + public DateRangeFilter(Date start, Date end) { + this.start = start; + this.end = end; + } + + @Override + public boolean accept(Fixing.Column column) { + Date date = column.getStartTime(); + return start.compareTo(date) <= 0 && end.compareTo(date) >= 0; + } + } // class DateRangeFilter + + public static class SectorRangeFilter implements Fixing.Filter { + + protected int min; + protected int max; + + public SectorRangeFilter(int min, int max) { + this.min = Math.min(min, max); + this.max = Math.max(min, max); + } + + @Override + public boolean accept(Fixing.Column column) { + for (SectorRange s: column.getSectors()) { + int v = s.getSector(); + if (v >= min && v <= max) { + return true; + } + } + return false; + } + } // class SectorRangeFilter + + public void generateOverview(Document document) { + generateOverview(document, FULL_EXTENT, ACCEPT); + } + + public void generateOverview( + Document document, + Range range, + Fixing.Filter filter + ) { + List<Fixing.Column> allColumns = new ArrayList<Fixing.Column>(); + + for (Fixing fixing: fixings) { + fixing.addAllColumns(allColumns, range, filter); + } + + Collections.sort(allColumns, Fixing.DATE_CMP); + + Element fixingsElement = document.createElement("fixings"); + + Element riverElement = document.createElement("river"); + + riverElement.setAttribute("from", String.valueOf(extent.start)); + riverElement.setAttribute("to", String.valueOf(extent.end)); + riverElement.setAttribute("rid", String.valueOf(riverId)); + riverElement.setAttribute("name", riverName); + + fixingsElement.appendChild(riverElement); + + SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT); + + Element esE = document.createElement("events"); + + for (Fixing.Column column: allColumns) { + + List<SectorRange> sectors = column.getSectors(range); + + if (!sectors.isEmpty()) { + Element eE = document.createElement("event"); + eE.setAttribute("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 :