view flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/FixingsOverview.java @ 4241:49cb65d5932d

Improved the historical discharge calculation. The calculation now creates new HistoricalWQKms (new subclass of WQKms). Those WQKms are used to create new facets from (new) type 'HistoricalDischargeCurveFacet'. The chart generator is improved to support those facets.
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Wed, 24 Oct 2012 14:34:35 +0200
parents 278b5508567e
children 658dc517fd7b
line wrap: on
line source
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.List;

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-2;

    public static final String DATE_FORMAT = "dd.MM.yyyy";

    public static final String SQL_RIVER_ID =
        "SELECT" +
        "    id AS river_id," +
        "    km_up " +
        "FROM rivers " +
        "WHERE" +
        "    name = :name";

    public static final String SQL_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 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(Range range) {
            super(range);
        }

        public SectorRange(double start, double end, int sector) {
            super(start, end);
            this.sector = sector;
        }

        public int getSector() {
            return sector;
        }

        public void setSector(int sector) {
            this.sector = sector;
        }

        public boolean enlarge(SectorRange other) {
            if (sector == other.sector
            && Math.abs(end-other.start) < FixingsOverview.EPSILON) {
                end = other.end;
                return true;
            }
            return false;
        }
    } // class SectorRange

    public static class Fixing implements Serializable {

        public static final Comparator<Column> DATE_CMP =
            new Comparator<Column>() {
                @Override
                public int compare(Column a, Column b) {
                    return a.startTime.compareTo(b.startTime);
                }
            };

        public interface Filter {

            boolean accept(Column column);

        } // interface Filter

        public class Column extends Range {

            protected int    columnId;
            protected Date   startTime;
            protected String name;

            protected List<SectorRange> sectors;

            public Column() {
            }

            public Column(int columnId, Date startTime, String name) {
                this.columnId  = columnId;
                this.startTime = startTime;
                this.name      = name;

                sectors = new ArrayList<SectorRange>();
            }

            public int getId() {
                return columnId;
            }

            public Fixing getFixing() {
                return Fixing.this;
            }

            public Date getStartTime() {
                return startTime;
            }

            public String getName() {
                return name;
            }

            public String getDescription() {
                return Fixing.this.description + "/" + name;
            }

            public List<SectorRange> getSectors() {
                return sectors;
            }

            public List<SectorRange> getSectors(Range range) {

                List<SectorRange> result =
                    new ArrayList<SectorRange>(sectors.size());

                for (SectorRange src: sectors) {
                    SectorRange dst = new SectorRange(src);
                    if (range == null || dst.clip(range)) {
                        result.add(dst);
                    }
                }

                return result;
            }

            public int findQSector(double km) {
                for (SectorRange sector: sectors) {
                    if (sector.inside(km)) {
                        return sector.getSector();
                    }
                }
                return -1;
            }

            public void buildSectors(
                GaugeFinder  gaugeFinder,
                List<QRange> qRanges
            ) {
                for (QRange qRange: qRanges) {
                    for (GaugeRange gRange: gaugeFinder.getGauges()) {
                        SectorRange sector = new SectorRange(qRange);
                        if (!sector.clip(gRange)) {
                            continue;
                        }
                        sector.setSector(gRange.classify(qRange.q));

                        if (sectors.isEmpty()
                        || !sectors.get(sectors.size()-1).enlarge(sector)) {
                            sectors.add(sector);
                        }
                    } // for all gauges
                } // for all Q ranges
            }

            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);
                    }
                }

                buildSectors(gaugeFinder, qRanges);
            }
        } // class Column

        protected int          wstId;
        protected String       description;
        protected List<Column> columns;

        public Fixing() {
        }

        public int getId() {
            return wstId;
        }

        public String getDescription() {
            return description;
        }

        public Fixing(int wstId, String description) {
            this.wstId       = wstId;
            this.description = description;
            columns = new ArrayList<Column>();
        }

        public void loadColumns(SQLQuery query) {
            query.setInteger("wst_id", wstId);
            List<Object []> list = query.list();
            for (Object [] row: list) {
                int    columnId  = (Integer)row[0];
                Date   startTime = (Date)   row[1];
                String name      = (String) row[2];
                columns.add(new Column(columnId, startTime, name));
            }
        }

        public void loadColumnsKmRange(SQLQuery query) {
            for (Column column: columns) {
                column.loadKmRange(query);
            }
        }

        public void adjustExtent(Range extent) {
            for (Column column: columns) {
                extent.extend(column);
            }
        }

        public void loadColumnsQRanges(
            SQLQuery    query,
            GaugeFinder gaugeFinder
        ) {
            for (Column column: columns) {
                column.loadQRanges(query, gaugeFinder);
            }
        }

        public void addAllColumns(
            List<Column> allColumns,
            Range        range,
            Filter       filter
        ) {
            for (Column column: columns) {
                if ((range == null || column.intersects(range))
                && (filter == null || filter.accept(column))) {
                    allColumns.add(column);
                }
            }
        }
    } // class Fixing


    protected String       riverName;
    protected int          riverId;
    protected boolean      isKmUp;
    protected List<Fixing> fixings;
    protected Range        extent;

    public FixingsOverview() {
        fixings = new ArrayList<Fixing>();
        extent  = new Range(Double.MAX_VALUE, -Double.MAX_VALUE);
    }

    public FixingsOverview(String riverName) {
        this();
        this.riverName = riverName;
    }

    protected boolean loadRiver(Session session) {
        SQLQuery query = session.createSQLQuery(SQL_RIVER_ID)
            .addScalar("river_id", StandardBasicTypes.INTEGER)
            .addScalar("km_up",    StandardBasicTypes.BOOLEAN);

        query.setString("name", riverName);

        List<Object []> list = query.list();

        if (list.isEmpty()) {
            log.warn("No river '" + riverName + "' found.");
            return false;
        }

        Object [] row = list.get(0);

        riverId = (Integer)row[0];
        isKmUp  = (Boolean)row[1];

        return true;
    }

    protected 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;
        }

        GaugeFinderFactory gff = GaugeFinderFactory.getInstance();

        GaugeFinder gaugeFinder = gff.getGaugeFinder(riverId, isKmUp);

        if (gaugeFinder == null) {
            return false;
        }

        loadFixings(session);
        loadFixingsColumns(session);
        loadFixingsColumnsKmRange(session);

        adjustExtent();

        loadFixingsColumnsQRanges(session, gaugeFinder);

        return true;
    }

    public static final Range FULL_EXTENT =
        new Range(-Double.MAX_VALUE, Double.MAX_VALUE);

    public static final Fixing.Filter ACCEPT = new Fixing.Filter() {
        @Override
        public boolean accept(Fixing.Column column) {
            return true;
        }
    };

    public static class NotFilter implements Fixing.Filter {
        protected Fixing.Filter child;

        public NotFilter(Fixing.Filter child) {
            this.child = child;
        }

        @Override
        public boolean accept(Fixing.Column column) {
            return !child.accept(column);
        }
    } // class NotFilter

    public static abstract class ComponentFilter implements Fixing.Filter {
        protected List<Fixing.Filter> children;

        public ComponentFilter() {
            children = new ArrayList<Fixing.Filter>();
        }

        public ComponentFilter(List<Fixing.Filter> children) {
            this.children = children;
        }

        public ComponentFilter add(Fixing.Filter filter) {
            children.add(filter);
            return this;
        }
    } // class ComponentFilter

    public static class OrFilter extends ComponentFilter {

        public OrFilter() {
        }

        public OrFilter(List<Fixing.Filter> children) {
            super(children);
        }

        @Override
        public boolean accept(Fixing.Column column) {
            for (Fixing.Filter child: children) {
                if (child.accept(column)) {
                    return true;
                }
            }
            return false;
        }
    } // class OrFilter

    public static class AndFilter extends ComponentFilter {

        public AndFilter() {
        }

        public AndFilter(List<Fixing.Filter> children) {
            super(children);
        }

        @Override
        public boolean accept(Fixing.Column column) {
            for (Fixing.Filter child: children) {
                if (!child.accept(column)) {
                    return false;
                }
            }
            return true;
        }
    } // class AndFilter

    public static class IdFilter implements Fixing.Filter {

        protected int columnId;

        public IdFilter(int columnId) {
            this.columnId = columnId;
        }

        @Override
        public boolean accept(Fixing.Column column) {
            return column.getId() == columnId;
        }
    } // class IdFilter

    public static class IdsFilter implements Fixing.Filter {

        protected int [] columnIds;

        public IdsFilter(int [] columnIds) {
            this.columnIds = columnIds;
        }

        @Override
        public boolean accept(Fixing.Column column) {
            int cid = column.getId();
            for (int i = columnIds.length-1; i >= 0; --i) {
                if (columnIds[i] == cid) {
                    return true;
                }
            }
            return false;
        }
    } // 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 SectorFilter implements Fixing.Filter {

        protected int sector;

        public SectorFilter(int sector) {
            this.sector = sector;
        }

        @Override
        public boolean accept(Fixing.Column column) {
            for (SectorRange s: column.getSectors()) {
                if (s.getSector() == sector) {
                    return true;
                }
            }
            return false;
        }
    } // class SectorFilter

    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 static class KmFilter implements Fixing.Filter {

        protected double km;

        public KmFilter(double km) {
            this.km = km;
        }

        @Override
        public boolean accept(Fixing.Column column) {
            for (SectorRange s: column.getSectors()) {
                if (s.inside(km)) {
                    return true;
                }
            }
            return false;
        }
    } // class KmFilter

    public void generateOverview(Document document) {
        generateOverview(document, FULL_EXTENT, ACCEPT);
    }

    public List<Fixing.Column> filter(Range range, Fixing.Filter filter) {
        List<Fixing.Column> allColumns = new ArrayList<Fixing.Column>();

        for (Fixing fixing: fixings) {
            fixing.addAllColumns(allColumns, range, filter);
        }

        Collections.sort(allColumns, Fixing.DATE_CMP);

        return allColumns;
    }

    protected static Range realRange(List<Fixing.Column> columns) {
        Range range = null;
        for (Fixing.Column column: columns) {
            if (range == null) {
                range = new Range(column);
            }
            else {
                range.extend(column);
            }
        }
        return range;
    }

    protected Element intersectingGauges(Document document, Range range) {
        Element gauges = document.createElement("gauges");

        if (range == null) {
            return gauges;
        }

        GaugeFinderFactory gff = GaugeFinderFactory.getInstance();

        GaugeFinder gf = gff.getGaugeFinder(riverId, isKmUp);

        if (gf == null) {
            return gauges;
        }

        for (GaugeRange gr: gf.getGauges()) {
            if (gr.intersects(range)) {
                Element gauge = document.createElement("gauge");
                gauge.setAttribute("from", String.valueOf(gr.getStart()));
                gauge.setAttribute("to",   String.valueOf(gr.getEnd()));
                gauge.setAttribute("name", gr.getName());
                gauges.appendChild(gauge);
            }
        }

        return gauges;
    }

    public void generateOverview(
        Document      document,
        Range         range,
        Fixing.Filter filter
    ) {
        List<Fixing.Column> allColumns = filter(range, filter);

        Element fixingsElement = document.createElement("fixings");

        Element riverElement = document.createElement("river");

        riverElement.setAttribute("from", String.valueOf(extent.start));
        riverElement.setAttribute("to",   String.valueOf(extent.end));
        riverElement.setAttribute("rid",  String.valueOf(riverId));
        riverElement.setAttribute("name", riverName);

        fixingsElement.appendChild(riverElement);

        fixingsElement.appendChild(
            intersectingGauges(
                document,
                realRange(allColumns)));

        SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT);

        Element esE = document.createElement("events");

        for (Fixing.Column column: allColumns) {

            List<SectorRange> sectors = column.getSectors(range);

            if (!sectors.isEmpty()) {
                Element eE = document.createElement("event");
                eE.setAttribute("description",
                    String.valueOf(column.getDescription()));
                eE.setAttribute("cid", String.valueOf(column.columnId));
                eE.setAttribute("date", df.format(column.startTime));

                for (SectorRange sector: sectors) {
                    Element sE = document.createElement("sector");

                    sE.setAttribute("from",  String.valueOf(sector.start));
                    sE.setAttribute("to",    String.valueOf(sector.end));
                    sE.setAttribute("class", String.valueOf(sector.sector));

                    eE.appendChild(sE);
                }

                esE.appendChild(eE);
            }
        }

        fixingsElement.appendChild(esE);

        document.appendChild(fixingsElement);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org